| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
- 2d 박스충돌
- 애니메이션
- 뱀과사다리게임
- 백준
- 24779
- 백준 1260 c++
- Unreal
- 백준 c++ 24479번
- 4134번
- BFS
- WinAPI
- directx12 그리기 연산2
- 바이토닉 수열
- lis응용
- 랜더링 파이프라인
- Perforce
- 다음소수
- C++
- 다익스트라
- 드래곤플라이트 모작
- unrealengine
- 2075번
- 11286번
- dx12 정리
- 백준 24444 c++
- 그리기 연산
- 루트서명
- dx12
- DirectX12
- 2565번
- Today
- Total
game-1 님의 블로그
[DirectX12] 상수 버퍼 본문
1. 상수 버퍼의 생성
상수 버퍼는 GPU에 데이터를 전달하기 위한 자원입니다.
일반적으로 Upload Heap에서 생성해서 CPU가 자주 갱신할 수 있도록 합니다.
cbuffer cbPerObject : register(b0)
{
float4x4 gWorldViewProj;
};
정점 버퍼나 색인 버퍼와는 다르게 상수 버퍼는 CPU가 프레임당 한 번 갱신하는 것이 일반적입니다.
예를 들어 카메라가 매 프레임 이동한다면, 프레임마다 상수 버퍼를 새 시야 행렬로 갱신해야 할 것입니다.
따라서 상수 버퍼는 기본 힙이 아니라 업로드 힙에 만들어야 합니다. 그래야 cpu가 버퍼의 내용을 갱신할 수 있습니다.
2. 상수 버퍼의 갱신
상수 버퍼는 Upload Heap에 있기 때문에 Map()함수로 CPU가 접근할 수 있습니다
이걸로 매 프레임마다 데이터를 갱신할 수 있습니다.
ComPtr<ID3D12Resource> mUploadBuffer;
BYTE* mMappedData = nullptr;
mUploadBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mMappedData));
상수 버퍼는 일반적으로 매 프레임마다 cpu가 데이터를 갱신해야 하는 자원입니다.
dx12에서는 상수 버퍼를 업로드 힙에 생성하기 때문에, cpu가 직접 접근해서 값을 쓸 수 있습니다.
이때 사용하는 함수가 바로 Map() 입니다.
Map()을 호출하면 mMappedData 포인터가 실제 GPU 자원의 메모리를 가리키게 됩니다.
이 포인터를 통해 memcpy로 데이터를 복사할 수 있습니다.
memcpy(mMappedData, &cbData, sizeof(cbData));
(주의) 상수 버퍼는 256바이트 정렬이 필수입니다.
sizeof(cbData)가 256의 배수가 아니면 패딩을 넣거나 버퍼 크기를 256의 배수로 조정해야합니다.
3. 업로드 버퍼 보조 클래스
매번 상수 버퍼를 만들고 Map, memcpy 하기가 번거롭기 때문에 이를 도와주는 유틸리티 클래스를 만들어 사용합니다.
주요 기능으로는 다양한 구조체 지원과
내부적으로 Map() + memcpy() 수행하는 것,
상수 버퍼를 자동으로 256바이트 정렬 처리해주는 것이 있습니다.
다음은 예시 클래스입니다.
template<typename T>
class UploadBuffer {
public:
UploadBuffer(ID3D12Device* device, UINT elementCount, bool isConstantBuffer) {
mElementByteSize = sizeof(T);
if (isConstantBuffer)
mElementByteSize = (sizeof(T) + 255) & ~255;
// 버퍼 생성
mUploadBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mMappedData));
}
void CopyData(int index, const T& data) {
memcpy(&mMappedData[index * mElementByteSize], &data, sizeof(T));
}
ID3D12Resource* Resource() const { return mUploadBuffer; }
};
4. 상수 버퍼 서술자( CBV : Constant Buffer View)
쉐이더에서 상수 버퍼를 사용하려면, GPU가 그 버퍼를 직접 참조할 수 있는 뷰가 필요합니다.
이때 사용하는 뷰가 CBV (Constant Buffer View)입니다.
GPU는 그냥 ID3D12Resource 객체만으로는 자원에 접근할 수 없기때문에 이러한 작업이 필요합니다.
gpu는 이 리소스를 어떻게 사용할지에 대한 정보가 담긴 서술자를 통해 접근합니다.
즉, 상수 버퍼를 gpu에 연결하려면 반드시 cbv를 만들어야 합니다.
1. 서술자 힙 생성
CBV, SRV, UAV는 공통 서술자 힙에 담깁니다.
D3D12_DESCRIPTOR_HEAP_DESC heapDesc = {};
heapDesc.NumDescriptors = 1;
heapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
heapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE; // 쉐이더에서 접근 가능
device->CreateDescriptorHeap(&heapDesc, IID_PPV_ARGS(&mCbvHeap));
2. CBV 생성
D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
cbvDesc.BufferLocation = mUploadBuffer->GetGPUVirtualAddress();
cbvDesc.SizeInBytes = (sizeof(ConstantData) + 255) & ~255; // 256바이트 정렬
device->CreateConstantBufferView(&cbvDesc, mCbvHeap->GetCPUDescriptorHandleForHeapStart());
5. 루트 서명과 서술자 테이블
루트 서명은 쉐이더와 바인딩할 리소스들의 구성 정보를 미리 정의해두는 구조체입니다.
쉐이더에서 상수 버퍼를 쓰고 있다면, C++에서는 이를 바인딩할 구조를 루트 서명으로 정의해줘야합니다.
루트 서명은 그리기 호출 전에 응용 프로그램이 반드시 렌더링 파이플아니에 묶어야 하는 자원들이 무엇이고 그 자원들이 셰이더 입력 레지스터들에 어떻게 대응되는지를 정의합니다.
루트 서명은 반드시 그리기 호출에 쓰이는 셰이더들과 호환되어야 합니다.
루트 서명은 여러 개의 루트 파라미터들로 구성됩니다.
각 루트 파라미터는 다음 중 하나일 수도 있습니다.
상수(constant), 루트 상수 버퍼(cbv), 서술자 테이블(descriptor table)
대부분의 경우 성능과 유연성 때문에서술자 테이블 방식을 많이 사용합니다.
서술자 테이블이란?
서술자 테이블은 CBV/ SRV/UAV를 하나의 테이블처럼 묶어서 루트 서명에 연결하는 구조입니다.
전체흐름
[CBV Resource] → [CBV] → [Descriptor Heap (Shader Visible)]
→ [Descriptor Table (in Root Signature)] → [Shader register(b0)]
'DirectX' 카테고리의 다른 글
| [DirectX12] 래스터화기 상태, 파이프라인 상태 객체, 기하구조 보조 구조체 (0) | 2025.04.08 |
|---|---|
| [DirectX12] Direct3D의 그리기 연산 (정점과 입력 배치) (0) | 2025.03.26 |
| [DirectX12] 렌더링 파이프 라인 1-4 (0) | 2025.03.17 |
| [DirectX12] 렌더링 파이프 라인 1-3 (테셀레이션 단계) (0) | 2025.03.15 |
| DX12 문제 내기 (0) | 2025.03.14 |