언리얼 렌더링 시스템 분석(13) - RHI 보충 자료: 최신 그래픽 API의 미스터리와 지침
🌐 원문 링크: 剖析虚幻渲染体系(13)- RHI补充篇:现代图形API之奥义与指南 (cnblogs.com/timlly)
📅 원문 발행일: 2021-12-12 | ✍️ 저자: Timlly (00 / )💡 시리즈 분류:
Unreal Engine Rendering| 국내 업계 표준 용어 감수 및 수식/도해 복원 적용 완료
이 기사는 RHI 장의 보충 자료입니다. 최신 그래픽 API의 특성, 원리, 메커니즘 및 최적화 기술을 자세하고 깊이 있게 설명합니다. 보다 구체적으로, 이 글에서는 주로 다음과 같은 내용을 다루고 있습니다.
최신 그래픽 API의 기본 개념.
최신 그래픽 API의 기능.
최신 그래픽 API 사용.
최신 그래픽 API의 원리와 메커니즘.
최신 그래픽 API에 대한 최적화 제안.
본 글에서 설명하는 모던 그래픽 API는 DirectX12, Vulkan, Metal 등을 지칭하며, DirectX11 및 Open GL(ES)은 포함하지 않으나 후자가 완전히 배제되는 것은 아닙니다.
UE의 RHI 패키지는 주로 DirectX이므로 이 글에서도 DirectX를 메인 퍼스펙티브로 사용하고, Vulkan, Metal 등을 보조 퍼스펙티브로 사용합니다.
13.1.2 개념 개요
우리 모두는 기존 API(아래 표)가 많이 있다는 것을 알고 있습니다. 각 API는 서로 다르지만 유사한 개념을 포함하는 고유한 특성과 자체 포함 시스템을 가지고 있습니다.
그래픽 API 적용 가능한 시스템 음영 언어
다이렉트X 윈도우, 엑스박스 HLSL(고수준 셰이딩 언어)
불칸 크로스 플랫폼 SPIR-V
금속 iOS, 맥OS MSL(메탈 셰이딩 언어)
오픈GL 크로스 플랫폼 GLSL(OpenGL 셰이딩 언어)
오픈GLES 모바일 단말기 ESGLSL
다음은 관련된 개념과 명사의 비교표입니다.
다이렉트X 불칸 오픈GL(ES) 금속
질감 이미지 텍스처 및 렌더 버퍼 질감
렌더 타겟 컬러 첨부 파일 컬러 첨부 파일 색상 첨부 파일 또는 렌더링 대상
명령 목록 명령 버퍼 컨텍스트, 표시 목록, NV_command_list의 일부 명령 버퍼
명령 목록 보조 명령 버퍼
- 병렬 명령 인코더
명령 목록 번들
- 경량 표시 목록 간접 명령 버퍼
명령 할당자 명령 풀 맥락의 일부 명령 대기열
명령 대기열 대기열 맥락의 일부 명령 대기열
복사 대기열 전송 대기열 glBlitFramebuffer() blit 명령 인코더
복사 엔진 이적 엔진
- 블릿 엔진
예측 조건부 렌더링 조건부 렌더링
- 깊이/스텐실 보기 깊이/스텐실 부착 깊이 부착 및 스텐실 부착 깊이 부착 및 스텐실 부착, 깊이 렌더 대상 및 스텐실 렌더 대상
렌더 타겟 뷰, 깊이/스텐실 뷰, 셰이더 리소스 뷰, 정렬되지 않은 액세스 뷰 이미지 보기 텍스처 뷰 텍스처 뷰
형식화된 버퍼 SRV, 형식화된 버퍼 UAV 버퍼 뷰, 텍셀 버퍼 텍스처 버퍼 텍스처 버퍼
상수 버퍼 보기(CBV) 균일한 버퍼 균일한 버퍼 일정한 주소 공간의 버퍼
래스터라이저 순서 보기(ROV) 프래그먼트 셰이더 인터록 GL_ARB_fragment_shader_interlock 래스터 순서 그룹
원시 또는 구조화된 버퍼 UAV 저장 버퍼 셰이더 저장 버퍼 장치 주소 공간의 버퍼
설명자 설명자
- 논쟁
설명자 힙 설명자 풀
- 더미
설명자 테이블 설명자 세트
- 인수 버퍼
더미 장치 메모리
배치 힙
서브패스 픽셀 로컬 저장소 프로그래밍 가능한 블렌딩
분할 장벽 이벤트
- ID3D12Fence::SetEventOnCompletion 울타리 울타리, 동기화 완료된 처리기, -[MTLComandBuffer waitUntilComplete]
자원 장벽 파이프라인 장벽, 메모리 장벽 텍스처 장벽, 메모리 장벽 텍스처 장벽, 메모리 장벽
울타리 신호기 울타리, 동기화 울타리, 이벤트
D3D12 울타리 타임라인 세마포어
- 이벤트
픽셀 셰이더 프래그먼트 셰이더 프래그먼트 셰이더 조각 셰이더 또는 조각 함수
헐 셰이더 테셀레이션 제어 셰이더 테셀레이션 제어 셰이더 테셀레이션 컴퓨팅 커널
도메인 셰이더 테셀레이션 평가 셰이더 테셀레이션 평가 셰이더 테셀레이션 후 버텍스 셰이더
자원 수집 조각 버퍼 조각 개체
- 수영장 더미
- 힙 유형, CPU 페이지 속성 메모리 유형 자동 관리, 텍스처 저장 힌트, 버퍼 저장 스토리지 모드, CPU 캐시 모드
GPU 가상 주소 버퍼 장치 주소
- 이미지 레이아웃, 스위즐 이미지 타일링
- 일치하는 의미론 인터페이스 매칭(in/out) 다양함(GLSL 4.20에서 제거됨)
- 실, 레인 호출 호출 실, 레인
스레드 그룹 작업 그룹 작업 그룹 스레드 그룹
파도, 파면 하위 그룹 하위 그룹 SIMD 그룹, 쿼드그룹
슬라이스 레이어
- 슬라이스
장치 논리적 장치 맥락 장치
멀티 어댑터 장치 장치 그룹 암시적(예: SLICrossFire) 동료 그룹
어댑터, 노드 물리적 장치
- 장치
뷰 인스턴싱 멀티뷰 렌더링 멀티뷰 렌더링 버텍스 증폭
리소스 상태 이미지 레이아웃
- 파이프라인 상태 파이프라인 무대와 프로그램 또는 프로그램 파이프라인 파이프라인 상태
루트 서명 파이프라인 레이아웃
- 루트 매개변수 설명자 세트 레이아웃 바인딩, 푸시 설명자
- 셰이더 매개변수 목록의 인수
D3DCompileFromFile의 결과 ID3DBlob 셰이더 모듈 셰이더 객체 셰이더 라이브러리
음영율 이미지 차광율 부속
- 래스터라이제이션 속도 맵
타일 희소 블록 희소 블록 희박한 타일
예약된 리소스(D12), 타일형 리소스(D11) 희소 이미지 희박한 질감 희박한 질감
창문 표면 HDC, GLXDrawable, EGLSurface 레이어
스왑체인 스왑체인 HDC, GLXDrawable, EGLSurface 쌍 레이어
- 스왑체인 이미지 기본 프레임 버퍼 드로어블 텍스처
스트림 아웃 피드백 변환 피드백 변환
- 위 표에서 볼 수 있듯이 Vulkan과 OpenGL(ES)은 비교적 유사하지만 더 많은 개념을 가지고 있습니다. 떠오르는 스타인 Metal은 DirectX와 동일한 개념을 많이 가지고 있지만 일부는 이전 제품을 혼합한 것과 동일한 Vulkan과도 동일합니다.
Vulkan의 경우 관련된 개념, 계층 및 데이터 상호 작용 관계가 아래 그림에 표시되어 있습니다.

Vulkan 개념 및 계층적 아키텍처 다이어그램. 여기에는 인스턴스, PhysicalDevice, 장치 및 기타 수준이 포함됩니다. 각 수준의 다양한 개념이나 리소스 간에는 복잡한 참조, 조합, 변환, 상호 작용 및 기타 관계가 있습니다.

금속 자원 및 개념적 프레임워크 다이어그램.
13.1.3 최신 그래픽 API 기능
기존 그래픽 API(DirectX11 이하, OpenGL, OpenGL ES)의 경우 GPU 프로그래밍은 주로 다음과 같이 매우 비쌉니다.
- 상태 검증:
API 태그와 데이터가 합법적인지 확인하세요.
API 상태를 하드웨어 상태로 인코딩합니다.
셰이더 컴파일:
셰이더 기계어 코드는 런타임에 생성됩니다.
상태와 셰이더 간의 상호 작용.
작업을 GPU로 보내기:
리소스 수명주기를 관리합니다.
- 배치 렌더링 명령.
위의 비용이 많이 드는 작업의 경우 기존 그래픽 API와 최신 그래픽 API는 다음과 같이 설명됩니다.
무대 빈도 기존 그래픽 API 최신 그래픽 API
애플리케이션 구축 한 번
- 셰이더 컴파일
콘텐츠 로딩 몇 번
- 상태 확인
무승부 통화 프레임당 1000회 상태 검증, 셰이더 컴파일, GPU로 작업 전송 작업을 GPU로 보내기
위에서 볼 수 있듯이 기존 API는 비용이 많이 드는 상태 확인, 셰이더 컴파일 및 GPU로 전송하는 작업을 모두 런타임에 넣는 반면, 최신 그래픽 API는 셰이더 컴파일을 애플리케이션 구축 기간에 넣고 상태 확인을 콘텐츠 로딩 시간으로 이동하여 드로우 콜 중에 GPU로 작업만 전송하므로 런타임의 작업 부하가 크게 줄어듭니다.
최신 그래픽 API(DirectX12, Vulkan, Metal)와 기존 그래픽 API의 설명 비교표는 다음과 같습니다.
최신 그래픽 API 기존 그래픽 API
객체 기반 상태, 전역 상태 없음. 단일 전역 상태 머신.
모든 상태 개념은 명령 버퍼에 배치됩니다. 상태는 단일 컨텍스트에 바인딩됩니다.
멀티스레드 인코딩이 가능하며 드라이버와 하드웨어에서 지원됩니다. 렌더링 작업은 순차적으로만 수행할 수 있습니다.
GPU 메모리 및 동기화를 정확하고 명시적으로 조작할 수 있습니다. GPU 메모리 및 동기화 세부 정보는 드라이버에 의해 숨겨지는 경우가 많습니다.
드라이버에는 런타임 오류 감지 기능이 없지만 개발자를 위한 유효성 검사 계층이 있습니다. 광범위한 런타임 오류 감지.
OpenGL(ES)과 같은 기존 API와 비교하여 Vulkan은 GPU 메모리 및 동기화와 같은 리소스를 정확하게 제어할 수 있는 멀티스레딩 및 경량 드라이버 레이어를 지원합니다. 런타임 시 리소스 힙 생성 및 소비를 방지하고, 런타임 확인을 방지하고, CPU와 GPU 간의 동기화 지점을 방지하고, 명령 대기열 메커니즘을 기반으로 하고, 전역 상태가 없습니다(아래 그림).

Vulkan은 더 가벼운 드라이버 레이어를 갖추고 있어 애플리케이션이 GPU를 더 자유롭게 제어하고 더 많은 하드웨어 성능을 발휘할 수 있습니다.

그래픽 API, 드라이버 계층, 운영 체제 및 커널 계층 아키텍처 다이어그램.

Metal(오른쪽)은 OpenGL(왼쪽)보다 드라이버 레이어가 더 가볍습니다.


DirectX11 드라이버(상단)와 DirectX12 애플리케이션(하단)으로 수행되는 작업 비교.
Vulkan의 고급 설계 개념 덕분에 렌더링 성능이 더 높으며 일반적으로 CPU, GPU, 대역폭, 에너지 소비 및 기타 지표 측면에서 OpenGL보다 우수합니다. 그러나 애플리케이션 자체의 CPU 또는 GPU 로드가 높으면 Vulkan 사용의 이점이 그다지 명확하지 않을 수 있습니다.

기존 API를 사용하는 렌더링 엔진의 경우 최신 그래픽 API로 마이그레이션하려는 경우 잠재적인 이점과 워크로드는 다음과 같습니다.

OpenGL(ES)에서 최신 그래픽 API로 마이그레이션하는 데 따른 비용 및 이점 비교. 가로축은 OpenGL(ES)에서 다른 그래픽 API를 마이그레이션하는 작업량이고 세로축은 잠재적인 성능 향상입니다. Vulkan과 DirectX12의 잠재 수익 비율과 작업 부하가 더 높고 Metal이 2위임을 알 수 있습니다.
일부 GPU 제조업체(예: NVidia)는 OpenGL 및 Vulkan 드라이버를 공유하며 애플리케이션 계층에서도 혼합할 수 있습니다.

NV의 OpenGL 및 Vulkan 공유 아키텍처 다이어그램. 성능과 이식성을 향상시키기 위해 리소스와 도구 상자를 공유할 수 있으므로 애플리케이션이 가장 중요한 곳에 Vulkan을 추가할 수 있습니다. OpenGL을 구입하면 Vulkan을 사용하게 되어 드라이버 개발 작업량을 줄일 수 있습니다.
최신 그래픽 API를 활용하면 다음과 같은 잠재적 이점을 얻을 수 있습니다.
멀티 코어 CPU의 활용도가 향상되었습니다. 멀티스레드 기록, 멀티스레드 렌더링, 멀티큐, 비동기 기술 등
더 작은 드라이버 레이어 오버헤드.
정확한 메모리 및 리소스 관리.
정확한 다중 장치 액세스를 제공합니다.
더 많은 그리기 호출, 더 많은 렌더링 세부정보.
최소, 최대, 평균 프레임 속도가 높아졌습니다.
보다 효율적인 GPU 하드웨어 사용.
통합 GPU 하드웨어를 더욱 효율적으로 사용할 수 있습니다.
시스템 전원을 줄입니다.
TBR 등 기존 API의 기술적 한계로 인해 불가능하다고 여겨졌던 새로운 아키텍처 설계가 가능합니다.
13.2 장치 컨텍스트
13.2.1 시작 프로세스
대부분의 그래픽 API에는 애플리케이션이 이를 사용할 때 여러 단계가 있습니다.
상태 다이어그램-v2 [] --> InitAPI InitAPI --> 자산 로드 자산 로드 --> 자산 업데이트 중 자산 업데이트 -> 프레젠테이션 프레젠테이션 --> 앱이 닫혔습니다. AppClosed-->자산 로드 중:아니요 AppClosed-->파기:예 파괴 --> []
InitAPI: API의 내부 작업에 액세스하는 데 필요한 핵심 데이터 구조를 생성합니다.
LoadingAssets: 그래픽 파이프라인을 설명하기 위해 사물(예: 셰이더)을 로드하는 데 필요한 데이터 구조를 생성하고, GPU가 실행할 명령 버퍼를 생성 및 채우고, 리소스를 GPU의 전용 메모리로 보냅니다.
UpdatingAssets: 애플리케이션 수준 로직을 수행하여 모든 균일 데이터를 셰이더로 업데이트합니다.
프레젠테이션: 명령 버퍼 목록을 명령 대기열로 보내고 스왑 체인을 제시합니다.
AppClosed: 애플리케이션이 종료 명령을 보내지 않으면 LoadingAssets, UpdatingAssets 및 Presentation 단계를 반복하고, 그렇지 않으면 Destroy 단계를 실행합니다.
파괴: GPU가 남은 모든 작업을 완료하고 모든 데이터 구조와 핸들을 파괴할 때까지 기다립니다.

최신 그래픽 API 시작 프로세스.
다음 장에서는 위의 단계 및 단계와 관련된 개념과 메커니즘을 자세히 설명합니다.
13.2.2 장치
그래픽 API의 초기화 단계에는 Factory, Instance, Device 등의 개념이 포함됩니다. 각 그래픽 API의 개념 비교표는 다음과 같습니다.
개념 UE 다이렉트X 12 다이렉트X 11 불칸 금속 OpenGL
진입 지점 FD동적 RHI IDXGI공장4 IDXGI공장 vk::인스턴스 CAMetalLayer OS에 따라 다름
물리적 장치
- IDXGIAdapter1 IDXGI어댑터 vk::물리적 장치 MTL장치 glGetString(GL_VENDOR)
논리적 장치
- ID3D12장치 ID3D11장치 vk::장치 MTL장치
- **진입점(entry point)**은 애플리케이션의 전역 인스턴스입니다. 일반적으로 애플리케이션에는 진입점 인스턴스가 하나만 있습니다. 글로벌 데이터, 구성 및 상태를 저장하는 데 사용됩니다.
물리적 장치는 하드웨어 장치(그래픽 카드 1, 그래픽 카드 2, 통합 그래픽 카드)에 해당하며 메모리 크기, 기능 지원 등 중요한 장치 세부정보를 쿼리할 수 있습니다.
논리적 장치는 텍스처, 버퍼, 큐, 파이프 및 기타 그래픽 데이터 구조 생성과 같은 API의 핵심 내부 기능에 액세스할 수 있습니다. 이러한 유형의 데이터 구조는 모든 최신 그래픽 API에서 거의 동일하며 변경 사항이 거의 없습니다. Vulkan과 DirectX 12는 논리 장치를 통해 메모리 데이터 구조를 생성하여 메모리를 제어합니다.
각 애플리케이션에는 일반적으로 단 하나의 진입점이 있으며, UE의 진입점은 FDynamicRHI의 하위 클래스입니다. 각 진입점에는 하나 이상의 물리적 장치가 있고, 각 물리적 장치에는 하나 이상의 논리적 장치가 있습니다.
13.2.3 스왑체인
애플리케이션의 백 캐시 및 스왑 체인은 시스템 또는 그래픽 API에 따라 다르며 다음 개념을 포함합니다.
개념 UE 다이렉트X 12 다이렉트X 11 불칸 금속 OpenGL
창 표면 FRHIRenderTargetView ID3D12리소스 ID3D11텍스처2D vk::표면 CAMetalLayer OS에 따라 다름
스왑체인
- IDXGISwapChain3 IDXGISwapChain vk::스왑체인 CAMetal드로어블 OS에 따라 다름
프레임 버퍼 FRHIRenderTargetView ID3D12리소스 ID3D11RenderTargetView vk::프레임버퍼 MTLRenderPass설명자 GLuint
DirectX에서는 Windows/Xbox만 API의 대상이 되므로 표면에 가장 가까운 것은 교환 링크에서 받은 텍스처 반환 버퍼입니다. 교환 링크는 DirectX 드라이버가 내부적으로 Surface를 생성하는 창 핸들을 받습니다. Vulkan의 경우 렌더링 가능한 창 표면을 생성하려면 다음 단계가 필요합니다.

