blob: 8914401645f119d3a6dbd5581c6e84fb32691989 [file] [log] [blame] [edit]
// Copyright 2017 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include <array>
#include <cstring>
#include <limits>
#include <tuple>
#include <type_traits>
#include <vector>
#include "dawn/tests/DawnTest.h"
#include "dawn/utils/ComboRenderPipelineDescriptor.h"
#include "dawn/utils/WGPUHelpers.h"
namespace dawn {
namespace {
constexpr static unsigned int kRTSize = 16;
class DrawQuad {
public:
DrawQuad() {}
DrawQuad(wgpu::Device device, const char* vsSource, const char* fsSource) : device(device) {
vsModule = utils::CreateShaderModule(device, vsSource);
fsModule = utils::CreateShaderModule(device, fsSource);
pipelineLayout = utils::MakeBasicPipelineLayout(device, nullptr);
}
void Draw(wgpu::RenderPassEncoder* pass) {
utils::ComboRenderPipelineDescriptor descriptor;
descriptor.layout = pipelineLayout;
descriptor.vertex.module = vsModule;
descriptor.cFragment.module = fsModule;
auto renderPipeline = device.CreateRenderPipeline(&descriptor);
pass->SetPipeline(renderPipeline);
pass->Draw(6, 1, 0, 0);
}
private:
wgpu::Device device;
wgpu::ShaderModule vsModule = {};
wgpu::ShaderModule fsModule = {};
wgpu::PipelineLayout pipelineLayout = {};
};
class RenderPassLoadOpTests : public DawnTest {
protected:
void SetUp() override {
DawnTest::SetUp();
wgpu::TextureDescriptor descriptor;
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.size.width = kRTSize;
descriptor.size.height = kRTSize;
descriptor.size.depthOrArrayLayers = 1;
descriptor.sampleCount = 1;
descriptor.format = wgpu::TextureFormat::RGBA8Unorm;
descriptor.mipLevelCount = 1;
descriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
renderTarget = device.CreateTexture(&descriptor);
renderTargetView = renderTarget.CreateView();
std::fill(expectZero.begin(), expectZero.end(), utils::RGBA8::kZero);
std::fill(expectGreen.begin(), expectGreen.end(), utils::RGBA8::kGreen);
std::fill(expectBlue.begin(), expectBlue.end(), utils::RGBA8::kBlue);
// draws a blue quad on the right half of the screen
const char* vsSource = R"(
@vertex
fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f {
var pos = array(
vec2f( 0.0, -1.0),
vec2f( 1.0, -1.0),
vec2f( 0.0, 1.0),
vec2f( 0.0, 1.0),
vec2f( 1.0, -1.0),
vec2f( 1.0, 1.0));
return vec4f(pos[VertexIndex], 0.0, 1.0);
})";
const char* fsSource = R"(
@fragment fn main() -> @location(0) vec4f {
return vec4f(0.0, 0.0, 1.0, 1.0);
})";
blueQuad = DrawQuad(device, vsSource, fsSource);
}
template <class T>
void TestClearColor(wgpu::TextureFormat format,
const wgpu::Color& clearColor,
const std::array<T, 4>& expectedPixelValue) {
constexpr wgpu::Extent3D kTextureSize = {1, 1, 1};
wgpu::TextureDescriptor textureDescriptor;
textureDescriptor.dimension = wgpu::TextureDimension::e2D;
textureDescriptor.size = kTextureSize;
textureDescriptor.usage =
wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
textureDescriptor.format = format;
wgpu::Texture texture = device.CreateTexture(&textureDescriptor);
utils::ComboRenderPassDescriptor renderPassDescriptor({texture.CreateView()});
renderPassDescriptor.cColorAttachments[0].clearValue = clearColor;
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
wgpu::RenderPassEncoder renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
renderPass.End();
const uint64_t bufferSize = sizeof(T) * expectedPixelValue.size();
wgpu::BufferDescriptor bufferDescriptor;
bufferDescriptor.size = bufferSize;
bufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
wgpu::Buffer buffer = device.CreateBuffer(&bufferDescriptor);
wgpu::ImageCopyTexture imageCopyTexture =
utils::CreateImageCopyTexture(texture, 0, {0, 0, 0});
wgpu::ImageCopyBuffer imageCopyBuffer =
utils::CreateImageCopyBuffer(buffer, 0, kTextureBytesPerRowAlignment);
encoder.CopyTextureToBuffer(&imageCopyTexture, &imageCopyBuffer, &kTextureSize);
wgpu::CommandBuffer commandBuffer = encoder.Finish();
queue.Submit(1, &commandBuffer);
EXPECT_BUFFER_U8_RANGE_EQ(reinterpret_cast<const uint8_t*>(expectedPixelValue.data()),
buffer, 0, bufferSize / sizeof(uint8_t));
}
wgpu::Texture renderTarget;
wgpu::TextureView renderTargetView;
std::array<utils::RGBA8, kRTSize * kRTSize> expectZero;
std::array<utils::RGBA8, kRTSize * kRTSize> expectGreen;
std::array<utils::RGBA8, kRTSize * kRTSize> expectBlue;
DrawQuad blueQuad = {};
};
// Tests clearing, loading, and drawing into color attachments
TEST_P(RenderPassLoadOpTests, ColorClearThenLoadAndDraw) {
// Part 1: clear once, check to make sure it's cleared
utils::ComboRenderPassDescriptor renderPassClearZero({renderTargetView});
auto commandsClearZeroEncoder = device.CreateCommandEncoder();
auto clearZeroPass = commandsClearZeroEncoder.BeginRenderPass(&renderPassClearZero);
clearZeroPass.End();
auto commandsClearZero = commandsClearZeroEncoder.Finish();
utils::ComboRenderPassDescriptor renderPassClearGreen({renderTargetView});
renderPassClearGreen.cColorAttachments[0].clearValue = {0.0f, 1.0f, 0.0f, 1.0f};
auto commandsClearGreenEncoder = device.CreateCommandEncoder();
auto clearGreenPass = commandsClearGreenEncoder.BeginRenderPass(&renderPassClearGreen);
clearGreenPass.End();
auto commandsClearGreen = commandsClearGreenEncoder.Finish();
queue.Submit(1, &commandsClearZero);
EXPECT_TEXTURE_EQ(expectZero.data(), renderTarget, {0, 0}, {kRTSize, kRTSize});
queue.Submit(1, &commandsClearGreen);
EXPECT_TEXTURE_EQ(expectGreen.data(), renderTarget, {0, 0}, {kRTSize, kRTSize});
// Part 2: draw a blue quad into the right half of the render target, and check result
utils::ComboRenderPassDescriptor renderPassLoad({renderTargetView});
renderPassLoad.cColorAttachments[0].loadOp = wgpu::LoadOp::Load;
wgpu::CommandBuffer commandsLoad;
{
auto encoder = device.CreateCommandEncoder();
auto pass = encoder.BeginRenderPass(&renderPassLoad);
blueQuad.Draw(&pass);
pass.End();
commandsLoad = encoder.Finish();
}
queue.Submit(1, &commandsLoad);
// Left half should still be green
EXPECT_TEXTURE_EQ(expectGreen.data(), renderTarget, {0, 0}, {kRTSize / 2, kRTSize});
// Right half should now be blue
EXPECT_TEXTURE_EQ(expectBlue.data(), renderTarget, {kRTSize / 2, 0}, {kRTSize / 2, kRTSize});
}
// Test clearing a color attachment with signed and unsigned integer formats.
TEST_P(RenderPassLoadOpTests, LoadOpClearOnIntegerFormats) {
// RGBA8Uint
{
constexpr wgpu::Color kClearColor = {2.f, 3.3f, 254.8f, 255.0f};
constexpr std::array<uint8_t, 4> kExpectedPixelValue = {2, 3, 254, 255};
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};
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};
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};
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};
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};
TestClearColor<int32_t>(wgpu::TextureFormat::RGBA32Sint, kClearColor, kExpectedPixelValue);
}
}
// This test verifies that input double values are being rendered correctly when clearing.
TEST_P(RenderPassLoadOpTests, LoadOpClearIntegerFormatsToLargeValues) {
constexpr double kUint32MaxDouble = 4294967295.0;
constexpr uint32_t kUint32Max = static_cast<uint32_t>(kUint32MaxDouble);
// RGBA32Uint for UINT32_MAX
{
constexpr wgpu::Color kClearColor = {kUint32MaxDouble, kUint32MaxDouble, kUint32MaxDouble,
kUint32MaxDouble};
constexpr std::array<uint32_t, 4> kExpectedPixelValue = {kUint32Max, kUint32Max, kUint32Max,
kUint32Max};
TestClearColor<uint32_t>(wgpu::TextureFormat::RGBA32Uint, kClearColor, kExpectedPixelValue);
}
constexpr double kSint32MaxDouble = 2147483647.0;
constexpr int32_t kSint32Max = static_cast<int32_t>(kSint32MaxDouble);
constexpr double kSint32MinDouble = -2147483648.0;
constexpr int32_t kSint32Min = static_cast<int32_t>(kSint32MinDouble);
// RGBA32Sint for SINT32 upper bound.
{
constexpr wgpu::Color kClearColor = {kSint32MaxDouble, kSint32MaxDouble, kSint32MaxDouble,
kSint32MaxDouble};
constexpr std::array<int32_t, 4> kExpectedPixelValue = {kSint32Max, kSint32Max, kSint32Max,
kSint32Max};
TestClearColor<int32_t>(wgpu::TextureFormat::RGBA32Sint, kClearColor, kExpectedPixelValue);
}
// RGBA32Sint for SINT32 lower bound.
{
constexpr wgpu::Color kClearColor = {kSint32MinDouble, kSint32MinDouble, kSint32MinDouble,
kSint32MinDouble};
constexpr std::array<int32_t, 4> kExpectedPixelValue = {kSint32Min, kSint32Min, kSint32Min,
kSint32Min};
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) {
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) {
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);
}
}
// Test clearing multiple color attachments with different big signed and unsigned integers can
// still work correctly.
TEST_P(RenderPassLoadOpTests, LoadOpClearWithBig32BitIntegralValuesOnMultipleColorAttachments) {
constexpr int32_t kMaxInt32RepresentableInFloat = 1 << std::numeric_limits<float>::digits;
constexpr int32_t kMinInt32RepresentableInFloat = -kMaxInt32RepresentableInFloat;
constexpr uint32_t kMaxUInt32RepresentableInFloat = 1 << std::numeric_limits<float>::digits;
std::array<float, 4> testColorForRGBA32Float = {
kMaxUInt32RepresentableInFloat, kMaxUInt32RepresentableInFloat - 1,
kMaxUInt32RepresentableInFloat - 2, kMaxUInt32RepresentableInFloat - 3};
std::array<uint32_t, 4> expectedDataForRGBA32Float;
for (uint32_t i = 0; i < expectedDataForRGBA32Float.size(); ++i) {
expectedDataForRGBA32Float[i] = *(reinterpret_cast<uint32_t*>(&testColorForRGBA32Float[i]));
}
struct AttachmentCase {
static AttachmentCase Int(wgpu::TextureFormat format,
wgpu::Color clearValue,
const std::array<int32_t, 4> expData) {
AttachmentCase attachmentCase;
static_assert(sizeof(int32_t) * expData.size() == sizeof(attachmentCase.mExpData));
attachmentCase.mFormat = format;
attachmentCase.mClearValue = clearValue;
memcpy(attachmentCase.mExpData, reinterpret_cast<const uint8_t*>(expData.data()),
sizeof(attachmentCase.mExpData));
return attachmentCase;
}
static AttachmentCase Uint(wgpu::TextureFormat format,
wgpu::Color clearValue,
const std::array<uint32_t, 4> expData) {
AttachmentCase attachmentCase;
static_assert(sizeof(uint32_t) * expData.size() == sizeof(attachmentCase.mExpData));
attachmentCase.mFormat = format;
attachmentCase.mClearValue = clearValue;
memcpy(attachmentCase.mExpData, reinterpret_cast<const uint8_t*>(expData.data()),
sizeof(attachmentCase.mExpData));
return attachmentCase;
}
wgpu::TextureFormat mFormat;
wgpu::Color mClearValue;
uint8_t mExpData[16];
};
using TestCase = std::vector<AttachmentCase>;
// Test cases are split so that the attachments in each case do not exceed the default
// maxColorAttachmentBytesPerSample.
static std::vector<TestCase> kTestCases = {
// 6 attachment case (Signed 1 and 2 components).
{AttachmentCase::Int(wgpu::TextureFormat::R32Sint, {kMaxInt32RepresentableInFloat, 0, 0, 0},
{kMaxInt32RepresentableInFloat, 0, 0, 0}),
AttachmentCase::Int(wgpu::TextureFormat::R32Sint,
{kMaxInt32RepresentableInFloat + 1, 0, 0, 0},
{kMaxInt32RepresentableInFloat + 1, 0, 0, 0}),
AttachmentCase::Int(wgpu::TextureFormat::R32Sint, {kMinInt32RepresentableInFloat, 0, 0, 0},
{kMinInt32RepresentableInFloat, 0, 0, 0}),
AttachmentCase::Int(wgpu::TextureFormat::R32Sint,
{kMinInt32RepresentableInFloat - 1, 0, 0, 0},
{kMinInt32RepresentableInFloat - 1, 0, 0, 0}),
AttachmentCase::Int(
wgpu::TextureFormat::RG32Sint,
{kMaxInt32RepresentableInFloat, kMaxInt32RepresentableInFloat + 1, 0, 0},
{kMaxInt32RepresentableInFloat, kMaxInt32RepresentableInFloat + 1, 0, 0}),
AttachmentCase::Int(
wgpu::TextureFormat::RG32Sint,
{kMinInt32RepresentableInFloat, kMinInt32RepresentableInFloat - 1, 0, 0},
{kMinInt32RepresentableInFloat, kMinInt32RepresentableInFloat - 1, 0, 0})},
// 4 attachment case (Signed 1 and 2 components).
{AttachmentCase::Int(wgpu::TextureFormat::R32Sint, {kMaxInt32RepresentableInFloat, 0, 0, 0},
{kMaxInt32RepresentableInFloat, 0, 0, 0}),
AttachmentCase::Int(wgpu::TextureFormat::R32Sint,
{kMaxInt32RepresentableInFloat + 1, 0, 0, 0},
{kMaxInt32RepresentableInFloat + 1, 0, 0, 0}),
AttachmentCase::Int(
wgpu::TextureFormat::RG32Sint,
{kMaxInt32RepresentableInFloat, kMaxInt32RepresentableInFloat + 1, 0, 0},
{kMaxInt32RepresentableInFloat, kMaxInt32RepresentableInFloat + 1, 0, 0}),
AttachmentCase::Int(
wgpu::TextureFormat::RG32Sint,
{kMinInt32RepresentableInFloat, kMinInt32RepresentableInFloat - 1, 0, 0},
{kMinInt32RepresentableInFloat, kMinInt32RepresentableInFloat - 1, 0, 0})},
// Signed 4 components.
{AttachmentCase::Int(
wgpu::TextureFormat::RGBA32Sint,
{kMaxInt32RepresentableInFloat, kMinInt32RepresentableInFloat,
kMaxInt32RepresentableInFloat + 1, kMinInt32RepresentableInFloat - 1},
{kMaxInt32RepresentableInFloat, kMinInt32RepresentableInFloat,
kMaxInt32RepresentableInFloat + 1, kMinInt32RepresentableInFloat - 1}),
AttachmentCase::Int(
wgpu::TextureFormat::RGBA32Sint,
{kMaxInt32RepresentableInFloat, kMinInt32RepresentableInFloat,
kMaxInt32RepresentableInFloat - 1, kMinInt32RepresentableInFloat + 1},
{kMaxInt32RepresentableInFloat, kMinInt32RepresentableInFloat,
kMaxInt32RepresentableInFloat - 1, kMinInt32RepresentableInFloat + 1})},
// Unsigned 1 components.
{AttachmentCase::Uint(wgpu::TextureFormat::R32Uint,
{kMaxUInt32RepresentableInFloat, 0, 0, 0},
{kMaxUInt32RepresentableInFloat, 0, 0, 0}),
AttachmentCase::Uint(wgpu::TextureFormat::R32Uint,
{kMaxUInt32RepresentableInFloat + 1, 0, 0, 0},
{kMaxUInt32RepresentableInFloat + 1, 0, 0, 0})},
// Unsigned 2 components.
{AttachmentCase::Uint(
wgpu::TextureFormat::RG32Uint,
{kMaxUInt32RepresentableInFloat, kMaxUInt32RepresentableInFloat, 0, 0},
{kMaxUInt32RepresentableInFloat, kMaxUInt32RepresentableInFloat, 0, 0}),
AttachmentCase::Uint(
wgpu::TextureFormat::RG32Uint,
{kMaxUInt32RepresentableInFloat, kMaxUInt32RepresentableInFloat + 1, 0, 0},
{kMaxUInt32RepresentableInFloat, kMaxUInt32RepresentableInFloat + 1, 0, 0})},
// Unsigned 4 component expectations (with use of signed inputs).
{AttachmentCase::Uint(
wgpu::TextureFormat::RGBA32Uint,
{kMaxUInt32RepresentableInFloat, kMaxUInt32RepresentableInFloat + 1,
kMaxUInt32RepresentableInFloat - 1, kMaxUInt32RepresentableInFloat - 2},
{kMaxUInt32RepresentableInFloat, kMaxUInt32RepresentableInFloat + 1,
kMaxUInt32RepresentableInFloat - 1, kMaxUInt32RepresentableInFloat - 2}),
AttachmentCase::Uint(
wgpu::TextureFormat::RGBA32Sint,
{kMaxUInt32RepresentableInFloat, kMaxUInt32RepresentableInFloat - 1,
kMaxUInt32RepresentableInFloat - 2, kMaxUInt32RepresentableInFloat - 3},
{static_cast<int32_t>(kMaxUInt32RepresentableInFloat),
static_cast<int32_t>(kMaxUInt32RepresentableInFloat - 1),
static_cast<int32_t>(kMaxUInt32RepresentableInFloat - 2),
static_cast<int32_t>(kMaxUInt32RepresentableInFloat - 3)})},
// Unsigned 4 component expectations from float.
{AttachmentCase::Uint(
wgpu::TextureFormat::RGBA32Float,
{kMaxUInt32RepresentableInFloat, kMaxUInt32RepresentableInFloat - 1,
kMaxUInt32RepresentableInFloat - 2, kMaxUInt32RepresentableInFloat - 3},
expectedDataForRGBA32Float)}};
for (const TestCase& testCase : kTestCases) {
if (testCase.size() > GetSupportedLimits().limits.maxColorAttachments) {
continue;
}
std::vector<wgpu::Texture> textures;
std::vector<wgpu::RenderPassColorAttachment> colorAttachmentsInfo;
std::vector<wgpu::Buffer> outputBuffers;
// Initialize the default values for the textures.
wgpu::TextureDescriptor textureDescriptor = {};
textureDescriptor.size = {1, 1, 1};
textureDescriptor.usage =
wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment;
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
for (const AttachmentCase& attachmentCase : testCase) {
textureDescriptor.format = attachmentCase.mFormat;
textures.push_back(device.CreateTexture(&textureDescriptor));
wgpu::RenderPassColorAttachment colorAttachmentInfo = {};
colorAttachmentInfo.view = textures.back().CreateView();
colorAttachmentInfo.loadOp = wgpu::LoadOp::Clear;
colorAttachmentInfo.storeOp = wgpu::StoreOp::Store;
colorAttachmentInfo.clearValue = attachmentCase.mClearValue;
colorAttachmentsInfo.push_back(colorAttachmentInfo);
// Create the output buffer to compare values against.
wgpu::BufferDescriptor bufferDescriptor = {};
bufferDescriptor.size = sizeof(attachmentCase.mExpData);
bufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
outputBuffers.push_back(device.CreateBuffer(&bufferDescriptor));
}
wgpu::RenderPassDescriptor renderPassDescriptor = {};
renderPassDescriptor.colorAttachmentCount = colorAttachmentsInfo.size();
renderPassDescriptor.colorAttachments = colorAttachmentsInfo.data();
wgpu::RenderPassEncoder renderPass;
renderPass = encoder.BeginRenderPass(&renderPassDescriptor);
renderPass.End();
for (uint32_t i = 0; i < testCase.size(); ++i) {
wgpu::ImageCopyTexture imageCopyTexture =
utils::CreateImageCopyTexture(textures[i], 0, {0, 0, 0});
wgpu::ImageCopyBuffer imageCopyBuffer =
utils::CreateImageCopyBuffer(outputBuffers[i], 0, kTextureBytesPerRowAlignment);
encoder.CopyTextureToBuffer(&imageCopyTexture, &imageCopyBuffer,
&textureDescriptor.size);
}
wgpu::CommandBuffer commandBuffer = encoder.Finish();
queue.Submit(1, &commandBuffer);
for (uint32_t i = 0; i < testCase.size(); ++i) {
EXPECT_BUFFER_U8_RANGE_EQ(testCase.at(i).mExpData, outputBuffers[i], 0,
sizeof(testCase.at(i).mExpData));
}
}
}
// Test using LoadOp::Clear with different big unsigned integers as clearValues and LoadOp::Load on
// the other color attachments in one render pass encoder works correctly.
TEST_P(RenderPassLoadOpTests, MixedUseOfLoadOpLoadAndLoadOpClearWithBigIntegerValues) {
constexpr int32_t kMaxUInt32RepresentableInFloat = 1 << std::numeric_limits<float>::digits;
wgpu::TextureDescriptor textureDescriptor = {};
textureDescriptor.size = {1, 1, 1};
textureDescriptor.usage = wgpu::TextureUsage::CopySrc | wgpu::TextureUsage::RenderAttachment;
textureDescriptor.format = wgpu::TextureFormat::R32Uint;
wgpu::Texture textureForLoad = device.CreateTexture(&textureDescriptor);
wgpu::Texture textureForClear = device.CreateTexture(&textureDescriptor);
constexpr uint32_t kExpectedLoadValue = 2u;
// Initialize textureForLoad with pixel value 2u.
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::ComboRenderPassDescriptor renderPassForInit({textureForLoad.CreateView()});
renderPassForInit.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
renderPassForInit.cColorAttachments[0].clearValue = {kExpectedLoadValue, 0, 0, 0};
wgpu::RenderPassEncoder renderPassEncoder = encoder.BeginRenderPass(&renderPassForInit);
renderPassEncoder.End();
wgpu::CommandBuffer commandBuffer = encoder.Finish();
queue.Submit(1, &commandBuffer);
}
// Then set the load operation to Load while we still set the clear color to a big integer value
// that cannot be represented by float.
constexpr uint32_t kExpectedClearValue = kMaxUInt32RepresentableInFloat + 1;
wgpu::Buffer outputBuffer;
{
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
utils::ComboRenderPassDescriptor renderPassForClear(
{textureForLoad.CreateView(), textureForClear.CreateView()});
renderPassForClear.cColorAttachments[0].loadOp = wgpu::LoadOp::Load;
renderPassForClear.cColorAttachments[0].clearValue = {kExpectedClearValue, 0, 0, 0};
renderPassForClear.cColorAttachments[1].loadOp = wgpu::LoadOp::Clear;
renderPassForClear.cColorAttachments[1].clearValue = {kExpectedClearValue, 0, 0, 0};
wgpu::RenderPassEncoder renderPassEncoder = encoder.BeginRenderPass(&renderPassForClear);
renderPassEncoder.End();
wgpu::BufferDescriptor bufferDescriptor = {};
bufferDescriptor.usage = wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst;
bufferDescriptor.size = 2 * sizeof(uint32_t);
outputBuffer = device.CreateBuffer(&bufferDescriptor);
wgpu::ImageCopyTexture imageCopyTextureForLoad =
utils::CreateImageCopyTexture(textureForLoad, 0, {0, 0, 0});
wgpu::ImageCopyBuffer imageCopyBufferForLoad =
utils::CreateImageCopyBuffer(outputBuffer, 0, kTextureBytesPerRowAlignment);
encoder.CopyTextureToBuffer(&imageCopyTextureForLoad, &imageCopyBufferForLoad,
&textureDescriptor.size);
wgpu::ImageCopyTexture imageCopyTextureForClear =
utils::CreateImageCopyTexture(textureForClear, 0, {0, 0, 0});
wgpu::ImageCopyBuffer imageCopyBufferForClear = utils::CreateImageCopyBuffer(
outputBuffer, sizeof(uint32_t), kTextureBytesPerRowAlignment);
encoder.CopyTextureToBuffer(&imageCopyTextureForClear, &imageCopyBufferForClear,
&textureDescriptor.size);
wgpu::CommandBuffer commandBuffer = encoder.Finish();
queue.Submit(1, &commandBuffer);
}
constexpr std::array<uint32_t, 2> kExpectedData = {kExpectedLoadValue, kExpectedClearValue};
EXPECT_BUFFER_U32_RANGE_EQ(kExpectedData.data(), outputBuffer, 0, kExpectedData.size());
}
DAWN_INSTANTIATE_TEST(RenderPassLoadOpTests,
D3D11Backend(),
D3D12Backend(),
MetalBackend(),
OpenGLBackend(),
OpenGLESBackend(),
VulkanBackend());
} // anonymous namespace
} // namespace dawn