이것저것 구현했는데 이 부분들(마우스 피킹, 그림자 등) 도 수정이 필요하기 때문에 일단 제쳐두고 큰 변경점에 대해서 작성한다.
Render Thread
Render Thread로 렌더링 관련 작업을 전부 위임하는 구조를 원했기 때문에 엔진 구조를 다시 손봤다.
일단 Buffer, PipelineState, RenderPass 등에 각각 있던 Bind()와같은 함수들을 전부 RenderContext라는 객체로 이동했다.

이제 이 RenderContext의 함수들을 Renderer가 호출하게 된다.

Renderer에서 함수를 호출하면 위에서 구현한 Enqueue함수로 렌더스레드에 명령을 보내게 된다.

렌더러의 함수들은 static 함수로 엔진 어디에서든 간편하게 호출 할 수 있는 구조를 목적으로 했다.


어쩌다 RenderContext 구조로 변경하게 되었나라고 하면 기존의 객체에서 Bind를 호출하는 방식보다
DX11처럼 Context에 이런저런 VB, IB, ShaderResource, Shader 등을 다 바인드 해놓고 드로우콜을 하는 부분이 좀 더 이해하기 쉽다고 생각해서 여기서 아이디어를 따왔고
이게 엔진 코드상에서 렌더링의 흐름을 파악하기도 더 쉽고 보기에도 깔끔해 보인다고 생각해서 바꾸게 되었다.
그리고 렌더스레드를 위한 함수로는 Enqueue가 있다.

대부분의 렌더러의 함수는 이제 이 Enqueue함수를 거치게 된다.
RenderThread를 사용하기로 설정한 경우(useRenderThread = true)
Queue에 명령을 push하게 되고 아닌 경우 바로 명령을 실행한다.

명령은 RenderCommandQueue라는 객체에 추가되는데 std::function을 사용한다.
AI말로는 std::function을 사용하게 되면 오버헤드가 크다고 해서 나중에 성능상으로 크게 문제가 되어 보이면
수정할 계획이다.



Execute는 RenderThread에서 호출되는 함수로
1. Renderer의 Submit 함수는 RenderThread에 현재 프레임에서 기록한 함수들과 이 함수들을 다 실행했을 때 실행될 콜백 함수를 제출한다.
2. RenderThread는 제출된 Queue가 없으면 계속 대기하다가 Renderer에서 Submit을 통해 명령 큐를 전달하면
wait상태에서 깨어나 제출된 큐를 스레드에서 실행하게 된다.
3. Execute가 끝나면 아까 받은 onComplete 콜백을 실행해서 메인스레드에게 이 커맨드 큐는 이제 비었다는 상태로 변경하도록 한다.

렌더 스레드를 사용했을 때 유의미한 프레임 차이를 낸 결과이다.
Material
Material도 변화가 많았는데 Material은 자기가 바인딩할 Texture2D의 핸들만 가지고 있고
이걸 렌더링파이프라인에 바인딩해주는 객체는 이제 Renderer 클래스의 BindMaterial()함수가 담당한다.
이제 TextureCube, ConstantBuffer는 더이상 Material에 필요하지 않게 되었다.
원래는 Material을 Skybox그리기 같은 곳에도 사용해야 했는데 이제 Entity의 MeshRenderer만을 위한 객체가
되었다.
구조를 단순화 할 수 있었던 이유는

다른 API는 이미지처럼 개별 슬롯 x번에 리소스 1을 바인딩 해라 이런식의 커맨드가 가능한데

Vulkan은 튜토리얼에서 배운 방식이 리소스를 바인딩 할 때 DescriptorSet이라는 클래스에
이번에 바인딩할 데이터(카메라라던가. 빛이라던가. 텍스쳐 정보라던가) 한 번에 넣고 보내는 방식을 사용한다.
이 부분을 셰이더 리소스를 Set번호로 묶어서 업데이트 빈도를 조절 할 수도 있다고 하는데
ex) Scene 데이터는 set = 0, Mesh별 데이터는 set = 1
이런식으로 짜려면 또 다른 API와 범용으로 쓰일 수 있는 구조를 생각해야 하기 때문에 굉장히 짜증나는 작업이 다시 진행되어야 함을 의미했다.
근데 아무리 생각해도 이렇게 불편한 방식을 개선해주지 않았을까? 라고 생각해서 계속 찾아봤는데
벌칸이 업데이트 되면서 DX11이나 다른 API처럼 슬롯 0에 TextureA바인딩 이런식의 작업을 가능하게 해주는 PushDescriptorSet이라는 함수를 추가해줬다는 걸 알았다.

좀 알아보니까 push하는 용량에 제한이 있다고는 하는데 아직 그런 부분에 대해서 걱정할 만큼 거창한 프로젝트는 아니라고 생각했기 때문에 바로 적용했다.
원래는 자기가 바인딩해야할 Texture를 포인터로 가지고 있었지만 이제 Texture2D의 uuid를 가지게 된다.

cache를 따로 둔 이유는 매번 Asset Manager에서 handle 기반으로 탐색해서 Texture2D를 가져오는 것이 비효율적이라고 생각했기 때문에 한번 로드하면 계속 쓸 수 있는 구조로 구상했다.

그리고 이름기반으로 TextureHandle을 설정하는 함수를 만들었고
아래의 LoadMaterialAsset은 Material을 바인딩하려고 했을 때 cache가 nullptr이면 AssetManager에서 해당 Asset을 찾아올때 사용할 수 있도록 했다.

일단은 여기까지만 작성하고 다음글은 렌더링 스레드 구조를 작성하면서 생긴 문제들을 포스팅 할 것이다.
'개인 공부 및 프로젝트 > Daydream Engine' 카테고리의 다른 글
| 자체 게임엔진 DevLog - 수학 라이브러리 (0) | 2026.04.20 |
|---|---|
| 자체 게임엔진 Devlog - Vulkan 렌더 스레드 문제 해결 (0) | 2026.04.08 |
| 자체 게임엔진 Devlog - 13 (0) | 2025.11.20 |
| 자체 게임엔진 Devlog - 12 (0) | 2025.10.27 |
| 자체 게임엔진 Devlog - 11 (0) | 2025.10.01 |