Vulkan WSI 단계 다이어그램.
MacOS 및 iOS 창은 애플리케이션에 뷰가 포함되고 뷰에 레이어가 포함될 수 있는 계층적 구조를 가지므로 Metal의 표면에 가장 가까운 것은 레이어 또는 이를 래핑하는 뷰입니다.
Metal과 OpenGL에는 스왑 체인 개념이 부족하여 스왑 체인을 운영 체제의 창 API에 맡깁니다.
DirectX 12 및 11에는 프레임 버퍼를 나타내는 명시적인 데이터 구조가 없으며 가장 가까운 것은 렌더 대상 뷰입니다.
Swapchain에는 다양한 상황에 대처하는 단일 버퍼링, 이중 버퍼링, 삼중 버퍼링이 포함됩니다. 애플리케이션은 명시적인 버퍼 회전을 수행해야 합니다.
DirectX: IDXGISwapChain3::GetCurrentBackBufferIndex()
다음은 Swapchain 사용에 대한 제안 사항입니다.
애플리케이션이 항상 vsync보다 느리게 실행되는 경우 스왑 체인에서 1개의 Surface를 사용하세요.
애플리케이션이 항상 vsync보다 빠르게 실행되는 경우 스왑 체인에서 2개의 Surface를 사용하면 메모리 소비를 줄일 수 있습니다.
애플리케이션이 때때로 vsync보다 느리게 실행되는 경우 스왑 체인에서 3개의 Surface를 사용하면 애플리케이션에 최고의 성능을 제공할 수 있습니다.

Vulkan 스왑 체인 작동 다이어그램.
13.3 파이프라인 리소스
최신 그래픽 렌더링 파이프라인에는 복잡한 프로세스, 개념, 리소스, 참조 및 데이터 흐름 관계가 포함됩니다. (아래 사진)

Vulkan 렌더링 파이프라인 다이어그램.
13.3.1 명령
최신 그래픽 API의 명령에는 다음 개념을 포함하여 애플리케이션이 GPU와 상호 작용하는 모든 작업이 포함됩니다.
개념 UE 다이렉트X 12 다이렉트X 11 불칸 금속 OpenGL
명령 대기열
- ID3D12CommandQueue ID3D11DeviceContext vk::큐 MTLCommandQueue
- 명령 할당자
- ID3D12CommandAllocator ID3D11DeviceContext vk::CommandPool MTLCommandQueue
- 명령 버퍼 FRHI명령목록 ID3D12그래픽명령목록 ID3D11DeviceContext vk::CommandBuffer MTLRenderCommandEncoder
- 명령 목록 FRHI명령목록 ID3D12CommandList[] ID3D11CommandList vk::제출정보 MTLCommandBuffer
- 명령 대기열을 사용하면 GPU 실행을 위해 작업을 대기열에 넣을 수 있습니다. GPU는 비동기식 컴퓨팅 장치이므로 항목이 대기열에 추가되는 시기를 제어하는 동안 GPU를 계속 사용해야 합니다.
명령 할당자를 사용하면 명령 버퍼를 생성하고 GPU가 실행할 기능을 정의할 수 있습니다. 권장되는 명령 할당자 수는 다음과 같습니다.
수백 개의 명령 할당자가 있는 경우 이는 잘못된 접근 방식입니다. Command Allocator는 증가만 합니다. 즉, 다음을 의미합니다.
할당자로부터 메모리를 회수할 수 없습니다. 할당자를 회수하면 할당자가 최악의 크기로 늘어납니다.
명령 목록에 할당하는 것이 좋습니다.
가능하다면 크기별로 풀을 할당하십시오.
할당자/명령 목록을 재사용하고 매 프레임마다 다시 생성하지 마세요.
명령 버퍼는 GPU에서 실행되는 프로세스(예: 그리기 호출)를 설명하고, CPU-GPU 액세스 가능 메모리에서 GPU 전용 메모리로 데이터를 복사하고, 현재 가위와 같은 그래픽 파이프라인의 다양한 측면을 동적으로 설정할 수 있는 비동기 컴퓨팅 장치입니다. 재사용과 정확한 제어를 달성하기 위해 Vulkan의 명령 버퍼에는 복잡한 상태와 전환(예: 유한 상태 머신)이 있습니다.

명령 목록은 일괄적으로 GPU에 푸시되는 명령 버퍼 세트입니다. 이는 GPU를 계속 사용하여 CPU와 GPU 간의 동기화를 줄이기 위해 수행됩니다. 각 명령 목록은 엄격하게 순서대로 실행됩니다. Command List는 보조 Command List(Bundle, Secondary Command List)를 호출할 수 있습니다. 두 수준의 명령 목록 모두 여러 번 호출할 수 있지만 마지막 제출이 완료될 때까지 기다려야 합니다.
다음 그림은 DX12 명령과 관련된 개념의 계층 구조 다이어그램입니다.

비슷한 명령 목록이나 할당자의 경우 재사용해 보세요.

명령 목록 또는 할당자를 재설정할 때 참조하는 리소스를 변경되지 않은 상태로 유지하십시오(파괴 또는 새 할당 없음).
하지만 데이터가 매우 다른 경우에는 파기되므로 파기 전에 메모리를 해제해야 합니다.
더 나은 성능을 위해 Command에 대한 권장 사항은 다음과 같습니다.
- 명령 버퍼에는 이중 버퍼링과 삼중 버퍼링을 사용하십시오. 다음 항목은 CPU에 채워지고 이전 항목은 여전히 GPU에서 실행됩니다.

- 프레임을 여러 명령 버퍼로 분할합니다. GPU 작업을 보다 정기적으로 제출할수록 명령이 더 일찍 제출될수록 지연 시간이 줄어듭니다.

명령 버퍼의 수를 제한하십시오. 예를 들어 프레임당 15~30개입니다.
여러 명령 버퍼를 하나의 커밋 호출로 일괄 처리하여 커밋 수를 제한합니다. 예를 들어 프레임당 대기열당 5개입니다.
명령 버퍼의 세분성을 제어합니다. 많은 양의 작업을 수행하고 소량의 작업을 여러 번 수행하지 마십시오.
프레임당 한 번씩 제출하여 프레임의 일부를 기록합니다.
여러 스레드에서 여러 명령 버퍼를 병렬로 기록합니다.

- 대부분의 개체와 데이터(설명자 및 CB와 같은 메모리 데이터를 포함하되 이에 국한되지 않음)는 GPU에서 사용될 때 그래픽 API에 의해 참조 계산되거나 버전이 지정되지 않습니다. GPU가 사용되는 동안 활성 상태를 유지하고 수정되지 않았는지 확인하세요. Command Buffer의 이중 버퍼링 및 삼중 버퍼링과 함께 사용할 수 있습니다.

- 링 버퍼를 사용하여 동적 데이터를 저장합니다.

13.3.2 렌더 패스
개념 UE 다이렉트X 12 다이렉트X 11 불칸 금속 OpenGL
렌더링 패스 FRHIR렌더패스정보 BeginRenderPass, EndRenderPass
- VkRenderPass MTLRenderPass설명자
- 서브패스 FRHIR렌더패스정보
- VkSubpass설명 프로그래밍 가능한 블렌딩 PLS
그리기 명령은 렌더 패스 인스턴스에 기록되어야 합니다. 각 렌더 패스 인스턴스는 렌더링 중에 사용할 입력 및 출력 이미지 리소스 세트를 정의합니다.

DirectX 12 녹화 명령 대기열 다이어그램. 명령에는 리소스, 래스터라이제이션 및 기타 유형이 포함됩니다.
최신 모바일 GPU는 일반적으로 TBR 아키텍처를 지원했습니다. 이 아키텍처의 특성을 더 잘 활용하고 렌더 패스 중 타일 캐시에 데이터를 유지하기 위해 Subpass 기술이 탄생했습니다. Subpass 기술을 사용하면 대역폭을 크게 줄이고 렌더링 효율성을 향상시킬 수 있습니다. 자세한 내용은 12.4.13 서브패스 및 10.4.4.2 서브패스 렌더링을 참조하세요.

Vulkan 렌더 패스와 관련된 다양한 개념, 리소스 및 대화형 관계.
OpenGL에서는 Pixel Local Storage 기술이 Subpass를 시뮬레이션하는 데 사용됩니다. Metal은 **Programmable Blending(PB)**을 사용하여 서브패스 메커니즘을 시뮬레이션합니다(아래 그림).


상단: 전통적인 멀티 패스 렌더링 지연 조명, 여러 GBuffer 텍스처는 GBuffer Pass 및 Lighting Pass 중에 Tile Memeory와 시스템 메모리 사이에서 앞뒤로 전송됩니다. 하단: Metal의 PB 기술을 사용하여 GBuffer 데이터는 GBuffer Pass 및 Lighting Pass 중에 Tile Memroy에 보관됩니다.

Metal은 Render Pass의 Store 및 Load 태그를 사용하여 타일 내의 프레임 버퍼를 정확하게 제어함으로써 읽기 및 쓰기 대역폭을 크게 줄입니다.
렌더 패스를 생성하고 사용하기 위한 의사코드는 다음과 같습니다:
Start a render pass
// 로써 코드 루프 회
Bind all the resources
Descriptor set(s)
Vertex and Index buffers
Pipeline state
Modify dynamic state
Draw
End render passVulkan의 렌더 패스 사용에 대한 제안:
- 여러 개의 서브패스가 작은 렌더 패스를 형성하는 경우에도 좋은 습관입니다.
깊이 프리패스, G-버퍼 렌더, 조명, 포스트 프로세스
- 종속성이 반드시 필요한 것은 아닙니다.
다중 섀도우 맵 패스는 다중 출력을 생성합니다.
- 렌더 패스에서 수행할 작업을 겹칩니다.
vkCmdClearAttachment 대신 load opclear를 사용하는 것이 좋습니다.
명시적인 장벽 대신 렌더 패스 첨부 파일을 사용하는 최종 레이아웃을 선호합니다.
'상관없음'을 활용하세요.
구문 분석 첨부 파일을 사용하여 MSAA 구문 분석을 수행합니다.
렌더 패스와 관련된 자세한 지침은 12.4.13 서브패스 및 10.4.4.2 서브패스 렌더링을 참조하세요.
13.3.3 텍스처, 셰이더
개념 UE 다이렉트X 12 다이렉트X 11 불칸 금속 OpenGL
질감 FRHI질감 ID3D12리소스 ID3D11텍스처2D vk::이미지 및 vk::ImageView MTL텍스처 GLuint
셰이더 FRHI셰이더 ID3DBlob ID3D11VertexShader, ID3D11PixelShader vk::셰이더모듈 MTL 라이브러리 GLuint
대부분의 최신 그래픽 API에는 균일한 버퍼와 텍스처를 이 데이터가 필요한 그래픽 파이프라인에 연결하는 바인딩 데이터 구조가 있습니다. Metal의 독특한 점은 setVertexBuffer를 사용하여 명령 인코더에서 유니폼을 바인딩할 수 있어 Vulkan, DirectX 12 및 OpenGL보다 빌드하기가 더 쉽다는 것입니다.
13.3.4 셰이더 바인딩
개념 UE 다이렉트X 12 다이렉트X 11 불칸 금속 OpenGL
셰이더 바인딩 FRHIUniformBuffer ID3D12루트서명 ID3D11DeviceContext::VSSetConstantBuffers(...) vk::PipelineLayout 및 vk::DescriptorSet [MTLRenderCommandEncoder setVertexBuffer:uniformBuffer] 글린트
파이프라인 상태 FGraphicsPipelineStateInitializer ID3D12파이프라인상태 다양한 상태 호출 vk::파이프라인 MTLRenderPipelineState 다양한 상태 호출
설명자
- D3D12_ROOT_DESCRIPTOR
- VkDescriptorBufferInfo, VkDescriptorImageInfo 논쟁
- 설명자 힙
- ID3D12설명자힙
- VkDescriptorPoolCreateInfo 더미
- 설명 테이블
- D3D12_ROOT_DESCRIPTOR_TABLE
- VkDescriptorSetLayoutCreateInfo 인수 버퍼
- 루트 매개변수
- D3D12_ROOT_PARAMETER
- VkDescriptorSetLayoutBinding 셰이더 매개변수 목록의 인수
- 루트 서명
- ID3D12루트서명
- VkPipelineLayoutCreateInfo
- 파이프라인 상태는 래스터 드로우 콜, 계산 일정 또는 레이 트레이싱 일정이 실행될 때 실행될 내용에 대한 전반적인 설명입니다. DirectX 11 및 OpenGL에는 전용 그래픽 파이프라인 개체가 없으며 대신 호출을 사용하여 그리기 호출 실행 사이에 파이프라인 상태를 설정합니다.
루트 서명은 상수 버퍼, 구조화된 버퍼, 샘플러, 텍스처, 구조화된 버퍼 등 셰이더가 액세스할 수 있는 리소스 유형을 정의하는 개체입니다(아래 그림).

특히 루트 서명은 설명자 테이블, 설명자 및 상수 데이터의 세 가지 유형의 리소스 및 데이터를 설정할 수 있습니다.

DirectX 12 루트 서명 데이터 구조 다이어그램.
CPU와 GPU에서 이 세 가지 리소스의 소비는 정반대이므로 사용량을 따져볼 필요가 있습니다.

루트 시그니처 세 가지 유형(설명자 테이블, 설명자, 상수 데이터)의 GPU 메모리 획득 소비는 순차적으로 감소하지만 CPU 소비는 순차적으로 증가합니다.
더 구체적으로 말하면, 테이블의 포인터를 변경하는 데는 비용이 거의 들지 않지만(포인터만 변경하면 동기화 오버헤드 없음) 테이블의 내용을 변경하는 것이 더 어렵습니다(사용 중인 테이블의 내용을 수정할 수 없으며 자동 이름 바꾸기 메커니즘이 없습니다).
따라서 Root Signature의 크기를 최대한 조절하고, Shader의 가시 범위를 효과적으로 조절하며, 필요한 경우에만 Root Signature 데이터를 업데이트하는 것이 필요합니다.
루트 서명은 DirectX 12에서 최대 64 DWORD에 도달할 수 있으며, 여기에는 데이터(많은 저장 공간을 차지함), 설명자(2 DWORD) 및 설명자 테이블에 대한 포인터(아래 그림)가 포함될 수 있습니다.

설명자는 셰이더 리소스(예: 버퍼, 버퍼 보기, 이미지 보기, 샘플러 또는 결합된 이미지 샘플러)의 매개변수를 설명하는 작은 데이터 조각입니다. 이는 단지 불투명한 데이터(OS 수명주기 관리 없음)일 뿐이며 하드웨어가 나타내는 보기입니다.

설명자의 데이터 범례.
설명자는 후속 그리기 명령에 사용하기 위해 명령 기록 중에 바인딩되는 설명자 테이블로 구성됩니다.
각 설명자 테이블의 콘텐츠 배열은 설명자 테이블에 저장할 수 있는 설명자를 결정하는 설명자 테이블의 레이아웃에 따라 결정됩니다. 파이프라인이 사용할 수 있는 설명자 테이블 또는 루트 매개변수의 순서는 루트 서명에 지정됩니다. 각 파이프라인 개체에서 사용하는 설명자 테이블 및 루트 매개변수의 수에는 제한이 있습니다.
설명자 힙은 메모리 할당을 처리하고 셰이더가 참조하는 객체에 대한 설명을 저장하는 데 사용되는 객체입니다.

루트 서명, 루트 매개변수, 설명자 테이블 및 설명자 힙 간의 관계입니다. 루트 서명은 여러 루트 매개변수 인스턴스를 저장합니다. 각 루트 매개변수는 설명자 테이블, UAV, SRV 등과 같은 개체일 수 있습니다. 루트 매개변수의 메모리 콘텐츠는 설명자 힙에 저장됩니다.

GPU 내 DX12 루트 서명 간의 상호 작용 다이어그램. 루트 서명은 모든 셰이더 단계에서 공유됩니다.
다음은 Vulkan 설명자 세트를 사용하는 예입니다. 다음과 같은 3개의 설명자 세트 A, B, C가 있는 것으로 알려져 있습니다.

다음 C++ 코드를 통해 바인딩합니다.
vkBeginCommandBuffer();
// ...
vkCmdBindPipeline(); // Binds shader
// 바인딩(Bind)Descriptor Set B 및 C, 내에서 C0, B2. A바인딩(Bind).
vkCmdBindDescriptorSets(firstSet = 0, pDescriptorSets = &descriptor_set_c);
vkCmdBindDescriptorSets(firstSet = 2, pDescriptorSets = &descriptor_set_b);
vkCmdDraw(); // or dispatch
// ...
vkEndCommandBuffer();위 코드로 바인딩한 후 Shader 리소스의 바인딩 시퀀스 번호는 아래 그림과 같습니다.

해당 GLSL 코드는 다음과 같습니다.
layout(set = 0, binding = 0) uniform sampler2D myTextureSampler;
layout(set = 0, binding = 2) uniform uniformBuffer0 {
float someData;
} ubo_0;
layout(set = 0, binding = 3) uniform uniformBuffer1 {
float moreData;
} ubo_1;
layout(set = 2, binding = 0) buffer storageBuffer {
float myResults;
} ssbo;복잡한 렌더링 장면의 경우 애플리케이션은 변경된 리소스 세트만 수정하고 변경 사항을 최소화하여 리소스 바인딩을 유지할 수 있습니다. 렌더링 의사코드는 다음과 같습니다.
foreach (scene) {
vkCmdBindDescriptorSet(0, 3, {sceneResources,modelResources,drawResources});
foreach (model) {
vkCmdBindDescriptorSet(1, 2, {modelResources,drawResources});
foreach (draw) {
vkCmdBindDescriptorSet(2, 1, {drawResources});
vkDraw();
}
}
}해당 셰이더 의사코드:
layout(set=0,binding=0) uniform { ... } sceneData;
layout(set=1,binding=0) uniform { ... } modelData;
layout(set=2,binding=0) uniform { ... } drawData;
void main() { }
Vulkan 바인딩 설명자 흐름도.
아래 그림은 또 다른 Vulkan VkDescriptorSetLayoutBinding 사례입니다.

셰이더 바인딩 사용과 관련된 권장 사항은 다음과 같습니다.
루트 서명은 RingBuffer 데이터 구조를 사용하고 정적 샘플러(최대 2032개)를 사용하여 단일 설명자 힙에 저장하는 것이 가장 좋습니다.
루트 서명 크기를 초과하지 마십시오.
루트 서명 내의 CBV 및 상수는 각 그리기 호출에 따라 변경될 가능성이 높습니다.
CB에 포함된 대부분의 상수 데이터는 루트 상수가 아니어야 합니다.
루트 서명에 직접 그릴 때마다 변경되는 작고 자주 사용되는 상수만 넣으십시오.
업데이트 빈도에 따라 설명자 테이블을 분할하고 가장 자주 업데이트되는 내용을 앞쪽에 배치합니다(DirectX 12만 해당, 반대로 Vulkan, Metal은 알 수 없음).
드로우별, 머티리얼별, 라이트별, 프레임별.

- 가장 자주 변경되는 데이터를 루트 서명 앞에 배치하여 드라이버에 업데이트 빈도 힌트를 제공합니다.

- 시작 시 루트 서명을 SGPR에 복사합니다.
레이아웃은 컴파일러에서 결정됩니다.
각 음영 단계에 대해 복사본을 만드십시오.
SGPR이 너무 많이 점유되면 루트 서명이 로컬 메모리로 분할됩니다(아래 그림). 이런 상황은 피해야 합니다! !

