어셈블리어 (180.♡.243.82)
2025년 12월 25일 PM 06:02 · 수정됨(01. 15. 00:29)

https://github.com/kidleon/MarkFramework2
- D3D11 SDK를 이용하여 렌더러를 제작 하는 중입니다. 화면에 파란색 하나 띄울려고 정말 별 생쑈를 하는 기분입니다.
- 단순히 렌더링만 되게 하면 간단하겠으나.. 실제로 게임으로써 효율적인 렌더러를 제작하기 위해서는 메모리 관리와 리소스 관리가 가장 중요 합니다.
- D3D11은 D3D9과 달리 RenderState를 동적으로 바꿀 수 있는 것이 아닌 아예 객체로 바뀌었습니다. 그말인 즉슨 한번 정한 렌더링 상태 값들은 다시 바꿀수 없다는 겁니다. 값을 하나만 바뀌어도 또 다른 객체를 생성해야 합니다. 그래서 각각의 상태값(구조체)를 통채로 Hash(fnv해쉬)화 시켜서 가지고 있다가 해시값만 비교해서 이미 생성된 렌더 상태를 반환하는 방식으로 작성했습니다.
- D3D9에서는 그러한 일들을 드라이버가 해주거나 D3D9 내부에서 스스로 처리 했다면 D3D11에서는 프로그래머가 직접 관리해야 하는 방식으로 빠진 것 같습니다.
- D3D11에서는 9과 달리 D3D11Context와 D3D11Device 두 종류의 객체로 나뉘어집니다. 9에서는 Device하나만 있었거든요. Context는 Thread Safe하지 않치만 Device는 Thread Safe합니다. 리소스 생성은 D3D11Device, 렌더링 상태 및 렌더링 관련 함수들은 D3D11Context가 담당 합니다.
- D3D11에서 렌더링 쓰레드를 따로 두기 위해서는 세가지 방법이 있습니다.
1. 완벽히 다른 쓰레드에 D3D11Context를 격리 시키고 렌더링 하는 방법
2. D3D11에서 제공하는 DefferedContext를 이용하여 여러개의 Job Thread가 RenderCommand를 채워주는 방법
3. 1번과 2번을 합쳐서 구현하는 방법..
- 첫번째 버전은 1번의 방법으로 좀 가벼운 게임을 목표로 합니다. 물론 아예 렌더링 쓰레드를 따로 두지 않고 싱글쓰레드로 만들어도 됩니다.
- D3D11의 공식적인 방법은 2번입니다.
- 쉐이더 역시 한번 컴파일 되면 그 후에는 캐싱해서 쉐이더 생성 요청이 들어 올 경우 캐싱된 쉐이더를 반환 합니다.

- D3D11 외부에서는 D3D11 객체나 API에 직접 접근하는 방법은 존재하지 않습니다. 심지어 쉐이더 API 역시 직접적인 접근을 차단할 예정입니다. 모든 것은 ISurfaceMaterial 이라는 인터페이스를 통해서만 컨트롤이 가능 하도록 작성 할 예정입니다.
- D3D11에서는 D3D11SurfaceMaterial이라는 Class로 ISurfaceMaterial을 상속 받아 실질적인 구현을 합니다.
- D3D11SurfaceMaterial 내부에서는 여러 상태 데이터를 저장하는 D3D11SurfaceMaterialBlock과 그 Block을 Pooling하는 D3D11SurfaceMaterialBlockPool이 존재하여 런타임시 메모리 할당/해제 이슈를 최소화 합니다.
- 인터페이스 프로그래밍의 경우 COM의 기본적인 방식이고 D3D역시 그러하게 구현되어 있는데요. 단점도 존재 합니다. dll로 빠져있다 보니 컴파일시에 컴파일러가 인라인 처리나 CPU의 슈퍼 스칼라에 맞는 명령어 재배치가 힘들어 국소적인 최적화만 가능한 단점이 있습니다. 그럼에도 클라이언트의 컨텐츠 구현부와 완전한 모듈 독립을 위해서 이 방식을 끝까지 밀고 나갑니다.
- 어짜피 dll내에서 컴파일러의 최적화의 도움 받을 수 있는건 다 받을 수 있으니 뭐 큰 차이는 없을겁니다.

