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

基于Objective-C++/Metal实现帧缓冲区读取并保存为PNG

Metal后端帧缓冲区转PNG文件实现方案(dearpygui Mac端适配)

需求目标

// 高层逻辑
// 创建纹理
// 将当前视图渲染到纹理
// 将纹理写入指定路径的PNG文件
// 是否需要释放纹理/缓冲区?

原未编译代码尝试

void OutputFrameBuffer(const char *filepath) {
  mvGraphics &graphics = GContext->graphics;
  mvGraphics_Metal *graphicsData =
      (mvGraphics_Metal *)graphics.backendSpecifics;

  mvViewportData *_view =
      (mvViewportData *)GContext->viewport->platformSpecifics;
  id<CAMetalDrawable> drawable = _view->layer.nextDrawable;

  id<MTLCommandBuffer> commandBuffer =
      [graphicsData->commandQueue commandBuffer];

  // 编码渲染通道以将图像渲染到可绘制纹理。
  [self drawScene:view withCommandBuffer:commandBuffer];

  id<MTLTexture> readTexture = drawable.texture;

  MTLOrigin readOrigin = MTLOriginMake(region.origin.x, region.origin.y, 0);
  MTLSize readSize = MTLSizeMake(region.size.width, region.size.height, 1);

  const id<MTLBuffer> pixelBuffer =
      [self readPixelsWithCommandBuffer:commandBuffer
                            fromTexture:readTexture
                               atOrigin:readOrigin
                               withSize:readSize];

  NSUInteger bytesPerPixel = sizeofPixelFormat(readTexture.pixelFormat);
  NSUInteger bytesPerRow = size.width * bytesPerPixel;
  NSUInteger bytesPerImage = size.height * bytesPerRow;

  id<MTLBuffer> _readBuffer =
      [readTexture.device newBufferWithLength:bytesPerImage
                                      options:MTLResourceStorageModeShared];

  id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];

  [blitEncoder copyFromTexture:readTexture
                   sourceSlice:0
                   sourceLevel:0
                  sourceOrigin:origin
                    sourceSize:size
                      toBuffer:_readBuffer
             destinationOffset:0
        destinationBytesPerRow:bytesPerRow
      destinationBytesPerImage:bytesPerImage];

  [blitEncoder endEncoding];
  [commandBuffer commit];

  // 应用必须等待GPU完成blit操作才能从_readBuffer读取数据。
  [commandBuffer waitUntilCompleted];

  // 从GPU读取纹理数据
  NSUInteger _bytesPerRow = readTexture.width * 4; // 假设为RGBA8格式
  NSUInteger bufferSize = bytesPerRow * readTexture.height;
  //   void *buffer = malloc(bufferSize);
  //   [renderTexture getBytes:buffer
  //               bytesPerRow:bytesPerRow
  //                fromRegion:MTLRegionMake2D(0, 0, renderTexture.width,
  //                                           renderTexture.height)
  //               mipmapLevel:0];

  // 将char*路径转换为NSString
  std::string fnameString(filepath);
  NSString *filePath = [NSString stringWithUTF8String:fnameString.c_str()];

  // 将纹理数据保存到文件
  NSData *imageData = [NSData dataWithBytesNoCopy:_readBuffer
                                           length:bufferSize
                                     freeWhenDone:YES];
  [imageData writeToFile:filePath atomically:YES];

  // 清理资源
  free(_readBuffer);
}

可用上下文结构体

struct mvViewportData
{
    GLFWwindow*   handle = nullptr;
    CAMetalLayer* layer = nullptr;
};

struct mvGraphics_Metal
{
    MTLRenderPassDescriptor* renderPassDescriptor;
    id <MTLCommandQueue>     commandQueue;
    id <MTLDevice>           device;
};

问题修复与解决方案

1. 编译错误修正

原代码存在未定义变量、无效函数调用等问题,需先修复:

  • 替换所有未定义的region/size/origin变量,使用当前视图的实际绘制尺寸
  • 移除未实现的readPixelsWithCommandBuffer调用,改用Metal BlitEncoder完成像素拷贝
  • 修正drawScene调用参数,替换为dearpygui内部渲染逻辑(若已有成熟渲染流程,可跳过自定义渲染步骤)

