33 분 소요

🔷 초기화

🔹 Engine Class

  • 엔진의 핵심적인 기능을 담당하는 클래스
// 그려질 화면 크기 정보를 가짐
D3D12_VIEWPORT	_viewport = {};		
D3D12_RECT	_scissorRect = {};	
// 
shared_ptr<class Device>			_device;
shared_ptr<class CommandQueue>		_cmdQueue;
shared_ptr<class SwapChain>			_swapChain;
shared_ptr<class DescriptorHeap>	_descHeap;
...
// (1) 응용 프로그램에 대한 뷰포트, 가위 직사각형 정보 설정
_viewport = { 0,0,static_cast<FLOAT>(window.width), static_cast<FLOAT>(window.height), 0.0f, 1.0f };
_scissorRect = CD3DX12_RECT(0, 0, window.width, window.height);

// (2) Device, CommandQueue, SwapChain, DescriptorHeap 생성
_device = make_shared<Device>();
_cmdQueue = make_shared<CommandQueue>();
_swapChain = make_shared<SwapChain>();
_descHeap = make_shared<DescriptorHeap>();

// (3) Device, CommandQueue, SwapChain, DescriptorHeap 초기화
_device->Init();
_cmdQueue->Init(_device->GetDevice(), _swapChain, _descHeap);
_swapChain->Init(window, _device->GetDXGI(), _cmdQueue->GetCommandQueue());
_descHeap->Init(_device->GetDevice(), _swapChain);


🔹 Device Class

  • 각종 객체를 생성할 수 있는 device와 _dxgi를 관리하는 클래스
  • 인력 사무소

ComPtr<IDXGIFactory>	_dxgi;		// 화면 관련 기능
ComPtr<ID3D12Device>	_device;	// 각종 객체 생성 (GPU)

1) IDXGIFactory 선언

  • 전체 화면 전환을 처리하는 DXGI 개체릴 생성하는 메서드를 구현
    ::CreateDXGIFactory(IID_PPV_ARGS(&_dxgi));
    

2) ID3D12Device 선언

  • 가상 어댑터
  • command allocator, command list, command queue, fence, resource, pipeline state object, heap, root signature, sampler, resource view … 생성 가능
    ::D3D12CreateDevice(nullptr, D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&_device));
    


🔹 CommandQueue Class

  • 렌더링 전·후에 대한 처리와 명령에 대한 정보를 가지는 클래스
  • 외주 일감 목록

    GPU엔 명령 대기열(Command Queue)가 1개 존재한다.
    CPU는 그리기 명령이 담긴 명령 목록(Command List)을 DirectX API를 통해 대기열에 제출한다.
    하지만, 명령은 제출하는 즉시 시행되지 않는다.


ComPtr<ID3D12CommandQueue>			_cmdQueue;
ComPtr<ID3D12CommandAllocator>		_cmdAlloc;
ComPtr<ID3D12GraphicsCommandList>	_cmdList;

ComPtr<ID3D12Fence>					_fence;
uint32								_fenceValue = 0; // 시간상의 특정 울타리 지점을 식별하는 정수

1) ID3D12CommandQueue 선언

  • 명령 목록을 제출
  • 명령 목록 동기화
  • 명령 큐 계측
  • 리소스 타일 매핑을 업데이트하는 메서드 제공
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;

device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&_cmdQueue));

2) ID3D12CommandAllocator 선언

  • GPU 명령에 대한 스토리지 할당
  • 명령 목록에 추가된 명령들은 이 할당자의 메모리에 저장된다.
  • 여러 명령 목록을 연관시켜도 되지만, 기록중인 명력 목록 외에는 전부 Close()되어야 한다.
device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&_cmdAlloc));

3) ID3D12GraphicsCommandList 선언

  • 렌더링을 위한 그래픽 명령 목록을 캡슐화
  • 명령 목록 실행을 계측
  • 파이프라인 상태 설정 및 지우기
device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, _cmdAlloc.Get(), nullptr, IID_PPV_ARGS(&_cmdList));
// 명령 기록이 끝났음을 기록한다.
_cmdList->Close();

4) ID3D12Fence 선언

  • CPU 동기화에 사용되는 개체 및 하나 이상의 GPU를 나타냄
  • 대기열에 울타리를 쳐서 명령을 처리한다.
  • 시간상의 특정 울타리 지점을 식별하는 정수(UINT64)값을 관리
device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&_fence));	// Fence 생성
_fenceEvent = ::CreateEvent(nullptr, FALSE, FALSE, nullptr);	

1) 렌더링 전 필요한 명력을 _cmdList에 추가하는 작업