가능한 한 정적 테이블을 사용하면 성능이 향상될 수 있습니다.
RST(루트 서명 테이블)를 가능한 한 작게 유지하십시오. 여러 RST를 사용할 수 있습니다.
목표는 드로우 콜당 하나의 슬롯만 변경하는 것입니다.
리소스 가시성을 가장 작은 단계 집합으로 제한합니다.
필요하지 않은 경우 D3D12_SHADER_VISIBILITY_ALL을 사용하지 마세요.
DENY_xxx_SHADER_ROOT_ACCESS를 사용해 보세요.
주의하세요. RST에는 경계 검사가 없습니다.
루트 서명을 변경한 후 리소스 바인딩을 정의되지 않은 상태로 두지 마십시오.
AMD 특정 권장사항:
RST 내에서는 상수 및 CBV 그리기 호출 변경만 이루어져야 합니다.
추첨당 하나 이상의 CBV가 변경되는 경우 CBV를 테이블에 배치하는 것이 좋습니다.
NV 관련 제안:
모든 상수와 CBV를 RST에 넣습니다.
RST의 상수와 CBV는 셰이더 속도를 높입니다.
루트 상수에는 CBV 생성이 필요하지 않으므로 CPU 작업이 줄어듭니다.
DescriptorSet를 캐시하고 재사용해 보세요.

Fortnite는 DescriptorSet 범례를 캐시하고 재사용합니다.
13.3.5 힙, 버퍼
개념 UE 다이렉트X 12 다이렉트X 11 불칸 금속 OpenGL
힙 FRHI자원 ID3D12리소스, ID3D12Heap
- Vk::메모리힙 MTL버퍼
- 버퍼 FRHIIndexBuffer, FRHHIVertexBuffer ID3D12리소스 ID3D11버퍼 vk::버퍼 & vk::BufferView MTL버퍼 GLuint
힙은 GPU 메모리가 포함된 개체이며 리소스(예: 버텍스 버퍼, 텍스처)를 GPU 전용 메모리에 업로드하는 데 사용할 수 있습니다.
버퍼는 주로 버텍스 인덱스, 버텍스 어트리뷰트, 상수 버퍼 및 기타 데이터를 GPU에 업로드하는 데 사용됩니다.
13.3.6 펜스, 배리어, 세마포어
개념 UE 다이렉트X 12 다이렉트X 11 불칸 금속 OpenGL
울타리 FRHIGPU울타리 ID3D12울타리 ID3D11울타리 vk::울타리 MTLFence glFenceSync
장벽 FRDG배리어배치 D3D12_RESOURCE_BARRIER
- vkCmdPipelineBarrier MTLFence glMemoryBarrier
세마포어
- 핸들 핸들 vk::세마포어 디스패치_세마포어_t OS에 따라 다름
이벤트 F이벤트
- Vk::이벤트 MTL이벤트, MTLShared이벤트 OS에 따라 다름
Fence는 CPU와 GPU를 동기화하는 데 사용되는 개체입니다. CPU나 GPU 중 하나는 다른 쪽이 따라잡을 수 있도록 울타리에서 기다리도록 지시할 수 있습니다. 리소스 할당 및 할당 취소를 관리하는 데 사용할 수 있으므로 전체 그래픽 메모리 사용량을 더 쉽게 관리할 수 있습니다.
Barrier는 명령 버퍼 내에서 사용되는 보다 세분화된 동기화 형식입니다.
세마포어는 명령 버퍼를 장치 큐에 제출하기 전에 대기하고 스왑 체인에서 다음 이미지를 가져오는 것과 같은 작업 간의 종속성을 도입하는 데 사용되는 개체입니다. Vulkan의 독특한 점은 세마포어가 API의 일부인 반면 DirectX와 Metal은 이를 OS 호출에 위임한다는 것입니다.
이벤트는 Barrier와 유사하며 명령 버퍼 내에서 작업을 동기화하는 데 사용됩니다. DirectX 및 OpenGL의 경우 운영 체제의 API를 사용하여 이벤트를 구현해야 합니다. UE 내에서 FEvent는 스레드 간 신호를 동기화하는 데 사용됩니다.

Vulkan 동기화 메커니즘: 세마포어(신호)는 큐를 동기화하는 데 사용됩니다. 펜스(fence)는 GPU와 CPU를 동기화하는 데 사용됩니다. Event(이벤트)와 Barrier(배리어)는 Command Buffer를 동기화하는데 사용됩니다.

여러 대기열 간의 Vulkan 세마포어 동기화 사례.
13.4 파이프라인 메커니즘
13.4.1 자원 관리
최신 하드웨어 아키텍처의 경우 일반적인 메모리 모델은 다음과 같습니다.

현대 컴퓨터 메모리 모델의 아키텍처 다이어그램. 위에서 아래로 용량은 점점 작아지고 있지만 대역폭은 점점 커지고 있습니다.
DirectX 11과 같은 기존 API의 경우 리소스 메모리는 운영 체제에 의존하여 수명 주기를 관리해야 합니다. 메모리는 항상 채워져 있으며 대부분은 직접 비디오 메모리가 되어 오버플로가 발생하고 시스템 메모리로 돌아갑니다. 이러한 상황은 이전에는 별로 주목을 받지 못했고, 비록 우리가 원하는 것이 아니더라도 운전자가 뒤에서 많은 추가 작업을 수행하는 데 우리 모두는 익숙한 것 같으며, 이로 인해 성능이 저하될 수도 있습니다.

DirectX 11 메모리 관리 모델 그림. 일부 리소스는 비디오 메모리와 시스템 메모리 모두에 존재합니다. 비디오 메모리가 소진된 경우 일부 리소스를 시스템 메모리로 마이그레이션해야 합니다.
반대로 DirectX 12, Vulkan 및 Metal과 같은 최신 그래픽 API를 사용하면 애플리케이션이 저장소 위치, 상태, 변환, 수명 주기 및 리소스 종속성을 정밀하게 제어할 수 있을 뿐만 아니라 정확한 데이터 형식 및 레이아웃, 압축 활성화 여부 등을 지정할 수 있습니다. 최신 그래픽 API 드라이버는 추가 메모리 관리 작업을 많이 수행하지 않으며 애플리케이션이 리소스 관리 방법을 더 잘 알고 있기 때문에 소유권은 애플리케이션에 의해 제어됩니다.

DX11과 DX12의 메모리 할당 비교 차트입니다. DX11은 전용 메모리 블록을 기반으로 하는 반면, DX12는 힙 할당을 기반으로 합니다.
최신 그래픽 API에서는 거의 모든 작업이 느리게 실행되므로 아직 처리 대기열에 있는 데이터와 리소스를 변경하지 않도록 주의하세요. 개발자는 리소스 수명주기, 스토리지 관리 및 리소스 충돌을 처리해야 합니다.
최신 그래픽 API를 사용하여 리소스 메모리를 관리할 때 가장 먼저 고려해야 할 사항은 메모리 공간을 예약하는 것입니다.
// DirectX 12로써 인터페이스 구현 및 VRAM
IDXGIAdapter3::QueryVideoMemoryInfo()
IDXGIAdapter3::SetVideoMemoryReservation()포그라운드 애플리케이션인 경우 QueryVideoMemory는 유휴 시스템에서 VRAM의 약 절반으로 시작합니다. 이보다 적으면 다른 헤비급 애플리케이션이 이미 실행 중임을 의미할 수 있습니다.
메모리 소모는 최소 사양 문제입니다. 애플리케이션은 필요한 메모리 공간을 추정하고, 예약된 메모리의 크기를 수정하기 위한 구성을 제공하고, 하드웨어 사양에 따라 합리적인 선택을 제공해야 합니다.
공간을 예약한 후 DirectX 12는 MakeResident를 통해 메모리를 두 번 할당할 수 있습니다. MakeResident는 동기 작업이며 메모리가 할당될 때까지 호출 스레드를 차단한다는 점에 유의해야 합니다. 사용 제안은 다음과 같습니다.
여러 MakeResident의 배치를 결합합니다.
렌더링 스레드에서 추출하여 추가 전용 스레드에 배치해야 합니다. 페이징 작업은 렌더링과 함께 인터리브됩니다. (아래 사진)

- 반드시 리소스를 준비한 후 사용하세요. 그렇지 않으면 전용 리소스 스레드를 사용해도 여전히 렉이 발생합니다.
이를 위해 실행 전략을 사용할 수 있습니다. 지금과 나중에 어떤 리소스가 사용될지 미리 예측하고 렌더링 스레드 전에 몇 프레임을 실행하면 더 많은 버퍼가 덜 버벅거리지만 대기 시간이 발생합니다.

상주 메커니즘을 사용하지 않고 시스템 메모리에서 사용할 수 있는 리소스를 미리 로드하고 이를 비디오 메모리로 즉시 이동하지 않는 것도 가능합니다. 리소스가 사용되면 비디오 메모리에 복사된 후 디스크립터를 다시 쓰거나 페이지를 다시 매핑합니다(아래). 메모리 사용량을 줄여야 하는 경우 작업을 취소하고 비디오 메모리 복사본을 회수합니다.

그러나 이 방법은 VR 애플리케이션에 있어 큰 과제에 직면해 있으며 긴 지연을 유발하는 솔루션은 분명히 실현 가능하지 않습니다. 시스템 메모리를 현명하게 사용하고 스트리밍을 미리 예측할 수 있습니다.
또한 리소스 충돌은 주의 깊게 처리해야 하며 동기화 개체를 사용하여 가능한 리소스 충돌을 제어해야 합니다.

상단: 데이터 업데이트를 처리할 때 CPU와 GPU 사이에 리소스 충돌이 있습니다. 하단: 데이터 업데이트를 실행하기 전에 GPU가 그리기 호출을 완료할 때까지 기다리려면 CPU가 명시적으로 동기화 대기에 참여해야 합니다.
일반적인 리소스 충돌 상황:
-그림자 지도.
지연된 음영 처리, 조명.
실시간 반사 및 굴절.
-...
- 후속 렌더링에서 렌더링 대상을 텍스처로 적용합니다.
13.4.1.1 자원 할당
Direct3D 11에서 ID3D11DeviceContext::Map이 D3D11_MAP_WRITE_DISCARD 플래그와 함께 호출될 때 GPU에서 버퍼를 계속 사용하는 경우 런타임은 이전 버퍼 데이터 대신 새 메모리 블록에 대한 포인터를 반환합니다. 이를 통해 애플리케이션이 새 버퍼를 데이터로 채우는 동안 GPU가 이전 데이터를 계속 사용할 수 있습니다. 애플리케이션에는 추가 메모리 관리가 필요하지 않으며 GPU 사용이 끝나면 이전 버퍼가 자동으로 삭제되거나 재사용됩니다.
D3D11과 같은 기존 API가 리소스를 할당할 때 각 리소스는 일반적으로 GPU VA(가상 주소) 및 물리적 페이지에 해당합니다.

D3D11 메모리 할당 모델.
Direct3D 12에서는 모든 동적 업데이트(상수 버퍼, 동적 꼭짓점 버퍼, 동적 텍스처 등 포함)가 애플리케이션에 의해 제어됩니다. 이러한 동적 업데이트에는 메모리 가용성을 보장하기 위해 애플리케이션에 의한 필수 GPU 펜싱 또는 버퍼링이 포함됩니다.

최신 그래픽 API를 사용하려면 리소스에 대한 모든 작업을 제어하는 애플리케이션이 필요합니다.

Vulkan 리소스 생성 단계: 먼저 CPU에 표시되는 스테이징 버퍼를 생성한 다음 스테이징 버퍼의 데이터를 비디오 메모리로 복사합니다.
D3D12와 같은 최신 그래픽 API에서는 GPU VA와 리소스의 물리적 페이지가 분리되며, 애플리케이션은 물리적 페이지 할당의 오버헤드를 더 잘 상각할 수 있고, 일시적으로 비어 있는 메모리를 재사용할 수 있으며, 장면에서 더 이상 사용하지 않는 메모리의 용도를 변경할 수도 있습니다.

D3D12 메모리 할당 모델.
다양한 힙 유형과 할당 위치는 다음과 같습니다.
힙 유형 메모리 위치
기본값 비디오 메모리
업로드 시스템 메모리
다시 읽어보기 시스템 메모리
다음 표는 가능한 복사 작업 조합입니다.
소스 목적지
업로드 기본값
기본값 기본값
기본값 다시 읽어보기
업로드 다시 읽어보기
조합에 따라 대기열 유형에 따라 복사 속도가 크게 달라집니다.

RTX 2080에서 힙 유형 간 64~256MB의 데이터를 복사할 때 명령 대기열 간의 비교.

RTX 2080에서 힙 유형 간에 데이터를 복사할 때 모든 명령 대기열의 평균 복사 시간과 데이터 크기를 비교합니다.
힙에는 여러 유형과 태그가 있으며 용도와 의미도 다릅니다.

리소스 힙의 경우 관련 속성은 다음과 같이 설명됩니다.

리소스를 생성하는 방법에는 3가지가 있습니다.
- 헌신. 단일 블록 리소스, D3D11 스타일.

- 배치되었습니다. 기존 힙의 오프셋입니다.

- 예약된. Tiled 리소스처럼 힙에 매핑됩니다.

이 3가지 리소스의 선택은 아래에 설명되어 있습니다.
힙 유형 설명
커밋됨 리소스별 상주가 필요합니다. 중첩(앨리어싱)은 필요하지 않습니다.
배치됨 더 빠른 생성 및 파괴; 유사한 거주자를 힙으로 그룹화할 수 있습니다. 다른 리소스와 중복되어야 합니다. 작은 자원 덩어리.
타일식 / 예약됨 유연한 메모리 관리가 필요합니다. ResourceMap의 CPU 및 GPU 오버헤드를 허용할 수 있습니다.
다음 표는 리소스 유형과 VA 및 물리적 페이지 간의 지원 관계를 보여줍니다.
힙 유형 실제 페이지 가상 주소
커밋됨 예 예
힙 예 아니요
배치됨 아니요 예
타일식 / 예약됨 아니요 예
GPU VA와 물리적 페이지 태그의 다양한 조합은 다양한 시나리오에 적합합니다. 아래 그림은 세 가지 방식으로 배포 메커니즘을 개략적으로 나타낸 것입니다.

약정 리소스 사용량 권장 사항:
RTV, DSV, UAV의 경우.
리소스에 맞게 필요한 가장 작은 크기의 힙을 할당합니다.
애플리케이션은 각 리소스에 대해 MakeResident/Evict를 호출해야 합니다.
응용 프로그램은 운영 체제의 페이징 논리에 따라 관리됩니다.
"MakeResident"에서 운영 체제는 리소스가 배치되는 위치를 결정합니다.
- 동기적으로 호출되면 반환될 때까지 정체됩니다.
전체 블록 할당과 자원의 하위 할당(Suballocation) 간의 비교 차트는 다음과 같습니다.

너무 많은 유형과 속성에 직면하여 필요에 따라 다양한 용도와 조합을 선택할 수 있습니다.
- 자주 GPU를 읽고 쓰는 경우(예: RT, DS, UAV):
비디오 메모리 할당: D3D12_HEAP_TYPE_DEFAULT / VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT.
먼저 할당되었습니다.
GPU 읽기가 빈번하고 CPU 쓰기가 적거나 단 하나인 경우:
비디오 메모리 할당: D3D12_HEAP_TYPE_DEFAULT / VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT.
시스템 메모리 D3D12_HEAP_TYPE_UPLOAD / VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT에 스테이징 복사 버퍼를 할당하고 스테이징 복사 버퍼에서 비디오 메모리로 데이터를 복사합니다.
대체 수단으로 시스템 메모리에 배치됩니다.
CPU 쓰기 및 GPU 읽기가 자주 발생하는 경우:
Vulkan 및 AMD GPU인 경우 DEVICE_LOCAL + HOST_VISIBLE 메모리를 사용하여 CPU에 직접 쓰고 GPU에서 읽습니다.
그렇지 않은 경우에는 시스템 메모리와 비디오 메모리에 복사본을 보관한 후 전송하세요.
GPU 쓰기 및 CPU 읽기가 자주 발생하는 경우:
캐시된 시스템 메모리 사용: D3D12_HEAP_TYPE_READBACK / HOST_VISIBLE + HOST_CACHED.
보다 효율적인 힙 사용을 위한 제안:
- 업로드 힙으로 채워진 기본 힙이 선호됩니다.
하나 이상의 커밋된 업로드 버퍼 리소스에서 링 버퍼를 구축하고 각 버퍼를 CPU 액세스를 위해 영구적으로 매핑합니다.

CPU 측에서는 필요에 따라 오프셋을 정렬하여 데이터가 각 버퍼에 순차적으로 기록됩니다.
각 프레임이 끝날 때 증가하는 울타리 값을 신호로 보내도록 GPU에 지시합니다.
GPU가 Fence에 도달하기 전에 업로드 힙 데이터를 수정하지 마십시오.
렌더링 프로세스 전반에 걸쳐 업로드 힙은 GPU로 전송된 동적 데이터를 저장하는 데 재사용됩니다.
더 큰 힙을 만듭니다.
약 10-100MB.
- 하위 할당은 배치된 자원을 저장하는 데 사용됩니다.

MakeResident/Evict는 리소스가 아닌 힙별로 호출됩니다.
할당에 대한 애플리케이션 추적이 필요합니다. 마찬가지로 애플리케이션은 각 힙에서 사용 가능한/사용된 메모리 범위를 추적해야 합니다.
GPU 메모리를 할당하거나 해제하려면 MakeResident/Evict를 주의해서 사용하세요.
CPU + GPU 비용이 상당하므로 MakeResident 및 UpdateTileMapping을 일괄 처리합니다.
필요한 경우 대규모 워크로드를 여러 프레임에 분산시킵니다.
MakeResident는 동기식입니다.
모든 리소스가 상주할 때까지 반환되지 않습니다.
일괄 처리. 미니 배치는 소규모 페이징 작업이 많이 발생하므로 비효율적입니다.
운영 체제는 리소스의 위치를 결정하기 위해 계산을 시작할 수 있으며, 이는 많은 시간이 소요됩니다. 호출 스레드는 반환될 때까지 중단됩니다.
메인 스레드가 막히는 것을 방지하려면 작업자 스레드에 있는지 확인하십시오.
MakeResident의 실패를 처리해야 합니다.
일반적으로 작업자 스레드에 사용 가능한 메모리가 충분하지 않음을 의미합니다.
하지만 메모리가 충분하더라도(조각화) 발생합니다.
비거주 읽기는 페이지 오류이며 프로그램 충돌을 일으킬 수 있습니다! !
Evict는 다음과 같이 설명되고 동작합니다.
퇴거자는 즉각적인 조치를 취할 수 없습니다. 다음 MakeResident 호출까지 지연됩니다.
MakeResident에 비해 소모량이 적습니다.
비디오 메모리가 오버플로되면 성능에 급격한 변동이 발생하므로 이를 해결하거나 방지하기 위해 일련의 조치를 취해야 합니다.
브라우저와 같이 메모리를 많이 사용하는 애플리케이션에 주의하세요. 사용자가 변경할 수 있는 해상도/품질 설정을 제공합니다.
1GB, 2GB 등 하드웨어 성능이 다른 구성을 고려해보세요.
비디오 메모리에 사용 가능한 메모리가 없는 경우 시스템 메모리에 오버플로 힙을 생성하고 비디오 메모리 힙의 일부 리소스를 시스템 메모리로 이동할 수 있습니다.
응용 프로그램은 어떤 리소스가 가장 중요한지 파악하여 중요하지 않은 리소스를 마이그레이션하는 동안 비디오 메모리에 유지하는 드라이버/운영 체제에 비해 이점이 있습니다.

