Company
교육 철학

인덱스 버퍼 / 상수 버퍼 클래스

개요

Index Buffer 클래스Constant Buffer 클래스는 DirectX 11의 버퍼 리소스를 래핑하여 편리하고 안전하게 사용할 수 있도록 하는 추상화 레이어입니다. 이 문서에서는 프로젝트의 실제 코드를 기반으로 각 클래스의 구조와 사용법을 다룹니다.
인덱스 버퍼와 상수 버퍼의 자세한 설명은 다음 페이지를 통해서 확인해보세요.

Part 1: Index Buffer 클래스

Index Buffer의 역할

인덱스 버퍼에는 그리려는 각 삼각형/선/점의 개별 인덱스가 들어 있습니다.
Index Buffer의 목적
정점 재사용을 통한 메모리 절약
정점 캐시 효율 향상
메쉬 데이터의 효율적인 관리
렌더링 파이프라인과의 통합
클래스의 책임
DirectX 11 인덱스 버퍼 생성
파이프라인에 인덱스 버퍼 바인딩
인덱스 데이터의 안전한 관리
리소스 수명 관리

IndexBuffer::Create 함수

인덱스 버퍼 생성하는 함수
bool IndexBuffer::Create(const std::vector<UINT>& indices) { desc.ByteWidth = sizeof(UINT) * indices.size(); desc.BindFlags = D3D11_BIND_FLAG::D3D11_BIND_INDEX_BUFFER; desc.Usage = D3D11_USAGE_DEFAULT; desc.CPUAccessFlags = 0; D3D11_SUBRESOURCE_DATA sub = {}; sub.pSysMem = indices.data(); if (!GetDevice()->CreateBuffer(&desc, &sub, buffer.GetAddressOf())) assert(NULL && "indices buffer create fail!!"); return true; }
C++
복사

코드 분석

매개변수
const std::vector<UINT>& indices
C++
복사
인덱스 데이터를 담은 벡터
UINT 타입 사용 (32비트 부호 없는 정수)
const 참조로 전달하여 복사 비용 방지
버퍼 설명 구조체 설정
ByteWidth (버퍼 크기)
desc.ByteWidth = sizeof(UINT) * indices.size();
C++
복사
전체 인덱스 배열의 바이트 크기 계산
UINT 크기(4 bytes) × 인덱스 개수
예: 인덱스 100개 = 4 × 100 = 400 bytes
BindFlags (바인딩 플래그)
desc.BindFlags = D3D11_BIND_FLAG::D3D11_BIND_INDEX_BUFFER;
C++
복사
이 버퍼가 인덱스 버퍼로 사용됨을 명시
Input Assembler 스테이지에서 사용
다른 플래그와 조합 불가
Usage (사용 방식)
desc.Usage = D3D11_USAGE_DEFAULT;
C++
복사
GPU 읽기/쓰기 가능
일반적인 정적 메쉬에 적합
CPU 접근 불가 (성능 최적화)
CPUAccessFlags (CPU 접근 플래그)
desc.CPUAccessFlags = 0;
C++
복사
CPU에서 직접 접근 불가
생성 후 수정하지 않는 정적 인덱스 버퍼
동적 수정이 필요하면 D3D11_CPU_ACCESS_WRITE 사용
서브리소스 데이터
D3D11_SUBRESOURCE_DATA sub = {}; sub.pSysMem = indices.data();
C++
복사
초기 데이터를 GPU로 전송
pSysMem: CPU 메모리의 인덱스 데이터 포인터
버퍼 생성 시 한 번만 복사됨
버퍼 생성 및 에러 처리
if (!GetDevice()->CreateBuffer(&desc, &sub, buffer.GetAddressOf())) assert(NULL && "indices buffer create fail!!"); return true;
C++
복사
GetDevice(): 싱글톤 디바이스 래퍼 접근
CreateBuffer(): DirectX 11 버퍼 생성 함수 호출
실패 시 assert로 즉시 프로그램 중단 (디버그 빌드)

IndexBuffer::Bind 함수

인덱스 버퍼를 파이프라인에 묶어주는 함수
void IndexBuffer::Bind() const { GetDevice()->BindIndexBuffer(buffer.Get(), DXGI_FORMAT_R32_UINT, 0); }
C++
복사

