프레임 자원
기존 방식에서는 cpu가 gpu에 명령 제출 후 flush를 통해 gpu가 명령들을 다 처리할때 까지 대기했다. 이러한 방식은 flush대기중에는 cpu가, flush후 다음 명령 제출까지는 gpu가 쉬게 된다. 이러한 성능 저하를 막기위해 프레임 자원이라는 기술을 사용한다.
기존에 flush를 통해 cpu가 gpu를 기다려줘야했던 이유는 commandAllocator 초기화 시점에 대한 이유와 매 프레임 변할 수 있는 자원들(오브젝트 위치 정보 등)을 gpu가 그리기를 처리하기도 전에 다음 프레임 값으로 바꿔버릴 위험이 있기 때문이었다.
따라서 commandAllocator 및 매 프레임 변하는 자원들을 묶어 프레임 자원이라는 것으로 만들고 이러한 프레임 자원 n개를 사용하면 기존의 방식보다 효울 적으로 루프를 돌릴 수 있다. 매 프레임 1,2,3,..n번째 프레임 자원을 돌아가며 사용하게 되는 것이다.
3개의 프레임자원을 사용한다고 가정하자.
전역 현재 프레임 자원 인덱스가 0으로 설정되있다. 이번 프레임은 0번째 프레임자원을 사용한다는 의미이다. 프레임이 시작되면 업데이트부터 시작한다. 이때 여러개의 프레임 자원을 사용해도 cpu가 gpu에 비해 빠르다면 gpu가 아직 사용중인 프레임 자원에 cpu가 한바퀴 돌아 도달할 수 도 있다. 이를 위해 업데이트 시작 시 플래그를 확인하여 현재 프레임 자원을 gpu가 다 사용했는지 확인한다.
업데이트가 시작되면 현재 프레임 자원에 현재 프레임 값들을 갱신한다. 이후 드로우에서 현재 프레임 자원을 사용해서 gpu명령들을 제출하면 된다. 이때 드로우명령이 호출될 때 그 시점에 매핑된 루트 매개변수들이나 정점버퍼등의 매개변수들의 포인팅은 스냅샷으로 저장되어 해당 드로우 명령에 사용되므로 이 드로우 명령이 처리되기전에 다음 프레임에서 매개변수들의 포인팅을 수정한다해도 문제가 없다. 물론 그 자원들의 값자체가 바뀌는 것은 안된다. gpu 명령들을 모두 제출하고나면 현재 전역 플래그 인덱스를 현재 프레임 자원에 기록하고 명령큐에도 플래그를 세운다. 이후 해당 프레임 자원에 cpu가 한바퀴돌아 다시 도달 했을때 지금 세운 플래그(프레임 자원에 기록되있음)에 gpu가 도달하지 못해다면 대기하는데 사용된다.
이렇게 프레임자원을 사용하면 cpu가 gpu보다 2프레임 앞서 나갈 수 있다. cpu가 gpu보다 빨라 대기가 발생하는 경우에도 cpu는 다른 처리에 활용하도록 프로그래밍 할 수 있으므로 큰 문제가 되지않는다. gpu가 쉬지않는다는 것만 보장해도 현대의 하드웨어 환경에서는 적합한 프로그램이라고 할 수 있다.
렌더 항목
렌더항목은 화면에 실제로 나타나게될 오브젝트들을 나타내는 구조체이다.
자신의 위치,회전,크기 정보를 담는 행렬과 갱신효율을 위한 더티플래그(n개의 프레임 자원을 사용할때는 한번 수정되면 그 시점부터 n프레임 갱신되어야 하므로 n으로 더티플래그가 갱신된다.), 이 오브젝트에 대한 상수버퍼 인덱스, 기하구조 정보등을 담는다.
패스별 상수 버퍼
카메라 관련 행렬, 조명 정보 등 한 프레임당 고정된 상수 버퍼 정보들은 패스별 상수 버퍼에 따로 담는다. 이는 렌더 항목 상수버퍼의 경우 매 오브젝트마다 매개변수를 수정해야하지만 패스별 상수 버퍼는 프레임마다 한번만 달아주면 되기때문에 이에 따라 분리하는 것이 효율적이기 때문이다.
기존 예제에서는 오브젝트 상수버퍼에 월드, 시야, 투영 행렬을 모두 곱해 쉐이더에 넘겨주었지만 지금 부터는 패스별 상수버퍼에 시야투영 행렬이 담겨서 넘어가고 렌더항목엥 월드행렬이 담겨서 넘어가므로 정점쉐이더에서 이 둘을 곱해 사용한다.
도형 기하구조
3d 도형의 기본적인 기하구조를 이용해 육면체, 원기둥, 구등의 기본 기하구조들을 코드내에서 생성할 수 있다.
도형 예제
위의 내용들을 토대로 여러 도형 기하구조를 인스터스하여 여러개 씩 출력하는 도형 예제를 구현할 수 있다.
해당 예제의 특징은 여러 프레임 자원의 오브젝트 및 패스병 상수버퍼를 하나의 배열상수버퍼에 모두 넣는다. 제일 앞에 첫 프레임 자원의 오브젝트 상수버퍼 배열을, 그 다음에 두번째 프레임 자원의 오브젝트 배열, 세번째 프레임 자원의 오브젝트 배열을 넣고 맨 뒤 세 부분에 각 프레임 자원의 패스별 상수버퍼를 넣는다. 이후 상수버퍼 매개변수 등록시 이러한 인덱싱에 기초하여 현재 프레임 자원 인덱스와 렌더항목 상수버퍼 인덱스를 이용해 특저 인덱스의 상수버퍼를 갱신 및 렌더링 한다.
또한 사용되는 4가지 기하구조를 한 geo 클래스에서 관리한다. subMesh로 정점버퍼 색인버퍼 부분 인덱스 및 베이스를 설정하여 구분사용한다.
루트 서명 추가 설명
루트 매개변수는 총 3가지이다.
서술자 테이블: 한 힙안의 특정 레인지의 서술자들을 묶는다.
루트 서술자: 한 자원을 다이렉트로 묶는다. 서술자로 표현하지만 자원 주소를 바로 넘겨주어 등록하는 식으로 사용된다. 이때 자원은 힙이 없어도 된다.
루트 상수: 바이트 배열을 다이렉트로 넘겨서 묶는다.
한 루트서명에는 최대 64개의 DWORD만 넣을 수 잇으며 각 매개변수의 비용은 다음과 같다.
서술자 테이블: DWORD 1개
루트 서술자: DWORD 2개
루트 상수: 32비트당 DWORD 1개
루트 상수는 편리하지만 DWORD를 많이 사용하기에 적당히 사용해야한다.
애니메이션
특정 기하구조에서 정점들의 위치를 매 프레임 수정하면 기하구조가 움직이는 애니메이션을 만들 수 있다. 애니메이션의 방법은 여러가지가 있다. 그 중 하나는 시간등의 매개변수를 쉐이더에 넘기고 쉐이더에서 해당 값을 토대로 기존 정점 위치를 애니메이션 정점 위치로 수정 후 렌더링하는 방법이있다. 또 다른 방법으로는 정점 버퍼를 업로드 버퍼로 설정해 매 프레임 애니메이션 위치값으로 수정 후 렌더링 하는 방법이다. 후자의 경우 정점버퍼를 기존 업로드 버퍼로 등록해 그대로 사용하면 된다.
지형과 파도 예제
위의 내용들을 토대로 고정 산악 지형 격자와 파도 애니메이션 격자를 렌더링하는 예제를 만들 수 있다.
해당 예제의 특징은 파도 애니메이션을 동적 정점 버퍼를 이용해 구현한다. 해당 파도 오브젝트를 위한 업로드 정점 버퍼를 사용하여(프레임 자원을 위해 총 3개) 해당 프레임의 업데이트에서 그 정점 버퍼의 각 정점 위치값을 수정하고 드로우에서 렌더링하여 애니메이션 한다.
다른 특징으로는 패스별 상수 버퍼 및 렌더항목 상수 버퍼를 루트 서술자로 사용한다. 따라서 상수버퍼 힙은 사용하지 않는다. 대신 프레임 자원에 패스별 및 렌더항목에 대한 상수버퍼 자원을 저장해둔다. 패스별 상수버퍼는 그 주소를 바로 가져와 등록하고 렌더항목 상수버퍼는 렌더항목의 상수버퍼 인덱스를 인덱싱하여 그 주소를 사용한다.
참고서적
DirectX 12를 이용한 3D 게임 프로그래밍 입문
'그래픽스 > DirectX12' 카테고리의 다른 글
[Directx12][8장][2]HLSL 구조체 채우기 (0) | 2023.03.21 |
---|---|
[Directx12][8장][1]조명 (1) | 2023.03.21 |
[Directx12][6장][2]파이프라이닝 구현 (0) | 2023.03.21 |
[Directx12][6장][1]파이프라이닝 구현 - 기본 지식 (0) | 2023.03.21 |
[Directx12][5장]렌더링 파이프라인 (0) | 2023.03.21 |