Notice
Recent Posts
Recent Comments
Link
«   2026/02   »
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
Tags
more
Archives
Today
Total
관리 메뉴

MOONSUN

[D3D] 상수버퍼(Constant Buffer) 와 HLSL 정렬규칙 본문

D3D

[D3D] 상수버퍼(Constant Buffer) 와 HLSL 정렬규칙

MoonSun_v 2025. 9. 10. 21:15

 

1. 상수 버퍼란?

GPU에서 여러 픽셀/버텍스를 그릴 때 공통으로 사용하는 상수 데이터를 저장하는 버퍼

 

예 : 월드/뷰/투영 행렬, 라이트 색상, 머티리얼 색상 등

 

 

1-1. 왜 상수 버퍼를 쓰는가?

  • 상수 버퍼는 한 번 GPU에 올리고 재사용 가능 ( 매번 셰이더마다 데이터를 직접 보내면 느림.. )
  • 관련 데이터를 구조체 단위로 묶어서 전달 가능
    • 예 : 월드/뷰/프로젝션 행렬을 struct ConstantBuffer { mWorld; mView; mProj; } 형태로 묶기 가능 

 

1-2. 사용 방식 (Direct11 기준)

1. C++에서 구조체 정의

struct ConstantBuffer
{
    XMMATRIX mWorld;
    XMMATRIX mView;
    XMMATRIX mProjection;
};

 

 

2. GPU용 상수 버퍼 생성

D3D11_BUFFER_DESC cbd = {};
cbd.Usage = D3D11_USAGE_DEFAULT;
cbd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbd.ByteWidth = sizeof(ConstantBuffer);
m_pDevice->CreateBuffer(&cbd, nullptr, &m_pConstantBuffer);

 

 

3. 상수 버퍼 업데이트

  • CPU에서 GPU로 넘기기 전에 Transpose(전치) 해준다.
  • HLSL에서는 Transpose(전치) 된 걸 받았으니 mul(v, M) 사용한다.
ConstantBuffer cb;
cb.mWorld = XMMatrixTranspose(m_World);
cb.mView  = XMMatrixTranspose(m_View);
cb.mProjection = XMMatrixTranspose(m_Projection);

m_pDeviceContext->UpdateSubresource(m_pConstantBuffer.Get(), 0, nullptr, &cb, 0, 0);

 

 

4. 셰이더에 바인딩

m_pDeviceContext->VSSetConstantBuffers(0, 1, m_pConstantBuffer.GetAddressOf());

 

 

이렇게 하면 Vertex Shader에서 바로 cb.mWorld, cb.mView 등 접근 가능

 

 

 

 

 2. HLSL 상수 버퍼 정렬 규칙

GPU는 메모리 접근 단위 때문에 데이터를 특정 방식으로 정렬해야 한다. 

상수 버퍼 안의 데이터는 16바이트 경계로 배치 되어야 함. 

( DirectX(HLSL)에서는 메모리를 16바이트(4 float) 단위로 접근하기 때문 )

 

 

2-1. 정렬 과 패딩

  • 정렬(alignment): 멤버를 해당 타입 크기나 구조체 최대 멤버 크기에 맞춰 시작 주소 배치.
    • 이유 : CPU/GPU는 특정 크기 단위로 데이터를 읽을 때 속도가 가장 빠름
      • 16바이트 정렬 → 메모리 주소가 16의 배수여야 함 

 

  • 패딩(padding): 정렬 규칙을 지키기 위해 데이터 사이에 추가되는 빈 공간.
    • GPU는 상수 버퍼를 16바이트 단위로 읽기 때문에,
      • float3처럼 12바이트짜리를 넣으면 나머지 4바이트를 패딩으로 채워야 함

 

struct ConstantBuffer
{
    Matrix mWorld;       // 64 bytes
    Matrix mView;        // 64 bytes
    Matrix mProjection;  // 64 bytes
};

// DirectXMath의 XMMATRIX 또는 일반적으로 4×4 행렬(Matrix)는 float4x4 구조
// 전체 행렬: 4행 × 16바이트 = 64바이트

예를 들어, 위와 같은 코드는 16바이트 배수로 추가 패딩이 필요 없는 경우이다.

 

struct CB_MarterialOverride
{
	int UseMaterialOverride = false;						// 4
    Vector3 BaseColorOverride = Vector3(1.0f, 1.0f, 1.0f);  // 12	// [16 byte]
    float MetalnessOverride = 0.0f;							// 4
    float RoughnessOverride = 0.0f							// 4
    Vector2 pad2;											// 8	// [16 byte]
}


cbuffer MaterialOverride : register(b6)
{
	int UseMaterialOverride;
    float3 BaseColorOverride;	// 16 
	float MetalnessOverride;
    float RoughnessOverride;
    float2 MaterialOverridePad;	// 16
}

위 코드는 16바이트 맞추려고 패딩 값(빈 값) 넣어주고 있음. (양쪽이 메모리 위치가 같게끔 작업 해주는 것)

 

 

 

CPU(C++)에서 구조체를 만들 때는 16바이트를 지키지 않아도 자동으로 패딩을 넣어주지만,

GPU(HLSL 상수 버퍼) 는 자동으로 패딩을 넣어주지 않으므로, 16바이트 단위로 맞지 않으면 값이 깨지거나 잘못 읽히는 문제 발생.