Structed Buffer

Date:     Updated:

카테고리:

태그:

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


Structed Buffer

// cpu
vector<D3D11_INPUT_ELEMENT_DESC> basicInputElements = {
    {"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,
        D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 4 * 3,
        D3D11_INPUT_PER_VERTEX_DATA, 0},
    {"TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 4 * 3 + 4 * 3,
        D3D11_INPUT_PER_VERTEX_DATA, 0},
};

D3D11Utils::CreateVertexShaderAndInputLayout(
    device, vertexPrefix + L"VertexShader.hlsl", basicInputElements,
    m_vertexShader, m_inputLayout);
// vertex shader
struct VSInput
{
    float3 position : POSITION;
    float3 normal : NORMAL;
    float2 texcoord : TEXCOORD;
};

PSInput main(VSInput input)
{
    // ...
}

일반적으로 Vertex Shader에서 사용하는 vertex buffer는 input element를 통해 구조체의 배열처럼 사용한다. DirectX의 Structed Buffer는 pixel shader나 compute shader에서 구조화된 버퍼 데이터를 처리할 수 있게 지원한다. Structed Buffer는 주로 Index를 통해 데이터에 접근하며, Random Access(Unordered Access Veiw)를 허용해 여러 스레드가 동시에 데이터에 접근할 수 있다.


template <typename T_ELEMENT> class StructuredBuffer {
  public:
    virtual void Initialize(ComPtr<ID3D11Device> &device) {
        D3D11Utils::CreateStructuredBuffer(device, UINT(m_cpu.size()),
                                           sizeof(T_ELEMENT), m_cpu.data(),
                                           m_gpu, m_srv, m_uav);
    }

    const auto GetBuffer() { return m_gpu.Get(); }
    const auto GetSRV() { return m_srv.Get(); }
    const auto GetUAV() { return m_uav.Get(); }
    const auto GetAddressOfSRV() { return m_srv.GetAddressOf(); }
    const auto GetAddressOfUAV() { return m_uav.GetAddressOf(); }

    vector<T_ELEMENT> m_cpu;
    ComPtr<ID3D11Buffer> m_gpu;

    ComPtr<ID3D11ShaderResourceView> m_srv;
    ComPtr<ID3D11UnorderedAccessView> m_uav;
};
  • m_cpu : cpu에서는 std::vector 형태로 데이터를 들고 있음
  • m_cpu에 데이터를 넣어주고, Initialize() 함수를 실행하면 Structed Buffer 생성
  • Structed Buffer는 Compute Shader와 Vertex Shader에서 모두 사용할 것이기 때문에 UAV, SRV 둘 다 생성


Example

아래 예제는 Compute Shader에서 particle의 위치를 변경하고, 변경된 위치를 Vertex Shader에서 받아와 렌더링하는 과정이다. 중요한 점은 Structed Buffer를 통해 Compute Shader를 실행하고, vertex buffer가 아닌 해당 structed buffer를 vertex shader가 가져다 쓴다는 것이다.


// cpu
StructuredBuffer<Particle> m_particles;

m_particles.m_cpu.resize(25600);

std::mt19937 gen(0);
std::uniform_real_distribution<float> dp(-1.0f, 1.0f);
std::uniform_real_distribution<float> dc(0.8f, 1.0f);
for (auto &p : m_particles.m_cpu) {
    p.position = Vector3(dp(gen), dp(gen), 1.0f);
    p.color = Vector3(dc(gen), dc(gen), dc(gen));
}

m_particles.Initialize(m_device);

m_context->CSSetUnorderedAccessViews(0, 1, m_particles.GetAddressOfUAV(),
                                        NULL);
m_context->CSSetShader(m_computeShader.Get(), 0, 0);
m_context->Dispatch(UINT(ceil(m_particles.m_cpu.size() / 256.0f)), 1, 1);
AppBase::ComputeShaderBarrier();
  • m_particles : Structed Buffer에 데이터를 채우고 초기화
    • UAV, SRV 모두 생성
  • CSSetUnorderedAccessViews() : 해당 structed buffer를 compute shader에 전달
  • Dispatch(UINT(ceil(m_particles.m_cpu.size() / 256.0f)), 1, 1);
    • Dispatch의 thread group count를 보면 CS의 num_thread는 (256,1,1)이겠거니..
  • 주의) 해당 버퍼를 Vertex Shader에서 쓰기 전 ComputeShaderBarrierComputeShaderBarrier()를 통해 연산이 끝나기를 기다려야 함


// compute shader
struct Particle
{
    float3 pos;
    float3 color;
};

static float dt = 1 / 60.0;

RWStructuredBuffer<Particle> outputParticles : register(u0);

[numthreads(256, 1, 1)]
void main(int3 gID : SV_GroupID, int3 gtID : SV_GroupThreadID,
          uint3 dtID : SV_DispatchThreadID)
{
    Particle p = outputParticles[dtID.x]; // Read
    
    float3 velocity = float3(-p.pos.y, p.pos.x, 0.0);
    
    p.pos += velocity * dt * 0.05;
    
    outputParticles[dtID.x].pos = p.pos; // Write
}
  • Shader에서 Structed Buffer를 사용하려면 struct가 정의되어 있어야 함
  • 버퍼를 UAV로 받아왔기 때문에 RW Buffer & register(u)로 받음


// cpu
AppBase::SetMainViewport();
const float clearColor[4] = {0.0f, 0.0f, 0.0f, 1.0f};
m_context->ClearRenderTargetView(m_backBufferRTV.Get(), clearColor);
m_context->OMSetRenderTargets(1, m_backBufferRTV.GetAddressOf(), NULL);
m_context->VSSetShader(m_vertexShader.Get(), 0, 0);
m_context->PSSetShader(m_pixelShader.Get(), 0, 0);
m_context->CSSetShader(NULL, 0, 0);

m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);

m_context->VSSetShaderResources(0, 1, m_particles.GetAddressOfSRV());
m_context->Draw(UINT(m_particles.m_cpu.size()), 0);
  • 이번 예제에서는 Compute Shader의 output을 backbuffer로 설정하지 않음
    • Render Tartget View를 VS, PS로 직접 렌더링
    • Viewport 초기화 필요
  • Vertex Shader에서는 Compute Shader에서 연산이 끝난 buffer를 받아 쓰기 때문에 Vertex Buffer가 필요 없음
  • DrawIndexed()가 아니라 Draw()로 렌더링
    • 이전에 공부할 때 Draw 함수는 왜 있나 했었는데 이런 사용법이 있네..


// vertex shader
struct Particle
{
    float3 position;
    float3 color;
};

StructuredBuffer<Particle> particles : register(t0);

PSInput main(uint vertexID : SV_VertexID)
{
    Particle p = particles[vertexID];
    
    // ...
}
  • 똑같은 Structed Buffer를 UAV가 아닌 SRV로 넘겨줬음
    • RW 아님 & regiter t로 받음
  • main 함수에서 VSInput이 아닌 vertex index로 접근


Result

crop (1)



맨 위로 이동하기

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

댓글 남기기