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();
PassResourceUsageTracker usageTracker(PassType::Render);
bool success =
mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
uint32_t width = 0;
uint32_t height = 0;
bool success =
mEncodingContext.TryEncode(this, [&](CommandAllocator* allocator) -> MaybeError {
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;
}

View File

@ -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<SetViewportCmd>(Command::SetViewport);

View File

@ -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<CommandEncoder> mCommandEncoder;
uint32_t mRenderTargetWidth;
uint32_t mRenderTargetHeight;
};
} // namespace dawn_native

View File

@ -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;
}

View File

@ -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;
static constexpr uint32_t kWidth = 5;
static constexpr uint32_t kHeight = 6;
// 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);
// 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 {
TopLeftTriangleColor,
BottomRightTriangleColor,
BackgroundColor,
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);
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;
// Test that by default the full viewport is used.
TEST_P(ViewportTest, DefaultViewportRect) {
TestViewportQuad(0, 0, kWidth, kHeight, false);
}
// 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);
}
// 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);
}
// 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);
}
// Test that by default the [0, 1] depth range is used.
TEST_P(ViewportTest, DefaultViewportDepth) {
TestViewportDepth(0.0, 1.0, false);
}
// Test various viewport depth ranges
TEST_P(ViewportTest, ViewportDepth) {
TestViewportDepth(0.0, 0.5);
TestViewportDepth(0.5, 1.0);
}
// 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);
utils::BasicRenderPass renderPass = utils::CreateBasicRenderPass(device, 1, 1);
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();
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_PIXEL_RGBA8_EQ(RGBA8::kZero, renderPass.color, 0, 0);
};
struct TestInfo {
ViewportParams viewport;
ColorType topLeftPoint;
ColorType bottomRightPoint;
float clearDepth = 1.0f;
bool setViewport = true;
};
void DoTest(const TestInfo& info) {
wgpu::CommandEncoder commandEncoder = device.CreateCommandEncoder();
// Create render targets for 2 render passes.
wgpu::Texture colorTexture1 = Create2DTextureForTest(wgpu::TextureFormat::RGBA8Unorm);
wgpu::Texture depthStencilTexture1 =
Create2DTextureForTest(wgpu::TextureFormat::Depth24PlusStencil8);
wgpu::Texture colorTexture2 = Create2DTextureForTest(wgpu::TextureFormat::RGBA8Unorm);
wgpu::Texture depthStencilTexture2 =
Create2DTextureForTest(wgpu::TextureFormat::Depth24PlusStencil8);
// Create render pass 1
// Note that we may explicitly call SetViewport() in this pass
{
utils::ComboRenderPassDescriptor renderPassDescriptor1(
{colorTexture1.CreateView()}, depthStencilTexture1.CreateView());
renderPassDescriptor1.cColorAttachments[0].clearColor = {0.0, 0.0, 1.0, 1.0};
renderPassDescriptor1.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
renderPassDescriptor1.cDepthStencilAttachmentInfo.clearDepth = info.clearDepth;
renderPassDescriptor1.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Clear;
wgpu::RenderPassEncoder renderPass1 =
commandEncoder.BeginRenderPass(&renderPassDescriptor1);
renderPass1.SetPipeline(CreatePipelineForTest(wgpu::CompareFunction::Less));
if (info.setViewport) {
ViewportParams viewport = info.viewport;
renderPass1.SetViewport(viewport.x, viewport.y, viewport.width, viewport.height,
viewport.minDepth, viewport.maxDepth);
}
renderPass1.Draw(6);
renderPass1.EndPass();
}
// Create render pass 2
// Note that we never explicitly call SetViewport() in this pass.
// Its viewport(x, y, width, height, minDepth, maxDepth) should be
// (0, 0, rendertarget's width, rendertarget's height, 0.0, 1.0) by default.
{
utils::ComboRenderPassDescriptor renderPassDescriptor2(
{colorTexture2.CreateView()}, depthStencilTexture2.CreateView());
renderPassDescriptor2.cColorAttachments[0].clearColor = {0.0, 0.0, 1.0, 1.0};
renderPassDescriptor2.cColorAttachments[0].loadOp = wgpu::LoadOp::Clear;
renderPassDescriptor2.cDepthStencilAttachmentInfo.clearDepth = 0.5;
renderPassDescriptor2.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Clear;
wgpu::RenderPassEncoder renderPass2 =
commandEncoder.BeginRenderPass(&renderPassDescriptor2);
renderPass2.SetPipeline(CreatePipelineForTest(wgpu::CompareFunction::Greater));
renderPass2.Draw(6);
renderPass2.EndPass();
}
wgpu::CommandBuffer commandBuffer = commandEncoder.Finish();
queue.Submit(1, &commandBuffer);
const RGBA8 kColor[ColorTypeCount] = {
RGBA8::kRed, // top-left triangle is red
RGBA8::kGreen, // bottom-right triangle is green
RGBA8::kBlue, // background is blue
};
EXPECT_PIXEL_RGBA8_EQ(kColor[info.topLeftPoint], colorTexture1, 0, 0);
EXPECT_PIXEL_RGBA8_EQ(kColor[info.bottomRightPoint], colorTexture1, kSize - 1, kSize - 1);
// In render pass 2. Point(0, 0) is tend to be covered by the top-left triangle. Point(3, 3)
// is tend to be covered by the bottom-right triangle. However, the bottom-right triangle's
// depth values are <= 0.5. And the depthCompare is Greater. As a result, point(0, 0) will
// be drawn as usual, its color is the top-left triangle's color. But point(3, 3) will not
// be drawn by any triangles. Its color is the backgroud color.
EXPECT_PIXEL_RGBA8_EQ(kColor[TopLeftTriangleColor], colorTexture2, 0, 0);
EXPECT_PIXEL_RGBA8_EQ(kColor[BackgroundColor], colorTexture2, kSize - 1, kSize - 1);
}
static constexpr uint32_t kSize = 4;
};
// The viewport is the same size as the backbuffer if it is not explicitly specified. And minDepth
// and maxDepth are 0.0 and 1.0 respectively. The viewport parameters below are not really used.
// Point(0, 0) is covered by the top-left triangle. Likewise, point(3, 3) is covered by the
// bottom-right triangle.
TEST_P(ViewportTest, Default) {
ViewportParams viewport = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
TestInfo info = {viewport, TopLeftTriangleColor, BottomRightTriangleColor, 1.0, false};
DoTest(info);
}
// Explicitly specify the viewport as its default value. The result is the same as it is in the test
// above.
TEST_P(ViewportTest, Basic) {
ViewportParams viewport = {0.0, 0.0, 4.0, 4.0, 0.0, 1.0};
TestInfo info = {viewport, TopLeftTriangleColor, BottomRightTriangleColor};
DoTest(info);
}
// Shift the viewport toward top-left by (2, 2). So the top-left triangle is outside of the back
// buffer. We can't see it. And point(0, 0) is covered by the bottom-right triangle now. Point(3, 3)
// is not covered by any triangles.
TEST_P(ViewportTest, ShiftToTopLeft) {
ViewportParams viewport = {-2.0, -2.0, 4.0, 4.0, 0.0, 1.0};
TestInfo info = {viewport, BottomRightTriangleColor, BackgroundColor};
DoTest(info);
}
// Shift the viewport toward bottom-right by (2, 2). So Point(0, 0) is not covered by any triangles.
// The top-left triangle is moved to the bottom-right of back buffer. Point(3, 3) is covered by it.
// While the bottom-right triangle is moved outside of back buffer now.
TEST_P(ViewportTest, ShiftToBottomRight) {
ViewportParams viewport = {2.0, 2.0, 4.0, 4.0, 0.0, 1.0};
TestInfo info = {viewport, BackgroundColor, TopLeftTriangleColor};
DoTest(info);
}
// After applying the minDepth/maxDepth value in viewport and projecting to framebuffer coordinate,
// depth values of the top-left triangle are >= 0.25. They are greater than the depth values in
// depth buffer, so it is not drawn at all. As a result, point(0, 0) is not covered by any
// triangles. But the bottom-right triangle is drawn as usual.
TEST_P(ViewportTest, ApplyDepth) {
ViewportParams viewport = {0.0, 0.0, 4.0, 4.0, 0.0, 0.5};
TestInfo info = {viewport, BackgroundColor, BottomRightTriangleColor, 0.25};
DoTest(info);
}
// Shift the viewport toward top-left by (2, 2). So the top-left triangle is outside of the back
// buffer. We can't see it. And point(0, 0) is covered by the bottom-right triangle now. Its depth
// value is < 0.25. So it is drawn as usual. Point(3, 3) is not covered by any triangles.
TEST_P(ViewportTest, ShiftToTopLeftAndApplyDepth) {
// Test failing on Linux Vulkan Intel.
// See https://bugs.chromium.org/p/dawn/issues/detail?id=187
DAWN_SKIP_TEST_IF(IsLinux() && IsVulkan() && IsIntel());
ViewportParams viewport = {-2.0, -2.0, 4.0, 4.0, 0.0, 0.5};
TestInfo info = {viewport, BottomRightTriangleColor, BackgroundColor, 0.25};
DoTest(info);
}
// Shift the viewport toward bottom-right by (2, 2). So point(0, 0) is not covered by any triangles.
// The top-left triangle is moved to the bottom-right of back buffer. However, depth values of the
// top-left triangle are >= 0.25. They are greater than the depth values in depth buffer, so it is
// not drawn at all. So point(3, 3) is not covered by any triangle, either.
TEST_P(ViewportTest, ShiftToBottomRightAndApplyDepth) {
ViewportParams viewport = {2.0, 2.0, 4.0, 4.0, 0.0, 0.5};
TestInfo info = {viewport, BackgroundColor, BackgroundColor, 0.25};
DoTest(info);
}
// Enlarge the viewport by 2 times. So the entire back buffer is covered by the top-left triangle.
TEST_P(ViewportTest, EnlargeViewport) {
ViewportParams viewport = {0.0, 0.0, 8.0, 8.0, 0.0, 1.0};
TestInfo info = {viewport, TopLeftTriangleColor, TopLeftTriangleColor};
DoTest(info);
}
// Enlarge the viewport by 2 times and shift toward top-left by (2, 2). back buffer sits exactly
// at the center of the whole viewport. So, point(0, 0) is covered by the top-left triangle, and
// point(3, 3) is covered by the bottom-right triangle.
TEST_P(ViewportTest, EnlargeViewportAndShiftToTopLeft) {
ViewportParams viewport = {-2.0, -2.0, 8.0, 8.0, 0.0, 1.0};
TestInfo info = {viewport, TopLeftTriangleColor, BottomRightTriangleColor};
DoTest(info);
}
// Enlarge the viewport by 2 times and shift toward bottom-right by (2, 2). Point(0, 0) is not
// covered by any triangle. Point(3, 3) is covered by the top-left triangle.
TEST_P(ViewportTest, EnlargeViewportAndShiftToBottomRight) {
ViewportParams viewport = {2.0, 2.0, 8.0, 8.0, 0.0, 1.0};
TestInfo info = {viewport, BackgroundColor, TopLeftTriangleColor};
DoTest(info);
}
// Enlarge the viewport by 2 times. So the entire back buffer tend to be covered by the top-left
// triangle. However, depth values of the top-left triangle are >= 0.25. They are greater than the
// depth values in depth buffer, so the top-left triangle is not drawn at all. As a result, neither
// point(0, 0) nor point(3, 3) is covered by any triangles.
TEST_P(ViewportTest, EnlargeViewportAndApplyDepth) {
ViewportParams viewport = {0.0, 0.0, 8.0, 8.0, 0.0, 0.5};
TestInfo info = {viewport, BackgroundColor, BackgroundColor, 0.25};
DoTest(info);
}
// Enlarge the viewport by 2 times and shift toward top-left by (2, 2). The back buffer sits exactly
// at the center of the whole viewport. However, depth values of the top-left triangle are >= 0.25.
// They are greater than the depth values in depth buffer, so the top-left triangle is not drawn at
// all. As a result, point(0, 0) is not covered by it. The bottom-right triangle is drawn because
// its depth values are < 0.25. So point(3, 3) is covered by it as usual.
TEST_P(ViewportTest, EnlargeViewportAndShiftToTopLeftAndApplyDepth) {
// Test failing on Linux Vulkan Intel.
// See https://bugs.chromium.org/p/dawn/issues/detail?id=187
DAWN_SKIP_TEST_IF(IsLinux() && IsVulkan() && IsIntel());
ViewportParams viewport = {-2.0, -2.0, 8.0, 8.0, 0.0, 0.5};
TestInfo info = {viewport, BackgroundColor, BottomRightTriangleColor, 0.25};
DoTest(info);
}
// Enlarge the viewport by 2 times and shift toward bottom-right by (2, 2). Point(0, 0) is not
// covered by any triangle. The point(3, 3) tend to be covered by the top-left triangle. However,
// depth values of the top-left triangle are >= 0.25. They are greater than the depth values in
// depth buffer, so the top-left triangle is not drawn at all. As a result, point(3, 3) is not
// covered by any triangle, either.
TEST_P(ViewportTest, EnlargeViewportAndShiftToBottomRightAndApplyDepth) {
ViewportParams viewport = {2.0, 2.0, 8.0, 8.0, 0.0, 0.5};
TestInfo info = {viewport, BackgroundColor, BackgroundColor, 0.25};
DoTest(info);
}
// Shrink the viewport to its half. So point(0, 0) is covered by the top-left triangle, while
// point(3, 3) is not covered by any triangles because the drawing area is too small to cover the
// entire back buffer.
TEST_P(ViewportTest, ShrinkViewport) {
ViewportParams viewport = {0.0, 0.0, 2.0, 2.0, 0.0, 1.0};
TestInfo info = {viewport, TopLeftTriangleColor, BackgroundColor};
DoTest(info);
}
// Shrink the viewport to its half and move toward top-left by (1, 1), So point(0, 0) is covered by
// bottom-right triangle, while point(3, 3) is not covered by any triangles.
TEST_P(ViewportTest, ShrinkViewportAndShiftToTopLeft) {
ViewportParams viewport = {-1.0, -1.0, 2.0, 2.0, 0.0, 1.0};
TestInfo info = {viewport, BottomRightTriangleColor, BackgroundColor};
DoTest(info);
}
// Shrink the viewport to its half and move toward bottom-right by (3, 3), So point(0, 0) is not
// covered by any triangles, and point(3, 3) is covered by the bottom-right triangle.
TEST_P(ViewportTest, ShrinkViewportAndShiftToBottomRight) {
ViewportParams viewport = {3.0, 3.0, 2.0, 2.0, 0.0, 1.0};
TestInfo info = {viewport, BackgroundColor, TopLeftTriangleColor};
DoTest(info);
}
// Shrink the viewport to its half. So point(0, 0) is tend to be covered by top-left triangle.
// However, depth values of the top-left triangle are >= 0.25. They are greater than the depth
// values in depth buffer, so the top-left triangle is not drawn at all. As a result, point(0, 0)
// is not covered by any triangle. Point(3, 3) is not covered by any triangles, either. Because the
// drawing area is too small to cover the entire back buffer.
TEST_P(ViewportTest, ShrinkViewportAndApplyDepth) {
ViewportParams viewport = {0.0, 0.0, 2.0, 2.0, 0.0, 0.5};
TestInfo info = {viewport, BackgroundColor, BackgroundColor, 0.25};
DoTest(info);
}
// Shrink the viewport to its half and move toward top-left by (1, 1), So point(0, 0) is covered by
// the bottom-right triangle, while point(3, 3) is not covered by any triangles.
TEST_P(ViewportTest, ShrinkViewportAndShiftToTopLeftAndApplyDepth) {
// Test failing on Linux Vulkan Intel.
// See https://bugs.chromium.org/p/dawn/issues/detail?id=187
DAWN_SKIP_TEST_IF(IsLinux() && IsVulkan() && IsIntel());
ViewportParams viewport = {-1.0, -1.0, 2.0, 2.0, 0.0, 0.5};
TestInfo info = {viewport, BottomRightTriangleColor, BackgroundColor, 0.25};
DoTest(info);
}
// Shrink the viewport to its half and move toward bottom-right by (3, 3), So point(0, 0) is not
// covered by any triangle. Point(3, 3) is tend to be covered by the top-left triangle. However,
// depth values of the top-left triangle are >= 0.25. They are greater than the depth values in
// depth buffer, so the top-left triangle is not drawn at all. As a result, point(3, 3) is not
// covered by any triangle, either.
TEST_P(ViewportTest, ShrinkViewportAndShiftToBottomRightAndApplyDepth) {
ViewportParams viewport = {3.0, 3.0, 2.0, 2.0, 0.0, 0.5};
TestInfo info = {viewport, BackgroundColor, BackgroundColor, 0.25};
DoTest(info);
}
// X and y have fractions and they are smaller than 0.5, which is the center of point(0, 0). So
// point(0, 0) is covered by the top left triangle as usual.
TEST_P(ViewportTest, DoNotTruncateXAndY) {
// Swiftshader seems to be using 4 bits of subpixel precision for viewport computations but
// advertises 0 bits of precision. This is within the allowed Vulkan behaviors so this test
// should likely be revisited.
DAWN_SKIP_TEST_IF(IsVulkan() && IsSwiftshader());
ViewportParams viewport = {0.49, 0.49, 4.0, 4.0, 0.0, 1.0};
TestInfo info = {viewport, TopLeftTriangleColor, BottomRightTriangleColor};
DoTest(info);
}
// X and y have fractions and they are not smaller than 0.5, which is the center of point(0, 0). So
// point(0, 0) is not covered by any trinagle.
TEST_P(ViewportTest, DoNotTruncateXAndY2) {
ViewportParams viewport = {0.5, 0.5, 4.0, 4.0, 0.0, 1.0};
TestInfo info = {viewport, BackgroundColor, BottomRightTriangleColor};
DoTest(info);
}
// Width and height have fractions and they are greater than 3.5, which is the center of
// point(3, 3). So point(3, 3) is covered by the bottom right triangle as usual.
TEST_P(ViewportTest, DoNotTruncateWidthAndHeight) {
// Test failing on many D3D12 backend and Intel devices.
// It also fails on Vulkan and GL backend on some devices.
// See https://bugs.chromium.org/p/dawn/issues/detail?id=205
// See https://bugs.chromium.org/p/dawn/issues/detail?id=257
DAWN_SKIP_TEST_IF(IsIntel() || !IsMetal());
ViewportParams viewport = {0.0, 0.0, 3.51, 3.51, 0.0, 1.0};
TestInfo info = {viewport, TopLeftTriangleColor, BottomRightTriangleColor};
DoTest(info);
}
// Width and height have fractions and they are not greater than 3.5, which is the center of
// point(3, 3). So point(3, 3) is not covered by any triangle.
TEST_P(ViewportTest, DoNotTruncateWidthAndHeight2) {
ViewportParams viewport = {0.0, 0.0, 3.5, 3.5, 0.0, 1.0};
TestInfo info = {viewport, TopLeftTriangleColor, BackgroundColor};
DoTest(info);
// Test with a 0x0, 0xN and nx0 viewport.
DoEmptyViewportTest(0, 0);
DoEmptyViewportTest(0, 1);
DoEmptyViewportTest(1, 0);
}
DAWN_INSTANTIATE_TEST(ViewportTest,

View File

@ -14,213 +14,123 @@
#include "tests/unittests/validation/ValidationTest.h"
#include "utils/WGPUHelpers.h"
#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_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());
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);
}
// 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) {
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());
// 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);
}
{
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) {
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());
// 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));
}
{
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) {
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 {};