blob: 4e1afec2bde5b127b60dc91266f09c9980f65105 [file] [log] [blame]
// Copyright 2021 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "dawn/tests/DawnTest.h"
#include "dawn/utils/ComboRenderPipelineDescriptor.h"
#include "dawn/utils/WGPUHelpers.h"
namespace dawn {
namespace {
constexpr wgpu::TextureFormat kDepthStencilFormat = wgpu::TextureFormat::Depth24PlusStencil8;
constexpr wgpu::TextureFormat kColorFormat = wgpu::TextureFormat::RGBA8Unorm;
constexpr uint32_t kRTWidth = 4;
constexpr uint32_t kRTHeight = 1;
class VertexOnlyRenderPipelineTest : public DawnTest {
protected:
void SetUp() override {
DawnTest::SetUp();
vertexBuffer =
utils::CreateBufferFromData<float>(device, wgpu::BufferUsage::Vertex,
{// The middle back line
-0.5f, 0.0f, 0.0f, 1.0f, 0.5f, 0.0f, 0.0f, 1.0f,
// The right front line
0.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 1.0f, 1.0f,
// The whole in-between line
-1.0f, 0.0f, 0.5f, 1.0f, 1.0f, 0.0f, 0.5f, 1.0f});
// Create a color texture as render target
{
wgpu::TextureDescriptor descriptor;
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.size = {kRTWidth, kRTHeight};
descriptor.format = kColorFormat;
descriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
renderTargetColor = device.CreateTexture(&descriptor);
}
// Create a DepthStencilView for vertex-only pipeline to write and full pipeline to read
{
wgpu::TextureDescriptor descriptor;
descriptor.dimension = wgpu::TextureDimension::e2D;
descriptor.size = {kRTWidth, kRTHeight};
descriptor.format = kDepthStencilFormat;
descriptor.usage = wgpu::TextureUsage::RenderAttachment | wgpu::TextureUsage::CopySrc;
depthStencilTexture = device.CreateTexture(&descriptor);
depthStencilView = depthStencilTexture.CreateView();
}
// The vertex-only render pass to modify the depth and stencil
renderPassDescNoColor = utils::ComboRenderPassDescriptor({}, depthStencilView);
renderPassDescNoColor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
renderPassDescNoColor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load;
// The complete render pass to read the depth and stencil and draw to color attachment
renderPassDescWithColor =
utils::ComboRenderPassDescriptor({renderTargetColor.CreateView()}, depthStencilView);
renderPassDescWithColor.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Load;
renderPassDescWithColor.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Load;
// Create a vertex-only render pipeline that only modify the depth in DepthStencilView, and
// ignore the stencil component
depthPipelineNoFragment =
CreateRenderPipeline(wgpu::CompareFunction::Always, wgpu::StencilOperation::Keep,
wgpu::CompareFunction::Always, true, false);
depthPipelineWithFragment =
CreateRenderPipeline(wgpu::CompareFunction::Always, wgpu::StencilOperation::Keep,
wgpu::CompareFunction::Always, true, true);
// Create a vertex-only render pipeline that only modify the stencil in DepthStencilView,
// and ignore the depth component
stencilPipelineNoFragment =
CreateRenderPipeline(wgpu::CompareFunction::Always, wgpu::StencilOperation::Replace,
wgpu::CompareFunction::Always, false, false);
stencilPipelineWithFragment =
CreateRenderPipeline(wgpu::CompareFunction::Always, wgpu::StencilOperation::Replace,
wgpu::CompareFunction::Always, false, true);
// Create a complete render pipeline that do both depth and stencil tests, and draw to color
// attachment
fullPipeline =
CreateRenderPipeline(wgpu::CompareFunction::Equal, wgpu::StencilOperation::Keep,
wgpu::CompareFunction::GreaterEqual, false, true);
}
wgpu::RenderPipeline CreateRenderPipeline(
wgpu::CompareFunction stencilCompare = wgpu::CompareFunction::Always,
wgpu::StencilOperation stencilPassOp = wgpu::StencilOperation::Keep,
wgpu::CompareFunction depthCompare = wgpu::CompareFunction::Always,
bool writeDepth = false,
bool useFragment = true) {
wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"(
@vertex
fn main(@location(0) pos : vec4f) -> @builtin(position) vec4f {
return pos;
})");
wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"(
@fragment fn main() -> @location(0) vec4f {
return vec4f(0.0, 1.0, 0.0, 1.0);
})");
utils::ComboRenderPipelineDescriptor descriptor;
descriptor.primitive.topology = wgpu::PrimitiveTopology::LineList;
descriptor.vertex.module = vsModule;
descriptor.vertex.bufferCount = 1;
descriptor.cBuffers[0].arrayStride = 4 * sizeof(float);
descriptor.cBuffers[0].attributeCount = 1;
descriptor.cAttributes[0].format = wgpu::VertexFormat::Float32x4;
descriptor.cFragment.module = fsModule;
descriptor.cTargets[0].format = kColorFormat;
wgpu::DepthStencilState* depthStencil = descriptor.EnableDepthStencil(kDepthStencilFormat);
depthStencil->stencilFront.compare = stencilCompare;
depthStencil->stencilBack.compare = stencilCompare;
depthStencil->stencilFront.passOp = stencilPassOp;
depthStencil->stencilBack.passOp = stencilPassOp;
depthStencil->depthWriteEnabled = writeDepth;
depthStencil->depthCompare = depthCompare;
if (!useFragment) {
descriptor.fragment = nullptr;
}
return device.CreateRenderPipeline(&descriptor);
}
void ClearAttachment(const wgpu::CommandEncoder& encoder) {
utils::ComboRenderPassDescriptor clearPass =
utils::ComboRenderPassDescriptor({renderTargetColor.CreateView()}, depthStencilView);
clearPass.cDepthStencilAttachmentInfo.depthLoadOp = wgpu::LoadOp::Clear;
clearPass.cDepthStencilAttachmentInfo.depthClearValue = 0.0f;
clearPass.cDepthStencilAttachmentInfo.stencilLoadOp = wgpu::LoadOp::Clear;
clearPass.cDepthStencilAttachmentInfo.stencilClearValue = 0x0;
for (auto& t : clearPass.cColorAttachments) {
t.loadOp = wgpu::LoadOp::Clear;
t.clearValue = {0.0, 0.0, 0.0, 0.0};
}
auto pass = encoder.BeginRenderPass(&clearPass);
pass.End();
}
// Render resource
wgpu::Buffer vertexBuffer;
// Render target
wgpu::Texture depthStencilTexture;
wgpu::TextureView depthStencilView;
wgpu::Texture renderTargetColor;
// Render result
const utils::RGBA8 filled = utils::RGBA8(0, 255, 0, 255);
const utils::RGBA8 notFilled = utils::RGBA8(0, 0, 0, 0);
// Render pass
utils::ComboRenderPassDescriptor renderPassDescNoColor{};
utils::ComboRenderPassDescriptor renderPassDescWithColor{};
// Render pipeline
wgpu::RenderPipeline stencilPipelineNoFragment;
wgpu::RenderPipeline stencilPipelineWithFragment;
wgpu::RenderPipeline depthPipelineNoFragment;
wgpu::RenderPipeline depthPipelineWithFragment;
wgpu::RenderPipeline fullPipeline;
};
// Test that a vertex-only render pipeline modify the stencil attachment as same as a complete
// render pipeline do.
TEST_P(VertexOnlyRenderPipelineTest, Stencil) {
auto doStencilTest = [&](const wgpu::RenderPassDescriptor* renderPass,
const wgpu::RenderPipeline& pipeline,
const utils::RGBA8& colorExpect) -> void {
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
ClearAttachment(encoder);
{
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(renderPass);
pass.SetPipeline(pipeline);
// Set the stencil reference to a arbitrary value
pass.SetStencilReference(0x42);
pass.SetVertexBuffer(0, vertexBuffer);
// Draw the whole line
pass.Draw(2, 1, 4, 0);
pass.End();
}
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_PIXEL_RGBA8_EQ(colorExpect, renderTargetColor, 0, 0);
EXPECT_PIXEL_RGBA8_EQ(colorExpect, renderTargetColor, 1, 0);
EXPECT_PIXEL_RGBA8_EQ(colorExpect, renderTargetColor, 2, 0);
EXPECT_PIXEL_RGBA8_EQ(colorExpect, renderTargetColor, 3, 0);
// Test that the stencil is set to the chosen value
ExpectAttachmentStencilTestData(depthStencilTexture, kDepthStencilFormat, 4, 1, 0, 0, 0x42);
};
doStencilTest(&renderPassDescWithColor, stencilPipelineWithFragment, filled);
doStencilTest(&renderPassDescNoColor, stencilPipelineNoFragment, notFilled);
}
// Test that a vertex-only render pipeline modify the depth attachment as same as a complete render
// pipeline do.
TEST_P(VertexOnlyRenderPipelineTest, Depth) {
auto doStencilTest = [&](const wgpu::RenderPassDescriptor* renderPass,
const wgpu::RenderPipeline& pipeline,
const utils::RGBA8& colorExpect) -> void {
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
ClearAttachment(encoder);
{
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(renderPass);
pass.SetPipeline(pipeline);
pass.SetStencilReference(0x0);
pass.SetVertexBuffer(0, vertexBuffer);
// Draw the whole line
pass.Draw(2, 1, 4, 0);
pass.End();
}
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
EXPECT_PIXEL_RGBA8_EQ(colorExpect, renderTargetColor, 0, 0);
EXPECT_PIXEL_RGBA8_EQ(colorExpect, renderTargetColor, 1, 0);
EXPECT_PIXEL_RGBA8_EQ(colorExpect, renderTargetColor, 2, 0);
EXPECT_PIXEL_RGBA8_EQ(colorExpect, renderTargetColor, 3, 0);
// Test that the stencil is set to the chosen value
uint8_t expectedStencil = 0;
ExpectAttachmentDepthStencilTestData(depthStencilTexture, kDepthStencilFormat, 4, 1, 0, 0,
{0.5, 0.5, 0.5, 0.5}, &expectedStencil);
};
doStencilTest(&renderPassDescWithColor, depthPipelineWithFragment, filled);
doStencilTest(&renderPassDescNoColor, depthPipelineNoFragment, notFilled);
}
// Test that vertex-only render pipelines and complete render pipelines cooperate correctly in a
// single encoder, each in a render pass
// In this test we first draw with a vertex-only pipeline to set up stencil in a region, than draw
// with another vertex-only pipeline to modify depth in another region, and finally draw with a
// complete pipeline with depth and stencil tests enabled. We check the color result of the final
// draw, and make sure that it correctly use the stencil and depth result set in previous
// vertex-only pipelines.
TEST_P(VertexOnlyRenderPipelineTest, MultiplePass) {
wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
ClearAttachment(encoder);
// Use the stencil pipeline to set the stencil on the middle
{
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescNoColor);
pass.SetStencilReference(0x1);
pass.SetPipeline(stencilPipelineNoFragment);
pass.SetVertexBuffer(0, vertexBuffer);
// Draw the middle line
pass.Draw(2, 1, 0, 0);
pass.End();
}
// Use the depth pipeline to set the depth on the right
{
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescNoColor);
pass.SetStencilReference(0x0);
pass.SetPipeline(depthPipelineNoFragment);
pass.SetVertexBuffer(0, vertexBuffer);
// Draw the right line
pass.Draw(2, 1, 2, 0);
pass.End();
}
// Use the complete pipeline to draw with depth and stencil tests
{
wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPassDescWithColor);
pass.SetStencilReference(0x1);
pass.SetPipeline(fullPipeline);
pass.SetVertexBuffer(0, vertexBuffer);
// Draw the full line with depth and stencil tests
pass.Draw(2, 1, 4, 0);
pass.End();
}
wgpu::CommandBuffer commands = encoder.Finish();
queue.Submit(1, &commands);
// Only the middle left pixel should pass both stencil and depth tests
EXPECT_PIXEL_RGBA8_EQ(notFilled, renderTargetColor, 0, 0);
EXPECT_PIXEL_RGBA8_EQ(filled, renderTargetColor, 1, 0);
EXPECT_PIXEL_RGBA8_EQ(notFilled, renderTargetColor, 2, 0);
EXPECT_PIXEL_RGBA8_EQ(notFilled, renderTargetColor, 3, 0);
}
DAWN_INSTANTIATE_TEST(VertexOnlyRenderPipelineTest,
D3D11Backend(),
D3D11Backend({"use_placeholder_fragment_in_vertex_only_pipeline"}),
D3D12Backend(),
D3D12Backend({"use_placeholder_fragment_in_vertex_only_pipeline"}),
MetalBackend(),
MetalBackend({"use_placeholder_fragment_in_vertex_only_pipeline"}),
OpenGLBackend(),
OpenGLBackend({"use_placeholder_fragment_in_vertex_only_pipeline"}),
OpenGLESBackend(),
OpenGLESBackend({"use_placeholder_fragment_in_vertex_only_pipeline"}),
VulkanBackend(),
VulkanBackend({"use_placeholder_fragment_in_vertex_only_pipeline"}));
} // anonymous namespace
} // namespace dawn