DirectX
는 컴퓨터에 GPU를 제어하고 프로그래밍 할 수 있도록 도와주는 MS의 API이다.
c++을 이용하여 프로그래밍하며, 다른 API에 비해 상당히 로우한 부분까지 직접 코딩해야해서 어렵다..
DX
는 크게 DX9, DX11과 DX12로 묶는다. 9,11이 운전 보조 장치가 달린 스포츠카 느낌이라면 DX12는 까다로운 F1머신이다. 9,11에 비해 더 더욱 로우한 프로그래밍을 해야하기에 사용자가 실력이 없다면, 프로젝트를 세팅하는 것 마저 버겁게 느껴질 수 있다.
이토록 어려운 DX12이지만, MS가 DirectX의 최신 기능은 12에만 업데이트해주기도 하고, 하기 싫어도 언젠간 해야하기에 나는 DX12로 DirectX를 시작했다.
DX를 하며 가장 많이 보게 된다고 해도 과언이 아닐 정도로 많이 쓰이는 COM(Computer Object Model)인터페이스이다.
COM 인터페이스는 C++의 클래스로 간주하여도 무방하지만, new delete를 사용하여 생성, 삭제 하는 것이 아닌 ComPtr이라는 친구로 COM 인터페이스를 관리할 수 있다.
COM 인터페이스를 관리하기 위한 객체로 그냥 스마트포인터라고 보면 편하다.
스마트 포인터처럼 참조 카운트가 존재하고 참조 카운트가 0이되면 삭제된다.
여러개의 COM 인터페이스를 구별하기 위해 사용되는 아이디이다. 모든 COM 인터페이스는 고유한 GUID를 가지고 있기에 구별하여 사용할 수 있다.
IID_PPV_ARGS(&comInterface);
DirectX에서는 IID_PPV_ARGS
라는 함수를 이용하여 인수로 들어가는 COM 인터페이스의 GUID를 알 수 있다.
Texture Type
은 각 차원의 원소에 해당 픽셀의 정보를 저장하는 행렬이다.
단순 이미지의 정보만 표시하는 것이 아닌 원소의 색상, 3차원 벡터등 다양한 정보를 담기에 Buffer
라고 생각하고 보는 것이 편하다.
위 사진은 저장할 수 있는 포멧들이다.
win32api에서 사용하였던 더블 버퍼링과 기본적인 개념은 비슷하다.
이미지를 그리는 버퍼(백 버퍼)와 출력하는 버퍼(프론트 버퍼)를 나누어 백 버퍼에 먼저 다 그린 이후 프론트 버퍼로 옮겨서 출력하는 식으로 이 과정을 프레젠팅이라고 부른다.
win32api의 더블 버퍼링과 다른 점은 DirectX에서는 더블 버퍼링을 위해 스왑 체인 기술을 사용한다는 것이다.
간단히 설명하여 프론트 버퍼로 정보를 옮겨올 때, 백 버퍼의 내용을 복사(Bit)하는 것이 아닌 백 버퍼와 프론트 버퍼의 포인터를 교체(Fliping) 해주는 방식이다.
DirectX 에서 3D렌더링을 하기 위해서는 깊이를 이해해야한다. DirectX에서는 깊이를 표현하기 위해 깊이 버퍼를 사용한다.
위에서 말했듯 버퍼는 곧 텍스쳐기에 깊이 버퍼또한 텍스쳐 타입이다.
각 자원의 원소가 픽셀의 깊이 정보를 담고 있으며 퍼센테이지로 표시되기에 가장 가까운 깊이는 0, 가장 먼 깊이는 1로 담기게 된다.
깊이 버퍼의 원소들과 백 버퍼의 원소들을 일대일 대응하기에, 해상도가 900x600이라면 깊이 버퍼또한 900x600이다.
DirectX에서는 깊이 버퍼링 알고리즘을 이용하여 깊이를 계산할 수 있다. 이럴 경우 그리는 순서와 상관없이 깊이에 따라 제대로 렌더링이 가능하다.
DirectX의 렌더링 과정에서 GPU는 자원에 자료를 기록하거나 자원에서 자료를 읽어서 사용한다.
하지만, GPU의 메모리는 작기 때문에 자원을 다 가져갈 경우 문제가 생기게 된다. 이를 해결하기 위해 렌더링 전에 리소스들을 파이프라인에 바인드(bind)한다. 이를 Resource Binding이라고 부른다.
파이프라인의 바인딩되는 리소스는 다음과 같다.
Vertex Buffer
: 렌더링할 물체의 정점 정보
Index Buffer
: 렌더링할 물체의 인덱스 정보
Texture
: 렌더링할 물체의 텍스쳐
Constant Buffer
: 상수를 데이터로 보내주는 버퍼
Unordered Access Buffer
: GPU에서 내용을 바꿀 수 있는 버퍼
Sampler
: 샘플링을 하기 위한 옵션
물론 이러한 버퍼들이 모두 파이프라인에 올라가는 것은 너무 무겁기에 실제로 바인딩되는 것은 자원이 아닌, 자원을 참조하는 Descrioptor(서술사) 객체이다.
서술자는 말 그대로 자원을 서술하는 경량 객체이다. 사실상 포인터라고 생각하면 된다.
자원 자체는 어떤 형식으로 쓰일지를 모르기에 서술자가 DirectX에게 자원의 사용법을 알려준다.
간단히 생각하여 서술자들을 모아두는 힙이다.
초기화 시점에 한번만 만들고 런타임 시점에서는 Readonly로 사용된다.
서술자 힙의 임의의 위치가 맵핑되게 된다.
어떤 리소스가 어떻게 파이프라인에 바인드 될 것인지 정의된 일종의 템플릿이다.
흔히들 계단현상이라고 부르는 현상이다.
픽셀이 무한히 작지 않기때문에 완벽한 선을 못그려 일어나는 현상인다. 이를 해결하기 위한 안티 앨리어싱방법이 DirectX에도 존재한다.
후면 버퍼를 해상도의 4배로 잡고, 정면 버퍼에 렌더링 이후 다시 환원과정을 거쳐서 표현한다. 이를 통해 안티 앨리어싱 효과를 볼 수 있다.
이러한 슈퍼 샘플링은 비용이 크게 들게 되는데, 이런 비용을 절감하기 위해 멀티 샘플링을 사용한다.
멀티 샘플링은 간단하게 픽셀당 한번만 계산한다는 것이다. 4배로 늘린 4개의 픽셀을 하나라고 치고 계산한다.
GPU가 지원하는 기능의 집합을 말한다.
GPU가 기능수준11을 지원한다고 하면, DX11의 기능 모두 지원해야한다.
사용자의 기능 수준이 낮을 경우 실행을 포기하는 대신 품질을 낮추는 방법을 택할 수 있다.
DirectX와 함께 쓰이는 API로, 그래픽카드마다 대응되는 드라이버를 만들어 독립적인 저수준의 작업을 관리한다.
이를 통해 사용자는 GPU의 세부사항을 고려할 필요가 없어진다.
자원들 중 GPU에 항상 필요한 것은 많지않다. 따라서 필요할 때만 쓰고 다 쓰면 내리는 식으로 상주성을 관리 할 수 있다.
DirectX에서 렌더링 커맨드는 CPU가 명령들을 모아 제출하면 GPU가 제출된 명령을 받아 순차적으로 실행하는 식으로 작동한다.
GPU에 제출하기 위한 명렬 리스트이다.
DX12는 다중 스레드를 효율적으로 사용할 수 있게 구축되어 여러 개의 커맨드 리스트를 만들어 GPU에 넘길 수 있다.
GPU의 명령 대기열 명령을 넣는 즉시 실행되지 않고, GPU가 처리할 준비가 되었다면 실행한다.
이 과정에서 CPU와 GPU가 병렬식으로 돌다보니 문제가 발생하는데 이를 울타리 기법을 통해 해결 할 수 있다.
정말 간단하게 CPU가 명령을 넘겼다면 GPU가 특정 지점까지의 명령을 처리할 때까지 CPU를 쉬게 하면 된다.
GPU가 자원을 읽을 때, 아직 자원의 자료를 다 기록하지 못하였거나 자원을 올리지 않았다면 자원 위험 상황 즉 문제상황이 발생하게 된다.
이를 해결하기 위해 DirectX에서는 자원들마다 상태를 지정하여 관리한다.