- 성능에 중요하지 않은 리소스를 비디오 메모리에서 시스템 메모리의 오버플로 힙으로 이동할 수도 있습니다. 상위 밉을 마이그레이션합니다.

- 비디오 메모리에서 리소스를 이동하는 단계:
로컬 복사본을 릴리스합니다.
- 시스템 메모리로 전송하기 전 리소스의 접근 패턴을 파악한다.
한 번만 읽으십시오.
지역성이 높고 예측 가능한 액세스 패턴이 더 좋습니다.
상위 밉을 마이그레이션하면 약 70%의 메모리를 절약할 수 있습니다.
임의로 하면 약간의 시각적 차이가 있을 수 있습니다.
현명하게 수행하면 시각적 차이가 없습니다.
텍스처가 힙에 리소스로 배치되면 구현하기가 더 쉽습니다.
겹치는(앨리어싱, 앨리어싱 또는 오버랩이라고도 함) 리소스는 메모리 공간을 크게 절약할 수 있습니다.
앨리어싱 장벽이 필요합니다.
커밋된 RTV/DSV 리소스는 드라이버에 의해 우선순위가 지정됩니다.
NV: 일관되게 읽을 때 구조화된 버퍼 대신 상수 버퍼를 사용합니다. 예를 들어 타일 조명.

중복되는 리소스의 그림입니다. GBuffer와 Helper RT는 시간상 중복되지 않으며 동일한 메모리에 할당될 수 있습니다.
- 어떤 힙에서 어떤 리소스가 할당되는지 최적화하면 성능이 2% 이상 향상될 수 있습니다. 자원 할당 규칙 조정을 포함합니다.

- LRU 자원 관리 전략에 협력하는 것은 매우 도움이 됩니다.
마지막으로 사용한 후 일정 기간 동안 리소스를 메모리에 유지합니다.
리소스 사용량이 상주하는 경우에만 도입됩니다.
통합 메모리 아키텍처를 사용하는 장치의 경우 스테이징 버퍼를 제거하세요.

- 일부 리소스(예: 버텍스 캐시, 인덱스 버퍼)의 비동기 생성을 수행합니다.

UE의 Vulkan RHI는 버텍스 및 인덱스 버퍼의 비동기 생성을 허용하여 렌더링 스레드 지연을 줄입니다.
물리적 메모리를 재사용하려면 리소스가 예약되었거나 배치되었는지 여부에 관계없이 D3D11의 타일형 리소스와 동일하게 다음 규칙을 따라야 합니다.
새로운 리소스가 물리적 메모리를 재사용하는 경우 앨리어싱 장벽을 대기열에 추가해야 합니다.
렌더링 리소스 또는 깊이 스텐실 리소스로 사용되는 물리적 메모리를 처음 사용하거나 재사용하는 경우 애플리케이션은 지우기 또는 복사 작업을 사용하여 리소스 메모리를 초기화해야 합니다.
D3D12는 메모리 매핑에 대한 명시적인 제어를 제공합니다. 각 프레임마다 큰 버퍼를 생성하고 모든 데이터를 임시로 저장할 수 있습니다. Const 버퍼에 대한 전용 요구 사항은 없으며 애플리케이션은 요청 시 이를 구축할 수 있습니다.
처리량이 높은 렌더링의 경우 다음이 권장됩니다.
드로우콜의 혜택을 누리기 위해서는 게임 로직에 관련 처리가 설치되어 있어야 합니다.
각 유닛(예: 포탑, 미사일 궤적)에 대해 CPU에서 계산한 위치나 색상 등의 데이터를 최대한 빨리 GPU에 업로드해야 합니다.
다음은 Ashes의 CPU 작업과 GPU 메모리 상호 작용에 대한 개략도입니다.

13.4.1.2 리소스 업데이트
최신 그래픽 API의 경우 리소스 업데이트의 특징은 일반적으로 다음과 같습니다.
CPU와 GPU는 동일한 스토리지를 공유하며 암시적 복사본이 없습니다. (Apple A7 이상 SoC와 같은 결합된 CPU-GPU 아키텍처에만 적용 가능)
자동 CPU 및 GPU 버퍼 일관성 모델.
CPU와 GPU는 명령 버퍼 실행 경계에서 쓰기를 관찰합니다.
명시적인 CPU 캐시 관리가 필요하지 않습니다.
성능이 크게 향상될 수 있지만 애플리케이션 개발자는 더 많은 동기화 책임을 맡아야 합니다.
리소스 구조(크기, 계층, 형식)는 런타임 컴파일 및 리소스 확인으로 인해 많은 오버헤드가 발생하므로 변경할 수 없지만 리소스의 내용은 변경할 수 있습니다. (아래 사진)

Metal에서 변경할 수 있는 리소스와 변경할 수 없는 리소스의 다이어그램입니다.
데이터 버퍼를 업데이트할 때 CPU는 기존 API처럼 LockXXX 인터페이스를 호출하지 않고 저장 영역에 직접 액세스합니다.
텍스처 데이터 업데이트 시, 빠르고 효율적으로 업로드 경로를 수행하기 위해 전용 저장 영역이 구현됩니다.
GPU의 복사 엔진을 사용하여 하드웨어 가속 파이프라인 업데이트를 구현할 수 있습니다.
동일한 픽셀 크기의 텍스처에 대해 서로 다른 픽셀 형식을 해석하여 다른 텍스처와 스토리지를 공유할 수 있습니다.
예를 들어 sRGB와 RGB, R32와 RGBA8888이 있습니다.
- 텍스처 저장소는 다른 버퍼와 공유될 수 있습니다.
행 선형 픽셀 데이터를 가정합니다.
- 분산된 여러 텍스처 데이터를 동일한 명령 버퍼에 업로드하고 패키징합니다.

13.4.2 파이프라인 상태 개체
D3D11에서 작은 상태 개체가 많으면 GPU 하드웨어 불일치 오버헤드가 발생합니다.

)
D3D12에서는 파이프라인 상태가 단일 개체로 그룹화되고 PSO가 하드웨어 상태에 직접 복사됩니다.

다음은 D3D11과 D3D12의 렌더링 컨텍스트를 비교한 것입니다.


상단: D3D11 장치 컨텍스트; 하단: D3D12 장치 컨텍스트.
파이프라인 상태에는 일반적으로 다음과 같은 개체가 있습니다.
파이프라인 상태 설명
깊이스텐실 DepthStencil 비교 기능 및 쓰기 마스크
샘플러 필터 상태, 주소 지정 모드, LOD 상태
렌더 파이프라인 버텍스 및 픽셀 셰이더 기능, 버텍스 데이터 레이아웃, 다중 샘플 상태, 혼합 상태, 색상 쓰기 마스크...
컴퓨트 셰이더에는 다음 파이프라인 상태가 포함됩니다.
파이프라인 상태 설명
컴퓨팅 상태 컴퓨팅 기능, 작업 그룹 구성
샘플러 필터 상태, 주소 지정 모드, LOD 상태
보다 구체적으로 PSO는 다음과 같은 상태(검은색과 흰색 사각형)를 포함합니다.

컴파일에 영향을 미치는 상태는 객체가 생성된 후에는 변경할 수 없습니다(예: VS, PS, RT, 픽셀 형식, 색상 쓰기 마스크, MSAA, 블렌딩 상태, 깊이 버퍼 상태):

PSO의 디자인 목적은 렌더링 프로세스 중에 암시적인 셰이더 컴파일 및 연결을 갖는 것이 아닙니다. 대부분의 하드웨어 명령(하드웨어 레지스터로 컴파일됨)은 PSO가 생성될 때 이미 생성됩니다. PSO의 셰이더 입력은 바이너리이므로 셰이더 캐시에 매우 친숙합니다. 다음 그림은 렌더링 파이프라인에서 PSO의 상호 작용 다이어그램입니다.

PSO가 루트 서명 및 설명자 테이블과 결합된 후의 작동 메커니즘 다이어그램은 다음과 같습니다.

개발자는 사용 중인 PSO를 동적으로 전환할 수 있습니다. 하드웨어는 실시간으로 하드웨어 상태를 계산하는 대신 사전 계산된 최소 상태를 하드웨어 레지스터에 직접 복사하기만 하면 됩니다. PSO를 사용하면 그리기 호출의 오버헤드가 크게 줄어들고 프레임당 더 많은 그리기 호출을 수행할 수 있습니다. 하지만 개발자는 다음 사항에 주의를 기울여야 합니다.
- PSO는 별도의 스레드에서 생성되어야 합니다. 컴파일에는 수백 밀리초가 걸릴 수 있습니다.
스트리밍 스레드는 PSO도 처리할 수 있습니다.
컬렉션 상태 및 생성.
막힘을 방지합니다.
전문화도 가능합니다.
동일한 스레드에서 유사한 PSO를 컴파일합니다.
예를 들어 혼합 상태는 다르지만 VS와 PS는 동일한 PSO입니다.
상태가 셰이더에 영향을 주지 않으면 셰이더 컴파일이 재사용됩니다.
동일한 셰이더를 동시에 컴파일하는 작업자 스레드는 첫 번째 컴파일 결과를 기다리므로 동일한 셰이더를 동시에 컴파일하는 다른 작업자 스레드의 대기 시간이 줄어듭니다.
중요하지 않은 변수의 경우 동일한 기본값을 사용해 보세요. 예를 들어, 깊이 테스트가 꺼진 경우 다음 데이터는 중요하지 않습니다. 동일한 기본값을 유지하십시오.
int DepthBias;
float DepthBiasClamp;
float SlopeScaledDepthBias;
bool DepthClipEnable;연속적인 Draw Call에서는 PSO 상태가 유사한지 확인하세요. (예를 들어 UE는 PS, VS 등의 키 값에 따라 그리기 명령을 정렬합니다.)
명령 버퍼에 설정된 모든 렌더링 상태는 하나의 호출로 결합됩니다.
조합 폭발을 최소화합니다.
사용하지 않는 순열을 조기에 제거합니다.
적절한 경우 Uber Shader를 고려하십시오.
D3D12에서는 Root에 상수를 넣습니다.
Vulkan에서는 전문화 상수입니다.
PSO를 즉시 구축하는 경우 미리 수행하십시오.
PSO 업데이트가 지연되었습니다.
더 빠르고 일찍 컴파일할수록 결과가 더 좋아집니다.
간단하고 다재다능하며 비용 효율적인 초기 셰이더입니다.
컴파일을 시작하고 더 나은 결과를 얻으세요.
컴파일 결과가 준비되면 PSO를 교체합니다.
일반 목적과 전문화는 특히 유용합니다.
미리 컴파일된 일반적인 예입니다.
특별한 경우에 더 나은 방법은 우선순위가 낮은 스레드에서 컴파일하는 것입니다.
셰이더 및 파이프라인 캐싱을 사용합니다.
애플리케이션은 파이프라인 캐시 개체를 할당하고 관리할 수 있습니다.
파이프라인 생성에 사용되는 파이프 캐시 개체입니다. 파이프라인 상태가 캐시에 이미 존재하는 경우 재사용됩니다.
애플리케이션은 다음에 실행될 때 재사용할 수 있도록 캐시를 디스크에 저장할 수 있습니다.
Vulkan의 장치 UUID를 사용하면 클라우드에 저장할 수도 있습니다.
캐시된 해시 값에 포인터를 사용하지 말고 셰이더 코드를 사용하세요.

- PSO 유사성에 따라 드로우 콜을 정렬합니다.
예를 들어 테셀레이션/GS가 켜져 있는지 여부에 따라 정렬할 수 있습니다.
- 루트 서명을 가능한 한 작게 유지하십시오.
업데이트 모드별로 그룹 설명이 설정됩니다.
- 업데이트 빈도별로 루트 항목을 정렬합니다.
빈도가 가장 높은 변수가 먼저 배치됩니다.
- PSO 및 기타 상태를 저장합니다.
대다수의 픽셀 셰이더에는 해시를 통해 액세스할 수 있는 순열이 몇 개만 있습니다.
- 각 주에 대해 고유한 상태 해시를 만듭니다.
모든 상태 블록을 고유 ID를 가진 풀에 넣습니다.
블록 ID를 비트로 사용하여 상태 해시를 구성합니다.
상태 관리에서 샘플러 상태 개체를 제거합니다.
UE는 16개의 고정 샘플러 상태를 채택합니다.

13.4.3 동기화
13.4.3.1 장벽
최신 그래픽 API는 Fence, Barrier, Semaphore, Event, Atomic 등과 같은 다양한 동기화 방법을 제공합니다.

CPU 배리어 사용 사례. 위: Barrier가 없으면 중복으로 인해 여러 CPU 코어 간의 종속성이 달성되지 않습니다. 하단: Barrier를 통해 Overlap을 해결하여 동기화를 달성합니다.
GPU에는 많은 수의 처리 스레드가 있습니다. Barrier가 없으면 드라이버와 하드웨어는 성능 향상을 위해 이러한 스레드가 중복을 처리하도록 허용하려고 합니다. 그러나 GPU 스레드 간에 종속성이 있는 경우 종속성이 정상인지 확인하기 위해 동기화를 위한 다양한 동기화 개체가 필요합니다. 이러한 동기화 개체의 기능은 다음과 같습니다.
- 동기화
엄격하고 올바른 작업 순서를 보장합니다. 셰이더 웨이브 실행이 겹치는 것을 방지하기 위한 UAV RAW/WAW 장벽과 같은 GPU 파이프라인의 깊이로 인해 종종 발생합니다.
다음과 같은 3개의 Draw Call(DC)이 있다고 가정합니다. 서로 다른 색상은 서로 다른 DC에 속하며 각 DC는 여러 웨이브를 생성합니다.

DC 3이 DC1에 의존한다고 가정합니다. DC1이 완료된 후 장벽이 추가되면 DC2는 실제로 중복하여 대기합니다.

DC2가 완료된 후 장벽이 추가되면 DC2에는 여전히 중복 대기가 발생합니다.

DC3과 DC2가 DC1이 써야 하는 서로 다른 리소스에 의존한다고 가정할 때 DC1-2와 DC1-3 사이에 장벽이 추가되면 더 많은 중복 대기가 도입됩니다.

원래 두 개의 장벽을 하나로 병합할 수 있습니다. 현재 동기화 지점은 하나만 있지만 여전히 약간의 중복 대기가 발생합니다.

이때 DC1과 DC3 사이의 Barrier는 분리될 수 있습니다. DC1 이후에는 "Done"으로 설정되고, DC3 이전에는 "Make Ready"로 설정됩니다. 이때 DC2는 DC1의 영향을 받지 않습니다. DC3만 DC1을 기다려야 합니다. 이러한 중복 대기는 크게 줄어듭니다.

따라서 Split Barrier는 동기화 대기 시간을 줄일 수 있습니다(위 예의 DC2와 같이 마지막 사용 종료와 새 사용 시작 사이에 다른 작업이 있는 경우). 여러 동시 장벽은 동기화를 줄이고 한 번에 여러 장벽을 제거하려고 시도할 수도 있습니다.
장벽이 손실되면 데이터 타이밍 문제가 발생합니다.
- 가시성
이전에 기록된 데이터가 대상 장치에 표시되는지 확인하십시오.
가시성은 여러 개의 소형 L1 캐시 및 대형 L2 캐시(주로 셰이더 코어에 연결됨)와 같은 GPU 내부의 여러 구성 요소와 관련됩니다. (아래 사진)

설명하기 위해 구체적인 예를 들어보세요. 버퍼 UAV를 SHADER_RESOURCE로 변환하려면 | CONSTANT_BUFFER 태그를 사용하면 텍스처 L1~L2가 새로 고쳐지고 셰이더 L1도 새로 고쳐집니다.

RENDER_TARGET을 COMMON 태그로 바꾸려면 다음과 같은 많은 작업이 필요합니다.
컬러 L1을 새로 고칩니다.
가능한 모든 L1을 새로 고칩니다.
L2를 새로 고칩니다.

이 작업은 매우 비용이 많이 들고 더 많은 시간과 메모리 대역폭을 차지합니다. 이 작업을 피하십시오. 또한 다음 제안 사항을 통해 소비를 줄일 수 있습니다.
여러 장벽을 단일 호출로 결합합니다. 여러 캐시의 새로 고침을 결합하여 중복 새로 고침을 줄입니다.
RT->COMMON을 커버하기 위해 추가 RT->SRV를 추가하는 등 이전 리소스 상태를 고려하지만 오버헤드는 없습니다!
스플릿 배리어(Split Barrier)도 가시성에 맞춰 조정됩니다. 이는 또한 장벽을 관찰하고 제거하는 데 추가 노력을 기울이는 것을 의미합니다.
형식 변환
데이터 형식이 압축 해제에 가장 일반적으로 사용되는 대상 단위와 호환되는지 확인하십시오.
많은 GPU 하드웨어는 대역폭을 절약하기 위해 DCC(델타 색상 압축), UBWC, AFBC 등과 같은 무손실 압축을 지원합니다. 그러나 이러한 압축된 데이터를 읽을 때 압축 해제가 발생할 수 있으며, UAV 쓰기도 압축 해제를 유발합니다.

NV Pascal 메모리 압축 범례.

NV의 다단계 캐스케이드 데이터 압축 기술. RLE, Delta, Bit-packing 및 기타 기술과 결합됩니다.
RT 및 DS 표면은 압축 시 더 나은 성능을 발휘하며 2배 이상 빠른 성능을 달성할 수 있습니다.
최신 하드웨어에는 전체 압축과 부분 압축이라는 두 가지 압축 방법이 있습니다. RT 또는 DS 콘텐츠를 읽으려면 전체 압축을 풀어야 하며, 파트는 SRV에도 사용할 수 있습니다.
압축을 풀어야 한다면 어딘가에서 성능 저하를 겪어야 합니다. 감압이 필요한 상황을 피하십시오.
장벽이 사라지면 예상치 못한 데이터 손상이 발생합니다.
Barrier의 GPU 소비는 종종 타임스탬프로 측정됩니다. 감압이 필요하지 않은 장벽은 일반적으로 미크론(μs) 수준의 시간만 필요합니다. MSAA 데이터가 포함된 표면의 압축을 풀어야 하는 경우가 아니면 백분율 수준의 소비가 필요한 경우는 거의 없습니다. 쓰기 가능한 표면당 장벽은 2개 이하여야 합니다.
프레임당 표면 쓰기가 큰 문제입니다. 표면에 쓰면 장벽 손실로 인해 데이터가 손상될 수 있습니다. 프레임당 표면당 장벽 2개를 초과하지 마십시오.
동기화에 대한 부정적인 사용 사례는 다음과 같습니다.
- RT->SRV->Copy_source->SRV->RT.
여러 개의 태그를 OR로 연결하여 결합할 수 있다는 점을 잊지 마세요.
읽기-읽기(SRV -> Copy_source, Copy_source -> SRV) 장벽이 없습니다.
리소스의 시작 상태가 올바른 상태로 설정되어야 합니다.
가끔 리소스를 복사하지만 항상 RT->SRV|Copy를 실행합니다.
RT -> SR은 오버헤드가 낮을 수 있지만 RT -> SRV|Copy는 오버헤드가 높을 수 있습니다.
리소스의 시작 상태가 올바른 상태로 설정되어야 합니다.
리소스의 다음 상태가 무엇인지 모르기 때문에 항상 명령 목록 끝에서 모든 리소스를 COMMON으로 변환합니다.
이를 수행하는 데 드는 비용은 엄청납니다! 모든 표면에 강제 감압이 발생합니다! 대부분의 명령 목록은 시작하기 전에 유휴 시간을 기다려야 합니다.
- 사용 중이거나 내부 루프에 있는 장벽만 고려됩니다.
차단 장벽 합병.
- 부정적 장벽 사용 사례 1:
void UploadTextures()
{
for(auto resource : resources)
{
pD3D12CmdList->Barrier(resource, Copy);
pD3D12CmdList->CopyTexture(src, dest);
pD3D12CmdList->Barrier(resource, SR);
}
}다음과 같이 변경되어야 합니다:
void UploadTextures()
{
BarrierList list;
// 텍스처(Texture)개 Barrier호출(Call)。
for(auto resource : resources)
AddBarrier(list, resource, Copy)
pD3D12CmdList->Barrier(list);
list->clear();
// 텍스처(Texture)。
for(auto resource : resources)
pD3D12CmdList-> CopyTexture(src, dest);
// 개 의 Barrier처리(Process)리소스 변환(Transform/Convert)。
for(auto resource : resources)
AddBarrier(list, resource, SR)
pD3D12CmdList->Barrier(list);
}- 부정적 장벽 사용 사례 2:
for (auto& stage : stages) {
for (auto& resource : resources) {
if (resource.state & STATE_READ == 0) {
ResourceBarrier (1, &resource.Barrier (STATE_READ));
}
}
}이상적인 그리기 순서는 다음과 같습니다.