코드 분석

BindIndexBuffer 호출
GetDevice()->BindIndexBuffer(buffer.Get(), DXGI_FORMAT_R32_UINT, 0);
C++
복사
매개변수 설명
1.
buffer.Get(): ComPtr에서 실제 ID3D11Buffer 포인터 획득
2.
DXGI_FORMAT_R32_UINT: 인덱스 포맷 (32비트 부호 없는 정수)
3.
0: 버퍼 오프셋 (바이트 단위, 일반적으로 0)
인덱스 포맷 옵션
DXGI_FORMAT_R32_UINT: 32비트, 최대 4,294,967,295 정점
DXGI_FORMAT_R16_UINT: 16비트, 최대 65,535 정점
const 멤버 함수
객체 상태를 변경하지 않음
읽기 전용 작업
안전한 멀티스레드 사용 가능 (파이프라인 상태 제외)

Part 2: Constant Buffer 클래스

Constant Buffer의 역할

상수 버퍼는 그릴 때 셰이더 단계로 전송할 데이터를 나타냅니다. 일반적으로 모델 뷰 투영 행렬이나 색상, 슬라이더 등과 같은 특정 변수 데이터를 여기에 넣습니다.
Constant Buffer의 목적
셰이더에 동적 데이터 전달
프레임마다 변경되는 데이터 효율적 업데이트
타입 안전성 제공
버퍼 슬롯 관리
클래스의 책임
DirectX 11 상수 버퍼 생성
상수 버퍼 데이터 업데이트
적절한 셰이더 스테이지에 바인딩
버퍼 타입별 관리 (Transform, Material 등)

ConstantBuffer::Create 함수

상수 버퍼를 생성해주는 함수
bool ConstantBuffer::Create(eCBType type, UINT size, void* data) { mType = type; mSize = size; desc.ByteWidth = size; desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; desc.Usage = D3D11_USAGE_DYNAMIC; desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; D3D11_SUBRESOURCE_DATA sub = {}; sub.pSysMem = data; bool succes = false; if (data == NULL) succes = GetDevice()->CreateBuffer(&desc, nullptr, buffer.GetAddressOf()); else succes = GetDevice()->CreateBuffer(&desc, &sub, buffer.GetAddressOf()); if (!succes) assert(NULL, "Create constant buffer failed!"); return true; }
C++
복사

코드 분석

