RHI: Replace IsFinalized/Finalize with IsValid/EnsureValid

Unified PSO validation semantics across D3D12 and OpenGL backends:
- IsValid() returns whether PSO is ready to use
- EnsureValid() ensures PSO is valid (compiles if needed)

Behavior by backend:
- D3D12: IsValid=false after creation, true after EnsureValid() with shaders
- OpenGL: IsValid always=true (immediate model)

Also added test_pipeline_state.cpp with 10 tests for RHIPipelineState.
This commit is contained in:
2026-03-25 12:28:33 +08:00
parent ca0d73c197
commit f808f8d197
7 changed files with 225 additions and 106 deletions

View File

@@ -13,6 +13,7 @@ set(TEST_SOURCES
test_command_list.cpp
test_command_queue.cpp
test_shader.cpp
test_pipeline_state.cpp
test_fence.cpp
test_sampler.cpp
${CMAKE_SOURCE_DIR}/tests/opengl/package/src/glad.c

View File

@@ -0,0 +1,172 @@
#include "fixtures/RHITestFixture.h"
#include "XCEngine/RHI/RHIPipelineState.h"
#include <cstring>
using namespace XCEngine::RHI;
TEST_P(RHITestFixture, PipelineState_Create_DefaultDesc) {
GraphicsPipelineDesc desc = {};
RHIPipelineState* pso = GetDevice()->CreatePipelineState(desc);
if (pso != nullptr) {
if (GetBackendType() == RHIType::D3D12) {
EXPECT_FALSE(pso->IsValid());
} else {
EXPECT_TRUE(pso->IsValid());
}
pso->Shutdown();
delete pso;
}
}
TEST_P(RHITestFixture, PipelineState_SetGet_RasterizerState) {
GraphicsPipelineDesc desc = {};
RHIPipelineState* pso = GetDevice()->CreatePipelineState(desc);
ASSERT_NE(pso, nullptr);
RasterizerDesc rasterizer = {};
rasterizer.fillMode = 2;
rasterizer.cullMode = 2;
rasterizer.depthClipEnable = true;
pso->SetRasterizerState(rasterizer);
const RasterizerDesc& retrieved = pso->GetRasterizerState();
EXPECT_EQ(retrieved.cullMode, 2u);
EXPECT_EQ(retrieved.fillMode, 2u);
pso->Shutdown();
delete pso;
}
TEST_P(RHITestFixture, PipelineState_SetGet_BlendState) {
GraphicsPipelineDesc desc = {};
RHIPipelineState* pso = GetDevice()->CreatePipelineState(desc);
ASSERT_NE(pso, nullptr);
BlendDesc blend = {};
blend.blendEnable = true;
blend.srcBlend = 1;
blend.dstBlend = 0;
pso->SetBlendState(blend);
const BlendDesc& retrieved = pso->GetBlendState();
EXPECT_TRUE(retrieved.blendEnable);
EXPECT_EQ(retrieved.srcBlend, 1u);
pso->Shutdown();
delete pso;
}
TEST_P(RHITestFixture, PipelineState_SetGet_DepthStencilState) {
GraphicsPipelineDesc desc = {};
RHIPipelineState* pso = GetDevice()->CreatePipelineState(desc);
ASSERT_NE(pso, nullptr);
DepthStencilStateDesc ds = {};
ds.depthTestEnable = true;
ds.depthWriteEnable = true;
ds.depthFunc = 3;
pso->SetDepthStencilState(ds);
const DepthStencilStateDesc& retrieved = pso->GetDepthStencilState();
EXPECT_TRUE(retrieved.depthTestEnable);
EXPECT_TRUE(retrieved.depthWriteEnable);
EXPECT_EQ(retrieved.depthFunc, 3u);
pso->Shutdown();
delete pso;
}
TEST_P(RHITestFixture, PipelineState_SetGet_InputLayout) {
GraphicsPipelineDesc desc = {};
RHIPipelineState* pso = GetDevice()->CreatePipelineState(desc);
ASSERT_NE(pso, nullptr);
InputElementDesc element = {};
element.semanticName = "POSITION";
element.semanticIndex = 0;
element.format = static_cast<uint32_t>(Format::R32G32B32A32_Float);
element.inputSlot = 0;
element.alignedByteOffset = 0;
InputLayoutDesc layoutDesc = {};
layoutDesc.elements.push_back(element);
pso->SetInputLayout(layoutDesc);
const InputLayoutDesc& retrieved = pso->GetInputLayout();
ASSERT_EQ(retrieved.elements.size(), 1u);
EXPECT_EQ(retrieved.elements[0].semanticName, "POSITION");
pso->Shutdown();
delete pso;
}
TEST_P(RHITestFixture, PipelineState_SetTopology) {
GraphicsPipelineDesc desc = {};
RHIPipelineState* pso = GetDevice()->CreatePipelineState(desc);
ASSERT_NE(pso, nullptr);
pso->SetTopology(3);
pso->Shutdown();
delete pso;
}
TEST_P(RHITestFixture, PipelineState_SetRenderTargetFormats) {
GraphicsPipelineDesc desc = {};
RHIPipelineState* pso = GetDevice()->CreatePipelineState(desc);
ASSERT_NE(pso, nullptr);
uint32_t formats[] = { static_cast<uint32_t>(Format::R8G8B8A8_UNorm) };
pso->SetRenderTargetFormats(1, formats, static_cast<uint32_t>(Format::D24_UNorm_S8_UInt));
pso->Shutdown();
delete pso;
}
TEST_P(RHITestFixture, PipelineState_EnsureValid_IsValid) {
GraphicsPipelineDesc desc = {};
desc.renderTargetFormats[0] = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
desc.depthStencilFormat = static_cast<uint32_t>(Format::D24_UNorm_S8_UInt);
RHIPipelineState* pso = GetDevice()->CreatePipelineState(desc);
ASSERT_NE(pso, nullptr);
if (GetBackendType() == RHIType::D3D12) {
EXPECT_FALSE(pso->IsValid());
pso->EnsureValid();
EXPECT_FALSE(pso->IsValid());
} else {
EXPECT_TRUE(pso->IsValid());
pso->EnsureValid();
EXPECT_TRUE(pso->IsValid());
}
pso->Shutdown();
delete pso;
}
TEST_P(RHITestFixture, PipelineState_Shutdown_Invalidates) {
GraphicsPipelineDesc desc = {};
desc.renderTargetFormats[0] = static_cast<uint32_t>(Format::R8G8B8A8_UNorm);
desc.depthStencilFormat = static_cast<uint32_t>(Format::D24_UNorm_S8_UInt);
RHIPipelineState* pso = GetDevice()->CreatePipelineState(desc);
ASSERT_NE(pso, nullptr);
pso->EnsureValid();
pso->Shutdown();
pso->Shutdown();
delete pso;
}
TEST_P(RHITestFixture, PipelineState_GetType) {
GraphicsPipelineDesc desc = {};
RHIPipelineState* pso = GetDevice()->CreatePipelineState(desc);
ASSERT_NE(pso, nullptr);
PipelineType type = pso->GetType();
EXPECT_EQ(type, PipelineType::Graphics);
pso->Shutdown();
delete pso;
}