// (1) _cmdAlloc과 _cmdList를 처음 생성했을 때와 같은 상태로 만든다.
// ⚠️ _cmdAlloc은 GPU가 명령 할당자에 담긴 모든 명령을 실행했을이 확실하기 전까진 재설정하지 않는다.
_cmdAlloc->Reset();
_cmdList->Reset(_cmdAlloc.Get(), nullptr);

// (2) 자원 상태 전이
D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
	_swapChain->GetCurrentBackBufferResource().Get(),
	D3D12_RESOURCE_STATE_PRESENT,
	D3D12_RESOURCE_STATE_RENDER_TARGET);
_cmdList->ResourceBarrier(1, &barrier);

(3) ViewPort, ScissorRect 설정
_cmdList->RSSetViewports(1, viewport);	// 뷰포트 설정
_cmdList->RSSetScissorRects(1, rect);	// 가위 직사각형 설정

// 화면 뒷 배경 설정
D3D12_CPU_DESCRIPTOR_HANDLE backBufferView = _descHeap->GetBackBufferView();
_cmdList->ClearRenderTargetView(backBufferView, Colors::BlanchedAlmond, 0, nullptr);	// 렌더 대상 뷰 지우기
_cmdList->OMSetRenderTargets(1, &backBufferView, FALSE, nullptr);	// 렌더링 대상 및 깊이 스텐실에 대한 CPU 설명자 핸들을 설정

자원 상태 전이(transition resource barrier)

  • 자원 위험 상황(resource hazard)를 방지하기 위해 개발자가 자원의 상태를 설정해 준다.
  • 전이 자원 장벽(transition resource barrier)들의 배열을 설정하여 기정한다.

    전이 자원 장벽(transition resource barrier)

    • GPU에게 자원의 상태가 전이됨을 알려주는 하나의 명령

뷰포트(viewport)

  • 장면을 그려 넣고자 하는 후면 버퍼의 부분직사각형(subrectangle)영역
  • 명령 목록을 재설정(Reset)하면 뷰포트들도 재설정해야 한다.

가위 직사각형(scissor rectangle)

  • 특정 픽셀들을 선별(culling)하는 용도로 사용
  • 렌더링 시 가위 직사각형의 바깥에 있는 픽셀들은 후면 버퍼에 래스터화(픽셀 선별)되지 않는다. (최적화 기법)
  • 명령 목록을 재설정(Reset)하면 가위 직사각형들도 재설정해야한다.

2) 모든 명령을 _cmdList에 추가한 뒤의 작업

// (1) 자원 상태 전이
D3D12_RESOURCE_BARRIER barrier = CD3DX12_RESOURCE_BARRIER::Transition(
	_swapChain->GetCurrentBackBufferResource().Get(),
	D3D12_RESOURCE_STATE_RENDER_TARGET,
	D3D12_RESOURCE_STATE_PRESENT);
_cmdList->ResourceBarrier(1, &barrier);
// (2) 명령 목록 닫음
_cmdList->Close();

// (3) 명령 목록에 있는 명령들을 대기열에 추가한다.
// 명령 대기열은 명령 목록의 할당자에 담긴 명령들을 참조한다.
ID3D12CommandList* cmdListArr[] = { _cmdList.Get() };
_cmdQueue->ExecuteCommandLists(_countof(cmdListArr), cmdListArr);

// (4) 제시
_swapChain->Present();

// (5) CPU와 GPU 동기화
WaitSync();

// (6) 현재 back buffer 색인을 바꿈
_swapChain->SwapIndex();

3) CPU와 GPU 동기화

// (1) 새 울타리 지점을 만들 댸 마다 울타리값을 1씩 증가시킴
_fenceValue++;

// (2) Signal 명령 추가
_cmdQueue->Signal(_fence.Get(), _fenceValue);

// (3) 새 울타리 지점은 GPU가 현재 Signal()명령까지의 모든 명령을 처리하지 전까지는 설정되지 않는다.
if (_fence->GetCompletedValue() < _fenceValue)
{
	_fence->SetEventOnCompletion(_fenceValue, _fenceEvent);
	::WaitForSingleObject(_fenceEvent, INFINITE);
}


🔹 SwapChain Class

  • 화면에 표시 될 정보를 관리하는 클래스

ComPtr<IDXGISwapChain>	_swapChain;
ComPtr<ID3D12Resource>	_renderTargets[SWAP_CHAIN_BUFFER_COUNT];

