구조 짜는 걸
좋아하는 엔지니어
회사에서는 그래픽스 엔진을 만들고, 퇴근 후엔 몇 년째 C++ 엔진을 혼자 만드는 중. Entity-Component, Memory Pool, Reflection, Container — 전부 라이브러리 없이 밑바닥부터 직접 짰고, 저장소와 이슈도 다 공개해둠. 남이 만든 걸 갖다 쓰기만 해서는 왜 그렇게 동작하는지 절반밖에 못 배운다고 생각함.
엔진 하나, 라이브러리 여섯
라이브러리는 독립 저장소로 먼저, 엔진은 git submodule로 조립모듈 · 라이브러리 클릭 시 그 자리에서 상세 표시
Engine이 Worker 4개(World · Render · RHI · Asset)와 Task Worker 4개를 띄우고, FrameGate 두 개로 Thread 사이 간격을 최대 3프레임으로 제한함. 입력은 lock-free 3중 버퍼로 World Thread에 넘김.
Threading & 프레임 동기화 →ECS 위에 Scene · Commander를 얹은 Layer. ProxyComponent는 Dirty 표시만 하고, Scene::Flush가 바뀐 것만 스냅샷을 떠서 람다로 Render Thread에 넘김 — World 코드엔 렌더 타입이 한 줄도 없음.
WorldContext 구조 →RenderScene(SceneProxy · MeshBatch)과 RenderGraph(Transform → Shadow → Geometry → Lighting → Debug)로 구성. (mesh, material) 조합 하나당 Draw Call 하나, Instance 행렬은 Compute Pass가 GPU에서 계산함.
RenderGraph Pass 흐름 →그래픽스 API를 부르는 유일한 Layer. CRTP Command로 기록하고 RHI Thread가 실행하며, 백엔드는 RHIContext 뒤의 GLContext · VulkanContext로 갈림. Vulkan 메모리는 Container의 TLSFArena로 서브 할당.
Vulkan · RHI 재설계 →요청은 AssetSystem 큐로, 파싱은 유휴 TaskWorker 4개가, GPU 업로드는 명령만 기록해서 RHI Thread로 넘김. OBJ · MTL · PNG(16비트 포함) · GLSL(#include 펼침) 지원.
Asset 파이프라인 →-
Entity는 ID, 데이터는 Component, System이 쓸 Component 조합은 Node가 묶는 구조. 실행 순서는 사람이 짠 목록이 아니라 의존성 Graph의 위상정렬이 정함.
- Sparse Set · Archetype 대신 Node데이터 이동 · 복제 없이 System별 Component 조합 선언
- Registry<Base> 하나로 통합Manager 5개 + Interface 6개를 걷어내고 생성 · 조회 · 삭제를 범용 Registry로
- 엔진 RenderGraph의 기반RenderGraph가 ECS::Graph를 상속해서 Pass 순서를 정함
- 의존
- Memory · Reflection · Container · Log · CMake
- 사용처
- Game-Engine
-
Unreal GC를 참고한 Mark-and-Sweep. 상태 하나(Idle → Marking → Sweeping → Purging)로 Cycle을 돌리고, 객체 단위 편차가 큰 Purge만 시간 예산을 둬서 여러 프레임에 나눠 실행함.
- 시간 예산 기반 Purge한 번에 못 끝내면 다음 Collect()가 그 지점부터 이어감 — Frame-time Spike 방지
- Compaction 대신 버킷 점유율 할당Accessor가 인스턴스 헤더에 붙어 있어 이동 불가 — 가장 꽉 찬 버킷부터 채워 단편화 관리
- ObjectPtr · RootPtr · RefPtr비활성 Handle · GC Root · 참조 카운트, 용도별 Pointer 3종
- 의존
- Reflection · Container · Log · CMake
- 사용처
- ECS · Game-Engine
-
GENERATE · PROPERTY · METHOD 매크로로 타입 · 멤버 · Method를 컴파일 타임에 등록. Unreal의 UHT 같은 코드 생성기 없이 Macro와 Template만 사용함.
- dynamic_cast 없는 Cast<T>단일 상속 체인만 지원 — 체인을 따라 올라가는 것으로 다운캐스트 판정
- 힙 할당 없는 Container 순회64바이트 SBO Iterator + 함수 Pointer 6개로 만든 수동 vtable
- GC 도달 가능성 판정의 근거Memory의 GC가 PropertyInfo 목록을 따라가며 마킹
- 의존
- Container · Log · CMake
- 사용처
- Memory · ECS · Game-Engine
-
STL 없이 만든 DynamicArray · HashMap/HashSet · List · RTree와 그 아래 할당기(Arena · LinearArena · TLSFArena). Allocator를 Template 인자로 분리해서 자료구조와 메모리 전략을 따로 바꿀 수 있음.
- Robin Hood HashMap100만 개 기준 std::unordered_map 대비 삽입 1.72배 · 조회 1.26배
- TLSFArena — O(1) 할당 · 해제 · 병합FL/SL Bitmap 탐색, 엔진의 VulkanMemory가 그대로 상속
- tombstone 없는 삭제backward-shift로 빈 칸을 물리적으로 없애서 조기 종료 규칙 유지
- 의존
- Log · CMake
- 사용처
- Reflection · Memory · ECS · Game-Engine
-
printf식 C 스타일 로그를
LOGINFO() << ...스트림 스타일로 바꾼 로거. CMake를 뺀 나머지 저장소 전부와 엔진이 이 로거를 씀.- C++ 스트림 스타일 매크로2025.01 C 스타일에서 전환 — 태그 v1.0 → v3.0
- LogType별 저장 · 개수 제한2024.12 — 레벨별로 따로 쌓고 보관 개수를 제한
- 플랫폼별 콘솔 분리Windows · Linux를 LogPlatform으로 분리(2025.03)
- 의존
- CMake
- 사용처
- Container · Reflection · Memory · ECS · Game-Engine
-
6개 저장소가 같은 규칙으로 빌드되도록 뽑아낸 공통 CMake 함수 모음. 각 저장소가 git submodule로 포함해서 씀.
- project_optionsC++17 + 컴파일러별 옵션(MSVC /Zi /MP, GCC · Clang -Wall -Wextra) 통일
- create_library · install_librarySTATIC · SHARED · MODULE · INTERFACE 라이브러리 생성과 헤더 설치
- add_external_librarygit 저장소를 ExternalProject로 받아 플랫폼별 라이브러리 이름 · 확장자로 연결
- 의존
- 없음
- 사용처
- Log · Container · Reflection · Memory · ECS · Game-Engine
외부 — glad · glm · stb · imgui · yaml-cpp · Vulkan SDK(shaderc · SPIRV-Reflect)
라이브러리가 엔진 안에서 실제로 쓰이는 지점
VkDeviceMemory 블록 안을 O(1)로 서브
할당함. 리소스마다 vkAllocateMemory를 부르지 않음.LinearArena 두 개에 번갈아
placement-new — 프레임마다 Reset만 해서 힙 할당이 거의 없음.PROPERTY로 등록 —
Memory의 GC가 그 Property 목록을 따라가며 Entity · Component · Node의 도달 가능성을 마킹함.각 상세 페이지 앞부분엔 구조와 설계 이유, 실제 커밋 기준 개발 히스토리를 정리해둠. 2026.08 전체 코드 리뷰에서 찾은 버그는 GitHub 이슈로 올린 뒤 대부분 바로 고쳤고, 페이지 안 "버그" 섹션에 원인과 함께 남겨둠.
3년 반의 개발 히스토리
7개 저장소의 실제 커밋 기록칸 하나 = 저장소 하나의 한 달 · 진할수록 많은 커밋 · 클릭 시 날짜별 커밋
커밋 히트맵을 불러오는 중…
- 01DX11 엔진 → 라이브러리 분리2023.03 — 2023.10
- 02라이브러리 단발 개선2023.11 — 2025.09
- 03라이브러리 재작성 → 엔진 재시작2025.10 — 2025.12
- 04엔진 뼈대 — RHI · Proxy · 멀티스레드2026.01 — 2026.03
- 05렌더링 — Deferred · 100만 Instance · Shadow2026.04 — 2026.06
- 06Vulkan · RHI 재설계 · 전체 코드 리뷰2026.07 — 현재
- 01DX11 엔진 → 라이브러리 분리2023.03 — 2023.10
엔진 안에서 막힌 문제를 독립 저장소로 분리 - 2023.03.23EngineDirectX 11 엔진 첫 커밋ImGui 에디터 레이어(파일 · 프로퍼티 브라우저)와 프로젝트 저장/불러오기까지 구현.
- 2023.05.13EngineComponentManager 재할당→ Pointer 무효화 문제vector가 재할당될 때마다 Component Pointer가 전부 무효화됨 — 6일 뒤 "memory pool" 아이디어 커밋으로 이어지고, 이게 라이브러리 분리의 출발점이 됨.
- 2023.05.20Memory · Log · ECS엔진에서 라이브러리 3개를 독립 저장소로 분리MemoryPool(05.20) → Log(05.31) → ECS(06.21) 순서. ECS는 SystemManager에 위상정렬을 넣는 데까지(09.02), Memory는 v4.x까지 진행.
- 02라이브러리 단발 개선2023.11 — 2025.09
뜸한 커밋, 라이브러리 단위로 지속 - 2024.01.14ReflectionReflection 저장소 시작TypeCast부터 완성(2024.04) — 프로퍼티 등록보다 안전한 다운캐스트를 먼저 풂.
- 2025.01.27LogC 스타일 → C++ 스트림 스타일 Log로 전환
- 2025.03.31ContainerVariant 하나로 Container 저장소 시작
- 03라이브러리 재작성 → 엔진 재시작2025.10 — 2025.12
커밋이 가장 몰린 구간 · 라이브러리 재작성 후 엔진 조립 - 2025.10.09CMake공통 CMake 저장소 분리Project · Library · ExternalProject 규칙을 한 곳에 모아 라이브러리 6개의 빌드 방식을 통일함.
- 2025.11.30MemoryMark-and-Sweep GC로 재작성옛 MemoryManager · MemoryPtr 구조를 걷어내고 Accessor · Collector · Pointer · Storage로 다시 짬. 순환 참조가 마킹을 무한 루프에 빠뜨리는 문제를 직접 겪고
IsMarked가드로 수정(12.15). - 2025.12.17ECSManager 5개 + Interface 6개 → Object · Registry · Graph전용 Manager를 전부 걷어내고 범용 Registry와 의존성 Graph로 교체한 통짜 재작성.
- 2025.12.18
- 04엔진 뼈대RHI · Proxy · 멀티스레드2026.01 — 2026.03
- 2026.01.27EngineRHI / Proxy 기반 아키텍처파일 45개가 한 번에 바뀐 커밋 — World가 렌더 타입을 모르는 지금의 경계가 여기서 확립됨.
- 2026.02.21EngineWGL Context 소유권을 RHI Thread로 이전→ 멀티스레드 렌더링 첫 성공
- 2026.02.22EngineRHIFrameExecutor(프레임 링) / RHITaskExecutor(업로드) 분리
- 05렌더링Deferred · 100만 Instance · Shadow2026.04 — 2026.06
엔진 커밋이 가장 많았던 석 달 - 2026.04.04EngineFrameGate 도입Thread 동기화와 프레임 데이터 보관의 분리
- 2026.04.17EngineMeshBatch Instancing + Normal Mapping
- 2026.05.21
- 2026.05.24Engine100만 Instance 5.4 → 22 FPS이슈 #25 한 달 추적 — 스칼라 GLM 행렬 곱은 GPU Compute TRS(TransformPass)로, 프레임당 200만 번 돌던 HashSet 삽입은 Dirty 가드 하나로 정리. Release 기준.
- 2026.06.02EnginePoint · Spot LightPhong, GBuffer UInt 비트 패킹
- 2026.06.27EngineDirectional Shadow 안정화텍셀 스냅으로 시머링 제거(06.25), RPDB + Normal Offset + NaN-safe 분모로 acne · 깜빡임 해결(06.27).
- 06Vulkan · RHI 재설계 · 전체 코드 리뷰2026.07 — 현재
- 2026.07.09EngineVulkan 백엔드 착수06.30 Instance · Device 선택부터 시작해 Surface · Swapchain(07.09), Semaphore/Fence 프레임 루프(07.15), GLSL → SPIR-V + SPIRV-Reflect까지. 07.22부터 VulkanMemory가 Container의 TLSFArena(07.21 추가)로 4MB 블록을 서브 할당.
- 2026.08.02전체전체 저장소 코드 리뷰버그 26건 등록, 6일 만에 25건 수정엔진 9 · ECS 5 · Memory 4 · Container 6 · Reflection 2건을 이슈로 올리고 PR 9개로 병합. 남은 1건은 LightingPass Blending(#52).
- 2026.09.03EngineVulkan Buffer · Texture 경로 완성생성 · 초기화 · 갱신 · 리사이즈 책임 분리, Staging Buffer 풀, Buffer → Image 복사, Barrier.
- 2026.10.18EngineRHI 공용 계층 재설계RHISystem 파사드 + RHIContext(GL / Vulkan) 분리, 리소스별 Transition 상태 모델, 상수용 UBO Ring Buffer, GLSL 단일 소스(GL 4.6 · Vulkan 1.1). update/vulkan-rhi 브랜치에서 진행 중.
커리어
현재
2023.03
2022.03
기술 스택
본업과 사이드 프로젝트 전반에 걸쳐 쓰는 것들| 엔진 아키텍처 | Multi-thread 렌더링(4-thread) · RHI 추상화 · Descriptor 기반 API · RenderPass + Proxy / MeshDrawCommand · ECS(Node · System · Graph) · Vulkan Barrier/Transition 모델 |
| C++ 시스템 | C++17/20 · 커스텀 Allocator(Pool · Array · LinearArena · TLSF) · Mark-and-Sweep GC · 런타임 Reflection (TMP/SFINAE) · STL-free Container(Robin Hood HashMap) |
| 그래픽스 API | OpenGL 4.6 / ES · Vulkan 1.1(백엔드 진행 중) · GLSL → SPIR-V(shaderc · SPIRV-Reflect) · RenderDoc · NSight Graphics |