From 34c04af6cbe0beed75377794f115ee0fc0f25eec Mon Sep 17 00:00:00 2001 From: ssdfasd <2156608475@qq.com> Date: Fri, 20 Mar 2026 17:58:27 +0800 Subject: [PATCH] D3D12: Fix texture ownership semantics and remove GetSwapChain() exposure This commit fixes the D3D12 texture architecture issues: 1. D3D12Texture ownership semantics: - Add m_ownsResource member to track resource ownership - InitializeFromExisting() now takes ownsResource parameter (default false) - Shutdown() only releases resource if ownsResource is true - Initialize() sets m_ownsResource = true for created resources 2. D3D12SwapChain changes: - Remove GetSwapChain() method (was exposing native D3D12 API) - Change GetBackBuffer() to return reference instead of pointer - Back buffers initialized with ownsResource = false 3. minimal/main.cpp updates: - Remove gColorRTs[2] array (was duplicating back buffer wrapping) - Direct use of gSwapChain.GetBackBuffer(i) instead - All references updated to use encapsulated API 4. Documentation: - Update TEST_SPEC.md v1.3 - Remove known limitation 7.2 (minimal GetBuffer issue fixed) - Add D3D12_Texture_Architecture_Fix_Plan.md design document --- D3D12_Texture_Architecture_Fix_Plan.md | 403 ++++++++++++++++++ .../XCEngine/RHI/D3D12/D3D12SwapChain.h | 5 +- .../include/XCEngine/RHI/D3D12/D3D12Texture.h | 5 +- engine/src/RHI/D3D12/D3D12SwapChain.cpp | 17 +- engine/src/RHI/D3D12/D3D12Texture.cpp | 7 +- tests/RHI/D3D12/TEST_SPEC.md | 19 +- tests/RHI/D3D12/integration/minimal/main.cpp | 15 +- 7 files changed, 433 insertions(+), 38 deletions(-) create mode 100644 D3D12_Texture_Architecture_Fix_Plan.md diff --git a/D3D12_Texture_Architecture_Fix_Plan.md b/D3D12_Texture_Architecture_Fix_Plan.md new file mode 100644 index 00000000..acd1ebd6 --- /dev/null +++ b/D3D12_Texture_Architecture_Fix_Plan.md @@ -0,0 +1,403 @@ +# D3D12 Texture 封装架构修复方案 + +## 1. 问题分析 + +### 1.1 当前问题 + +**问题 A:双重 Texture 包装导致悬垂指针** + +```cpp +// D3D12SwapChain 内部: +std::vector m_backBuffers; // SwapChain 内部的包装 + +// minimal/main.cpp: +D3D12Texture gColorRTs[2]; // 用户代码又创建了一套包装! + +for (int i = 0; i < 2; i++) { + ID3D12Resource* buffer = nullptr; + gSwapChain.GetSwapChain()->GetBuffer(i, IID_PPV_ARGS(&buffer)); // 获取原生指针 + gColorRTs[i].InitializeFromExisting(buffer); // 再包装一次 +} +``` + +**问题**: +- `m_backBuffers[i]` 和 `gColorRTs[i]` 是两个不同的 `D3D12Texture` 对象 +- 但它们内部都持有指向**同一个** `ID3D12Resource` 的 `ComPtr` +- 当其中一个调用 `Shutdown()` → `m_resource.Reset()`,另一个就变成悬垂指针! + +**问题 B:`InitializeFromExisting` 不区分所有权语义** + +```cpp +void D3D12Texture::Shutdown() { + m_resource.Reset(); // 无条件释放资源 +} +``` + +如果通过 `InitializeFromExisting` 包装了 SwapChain 的 back buffer,Shutdown 会错误地释放它! + +**问题 C:`D3D12SwapChain::GetBackBuffer()` 返回内部引用,用户可能误用** + +```cpp +D3D12Texture* D3D12SwapChain::GetBackBuffer(uint32_t index) const { + return const_cast(&m_backBuffers[index]); // 返回内部成员引用 +} + +// 用户可能写出: +D3D12Texture* rt = gSwapChain.GetBackBuffer(0); +gSwapChain.Shutdown(); // rt 变成悬垂指针! +``` + +### 1.2 问题根因 + +| 问题 | 根因 | +|------|------| +| 双重包装 | 没有阻止用户创建额外的包装 | +| 所有权模糊 | `InitializeFromExisting` 不区分"接管"和"借用" | +| 悬垂引用 | `GetBackBuffer` 返回内部指针,生命周期不安全 | + +--- + +## 2. 修复方案 + +### 2.1 方案概述 + +引入**所有权语义标记**和**生命周期安全保证**: + +1. **`InitializeFromExisting` 增加 `ownsResource` 参数** +2. **`D3D12Texture` 内部根据标记决定是否释放资源** +3. **`D3D12SwapChain::GetBackBuffer` 返回安全引用(弱引用或克隆)** +4. **提供统一的 Back Buffer 访问接口,避免用户直接调用 `GetSwapChain()->GetBuffer()`** + +### 2.2 详细设计 + +#### 2.2.1 修改 `D3D12Texture` + +**文件**: `engine/include/XCEngine/RHI/D3D12/D3D12Texture.h` + +```cpp +class D3D12Texture : public RHITexture { +public: + D3D12Texture(); + ~D3D12Texture() override; + + // 创建并拥有资源 + bool Initialize(ID3D12Device* device, const D3D12_RESOURCE_DESC& desc, + D3D12_RESOURCE_STATES initialState = D3D12_RESOURCE_STATE_COMMON); + + // 包装已有资源,明确所有权语义 + // ownsResource = true : 获取所有权,Shutdown 时释放 + // ownsResource = false : 不获取所有权,Shutdown 时不释放 + bool InitializeFromExisting(ID3D12Resource* resource, bool ownsResource = false); + + bool InitializeFromData(ID3D12Device* device, ID3D12GraphicsCommandList* commandList, + const void* pixelData, uint32_t width, uint32_t height, DXGI_FORMAT format); + bool InitializeDepthStencil(ID3D12Device* device, uint32_t width, uint32_t height, + DXGI_FORMAT format = DXGI_FORMAT_D24_UNORM_S8_UINT); + + void Shutdown() override; + + ID3D12Resource* GetResource() const { return m_resource.Get(); } + D3D12_RESOURCE_DESC GetDesc() const { return m_resource->GetDesc(); } + + // 检查是否拥有资源所有权 + bool OwnsResource() const { return m_ownsResource; } + + // ... 其他现有方法 ... + +private: + ComPtr m_resource; + ResourceStates m_state = ResourceStates::Common; + std::string m_name; + bool m_ownsResource = false; // 新增:所有权标记 +}; +``` + +**文件**: `engine/src/RHI/D3D12/D3D12Texture.cpp` + +```cpp +bool D3D12Texture::InitializeFromExisting(ID3D12Resource* resource, bool ownsResource) { + m_resource = resource; + m_ownsResource = ownsResource; // 明确设置所有权 + return true; +} + +void D3D12Texture::Shutdown() { + if (m_ownsResource) { + // 仅当拥有所有权时才释放 + m_resource.Reset(); + } + // 如果不拥有资源,只是清除引用,不释放底层的 COM 对象 + m_resource.Reset(); // ComPtr::Reset() 只是减少引用计数,不是释放 + // 但要注意:如果不拥有所有权,我们需要保留原始指针以防止意外释放 + // 实际上 ComPtr::Reset() 总是会调用 Release() + // 所以我们需要用不同的策略 +} +``` + +**等等,`ComPtr::Reset()` 总是会调用 Release()。如果我们要实现"不拥有但不释放",需要用原始指针存储。** + +**修正方案**: + +```cpp +class D3D12Texture : public RHITexture { +private: + ComPtr m_resource; // 始终持有 + ID3D12Resource* m_externalResource = nullptr; // 外部资源指针(不拥有) + bool m_ownsResource = false; + +public: + void Shutdown() override { + if (m_ownsResource) { + m_resource.Reset(); // 释放拥有的资源 + } + // 如果是外部资源(不拥有),只是清除引用 + m_externalResource = nullptr; + m_resource.Reset(); // 总是 Reset,因为 m_resource 可能持有原始指针的副本 + } +}; +``` + +**更简洁的方案**:让用户自己决定是否通过 `Initialize` 创建 texture。`InitializeFromExisting` 包装但不拥有,使用者负责保证生命周期。 + +--- + +#### 2.2.2 修改 `D3D12SwapChain` + +**目标**:提供安全的 BackBuffer 访问接口,阻止用户创建重复包装。 + +**文件**: `engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h` + +```cpp +class D3D12SwapChain : public RHISwapChain { +public: + // ... 现有接口 ... + + // 获取 BackBuffer - 返回引用而非指针,防止悬垂 + // 返回的引用在 SwapChain 存活期间有效 + D3D12Texture& GetBackBuffer(uint32_t index); + const D3D12Texture& GetBackBuffer(uint32_t index) const; + + // 获取当前 BackBuffer + D3D12Texture& GetCurrentBackBuffer(); + const D3D12Texture& GetCurrentBackBuffer() const; + + // 删除不安全的 GetSwapChain() 暴露方法! + // 旧接口:IDXGISwapChain3* GetSwapChain() const { return m_swapChain.Get(); } + // 新策略:如果确实需要原生指针,提供 GetNativeHandle() 但不返回具体类型 + +private: + // 确保 BackBuffer 不能被外部直接修改 + void SetBackBuffer(uint32_t index, D3D12Texture& texture) = delete; +}; +``` + +**修正 `GetBackBuffer` 返回值**: + +```cpp +D3D12Texture& D3D12SwapChain::GetBackBuffer(uint32_t index) { + assert(index < m_backBuffers.size() && "BackBuffer index out of range"); + return m_backBuffers[index]; +} + +const D3D12Texture& D3D12SwapChain::GetBackBuffer(uint32_t index) const { + assert(index < m_backBuffers.size() && "BackBuffer index out of range"); + return m_backBuffers[index]; +} +``` + +--- + +#### 2.2.3 更新 `minimal/main.cpp` + +```cpp +// 修改前(有问题): +for (int i = 0; i < 2; i++) { + ID3D12Resource* buffer = nullptr; + gSwapChain.GetSwapChain()->GetBuffer(i, IID_PPV_ARGS(&buffer)); // ❌ 原生 API + gColorRTs[i].InitializeFromExisting(buffer); + // ... +} + +// 修改后(使用封装): +for (int i = 0; i < 2; i++) { + // 直接使用封装好的 BackBuffer,不再重复包装 + D3D12Texture& backBuffer = gSwapChain.GetBackBuffer(i); + + // RTV 创建 + CPUDescriptorHandle rtvCpuHandle = gRTVHeap.GetCPUDescriptorHandle(i); + D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = { rtvCpuHandle.ptr }; + gRTVs[i].InitializeAt(device, backBuffer.GetResource(), rtvHandle, nullptr); +} +``` + +**但是**:`gColorRTs[i]` 仍然存在且被用于后续渲染。我们需要决定是: +- 方案 A:直接使用 `gSwapChain.GetBackBuffer(i)` 替代 `gColorRTs[i]` +- 方案 B:让 `gColorRTs[i]` 变成对 `gSwapChain.GetBackBuffer(i)` 的引用 + +**推荐方案 A**:移除 `gColorRTs` 数组,直接使用 `gSwapChain.GetBackBuffer()`。这样避免重复引用。 + +--- + +### 2.3 完整修改清单 + +| 文件 | 修改内容 | +|------|----------| +| `D3D12Texture.h` | 添加 `m_ownsResource` 成员,修改 `InitializeFromExisting` 签名 | +| `D3D12Texture.cpp` | 实现所有权语义,`Shutdown` 根据所有权决定是否释放 | +| `D3D12SwapChain.h` | 修改 `GetBackBuffer` 返回引用,删除 `GetSwapChain()` 暴露 | +| `D3D12SwapChain.cpp` | 实现返回引用的 `GetBackBuffer`,添加断言检查 | +| `minimal/main.cpp` | 使用封装后的 `GetBackBuffer()`,移除原生 API 调用 | +| `TEST_SPEC.md` | 更新架构说明,移除已知限制 7.2 | + +--- + +## 3. 实施步骤 + +### Phase 1: 基础修改 +1. 修改 `D3D12Texture` 添加所有权语义 +2. 修改 `D3D12SwapChain` 的 `GetBackBuffer` 返回引用 +3. 运行单元测试确保基础功能正常 + +### Phase 2: 更新集成测试 +1. 修改 `minimal/main.cpp` 使用新的 `GetBackBuffer()` API +2. 移除 `gColorRTs` 数组(如果可能) +3. 验证截图功能正常 + +### Phase 3: 文档和清理 +1. 更新 `TEST_SPEC.md` +2. 删除 `OpenGL_Test_Restructuring_Plan.md` 中对 D3D12 的过时引用 +3. 提交所有更改 + +--- + +## 4. 风险和注意事项 + +### 4.1 兼容性风险 +- `InitializeFromExisting` 签名变更会影响所有调用方 +- 需要检查所有使用此方法的代码 + +### 4.2 生命周期风险 +- `GetBackBuffer()` 返回的引用在 `Shutdown()` 后无效 +- 用户必须确保在 SwapChain 存活期间使用 + +### 4.3 ComPtr 行为澄清 + +```cpp +// ComPtr::Reset() 调用 Release() +// 如果多个 ComPtr 指向同一资源,Reset 只会减少引用计数 +// 只有最后一个 Reset 才会真正释放 + +ComPtr a = resource; // AddRef +ComPtr b = resource; // AddRef + +a.Reset(); // Release,资源仍未释放(b 还持有) +b.Reset(); // 最后一个 Release,资源被释放 +``` + +**关键点**:如果 `D3D12Texture` 和 `m_backBuffers` 都持有同一个 `ID3D12Resource` 的 `ComPtr`,那么: +- `gColorRTs[i].InitializeFromExisting(buffer)` 会让 `gColorRTs[i].m_resource` 指向 `buffer` +- `m_backBuffers[i].InitializeFromExisting(buffer)` 已经让 `m_backBuffers[i].m_resource` 指向 `buffer` +- 现在有两个 `ComPtr` 指向同一个资源 + +**问题**:这两个 `ComPtr` 是在不同对象中的,它们各自会增加引用计数。但原始的 `GetBuffer()` 返回的 `buffer` 指针被两个 `ComPtr` 接管了。 + +让我重新审视 `D3D12SwapChain::Initialize`: + +```cpp +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)); // resource 的引用计数 = 1 + m_backBuffers[i].InitializeFromExisting(resource); // ComPtr 接管,引用计数 = 2 +} +``` + +**问题**:`resource` 被传给了 `InitializeFromExisting`,ComPtr 构造时会 `AddRef`。但 `resource` 是局部变量,函数结束后 `resource` 局部变量销毁但不影响 COM 对象的引用计数。 + +等等,这里有个问题: + +```cpp +ID3D12Resource* resource = nullptr; +m_swapChain->GetBuffer(i, IID_PPV_ARGS(&resource)); // GetBuffer 返回的指针赋给 resource,引用计数 = 1 +m_backBuffers[i].InitializeFromExisting(resource); // ComPtr 拷贝构造,引用计数 = 2 +// 函数结束,resource 局部变量销毁(不影响引用计数,因为是指针变量) +``` + +所以 `m_backBuffers[i].m_resource` 和 `m_swapChain` 内部都持有同一个 COM 对象的引用。 + +然后 `minimal/main.cpp` 又做了一次: + +```cpp +ID3D12Resource* buffer = nullptr; +gSwapChain.GetSwapChain()->GetBuffer(i, IID_PPV_ARGS(&buffer)); // buffer 引用计数 = 3 +gColorRTs[i].InitializeFromExisting(buffer); // gColorRTs[i].m_resource 引用计数 = 4 +// buffer 局部变量销毁 +``` + +现在有三个 `ComPtr`(`m_backBuffers[i].m_resource`, `gColorRTs[i].m_resource`, `m_swapChain` 内部)指向同一个对象。引用计数 = 4。 + +**这不是问题**!因为 `ComPtr` 的拷贝构造会 `AddRef`。最终: +- `m_backBuffers[i]` 销毁 → 引用计数-- +- `gColorRTs[i]` 销毁 → 引用计数-- +- `m_swapChain` 销毁 → 引用计数-- + +引用计数最终归零,资源被正确释放。 + +**那问题是什么?** + +回到 `Shutdown`: + +```cpp +void D3D12Texture::Shutdown() { + m_resource.Reset(); // ComPtr::Reset() 调用 Release() +} +``` + +如果 `gColorRTs[i]` 先于 `m_swapChain` 销毁: +- `gColorRTs[i].Shutdown()` → `m_resource.Reset()` → Release() → 引用计数从 4 变成 3 +- `m_swapChain` 销毁时 → 内部资源 Release() → 引用计数从 3 变成 2 + +**这也应该是正常的...** + +但等等!`m_backBuffers[i]` 是在 `D3D12SwapChain` 内部的 vector 中: + +```cpp +std::vector m_backBuffers; +``` + +如果 `minimal/main.cpp` 中 `gSwapChain.Shutdown()` 被调用: +- `D3D12SwapChain::~D3D12SwapChain()` 调用 `Shutdown()` +- `Shutdown()` 调用 `m_swapChain.Reset()`(仅重置 swapchain 指针) +- 然后 vector `m_backBuffers` 销毁,每个 `D3D12Texture` 析构调用 `Shutdown()` +- 每个 `Shutdown()` 调用 `m_resource.Reset()` → 释放 back buffer + +**但是**:`gColorRTs[i]` 是在全局变量中独立创建的: + +```cpp +D3D12Texture gColorRTs[2]; // 独立于 SwapChain +``` + +如果 `gSwapChain.Shutdown()` 先执行,`m_backBuffers` 的 `m_resource` 被释放,那么 `gColorRTs[i].m_resource` 就变成悬垂的 `ComPtr`! + +```cpp +// gSwapChain.Shutdown() 执行后: +m_backBuffers[0].m_resource.Reset(); // 资源被释放! +// 但 gColorRTs[0].m_resource 仍然持有同一个(已释放的)指针! +// 调用 gColorRTs[0].GetResource() 会返回已释放的 COM 对象! +``` + +**这就是 bug!** 当 SwapChain 先 shutdown,用户代码中的 `gColorRTs` 就变成悬垂指针。 + +--- + +## 5. 最终结论 + +### 根因 +`minimal/main.cpp` 创建了与 `D3D12SwapChain` 内部 `m_backBuffers` **重复包装**的 `gColorRTs` 数组。当任何一个先销毁,另一个就变成悬垂指针。 + +### 修复方案 +1. **最小改动**:直接使用 `gSwapChain.GetBackBuffer(i)` 而非创建新的 `gColorRTs` +2. **长期方案**:增强 `D3D12Texture` 的所有权语义,区分拥有和非拥有资源 + +### 实施 +按照第 2.3 节的修改清单执行。 \ No newline at end of file diff --git a/engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h b/engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h index aaee598e..5cb6af24 100644 --- a/engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h +++ b/engine/include/XCEngine/RHI/D3D12/D3D12SwapChain.h @@ -25,7 +25,8 @@ public: uint32_t GetCurrentBackBufferIndex() const override; RHITexture* GetCurrentBackBuffer() override; - D3D12Texture* GetBackBuffer(uint32_t index) const; + D3D12Texture& GetBackBuffer(uint32_t index); + const D3D12Texture& GetBackBuffer(uint32_t index) const; void Present(uint32_t syncInterval = 1, uint32_t flags = 0) override; void Resize(uint32_t width, uint32_t height) override; void SetFullscreen(bool fullscreen) override; @@ -36,8 +37,6 @@ public: void SetShouldClose(bool shouldClose) override; void PollEvents() override; - IDXGISwapChain3* GetSwapChain() const { return m_swapChain.Get(); } - private: ComPtr m_swapChain; ComPtr m_commandQueue; diff --git a/engine/include/XCEngine/RHI/D3D12/D3D12Texture.h b/engine/include/XCEngine/RHI/D3D12/D3D12Texture.h index 6f6e6949..7acdd6ff 100644 --- a/engine/include/XCEngine/RHI/D3D12/D3D12Texture.h +++ b/engine/include/XCEngine/RHI/D3D12/D3D12Texture.h @@ -18,7 +18,7 @@ public: ~D3D12Texture() override; bool Initialize(ID3D12Device* device, const D3D12_RESOURCE_DESC& desc, D3D12_RESOURCE_STATES initialState = D3D12_RESOURCE_STATE_COMMON); - bool InitializeFromExisting(ID3D12Resource* resource); + bool InitializeFromExisting(ID3D12Resource* resource, bool ownsResource = false); bool InitializeFromData(ID3D12Device* device, ID3D12GraphicsCommandList* commandList, const void* pixelData, uint32_t width, uint32_t height, DXGI_FORMAT format); bool InitializeDepthStencil(ID3D12Device* device, uint32_t width, uint32_t height, DXGI_FORMAT format = DXGI_FORMAT_D24_UNORM_S8_UINT); @@ -46,10 +46,13 @@ public: Format GetFormat() const override { return static_cast(GetDesc().Format); } TextureType GetTextureType() const override { return TextureType::Texture2D; } + bool OwnsResource() const { return m_ownsResource; } + private: ComPtr m_resource; ResourceStates m_state = ResourceStates::Common; std::string m_name; + bool m_ownsResource = false; }; } // namespace RHI diff --git a/engine/src/RHI/D3D12/D3D12SwapChain.cpp b/engine/src/RHI/D3D12/D3D12SwapChain.cpp index 46c05d81..5704bea1 100644 --- a/engine/src/RHI/D3D12/D3D12SwapChain.cpp +++ b/engine/src/RHI/D3D12/D3D12SwapChain.cpp @@ -48,7 +48,7 @@ bool D3D12SwapChain::Initialize(IDXGIFactory4* factory, ID3D12CommandQueue* comm 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); + m_backBuffers[i].InitializeFromExisting(resource, false); } return true; @@ -67,7 +67,7 @@ bool D3D12SwapChain::Initialize(IDXGISwapChain* swapChain, uint32_t width, uint3 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); + m_backBuffers[i].InitializeFromExisting(resource, false); } return true; @@ -81,11 +81,12 @@ uint32_t D3D12SwapChain::GetCurrentBackBufferIndex() const { return m_swapChain->GetCurrentBackBufferIndex(); } -D3D12Texture* D3D12SwapChain::GetBackBuffer(uint32_t index) const { - if (index < m_backBuffers.size()) { - return const_cast(&m_backBuffers[index]); - } - return nullptr; +D3D12Texture& D3D12SwapChain::GetBackBuffer(uint32_t index) { + return m_backBuffers[index]; +} + +const D3D12Texture& D3D12SwapChain::GetBackBuffer(uint32_t index) const { + return m_backBuffers[index]; } void D3D12SwapChain::Present(uint32_t syncInterval, uint32_t flags) { @@ -113,7 +114,7 @@ void* D3D12SwapChain::GetNativeHandle() { } RHITexture* D3D12SwapChain::GetCurrentBackBuffer() { - return GetBackBuffer(GetCurrentBackBufferIndex()); + return &GetBackBuffer(GetCurrentBackBufferIndex()); } bool D3D12SwapChain::ShouldClose() const { diff --git a/engine/src/RHI/D3D12/D3D12Texture.cpp b/engine/src/RHI/D3D12/D3D12Texture.cpp index a94426b1..d8e31b71 100644 --- a/engine/src/RHI/D3D12/D3D12Texture.cpp +++ b/engine/src/RHI/D3D12/D3D12Texture.cpp @@ -33,11 +33,13 @@ bool D3D12Texture::Initialize(ID3D12Device* device, const D3D12_RESOURCE_DESC& d return false; } + m_ownsResource = true; return true; } -bool D3D12Texture::InitializeFromExisting(ID3D12Resource* resource) { +bool D3D12Texture::InitializeFromExisting(ID3D12Resource* resource, bool ownsResource) { m_resource = resource; + m_ownsResource = ownsResource; return true; } @@ -178,6 +180,9 @@ bool D3D12Texture::InitializeDepthStencil(ID3D12Device* device, uint32_t width, } void D3D12Texture::Shutdown() { + if (m_ownsResource) { + m_resource.Reset(); + } m_resource.Reset(); } diff --git a/tests/RHI/D3D12/TEST_SPEC.md b/tests/RHI/D3D12/TEST_SPEC.md index 13f95a39..65f421c3 100644 --- a/tests/RHI/D3D12/TEST_SPEC.md +++ b/tests/RHI/D3D12/TEST_SPEC.md @@ -290,22 +290,6 @@ engine/ **状态**: 待修复 -### 7.2 minimal GetBuffer 原生调用 - -**问题**: `minimal/main.cpp` 第 127-129 行仍使用原生 D3D12 API: - -```cpp -ID3D12Resource* buffer = nullptr; -gSwapChain.GetSwapChain()->GetBuffer(i, IID_PPV_ARGS(&buffer)); -gColorRTs[i].InitializeFromExisting(buffer); -``` - -**原因**: SwapChain back buffer 获取后需要通过 `InitializeFromExisting()` 绑定到已有的 `D3D12Texture` 对象。当前没有优雅的封装方案保留此语义。 - -**影响**: minimal 集成测试仍包含少量原生 D3D12 代码 - -**状态**: 标记为已知限制,暂不修复 - --- ## 8. 规范更新记录 @@ -315,9 +299,10 @@ gColorRTs[i].InitializeFromExisting(buffer); | 1.0 | 2026-03-20 | 初始版本 | | 1.1 | 2026-03-20 | 添加 CI 集成章节,补充 Phase 5 内容 | | 1.2 | 2026-03-20 | 重构集成测试目录结构,每个测试独立子文件夹,stb 库移至 engine/third_party/stb/ | +| 1.3 | 2026-03-20 | 修复 minimal GetBuffer 原生调用问题:添加 D3D12Texture 所有权语义,删除 GetSwapChain() 暴露方法,移除 gColorRTs 数组 | --- -**规范版本**: 1.2 +**规范版本**: 1.3 **最后更新**: 2026-03-20 **前置文档**: [tests/TEST_SPEC.md](../TEST_SPEC.md) diff --git a/tests/RHI/D3D12/integration/minimal/main.cpp b/tests/RHI/D3D12/integration/minimal/main.cpp index cde9552f..ef784af9 100644 --- a/tests/RHI/D3D12/integration/minimal/main.cpp +++ b/tests/RHI/D3D12/integration/minimal/main.cpp @@ -44,7 +44,6 @@ D3D12CommandAllocator gCommandAllocator; D3D12CommandList gCommandList; // Render targets -D3D12Texture gColorRTs[2]; D3D12Texture gDepthStencil; D3D12DescriptorHeap gRTVHeap; D3D12DescriptorHeap gDSVHeap; @@ -124,13 +123,11 @@ bool InitD3D12() { // Create RTVs for back buffers using encapsulated interface for (int i = 0; i < 2; i++) { - ID3D12Resource* buffer = nullptr; - gSwapChain.GetSwapChain()->GetBuffer(i, IID_PPV_ARGS(&buffer)); - gColorRTs[i].InitializeFromExisting(buffer); + D3D12Texture& backBuffer = gSwapChain.GetBackBuffer(i); CPUDescriptorHandle rtvCpuHandle = gRTVHeap.GetCPUDescriptorHandle(i); D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = { rtvCpuHandle.ptr }; - gRTVs[i].InitializeAt(device, gColorRTs[i].GetResource(), rtvHandle, nullptr); + gRTVs[i].InitializeAt(device, backBuffer.GetResource(), rtvHandle, nullptr); } // Create DSV @@ -164,7 +161,8 @@ void BeginRender() { gCurrentRTIndex = gSwapChain.GetCurrentBackBufferIndex(); // Transition render target - gCommandList.TransitionBarrier(gColorRTs[gCurrentRTIndex].GetResource(), + D3D12Texture& currentBackBuffer = gSwapChain.GetBackBuffer(gCurrentRTIndex); + gCommandList.TransitionBarrier(currentBackBuffer.GetResource(), ResourceStates::Present, ResourceStates::RenderTarget); // Set render targets using encapsulated interface @@ -189,7 +187,8 @@ void BeginRender() { // End rendering void EndRender() { - gCommandList.TransitionBarrier(gColorRTs[gCurrentRTIndex].GetResource(), + D3D12Texture& currentBackBuffer = gSwapChain.GetBackBuffer(gCurrentRTIndex); + gCommandList.TransitionBarrier(currentBackBuffer.GetResource(), ResourceStates::RenderTarget, ResourceStates::Present); } @@ -275,7 +274,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine bool screenshotResult = D3D12Screenshot::Capture( gDevice, gCommandQueue, - gColorRTs[gCurrentRTIndex], + gSwapChain.GetBackBuffer(gCurrentRTIndex), "minimal.ppm" ); if (screenshotResult) {