DX9 to DX12 Migration - Phase 1: Foundation & Planning

Major Changes:
- Created comprehensive DX12 migration plan document
- Established DX12 engine module structure
- Implemented core infrastructure:
  * DX12Device: Device initialization, adapter selection, feature detection
  * DX12CommandQueue: Command queue management with fence synchronization

Architecture:
- DX12/Core: Fundamental objects (device, command queue)
- DX12/Resources: Resource management (planned)
- DX12/Rendering: Rendering abstractions (planned)

Key Features Implemented:
- Automatic adapter selection (chooses best GPU)
- Debug layer integration with GPU-based validation
- Feature level detection (11_0 to 12_1)
- Advanced feature detection (raytracing, mesh shaders, VRS)
- Descriptor size caching
- Fence-based GPU/CPU synchronization
- Command list execution with automatic fence signaling

Migration Strategy:
- Option A: Gradual transition via DX11 (recommended, 8-10 months)
- Option B: Direct DX12 migration (high-risk, 3-4 months)
- Option C: Multi-backend architecture (expert-level)

Technical Details:
- Supports Windows 10 1809+
- Requires DX12 capable GPU (most 2015+ hardware)
- Feature Level 11_0 minimum
- ComPtr for automatic resource management
- Explicit synchronization with fences

Documentation:
- DX9_TO_DX12_MIGRATION_PLAN.md: 15KB comprehensive guide
- Client/Engine/DX12/README.md: Module documentation

Status: Phase 1 Complete
Next: Command list management, swap chain, resource system

Files added: 6 (2 .h, 2 .cpp, 2 .md)
Lines of code: ~400 (core infrastructure)

This is a foundational commit establishing the DX12 architecture.
Full migration will take 3-4 months of development.
This commit is contained in:
2025-12-01 10:23:56 +09:00
parent fa4459533c
commit b58d615cf2
6 changed files with 1360 additions and 0 deletions

View File

@@ -0,0 +1,117 @@
// DX12CommandQueue.cpp: Implementation
//////////////////////////////////////////////////////////////////////
#include "DX12CommandQueue.h"
#include <stdexcept>
DX12CommandQueue::DX12CommandQueue()
: m_fenceEvent(NULL)
, m_nextFenceValue(1)
, m_lastCompletedFenceValue(0)
, m_type(D3D12_COMMAND_LIST_TYPE_DIRECT)
{
}
DX12CommandQueue::~DX12CommandQueue()
{
Shutdown();
}
bool DX12CommandQueue::Initialize(ID3D12Device* device, D3D12_COMMAND_LIST_TYPE type)
{
m_type = type;
// Create command queue
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = type;
queueDesc.Priority = D3D12_COMMAND_QUEUE_PRIORITY_NORMAL;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.NodeMask = 0;
HRESULT hr = device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_commandQueue));
if (FAILED(hr))
return false;
// Create fence
hr = device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence));
if (FAILED(hr))
return false;
// Create fence event
m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (m_fenceEvent == NULL)
return false;
return true;
}
void DX12CommandQueue::Shutdown()
{
// Wait for all GPU work to complete
if (m_fence && m_fenceEvent)
{
Flush();
}
// Close fence event
if (m_fenceEvent)
{
CloseHandle(m_fenceEvent);
m_fenceEvent = NULL;
}
m_fence.Reset();
m_commandQueue.Reset();
}
UINT64 DX12CommandQueue::ExecuteCommandList(ID3D12CommandList* commandList)
{
return ExecuteCommandLists(&commandList, 1);
}
UINT64 DX12CommandQueue::ExecuteCommandLists(ID3D12CommandList* const* commandLists, UINT count)
{
m_commandQueue->ExecuteCommandLists(count, commandLists);
return Signal();
}
UINT64 DX12CommandQueue::Signal()
{
UINT64 fenceValue = m_nextFenceValue++;
m_commandQueue->Signal(m_fence.Get(), fenceValue);
return fenceValue;
}
bool DX12CommandQueue::IsFenceComplete(UINT64 fenceValue)
{
if (fenceValue > m_lastCompletedFenceValue)
{
m_lastCompletedFenceValue = m_fence->GetCompletedValue();
}
return fenceValue <= m_lastCompletedFenceValue;
}
void DX12CommandQueue::WaitForFenceValue(UINT64 fenceValue)
{
if (IsFenceComplete(fenceValue))
return;
// Set event when fence reaches the value
m_fence->SetEventOnCompletion(fenceValue, m_fenceEvent);
// Wait for the event
WaitForSingleObject(m_fenceEvent, INFINITE);
m_lastCompletedFenceValue = fenceValue;
}
void DX12CommandQueue::Flush()
{
UINT64 fenceValue = Signal();
WaitForFenceValue(fenceValue);
}
UINT64 DX12CommandQueue::GetLastCompletedFenceValue() const
{
return m_fence->GetCompletedValue();
}

View File

