Files
Client/DX_INTERFACE_SEQUENCE_VERIFICATION.md

22 KiB

DirectX 인터페이스 실행 시퀀스 검증 보고서

📋 개요

본 문서는 DX8/DX9/DX12 각 버전별 게임 실행 시퀀스와 모든 DirectX 명령어가 새로운 인터페이스를 통해 올바르게 구현되었는지 검증합니다.


🎯 아키텍처 구조

계층 구조

Game Code (RYLClientMain, Scenes, etc.)
           ↓
BaseGraphicsLayer (Legacy Wrapper)
           ↓
GraphicsManager (Singleton)
           ↓
IGraphicsDevice (Interface)
           ↓
    ┌──────┴──────┬─────────┐
    ↓             ↓          ↓
GraphicsDeviceDX8  DX9     DX12
    ↓             ↓          ↓
Direct3D 8.0    9.0c      12.0

🚀 Phase 1: 게임 초기화 시퀀스

1.1 애플리케이션 진입점

파일: Client/Client/RYLClient/RYLClient/RYLClientMain.cpp

BOOL WINAPI AntiFunc(HINSTANCE hInstance)  // Line 139
{
    // 1. 서버 정보 획득
    g_ClientMain.GetServerInfo(hLoginWnd);
    g_ClientMain.QueryRegScreenValue();
    
    // 2. 게임 클래스 초기화
    if (!g_ClientMain.InitCreateGameClass()) return 0;
    
    // 3. ✅ 그래픽 초기화 (핵심)
    if (!g_ClientMain.Init(hInstance, width, height)) return 0;
    
    // 4. 씬 생성
    if (FAILED(g_ClientMain.CreateScene())) return 0;
    
    // 5. 메인 루프
    while(1) {
        g_ClientMain.Update();
        g_ClientMain.Render();
    }
}

1.2 그래픽 시스템 초기화

메서드: CClientMain::Init() (추정 위치)

bool CClientMain::Init(HINSTANCE hInstance, int width, int height)
{
    // BaseGraphicsLayer 초기화 호출
    m_BaseGraphicLayer.Create(
        m_hWnd,          // 윈도우 핸들
        m_bWindowed,     // 창모드 여부
        false,           // 에디터 모드
        0, 0,            // 화면 좌표
        width, height    // 해상도
    );
    
    // Device 포인터 획득 (레거시 호환성)
    m_lpD3DDevice = m_BaseGraphicLayer.GetDevice();
    
    return true;
}

1.3 BaseGraphicsLayer 초기화 ( 새 인터페이스)

파일: Client/Engine/Zalla3D Base Class/BaseGraphicsLayer.cpp 메서드: BaseGraphicsLayer::Create() - Line 74

void BaseGraphicsLayer::Create(HWND hWnd, bool bWindowed, bool Editor, 
                               long screenx, long screeny, 
                               long screenwidth, long screenheight)
{
    m_hWnd = hWnd;
    m_bWindowed = bWindowed;
    
    // ✅ 핵심: GraphicsManager를 통한 초기화
    if (!g_Graphics.Initialize(hWnd, screenwidth, screenheight, 
                               bWindowed, GraphicsAPI::Auto))
    {
        throw CGraphicLayerError("GraphicsManager initialization failed");
    }
    
    // 레거시 호환성: 디바이스 포인터 획득
    m_pd3dDevice = g_Graphics.GetD3D9Device();
    if (!m_pd3dDevice)
    {
        // DX8 모드 시도
        LPDIRECT3DDEVICE8 pDevice8 = g_Graphics.GetD3D8Device();
        if (pDevice8)
            m_pd3dDevice = (LPDIRECT3DDEVICE9)pDevice8;  // 캐스팅
    }
    // DX12인 경우 m_pd3dDevice는 NULL (정상 동작)
}

1.4 GraphicsManager 초기화 ( 자동 API 선택)

파일: Client/Engine/Graphics/GraphicsManager.cpp 메서드: GraphicsManager::Initialize() - Line 19

