장치 초기화

Jaemyeong Lee·2025년 3월 9일
0

수업


주제

DirectX11 장치 초기화 – Device, DeviceContext, SwapChain, RenderTargetView, Viewport 구성과 렌더링 준비 구조 만들기


개념

● 장치 초기화의 목적

DirectX11을 사용해 GPU 렌더링을 하기 위해서는 가장 먼저 렌더링을 위한 기본 구성 요소들(Device, Context, SwapChain 등)을 초기화해야 한다. 이 구성은 앞으로 모든 그래픽 작업의 기초 토대가 된다.

기본적으로 아래의 5단계를 거친다:

  1. ID3D11Device: GPU 리소스를 생성하는 커맨드 센터
  2. ID3D11DeviceContext: 생성된 리소스를 파이프라인에 연결하고 명령 전달
  3. IDXGISwapChain: 후면 버퍼와 전면 버퍼를 교환하여 화면에 출력
  4. ID3D11RenderTargetView: 후면 버퍼를 GPU에 설명하는 뷰 객체
  5. D3D11_VIEWPORT: 실제로 출력할 화면의 위치와 크기 지정

● COM 객체와 ComPtr

DirectX는 COM(Component Object Model) 기반으로 구현되어 있어 모든 객체는 참조 카운트를 기반으로 동작한다.

수동으로 AddRef()Release()를 호출하면 실수하기 쉽기 때문에, Microsoft::WRL::ComPtr을 사용해 자동으로 참조를 관리하는 것이 필수적이다.

#include <wrl.h>
using namespace Microsoft::WRL;

ComPtr<ID3D11Device> _device;
ComPtr<ID3D11DeviceContext> _deviceContext;
ComPtr<IDXGISwapChain> _swapChain;

● SwapChain (스왑 체인)

SwapChain후면 버퍼에서 전면 버퍼로 전환하는 역할을 한다.

  • 후면 버퍼(BackBuffer): 우리가 그림을 그리는 곳
  • 전면 버퍼(FrontBuffer): 사용자에게 보여지는 화면

스왑 체인은 이 둘 사이의 고속 복사 및 교체를 관리한다.


● RenderTargetView 생성

DirectX에서 텍스처만으로는 어디에 그려야 할지 알 수 없다. 따라서 텍스처에 대해 "이건 최종 결과물을 그리는 용도입니다"라고 알려주는 뷰(View)를 만들어야 한다.

ID3D11RenderTargetView는 이러한 역할을 한다.


● Viewport 설정

GPU가 출력할 화면의 크기와 위치를 명확히 인식할 수 있도록 설정하는 구조체가 D3D11_VIEWPORT다. DirectX에서는 3D 공간의 결과를 2D 화면으로 출력할 때 반드시 설정이 필요하다.


● 렌더링 흐름

렌더링을 하기 위한 기본 흐름은 아래와 같다:

  1. RenderBegin()
    • 렌더 타겟 등록
    • 화면 클리어
    • 뷰포트 설정
  2. RenderEnd()
    • 후면 버퍼 → 전면 버퍼로 복사 (화면 출력)

용어 정리

용어설명
COM 객체Microsoft의 COM 기반으로 만든 객체. 참조 카운트 기반
ComPtrCOM 객체를 자동으로 관리해주는 스마트 포인터
ID3D11DeviceGPU 리소스를 생성하는 객체
ID3D11DeviceContext렌더링 파이프라인에 명령을 전달하는 객체
IDXGISwapChain후면/전면 버퍼를 관리하고 교환해주는 구조
ID3D11RenderTargetView텍스처에 렌더링용 뷰(View)를 부여하는 객체
D3D11_VIEWPORT출력할 화면의 크기와 위치 지정
D3D11CreateDeviceAndSwapChainDevice, Context, SwapChain을 한 번에 생성하는 함수

코드 분석

✅ Game.h

class Game
{
public:
    Game();
    ~Game();

    void Init(HWND hwnd);
    void Update();
    void Render();

private:
    void CreateDeviceAndSwapChain();
    void CreateRenderTargetView();
    void SetViewport();
    void RenderBegin();
    void RenderEnd();

private:
    HWND _hwnd;
    uint32 _width = 0;
    uint32 _height = 0;

