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
This commit is contained in:
2026-03-20 17:58:27 +08:00
parent 0017388498
commit 34c04af6cb
7 changed files with 433 additions and 38 deletions

View File

@@ -0,0 +1,403 @@
# D3D12 Texture 封装架构修复方案
## 1. 问题分析
### 1.1 当前问题
**问题 A双重 Texture 包装导致悬垂指针**
```cpp
// D3D12SwapChain 内部:
std::vector<D3D12Texture> 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 bufferShutdown 会错误地释放它!
**问题 C`D3D12SwapChain::GetBackBuffer()` 返回内部引用,用户可能误用**
```cpp
D3D12Texture* D3D12SwapChain::GetBackBuffer(uint32_t index) const {
return const_cast<D3DTexture*>(&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<ID3D12Resource> 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<ID3D12Resource> 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<ID3D12Resource> a = resource; // AddRef
ComPtr<ID3D12Resource> 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<D3D12Texture> 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 节的修改清单执行。

View File

@@ -25,7 +25,8 @@ public:
uint32_t GetCurrentBackBufferIndex() const override; uint32_t GetCurrentBackBufferIndex() const override;
RHITexture* GetCurrentBackBuffer() 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 Present(uint32_t syncInterval = 1, uint32_t flags = 0) override;
void Resize(uint32_t width, uint32_t height) override; void Resize(uint32_t width, uint32_t height) override;
void SetFullscreen(bool fullscreen) override; void SetFullscreen(bool fullscreen) override;
@@ -36,8 +37,6 @@ public:
void SetShouldClose(bool shouldClose) override; void SetShouldClose(bool shouldClose) override;
void PollEvents() override; void PollEvents() override;
IDXGISwapChain3* GetSwapChain() const { return m_swapChain.Get(); }
private: private:
ComPtr<IDXGISwapChain3> m_swapChain; ComPtr<IDXGISwapChain3> m_swapChain;
ComPtr<ID3D12CommandQueue> m_commandQueue; ComPtr<ID3D12CommandQueue> m_commandQueue;

View File

@@ -18,7 +18,7 @@ public:
~D3D12Texture() override; ~D3D12Texture() override;
bool Initialize(ID3D12Device* device, const D3D12_RESOURCE_DESC& desc, D3D12_RESOURCE_STATES initialState = D3D12_RESOURCE_STATE_COMMON); 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, bool InitializeFromData(ID3D12Device* device, ID3D12GraphicsCommandList* commandList,
const void* pixelData, uint32_t width, uint32_t height, DXGI_FORMAT format); 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); 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<Format>(GetDesc().Format); } Format GetFormat() const override { return static_cast<Format>(GetDesc().Format); }
TextureType GetTextureType() const override { return TextureType::Texture2D; } TextureType GetTextureType() const override { return TextureType::Texture2D; }
bool OwnsResource() const { return m_ownsResource; }
private: private:
ComPtr<ID3D12Resource> m_resource; ComPtr<ID3D12Resource> m_resource;
ResourceStates m_state = ResourceStates::Common; ResourceStates m_state = ResourceStates::Common;
std::string m_name; std::string m_name;
bool m_ownsResource = false;
}; };
} // namespace RHI } // namespace RHI

View File

@@ -48,7 +48,7 @@ bool D3D12SwapChain::Initialize(IDXGIFactory4* factory, ID3D12CommandQueue* comm
for (uint32_t i = 0; i < m_bufferCount; ++i) { for (uint32_t i = 0; i < m_bufferCount; ++i) {
ID3D12Resource* resource = nullptr; ID3D12Resource* resource = nullptr;
m_swapChain->GetBuffer(i, IID_PPV_ARGS(&resource)); m_swapChain->GetBuffer(i, IID_PPV_ARGS(&resource));
m_backBuffers[i].InitializeFromExisting(resource); m_backBuffers[i].InitializeFromExisting(resource, false);
} }
return true; return true;
@@ -67,7 +67,7 @@ bool D3D12SwapChain::Initialize(IDXGISwapChain* swapChain, uint32_t width, uint3
for (uint32_t i = 0; i < m_bufferCount; ++i) { for (uint32_t i = 0; i < m_bufferCount; ++i) {
ID3D12Resource* resource = nullptr; ID3D12Resource* resource = nullptr;
m_swapChain->GetBuffer(i, IID_PPV_ARGS(&resource)); m_swapChain->GetBuffer(i, IID_PPV_ARGS(&resource));
m_backBuffers[i].InitializeFromExisting(resource); m_backBuffers[i].InitializeFromExisting(resource, false);
} }
return true; return true;
@@ -81,11 +81,12 @@ uint32_t D3D12SwapChain::GetCurrentBackBufferIndex() const {
return m_swapChain->GetCurrentBackBufferIndex(); return m_swapChain->GetCurrentBackBufferIndex();
} }
D3D12Texture* D3D12SwapChain::GetBackBuffer(uint32_t index) const { D3D12Texture& D3D12SwapChain::GetBackBuffer(uint32_t index) {
if (index < m_backBuffers.size()) { return m_backBuffers[index];
return const_cast<D3D12Texture*>(&m_backBuffers[index]); }
}
return nullptr; const D3D12Texture& D3D12SwapChain::GetBackBuffer(uint32_t index) const {
return m_backBuffers[index];
} }
void D3D12SwapChain::Present(uint32_t syncInterval, uint32_t flags) { void D3D12SwapChain::Present(uint32_t syncInterval, uint32_t flags) {
@@ -113,7 +114,7 @@ void* D3D12SwapChain::GetNativeHandle() {
} }
RHITexture* D3D12SwapChain::GetCurrentBackBuffer() { RHITexture* D3D12SwapChain::GetCurrentBackBuffer() {
return GetBackBuffer(GetCurrentBackBufferIndex()); return &GetBackBuffer(GetCurrentBackBufferIndex());
} }
bool D3D12SwapChain::ShouldClose() const { bool D3D12SwapChain::ShouldClose() const {

View File

@@ -33,11 +33,13 @@ bool D3D12Texture::Initialize(ID3D12Device* device, const D3D12_RESOURCE_DESC& d
return false; return false;
} }
m_ownsResource = true;
return true; return true;
} }
bool D3D12Texture::InitializeFromExisting(ID3D12Resource* resource) { bool D3D12Texture::InitializeFromExisting(ID3D12Resource* resource, bool ownsResource) {
m_resource = resource; m_resource = resource;
m_ownsResource = ownsResource;
return true; return true;
} }
@@ -178,6 +180,9 @@ bool D3D12Texture::InitializeDepthStencil(ID3D12Device* device, uint32_t width,
} }
void D3D12Texture::Shutdown() { void D3D12Texture::Shutdown() {
if (m_ownsResource) {
m_resource.Reset();
}
m_resource.Reset(); m_resource.Reset();
} }