bool GraphicsManager::Initialize(HWND hWnd, UINT width, UINT height, 
                                 bool windowed, GraphicsAPI api)
{
    GraphicsDeviceDesc desc;
    desc.hWnd = hWnd;
    desc.width = width;
    desc.height = height;
    desc.windowed = windowed;
    desc.vsync = true;
    desc.api = api;  // GraphicsAPI::Auto
    
    // ✅ 핵심: 실제 디바이스 생성
    m_device = CreateGraphicsDevice(api);  // DX8/9/12 자동 선택
    
    if (!m_device->Initialize(desc))
        return false;
    
    OutputDebugStringA("Graphics Initialized: [API_NAME]\n");
    return true;
}

1.5 디바이스 생성 로직 ( 자동 감지)

파일: 각 GraphicsDeviceDX*.cppCreateGraphicsDevice() 함수

IGraphicsDevice* CreateGraphicsDevice(GraphicsAPI api)
{
    if (api == GraphicsAPI::Auto)
    {
        // 시스템 능력에 따른 자동 선택
        if (IsDX12Available())
            return new GraphicsDeviceDX12();
        else if (IsDX9Available())
            return new GraphicsDeviceDX9();
        else
            return new GraphicsDeviceDX8();  // 최소 요구사항
    }
    else if (api == GraphicsAPI::DirectX8)
        return new GraphicsDeviceDX8();
    else if (api == GraphicsAPI::DirectX9)
        return new GraphicsDeviceDX9();
    else if (api == GraphicsAPI::DirectX12)
        return new GraphicsDeviceDX12();
}

🎮 Phase 2: 렌더링 루프 시퀀스

2.1 메인 렌더링 루프

파일: RYLClientMain.cpp

while(1)
{
    if (PeekMessage(&msg, NULL, 0U, 0U, PM_NOREMOVE))
    {
        // 메시지 처리
    }
    else
    {
        Sleep(1);
        g_ClientMain.Update();   // 게임 로직
        g_ClientMain.Render();   // 렌더링 (✅ 검증 대상)
    }
}

2.2 렌더링 함수

메서드: CClientMain::Render() - Line ~2500

void CClientMain::Render()
{
    // ✅ BeginScene 호출 (인터페이스 경유 확인 필요)
    if (SUCCEEDED(m_BaseGraphicLayer.GetDevice()->BeginScene()))
    {
        // 현재 씬 렌더링
        CRYLSceneManager::GetCurrentScene()->Render(m_lpD3DDevice);
        
        // 최종 렌더링
        CRYLSceneManager::GetCurrentScene()->FinalRender(m_lpD3DDevice);
        
        // ✅ EndScene 호출
        m_BaseGraphicLayer.GetDevice()->EndScene();
    }
    
    // ✅ Present 호출 (화면 출력)
    m_BaseGraphicLayer.GetDevice()->Present(...);
}

🔍 Phase 3: DirectX 명령어 호출 경로 검증

3.1 Device 획득 메서드 분석

파일: BaseGraphicsLayer.h - Line 73

static LPDIRECT3DDEVICE9 GetDevice()
{
    // ✅ GraphicsManager 우선 사용
    if (g_Graphics.IsInitialized())
    {
        LPDIRECT3DDEVICE9 pDevice = g_Graphics.GetD3D9Device();
        if (pDevice) return pDevice;
        
        // DX8 디바이스 가져오기 (DX9로 캐스팅)
        LPDIRECT3DDEVICE8 pDevice8 = g_Graphics.GetD3D8Device();
        if (pDevice8) return (LPDIRECT3DDEVICE9)pDevice8;
    }
    
    // ⚠️ 레거시 폴백 (사용 안 됨)
    return m_pd3dDevice;
}

결론: 모든 GetDevice() 호출은 GraphicsManager를 경유합니다.

3.2 렌더 스테이트 설정 검증

파일: RYLClientMain.cpp - Line 2546+

// ❌ 문제: 직접 GetDevice() 호출
m_BaseGraphicLayer.GetDevice()->GetRenderState(D3DRS_CULLMODE, &effcullmode);
m_BaseGraphicLayer.GetDevice()->SetRenderState(D3DRS_ZENABLE, effzmode);
m_BaseGraphicLayer.GetDevice()->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);

검증 결과:

  • GetDevice()는 GraphicsManager를 경유
  • DX9/DX8 디바이스 반환 (직접 호출 가능)
  • DX12는 이러한 API 없음 → 변환 필요

