22// Licensed under the Six Labors Split License.
33
44using System . Diagnostics ;
5+ using System . Diagnostics . CodeAnalysis ;
56using System . Runtime . CompilerServices ;
67using System . Runtime . InteropServices ;
78using Silk . NET . WebGPU ;
@@ -26,6 +27,28 @@ public bool TryReadRegion<TPixel>(
2627 Rectangle sourceRectangle ,
2728 Buffer2DRegion < TPixel > destination )
2829 where TPixel : unmanaged, IPixel < TPixel >
30+ => this . TryReadRegion ( configuration , target , sourceRectangle , destination , out _ ) ;
31+
32+ /// <summary>
33+ /// Attempts to read source pixels from the target into a caller-provided buffer.
34+ /// </summary>
35+ /// <typeparam name="TPixel">The pixel format.</typeparam>
36+ /// <param name="configuration">The active processing configuration.</param>
37+ /// <param name="target">The target frame.</param>
38+ /// <param name="sourceRectangle">Source rectangle in target-local coordinates.</param>
39+ /// <param name="destination">
40+ /// The caller-allocated region to receive the pixel data.
41+ /// Must be at least as large as <paramref name="sourceRectangle"/> (clamped to target bounds).
42+ /// </param>
43+ /// <param name="error">Receives the failure reason when readback cannot complete.</param>
44+ /// <returns><see langword="true"/> when readback succeeds; otherwise <see langword="false"/>.</returns>
45+ public bool TryReadRegion < TPixel > (
46+ Configuration configuration ,
47+ ICanvasFrame < TPixel > target ,
48+ Rectangle sourceRectangle ,
49+ Buffer2DRegion < TPixel > destination ,
50+ [ NotNullWhen ( false ) ] out string ? error )
51+ where TPixel : unmanaged, IPixel < TPixel >
2952 {
3053 this . ThrowIfDisposed ( ) ;
3154 Guard . NotNull ( configuration , nameof ( configuration ) ) ;
@@ -39,12 +62,14 @@ public bool TryReadRegion<TPixel>(
3962 capability . Queue == 0 ||
4063 capability . TargetTexture == 0 )
4164 {
65+ error = "Readback is only available for native WebGPU targets with valid device, queue, and texture handles." ;
4266 return false ;
4367 }
4468
4569 if ( ! TryGetCompositeTextureFormat < TPixel > ( out WebGPUTextureFormatId expectedFormat , out FeatureName requiredFeature ) ||
4670 expectedFormat != capability . TargetFormat )
4771 {
72+ error = $ "Pixel type '{ typeof ( TPixel ) . Name } ' is not compatible with target format '{ capability . TargetFormat } '.";
4873 return false ;
4974 }
5075
@@ -60,6 +85,7 @@ public bool TryReadRegion<TPixel>(
6085
6186 if ( source . Width <= 0 || source . Height <= 0 )
6287 {
88+ error = "The requested source rectangle does not intersect the target bounds." ;
6389 return false ;
6490 }
6591
@@ -70,6 +96,7 @@ public bool TryReadRegion<TPixel>(
7096 if ( requiredFeature != FeatureName . Undefined
7197 && ! WebGPURuntime . GetOrCreateDeviceState ( api , device ) . HasFeature ( requiredFeature ) )
7298 {
99+ error = $ "The target device does not support required feature '{ requiredFeature } ' for pixel type '{ typeof ( TPixel ) . Name } '.";
73100 return false ;
74101 }
75102
@@ -97,13 +124,15 @@ public bool TryReadRegion<TPixel>(
97124 readbackBuffer = api . DeviceCreateBuffer ( device , in bufferDescriptor ) ;
98125 if ( readbackBuffer is null )
99126 {
127+ error = "WebGPU.DeviceCreateBuffer returned null for readback." ;
100128 return false ;
101129 }
102130
103131 CommandEncoderDescriptor encoderDescriptor = default ;
104132 commandEncoder = api . DeviceCreateCommandEncoder ( device , in encoderDescriptor ) ;
105133 if ( commandEncoder is null )
106134 {
135+ error = "WebGPU.DeviceCreateCommandEncoder returned null." ;
107136 return false ;
108137 }
109138
@@ -134,6 +163,7 @@ public bool TryReadRegion<TPixel>(
134163 commandBuffer = api . CommandEncoderFinish ( commandEncoder , in commandBufferDescriptor ) ;
135164 if ( commandBuffer is null )
136165 {
166+ error = "WebGPU.CommandEncoderFinish returned null." ;
137167 return false ;
138168 }
139169
@@ -157,13 +187,15 @@ void Callback(BufferMapAsyncStatus status, void* userData)
157187 api . BufferMapAsync ( readbackBuffer , MapMode . Read , 0 , ( nuint ) readbackByteCount , callback , null ) ;
158188 if ( ! WaitForMapSignal ( lease . WgpuExtension , device , mapReady ) || mapStatus != BufferMapAsyncStatus . Success )
159189 {
190+ error = $ "WebGPU readback map failed with status '{ mapStatus } '.";
160191 return false ;
161192 }
162193
163194 void * mapped = api . BufferGetConstMappedRange ( readbackBuffer , 0 , ( nuint ) readbackByteCount ) ;
164195 if ( mapped is null )
165196 {
166197 api . BufferUnmap ( readbackBuffer ) ;
198+ error = "WebGPU.BufferGetConstMappedRange returned null." ;
167199 return false ;
168200 }
169201
@@ -181,6 +213,7 @@ void Callback(BufferMapAsyncStatus status, void* userData)
181213 . CopyTo ( MemoryMarshal . AsBytes ( destination . DangerousGetRowSpan ( y ) ) ) ;
182214 }
183215
216+ error = null ;
184217 return true ;
185218 }
186219 finally
0 commit comments