@@ -0,0 +1,44 @@
// DX12CommandQueue.h: Command Queue Management for DX12
//////////////////////////////////////////////////////////////////////
#pragma once
#include <d3d12.h>
#include <wrl/client.h>
#include <queue>
using Microsoft::WRL::ComPtr;
class DX12CommandQueue
{
public:
DX12CommandQueue();
~DX12CommandQueue();
// Initialization
bool Initialize(ID3D12Device* device, D3D12_COMMAND_LIST_TYPE type);
void Shutdown();
// Command execution
UINT64 ExecuteCommandList(ID3D12CommandList* commandList);
UINT64 ExecuteCommandLists(ID3D12CommandList* const* commandLists, UINT count);
// Synchronization
UINT64 Signal();
bool IsFenceComplete(UINT64 fenceValue);
void WaitForFenceValue(UINT64 fenceValue);
void Flush();
// Getters
ID3D12CommandQueue* GetQueue() const { return m_commandQueue.Get(); }
UINT64 GetNextFenceValue() const { return m_nextFenceValue; }
UINT64 GetLastCompletedFenceValue() const;
private:
ComPtr<ID3D12CommandQueue> m_commandQueue;
ComPtr<ID3D12Fence> m_fence;
HANDLE m_fenceEvent;
UINT64 m_nextFenceValue;
UINT64 m_lastCompletedFenceValue;
D3D12_COMMAND_LIST_TYPE m_type;
};

View File

@@ -0,0 +1,242 @@
// DX12Device.cpp: Implementation of DX12Device class
//////////////////////////////////////////////////////////////////////
#include "DX12Device.h"
#include <stdexcept>
#include <string>
DX12Device::DX12Device()
: m_featureLevel(D3D_FEATURE_LEVEL_11_0)
, m_raytracingSupported(false)
, m_meshShaderSupported(false)
, m_variableRateShadingSupported(false)
, m_rtvDescriptorSize(0)
, m_dsvDescriptorSize(0)
, m_cbvSrvUavDescriptorSize(0)
, m_samplerDescriptorSize(0)
, m_debugLayerEnabled(false)
{
}
DX12Device::~DX12Device()
{
Shutdown();
}
bool DX12Device::Initialize(bool enableDebugLayer)
{
m_debugLayerEnabled = enableDebugLayer;
// 1. Enable debug layer if requested
if (m_debugLayerEnabled)
{
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&m_debugController))))
{
m_debugController->EnableDebugLayer();
// Enable additional debug features
ComPtr<ID3D12Debug1> debug1;
if (SUCCEEDED(m_debugController.As(&debug1)))
{
debug1->SetEnableGPUBasedValidation(TRUE);
debug1->SetEnableSynchronizedCommandQueueValidation(TRUE);
}
}
}
// 2. Create DXGI Factory
if (!CreateDXGIFactory())
return false;
// 3. Select best adapter
if (!SelectAdapter())
return false;
// 4. Create D3D12 Device
if (!CreateDevice())
return false;
// 5. Check feature support
if (!CheckFeatureSupport())
return false;
// 6. Cache descriptor sizes
m_rtvDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
m_dsvDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
m_cbvSrvUavDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
m_samplerDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER);
return true;
}
void DX12Device::Shutdown()
{
// Release in reverse order
m_device.Reset();
m_adapter.Reset();
m_factory.Reset();
m_debugController.Reset();
}
bool DX12Device::CreateDXGIFactory()
{
UINT factoryFlags = 0;
if (m_debugLayerEnabled)
{
factoryFlags = DXGI_CREATE_FACTORY_DEBUG;
}
HRESULT hr = CreateDXGIFactory2(factoryFlags, IID_PPV_ARGS(&m_factory));
if (FAILED(hr))
{
return false;
}
return true;
}
bool DX12Device::SelectAdapter()
{
ComPtr<IDXGIAdapter1> adapter;
SIZE_T maxDedicatedVideoMemory = 0;
UINT adapterIndex = 0;
// Enumerate adapters and select the one with most dedicated video memory
while (m_factory->EnumAdapters1(adapterIndex, &adapter) != DXGI_ERROR_NOT_FOUND)
{
DXGI_ADAPTER_DESC1 desc;
adapter->GetDesc1(&desc);
// Skip software adapters
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
{
adapterIndex++;
continue;
}
// Check if this adapter supports D3D12
if (SUCCEEDED(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0,
__uuidof(ID3D12Device), nullptr)))
{
if (desc.DedicatedVideoMemory > maxDedicatedVideoMemory)
{
maxDedicatedVideoMemory = desc.DedicatedVideoMemory;
m_adapter = adapter;
}
}
adapterIndex++;
}
if (!m_adapter)
{
// Fallback to WARP adapter (software rendering)
if (FAILED(m_factory->EnumWarpAdapter(IID_PPV_ARGS(&m_adapter))))
{
return false;
}
}
return true;
}
bool DX12Device::CreateDevice()
{
// Try to create device with highest feature level first
D3D_FEATURE_LEVEL featureLevels[] = {
D3D_FEATURE_LEVEL_12_1,
D3D_FEATURE_LEVEL_12_0,
D3D_FEATURE_LEVEL_11_1,
D3D_FEATURE_LEVEL_11_0
};
for (auto level : featureLevels)
{
HRESULT hr = D3D12CreateDevice(
m_adapter.Get(),
level,
IID_PPV_ARGS(&m_device)
);
if (SUCCEEDED(hr))
{
m_featureLevel = level;
break;
}
}
if (!m_device)
{
return false;
}
// Set debug info queue if debug layer is enabled
if (m_debugLayerEnabled)
{
ComPtr<ID3D12InfoQueue> infoQueue;
if (SUCCEEDED(m_device.As(&infoQueue)))
{
infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_CORRUPTION, TRUE);
infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_ERROR, TRUE);
infoQueue->SetBreakOnSeverity(D3D12_MESSAGE_SEVERITY_WARNING, FALSE);
// Filter out some common but harmless messages
D3D12_MESSAGE_SEVERITY severities[] = {
D3D12_MESSAGE_SEVERITY_INFO
};
D3D12_INFO_QUEUE_FILTER filter = {};
filter.DenyList.NumSeverities = _countof(severities);
filter.DenyList.pSeverityList = severities;
infoQueue->PushStorageFilter(&filter);
}
}
return true;
}
bool DX12Device::CheckFeatureSupport()
{
// Check raytracing support (DXR)
D3D12_FEATURE_DATA_D3D12_OPTIONS5 options5 = {};
if (SUCCEEDED(m_device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS5,
&options5, sizeof(options5))))
{
m_raytracingSupported = (options5.RaytracingTier != D3D12_RAYTRACING_TIER_NOT_SUPPORTED);
}
// Check mesh shader support
D3D12_FEATURE_DATA_D3D12_OPTIONS7 options7 = {};
if (SUCCEEDED(m_device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS7,
&options7, sizeof(options7))))
{
m_meshShaderSupported = (options7.MeshShaderTier != D3D12_MESH_SHADER_TIER_NOT_SUPPORTED);
}
// Check variable rate shading support
D3D12_FEATURE_DATA_D3D12_OPTIONS6 options6 = {};
if (SUCCEEDED(m_device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS6,
&options6, sizeof(options6))))
{
m_variableRateShadingSupported = (options6.VariableShadingRateTier != D3D12_VARIABLE_SHADING_RATE_TIER_NOT_SUPPORTED);
}
return true;
}
UINT DX12Device::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE type) const
{
switch (type)
{
case D3D12_DESCRIPTOR_HEAP_TYPE_RTV:
return m_rtvDescriptorSize;
case D3D12_DESCRIPTOR_HEAP_TYPE_DSV:
return m_dsvDescriptorSize;
case D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV:
return m_cbvSrvUavDescriptorSize;
case D3D12_DESCRIPTOR_HEAP_TYPE_SAMPLER:
return m_samplerDescriptorSize;
default:
return 0;
}
}

