Compute Shader to identify dirty regions of a CanvasBitmap

Open
#900 5 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
35/100
Issue type
Bug
Clarity
Needs clarification
Activity status
Stale
Tech stack
csharp

Research direction

Start at GetDirtyPixelRegion and the LoadReadOnlyTexture2D calls where the COM exception is reported, then inspect the GetDirtyRects shader entry point and its texture and buffer inputs. Reproduce the exception and verify that the two bitmap inputs can be loaded and compared successfully, with the resulting buffer representing the dirty bounding region.

Written by the indexing model from the issue text.

Description

question :question: untriaged :toolbox:

I'm trying to create a compute shader to identify the bounding box of dirty regions between two CanvasBitmaps, because Direct3D11CaptureFrame objects don't receive dirty regions in Windows10 and Win11 prior to build 26100. I have the following code sketched out, but am getting a COM exception early on. Any tips/advice?

    public partial class TextureDiffer {
        private static GraphicsDevice gpu = GraphicsDevice.GetDefault();
        public static RectInt32 GetDirtyPixelRegion(CanvasBitmap beforeBitmap, CanvasBitmap afterBitmap) {
            int length = (int)(beforeBitmap.SizeInPixels.Width * beforeBitmap.SizeInPixels.Height * 4);

            // Copy pixel data from CanvasBitmaps into buffers
            // NOTE: this is unfortunately copying data out of GPU to CPU, only for us to copy back to GPU
            // It would be so much nicer if we just wrap a CanvasBitmap or CanvasRenderTarget and directly access the texture
            var beforeSpan = new ReadOnlySpan<byte>(beforeBitmap.GetPixelBytes());
            var afterSpan = new ReadOnlySpan<byte>(afterBitmap.GetPixelBytes());

//          vvvvvvvvvvvvvvvvvvvv the issue starts here for me

            // the following line throws a 'System.ComponentModel.Win32Exception' in ComputeSharp.Core.dll error
            using (var beforeFrameBuffer = gpu.LoadReadOnlyTexture2D<Rgba32, float4>(beforeSpan))
            using (var afterFrameBuffer = gpu.LoadReadOnlyTexture2D<Rgba32, float4>(afterSpan)) {
            // the above lines throw a 'System.ComponentModel.Win32Exception' in ComputeSharp.Core.dll error


                // Create an output buffer to store the min/max dirty rectangle
                using var resultBuffer = gpu.AllocateReadWriteBuffer<int>(4); // minX, minY, maxX, maxY
                resultBuffer[0] = int.MaxValue;
                resultBuffer[1] = int.MaxValue;

                // Launch the shader to compare the two frames
                gpu.For(
                    resultBuffer.Length,
                    new GetDirtyRects(length, beforeFrameBuffer, afterFrameBuffer, resultBuffer));

                // Read the result buffer (minX, minY, maxX, maxY)
                var resultsArray = resultBuffer;
                var mbr = new {
                    minX = resultsArray[0],
                    minY = resultsArray[1],
                    maxX = resultsArray[2],
                    maxY = resultsArray[3]
                };

                // Return the dirty region as a Rect
                return new RectInt32 {
                    X = mbr.minX,
                    Y = mbr.minY,
                    Width = mbr.maxX - mbr.minX,
                    Height = mbr.maxY - mbr.minY
                };
            }
        }

        // Compute shader to compare two frames and find the dirty region
        [ThreadGroupSize(16, 16, 1)]
        [GeneratedComputeShaderDescriptor]
        public readonly partial struct GetDirtyRects(
            int width,
            ReadOnlyTexture2D<Rgba32, float4> beforeBuffer,
            ReadOnlyTexture2D<Rgba32, float4> afterBuffer,
            ReadWriteBuffer<int> resultBuffer
        ) : IComputeShader {
            public void Execute() {
                int index = ThreadIds.X + (ThreadIds.Y * width);

                float4 before = beforeBuffer[ThreadIds.XY].RGBA;
                float4 after = afterBuffer[ThreadIds.XY].RGBA;

                // Compare the before and after pixels
                if (before.R != after.R || before.G != after.G || before.B != after.B || before.A != after.A) {
                    // Update min/max bounding rectangle using atomic operations
                    Hlsl.InterlockedMin(ref resultBuffer[0], ThreadIds.X); // Update minX
                    Hlsl.InterlockedMin(ref resultBuffer[1], ThreadIds.Y); // Update minY
                    Hlsl.InterlockedMax(ref resultBuffer[2], ThreadIds.X); // Update maxX
                    Hlsl.InterlockedMax(ref resultBuffer[3], ThreadIds.Y); // Update maxY
                }
            }
        }
    }
Dominant language
C#
Stars
3.2k
Forks
148
Avg merge
4h 32m
Merged PRs (30d)
3

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from Sergio0694/ComputeSharp

All issues in Sergio0694/ComputeSharp

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.