| // 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 <algorithm> |
| #include <array> |
| #include <cmath> |
| #include <utility> |
| #include <vector> |
| |
| #include "dawn/common/Assert.h" |
| #include "dawn/common/Constants.h" |
| #include "dawn/tests/DawnTest.h" |
| #include "dawn/utils/ComboRenderPipelineDescriptor.h" |
| #include "dawn/utils/WGPUHelpers.h" |
| |
| namespace dawn { |
| namespace { |
| |
| constexpr static unsigned int kRTSize = 64; |
| |
| class ColorStateTest : public DawnTest { |
| protected: |
| void SetUp() override { |
| DawnTest::SetUp(); |
| |
| wgpu::BindGroupLayout bindGroupLayout = utils::MakeBindGroupLayout( |
| device, {{0, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::Uniform}}); |
| pipelineLayout = utils::MakePipelineLayout(device, {bindGroupLayout}); |
| |
| // TODO(crbug.com/dawn/489): D3D12_Microsoft_Basic_Render_Driver_CPU |
| // produces invalid results for these tests. |
| DAWN_SUPPRESS_TEST_IF(IsD3D12() && IsWARP()); |
| |
| vsModule = utils::CreateShaderModule(device, R"( |
| @vertex |
| fn main(@builtin(vertex_index) VertexIndex : u32) -> @builtin(position) vec4f { |
| var pos = array( |
| vec2f(-1.0, -1.0), |
| vec2f(3.0, -1.0), |
| vec2f(-1.0, 3.0)); |
| return vec4f(pos[VertexIndex], 0.0, 1.0); |
| } |
| )"); |
| |
| renderPass = utils::CreateBasicRenderPass(device, kRTSize, kRTSize); |
| } |
| |
| struct TriangleSpec { |
| utils::RGBA8 color; |
| std::array<float, 4> blendFactor = {}; |
| }; |
| |
| // Set up basePipeline and testPipeline. testPipeline has the given blend state on the first |
| // attachment. basePipeline has no blending |
| void SetupSingleSourcePipelines(wgpu::ColorTargetState colorTargetState) { |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| struct MyBlock { |
| color : vec4f |
| } |
| |
| @group(0) @binding(0) var<uniform> myUbo : MyBlock; |
| |
| @fragment fn main() -> @location(0) vec4f { |
| return myUbo.color; |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor baseDescriptor; |
| baseDescriptor.layout = pipelineLayout; |
| baseDescriptor.vertex.module = vsModule; |
| baseDescriptor.cFragment.module = fsModule; |
| baseDescriptor.cTargets[0].format = renderPass.colorFormat; |
| |
| basePipeline = device.CreateRenderPipeline(&baseDescriptor); |
| |
| utils::ComboRenderPipelineDescriptor testDescriptor; |
| testDescriptor.layout = pipelineLayout; |
| testDescriptor.vertex.module = vsModule; |
| testDescriptor.cFragment.module = fsModule; |
| testDescriptor.cTargets[0] = colorTargetState; |
| testDescriptor.cTargets[0].format = renderPass.colorFormat; |
| |
| testPipeline = device.CreateRenderPipeline(&testDescriptor); |
| } |
| |
| // Create a bind group to set the colors as a uniform buffer |
| template <size_t N> |
| wgpu::BindGroup MakeBindGroupForColors(std::array<utils::RGBA8, N> colors) { |
| std::array<float, 4 * N> data; |
| for (unsigned int i = 0; i < N; ++i) { |
| data[4 * i + 0] = static_cast<float>(colors[i].r) / 255.f; |
| data[4 * i + 1] = static_cast<float>(colors[i].g) / 255.f; |
| data[4 * i + 2] = static_cast<float>(colors[i].b) / 255.f; |
| data[4 * i + 3] = static_cast<float>(colors[i].a) / 255.f; |
| } |
| |
| uint32_t bufferSize = static_cast<uint32_t>(4 * N * sizeof(float)); |
| |
| wgpu::Buffer buffer = |
| utils::CreateBufferFromData(device, &data, bufferSize, wgpu::BufferUsage::Uniform); |
| return utils::MakeBindGroup(device, testPipeline.GetBindGroupLayout(0), |
| {{0, buffer, 0, bufferSize}}); |
| } |
| |
| // Test that after drawing a triangle with the base color, and then the given triangle spec, the |
| // color is as expected |
| void DoSingleSourceTest(utils::RGBA8 base, |
| const TriangleSpec& triangle, |
| const utils::RGBA8& expected) { |
| wgpu::Color blendConstant{triangle.blendFactor[0], triangle.blendFactor[1], |
| triangle.blendFactor[2], triangle.blendFactor[3]}; |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| { |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| // First use the base pipeline to draw a triangle with no blending |
| pass.SetPipeline(basePipeline); |
| pass.SetBindGroup(0, MakeBindGroupForColors(std::array<utils::RGBA8, 1>({{base}}))); |
| pass.Draw(3); |
| |
| // Then use the test pipeline to draw the test triangle with blending |
| pass.SetPipeline(testPipeline); |
| pass.SetBindGroup( |
| 0, MakeBindGroupForColors(std::array<utils::RGBA8, 1>({{triangle.color}}))); |
| pass.SetBlendConstant(&blendConstant); |
| pass.Draw(3); |
| pass.End(); |
| } |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(expected, renderPass.color, kRTSize / 2, kRTSize / 2); |
| } |
| |
| // Given a vector of tests where each element is <testColor, expectedColor>, check that all |
| // expectations are true for the given blend operation |
| void CheckBlendOperation(utils::RGBA8 base, |
| wgpu::BlendOperation operation, |
| std::vector<std::pair<utils::RGBA8, utils::RGBA8>> tests) { |
| wgpu::BlendComponent blendComponent; |
| blendComponent.operation = operation; |
| blendComponent.srcFactor = wgpu::BlendFactor::One; |
| blendComponent.dstFactor = wgpu::BlendFactor::One; |
| |
| wgpu::BlendState blend; |
| blend.color = blendComponent; |
| blend.alpha = blendComponent; |
| |
| wgpu::ColorTargetState descriptor; |
| descriptor.blend = &blend; |
| descriptor.writeMask = wgpu::ColorWriteMask::All; |
| |
| SetupSingleSourcePipelines(descriptor); |
| |
| for (const auto& [triangleColor, expectedColor] : tests) { |
| DoSingleSourceTest(base, {triangleColor}, expectedColor); |
| } |
| } |
| |
| // Given a vector of tests where each element is <testSpec, expectedColor>, check that all |
| // expectations are true for the given blend factors |
| void CheckBlendFactor(utils::RGBA8 base, |
| wgpu::BlendFactor colorSrcFactor, |
| wgpu::BlendFactor colorDstFactor, |
| wgpu::BlendFactor alphaSrcFactor, |
| wgpu::BlendFactor alphaDstFactor, |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests) { |
| wgpu::BlendComponent colorBlend; |
| colorBlend.operation = wgpu::BlendOperation::Add; |
| colorBlend.srcFactor = colorSrcFactor; |
| colorBlend.dstFactor = colorDstFactor; |
| |
| wgpu::BlendComponent alphaBlend; |
| alphaBlend.operation = wgpu::BlendOperation::Add; |
| alphaBlend.srcFactor = alphaSrcFactor; |
| alphaBlend.dstFactor = alphaDstFactor; |
| |
| wgpu::BlendState blend; |
| blend.color = colorBlend; |
| blend.alpha = alphaBlend; |
| |
| wgpu::ColorTargetState descriptor; |
| descriptor.blend = &blend; |
| descriptor.writeMask = wgpu::ColorWriteMask::All; |
| |
| SetupSingleSourcePipelines(descriptor); |
| |
| for (const auto& [triangles, expectedColor] : tests) { |
| DoSingleSourceTest(base, triangles, expectedColor); |
| } |
| } |
| |
| void CheckSrcBlendFactor(utils::RGBA8 base, |
| wgpu::BlendFactor colorFactor, |
| wgpu::BlendFactor alphaFactor, |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests) { |
| CheckBlendFactor(base, colorFactor, wgpu::BlendFactor::One, alphaFactor, |
| wgpu::BlendFactor::One, tests); |
| } |
| |
| void CheckDstBlendFactor(utils::RGBA8 base, |
| wgpu::BlendFactor colorFactor, |
| wgpu::BlendFactor alphaFactor, |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests) { |
| CheckBlendFactor(base, wgpu::BlendFactor::One, colorFactor, wgpu::BlendFactor::One, |
| alphaFactor, tests); |
| } |
| |
| wgpu::PipelineLayout pipelineLayout; |
| utils::BasicRenderPass renderPass; |
| wgpu::RenderPipeline basePipeline; |
| wgpu::RenderPipeline testPipeline; |
| wgpu::ShaderModule vsModule; |
| }; |
| |
| namespace { |
| // Add two colors and clamp |
| constexpr utils::RGBA8 operator+(const utils::RGBA8& col1, const utils::RGBA8& col2) { |
| int r = static_cast<int>(col1.r) + static_cast<int>(col2.r); |
| int g = static_cast<int>(col1.g) + static_cast<int>(col2.g); |
| int b = static_cast<int>(col1.b) + static_cast<int>(col2.b); |
| int a = static_cast<int>(col1.a) + static_cast<int>(col2.a); |
| r = (r > 255 ? 255 : (r < 0 ? 0 : r)); |
| g = (g > 255 ? 255 : (g < 0 ? 0 : g)); |
| b = (b > 255 ? 255 : (b < 0 ? 0 : b)); |
| a = (a > 255 ? 255 : (a < 0 ? 0 : a)); |
| |
| return utils::RGBA8(static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), |
| static_cast<uint8_t>(a)); |
| } |
| |
| // Subtract two colors and clamp |
| constexpr utils::RGBA8 operator-(const utils::RGBA8& col1, const utils::RGBA8& col2) { |
| int r = static_cast<int>(col1.r) - static_cast<int>(col2.r); |
| int g = static_cast<int>(col1.g) - static_cast<int>(col2.g); |
| int b = static_cast<int>(col1.b) - static_cast<int>(col2.b); |
| int a = static_cast<int>(col1.a) - static_cast<int>(col2.a); |
| r = (r > 255 ? 255 : (r < 0 ? 0 : r)); |
| g = (g > 255 ? 255 : (g < 0 ? 0 : g)); |
| b = (b > 255 ? 255 : (b < 0 ? 0 : b)); |
| a = (a > 255 ? 255 : (a < 0 ? 0 : a)); |
| |
| return utils::RGBA8(static_cast<uint8_t>(r), static_cast<uint8_t>(g), static_cast<uint8_t>(b), |
| static_cast<uint8_t>(a)); |
| } |
| |
| // Get the component-wise minimum of two colors |
| utils::RGBA8 min(const utils::RGBA8& col1, const utils::RGBA8& col2) { |
| return utils::RGBA8(std::min(col1.r, col2.r), std::min(col1.g, col2.g), |
| std::min(col1.b, col2.b), std::min(col1.a, col2.a)); |
| } |
| |
| // Get the component-wise maximum of two colors |
| utils::RGBA8 max(const utils::RGBA8& col1, const utils::RGBA8& col2) { |
| return utils::RGBA8(std::max(col1.r, col2.r), std::max(col1.g, col2.g), |
| std::max(col1.b, col2.b), std::max(col1.a, col2.a)); |
| } |
| |
| // Blend two RGBA8 color values parameterized by the provided factors in the range [0.f, 1.f] |
| utils::RGBA8 mix(const utils::RGBA8& col1, const utils::RGBA8& col2, std::array<float, 4> fac) { |
| float r = static_cast<float>(col1.r) * (1.f - fac[0]) + static_cast<float>(col2.r) * fac[0]; |
| float g = static_cast<float>(col1.g) * (1.f - fac[1]) + static_cast<float>(col2.g) * fac[1]; |
| float b = static_cast<float>(col1.b) * (1.f - fac[2]) + static_cast<float>(col2.b) * fac[2]; |
| float a = static_cast<float>(col1.a) * (1.f - fac[3]) + static_cast<float>(col2.a) * fac[3]; |
| |
| return utils::RGBA8({static_cast<uint8_t>(std::round(r)), static_cast<uint8_t>(std::round(g)), |
| static_cast<uint8_t>(std::round(b)), static_cast<uint8_t>(std::round(a))}); |
| } |
| |
| // Blend two RGBA8 color values parameterized by the provided RGBA8 factor |
| utils::RGBA8 mix(const utils::RGBA8& col1, const utils::RGBA8& col2, const utils::RGBA8& fac) { |
| std::array<float, 4> f = {{ |
| static_cast<float>(fac.r) / 255.f, |
| static_cast<float>(fac.g) / 255.f, |
| static_cast<float>(fac.b) / 255.f, |
| static_cast<float>(fac.a) / 255.f, |
| }}; |
| return mix(col1, col2, f); |
| } |
| |
| constexpr std::array<utils::RGBA8, 8> kColors = {{ |
| // check operations over multiple channels |
| utils::RGBA8(64, 0, 0, 0), |
| utils::RGBA8(0, 64, 0, 0), |
| utils::RGBA8(64, 0, 32, 0), |
| utils::RGBA8(0, 64, 32, 0), |
| utils::RGBA8(128, 0, 128, 128), |
| utils::RGBA8(0, 128, 128, 128), |
| |
| // check cases that may cause overflow |
| utils::RGBA8(0, 0, 0, 0), |
| utils::RGBA8(255, 255, 255, 255), |
| }}; |
| } // namespace |
| |
| // Test compilation and usage of the fixture |
| TEST_P(ColorStateTest, Basic) { |
| wgpu::BlendComponent blendComponent; |
| // Spot-test for defaulting of these three fields. |
| blendComponent.operation = wgpu::BlendOperation::Undefined; // add |
| blendComponent.srcFactor = wgpu::BlendFactor::Undefined; // one |
| blendComponent.dstFactor = wgpu::BlendFactor::Undefined; // zero |
| |
| wgpu::BlendState blend; |
| blend.color = blendComponent; |
| blend.alpha = blendComponent; |
| |
| wgpu::ColorTargetState descriptor; |
| descriptor.blend = &blend; |
| descriptor.writeMask = wgpu::ColorWriteMask::All; |
| |
| SetupSingleSourcePipelines(descriptor); |
| |
| DoSingleSourceTest(utils::RGBA8(0, 0, 0, 0), {utils::RGBA8(255, 0, 0, 0)}, |
| utils::RGBA8(255, 0, 0, 0)); |
| } |
| |
| // The following tests check test that the blend operation works |
| TEST_P(ColorStateTest, BlendOperationAdd) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<utils::RGBA8, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { return std::make_pair(color, base + color); }); |
| CheckBlendOperation(base, wgpu::BlendOperation::Add, tests); |
| } |
| |
| TEST_P(ColorStateTest, BlendOperationSubtract) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<utils::RGBA8, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { return std::make_pair(color, color - base); }); |
| CheckBlendOperation(base, wgpu::BlendOperation::Subtract, tests); |
| } |
| |
| TEST_P(ColorStateTest, BlendOperationReverseSubtract) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<utils::RGBA8, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { return std::make_pair(color, base - color); }); |
| CheckBlendOperation(base, wgpu::BlendOperation::ReverseSubtract, tests); |
| } |
| |
| TEST_P(ColorStateTest, BlendOperationMin) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<utils::RGBA8, utils::RGBA8>> tests; |
| std::transform( |
| kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { return std::make_pair(color, min(base, color)); }); |
| CheckBlendOperation(base, wgpu::BlendOperation::Min, tests); |
| } |
| |
| TEST_P(ColorStateTest, BlendOperationMax) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<utils::RGBA8, utils::RGBA8>> tests; |
| std::transform( |
| kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { return std::make_pair(color, max(base, color)); }); |
| CheckBlendOperation(base, wgpu::BlendOperation::Max, tests); |
| } |
| |
| // The following tests check that the Source blend factor works |
| TEST_P(ColorStateTest, SrcBlendFactorZero) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform( |
| kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { return std::make_pair(TriangleSpec({{color}}), base); }); |
| CheckSrcBlendFactor(base, wgpu::BlendFactor::Zero, wgpu::BlendFactor::Zero, tests); |
| } |
| |
| TEST_P(ColorStateTest, SrcBlendFactorOne) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| return std::make_pair(TriangleSpec({{color}}), base + color); |
| }); |
| CheckSrcBlendFactor(base, wgpu::BlendFactor::One, wgpu::BlendFactor::One, tests); |
| } |
| |
| TEST_P(ColorStateTest, SrcBlendFactorSrc) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| utils::RGBA8 fac = color; |
| fac.a = 0; |
| utils::RGBA8 expected = base + mix(utils::RGBA8(0, 0, 0, 0), color, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckSrcBlendFactor(base, wgpu::BlendFactor::Src, wgpu::BlendFactor::Zero, tests); |
| } |
| |
| TEST_P(ColorStateTest, SrcBlendFactorOneMinusSrc) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| utils::RGBA8 fac = utils::RGBA8(255, 255, 255, 255) - color; |
| fac.a = 0; |
| utils::RGBA8 expected = base + mix(utils::RGBA8(0, 0, 0, 0), color, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckSrcBlendFactor(base, wgpu::BlendFactor::OneMinusSrc, wgpu::BlendFactor::Zero, tests); |
| } |
| |
| TEST_P(ColorStateTest, SrcBlendFactorSrcAlpha) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| utils::RGBA8 fac(color.a, color.a, color.a, color.a); |
| utils::RGBA8 expected = base + mix(utils::RGBA8(0, 0, 0, 0), color, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckSrcBlendFactor(base, wgpu::BlendFactor::SrcAlpha, wgpu::BlendFactor::SrcAlpha, tests); |
| } |
| |
| TEST_P(ColorStateTest, SrcBlendFactorOneMinusSrcAlpha) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform( |
| kColors.begin(), kColors.end(), std::back_inserter(tests), [&](const utils::RGBA8& color) { |
| utils::RGBA8 fac = |
| utils::RGBA8(255, 255, 255, 255) - utils::RGBA8(color.a, color.a, color.a, color.a); |
| utils::RGBA8 expected = base + mix(utils::RGBA8(0, 0, 0, 0), color, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckSrcBlendFactor(base, wgpu::BlendFactor::OneMinusSrcAlpha, |
| wgpu::BlendFactor::OneMinusSrcAlpha, tests); |
| } |
| |
| TEST_P(ColorStateTest, SrcBlendFactorDst) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| utils::RGBA8 fac = base; |
| fac.a = 0; |
| utils::RGBA8 expected = base + mix(utils::RGBA8(0, 0, 0, 0), color, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckSrcBlendFactor(base, wgpu::BlendFactor::Dst, wgpu::BlendFactor::Zero, tests); |
| } |
| |
| TEST_P(ColorStateTest, SrcBlendFactorOneMinusDst) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| utils::RGBA8 fac = utils::RGBA8(255, 255, 255, 255) - base; |
| fac.a = 0; |
| utils::RGBA8 expected = base + mix(utils::RGBA8(0, 0, 0, 0), color, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckSrcBlendFactor(base, wgpu::BlendFactor::OneMinusDst, wgpu::BlendFactor::Zero, tests); |
| } |
| |
| TEST_P(ColorStateTest, SrcBlendFactorDstAlpha) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| utils::RGBA8 fac(base.a, base.a, base.a, base.a); |
| utils::RGBA8 expected = base + mix(utils::RGBA8(0, 0, 0, 0), color, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckSrcBlendFactor(base, wgpu::BlendFactor::DstAlpha, wgpu::BlendFactor::DstAlpha, tests); |
| } |
| |
| TEST_P(ColorStateTest, SrcBlendFactorOneMinusDstAlpha) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform( |
| kColors.begin(), kColors.end(), std::back_inserter(tests), [&](const utils::RGBA8& color) { |
| utils::RGBA8 fac = |
| utils::RGBA8(255, 255, 255, 255) - utils::RGBA8(base.a, base.a, base.a, base.a); |
| utils::RGBA8 expected = base + mix(utils::RGBA8(0, 0, 0, 0), color, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckSrcBlendFactor(base, wgpu::BlendFactor::OneMinusDstAlpha, |
| wgpu::BlendFactor::OneMinusDstAlpha, tests); |
| } |
| |
| TEST_P(ColorStateTest, SrcBlendFactorSrcAlphaSaturated) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| uint8_t f = std::min(color.a, static_cast<uint8_t>(255 - base.a)); |
| utils::RGBA8 fac(f, f, f, 255); |
| utils::RGBA8 expected = base + mix(utils::RGBA8(0, 0, 0, 0), color, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckSrcBlendFactor(base, wgpu::BlendFactor::SrcAlphaSaturated, |
| wgpu::BlendFactor::SrcAlphaSaturated, tests); |
| } |
| |
| TEST_P(ColorStateTest, SrcBlendFactorConstant) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| auto triangleSpec = TriangleSpec({{color}, {{0.2f, 0.4f, 0.6f, 0.8f}}}); |
| utils::RGBA8 expected = |
| base + mix(utils::RGBA8(0, 0, 0, 0), color, triangleSpec.blendFactor); |
| return std::make_pair(triangleSpec, expected); |
| }); |
| CheckSrcBlendFactor(base, wgpu::BlendFactor::Constant, wgpu::BlendFactor::Constant, tests); |
| } |
| |
| TEST_P(ColorStateTest, SrcBlendFactorOneMinusConstant) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| auto triangleSpec = TriangleSpec({{color}, {{0.2f, 0.4f, 0.6f, 0.8f}}}); |
| std::array<float, 4> f = {{0.8f, 0.6f, 0.4f, 0.2f}}; |
| utils::RGBA8 expected = base + mix(utils::RGBA8(0, 0, 0, 0), color, f); |
| return std::make_pair(triangleSpec, expected); |
| }); |
| CheckSrcBlendFactor(base, wgpu::BlendFactor::OneMinusConstant, |
| wgpu::BlendFactor::OneMinusConstant, tests); |
| } |
| |
| // The following tests check that the Destination blend factor works |
| TEST_P(ColorStateTest, DstBlendFactorZero) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform( |
| kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { return std::make_pair(TriangleSpec({{color}}), color); }); |
| CheckDstBlendFactor(base, wgpu::BlendFactor::Zero, wgpu::BlendFactor::Zero, tests); |
| } |
| |
| TEST_P(ColorStateTest, DstBlendFactorOne) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| return std::make_pair(TriangleSpec({{color}}), base + color); |
| }); |
| CheckDstBlendFactor(base, wgpu::BlendFactor::One, wgpu::BlendFactor::One, tests); |
| } |
| |
| TEST_P(ColorStateTest, DstBlendFactorSrc) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| utils::RGBA8 fac = color; |
| fac.a = 0; |
| utils::RGBA8 expected = color + mix(utils::RGBA8(0, 0, 0, 0), base, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckDstBlendFactor(base, wgpu::BlendFactor::Src, wgpu::BlendFactor::Zero, tests); |
| } |
| |
| TEST_P(ColorStateTest, DstBlendFactorOneMinusSrc) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| utils::RGBA8 fac = utils::RGBA8(255, 255, 255, 255) - color; |
| fac.a = 0; |
| utils::RGBA8 expected = color + mix(utils::RGBA8(0, 0, 0, 0), base, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckDstBlendFactor(base, wgpu::BlendFactor::OneMinusSrc, wgpu::BlendFactor::Zero, tests); |
| } |
| |
| TEST_P(ColorStateTest, DstBlendFactorSrcAlpha) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| utils::RGBA8 fac(color.a, color.a, color.a, color.a); |
| utils::RGBA8 expected = color + mix(utils::RGBA8(0, 0, 0, 0), base, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckDstBlendFactor(base, wgpu::BlendFactor::SrcAlpha, wgpu::BlendFactor::SrcAlpha, tests); |
| } |
| |
| TEST_P(ColorStateTest, DstBlendFactorOneMinusSrcAlpha) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform( |
| kColors.begin(), kColors.end(), std::back_inserter(tests), [&](const utils::RGBA8& color) { |
| utils::RGBA8 fac = |
| utils::RGBA8(255, 255, 255, 255) - utils::RGBA8(color.a, color.a, color.a, color.a); |
| utils::RGBA8 expected = color + mix(utils::RGBA8(0, 0, 0, 0), base, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckDstBlendFactor(base, wgpu::BlendFactor::OneMinusSrcAlpha, |
| wgpu::BlendFactor::OneMinusSrcAlpha, tests); |
| } |
| |
| TEST_P(ColorStateTest, DstBlendFactorDst) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| utils::RGBA8 fac = base; |
| fac.a = 0; |
| utils::RGBA8 expected = color + mix(utils::RGBA8(0, 0, 0, 0), base, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckDstBlendFactor(base, wgpu::BlendFactor::Dst, wgpu::BlendFactor::Zero, tests); |
| } |
| |
| TEST_P(ColorStateTest, DstBlendFactorOneMinusDst) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| utils::RGBA8 fac = utils::RGBA8(255, 255, 255, 255) - base; |
| fac.a = 0; |
| utils::RGBA8 expected = color + mix(utils::RGBA8(0, 0, 0, 0), base, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckDstBlendFactor(base, wgpu::BlendFactor::OneMinusDst, wgpu::BlendFactor::Zero, tests); |
| } |
| |
| TEST_P(ColorStateTest, DstBlendFactorDstAlpha) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| utils::RGBA8 fac(base.a, base.a, base.a, base.a); |
| utils::RGBA8 expected = color + mix(utils::RGBA8(0, 0, 0, 0), base, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckDstBlendFactor(base, wgpu::BlendFactor::DstAlpha, wgpu::BlendFactor::DstAlpha, tests); |
| } |
| |
| TEST_P(ColorStateTest, DstBlendFactorOneMinusDstAlpha) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform( |
| kColors.begin(), kColors.end(), std::back_inserter(tests), [&](const utils::RGBA8& color) { |
| utils::RGBA8 fac = |
| utils::RGBA8(255, 255, 255, 255) - utils::RGBA8(base.a, base.a, base.a, base.a); |
| utils::RGBA8 expected = color + mix(utils::RGBA8(0, 0, 0, 0), base, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckDstBlendFactor(base, wgpu::BlendFactor::OneMinusDstAlpha, |
| wgpu::BlendFactor::OneMinusDstAlpha, tests); |
| } |
| |
| TEST_P(ColorStateTest, DstBlendFactorSrcAlphaSaturated) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| uint8_t f = std::min(color.a, static_cast<uint8_t>(255 - base.a)); |
| utils::RGBA8 fac(f, f, f, 255); |
| utils::RGBA8 expected = color + mix(utils::RGBA8(0, 0, 0, 0), base, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckDstBlendFactor(base, wgpu::BlendFactor::SrcAlphaSaturated, |
| wgpu::BlendFactor::SrcAlphaSaturated, tests); |
| } |
| |
| TEST_P(ColorStateTest, DstBlendFactorConstant) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| auto triangleSpec = TriangleSpec({{color}, {{0.2f, 0.4f, 0.6f, 0.8f}}}); |
| utils::RGBA8 expected = |
| color + mix(utils::RGBA8(0, 0, 0, 0), base, triangleSpec.blendFactor); |
| return std::make_pair(triangleSpec, expected); |
| }); |
| CheckDstBlendFactor(base, wgpu::BlendFactor::Constant, wgpu::BlendFactor::Constant, tests); |
| } |
| |
| TEST_P(ColorStateTest, DstBlendFactorOneMinusConstant) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| auto triangleSpec = TriangleSpec({{color}, {{0.2f, 0.4f, 0.6f, 0.8f}}}); |
| std::array<float, 4> f = {{0.8f, 0.6f, 0.4f, 0.2f}}; |
| utils::RGBA8 expected = color + mix(utils::RGBA8(0, 0, 0, 0), base, f); |
| return std::make_pair(triangleSpec, expected); |
| }); |
| CheckDstBlendFactor(base, wgpu::BlendFactor::OneMinusConstant, |
| wgpu::BlendFactor::OneMinusConstant, tests); |
| } |
| |
| // Check that the color write mask works |
| TEST_P(ColorStateTest, ColorWriteMask) { |
| wgpu::BlendComponent blendComponent; |
| blendComponent.operation = wgpu::BlendOperation::Add; |
| blendComponent.srcFactor = wgpu::BlendFactor::One; |
| blendComponent.dstFactor = wgpu::BlendFactor::One; |
| |
| wgpu::BlendState blend; |
| blend.color = blendComponent; |
| blend.alpha = blendComponent; |
| |
| wgpu::ColorTargetState descriptor; |
| descriptor.blend = &blend; |
| { |
| // Test single channel color write |
| descriptor.writeMask = wgpu::ColorWriteMask::Red; |
| SetupSingleSourcePipelines(descriptor); |
| |
| utils::RGBA8 base(32, 64, 128, 192); |
| for (auto& color : kColors) { |
| utils::RGBA8 expected = base + utils::RGBA8(color.r, 0, 0, 0); |
| DoSingleSourceTest(base, {color}, expected); |
| } |
| } |
| |
| { |
| // Test multi channel color write |
| descriptor.writeMask = wgpu::ColorWriteMask::Green | wgpu::ColorWriteMask::Alpha; |
| SetupSingleSourcePipelines(descriptor); |
| |
| utils::RGBA8 base(32, 64, 128, 192); |
| for (auto& color : kColors) { |
| utils::RGBA8 expected = base + utils::RGBA8(0, color.g, 0, color.a); |
| DoSingleSourceTest(base, {color}, expected); |
| } |
| } |
| |
| { |
| // Test no channel color write |
| descriptor.writeMask = wgpu::ColorWriteMask::None; |
| SetupSingleSourcePipelines(descriptor); |
| |
| utils::RGBA8 base(32, 64, 128, 192); |
| for (auto& color : kColors) { |
| DoSingleSourceTest(base, {color}, base); |
| } |
| } |
| } |
| |
| // Check that the color write mask works when blending is disabled |
| TEST_P(ColorStateTest, ColorWriteMaskBlendingDisabled) { |
| { |
| wgpu::BlendComponent blendComponent; |
| blendComponent.operation = wgpu::BlendOperation::Add; |
| blendComponent.srcFactor = wgpu::BlendFactor::One; |
| blendComponent.dstFactor = wgpu::BlendFactor::Zero; |
| |
| wgpu::BlendState blend; |
| blend.color = blendComponent; |
| blend.alpha = blendComponent; |
| |
| wgpu::ColorTargetState descriptor; |
| descriptor.blend = &blend; |
| descriptor.writeMask = wgpu::ColorWriteMask::Red; |
| SetupSingleSourcePipelines(descriptor); |
| |
| utils::RGBA8 base(32, 64, 128, 192); |
| utils::RGBA8 expected(32, 0, 0, 0); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| { |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| pass.SetPipeline(testPipeline); |
| pass.SetBindGroup(0, MakeBindGroupForColors(std::array<utils::RGBA8, 1>({{base}}))); |
| pass.Draw(3); |
| pass.End(); |
| } |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| EXPECT_PIXEL_RGBA8_EQ(expected, renderPass.color, kRTSize / 2, kRTSize / 2); |
| } |
| } |
| |
| // Test that independent color states on render targets works |
| TEST_P(ColorStateTest, IndependentColorState) { |
| // Compatibility mode doesn't support per-draw-buffer blending |
| DAWN_TEST_UNSUPPORTED_IF(IsCompatibilityMode()); |
| |
| DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("disable_indexed_draw_buffers")); |
| |
| std::array<wgpu::Texture, 4> renderTargets; |
| std::array<wgpu::TextureView, 4> renderTargetViews; |
| |
| 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; |
| |
| for (uint32_t i = 0; i < 4; ++i) { |
| renderTargets[i] = device.CreateTexture(&descriptor); |
| renderTargetViews[i] = renderTargets[i].CreateView(); |
| } |
| |
| utils::ComboRenderPassDescriptor renderPass( |
| {renderTargetViews[0], renderTargetViews[1], renderTargetViews[2], renderTargetViews[3]}); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| struct MyBlock { |
| color0 : vec4f, |
| color1 : vec4f, |
| color2 : vec4f, |
| color3 : vec4f, |
| } |
| |
| @group(0) @binding(0) var<uniform> myUbo : MyBlock; |
| |
| struct FragmentOut { |
| @location(0) fragColor0 : vec4f, |
| @location(1) fragColor1 : vec4f, |
| @location(2) fragColor2 : vec4f, |
| @location(3) fragColor3 : vec4f, |
| } |
| |
| @fragment fn main() -> FragmentOut { |
| var output : FragmentOut; |
| output.fragColor0 = myUbo.color0; |
| output.fragColor1 = myUbo.color1; |
| output.fragColor2 = myUbo.color2; |
| output.fragColor3 = myUbo.color3; |
| return output; |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor baseDescriptor; |
| baseDescriptor.layout = pipelineLayout; |
| baseDescriptor.vertex.module = vsModule; |
| baseDescriptor.cFragment.module = fsModule; |
| baseDescriptor.cFragment.targetCount = 4; |
| |
| basePipeline = device.CreateRenderPipeline(&baseDescriptor); |
| |
| utils::ComboRenderPipelineDescriptor testDescriptor; |
| testDescriptor.layout = pipelineLayout; |
| testDescriptor.vertex.module = vsModule; |
| testDescriptor.cFragment.module = fsModule; |
| testDescriptor.cFragment.targetCount = 4; |
| |
| // set color states |
| wgpu::BlendComponent blendComponent0; |
| blendComponent0.operation = wgpu::BlendOperation::Add; |
| blendComponent0.srcFactor = wgpu::BlendFactor::One; |
| blendComponent0.dstFactor = wgpu::BlendFactor::One; |
| |
| wgpu::BlendState blend0; |
| blend0.color = blendComponent0; |
| blend0.alpha = blendComponent0; |
| |
| wgpu::BlendComponent blendComponent1; |
| blendComponent1.operation = wgpu::BlendOperation::Subtract; |
| blendComponent1.srcFactor = wgpu::BlendFactor::One; |
| blendComponent1.dstFactor = wgpu::BlendFactor::One; |
| |
| wgpu::BlendState blend1; |
| blend1.color = blendComponent1; |
| blend1.alpha = blendComponent1; |
| |
| // Blend state intentionally omitted for target 2 |
| |
| wgpu::BlendComponent blendComponent3; |
| blendComponent3.operation = wgpu::BlendOperation::Min; |
| blendComponent3.srcFactor = wgpu::BlendFactor::One; |
| blendComponent3.dstFactor = wgpu::BlendFactor::One; |
| |
| wgpu::BlendState blend3; |
| blend3.color = blendComponent3; |
| blend3.alpha = blendComponent3; |
| |
| testDescriptor.cTargets[0].blend = &blend0; |
| testDescriptor.cTargets[1].blend = &blend1; |
| testDescriptor.cTargets[3].blend = &blend3; |
| |
| testPipeline = device.CreateRenderPipeline(&testDescriptor); |
| |
| for (unsigned int c = 0; c < kColors.size(); ++c) { |
| utils::RGBA8 base = kColors[((c + 31) * 29) % kColors.size()]; |
| utils::RGBA8 color0 = kColors[((c + 19) * 13) % kColors.size()]; |
| utils::RGBA8 color1 = kColors[((c + 11) * 43) % kColors.size()]; |
| utils::RGBA8 color2 = kColors[((c + 7) * 3) % kColors.size()]; |
| utils::RGBA8 color3 = kColors[((c + 13) * 71) % kColors.size()]; |
| |
| utils::RGBA8 expected0 = color0 + base; |
| utils::RGBA8 expected1 = color1 - base; |
| utils::RGBA8 expected2 = color2; |
| utils::RGBA8 expected3 = min(color3, base); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| { |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); |
| pass.SetPipeline(basePipeline); |
| pass.SetBindGroup( |
| 0, MakeBindGroupForColors(std::array<utils::RGBA8, 4>({{base, base, base, base}}))); |
| pass.Draw(3); |
| |
| pass.SetPipeline(testPipeline); |
| pass.SetBindGroup(0, MakeBindGroupForColors(std::array<utils::RGBA8, 4>( |
| {{color0, color1, color2, color3}}))); |
| pass.Draw(3); |
| pass.End(); |
| } |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(expected0, renderTargets[0], kRTSize / 2, kRTSize / 2) |
| << "Attachment slot 0 should have been " << color0 << " + " << base << " = " |
| << expected0; |
| EXPECT_PIXEL_RGBA8_EQ(expected1, renderTargets[1], kRTSize / 2, kRTSize / 2) |
| << "Attachment slot 1 should have been " << color1 << " - " << base << " = " |
| << expected1; |
| EXPECT_PIXEL_RGBA8_EQ(expected2, renderTargets[2], kRTSize / 2, kRTSize / 2) |
| << "Attachment slot 2 should have been " << color2 << " = " << expected2 |
| << "(no blending)"; |
| EXPECT_PIXEL_RGBA8_EQ(expected3, renderTargets[3], kRTSize / 2, kRTSize / 2) |
| << "Attachment slot 3 should have been min(" << color3 << ", " << base |
| << ") = " << expected3; |
| } |
| } |
| |
| // Test that the default blend color is correctly set at the beginning of every subpass |
| TEST_P(ColorStateTest, DefaultBlendColor) { |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| struct MyBlock { |
| color : vec4f |
| } |
| |
| @group(0) @binding(0) var<uniform> myUbo : MyBlock; |
| |
| @fragment fn main() -> @location(0) vec4f { |
| return myUbo.color; |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor baseDescriptor; |
| baseDescriptor.layout = pipelineLayout; |
| baseDescriptor.vertex.module = vsModule; |
| baseDescriptor.cFragment.module = fsModule; |
| baseDescriptor.cTargets[0].format = renderPass.colorFormat; |
| |
| basePipeline = device.CreateRenderPipeline(&baseDescriptor); |
| |
| utils::ComboRenderPipelineDescriptor testDescriptor; |
| testDescriptor.layout = pipelineLayout; |
| testDescriptor.vertex.module = vsModule; |
| testDescriptor.cFragment.module = fsModule; |
| testDescriptor.cTargets[0].format = renderPass.colorFormat; |
| |
| wgpu::BlendComponent blendComponent; |
| blendComponent.operation = wgpu::BlendOperation::Add; |
| blendComponent.srcFactor = wgpu::BlendFactor::Constant; |
| blendComponent.dstFactor = wgpu::BlendFactor::One; |
| |
| wgpu::BlendState blend; |
| blend.color = blendComponent; |
| blend.alpha = blendComponent; |
| |
| testDescriptor.cTargets[0].blend = &blend; |
| |
| testPipeline = device.CreateRenderPipeline(&testDescriptor); |
| constexpr wgpu::Color kWhite{1.0f, 1.0f, 1.0f, 1.0f}; |
| |
| // Check that the initial blend color is (0,0,0,0) |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| { |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| pass.SetPipeline(basePipeline); |
| pass.SetBindGroup(0, MakeBindGroupForColors( |
| std::array<utils::RGBA8, 1>({{utils::RGBA8(0, 0, 0, 0)}}))); |
| pass.Draw(3); |
| pass.SetPipeline(testPipeline); |
| pass.SetBindGroup(0, MakeBindGroupForColors(std::array<utils::RGBA8, 1>( |
| {{utils::RGBA8(255, 255, 255, 255)}}))); |
| pass.Draw(3); |
| pass.End(); |
| } |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(0, 0, 0, 0), renderPass.color, kRTSize / 2, kRTSize / 2); |
| } |
| |
| // Check that setting the blend color works |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| { |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| pass.SetPipeline(basePipeline); |
| pass.SetBindGroup(0, MakeBindGroupForColors( |
| std::array<utils::RGBA8, 1>({{utils::RGBA8(0, 0, 0, 0)}}))); |
| pass.Draw(3); |
| pass.SetPipeline(testPipeline); |
| pass.SetBlendConstant(&kWhite); |
| pass.SetBindGroup(0, MakeBindGroupForColors(std::array<utils::RGBA8, 1>( |
| {{utils::RGBA8(255, 255, 255, 255)}}))); |
| pass.Draw(3); |
| pass.End(); |
| } |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(255, 255, 255, 255), renderPass.color, kRTSize / 2, |
| kRTSize / 2); |
| } |
| |
| // Check that the blend color is not inherited between render passes |
| { |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| { |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| pass.SetPipeline(basePipeline); |
| pass.SetBindGroup(0, MakeBindGroupForColors( |
| std::array<utils::RGBA8, 1>({{utils::RGBA8(0, 0, 0, 0)}}))); |
| pass.Draw(3); |
| pass.SetPipeline(testPipeline); |
| pass.SetBlendConstant(&kWhite); |
| pass.SetBindGroup(0, MakeBindGroupForColors(std::array<utils::RGBA8, 1>( |
| {{utils::RGBA8(255, 255, 255, 255)}}))); |
| pass.Draw(3); |
| pass.End(); |
| } |
| { |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| pass.SetPipeline(basePipeline); |
| pass.SetBindGroup(0, MakeBindGroupForColors( |
| std::array<utils::RGBA8, 1>({{utils::RGBA8(0, 0, 0, 0)}}))); |
| pass.Draw(3); |
| pass.SetPipeline(testPipeline); |
| pass.SetBindGroup(0, MakeBindGroupForColors(std::array<utils::RGBA8, 1>( |
| {{utils::RGBA8(255, 255, 255, 255)}}))); |
| pass.Draw(3); |
| pass.End(); |
| } |
| |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8(0, 0, 0, 0), renderPass.color, kRTSize / 2, kRTSize / 2); |
| } |
| } |
| |
| // This tests a problem in the OpenGL backend where a previous color write mask |
| // persisted and prevented a render pass loadOp from fully clearing the output |
| // attachment. |
| TEST_P(ColorStateTest, ColorWriteMaskDoesNotAffectRenderPassLoadOpClear) { |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| struct MyBlock { |
| color : vec4f |
| } |
| |
| @group(0) @binding(0) var<uniform> myUbo : MyBlock; |
| |
| @fragment fn main() -> @location(0) vec4f { |
| return myUbo.color; |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor baseDescriptor; |
| baseDescriptor.layout = pipelineLayout; |
| baseDescriptor.vertex.module = vsModule; |
| baseDescriptor.cFragment.module = fsModule; |
| baseDescriptor.cTargets[0].format = renderPass.colorFormat; |
| |
| basePipeline = device.CreateRenderPipeline(&baseDescriptor); |
| |
| utils::ComboRenderPipelineDescriptor testDescriptor; |
| testDescriptor.layout = pipelineLayout; |
| testDescriptor.vertex.module = vsModule; |
| testDescriptor.cFragment.module = fsModule; |
| testDescriptor.cTargets[0].format = renderPass.colorFormat; |
| testDescriptor.cTargets[0].writeMask = wgpu::ColorWriteMask::Red; |
| |
| testPipeline = device.CreateRenderPipeline(&testDescriptor); |
| |
| utils::RGBA8 base(32, 64, 128, 192); |
| utils::RGBA8 expected(0, 0, 0, 0); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| { |
| // Clear the render attachment to |base| |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| pass.SetPipeline(basePipeline); |
| pass.SetBindGroup(0, MakeBindGroupForColors(std::array<utils::RGBA8, 1>({{base}}))); |
| pass.Draw(3); |
| |
| // Set a pipeline that will dirty the color write mask |
| pass.SetPipeline(testPipeline); |
| pass.End(); |
| } |
| { |
| // This renderpass' loadOp should clear all channels of the render attachment |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); |
| pass.End(); |
| } |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(expected, renderPass.color, kRTSize / 2, kRTSize / 2); |
| } |
| |
| TEST_P(ColorStateTest, SparseAttachmentsDifferentColorMask) { |
| // Compatibility mode doesn't support per-draw-buffer color mask |
| DAWN_TEST_UNSUPPORTED_IF(IsCompatibilityMode()); |
| |
| DAWN_TEST_UNSUPPORTED_IF(HasToggleEnabled("disable_indexed_draw_buffers")); |
| |
| wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"( |
| struct Outputs { |
| @location(1) o1 : vec4f, |
| @location(3) o3 : vec4f, |
| } |
| |
| @fragment fn main() -> Outputs { |
| return Outputs(vec4f(1.0), vec4f(0.0, 1.0, 1.0, 1.0)); |
| } |
| )"); |
| |
| utils::ComboRenderPipelineDescriptor pipelineDesc; |
| pipelineDesc.vertex.module = vsModule; |
| pipelineDesc.cFragment.module = fsModule; |
| pipelineDesc.cFragment.targetCount = 4; |
| pipelineDesc.cTargets[0].format = wgpu::TextureFormat::Undefined; |
| pipelineDesc.cTargets[0].writeMask = wgpu::ColorWriteMask::None; |
| pipelineDesc.cTargets[1].format = wgpu::TextureFormat::RGBA8Unorm; |
| pipelineDesc.cTargets[2].format = wgpu::TextureFormat::Undefined; |
| pipelineDesc.cTargets[2].writeMask = wgpu::ColorWriteMask::None; |
| pipelineDesc.cTargets[3].format = wgpu::TextureFormat::RGBA8Unorm; |
| pipelineDesc.cTargets[3].writeMask = wgpu::ColorWriteMask::Green | wgpu::ColorWriteMask::Alpha; |
| wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc); |
| |
| wgpu::TextureDescriptor texDesc; |
| texDesc.dimension = wgpu::TextureDimension::e2D; |
| texDesc.format = wgpu::TextureFormat::RGBA8Unorm; |
| texDesc.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc; |
| texDesc.size = {1, 1}; |
| wgpu::Texture attachment1 = device.CreateTexture(&texDesc); |
| wgpu::Texture attachment3 = device.CreateTexture(&texDesc); |
| |
| wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); |
| { |
| wgpu::RenderPassColorAttachment colorAttachments[4]{}; |
| colorAttachments[0].view = nullptr; |
| colorAttachments[1].view = attachment1.CreateView(); |
| colorAttachments[1].loadOp = wgpu::LoadOp::Load; |
| colorAttachments[1].storeOp = wgpu::StoreOp::Store; |
| colorAttachments[2].view = nullptr; |
| colorAttachments[3].view = attachment3.CreateView(); |
| colorAttachments[3].loadOp = wgpu::LoadOp::Load; |
| colorAttachments[3].storeOp = wgpu::StoreOp::Store; |
| |
| wgpu::RenderPassDescriptor rpDesc; |
| rpDesc.colorAttachmentCount = 4; |
| rpDesc.colorAttachments = colorAttachments; |
| |
| wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rpDesc); |
| pass.SetPipeline(pipeline); |
| pass.Draw(3); |
| pass.End(); |
| } |
| wgpu::CommandBuffer commands = encoder.Finish(); |
| queue.Submit(1, &commands); |
| |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kWhite, attachment1, 0, 0); |
| EXPECT_PIXEL_RGBA8_EQ(utils::RGBA8::kGreen, attachment3, 0, 0); |
| } |
| |
| // This is a regression test against an Intel driver issue about using DstAlpha as |
| // SrcBlendFactor for both color and alpha blend factors. |
| TEST_P(ColorStateTest, SrcBlendFactorDstAlphaDstBlendFactorZero) { |
| utils::RGBA8 base(32, 64, 128, 192); |
| std::vector<std::pair<TriangleSpec, utils::RGBA8>> tests; |
| std::transform(kColors.begin(), kColors.end(), std::back_inserter(tests), |
| [&](const utils::RGBA8& color) { |
| utils::RGBA8 fac(base.a, base.a, base.a, base.a); |
| utils::RGBA8 expected = mix(utils::RGBA8(0, 0, 0, 0), color, fac); |
| return std::make_pair(TriangleSpec({{color}}), expected); |
| }); |
| CheckBlendFactor(base, wgpu::BlendFactor::DstAlpha, wgpu::BlendFactor::Zero, |
| wgpu::BlendFactor::DstAlpha, wgpu::BlendFactor::Zero, tests); |
| } |
| |
| DAWN_INSTANTIATE_TEST(ColorStateTest, |
| D3D11Backend(), |
| D3D12Backend(), |
| MetalBackend(), |
| OpenGLBackend(), |
| OpenGLESBackend(), |
| VulkanBackend()); |
| |
| } // anonymous namespace |
| } // namespace dawn |