Skip to content

Compute Shader to identify dirty regions of a CanvasBitmap #900

@kevinmershon

Description

@kevinmershon

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
                }
            }
        }
    }

Metadata

Metadata

Assignees

No one assigned

    Labels

    question ❓Further information is requesteduntriaged 🧰A new issue that needs initial triage

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions