1
\$\begingroup\$

I created a single pass gaussian blur using HLSL compute shader. I also want it to be separable, which means, that first I perform blur along the horizontal direction, write out the result to the texture, then perform the vertical blur with the horizontally blurred data.

I do this by creating DeviceMemoryBarriers before and after writing out the blur results to the globallycoherent Texture2D.

This is my shader:

Texture2D<float4> input:register(t0);
globallycoherent RWTexture2D<float4> input_output:register(u0);

// Note: Shader requires feature: Typed UAV additional format loads!
[numthreads(16, 16, 1)]
void main(uint3 DTid : SV_DispatchThreadID)
{
    // Query the texture dimensions (width, height):
    uint2 dim;
    input_output.GetDimensions(dim.x, dim.y);

    // Determine if the thread is alive (it is alive when the dispatchthreadID can directly index a pixel)
    if (DTid.x < dim.x && DTid.y < dim.y)
    {
        // Do bilinear downsampling first and write it out:
        input_output[DTid.xy] = input.SampleLevel(sampler_linear_clamp, ((float2)DTid + 0.5f) / (float2)dim, 0);
        DeviceMemoryBarrier();

        uint i = 0;
        float4 sum = 0;

        // Gather samples in the X (horizontal) direction:
        [unroll]
        for (i = 0; i < 9; ++i)
        {
            sum += input_output[DTid.xy + uint2(gaussianOffsets[i], 0)] * gaussianWeightsNormalized[i];
        }
        // Write out the result of the horizontal blur:
        DeviceMemoryBarrier();
        input_output[DTid.xy] = sum;
        DeviceMemoryBarrier();
        sum = 0;

        // Gather samples in the Y (vertical) direction:
        [unroll]
        for (i = 0; i < 9; ++i)
        {
            sum += input_output[DTid.xy + uint2(0, gaussianOffsets[i])] * gaussianWeightsNormalized[i];
        }
        // Write out the result of the vertical blur:
        DeviceMemoryBarrier();
        input_output[DTid.xy] = sum;
    }
}

The problem is that the result flickers a bit, and has some errors in the image, too. Seems a bit like a thread group can't see writes by other groups.

But there is a globallycoherent modifier before the RWTexture2D which should flush the entire resource so that writes are visible in every thread group (MSDN). Indeed, if I remove that modifier, then the flickering becomes a whole lot worse than if I leave it there.

Here is a screenshot of the problem: enter image description here

(Notice the lines on the windmill, and it also flickers on the whole image from time to time which is not visible on a still shot)

Anyone here has an idea what I can do about it? (PS. the blur is performed when creating mipmaps, so I very much want to avoid multiple passes because it is already one pass for each mip)

\$\endgroup\$
2
\$\begingroup\$

I'm not an expert in compute shaders, but it looks like the memory barriers you're using there say:

"Ensure all current writes in progress finish before proceeding to the next step"

...while I think what you mean/want is:

"Ensure all writes to the texels I'm about to try to read finish before proceeding to the next step"

Those writes might not have even been attempted by another thread group yet, so even after all current writes complete and you proceed to the vertical blur, you can end up sampling texels from input_output that haven't been written yet this frame.

Here it looks like you're getting last frame's blurred result in these samples instead, which would explain why you see the lines trailing behind the moving vanes of the windmill along the borders separating vertically stacked threadgroup chunks.

You could avoid this by performing the blur in two passes, fully populating the intermediate buffer before trying to read from it. You'd likely want to output to another buffer yet, rather than stuffing the result into the same buffer you're reading - otherwise you can hit a similar but more subtle issue where your second pass samples pick up a mix of texels that have been blurred only horizontally and those that have already gotten both the horizontal and vertical passes.

If you need to do this in a single pass, you could have each threadgroup maintain its own local strip of horizontally-blurred intermediates, half the kernel size larger than the thread group's span above & below, then read from that - but you compute some intermediates twice that way.

\$\endgroup\$
  • \$\begingroup\$ Thank you for the answer, seems like I misunderstood a little how the compute shaders organize the dispatches, so I will go by the multipass approach. \$\endgroup\$ – János Turánszki Mar 20 '17 at 11:28

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.