diff --git a/tests/Testbed/Reference/StencilBuffer_Set50.Ref.tga b/tests/Testbed/Reference/StencilBuffer_Set50.Ref.tga new file mode 100644 index 0000000000..86a6b3081b Binary files /dev/null and b/tests/Testbed/Reference/StencilBuffer_Set50.Ref.tga differ diff --git a/tests/Testbed/TestDepthBuffer.cpp b/tests/Testbed/TestDepthBuffer.cpp index ba2e3b79ea..b3df6fc16c 100644 --- a/tests/Testbed/TestDepthBuffer.cpp +++ b/tests/Testbed/TestDepthBuffer.cpp @@ -43,7 +43,7 @@ DEF_TEST( DepthBuffer ) RenderTarget* renderTarget = renderer->CreateRenderTarget(renderTargetDesc); renderTarget->SetName("renderTarget"); - // Create PSO for rendering + // Create PSO for rendering to the depth buffer PipelineLayout* psoLayout = renderer->CreatePipelineLayout(Parse("cbuffer(Scene@1):vert:frag")); GraphicsPipelineDescriptor psoDesc; @@ -107,7 +107,9 @@ DEF_TEST( DepthBuffer ) }; const TextureRegion readbackTexRegion{ readbackTexPosition, Extent3D{ 1, 1, 1 } }; - float readbackDepthValue = -1.0f; + constexpr float invalidDepthValue = -1.0f; + + float readbackDepthValue = invalidDepthValue; DstImageDescriptor dstImageDesc; { @@ -128,7 +130,7 @@ DEF_TEST( DepthBuffer ) { dstImageDesc.format = ImageFormat::Depth; dstImageDesc.data = readbackDepthBuffer.data(); - dstImageDesc.dataSize = sizeof(float) * readbackDepthBuffer.size(); + dstImageDesc.dataSize = sizeof(decltype(readbackDepthBuffer)::value_type) * readbackDepthBuffer.size(); dstImageDesc.dataType = DataType::Float32; } renderer->ReadTexture(*readbackTex, TextureRegion{ Offset3D{}, texDesc.extent }, dstImageDesc); diff --git a/tests/Testbed/TestStencilBuffer.cpp b/tests/Testbed/TestStencilBuffer.cpp index 7360f2fa28..c12e371856 100644 --- a/tests/Testbed/TestStencilBuffer.cpp +++ b/tests/Testbed/TestStencilBuffer.cpp @@ -6,11 +6,172 @@ */ #include "Testbed.h" +#include +#include +#include +#include +#include DEF_TEST( StencilBuffer ) { - //todo + const Extent2D resolution{ swapChain->GetResolution() }; + + if (shaders[VSSolid] == nullptr || shaders[PSSolid] == nullptr) + { + Log::Errorf("Missing shaders for backend\n"); + return TestResult::FailedErrors; + } + + // Create texture for readback with depth-only format (D32Float) + TextureDescriptor texDesc; + { + texDesc.format = Format::D24UNormS8UInt; + texDesc.extent.width = resolution.width; + texDesc.extent.height = resolution.height; + texDesc.bindFlags = BindFlags::DepthStencilAttachment; + texDesc.mipLevels = 1; + } + Texture* readbackTex = renderer->CreateTexture(texDesc); + readbackTex->SetName("readbackTex"); + + // Create depth-only render target for scene + RenderTargetDescriptor renderTargetDesc; + { + renderTargetDesc.resolution = resolution; + renderTargetDesc.depthStencilAttachment = readbackTex; + } + RenderTarget* renderTarget = renderer->CreateRenderTarget(renderTargetDesc); + renderTarget->SetName("renderTarget"); + + // Create PSO for rendering to the stencil buffer with dynamic reference value + PipelineLayout* psoLayout = renderer->CreatePipelineLayout(Parse("cbuffer(Scene@1):vert:frag")); + + GraphicsPipelineDescriptor psoDesc; + { + psoDesc.pipelineLayout = psoLayout; + psoDesc.renderPass = renderTarget->GetRenderPass(); + psoDesc.vertexShader = shaders[VSSolid]; + psoDesc.stencil.testEnabled = true; + psoDesc.stencil.referenceDynamic = true; + psoDesc.stencil.front.compareOp = LLGL::CompareOp::Greater; + psoDesc.stencil.front.stencilFailOp = LLGL::StencilOp::Keep; + psoDesc.stencil.front.depthFailOp = LLGL::StencilOp::Keep; + psoDesc.stencil.front.depthPassOp = LLGL::StencilOp::Replace; + psoDesc.rasterizer.cullMode = CullMode::Back; + } + PipelineState* pso = renderer->CreatePipelineState(psoDesc); + + if (const Report* report = pso->GetReport()) + { + if (report->HasErrors()) + { + Log::Errorf("PSO creation failed:\n%s", report->GetText()); + return TestResult::FailedErrors; + } + } + + // Update scene constants + sceneConstants.wMatrix.LoadIdentity(); + Gs::Translate(sceneConstants.wMatrix, Gs::Vector3f{ 0, 0, 2 }); + Gs::RotateFree(sceneConstants.wMatrix, Gs::Vector3f{ 0, 1, 0 }, Gs::Deg2Rad(20.0f)); + + Gs::Matrix4f vMatrix; + vMatrix.LoadIdentity(); + Gs::Translate(vMatrix, Gs::Vector3f{ 0, 0, -3 }); + vMatrix.MakeInverse(); + + Gs::Matrix4f vpMatrix = projection * vMatrix; + sceneConstants.wvpMatrix = vpMatrix * sceneConstants.wMatrix; + + // Render scene + constexpr std::uint32_t stencilRef = 50; + static_assert(stencilRef <= UINT8_MAX, "'stencilRef' must not be greater than UINT8_MAX"); + + cmdBuffer->Begin(); + { + cmdBuffer->UpdateBuffer(*sceneCbuffer, 0, &sceneConstants, sizeof(sceneConstants)); + cmdBuffer->BeginRenderPass(*renderTarget); + { + // Draw scene + cmdBuffer->Clear(ClearFlags::Stencil); + cmdBuffer->SetPipelineState(*pso); + cmdBuffer->SetStencilReference(stencilRef); + cmdBuffer->SetViewport(resolution); + cmdBuffer->SetVertexBuffer(*meshBuffer); + cmdBuffer->SetIndexBuffer(*meshBuffer, Format::R32UInt, models[ModelCube].indexBufferOffset); + cmdBuffer->SetResource(0, *sceneCbuffer); + cmdBuffer->DrawIndexed(models[ModelCube].numIndices, 0); + } + cmdBuffer->EndRenderPass(); + } + cmdBuffer->End(); + + // Readback depth buffer and compare with expected result + const Offset3D readbackTexPosition + { + static_cast(resolution.width/2), + static_cast(resolution.height/2), + 0, + }; + const TextureRegion readbackTexRegion{ readbackTexPosition, Extent3D{ 1, 1, 1 } }; + + constexpr std::uint8_t invalidStencilValue = 0xFF; + + std::uint8_t readbackStencilValue = invalidStencilValue; + + DstImageDescriptor dstImageDesc; + { + dstImageDesc.format = ImageFormat::Stencil; + dstImageDesc.data = &readbackStencilValue; + dstImageDesc.dataSize = sizeof(readbackStencilValue); + dstImageDesc.dataType = DataType::UInt8; + } + renderer->ReadTexture(*readbackTex, readbackTexRegion, dstImageDesc); + + const int deltaStencilValue = std::abs(static_cast(readbackStencilValue) - static_cast(stencilRef)); + + // Match entire depth and create delta heat map + std::vector readbackStencilBuffer; + readbackStencilBuffer.resize(texDesc.extent.width * texDesc.extent.height, 0); + { + dstImageDesc.format = ImageFormat::Stencil; + dstImageDesc.data = readbackStencilBuffer.data(); + dstImageDesc.dataSize = sizeof(decltype(readbackStencilBuffer)::value_type) * readbackStencilBuffer.size(); + dstImageDesc.dataType = DataType::UInt8; + } + renderer->ReadTexture(*readbackTex, TextureRegion{ Offset3D{}, texDesc.extent }, dstImageDesc); + + SaveStencilImageTGA(readbackStencilBuffer, resolution, "StencilBuffer_Set50"); + + const int diff = DiffImagesTGA("StencilBuffer_Set50"); + + // Clear resources + renderer->Release(*pso); + renderer->Release(*psoLayout); + renderer->Release(*renderTarget); + renderer->Release(*readbackTex); + + // Evaluate readback result + if (readbackStencilValue == invalidStencilValue) + { + Log::Errorf("Failed to read back value from stencil buffer texture at center\n"); + return TestResult::FailedErrors; + } + if (deltaStencilValue > 0) + { + Log::Errorf( + "Mismatch between stencil buffer value at center (%d) and expected value (%d): delta = %d\n", + static_cast(readbackStencilValue), static_cast(stencilRef), deltaStencilValue + ); + return TestResult::FailedMismatch; + } + if (diff != 0) + { + Log::Errorf("Mismatch between reference and result images for stencil buffer (diff = %d)\n", diff); + return TestResult::FailedMismatch; + } + return TestResult::Passed; } diff --git a/tests/Testbed/TestbedContext.cpp b/tests/Testbed/TestbedContext.cpp index 5b7295bbb8..911ada6aab 100644 --- a/tests/Testbed/TestbedContext.cpp +++ b/tests/Testbed/TestbedContext.cpp @@ -148,14 +148,14 @@ void TestbedContext::RunAllTests() RUN_TEST( BufferCopy ); RUN_TEST( TextureTypes ); RUN_TEST( TextureWriteAndRead ); - RUN_TEST( TextureCopy ); - RUN_TEST( TextureToBufferCopy ); - RUN_TEST( BufferToTextureCopy ); + //RUN_TEST( TextureCopy ); //TODO: not implemented yet + //RUN_TEST( TextureToBufferCopy ); //TODO: not implemented yet + //RUN_TEST( BufferToTextureCopy ); //TODO: not implemented yet RUN_TEST( DepthBuffer ); RUN_TEST( StencilBuffer ); RUN_TEST( RenderTargetNoAttachments ); RUN_TEST( RenderTarget1Attachment ); - RUN_TEST( RenderTargetNAttachments ); + //RUN_TEST( RenderTargetNAttachments ); //TODO: not implemented yet #undef RUN_TEST @@ -944,6 +944,18 @@ void TestbedContext::SaveDepthImageTGA(const std::vector& image, const LL SaveImageTGA(colors, extent, path + name + ".Result.tga", verbose); } +void TestbedContext::SaveStencilImageTGA(const std::vector& image, const LLGL::Extent2D& extent, const std::string& name) +{ + std::vector colors; + colors.resize(image.size()); + + for (std::size_t i = 0; i < image.size(); ++i) + colors[i] = ColorRGBub{ image[i] }; + + const std::string path = outputDir + moduleName + "/"; + SaveImageTGA(colors, extent, path + name + ".Result.tga", verbose); +} + static int GetColorDiff(std::uint8_t a, std::uint8_t b) { return static_cast(a < b ? b - a : a - b); diff --git a/tests/Testbed/TestbedContext.h b/tests/Testbed/TestbedContext.h index f876e66258..b71c58f8fa 100644 --- a/tests/Testbed/TestbedContext.h +++ b/tests/Testbed/TestbedContext.h @@ -189,6 +189,7 @@ class TestbedContext void SaveDepthImageTGA(const std::vector& image, const LLGL::Extent2D& extent, const std::string& name); void SaveDepthImageTGA(const std::vector& image, const LLGL::Extent2D& extent, const std::string& name, float nearPlane, float farPlane); + void SaveStencilImageTGA(const std::vector& image, const LLGL::Extent2D& extent, const std::string& name); // Creates a heat-map image from the two input filenames and returns the highest difference pixel value. A negative value indicates an error. int DiffImagesTGA(const std::string& name, int threshold = 1, int scale = 1);