更改纹理的一小部分会导致其余纹理变黑
Changing a small region of my texture causes the rest of the texture to go black
我有一个应用程序在多个线程上对 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
,如文档所述:
The existing contents of the texture are preserved.
但没有任何区别。
最后,当一个线程完成时,它通过执行以下操作上传其 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 而不是 iOS 上,纹理的存储模式默认为 MTLStorageModeManaged
。这意味着您必须显式同步纹理以获得 CPU 到 "see" GPU 所做的任何修改。由于您的初始 low-resolution 图像被绘制到纹理中,因此仅在您同步的 GPU unless/until 上。由于您未能做到这一点,因此 CPU 具有未初始化的数据。当您使用 CPU 替换区域时,它正在修改其(未初始化的)副本,然后将其推送到 GPU。替换绘制的内容。
要同步,您应该创建一个 blit 命令编码器 (MTLBlitCommandEncoder
) 并使用它来使用 -synchronizeResource:
或(如果您想要更具选择性)[=13= 对同步命令进行编码].
最后,我不确定,但我会担心各种 -replaceRegion:...
调用的线程安全。所以,你应该使用串行调度队列或其他东西来序列化它们。
我有一个应用程序在多个线程上对 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
,如文档所述:
The existing contents of the texture are preserved.
但没有任何区别。
最后,当一个线程完成时,它通过执行以下操作上传其 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 而不是 iOS 上,纹理的存储模式默认为 MTLStorageModeManaged
。这意味着您必须显式同步纹理以获得 CPU 到 "see" GPU 所做的任何修改。由于您的初始 low-resolution 图像被绘制到纹理中,因此仅在您同步的 GPU unless/until 上。由于您未能做到这一点,因此 CPU 具有未初始化的数据。当您使用 CPU 替换区域时,它正在修改其(未初始化的)副本,然后将其推送到 GPU。替换绘制的内容。
要同步,您应该创建一个 blit 命令编码器 (MTLBlitCommandEncoder
) 并使用它来使用 -synchronizeResource:
或(如果您想要更具选择性)[=13= 对同步命令进行编码].
最后,我不确定,但我会担心各种 -replaceRegion:...
调用的线程安全。所以,你应该使用串行调度队列或其他东西来序列化它们。