Compute Shader to identify dirty regions of a CanvasBitmap
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
}
}
}
}
Try setting ComputeSharpEnableDebugOutput in your .csproj and include the info in the output (and the HRESULT) 🙂
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 onusing (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.
Oh, that error usually happens when running in Debug, when you don't have the DirectX Debug tools installed.
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)
@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.