- D3DBuffer는 사실 렌더링의 가장 기본적인 리소스 이기도 합니다. 형태만 다를뿐 CUDA나 Vulkan, Metal, OpenGL 모든 형태의 GPU를 사용하는 SDK에서 존재 하는 VRAM을 사용하는 GPU버퍼 입니다.
- 종류 별로 크게 나누면 Static과 Dynamic이 존재 합니다. Static은 VRAM에만 메모리를 할당 하여 한번 지오메트리 데이터를 업로드 하면 '왠만하면' 안바꾸는 형태에 사용 합니다. 배경 그래픽 데이터들이 여기에 해당됩니다.
- Dynamic은 매 프레임 계속 변화하는 그래픽 데이터들에서 주로 사용합니다. Dynamic은 옵션마다 좀 다르긴 하지만 기본적으로 D3D11 SDK내부에서 시스템 메모리에도 할당하고 GPU에도 할당합니다. 그리고 그래픽 드라이버가 이 두 메모리가 내부적으로 데이터 IO가 일어날 때 일종의 동기화(전송)를 합니다.
- 계속 변화 하는 그래픽 데이터는 파티클(이펙트), 캐릭터(CPU스키닝), UI등등이 여기에 주로 포함됩니다.
- 게임에서는 보통 3가지의 접근 방법으로 GPU 메모리 버퍼를 할당 합니다. 언리얼, 유니티도 마찬가지 입니다.
1. Dynamic Buffer로 크게 하나 잡아서 매 프레임 리셋하고 프레임이 시작되면 그때그때 큰 버퍼를 쪼게 가며 할당받아서 데이터를 채워넣고 렌더링 큐에 Send하는 방식 입니다. 메모리 할당/해제 비용이 없어서 매우 빠릅니다. 다만 버퍼를 그때그때 Lock걸고 데이터를 버퍼에 copy해야 합니다. 또한 Lock을 걸 때 해당 버퍼가 GPU상에서 '아직' 렌더링용으로 사용되고 있으면.. GPU 스톨에 온갖 에러나고 프로그램이 죽을 수도 있습니다. 그래서 여러개 만들어 놓고 사용 해야 합니다. 해당 방식은 nVidia의 TransientBuffer라는 방식으로도 유명합니다.
2. 또 하나의 방법은 좀더 GPU 친화적인 방식으로 Static으로 커다란 버퍼를 생성 한 후에 필요할 때 버퍼를 할당 받아서 그래픽 데이터를 업로드 하는 방식입니다. 이는 GPU VRAM 단편화를 막기 위해서 사용 하는 방식입니다.
이것은 위의 TransientBuffer 방식과 달리 매프레임 업데이트 하는 것이 아닌 생성할 때 한번만 지오메트리 데이터를 업로드 합니다. 버퍼에 Lock거는 방법이 아니라 UpdateResources라는 API를 통해서 일종의 '업로드API'를 이용하여 데이터를 부분 적으로 업데이트 합니다.
이 방식은 Lock거는 방법보다는 속도가 느립니다. 왜냐하면 데이터를 API를 통해 업데이트 한다고 하면 해당 데이터는 그래픽 드라이버 내부에서 똑같은 버퍼를 할당 하여 해당 버퍼가 렌더링이 끝나면 그때서야 업데이트 합니다.
뭐.. 드라이버 내부 풀링을 할 테니 할당 비용은 비싸지 않겠지만 드라이버는 안전을 위해 대기하므로 속도 자체가 빠를 수는 없습니다. 그럼에도 안전하고 워낙 찰나의 순간이라 생성시에는 문제가 안됩니다.
그래도 이방식을 주로 쓰는 이유는 버퍼를 무수히 할당하면 예전에는 그래픽 드라이버가 죽었습니다 -_-;;
좀 나아졌다고 했지만 단독 버퍼가 많을 경우 렌더링시 바인딩 할 때 비용도 올라가고 그래픽 드라이버 내부에서 관리 비용이 점점 올라가게 됩니다.
3. 그냥 개체 하나에 하나의 버퍼

