아니 이게 말이 돼!?!?!?!?!
수업을 들으면서 울뻔했다. DX12의 기본적인 이론들만 가지고 왔다고 말씀하시는 선생님의 고운 목소리가... 밉게만 들렸다... 내 인생도 이렇게 복잡하진 않으렷다... 다 죽었어 DX12... 이번 겨울방학 특강으로 널 꾸깃꾸깃하게 해주지... 선생님의 PPT를 기반으로 간단하게만 정리해보았다.
COM 인터페이스
작년에 ComPtr을 거짓말 안 치고 100번은 친 것 같다는 생각이 들었다. 그만큼 익숙한 이름인데, 정작 정확한 개념은 모르는 친구였다.
COM(Component Object Model) 인터페이스는 C++ 클래스로 보아도 무방하다. IUnknown 인터페이스를 상속받고 있다. new와 delete를 사용하여 생성, 삭제할 수 없고 별도의 API 함수를 이용해야 한다. MS에서는 COM 객체들을 관리하기 위해 ComPtr이라는 스마트 포인터를 제공한다.
이 친구들은 모두 인터페이스이기 때문에 I로 시작된다고 한다. ID3D...로 시작된다면 얘는 COM 객체구나~라고 생각하면 ㅚ겠다.
GUID
IID_PPV_ARGS... 작년에 ComPtr 다음으로 많이 쳐봤던 아이이다. COM 인터페이스를 구별하기 위해 사용되는 아이디라고 한다. 같은 형식의 COM 인터페이스라도 고유한 GUID 덕분에 구별이 가능하다.
IID_PPV_ARGS 매크로를 활용하여 GUID를 얻을 수 있다고 한다.
Texture Type
Texture는 2차원의 이미지를 저장할뿐만 아니라, 훨씬 범용적으로 사용된다. 원소의 색상, 3차원 벡터 등 다양한 정보를 담을 수 있다.
형식이... 대가족이다... 개발할 때는 저장하고 싶은 정보에 해당하는 포맷을 넣어주면 되겠다. TypeLess도 있는데, 일단 메모리만 확보해두고 구체적인 해석은 나중에 지정한다고 한다.
이러면 편하게 다 TypeLess 쓰면 되는 거 아닌가? 생각해봤는데, C#에서도 object를 남발하면 복잡해지듯, 여기서도 엄청 복잡해질 것 같다.
Double Buffering
Win32API에서도 다섯 번은 해본 더블 버퍼링... 하지만 DX12에서도 스왑 체인으로 머리 빙빙 싸매며 겨우 겨우 따라쳤던 더블 버퍼링! 더블 버퍼링은 이미지에 그림을 그리는 백 버퍼, 출력하는 프론트 버퍼로 두 개의 버퍼가 서로의 역할을 하며 화면을 끊기지 않게 보여주는 아이들이다.
이 모든 현상은 컴퓨터가 그리는 모습을 보기 때문이다! 그래서 더블 버퍼링은 그리는 과정을 백 버퍼에 꽁꽁 숨겨 완성되면 보여주는 기술이다.
SwapChain
더블 버퍼링을 하기 위해 DX12에서는 Swap Chain을 사용한다. 더블 버퍼링과 비슷하지만, 출력할 때 백 버퍼와 프론트 버퍼가 플리핑을 하며 서로의 역할을 바꾸는 기술이다. 프론트 버퍼가 백 버퍼를 복사하는 과정을 생략한 것이다. 말로 설명하려니 설명이 명확하지 않네...
IDXGISwapChain 인터페이스가 제공되어있다.
Depth
Depth Buffer
깊이 버퍼는 텍스쳐!
각 픽셀의 깊이 정보를 담고 있으며 절두체 안에서 0은 가장 가까운, 1은 가장 먼 물체의 깊이를 나타낸다. 화면의 픽셀과 1:1로 대응되어서 화면의 크기 == 깊이 버퍼의 크기이다. 예를 들어, 1920x1080 화면의 깊이 버퍼는 1920x1080인 것이다.
Depth Buffering
특정 물체의 픽셀이 다른 물체보다 앞에있는지 판정하기 위해 DX는 깊이 버퍼링을 사용한다.
첫번 째 방법은 단순히 물체를 먼 것에서 가까운 순서로 그리는 것이다. 직관적이고 쉬운 이 방법은 물체를 정렬해야 한다는 것, 맞물린 물린 물체는 제대로 처리하지 못한다는 단점이 있다.
두 번째 방법은 도장깨기(내가 붙인 것...) 알고리즘이다. 렌더링을 할 때 깊이 버퍼에 있는 깊이 값과 비교해서 더 가까운, 즉 더 작은 깊이를 가진 픽셀들이 백 버퍼에 기록되는 알고리즘이다.
깊이 버퍼를 다시 한 번 상기시키니 Transparent 머티리얼을 만드는 게 새삼 어려워보였다... 상용 엔진의 힘이자 폐해이다.
Resources
렌더링 과정에서 GPU는 자원들에 자료를 기록하거나 자원들에서 자료를 읽어 들인다. 리소스를 크게 구분하자면 텍스쳐, 그리고 버퍼로 나누어진다고 한다.
DX12는 리소스를 바인딩해야하는 어마어마한 귀찮고 복잡한 특성을 가지고 있다. 렌더링 호출마다 값이 달라지는 자원도 있으며 그렇기 때문에 렌더링 때마다 자원들의 바인딩을 갱신해주어야 한다.
Descriptor
파이프라인에 실제로 바인딩되는 것은 해당 자원이 아닌, 자원을 참조하는 Descriptor 객체이다. 자원의 사이즈가 큰 경우도 당연히 있는데, 이것을 렌더링을 할 때마다 복사하는 것보다, Descriptor 객체가 참조하는 게 나은 구조인 것 같다.
이렇게 Descriptor 객체를 거치게 되면, 데이터 쪼가리일뿐인 GPU 자원의 용도를 부여할 수 있다. (Render Target View, Depth Stencil Buffer 등등의 용도...)
Descriptor Heap
그래 여기까지는 어떻게 어떻게 이해할 수 있었는데, 강의를 듣다가 이 부분에서 막혀버린 것이다 ㅠ_ㅠ!
Descriptor Heap = 서술자들의 배열. 서술자의 종류마다 개별적인 서술자 힙이 필요하며, 같은 종류의 서술자는 같은 서술자 힙에 저장된다. 한 종류에 대해 여러 개의 힙을 둘 수도 있다.
응용 프로그램의 초기화 시점에 생성해야 하는데, 형식 점검이나 유효성 검증 때문이라고 한다.
근데 얘가 어떤 용도인지는 잘 모르겠다... 내일 선생님께 여쭤봐야지
Descriptor Table
Descriptor의 논리적 배열. Desciptor Heap의 임의의 위치가 Descriptor Table에 맵핑된다.
Root Signature
어떤 자원이 어떻게 Pipeline에 바인딩될지 정의하는 것. 일종의 템플릿.
Aliasing
계단현상! 안티 에이징처럼 앨리어싱을 막는 기술도 안티 앨리어싱이다.
슈퍼샘플링
원래 화면 해상도의 N배(보통 4)로 잡고 장면을 백 버퍼에 렌더링한 후, 환원 공정(Down Sampling)을 거쳐 원래 크기로 되돌린 후에 화면에 출력하는 것. 이때 환원 공정은 4픽셀의 색상의 평균을 해당 블록의 최종 색상으로 정한다.
그런데, 슈퍼 샘플링은 모든 서브 픽셀들을 계산하기 때문에 픽셀 처리량, 메모리 소비량이 4배이다.
멀티샘플링
슈퍼샘플링의 성능 부분을 보완한 멀티샘플링. 화면 해상도를 N배로 늘리는 것은 똑같지만, 각 픽셀마다가 아닌, 4개의 픽셀을 하나의 픽셀로 쳐 평균 색상을 구하는 바업이다. 그 색상과 서브 픽셀들의 가시성, 포괄도를 이용해 최종 색상을 결정하는 방법.
슈퍼샘플링이 멀티샘플링보다는 조금 더 좋은 결과를 낼 것 같지만, 성능 면에서는 멀티 샘플링이 더 우세한 것 같다.
Feature Level
GPU가 지원하는 기능들의 집합을 정의한다. 기능 수준 11을 지원하는 GPU는 반드시 Direct3D 11의 기능 집합 전체를 지원해야 한다.
사용자의 하드웨어가 특정 Feature Level을 지원하지 않는 경우, 실행을 포기하기보다는 더 낮은 기능 수준으로 후퇴하는 전략을 사용할 수도 있다.
DXGI
Direct3D와 함께 쓰이는 API. DX 그래픽 런타임에 독립적인 저수준의 작업을 관리하는 친구이다. DX 그래픽을 위한 기본적, 공통적인 프레임워크를 제공한다.
인터페이스 | 설명 |
IDXGIFactory | 주로 Swap Chain, Display Adapter 열거에 쓰인다. |
IDXGIAdapter | Display Adapter는 그래픽 기능성을 구현한다. |
IDXGIOutput | 한 시스템에 연결된 모니터 디스플레이 인터페이스이다. |
따라 적었긴 하지만, 모르는 말 투성이라... 후에 더 알게 되겠지만, 아무튼 DXGI는 저수준의 작업을 관리하는 API이다. 특히 Swap Chain을 구현할 때 IDXGIFactory를 쓰니 기억해두어야겠다.
상주성
자원이 GPU 메모리에 들어있는지의 여부. 자원들이 항상 GPU에 필요하지 않으므로, DX12에서 응용 프로그램은 자원을 GPU 메모리에서 내리는 퇴거, 다시 올리는 입주로 자원의 상주성을 관리할 수 있다.
게임에 필요한 자원들을 모두 GPU에 담지 못할 수도 있고, 다른 프로그램이 GPU 메모리를 사용해야할 수도 있으니 상주성 관리가 필수이다.
Command
Command Queue
GPU의 명령 대기열. 명령을 넣는 즉시 실행되지 않고 GPU가 처리할 준비가 되면 실행된다. 이전에 제출된 명령들을 바쁘게 처리하고 있다면, 명령들이 큐에 남아있다.
Command Queue가 비게 되면, GPU는 그냥 놀게 되고, 꽉 차면 CPU가 놀게 되는데, 두 상황 모두 바람직하지 않다. 최적의 성능을 얻으려면 최대한 둘 다 바쁘게 돌아가게 만들어야 한다.
(이건 왜지...? GPU가 논다 해서 CPU가 더 바쁘게 돌아가는 것도 아니고...)
Command List
ID3D12CommandList, ID3D12GraphicsCommandList. Command Queue에 제출하는 명령 리스트. 여러 메서드를 사용해서 명령을 저장하고 커맨드 큐에 올린다. 모두 큐에 추가했다면, 항상 Close 메서드를 호출해 명령 기록이 끝났음을 알려야한다.
DX12에서는 다중 스레드 렌더링으로 여러 개의 Command List를 Command Queue로 실행시킬 수 있다.
Command Allocator
Command List에 추가된 명령들을 저장하는 할당자. Command List가 Command Queue에게 명령 목록을 제출하면, Command queue는 이 할당자에 담긴 명령을 참조한다.
Fence
GPU와 CPU의 동기화 수단으로 쓰인다. 펜스 객체는 UINT64값 하나로 관리할 수 있다. CPU가 GPU의 리소스 사용을 완료할 때까지 기다리게 해준다.
펜스 N일 때의 명령을 GPU가 모두 처리할 때까지 기다린 후, 펜스 N+1의 명령을 CPU가 올릴 수 있게 해주는 울타리.
자원상태정의
GPU가 자원에 자료를 다 기록하기 전, 자원의 자료를 읽으려고 하는 상태를 자원 위험 상황이라고 부른다.
이를 해결하기 위해 DX에서는 자원들에게 상태를 부여한다. 새로 생성된 자원은 Default 값.
(Swap Chain 구현은 ResourceBarrier를 이용하여 백 버퍼를 PRESENT와 RENDER_TARGET State를 매번 바꿔준다.)
물론... 이론을 이해하는 것과 코드를 이해하는 것은 천지 천지 천지 천지차이라는 것을 알지만... 이 이론도 완벽히 이해하지는 못했지만... 그래도 DX12를 배웠다고 하면 이 정도는 기본으로 알아야하는 거니까! 기록해놓고 두고 두고 봐야겠다.
정말 많은 도움이 되었다.
'Direct X' 카테고리의 다른 글
[DX12] 빈 화면을 렌더링해보자! (1) | 2023.02.26 |
---|---|
[DX12] 장치를 초기화 해보자! (3) (RTV, DSV, Viewport) (0) | 2023.02.26 |
[DX12] 장치를 초기화 해보자! (2) (Command Object, Swap Chain) (0) | 2023.02.13 |
[DX12] 장치를 초기화 해보자! (1) (Device, Fence, Desc Size, MSAA) (0) | 2023.02.13 |
[DX12] DX12를 들어가며 (0) | 2023.02.09 |