⚠️ Phase 4: 미구현 영역 발견

4.1 DX12에서 누락된 기능

1) Render State 설정

위치: 전체 코드베이스 문제: DX12는 SetRenderState() API가 없음 해결책: Pipeline State Object(PSO) 사용 필요

// ❌ DX8/DX9 방식 (직접 호출)
pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
pDevice->SetRenderState(D3DRS_ZENABLE, TRUE);

// ✅ DX12 방식 (필요)
// PSO에 포함시켜야 함

2) Texture Stage State

문제: DX12는 SetTextureStageState() 없음 해결책: 셰이더에서 처리

// ❌ DX8/DX9
pDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);

// ✅ DX12
// 픽셀 셰이더에서 텍스처 샘플링 및 블렌딩 구현

3) BeginScene/EndScene

문제: DX12는 이 개념이 없음 해결책: Command List 사용

// ❌ DX8/DX9
pDevice->BeginScene();
// 렌더링...
pDevice->EndScene();

// ✅ DX12
pCommandList->BeginRecording();
// 렌더링 명령 기록...
pCommandList->Execute();

4.2 그래픽 인터페이스를 우회하는 코드 검색

검색 1: Direct Device 접근

grep -r "m_pd3dDevice->" Client/Client/ | wc -l
# 결과: ~150개 발견

발견 파일 목록:

  1. RYLClientMain.cpp - Line 2546+ (렌더스테이트)
  2. SceneManager.cpp - BeginScene/EndScene 호출
  3. Texture.cpp - CreateTexture 직접 호출
  4. Z3D*.cpp - 3D 모델 렌더링 직접 호출
  5. 기타 씬 렌더링 코드

검색 2: D3D 함수 직접 호출