- 2번을 말씀드리자면 static을 크게 잡는 방식은 메모리 할당/해제시 '관리'가 필요로 합니다. 메모리를 효율 적으로 쓰기 위해서는 해제시 다시 해제된 메모리를 기존 버퍼에 병합시켜서 메모리 leak이 나지 않게 해줘야 하기도 합니다. 굉장히 복잡하죠. 하지만 GPU Vram은 직접적인 관리가 불가능 합니다.
- 그래서 위와 같이 가상 힙 관리 코드를 작성해서 할당한 ID3D11Buffer와 같은 양의 크기를 설정하고 그걸 가상으로 쪼게서 Offset만 반환하고 해제 시 가상으로 병합 하는 방식을 사용합니다. 리눅스 커널이나 GPU메모리 관리시 주로 사용 하는 방법입니다.
- malloc이나 new를 하게 되면 내부적으로 메모리를 쪼게서 주고.. 해제하면 다시 병합하고 이 과정이 거치게 되는데.. 복잡한 방식 보다는 단순하지만 빠르고 간단한 구현의 방법으로 Buddy 할당 방식으로 구현 했습니다. 자료도 많고 문서를 봐도 이해하기가 어렵지 않습니다. 단순히 XOR 연산 한번이 핵심 알고리즘이라 매우 빠릅니다.
- 이진 트리 방식으로 구현하기에 2의 제곱으로만 메모리가 할당되어 GPU친화적인 16바이트 정렬을 따로 해줄 필요 없는 점 역시 GPU메모리 가상 매핑 할당자로 매우 쓸만 합니다.
------------------------------------------------------------------------------------------------
- 이제 거의 준비가 되었고 다음은 렌더큐를 제작하고 화면에 띄울 차례네요.. 지금까지 만든 모든 함수의 디버깅은 그때몰아서 하는걸로 (-_-;)
- 원래 이렇게 만들면 안되고 하나 만들고 테스트하고 하나 만들고 테스트 해야 하지만.. 뭐 개인 프로젝트니까..ㅎㅎ 알게 뭐겠어요.. 내맘이지.. ㅎㅎ
읽어주셔서 감사합니다.
댓글 (7)
- J
j5nqw
25.12.25 · 125.♡.92.159
direct9 이후로 direct 공부를 안했었는데, 11 공부해보고 싶네요ㅎ -
비비쥬얼씨뿔뿔
→ j5nqw 작성자
25.12.25 · 180.♡.243.82
저도 직접 작성 하는건 첨인데요. 최종적으로는 D3D12로 가야 하지만 D3D12는 워낙 로우하고 직접 관리해야 하는 부분도 많아서 D3D11로 시작하는게 나을것 같아요. 호환성도 여전히 유지되고요.. D3D12도 D3D11 이랑 비슷한 인터페이스라..
D3D9하셨으면 연세(?)가 좀 되실 것 같은데.. 함 해보세요.. 재미있어요 ㅎㅎ -
DDuDoong
25.12.26 · 39.♡.255.203
대단하시다고 밖에 드릴 말이 없네요. ^^ -
SSDK
25.12.26 · 127.♡.0.1
대단하시다고 밖에 드릴 말이 없네요. ^^(2) -
에에놀미타
25.12.27 · 211.♡.112.55
크로스플랫폼이라면 bgfx 같은 렌더러 엔진 쓰는 게 좋을 것 같네요 -
비비쥬얼씨뿔뿔
→ 에놀미타 작성자
25.12.27 · 180.♡.243.82
개인 프로젝트라 남의 코드는 특수목적이 아닌이상 왠만하면 안쓰는 걸로 합니다. -
박박달냥
01.15 · 218.♡.121.249
이게다 무슨소리야 ㄷㄷ
댓글을 작성하려면 이 필요합니다.