0

我有一个大的NSView,它显示了一个自定义 Quartz2D 绘图,它以高帧速率反复变化。但是,只有绘图的某些部分可能会因帧而异。到目前为止,我的方法是首先绘制到屏幕外位图上下文,然后从该上下文创建图像,最后使用该图像更新视图的 CoreAnimation 层的内容。

我的第一个问题是,这种方法通常是否有意义,在性能方面是否可行?

绘制到屏幕外位图上下文的速度足够快,并且经过优化以仅重绘脏区域。所以在这一步之后,我有一组矩形,它们标记了屏幕外缓冲区中应该显示在屏幕上的区域。现在,我只需使用从屏幕外位图上下文创建的图像更新 CoreAnimation 层的内容,这基本上可以正常工作,但我会闪烁,看起来新帧很快显示在屏幕上,但不完全(或不在全部)绘制了。我玩过CATransaction lock/unlock/begin/end/flush, NSView lockFocus/unlockFocusNSDisableScreenUpdates/NSEnableScreenUpdates但还没有找到解决闪烁的方法,所以我想知道真正正确的同步顺序是什么?

这是初始化代码的草图:

NSView* theView = ...

CALayer* layer = [[CALayer new] autorelease];
layer.actions = [NSDictionary dictionaryWithObject:[NSNull null] forKey:@"contents"];
[theView setLayer: layer];
[theView setWantsLayer: YES];

// bitmapContext gets re-created when the view size increases.
CGContextRef bitmapContext = CGBitmapContextCreate(...);

这里是绘图代码的草图:

CGRect[] dirtyRegions = ...

NSDisableScreenUpdates();

[CATransaction begin];
[CATransaction setDisableActions: YES];

// draw into dirty regions of bitmapContext 
// ...

// create image from bitmap context
void* buffer = CGBitmapContextGetData(bitmapContext); 
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, buffer, ...);
CGImageRef image = CGImageCreate(..., provider, ...);

// update layer contents, dirty regions are ignored
layer.contents = image;

[CATransaction commit];

NSEnableScreenUpdates();

我还想利用有关脏区的知识。有没有办法使用这种方法只更新屏幕上的脏区域?

谢谢你的帮助!

更新:我想我发现了导致闪烁的问题。我使用来自位图上下文的像素缓冲区创建图像CGImageCreate(...)。如果我CGBitmapContextCreateImage(...)改用它,它会起作用。CGBitmapContextCreateImage写时复制,所以如果我理解正确,它会在位图上下文再次更新时写入像素,这可以解释为什么它不能更早地工作。我读过CGBitmapContextCreateImage应该小心使用的地方,因为它调用内核可能会影响性能,所以我想我会简单地将相关像素复制到新的图像缓冲区中,同时考虑到脏区。这有意义吗?

4

2 回答 2

1

在尝试了很多不同的方法后,我放弃了使用 CoreAnimation 来上传像素数据,并决定使用 CoreVideo 像素缓冲区(CVPixelBufferRef)结合 OpenGL 来移动屏幕上的像素。

CoreVideo 提供了一些方便的函数来从像素缓冲区 ( CVOpenGLTextureCacheCreateTextureFromImage) 创建 OpenGL 纹理,在纹理缓存中管理它们 ( CVOpenGLTextureCacheRef),并安全地绘制到缓冲区中 ( CVPixelBufferLockBaseAddress/CVPixelBufferUnlockBaseAddress)。然后可以使用普通的 OpenGL 纹理映射命令 ( glTexCoord2fv) 将脏矩形上传到窗口后台缓冲区。

另一种同样有效并具有类似 API 的方法是IOSurface,有关此的更多信息是这里

于 2013-12-08T22:28:05.570 回答
1

“正常”的方式是反过来工作——调用CALayer -setNeedsDisplay以指示内容的更改何时可用并响应以-drawInContext:按需绘制。因此,您允许从您身上提取图层内容,而不是推动它们。

我不得不承认,我很惊讶你在尝试推送时会撕裂,但假设你从最简单的事情开始,layer.contents = image以及你通过事务和屏幕更新锁定添加的所有额外复杂性都是试图解决撕裂,并且您绝对确定不会因为代码过于复杂而造成问题,您可能应该做的是排队更新,创建一个CVDisplayLink然后仅在相关显示或显示即将更新时推送任何待处理的更新。这与仅在基于旧 CRT 输出的垂直回溯期间更新的方法基本相同。

于 2013-08-28T23:55:25.730 回答