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 <enga@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Jiawei Shao <jiawei.shao@intel.com>
This commit is contained in:
Jiawei Shao 2022-06-27 04:28:32 +00:00 committed by Dawn LUCI CQ
parent ceb7236547
commit dadf11909a
2 changed files with 270 additions and 26 deletions

View File

@ -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<double>((static_cast<uint64_t>(1) << (bitsPerComponent - 1)) - 1);
minValue = -static_cast<double>(static_cast<uint64_t>(1) << (bitsPerComponent - 1));
break;
}
case wgpu::TextureComponentType::Uint: {
const uint32_t bitsPerComponent =
(aspectInfo.block.byteSize * 8 / format.componentCount);
maxValue = static_cast<double>((static_cast<uint64_t>(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,11 +889,13 @@ Ref<RenderPassEncoder> CommandEncoder::BeginRenderPass(const RenderPassDescripto
cmd->colorAttachments[index].loadOp = descriptor->colorAttachments[i].loadOp;
cmd->colorAttachments[index].storeOp = descriptor->colorAttachments[i].storeOp;
cmd->colorAttachments[index].clearColor =
HasDeprecatedColor(descriptor->colorAttachments[i])
Color color = HasDeprecatedColor(descriptor->colorAttachments[i])
? descriptor->colorAttachments[i].clearColor
: descriptor->colorAttachments[i].clearValue;
cmd->colorAttachments[index].clearColor =
ClampClearColorValueToLegalRange(color, view->GetFormat());
usageTracker.TextureViewUsedAs(view, wgpu::TextureUsage::RenderAttachment);
if (resolveTarget != nullptr) {

View File

@ -13,6 +13,8 @@
// limitations under the License.
#include <array>
#include <limits>
#include <tuple>
#include "dawn/tests/DawnTest.h"
@ -97,7 +99,7 @@ class RenderPassLoadOpTests : public DawnTest {
}
template <class T>
void TestIntegerClearColor(wgpu::TextureFormat format,
void TestClearColor(wgpu::TextureFormat format,
const wgpu::Color& clearColor,
const std::array<T, 4>& expectedPixelValue) {
constexpr wgpu::Extent3D kTextureSize = {1, 1, 1};
@ -131,8 +133,8 @@ class RenderPassLoadOpTests : public DawnTest {
wgpu::CommandBuffer commandBuffer = encoder.Finish();
queue.Submit(1, &commandBuffer);
EXPECT_BUFFER_U32_RANGE_EQ(reinterpret_cast<const uint32_t*>(expectedPixelValue.data()),
buffer, 0, bufferSize / sizeof(uint32_t));
EXPECT_BUFFER_U8_RANGE_EQ(reinterpret_cast<const uint8_t*>(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<uint8_t, 4> kExpectedPixelValue = {2, 3, 254, 255};
TestIntegerClearColor<uint8_t>(wgpu::TextureFormat::RGBA8Uint, kClearColor,
kExpectedPixelValue);
TestClearColor<uint8_t>(wgpu::TextureFormat::RGBA8Uint, kClearColor, kExpectedPixelValue);
}
// RGBA8Sint
{
constexpr wgpu::Color kClearColor = {2.f, -3.3f, 126.8f, -128.0f};
constexpr std::array<int8_t, 4> kExpectedPixelValue = {2, -3, 126, -128};
TestIntegerClearColor<int8_t>(wgpu::TextureFormat::RGBA8Sint, kClearColor,
kExpectedPixelValue);
TestClearColor<int8_t>(wgpu::TextureFormat::RGBA8Sint, kClearColor, kExpectedPixelValue);
}
// RGBA16Uint
{
constexpr wgpu::Color kClearColor = {2.f, 3.3f, 512.7f, 65535.f};
constexpr std::array<uint16_t, 4> kExpectedPixelValue = {2, 3, 512, 65535u};
TestIntegerClearColor<uint16_t>(wgpu::TextureFormat::RGBA16Uint, kClearColor,
kExpectedPixelValue);
TestClearColor<uint16_t>(wgpu::TextureFormat::RGBA16Uint, kClearColor, kExpectedPixelValue);
}
// RGBA16Sint
{
constexpr wgpu::Color kClearColor = {2.f, -3.3f, 32767.8f, -32768.0f};
constexpr std::array<int16_t, 4> kExpectedPixelValue = {2, -3, 32767, -32768};
TestIntegerClearColor<int16_t>(wgpu::TextureFormat::RGBA16Sint, kClearColor,
kExpectedPixelValue);
TestClearColor<int16_t>(wgpu::TextureFormat::RGBA16Sint, kClearColor, kExpectedPixelValue);
}
// RGBA32Uint
{
constexpr wgpu::Color kClearColor = {2.f, 3.3f, 65534.8f, 65537.f};
constexpr std::array<uint32_t, 4> kExpectedPixelValue = {2, 3, 65534, 65537};
TestIntegerClearColor<uint32_t>(wgpu::TextureFormat::RGBA32Uint, kClearColor,
kExpectedPixelValue);
TestClearColor<uint32_t>(wgpu::TextureFormat::RGBA32Uint, kClearColor, kExpectedPixelValue);
}
// RGBA32Sint
{
constexpr wgpu::Color kClearColor = {2.f, -3.3f, 65534.8f, -65537.f};
constexpr std::array<int32_t, 4> kExpectedPixelValue = {2, -3, 65534, -65537};
TestIntegerClearColor<int32_t>(wgpu::TextureFormat::RGBA32Sint, kClearColor,
kExpectedPixelValue);
TestClearColor<int32_t>(wgpu::TextureFormat::RGBA32Sint, kClearColor, kExpectedPixelValue);
}
}
@ -258,8 +254,7 @@ TEST_P(RenderPassLoadOpTests, LoadOpClearIntegerFormatsToLargeValues) {
kUint32MaxDouble};
constexpr std::array<uint32_t, 4> kExpectedPixelValue = {kUint32Max, kUint32Max, kUint32Max,
kUint32Max};
TestIntegerClearColor<uint32_t>(wgpu::TextureFormat::RGBA32Uint, kClearColor,
kExpectedPixelValue);
TestClearColor<uint32_t>(wgpu::TextureFormat::RGBA32Uint, kClearColor, kExpectedPixelValue);
}
constexpr double kSint32MaxDouble = 2147483647.0;
@ -274,8 +269,7 @@ TEST_P(RenderPassLoadOpTests, LoadOpClearIntegerFormatsToLargeValues) {
kSint32MaxDouble};
constexpr std::array<int32_t, 4> kExpectedPixelValue = {kSint32Max, kSint32Max, kSint32Max,
kSint32Max};
TestIntegerClearColor<int32_t>(wgpu::TextureFormat::RGBA32Sint, kClearColor,
kExpectedPixelValue);
TestClearColor<int32_t>(wgpu::TextureFormat::RGBA32Sint, kClearColor, kExpectedPixelValue);
}
// RGBA32Sint for SINT32 lower bound.
@ -284,7 +278,221 @@ TEST_P(RenderPassLoadOpTests, LoadOpClearIntegerFormatsToLargeValues) {
kSint32MinDouble};
constexpr std::array<int32_t, 4> kExpectedPixelValue = {kSint32Min, kSint32Min, kSint32Min,
kSint32Min};
TestIntegerClearColor<int32_t>(wgpu::TextureFormat::RGBA32Sint, kClearColor,
TestClearColor<int32_t>(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<uint8_t>::max();
using TestCase = std::tuple<wgpu::TextureFormat, wgpu::Color, std::array<uint8_t, 4>>;
constexpr std::array<TestCase, 7> 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<uint8_t>(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<int8_t>::max();
constexpr int16_t kSint8Min = std::numeric_limits<int8_t>::min();
using TestCase = std::tuple<wgpu::TextureFormat, wgpu::Color, std::array<int8_t, 4>>;
constexpr std::array<TestCase, 7> 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<int8_t>(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<uint16_t>::max();
using TestCase = std::tuple<wgpu::TextureFormat, wgpu::Color, std::array<uint16_t, 4>>;
constexpr std::array<TestCase, 7> 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<uint16_t>(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<int16_t>::max();
constexpr int32_t kSint16Min = std::numeric_limits<int16_t>::min();
using TestCase = std::tuple<wgpu::TextureFormat, wgpu::Color, std::array<int16_t, 4>>;
constexpr std::array<TestCase, 7> 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<int16_t>(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<uint32_t>::max();
using TestCase = std::tuple<wgpu::TextureFormat, wgpu::Color, std::array<uint32_t, 4>>;
constexpr std::array<TestCase, 7> 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<uint32_t>(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<int32_t>::max();
constexpr int64_t kSint32Min = std::numeric_limits<int32_t>::min();
using TestCase = std::tuple<wgpu::TextureFormat, wgpu::Color, std::array<int32_t, 4>>;
constexpr std::array<TestCase, 7> 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<int32_t>(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<uint8_t, 4> kExpectedPixelValue = {0, 0, 255u, 255u};
TestClearColor<uint8_t>(wgpu::TextureFormat::RGBA8Unorm, kClearColor, kExpectedPixelValue);
}
// RGB10A2Unorm - Test components RGB
{
constexpr wgpu::Color kClearColor = {-0.1f, 0, 1.1f, 1};
constexpr std::array<uint8_t, 4> kExpectedPixelValue = {0, 0, 0xF0u, 0xFFu};
TestClearColor<uint8_t>(wgpu::TextureFormat::RGB10A2Unorm, kClearColor,
kExpectedPixelValue);
}
// RGB10A2Unorm - Test component A < 0
{
constexpr wgpu::Color kClearColor = {0, 0, 0, -0.1f};
constexpr std::array<uint8_t, 4> kExpectedPixelValue = {0, 0, 0, 0};
TestClearColor<uint8_t>(wgpu::TextureFormat::RGB10A2Unorm, kClearColor,
kExpectedPixelValue);
}
// RGB10A2Unorm - Test component A > 1
{
constexpr wgpu::Color kClearColor = {0, 0, 0, 1.1f};
constexpr std::array<uint8_t, 4> kExpectedPixelValue = {0, 0, 0, 0xC0u};
TestClearColor<uint8_t>(wgpu::TextureFormat::RGB10A2Unorm, kClearColor,
kExpectedPixelValue);
}
}