매개변수
eCBType type // 상수 버퍼 타입 (Transform, Material 등) UINT size // 버퍼 크기 (바이트) void* data // 초기 데이터 (nullptr 가능)
C++
복사
eCBType 열거형 예시
enum class eCBType { Transform, // b0: 변환 행렬 Material, // b1: Material 속성 Grid, // b2: 그리드 정보 Animation, // b3: 애니메이션 데이터 Light, // b4: 조명 정보 End };
C++
복사
멤버 변수 초기화
mType = type; mSize = size;
C++
복사
mType: 버퍼 타입 저장 (바인딩 슬롯 결정에 사용)
mSize: 버퍼 크기 저장 (데이터 업데이트 시 사용)
버퍼 설명 구조체 설정
ByteWidth (버퍼 크기)
desc.ByteWidth = size;
C++
복사
사용자가 지정한 크기 사용
16바이트 배수여야 함 (DirectX 11 제약)
예: sizeof(TransformData), sizeof(MaterialData)
BindFlags (바인딩 플래그)
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
C++
복사
상수 버퍼로 사용됨을 명시
다른 플래그와 조합 불가
모든 셰이더 스테이지에서 사용 가능
Usage (사용 방식)
desc.Usage = D3D11_USAGE_DYNAMIC;
C++
복사
CPU에서 자주 업데이트하는 버퍼
Map/Unmap을 통한 빠른 데이터 전송
매 프레임 변경되는 Transform 등에 적합
CPUAccessFlags (CPU 접근 플래그)
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
C++
복사
CPU에서 쓰기 가능
Map을 통한 데이터 업데이트 허용
DYNAMIC Usage와 함께 사용
조건부 버퍼 생성
bool succes = false; if (data == NULL) succes = GetDevice()->CreateBuffer(&desc, nullptr, buffer.GetAddressOf()); else succes = GetDevice()->CreateBuffer(&desc, &sub, buffer.GetAddressOf());
C++
복사
초기 데이터 없음
버퍼만 할당하고 데이터는 나중에 설정
매 프레임 갱신되는 버퍼에 적합
약간의 성능 이점
초기 데이터 있음
생성과 동시에 데이터 초기화
정적 데이터가 있을 때 유용
추가 업데이트 없이 바로 사용 가능

ConstantBuffer::SetData 함수

상수 버퍼에 데이터를 세팅해주는 함수
void ConstantBuffer::SetData(void* data) const { GetDevice()->SetDataBuffer(buffer.Get(), data, mSize); }
C++
복사

코드 분석

SetDataBuffer 호출
GetDevice()->SetDataBuffer(buffer.Get(), data, mSize);
C++
복사
내부 구현 (GraphicDevice 클래스)
void GraphicDevice_DX11::SetDataBuffer(ID3D11Buffer* buffer, void* data, UINT size) { D3D11_MAPPED_SUBRESOURCE mappedResource; // 버퍼를 CPU 메모리에 매핑 mContext->Map(buffer, 0, D3D11_MAP_WRITE_DISCARD, 0, &mappedResource); // 데이터 복사 memcpy(mappedResource.pData, data, size); // 매핑 해제 mContext->Unmap(buffer, 0); }
C++
복사
Map 플래그
D3D11_MAP_WRITE_DISCARD: 기존 데이터 폐기, 가장 빠름
GPU가 사용 중이면 새 메모리 블록 할당
이전 데이터 보존 불필요
장점
간단한 인터페이스
자동 메모리 관리
에러 처리 내장

ConstantBuffer::Bind 함수

상수 버퍼를 해당 파이프라인 스테이지에 묶어주는 함수
void ConstantBuffer::Bind(eShaderStage stage) const { GetDevice()->BindConstantBuffer(stage, mType, buffer.Get()); }
C++
복사

코드 분석

BindConstantBuffer 호출
GetDevice()->BindConstantBuffer(stage, mType, buffer.Get());
C++
복사
매개변수
stage: 바인딩할 셰이더 스테이지 (VS, PS, GS 등)
mType: 버퍼 타입 (슬롯 번호 결정)
buffer.Get(): 실제 버퍼 포인터
eShaderStage 열거형
enum class eShaderStage { VS, // Vertex Shader HS, // Hull Shader DS, // Domain Shader GS, // Geometry Shader PS, // Pixel Shader CS, // Compute Shader };
C++
복사
내부 구현 (GraphicDevice 클래스)
void GraphicDevice_DX11::BindConstantBuffer(eShaderStage stage, eCBType type, ID3D11Buffer* buffer) { UINT slot = static_cast<UINT>(type); switch (stage) { case eShaderStage::VS: mContext->VSSetConstantBuffers(slot, 1, &buffer); break; case eShaderStage::HS: mContext->HSSetConstantBuffers(slot, 1, &buffer); break; case eShaderStage::DS: mContext->DSSetConstantBuffers(slot, 1, &buffer); break; case eShaderStage::GS: mContext->GSSetConstantBuffers(slot, 1, &buffer); break; case eShaderStage::PS: mContext->PSSetConstantBuffers(slot, 1, &buffer); break; case eShaderStage::CS: mContext->CSSetConstantBuffers(slot, 1, &buffer); break; } }
C++
복사
슬롯 번호 자동 결정
eCBType 값이 직접 슬롯 번호로 사용
Transform = 0 → register(b0)
Material = 1 → register(b1)
타입 안전성 보장

Part 3: 사용 예시

Index Buffer 사용

메쉬 생성 시
// 정점 인덱스 배열 준비 std::vector<UINT> indices = { 0, 1, 2, // 첫 번째 삼각형 0, 2, 3 // 두 번째 삼각형 }; // Index Buffer 생성 IndexBuffer indexBuffer; indexBuffer.Create(indices); // 렌더링 시 바인딩 indexBuffer.Bind(); // Draw Call UINT indexCount = indices.size(); GetDevice()->GetContext()->DrawIndexed(indexCount, 0, 0);
C++
복사
Mesh 클래스 통합
class Mesh { private: VertexBuffer mVertexBuffer; IndexBuffer mIndexBuffer; public: void Create(const std::vector<Vertex>& vertices, const std::vector<UINT>& indices) { mVertexBuffer.Create(vertices); mIndexBuffer.Create(indices); } void Bind() { mVertexBuffer.Bind(); mIndexBuffer.Bind(); } void Render() { Bind(); GetDevice()->GetContext()->DrawIndexed( mIndexBuffer.GetIndexCount(), 0, 0 ); } };
C++
복사

Constant Buffer 사용

Transform 상수 버퍼
// Transform 데이터 구조체 struct TransformData { Matrix world; Matrix view; Matrix projection; }; // Constant Buffer 생성 ConstantBuffer transformCB; transformCB.Create(eCBType::Transform, sizeof(TransformData), nullptr); // 매 프레임 업데이트 void Update() { TransformData data; data.world = GetWorldMatrix(); data.view = GetViewMatrix(); data.projection = GetProjectionMatrix(); transformCB.SetData(&data); } // 렌더링 시 바인딩 void Render() { transformCB.Bind(eShaderStage::VS); // Draw... }
C++
복사
Material 상수 버퍼
struct MaterialData { Vector4 albedoColor; float metallic; float roughness; float ao; float padding; }; ConstantBuffer materialCB; materialCB.Create(eCBType::Material, sizeof(MaterialData), nullptr); void SetMaterial(const Material& material) { MaterialData data; data.albedoColor = material.albedoColor; data.metallic = material.metallic; data.roughness = material.roughness; data.ao = material.ao; materialCB.SetData(&data); materialCB.Bind(eShaderStage::PS); }
C++
복사
대응하는 HLSL

Part 4: 클래스 설계 패턴

래퍼 패턴 (Wrapper Pattern)

목적
DirectX 11 API의 복잡성 은닉
사용하기 쉬운 인터페이스 제공
에러 처리 통합
리소스 관리 단순화
이점
코드 가독성 향상
재사용성 증가
유지보수 용이
플랫폼 독립적 설계 가능

RAII (Resource Acquisition Is Initialization)

ComPtr 사용
class IndexBuffer { private: Microsoft::WRL::ComPtr<ID3D11Buffer> buffer; // 소멸 시 자동으로 Release 호출 };
C++
복사
장점
메모리 누수 방지
예외 안전성
명시적 Release 불필요
스마트 포인터 패턴

싱글톤 디바이스 접근

GetDevice() 패턴
GetDevice()->CreateBuffer(...); GetDevice()->BindIndexBuffer(...);
C++
복사
장점
전역 디바이스 접근
간결한 코드
디바이스 전달 불필요
주의점
멀티스레드 환경 고려
테스트 용이성 감소
결합도 증가

Part 5: 최적화 및 모범 사례

Index Buffer 최적화

16비트 vs 32비트
// 정점 개수가 65,536 미만 std::vector<USHORT> indices16; // 2 bytes per index indexBuffer.Create(indices16); // DXGI_FORMAT_R16_UINT 사용 // 정점 개수가 65,536 이상 std::vector<UINT> indices32; // 4 bytes per index indexBuffer.Create(indices32); // DXGI_FORMAT_R32_UINT 사용
C++
복사
메모리 절약
16비트 인덱스: 50% 메모리 절약
대부분의 메쉬는 65,536 정점 미만
큰 메쉬는 서브메쉬로 분할 고려
정점 캐시 최적화
인접한 삼각형이 정점 공유
Tom Forsyth 알고리즘 활용
DirectXMesh 라이브러리 사용

Constant Buffer 최적화

16바이트 정렬
// 나쁜 예 struct BadData { float value1; // 4 bytes float4 vector; // 16 bytes (12 bytes 패딩!) }; // 좋은 예 struct GoodData { float4 vector; // 16 bytes float value1; // 4 bytes float padding[3]; // 명시적 패딩 };
C++
복사
크기 제한
최대 64KB
권장 256 bytes 이하
업데이트 빈도에 따라 분할
배칭
// 나쁜 예: 매번 업데이트 for (auto& obj : objects) { transformCB.SetData(&obj.transform); transformCB.Bind(eShaderStage::VS); obj.Render(); } // 좋은 예: 변경 감지 Matrix prevTransform; for (auto& obj : objects) { if (obj.transform != prevTransform) { transformCB.SetData(&obj.transform); transformCB.Bind(eShaderStage::VS); prevTransform = obj.transform; } obj.Render(); }
C++
복사

버퍼 재사용

풀링 패턴
class BufferPool { std::vector<ConstantBuffer> mBuffers; public: ConstantBuffer* Acquire(eCBType type, UINT size) { // 재사용 가능한 버퍼 검색 for (auto& cb : mBuffers) { if (cb.GetType() == type && cb.GetSize() == size && !cb.IsInUse()) { cb.SetInUse(true); return &cb; } } // 새 버퍼 생성 ConstantBuffer newBuffer; newBuffer.Create(type, size, nullptr); mBuffers.push_back(newBuffer); return &mBuffers.back(); } void Release(ConstantBuffer* buffer) { buffer->SetInUse(false); } };
C++
복사

Part 6: 에러 처리 및 디버깅

Assert 사용

현재 구현
if (!GetDevice()->CreateBuffer(&desc, &sub, buffer.GetAddressOf())) assert(NULL && "indices buffer create fail!!");
C++
복사
개선된 에러 처리
HRESULT hr = GetDevice()->CreateBuffer(&desc, &sub, buffer.GetAddressOf()); if (FAILED(hr)) { // 에러 로깅 LOG_ERROR("Failed to create index buffer. HRESULT: 0x%08X", hr); // 디버그 빌드에서는 중단 assert(false && "indices buffer create fail!!"); // 릴리즈 빌드에서는 false 반환 return false; }
C++
복사

디버그 정보

버퍼 이름 설정
#ifdef _DEBUG void IndexBuffer::SetDebugName(const std::wstring& name) { if (buffer) { buffer->SetPrivateData(WKPDID_D3DDebugObjectName, name.size(), name.c_str()); } } #endif
C++
복사
사용 예
IndexBuffer indexBuffer; indexBuffer.Create(indices); indexBuffer.SetDebugName(L"PlayerMesh_IndexBuffer");
C++
복사

유효성 검사

크기 확인
bool ConstantBuffer::Create(eCBType type, UINT size, void* data) { // 16바이트 정렬 확인 if (size % 16 != 0) { LOG_WARNING("Constant buffer size is not 16-byte aligned: %u", size); size = (size + 15) & ~15; // 자동 정렬 } // 크기 제한 확인 if (size > 65536) { LOG_ERROR("Constant buffer size exceeds 64KB: %u", size); return false; } // ... 나머지 코드 }
C++
복사

결론

핵심 요약

Index Buffer 클래스
DirectX 11 인덱스 버퍼 래핑
간단한 생성 및 바인딩 인터페이스
RAII 패턴으로 안전한 리소스 관리
정점 재사용으로 메모리 절약
Constant Buffer 클래스
셰이더에 동적 데이터 전달
타입 안전한 버퍼 관리
빠른 데이터 업데이트 (Map/Unmap)
자동 슬롯 번호 관리
설계 패턴
래퍼 패턴으로 API 복잡성 은닉
RAII로 자동 리소스 관리
싱글톤 디바이스 접근

실전 적용 가이드

Index Buffer
1.
정점 개수에 따라 16비트/32비트 선택
2.
정점 캐시 최적화 적용
3.
정적 메쉬는 USAGE_DEFAULT 사용
4.
동적 메쉬는 USAGE_DYNAMIC 고려
Constant Buffer
1.
16바이트 정렬 규칙 준수
2.
업데이트 빈도에 따라 버퍼 분할
3.
USAGE_DYNAMIC으로 빠른 업데이트
4.
변경 감지로 불필요한 업데이트 방지
최적화
1.
버퍼 재사용으로 할당 비용 감소
2.
배칭으로 상태 변경 최소화
3.
에러 처리 및 로깅 강화
4.
디버그 이름 설정으로 프로파일링 용이
이 클래스들은 DirectX 11의 복잡한 버퍼 관리를 추상화하여 사용하기 쉽고 안전한 인터페이스를 제공합니다. 적절한 사용과 최적화를 통해 효율적인 렌더링 시스템을 구축할 수 있습니다.