# 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 #include #include #include #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 **상태**: 계획 단계 - 실행 대기 중 **⚠️ 중요**: 이 문서는 계획서입니다. 실제 구현은 예상보다 복잡할 수 있습니다.