更改纹理的一小部分会导致其余纹理变黑

时间:2019-05-26 05:56:03

标签: macos textures metal

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

我大部分时间都在工作,但是当第一次调用-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:的调用,还有其他清除纹理的方法吗?)

1 个答案:

答案 0 :(得分:2)

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

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

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