Append/Consume Buffer
카테고리: Graphics
태그: DirectX
홍정모님의 그래픽스 새싹코스 강의를 듣고 정리한 내용입니다.
Append/Consume Buffer
이전 회사에서 Detection 모델의 post-process를 compute shader로 구현한 적이 있었다. 이때 append/consume 버퍼를 이용해서 감회가 새로운데, 내가 느낀 append/consume 버퍼의 필요성은 버퍼에 담긴 데이터의 개수를 모르는 상황이 있기 때문이다. Detection 모델의 경우 매 프레임 탐지된 오브젝트 후보의 개수가 다르다. 따라서 compute buffer에 담을 데이터의 개수도 매 프레임 다르다는 이야기인데, 그럼 버퍼의 사이즈를 어떻게 정의해야 할까? 버퍼의 크기는 dynamic하게 바꿀 수 없기 때문에 일단 큰 사이즈로 버퍼를 생성하고, 매 프레임 데이터의 개수(count)를 들고 다니는 형식으로 상황을 해결한다.
template <typename T_ELEMENT>
class AppendBuffer : public StructuredBuffer<T_ELEMENT> {
typedef StructuredBuffer<T_ELEMENT> BASE;
public:
void Initialize(ComPtr<ID3D11Device> &device) {
D3D11Utils::CreateAppendBuffer(device, UINT(BASE::m_cpu.size()),
sizeof(T_ELEMENT), BASE::m_cpu.data(),
BASE::m_gpu, BASE::m_srv, BASE::m_uav);
}
friend void swap(AppendBuffer<T_ELEMENT> &lhs,
AppendBuffer<T_ELEMENT> &rhs);
};
// CreateAppendBuffer()...
D3D11_UNORDERED_ACCESS_VIEW_DESC uavDesc;
ZeroMemory(&uavDesc, sizeof(uavDesc));
uavDesc.Format = DXGI_FORMAT_UNKNOWN;
uavDesc.ViewDimension = D3D11_UAV_DIMENSION_BUFFER;
uavDesc.Buffer.NumElements = numElements;
uavDesc.Buffer.Flags =
D3D11_BUFFER_UAV_FLAG_APPEND; // <- AppendBuffer로 사용
device->CreateUnorderedAccessView(buffer.Get(), &uavDesc,
uav.GetAddressOf());
- Append/Consume 버퍼는 Structured 버퍼와 동일한 구조를 가짐
- UAV 생성할 때 Flag값만 APPEND로 설정
- CPU에서는 Append Buffer로 동일하게 생성하고, GPU(Shader)에서 append/consume 명시
- 매 프레임 Append Buffer & Consume Buffer가 서로 바뀌는 상황이 많음
- Compute Shader에서 하는 작업이 consume에서 빼온 데이터를 append로 추가하는 일
- swap() 함수 추가
Example
// Initialize()...
m_consume.Initialize(m_device);
m_append.m_cpu.resize(m_consume.m_cpu.size());
m_append.Initialize(m_device);
m_countStaging.Initialize(m_device, {0});
m_consume
의 cpu에 데이터를 추가한 후 버퍼 생성m_append
버퍼의 사이즈는 일단 m_consume과 동일하게 지정 후 생성- Compute Shader 작업 후 Append Buffer에 count를 담아 올 staging buffer 생성
- 4byte UINT 데이터 하나만 받아오기 때문에 4byte 크기로 생성
// Render()...
ID3D11UnorderedAccessView *uavs[2] = {m_consume.GetUAV(),
m_append.GetUAV()};
// m_consume, m_append 버퍼의 초기 count값
UINT initCounts[2] = {UINT(m_consume.m_cpu.size()), 0};
m_context->CSSetUnorderedAccessViews(0, 2, uavs, initCounts);
m_context->CSSetShader(m_computeShader.Get(), 0, 0);
m_context->Dispatch(UINT(ceil(m_consume.m_cpu.size() / 256.0f)), 1, 1);
AppBase::ComputeShaderBarrier();
m_consume
,m_append
순서로 Set UAV- 이때 초기 count값 명시 해야 함
- 주의) structured buffer와 달리 consume buffer를 사용할 때에는 Dispatch의 thread_group_count를 딱 떨어지게 써야 함
- 잘못하면 비어있는 buffer에 consume 명령을 수행할 수 있음 -> 오류
// compute shader
struct Particle
{
float3 pos;
float3 color;
};
static float dt = 1 / 60.0;
ConsumeStructuredBuffer<Particle> inputParticles : register(u0);
AppendStructuredBuffer<Particle> outputParticles : register(u1);
[numthreads(256, 1, 1)]
void main(int3 gID : SV_GroupID, int3 gtID : SV_GroupThreadID,
uint3 dtID : SV_DispatchThreadID)
{
Particle p = inputParticles.Consume();
float3 velocity = float3(-p.pos.y, p.pos.x, 0.0) * 0.1;
p.pos += velocity * dt;
outputParticles.Append(p); // Write
}
- CPU에서
CSSetUnorderedAccessViews
세팅 할 때 consume/append 순으로 지정- shader에서도 consume/append 순으로 받아와야 함
- RWStructed 대신 Consume/Append Structed
- 사용 법은 Consume 버퍼에서 데이터 빼온 후 작업 후 Append 버퍼에 추가
// Render()...
// AppendUAV 개수 복사
m_context->CopyStructureCount(m_countStaging.m_gpu.Get(), 0,
m_append.GetUAV());
m_countStaging.Download(m_context);
uint32_t appendCount = m_countStaging.m_cpu[0];
// Setting VS, PS...
m_context->VSSetShaderResources(0, 1, m_append.GetAddressOfSRV());
m_context->Draw(UINT(appendCount), 0);
// DrawIndexedInstancedIndirect()를 사용하면 AppendCount를 CPU로 복사하지
ID3D11ShaderResourceView *nullSRVs[1] = {NULL};
m_context->VSSetShaderResources(0, 1, nullSRVs);
hlab::swap(m_consume, m_append);
- staging buffer를 통해 append buffer의
count
CPU로 받아오기 - compute shader 작업이 끝난 append buffer를
VS-SRV
로 세팅 Draw()
함수로 그려야 하는데, 이때 인자로 count 값이 필요함- count 값을 구하는 과정은
- GPU에 있는 append buffer의 count를 stating buffer로 옮기고, CPU로 가져오기
- 매우 비효율적으로 느껴짐..
DrawIndexedInstancedIndirect()
함수를 이용하면 count값을 CPU로 가져오지 않고도 Draw가 가능하다고 함
- 다음 프레임에서는 append와 consume 버퍼의 역할이 반대 ->
swap()
Result
- 결과는 Structured Buffer를 사용할 때와 동일..
댓글 남기기