From dadf11909a2efb8dea2d88261f6aa9715513d61c Mon Sep 17 00:00:00 2001 From: Jiawei Shao Date: Mon, 27 Jun 2022 04:28:32 +0000 Subject: [PATCH] Clamp clear color value to legal range for uint/sint formats This patch clamps GPURenderPassColorAttachment.clearValue into the legal range for uint/sint formats according to the latest WebGPU SPEC. This patch also adds tests to verify we can directly apply floating point clear values for normalized floating formats. We don't handle non-normalized floating formats as the exact choice of the converted value is implementation-defined. Bug: dawn:1449 Test: dawn_end2end_tests Change-Id: I864f3e9bafb9c7ecd0ed9cfa7f401174da000884 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/94501 Reviewed-by: Austin Eng Kokoro: Kokoro Commit-Queue: Jiawei Shao --- src/dawn/native/CommandEncoder.cpp | 42 ++- .../tests/end2end/RenderPassLoadOpTests.cpp | 254 ++++++++++++++++-- 2 files changed, 270 insertions(+), 26 deletions(-) diff --git a/src/dawn/native/CommandEncoder.cpp b/src/dawn/native/CommandEncoder.cpp index 03aafc55a8..b8d549a877 100644 --- a/src/dawn/native/CommandEncoder.cpp +++ b/src/dawn/native/CommandEncoder.cpp @@ -645,6 +645,40 @@ bool IsReadOnlyDepthStencilAttachment( return true; } +Color ClampClearColorValueToLegalRange(const Color& originalColor, const Format& format) { + const AspectInfo& aspectInfo = format.GetAspectInfo(Aspect::Color); + double minValue = 0; + double maxValue = 0; + switch (aspectInfo.baseType) { + case wgpu::TextureComponentType::Float: { + return originalColor; + } + case wgpu::TextureComponentType::Sint: { + const uint32_t bitsPerComponent = + (aspectInfo.block.byteSize * 8 / format.componentCount); + maxValue = + static_cast((static_cast(1) << (bitsPerComponent - 1)) - 1); + minValue = -static_cast(static_cast(1) << (bitsPerComponent - 1)); + break; + } + case wgpu::TextureComponentType::Uint: { + const uint32_t bitsPerComponent = + (aspectInfo.block.byteSize * 8 / format.componentCount); + maxValue = static_cast((static_cast(1) << bitsPerComponent) - 1); + break; + } + case wgpu::TextureComponentType::DepthComparison: + default: + UNREACHABLE(); + break; + } + + return {std::clamp(originalColor.r, minValue, maxValue), + std::clamp(originalColor.g, minValue, maxValue), + std::clamp(originalColor.b, minValue, maxValue), + std::clamp(originalColor.a, minValue, maxValue)}; +} + } // namespace MaybeError ValidateCommandEncoderDescriptor(const DeviceBase* device, @@ -855,10 +889,12 @@ Ref CommandEncoder::BeginRenderPass(const RenderPassDescripto cmd->colorAttachments[index].loadOp = descriptor->colorAttachments[i].loadOp; cmd->colorAttachments[index].storeOp = descriptor->colorAttachments[i].storeOp; + Color color = HasDeprecatedColor(descriptor->colorAttachments[i]) + ? descriptor->colorAttachments[i].clearColor + : descriptor->colorAttachments[i].clearValue; + cmd->colorAttachments[index].clearColor = - HasDeprecatedColor(descriptor->colorAttachments[i]) - ? descriptor->colorAttachments[i].clearColor - : descriptor->colorAttachments[i].clearValue; + ClampClearColorValueToLegalRange(color, view->GetFormat()); usageTracker.TextureViewUsedAs(view, wgpu::TextureUsage::RenderAttachment); diff --git a/src/dawn/tests/end2end/RenderPassLoadOpTests.cpp b/src/dawn/tests/end2end/RenderPassLoadOpTests.cpp index 681ec95819..cc989a7401 100644 --- a/src/dawn/tests/end2end/RenderPassLoadOpTests.cpp +++ b/src/dawn/tests/end2end/RenderPassLoadOpTests.cpp @@ -13,6 +13,8 @@ // limitations under the License. #include +#include +#include #include "dawn/tests/DawnTest.h" @@ -97,9 +99,9 @@ class RenderPassLoadOpTests : public DawnTest { } template - void TestIntegerClearColor(wgpu::TextureFormat format, - const wgpu::Color& clearColor, - const std::array& expectedPixelValue) { + void TestClearColor(wgpu::TextureFormat format, + const wgpu::Color& clearColor, + const std::array& expectedPixelValue) { constexpr wgpu::Extent3D kTextureSize = {1, 1, 1}; wgpu::TextureDescriptor textureDescriptor; @@ -131,8 +133,8 @@ class RenderPassLoadOpTests : public DawnTest { wgpu::CommandBuffer commandBuffer = encoder.Finish(); queue.Submit(1, &commandBuffer); - EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast(expectedPixelValue.data()), - buffer, 0, bufferSize / sizeof(uint32_t)); + EXPECT_BUFFER_U8_RANGE_EQ(reinterpret_cast(expectedPixelValue.data()), + buffer, 0, bufferSize / sizeof(uint8_t)); } wgpu::Texture renderTarget; @@ -192,48 +194,42 @@ TEST_P(RenderPassLoadOpTests, LoadOpClearOnIntegerFormats) { { constexpr wgpu::Color kClearColor = {2.f, 3.3f, 254.8f, 255.0f}; constexpr std::array kExpectedPixelValue = {2, 3, 254, 255}; - TestIntegerClearColor(wgpu::TextureFormat::RGBA8Uint, kClearColor, - kExpectedPixelValue); + TestClearColor(wgpu::TextureFormat::RGBA8Uint, kClearColor, kExpectedPixelValue); } // RGBA8Sint { constexpr wgpu::Color kClearColor = {2.f, -3.3f, 126.8f, -128.0f}; constexpr std::array kExpectedPixelValue = {2, -3, 126, -128}; - TestIntegerClearColor(wgpu::TextureFormat::RGBA8Sint, kClearColor, - kExpectedPixelValue); + TestClearColor(wgpu::TextureFormat::RGBA8Sint, kClearColor, kExpectedPixelValue); } // RGBA16Uint { constexpr wgpu::Color kClearColor = {2.f, 3.3f, 512.7f, 65535.f}; constexpr std::array kExpectedPixelValue = {2, 3, 512, 65535u}; - TestIntegerClearColor(wgpu::TextureFormat::RGBA16Uint, kClearColor, - kExpectedPixelValue); + TestClearColor(wgpu::TextureFormat::RGBA16Uint, kClearColor, kExpectedPixelValue); } // RGBA16Sint { constexpr wgpu::Color kClearColor = {2.f, -3.3f, 32767.8f, -32768.0f}; constexpr std::array kExpectedPixelValue = {2, -3, 32767, -32768}; - TestIntegerClearColor(wgpu::TextureFormat::RGBA16Sint, kClearColor, - kExpectedPixelValue); + TestClearColor(wgpu::TextureFormat::RGBA16Sint, kClearColor, kExpectedPixelValue); } // RGBA32Uint { constexpr wgpu::Color kClearColor = {2.f, 3.3f, 65534.8f, 65537.f}; constexpr std::array kExpectedPixelValue = {2, 3, 65534, 65537}; - TestIntegerClearColor(wgpu::TextureFormat::RGBA32Uint, kClearColor, - kExpectedPixelValue); + TestClearColor(wgpu::TextureFormat::RGBA32Uint, kClearColor, kExpectedPixelValue); } // RGBA32Sint { constexpr wgpu::Color kClearColor = {2.f, -3.3f, 65534.8f, -65537.f}; constexpr std::array kExpectedPixelValue = {2, -3, 65534, -65537}; - TestIntegerClearColor(wgpu::TextureFormat::RGBA32Sint, kClearColor, - kExpectedPixelValue); + TestClearColor(wgpu::TextureFormat::RGBA32Sint, kClearColor, kExpectedPixelValue); } } @@ -258,8 +254,7 @@ TEST_P(RenderPassLoadOpTests, LoadOpClearIntegerFormatsToLargeValues) { kUint32MaxDouble}; constexpr std::array kExpectedPixelValue = {kUint32Max, kUint32Max, kUint32Max, kUint32Max}; - TestIntegerClearColor(wgpu::TextureFormat::RGBA32Uint, kClearColor, - kExpectedPixelValue); + TestClearColor(wgpu::TextureFormat::RGBA32Uint, kClearColor, kExpectedPixelValue); } constexpr double kSint32MaxDouble = 2147483647.0; @@ -274,8 +269,7 @@ TEST_P(RenderPassLoadOpTests, LoadOpClearIntegerFormatsToLargeValues) { kSint32MaxDouble}; constexpr std::array kExpectedPixelValue = {kSint32Max, kSint32Max, kSint32Max, kSint32Max}; - TestIntegerClearColor(wgpu::TextureFormat::RGBA32Sint, kClearColor, - kExpectedPixelValue); + TestClearColor(wgpu::TextureFormat::RGBA32Sint, kClearColor, kExpectedPixelValue); } // RGBA32Sint for SINT32 lower bound. @@ -284,8 +278,222 @@ TEST_P(RenderPassLoadOpTests, LoadOpClearIntegerFormatsToLargeValues) { kSint32MinDouble}; constexpr std::array kExpectedPixelValue = {kSint32Min, kSint32Min, kSint32Min, kSint32Min}; - TestIntegerClearColor(wgpu::TextureFormat::RGBA32Sint, kClearColor, - kExpectedPixelValue); + TestClearColor(wgpu::TextureFormat::RGBA32Sint, kClearColor, kExpectedPixelValue); + } +} + +// Test clearing a color attachment on Uint8 formats (R8Uint, RG8Uint, RGBA8Uint) when the clear +// values are out of bound. +TEST_P(RenderPassLoadOpTests, LoadOpClearIntegerFormatsOutOfBound_Uint8) { + constexpr uint16_t kUint8Max = std::numeric_limits::max(); + + using TestCase = std::tuple>; + constexpr std::array kTestCases = {{ + {wgpu::TextureFormat::R8Uint, {-1, 0, 0, 0}, {0, 0, 0, 0}}, + {wgpu::TextureFormat::R8Uint, {0, 0, 0, 0}, {0, 0, 0, 0}}, + {wgpu::TextureFormat::R8Uint, {kUint8Max, 0, 0, 0}, {kUint8Max, 0, 0, 0}}, + {wgpu::TextureFormat::R8Uint, {kUint8Max + 1, 0, 0, 0}, {kUint8Max, 0, 0, 0}}, + {wgpu::TextureFormat::RG8Uint, {0, kUint8Max, 0, 0}, {0, kUint8Max, 0, 0}}, + {wgpu::TextureFormat::RG8Uint, {-1, kUint8Max + 1, 0, 0}, {0, kUint8Max, 0, 0}}, + {wgpu::TextureFormat::RGBA8Uint, + {-1, 0, kUint8Max, kUint8Max + 1}, + {0, 0, kUint8Max, kUint8Max}}, + }}; + + for (const TestCase& testCase : kTestCases) { + auto [format, clearColor, expectedPixelValue] = testCase; + TestClearColor(format, clearColor, expectedPixelValue); + } +} + +// Test clearing a color attachment on Sint8 formats (R8Sint, RG8Sint, RGBA8Sint) when the clear +// values are out of bound. +TEST_P(RenderPassLoadOpTests, LoadOpClearIntegerFormatsOutOfBound_Sint8) { + constexpr int16_t kSint8Max = std::numeric_limits::max(); + constexpr int16_t kSint8Min = std::numeric_limits::min(); + + using TestCase = std::tuple>; + constexpr std::array kTestCases = {{ + {wgpu::TextureFormat::R8Sint, {kSint8Min - 1, 0, 0, 0}, {kSint8Min, 0, 0, 0}}, + {wgpu::TextureFormat::R8Sint, {kSint8Min, 0, 0, 0}, {kSint8Min, 0, 0, 0}}, + {wgpu::TextureFormat::R8Sint, {kSint8Max, 0, 0, 0}, {kSint8Max, 0, 0, 0}}, + {wgpu::TextureFormat::R8Sint, {kSint8Max + 1, 0, 0, 0}, {kSint8Max, 0, 0, 0}}, + {wgpu::TextureFormat::RG8Sint, {kSint8Min, kSint8Max, 0, 0}, {kSint8Min, kSint8Max, 0, 0}}, + {wgpu::TextureFormat::RG8Sint, + {kSint8Min - 1, kSint8Max + 1, 0, 0}, + {kSint8Min, kSint8Max, 0, 0}}, + {wgpu::TextureFormat::RGBA8Sint, + {kSint8Min - 1, kSint8Min, kSint8Max, kSint8Max + 1}, + {kSint8Min, kSint8Min, kSint8Max, kSint8Max}}, + }}; + + for (const TestCase& testCase : kTestCases) { + auto [format, clearColor, expectedPixelValue] = testCase; + TestClearColor(format, clearColor, expectedPixelValue); + } +} + +// Test clearing a color attachment on Uint16 formats (R16Uint, RG16Uint, RGBA16Uint) when the clear +// values are out of bound. +TEST_P(RenderPassLoadOpTests, LoadOpClearIntegerFormatsOutOfBound_Uint16) { + constexpr uint32_t kUint16Max = std::numeric_limits::max(); + + using TestCase = std::tuple>; + constexpr std::array kTestCases = {{ + {wgpu::TextureFormat::R16Uint, {-1, 0, 0, 0}, {0, 0, 0, 0}}, + {wgpu::TextureFormat::R16Uint, {0, 0, 0, 0}, {0, 0, 0, 0}}, + {wgpu::TextureFormat::R16Uint, {kUint16Max, 0, 0, 0}, {kUint16Max, 0, 0, 0}}, + {wgpu::TextureFormat::R16Uint, {kUint16Max + 1, 0, 0, 0}, {kUint16Max, 0, 0, 0}}, + {wgpu::TextureFormat::RG16Uint, {0, kUint16Max, 0, 0}, {0, kUint16Max, 0, 0}}, + {wgpu::TextureFormat::RG16Uint, {-1, kUint16Max + 1, 0, 0}, {0, kUint16Max, 0, 0}}, + {wgpu::TextureFormat::RGBA16Uint, + {-1, 0, kUint16Max, kUint16Max + 1}, + {0, 0, kUint16Max, kUint16Max}}, + }}; + + for (const TestCase& testCase : kTestCases) { + auto [format, clearColor, expectedPixelValue] = testCase; + TestClearColor(format, clearColor, expectedPixelValue); + } +} + +// Test clearing a color attachment on Sint16 formats (R16Sint, RG16Sint, RGBA16Sint) when the clear +// values are out of bound. +TEST_P(RenderPassLoadOpTests, LoadOpClearIntegerFormatsOutOfBound_Sint16) { + constexpr int32_t kSint16Max = std::numeric_limits::max(); + constexpr int32_t kSint16Min = std::numeric_limits::min(); + + using TestCase = std::tuple>; + constexpr std::array kTestCases = {{ + {wgpu::TextureFormat::R16Sint, {kSint16Min - 1, 0, 0, 0}, {kSint16Min, 0, 0, 0}}, + {wgpu::TextureFormat::R16Sint, {kSint16Min, 0, 0, 0}, {kSint16Min, 0, 0, 0}}, + {wgpu::TextureFormat::R16Sint, {kSint16Max, 0, 0, 0}, {kSint16Max, 0, 0, 0}}, + {wgpu::TextureFormat::R16Sint, {kSint16Max + 1, 0, 0, 0}, {kSint16Max, 0, 0, 0}}, + {wgpu::TextureFormat::RG16Sint, + {kSint16Min, kSint16Max, 0, 0}, + {kSint16Min, kSint16Max, 0, 0}}, + {wgpu::TextureFormat::RG16Sint, + {kSint16Min - 1, kSint16Max + 1, 0, 0}, + {kSint16Min, kSint16Max, 0, 0}}, + {wgpu::TextureFormat::RGBA16Sint, + {kSint16Min - 1, kSint16Min, kSint16Max, kSint16Max + 1}, + {kSint16Min, kSint16Min, kSint16Max, kSint16Max}}, + }}; + + for (const TestCase& testCase : kTestCases) { + auto [format, clearColor, expectedPixelValue] = testCase; + TestClearColor(format, clearColor, expectedPixelValue); + } +} + +// Test clearing a color attachment on Uint32 formats (R32Uint, RG32Uint, RGBA32Uint) when the clear +// values are out of bound. +TEST_P(RenderPassLoadOpTests, LoadOpClearIntegerFormatsOutOfBound_Uint32) { + // TODO(http://crbug.com/dawn/537): Implemement a workaround to enable clearing integer formats + // to large values on D3D12. + DAWN_SUPPRESS_TEST_IF(IsD3D12()); + + // TODO(crbug.com/dawn/1109): Re-enable once fixed on Mac Mini 8,1s w/ 11.5. + DAWN_SUPPRESS_TEST_IF(IsMetal() && IsIntel() && IsMacOS(11, 5)); + + // TODO(crbug.com/dawn/1463): Re-enable, might be the same as above just on + // 12.4 instead of 11.5. + DAWN_SUPPRESS_TEST_IF(IsMetal() && IsIntel() && IsMacOS(12, 4)); + + constexpr uint64_t kUint32Max = std::numeric_limits::max(); + + using TestCase = std::tuple>; + constexpr std::array kTestCases = {{ + {wgpu::TextureFormat::R32Uint, {-1, 0, 0, 0}, {0, 0, 0, 0}}, + {wgpu::TextureFormat::R32Uint, {0, 0, 0, 0}, {0, 0, 0, 0}}, + {wgpu::TextureFormat::R32Uint, {kUint32Max, 0, 0, 0}, {kUint32Max, 0, 0, 0}}, + {wgpu::TextureFormat::R32Uint, {kUint32Max + 1, 0, 0, 0}, {kUint32Max, 0, 0, 0}}, + {wgpu::TextureFormat::RG32Uint, {0, kUint32Max, 0, 0}, {0, kUint32Max, 0, 0}}, + {wgpu::TextureFormat::RG32Uint, {-1, kUint32Max + 1, 0, 0}, {0, kUint32Max, 0, 0}}, + {wgpu::TextureFormat::RGBA32Uint, + {-1, 0, kUint32Max, kUint32Max + 1}, + {0, 0, kUint32Max, kUint32Max}}, + }}; + + for (const TestCase& testCase : kTestCases) { + auto [format, clearColor, expectedPixelValue] = testCase; + TestClearColor(format, clearColor, expectedPixelValue); + } +} + +// Test clearing a color attachment on Sint32 formats (R32Sint, RG32Sint, RGBA32Sint) when the clear +// values are out of bound. +TEST_P(RenderPassLoadOpTests, LoadOpClearIntegerFormatsOutOfBound_Sint32) { + // TODO(http://crbug.com/dawn/537): Implemement a workaround to enable clearing integer formats + // to large values on D3D12. + DAWN_SUPPRESS_TEST_IF(IsD3D12()); + + // TODO(crbug.com/dawn/1109): Re-enable once fixed on Mac Mini 8,1s w/ 11.5. + DAWN_SUPPRESS_TEST_IF(IsMetal() && IsIntel() && IsMacOS(11, 5)); + + // TODO(crbug.com/dawn/1463): Re-enable, might be the same as above just on + // 12.4 instead of 11.5. + DAWN_SUPPRESS_TEST_IF(IsMetal() && IsIntel() && IsMacOS(12, 4)); + + constexpr int64_t kSint32Max = std::numeric_limits::max(); + constexpr int64_t kSint32Min = std::numeric_limits::min(); + + using TestCase = std::tuple>; + constexpr std::array kTestCases = {{ + {wgpu::TextureFormat::R32Sint, {kSint32Min - 1, 0, 0, 0}, {kSint32Min, 0, 0, 0}}, + {wgpu::TextureFormat::R32Sint, {kSint32Min, 0, 0, 0}, {kSint32Min, 0, 0, 0}}, + {wgpu::TextureFormat::R32Sint, {kSint32Max, 0, 0, 0}, {kSint32Max, 0, 0, 0}}, + {wgpu::TextureFormat::R32Sint, {kSint32Max + 1, 0, 0, 0}, {kSint32Max, 0, 0, 0}}, + {wgpu::TextureFormat::RG32Sint, + {kSint32Min, kSint32Max, 0, 0}, + {kSint32Min, kSint32Max, 0, 0}}, + {wgpu::TextureFormat::RG32Sint, + {kSint32Min - 1, kSint32Max + 1, 0, 0}, + {kSint32Min, kSint32Max, 0, 0}}, + {wgpu::TextureFormat::RGBA32Sint, + {kSint32Min - 1, kSint32Min, kSint32Max, kSint32Max + 1}, + {kSint32Min, kSint32Min, kSint32Max, kSint32Max}}, + }}; + + for (const TestCase& testCase : kTestCases) { + auto [format, clearColor, expectedPixelValue] = testCase; + TestClearColor(format, clearColor, expectedPixelValue); + } +} + +// Test clearing a color attachment on normalized formats when the clear values are out of bound. +// Note that we don't test RGBA8Snorm because it doesn't support being used as render attachments in +// current WebGPU SPEC. +TEST_P(RenderPassLoadOpTests, LoadOpClearNormalizedFormatsOutOfBound) { + // RGBA8Unorm + { + constexpr wgpu::Color kClearColor = {-0.1f, 0, 1, 1.1f}; + constexpr std::array kExpectedPixelValue = {0, 0, 255u, 255u}; + TestClearColor(wgpu::TextureFormat::RGBA8Unorm, kClearColor, kExpectedPixelValue); + } + + // RGB10A2Unorm - Test components RGB + { + constexpr wgpu::Color kClearColor = {-0.1f, 0, 1.1f, 1}; + constexpr std::array kExpectedPixelValue = {0, 0, 0xF0u, 0xFFu}; + TestClearColor(wgpu::TextureFormat::RGB10A2Unorm, kClearColor, + kExpectedPixelValue); + } + + // RGB10A2Unorm - Test component A < 0 + { + constexpr wgpu::Color kClearColor = {0, 0, 0, -0.1f}; + constexpr std::array kExpectedPixelValue = {0, 0, 0, 0}; + TestClearColor(wgpu::TextureFormat::RGB10A2Unorm, kClearColor, + kExpectedPixelValue); + } + + // RGB10A2Unorm - Test component A > 1 + { + constexpr wgpu::Color kClearColor = {0, 0, 0, 1.1f}; + constexpr std::array kExpectedPixelValue = {0, 0, 0, 0xC0u}; + TestClearColor(wgpu::TextureFormat::RGB10A2Unorm, kClearColor, + kExpectedPixelValue); } }