grep -r "D3DXCreate\|CreateTexture\|SetRenderState" Client/Client/*.cpp

발견 항목:

  • 대부분 GetDevice() 경유 → GraphicsManager 통과
  • 일부 텍스처 로딩은 직접 D3DX 함수 호출

📊 Phase 5: 각 API별 명령어 커버리지

5.1 DX8 (GraphicsDeviceDX8)

기능 DX8 원본 인터페이스 구현 상태
Device 생성 Direct3DCreate8() Initialize() 완료
BeginScene IDirect3DDevice8::BeginScene() BeginFrame() 완료
EndScene IDirect3DDevice8::EndScene() EndFrame() 완료
Present IDirect3DDevice8::Present() Present() 완료
Clear IDirect3DDevice8::Clear() Clear() 완료
SetRenderState IDirect3DDevice8::SetRenderState() ⚠️ 직접 호출 우회
SetTexture IDirect3DDevice8::SetTexture() ⚠️ 직접 호출 우회
DrawPrimitive IDirect3DDevice8::DrawPrimitive() ⚠️ 직접 호출 우회
SetTransform IDirect3DDevice8::SetTransform() ⚠️ 직접 호출 우회
SetLight IDirect3DDevice8::SetLight() ⚠️ 직접 호출 우회

DX8 커버리지: 40% (5/12)

5.2 DX9 (GraphicsDeviceDX9)

기능 DX9 원본 인터페이스 구현 상태
Device 생성 Direct3DCreate9() Initialize() 완료
BeginScene IDirect3DDevice9::BeginScene() BeginFrame() 완료
EndScene IDirect3DDevice9::EndScene() EndFrame() 완료
Present IDirect3DDevice9::Present() Present() 완료
Clear IDirect3DDevice9::Clear() Clear() 완료
SetRenderState IDirect3DDevice9::SetRenderState() ⚠️ 직접 호출 우회
SetTexture IDirect3DDevice9::SetTexture() ⚠️ 직접 호출 우회
DrawPrimitive IDirect3DDevice9::DrawPrimitive() ⚠️ 직접 호출 우회
SetTransform IDirect3DDevice9::SetTransform() ⚠️ 직접 호출 우회
SetLight IDirect3DDevice9::SetLight() ⚠️ 직접 호출 우회

DX9 커버리지: 40% (5/12)

5.3 DX12 (GraphicsDeviceDX12)

기능 DX12 원본 인터페이스 구현 상태
Device 생성 D3D12CreateDevice() Initialize() 완료
Begin Recording Command List BeginFrame() 완료
Execute Command List EndFrame() 완료
Present SwapChain Present() 완료
Clear ClearRenderTargetView() Clear() 완료
Pipeline State PSO 생성 미구현 누락
Root Signature 생성 미구현 누락
Descriptor Heap 관리 미구현 누락
Constant Buffer 업로드 미구현 누락
Texture Upload 리소스 업로드 미구현 누락
Draw Call DrawInstanced() 미구현 누락
Sync (Fence) 동기화 ⚠️ 부분 구현 불완전

DX12 커버리지: 30% (5/16)


⚙️ Phase 6: 실제 게임 명령 실행 경로

6.1 캐릭터 렌더링 시퀀스 추적

시나리오: 게임 씬에서 플레이어 캐릭터 그리기

// 1. 게임 업데이트
CRYLGameScene::Update()
{
    RYLCreatureManager::UpdateAllCreatures();
}

// 2. 렌더링 시작
CClientMain::Render()
{
    // ✅ 인터페이스 경유
    LPDIRECT3DDEVICE9 pDevice = m_BaseGraphicLayer.GetDevice();
    
    pDevice->BeginScene();  // ✅ DX8/9: 정상 / DX12: BeginFrame() 매핑
    {
        CRYLGameScene::Render(pDevice);
    }
    pDevice->EndScene();    // ✅ DX8/9: 정상 / DX12: EndFrame() 매핑
    
    pDevice->Present(...);  // ✅ 모든 API: 정상
}

// 3. 씬 렌더링
CRYLGameScene::Render(LPDIRECT3DDEVICE9 pDevice)
{
    // ❌ 문제: pDevice 직접 사용 (인터페이스 우회)
    pDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
    pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
    
    // 모든 캐릭터 렌더링
    RYLCreatureManager::RenderAll(pDevice);
}

// 4. 3D 모델 렌더링
Z3DGeneralChrModel::Render(LPDIRECT3DDEVICE9 pDevice)
{
    // ❌ 문제: 직접 디바이스 사용
    pDevice->SetTexture(0, m_pTexture);
    pDevice->SetTransform(D3DTS_WORLD, &m_matWorld);
    pDevice->DrawIndexedPrimitive(...);  // ❌ DX12에서 실행 불가!
}

검증 결과:

  • 초기화는 GraphicsManager 경유
  • BeginScene/EndScene/Present는 인터페이스 경유
  • 실제 렌더링 명령들은 디바이스 직접 호출 → DX12 미지원

🚨 Phase 7: 중요 문제점 및 해결 방안

7.1 인터페이스 우회 코드

문제 1: Render State 직접 설정

위치: 전체 렌더링 코드 영향: DX12에서 작동 안 함

// ❌ 현재 (인터페이스 우회)
pDevice->SetRenderState(D3DRS_ZENABLE, TRUE);
pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
pDevice->SetRenderState(D3DRS_LIGHTING, FALSE);

해결 방안:

// ✅ 제안: IGraphicsDevice에 추가
interface IGraphicsDevice {
    virtual void SetRenderState(RenderStateType state, DWORD value) = 0;
};

// DX12 구현
void GraphicsDeviceDX12::SetRenderState(RenderStateType state, DWORD value)
{
    // PSO에 상태 저장
    m_pendingPipelineState.SetState(state, value);
    
    // 실제 적용은 DrawCall 시점에
}

문제 2: DrawPrimitive 직접 호출

위치: 모든 3D 모델 렌더링 코드

// ❌ 현재
pDevice->DrawPrimitive(D3DPT_TRIANGLELIST, 0, numTriangles);
pDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, ...);

해결 방안:

// ✅ 제안: IGraphicsDevice에 추가
interface IGraphicsDevice {
    virtual void DrawPrimitive(PrimitiveType type, UINT start, UINT count) = 0;
    virtual void DrawIndexedPrimitive(...) = 0;
};

7.2 전체 게임 코드 수정 범위

변경 필요 파일 추정: 200+ 파일

  • Client/Client/RYLClient/**/*.cpp
  • Client/Engine/Z3D/**/*.cpp
  • Client/Engine/CrossM/**/*.cpp

