修复D3D12SwapChain初始化bug并添加单元测试

- 修复Initialize(IDXGIFactory4*, ...)重载缺少m_backBuffers初始化的问题
- 新增test_swap_chain.cpp单元测试文件,包含6个SwapChain测试用例
- 更新unit CMakeLists.txt添加test_swap_chain.cpp和Res路径
This commit is contained in:
2026-03-20 17:07:24 +08:00
parent dba3dc23f2
commit c52b4ef35c
3 changed files with 203 additions and 2 deletions

View File

@@ -44,6 +44,13 @@ bool D3D12SwapChain::Initialize(IDXGIFactory4* factory, ID3D12CommandQueue* comm
m_height = height;
m_bufferCount = bufferCount;
m_backBuffers.resize(m_bufferCount);
for (uint32_t i = 0; i < m_bufferCount; ++i) {
ID3D12Resource* resource = nullptr;
m_swapChain->GetBuffer(i, IID_PPV_ARGS(&resource));
m_backBuffers[i].InitializeFromExisting(resource);
}
return true;
}

View File

@@ -18,6 +18,7 @@ set(TEST_SOURCES
test_root_signature.cpp
test_pipeline_state.cpp
test_views.cpp
test_swap_chain.cpp
)
add_executable(d3d12_engine_tests ${TEST_SOURCES})
@@ -38,12 +39,12 @@ target_include_directories(d3d12_engine_tests PRIVATE
)
target_compile_definitions(d3d12_engine_tests PRIVATE
TEST_RESOURCES_DIR="${PROJECT_ROOT_DIR}/tests/RHI/D3D12/integration/Res"
TEST_RESOURCES_DIR="${PROJECT_ROOT_DIR}/tests/RHI/D3D12/integration/minimal/Res"
)
add_custom_command(TARGET d3d12_engine_tests POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${PROJECT_ROOT_DIR}/tests/RHI/D3D12/integration/Res
${PROJECT_ROOT_DIR}/tests/RHI/D3D12/integration/minimal/Res
$<TARGET_FILE_DIR:d3d12_engine_tests>/Res
)

View File

@@ -0,0 +1,193 @@
#include "fixtures/D3D12TestFixture.h"
#include "XCEngine/RHI/D3D12/D3D12SwapChain.h"
#include <windows.h>
using namespace XCEngine::RHI;
class SwapChainTestFixture : public ::testing::Test {
protected:
void SetUp() override {
HRESULT hr = D3D12CreateDevice(
nullptr,
D3D_FEATURE_LEVEL_12_0,
IID_PPV_ARGS(&mDevice)
);
if (FAILED(hr)) {
GTEST_SKIP() << "Failed to create D3D12 device";
return;
}
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
hr = mDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue));
if (FAILED(hr)) {
GTEST_SKIP() << "Failed to create command queue";
return;
}
hr = CreateDXGIFactory1(IID_PPV_ARGS(&mFactory));
if (FAILED(hr)) {
GTEST_SKIP() << "Failed to create DXGI factory";
return;
}
WNDCLASSEX wc = {};
wc.cbSize = sizeof(WNDCLASSEX);
wc.lpfnWndProc = DefWindowProc;
wc.hInstance = GetModuleHandle(nullptr);
wc.lpszClassName = "SwapChainTest";
RegisterClassEx(&wc);
mHWND = CreateWindowEx(
0,
"SwapChainTest",
"SwapChain Test",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
800,
600,
NULL,
NULL,
wc.hInstance,
NULL
);
if (!mHWND) {
GTEST_SKIP() << "Failed to create window";
return;
}
}
void TearDown() override {
if (mCommandQueue) {
WaitForGPU();
}
if (mHWND) {
DestroyWindow(mHWND);
}
mSwapChain.Shutdown();
mCommandQueue.Reset();
mDevice.Reset();
mFactory.Reset();
}
void WaitForGPU() {
if (!mCommandQueue || !mDevice) return;
ComPtr<ID3D12Fence> fence;
UINT64 fenceValue = 1;
HRESULT hr = mDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence));
if (SUCCEEDED(hr)) {
mCommandQueue->Signal(fence.Get(), fenceValue);
if (fence->GetCompletedValue() < fenceValue) {
HANDLE eventHandle = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (eventHandle) {
fence->SetEventOnCompletion(fenceValue, eventHandle);
WaitForSingleObject(eventHandle, INFINITE);
CloseHandle(eventHandle);
}
}
}
}
Microsoft::WRL::ComPtr<ID3D12Device> mDevice;
Microsoft::WRL::ComPtr<ID3D12CommandQueue> mCommandQueue;
Microsoft::WRL::ComPtr<IDXGIFactory4> mFactory;
HWND mHWND = nullptr;
D3D12SwapChain mSwapChain;
};
TEST_F(SwapChainTestFixture, SwapChain_Initialize_FromFactory) {
bool result = mSwapChain.Initialize(
mFactory.Get(),
mCommandQueue.Get(),
mHWND,
800,
600,
2
);
ASSERT_TRUE(result);
}
TEST_F(SwapChainTestFixture, SwapChain_GetBackBuffer_ValidIndex) {
ASSERT_TRUE(mSwapChain.Initialize(
mFactory.Get(),
mCommandQueue.Get(),
mHWND,
800,
600,
2
));
D3D12Texture* backBuffer0 = mSwapChain.GetBackBuffer(0);
ASSERT_NE(backBuffer0, nullptr);
D3D12Texture* backBuffer1 = mSwapChain.GetBackBuffer(1);
ASSERT_NE(backBuffer1, nullptr);
ID3D12Resource* resource0 = backBuffer0->GetResource();
ID3D12Resource* resource1 = backBuffer1->GetResource();
ASSERT_NE(resource0, nullptr);
ASSERT_NE(resource1, nullptr);
ASSERT_NE(resource0, resource1);
}
TEST_F(SwapChainTestFixture, SwapChain_GetBackBuffer_InvalidIndex) {
ASSERT_TRUE(mSwapChain.Initialize(
mFactory.Get(),
mCommandQueue.Get(),
mHWND,
800,
600,
2
));
D3D12Texture* backBuffer = mSwapChain.GetBackBuffer(99);
ASSERT_TRUE(backBuffer == nullptr);
}
TEST_F(SwapChainTestFixture, SwapChain_GetCurrentBackBufferIndex) {
ASSERT_TRUE(mSwapChain.Initialize(
mFactory.Get(),
mCommandQueue.Get(),
mHWND,
800,
600,
2
));
uint32_t index = mSwapChain.GetCurrentBackBufferIndex();
EXPECT_LT(index, 2u);
}
TEST_F(SwapChainTestFixture, SwapChain_Present_DoesNotCrash) {
ASSERT_TRUE(mSwapChain.Initialize(
mFactory.Get(),
mCommandQueue.Get(),
mHWND,
800,
600,
2
));
ASSERT_NO_FATAL_FAILURE(mSwapChain.Present(0, 0));
}
TEST_F(SwapChainTestFixture, SwapChain_Shutdown_Cleanup) {
ASSERT_TRUE(mSwapChain.Initialize(
mFactory.Get(),
mCommandQueue.Get(),
mHWND,
800,
600,
2
));
ASSERT_NO_FATAL_FAILURE(mSwapChain.Shutdown());
}