Append/Consume Buffer

Date:     Updated:

카테고리:

태그:

홍정모님의 그래픽스 새싹코스 강의를 듣고 정리한 내용입니다.


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

crop (1)

  • 결과는 Structured Buffer를 사용할 때와 동일..



맨 위로 이동하기

Graphics 카테고리 내 다른 글 보러가기

댓글 남기기