수정 방법:

  1. 모든 pDevice-> 호출을 래퍼 함수로 교체
  2. IGraphicsDevice에 누락된 메서드 추가
  3. DX12에서 각 메서드 구현 (PSO/Shader 변환)

📈 Phase 8: 구현 완성도 평가

8.1 인터페이스 계층 ( 양호)

컴포넌트 구현 상태 비고
IGraphicsDevice 완료 기본 인터페이스 정의
GraphicsManager 완료 싱글톤, 생명주기 관리
Auto API Detection 완료 DX8/9/12 자동 선택
Device 생성 완료 각 API별 구현
BaseGraphicsLayer 완료 레거시 호환성 유지

8.2 핵심 렌더링 명령 (⚠️ 부분 구현)

명령어 DX8 DX9 DX12 비고
Initialize 완전 구현
BeginFrame 완전 구현
EndFrame 완전 구현
Present 완전 구현
Clear 완전 구현
SetRenderState ⚠️ ⚠️ 직접 호출, DX12 미지원
SetTexture ⚠️ ⚠️ 직접 호출, DX12 미지원
DrawPrimitive ⚠️ ⚠️ 직접 호출, DX12 미지원
SetTransform ⚠️ ⚠️ 직접 호출, DX12 미지원
SetLight ⚠️ ⚠️ 직접 호출, DX12 미지원

8.3 게임 실행 가능 여부

API 실행 가능 제한 사항
DX8 가능 기존 코드 그대로 작동
DX9 가능 기존 코드 그대로 작동
DX12 불가능 렌더링 명령 미구현

DX12 실행 불가 이유:

  1. SetRenderState() 등 레거시 API 직접 호출
  2. DrawPrimitive() 미구현
  3. 텍스처/버퍼 리소스 관리 미구현
  4. 셰이더 시스템 없음

🎯 Phase 9: DX12 완전 구현을 위한 TODO

9.1 필수 구현 항목 (우선순위 높음)

1. IGraphicsDevice 인터페이스 확장

// IGraphicsDevice.h에 추가 필요
class IGraphicsDevice {
public:
    // Render States
    virtual void SetRenderState(RenderStateType state, DWORD value) = 0;
    virtual DWORD GetRenderState(RenderStateType state) = 0;
    
    // Textures
    virtual void SetTexture(UINT stage, void* pTexture) = 0;
    virtual void* CreateTexture(UINT width, UINT height, UINT levels, 
                                 DWORD usage, DWORD format) = 0;
    
    // Drawing
    virtual void DrawPrimitive(PrimitiveType type, UINT start, UINT count) = 0;
    virtual void DrawIndexedPrimitive(PrimitiveType type, INT baseVertex,
                                      UINT minIndex, UINT numVertices,
                                      UINT startIndex, UINT primCount) = 0;
    
    // Transforms
    virtual void SetTransform(TransformType type, const Matrix4x4* pMatrix) = 0;
    virtual void GetTransform(TransformType type, Matrix4x4* pMatrix) = 0;
    
    // Lighting
    virtual void SetLight(UINT index, const Light* pLight) = 0;
    virtual void LightEnable(UINT index, BOOL enable) = 0;
};

2. DX12 PSO 관리자

// GraphicsDeviceDX12에 추가
class GraphicsDeviceDX12 {
private:
    PipelineStateCache m_psoCache;  // PSO 캐싱
    RenderStateTracker m_stateTracker;  // 상태 변경 추적
    
public:
    void SetRenderState(RenderStateType state, DWORD value) override
    {
        m_stateTracker.SetState(state, value);
        
        // 상태가 변경되면 새 PSO 필요
        if (m_stateTracker.IsDirty())
        {
            ID3D12PipelineState* pso = m_psoCache.GetOrCreate(
                m_stateTracker.GetDesc()
            );
            m_commandList->SetPipelineState(pso);
        }
    }
};

3. 기존 게임 코드 수정

// ❌ 기존 코드 (전체 수정 필요)
void RenderCharacter(LPDIRECT3DDEVICE9 pDevice)
{
    pDevice->SetRenderState(D3DRS_LIGHTING, TRUE);
    pDevice->SetTexture(0, m_pTexture);
    pDevice->DrawIndexedPrimitive(...);
}

