You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 03:07:14