Constant Buffer

 

  • 상수 버퍼(Constant buffer)는 정점 및 픽셀 셰이더에서 사용될 상수를 모아 놓은 버퍼이다.
  • 상수 버퍼 사용을 위해 cpp 코드 영역에 상수 버퍼 타입의 구조체를 정의하고 셰이더에도 동일한 포맷으로 상수 버퍼 구조체를 정의해야 한다.
  • 시스템 메모리에서 구조체 변수 생성 및 값 설정 후 정점 혹은 픽셀 셰이더에 Set 시킨다.
  • 값 설정 및 set은 보통 매 프레임 실행되는 Render() 함수에 적용한다.
  • 상수 버퍼는 256 바이트의 배수로 만들어야 한다. 이를 위해서 크기에 255를 더하고 비트마스크를 통해 하위 2바이트를 전부 0으로 만든다.

 

상수 버퍼를 사용하는 이유는 셰이더에서 매번 사용되는 상수, 하지만 cpp 파일에서 전달해주어야 한다고 가정할 때,
개별적으로 하나하나 값을 전달해 주는 것은 대역폭(bandwidth)이 크고 부담이 많이 된다고 한다.
따라서 하나의 구조체로 묶어 보내도록 하기 위해 constant buffer라는 개념을 사용한다고 한다.

 


 

<RootSignature.cpp>

 

더보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include "pch.h"
#include "RootSignature.h"
 
void RootSignature::Init(ComPtr<ID3D12Device> device)
{
    CD3DX12_ROOT_PARAMETER param[2];
    param[0].InitAsConstantBufferView(0); // 0번 -> b0 -> CBV 
    param[1].InitAsConstantBufferView(1); // 1번 -> b1 -> CBV
 
    D3D12_ROOT_SIGNATURE_DESC sigDesc = CD3DX12_ROOT_SIGNATURE_DESC(2, param);
    sigDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT; // 입력 조립기 단계
 
    ComPtr<ID3DBlob> blobSignature;
    ComPtr<ID3DBlob> blobError;
    ::D3D12SerializeRootSignature(&sigDesc, D3D_ROOT_SIGNATURE_VERSION_1, &blobSignature, &blobError);
    device->CreateRootSignature(0, blobSignature->GetBufferPointer(), blobSignature->GetBufferSize(), IID_PPV_ARGS(&_signature));
}
cs

 

 

위의 코드 중 6~8번 줄을 보자.

여기서는 루트 테이블에 저장하기 위한 두 개의 CBV를 생성한 뒤

10번 줄에서 루트 서명을 하면서 두 개의 인자를 넣어서 생성하게 된다. (2 = number of parameters, param)

 

 

루트 테이블에는 다양한 데이터가 들어갈 수 있다. 1번과 2번에 이어져있는 것들은 서술자 테이블 안의 요소들이다.

 

 

  • 루트 테이블 = 루트 시그니쳐를 만들 때 번호들로 이루어진 일련의 테이블
  • 1번사진의 좌측은 외부 코드에서 어떤 칸에 데이터를 저장할지를 알리는 슬롯.
  • 중앙은 상수, 루트 CBV, 서술자 테이블이 들어갈 수 있으며 서술자 테이블은 어떠한 정책과 같아서 하나만 사용된다.
  • 우측은 레지스터의 이름이 들어간다.
  • b = Contant buffer
  • t = Shader Resource
  • u = Unordered Access
  • s = sampler
  • 서술자 테이블을 사용하는 이유 : 루트 테이블을 만들 때 크기가 정해져있기 때문에 (64 DWORD) 공간을 활용하기 위함과 인자를 너무 많이 늘리면 성능에도 문제가 생길 수 있기 때문.

루트시그니쳐(서명)에 관한 더욱 자세한 내용은 아마 다음 글에서 다룰듯 하다.

 


 

<ConstantBuffer.cpp>

 

더보기

 

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include "pch.h"
#include "ConstantBuffer.h"
#include "Engine.h"
 
ConstantBuffer::ConstantBuffer()
{
}
 
ConstantBuffer::~ConstantBuffer()
{
    if (_cbvBuffer)
    {
        if (_cbvBuffer != nullptr)
            _cbvBuffer->Unmap(0, nullptr);
 
        _cbvBuffer = nullptr;
    }
}
 
 
 
