From c275f7cf9fab3b3b99b6d5d4a89c2b0262c720b9 Mon Sep 17 00:00:00 2001 From: Austin Eng Date: Fri, 21 Jul 2017 18:36:48 -0400 Subject: [PATCH] end2end: Add depth stencil state tests --- src/tests/CMakeLists.txt | 1 + src/tests/end2end/DepthStencilStateTests.cpp | 525 +++++++++++++++++++ 2 files changed, 526 insertions(+) create mode 100644 src/tests/end2end/DepthStencilStateTests.cpp diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 15b3bb29e7..2a2d9261df 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -70,6 +70,7 @@ add_executable(nxt_end2end_tests ${END2END_TESTS_DIR}/BufferTests.cpp ${END2END_TESTS_DIR}/InputStateTests.cpp ${END2END_TESTS_DIR}/CopyTests.cpp + ${END2END_TESTS_DIR}/DepthStencilStateTests.cpp ${TESTS_DIR}/End2EndTestsMain.cpp ${TESTS_DIR}/NXTTest.cpp ${TESTS_DIR}/NXTTest.h diff --git a/src/tests/end2end/DepthStencilStateTests.cpp b/src/tests/end2end/DepthStencilStateTests.cpp new file mode 100644 index 0000000000..38972ef4d2 --- /dev/null +++ b/src/tests/end2end/DepthStencilStateTests.cpp @@ -0,0 +1,525 @@ + +// Copyright 2017 The NXT 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 "tests/NXTTest.h" + +#include "common/Assert.h" +#include "utils/NXTHelpers.h" + +constexpr static unsigned int kRTSize = 64; + +class DepthStencilStateTest : public NXTTest { + protected: + void SetUp() override { + NXTTest::SetUp(); + + renderpass = device.CreateRenderPassBuilder() + .SetAttachmentCount(2) + .AttachmentSetFormat(0, nxt::TextureFormat::R8G8B8A8Unorm) + .AttachmentSetFormat(1, nxt::TextureFormat::D32FloatS8Uint) + .SetSubpassCount(1) + .SubpassSetColorAttachment(0, 0, 0) + .SubpassSetDepthStencilAttachment(0, 1) + .GetResult(); + + renderTarget = device.CreateTextureBuilder() + .SetDimension(nxt::TextureDimension::e2D) + .SetExtent(kRTSize, kRTSize, 1) + .SetFormat(nxt::TextureFormat::R8G8B8A8Unorm) + .SetMipLevels(1) + .SetAllowedUsage(nxt::TextureUsageBit::OutputAttachment | nxt::TextureUsageBit::TransferSrc) + .SetInitialUsage(nxt::TextureUsageBit::OutputAttachment) + .GetResult(); + + renderTargetView = renderTarget.CreateTextureViewBuilder().GetResult(); + + depthTexture = device.CreateTextureBuilder() + .SetDimension(nxt::TextureDimension::e2D) + .SetExtent(kRTSize, kRTSize, 1) + .SetFormat(nxt::TextureFormat::D32FloatS8Uint) + .SetMipLevels(1) + .SetAllowedUsage(nxt::TextureUsageBit::OutputAttachment) + .SetInitialUsage(nxt::TextureUsageBit::OutputAttachment) + .GetResult(); + + depthTextureView = depthTexture.CreateTextureViewBuilder().GetResult(); + + framebuffer = device.CreateFramebufferBuilder() + .SetRenderPass(renderpass) + .SetAttachment(0, renderTargetView) + .SetAttachment(1, depthTextureView) + .SetDimensions(kRTSize, kRTSize) + .GetResult(); + + vsModule = utils::CreateShaderModule(device, nxt::ShaderStage::Vertex, R"( + #version 450 + layout(set = 0, binding = 0) uniform myBlock { + vec3 color; + float depth; + } myUbo; + void main() { + const vec2 pos[6] = vec2[6]( + vec2(-1.f, 1.f), vec2(-1.f, -1.f), vec2(1.f, -1.f), // front-facing + vec2(-1.f, 1.f), vec2(1.f, 1.f), vec2(1.f, -1.f) // back-facing + ); + gl_Position = vec4(pos[gl_VertexIndex], myUbo.depth, 1.f); + } + )"); + + fsModule = utils::CreateShaderModule(device, nxt::ShaderStage::Fragment, R"( + #version 450 + layout(set = 0, binding = 0) uniform myBlock { + vec3 color; + float depth; + } myUbo; + layout(location = 0) out vec4 fragColor; + void main() { + fragColor = vec4(myUbo.color, 1.f); + } + )"); + + bindGroupLayout = device.CreateBindGroupLayoutBuilder() + .SetBindingsType(nxt::ShaderStageBit::Vertex | nxt::ShaderStageBit::Fragment, nxt::BindingType::UniformBuffer, 0, 1) + .GetResult(); + + pipelineLayout = device.CreatePipelineLayoutBuilder() + .SetBindGroupLayout(0, bindGroupLayout) + .GetResult(); + } + + struct TestSpec { + const nxt::DepthStencilState& depthStencilState; + RGBA8 color; + float depth; + uint32_t stencil; + }; + + // Check whether a depth comparison function works as expected + // The less, equal, greater booleans denote wether the respective triangle should be visible based on the comparison function + void CheckDepthCompareFunction(nxt::CompareFunction compareFunction, bool less, bool equal, bool greater) { + nxt::DepthStencilState baseState = device.CreateDepthStencilStateBuilder() + .SetDepthCompareFunction(nxt::CompareFunction::Always) + .SetDepthWriteEnabled(true) + .GetResult(); + + nxt::DepthStencilState state = device.CreateDepthStencilStateBuilder() + .SetDepthCompareFunction(compareFunction) + .SetDepthWriteEnabled(true) + .GetResult(); + + RGBA8 baseColor = RGBA8(255, 255, 255, 255); + RGBA8 lessColor = RGBA8(255, 0, 0, 255); + RGBA8 equalColor = RGBA8(0, 255, 0, 255); + RGBA8 greaterColor = RGBA8(0, 0, 255, 255); + + // Base triangle at depth 0.5, depth always, depth write enabled + TestSpec base = { baseState, baseColor, 0.5f, 0u }; + + // Draw the base triangle, then a triangle in front of the base triangle with the given depth comparison function + DoTest({ base, { state, lessColor, 0.f, 0u } }, less ? lessColor : baseColor); + + // Draw the base triangle, then a triangle in at the same depth as the base triangle with the given depth comparison function + DoTest({ base, { state, equalColor, 0.5f, 0u } }, equal ? equalColor : baseColor); + + // Draw the base triangle, then a triangle behind the base triangle with the given depth comparison function + DoTest({ base, { state, greaterColor, 1.0f, 0u } }, greater ? greaterColor : baseColor); + } + + // Check whether a stencil comparison function works as expected + // The less, equal, greater booleans denote wether the respective triangle should be visible based on the comparison function + void CheckStencilCompareFunction(nxt::CompareFunction compareFunction, bool less, bool equal, bool greater) { + nxt::DepthStencilState baseState = device.CreateDepthStencilStateBuilder() + .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Always, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep, nxt::StencilOperation::Replace) + .GetResult(); + + nxt::DepthStencilState state = device.CreateDepthStencilStateBuilder() + .SetStencilFunction(nxt::Face::Both, compareFunction, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep) + .GetResult(); + + RGBA8 baseColor = RGBA8(255, 255, 255, 255); + RGBA8 lessColor = RGBA8(255, 0, 0, 255); + RGBA8 equalColor = RGBA8(0, 255, 0, 255); + RGBA8 greaterColor = RGBA8(0, 0, 255, 255); + + // Base triangle with stencil reference 1 + TestSpec base = { baseState, baseColor, 0.0f, 1u }; + + // Draw the base triangle, then a triangle with stencil reference 0 with the given stencil comparison function + DoTest({ base, { state, lessColor, 0.f, 0u } }, less ? lessColor : baseColor); + + // Draw the base triangle, then a triangle with stencil reference 1 with the given stencil comparison function + DoTest({ base, { state, equalColor, 0.f, 1u } }, equal ? equalColor : baseColor); + + // Draw the base triangle, then a triangle with stencil reference 2 with the given stencil comparison function + DoTest({ base, { state, greaterColor, 0.f, 2u } }, greater ? greaterColor : baseColor); + } + + // Given the provided `initialStencil` and `reference`, check that applying the `stencilOperation` produces the `expectedStencil` + void CheckStencilOperation(nxt::StencilOperation stencilOperation, uint32_t initialStencil, uint32_t reference, uint32_t expectedStencil) { + nxt::DepthStencilState baseState = device.CreateDepthStencilStateBuilder() + .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Always, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep, nxt::StencilOperation::Replace) + .GetResult(); + + nxt::DepthStencilState state = device.CreateDepthStencilStateBuilder() + .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Always, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep, stencilOperation) + .GetResult(); + + CheckStencil({ + // Wipe the stencil buffer with the initialStencil value + { baseState, RGBA8(255, 255, 255, 255), 0.f, initialStencil }, + + // Draw a triangle with the provided stencil operation and reference + { state, RGBA8(255, 0, 0, 255), 0.f, reference }, + }, expectedStencil); + } + + // Draw a list of test specs, and check if the stencil value is equal to the expected value + void CheckStencil(std::vector testParams, uint32_t expectedStencil) { + nxt::DepthStencilState state = device.CreateDepthStencilStateBuilder() + .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Equal, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep) + .GetResult(); + + testParams.push_back({ state, RGBA8(0, 255, 0, 255), 0, expectedStencil }); + DoTest(testParams, RGBA8(0, 255, 0, 255)); + } + + // Each test param represents a pair of triangles with a color, depth, stencil value, and depthStencil state, one frontfacing, one backfacing + // Draw the triangles in order and check the expected colors for the frontfaces and backfaces + void DoTest(const std::vector &testParams, const RGBA8& expectedFront, const RGBA8& expectedBack) { + nxt::CommandBufferBuilder builder = device.CreateCommandBufferBuilder(); + + struct TriangleData { + float color[3]; + float depth; + }; + + renderTarget.TransitionUsage(nxt::TextureUsageBit::OutputAttachment); + builder.BeginRenderPass(renderpass, framebuffer) + .BeginRenderSubpass(); + + for (size_t i = 0; i < testParams.size(); ++i) { + const TestSpec& test = testParams[i]; + + TriangleData data = { + { static_cast(test.color.r) / 255.f, static_cast(test.color.g) / 255.f, static_cast(test.color.b) / 255.f }, + test.depth, + }; + // Upload a buffer for each triangle's depth and color data + nxt::Buffer buffer = utils::CreateFrozenBufferFromData(device, &data, sizeof(TriangleData), nxt::BufferUsageBit::Uniform); + + nxt::BufferView view = buffer.CreateBufferViewBuilder() + .SetExtent(0, sizeof(TriangleData)) + .GetResult(); + + // Create a bind group for the data + nxt::BindGroup bindGroup = device.CreateBindGroupBuilder() + .SetLayout(bindGroupLayout) + .SetUsage(nxt::BindGroupUsage::Frozen) + .SetBufferViews(0, 1, &view) + .GetResult(); + + // Create a pipeline for the triangles with the test spec's depth stencil state + nxt::RenderPipeline pipeline = device.CreateRenderPipelineBuilder() + .SetSubpass(renderpass, 0) + .SetLayout(pipelineLayout) + .SetStage(nxt::ShaderStage::Vertex, vsModule, "main") + .SetStage(nxt::ShaderStage::Fragment, fsModule, "main") + .SetDepthStencilState(test.depthStencilState) + .GetResult(); + + builder.SetRenderPipeline(pipeline) + .SetStencilReference(test.stencil) // Set the stencil reference + .SetBindGroup(0, bindGroup) // Set the bind group which contains color and depth data + .DrawArrays(6, 1, 0, 0); + } + + nxt::CommandBuffer commands = builder + .EndRenderSubpass() + .EndRenderPass() + .GetResult(); + + queue.Submit(1, &commands); + + EXPECT_PIXEL_RGBA8_EQ(expectedFront, renderTarget, kRTSize / 4, kRTSize / 2) << "Front face check failed"; + EXPECT_PIXEL_RGBA8_EQ(expectedBack, renderTarget, 3 * kRTSize / 4, kRTSize / 2) << "Back face check failed"; + } + + void DoTest(const std::vector &testParams, const RGBA8& expected) { + DoTest(testParams, expected, expected); + } + + nxt::RenderPass renderpass; + nxt::Texture renderTarget; + nxt::Texture depthTexture; + nxt::TextureView renderTargetView; + nxt::TextureView depthTextureView; + nxt::Framebuffer framebuffer; + nxt::ShaderModule vsModule; + nxt::ShaderModule fsModule; + nxt::BindGroupLayout bindGroupLayout; + nxt::PipelineLayout pipelineLayout; +}; + +// Test compilation and usage of the fixture +TEST_P(DepthStencilStateTest, Basic) { + nxt::DepthStencilState state = device.CreateDepthStencilStateBuilder() + .GetResult(); + + DoTest({ + { state, RGBA8(0, 255, 0, 255), 0.5f, 0u }, + }, RGBA8(0, 255, 0, 255)); +} + +// Test defaults: depth and stencil tests disabled +TEST_P(DepthStencilStateTest, DepthStencilDisabled) { + nxt::DepthStencilState state = device.CreateDepthStencilStateBuilder() + .GetResult(); + + TestSpec specs[3] = { + { state, RGBA8(255, 0, 0, 255), 0.0f, 0u }, + { state, RGBA8(0, 255, 0, 255), 0.5f, 0u }, + { state, RGBA8(0, 0, 255, 255), 1.0f, 0u }, + }; + + // Test that for all combinations, the last triangle drawn is the one visible + // We check against three triangles because the stencil test may modify results + for (uint32_t last = 0; last < 3; ++last) { + uint32_t i = (last + 1) % 3; + uint32_t j = (last + 2) % 3; + DoTest({ specs[i], specs[j], specs[last] }, specs[last].color); + DoTest({ specs[j], specs[i], specs[last] }, specs[last].color); + } +} + +// The following tests check that each depth comparison function works +TEST_P(DepthStencilStateTest, DepthAlways) { + CheckDepthCompareFunction(nxt::CompareFunction::Always , true, true, true); +} + +TEST_P(DepthStencilStateTest, DepthEqual) { + CheckDepthCompareFunction(nxt::CompareFunction::Equal, false, true, false); +} + +TEST_P(DepthStencilStateTest, DepthGreater) { + CheckDepthCompareFunction(nxt::CompareFunction::Greater, false, false, true); +} + +TEST_P(DepthStencilStateTest, DepthGreaterEqual) { + CheckDepthCompareFunction(nxt::CompareFunction::GreaterEqual, false, true, true); +} + +TEST_P(DepthStencilStateTest, DepthLess) { + CheckDepthCompareFunction(nxt::CompareFunction::Less, true, false, false); +} + +TEST_P(DepthStencilStateTest, DepthLessEqual) { + CheckDepthCompareFunction(nxt::CompareFunction::LessEqual, true, true, false); +} + +TEST_P(DepthStencilStateTest, DepthNever) { + CheckDepthCompareFunction(nxt::CompareFunction::Never, false, false, false); +} + +TEST_P(DepthStencilStateTest, DepthNotEqual) { + CheckDepthCompareFunction(nxt::CompareFunction::NotEqual, true, false, true); +} + +// Test that disabling depth writes works and leaves the depth buffer unchanged +TEST_P(DepthStencilStateTest, DepthWriteDisabled) { + nxt::DepthStencilState baseState = device.CreateDepthStencilStateBuilder() + .SetDepthCompareFunction(nxt::CompareFunction::Always) + .SetDepthWriteEnabled(true) + .GetResult(); + + nxt::DepthStencilState noDepthWrite = device.CreateDepthStencilStateBuilder() + .SetDepthCompareFunction(nxt::CompareFunction::Always) + .SetDepthWriteEnabled(false) + .GetResult(); + + nxt::DepthStencilState checkState = device.CreateDepthStencilStateBuilder() + .SetDepthCompareFunction(nxt::CompareFunction::Equal) + .GetResult(); + + DoTest({ + { baseState, RGBA8(255, 255, 255, 255), 1.f, 0u }, // Draw a base triangle with depth enabled + { noDepthWrite, RGBA8(0, 0, 0, 255), 0.f, 0u }, // Draw a second triangle without depth enabled + { checkState, RGBA8(0, 255, 0, 255), 1.f, 0u }, // Draw a third triangle which should occlude the second even though it is behind it + }, RGBA8(0, 255, 0, 255)); +} + +// The following tests check that each stencil comparison function works +TEST_P(DepthStencilStateTest, StencilAlways) { + CheckStencilCompareFunction(nxt::CompareFunction::Always, true, true, true); +} + +TEST_P(DepthStencilStateTest, StencilEqual) { + CheckStencilCompareFunction(nxt::CompareFunction::Equal, false, true, false); +} + +TEST_P(DepthStencilStateTest, StencilGreater) { + CheckStencilCompareFunction(nxt::CompareFunction::Greater, false, false, true); +} + +TEST_P(DepthStencilStateTest, StencilGreaterEqual) { + CheckStencilCompareFunction(nxt::CompareFunction::GreaterEqual, false, true, true); +} + +TEST_P(DepthStencilStateTest, StencilLess) { + CheckStencilCompareFunction(nxt::CompareFunction::Less, true, false, false); +} + +TEST_P(DepthStencilStateTest, StencilLessEqual) { + CheckStencilCompareFunction(nxt::CompareFunction::LessEqual, true, true, false); +} + +TEST_P(DepthStencilStateTest, StencilNever) { + CheckStencilCompareFunction(nxt::CompareFunction::Never, false, false, false); +} + +TEST_P(DepthStencilStateTest, StencilNotEqual) { + CheckStencilCompareFunction(nxt::CompareFunction::NotEqual, true, false, true); +} + +// The following tests check that each stencil operation works +TEST_P(DepthStencilStateTest, StencilKeep) { + CheckStencilOperation(nxt::StencilOperation::Keep, 1, 3, 1); +} + +TEST_P(DepthStencilStateTest, StencilZero) { + CheckStencilOperation(nxt::StencilOperation::Zero, 1, 3, 0); +} + +TEST_P(DepthStencilStateTest, StencilReplace) { + CheckStencilOperation(nxt::StencilOperation::Replace, 1, 3, 3); +} + +TEST_P(DepthStencilStateTest, StencilInvert) { + CheckStencilOperation(nxt::StencilOperation::Invert, 0xf0, 3, 0x0f); +} + +TEST_P(DepthStencilStateTest, StencilIncrementClamp) { + CheckStencilOperation(nxt::StencilOperation::IncrementClamp, 1, 3, 2); + CheckStencilOperation(nxt::StencilOperation::IncrementClamp, 0xff, 3, 0xff); +} + +TEST_P(DepthStencilStateTest, StencilIncrementWrap) { + CheckStencilOperation(nxt::StencilOperation::IncrementWrap, 1, 3, 2); + CheckStencilOperation(nxt::StencilOperation::IncrementWrap, 0xff, 3, 0); +} + +TEST_P(DepthStencilStateTest, StencilDecrementClamp) { + CheckStencilOperation(nxt::StencilOperation::DecrementClamp, 1, 3, 0); + CheckStencilOperation(nxt::StencilOperation::DecrementClamp, 0, 3, 0); +} + +TEST_P(DepthStencilStateTest, StencilDecrementWrap) { + CheckStencilOperation(nxt::StencilOperation::DecrementWrap, 1, 3, 0); + CheckStencilOperation(nxt::StencilOperation::DecrementWrap, 0, 3, 0xff); +} + +// Check that the setting a stencil read mask works +TEST_P(DepthStencilStateTest, StencilReadMask) { + nxt::DepthStencilState baseState = device.CreateDepthStencilStateBuilder() + .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Always, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep, nxt::StencilOperation::Replace) + .GetResult(); + + nxt::DepthStencilState state = device.CreateDepthStencilStateBuilder() + .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Equal, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep) + .SetStencilMask(0x2, 0xff) + .GetResult(); + + RGBA8 baseColor = RGBA8(255, 255, 255, 255); + RGBA8 red = RGBA8(255, 0, 0, 255); + RGBA8 green = RGBA8(0, 255, 0, 255); + + TestSpec base = { baseState, baseColor, 0.5f, 3u }; // Base triangle to set the stencil to 3 + DoTest({ base, { state, red, 0.f, 1u } }, baseColor); // Triangle with stencil reference 1 and read mask 2 does not draw because (3 & 2 != 1) + DoTest({ base, { state, green, 0.f, 2u } }, green); // Triangle with stencil reference 2 and read mask 2 draws because (3 & 2 == 2) +} + +// Check that setting a stencil write mask works +TEST_P(DepthStencilStateTest, StencilWriteMask) { + nxt::DepthStencilState baseState = device.CreateDepthStencilStateBuilder() + .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Always, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep, nxt::StencilOperation::Replace) + .SetStencilMask(0xff, 0x1) + .GetResult(); + + nxt::DepthStencilState state = device.CreateDepthStencilStateBuilder() + .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Equal, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep) + .GetResult(); + + RGBA8 baseColor = RGBA8(255, 255, 255, 255); + RGBA8 green = RGBA8(0, 255, 0, 255); + + TestSpec base = { baseState, baseColor, 0.5f, 3u }; // Base triangle with stencil reference 3 and mask 1 to set the stencil 1 + DoTest({ base, { state, green, 0.f, 2u } }, baseColor); // Triangle with stencil reference 2 does not draw because 2 != (3 & 1) + DoTest({ base, { state, green, 0.f, 1u } }, green); // Triangle with stencil reference 1 draws because 1 == (3 & 1) +} + +// Test that the stencil operation is executed on stencil fail +TEST_P(DepthStencilStateTest, StencilFail) { + nxt::DepthStencilState baseState = device.CreateDepthStencilStateBuilder() + .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Always, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep, nxt::StencilOperation::Replace) + .GetResult(); + + nxt::DepthStencilState state = device.CreateDepthStencilStateBuilder() + .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Less, nxt::StencilOperation::Replace, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep) + .GetResult(); + + CheckStencil({ + { baseState, RGBA8(255, 255, 255, 255), 1.f, 1 }, // Triangle to set stencil value to 1 + { state, RGBA8(0, 0, 0, 255), 0.f, 2 } // Triangle with stencil reference 2 fails the Less comparison function + }, 2); // Replace the stencil on failure, so it should be 2 +} + +// Test that the stencil operation is executed on stencil pass, depth fail +TEST_P(DepthStencilStateTest, StencilDepthFail) { + nxt::DepthStencilState baseState = device.CreateDepthStencilStateBuilder() + .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Always, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep, nxt::StencilOperation::Replace) + .SetDepthWriteEnabled(true) + .GetResult(); + + nxt::DepthStencilState state = device.CreateDepthStencilStateBuilder() + .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Greater, nxt::StencilOperation::Keep, nxt::StencilOperation::Replace, nxt::StencilOperation::Keep) + .SetDepthWriteEnabled(true) + .SetDepthCompareFunction(nxt::CompareFunction::Less) + .GetResult(); + + CheckStencil({ + { baseState, RGBA8(255, 255, 255, 255), 0.f, 1 }, // Triangle to set stencil value to 1. Depth is 0 + { state, RGBA8(0, 0, 0, 255), 1.f, 2 } }, // Triangle with stencil reference 2 passes the Greater comparison function. At depth 1, it fails the Less depth test + 2); // Replace the stencil on stencil pass, depth failure, so it should be 2 +} + +// Test that the stencil operation is executed on stencil pass, depth pass +TEST_P(DepthStencilStateTest, StencilDepthPass) { + nxt::DepthStencilState baseState = device.CreateDepthStencilStateBuilder() + .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Always, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep, nxt::StencilOperation::Replace) + .SetDepthWriteEnabled(true) + .GetResult(); + + nxt::DepthStencilState state = device.CreateDepthStencilStateBuilder() + .SetStencilFunction(nxt::Face::Both, nxt::CompareFunction::Greater, nxt::StencilOperation::Keep, nxt::StencilOperation::Keep, nxt::StencilOperation::Replace) + .SetDepthWriteEnabled(true) + .SetDepthCompareFunction(nxt::CompareFunction::Less) + .GetResult(); + + CheckStencil({ + { baseState, RGBA8(255, 255, 255, 255), 1.f, 1 }, // Triangle to set stencil value to 1. Depth is 0 + { state, RGBA8(0, 0, 0, 255), 0.f, 2 } }, // Triangle with stencil reference 2 passes the Greater comparison function. At depth 0, it pass the Less depth test +2); // Replace the stencil on stencil pass, depth pass, so it should be 2 +} + +NXT_INSTANTIATE_TEST(DepthStencilStateTest, MetalBackend, OpenGLBackend)