diff --git a/dawn.json b/dawn.json index a5e752e233..8fb9c66f43 100644 --- a/dawn.json +++ b/dawn.json @@ -825,6 +825,17 @@ {"name": "color", "type": "color", "annotation": "const*"} ] }, + { + "name": "set viewport", + "args": [ + {"name": "x", "type": "float"}, + {"name": "y", "type": "float"}, + {"name": "width", "type": "float"}, + {"name": "height", "type": "float"}, + {"name": "min depth", "type": "float"}, + {"name": "max depth", "type": "float"} + ] + }, { "name": "set scissor rect", "args": [ diff --git a/src/dawn_native/CommandEncoder.cpp b/src/dawn_native/CommandEncoder.cpp index 261ca31825..6a2656f830 100644 --- a/src/dawn_native/CommandEncoder.cpp +++ b/src/dawn_native/CommandEncoder.cpp @@ -1209,6 +1209,10 @@ namespace dawn_native { mIterator.NextCommand(); } break; + case Command::SetViewport: { + mIterator.NextCommand(); + } break; + case Command::SetScissorRect: { mIterator.NextCommand(); } break; diff --git a/src/dawn_native/Commands.cpp b/src/dawn_native/Commands.cpp index 93f7ddfc97..fbc9172153 100644 --- a/src/dawn_native/Commands.cpp +++ b/src/dawn_native/Commands.cpp @@ -112,6 +112,10 @@ namespace dawn_native { SetStencilReferenceCmd* cmd = commands->NextCommand(); cmd->~SetStencilReferenceCmd(); } break; + case Command::SetViewport: { + SetViewportCmd* cmd = commands->NextCommand(); + cmd->~SetViewportCmd(); + } break; case Command::SetScissorRect: { SetScissorRectCmd* cmd = commands->NextCommand(); cmd->~SetScissorRectCmd(); @@ -229,6 +233,10 @@ namespace dawn_native { commands->NextCommand(); break; + case Command::SetViewport: + commands->NextCommand(); + break; + case Command::SetScissorRect: commands->NextCommand(); break; diff --git a/src/dawn_native/Commands.h b/src/dawn_native/Commands.h index 04ae7f58a6..c514e9ca34 100644 --- a/src/dawn_native/Commands.h +++ b/src/dawn_native/Commands.h @@ -51,6 +51,7 @@ namespace dawn_native { SetComputePipeline, SetRenderPipeline, SetStencilReference, + SetViewport, SetScissorRect, SetBlendColor, SetBindGroup, @@ -192,6 +193,10 @@ namespace dawn_native { uint32_t reference; }; + struct SetViewportCmd { + float x, y, width, height, minDepth, maxDepth; + }; + struct SetScissorRectCmd { uint32_t x, y, width, height; }; diff --git a/src/dawn_native/RenderPassEncoder.cpp b/src/dawn_native/RenderPassEncoder.cpp index c3820d0e01..026a71a80a 100644 --- a/src/dawn_native/RenderPassEncoder.cpp +++ b/src/dawn_native/RenderPassEncoder.cpp @@ -21,6 +21,7 @@ #include "dawn_native/Device.h" #include "dawn_native/RenderPipeline.h" +#include #include namespace dawn_native { @@ -140,6 +141,44 @@ namespace dawn_native { cmd->color = *color; } + void RenderPassEncoderBase::SetViewport(float x, + float y, + float width, + float height, + float minDepth, + float maxDepth) { + if (mTopLevelEncoder->ConsumedError(ValidateCanRecordCommands())) { + return; + } + + if (isnan(x) || isnan(y) || isnan(width) || isnan(height) || isnan(minDepth) || + isnan(maxDepth)) { + mTopLevelEncoder->HandleError("NaN is not allowed."); + return; + } + + // 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) { + mTopLevelEncoder->HandleError("Width and height must be greater than 0."); + return; + } + + if (minDepth < 0 || minDepth > 1 || maxDepth < 0 || maxDepth > 1) { + mTopLevelEncoder->HandleError("minDepth and maxDepth must be in [0, 1]."); + return; + } + + SetViewportCmd* cmd = mAllocator->Allocate(Command::SetViewport); + cmd->x = x; + cmd->y = y; + cmd->width = width; + cmd->height = height; + cmd->minDepth = minDepth; + cmd->maxDepth = maxDepth; + } + void RenderPassEncoderBase::SetScissorRect(uint32_t x, uint32_t y, uint32_t width, diff --git a/src/dawn_native/RenderPassEncoder.h b/src/dawn_native/RenderPassEncoder.h index c6029c5d7d..8250464c5d 100644 --- a/src/dawn_native/RenderPassEncoder.h +++ b/src/dawn_native/RenderPassEncoder.h @@ -50,6 +50,12 @@ namespace dawn_native { void SetStencilReference(uint32_t reference); void SetBlendColor(const Color* color); + void SetViewport(float x, + float y, + float width, + float height, + float minDepth, + float maxDepth); void SetScissorRect(uint32_t x, uint32_t y, uint32_t width, uint32_t height); template diff --git a/src/tests/unittests/validation/DynamicStateCommandValidationTests.cpp b/src/tests/unittests/validation/DynamicStateCommandValidationTests.cpp index d76a84ce43..d88e5511b7 100644 --- a/src/tests/unittests/validation/DynamicStateCommandValidationTests.cpp +++ b/src/tests/unittests/validation/DynamicStateCommandValidationTests.cpp @@ -14,6 +14,215 @@ #include "tests/unittests/validation/ValidationTest.h" +#include + +class SetViewportTest : public ValidationTest {}; + +// Test to check basic use of SetViewport +TEST_F(SetViewportTest, Success) { + DummyRenderPass renderPass(device); + + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + { + dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetViewport(0.0, 0.0, 1.0, 1.0, 0.0, 1.0); + pass.EndPass(); + } + encoder.Finish(); +} + +// Test to check that NaN in viewport parameters is not allowed +TEST_F(SetViewportTest, ViewportParameterNaN) { + DummyRenderPass renderPass(device); + + // x or y is NaN. + { + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + dawn::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. + { + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + dawn::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. + { + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + dawn::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()); + } +} + +// Test to check that an empty viewport is not allowed +TEST_F(SetViewportTest, EmptyViewport) { + DummyRenderPass renderPass(device); + + // Width of viewport is zero. + { + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + dawn::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()); + } + + // Height of viewport is zero. + { + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + dawn::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()); + } + + // Both width and height of viewport are zero. + { + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + dawn::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()); + } +} + +// Test to check that viewport larger than the framebuffer is allowed +TEST_F(SetViewportTest, ViewportLargerThanFramebuffer) { + DummyRenderPass renderPass(device); + + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + { + dawn::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(); +} + +// Test to check that negative x in viewport is allowed +TEST_F(SetViewportTest, NegativeX) { + DummyRenderPass renderPass(device); + + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + { + dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetViewport(-1.0, 0.0, 1.0, 1.0, 0.0, 1.0); + pass.EndPass(); + } + encoder.Finish(); +} + +// Test to check that negative y in viewport is allowed +TEST_F(SetViewportTest, NegativeY) { + DummyRenderPass renderPass(device); + + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + { + dawn::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); + + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + { + dawn::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); + + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + { + dawn::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_F(SetViewportTest, MinDepthOutOfRange) { + DummyRenderPass renderPass(device); + + { + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + dawn::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()); + } + + { + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + dawn::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()); + } +} + +// Test to check that maxDepth out of range [0, 1] is not allowed +TEST_F(SetViewportTest, MaxDepthOutOfRange) { + DummyRenderPass renderPass(device); + + { + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + dawn::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()); + } + + { + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + dawn::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()); + } +} + +// Test to check that minDepth equal or less than maxDepth is allowed +TEST_F(SetViewportTest, MinDepthEqualOrLessThanMaxDepth) { + DummyRenderPass renderPass(device); + + { + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetViewport(0.0, 0.0, 1.0, 1.0, 0.5, 0.5); + pass.EndPass(); + encoder.Finish(); + } + + { + dawn::CommandEncoder encoder = device.CreateCommandEncoder(); + dawn::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); + pass.SetViewport(0.0, 0.0, 1.0, 1.0, 0.8, 0.5); + pass.EndPass(); + encoder.Finish(); + } +} + class SetScissorRectTest : public ValidationTest { };