void ConstantBuffer::Init(uint32 size, uint32 count)
{
    // 상수 버퍼는 256 바이트 배수로 만들어야 한다
    // 0 256 512 768
    _elementSize = (size + 255& ~255;
    _elementCount = count;
 
    CreateBuffer();
}
 
void ConstantBuffer::CreateBuffer()
{
    uint32 bufferSize = _elementSize * _elementCount;
    D3D12_HEAP_PROPERTIES heapProperty = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
    D3D12_RESOURCE_DESC desc = CD3DX12_RESOURCE_DESC::Buffer(bufferSize);
 
    DEVICE->CreateCommittedResource(
        &heapProperty,
        D3D12_HEAP_FLAG_NONE,
        &desc,
        D3D12_RESOURCE_STATE_GENERIC_READ,
        nullptr,
        IID_PPV_ARGS(&_cbvBuffer));
 
    _cbvBuffer->Map(0, nullptr, reinterpret_cast<void**>(&_mappedBuffer));
    // We do not need to unmap until we are done with the resource.  However, we must not write to
    // the resource while it is in use by the GPU (so we must use synchronization techniques).
}
 
void ConstantBuffer::Clear()
{
    _currentIndex = 0;
}
 
void ConstantBuffer::PushData(int32 rootParamIndex, void* buffer, uint32 size)
{
    assert(_currentIndex < _elementSize);
 
    ::memcpy(&_mappedBuffer[_currentIndex * _elementSize], buffer, size);
 
    D3D12_GPU_VIRTUAL_ADDRESS address = GetGpuVirtualAddress(_currentIndex);
    CMD_LIST->SetGraphicsRootConstantBufferView(rootParamIndex, address);
    _currentIndex++;
}
 
D3D12_GPU_VIRTUAL_ADDRESS ConstantBuffer::GetGpuVirtualAddress(uint32 index)
{
    D3D12_GPU_VIRTUAL_ADDRESS objCBAddress = _cbvBuffer->GetGPUVirtualAddress();
    objCBAddress += index * _elementSize;
    return objCBAddress;
}
cs

 

 

 

<CBV 생성 및 설정>

 

더보기
Game.cpp
Mesh.Cpp
default.hlsli

 

 

ConstantBuffer.cpp의 소스는 GPU의 RAM 부분에 여러개의 CBV 버퍼를 생성하여 관리하는 소스이며

각 메소드의 역할은 다음과 같다.

 

  • Init = 요소의 크기와 갯수를 지정.
  • CreateBuffer = GPU의 RAM 부분에 저장될 bufferSize 크기의 버퍼를 생성. 
  • PushData = 사용할 버퍼의 Index에 데이터를 복사해준 뒤 앞서 생성한 b0과 b1에게 참조할 주소를 전달

 

아래의 소스들은 우선 Game에서 Transform을 설정하여 mesh에게 넘겨준 뒤,

mesh 에서는 설정된 Transform의 값을 버퍼에 넣어주는 역할을 하며

default.hlsli의 TEST_B0과 TEST_B1의 b0와 b1에 offset0과 offset1로써 세팅이 되기 때문에

결론적으로 정점의 정보 중 좌표(offset0)와 색상(offset1) 값을 지닌 상수버퍼의 값에 따라 조정이 가능해졌다.

 

 


 

위의 과정이 도식화 된 이미지

 

즉시의 부분은 ConstantBuffer::CreateBuffer() 메소드의 DEVICE->CreateCommittedResource와 같이 디바이스를 통해 즉시 실행되는 부분을 의미하고

 

나중에는 커맨드 패턴에 의해 나중에 실행되는 부분을 의미한다.

 

이렇게 실행되는 타이밍이 다르기 때문에 GPU의 램에 여러개의 버퍼가 존재하는 것인데,

DEVICE에서 즉시 버퍼를 생성하고 값을 쓰는것과 달리 레지스터의 포인터가 주소를 참조하는 작업은 CommandList에 들어가 늦게 실행되기 때문이다. 

버퍼를 하나만 사용하게 되면 버퍼에 입력되는 value가 계속 변하기 때문에 원하지 않는 value를 참조하는것을 피하기 위해 여러개의 버퍼를 사용하는 것이다.

 


 

 

결과적으로 두개의 버퍼를 사용하여 두개의 삼각형을 생성할 수 있게 되었다.

+ Recent posts