View File

@@ -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. 规范更新记录 ## 8. 规范更新记录
@@ -315,9 +299,10 @@ gColorRTs[i].InitializeFromExisting(buffer);
| 1.0 | 2026-03-20 | 初始版本 | | 1.0 | 2026-03-20 | 初始版本 |
| 1.1 | 2026-03-20 | 添加 CI 集成章节,补充 Phase 5 内容 | | 1.1 | 2026-03-20 | 添加 CI 集成章节,补充 Phase 5 内容 |
| 1.2 | 2026-03-20 | 重构集成测试目录结构每个测试独立子文件夹stb 库移至 engine/third_party/stb/ | | 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 **最后更新**: 2026-03-20
**前置文档**: [tests/TEST_SPEC.md](../TEST_SPEC.md) **前置文档**: [tests/TEST_SPEC.md](../TEST_SPEC.md)

View File

@@ -44,7 +44,6 @@ D3D12CommandAllocator gCommandAllocator;
D3D12CommandList gCommandList; D3D12CommandList gCommandList;
// Render targets // Render targets
D3D12Texture gColorRTs[2];
D3D12Texture gDepthStencil; D3D12Texture gDepthStencil;
D3D12DescriptorHeap gRTVHeap; D3D12DescriptorHeap gRTVHeap;
D3D12DescriptorHeap gDSVHeap; D3D12DescriptorHeap gDSVHeap;
@@ -124,13 +123,11 @@ bool InitD3D12() {
// Create RTVs for back buffers using encapsulated interface // Create RTVs for back buffers using encapsulated interface
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
ID3D12Resource* buffer = nullptr; D3D12Texture& backBuffer = gSwapChain.GetBackBuffer(i);
gSwapChain.GetSwapChain()->GetBuffer(i, IID_PPV_ARGS(&buffer));
gColorRTs[i].InitializeFromExisting(buffer);
CPUDescriptorHandle rtvCpuHandle = gRTVHeap.GetCPUDescriptorHandle(i); CPUDescriptorHandle rtvCpuHandle = gRTVHeap.GetCPUDescriptorHandle(i);
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle = { rtvCpuHandle.ptr }; 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 // Create DSV
@@ -164,7 +161,8 @@ void BeginRender() {
gCurrentRTIndex = gSwapChain.GetCurrentBackBufferIndex(); gCurrentRTIndex = gSwapChain.GetCurrentBackBufferIndex();
// Transition render target // Transition render target
gCommandList.TransitionBarrier(gColorRTs[gCurrentRTIndex].GetResource(), D3D12Texture& currentBackBuffer = gSwapChain.GetBackBuffer(gCurrentRTIndex);
gCommandList.TransitionBarrier(currentBackBuffer.GetResource(),
ResourceStates::Present, ResourceStates::RenderTarget); ResourceStates::Present, ResourceStates::RenderTarget);
// Set render targets using encapsulated interface // Set render targets using encapsulated interface
@@ -189,7 +187,8 @@ void BeginRender() {
// End rendering // End rendering
void EndRender() { void EndRender() {
gCommandList.TransitionBarrier(gColorRTs[gCurrentRTIndex].GetResource(), D3D12Texture& currentBackBuffer = gSwapChain.GetBackBuffer(gCurrentRTIndex);
gCommandList.TransitionBarrier(currentBackBuffer.GetResource(),
ResourceStates::RenderTarget, ResourceStates::Present); ResourceStates::RenderTarget, ResourceStates::Present);
} }
@@ -275,7 +274,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine
bool screenshotResult = D3D12Screenshot::Capture( bool screenshotResult = D3D12Screenshot::Capture(
gDevice, gDevice,
gCommandQueue, gCommandQueue,
gColorRTs[gCurrentRTIndex], gSwapChain.GetBackBuffer(gCurrentRTIndex),
"minimal.ppm" "minimal.ppm"
); );
if (screenshotResult) { if (screenshotResult) {