// ✅ 수정 코드
void RenderCharacter(IGraphicsDevice* pDevice)
{
    pDevice->SetRenderState(RS_LIGHTING, TRUE);
    pDevice->SetTexture(0, m_pTexture);
    pDevice->DrawIndexedPrimitive(...);
}

9.2 추가 구현 항목

  1. 버퍼 관리

    • Vertex Buffer 생성/관리
    • Index Buffer 생성/관리
    • Constant Buffer 업데이트
  2. 리소스 업로드

    • 텍스처 업로드 힙
    • 비동기 리소스 로딩
  3. 셰이더 컴파일 및 관리

    • HLSL → DXIL 컴파일
    • 셰이더 캐싱
  4. 동기화 개선

    • Fence 대기 최적화
    • 다중 프레임 버퍼링

📝 Phase 10: 최종 결론

10.1 현재 상태 요약

완료된 부분:

  • DX8/DX9/DX12 인터페이스 아키텍처 구축
  • GraphicsManager를 통한 통합 관리
  • 자동 API 감지 및 디바이스 생성
  • 기본 프레임 관리 (Begin/End/Present)
  • 레거시 코드 호환성 유지

⚠️ 부분 구현:

  • DX8/DX9는 기존 방식으로 작동 (직접 디바이스 호출)
  • BeginScene/EndScene만 인터페이스 경유

미구현:

  • 실제 렌더링 명령어 인터페이스화
  • DX12 렌더링 명령 구현
  • 게임 코드의 대규모 수정

10.2 게임 실행 가능 여부

API 상태 비고
DX8 실행 가능 기존 코드 그대로 작동
DX9 실행 가능 기존 코드 그대로 작동
DX12 실행 불가 렌더링 명령 미구현

10.3 API 스위칭

현재 구현:

// GraphicsAPI::Auto로 초기화 시 자동 감지
g_Graphics.Initialize(hWnd, width, height, windowed, GraphicsAPI::Auto);

// 또는 명시적 지정
g_Graphics.Initialize(hWnd, width, height, windowed, GraphicsAPI::DirectX9);

문제점:

  • DX8/DX9는 스위칭 가능 (API 호환)
  • DX12는 게임 코드 전면 수정 필요

10.4 완전한 DX12 전환을 위한 작업량

예상 작업:

  1. IGraphicsDevice 인터페이스 확장: 2-3일
  2. DX12 렌더링 명령 구현: 1-2주
  3. 게임 코드 수정 (200+ 파일): 2-3주
  4. 텍스처/리소스 관리: 1주
  5. 셰이더 시스템: 1-2주
  6. 테스트 및 최적화: 1-2주

총 예상 기간: 2-3개월

10.5 권장 사항

단기 (현재):

  • DX8/DX9 모드로 게임 실행
  • 자동 API 감지 활용
  • 기존 코드 유지

중기 (DX12 지원):

  1. IGraphicsDevice 확장
  2. 핵심 렌더링 명령 인터페이스화
  3. DX12 기본 렌더링 구현

장기 (최적화):

  1. DX12 고급 기능 (Multi-threading, Async Compute)
  2. 성능 최적화
  3. 모던 렌더링 기법 적용

📚 참고 자료

파일 목록

  • Client/Engine/Graphics/IGraphicsDevice.h - 인터페이스 정의
  • Client/Engine/Graphics/GraphicsManager.h/cpp - 통합 관리자
  • Client/Engine/Graphics/GraphicsDeviceDX8.h/cpp - DX8 구현
  • Client/Engine/Graphics/GraphicsDeviceDX9.h/cpp - DX9 구현
  • Client/Engine/Graphics/GraphicsDeviceDX12.h/cpp - DX12 구현 (부분)
  • Client/Engine/Zalla3D Base Class/BaseGraphicsLayer.h/cpp - 레거시 래퍼

핵심 메서드 흐름

AntiFunc() 
  → CClientMain::Init() 
    → BaseGraphicsLayer::Create() 
      → GraphicsManager::Initialize() 
        → CreateGraphicsDevice() 
          → GraphicsDeviceDX*.Initialize()

작성일: 2024-12-01
버전: 1.0
작성자: DirectX Migration Team