对thread_position_in_grid感到困惑

时间:2019-04-24 02:16:22

标签: metal compute-shader

我正在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,就像这样:

A 2D gradient where the red channel increases from 0 to 0.5 along half the width and is 0.5 for the remainder of the width. The green channel does the same vertically.

如果我将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,但是我得到的是:

Even worse results

这是怎么回事?在第一种情况下,就像超过中点的所有值都为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:

A gradient stretching from black to 50% red horizontally and black to 50% green vertically.

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 计算需要的线程组数量, 如有必要,提供部分线程组。当计算命令 已编码,对参数或资源的任何必要引用 先前在编码器上设置的记录作为命令的一部分。 对命令进行编码后,您可以安全地将编码状态更改为 设置编码其他命令所需的参数。