首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >CGBitmapContext 2x与CALayer的绘图()相比速度慢

CGBitmapContext 2x与CALayer的绘图()相比速度慢
EN

Stack Overflow用户
提问于 2020-05-16 14:32:58
回答 1查看 171关注 0票数 0

我有一些我不能更改的代码,它可以在任何时候绘制。它是下面BackgroundThread中的main()函数--假装它不能以任何方式被修改。运行这将使用70%-80%的CPU。

如果我没有运行线程,而是复制它在View:: draw ()中所做的操作(即在任意位置绘制5000个白色矩形),这将使用大约30%的CPU。

区别从哪里来?看看仪器,尽管从CGContextFillRect开始调用堆栈是相同的,但是View::draw()版本只花了16%的时间做memset(),而线程版本花费了80%的时间。

下面的代码是快速版本。注释掉快行,取消对慢行的注释,切换到慢速(线程)版本。用swiftc test.swift -otest && ./test编译。我用的是macOS 10.13,集成图形,如果这有关系的话。

有什么我可以做的,使线程版本的速度与View::draw()版本一样快?

代码语言:javascript
运行
复制
import Cocoa

let NSApp = NSApplication.shared,
  vwaitSem = DispatchSemaphore(value: 0)

var
    mainWindow: NSWindow?,
    screen: CGContext?,
    link: CVDisplayLink?

  class View: NSView, CALayerDelegate {
    var lastTime: CFTimeInterval = 0
    override var acceptsFirstResponder: Bool {return true}
    required init(coder aDecoder: NSCoder) {fatalError("This class does not support NSCoding")}
    override func makeBackingLayer() -> CALayer {return CALayer()}

    override init(frame: CGRect) {
      super.init(frame: frame)
      self.wantsLayer = true
      self.layer?.contentsScale = 2.0
      self.layer?.backgroundColor = CGColor(red:0, green:0, blue:0, alpha: 1)
      self.layerContentsRedrawPolicy = NSView.LayerContentsRedrawPolicy.onSetNeedsDisplay  // FAST
    }

    func draw(_ layer: CALayer, in ctx: CGContext) {
      let now = CACurrentMediaTime(), timePassed = ((now-lastTime)*1000).rounded()
      // NSLog("\(timePassed)")
      lastTime = now

      ctx.setFillColor(CGColor.white)
      ctx.setStrokeColor(CGColor.white)
      for _ in 0...5000 {
        let rect = CGRect(x: CGFloat(arc4random_uniform(640)+1), y: CGFloat(arc4random_uniform(480)+1), width:6, height:6)
        ctx.setFillColor(CGColor.white)
        ctx.fill(rect)
      }
    }
  }

  func displayLinkOutputCallback(_ displayLink: CVDisplayLink, _ nowPtr: UnsafePointer<CVTimeStamp>,
  _ outputTimePtr: UnsafePointer<CVTimeStamp>, _ flagsIn: CVOptionFlags, _ flagsOut: UnsafeMutablePointer<CVOptionFlags>,
  _ displayLinkContext: UnsafeMutableRawPointer?) -> CVReturn {
    DispatchQueue.main.async {
      // mainWindow!.contentView!.layer!.contents = screen!.makeImage()        // SLOW
      mainWindow!.contentView!.display()                                 // FAST
      vwaitSem.signal()
    }
    return kCVReturnSuccess
  }

class BackgroundThread: Thread {
  var lastTime: CFTimeInterval = 0
  override func main() {
    while true {
      let now = CACurrentMediaTime(), timePassed = ((now-lastTime)*1000).rounded()
      // NSLog("\(timePassed)")
      lastTime = now

      screen?.clear(CGRect(x:0, y:0, width:640*2, height:480*2))
      for _ in 0...5000 {
        screen?.setFillColor(CGColor.white)
        screen?.setStrokeColor(CGColor.white)
        screen?.fill(CGRect(x: CGFloat(arc4random_uniform(640*2)+1), y: CGFloat(arc4random_uniform(480*2)+1), width: 6*2, height: 6*2))
      }
      vwaitSem.wait()
    }
  }
}

