From 863379b5b4a5a4067295940abc50b56fb010ba1a Mon Sep 17 00:00:00 2001 From: Run Yu Date: Fri, 23 Jan 2026 14:49:33 -0500 Subject: [PATCH 1/3] wgpu: fix the readPixels function so test_Scissor can draw the triangle --- filament/backend/src/webgpu/WebGPUDriver.cpp | 24 +++++++++++++------- filament/backend/test/test_Scissor.cpp | 17 ++++++++++---- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/filament/backend/src/webgpu/WebGPUDriver.cpp b/filament/backend/src/webgpu/WebGPUDriver.cpp index b62cd6e1f1f2..e9bb7f8b5f5d 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.cpp +++ b/filament/backend/src/webgpu/WebGPUDriver.cpp @@ -1472,6 +1472,7 @@ void WebGPUDriver::readPixels(Handle sourceRenderTargetHandle, c const auto srcTarget{ handleCast(sourceRenderTargetHandle) }; assert_invariant(srcTarget); + uint32_t srcMipLevel = 0; wgpu::Texture srcTexture{ nullptr }; if (srcTarget->isDefaultRenderTarget()) { assert_invariant(mSwapChain); @@ -1482,6 +1483,7 @@ void WebGPUDriver::readPixels(Handle sourceRenderTargetHandle, c // TODO we are currently assuming the first attachment is the desired texture. if (colorAttachmentInfos[0].handle) { auto texture = handleCast(colorAttachmentInfos[0].handle); + srcMipLevel = colorAttachmentInfos[0].level; if (texture) { srcTexture = texture->getTexture(); } @@ -1494,15 +1496,21 @@ void WebGPUDriver::readPixels(Handle sourceRenderTargetHandle, c return; } const uint32_t srcWidth {srcTexture.GetWidth()}; - const uint32_t srcHeight{srcTexture.GetHeight()}; + const uint32_t srcHeight {srcTexture.GetHeight()}; + const uint32_t srcMipLevelCount {srcTexture.GetMipLevelCount()}; + + const uint32_t mipLevelImageScale = 1 << srcMipLevel; + const uint32_t mipLeveledSrcWidth = srcWidth / mipLevelImageScale; + const uint32_t mipLeveledSrcHeight = srcHeight / mipLevelImageScale; + assert_invariant(mipLeveledSrcWidth > 0 && mipLeveledSrcHeight > 0); // Clamp read region to texture bounds - if (UTILS_UNLIKELY(x >= srcWidth || y >= srcHeight)) { + if (UTILS_UNLIKELY(x >= mipLeveledSrcWidth || y >= mipLeveledSrcHeight)) { scheduleDestroy(std::move(pixelBufferDescriptor)); return; } - auto actualWidth{ std::min(width, srcWidth - x) }; - auto actualHeight{ std::min(height, srcHeight - y)}; + auto actualWidth{ std::min(width, mipLeveledSrcWidth - x) }; + auto actualHeight{ std::min(height, mipLeveledSrcHeight - y)}; if (UTILS_UNLIKELY(actualWidth == 0 || actualHeight == 0)) { scheduleDestroy(std::move(pixelBufferDescriptor)); return; @@ -1526,6 +1534,7 @@ void WebGPUDriver::readPixels(Handle sourceRenderTargetHandle, c // If the source format is different from the destination (e.g. BGRA vs RGBA), // we need to perform a conversion using an intermediate blit. if (conversionNecessary(srcFormat, dstFormat, pixelBufferDescriptor.type)) { + // TODO: check if the blit process here is correct when mipmap level > 0 const wgpu::TextureDescriptor stagingDescriptor{ .label = "readpixels_staging_texture", .usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment, @@ -1536,7 +1545,7 @@ void WebGPUDriver::readPixels(Handle sourceRenderTargetHandle, c .depthOrArrayLayers = 1, }, .format = dstFormat, - .mipLevelCount = 1, + .mipLevelCount = srcMipLevelCount, .sampleCount = srcTexture.GetSampleCount(), }; stagingTexture = mDevice.CreateTexture(&stagingDescriptor); @@ -1586,12 +1595,11 @@ void WebGPUDriver::readPixels(Handle sourceRenderTargetHandle, c // WebGPU's texture coordinates for copies are top-left, but Filament's y-coordinate is // bottom-left. We must flip the y-coordinate relative to the texture we are reading from. - const uint32_t textureHeight{ textureToReadFrom.GetHeight() }; - const uint32_t flippedY{ textureHeight - readY - actualHeight }; + const uint32_t flippedY{ mipLeveledSrcHeight - readY - actualHeight }; const wgpu::TexelCopyTextureInfo source{ .texture = textureToReadFrom, // Read from the original or intermediate texture - .mipLevel = 0, + .mipLevel = srcMipLevel, .origin = {.x = readX, .y = flippedY, .z = 0,}, }; const wgpu::TexelCopyBufferInfo destination{ diff --git a/filament/backend/test/test_Scissor.cpp b/filament/backend/test/test_Scissor.cpp index 8b7d5bf7882d..a1cce7192d0e 100644 --- a/filament/backend/test/test_Scissor.cpp +++ b/filament/backend/test/test_Scissor.cpp @@ -32,7 +32,7 @@ using namespace filament; using namespace filament::backend; TEST_F(BackendTest, ScissorViewportRegion) { - SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731"); + // SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731"); auto& api = getDriverApi(); constexpr int kSrcTexWidth = 1024; @@ -84,7 +84,11 @@ TEST_F(BackendTest, ScissorViewportRegion) { kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1, TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS)); Handle depthTexture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1, - TextureFormat::DEPTH16, 1, 512, 512, 1, TextureUsage::DEPTH_ATTACHMENT)); + TextureFormat::DEPTH16, 1, 512, 512, 1, TextureUsage::SAMPLEABLE | TextureUsage::DEPTH_ATTACHMENT)); + // Handle srcTexture = addCleanup( + // api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 512, 512, 1, + // TextureUsage::SAMPLEABLE | + // TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS)); // Render into the bottom-left quarter of the texture. Viewport srcRect = { @@ -103,8 +107,11 @@ TEST_F(BackendTest, ScissorViewportRegion) { // We purposely set the render target width and height to smaller than the texture, to check // that this case is handled correctly. Handle rt = addCleanup(api.createRenderTarget( - TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH, kSrcRtHeight, kSrcRtHeight, 1, - 1, { srcTexture, kSrcLevel, 0 }, { depthTexture, 0, 0 }, {})); + TargetBufferFlags::COLOR, kSrcRtHeight, kSrcRtHeight, + 1, 1, { srcTexture, kSrcLevel, 0 }, {}, {})); + + // Handle rt = addCleanup(api.createRenderTarget(TargetBufferFlags::COLOR, 512, + // 512, 1, 1, { srcTexture, 0, 0 }, {depthTexture, 0, 0}, {})); TrianglePrimitive triangle(api); @@ -129,6 +136,8 @@ TEST_F(BackendTest, ScissorViewportRegion) { EXPECT_IMAGE(rt, ScreenshotParams(kSrcTexWidth >> 1, kSrcTexHeight >> 1, "scissor", 15842520)); + // EXPECT_IMAGE(rt, + // ScreenshotParams(1024, 1024, "scissor", 15842520)); api.commit(swapChain); api.endFrame(0); From cf694a3334f76fe0597f556f934f3c932c0028cf Mon Sep 17 00:00:00 2001 From: Run Yu Date: Fri, 23 Jan 2026 14:56:08 -0500 Subject: [PATCH 2/3] wgpu: add TextureUsage::SAMPLEABLE to the depth texture in test_Scissor, which is needed to draw the triangle if the test is enabled. --- filament/backend/test/test_Scissor.cpp | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/filament/backend/test/test_Scissor.cpp b/filament/backend/test/test_Scissor.cpp index a1cce7192d0e..ecccd73e7755 100644 --- a/filament/backend/test/test_Scissor.cpp +++ b/filament/backend/test/test_Scissor.cpp @@ -32,7 +32,7 @@ using namespace filament; using namespace filament::backend; TEST_F(BackendTest, ScissorViewportRegion) { - // SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731"); + SKIP_IF(Backend::WEBGPU, "test cases fail in WebGPU, see b/424157731"); auto& api = getDriverApi(); constexpr int kSrcTexWidth = 1024; @@ -83,12 +83,10 @@ TEST_F(BackendTest, ScissorViewportRegion) { Handle srcTexture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D, kNumLevels, kSrcTexFormat, 1, kSrcTexWidth, kSrcTexHeight, 1, TextureUsage::SAMPLEABLE | TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS)); - Handle depthTexture = addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1, - TextureFormat::DEPTH16, 1, 512, 512, 1, TextureUsage::SAMPLEABLE | TextureUsage::DEPTH_ATTACHMENT)); - // Handle srcTexture = addCleanup( - // api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::RGBA8, 1, 512, 512, 1, - // TextureUsage::SAMPLEABLE | - // TextureUsage::COLOR_ATTACHMENT TEXTURE_USAGE_READ_PIXELS)); + + Handle depthTexture = + addCleanup(api.createTexture(SamplerType::SAMPLER_2D, 1, TextureFormat::DEPTH16, 1, + 512, 512, 1, TextureUsage::DEPTH_ATTACHMENT | TextureUsage::SAMPLEABLE)); // Render into the bottom-left quarter of the texture. Viewport srcRect = { @@ -107,11 +105,8 @@ TEST_F(BackendTest, ScissorViewportRegion) { // We purposely set the render target width and height to smaller than the texture, to check // that this case is handled correctly. Handle rt = addCleanup(api.createRenderTarget( - TargetBufferFlags::COLOR, kSrcRtHeight, kSrcRtHeight, - 1, 1, { srcTexture, kSrcLevel, 0 }, {}, {})); - - // Handle rt = addCleanup(api.createRenderTarget(TargetBufferFlags::COLOR, 512, - // 512, 1, 1, { srcTexture, 0, 0 }, {depthTexture, 0, 0}, {})); + TargetBufferFlags::COLOR | TargetBufferFlags::DEPTH, kSrcRtHeight, kSrcRtHeight, 1, + 1, { srcTexture, kSrcLevel, 0 }, { depthTexture, 0, 0 }, {})); TrianglePrimitive triangle(api); @@ -136,8 +131,6 @@ TEST_F(BackendTest, ScissorViewportRegion) { EXPECT_IMAGE(rt, ScreenshotParams(kSrcTexWidth >> 1, kSrcTexHeight >> 1, "scissor", 15842520)); - // EXPECT_IMAGE(rt, - // ScreenshotParams(1024, 1024, "scissor", 15842520)); api.commit(swapChain); api.endFrame(0); From 0ad90d139dd4805a30397edec84f8a6a36cfe305 Mon Sep 17 00:00:00 2001 From: Run Yu Date: Wed, 28 Jan 2026 21:35:45 -0500 Subject: [PATCH 3/3] wgpu: code clean up based on feedback --- filament/backend/src/webgpu/WebGPUDriver.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/filament/backend/src/webgpu/WebGPUDriver.cpp b/filament/backend/src/webgpu/WebGPUDriver.cpp index e9bb7f8b5f5d..3e8391fd17ef 100644 --- a/filament/backend/src/webgpu/WebGPUDriver.cpp +++ b/filament/backend/src/webgpu/WebGPUDriver.cpp @@ -1499,10 +1499,9 @@ void WebGPUDriver::readPixels(Handle sourceRenderTargetHandle, c const uint32_t srcHeight {srcTexture.GetHeight()}; const uint32_t srcMipLevelCount {srcTexture.GetMipLevelCount()}; - const uint32_t mipLevelImageScale = 1 << srcMipLevel; - const uint32_t mipLeveledSrcWidth = srcWidth / mipLevelImageScale; - const uint32_t mipLeveledSrcHeight = srcHeight / mipLevelImageScale; - assert_invariant(mipLeveledSrcWidth > 0 && mipLeveledSrcHeight > 0); + constexpr uint32_t minValidTextureSize = 1; + const uint32_t mipLeveledSrcWidth = std::max(minValidTextureSize, srcWidth >> srcMipLevel); + const uint32_t mipLeveledSrcHeight = std::max(minValidTextureSize, srcHeight >> srcMipLevel); // Clamp read region to texture bounds if (UTILS_UNLIKELY(x >= mipLeveledSrcWidth || y >= mipLeveledSrcHeight)) {