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 <cwallez@chromium.org>
Reviewed-by: Austin Eng <enga@chromium.org>
This commit is contained in:
Corentin Wallez 2020-10-11 18:39:32 +00:00 committed by Commit Bot service account
parent 1c25198384
commit 6c3da3dc5b
6 changed files with 275 additions and 535 deletions

View File

@ -515,10 +515,11 @@ namespace dawn_native {
DeviceBase* device = GetDevice(); DeviceBase* device = GetDevice();
PassResourceUsageTracker usageTracker(PassType::Render); PassResourceUsageTracker usageTracker(PassType::Render);
bool success =
mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
uint32_t width = 0; uint32_t width = 0;
uint32_t height = 0; uint32_t height = 0;
bool success =
mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
uint32_t sampleCount = 0; uint32_t sampleCount = 0;
DAWN_TRY(ValidateRenderPassDescriptor(device, descriptor, &width, &height, DAWN_TRY(ValidateRenderPassDescriptor(device, descriptor, &width, &height,
@ -579,8 +580,8 @@ namespace dawn_native {
}); });
if (success) { if (success) {
RenderPassEncoder* passEncoder = RenderPassEncoder* passEncoder = new RenderPassEncoder(
new RenderPassEncoder(device, this, &mEncodingContext, std::move(usageTracker)); device, this, &mEncodingContext, std::move(usageTracker), width, height);
mEncodingContext.EnterPass(passEncoder); mEncodingContext.EnterPass(passEncoder);
return passEncoder; return passEncoder;
} }

View File

@ -35,8 +35,13 @@ namespace dawn_native {
RenderPassEncoder::RenderPassEncoder(DeviceBase* device, RenderPassEncoder::RenderPassEncoder(DeviceBase* device,
CommandEncoder* commandEncoder, CommandEncoder* commandEncoder,
EncodingContext* encodingContext, EncodingContext* encodingContext,
PassResourceUsageTracker usageTracker) PassResourceUsageTracker usageTracker,
: RenderEncoderBase(device, encodingContext), mCommandEncoder(commandEncoder) { uint32_t renderTargetWidth,
uint32_t renderTargetHeight)
: RenderEncoderBase(device, encodingContext),
mCommandEncoder(commandEncoder),
mRenderTargetWidth(renderTargetWidth),
mRenderTargetHeight(renderTargetHeight) {
mUsageTracker = std::move(usageTracker); mUsageTracker = std::move(usageTracker);
} }
@ -94,15 +99,19 @@ namespace dawn_native {
return DAWN_VALIDATION_ERROR("NaN is not allowed."); return DAWN_VALIDATION_ERROR("NaN is not allowed.");
} }
// TODO(yunchao.he@intel.com): there are more restrictions for x, y, width and height in if (x < 0 || y < 0 || width < 0 || height < 0) {
// Vulkan, and height can be a negative value in Vulkan 1.1. Revisit this part later return DAWN_VALIDATION_ERROR("X, Y, width and height must be non-negative.");
// (say, for WebGPU v1).
if (width <= 0 || height <= 0) {
return DAWN_VALIDATION_ERROR("Width and height must be greater than 0.");
} }
if (minDepth < 0 || minDepth > 1 || maxDepth < 0 || maxDepth > 1) { if (x + width > mRenderTargetWidth || y + height > mRenderTargetHeight) {
return DAWN_VALIDATION_ERROR("minDepth and maxDepth must be in [0, 1]."); 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<SetViewportCmd>(Command::SetViewport); SetViewportCmd* cmd = allocator->Allocate<SetViewportCmd>(Command::SetViewport);

View File

@ -27,7 +27,9 @@ namespace dawn_native {
RenderPassEncoder(DeviceBase* device, RenderPassEncoder(DeviceBase* device,
CommandEncoder* commandEncoder, CommandEncoder* commandEncoder,
EncodingContext* encodingContext, EncodingContext* encodingContext,
PassResourceUsageTracker usageTracker); PassResourceUsageTracker usageTracker,
uint32_t renderTargetWidth,
uint32_t renderTargetHeight);
static RenderPassEncoder* MakeError(DeviceBase* device, static RenderPassEncoder* MakeError(DeviceBase* device,
CommandEncoder* commandEncoder, CommandEncoder* commandEncoder,
@ -58,6 +60,9 @@ namespace dawn_native {
// For render and compute passes, the encoding context is borrowed from the command encoder. // 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. // Keep a reference to the encoder to make sure the context isn't freed.
Ref<CommandEncoder> mCommandEncoder; Ref<CommandEncoder> mCommandEncoder;
uint32_t mRenderTargetWidth;
uint32_t mRenderTargetHeight;
}; };
} // namespace dawn_native } // namespace dawn_native

View File

@ -1176,6 +1176,16 @@ namespace dawn_native { namespace vulkan {
viewport.minDepth = cmd->minDepth; viewport.minDepth = cmd->minDepth;
viewport.maxDepth = cmd->maxDepth; 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); device->fn.CmdSetViewport(commands, 0, 1, &viewport);
break; break;
} }

View File

@ -18,390 +18,195 @@
#include "utils/WGPUHelpers.h" #include "utils/WGPUHelpers.h"
class ViewportTest : public DawnTest { class ViewportTest : public DawnTest {
protected: private:
wgpu::RenderPipeline CreatePipelineForTest(wgpu::CompareFunction depthCompare) { void SetUp() override {
utils::ComboRenderPipelineDescriptor pipelineDescriptor(device); DawnTest::SetUp();
// Draw two triangles: mQuadVS =
// 1. The top-left triangle is red. Its depth values are >= 0.5. After viewport is applied, utils::CreateShaderModule(device, utils::SingleShaderStage::Vertex, R"(#version 450
// the depth might be >= 0.25 if minDepth is 0 and maxDepth is 0.5. const vec2 pos[6] = vec2[6](vec2(-1.0f, 1.0f),
// 2. The bottom-right triangle is green. Its depth values are <= 0.5. After viewport is vec2(-1.0f, -1.0f),
// applied, the depth might be <= 0.25 if minDepth is 0 and maxDepth is 0.5. vec2( 1.0f, 1.0f),
const char* vs = vec2( 1.0f, 1.0f),
R"(#version 450 vec2(-1.0f, -1.0f),
layout(location = 0) out vec4 color; vec2( 1.0f, -1.0f));
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() { void main() {
gl_Position = vec4(pos[gl_VertexIndex], 1.0); gl_Position = vec4(pos[gl_VertexIndex], 0.0, 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 = mQuadFS =
R"(#version 450 utils::CreateShaderModule(device, utils::SingleShaderStage::Fragment, R"(#version 450
layout(location = 0) in vec4 color;
layout(location = 0) out vec4 fragColor; layout(location = 0) out vec4 fragColor;
void main() { void main() {
fragColor = color; fragColor = vec4(1.0);
})"; })");
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) { protected:
wgpu::TextureDescriptor textureDescriptor; wgpu::ShaderModule mQuadVS;
textureDescriptor.dimension = wgpu::TextureDimension::e2D; wgpu::ShaderModule mQuadFS;
textureDescriptor.format = format;
textureDescriptor.usage = static constexpr uint32_t kWidth = 5;
wgpu::TextureUsage::OutputAttachment | wgpu::TextureUsage::CopySrc; static constexpr uint32_t kHeight = 6;
textureDescriptor.mipLevelCount = 1;
textureDescriptor.sampleCount = 1; // Viewport parameters are float, but use uint32_t because implementations of Vulkan are allowed
textureDescriptor.size = {kSize, kSize, 1}; // to just discard the fractional part.
return device.CreateTexture(&textureDescriptor); 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);
// 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();
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
// 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);
}
}
}
} }
enum ColorType { void TestViewportDepth(float minDepth, float maxDepth, bool doViewportCall = true) {
TopLeftTriangleColor, // Create a pipeline drawing 3 points at depth 1.0, 0.5 and 0.0.
BottomRightTriangleColor, utils::ComboRenderPipelineDescriptor pipelineDesc(device);
BackgroundColor, 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);
ColorTypeCount, // 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<float> expected = {
maxDepth,
(maxDepth + minDepth) / 2,
minDepth,
}; };
EXPECT_TEXTURE_EQ(expected.data(), depthTexture, 0, 0, 3, 1, 0, 0);
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 // Test that by default the full viewport is used.
// and maxDepth are 0.0 and 1.0 respectively. The viewport parameters below are not really used. TEST_P(ViewportTest, DefaultViewportRect) {
// Point(0, 0) is covered by the top-left triangle. Likewise, point(3, 3) is covered by the TestViewportQuad(0, 0, kWidth, kHeight, false);
// 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 // Test various viewport values in the X direction.
// above. TEST_P(ViewportTest, VaryingInX) {
TEST_P(ViewportTest, Basic) { TestViewportQuad(0, 0, kWidth - 1, kHeight);
ViewportParams viewport = {0.0, 0.0, 4.0, 4.0, 0.0, 1.0}; TestViewportQuad(1, 0, kWidth - 1, kHeight);
TestInfo info = {viewport, TopLeftTriangleColor, BottomRightTriangleColor}; TestViewportQuad(2, 0, 1, kHeight);
DoTest(info);
} }
// Shift the viewport toward top-left by (2, 2). So the top-left triangle is outside of the back // Test various viewport values in the Y direction.
// buffer. We can't see it. And point(0, 0) is covered by the bottom-right triangle now. Point(3, 3) TEST_P(ViewportTest, VaryingInY) {
// is not covered by any triangles. TestViewportQuad(0, 0, kWidth, kHeight - 1);
TEST_P(ViewportTest, ShiftToTopLeft) { TestViewportQuad(0, 1, kWidth, kHeight - 1);
ViewportParams viewport = {-2.0, -2.0, 4.0, 4.0, 0.0, 1.0}; TestViewportQuad(0, 2, kWidth, 1);
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. // Test various viewport values in both X and Y
// The top-left triangle is moved to the bottom-right of back buffer. Point(3, 3) is covered by it. TEST_P(ViewportTest, SubBoxes) {
// While the bottom-right triangle is moved outside of back buffer now. TestViewportQuad(1, 1, kWidth - 2, kHeight - 2);
TEST_P(ViewportTest, ShiftToBottomRight) { TestViewportQuad(2, 2, 2, 2);
ViewportParams viewport = {2.0, 2.0, 4.0, 4.0, 0.0, 1.0}; TestViewportQuad(2, 3, 2, 1);
TestInfo info = {viewport, BackgroundColor, TopLeftTriangleColor};
DoTest(info);
} }
// After applying the minDepth/maxDepth value in viewport and projecting to framebuffer coordinate, // Test that by default the [0, 1] depth range is used.
// depth values of the top-left triangle are >= 0.25. They are greater than the depth values in TEST_P(ViewportTest, DefaultViewportDepth) {
// depth buffer, so it is not drawn at all. As a result, point(0, 0) is not covered by any TestViewportDepth(0.0, 1.0, false);
// 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 // Test various viewport depth ranges
// buffer. We can't see it. And point(0, 0) is covered by the bottom-right triangle now. Its depth TEST_P(ViewportTest, ViewportDepth) {
// value is < 0.25. So it is drawn as usual. Point(3, 3) is not covered by any triangles. TestViewportDepth(0.0, 0.5);
TEST_P(ViewportTest, ShiftToTopLeftAndApplyDepth) { TestViewportDepth(0.5, 1.0);
// 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. // Test that a draw with an empty viewport doesn't draw anything.
// The top-left triangle is moved to the bottom-right of back buffer. However, depth values of the TEST_P(ViewportTest, EmptyViewport) {
// top-left triangle are >= 0.25. They are greater than the depth values in depth buffer, so it is utils::ComboRenderPipelineDescriptor pipelineDescriptor(device);
// not drawn at all. So point(3, 3) is not covered by any triangle, either. pipelineDescriptor.cColorStates[0].format = wgpu::TextureFormat::RGBA8Unorm;
TEST_P(ViewportTest, ShiftToBottomRightAndApplyDepth) { pipelineDescriptor.vertexStage.module = mQuadVS;
ViewportParams viewport = {2.0, 2.0, 4.0, 4.0, 0.0, 0.5}; pipelineDescriptor.cFragmentStage.module = mQuadFS;
TestInfo info = {viewport, BackgroundColor, BackgroundColor, 0.25}; wgpu::RenderPipeline pipeline = device.CreateRenderPipeline(&pipelineDescriptor);
DoTest(info);
}
// Enlarge the viewport by 2 times. So the entire back buffer is covered by the top-left triangle. utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
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 auto DoEmptyViewportTest = [&](uint32_t width, uint32_t height) {
// at the center of the whole viewport. So, point(0, 0) is covered by the top-left triangle, and wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
// point(3, 3) is covered by the bottom-right triangle. wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass.renderPassInfo);
TEST_P(ViewportTest, EnlargeViewportAndShiftToTopLeft) { pass.SetPipeline(pipeline);
ViewportParams viewport = {-2.0, -2.0, 8.0, 8.0, 0.0, 1.0}; pass.SetViewport(0.0f, 0.0f, width, height, 0.0f, 1.0f);
TestInfo info = {viewport, TopLeftTriangleColor, BottomRightTriangleColor}; pass.Draw(6);
DoTest(info); pass.EndPass();
}
// Enlarge the viewport by 2 times and shift toward bottom-right by (2, 2). Point(0, 0) is not wgpu::CommandBuffer commands = encoder.Finish();
// covered by any triangle. Point(3, 3) is covered by the top-left triangle. queue.Submit(1, &commands);
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 EXPECT_PIXEL_RGBA8_EQ(RGBA8::kZero, renderPass.color, 0, 0);
// 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 // Test with a 0x0, 0xN and nx0 viewport.
// at the center of the whole viewport. However, depth values of the top-left triangle are >= 0.25. DoEmptyViewportTest(0, 0);
// They are greater than the depth values in depth buffer, so the top-left triangle is not drawn at DoEmptyViewportTest(0, 1);
// all. As a result, point(0, 0) is not covered by it. The bottom-right triangle is drawn because DoEmptyViewportTest(1, 0);
// 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, DAWN_INSTANTIATE_TEST(ViewportTest,

View File

@ -14,213 +14,123 @@
#include "tests/unittests/validation/ValidationTest.h" #include "tests/unittests/validation/ValidationTest.h"
#include "utils/WGPUHelpers.h"
#include <cmath> #include <cmath>
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 to check basic use of SetViewport
TEST_F(SetViewportTest, Success) { TEST_F(SetViewportTest, Success) {
DummyRenderPass renderPass(device); TestViewportCall(true, 0.0, 0.0, 1.0, 1.0, 0.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();
}
encoder.Finish();
} }
// Test to check that NaN in viewport parameters is not allowed // Test to check that NaN in viewport parameters is not allowed
TEST_F(SetViewportTest, ViewportParameterNaN) { TEST_F(SetViewportTest, ViewportParameterNaN) {
DummyRenderPass renderPass(device); 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);
// x or y is NaN. 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);
wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); TestViewportCall(false, 0.0, 0.0, 1.0, 1.0, NAN, 1.0);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); TestViewportCall(false, 0.0, 0.0, 1.0, 1.0, 0.0, NAN);
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());
}
} }
// Test to check that an empty viewport is not allowed // Test to check that an empty viewport is allowed.
TEST_F(SetViewportTest, EmptyViewport) { TEST_F(SetViewportTest, EmptyViewport) {
DummyRenderPass renderPass(device);
// Width of viewport is zero. // Width of viewport is zero.
{ TestViewportCall(true, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0);
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());
}
// Height of viewport is zero. // Height of viewport is zero.
{ TestViewportCall(true, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0);
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());
}
// Both width and height of viewport are zero. // Both width and height of viewport are zero.
{ 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(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 to check that viewport larger than the framebuffer is disallowed
TEST_F(SetViewportTest, ViewportLargerThanFramebuffer) { 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(); // Width is larger than the rendertarget's width
{ TestViewportCall(false, 0.0, 0.0, kWidth + 1.0, kHeight, 0.0, 1.0);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); TestViewportCall(false, 0.0, 0.0, nextafter(float(kWidth), 1000.0f), kHeight, 0.0, 1.0);
pass.SetViewport(0.0, 0.0, renderPass.width + 1, renderPass.height + 1, 0.0, 1.0);
pass.EndPass(); // Height is larger than the rendertarget's height
} TestViewportCall(false, 0.0, 0.0, kWidth, kHeight + 1.0, 0.0, 1.0);
encoder.Finish(); 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 to check that negative x in viewport is disallowed
TEST_F(SetViewportTest, NegativeX) { TEST_F(SetViewportTest, NegativeXYWidthHeight) {
DummyRenderPass renderPass(device); // 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(); // Nonzero negative values are disallowed
{ TestViewportCall(false, -1.0, 0.0, 1.0, 1.0, 0.0, 1.0);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); TestViewportCall(false, 0.0, -1.0, 1.0, 1.0, 0.0, 1.0);
pass.SetViewport(-1.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);
pass.EndPass(); TestViewportCall(false, 0.0, 0.0, 1.0, -1.0, 0.0, 1.0);
}
encoder.Finish();
} }
// Test to check that negative y in viewport is allowed // Test to check that minDepth out of range [0, 1] is disallowed
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_F(SetViewportTest, MinDepthOutOfRange) { TEST_F(SetViewportTest, MinDepthOutOfRange) {
DummyRenderPass renderPass(device); // MinDepth is -1
TestViewportCall(false, 0.0, 0.0, 1.0, 1.0, -1.0, 1.0);
{ // MinDepth is 2 or 1 + epsilon
wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); TestViewportCall(false, 0.0, 0.0, 1.0, 1.0, 2.0, 1.0);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); TestViewportCall(false, 0.0, 0.0, 1.0, 1.0, nextafter(1.0f, 1000.0f), 1.0);
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());
}
} }
// 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) { TEST_F(SetViewportTest, MaxDepthOutOfRange) {
DummyRenderPass renderPass(device); // MaxDepth is -1
TestViewportCall(false, 0.0, 0.0, 1.0, 1.0, 1.0, -1.0);
{ // MaxDepth is 2 or 1 + epsilon
wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); TestViewportCall(false, 0.0, 0.0, 1.0, 1.0, 1.0, 2.0);
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass); TestViewportCall(false, 0.0, 0.0, 1.0, 1.0, 1.0, nextafter(1.0f, 1000.0f));
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());
}
} }
// 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) { TEST_F(SetViewportTest, MinDepthEqualOrGreaterThanMaxDepth) {
DummyRenderPass renderPass(device); 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);
{
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();
}
} }
class SetScissorRectTest : public ValidationTest {}; class SetScissorRectTest : public ValidationTest {};