【问题标题】:How to snapshot and save a rendered Metal drawable in macOS Cocoa Objective-C?如何在 macOS Cocoa Objective-C 中快照和保存渲染的 Metal drawable?
【发布时间】:2020-06-04 22:39:22
【问题描述】:

我有一个应用程序,它使用 Metal 对屏幕进行一些渲染(必须使用 CAMetalLayer,而不是 MTKView),我想为用户提供保存结果的快照到磁盘。尝试在转换为 Objective-C 时遵循https://stackoverflow.com/a/47632198/2752221 的答案,我首先像这样编写了一个 commandBuffer 完成回调(注意这是手动保留/释放代码,而不是 ARC;抱歉,遗留代码):

[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> buffer) {
    [self performSelectorOnMainThread:@selector(saveImageTakingDrawable:) withObject:[drawable retain] waitUntilDone:NO];
    optionKeyPressedAndUnhandled_ = NO;
}];

我在致电[commandBuffer presentDrawable:drawable] 后立即执行此操作;我的id &lt;CAMetalDrawable&gt; drawable 仍在范围内。这是我对saveImageTakingDrawable: 的实现:

- (void)saveImageTakingDrawable:(id <CAMetalDrawable>)drawable
{
    // We need to have an image to save
    if (!drawable) { NSBeep(); return; }

    id<MTLTexture> displayTexture = drawable.texture;

    if (!displayTexture) { NSBeep(); return; }

    CIImage *ciImage = [CIImage imageWithMTLTexture:displayTexture options:nil];

    // release the metal texture as soon as we can, to free up the system resources
    [drawable release];

    if (!ciImage) { NSBeep(); return; }

    NSCIImageRep *rep = [NSCIImageRep imageRepWithCIImage:ciImage];

    if (!rep) { NSBeep(); return; }

    NSImage *nsImage = [[[NSImage alloc] initWithSize:rep.size] autorelease];
    [nsImage addRepresentation:rep];

    NSData *tiffData = [nsImage TIFFRepresentation];

    if (!tiffData) { NSBeep(); return; }

    ... filesystem cruft culminating in ...

    if ([tiffData writeToFile:filePath options:NSDataWritingWithoutOverwriting error:nil])
    {
        // play a sound to acknowledge saving
        [[NSSound soundNamed:@"Tink"] play];
        return;
    }

    NSBeep();
    return;
}

结果是“叮当”的声音和一个 7.8 MB 大小合理的 .tif 文件 (1784x1090),但它是透明的,其中没有可用的图像数据;在 Hex Fiend 中查看文件显示整个文件都是零,除了相当简短的页眉和页脚部分。

我怀疑基本方法由于某种原因存在缺陷。当我尝试这个快照时,我得到了几个控制台日志:

2020-06-04 18:20:40.203669-0400 MetalTest[37773:1065740] [CAMetalLayerDrawable texture] should not be called after already presenting this drawable. Get a nextDrawable instead.
Input Metal texture was created with a device that does not match the current context device.
Input Metal texture was created with a device that does not match the current context device.
2020-06-04 18:20:40.247637-0400 MetalTest[37773:1065740] [plugin] AddInstanceForFactory: No factory registered for id <CFUUID 0x600000297260> F8BB1C28-BAE8-11D6-9C31-00039315CD46
2020-06-04 18:20:40.281161-0400 MetalTest[37773:1065740]  HALC_ShellDriverPlugIn::Open: Can't get a pointer to the Open routine

第一个日志似乎表明我什至不允许在它首先呈现后从可绘制对象中取出纹理。那么......这样做的正确方法是什么?

更新:

请注意,我不喜欢saveImageTakingDrawable: 代码的后面部分。我很乐意写出 PNG 而不是 TIFF,如果有办法在不使用 CIImageNSCIImageRepNSImage 的情况下到达我要去的地方,那就更好了。我只想将drawable的纹理图像保存为PNG或TIFF,不知何故

【问题讨论】:

  • 是否允许我标记@KenThomases?我会尝试。 :->
  • 是否可以使用此线程中的 NSTask 和 'screencapture' 代码:stackoverflow.com/questions/61731812/…
  • 对于它的价值,标记不起作用,但我还是找到了它。 :)

标签: objective-c macos cocoa metal


【解决方案1】:

我只是想以某种方式将可绘制对象的纹理图像保存为 PNG 或 TIFF。

这是您可以测试的另一种方法;需要将路径设置为要保存图像文件的任何位置。

- (void) windowCapture: (id)sender {
 NSTask *task = [[NSTask alloc]init];
 [task setLaunchPath:@"/bin/sh"]; 
 NSArray *args = [NSArray arrayWithObjects: @"-c", @"screencapture -i -c -Jwindow", nil];
 [task setArguments:args]; 
 NSPipe *pipe = [NSPipe pipe];
 [task setStandardOutput:pipe]; 
 [task launch];
 [task waitUntilExit];
 int status = [task terminationStatus]; 
 NSData *dataRead = [[pipe fileHandleForReading] readDataToEndOfFile];
 NSString *pipeOutput = [[[NSString alloc] initWithData:dataRead encoding:NSUTF8StringEncoding]autorelease];
 // Tell us if there was a problem
 if (!(status == 0)){NSLog(@"Error: %@",pipeOutput);}
 [task release];
 // Get image data from pasteboard and write to file
 NSPasteboard *pboard = [NSPasteboard generalPasteboard];
 NSData *pngData = [pboard dataForType:NSPasteboardTypePNG];
 NSError *err;
 BOOL success = [pngData writeToFile:@"/Users/xxxx/Desktop/ABCD.png" options:NSDataWritingAtomic error:&err];
 if(!success){NSLog(@"Unable to write to file: %@",err);} else {NSLog(@"File written to desktop.");}
}

【讨论】:

    【解决方案2】:

    我认为您应该将 blit 注入命令缓冲区(在提交之前)以将纹理复制到您自己的纹理中。 (如您所见,drawable 的纹理在呈现后使用起来并不安全。)

    一种策略是将图层的framebufferOnly 设置为false 以进行传递,然后使用MTLBlitCommandEncoder 将可绘制纹理的副本编码为您自己的纹理。或者,如果像素格式不同,则使用渲染命令编码器将可绘制纹理中的四边形绘制编码为您自己的。

    另一种策略是将您自己的纹理替换为您的代码当前使用可绘制纹理的渲染目标颜色附件。主要渲染到您的纹理,然后将其绘制到可绘制对象的纹理。

    无论哪种方式,您的纹理的storageMode 可以是MTLStorageModeManaged,因此您可以使用-getBytes:... 方法之一访问其数据。您只需要确保命令缓冲区最后对 blit 编码器的 synchronizeResource: 命令进行编码即可。

    然后,您可以使用 -initWithBitmapDataPlanes:pixelsWide:pixelsHigh:bitsPerSample:samplesPerPixel:hasAlpha:isPlanar:colorSpaceName:bitmapFormat:bytesPerRow:bitsPerPixel: 方法使用字节构造 NSBitmapImageRep。然后,使用 -representationUsingType:properties: 从中获取 PNG 数据并将其保存到文件中。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-11-30
      • 2011-01-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-07
      • 1970-01-01
      相关资源
      最近更新 更多