let width = 640, height = 480,
  appMenuItem = NSMenuItem(),
  quitMenuItem = NSMenuItem(title:"Quit",
    action:#selector(NSApplication.terminate), keyEquivalent:"q"),
  window = NSWindow(contentRect:NSMakeRect(0,0, CGFloat(width), CGFloat(height)),
      styleMask:[.closable,.titled], backing:.buffered, defer:false),
  colorProfile = ColorSyncProfileCreateWithDisplayID(0),
  colorSpace = CGColorSpace(platformColorSpaceRef: colorProfile!.toOpaque()),
  screen_ = CGContext(data: nil, width: Int(width)*2, height:Int(height)*2, bitsPerComponent:8, bytesPerRow: 0,
    space: colorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue),
  backgroundThread = BackgroundThread()

NSApp.setActivationPolicy(NSApplication.ActivationPolicy.regular)
NSApp.mainMenu = NSMenu()
NSApp.mainMenu?.addItem(appMenuItem)
appMenuItem.submenu = NSMenu()
appMenuItem.submenu?.addItem(quitMenuItem)
window.cascadeTopLeft(from:NSMakePoint(20,20))
window.makeKeyAndOrderFront(nil)
window.contentView = View()
window.makeFirstResponder(window.contentView)
NSApp.activate(ignoringOtherApps:true)

mainWindow = window
screen = screen_

CVDisplayLinkCreateWithCGDisplay(CGMainDisplayID(), &link)
CVDisplayLinkSetOutputCallback(link!, displayLinkOutputCallback, UnsafeMutableRawPointer(Unmanaged.passUnretained(window).toOpaque()))
CVDisplayLinkStart(link!)

// backgroundThread.start()   // SLOW
NSApp.run()
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-05-17 13:07:47

我误读了makeImage()文档中的注释,认为它不会复制数据,除非它真的必须这样做。仪器显示它确实复制了数据。每一帧。

因此,我切换到了Metal,现在我可以从后台线程中使用与单独使用CGContext相同的性能/CPU使用,据我所知,没有副本。

以下是一些工作代码:

代码语言:javascript
运行
复制
import Cocoa
import MetalKit

class View: MTKView {
  var screen: CGContext?
  var commandQueue: MTLCommandQueue?
  var buffer: MTLBuffer?
  var texture: MTLTexture?
  var vwaitSem = DispatchSemaphore(value: 0)
  var backgroundThread: Thread?
  var allocationSize = 0

  func alignUp(size: Int, align: Int) -> Int {return (size+(align-1)) & ~(align-1)}
  override var acceptsFirstResponder: Bool {return true}
  required init(coder aDecoder: NSCoder) {fatalError("This class does not support NSCoding")}
  init() {super.init(frame: CGRect(x:0, y:0, width:0, height: 0), device: MTLCreateSystemDefaultDevice())}

  override func viewDidMoveToWindow() {
    layer?.contentsScale = NSScreen.main!.backingScaleFactor
    let metalLayer = layer as! CAMetalLayer
    let pixelRowAlignment = metalLayer.device!.minimumLinearTextureAlignment(for: metalLayer.pixelFormat)
    let bytesPerRow = alignUp(size: Int(layer!.frame.width)*Int(layer!.contentsScale)*4, align: pixelRowAlignment)
    let pagesize = Int(getpagesize())
    var data: UnsafeMutableRawPointer? = nil

    allocationSize = alignUp(size: bytesPerRow*Int(layer!.frame.height)*Int(layer!.contentsScale), align: pagesize)
    posix_memalign(&data, pagesize, allocationSize)

    let colorProfile = ColorSyncProfileCreateWithDisplayID(0),
      colorSpace = CGColorSpace(platformColorSpaceRef: colorProfile!.toOpaque()),
      screen_ = CGContext(data: data,
        width: Int(layer!.frame.width)*Int(layer!.contentsScale),
        height: Int(layer!.frame.height)*Int(layer!.contentsScale),
        bitsPerComponent:8, bytesPerRow: bytesPerRow,
        space: colorSpace!, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue)!,
      buffer_ = metalLayer.device!.makeBuffer(bytesNoCopy: data!, length: allocationSize, options: .storageModeManaged,
        deallocator: { pointer, length in free(self.screen!.data!) })!,
      textureDescriptor = MTLTextureDescriptor()

    textureDescriptor.pixelFormat = metalLayer.pixelFormat
    textureDescriptor.width = screen_.width
    textureDescriptor.height = screen_.height
    textureDescriptor.storageMode = buffer_.storageMode
    textureDescriptor.usage = MTLTextureUsage(rawValue: MTLTextureUsage.shaderRead.rawValue)
    texture = buffer_.makeTexture(descriptor: textureDescriptor, offset: 0, bytesPerRow: screen_.bytesPerRow)
    commandQueue = device?.makeCommandQueue()

    screen = screen_
    buffer = buffer_
    backgroundThread = BackgroundThread(screen: screen!, vwaitSem: vwaitSem)
    backgroundThread!.start()
  }

  override func draw(_ dirtyRect: NSRect) {
    if let drawable = currentDrawable {
      buffer!.didModifyRange(0..<allocationSize)
      texture!.replace(region: MTLRegionMake2D(0,0, screen!.width, screen!.height),
        mipmapLevel:0, slice:0, withBytes: screen!.data!, bytesPerRow: screen!.bytesPerRow, bytesPerImage: 0)

      let commandBuffer = commandQueue!.makeCommandBuffer()!
      let blitPass = commandBuffer.makeBlitCommandEncoder()!
      blitPass.copy(from: texture!, sourceSlice:0, sourceLevel:0, sourceOrigin: MTLOrigin(x:0,y:0,z:0),
        sourceSize: MTLSize(width:screen!.width, height:screen!.height, depth: 1),
        to: drawable.texture, destinationSlice:0, destinationLevel:0, destinationOrigin: MTLOrigin(x:0,y:0,z:0))
      blitPass.endEncoding()

      if let renderPass = currentRenderPassDescriptor {
        renderPass.colorAttachments[0].texture = drawable.texture
        renderPass.colorAttachments[0].loadAction = .load  
        commandBuffer.makeRenderCommandEncoder(descriptor: renderPass)!.endEncoding()

        commandBuffer.addCompletedHandler {cb in self.vwaitSem.signal()}
        commandBuffer.present(drawable)
        commandBuffer.commit()
      }
    }
  }
}

class BackgroundThread: Thread {
  var screen: CGContext
  var vwaitSem: DispatchSemaphore
  var x = 0

  init(screen:CGContext, vwaitSem:DispatchSemaphore) {
    self.screen = screen
    self.vwaitSem = vwaitSem
  }

  override func main() {
    while true {
      // screen.clear(CGRect(x:0,y:0, width:screen.width, height:screen.height))
      // screen.setFillColor(CGColor.white)
      // screen.fill(CGRect(x:x, y:0, width:100, height:100))
      // x += 1

      screen.clear(CGRect(x:0,y:0, width:screen.width, height:screen.height))
      screen.setFillColor(CGColor.white)
      let screenWidth = UInt32(screen.width), screenHeight = UInt32(screen.height)
      for _ in 0...5000 {
        let rect = CGRect(x: CGFloat(arc4random_uniform(screenWidth+1)),
          y: CGFloat(arc4random_uniform(screenHeight+1)), width:6, height:6)
        screen.fill(rect)
      }
      vwaitSem.wait()
    }
  }
}

let width = 640, height = 480,
  appMenuItem = NSMenuItem(),
  quitMenuItem = NSMenuItem(title:"Quit",
    action:#selector(NSApplication.terminate), keyEquivalent:"q"),
  window = NSWindow(contentRect:NSMakeRect(0,0, CGFloat(width), CGFloat(height)),
      styleMask:[.closable,.titled], backing:.buffered, defer:false)

NSApp.setActivationPolicy(NSApplication.ActivationPolicy.regular)
NSApp.mainMenu = NSMenu()
NSApp.mainMenu?.addItem(appMenuItem)
appMenuItem.submenu = NSMenu()
appMenuItem.submenu?.addItem(quitMenuItem)
window.cascadeTopLeft(from:NSMakePoint(20,20))
window.makeKeyAndOrderFront(nil)
window.contentView = View()
window.makeFirstResponder(window.contentView)
NSApp.activate(ignoringOtherApps:true)
NSApp.run()
票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/61838412

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档