    ComPtr<ID3D11Device> _device;
    ComPtr<ID3D11DeviceContext> _deviceContext;
    ComPtr<IDXGISwapChain> _swapChain;
    ComPtr<ID3D11RenderTargetView> _renderTargetView;

    D3D11_VIEWPORT _viewport = { 0 };
    float _clearColor[4] = { 0.5f, 0.5f, 0.5f, 0.5f };
};

✅ Init()

void Game::Init(HWND hwnd)
{
    _hwnd = hwnd;
    _width = GWinSizeX;
    _height = GWinSizeY;

    CreateDeviceAndSwapChain();
    CreateRenderTargetView();
    SetViewport();
}

✅ CreateDeviceAndSwapChain()

void Game::CreateDeviceAndSwapChain()
{
    DXGI_SWAP_CHAIN_DESC desc = {};
    desc.BufferDesc.Width = _width;
    desc.BufferDesc.Height = _height;
    desc.BufferDesc.RefreshRate.Numerator = 60;
    desc.BufferDesc.RefreshRate.Denominator = 1;
    desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    desc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    desc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

    desc.SampleDesc.Count = 1;
    desc.SampleDesc.Quality = 0;

    desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    desc.BufferCount = 1;
    desc.OutputWindow = _hwnd;
    desc.Windowed = true;
    desc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

    HRESULT hr = ::D3D11CreateDeviceAndSwapChain(
        nullptr,
        D3D_DRIVER_TYPE_HARDWARE,
        nullptr,
        0,
        nullptr,
        0,
        D3D11_SDK_VERSION,
        &desc,
        _swapChain.GetAddressOf(),
        _device.GetAddressOf(),
        nullptr,
        _deviceContext.GetAddressOf()
    );

    CHECK(hr);
}

✅ CreateRenderTargetView()

void Game::CreateRenderTargetView()
{
    ComPtr<ID3D11Texture2D> backBuffer;
    HRESULT hr = _swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)backBuffer.GetAddressOf());
    CHECK(hr);

    hr = _device->CreateRenderTargetView(backBuffer.Get(), nullptr, _renderTargetView.GetAddressOf());
    CHECK(hr);
}

✅ SetViewport()

void Game::SetViewport()
{
    _viewport.TopLeftX = 0.f;
    _viewport.TopLeftY = 0.f;
    _viewport.Width = static_cast<float>(_width);
    _viewport.Height = static_cast<float>(_height);
    _viewport.MinDepth = 0.f;
    _viewport.MaxDepth = 1.f;
}

✅ RenderBegin() / RenderEnd()

void Game::RenderBegin()
{
    _deviceContext->OMSetRenderTargets(1, _renderTargetView.GetAddressOf(), nullptr);
    _deviceContext->ClearRenderTargetView(_renderTargetView.Get(), _clearColor);
    _deviceContext->RSSetViewports(1, &_viewport);
}

void Game::RenderEnd()
{
    HRESULT hr = _swapChain->Present(1, 0);
    CHECK(hr);
}

✅ Render()

void Game::Render()
{
    RenderBegin();

    // TODO: 이후에 삼각형, 메시 등을 그릴 공간

    RenderEnd();
}

핵심

  • DirectX 렌더링의 최소 토대는 _device, _deviceContext, _swapChain이다. 이 3개는 항상 세트로 초기화해야 한다.
  • 스마트 포인터(ComPtr)로 COM 객체를 관리하지 않으면 메모리 누수가 발생할 수 있으므로 반드시 사용할 것.
  • CreateDeviceAndSwapChain()은 핵심 3요소를 한 번에 생성하는 함수로, 무조건 알고 있어야 한다.
  • 후면 버퍼(Texture2D)는 GPU가 이해할 수 있도록 RenderTargetView로 감싸야 한다.
  • RenderBegin()RenderEnd()의 구성은 프레임의 기본 단위이며, 이 구조는 앞으로 셰이더, 정점 버퍼, 드로우콜을 넣는 핵심 루틴이다.
  • ✅ DirectX 함수는 인자 수가 많지만, "뭘 하고 있는가?"에 집중하면 전체 구조가 보인다.

profile
李家네_공부방

0개의 댓글