Game Engine Render Resource 동기화 방식 및 Resource 관리 방식
void* 형태로 서로 알고 있을경우 Resource 동기화는 어떻게 할 것인가
- 추가되는 Resource 뿐만 아니라 변경되는 Resource는 즉시 처리하는 방식이 아닌 Graphic 측에서 현재 프레임에 추가 및 변경 된 Resource는 각각의 Queue에 보관해둔 뒤 실질적인 Rendering이 실행되기 전 변환을 하여 Instance Layer를 재구성을 한 후에 Rendering을 실행한다.
- 동기화 부분에 대해서 많은 고민을 해보았지만 해당 Resource가 변경될 시점마다 Graphic 측에 알려주어 동기화 하는 방식이 단순하면서도 깔끔할 것이라는 생각이 들어 해당 Resource가 변경 될때마다 해당 Resource에 대응하는 미리 Casting된 Resource도 재설정을 해주는 방식으로 변경하였다.
- Graphic Engine 측에 필요한 Resource 동기화는 한정적이기 때문에 해당 부분만 조금 신경써서 동기화를 해준다면 문제가 없을 것이라고 판단 되었다.
// Render Resource & Render Data Convert Class
class RenderDataConverter
{
...
public:
void PushMesh(MeshBuffer* mesh); // 현재 프레임에 생성된 Mesh 임시 Queue에 삽입..
void PushMaterial(MaterialBuffer* material); // 현재 프레임에 생성된 Material 임시 Queue에 삽입..
void PushAnimation(AnimationBuffer* animation); // 현재 프레임에 생성된 Animation 임시 Queue에 삽입..
public:
void PushChangeMesh(MeshBuffer* mesh); // 현재 프레임에 변경된 Mesh 임시 Queue에 삽입..
void PushChangeMaterial(MaterialBuffer* material); // 현재 프레임에 변경된 Material 임시 Queue에 삽입..
void PushChangeAnimation(AnimationBuffer* animation); // 현재 프레임에 생성된 Animation 임시 Queue에 삽입..
public:
void DeleteRenderData(UINT index); // 해당 Render Data 즉시 제거..
void DeleteInstance(UINT index); // 해당 Instance Resource 즉시 제거..
void DeleteMesh(UINT index); // 해당 Mesh Resource 즉시 제거..
void DeleteMaterial(UINT index); // 해당 Material Resource 즉시 제거..
void DeleteAnimation(UINT index); // 해당 Animation Resource 즉시 제거..
private:
std::queue<MeshBuffer*> m_PushMeshList; // Game Engine 측에서 현재 프레임에 생성한 Mesh Resource Queue..
std::queue<MeshBuffer*> m_ChangeMeshList; // Game Engine 측에서 현재 프레임에 변경한 Mesh Resource Queue..
std::queue<MaterialBuffer*> m_PushMaterialList; // Game Engine 측에서 현재 프레임에 생성한 Material Resource Queue..
std::queue<MaterialBuffer*> m_ChangeMaterialList; // Game Engine 측에서 현재 프레임에 변경한 Material Resource Queue..
std::queue<AnimationBuffer*> m_PushAnimationList; // Game Engine 측에서 현재 프레임에 생성한 Animation Resource Queue..
std::queue<AnimationBuffer*> m_ChangeAnimationList; // Game Engine 측에서 현재 프레임에 변경한 Animation Resource Queue..
...
}
// Convert Push Resource (Game Engine Data -> Graphic Engine Data)
void RenderDataConverter::ConvertPushResource()
{
// 현재 프레임 동안 쌓아둔 Push Mesh Queue 처리..
while (!m_PushMeshList.empty())
{
// 현재 추가된 Mesh Buffer..
MeshBuffer* mesh = m_PushMeshList.front();
// 추가된 Resource 변환..
ConvertPushResource<MeshBuffer, MeshRenderBuffer>(mesh, m_MeshList);
// 변환된 Mesh Buffer Pop..
m_PushMeshList.pop();
}
// 현재 프레임 동안 쌓아둔 Push Material Queue 처리..
while (!m_PushMaterialList.empty())
{
// 현재 추가된 Material Buffer..
MaterialBuffer* material = m_PushMaterialList.front();
// 추가된 Resource 변환..
ConvertPushResource<MaterialBuffer, MaterialRenderBuffer>(material, m_MaterialList);
// 변환된 Material Buffer Pop..
m_PushMaterialList.pop();
}
// 현재 프레임 동안 쌓아둔 Push Animation Queue 처리..
while (!m_PushAnimationList.empty())
{
// 현재 추가된 Animation Buffer..
AnimationBuffer* animation = m_PushAnimationList.front();
// 추가된 Resource 변환..
ConvertPushResource<AnimationBuffer, AnimationRenderBuffer>(animation, m_AnimationList);
// 변환된 Animation Buffer Pop..
m_PushAnimationList.pop();
}
}
// Convert Change Resource (Game Engine Data -> Graphic Engine Data)
void RenderDataConverter::ConvertChangeResource()
{
// 현재 프레임 동안 쌓아둔 Change Mesh Queue 처리..
while (!m_ChangeMeshList.empty())
{
// 현재 변동된 Resource가 있는 Mesh Buffer..
MeshBuffer* mesh = m_ChangeMeshList.front();
// 변동된 Resource 변환..
ConvertChangeResource<MeshBuffer, MeshRenderBuffer>(mesh, m_MeshList);
// 변환된 Mesh Buffer Pop..
m_ChangeMeshList.pop();
}
// 현재 프레임 동안 쌓아둔 Change Material Queue 처리..
while (!m_ChangeMaterialList.empty())
{
// 현재 변동된 Resource가 있는 Material Buffer..
MaterialBuffer* material = m_ChangeMaterialList.front();
// 변동된 Resource 변환..
ConvertChangeResource<MaterialBuffer, MaterialRenderBuffer>(material, m_MaterialList);
// 변환된 Material Buffer Pop..
m_ChangeMaterialList.pop();
}
// 현재 프레임 동안 쌓아둔 Change Animation Queue 처리..
while (!m_ChangeAnimationList.empty())
{
// 현재 추가된 Animation Buffer..
AnimationBuffer* animation = m_ChangeAnimationList.front();
// 추가된 Resource 변환..
ConvertChangeResource<AnimationBuffer, AnimationRenderBuffer>(animation, m_AnimationList);
// 변환된 Animation Buffer Pop..
m_ChangeAnimationList.pop();
}
}
- Game Engine 측에서 Load한 Resource 삽입 및 Load된 Resource 변경 과정이다.
- 해당 Queue에 쌓인 데이터를 변환하는 과정은 실질적인 Rendering에 들어가기 전에 실행한다.
그렇다면 Resource 해제는 어디서 하는 것이 맞는가
- 당연한 소리이지만 Resource를 해제하는 것도 Game Engine에서 Graphic Engine에 요청을하여 Resource 해제를 해주어야 한다. 일단 Graphic API를 Game Engine 측에선 모르기에 형변환이 불가능하며, 해당 Reosurce 해제는 생성한 곳 즉, Graphic Engine 측에서 형변환을 하여 해당 구조체에 맞는 해제 방식을 통해 Resource 해제를 실행시켜 주어야한다.
Resource 세분화 및 관리 방식
- Instancing을 하기 전에는 Resource 재활용에 크게 신경을 쓰지 않고 구조를 짜다보니, 막상 Instancing을 추가하게 되면서 Mesh, Material, Animation 등 여러 Instancing을 활용할 수 있는 Resource를 분리해야 한다는 생각이 들었다.
- Instancing을 할 수 있는 Resource들을 기준으로 Graphic Engine 측에서 Instance Layer를 구성하여 각각의 Batch로 모아서 그린다.
// Rendering 실행 전 변환 해야할 Data 변환..
void RenderManager::ConvertRenderData()
{
// Render Resource 동기화 작업..
m_Converter->ResourceUpdate();
// 현재 프레임 동안 추가된 Render Data Convert 작업..
ConvertPushInstance();
// 현재 프레임 동안 변경된 Render Data Convert 작업..
ConvertChangeInstance();
}
// 현재 프레임 진행중 추가된 Instance 삽입..
void RenderManager::ConvertPushInstance()
{
// Queue가 비어있다면 처리하지 않는다..
if (m_PushInstanceList.empty()) return;
// 현재 프레임 진행중 쌓아둔 추가된 Render Data List 삽입 작업..
while (!m_PushInstanceList.empty())
{
// 해당 원본 Mesh Data 추출..
MeshData* originMeshData = m_PushInstanceList.front();
// 새로운 Render Data 생성..
RenderData* convertRenderData = new RenderData();
// Mesh Data -> Render Data 변환..
m_Converter->ConvertMeshData(originMeshData, convertRenderData);
// Object Type에 따른 리스트 삽입..
switch (convertRenderData->m_ObjectData->ObjType)
{
case OBJECT_TYPE::BASE:
case OBJECT_TYPE::SKINNING:
case OBJECT_TYPE::TERRAIN:
PushOpacityMeshData(convertRenderData);
break;
case OBJECT_TYPE::PARTICLE_SYSTEM:
PushTransparencyRenderData(convertRenderData);
break;
default:
PushUnRenderData(convertRenderData);
break;
}
// 변환한 Mesh Data Pop..
m_PushInstanceList.pop();
}
// Instance Resize..
InstanceResize();
}
// 현재 프레임 진행 중 변경된 Instance 재설정..
void RenderManager::ConvertChangeInstance()
{
// Queue가 비어있다면 처리하지 않는다..
if (m_ChangeInstanceList.empty()) return;
// 현재 프레임 진행중 쌓아둔 변경된 Render Data List 삽입 작업..
while (!m_ChangeInstanceList.empty())
{
// 해당 원본 Mesh Data 추출..
MeshData* originMeshData = m_ChangeInstanceList.front();
// 해당 Render Data 추출..
RenderData* convertRenderData = (RenderData*)originMeshData->Render_Data;
// 해당 Instance Buffer 추출..
InstanceRenderBuffer* instance = m_Converter->GetInstance(convertRenderData->m_InstanceIndex);
if (instance)
{
// 혹시라도 변하지 않은 경우 변환하지 않음..
if (instance->m_Mesh->m_BufferIndex == originMeshData->Mesh_Buffer->BufferIndex &&
instance->m_Material->m_BufferIndex == originMeshData->Material_Buffer->BufferIndex)
{
m_ChangeInstanceList.pop();
continue;
}
}
// Object Type에 따른 List 삽입 제거 작업..
switch (originMeshData->Object_Data->ObjType)
{
case OBJECT_TYPE::BASE:
case OBJECT_TYPE::SKINNING:
case OBJECT_TYPE::TERRAIN:
ChangeOpacityMeshData(originMeshData);
break;
case OBJECT_TYPE::PARTICLE_SYSTEM:
ChangeTransparencyRenderData(originMeshData);
break;
default:
ChangeUnRenderData(originMeshData);
break;
}
// 변환한 Mesh Data Pop..
m_ChangeInstanceList.pop();
}
// 모든 변환이 끝난후 List 재설정..
CheckInstanceLayer(m_OpacityMeshList);
CheckInstanceLayer(m_TransparencyMeshList);
}
- Game Engine 측에서 생성 및 변경한 Object를 해당하는 Instance Layer에 삽입 및 재설정 하는 과정이다.
- 해당 Object의 Type에 따른 Instance Layer List를 따로두어 관리하므로 구분하여 해당하는 List에 삽입한다.
Graphic Resource의 종류
1) Mesh Buffer (Vertex Buffer, Index Buffer)
2) Material Buffer (Diffuse Map, Normal Map, Emissive Map, ORM Map Texture Buffer, Material Factor)
3) Animation Buffer (Model Animation List Buffer)
// Game Engine 측 MaterialBuffer와 대응하는 DirectX 11 전용 Material Data Class
class MaterialRenderBuffer : public RenderResource
{
public:
UINT m_BufferIndex;
MaterialProperty* m_MaterialProperty;
ID3D11ShaderResourceView* m_Albedo;
ID3D11ShaderResourceView* m_Normal;
ID3D11ShaderResourceView* m_Emissive;
ID3D11ShaderResourceView* m_ORM;
};
// Game Engine 측 MeshBuffer와 대응하는 DirectX 11 전용 Mesh Data Class
class MeshRenderBuffer : public RenderResource
{
public:
UINT m_BufferIndex;
UINT m_IndexCount;
UINT m_Stride;
UINT m_Offset;
ID3D11Buffer* m_VertexBuf;
ID3D11Buffer* m_IndexBuf;
MeshProperty* m_MeshProperty;
};
// Game Engine 측 AnimationBuffer와 대응하는 DirectX 11 전용 Animation Data Class
class AnimationRenderBuffer : public RenderResource
{
public:
UINT m_BufferIndex;
UINT m_FrameOffset;
std::vector<UINT> m_AnimationOffset;
ID3D11ShaderResourceView* m_AnimationBuf;
};
// Instance Layer를 관리하기 위한 Render Buffer Class
class InstanceRenderBuffer : public RenderResource
{
public:
UINT m_BufferIndex = 0;
UINT m_Type = 0; // Instance Object Type.. 아마 Shader Type으로 바뀔 것 이다..
MeshRenderBuffer* m_Mesh; // Instance의 기준이 되는 Mesh Buffer..
MaterialRenderBuffer* m_Material; // Instance의 기준이 되는 Material Buffer..
AnimationRenderBuffer* m_Animation; // Instance의 기준이 되는 Animation Buffer..
};
// 동일 Instance를 관리하기 위한 Instance Layer Class
class InstanceLayer
{
public:
void PushRenderData(RenderData* renderData)
{
m_MeshList.push_back(renderData);
}
void DeleteRenderData(UINT index)
{
m_MeshList.erase(std::next(m_MeshList.begin(), index));
}
public:
UINT m_LayerIndex = 0;
UINT m_RenderCount = 0;
InstanceRenderBuffer* m_Instance; // Instancing 기준이 되는 Mesh Data..
std::vector<RenderData*> m_MeshList; // Instancing 기준에 해당하는 Render Data List..
};
- 같은 Resource를 통해 Rendering이 되는 Object끼리 같은 List에 배치를 하기위해 Graphic Engine 측에서 관리하는 Instance Layer이다.
- 해당 Instance Layer 별로 1개의 Batch를 만들어 Draw Call을 최소화 하도록 하였다.
Resource 세분화 및 변경 과정
'DirectX 11' 카테고리의 다른 글
[DirectX 11] Hierarchical Z-Buffer Occlusion Culling (0) | 2022.04.04 |
---|---|
[DirectX 11] Fast Picking (Object ID Picking) (6) | 2022.03.29 |
[DirectX 11] Game Engine & Graphic Engine 분리 및 Render Data 전달 방식 (0) | 2022.03.07 |
[DirectX 11] Hardware Instancing (0) | 2022.03.02 |
[DirectX 11] Deferred Fog (2) | 2022.02.21 |