diff --git a/packages/webgpu/cpp/rnwgpu/api/GPUQueue.cpp b/packages/webgpu/cpp/rnwgpu/api/GPUQueue.cpp index b8fe87303..d3c0d65af 100644 --- a/packages/webgpu/cpp/rnwgpu/api/GPUQueue.cpp +++ b/packages/webgpu/cpp/rnwgpu/api/GPUQueue.cpp @@ -124,7 +124,7 @@ void GPUQueue::copyExternalImageToTexture( throw std::runtime_error("Invalid input for GPUQueue::writeTexture()"); } - if (source->flipY) { + if (source->flipY.value_or(false)) { // Calculate the row size and total size uint32_t rowSize = bytesPerPixel * source->source->getWidth(); uint32_t totalSize = source->source->getSize(); diff --git a/packages/webgpu/package.json b/packages/webgpu/package.json index dd7992f21..23246a323 100644 --- a/packages/webgpu/package.json +++ b/packages/webgpu/package.json @@ -1,6 +1,6 @@ { "name": "react-native-wgpu", - "version": "0.5.8", + "version": "0.5.9", "description": "React Native WebGPU", "main": "lib/commonjs/index", "module": "lib/module/index", diff --git a/packages/webgpu/src/__tests__/ExternalTexture.spec.ts b/packages/webgpu/src/__tests__/ExternalTexture.spec.ts index 9e293a0aa..47976ec4b 100644 --- a/packages/webgpu/src/__tests__/ExternalTexture.spec.ts +++ b/packages/webgpu/src/__tests__/ExternalTexture.spec.ts @@ -281,4 +281,128 @@ describe("External Textures", () => { const image = encodeImage(result); checkImage(image, "snapshots/f2.png"); }); + it("flipY: false should not flip (same as omitting flipY)", async () => { + const result = await client.eval( + ({ gpu, device, ctx, canvas, urls: { fTexture } }) => { + const module = device.createShaderModule({ + label: "our hardcoded textured quad shaders", + code: /* wgsl */ ` + struct OurVertexShaderOutput { + @builtin(position) position: vec4f, + @location(0) texcoord: vec2f, + }; + + @vertex fn vs( + @builtin(vertex_index) vertexIndex : u32 + ) -> OurVertexShaderOutput { + let pos = array( + // 1st triangle + vec2f( 0.0, 0.0), // center + vec2f( 1.0, 0.0), // right, center + vec2f( 0.0, 1.0), // center, top + + // 2st triangle + vec2f( 0.0, 1.0), // center, top + vec2f( 1.0, 0.0), // right, center + vec2f( 1.0, 1.0), // right, top + ); + + var vsOutput: OurVertexShaderOutput; + let xy = pos[vertexIndex]; + vsOutput.position = vec4f(xy, 0.0, 1.0); + vsOutput.texcoord = xy; + return vsOutput; + } + + @group(0) @binding(0) var ourSampler: sampler; + @group(0) @binding(1) var ourTexture: texture_2d; + + @fragment fn fs(fsInput: OurVertexShaderOutput) -> @location(0) vec4f { + return textureSample(ourTexture, ourSampler, fsInput.texcoord); + } + `, + }); + + const presentationFormat = gpu.getPreferredCanvasFormat(); + const pipeline = device.createRenderPipeline({ + label: "hardcoded textured quad pipeline", + layout: "auto", + vertex: { + module, + }, + fragment: { + module, + targets: [{ format: presentationFormat }], + }, + }); + + return fetch(fTexture).then((res) => { + return res.blob().then((blob) => { + return createImageBitmap(blob, { + colorSpaceConversion: "none", + }).then((source) => { + const texture = device.createTexture({ + label: fTexture, + format: "rgba8unorm", + size: [source.width, source.height], + usage: + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.RENDER_ATTACHMENT, + }); + // Explicitly pass flipY: false - should behave same as omitting it + device.queue.copyExternalImageToTexture( + { source, flipY: false }, + { texture }, + { width: source.width, height: source.height }, + ); + const sampler = device.createSampler({ + addressModeU: "repeat", + addressModeV: "repeat", + magFilter: "linear", + }); + + const bindGroup = device.createBindGroup({ + layout: pipeline.getBindGroupLayout(0), + entries: [ + { binding: 0, resource: sampler }, + { binding: 1, resource: texture.createView() }, + ], + }); + + const renderPassDescriptor: GPURenderPassDescriptor = { + label: "our basic canvas renderPass", + colorAttachments: [ + { + view: ctx.getCurrentTexture().createView(), + clearValue: [0.3, 0.3, 0.3, 1], + loadOp: "clear", + storeOp: "store", + }, + ], + }; + + const encoder = device.createCommandEncoder({ + label: "render quad encoder", + }); + const pass = encoder.beginRenderPass(renderPassDescriptor); + pass.setPipeline(pipeline); + pass.setBindGroup(0, bindGroup); + pass.draw(6); + pass.end(); + + const commandBuffer = encoder.finish(); + device.queue.submit([commandBuffer]); + return canvas.getImageData(); + }); + }); + }); + }, + {}, + ); + const image = encodeImage(result); + // flipY: false should produce the same result as omitting flipY (f2.png) + // This test catches the bug where std::optional was checked incorrectly + checkImage(image, "snapshots/f2.png"); + }); });