그러나 위의 코드는 재료 및 단계별로 장벽을 추가하여 이상적인 실행 순서를 방해하고 수많은 연속 유휴 대기를 생성합니다.

일부 도구(RGP, PIX)는 Barrier에 대한 자세한 정보를 표시하거나 경고를 표시합니다.

그래픽 API의 Flush 명령은 동기화를 달성할 수 있지만 셰이더 코어가 겹치지 않도록 GPU의 대기열이 강제로 실행되어 유휴 상태가 발생하고 활용도가 감소한다는 점에 유의해야 합니다.

DirectX 12 및 Vulkan의 Barrier는 그래픽 API의 Flush와 동일하며 이는 D3D12_RESOURCE_UAV_BARRIER와 동일합니다. 그리기/디스패치 사이의 전환/파이프라인 장벽을 위한 스레드 플러시를 추가하고 장벽 간의 비종속 그리기/디스패치를 그룹화해 보세요. (결론 중 이 부분은 향후 GPU에서는 적용되지 않을 수 있습니다.)
스레드는 메모리에 액세스할 때 지연을 일으키고 캐시 새로 고침으로 인해 유휴 상태가 발생합니다. 제한된 셰이더가 사용하는 작업에는 깊이 래스터라이제이션, 온칩 테셀레이션, GS 및 DMA(직접 메모리 액세스)만 포함됩니다. 지연과 유휴 상태를 줄이기 위해 CPU 측에는 여러 프런트엔드(프런트엔드), 동시 멀티스레딩(하이퍼스레딩) 및 실행 리소스를 공유하는 두 명령 스트림의 인터리빙이 필요합니다.
즉, GPU 장벽은 GPU 스레드 동기화, 캐시 새로 고침, 데이터 변환(압축 해제)을 포함하고 가시성과 종속성을 설명합니다.
Barrier가 성능 저하의 원인이 되는 것을 방지하려면 다음 Barrier 사용 규칙 및 권장 사항을 따라야 합니다.
- 장벽을 최대한 결합하십시오.
최소한의 사용 플래그 세트를 사용하십시오. 과도한 플러시를 피하십시오.
읽기-읽기 장벽을 피하세요. 모든 후속 읽기에 대해 올바른 상태의 리소스를 얻습니다.
가능하면 분할 장벽을 사용하십시오.

배리어 조인트 배치 사례 1. 위: 배치되지 않은 배리어로 인해 GPU 유휴 시간이 더 길어집니다. 하단: 일괄 처리 후 장벽은 GPU 작업을 더욱 컴팩트하게 만들고 유휴 시간을 줄입니다.

배리어 조인트 배치 사례 2. 위: 배치되지 않은 배리어로 인해 GPU 유휴 상태가 더 커집니다. 하단: 배치 후 배리어는 GPU 작업을 더욱 컴팩트하게 만들고 유휴 시간을 줄입니다.

배리어 조인트 배치 사례 3. 위: 이전 리소스의 장벽을 탐색하여 서로 다른 시점의 장벽을 검색합니다. 중간: 이러한 장벽의 공통 시점을 찾습니다. 하단: 후속 장벽을 동일한 시점으로 마이그레이션하고 일괄 통합을 수행합니다.
COPY_SOURCE는 SHADER_RESOURCE보다 훨씬 비쌀 수 있습니다.
장벽의 수는 쓰여지는 표면 수의 약 2배여야 합니다.
Barrier는 GPU 활용도를 줄이고, 디스패치가 클수록 활용도가 높아지며, 스레드 실행 시간이 길어지면 Flush 소비가 높아집니다.
리소스를 쓰려면 마지막 Queue에 Barrier를 삽입하는 것이 가장 좋습니다.
세마포어(세마포어) 근처에 전환을 배치합니다.
원본/대상 대기열을 명시적으로 지정해야 합니다.
아직 렌더 패스를 사용할 수 없는 경우 작업 경계의 배칭 장벽, 렌더 패스가 대부분의 장벽 문제에 대한 최상의 솔루션입니다.

- 독립적인 업무가 중첩될 수 있도록 장벽을 이동하세요.

상단: 장벽은 두 개의 독립적인 작업 사이에 배치되어 중간에 유휴 시간이 많이 발생합니다. 하단: 두 작업이 잘 겹칠 수 있도록 Barrier를 두 작업의 끝으로 이동하여 유휴 상태를 줄이고 전체 실행 시간을 줄입니다.
- 각 리소스의 상태를 추적하지 마세요.
변환할 리소스가 그리 많지 않습니다!
상태 추적으로 인해 일괄 처리가 어려워집니다.
안전하지 않습니다.
장벽은 비용이 많이 들기 때문에 모든 것을 변환하지 마십시오!
비용은 일반적으로 해상도에 따라 다릅니다.
소비 비용은 GPU 세대에 따라 다릅니다.
가능한 한 장애물이 적습니다. 모든 리소스 상태를 추적하지 마세요.
가능하면 렌더 패스를 사용하는 것이 좋습니다.
원하는 상태를 명확히 하세요.
장벽을 병합하려면 유니온 비트를 사용하세요.

- 운전자가 리소스 변환을 처리하고 분할 장벽을 사용하는 등의 시간을 허용합니다.


Split Barrier는 케이스를 자동으로 생성합니다. 위: 생산자 경계의 장벽; 하단: Depth는 나중에 읽을 예정이므로 쓰기가 종료되고 읽기 상태로 전환됩니다.
장벽 구현 옵션에는 다음이 포함됩니다.
- 손으로 배치.
간단한 엔진에 매우 친숙합니다.
하지만 금방 복잡해집니다.
뒤에서 자동으로 생성됩니다.
자원별 추적.
정확하기는 어렵습니다.
주문형 전환으로 인해 일괄 처리가 부족할 수 있으며 종종 이상적이지 않은 장소에 장벽이 생성될 수 있습니다.
D3D12에서 렌더 패스를 사용하여 시뮬레이션되었습니다.
더 나은 휴대성.
- 프레임 그래프.
각 패스를 분석하고 종속성을 찾아보세요.
그런 다음 각 리소스에 대한 메모리 중복(앨리어싱) 정도를 결정할 수 있습니다.
예를 들어 Frostbite의 프레임 그래프, UE의 RDG 등이 있습니다.
모든 리소스 변환은 메인 렌더링 스레드에 의해 제출됩니다. 기본 렌더링 스레드는 명령 목록을 기록하고 모든 다중 스레드 동기화를 수행할 수도 있습니다.

Ubisoft의 Anvil Next 엔진은 정밀하고 자동화된 리소스 추적 및 종속성 관리를 구현하고 리소스 수명을 자동으로 추적하여 메모리 재사용 옵션(배치된 리소스에 대한)을 결정하고 리소스 액세스 동기화를 자동으로 추적하며 사용자는 워크로드에 더 잘 일치하도록 수동 동기화를 추가할 수 있습니다. (아래 사진)

13.4.3.2 울타리
울타리는 GPU의 세마포어입니다. 사용 사례는 퇴거 전에 GPU가 리소스 처리를 완료하는지 확인하는 것입니다.
프레임별 울타리를 사용하여 프레임별 리소스를 보호할 수 있습니다. 가능한 한 많은 리소스를 포함하려면 단일 울타리를 사용하십시오.
펜스 작업은 명령 목록이나 번들이 아닌 명령 대기열에서 수행됩니다.
울타리당 CPU 및 GPU 비용은 ExecuteCommandLists와 거의 같습니다. Fence가 ExecuteCommandList별로 ExecuteCommandList를 호출하는 것보다 더 세밀하게 신호를 트리거할 것이라고 기대하지 마세요.
Fence에는 암시적 획득/해제 장벽이 포함되어 있으며, 이는 Fence의 오버헤드가 높은 이유 중 하나입니다.
Fence를 사용하여 리소스를 세분화하여 재사용해 보세요. 이상적인 상황은 결국 SignalFence를 사용하여 모든 리소스 재사용을 동기화하는 것입니다.
다음은 DX12에서 Barrier와 Fence를 사용하기 위한 샘플 코드입니다.
// ------ Barrier ------
// 그림자(Shadow)로부터 상태 까지 뎁스 상태 ,로써 씬 뎁스(SceneDepth)렌더링(Render) 내에서
pCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(pShadowTexture,
D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_DEPTH_WRITE));
// 그림자(Shadow)로써 픽셀(Pixel)셰이딩 의 Shader Resource 을(를) 활용하여 ,렌더링(Render),그림자(Shadow)샘플링
pCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(pShadowTexture,
D3D12_RESOURCE_STATE_DEPTH_WRITE, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE));
// 그림자(Shadow)복구 까지 상태
pCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(pShadowTexture,
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE, D3D12_RESOURCE_STATE_COMMON));
// ------ Fence ------
// 생성(Create)개 Fence, 내에서 fenceValue로
ComPtr<ID3D12Fence> pFence;
pDevice->CreateFence(fenceValue, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&pFence)));
// Fence신호(Signal)。
pCommandQueue->Signal(pFence.Get(), fenceValue);
// Fence1:에 의해 CPUFence의 (),만약 fenceValue,이면 호출(Call)DoOtherWork
if (pFence->GetCompletedValue() < fenceValue)
{
DoOtherWork();
}
// Fence2:Fence의 구현 CPU 및 GPU동기화
if (pFence->GetCompletedValue() < fenceValue)
{
pFence->SetEventOnCompletion(fenceValue, hEvent);
WaitForSingleObject(hEvent, INFINITE);
}펜스와 세마포어는 모든 GPU 실행과 메모리 액세스를 동기화하므로 때로는 아무것도 기다리지 않거나 차단해도 괜찮습니다.
CPU 및 GPU 동기화 모델은 다음과 같은 방법을 고려할 수 있습니다.
- 실행 후 잊어버리세요.
작업이 시작되면 펜스를 통해 동기화가 이루어집니다. 그러나 일부 워크로드는 프레임마다 다르기 때문에 전체 프레임 성능에 영향을 미치는 예기치 않은 작업 쌍이 발생합니다.

- 같은 상황에서 애플리케이션은 ECL 사이에 CPU 지연 시간을 도입하고 CPU 지연 시간이 GPU로 전송되어 예상치 못한 작업 페어링 등이 발생합니다.

- 악수.
페어링 결정성을 보장하기 위해 작업 페어링의 시작과 끝을 동기화합니다. 일부 비동기 기회가 누락될 수 있습니다(HW 관리 가능).

동시에 CPU가 ECL(ExecuteCommandLists)을 통해 GPU를 스케줄링할 수 있다는 점도 주목해야 합니다. 이는 CPU의 공백이 GPU로 전송된다는 의미입니다.
13.4.3.3 파이프라인 장벽
Pipeline Barrier는 Vulkan에서 명령 간의 실행 종속성(Execution 종속성) 문제와 메모리 종속성(Memory 종속성) 문제를 해결하기 위해 사용됩니다.
대부분의 Vulkan 명령은 대기열 제출 순서대로 시작되지만 동일한 파이프라인 단계가 사용되더라도 순서에 관계없이 실행될 수 있습니다.
두 명령이 서로 의존하는 경우 Vulkan에는 두 가지 동기화 범위를 알려야 합니다.
srcStageMask: Barrier 이전에 일어나는 일입니다.
dstStageMask: Barrier 이후에 발생하는 일입니다.
메모리 데이터에 대한 종속성이 있는 경우 Vulkan에는 두 가지 액세스 범위를 알려야 합니다.
srcAccessMask: Barrier 이전에 발생하는 명령 메모리 액세스입니다. Barrier가 수행하는 모든 캐시 정리(또는 플러시)는 여기에서만 발생합니다.
dstAccessMask: Barrier 이후에 발생하는 명령 메모리 액세스입니다. Barrier가 수행한 캐시 무효화는 여기에서만 발생합니다.
구체적인 예는 다음과 같습니다.
vkCmdCopyBuffer(cb, buffer_a, buffer_b, 1, ®ion); // buffer_a는
vkCmdCopyBuffer(cb, buffer_c, buffer_a, 1, ®ion); // buffer_a는위 코드는 파이프라인 장벽을 사용하지 않으며 WAR(읽은 후 쓰기) 충돌을 유발합니다. 충돌을 방지하기 위해 파이프라인 장벽을 추가할 수 있습니다.
vkCmdCopyBuffer(cb, buffer_a, buffer_b, 1, ®ion);
// 생성(Create)VkBufferMemoryBarrier
auto buffer_barrier = lvl_init_struct<VkBufferMemoryBarrier>();
buffer_barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
buffer_barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
buffer_barrier.buffer = buffer_a;
// 추가(Add)VkBufferMemoryBarrier
vkCmdPipelineBarrier(cb, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 1, &buffer_barrier, 0,nullptr);
// 데이터 。
vkCmdCopyBuffer(cb, buffer_c, buffer_a, 1, ®ion);파이프라인 단계 비트는 다음과 같이 주문됩니다.
Vulkan 사양에 정의된 논리적 순서입니다.
srcStageMask에서 각 스테이지 비트는 모든 이전 스테이지를 기다려야 합니다.
dstStageMask에서 각 단계 비트는 모든 이후 단계를 차단해야 합니다.

상위: 파이프라인 단계 종속성 비트가 제대로 설정되지 않아 병렬 처리 속도가 감소합니다. 하단: 파이프라인 단계 종속성 비트가 잘 설정되어 있어 병렬 효율성이 향상되고 전체 실행 시간이 단축됩니다.

위 그림의 Vertex_Shader 단계는 모든 회색 단계를 기다리며 모든 녹색 단계도 차단합니다.
- 일반적으로 동기화되는 비트만 설정하면 됩니다.
메모리 액세스 마스크 비트는 독립적입니다.
동기화되는 모든 비트를 설정해야 합니다.
단, 필수 액세스 마스크를 사용하려면 각 파이프라인 단계를 명시적으로 지정해야 합니다. (이것은 일반적인 오류 원인입니다.)
다음 명령 대기열을 가정합니다.
Command A
Barrier1
Command B
Barrier2
Command CA, B, C가 순서대로 실행되기 위해서는 Barrier1.dstMask가 Barrier2.srcMask와 같거나 이전인지 확인해야 합니다. 다음 표는 다양한 상황에 대한 종속성을 보여줍니다.
Barrier1.dst마스크 Barrier2.src마스크 의존성 체인?
DRAW_INDIRECT DRAW_INDIRECT 예
DRAW_INDIRECT COMPUTE_SHADER 아니요
COMPUTE_SHADER DRAW_INDIRECT 예
BOTTOM_OF_PIPE 또는 ALL_COMMANDS DRAW_INDIRECT 예(느릴 수 있음)
다음은 특수 실행 종속성에 대한 설명입니다.
srcStageMask = ALL_COMMANDS: 모든 단계를 차단하고 기다리며 GPU가 유휴 상태가 될 때까지 강제로 기다리게 되어 일반적으로 성능이 저하됩니다.
srcStageMask = NONE 또는 TOP_OF_PIPE: 아무것도 기다리지 않으며 이전 장벽의 dstStageMask = ALL_COMMANDS 플래그를 사용하여 실행 종속성 체인만 빌드할 수 있습니다.
dstStageMask = NONE 또는 BOTTOM_OF_PIPE: 이 장벽을 기다리는 것이 없습니다. srcStageMask = ALL_COMMANDS를 사용하여 실행 종속성 체인을 구축하세요.
다음은 특수 메모리 액세스 마스크에 대한 설명입니다.
NONE: 실행 장벽을 정의하는 데 사용되는 메모리 액세스가 없습니다.
MEMORY_READ, MEMORY_WRITE: StageMask에서 허용되는 모든 메모리 액세스.
SHADER_READ: sync2에서 (SAMPLER_READ | STORAGE_READ | UNIFORM_READ)로 확장됩니다.
SHADER_WRITE: sync2에서 STORAGE_WRITE(2^32보다 큼)로 확장되었습니다.
파이프라인 장벽에 대한 자세한 지침은 12.4.13 서브패스를 참조하세요.
13.4.4 병렬 명령 기록
최신 그래픽 API가 출현하기 전에는 여러 스레드에서 렌더링 명령을 병렬로 기록할 수 없었기 때문에 렌더링 스레드가 위치한 CPU 코어는 매우 바쁜 반면 다른 코어는 유휴 상태였습니다.

Vulkan과 같은 최신 그래픽 API는 스레드 안전성과 호출 결과를 자세히 설명하는 광범위한 사양을 통해 처음부터 스레드 친화적으로 만들어졌으며 모든 제어와 책임은 애플리케이션에 있습니다.
최신 CPU 코어 수가 증가함에 따라 애플리케이션에서는 멀티스레드 처리 및 렌더링에 대한 수요가 점점 더 커지고 있습니다. 가장 확실한 것은 여러 스레드에서 렌더링 작업을 생성하고 여러 스레드 간의 검증 및 제출 비용을 상각할 수 있다는 것입니다. 구체적인 사용 사례는 다음과 같습니다.
- 스레드 리소스 업데이트.
CPU 버텍스 데이터 또는 인스턴스화된 데이터 애니메이션(예: 변형 애니메이션)
CPU 통합 버퍼 데이터 업데이트. (예: 변경 매트릭스 업데이트)
병렬 렌더링 상태 생성.
셰이더 컴파일 및 상태 검증.
- 스레드 렌더링 및 그리기 호출.
여러 스레드에서 명령 버퍼를 생성합니다.
Vulkan은 독립적인 작업 설명과 커밋을 지원합니다.