View File

@@ -0,0 +1,66 @@
// DX12Device.h: DirectX 12 Device Management
//
// Core class for managing D3D12 device, adapters, and feature support
//////////////////////////////////////////////////////////////////////
#pragma once
#include <d3d12.h>
#include <dxgi1_6.h>
#include <wrl/client.h>
#include <vector>
using Microsoft::WRL::ComPtr;
class DX12Device
{
public:
DX12Device();
~DX12Device();
// Initialization
bool Initialize(bool enableDebugLayer = false);
void Shutdown();
// Getters
ID3D12Device* GetDevice() const { return m_device.Get(); }
IDXGIFactory4* GetFactory() const { return m_factory.Get(); }
IDXGIAdapter1* GetAdapter() const { return m_adapter.Get(); }
// Feature Support Queries
D3D_FEATURE_LEVEL GetFeatureLevel() const { return m_featureLevel; }
bool IsRaytracingSupported() const { return m_raytracingSupported; }
bool IsMeshShaderSupported() const { return m_meshShaderSupported; }
// Helper functions
UINT GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE type) const;
private:
// Initialization helpers
bool CreateDXGIFactory();
bool SelectAdapter();
bool CreateDevice();
bool CheckFeatureSupport();
private:
// Core DX12 objects
ComPtr<IDXGIFactory4> m_factory;
ComPtr<IDXGIAdapter1> m_adapter;
ComPtr<ID3D12Device> m_device;
ComPtr<ID3D12Debug> m_debugController;
// Device capabilities
D3D_FEATURE_LEVEL m_featureLevel;
bool m_raytracingSupported;
bool m_meshShaderSupported;
bool m_variableRateShadingSupported;
// Descriptor sizes (cached for performance)
UINT m_rtvDescriptorSize;
UINT m_dsvDescriptorSize;
UINT m_cbvSrvUavDescriptorSize;
UINT m_samplerDescriptorSize;
// Debug flag
bool m_debugLayerEnabled;
};

View File