2. 纹理转PNG核心逻辑修正

Metal纹理存储的是原始BGRA格式像素数据,直接写入文件无法生成标准PNG,需通过CoreGraphics完成格式转换:

  • 创建CGContext将BGRA像素转为RGBA格式
  • 生成CGImage并导出为PNG数据

3. 资源管理规范

  • Metal对象(如MTLBuffer)是Objective-C对象,需用ARC或手动release管理,禁止用free直接释放
  • CAMetalDrawable的texture由框架自动回收,无需手动释放

完整修正代码

void OutputFrameBuffer(const char *filepath) {
  mvGraphics &graphics = GContext->graphics;
  mvGraphics_Metal *graphicsData =
      (mvGraphics_Metal *)graphics.backendSpecifics;

  mvViewportData *_view =
      (mvViewportData *)GContext->viewport->platformSpecifics;
  
  // 获取视图实际绘制尺寸
  CGSize drawableSize = _view->layer.drawableSize;
  MTLSize size = MTLSizeMake(drawableSize.width, drawableSize.height, 1);
  MTLOrigin origin = MTLOriginMake(0, 0, 0);
  
  id<MTLCommandBuffer> commandBuffer =
      [graphicsData->commandQueue commandBuffer];

  // 获取当前可绘制纹理(确保dearpygui已完成场景渲染)
  id<CAMetalDrawable> drawable = _view->layer.nextDrawable;

  // 创建共享缓冲区用于读取像素数据
  NSUInteger bytesPerPixel = 4; // Metal默认BGRA8Unorm格式
  NSUInteger bytesPerRow = drawableSize.width * bytesPerPixel;
  NSUInteger bytesPerImage = drawableSize.height * bytesPerRow;
  
  id<MTLBuffer> readBuffer =
      [graphicsData->device newBufferWithLength:bytesPerImage
                                      options:MTLResourceStorageModeShared];

  // 拷贝纹理数据到共享缓冲区
  id<MTLBlitCommandEncoder> blitEncoder = [commandBuffer blitCommandEncoder];
  [blitEncoder copyFromTexture:drawable.texture
                   sourceSlice:0
                   sourceLevel:0
                  sourceOrigin:origin
                    sourceSize:size
                      toBuffer:readBuffer
             destinationOffset:0
        destinationBytesPerRow:bytesPerRow
      destinationBytesPerImage:bytesPerImage];
  [blitEncoder endEncoding];
  
  [commandBuffer commit];
  [commandBuffer waitUntilCompleted];

  // 转换路径格式
  std::string fnameString(filepath);
  NSString *filePath = [NSString stringWithUTF8String:fnameString.c_str()];
  
  // 将原始像素数据转为PNG
  void* pixelData = [readBuffer contents];
  CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
  // 适配Metal BGRA格式到CoreGraphics RGBA格式
  CGContextRef context = CGBitmapContextCreate(pixelData, drawableSize.width, drawableSize.height,
                                               8, bytesPerRow, colorSpace,
                                               kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
  CGImageRef imageRef = CGBitmapContextCreateImage(context);
  
  NSBitmapImageRep* rep = [[NSBitmapImageRep alloc] initWithCGImage:imageRef];
  NSDictionary* properties = @{NSImageCompressionFactor: @1.0};
  NSData* pngData = [rep representationUsingType:NSPNGFileType properties:properties];
  [pngData writeToFile:filePath atomically:YES];

  // 释放CoreGraphics资源
  CGImageRelease(imageRef);
  CGContextRelease(context);
  CGColorSpaceRelease(colorSpace);
  [rep release];
  
  // 若项目禁用ARC,需手动释放Objective-C对象
  // [readBuffer release];
  // [filePath release];
}

关键注意事项

  • 需确保dearpygui的Metal渲染流程已将画面绘制到drawable.texture,否则会拷贝空白或旧帧数据
  • Metal默认纹理格式为BGRA8Unorm,转换时需注意CoreGraphics的字节顺序配置,避免颜色反转
  • 推荐启用ARC简化Objective-C对象内存管理

内容的提问来源于stack exchange,提问作者hun

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 06:44:56