Vulkan 리소스, 명령, 그리기, 제출 등 간의 관계에 대한 개략도. 작업 사양에는 바인딩 파이프라인 상태, 버텍스 및 인덱스 버퍼, 설명자 세트 및 그리기 지침이 포함됩니다. 관련된 리소스에는 명령 버퍼, 도면 상태 및 리소스 참조가 포함됩니다. 리소스 참조는 설명자를 통해 리소스의 실제 위치를 지정합니다. 작업 사양은 vkQueueSubmit을 통해 제출되며, 제출 시 정확한 동기화 작업을 지정할 수 있습니다. 대기열은 최종적으로 GPU 내부에서 실행됩니다.
최신 그래픽 API의 명령 버퍼의 경우 모든 렌더링은 한 번의 사용으로 여러 번 제출할 수 있는 명령 버퍼를 통해 수행됩니다. 드라이버는 이에 따라 버퍼를 최적화할 수 있습니다. 기본 및 보조 명령 버퍼가 있어 정적 작업을 재사용할 수 있습니다. 게다가 명령 버퍼 전체에 걸쳐 상태가 상속되지 않습니다!

Vulkan 멀티코어는 명령 버퍼 다이어그램을 병렬로 생성합니다.

Vulkan 병렬 패스 호출 및 범례.
Vulkan의 명령 버퍼를 재사용하려는 경우 애플리케이션은 Fence 등을 사용하여 스레드 안전을 보장하기 위해 재사용된 명령 버퍼가 사용되지 않도록 할 수 있습니다.

또한 Metal을 사용하면 애플리케이션이 많은 경량 명령 버퍼를 명시적으로 구성하고 제출할 수 있습니다. 이러한 버퍼는 여러 스레드에 병렬로 기록될 수 있으며(아래 이미지) 실행 순서는 애플리케이션에서 지정할 수 있습니다. 이 접근 방식은 매우 효율적이며 확장 가능한 실행 성능을 보장합니다.

금속 병렬 녹음 명령 버퍼 다이어그램.

메탈 패럴렐 패스 콜 및 레전드.
Vulkan 및 Metal과 유사하게 DirectX 12에는 다중 스레드 녹음 렌더링 명령 메커니즘도 있습니다.

DX12 멀티스레드 녹화 모델. 참고로 그림의 Bundle A는 두 번 실행됩니다.
병렬로 생성 및 재사용되는 명령 버퍼 외에도 명령 할당자(풀)는 여러 스레드에 의해 병렬로 생성될 수 있으며, 다른 스레드의 명령 버퍼는 다른 명령 할당자(풀) 인스턴스에 의해 생성되어야 합니다(그렇지 않으면 추가 동기화 작업이 필요함).

따라서 좋은 설계 방식에서는 각 스레드에 여러 개의 명령 버퍼가 있어야 하며 스레드에는 더 이상 사용되지 않는 명령 할당자(풀)를 빠르게 재설정하고 재사용하기 위해 프레임당 여러 개의 독립 버퍼가 있을 수 있습니다.

여러 명령 대기열을 사용하여 그리기 지침을 제출하는 것은 GPU에서 병렬로 실행될 수 있지만 CPU 스레드와 마찬가지로 OS 일정, 드라이버 계층, GPU 아키텍처 및 상태, 대기열 및 명령 목록 유형에 따라 달라집니다.

GPU 코어 활용도를 향상시키는 여러 명령 대기열의 개략도.
또한 D3D12의 명령 대기열은 하드웨어 대기열과 동일하지 않다는 점을 지적해야 합니다. 하드웨어 대기열은 여러 개 있을 수도 있고 하나만 있을 수도 있습니다. 운영 체제/스케줄러는 병렬 제출을 평면화하고 Fence를 사용하여 스케줄러에 종속성을 표시합니다. 구체적인 세부 정보는 GPUView/PIX/RGP/Nsight와 같은 도구를 통해 볼 수 있습니다!
Vulkan의 대기열은 매우 다릅니다. 공개 대기열에 명시적으로 바인딩되어 있지만 여전히 하드웨어 대기열이라는 보장은 없습니다. Vulkan의 대기열 제품군은 D3D12 엔진과 유사합니다.
멀티코어 CPU는 병렬 작업 및 캐시 일관성 문제에 직면해 있습니다. GPU의 경우에도 마찬가지입니다. 명령 프로세서는 작업 스케줄러와 동일하고 셰이더 코어는 작업자 코어와 동일합니다.
다른 명령 대기열이 제출될 때 제출과 렌더링 사이의 유휴 시간 없이 새로운 명령 대기열을 병렬로 구축할 수 있습니다. 명령 목록은 재사용할 수 있지만 동시 사용 중지는 애플리케이션이 담당합니다.
작업을 너무 많은 명령 대기열로 분할하지 마십시오. 15~30개의 명령 대기열 및 5~10개의 ExecuteCommandLists 호출과 같이 각 프레임에 대해 합리적인 수의 작업을 계획할 수 있습니다.
각 ExecuteCommandLists에는 고정된 CPU 오버헤드가 있으므로 이 호출 후에 새로 고침이 트리거되고 명령 대기열이 결합되어 호출 수를 줄입니다. GPU가 ExecuteCommandList당 200μs(바람직하게는 500μs) 동안 실행되도록 허용해 보십시오. 충분한 작업을 제출하면 OS 스케줄러(스케줄러)의 대기 시간을 숨길 수 있습니다. 적은 양의 작업에 대한 ExecuteCommandLists의 실행 시간이 새 작업을 제출하는 OS 스케줄러보다 빠르기 때문입니다.

소수의 명령 대기열 제출로 인해 유휴 사례가 많이 발생했습니다.
번들은 프레임 간에 작업을 더 일찍 커밋할 수 있는 좋은 방법입니다. 그러나 GPU에서는 번들이 본질적으로 더 빠르지 않으므로 주의해서 다루십시오. 호출 명령 목록에서 상태 상속을 최대한 활용하면(그러나 상속된 상태를 조정하려면 CPU 또는 GPU 비용이 필요할 수 있음) CPU 효율성을 상당히 향상시킬 수 있습니다. NV의 경우 각 Dispatch에 동일한 도면이 5개 이상 있으면 Bundle을 사용하세요. AMD는 CPU 측에 병목 현상이 발생하는 경우에만 번들을 사용할 것을 권장합니다.
13.4.5 다중 대기열
최신 그래픽 API는 복사 대기열, 계산 대기열, 그래픽 대기열이라는 세 가지 유형의 대기열을 지원합니다. 그래픽 큐는 컴퓨팅 큐를 구동할 수 있고, 컴퓨팅 큐는 복사 큐를 구동할 수 있습니다. (아래 사진)

Copy Queue는 일반적으로 데이터를 복사하는 데 사용됩니다. 이는 PCIe 데이터 전송(하드웨어 지원 최적화 포함)에 매우 적합하며 셰이더 리소스를 차지하지 않습니다. CPU와 GPU 간에 텍스처와 데이터를 전송하고, Mimap 생성을 가속화하고, 상수 버퍼를 채우는 데 일반적으로 사용됩니다. 비동기 데이터 복사 및 전송을 활성화하고 그래픽 및 Compute Engine과 병렬로 실행합니다.
컴퓨팅 큐는 일반적으로 로컬 간 리소스(즉, GPU 메모리 내)에 사용되며 그래픽 큐와 비동기적으로 실행되는 컴퓨팅 작업에도 사용할 수 있습니다. 복사 엔진을 구동할 수 있습니다. 컴퓨트 셰이더에는 다음 파이프라인 상태가 포함됩니다.
파이프라인 상태 설명
컴퓨팅 상태 컴퓨팅 기능, 작업 그룹 구성
샘플러 필터 상태, 주소 지정 모드, LOD 상태
그래픽 대기열은 모든 작업을 수행할 수 있으며 드로잉은 일반적으로 가장 큰 작업량입니다. Compute Engine 및 Copy Engine을 구동할 수 있습니다.
하드웨어 수준에서 GPU에는 Copy Engine, Compute Engine 및 3D Engine의 세 가지 엔진이 있습니다. 또한 병렬로 실행하고 울타리, 신호 또는 장벽을 통해 대기 및 동기화할 수도 있습니다.

DirectX12의 CPU 스레드, 명령 목록, 명령 대기열 및 GPU 엔진 간의 작동 메커니즘에 대한 개략도.
기록 단계에서는 대기열 유형을 지정해야 합니다. 동일한 유형은 여러 대기열을 지원합니다. 동일한 대기열 내에서 작업은 순서대로 실행되지만 하드웨어 엔진에서 다른 대기열이 중단될 수 있습니다.

Async Quque의 병렬 기능을 활용하면 렌더링 효율성이 더욱 향상될 수 있습니다. 병렬 아이디어는 서로 다른 병목 현상이 있는 작업 부하를 함께 배열하는 것입니다. 예를 들어 섀도우 맵 렌더링은 일반적으로 지오메트리 처리량에 의해 제한되는 반면, 컴퓨트 셰이더는 일반적으로 데이터 수집에 의해 제한되며(LDS는 메모리 획득 효율성을 최적화하는 데 사용될 수 있음) ALU에 의해 거의 제한되지 않습니다.
그러나 부적절하게 사용할 경우 Async Compute는 Graphics Queue의 성능에 영향을 미칠 수 있습니다. 예를 들어 Lighting과 CS를 함께 배치하면 ALU에 대한 동시 경쟁이 발생합니다. 파이프라인의 병렬 상태를 모니터링하고, 병렬 병목 현상을 식별하고, 이를 최적화하는 방법을 찾으려면 항상 프로파일러 도구를 사용해야 합니다.
렌더링 엔진의 경우 장벽을 효과적으로 처리할 수 있고 사용자가 병렬화할 수 있는 작업을 수동으로 지정할 수 있도록 하는 작업 기반 렌더러(예: UE의 TaskGraph 및 RDG)를 구축하는 것이 가장 좋습니다. 작업은 너무 작아서는 안 되며 프레임당 펜스 수는 한 자리 수 범위로 유지되어야 합니다. 각 신호는 프런트엔드를 정지시키고 파이프라인을 플러시하기 때문입니다.
다음 그림은 프레임 렌더링의 다양한 단계에 소요되는 시간의 예입니다.

그 중 Lighting, Post Process 및 대부분의 그림자 관련 작업은 Compute Shader에 배치할 수 있습니다. 또한 프레임의 후처리가 동일한 프레임의 이전 부분(자르기, 그림자, 조명 등)을 기다리는 것을 방지하기 위해 다음 프레임의 이전 단계와 병렬로 컴퓨팅 대기열에 배치할 수 있습니다.

최신 그래픽 API를 사용하면 렌더링 엔진이 프레임 간의 중첩을 쉽게 구현할 수 있습니다. 기본 아이디어는 다음과 같습니다.
대기열 가능한 프레임 수를 2 대신 3으로 설정합니다.
그래픽 대기열과 별도의 렌더링 대기열을 만듭니다.
렌더링이 끝나면 즉시 렌더링하지 않고 계산 작업을 발행하고 프레임 포스트 작업을 렌더러로 보냅니다.
프레임의 사후 작업이 완료되면 실제 렌더링을 위해 특수 그래픽 대기열에 신호를 보냅니다. (아래 사진)

그러나 이 접근 방식에는 몇 가지 단점이 있습니다.
구현이 복잡하고 다양한 동기화 및 대기가 발생합니다.
프레임은 여러 제출물로 분할됩니다. (명령 버퍼를 1-2ms 범위 내로 유지하십시오)
추가로 1/2~1/3 지연이 발생합니다.
Async Compute를 도입한 후 성능은 일반적으로 약 15% 향상될 수 있습니다.

작업 그룹 최적화를 위해 PS에서 CS로 마이그레이션하기 위한 기존 권장 사항은 다음과 같습니다.
- 작업 그룹 크기(8, 8, 1)를 사용하여 PS를 CS로 마이그레이션합니다.
공간적 위치성을 얻기 위한 1파동/V$(그러나 PS보다 더 나쁠 수도 있음).
AMD의 GCN은 다음 CU로 이동하기 전에 CU별로(1 V$/CU) 하나의 작업 그룹을 실행합니다.
스레드(레인, 스레드)를 8x8로 매핑하는 것은 선형 블록입니다.
실제로는 (4x1) 모드 텍스처 획득 사각형(쿼드)일 수 있습니다.
실제로 V$ 은행 충돌이 발생할 수 있습니다.
4개 스레드 그룹의 GCN 샘플.
위의 내용은 잘못된 구성입니다. 좋은 작업 그룹 구성 예는 다음과 같습니다.
- (512, 1, 1)의 작업 그룹은 (32, 16, 1)로 구성됩니다.
8 wave/V$가 지역성을 획득합니다.
각 웨이브는 8x8 타일입니다. (GPU 제조사와 GPU 시리즈마다 차이가 있습니다. 여기서는 AMD의 GCN 아키텍처를 참고로 합니다.)
8개의 웨이브가 4x2 8x8타일 모음으로 구성됩니다. (아래 사진)

- 스레드-8x8 타일 매핑은 스위즐 블록 선형입니다.
2x2 모드에 적합한 텍스처 가져오기 블록입니다. (위 사진)
- 전용 셰이더 최적화.
캐시 적중을 얻기 위해 2D 공간의 지역성에 크게 의존합니다.
웨이브 실행 중 종속성이 줄어듭니다.
로컬 메모리를 사용하는 일반적인 기술은 입력을 청크로 분할한 다음 작업 그룹에서 각 청크를 처리할 때 로컬 메모리로 이동할 수 있는 것입니다.

다음은 PS 및 CS에 대한 NV 및 AMD의 성능 설명 및 권장 사항입니다.

PS를 사용하는 NV에 대한 권장 사항: 공유 메모리가 필요하지 않음, 스레드가 동시에 완료됨, 고주파수 CB 액세스, 2D 버퍼 저장; CS 사용에 대한 NV 권장 사항: 메모리를 공유하기 위해 스레드 그룹 요구, 스레드가 순서 없이 완료될 것으로 예상, 레지스터의 높은 빈도 사용, 1D 또는 3D 버퍼 저장.
PS 사용에 대한 AMD의 권장 사항: DS 컬링의 이점, 그래픽 렌더링 필요, 색상 압축 활용 필요; CS 사용에 대한 AMD의 권장 사항: PS 권장 사항을 제외한 모든 상황.
Async Compute 및 다중 유형 대기열을 사용하면 기존 게임 엔진의 순차적 실행 프로세스를 병렬 프로세스로 변환할 수 있습니다.


위: 전통적인 게임 엔진의 선형 렌더링 프로세스. 하단: GPU의 여러 엔진을 사용한 병렬 실행.
이러한 병렬 접근 방식은 단일 프레임의 렌더링 시간과 지연 시간을 줄여 드로 콜과 렌더링 효과를 향상시킬 수 있습니다.
그러나 병렬 구현 중에는 각 작업의 병목 현상에 특별한 주의를 기울여야 합니다. 일반적인 병목 현상에는 데이터 전송, 셰이더 처리량 및 기하학적 데이터 처리가 포함됩니다. 관련된 작업은 다음과 같습니다.

더 나은 병렬 효율성을 위해 각 엔진의 겹치는 부분에 동일한 병목 현상이 있는 작업을 배열하지 마십시오.

상단: 선형 실행 다이어그램; 중간: 섀도우 맵 및 스트림 텍스처, 지연 조명 및 애니메이트 파티클 병목 현상이 충돌하며 약간의 병렬 효율성만 얻을 수 있습니다. 하단: 동일한 병목 현상이 있는 작업을 피하고 더 많은 병렬 효율성을 얻습니다.
아래 그림의 왼쪽은 양호한 병렬 페어링을 보여주고, 오른쪽은 나쁜 병렬 페어링을 보여줍니다.

제약 없는 스케줄링은 구현이 단순하다는 이점이 있지만 프레임 간 불확실성과 페어링 제어 부족이라는 단점을 통해 열악한 기술 페어링에 대한 기회를 만듭니다.

더 나은 접근 방식은 Fences를 영리하게 사용하여 비동기 컴퓨팅 작업을 명시적으로 예약하는 것입니다. 이점은 프레임 간 결정성이 있으며 앱이 기술 페어링을 완벽하게 제어할 수 있다는 것입니다! 단점은 구현이 약간 더 복잡하다는 것입니다.

Copy Queue의 특징, 설명 및 사용 제안은 다음과 같습니다.
PCIE를 통한 복제를 위해 특별히 설계된 특수 하드웨어입니다.
다른 대기열과 독립적으로 작동하여 그래픽 처리를 위해 그래픽 및 컴퓨팅 대기열을 자유롭게 남겨둡니다.
시스템 메모리에서 로컬(비디오 메모리)로 복사하는 경우 복사 대기열을 사용합니다. 예를 들어 텍스처 스트리밍.
복사 대기열을 사용하여 PCIE에서 리소스를 전송합니다. 비동기 전송에는 여러 GPU를 사용하는 것이 필수적입니다.
복사 대기열 완료 시 회전을 피하세요. 이전 계획을 미리 세워야 합니다.
깊이 + 템플릿 리소스를 복사하거나 깊이만 복사하면 느린 경로가 발생할 수 있습니다. (NV 적응만 해당)
다중 GPU에서는 p2p 전송이 지원됩니다.
복사 대기열에서 대기하는 일이 없도록 GPU에 충분한 작업이 있는지 확인하십시오.
가능한 한 빨리 복사를 시작하십시오. 이상적으로는 로컬 메모리에 복사가 필요하기 몇 프레임 전이 좋습니다.
- 비디오 메모리 내에서 로컬 간 복사에는 두 가지 상황이 있습니다.
사례 1: 결과를 즉시 전송해야 하는 경우 Graphic Queue 또는 Compute Queue를 사용합니다.
- 사례 2: 전송 결과가 즉시 필요하지 않은 경우 Copy Queue를 사용합니다. 예를 들어 버퍼(상수, 버텍스, 인덱스 버퍼 등) 업로드 및 비디오 메모리 조각 모음이 있습니다.
복사 대기열 이동을 사용하여 프레임당 대역폭의 1%를 차지하는 등 비디오 메모리 조각 모음을 수행합니다. 그래픽 대기열이 계속 렌더링되도록 하여 복사 대기열이 스트리밍으로 사용 중이지 않을 때 프레임에서 실행합니다.

비동기 컴퓨팅에서는 다음을 권장합니다.
가능한 한 적게 동기화하십시오. 이상적으로는 프레임당 1-2회만 동기화하십시오. 각 동기화 지점에는 상당한 오버헤드가 있습니다.
대규모 순차 작업 부하를 비동기 대기열로 이동합니다. 파이프라인의 배수/채우기 단계를 겹칠 수 있는 더 많은 기회.
보다 급진적인 접근 방식: 다음 프레임과 겹칩니다.
일반적으로 프레임은 래스터 작업이 많은 작업으로 시작하여 계산량이 많은 포스트 프로세스 작업으로 끝납니다.
- 딜레이 증가 가능성!
13.4.6 기타 파이프라인 기술
레이 트레이싱을 지원하는 최신 그래픽 API 기능을 사용하면 하이브리드 레이 트레이싱 그림자를 구현할 수 있습니다.

그 결과 고품질 그림자 효과가 생성됩니다.


위: 전통적인 그림자 맵 효과; 하단: 하이브리드 레이 트레이싱 그림자 효과.
GPU 파이프라인을 제거하면 활용도가 감소하여 작은 여유 공간이 많이 발생한다는 점은 언급할 가치가 있습니다.

