我正在macOS上的Metal中使用计算着色器。我正在尝试做一些非常基本的事情来学习它们如何工作。我看到一些我不理解的输出。我以为我会先尝试生成一个简单的2D渐变。红色通道将沿宽度从0增加到1,绿色通道将沿高度从0增加到1。所以我写了这个内核:
kernel void myKernel(texture2d<half, access::write> outTexture [[ texture(MBKT_OutputTexture) ]],
uint2 gid [[thread_position_in_grid]])
{
half4 color = half4((float)gid.x / 480.0, (float)gid.y / 360.0, 0.0, 1.0);
outTexture.write(color, gid);
}
我得到的是在中点从0增加到0.5,对于图像的其余部分增加0.5,就像这样:
如果我将2个值取反,则内核将对此进行计算:
half4 color = half4(1.0 - (float)gid.x / 480.0, 1.0 - (float)gid.y / 360.0, 0.0, 1.0);
结果甚至更陌生。我希望它在左侧和底部为1.0,在中间降至0.5,但是我得到的是:
这是怎么回事?在第一种情况下,就像超过中点的所有值都为0.5。在第二种情况下,它的左/下边缘为0.5,中间为1.0,然后在一个像素后翻转回0.0。
奇怪的是,如果我使用thread_position_in_grid
将值拉出缓冲区,则它可以正常工作。例如,我可以计算一个Mandelbrot集,结果是正确的。但是我对上面的简单内核所发生的事情感到困惑。谁能向我解释一下?
这是MTKViewDelegate
中我的计算内核设置代码。这基于Apple的“ Hello Compute”示例代码:
_metalView = metalView;
_device = metalView.device;
_commandQueue = [_device newCommandQueue];
_metalView.colorPixelFormat = MTLPixelFormatBGRA8Unorm_sRGB;
// Load all the shader files with a .metal file extension in the project
id<MTLLibrary> defaultLibrary = [_device newDefaultLibrary];
// Load the kernel function from the library
id<MTLFunction> kernelFunction = [defaultLibrary newFunctionWithName:@"myKernel"];
// Create a compute pipeline state
NSError* error = nil;
_computePipelineState = [_device newComputePipelineStateWithFunction:kernelFunction
error:&error];
if(!_computePipelineState)
{
NSLog(@"Failed to create compute pipeline state, error %@", error);
return nil;
}
这是我创建输出纹理和线程组的代码:
MTLTextureDescriptor* outputTextureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatBGRA8Unorm_sRGB
width:_viewportSize.x
height:_viewportSize.y
mipmapped:NO];
_outputTexture = [_device newTextureWithDescriptor:outputTextureDescriptor];
// Set the compute kernel's threadgroup size of 16x16
_threadgroupSize = MTLSizeMake(16, 16, 1);
// Calculate the number of rows and columns of threadgroups given the width of the input image
// Ensure that you cover the entire image (or more) so you process every pixel
_threadgroupCount.width = (_viewportSize.x + _threadgroupSize.width - 1) / _threadgroupSize.width;
_threadgroupCount.height = (_viewportSize.y + _threadgroupSize.height - 1) / _threadgroupSize.height;
// Since we're only dealing with a 2D data set, set depth to 1
_threadgroupCount.depth = 1;
在我的测试中,_viewportSize
是480 x 360。
我已经完成了@Egor_Shkorov在评论中建议的其他测试。我使用threads_per_grid
变量代替硬编码480和360:
kernel void myKernel(
texture2d<half, access::write> outTexture [[ texture(MBKT_OutputTexture) ]],
uint2 gid [[thread_position_in_grid]],
uint2 tpg [[threads_per_grid]])
{
half4 color = half4((float)gid.x / tpg.x, (float)gid.y / tpg.y, 0.0, 1.0);
outTexture.write(color, gid);
}
这可以改善效果,使梯度在每个方向上一直延伸,但仍仅从0到0.5而不是在每个方向上都变为1:
答案 0 :(得分:0)
非常相似的事情发生在我身上。 thread_position_in_grid
的值似乎被限制在一个小范围内,而不是整个网格(可能只有 threadgroup
的大小)。
简而言之,可能是因为你在打电话
_commandEncoder.dispatchThreads(threadGroupCount, threadsPerThreadgroup: threadGroupSize)
代替
_commandEncoder.dispatchThreadgroups(threadGroupCount, threadsPerThreadgroup: threadGroupSize)
我注意到属性 thread_position_in_grid
在这些函数下导致不同的值。不确定这是否是预期行为,因为我在文档中找不到相关描述,我希望此属性指的是整个网格中的位置。此外,Metal 会在使用 dispatchThreads()
时决定线程组的数量,并且可以创建非统一线程组,这可能与问题有关。
dispatchThreads(_:threadsPerThreadgroup:)
仅当设备支持非统一时才使用此方法 线程组大小。见Metal Feature Set Tables。这种方法编码一个 指定网格中任意数量线程的调度调用 (threadsPerGrid)。 Metal 计算需要的线程组数量, 如有必要,提供部分线程组。当计算命令 已编码,对参数或资源的任何必要引用 先前在编码器上设置的记录作为命令的一部分。 对命令进行编码后,您可以安全地将编码状态更改为 设置编码其他命令所需的参数。