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); } }