GPU 활용도가 낮으면 지연 시간이 발생하는 일반적인 원인이 됩니다.
대역폭을 줄이기 위해 최신 GPU는 내부 구성 요소 간에 압축 형식을 널리 사용합니다. 샘플링 시 압축된 데이터를 비디오 메모리에서 읽은 다음 Shader Core에서 압축을 풉니다. (아래 사진)

데이터를 내보내(쓰기)해야 하는 경우 먼저 컬러 블록으로 압축된 다음 압축된 데이터가 비디오 메모리에 기록됩니다. (아래 사진)

GPU 공급업체 도구는 일반적으로 텍스처 형식과 압축 활성화 여부를 관찰할 수 있습니다.

GPU 내부에서 이러한 종류의 데이터 압축을 수행하려면 다음 사항에 주의해야 합니다.
독점 대기열 소유권을 사용합니다. 공유 소유권의 경우 드라이버는 압축을 읽거나 쓸 수 없는 하드웨어 블록에서 작동하고 있다고 가정해야 합니다.
이미지 형식을 명시적으로 지정합니다. UNKNOWN/MUTABLE은 압축을 차단하고 VK_KHR_image_format_list에서 작동합니다.
꼭 필요한 이미지 용도만 사용하세요. 그렇지 않으면 리소스가 최적의 압축 수준보다 낮아질 수 있습니다.
렌더링 또는 깊이 대상을 정리합니다. 추가 대역폭 전송을 방지하기 위해 메타데이터가 재설정됩니다.
13.4.6.1 웨이브
DirectX 12 및 Vulkan의 Wave와 관련된 개념은 다음과 같습니다.
다이렉트X 12 불칸 설명
레인 호출 웨이브 내에서 실행되는 셰이더 호출(스레드)입니다.
웨이브 하위 그룹 셰이더 호출 모음으로, 제조사마다 호출 횟수가 다릅니다.

레인 및 웨이브 구조의 개략도.
Wave[DX] 실행 모드: 모든 레인이 동시에 잠금 단계로 실행됩니다. 하위 그룹[VK] 실행 모델: 하위 그룹 작업에는 암시적 장벽이 포함됩니다.
Wave 메커니즘의 장점은 다음과 같습니다.
- 장벽이나 인터록 지침의 사용을 줄입니다.
더 간단한 셰이더 코드.
유지 관리가 쉽고 코딩이 쉽습니다.
DFC 일관성을 더욱 효과적으로 제어할 수 있습니다.
제어 흐름(흐름) 일관성을 개선하는 데 도움이 됩니다.
- 메모리 액세스 일관성을 향상시키는 데 도움이 됩니다.
셰이더 스칼라화는 병렬 스레드 작업 속도를 높일 수 있으며 조명, GPU 기반 오클루전 컬링, SSR 등에 사용될 수 있습니다.
Wave 명령어 세트는 불필요한 동기화를 제거하여 스칼라 연산의 효율성을 높이고 DirectX 11 및 DirectX 12를 지원합니다. Threadgroup 및 Dispatch와는 다른 수준을 처리하고 다른 메모리를 사용하므로(아래 그림) 동기화를 위해서는 올바른 수준의 Atom을 사용해야 합니다.

Wave 작업을 사용하여 텍스처에 액세스할 때 컴퓨팅 셰이더의 스레드 인덱스가 ROW_MAJOR 모드로 구성된 경우 이웃을 잘 유지하지 못하고 캐시에 많이 적중할 수 없는 선형 텍스처와 일치합니다.

표준 혼합을 사용하여 텍스처 액세스를 최적화할 수 있습니다. 이 텍스처 레이아웃 모드는 인접한 픽셀이 메모리에 밀접하게 저장되도록 하여 캐시 적중률을 향상시킵니다.

다음은 성능 분석 도구인 RGP에서 캡쳐한 Wave 단위로 실행된 VS, PS, CS의 범례이다.


Wave를 지원하는 GPU의 경우 데이터는 웨이브 균일하지만 셰이더 컴파일러는 이를 인식하지 못합니다. 일반적인 응용 프로그램은 광원을 탐색하고 광원 인덱스가 파동 균일하다는 것을 컴파일러에 알리고 VGPR의 데이터를 SGPR에 넣는 것입니다.
Capcom의 RE 엔진은 Wave 작업을 사용하여 성능을 약 4.3% 향상시킵니다.

Wave에 대한 자세한 기술 정보는 D3D12 및 Vulkan의 Wave 프로그래밍을 참조하세요.
13.4.6.2 간접 실행
ExecuteIndirect 메커니즘을 사용하면 MultiExecuteIndirect()와 유사하게 여러 Draw, DrawIndexed 및 Dispatch를 동일한 호출로 결합할 수 있습니다. 추첨/디스패치 사이에 다음 데이터가 변경될 수 있습니다.
버텍스 버퍼, 인덱스 버퍼, 프리미티브 개수 등
루트 서명, 루트 상수.
루트 SRV 및 UAV.
다음은 DX 12의 ExecuteIndirect 인터페이스입니다.

이 인터페이스를 사용하면 다음을 달성할 수 있습니다.
하나의 ExecuteIndirect로 수천 개의 다양한 개체를 그립니다. 수백 개의 개체에 대해 많은 CPU 시간을 절약합니다.
간접 계산 작업. 최적의 성능을 위해 NULL 카운터 버퍼 매개변수를 사용할 수 있습니다.
그래픽 그리기 호출. 최적의 성능을 위해 카운터 버퍼 수를 ArgMaxCount 호출과 유사하게 유지하십시오.
다음은 DX11 및 DX12 드로잉 트리를 비교한 것입니다.

또한 GPU 기반 오클루전 컬링(Occlusion Culling)도 구현할 수 있습니다.
13.4.6.3 예측
예측은 DX12의 기능입니다. 쿼리와 완전히 분리됩니다. 버퍼의 특정 위치 값을 예측합니다. GPU는 SetPredication을 실행할 때 버퍼 값을 읽습니다.

예측을 지원하는 API는 다음과 같습니다.
-DrawInstanced
DrawIndexedInstanced
파견
-CopyTextureRegion
-CopyBufferRegion
-복사자원
-CopyTiles
-ResolveSubresource
ClearDepthStencilView
ClearRenderTargetView
ClearUnorderedAccessViewUint
ClearUnorderedAccessViewFloat
-간접 실행
사용 사례는 비동기식 CPU 기반 폐색 선별입니다. 한 CPU 스레드는 명령 목록을 기록하고 다른 CPU 스레드는 소프트웨어(비하드웨어) 폐색 쿼리를 실행하여 예측 버퍼에 채웁니다. (아래 사진)

13.4.6.4 UAV 오버랩
가장 먼저 이해해야 할 점은 최신 그래픽 API는 종속성이 없는 경우 병렬로 실행될 수 있다는 것입니다.
UAV Barrier의 특정 종속성은 읽기인지 쓰기인지 명확하지 않습니다. 각 배치를 별도의 위치에 기록하는 경우 WAW(write-after-write) 오류를 피할 수 있다면 병렬로 실행할 수 있습니다.
UAV 동기화는 각 컴퓨팅 셰이더의 일정에 따라 제어될 수 있습니다. UAV 동기화를 비활성화하면 병렬 실행이 가능해집니다. DirectX 11에서는 AGS 및 NVAPI를 사용하여 동등한 기능을 도입할 수 있습니다.
UAV 오버랩 메커니즘을 활성화하면 Capcom RE 엔진의 전반적인 성능이 약 3.5% 향상되었습니다.

13.4.6.5 다중 GPU
최신 그래픽 API는 여러 GPU를 명시적이고 정확하게 제어하고 다중 GPU 병렬 렌더링을 조정하여 효율성을 향상시킬 수 있습니다. 주로 반영:
모든 GPU의 콘텐츠를 완벽하게 제어할 수 있습니다.
지정된 그래픽 프로세서에 리소스를 생성합니다.
특정 GPU에서 명령 목록을 실행합니다.
GPU 간에 리소스를 명시적으로 복사합니다. DirectX 12 복사 대기열 사용 사례에 적합합니다.
GPU 간에 작업 부하를 분산합니다. AFR(Across Frame Rendering)로 제한되지 않습니다.

다중 GPU 협업 작업의 개략도.
위에서 언급한 기술이나 기능 외에도 최신 그래픽 API는 Conservative Raster, Typed UAV Loads, Rasterizer-Ordered Views, Stencil Reference Output, UAV 슬롯, Sparse Resource 및 기타 기능도 지원합니다.
13.5 포괄적인 애플리케이션
이 장에서는 최신 그래픽 API의 다음과 같은 일반적이고 포괄적인 응용 프로그램에 대해 설명합니다.
13.5.1 렌더링 하드웨어 인터페이스
Vulkan, DirectX 및 Metal을 포함하여 세 가지 유형의 최신 그래픽 API가 있습니다. 렌더링 엔진인 경우 여러 플랫폼에서 실행하려면 각 그래픽 API의 차이점을 캡슐화하는 중간 추상화 계층이 필요합니다. 이를 통해 상위 계층에서 통합 호출 방법을 제공하고 개발 효율성을 향상하며 확장성과 최적화 가능성을 얻을 수 있습니다.
UE는 이 캡슐화 계층을 **RHI(Rendering Hardware Interface, 렌더링 하드웨어 인터페이스)**라고 부릅니다. 보다 구체적으로 UE는 다양한 플랫폼 간의 차이점을 캡슐화하기 위해 FDynamicRHI 및 해당 하위 클래스를 제공합니다. 다음은 FDynamicRHI의 상속 구조 다이어그램입니다:
클래스 다이어그램-v2 클래스 FDynamicRHI{ 무효* RHIGetNativeDevice() 무효* RHIGetNativeInstance() IRHICommandContext* RHIGetDefaultContext() IRHIComputeContext* RHIGetDefaultAsyncComputeContext() IRHICommandContextContainer* RHIGetCommandContextContainer() } FDynamicRHI <|-- FMetalDynamicRHI 클래스 FMetalDynamicRHI{ FMetalRHIImmediateCommandContext ImmediateContext FMetalRHICommandContext* AsyncComputeContext } FDynamicRHI <|-- FD3D12DynamicRHI 클래스 FD3D12DynamicRHI{ 정적 FD3D12DynamicRHI* SingleD3DRHI FD3D12어댑터* 선택어댑터 FD3D12Device* GetRHIDevice() }
FDynamicRHI <|-- FD3D11DynamicRHI
클래스 FD3D11DynamicRHI{
IDXGIFactory1* DXGIFactory1
FD3D11장치* Direct3D장치
FD3D11DeviceContext* Direct3DDeviceIMContext
}
FDynamicRHI <|-- FOpenGLDynamicRHI
클래스 FOpenGLDynamicRHI{
FPlatformOpenGLDevice* 플랫폼장치
}
FDynamicRHI <|-- FVulkanDynamicRHI
클래스 FVulkanDynamicRHI{
VkInstance인스턴스
FVulkanDevice* 장치
}
그 중 FDynamicRHI는 통합 호출 인터페이스를 제공하며, 특정 하위 클래스는 해당 그래픽 API 플랫폼에 대한 호출 구현을 담당합니다.
자세한 내용은 언리얼 렌더링 시스템 분석(10) - RHI를 참조하세요.
13.5.2 멀티스레드 렌더링
무어의 법칙이 둔화되면서 CPU 제조업체는 멀티 코어 CPU로 발전하게 되었습니다. 그래픽 API 제조업체로서 멀티 코어 CPU를 최대한 활용하는 방향으로 나아가고 있습니다. 최신 그래픽 API의 중요한 변화는 멀티 코어 CPU에서 렌더링을 실현할 수 있다는 것입니다.

DX9, DX11 및 DX12의 멀티 스레드 모델 비교 다이어그램.

DX11과 DX12의 GPU 실행 모델 비교 다이어그램.
최신 그래픽 API를 사용하여 멀티스레드 렌더링을 구현하려면 CPU 멀티스레딩과 GPU 멀티스레딩을 모두 고려해야 합니다. CPU 측의 멀티스레딩을 고려해야 합니다.
- 다중 스레드 명령 버퍼 구성.
대기열에 제출하는 것은 스레드로부터 안전하지 않습니다.
프레임을 대규모 렌더링 작업으로 분할합니다.
메인 스레드에서 셰이더 컴파일을 분리합니다.
일괄 명령 버퍼를 제출합니다.
제출 및 렌더링 중에 스레드를 차단하지 마십시오.
병렬화할 수 있는 작업은 다음과 같습니다.
명령 목록이 생성됩니다. 다른 명령 버퍼가 필요합니다.
설명자 세트 생성. 다른 설명자 풀을 사용해야 합니다.
번들 생성.
PSO 생성.
자원 생성.
동적 데이터 생성.
GPU 측의 멀티스레딩은 하드웨어 컴퓨팅 유닛, 코어, 메모리 크기 및 대역폭, ALU 등의 성능뿐만 아니라 CU, SIMD, Wave, 스레드 수와 같은 지표도 고려해야 합니다. 다음 표는 Radeon Fury 및 Radeon Fury X의 하드웨어 매개변수입니다.
라데온 퓨리 X 라데온 퓨리
컴퓨팅 단위(CU) 64 56
핵심 주파수 1050MHz 1000MHz
메모리 크기 4GB 4GB
메모리 대역폭 512GB/초 512GB/초
ALU 8.6 TFlop 7.17 TF롭스
위 표에서 Radeon Fury X의 최대 스레드 수는 다음과 같다는 결론을 내릴 수 있습니다.
Radeon Fury X는 수년 전(2015년)의 GPU 제품이며 현재 GPU는 수백만 개의 스레드에 도달할 수 있습니다.
지연과 유휴 상태를 줄이기 위해 CPU 측에는 동시 멀티스레딩(하이퍼스레딩)을 사용하여 실행 리소스를 공유하는 두 명령 스트림을 인터리브하는 여러 프런트엔드(프런트엔드)가 필요합니다. 다음은 Bloom과 DOF가 병렬로 실행되는 그림입니다.

실행 리소스를 공유하는 두 명령 흐름을 인터리브하는 예: Bloom 및 DOF.

동기화를 위해서는 큐 내 장벽과 큐 간 장벽을 사용하세요.

