| // Copyright 2019 The Dawn Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include "tests/DawnTest.h" |
| |
| #include "utils/ComboRenderPipelineDescriptor.h" |
| #include "utils/WGPUHelpers.h" |
| |
| class ViewportTest : public DawnTest { |
| protected: |
| wgpu::RenderPipeline CreatePipelineForTest(wgpu::CompareFunction depthCompare) { |
| utils::ComboRenderPipelineDescriptor pipelineDescriptor(device); |
| |
| // Draw two triangles: |
| // 1. The top-left triangle is red. Its depth values are >= 0.5. After viewport is applied, |
| // the depth might be >= 0.25 if minDepth is 0 and maxDepth is 0.5. |
| // 2. The bottom-right triangle is green. Its depth values are <= 0.5. After viewport is |
| // applied, the depth might be <= 0.25 if minDepth is 0 and maxDepth is 0.5. |
| const char* vs = |
| R"(#version 450 |
| layout(location = 0) out vec4 color; |
| const vec3 pos[6] = vec3[6](vec3(-1.0f, 1.0f, 1.0f), |
| vec3(-1.0f, -1.0f, 0.5f), |
| vec3( 1.0f, 1.0f, 0.5f), |
| vec3( 1.0f, 1.0f, 0.5f), |
| vec3(-1.0f, -1.0f, 0.5f), |
| vec3( 1.0f, -1.0f, 0.0f)); |
| void main() { |
| gl_Position = vec4(pos[gl_VertexIndex], 1.0); |
| if (gl_VertexIndex < 3) { |
| color = vec4(1.0, 0.0, 0.0, 1.0); |
| } else { |
| color = vec4(0.0, 1.0, 0.0, 1.0); |
| } |
| })"; |
| pipelineDescriptor.vertexStage.module = |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, vs); |
| |
| const char* fs = |
| R"(#version 450 |
| layout(location = 0) in vec4 color; |
| layout(location = 0) out vec4 fragColor; |
| void main() { |
| fragColor = color; |
| })"; |
| pipelineDescriptor.cFragmentStage.module = |
| utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, fs); |
| |
| pipelineDescriptor.cDepthStencilState.depthCompare = depthCompare; |
| pipelineDescriptor.depthStencilState = &pipelineDescriptor.cDepthStencilState; |
| |
| return device.CreateRenderPipeline(&pipelineDescriptor); |
| } |
| |
| wgpu::Texture Create2DTextureForTest(wgpu::TextureFormat format) { |
| wgpu::TextureDescriptor textureDescriptor; |
| textureDescriptor.dimension = wgpu::TextureDimension::e2D; |
| textureDescriptor.format = format; |
| textureDescriptor.usage = |
| wgpu::TextureUsage::OutputAttachment | wgpu::TextureUsage::CopySrc; |
| textureDescriptor.mipLevelCount = 1; |
| textureDescriptor.sampleCount = 1; |
| textureDescriptor.size = {kSize, kSize, 1}; |
| return device.CreateTexture(&textureDescriptor); |
| } |
| |
| enum ColorType { |
| TopLeftTriangleColor, |
| BottomRightTriangleColor, |
| BackgroundColor, |
| |
| ColorTypeCount, |
| }; |
| |
| struct ViewportParams { |
| float x, y, width, height, minDepth, maxDepth; |
| }; |
| |
| struct TestInfo { |
| ViewportParams viewport; |
| ColorType topLeftPoint; |
| ColorType bottomRightPoint; |
| float clearDepth = 1.0f; |
| bool setViewport = true; |
| }; |
| |
| void DoTest(const TestInfo& info) { |
| wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder(); |
| |
| // Create render targets for 2 render passes. |
| wgpu::Texture colorTexture1 = Create2DTextureForTest(wgpu::TextureFormat::RGBA8Unorm); |
| wgpu::Texture depthStencilTexture1 = |
| Create2DTextureForTest(wgpu::TextureFormat::Depth24PlusStencil8); |
| |
| wgpu::Texture colorTexture2 = Create2DTextureForTest(wgpu::TextureFormat::RGBA8Unorm); |
| wgpu::Texture depthStencilTexture2 = |
| Create2DTextureForTest(wgpu::TextureFormat::Depth24PlusStencil8); |
| |
| // Create render pass 1 |
| // Note that we may explicitly call SetViewport() in this pass |
| { |
| utils::ComboRenderPassDescriptor renderPassDescriptor1( |
| {colorTexture1.CreateView()}, depthStencilTexture1.CreateView()); |
| renderPassDescriptor1.cColorAttachments[0].clearColor = {0.0, 0.0, 1.0, 1.0}; |
| renderPassDescriptor1.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear; |
| |
| renderPassDescriptor1.cDepthStencilAttachmentInfo.clearDepth = info.clearDepth; |
| renderPassDescriptor1.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Clear; |
| |
| wgpu::RenderPassEncoder renderPass1 = |
| commandEncoder.BeginRenderPass(&renderPassDescriptor1); |
| renderPass1.SetPipeline(CreatePipelineForTest(wgpu::CompareFunction::Less)); |
| if (info.setViewport) { |
| ViewportParams viewport = info.viewport; |
| renderPass1.SetViewport(viewport.x, viewport.y, viewport.width, viewport.height, |
| viewport.minDepth, viewport.maxDepth); |
| } |
| renderPass1.Draw(6); |
| renderPass1.EndPass(); |
| } |
| |
| // Create render pass 2 |
| // Note that we never explicitly call SetViewport() in this pass. |
| // Its viewport(x, y, width, height, minDepth, maxDepth) should be |
| // (0, 0, rendertarget's width, rendertarget's height, 0.0, 1.0) by default. |
| { |
| utils::ComboRenderPassDescriptor renderPassDescriptor2( |
| {colorTexture2.CreateView()}, depthStencilTexture2.CreateView()); |
| renderPassDescriptor2.cColorAttachments[0].clearColor = {0.0, 0.0, 1.0, 1.0}; |
| renderPassDescriptor2.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear; |
| |
| renderPassDescriptor2.cDepthStencilAttachmentInfo.clearDepth = 0.5; |
| renderPassDescriptor2.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Clear; |
| |
| wgpu::RenderPassEncoder renderPass2 = |
| commandEncoder.BeginRenderPass(&renderPassDescriptor2); |
| renderPass2.SetPipeline(CreatePipelineForTest(wgpu::CompareFunction::Greater)); |
| renderPass2.Draw(6); |
| renderPass2.EndPass(); |
| } |
| |
| wgpu::CommandBuffer commandBuffer = commandEncoder.Finish(); |
| queue.Submit(1, &commandBuffer); |
| |
| const RGBA8 kColor[ColorTypeCount] = { |
| RGBA8::kRed, // top-left triangle is red |
| RGBA8::kGreen, // bottom-right triangle is green |
| RGBA8::kBlue, // background is blue |
| }; |
| |
| EXPECT_PIXEL_RGBA8_EQ(kColor[info.topLeftPoint], colorTexture1, 0, 0); |
| EXPECT_PIXEL_RGBA8_EQ(kColor[info.bottomRightPoint], colorTexture1, kSize - 1, kSize - 1); |
| |
| // In render pass 2. Point(0, 0) is tend to be covered by the top-left triangle. Point(3, 3) |
| // is tend to be covered by the bottom-right triangle. However, the bottom-right triangle's |
| // depth values are <= 0.5. And the depthCompare is Greater. As a result, point(0, 0) will |
| // be drawn as usual, its color is the top-left triangle's color. But point(3, 3) will not |
| // be drawn by any triangles. Its color is the backgroud color. |
| EXPECT_PIXEL_RGBA8_EQ(kColor[TopLeftTriangleColor], colorTexture2, 0, 0); |
| EXPECT_PIXEL_RGBA8_EQ(kColor[BackgroundColor], colorTexture2, kSize - 1, kSize - 1); |
| } |
| |
| static constexpr uint32_t kSize = 4; |
| }; |
| |
| // The viewport is the same size as the backbuffer if it is not explicitly specified. And minDepth |
| // and maxDepth are 0.0 and 1.0 respectively. The viewport parameters below are not really used. |
| // Point(0, 0) is covered by the top-left triangle. Likewise, point(3, 3) is covered by the |
| // bottom-right triangle. |
| TEST_P(ViewportTest, Default) { |
| ViewportParams viewport = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; |
| TestInfo info = {viewport, TopLeftTriangleColor, BottomRightTriangleColor, 1.0, false}; |
| DoTest(info); |
| } |
| |
| // Explicitly specify the viewport as its default value. The result is the same as it is in the test |
| // above. |
| TEST_P(ViewportTest, Basic) { |
| ViewportParams viewport = {0.0, 0.0, 4.0, 4.0, 0.0, 1.0}; |
| TestInfo info = {viewport, TopLeftTriangleColor, BottomRightTriangleColor}; |
| DoTest(info); |
| } |
| |
| // Shift the viewport toward top-left by (2, 2). So the top-left triangle is outside of the back |
| // buffer. We can't see it. And point(0, 0) is covered by the bottom-right triangle now. Point(3, 3) |
| // is not covered by any triangles. |
| TEST_P(ViewportTest, ShiftToTopLeft) { |
| ViewportParams viewport = {-2.0, -2.0, 4.0, 4.0, 0.0, 1.0}; |
| TestInfo info = {viewport, BottomRightTriangleColor, BackgroundColor}; |
| DoTest(info); |
| } |
| |
| // Shift the viewport toward bottom-right by (2, 2). So Point(0, 0) is not covered by any triangles. |
| // The top-left triangle is moved to the bottom-right of back buffer. Point(3, 3) is covered by it. |
| // While the bottom-right triangle is moved outside of back buffer now. |
| TEST_P(ViewportTest, ShiftToBottomRight) { |
| ViewportParams viewport = {2.0, 2.0, 4.0, 4.0, 0.0, 1.0}; |
| TestInfo info = {viewport, BackgroundColor, TopLeftTriangleColor}; |
| DoTest(info); |
| } |
| |
| // After applying the minDepth/maxDepth value in viewport and projecting to framebuffer coordinate, |
| // depth values of the top-left triangle are >= 0.25. They are greater than the depth values in |
| // depth buffer, so it is not drawn at all. As a result, point(0, 0) is not covered by any |
| // triangles. But the bottom-right triangle is drawn as usual. |
| TEST_P(ViewportTest, ApplyDepth) { |
| ViewportParams viewport = {0.0, 0.0, 4.0, 4.0, 0.0, 0.5}; |
| TestInfo info = {viewport, BackgroundColor, BottomRightTriangleColor, 0.25}; |
| DoTest(info); |
| } |
| |
| // Shift the viewport toward top-left by (2, 2). So the top-left triangle is outside of the back |
| // buffer. We can't see it. And point(0, 0) is covered by the bottom-right triangle now. Its depth |
| // value is < 0.25. So it is drawn as usual. Point(3, 3) is not covered by any triangles. |
| TEST_P(ViewportTest, ShiftToTopLeftAndApplyDepth) { |
| // Test failing on Linux Vulkan Intel. |
| // See https://bugs.chromium.org/p/dawn/issues/detail?id=187 |
| DAWN_SKIP_TEST_IF(IsLinux() && IsVulkan() && IsIntel()); |
| |
| ViewportParams viewport = {-2.0, -2.0, 4.0, 4.0, 0.0, 0.5}; |
| TestInfo info = {viewport, BottomRightTriangleColor, BackgroundColor, 0.25}; |
| DoTest(info); |
| } |
| |
| // Shift the viewport toward bottom-right by (2, 2). So point(0, 0) is not covered by any triangles. |
| // The top-left triangle is moved to the bottom-right of back buffer. However, depth values of the |
| // top-left triangle are >= 0.25. They are greater than the depth values in depth buffer, so it is |
| // not drawn at all. So point(3, 3) is not covered by any triangle, either. |
| TEST_P(ViewportTest, ShiftToBottomRightAndApplyDepth) { |
| ViewportParams viewport = {2.0, 2.0, 4.0, 4.0, 0.0, 0.5}; |
| TestInfo info = {viewport, BackgroundColor, BackgroundColor, 0.25}; |
| DoTest(info); |
| } |
| |
| // Enlarge the viewport by 2 times. So the entire back buffer is covered by the top-left triangle. |
| TEST_P(ViewportTest, EnlargeViewport) { |
| ViewportParams viewport = {0.0, 0.0, 8.0, 8.0, 0.0, 1.0}; |
| TestInfo info = {viewport, TopLeftTriangleColor, TopLeftTriangleColor}; |
| DoTest(info); |
| } |
| |
| // Enlarge the viewport by 2 times and shift toward top-left by (2, 2). back buffer sits exactly |
| // at the center of the whole viewport. So, point(0, 0) is covered by the top-left triangle, and |
| // point(3, 3) is covered by the bottom-right triangle. |
| TEST_P(ViewportTest, EnlargeViewportAndShiftToTopLeft) { |
| ViewportParams viewport = {-2.0, -2.0, 8.0, 8.0, 0.0, 1.0}; |
| TestInfo info = {viewport, TopLeftTriangleColor, BottomRightTriangleColor}; |
| DoTest(info); |
| } |
| |
| // Enlarge the viewport by 2 times and shift toward bottom-right by (2, 2). Point(0, 0) is not |
| // covered by any triangle. Point(3, 3) is covered by the top-left triangle. |
| TEST_P(ViewportTest, EnlargeViewportAndShiftToBottomRight) { |
| ViewportParams viewport = {2.0, 2.0, 8.0, 8.0, 0.0, 1.0}; |
| TestInfo info = {viewport, BackgroundColor, TopLeftTriangleColor}; |
| DoTest(info); |
| } |
| |
| // Enlarge the viewport by 2 times. So the entire back buffer tend to be covered by the top-left |
| // triangle. However, depth values of the top-left triangle are >= 0.25. They are greater than the |
| // depth values in depth buffer, so the top-left triangle is not drawn at all. As a result, neither |
| // point(0, 0) nor point(3, 3) is covered by any triangles. |
| TEST_P(ViewportTest, EnlargeViewportAndApplyDepth) { |
| ViewportParams viewport = {0.0, 0.0, 8.0, 8.0, 0.0, 0.5}; |
| TestInfo info = {viewport, BackgroundColor, BackgroundColor, 0.25}; |
| DoTest(info); |
| } |
| |
| // Enlarge the viewport by 2 times and shift toward top-left by (2, 2). The back buffer sits exactly |
| // at the center of the whole viewport. However, depth values of the top-left triangle are >= 0.25. |
| // They are greater than the depth values in depth buffer, so the top-left triangle is not drawn at |
| // all. As a result, point(0, 0) is not covered by it. The bottom-right triangle is drawn because |
| // its depth values are < 0.25. So point(3, 3) is covered by it as usual. |
| TEST_P(ViewportTest, EnlargeViewportAndShiftToTopLeftAndApplyDepth) { |
| // Test failing on Linux Vulkan Intel. |
| // See https://bugs.chromium.org/p/dawn/issues/detail?id=187 |
| DAWN_SKIP_TEST_IF(IsLinux() && IsVulkan() && IsIntel()); |
| |
| ViewportParams viewport = {-2.0, -2.0, 8.0, 8.0, 0.0, 0.5}; |
| TestInfo info = {viewport, BackgroundColor, BottomRightTriangleColor, 0.25}; |
| DoTest(info); |
| } |
| |
| // Enlarge the viewport by 2 times and shift toward bottom-right by (2, 2). Point(0, 0) is not |
| // covered by any triangle. The point(3, 3) tend to be covered by the top-left triangle. However, |
| // depth values of the top-left triangle are >= 0.25. They are greater than the depth values in |
| // depth buffer, so the top-left triangle is not drawn at all. As a result, point(3, 3) is not |
| // covered by any triangle, either. |
| TEST_P(ViewportTest, EnlargeViewportAndShiftToBottomRightAndApplyDepth) { |
| ViewportParams viewport = {2.0, 2.0, 8.0, 8.0, 0.0, 0.5}; |
| TestInfo info = {viewport, BackgroundColor, BackgroundColor, 0.25}; |
| DoTest(info); |
| } |
| |
| // Shrink the viewport to its half. So point(0, 0) is covered by the top-left triangle, while |
| // point(3, 3) is not covered by any triangles because the drawing area is too small to cover the |
| // entire back buffer. |
| TEST_P(ViewportTest, ShrinkViewport) { |
| ViewportParams viewport = {0.0, 0.0, 2.0, 2.0, 0.0, 1.0}; |
| TestInfo info = {viewport, TopLeftTriangleColor, BackgroundColor}; |
| DoTest(info); |
| } |
| |
| // Shrink the viewport to its half and move toward top-left by (1, 1), So point(0, 0) is covered by |
| // bottom-right triangle, while point(3, 3) is not covered by any triangles. |
| TEST_P(ViewportTest, ShrinkViewportAndShiftToTopLeft) { |
| ViewportParams viewport = {-1.0, -1.0, 2.0, 2.0, 0.0, 1.0}; |
| TestInfo info = {viewport, BottomRightTriangleColor, BackgroundColor}; |
| DoTest(info); |
| } |
| |
| // Shrink the viewport to its half and move toward bottom-right by (3, 3), So point(0, 0) is not |
| // covered by any triangles, and point(3, 3) is covered by the bottom-right triangle. |
| TEST_P(ViewportTest, ShrinkViewportAndShiftToBottomRight) { |
| ViewportParams viewport = {3.0, 3.0, 2.0, 2.0, 0.0, 1.0}; |
| TestInfo info = {viewport, BackgroundColor, TopLeftTriangleColor}; |
| DoTest(info); |
| } |
| |
| // Shrink the viewport to its half. So point(0, 0) is tend to be covered by top-left triangle. |
| // However, depth values of the top-left triangle are >= 0.25. They are greater than the depth |
| // values in depth buffer, so the top-left triangle is not drawn at all. As a result, point(0, 0) |
| // is not covered by any triangle. Point(3, 3) is not covered by any triangles, either. Because the |
| // drawing area is too small to cover the entire back buffer. |
| TEST_P(ViewportTest, ShrinkViewportAndApplyDepth) { |
| ViewportParams viewport = {0.0, 0.0, 2.0, 2.0, 0.0, 0.5}; |
| TestInfo info = {viewport, BackgroundColor, BackgroundColor, 0.25}; |
| DoTest(info); |
| } |
| |
| // Shrink the viewport to its half and move toward top-left by (1, 1), So point(0, 0) is covered by |
| // the bottom-right triangle, while point(3, 3) is not covered by any triangles. |
| TEST_P(ViewportTest, ShrinkViewportAndShiftToTopLeftAndApplyDepth) { |
| // Test failing on Linux Vulkan Intel. |
| // See https://bugs.chromium.org/p/dawn/issues/detail?id=187 |
| DAWN_SKIP_TEST_IF(IsLinux() && IsVulkan() && IsIntel()); |
| |
| ViewportParams viewport = {-1.0, -1.0, 2.0, 2.0, 0.0, 0.5}; |
| TestInfo info = {viewport, BottomRightTriangleColor, BackgroundColor, 0.25}; |
| DoTest(info); |
| } |
| |
| // Shrink the viewport to its half and move toward bottom-right by (3, 3), So point(0, 0) is not |
| // covered by any triangle. Point(3, 3) is tend to be covered by the top-left triangle. However, |
| // depth values of the top-left triangle are >= 0.25. They are greater than the depth values in |
| // depth buffer, so the top-left triangle is not drawn at all. As a result, point(3, 3) is not |
| // covered by any triangle, either. |
| TEST_P(ViewportTest, ShrinkViewportAndShiftToBottomRightAndApplyDepth) { |
| ViewportParams viewport = {3.0, 3.0, 2.0, 2.0, 0.0, 0.5}; |
| TestInfo info = {viewport, BackgroundColor, BackgroundColor, 0.25}; |
| DoTest(info); |
| } |
| |
| // X and y have fractions and they are smaller than 0.5, which is the center of point(0, 0). So |
| // point(0, 0) is covered by the top left triangle as usual. |
| TEST_P(ViewportTest, DoNotTruncateXAndY) { |
| // Swiftshader seems to be using 4 bits of subpixel precision for viewport computations but |
| // advertises 0 bits of precision. This is within the allowed Vulkan behaviors so this test |
| // should likely be revisited. |
| DAWN_SKIP_TEST_IF(IsVulkan() && IsSwiftshader()); |
| |
| ViewportParams viewport = {0.49, 0.49, 4.0, 4.0, 0.0, 1.0}; |
| TestInfo info = {viewport, TopLeftTriangleColor, BottomRightTriangleColor}; |
| DoTest(info); |
| } |
| |
| // X and y have fractions and they are not smaller than 0.5, which is the center of point(0, 0). So |
| // point(0, 0) is not covered by any trinagle. |
| TEST_P(ViewportTest, DoNotTruncateXAndY2) { |
| ViewportParams viewport = {0.5, 0.5, 4.0, 4.0, 0.0, 1.0}; |
| TestInfo info = {viewport, BackgroundColor, BottomRightTriangleColor}; |
| DoTest(info); |
| } |
| |
| // Width and height have fractions and they are greater than 3.5, which is the center of |
| // point(3, 3). So point(3, 3) is covered by the bottom right triangle as usual. |
| TEST_P(ViewportTest, DoNotTruncateWidthAndHeight) { |
| // Test failing on many D3D12 backend and Intel devices. |
| // It also fails on Vulkan and GL backend on some devices. |
| // See https://bugs.chromium.org/p/dawn/issues/detail?id=205 |
| // See https://bugs.chromium.org/p/dawn/issues/detail?id=257 |
| DAWN_SKIP_TEST_IF(IsIntel() || !IsMetal()); |
| ViewportParams viewport = {0.0, 0.0, 3.51, 3.51, 0.0, 1.0}; |
| TestInfo info = {viewport, TopLeftTriangleColor, BottomRightTriangleColor}; |
| DoTest(info); |
| } |
| |
| // Width and height have fractions and they are not greater than 3.5, which is the center of |
| // point(3, 3). So point(3, 3) is not covered by any triangle. |
| TEST_P(ViewportTest, DoNotTruncateWidthAndHeight2) { |
| ViewportParams viewport = {0.0, 0.0, 3.5, 3.5, 0.0, 1.0}; |
| TestInfo info = {viewport, TopLeftTriangleColor, BackgroundColor}; |
| DoTest(info); |
| } |
| |
| DAWN_INSTANTIATE_TEST(ViewportTest, D3D12Backend(), MetalBackend(), OpenGLBackend(), VulkanBackend()); |