CAMetalLayer窗口调整大小时出现内存泄漏问题
macOS下CAMetalLayer窗口调整大小时内存暴增问题
我在macOS上创建了一个完全基于CAMetalLayer的窗口,使用CVDisplayLink以显示器刷新率持续渲染,即使窗口空闲也不会停止。
每次调整窗口大小时,内存使用率每秒增加数百兆字节。
未调整窗口大小时,内存仅以约0.3MB/s的速度缓慢增长——这是因为未添加@autoreleasepool导致的小泄漏,仅作演示可忽略。我推测调整窗口大小时,新的帧缓冲区被分配但未被释放。
通过Instruments.app排查发现,所有内存均在CAMetalLayer的nextDrawable方法中分配。
核心代码示例
// ... - (void)setFrameSize:(NSSize)size { [super setFrameSize:size]; metalLayer.drawableSize = size; } // ... - (void)renderOneFrame { id<CAMetalDrawable> drawable = [metalLayer nextDrawable]; id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer]; MTLRenderPassDescriptor* passDesc = [[MTLRenderPassDescriptor alloc] init]; passDesc.colorAttachments[0].texture = drawable.texture; passDesc.colorAttachments[0].loadAction = MTLLoadActionClear; passDesc.colorAttachments[0].storeAction = MTLStoreActionStore; id<MTLRenderCommandEncoder> commandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:passDesc]; [commandEncoder setRenderPipelineState:renderPipeline]; [commandEncoder endEncoding]; [commandBuffer presentDrawable:drawable]; [commandBuffer commit]; } // ...
这是一个极简可运行示例——我甚至没有执行任何实际渲染操作!
我仅通过修改CAMetalLayer的drawableSize属性来调整帧缓冲区大小,查阅该属性与nextDrawable的官方文档后未找到相关问题线索。Apple的CAMetalLayer示例代码采用相同的窗口大小调整方式,却未出现此类内存泄漏问题。
完整源码
Objective-C代码(main.m)
// main.m #import <Cocoa/Cocoa.h> #import <Metal/Metal.h> #import <QuartzCore/QuartzCore.h> @interface MainView : NSView { CAMetalLayer* metalLayer; CVDisplayLinkRef displayLink; id<MTLDevice> device; id<MTLCommandQueue> commandQueue; id<MTLRenderPipelineState> renderPipeline; } @end @implementation MainView - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; self.wantsLayer = YES; self.layer = [CAMetalLayer layer]; metalLayer = (CAMetalLayer*)self.layer; device = MTLCreateSystemDefaultDevice(); metalLayer.device = device; NSURL* path = [NSURL fileURLWithPath:@"out/shaders.metallib" isDirectory:false]; id<MTLLibrary> library = [device newLibraryWithURL:path error:nil]; MTLRenderPipelineDescriptor* desc = [[MTLRenderPipelineDescriptor alloc] init]; desc.vertexFunction = [library newFunctionWithName:@"vertexShader"]; desc.fragmentFunction = [library newFunctionWithName:@"fragmentShader"]; renderPipeline = [device newRenderPipelineStateWithDescriptor:desc error:nil]; commandQueue = [device newCommandQueue]; CVDisplayLinkCreateWithActiveCGDisplays(&displayLink); CVDisplayLinkSetOutputCallback(displayLink, displayLinkCallback, self); CVDisplayLinkStart(displayLink); return self; } - (void)setFrameSize:(NSSize)size { [super setFrameSize:size]; metalLayer.drawableSize = size; } static CVReturn displayLinkCallback( CVDisplayLinkRef displayLink, const CVTimeStamp* now, const CVTimeStamp* outputTime, CVOptionFlags flagsIn, CVOptionFlags* flagsOut, void* displayLinkContext) { [(MainView*)displayLinkContext renderOneFrame]; return kCVReturnSuccess; } - (void)renderOneFrame { id<CAMetalDrawable> drawable = [metalLayer nextDrawable]; id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer]; MTLRenderPassDescriptor* passDesc = [[MTLRenderPassDescriptor alloc] init]; passDesc.colorAttachments[0].texture = drawable.texture; passDesc.colorAttachments[0].loadAction = MTLLoadActionClear; passDesc.colorAttachments[0].storeAction = MTLStoreActionStore; id<MTLRenderCommandEncoder> commandEncoder = [commandBuffer renderCommandEncoderWithDescriptor:passDesc]; [commandEncoder setRenderPipelineState:renderPipeline]; [commandEncoder endEncoding]; [commandBuffer presentDrawable:drawable]; [commandBuffer commit]; } @end int main() { [NSApplication sharedApplication]; [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; NSRect rect = NSMakeRect(100, 100, 500, 400); NSWindow* window = [NSWindow alloc]; [window initWithContentRect:rect styleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskResizable backing:NSBackingStoreBuffered defer:NO]; MainView* view = [[MainView alloc] initWithFrame:rect]; window.contentView = view; [window makeKeyAndOrderFront:nil]; [NSApp activateIgnoringOtherApps:YES]; [NSApp run]; }
Metal着色器代码(shaders.metal)
// shaders.metal struct V { float4 p [[position]]; }; vertex V vertexShader(const device V* v [[buffer(0)]], uint i [[vertex_id]]) { return v[i]; } fragment float4 fragmentShader(V v [[stage_in]]) { return v.p; }
编译命令
- 编译着色器:
xcrun -sdk macosx metal shaders.metal -o out/shaders.metallib - 编译Objective-C源码:
clang -framework Cocoa -framework QuartzCore -framework Metal -o out/example main.m
内容的提问来源于stack exchange,提问作者Luna Razzaghipour
相关产品推荐
相关产品推荐

