ComputeSharp icon indicating copy to clipboard operation
ComputeSharp copied to clipboard

Compute Shader to identify dirty regions of a CanvasBitmap

Open kevinmershon opened this issue 1 year ago • 5 comments

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

kevinmershon avatar Feb 07 '25 04:02 kevinmershon

Try setting ComputeSharpEnableDebugOutput in your .csproj and include the info in the output (and the HRESULT) 🙂

Sergio0694 avatar Feb 07 '25 06:02 Sergio0694

Win32Exception: The application requested an operation that depends on an SDK component that is missing or mismatched.

   at System.ComponentModel.Win32ExceptionExtensions.Throw(Int32 hresult)
   at System.ComponentModel.Win32ExceptionExtensions.ThrowIfFailed(Win32Exception _, Int32 hresult)
   at ComputeSharp.Core.Extensions.HRESULTExtensions.<Assert>g__AssertWithDebugInfo|1_0(Int32 result)
   at ComputeSharp.Core.Extensions.HRESULTExtensions.Assert(Int32 result)
   at ComputeSharp.Core.Extensions.HRESULTExtensions.Assert(HRESULT result)
   at ComputeSharp.Graphics.Helpers.DeviceHelper.EnableDebugMode()
   at ComputeSharp.Graphics.Helpers.DeviceHelper.CreateDXGIFactory6(IDXGIFactory6** dxgiFactory6)
   at ComputeSharp.Graphics.Helpers.DeviceHelper.TryGetDefaultDevice(ID3D12Device** d3D12Device, IDXGIAdapter** dxgiAdapter, DXGI_ADAPTER_DESC1* dxgiDescription1)
   at ComputeSharp.Graphics.Helpers.DeviceHelper.GetOrCreateDefaultDevice()
   at ComputeSharp.Graphics.Helpers.DeviceHelper.<GetDefaultDeviceFromCacheOrCreateInstance>g__InitializeAndAssignDefaultDevice|10_0()
   at ComputeSharp.Graphics.Helpers.DeviceHelper.GetDefaultDeviceFromCacheOrCreateInstance()
   at ComputeSharp.GraphicsDevice.GetDefault()

Hresult is -2147467259

The actual line is just GraphicsDevice.getDefault() I guess.

My project is targeting Windows 11 build 26100 and later, but set to support Windows 10 build 19041. I tried setting supports to Win11 build 22621 and 26100 and the problem did not resolve. I only had the SDK for 22621 installed so I installed SDK for 26100 and 19041 and still had the same problem. I also tried the following lines and get the same exception.

Predicate<GraphicsDeviceInfo> predicate = (e) => {
    return true;
};
var devices = GraphicsDevice.QueryDevices(predicate).ToArray();

For what it's worth, I'm developing this C# code within a VMWare Fusion Win11 virtual machine running on an M4 Mac, so the graphics driver may be nonsensical. I will give this code a try on a couple real Windows 11 computers and edit this post with updates.

edit:

  • On a physical machine running Windows 10 with intel integrated graphics, I'm able to get past GraphicsDevices.getDefault() but then get the same error on using (var beforeFrameBuffer = gpu.LoadReadOnlyTexture2D<Rgba32, float4>(beforeSpan))
  • On a second physical machine running Windows 11 build 22631 with an RTX 3080 I get the same error at GraphicsDevices.getDefault() and the predicate query snippet above.

kevinmershon avatar Feb 07 '25 16:02 kevinmershon

Oh, that error usually happens when running in Debug, when you don't have the DirectX Debug tools installed.

Sergio0694 avatar Feb 07 '25 17:02 Sergio0694

I'm focusing on getting the Windows 10 machine to work because it gets furthest into the execution flow. This is what's logged for it:

[D3D12 message #0 for "[58413] Intel(R) UHD Graphics 620" (HW: True, UMA: True)]
[Category]: D3D12_MESSAGE_CATEGORY_INITIALIZATION
[Severity]: D3D12_MESSAGE_SEVERITY_MESSAGE
[ID]: D3D12_MESSAGE_ID_CREATEDEVICE_DEBUG_LAYER_STARTUP_OPTIONS
[Description]: "Device Debug Layer Startup Options: GPU-Based Validation is enabled (disabled by default). This results in new validation not possible during API calls on the CPU, by creating patched shaders that have validation added directly to the shader. However, it can slow things down a lot, especially for applications with numerous PSOs. Time to see the first render frame may take several minutes."
Exception thrown: 'System.ComponentModel.Win32Exception' in ComputeSharp.Core.dll
'FluidLink.exe' (CoreCLR: clrhost): Loaded 'C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\PrivateAssemblies\Runtime\Microsoft.VisualStudio.Debugger.Runtime.NetCoreApp.dll'. Skipped loading symbols. Module is optimized and the debugger option 'Just My Code' is enabled.

The related exception is The component cannot be found. with Hresult -2003292336.

at

   at System.ComponentModel.Win32ExceptionExtensions.Throw(Int32 hresult)
   at System.ComponentModel.Win32ExceptionExtensions.ThrowIfFailed(Win32Exception _, Int32 hresult)
   at ComputeSharp.Core.Extensions.HRESULTExtensions.<Assert>g__AssertWithDebugInfo|1_0(Int32 result)
   at ComputeSharp.Core.Extensions.HRESULTExtensions.Assert(Int32 result)
   at ComputeSharp.Core.Extensions.HRESULTExtensions.Assert(HRESULT result)
   at ComputeSharp.Graphics.Helpers.WICHelper.LoadTexture[T](GraphicsDevice device, ReadOnlySpan`1 span)
   at ComputeSharp.GraphicsDeviceExtensions.LoadReadOnlyTexture2D[T,TPixel](GraphicsDevice device, ReadOnlySpan`1 span)

kevinmershon avatar Feb 07 '25 17:02 kevinmershon

@Sergio0694 Following the guide here I installed DirectX SDK and ran the DirectX Control Panel and forced debug mode on. It's worth noting that Microsoft has put up a notice saying they're not longer updating the DirectX SDK AND the link they have on the footer of this page is dead.

edit: another thing to add is that you said on ticket #781 that this mode is required for when running an app in Debug mode. Is there any way to disable that via ComputeSharp API calls? I'd like to be able to debug this application without the DirectX debugging requirements sometimes.

Image

kevinmershon avatar Feb 07 '25 18:02 kevinmershon