인터리브 명령 흐름을 구현하기 위해 DirectX를 사용하는 방법을 보여줍니다.
다음은 DX12를 사용한 가장 간단한 멀티스레드 렌더링을 위한 의사코드입니다.
// 스레드(Thread)렌더링(Render)함수 。
void OnRender_MainThread()
{
// 개 렌더링(Render)스레드(Thread)렌더링(Render)
for workerId in workerIdList
{
SetEvent(BeginRendering_Events[workerId]);
}
// Pre Command List 을(를) 활용하여 렌더링(Render)
// 리셋/초기화(Reset) Pre Command List
pPreCommandList->Reset(...);
// 설정(Set)버퍼 로부터 프레젠트 상태 까지 렌더링(Render)의 배리어
pPreCommandList->ResourceBarrier(1, (..., D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
// 클리어 버퍼 컬러
pPreCommandList->ClearRenderTargetView(...);
// 클리어 버퍼 뎁스 /스텐실
pPreCommandList->ClearDepthStencilView(...);
// Pre Command List 의
// ...
// 비활성화(Disable) Pre Command List
pPreCommandList->Close();
// Post Command List 을(를) 활용하여 렌더링(Render)
// 설정(Set)버퍼 로부터 프레젠트 상태 까지 렌더링(Render)의 배리어
pPostCommandList->ResourceBarrier(1, (..., D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
// Post Command List 의
// ...
// 비활성화(Disable) Post Command List
pPostCommandList->Close();
// 스레드(Thread) 1
WaitForMultipleObjects(Task1_Events);
// 렌더링(Render)(Pre Command List 및 스레드(Thread)의 을(를) 활용하여 1 의 Command List)
pCommandQueue->ExecuteCommandLists(..., pPreCommandList + pCommandListsForTask1);
// 스레드(Thread) 2
WaitForMultipleObjects(Task2_Events);
// 렌더링(Render)(스레드(Thread)의 을(를) 활용하여 2 의 Command List)
pCommandQueue->ExecuteCommandLists(..., pCommandListsForTask2);
// ...
// 스레드(Thread) N
WaitForMultipleObjects(TaskN_Events);
// 렌더링(Render)(스레드(Thread)의 을(를) 활용하여 N 의 Command List)
pCommandQueue->ExecuteCommandLists(..., pCommandListsForTaskN);
// 의 Command List(pPostCommandList)
pCommandQueue->ExecuteCommandLists(..., pPostCommandList);
// 을(를) 활용하여 SwapChain 프레젠트
pSwapChain->Present(...);
}
void OnRender_WorkerThread(workerId)
{
// 회 루프 테이블 스레드(Thread)렌더링(Render)
while (running)
{
// 스레드(Thread)렌더링(Render)이벤트
WaitForSingleObject(BeginRendering_Events[workerId]);
// 렌더링(Render) 1
{
pCommandList1->SetGraphicsRootSignature(...);
pCommandList1->IASetVertexBuffers(...);
pCommandList1->IASetIndexBuffer(...);
// ...
pCommandList1->DrawIndexedInstanced(...);
pCommandList1->Close();
// 스레드(Thread)현재(Current)스레드(Thread)의 렌더링(Render) 1
SetEvent(Task1_Events[workerId]);
}
// 렌더링(Render) 2
{
pCommandList2->SetGraphicsRootSignature(...);
pCommandList2->IASetVertexBuffers(...);
pCommandList2->IASetIndexBuffer(...);
// ...
pCommandList2->DrawIndexedInstanced(...);
pCommandList2->Close();
// 스레드(Thread)현재(Current)스레드(Thread)의 렌더링(Render) 2
SetEvent(Task2_Events[workerId]);
}
// 렌더링(Render)
// ...
// 렌더링(Render) N
{
pCommandListN->SetGraphicsRootSignature(...);
pCommandListN->IASetVertexBuffers(...);
pCommandListN->IASetIndexBuffer(...);
// ...
pCommandListN->DrawIndexedInstanced(...);
pCommandListN->Close();
// 스레드(Thread)현재(Current)스레드(Thread)의 렌더링(Render) N
SetEvent(TaskN_Events[workerId]);
}
}
}위의 코드는 처리를 위해 하위 스레드에 작업을 성공적으로 할당하는 반면, 메인 스레드는 준비 및 렌더링 후 처리와 같은 작업에만 집중합니다.
하위 스레드는 적시에 작업 상태를 메인 스레드에 알리기만 하면 됩니다. 여러 명령 목록을 사용하면 한 프레임의 렌더링 명령을 중단 없이 처리할 수 있습니다. 동시에 메인 스레드도 자체 작업에 집중할 수 있습니다. 적절한 상황에서는 하위 스레드가 단계별 작업을 완료할 때까지 기다리고 명령 대기열을 사용하여 하위 스레드의 관련 명령 목록을 GPU에 제출합니다.
물론 렌더링 순서가 올바른 경우 하위 스레드는 명령 대기열을 통해 명령 목록에 있는 명령을 제출할 수도 있습니다. 설명의 편의를 위해 Command List를 제출하는 Command Queue의 작업은 메인 스레드에 배치됩니다.
엔진의 멀티스레드 렌더링을 구현할 때 엔진이 모든 코어를 포괄하여 모든 코어의 컴퓨팅 성능을 최대한 활용하고 병렬 효율성을 향상시킬 수 있는지 확인하십시오. Task Graph를 사용하는 다중 스레드 시스템이 더 좋습니다. 한 스레드는 모든 명령 대기열을 제출하고 다른 작업자 스레드는 명령 대기열을 병렬로 작성합니다.
또한 최신 3D 게임에서는 후처리가 광범위하게 사용되며 후처리와 같은 작업은 메인 스레드나 하나 이상의 하위 스레드에 배치될 수 있습니다.

좋은 작업 스케줄링을 갖춘 멀티스레드 렌더링 사례 1.

좋은 작업 스케줄링을 갖춘 멀티스레드 렌더링 사례 2.
다음 그림은 D3D11과 D3D12 간의 멀티스레드 성능 비교 차트입니다.

D3D12의 멀티스레딩 효율이 더 높다는 것을 알 수 있습니다. D3D11에 비해 전체 프레임 시간은 약 31%, GPU 시간은 약 50% 감소합니다.
이달 초(2021년 12월) 에픽게임즈가 개최한 UOD 2021 컨퍼런스에서 Tencent Photon에서 근무하는 Leon Wei는 멀티스레드 렌더링 시스템을 변형하여 OpenGL API의 병렬 처리 및 제출에 대해 설명했습니다.
그의 아이디어는 먼저 현재 UE 멀티스레드 렌더링 시스템의 전체 메커니즘을 요약하는 것입니다.

그런 다음 OpenGL 호출 중 어떤 API가 더 많은 시간을 소비하는지 알아보세요.
glBufferData()
glBufferSubData()
glCompressedTexImage2D() / glCompressedTexImage3D()
glCompressedTexSubImage2D() / glCompressedTexSubImage3D()
glTexImage2D() / glTexImage3D()
glTexSubImage2D() / glTexSubImage3D()
glcompileshader / glshadersource
gllinkprogram
......그런 다음 RHI 메인 스레드에서 다른 보조 RHI 스레드로 시간이 많이 걸리는 API를 추출하는 방법을 찾으십시오.


상위: 시간이 많이 걸리는 그래픽 API 호출은 동일한 RHI 스레드에서 호출될 때 스레드의 효율성에 영향을 미칩니다. 하단: RHI 메인 스레드가 막히지 않도록 시간이 많이 걸리는 API를 다른 보조 스레드로 추출합니다.
다음 그림은 수정된 다중 RHI 보조 스레드의 아키텍처 다이어그램입니다.

새로운 Multi-RHI 아키텍처에서는 멀티스레딩, 리소스 간 동기화 등의 추가 작업을 처리해야 합니다. 자세한 내용은 Leon Wei의 글: UE4 기반 다중 RHI 스레드 구현을 참조하세요.
13.5.3 프레임 그래프
최신 그래픽 API는 애플리케이션에 너무 많은 권한을 제공하므로 이 모든 것이 게임 애플리케이션 계층 개발자에게 노출된다면 재앙이 될 것입니다.
기본적이고 중요한 미들 레이어 역할로서 게임 엔진은 최신 그래픽 API가 가져오는 순회를 잘 제어하고 복잡성을 최대한 숨길 수 있는 메커니즘을 구현하는 데 매우 필요합니다. 이때 이러한 문제를 해결하기 위해 프레임 그래프가 탄생했습니다.

프레임 그래프는 추가 분리 및 최적화를 위해 엔진의 다양한 렌더링 기능(Feature), 상위 계층 렌더링 로직(Renderer) 및 하위 계층 리소스(Shader, RenderContext, 그래픽 API 등)를 분리하는 것을 목표로 합니다.
렌더링 파이프라인의 복잡성과 종속성을 해결하기 위해 Ubisoft의 Anvil 엔진은 파이프라인 상태와 리소스를 정확하게 관리하기 위해 생산자 시스템과 셰이더 입력 그룹을 구축했습니다.

Anvil 엔진의 복잡한 렌더링 파이프라인의 개략도.
그중 Anvil 엔진의 생산자 시스템의 목표는 리소스 종속성(리소스 수명주기, 대기열 간 동기화, 리소스 상태 변환, 명령 대기열 순차 실행 및 병합)을 실현하고 리소스 종속성을 정확하게 추적하는 것입니다.

Anvil 엔진은 리소스 종속성과 수명 주기 범례를 추적합니다.

Anvil 엔진은 메모리 재사용 범례를 구현합니다.

Anvil 엔진은 리소스 동기화 범례를 구현합니다.

Anvil 엔진은 상태 전환 범례를 구현하고 최적화합니다.
또한 Anvil은 일정 그래프(Schedule Graph)를 자동으로 생성할 수 있으며 GPU 실행 순서, 명령 대기열, 생산자 및 기타 정보를 볼 수 있습니다.

Anvil 엔진으로 생성된 일정 그래프.

앤빌 엔진이 생성한 스케줄 그래프의 일부를 확대한 모습입니다.
Anvil 엔진의 셰이더 입력 그룹은 오프라인 단계에서 PSO를 수집하고 컴파일하려고 시도합니다.

PSO의 경우 시간이 많이 걸리는 상태를 오프라인 및 로드 순간으로 전환해 보세요.

DX12와 같은 최신 그래픽 API를 기반으로 한 위 시스템 구축이 완료된 후 Anvil의 CPU는 약 15%-30%의 평균 향상을 달성할 수 있는 반면 GPU는 약 5%의 향상만 달성할 수 있습니다.

또한 UE의 RDG와 Frostbite의 프레임 그래프는 모두 렌더링 그래프를 기반으로 하여 최신 그래픽 API의 멀티스레드 렌더링, 리소스 관리, 상태 전환, 동기화 작업 등을 구현합니다.

Frost 엔진은 프레임 그래프를 사용하여 디퍼드 렌더링(Deferred Shading) 시퀀스 및 종속성 그래프를 구현합니다.
13.5.4 GPU 기반 렌더링 파이프라인
다음은 시나리오가 작업 항목으로 분류되는 방식에 대한 높은 수준의 개요입니다.
먼저 Coarse-grained view culling을 수행한 후, 살아남은 클러스터에 대해 다양한 테스트를 통해 Triangle culling을 실시한다.
채널에서 빠른 압축을 실행하여 그리드 점이 완전히 컬링된 경우(폐색 또는 프러스텀 컬링의 경우) 제로 크기 그리기가 발생하지 않도록 합니다.
파이프라인 끝에는 DirectX 12의 ExecuteIndirect를 사용하여 GPU 컬링을 수행하는 인덱싱된 그리기 매개변수 세트가 있습니다(OpenGL에는 AMD_mul _draw_indirect 확장이 필요하고 Xbox One에는 ExecuteIndirect 특수 확장이 필요합니다).
간접 매개변수를 통해 PSO를 전환한다는 것은 상태 또는 리소스 변경에 관계없이 전체 장면에 대해 단일 ExecuteIndirect만 호출할 수 있음을 의미합니다.

GPU 기반 파이프라인의 컷 프레임 다이어그램.
GPU 기반 렌더링 파이프라인 실행 프로세스는 또한 다양한 절단 기술(프러스텀 절단, 클러스터 절단, 삼각형 절단, 제로 영역 기본 절단, 작은 영역 기본 절단, 방향 절단, 깊이 절단, 블록 깊이 절단, 계층적 깊이 절단)과 최적화 기술(비교차 데이터 구조, 일괄 처리, 압축)을 결합하여 더 높은 렌더링 성능을 얻을 수 있습니다.
UE5의 Nanite와 Lumen은 GPU 기반 렌더링 파이프라인 기술을 완벽하게 활용하여 PC에서 영화 및 TV 수준의 실시간 렌더링 효과를 구현합니다.
GPU 기반 렌더링 파이프라인에 대한 자세한 내용은 다음을 참조하세요.
Compute를 통한 그래픽 파이프라인 최적화
GPU 기반 렌더링 파이프라인
언리얼 렌더링 시스템 분석(06) - UE5 특집 1부(기능 및 Nanite)
언리얼 렌더링 시스템 분석(06) - UE5 Special Part 2(Lumen 및 기타)
13.5.5 성능 모니터
AMD는 파이프라인의 많은 데이터(버텍스 캐시 효율성, 클리핑 속도, 오버드로잉 등)를 모니터링할 수 있는 DirectX12용 성능 감지 도구를 제공합니다.

NV의 공식 개발자는 다음과 같이 OpenGL, DX11 및 DX12의 일부 기능과 API 성능을 테스트했습니다.



DX12의 멀티스레딩, 번들, 기본 API 호출 및 기타 성능이 다른 기존 그래픽 API보다 훨씬 앞서 있음을 알 수 있습니다.
AMD는 또한 관련 성능 분석 도구도 제공합니다. 렌더링 파이프라인의 일반적인 상태는 다음과 같습니다.

일반적인 파이프라인 상태: 내부 그리기, 외부 그리기, 점유, 채우기, 배수.
Radeon GPU 프로파일러(RGP)는 Wave 실행 세부 정보를 볼 수 있습니다.

AMD의 내부 도구는 Wave의 수명 주기, 개별 구성 요소의 지침 상태 및 문제까지 추적할 수 있습니다.

모든 수준에서 평균 대기 시간과 캐시 적중률을 추정할 수도 있습니다.

아래 그림은 장벽 및 시리즈 종속성 + 적은 수의 작업으로 인해 많은 양의 유휴 GPU가 발생함을 보여줍니다.

아래 이미지는 GPU를 채울 수 없는 단순한 형상을 보여주며 SIMULTANEOUS_USE_BIT 명령 버퍼가 병렬 처리를 차단하여 많은 유휴 상태를 유발합니다.

아래 그림은 여러 배수 및 채우기 상태로 인해 감소된 GPU 사용률을 보여줍니다.

아래 그림은 Cold Cache로 인한 GPU 시간 소비 증가를 보여줍니다.

그러나 RGP 디스플레이 파이프라인의 웨이브 점유율이 높더라도 명령 캐시 누락 및 많은 유휴 시간으로 인해 성능이 높지 않을 수 있습니다.

증상을 분석한 후 올바른 약을 처방하고 다양한 조치를 취해야 진정한 GPU 성능을 얻을 수 있습니다. 다음 그림은 AMD 분석 도구를 통한 일반적인 패스의 성능을 보여줍니다.

자세히 보기: 엔진 최적화 핫랩.
GPUView는 GPU의 실행 세부 정보도 볼 수 있습니다(다중 지원).

또한 동기화 유효성 검사를 사용하여 올바른 Vulkan 동기화 확인에서는 Vulkan 동기화 오류를 확인하는 방법을 자세히 설명합니다.

다음 그림은 Vulkan 검증 계층의 작동 메커니즘을 보여줍니다.

13.6 이 기사 요약
이 기사에서는 주로 최신 그래픽 API의 특징, 메커니즘 및 사용 제안을 설명하고 몇 가지 적용 사례를 제공합니다.
13.6.1 Vulkan 기여자 목록
저자가 Vulkan 정보를 확인할 때 우연히 Vulkan 1.2 기여자 목록을 보게 되었습니다: 부록 I: 크레딧(정보).
그들이 속한 회사와 산업을 대략적으로 요약하면 다음과 같습니다.
회사 산업 인원수
구글 OS 26
AMD CPU, GPU 21
삼성전자 장비 19
엔비디아 GPU 18
인텔 CPU, GPU 18
LunarG 소프트웨어 16
퀄컴 GPU 11
상상기술 GPU 11
팔 GPU 10
크로노스 소프트웨어 표준 7
오큘러스 VR 6
코드플레이 소프트웨어 6
독립 소프트웨어 6
유니티 기술 게임 엔진 4
밸브 소프트웨어 소프트웨어 4
에픽게임즈 게임 엔진 3
미디어텍 소프트웨어 3
이갈리아 소프트웨어 3
모비카 소프트웨어 3
레드햇 OS 2
블리자드 엔터테인먼트 게임 1
화웨이 장비 1
위 표의 데이터에서 총 사람 수가 223명임을 알 수 있으며 많은 흥미로운 결론을 도출할 수 있습니다.
Vulkan 표준은 주로 OS, GPU, CPU 및 기타 회사에서 제공되며 절반 이상을 차지합니다.
Microsoft와 Apple은 자체 그래픽 API 표준인 DirectX와 Metal을 갖고 있기 때문에 목록에 포함되지 않았습니다.
게임업계에는 유니티, 에픽게임즈, 블리자드 등의 기업만 상장돼 전체 인원(223명)의 3.6%에 불과하다.
전체 중국인 용의자는 13명으로 전체의 5.8%에 불과하다.
국내 기업 중 화웨이만 상장돼 직원은 1명으로 전체 직원의 0.45%에 불과하다.
정리하자면, 국내 그래픽 렌더링 기술과 외국은 아직 격차가 크다! 우리는 자신을 향상시키기 위해 끊임없이 노력해야 합니다!
13.6.2 이 기사에 대한 생각
평소와 마찬가지로 이 기사에서는 최신 그래픽 API에 대한 이해와 숙달을 심화하기 위한 몇 가지 작은 생각도 정리합니다.
최신 그래픽 API의 특징은 무엇입니까? 자세히 기재해 주십시오.
최신 그래픽 API의 동기화 방법은 무엇입니까? 기능과 효율적인 사용 방법에 대해 이야기해 보세요.
최신 그래픽 API의 포괄적인 애플리케이션은 무엇입니까? 구현 과정에 대해 이야기해 보겠습니다.
특별 지침
모든 참고문헌의 저자에게 감사드립니다. 일부 사진은 참고 자료와 인터넷에서 가져온 것이므로 삭제되었습니다.
이 시리즈의 기사는 저자가 직접 작성한 것이며 블로그에만 게시됩니다. 이 글의 링크를 공유하셔도 좋지만 무단 전재는 허용되지 않습니다!
계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.
계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.
계속되는 일련의 기사. 전체 목록을 보려면 콘텐츠 개요를 클릭하세요.
📚 참고 자료 및 공식 링크 (References)
- Unreal Engine Source
- Rendering and Graphics
- Materials
- Graphics Programming
- Vulkan® 1.2.201 - A Specification
- Vulkan 1.1 Quick Reference
- Vulkan Tutorial
- Vulkan® Guide
- Vulkan Decoder Ring
- A Comparison of Modern Graphics APIs
- Raw Vulkan
- Raw Metal
- Raw DirectX 12
- DirectX 12 기술 백서 (Whitepaper)
- Raw DirectX 11
- Raw OpenGL
- Understanding Vulkan® Objects
- UE4/UE5 RHI(Vulkan)
- Direct3D 12 programming guide
- Direct3D 11 Programming guide
- Right on Queue: Advanced DirectX 12 Programming
- Metal API Overview
- Metal Programming Guide
- Metal Best Practices Guide
- Metal Shading Language Specification
- Working with Metal—Overview
- OpenGL 4.6 Core Specification
- OpenGL® ES 3.1 Reference Pages
- 📖 모바일 게임 성능 최적화를 위한 일반적인 기법
- 0x11: API :Attachment Fetch
- Using Next-Generation APIs on Mobile GPUs
- What's the main difference of pipeline process between Vulkan and DX12?
- Encoder results reuse
- Encoding Indirect Command Buffers on the CPU
- Rendering a Scene with Deferred Lighting in C++
- 기반UE4 RHI구현
- Yet another blog explaining Vulkan synchronization
- ENGINE OPTIMIZATION HOT LAP
- Vulkan Multi-Threading
- An exploratory study of high performance graphics application programming interfaces
- Approaching Minimum Overhead with Direct3D12
- The NVIDIA Turing GPU Architecture Deep Dive: Prelude to GeForce RTX
- EXPLORING COMPRESSION IN THE GPU MEMORY HIERARCHY FOR GRAPHICS AND COMPUTE
- Optimizing Data Transfer Using Lossless Compression with NVIDIA nvcomp
- Breaking Down Barriers - An Intro to GPU Synchronization
- DirectX 12: A New Meaning for Efficiency and Performance
- DirectX12: A Resource Heap Type Copying Time Analysis
- Ensure Correct Vulkan Synchronization by Using Synchronization Validation
- Memory Management in Vulkan™ and DX12
- Practical DirectX 12 - Programming Model and Hardware Capabilities
- D3D12 & Vulkan Done Right
- GDC2017: Moving To DX12 Lessons Learned
- Getting The Best Out Of D3D 12
- DX12 & Vulkan Dawn of a New Generation of Graphics APIs
- Introduction to Direct 3D 12 by Ivan Nevraev
- Surfing the wave(front)s with Radeon™ GPU Profiler
- Vulkan on NVIDIA GPUs
- http://cdn.imgtec.com/sdk-documentation/Vulkan.Migrating
- VULKAN FAST PATHS
- Real-Time Rendering
- An Overview of Next-Generation Graphics APIs
- Getting Explicit How hard is Vulkan Really?
- A sampling of UE4 rendering improvements Arne Schober
- Advanced Rendering with DirectX 11 and DirectX 12
- Porting your engine to Vulkan or DX12
- GDC 2016:D3D12 & Vulkan: Lessons learned
- GDC 2017:D3D12 & Vulkan: Lessons learned
- DirectX 12 Optimization Techniques in Capcom's RE ENGINE
- DirectX™ 12 Case Studies
- Efficient rendering in The Division 2
- Optimizing the Graphics Pipeline with Compute
- Deep Dive: Asynchronous Compute
- Explicit DirectX12 Multi GPU rendering
- Get the Most from Vulkan in Unity with Practical Examples from Infinite Dreams
- Wave Programming in D3D12 and Vulkan
- AMD GPU Performance Revealed
- CONCURRENCY MODEL IN EXPLICIT GRAPHICS APIS
- https://gpuopen.com/wp-content/uploads/slides/GPUOpen_Let’sBuild2020_AMD
- Optimizing for the RDNA Architecture
- Optimization with Radeon GPU Profiler A Vulkan Case
- High Zombie Throughput in Modern Graphics
- How we rethought driver abstraction
- Introduction to 3D Game Programming with DirectX® 12
- Triangles Are Precious - Let's Treat Them With Care
- FrameGraph: Extensible Rendering Architecture in Frostbite
- Advanced Graphics Techniques Tutorial Day: Practical DirectX 12 - Programming Model and Hardware Capabilities
- Programming GPU
- Efficient GPU Programming in Modern C++
- GPU-Driven Rendering Pipelines
- Understanding DirectX* Multithreaded Rendering Performance by Experiments
- Bringing Fortnite to Mobile with Vulkan and OpenGL ES
- DirectX 12: Advanced Graphics Performance
- DX12 Do's And Don'ts
- Vulkan Subgroup Tutorial
- Vulkan Best Practices for Mobile Developers
- API without Secrets: Introduction to Vulkan
- Advanced Graphics Tech: How to Thrive on the Bleeding Edge Whilst Avoiding Death by 1,000 Paper Cuts
- Ubisoft's Experience Developing with Vulkan
- VRS Tier 1 with DirectX 12 From Theory To Practice
- Vulkan in Rocksolid
- OPTIMISING A AAA VULKAN TITLE ON DESKTOP
- Making use of New Vulkan Features
- Vulkan: The State of the Union
- Microsoft Game Development Guide