From 6d4fb9eb1070bb8fa9cd8a8b36cccc4f971e749c Mon Sep 17 00:00:00 2001 From: Osei Fortune Date: Fri, 13 Nov 2026 05:17:58 -0400 Subject: [PATCH 1/3] fix(webgpu): createView passes the requested aspect The aspect was parsed but never copied into the subresource range, so every view was created with aspect 'all'. Binding a depth-stencil texture through such a view fails validation, which invalidated the command encoder, so on Android nothing was ever submitted and the canvas stayed blank. --- packages/canvas/platforms/ios/src/cpp/webgpu/GPUTextureImpl.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/canvas/platforms/ios/src/cpp/webgpu/GPUTextureImpl.cpp b/packages/canvas/platforms/ios/src/cpp/webgpu/GPUTextureImpl.cpp index 8904ca08d..d6b873669 100644 --- a/packages/canvas/platforms/ios/src/cpp/webgpu/GPUTextureImpl.cpp +++ b/packages/canvas/platforms/ios/src/cpp/webgpu/GPUTextureImpl.cpp @@ -305,6 +305,7 @@ void GPUTextureImpl::CreateView(const v8::FunctionCallbackInfo &args) } else if (aspectStr == "depth-only") { aspect = CanvasTextureAspectDepthOnly; } + range.aspect = aspect; } From 0d7962efe8c82b35e895f7c2185531ffb0a14231 Mon Sep 17 00:00:00 2001 From: Osei Fortune Date: Fri, 13 Nov 2026 05:17:58 -0400 Subject: [PATCH 2/3] fix(webgpu): createRenderBundleEncoder reads sampleCount and read-only flags sampleCount was always 1 and depthReadOnly/stencilReadOnly always false, so a bundle recorded for an MSAA pass rejected its pipelines. --- .../platforms/ios/src/cpp/webgpu/GPUDeviceImpl.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/canvas/platforms/ios/src/cpp/webgpu/GPUDeviceImpl.cpp b/packages/canvas/platforms/ios/src/cpp/webgpu/GPUDeviceImpl.cpp index 87878b32c..d1b80e544 100644 --- a/packages/canvas/platforms/ios/src/cpp/webgpu/GPUDeviceImpl.cpp +++ b/packages/canvas/platforms/ios/src/cpp/webgpu/GPUDeviceImpl.cpp @@ -1194,10 +1194,23 @@ void GPUDeviceImpl::CreateRenderBundleEncoder(const v8::FunctionCallbackInfo sampleCountVal; + if (options->Get(context, ConvertToV8String(isolate, "sampleCount")).ToLocal(&sampleCountVal) && + sampleCountVal->IsUint32()) { + sampleCount = sampleCountVal->Uint32Value(context).FromJust(); + } bool depthReadOnly = false; + v8::Local depthReadOnlyVal; + if (options->Get(context, ConvertToV8String(isolate, "depthReadOnly")).ToLocal(&depthReadOnlyVal)) { + depthReadOnly = depthReadOnlyVal->BooleanValue(isolate); + } bool stencilReadOnly = false; + v8::Local stencilReadOnlyVal; + if (options->Get(context, ConvertToV8String(isolate, "stencilReadOnly")).ToLocal(&stencilReadOnlyVal)) { + stencilReadOnly = stencilReadOnlyVal->BooleanValue(isolate); + } CanvasCreateRenderBundleEncoderDescriptor descriptor{ *label, From ac65a7e719c6fd633fa42cad94380eea58ee456b Mon Sep 17 00:00:00 2001 From: Osei Fortune Date: Fri, 13 Nov 2026 05:17:58 -0400 Subject: [PATCH 3/3] fix(webgpu): Android bgra fallback for render bundle color formats createTexture and the pipelines swap bgra8unorm for rgba8unorm on Android, but render bundles kept bgra8unorm and no longer matched the pass they ran in. --- packages/canvas/WebGPU/GPUDevice.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/canvas/WebGPU/GPUDevice.ts b/packages/canvas/WebGPU/GPUDevice.ts index 061bcb3db..027c578f2 100644 --- a/packages/canvas/WebGPU/GPUDevice.ts +++ b/packages/canvas/WebGPU/GPUDevice.ts @@ -277,6 +277,11 @@ export class GPUDevice extends EventTarget { } createRenderBundleEncoder(descriptor: { colorFormats: (null | GPUTextureFormat)[]; depthReadOnly?: boolean; depthStencilFormat?: GPUTextureFormat; label?: string; sampleCount?: number; stencilReadOnly?: boolean }) { + if (__ANDROID__ && Array.isArray(descriptor?.colorFormats)) { + // Same bgra -> rgba fallback as createTexture and the pipelines, or a bundle can't match the pass it runs in. + const colorFormats = descriptor.colorFormats.map((format) => (format === 'bgra8unorm' ? 'rgba8unorm' : format === 'bgra8unorm-srgb' ? 'rgba8unorm-srgb' : format)); + descriptor = { ...descriptor, colorFormats }; + } return GPURenderBundleEncoder.fromNative(this.native.createRenderBundleEncoder(descriptor)); }