From 6c3da3dc5b875a9500770644e0966c9f731dae7f Mon Sep 17 00:00:00 2001 From: Corentin Wallez Date: Sun, 11 Oct 2020 18:39:32 +0000 Subject: [PATCH] Make SetViewport validation match upstream WebGPU. As a side-effect this allows empty viewports which need special handling in Vulkan because it is not allowed to set width to 0 (but ok to set height to 0). Validation tests are updated to cover the new validation checks. Most of the viewport end2end tests are rewritten because they didn't pass the new validation. A new end2end test is added to test various kinds of empty viewports to cover the extra logic in the Vulkan backend. Bug: dawn:542 Change-Id: I8bb25612eeed04162a6b942983167eacab3a1906 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/29681 Commit-Queue: Corentin Wallez Reviewed-by: Austin Eng --- src/dawn_native/CommandEncoder.cpp | 9 +- src/dawn_native/RenderPassEncoder.cpp | 27 +- src/dawn_native/RenderPassEncoder.h | 7 +- src/dawn_native/vulkan/CommandBufferVk.cpp | 10 + src/tests/end2end/ViewportTests.cpp | 503 ++++++------------ .../DynamicStateCommandValidationTests.cpp | 254 +++------ 6 files changed, 275 insertions(+), 535 deletions(-) diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp index c7d354a870..b52fc07dfa 100644 --- a/src/dawn_native/CommandEncoder.cpp +++ b/src/dawn_native/CommandEncoder.cpp @@ -515,10 +515,11 @@ namespace dawn_native { DeviceBase* device = GetDevice(); PassResourceUsageTracker usageTracker(PassType::Render); + + uint32_t width = 0; + uint32_t height = 0; bool success = mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError { - uint32_t width = 0; - uint32_t height = 0; uint32_t sampleCount = 0; DAWN_TRY(ValidateRenderPassDescriptor(device, descriptor, &width, &height, @@ -579,8 +580,8 @@ namespace dawn_native { }); if (success) { - RenderPassEncoder* passEncoder = - new RenderPassEncoder(device, this, &mEncodingContext, std::move(usageTracker)); + RenderPassEncoder* passEncoder = new RenderPassEncoder( + device, this, &mEncodingContext, std::move(usageTracker), width, height); mEncodingContext.EnterPass(passEncoder); return passEncoder; } diff --git a/src/dawn_native/RenderPassEncoder.cpp b/src/dawn_native/RenderPassEncoder.cpp index e51b52f766..6068072e81 100644 --- a/src/dawn_native/RenderPassEncoder.cpp +++ b/src/dawn_native/RenderPassEncoder.cpp @@ -35,8 +35,13 @@ namespace dawn_native { RenderPassEncoder::RenderPassEncoder(DeviceBase* device, CommandEncoder* commandEncoder, EncodingContext* encodingContext, - PassResourceUsageTracker usageTracker) - : RenderEncoderBase(device, encodingContext), mCommandEncoder(commandEncoder) { + PassResourceUsageTracker usageTracker, + uint32_t renderTargetWidth, + uint32_t renderTargetHeight) + : RenderEncoderBase(device, encodingContext), + mCommandEncoder(commandEncoder), + mRenderTargetWidth(renderTargetWidth), + mRenderTargetHeight(renderTargetHeight) { mUsageTracker = std::move(usageTracker); } @@ -94,15 +99,19 @@ namespace dawn_native { return DAWN_VALIDATION_ERROR("NaN is not allowed."); } - // TODO(yunchao.he@intel.com): there are more restrictions for x, y, width and height in - // Vulkan, and height can be a negative value in Vulkan 1.1. Revisit this part later - // (say, for WebGPU v1). - if (width <= 0 || height <= 0) { - return DAWN_VALIDATION_ERROR("Width and height must be greater than 0."); + if (x < 0 || y < 0 || width < 0 || height < 0) { + return DAWN_VALIDATION_ERROR("X, Y, width and height must be non-negative."); } - if (minDepth < 0 || minDepth > 1 || maxDepth < 0 || maxDepth > 1) { - return DAWN_VALIDATION_ERROR("minDepth and maxDepth must be in [0, 1]."); + if (x + width > mRenderTargetWidth || y + height > mRenderTargetHeight) { + return DAWN_VALIDATION_ERROR( + "The viewport must be contained in the render targets"); + } + + // Check for depths being in [0, 1] and min <= max in 3 checks instead of 5. + if (minDepth < 0 || minDepth > maxDepth || maxDepth > 1) { + return DAWN_VALIDATION_ERROR( + "minDepth and maxDepth must be in [0, 1] and minDepth <= maxDepth."); } SetViewportCmd* cmd = allocator->Allocate(Command::SetViewport); diff --git a/src/dawn_native/RenderPassEncoder.h b/src/dawn_native/RenderPassEncoder.h index d5a2f7aebb..0fac863d82 100644 --- a/src/dawn_native/RenderPassEncoder.h +++ b/src/dawn_native/RenderPassEncoder.h @@ -27,7 +27,9 @@ namespace dawn_native { RenderPassEncoder(DeviceBase* device, CommandEncoder* commandEncoder, EncodingContext* encodingContext, - PassResourceUsageTracker usageTracker); + PassResourceUsageTracker usageTracker, + uint32_t renderTargetWidth, + uint32_t renderTargetHeight); static RenderPassEncoder* MakeError(DeviceBase* device, CommandEncoder* commandEncoder, @@ -58,6 +60,9 @@ namespace dawn_native { // For render and compute passes, the encoding context is borrowed from the command encoder. // Keep a reference to the encoder to make sure the context isn't freed. Ref mCommandEncoder; + + uint32_t mRenderTargetWidth; + uint32_t mRenderTargetHeight; }; } // namespace dawn_native diff --git a/src/dawn_native/vulkan/CommandBufferVk.cpp b/src/dawn_native/vulkan/CommandBufferVk.cpp index db080a8f09..cb239ae530 100644 --- a/src/dawn_native/vulkan/CommandBufferVk.cpp +++ b/src/dawn_native/vulkan/CommandBufferVk.cpp @@ -1176,6 +1176,16 @@ namespace dawn_native { namespace vulkan { viewport.minDepth = cmd->minDepth; viewport.maxDepth = cmd->maxDepth; + // Vulkan disallows width = 0, but VK_KHR_maintenance1 which we require allows + // height = 0 so use that to do an empty viewport. + if (viewport.width == 0) { + viewport.height = 0; + + // Set the viewport x range to a range that's always valid. + viewport.x = 0; + viewport.width = 1; + } + device->fn.CmdSetViewport(commands, 0, 1, &viewport); break; } diff --git a/src/tests/end2end/ViewportTests.cpp b/src/tests/end2end/ViewportTests.cpp index 2145ab445c..bc1ca4fda5 100644 --- a/src/tests/end2end/ViewportTests.cpp +++ b/src/tests/end2end/ViewportTests.cpp @@ -18,390 +18,195 @@ #include "utils/WGPUHelpers.h" class ViewportTest : public DawnTest { - protected: - wgpu::RenderPipeline CreatePipelineForTest(wgpu::CompareFunction depthCompare) { - utils::ComboRenderPipelineDescriptor pipelineDescriptor(device); + private: + void SetUp() override { + DawnTest::SetUp(); - // 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)); + mQuadVS = + utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(#version 450 + const vec2 pos[6] = vec2[6](vec2(-1.0f, 1.0f), + vec2(-1.0f, -1.0f), + vec2( 1.0f, 1.0f), + vec2( 1.0f, 1.0f), + vec2(-1.0f, -1.0f), + vec2( 1.0f, -1.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); + gl_Position = vec4(pos[gl_VertexIndex], 0.0, 1.0); + })"); - const char* fs = - R"(#version 450 - layout(location = 0) in vec4 color; + mQuadFS = + utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(#version 450 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); + fragColor = vec4(1.0); + })"); } - 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); - } + protected: + wgpu::ShaderModule mQuadVS; + wgpu::ShaderModule mQuadFS; - enum ColorType { - TopLeftTriangleColor, - BottomRightTriangleColor, - BackgroundColor, + static constexpr uint32_t kWidth = 5; + static constexpr uint32_t kHeight = 6; - ColorTypeCount, - }; + // Viewport parameters are float, but use uint32_t because implementations of Vulkan are allowed + // to just discard the fractional part. + void TestViewportQuad(uint32_t x, + uint32_t y, + uint32_t width, + uint32_t height, + bool doViewportCall = true) { + // Create a pipeline that will draw a white quad. + utils::ComboRenderPipelineDescriptor pipelineDesc(device); + pipelineDesc.vertexStage.module = mQuadVS; + pipelineDesc.cFragmentStage.module = mQuadFS; + pipelineDesc.cColorStates[0].format = wgpu::TextureFormat::RGBA8Unorm; + wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc); - struct ViewportParams { - float x, y, width, height, minDepth, maxDepth; - }; + // Render the quad with the viewport call. + utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, kWidth, kHeight); + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); + pass.SetPipeline(pipeline); + if (doViewportCall) { + pass.SetViewport(x, y, width, height, 0.0, 1.0); + } + pass.Draw(6); + pass.EndPass(); - struct TestInfo { - ViewportParams viewport; - ColorType topLeftPoint; - ColorType bottomRightPoint; - float clearDepth = 1.0f; - bool setViewport = true; - }; + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); - 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); + // Check that only the texels that are in the veiwport were drawn. + for (uint32_t checkX = 0; checkX < kWidth; checkX++) { + for (uint32_t checkY = 0; checkY < kHeight; checkY++) { + if (checkX >= x && checkX < x + width && checkY >= y && checkY < y + height) { + EXPECT_PIXEL_RGBA8_EQ(RGBA8::kWhite, rp.color, checkX, checkY); + } else { + EXPECT_PIXEL_RGBA8_EQ(RGBA8::kZero, rp.color, checkX, checkY); + } } - 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; + void TestViewportDepth(float minDepth, float maxDepth, bool doViewportCall = true) { + // Create a pipeline drawing 3 points at depth 1.0, 0.5 and 0.0. + utils::ComboRenderPipelineDescriptor pipelineDesc(device); + pipelineDesc.vertexStage.module = + utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(#version 450 + const vec3 points[3] = vec3[3](vec3(-0.9f, 0.0f, 1.0f), + vec3( 0.0f, 0.0f, 0.5f), + vec3( 0.9f, 0.0f, 0.0f)); + void main() { + gl_Position = vec4(points[gl_VertexIndex], 1.0); + gl_PointSize = 1.0; + })"); + pipelineDesc.cFragmentStage.module = mQuadFS; + pipelineDesc.colorStateCount = 0; + pipelineDesc.primitiveTopology = wgpu::PrimitiveTopology::PointList; + pipelineDesc.depthStencilState = &pipelineDesc.cDepthStencilState; + pipelineDesc.cDepthStencilState.depthWriteEnabled = true; + pipelineDesc.cDepthStencilState.format = wgpu::TextureFormat::Depth32Float; + wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDesc); + + // Create the texture that will store the post-viewport-transform depth. + wgpu::TextureDescriptor depthDesc; + depthDesc.size = {3, 1, 1}; + depthDesc.format = wgpu::TextureFormat::Depth32Float; + depthDesc.usage = wgpu::TextureUsage::OutputAttachment | wgpu::TextureUsage::CopySrc; + wgpu::Texture depthTexture = device.CreateTexture(&depthDesc); + + // Render the three points with the viewport call. + utils::ComboRenderPassDescriptor rpDesc({}, depthTexture.CreateView()); + rpDesc.cDepthStencilAttachmentInfo.clearDepth = 0.0f; + rpDesc.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Clear; + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rpDesc); + pass.SetPipeline(pipeline); + if (doViewportCall) { + pass.SetViewport(0, 0, 3, 1, minDepth, maxDepth); + } + pass.Draw(3); + pass.EndPass(); + + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + + // Check that the viewport transform was computed correctly for the depth. + std::vector expected = { + maxDepth, + (maxDepth + minDepth) / 2, + minDepth, + }; + EXPECT_TEXTURE_EQ(expected.data(), depthTexture, 0, 0, 3, 1, 0, 0); + } }; -// 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); +// Test that by default the full viewport is used. +TEST_P(ViewportTest, DefaultViewportRect) { + TestViewportQuad(0, 0, kWidth, kHeight, false); } -// 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); +// Test various viewport values in the X direction. +TEST_P(ViewportTest, VaryingInX) { + TestViewportQuad(0, 0, kWidth - 1, kHeight); + TestViewportQuad(1, 0, kWidth - 1, kHeight); + TestViewportQuad(2, 0, 1, kHeight); } -// 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); +// Test various viewport values in the Y direction. +TEST_P(ViewportTest, VaryingInY) { + TestViewportQuad(0, 0, kWidth, kHeight - 1); + TestViewportQuad(0, 1, kWidth, kHeight - 1); + TestViewportQuad(0, 2, kWidth, 1); } -// 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); +// Test various viewport values in both X and Y +TEST_P(ViewportTest, SubBoxes) { + TestViewportQuad(1, 1, kWidth - 2, kHeight - 2); + TestViewportQuad(2, 2, 2, 2); + TestViewportQuad(2, 3, 2, 1); } -// 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); +// Test that by default the [0, 1] depth range is used. +TEST_P(ViewportTest, DefaultViewportDepth) { + TestViewportDepth(0.0, 1.0, false); } -// 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); +// Test various viewport depth ranges +TEST_P(ViewportTest, ViewportDepth) { + TestViewportDepth(0.0, 0.5); + TestViewportDepth(0.5, 1.0); } -// 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); -} +// Test that a draw with an empty viewport doesn't draw anything. +TEST_P(ViewportTest, EmptyViewport) { + utils::ComboRenderPipelineDescriptor pipelineDescriptor(device); + pipelineDescriptor.cColorStates[0].format = wgpu::TextureFormat::RGBA8Unorm; + pipelineDescriptor.vertexStage.module = mQuadVS; + pipelineDescriptor.cFragmentStage.module = mQuadFS; + wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor); -// 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); -} + utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1); -// 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); -} + auto DoEmptyViewportTest = [&](uint32_t width, uint32_t height) { + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo); + pass.SetPipeline(pipeline); + pass.SetViewport(0.0f, 0.0f, width, height, 0.0f, 1.0f); + pass.Draw(6); + pass.EndPass(); -// 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); -} + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); -// 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); -} + EXPECT_PIXEL_RGBA8_EQ(RGBA8::kZero, renderPass.color, 0, 0); + }; -// 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); + // Test with a 0x0, 0xN and nx0 viewport. + DoEmptyViewportTest(0, 0); + DoEmptyViewportTest(0, 1); + DoEmptyViewportTest(1, 0); } DAWN_INSTANTIATE_TEST(ViewportTest, diff --git a/src/tests/unittests/validation/DynamicStateCommandValidationTests.cpp b/src/tests/unittests/validation/DynamicStateCommandValidationTests.cpp index cc166400a7..b68fce57c3 100644 --- a/src/tests/unittests/validation/DynamicStateCommandValidationTests.cpp +++ b/src/tests/unittests/validation/DynamicStateCommandValidationTests.cpp @@ -14,213 +14,123 @@ #include "tests/unittests/validation/ValidationTest.h" +#include "utils/WGPUHelpers.h" + #include -class SetViewportTest : public ValidationTest {}; +class SetViewportTest : public ValidationTest { + protected: + void TestViewportCall(bool success, + float x, + float y, + float width, + float height, + float minDepth, + float maxDepth) { + utils::BasicRenderPass rp = utils::CreateBasicRenderPass(device, kWidth, kHeight); + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&rp.renderPassInfo); + pass.SetViewport(x, y, width, height, minDepth, maxDepth); + pass.EndPass(); + + if (success) { + encoder.Finish(); + } else { + ASSERT_DEVICE_ERROR(encoder.Finish()); + } + } + + static constexpr uint32_t kWidth = 5; + static constexpr uint32_t kHeight = 3; +}; // Test to check basic use of SetViewport TEST_F(SetViewportTest, Success) { - DummyRenderPass renderPass(device); - - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - { - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(0.0, 0.0, 1.0, 1.0, 0.0, 1.0); - pass.EndPass(); - } - encoder.Finish(); + TestViewportCall(true, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0); } // Test to check that NaN in viewport parameters is not allowed TEST_F(SetViewportTest, ViewportParameterNaN) { - DummyRenderPass renderPass(device); - - // x or y is NaN. - { - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(NAN, 0.0, 1.0, 1.0, 0.0, 1.0); - pass.EndPass(); - ASSERT_DEVICE_ERROR(encoder.Finish()); - } - - // width or height is NaN. - { - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(0.0, 0.0, NAN, 1.0, 0.0, 1.0); - pass.EndPass(); - ASSERT_DEVICE_ERROR(encoder.Finish()); - } - - // minDepth or maxDepth is NaN. - { - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(0.0, 0.0, 1.0, 1.0, NAN, 1.0); - pass.EndPass(); - ASSERT_DEVICE_ERROR(encoder.Finish()); - } + TestViewportCall(false, NAN, 0.0, 1.0, 1.0, 0.0, 1.0); + TestViewportCall(false, 0.0, NAN, 1.0, 1.0, 0.0, 1.0); + TestViewportCall(false, 0.0, 0.0, NAN, 1.0, 0.0, 1.0); + TestViewportCall(false, 0.0, 0.0, 1.0, NAN, 0.0, 1.0); + TestViewportCall(false, 0.0, 0.0, 1.0, 1.0, NAN, 1.0); + TestViewportCall(false, 0.0, 0.0, 1.0, 1.0, 0.0, NAN); } -// Test to check that an empty viewport is not allowed +// Test to check that an empty viewport is allowed. TEST_F(SetViewportTest, EmptyViewport) { - DummyRenderPass renderPass(device); - // Width of viewport is zero. - { - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(0.0, 0.0, 0.0, 1.0, 0.0, 1.0); - pass.EndPass(); - ASSERT_DEVICE_ERROR(encoder.Finish()); - } + TestViewportCall(true, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0); // Height of viewport is zero. - { - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(0.0, 0.0, 1.0, 0.0, 0.0, 1.0); - pass.EndPass(); - ASSERT_DEVICE_ERROR(encoder.Finish()); - } + TestViewportCall(true, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0); // Both width and height of viewport are zero. - { - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(0.0, 0.0, 0.0, 0.0, 0.0, 1.0); - pass.EndPass(); - ASSERT_DEVICE_ERROR(encoder.Finish()); - } + TestViewportCall(true, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0); } -// Test to check that viewport larger than the framebuffer is allowed +// Test to check that viewport larger than the framebuffer is disallowed TEST_F(SetViewportTest, ViewportLargerThanFramebuffer) { - DummyRenderPass renderPass(device); + // Control case: width and height are set to the render target size. + TestViewportCall(true, 0.0, 0.0, kWidth, kHeight, 0.0, 1.0); - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - { - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(0.0, 0.0, renderPass.width + 1, renderPass.height + 1, 0.0, 1.0); - pass.EndPass(); - } - encoder.Finish(); + // Width is larger than the rendertarget's width + TestViewportCall(false, 0.0, 0.0, kWidth + 1.0, kHeight, 0.0, 1.0); + TestViewportCall(false, 0.0, 0.0, nextafter(float(kWidth), 1000.0f), kHeight, 0.0, 1.0); + + // Height is larger than the rendertarget's height + TestViewportCall(false, 0.0, 0.0, kWidth, kHeight + 1.0, 0.0, 1.0); + TestViewportCall(false, 0.0, 0.0, kWidth, nextafter(float(kHeight), 1000.0f), 0.0, 1.0); + + // x + width is larger than the rendertarget's width + TestViewportCall(false, 2.0, 0.0, kWidth - 1.0, kHeight, 0.0, 1.0); + TestViewportCall(false, 1.0, 0.0, nextafter(float(kWidth - 1.0), 1000.0f), kHeight, 0.0, 1.0); + + // Height is larger than the rendertarget's height + TestViewportCall(false, 0.0, 2.0, kWidth, kHeight - 1.0, 0.0, 1.0); + TestViewportCall(false, 0.0, 1.0, kWidth, nextafter(float(kHeight - 1.0), 1000.0f), 0.0, 1.0); } -// Test to check that negative x in viewport is allowed -TEST_F(SetViewportTest, NegativeX) { - DummyRenderPass renderPass(device); +// Test to check that negative x in viewport is disallowed +TEST_F(SetViewportTest, NegativeXYWidthHeight) { + // Control case: everything set to 0 is allowed. + TestViewportCall(true, +0.0, +0.0, +0.0, +0.0, 0.0, 1.0); + TestViewportCall(true, -0.0, -0.0, -0.0, -0.0, 0.0, 1.0); - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - { - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(-1.0, 0.0, 1.0, 1.0, 0.0, 1.0); - pass.EndPass(); - } - encoder.Finish(); + // Nonzero negative values are disallowed + TestViewportCall(false, -1.0, 0.0, 1.0, 1.0, 0.0, 1.0); + TestViewportCall(false, 0.0, -1.0, 1.0, 1.0, 0.0, 1.0); + TestViewportCall(false, 0.0, 0.0, -1.0, 1.0, 0.0, 1.0); + TestViewportCall(false, 0.0, 0.0, 1.0, -1.0, 0.0, 1.0); } -// Test to check that negative y in viewport is allowed -TEST_F(SetViewportTest, NegativeY) { - DummyRenderPass renderPass(device); - - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - { - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(0.0, -1.0, 1.0, 1.0, 0.0, 1.0); - pass.EndPass(); - } - encoder.Finish(); -} - -// Test to check that negative width in viewport is not allowed -TEST_F(SetViewportTest, NegativeWidth) { - DummyRenderPass renderPass(device); - - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - { - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(0.0, 0.0, -1.0, 1.0, 0.0, 1.0); - pass.EndPass(); - ASSERT_DEVICE_ERROR(encoder.Finish()); - } -} - -// Test to check that negative height in viewport is not allowed -TEST_F(SetViewportTest, NegativeHeight) { - DummyRenderPass renderPass(device); - - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - { - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(0.0, 0.0, 0.0, -1.0, 0.0, 1.0); - pass.EndPass(); - ASSERT_DEVICE_ERROR(encoder.Finish()); - } -} - -// Test to check that minDepth out of range [0, 1] is not allowed +// Test to check that minDepth out of range [0, 1] is disallowed TEST_F(SetViewportTest, MinDepthOutOfRange) { - DummyRenderPass renderPass(device); + // MinDepth is -1 + TestViewportCall(false, 0.0, 0.0, 1.0, 1.0, -1.0, 1.0); - { - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(0.0, 0.0, 1.0, 1.0, -1.0, 1.0); - pass.EndPass(); - ASSERT_DEVICE_ERROR(encoder.Finish()); - } - - { - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(0.0, 0.0, 1.0, 1.0, 2.0, 1.0); - pass.EndPass(); - ASSERT_DEVICE_ERROR(encoder.Finish()); - } + // MinDepth is 2 or 1 + epsilon + TestViewportCall(false, 0.0, 0.0, 1.0, 1.0, 2.0, 1.0); + TestViewportCall(false, 0.0, 0.0, 1.0, 1.0, nextafter(1.0f, 1000.0f), 1.0); } -// Test to check that maxDepth out of range [0, 1] is not allowed +// Test to check that minDepth out of range [0, 1] is disallowed TEST_F(SetViewportTest, MaxDepthOutOfRange) { - DummyRenderPass renderPass(device); + // MaxDepth is -1 + TestViewportCall(false, 0.0, 0.0, 1.0, 1.0, 1.0, -1.0); - { - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(0.0, 0.0, 1.0, 1.0, 0.0, -1.0); - pass.EndPass(); - ASSERT_DEVICE_ERROR(encoder.Finish()); - } - - { - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(0.0, 0.0, 1.0, 1.0, 0.0, 2.0); - pass.EndPass(); - ASSERT_DEVICE_ERROR(encoder.Finish()); - } + // MaxDepth is 2 or 1 + epsilon + TestViewportCall(false, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0); + TestViewportCall(false, 0.0, 0.0, 1.0, 1.0, 1.0, nextafter(1.0f, 1000.0f)); } -// Test to check that minDepth equal or greater than maxDepth is allowed +// Test to check that minDepth equal or greater than maxDepth is disallowed TEST_F(SetViewportTest, MinDepthEqualOrGreaterThanMaxDepth) { - DummyRenderPass renderPass(device); - - { - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(0.0, 0.0, 1.0, 1.0, 0.5, 0.5); - pass.EndPass(); - encoder.Finish(); - } - - { - wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); - wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); - pass.SetViewport(0.0, 0.0, 1.0, 1.0, 0.8, 0.5); - pass.EndPass(); - encoder.Finish(); - } + TestViewportCall(true, 0.0, 0.0, 1.0, 1.0, 0.5, 0.5); + TestViewportCall(false, 0.0, 0.0, 1.0, 1.0, 0.8, 0.5); } class SetScissorRectTest : public ValidationTest {};