1) ID3D12Resource

  • CPU 및 GPU의 일반화된 기능을 캡슐화하여 실제 메모리 또는 힙을 읽고 쓸 수 있음
  • 셰이더 샘플링에 최적화된 다차원 데이터 뿐만 아니라 간단한 데이터 배열을 구성하고 조작하기 위한 추상화가 포함됨
DXGI_SWAP_CHAIN_DESC chainDesc = {};
chainDesc.BufferDesc.Width = static_cast<uint32>(window.width);
chainDesc.BufferDesc.Height = static_cast<uint32>(window.height);
chainDesc.BufferDesc.RefreshRate.Numerator = 60;			// 화면 갱신 비율
chainDesc.BufferDesc.RefreshRate.Denominator = 1;			// 화면 갱신 비율
chainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;	// 버퍼의 디스플레이 형식
chainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
chainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
chainDesc.SampleDesc.Count = 1;								// 멀티 샘플링 OFF
chainDesc.SampleDesc.Quality = 0;
chainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;	// 후면 버퍼에 렌더링할 것 
chainDesc.BufferCount = SWAP_CHAIN_BUFFER_COUNT;			// 전면+후면 버퍼
chainDesc.OutputWindow = window.hwnd;
chainDesc.Windowed = window.windowed;
chainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;		// 전면 후면 버퍼 교체 시 이전 프레임 정보 버림
chainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;

2) IDXGISwapChain

  • 렌더링된 데이터를 출력에 표시하기 전에 저장하기 위해 하나 이상의 Surface를 구현
dxgi->CreateSwapChain(cmdQueue.Get(), &chainDesc, &_swapChain);

// (2) 렌더링 대상이 되는 버퍼(ID3D12Resource) 가져옴
for (int32 i = 0; i < SWAP_CHAIN_BUFFER_COUNT; i++)
	_swapChain->GetBuffer(i, IID_PPV_ARGS(&_renderTargets[i]));

1) 그려질 화면을 제시

_swapChain->Present(0, 0);

2) back buffer를 가리키는 색인 변경 (1->2, 2->1)

_backBufferIndex = (_backBufferIndex + 1) % SWAP_CHAIN_BUFFER_COUNT;


🔹 DescriptorHeap Class

  • 화면에 표시되는 정보(RTV)를 생성하는 클래스
  • 기안서

view

  • GPU자원들이 파이프라인에 직접 묶이는 것이 아닌 해당 자원을 참조하는 서술자 객체
  • 자원을 GPU에게 서술해주는 경량의 자료구조

heap

  • 응용프로그램이 사용하는 서술자들이 저장되는 곳
    • 서술자마다 개별적인 heap이 필요하다.
    • 같은 종류의 서술자들은 같은 서술자 힙에 저장된다.
    • 한 종류의 서술자에 대해 여러개의 heap을 둘 수 있다.

ComPtr<ID3D12DescriptorHeap>	_rtvHeap;

1) ID3D12DescriptorHeap

  • 설명자(descriptor)의 연속 할당 컬렉션으로, 모든 설명자에 대한 하나의 할당
  • SRV, UAV, CBV, Sampler (파이프라인 상태 개체(Pipeline State Object:PSO)에 속하지 않은 유형)
// (1) 렌더 대상 뷰(RenderTargetView)의 크기 가져옴 (offset을 위함)
_rtvHeapSize = device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);

// (2) 렌더 타겟 힙 생성
D3D12_DESCRIPTOR_HEAP_DESC rtvDesc = {};
rtvDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvDesc.NumDescriptors = SWAP_CHAIN_BUFFER_COUNT;
rtvDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
rtvDesc.NodeMask = 0;

device->CreateDescriptorHeap(&rtvDesc, IID_PPV_ARGS(&_rtvHeap));

// (3) RTV 설정
D3D12_CPU_DESCRIPTOR_HANDLE rtvHeapBegin = _rtvHeap->GetCPUDescriptorHandleForHeapStart();

for (int i = 0; i < SWAP_CHAIN_BUFFER_COUNT; i++)
{
	_rtvHandle[i] = CD3DX12_CPU_DESCRIPTOR_HANDLE(rtvHeapBegin, i * _rtvHeapSize);
	device->CreateRenderTargetView(swapChain->GetRenderTarget(i).Get(), nullptr, _rtvHandle[i]);
}

Render Target View(RTV)

  • 교환사슬에서 렌더링의 대상이 되는 버퍼 자원 서술



🔹 참조관계

image

DescriptorHeap 클래스를 SwapChain 클래스에 포함하도록 변경하는게 좋을 것 같음


image


🔹 결과

image


📑. 참고

카테고리:

업데이트:

댓글남기기