Skip to content

Commit

Permalink
[Testbed] Improved SceneUpdate test and added more documentation to v…
Browse files Browse the repository at this point in the history
…arious unit tests.

- Added multiple iterations of command buffer encoding to SceneUpdate, to better test updating buffer resources with the CommandBuffer interface.
- Added documentation to SceneUpdate, MipMaps, DualSourceBlending, and BlendStates tests that describe what the intention of the respective test is.
  • Loading branch information
LukasBanana committed Jun 25, 2024
1 parent 6d1e800 commit e5dfadf
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 35 deletions.
4 changes: 4 additions & 0 deletions tests/Testbed/UnitTests/TestBlendStates.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
#include <Gauss/Scale.h>


/*
Renders a matrix of source/destination blend state combinations to ensure the configurations work the same on all backends.
Each combination is tested with two simple geometries (rectangles) that overlap to visualize its blending effect.
*/
DEF_TEST( BlendStates )
{
if (shaders[VSTextured] == nullptr || shaders[PSTextured] == nullptr)
Expand Down
15 changes: 15 additions & 0 deletions tests/Testbed/UnitTests/TestDualSourceBlending.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@
#include <LLGL/Utils/Parse.h>


/*
Renders a fullscreen triangle directly to the swap-chain, i.e. only a single color attachment but the shader has two outputs.
This requires the dual-source blending feature, which is denoted as "Src1" in the blend states.
The shading-languages have different semantics to describe the respective blending outputs:
- HLSL uses the same semantic as if two color attachments where active:
float4 colorA : SV_Target0;
float4 colorB : SV_Target1;
- GLSL uses the same output location but with two different indices:
layout(location = 0, index = 0) out vec4 colorA;
layout(location = 0, index = 1) out vec4 colorB;
- Metal uses a similar semantic as GLSL:
float4 colorA [[color(0), index(0)]];
float4 colorB [[color(0), index(1)]];
*/
DEF_TEST( DualSourceBlending )
{
if (shaders[VSDualSourceBlend] == nullptr || shaders[PSDualSourceBlend] == nullptr)
Expand Down
5 changes: 5 additions & 0 deletions tests/Testbed/UnitTests/TestMipMaps.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ static bool IsPowerOfTwoExtent(const Extent3D& extent)
return (IsPowerOfTwo(extent.width) && IsPowerOfTwo(extent.height) && IsPowerOfTwo(extent.depth));
}

/*
This test doesn't render anything but only evaluates the MIP-map levels of the textures already loaded by the testbed.
Non-power-of-two (NPOT) textures are accepted to use different minification filters (such as box-filter, which can incur undersampling),
which requires a larger threshold when comparing with the reference images.
*/
DEF_TEST( MipMaps )
{
TestResult result = TestResult::Passed;
Expand Down
94 changes: 59 additions & 35 deletions tests/Testbed/UnitTests/TestSceneUpdate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@
#include <Gauss/Scale.h>


/*
Renders a scene (segmented cube) with various different rotations.
The primary command buffer is encoded in several iterations and immediately submitted to the command queue.
Only the last iteration takes a framebuffer capture to ensure the buffer updates are encoded correctly
and not erroneously overridden by faulty CPU/GPU synchronization.
*/
DEF_TEST( SceneUpdate )
{
static TestResult result = TestResult::Passed;
Expand Down Expand Up @@ -62,52 +68,70 @@ DEF_TEST( SceneUpdate )
Gs::Scale(wMatrix, Gs::Vector3f{ 1, scale, 1 });
};

// Render scene
constexpr unsigned numFrames = 10;
const float rotation = static_cast<float>(frame) * 90.0f / static_cast<float>(numFrames - 1);
constexpr unsigned numSceneIterations = 3;

// Render scene
Texture* readbackTex = nullptr;

const IndexedTriangleMesh& mesh = models[ModelCube];

cmdBuffer->Begin();
const float semiRandomRotations[2] = { -10.0f, -5.0f };

// Render the scene several times before taking the frame capture for comparison.
// This ensures that the buffer updates (sceneCbuffer) are encoded correctly and CPU/GPU synchronization works as intended.
for_range(i, numSceneIterations)
{
// Graphics can be set inside and outside a render pass, so test binding this PSO outside the render pass
cmdBuffer->SetVertexBuffer(*meshBuffer);
cmdBuffer->SetIndexBuffer(*meshBuffer, Format::R32UInt, mesh.indexBufferOffset);
cmdBuffer->SetPipelineState(*pso);
const bool isLastIteration = (i + 1 == for_range_end(i));

// First render the object in a semi-random rotation and use the frame dependent rotation in the last iteration
const float rotation =
(
isLastIteration
? static_cast<float>(frame) * 90.0f / static_cast<float>(numFrames - 1)
: semiRandomRotations[i % (sizeof(semiRandomRotations)/sizeof(semiRandomRotations[0]))]
);

cmdBuffer->BeginRenderPass(*swapChain);
cmdBuffer->Begin();
{
// Draw scene
cmdBuffer->Clear(ClearFlags::ColorDepth);
cmdBuffer->SetViewport(opt.resolution);
cmdBuffer->SetResource(0, *sceneCbuffer);

// Draw top part
sceneConstants.solidColor = { 1.0f, 0.7f, 0.6f, 1.0f }; // red
TransformWorldMatrix(sceneConstants.wMatrix, 0.5f, 0.5f, rotation);
cmdBuffer->UpdateBuffer(*sceneCbuffer, 0, &sceneConstants, sizeof(sceneConstants));
cmdBuffer->DrawIndexed(mesh.numIndices, 0);

// Draw middle part
sceneConstants.solidColor = { 0.5f, 1.0f, 0.4f, 1.0f }; // green
TransformWorldMatrix(sceneConstants.wMatrix, -0.25f, 0.25f, rotation);
cmdBuffer->UpdateBuffer(*sceneCbuffer, 0, &sceneConstants, sizeof(sceneConstants));
cmdBuffer->DrawIndexed(mesh.numIndices, 0);

// Draw bottom part
sceneConstants.solidColor = { 0.3f, 0.7f, 1.0f, 1.0f }; // blue
TransformWorldMatrix(sceneConstants.wMatrix, -0.75f, 0.25f, rotation);
cmdBuffer->UpdateBuffer(*sceneCbuffer, 0, &sceneConstants, sizeof(sceneConstants));
cmdBuffer->DrawIndexed(mesh.numIndices, 0);

// Capture framebuffer
readbackTex = CaptureFramebuffer(*cmdBuffer, swapChain->GetColorFormat(), opt.resolution);
// Graphics can be set inside and outside a render pass, so test binding this PSO outside the render pass
cmdBuffer->SetVertexBuffer(*meshBuffer);
cmdBuffer->SetIndexBuffer(*meshBuffer, Format::R32UInt, mesh.indexBufferOffset);
cmdBuffer->SetPipelineState(*pso);

cmdBuffer->BeginRenderPass(*swapChain);
{
// Draw scene
cmdBuffer->Clear(ClearFlags::ColorDepth);
cmdBuffer->SetViewport(opt.resolution);
cmdBuffer->SetResource(0, *sceneCbuffer);

// Draw top part
sceneConstants.solidColor = { 1.0f, 0.7f, 0.6f, 1.0f }; // red
TransformWorldMatrix(sceneConstants.wMatrix, 0.5f, 0.5f, rotation);
cmdBuffer->UpdateBuffer(*sceneCbuffer, 0, &sceneConstants, sizeof(sceneConstants));
cmdBuffer->DrawIndexed(mesh.numIndices, 0);

// Draw middle part
sceneConstants.solidColor = { 0.5f, 1.0f, 0.4f, 1.0f }; // green
TransformWorldMatrix(sceneConstants.wMatrix, -0.25f, 0.25f, rotation);
cmdBuffer->UpdateBuffer(*sceneCbuffer, 0, &sceneConstants, sizeof(sceneConstants));
cmdBuffer->DrawIndexed(mesh.numIndices, 0);

// Draw bottom part
sceneConstants.solidColor = { 0.3f, 0.7f, 1.0f, 1.0f }; // blue
TransformWorldMatrix(sceneConstants.wMatrix, -0.75f, 0.25f, rotation);
cmdBuffer->UpdateBuffer(*sceneCbuffer, 0, &sceneConstants, sizeof(sceneConstants));
cmdBuffer->DrawIndexed(mesh.numIndices, 0);

// Capture framebuffer in last iteration
if (isLastIteration)
readbackTex = CaptureFramebuffer(*cmdBuffer, swapChain->GetColorFormat(), opt.resolution);
}
cmdBuffer->EndRenderPass();
}
cmdBuffer->EndRenderPass();
cmdBuffer->End();
}
cmdBuffer->End();

// Match entire color buffer and create delta heat map
const std::string colorBufferName = "SceneUpdate_Frame" + std::to_string(frame);
Expand Down
4 changes: 4 additions & 0 deletions tests/Testbed/UnitTests/TestShaderErrors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@
#include <string>


/*
Ensure shaders with syntax and/or semantic errors are reported correctly and don't crash the PSO creation.
Erroneous PSOs must report their failure in the LLGL::Report object.
*/
DEF_TEST( ShaderErrors )
{
TestResult result = TestResult::Passed;
Expand Down

0 comments on commit e5dfadf

Please sign in to comment.