【问题标题】:Changing a small region of my texture causes the rest of the texture to go black更改纹理的一小部分会导致纹理的其余部分变黑
【发布时间】:2019-05-26 05:56:02
【问题描述】:

我有一个应用程序在多个线程上对 CPU 进行一些计算。每个线程计算输出图像的 64x64 像素区域。当一个线程完成计算后,它会将计算得到的像素数据通过-[MTLTexture replaceRegion:]上传到MTLTexture。在任何 CPU 线程启动之前,我已将完整结果的低分辨率版本放入纹理中,我希望 CPU 上的线程在以更高分辨率计算图像数据时覆盖图像数据。

我大部分时间都在工作,但是当第一次调用 -replaceRegion: 时,它似乎将纹理清除为黑色,然后根据要求替换 64x64 区域。

这是我创建纹理的方式:

MTLTextureDescriptor*   outputTextureDesc   = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm
                                                                                             width:viewportSize.width
                                                                                            height:viewportSize.height
                                                                                         mipmapped:NO];
outputTextureDesc.usage = MTLTextureUsageShaderWrite | MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
_outputTexture = [device newTextureWithDescriptor:outputTextureDesc];

然后,当我想将输出的低分辨率版本放入纹理中时,我从另一个纹理中复制它,如下所示:

const Vertex2D quadVertices[] =
{
    //Pixel Positions, Texture Coordinates
    { { viewportSize.x / 2.0,  viewportSize.y / -2.0 }, { right, bottom } },
    { { viewportSize.x / -2.0, viewportSize.y / -2.0 }, { left, bottom } },
    { { viewportSize.x / -2.0, viewportSize.y / 2.0 }, { left, top } },

    { { viewportSize.x / 2.0,  viewportSize.y / -2.0 }, { right, bottom } },
    { { viewportSize.x / -2.0, viewportSize.y / 2.0 }, { left, top } },
    { { viewportSize.x / 2.0, viewportSize.y / 2.0 }, { right, top } },
};

MTLRenderPassDescriptor *renderPassDescriptor = [MTLRenderPassDescriptor renderPassDescriptor];
renderPassDescriptor.colorAttachments [ 0 ].texture = _outputTexture;
renderPassDescriptor.colorAttachments [ 0 ].loadAction = MTLLoadActionDontCare;
renderPassDescriptor.colorAttachments [ 0 ].storeAction = MTLStoreActionStore;

if(renderPassDescriptor != nil)
{
    id<MTLCommandBuffer> commandBuffer = [commandQueue commandBuffer];
    commandBuffer.label = @"Copy Selection Command Buffer";

    // Create a render command encoder so we can render into something
    id<MTLRenderCommandEncoder> renderEncoder =
        [commandBuffer renderCommandEncoderWithDescriptor:renderPassDescriptor];
    renderEncoder.label = @"Copy Selection Command Encoder";

    [renderEncoder setViewport:(MTLViewport){0.0, 0.0, viewportSize.x, viewportSize.y, -1.0, 1.0 }];

    [renderEncoder setRenderPipelineState:renderPipelineState];

    [renderEncoder setVertexBytes:quadVertices
                           length:sizeof(quadVertices)
                          atIndex:MBV_VertexIndex];

    [renderEncoder setVertexBytes:&viewportSize
                           length:sizeof(viewportSize)
                          atIndex:MBV_ViewportSize];

    [renderEncoder setFragmentTexture:oldRender
                              atIndex:MBF_Texture];

    // Draw the vertices of our triangles
    [renderEncoder drawPrimitives:MTLPrimitiveTypeTriangle
                      vertexStart:0
                      vertexCount:6];

    [renderEncoder endEncoding];
    [commandBuffer commit];
    [commandBuffer waitUntilCompleted];
}

我还尝试将 renderPassDescriptor.colorAttachment [ 0 ].loadAction 更改为 MTLLoadActionLoad 而不是 MTLLoadActionDontCare,正如文档所述:

纹理的现有内容被保留。

但它没有任何区别。

最后,当一个线程完成时,它通过这样做上传它的 64x64 像素结果:

MTLRegion currentRegion = { { colStart, rowStart, 0}, { colEnd - colStart, rowEnd - rowStart, 1 } };
[_outputTexture replaceRegion:currentRegion
                  mipmapLevel:0
                    withBytes:_outputBitmap + (rowStart * (int)viewportSize.width) + colStart
                  bytesPerRow:viewportSize.width * sizeof(*_outputBitmap)];

如果我在低分辨率数据的初始副本之后使用 Metal 调试工具查看纹理,它包含正确的像素。但是在第一次调用-replaceRegion: 之后,除了被替换的区域之外的所有内容都是黑色的。对-replaceRegion: 的后续调用正常工作,不会覆盖之前写入的结果。

我应该提一下,这个纹理也会被显示,因为它是由MTKView 更新的。在高分辨率图块开始填充之前,我有时确实会看到一两帧的低分辨率副本。知道为什么调用-replaceRegion: 会清除纹理吗? (或者如果不是对-replaceRegion:的调用,还有什么可能会清除纹理?)

【问题讨论】:

    标签: macos textures metal


    【解决方案1】:

    假设这是在 macOS 而不是 iOS 上,纹理的存储模式默认为 MTLStorageModeManaged。这意味着您必须显式同步纹理以使 CPU “看到” GPU 所做的任何修改。由于您的初始低分辨率图像被绘制到纹理中,除非/直到您同步,否则仅在 GPU 上。由于您没有这样做,因此 CPU 有未初始化的数据。当您使用 CPU 替换区域时,它会修改其(未初始化的)副本,然后将其推送到 GPU。这会替换绘制的内容。

    要进行同步,您应该创建一个 blit 命令编码器 (MTLBlitCommandEncoder) 并使用它来使用 -synchronizeResource: 或(如果您想更有选择性)-synchronizeTexture:slice:level: 对同步命令进行编码。

    最后,我不确定,但我会担心各种-replaceRegion:... 调用的线程安全。因此,您应该使用串行调度队列或其他东西来序列化它们。

    【讨论】:

    • 这是在 macOS 上,所以我会试一试。感谢您的建议。我也计划添加一些序列化,只是还没有完成。
    猜你喜欢
    • 2023-03-18
    • 2019-04-16
    • 1970-01-01
    • 2013-10-22
    • 1970-01-01
    • 2016-11-07
    • 2014-01-05
    • 1970-01-01
    • 2014-04-15
    相关资源
    最近更新 更多