@@ -0,0 +1,198 @@
# DirectX 12 Engine Module
## Overview
This is a **ground-up DirectX 12 rendering backend** for the RiskYourLife game client. DX12 provides low-level GPU control, enabling significant performance improvements through explicit resource management and multi-threaded command recording.
## Architecture
```
DX12/
├── Core/ # Fundamental DX12 objects
│ ├── DX12Device.h/cpp # Device & adapter management
│ ├── DX12CommandQueue.h/cpp # Command queue & fence synchronization
│ ├── DX12CommandList.h/cpp # Command list management (TODO)
│ └── DX12SwapChain.h/cpp # Swap chain management (TODO)
├── Resources/ # Resource management
│ ├── DX12ResourceManager.h/cpp # Central resource management (TODO)
│ ├── DX12UploadBuffer.h/cpp # Upload heap management (TODO)
│ ├── DX12Texture.h/cpp # Texture resources (TODO)
│ └── DX12Buffer.h/cpp # Buffer resources (TODO)
└── Rendering/ # Rendering abstractions
├── DX12RenderContext.h/cpp # Rendering context (TODO)
├── DX12StateCache.h/cpp # State caching layer (TODO)
└── DX12ImmediateContext.h/cpp # DX9-style wrapper (TODO)
```
## Current Status
### ✅ Implemented
- **DX12Device**: Core device initialization, adapter selection, feature detection
- **DX12CommandQueue**: Command queue management, fence synchronization
### 🚧 In Progress
- Command list management
- Swap chain setup
- Resource management system
### 📋 Planned
- Pipeline state objects (PSO)
- Root signatures
- Descriptor heap manager
- DX9 compatibility layer
- Multi-threaded rendering
## Key Concepts
### 1. Command Lists vs Immediate Mode
**DX9 (Old)**:
```cpp
device->SetRenderState(...);
device->DrawPrimitive(...); // Executed immediately
```
**DX12 (New)**:
```cpp
commandList->SetGraphicsRootSignature(...);
commandList->SetPipelineState(...);
commandList->DrawInstanced(...); // Recorded, not executed yet
commandQueue->ExecuteCommandLists(...); // Now it executes
```
### 2. Explicit Synchronization
```cpp
// Signal after GPU work
UINT64 fenceValue = commandQueue->Signal();
// Later, wait for completion
commandQueue->WaitForFenceValue(fenceValue);
```
### 3. Resource States
Resources must be transitioned between states:
- `D3D12_RESOURCE_STATE_RENDER_TARGET`
- `D3D12_RESOURCE_STATE_PRESENT`
- `D3D12_RESOURCE_STATE_COPY_DEST`
- etc.
### 4. Descriptor Heaps
Descriptors (views) are stored in heaps:
- **RTV Heap**: Render Target Views
- **DSV Heap**: Depth Stencil Views
- **CBV/SRV/UAV Heap**: Constant/Shader Resource/Unordered Access Views
- **Sampler Heap**: Texture samplers
## Usage Example
```cpp
// Initialize device
DX12Device device;
device.Initialize(true); // Enable debug layer
// Create command queue
DX12CommandQueue graphicsQueue;
graphicsQueue.Initialize(device.GetDevice(), D3D12_COMMAND_LIST_TYPE_DIRECT);
// Execute commands
graphicsQueue.ExecuteCommandList(commandList);
// Wait for GPU
graphicsQueue.Flush();
```
## Requirements
### Software
- **Windows 10** version 1809 or later
- **Visual Studio 2019** or later
- **Windows SDK** 10.0.17763.0 or later
### Hardware
- **GPU**: DirectX 12 capable (most GPUs since 2015)
- **Feature Level**: 11_0 minimum, 12_0+ recommended
### Libraries
- `d3d12.lib`
- `dxgi.lib`
- `dxguid.lib`
- `d3dcompiler.lib`
## Performance Considerations
### Advantages of DX12
✅ Multi-threaded command recording
✅ Lower CPU overhead (when optimized)
✅ Explicit memory management
✅ Better GPU utilization
✅ Advanced features (raytracing, mesh shaders)
### Challenges
⚠️ Steep learning curve
⚠️ More complex code
⚠️ Requires careful synchronization
⚠️ Initial performance may be worse than DX9
## Migration Strategy
### Phase 1: Infrastructure ✅ (Current)
- Device initialization
- Command queue
- Basic synchronization
### Phase 2: Resources 🚧
- Upload buffers
- Texture loading
- Buffer management
### Phase 3: Rendering Pipeline 📋
- Root signatures
- Pipeline state objects
- Descriptor heaps
### Phase 4: Compatibility Layer 📋
- DX9-style wrapper API
- Gradual integration with existing code
## Debug Tips
### Enable Debug Layer
```cpp
device.Initialize(true); // Enables validation
```
### Use PIX for Windows
Download from: https://devblogs.microsoft.com/pix/download/
### Common Issues
**Issue**: `DEVICE_REMOVED`
- Check resource state transitions
- Verify descriptor validity
- Enable GPU-based validation
**Issue**: Synchronization bugs
- Use fences properly
- Wait for previous frame before reusing resources
- Check command allocator reset timing
## References
- [DirectX 12 Programming Guide](https://docs.microsoft.com/en-us/windows/win32/direct3d12/directx-12-programming-guide)
- [Learning DirectX 12](https://www.3dgep.com/learning-directx-12-1/)
- [DX12 Best Practices](https://developer.nvidia.com/dx12-dos-and-donts)
## Authors
- **Initial Implementation**: Claude AI (Anthropic)
- **Date**: December 2025
- **Status**: Early Development / Prototype
---
**⚠️ WARNING**: This is experimental code. Not production-ready.

View File

@@ -0,0 +1,693 @@
# DirectX 9 to DirectX 12 Migration Plan
## RiskYourLife Client - Revolutionary Upgrade
---
## ⚠️ WARNING: MAJOR ARCHITECTURAL CHANGE
DirectX 12는 DirectX 9와 **완전히 다른 아키텍처**입니다. 이는 단순한 API 변환이 아닌 **전면적인 렌더링 엔진 재설계**가 필요합니다.
### DX9 vs DX12 핵심 차이점
| 측면 | DirectX 9 | DirectX 12 |
|------|-----------|------------|
| **추상화 레벨** | High-level (자동화) | Low-level (수동 제어) |
| **리소스 관리** | 드라이버가 자동 관리 | 개발자가 명시적 관리 |
| **멀티스레딩** | 제한적 | 완전한 멀티스레드 지원 |
| **메모리 관리** | 자동 | Descriptor Heaps, 명시적 할당 |
| **커맨드 제출** | Immediate | Command Lists + Command Queue |
| **동기화** | 암묵적 | 명시적 Fence 사용 |
| **파이프라인 스테이트** | 개별 설정 | Pipeline State Objects (PSO) |
| **셰이더 모델** | SM 3.0 | SM 5.0/5.1/6.0+ |
| **루트 시그니처** | 없음 | 필수 (리소스 바인딩 정의) |
---
## 1. 현재 DX9 코드베이스 분석
### 1.1 사용 중인 DX9 패턴
#### A. 디바이스 생성 및 관리
```cpp
// DX9 - BaseGraphicsLayer.cpp
LPDIRECT3D9 m_pD3D = Direct3DCreate9(D3D_SDK_VERSION);
m_pD3D->CreateDevice(..., &m_pd3dDevice);
```
#### B. 즉시 렌더링 (Immediate Mode)
```cpp
// DX9 - 모든 렌더링 코드
m_pd3dDevice->SetRenderState(...);
m_pd3dDevice->SetTexture(0, pTexture);
m_pd3dDevice->DrawPrimitive(...);
m_pd3dDevice->Present();
```
#### C. 자동 리소스 관리
```cpp
// DX9 - Texture.cpp
D3DXCreateTextureFromFile(m_pd3dDevice, filename, &pTexture);
pTexture->Release(); // 간단한 해제
```
#### D. 고정 함수 파이프라인 + 기본 셰이더
```cpp
// DX9
m_pd3dDevice->SetVertexShader(pVS);
m_pd3dDevice->SetPixelShader(pPS);
```
### 1.2 변환이 필요한 주요 컴포넌트
1. **BaseGraphicsLayer** (전면 재작성 필요)
- 디바이스 생성 로직
- 스왑체인 관리
- 커맨드 큐/리스트 시스템
2. **리소스 관리 시스템** (새로 구축)
- Descriptor Heaps
- 리소스 상태 추적
- 메모리 할당자
3. **렌더링 파이프라인** (재설계)
- Command List 기반 렌더링
- PSO (Pipeline State Objects)
- Root Signature
4. **텍스처 시스템** (재작성)
- 리소스 업로드 큐
- Subresource 관리
- Format 변환
5. **셰이더 시스템** (업그레이드)
- HLSL 5.0/6.0
- Shader Reflection
- Root Constants
---
## 2. DX12 아키텍처 설계
### 2.1 새로운 클래스 구조
```
DX12GraphicsDevice
├── DX12CommandQueue (Direct/Compute/Copy)
├── DX12SwapChain
├── DX12DescriptorHeapManager
│ ├── RTV Heap
│ ├── DSV Heap
│ ├── CBV_SRV_UAV Heap
│ └── Sampler Heap
├── DX12ResourceManager
│ ├── Upload Buffer Pool
│ ├── Resource State Tracker
│ └── Memory Allocator
├── DX12PipelineStateManager
│ └── PSO Cache
└── DX12RootSignatureManager
└── Root Signature Cache
DX12CommandContext (Per-thread)
├── Command Allocator Pool
├── Command List
└── Resource Barrier Batch
DX12Fence
└── GPU/CPU Synchronization
```
### 2.2 렌더링 플로우 재설계
```cpp
// DX12 렌더링 플로우
Frame Begin:
1. Wait for previous frame fence
2. Reset command allocator
3. Reset command list
Record Commands:
4. Set root signature
5. Set PSO
6. Set descriptor heaps
7. Bind resources via root parameters
8. Resource barriers (if needed)
9. Set render targets
10. Set viewport/scissor
11. Draw calls
12. Resource barriers (for present)
Submit:
13. Close command list
14. Execute on command queue
15. Signal fence
16. Present swap chain
```
---
## 3. 마이그레이션 전략
### 3.1 하이브리드 접근법 (권장)
DX12는 너무 복잡하므로, **점진적 전환**을 권장합니다:
#### Option A: DX12 래퍼 레이어 구축 (추천)
```
기존 DX9 코드
DX12 Abstraction Layer (새로 구축)
DX12 API
```
**장점**:
- 기존 코드 로직 유지
- 단계적 최적화 가능
- 롤백 용이
**단점**:
- 추가 추상화 레이어 오버헤드
- DX12의 모든 기능 활용 못할 수 있음
#### Option B: 전면 재작성
모든 렌더링 코드를 DX12 네이티브로 재작성
**장점**:
- 최고 성능
- DX12 기능 완전 활용
**단점**:
- 개발 시간 매우 김 (3-6개월+)
- 높은 리스크
### 3.2 단계별 마이그레이션 (Option A 기준)
#### **Phase 1: DX12 인프라 구축** (2-3주)
1. ✅ dx12 브랜치 생성
2. ⏳ DX12 디바이스 및 커맨드 큐 초기화
3. ⏳ 스왑체인 생성
4. ⏳ 기본 Descriptor Heap 시스템
5. ⏳ 커맨드 리스트 관리자
6. ⏳ 펜스 동기화 시스템
#### **Phase 2: 리소스 관리 시스템** (2주)
7. ⏳ Upload Buffer 관리자
8. ⏳ 리소스 상태 추적 시스템
9. ⏳ 텍스처 업로드 시스템
10. ⏳ 버퍼 관리 (Vertex/Index/Constant)
#### **Phase 3: 파이프라인 추상화** (2주)
11. ⏳ Root Signature 관리
12. ⏳ PSO 캐싱 시스템
13. ⏳ 셰이더 컴파일 및 로딩
14. ⏳ Input Layout 변환
#### **Phase 4: 렌더링 래퍼** (3주)
15. ⏳ DX9-style API 래퍼 구축
- `SetRenderState()` → PSO 변경
- `SetTexture()` → Descriptor 바인딩
- `DrawPrimitive()` → Command List 기록
16. ⏳ 즉시 모드 에뮬레이션
17. ⏳ 상태 캐싱 레이어
#### **Phase 5: 기존 코드 통합** (2주)
18. ⏳ BaseGraphicsLayer를 DX12 백엔드로 연결
19. ⏳ 텍스처 시스템 연동
20. ⏳ 메시 렌더링 연동
21. ⏳ UI 렌더링 연동
#### **Phase 6: 최적화 및 테스트** (2-3주)
22. ⏳ 멀티스레드 커맨드 리스트 생성
23. ⏳ 리소스 스트리밍 최적화
24. ⏳ GPU 프로파일링
25. ⏳ 메모리 사용량 최적화
26. ⏳ 성능 벤치마크
**총 예상 기간**: 12-15주 (3-4개월)
---
## 4. 핵심 코드 변환 예제
### 4.1 디바이스 생성
```cpp
// === DX9 (현재) ===
LPDIRECT3D9 pD3D = Direct3DCreate9(D3D_SDK_VERSION);
D3DPRESENT_PARAMETERS d3dpp;
// ... d3dpp 설정 ...
pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &m_pd3dDevice);
// === DX12 (변환 후) ===
// 1. 디버그 레이어 활성화 (개발용)
ID3D12Debug* pDebug;
D3D12GetDebugInterface(IID_PPV_ARGS(&pDebug));
pDebug->EnableDebugLayer();
// 2. Factory 생성
IDXGIFactory4* pFactory;
CreateDXGIFactory2(DXGI_CREATE_FACTORY_DEBUG, IID_PPV_ARGS(&pFactory));
// 3. 어댑터 선택
IDXGIAdapter1* pAdapter;
for (UINT i = 0; pFactory->EnumAdapters1(i, &pAdapter) != DXGI_ERROR_NOT_FOUND; ++i) {
DXGI_ADAPTER_DESC1 desc;
pAdapter->GetDesc1(&desc);
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE) continue;
// 4. 디바이스 생성
if (SUCCEEDED(D3D12CreateDevice(pAdapter, D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&m_pDevice)))) {
break;
}
}
// 5. 커맨드 큐 생성
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
m_pDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_pCommandQueue));
// 6. 스왑체인 생성
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.Width = width;
swapChainDesc.Height = height;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 2; // Double buffering
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
IDXGISwapChain1* pSwapChain1;
pFactory->CreateSwapChainForHwnd(m_pCommandQueue, hWnd,
&swapChainDesc, nullptr, nullptr, &pSwapChain1);
pSwapChain1->QueryInterface(IID_PPV_ARGS(&m_pSwapChain));
```
### 4.2 텍스처 로딩
```cpp
// === DX9 (현재) ===
IDirect3DTexture9* pTexture;
D3DXCreateTextureFromFile(m_pd3dDevice, L"texture.png", &pTexture);
m_pd3dDevice->SetTexture(0, pTexture);
// === DX12 (변환 후) ===
// 1. 텍스처 데이터 로드 (CPU)
int width, height, channels;
unsigned char* imageData = stbi_load("texture.png", &width, &height, &channels, 4);
// 2. 디폴트 힙에 텍스처 리소스 생성
D3D12_RESOURCE_DESC texDesc = {};
texDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
texDesc.Width = width;
texDesc.Height = height;
texDesc.DepthOrArraySize = 1;
texDesc.MipLevels = 1;
texDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
texDesc.SampleDesc.Count = 1;
texDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
texDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
ID3D12Resource* pTexture;
m_pDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&texDesc,
D3D12_RESOURCE_STATE_COPY_DEST,
nullptr,
IID_PPV_ARGS(&pTexture));
// 3. 업로드 버퍼 생성
UINT64 uploadBufferSize;
m_pDevice->GetCopyableFootprints(&texDesc, 0, 1, 0, nullptr, nullptr, nullptr, &uploadBufferSize);
ID3D12Resource* pUploadBuffer;
m_pDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Buffer(uploadBufferSize),
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&pUploadBuffer));
// 4. 텍스처 데이터를 업로드 버퍼에 복사
D3D12_SUBRESOURCE_DATA textureData = {};
textureData.pData = imageData;
textureData.RowPitch = width * 4;
textureData.SlicePitch = textureData.RowPitch * height;
UpdateSubresources(m_pCommandList, pTexture, pUploadBuffer, 0, 0, 1, &textureData);
// 5. 리소스 배리어 (COPY_DEST -> PIXEL_SHADER_RESOURCE)
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition.pResource = pTexture;
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
m_pCommandList->ResourceBarrier(1, &barrier);
// 6. SRV (Shader Resource View) 생성
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = texDesc.Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = 1;
m_pDevice->CreateShaderResourceView(pTexture, &srvDesc, descriptorHandle);
// 7. 커맨드 리스트 실행 및 업로드 버퍼 대기
ExecuteCommandList();
WaitForGPU();
pUploadBuffer->Release();
```
### 4.3 기본 렌더링
```cpp
// === DX9 (현재) ===
m_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0,0,255), 1.0f, 0);
m_pd3dDevice->BeginScene();
m_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
m_pd3dDevice->SetTexture(0, pTexture);
m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
m_pd3dDevice->EndScene();
m_pd3dDevice->Present(NULL, NULL, NULL, NULL);
// === DX12 (변환 후) ===
// 1. 커맨드 할당자 리셋
m_pCommandAllocator->Reset();
m_pCommandList->Reset(m_pCommandAllocator, m_pPSO);
// 2. Root Signature 및 Descriptor Heap 설정
m_pCommandList->SetGraphicsRootSignature(m_pRootSignature);
ID3D12DescriptorHeap* ppHeaps[] = { m_pSrvHeap };
m_pCommandList->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
// 3. 백버퍼를 렌더 타겟으로 전환
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Transition.pResource = m_pRenderTargets[m_frameIndex];
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PRESENT;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_RENDER_TARGET;
m_pCommandList->ResourceBarrier(1, &barrier);
// 4. 렌더 타겟 설정
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_pRtvHeap->GetCPUDescriptorHandleForHeapStart(),
m_frameIndex, m_rtvDescriptorSize);
m_pCommandList->OMSetRenderTargets(1, &rtvHandle, FALSE, nullptr);
// 5. 클리어
const float clearColor[] = { 0.0f, 0.0f, 1.0f, 1.0f };
m_pCommandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
// 6. 뷰포트 및 시저 렉트 설정
m_pCommandList->RSSetViewports(1, &m_viewport);
m_pCommandList->RSSetScissorRects(1, &m_scissorRect);
// 7. 버텍스 버퍼 설정
m_pCommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_pCommandList->IASetVertexBuffers(0, 1, &m_vertexBufferView);
// 8. 텍스처 바인딩 (Root Descriptor Table)
m_pCommandList->SetGraphicsRootDescriptorTable(0, m_pSrvHeap->GetGPUDescriptorHandleForHeapStart());
// 9. 그리기
m_pCommandList->DrawInstanced(3, 1, 0, 0);
// 10. 백버퍼를 Present 상태로 전환
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_RENDER_TARGET;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PRESENT;
m_pCommandList->ResourceBarrier(1, &barrier);
// 11. 커맨드 리스트 닫기 및 실행
m_pCommandList->Close();
ID3D12CommandList* ppCommandLists[] = { m_pCommandList };
m_pCommandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
// 12. Present
m_pSwapChain->Present(1, 0);
// 13. 펜스 신호
const UINT64 fence = m_fenceValue;
m_pCommandQueue->Signal(m_pFence, fence);
m_fenceValue++;
// 14. 다음 프레임 인덱스
m_frameIndex = m_pSwapChain->GetCurrentBackBufferIndex();
// 15. 이전 프레임 대기
if (m_pFence->GetCompletedValue() < fence) {
m_pFence->SetEventOnCompletion(fence, m_fenceEvent);
WaitForSingleObject(m_fenceEvent, INFINITE);
}
```
---
## 5. 필요한 새로운 파일들
### 5.1 DX12 코어 클래스
```
Client/Engine/DX12/
├── DX12Device.h/cpp
├── DX12CommandQueue.h/cpp
├── DX12CommandList.h/cpp
├── DX12SwapChain.h/cpp
├── DX12DescriptorHeap.h/cpp
├── DX12Resource.h/cpp
├── DX12Fence.h/cpp
├── DX12PipelineState.h/cpp
├── DX12RootSignature.h/cpp
└── DX12Helpers.h (유틸리티 매크로)
```
### 5.2 리소스 관리
```
Client/Engine/DX12/Resources/
├── DX12ResourceManager.h/cpp
├── DX12UploadBuffer.h/cpp
├── DX12Texture.h/cpp
├── DX12Buffer.h/cpp
├── DX12MemoryAllocator.h/cpp
└── DX12ResourceStateTracker.h/cpp
```
### 5.3 렌더링 래퍼
```
Client/Engine/DX12/Rendering/
├── DX12RenderContext.h/cpp
├── DX12StateCache.h/cpp
├── DX12ImmediateContext.h/cpp (DX9 에뮬레이션)
└── DX12Renderer.h/cpp
```
---
## 6. 프로젝트 설정 변경
### 6.1 필요 라이브러리
```
d3d12.lib
dxgi.lib
dxguid.lib
d3dcompiler.lib // 셰이더 컴파일
dxcompiler.lib // DXC (선택사항, SM 6.0+)
```
### 6.2 필요 헤더
```cpp
#include <d3d12.h>
#include <dxgi1_6.h>
#include <d3dcompiler.h>
#include <DirectXMath.h>
#include "d3dx12.h" // 헬퍼 헤더 (별도 다운로드)
```
### 6.3 Windows SDK 요구사항
- **최소**: Windows 10 SDK (10.0.17763.0)
- **권장**: Windows 11 SDK (최신)
---
## 7. 리스크 및 도전 과제
### 7.1 기술적 도전
1. **복잡도 급증**
- DX9: ~100줄로 기본 렌더링 가능
- DX12: ~1000줄 필요
2. **디버깅 난이도**
- 잘못된 리소스 상태 → 크래시
- 동기화 문제 → 간헐적 버그
- 메모리 누수 추적 어려움
3. **성능 최적화**
- 초기에는 DX9보다 느릴 수 있음
- 멀티스레딩 활용 필수
- CPU 오버헤드 관리
### 7.2 호환성 문제
1. **하드웨어 요구사항**
- DX12 지원 GPU 필수 (대부분 2015년 이후)
- Feature Level 11_0 이상
2. **OS 요구사항**
- Windows 10 이상 필수
- Windows 7/8 지원 불가
### 7.3 개발 리소스
- **예상 개발 시간**: 3-4개월 (풀타임 1인 기준)
- **필요 전문성**: DX12, 멀티스레딩, GPU 아키텍처
- **테스트 부담**: 다양한 GPU 벤더 테스트 필요
---
## 8. 대안: DX11 고려
### 왜 DX11이 더 나을 수 있는가?
| 측면 | DX11 | DX12 |
|------|------|------|
| **학습 곡선** | 완만 (DX9와 유사) | 매우 가파름 |
| **개발 시간** | 1-2개월 | 3-4개월 |
| **성능** | 중상 | 최상 (최적화 시) |
| **호환성** | Windows 7+ | Windows 10+ |
| **디버깅** | 쉬움 | 어려움 |
| **멀티스레딩** | 제한적 | 완전 지원 |
**결론**: DX11이 **위험/보상 비율**이 더 좋음
---
## 9. 권장 접근 방식
### Option 1: DX11로 먼저 전환 (추천) ⭐⭐⭐⭐⭐
```
DX9 → DX11 (2개월) → 안정화 → DX12 (3개월)
```
**이점**:
- 단계적 학습
- 중간 마일스톤
- 위험 분산
### Option 2: DX12 직행 (고위험 고수익) ⭐⭐⭐
```
DX9 → DX12 (4개월) → 고통 → 고성능
```
**이점**:
- 최신 기술
- 최고 성능
- 장기적으로 유리
### Option 3: 멀티 백엔드 아키텍처 (전문가용) ⭐⭐⭐⭐
```
추상화 레이어
├── DX9 백엔드 (기존)
├── DX11 백엔드 (신규)
└── DX12 백엔드 (신규)
```
**이점**:
- 최대 호환성
- 점진적 마이그레이션
- 각 플랫폼 최적화
**단점**:
- 개발 부담 3배
- 유지보수 복잡
---
## 10. 실행 계획
### 즉시 시작 가능한 작업
1. ✅ dx12 브랜치 생성 완료
2. ⏳ DX12 샘플 프로젝트 생성
- 최소 삼각형 렌더링
- 텍스처 매핑
- 기본 조명
3. ⏳ 성능 벤치마크 툴 준비
4. ⏳ DX11 마이그레이션 대안 평가
### 다음 결정 포인트
**질문**: DX11 경유 vs DX12 직행?
**제안**:
1. DX12 프로토타입 2주 제작
2. 복잡도 및 성능 평가
3. DX11 경유 여부 결정
---
## 11. 리소스 및 참고자료
### 학습 자료
- [DirectX 12 Programming Guide](https://docs.microsoft.com/en-us/windows/win32/direct3d12/directx-12-programming-guide)
- [Introduction to 3D Game Programming with DirectX 12](https://www.amazon.com/Introduction-3D-Game-Programming-DirectX/dp/1942270062)
- [DirectX 12 Graphics Samples](https://github.com/microsoft/DirectX-Graphics-Samples)
### 헬퍼 라이브러리
- **d3dx12.h**: 필수 헬퍼 함수
- **DirectX Tool Kit for DX12**: 유틸리티 라이브러리
- **DirectX Shader Compiler (DXC)**: 최신 셰이더 컴파일러
### 디버깅 도구
- **PIX for Windows**: GPU 디버거
- **RenderDoc**: 프레임 캡처
- **GPU Validation Layer**: 리소스 상태 검증
---
## 12. 최종 권고사항
### 🚨 현실적인 조언
**DX9 → DX12는 6개월 이상 걸릴 수 있는 대형 프로젝트입니다.**
**추천 경로**:
```
Phase 1: DX9 → DX11 (2-3개월)
Phase 2: 안정화 및 최적화 (1개월)
Phase 3: DX11 → DX12 (3-4개월)
Phase 4: DX12 최적화 (2개월)
```
**총 기간**: 8-10개월
### 현재 작업 제안
1. **DX12 기초 프로토타입 제작** (현재 브랜치)
- 기본 디바이스 초기화
- 단순 삼각형 렌더링
- 복잡도 평가
2. **병렬로 DX11 브랜치 생성**
- DX9 → DX11은 상대적으로 쉬움
- 2-3주면 작동 가능
- 즉각적인 성능 향상
3. **의사결정**
- DX11 성공 시 → DX12 장기 프로젝트화
- DX12 프로토타입 성공 시 → 직행 고려
---
**작성일**: 2025-12-01
**작성자**: Claude AI (Anthropic)
**현재 브랜치**: dx12
**상태**: 계획 단계 - 실행 대기 중
**⚠️ 중요**: 이 문서는 계획서입니다. 실제 구현은 예상보다 복잡할 수 있습니다.