[DX] Constant Buffer, Buffer DESC (Usage)
📚 Constant Buffer
World를 초기화할 때 Shader->AsMatrix("World")->SetMatrix(~); 와 같이 쉐이더에 World를 설정하면 쉐이더에서는 matrix World와 flaot4 color 등이 전역변수로 들어가게 된다.
그럼 자동으로 cbuffer라는 곳에 저장이 되는데, 여기서 문제점이 발생한다.
프로그래머가 world를 수정하고 싶어서 AsMatrix와 같은 방식으로 접근하려 하면 cbuffer에 함께 들어있는 color값도 함께 바뀌고, 반대로 color만 수정하고 싶은데 world도 자동으로 바뀐다.
따라서 위처럼 초기화하여 설정하는 것이 많으면 많을수록 전역변수가 늘어나고 cbuffer 변수가 늘어나서 프로그램 속도가 느려진다.
이를 해결하기 위해 cbuffer에 들어가는 값들을 새로운 구역으로 나눠 관리할건데, 쉐이더에서는 cbuffer라고 부르고, DX에선 constant buffer라고 부른다.
또한 cbuffer는 사용할 때 뒤에 구역번호가 붙는데(ex. c0, b1,,,) DX에서는 구역번호가 빠지고 구조체 구역으로 보내서 쉐이더 변수를 한꺼번에 다룰 수 있다.
Constant buffer(상수버퍼)라는 이름이 지어진 이유는, CPU에서 데이터를 GPU로 복사해주면, GPU입장에서는 수정이 불가능하다는 의미로 쉐이더에서 cbuffer라고 이름을 지은것이다.
(근데 HLSL에서만 수정이 불가능한것이지, DX에서는 수정이 가능하다)
📚 Buffer DESC 정보
DirectX의 Buffer를 초기화할 때 D3D11_BUFFER_DESC 변수를 선언하고, 구조체 안에 존재하는 값들을 초기화해준다.
- ByteWidth : 버퍼의 크기(바이트)
- Usage : 버퍼를 읽고 쓰는 방법을 식별한다. 즉, CPU와 GPU에서 각각 접근할 수 있는지의 여부를 설정
- BindFlags : 버퍼가 파이프라인에 바인딩되는 방식을 식별
- CPUAccessFlags : CPU에 접근할 수 있는지 없는지의 플래그 설정
- D3D11_CPU_ACCESS_READ: GPU->CPU 보내기
- D3D11_CPU_ACCESS_WRITE: CPU->GPU 보내기
💡 Usage
여기서 주의깊게 볼 항목은 Usage이다. Usage를 상황에 맞게 잘 설정해줘야 하는 이유는, Usage의 설정에 따라 프로그램의 속도가 달라지기 때문이다.
- D3D11_USAGE_DEFAULT
- CPU는 원래 쓰기가 불가능하고 읽기만 가능하다. GPU는 읽기와 쓰기 모두 가능하다.
- DEFAULT 상태에서도 예외적으로 CPU 쓰기가 가능한 상황은 UpdateSubResource 함수를 사용한 경우이다. - D3D11_USAGE_IMMUTABLE
- GPU 읽기만 가능. 즉, 렌더링 용으로만 접근하겠다는 의미
- IMMUTABLE은 상수처럼 생각하면 좋다. 처음 데이터를 생성할 때 데이터를 복사해줬다면 더이상 접근이 불가하여 데이터를 수정할 수 없다.
- 렌더링 용으로만 사용하기 때문에 속도가 가장 빠르다. - D3D11_USAGE_DYNAMIC
- CPU 쓰기와 GPU 읽기만 가능
- IMMUTABLE 보다는 느리다. - D3D11_USAGE_STAGING
- CPU와 GPU의 읽기 쓰기가 모두 가능
- 모든 읽기쓰기 기능이 가능하기 때문에 가장 느리다.
* 속도 우선순위 : IMMUTABLE -> DEFAULT -> DYNAMIC -> STAGING
📄 Buffer.cpp
#include "Framework.h"
#include "Buffers.h"
VertexBuffer::VertexBuffer(void * data, UINT count, UINT stride, UINT slot, bool bCpuWrite, bool bGpuWrite)
: data(data), count(count), stride(stride), slot(slot)
, bCpuWrite(bCpuWrite), bGpuWrite(bGpuWrite)
{
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(D3D11_BUFFER_DESC));
desc.ByteWidth = stride * count;
desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
// desc.Usage : CPU에서 접근할수있느냐, GPU에서 접근할수있느냐를 알려줌
if (bCpuWrite == false && bGpuWrite == false)
{
// 렌더링용으로만 사용하므로 속도가 가장 빠르다
// IMMUTABLE : GPU 읽기만 가능(=렌더링 용으로만 접근)
desc.Usage = D3D11_USAGE_IMMUTABLE;
}
else if (bCpuWrite == true && bGpuWrite == false)
{
// IMMUTABLE 보다는 느리다
desc.Usage = D3D11_USAGE_DYNAMIC; // CPU 쓰기/GPU 읽기만
desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
}
else if (bCpuWrite == false && bGpuWrite == true)
{
// Default : CPU는 원래 쓰기가 불가능하고 읽기만 가능, GPU는 읽기쓰기 둘다 가능
// 예외적으로 CPU 쓰기가 가능한 상황 : UpdateSubResource 함수 사용 시
desc.Usage = D3D11_USAGE_DEFAULT;
}
else
{
// STAGING : 읽고 쓰기 전부 가능
desc.Usage = D3D11_USAGE_STAGING;
// D3D11_CPU_ACCESS_READ: GPU->CPU 보내기
// D3D11_CPU_ACCESS_WRITE: CPU->GPU 보내기
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ | D3D11_CPU_ACCESS_WRITE;
}
D3D11_SUBRESOURCE_DATA subResource = { 0 };
subResource.pSysMem = data;
Check(D3D::GetDevice()->CreateBuffer(&desc, &subResource, &buffer));
}
VertexBuffer::~VertexBuffer()
{
SafeRelease(buffer);
}
void VertexBuffer::Render()
{
UINT offset = 0;
D3D::GetDC()->IASetVertexBuffers(slot, 1, &buffer, &stride, &offset);
}
///////////////////////////////////////////////////////////////////////////////
IndexBuffer::IndexBuffer(void * data, UINT count)
: data(data), count(count)
{
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(D3D11_BUFFER_DESC));
desc.ByteWidth = sizeof(UINT) * count;
desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
// Index 버퍼는 한번 들어가면 절대 바뀔일이 없으므로 IMMUTABLE
desc.Usage = D3D11_USAGE_IMMUTABLE;
D3D11_SUBRESOURCE_DATA subResource = { 0 };
subResource.pSysMem = data;
Check(D3D::GetDevice()->CreateBuffer(&desc, &subResource, &buffer));
}
IndexBuffer::~IndexBuffer()
{
SafeRelease(buffer);
}
void IndexBuffer::Render()
{
D3D::GetDC()->IASetIndexBuffer(buffer, DXGI_FORMAT_R32_UINT, 0);
}
///////////////////////////////////////////////////////////////////////////////
ConstantBuffer::ConstantBuffer(void * data, UINT dataSize)
{
D3D11_BUFFER_DESC desc;
ZeroMemory(&desc, sizeof(D3D11_BUFFER_DESC));
desc.ByteWidth = dataSize;
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
desc.Usage = D3D11_USAGE_DYNAMIC;
desc.CPUAccessFlags = D3D10_CPU_ACCESS_WRITE;
Check(D3D::GetDevice()->CreateBuffer(&desc, NULL, &buffer));
}
ConstantBuffer::~ConstantBuffer()
{
SafeRelease(buffer);
}
void ConstantBuffer::Render()
{
D3D11_MAPPED_SUBRESOURCE subResource;
D3D::GetDC()->Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &subResource);
{
memcpy(subResource.pData, data, dataSize);
}
// Map을 하면 반드시 Unmap 해야 한다.
D3D::GetDC()->Unmap(buffer, 0);
}
참조
https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ns-d3d11-d3d11_buffer_desc
D3D11_BUFFER_DESC (d3d11.h) - Win32 apps
Describes a buffer resource. (D3D11_BUFFER_DESC)
learn.microsoft.com
https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_usage
D3D11_USAGE (d3d11.h) - Win32 apps
Identifies expected resource use during rendering. The usage directly reflects whether a resource is accessible by the CPU and/or the graphics processing unit (GPU).
learn.microsoft.com