我一直在努力解决基于Cocoa的简单可视化程序的性能相关问题,该程序不断将热图绘制为位图并在屏幕上显示位图。从广义上讲,热图是不断滚动的实时信息源。在每次重绘时,在位图的右侧添加新像素,然后将位图向左移动,从而创建持续的信息流。
我当前的方法是在自定义NVSView
中实现的,具有用于底层像素的循环缓冲区。实际上,图像被旋转90度,因此添加新数据只需要附加缓冲区(向图像添加行)。然后我使用CGDataProviderCreateWithData
创建CoreGraphics图像并将其绘制到旋转的上下文中。 (代码如下,虽然对问题不太重要。)
我希望找到一种更有效的方法来实现这一目标。看起来每次重绘完整位图都是过度的,在我的尝试中,它使用了惊人的高CPU(~20-30%)。我觉得应该有办法以某种方式指示GPU缓存,但是移动现有像素然后追加新数据。
我正在考虑一种方法,它将使用两个CGBitmapContextCreate
,一个上下文用于当前屏幕上的内容,另一个上下文用于修改。先前的像素将从一个上下文复制到另一个上下文,但我不确定这会显着提高性能。在我走得太远之前,有没有更好的方法来处理这种更新?
以下是我当前实施的相关代码,但我认为我更需要更高级别的指导:
class PlotView: NSView
{
private let bytesPerPixel = 4
private let height = 512
private let bufferLength = 5242880
private var buffer: TPCircularBuffer
// ... Other code that appends data to buffer and set needsDisplay ...
override func drawRect(dirtyRect: NSRect) {
super.drawRect(dirtyRect)
// get context
guard let context = NSGraphicsContext.currentContext() else {
return
}
// get colorspace
guard let colorSpace = CGColorSpaceCreateDeviceRGB() else {
return
}
// number of sampels to draw
let maxSamples = Int(frame.width) * height
let maxBytes = maxSamples * bytesPerPixel
// bytes for reading
var availableBytes: Int32 = 0
let bufferStart = UnsafeMutablePointer<UInt8>(TPCircularBufferTail(&buffer, &availableBytes))
let samplesStart: UnsafeMutablePointer<UInt8>
let samplesCount: Int
let samplesBytes: Int
// buffer management
if Int(availableBytes) > maxBytes {
// advance buffer start
let bufferOffset = Int(availableBytes) - maxBytes
// set sample start
samplesStart = bufferStart.advancedBy(bufferOffset)
// consume values
TPCircularBufferConsume(&buffer, Int32(bufferOffset))
// number of samples
samplesCount = maxSamples
samplesBytes = maxBytes
}
else {
// set to start
samplesStart = bufferStart
// number of samples
samplesBytes = Int(availableBytes)
samplesCount = samplesBytes / bytesPerPixel
}
// get dimensions
let rows = height
let columns = samplesCount / rows
// bitmap info
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.NoneSkipFirst.rawValue)
// counts
let bytesPerComponent = sizeof(UInt8)
let bytesPerPixel = sizeof(UInt8) * bytesPerPixel
// prepare image
let provider = CGDataProviderCreateWithData(nil, samplesStart, samplesBytes, nil)
let image = CGImageCreate(rows, columns, 8 * bytesPerComponent, 8 * bytesPerPixel, rows * bytesPerPixel, colorSpace, bitmapInfo, provider, nil, false, .RenderingIntentDefault)
// make rotated size
let rotatedSize = CGSize(width: frame.width, height: frame.height)
let rotatedCenterX = rotatedSize.width / 2, rotatedCenterY = rotatedSize.height / 2
// rotate, from: http://stackoverflow.com/questions/10544887/rotating-a-cgimage
CGContextTranslateCTM(context.CGContext, rotatedCenterX, rotatedCenterY)
CGContextRotateCTM(context.CGContext, CGFloat(0 - M_PI_2))
CGContextScaleCTM(context.CGContext, 1, -1)
CGContextTranslateCTM(context.CGContext, -rotatedCenterY, -rotatedCenterX)
// draw
CGContextDrawImage(context.CGContext, NSRect(origin: NSPoint(x: rotatedSize.height - CGFloat(rows), y: 0), size: NSSize(width: rows, height: columns)), image)
}
}