개요
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의 복잡한 버퍼 관리를 추상화하여 사용하기 쉽고 안전한 인터페이스를 제공합니다. 적절한 사용과 최적화를 통해 효율적인 렌더링 시스템을 구축할 수 있습니다.


