꽃미남 프로그래머 김포프가 창립한 탑 프로그래머 양성 교육 기관 POCU 아카데미 오픈!
절찬리에 수강생 모집 중!
프로그래밍 언어 입문서가 아닌 프로그래밍 기초 개념 입문서
문과생, 비전공자를 위한 프로그래밍 입문책입니다.
jobGuid 꽃미남 프로그래머 "Pope Kim"님의 이론이나 수학에 치우치지 않고 실무에 곧바로 쓸 수 있는 실용적인 셰이더 프로그래밍 입문서 #겁나친절 jobGuid "1판의내용"에 "새로바뀐북미게임업계분위기"와 "비자관련정보", "1판을 기반으로 북미취업에 성공하신 분들의 생생한 경험담"을 담았습니다.
Posted by 알 수 없는 사용자

제가 연재하는 날은 아니지만, 최근 플밍 관련 글도 자주 올라오지 않고, 겸사 겸사 정리하고 있던 내용도 있던 찰나에 불쑥 제가 한번 난입했습니다. ㅎㅎㅎ.

 

다들 DirectX11 공부를 하고 계신지 모르겠군요. DirectX11이 언제 대중화 될지는 모르겠지만, 언리얼4 등의 테크 데모를 보니, 이젠 DirectX9로는 표현력과 성능 상의 한계가 온 것 같다는 생각이 들기도 하고, 향후에는 확실히 더 높은 퀄리티의 그래픽을 만들어내기 위해서는 DirectX11으로 옮겨 갈 것같다는 생각에 저도 DirectX11 공부를 시작했습니다.

하지만, 공부를 시작하면서 뭘 어떻게 시작해야 할지 몰라서, 꽤 헤맸는데요. 그 이유는 일단 관련 서적이 부족해서 기반 지식을 무엇으로 익혀야 할지 턱 막히는 기분이었고, MSDN과 같은 경우에도 DirectX11의 경우에는 DirectX10을 기반으로 DirectX11에 대한 내용이 쓰여져 있기 때문에, DirectX11로만 찾아보면 별로 내용이 없더라구요. 거기에 DirectX11이 DirectX9과 비교해서는 굉장히 많은 부분이 바뀌어서, 머리 속에는 DirectX9에 대한 지식이 들어있는데, 이게 되려 발목을 잡아서, 뭐가 어떻게 바뀌었는지를 일일이 찾아서 확인해야만 하니 학습 속도가 어느 정도까지는 엄청 늦더란말이죠... (물론, 제가 머리가 나빠서 그런거에요.. ㅎㅎ)

암튼 그렇게 그렇게 틈틈히 공부를 한 달 정도 했더니, 아주 약간은 익숙해 졌고, 이제는 아키텍쳐를 조금 이해가 되기도 하고, DirectX9와 비교해서 "아~ 그래서 이렇게 바꿨겠구나~" 라는 것도 조금씩 느끼게 되면서 나름 재밌어지기 시작했습니다. ㅎㅎ

제가 한 달 정도 보면서 DirectX9와 비교해서 헷갈린 부분들이 참 많은데요. 그 중에, Buffer 관련한 것들을 공부하다가 가장 헷갈렸던 것이 바로 이 Lock 관련한 내용인데요! 오늘은 이것을 정리해보려고 합니다.


[DirectX9에서의 Buffer(or Texture)의 Lock 처리]

DirectX9에서는 Lock에 대해서는 뭐 저보다 다들 잘 아실테니 특별히 설명을 할 필요가 없겠죠?! ㅎㅎ. 기억을 되살리기 위해서, DX9의 Lock 에 대해서 간단히 볼까요?

뭐, 간단하게 정리하자면, Static Buffer(or Texture) 이냐 Dynamic Buffer(or Texture)이냐에 따라서 어떻게 Lock을 설정하느냐? 라는 문제가 되겠습니다. 일반적으로 Static Buffer의 경우에는 D3DPOOL_MANAGED / D3DUSAGE_WRITEONLY로 버퍼를 생성하고, D3DLOCK_DISCARD를 사용해서 Lock을 수행합니다. Dynamic Buffer에 대해서는 D3DPOOL_DEFAULT / D3DUAGE_DYNAMIC(+D3DUSAGE_WRITEONLY) 로 버퍼를 생성하고 D3DLOCK_DISCARD(or D3DLOCK_NOOVERWRITE)를 사용하여 Lock을  처리하고요.


[DirectX11에서의 Buffer의 Lock 처리]

먼저, DirectX11에서 Buffer를 만드는 방법부터 보도록 하겠습니다. 이 부분은 아주 크게 다르지는 않습니다. Buffer를 생성할 때 중요한 것은 DirectX9와 동일하고, "Static Buffer를 만들 것인가? Dynamic Buffer를 만들 것인가?"의 문제입니다.

DirectX11에서는 Buffer를 생성할 때, D3D11_BUFFER_DESC 구조체의 Usage와 CPUAccessFlags 항목을 통해서 Static / Dynamic 여부를 결정하게 됩니다. 그럼 Static / Dynamic Buffer에 대해서 어떻게 설정하게 되는지 알아보도록 하겠습니다.

if (dynamic == true)
{
desc.Usage = D3D11_USAGE_DYNAMIC;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
}
else
{
desc.Usage = D3D11_USAGE_DEFAULT;
desc.CPUAccessFlags = 0;
}

Buffer의 성격은 사실 Usage를 어떻게 설정하느냐? 에 따라서 결정이 되구요. 그 성격에 따라서, Lock 이나 다른 옵션들은 거기에 맞추어 주어야 합니다. Usage의 설정은 다음과 같습니다. 

D3D11_USAGE_DEFAULT
  - GPU에 의해 read/write 가능
        D3D11_USAGE_IMMUTABLE
- GPU에 의해 오직 read 할 수 있다. 모든 CPU에서 접근 불가! 반드시 생성할 때 초기화되어야 한다. 생성 후 변경 불가 D3D11_USAGE_DYNAMIC
- GPU에서 read 가능 CPU에서 쓰기 가능, 매 프레임마다 cpu에서 업데이트 할 때 사용. Map을 통해서 업데이트 한다.
D3D11_USAGE_STAGING
- GPU에서 CPU로 데이터 이동(복사)를 지원한다. 즉, 읽기만 가능

이제 위의 코드가 조금 이해가 되시겠지요? Immutable의 경우에는 생성할 때 한번 데이터가 설정되면, 이 버퍼는 변경이 될 수 없습니다. 따라서, Lock을 한다거나 하는 것 자체가 되지 않습니다. 그렇다고 Default로 설정했을 때보다 특별하게 더 나은 성능을 주는 것은 아니라고 합니다. 그래서 일반적으로 Static Buffer의 경우에는 Default로 생성합니다.  여기까지는 약간 다르긴 해도 특별히 DirectX9와 비교해서 쉽게 이해할만 합니다. 

자, 이렇게 Buffer를 생성했다면, 데이터를 채우기 위해서는 DirectX9에서는 Lock을 사용했습니다. 그런데, DirectX11에서는 Lock이라는 함수는 존재하지 않습니다. 대신, Map ~ Unmap 이라는 것과 UpdateSubResource 라는 함수를 이용해야 한다고 하네요. 음... 이제부터가 문제입니다. 과연 언제, Map ~ Unmap을 사용하고, UpdateSubResource를 사용해야 하는 것일까요? 아마도, DirectX9의 경험으로 봤을 때, "Static / Dynamic Buffer에 따라서, 설정하는 방식이 다르지 않을까?" 라는 정도를 유추해볼 수 있겠네요.

* Map ~ Unmap과 UpdateSubResource에 대해서는 설명하기에는 내용이 너무 길어지기 때문에, msdn이나 강좌를 참고하도록 하겠습니다.
- UpdateSubResource : http://msdn.microsoft.com/en-us/library/windows/desktop/ff476486(v=vs.85).aspx
- Map : http://msdn.microsoft.com/en-us/library/windows/desktop/ff476457(v=vs.85).aspx

"How to: Create Vertex Buffer" 페이지를 참고하면 다음과 같이 나와있습니다.

Here are some ways to initialize a vertex buffer that changes over time.

  1. Create a 2nd buffer with D3D11_USAGE_STAGING; fill the second buffer using ID3D11DeviceContext::Map,ID3D11DeviceContext::Unmap; use ID3D11DeviceContext::CopyResource to copy from the staging buffer to the default buffer.
  2. Use ID3D11DeviceContext::UpdateSubresource to copy data from memory.
  3. Create a buffer with D3D11_USAGE_DYNAMIC, and fill it with ID3D11DeviceContext::MapID3D11DeviceContext::Unmap (using the Discard and NoOverwrite flags appropriately).

#1 and #2 are useful for content that changes less than once per frame. In general, GPU reads will be fast and CPU updates will be slower.

#3 is useful for content that changes more than once per frame. In general, GPU reads will be slower, but CPU updates will be faster.

정리가 참 잘 되어있네요. 하지만, 이 정도로는 정보가 조금 부족해서 좀 더 자료를 찾아보았습니다. 

제가 찾던 정보가 여기 다 있네요. 간단하게 정리하면 이렇군요.

  • Default Buffer의 경우, UpdateSubResource를 사용해서만 업데이트 할 수 있다.
    • RenderTarget에서 사용할 때 좋다. 
    • 자주 업데이트 되지 않는 (프레임 당 한번 이하) 텍스쳐에도 괜찮다.
    • Vertex / Index Buffer에서 Dynamic이 아닐 경우, 사용.
  • Immutable Buffer의 경우, 초기에 한번 설정되면 끝난다.
    • Default 보다 좋은 성능을 주는 것은 아니다.
  • Dynamic Buffer의 경우, Map ~ Unmap을 사용한다.
    • Discard, NoOverwrite 사용가능
    • 프레임당 한번 이상 자주 업데이트 되는 Dynamic Texture나 Dynamic Vertex/Index Buffer에 적합하다.
  • Staging Buffer의 경우, Map ~ Unmap만 사용가능
    • 읽기만 가능!
    • Double Buffering 으로 사용한다.
  • Constant Buffer는 UpdateSubResource로 업데이트 한다.
    • Constant Buffer는 예외적으로 매우 자주 업데이트 된다.
    • GPU가 아니라 시스템 메모리에서 시스템 메모리로 카피해서 최소한으로 GPU에 전달하도록 하는 것이 좋다.

여기에 추가적으로 몇 가지 이슈가 더 있습니다. DirectX11에서 지원하는 Multithread Rendering의 경우에는 어떻게 처리할 것인가? 부분 갱신을 할 때에는 어떤 함수를 사용하는가? 같은 문제들을 어떻게 처리해야 하는지도 같이 봐야 합니다. (사실 저는 아직 여기까지 정리를 하지 못했네요.. ㅎㅎ)

[마무리]
지금까지 (제 생각에는) DirectX9 -> DirectX11로 넘어가면서 많이 혼란스러웠던 부분 중에 하나인 Buffer 생성 및 Lock 에 대해서 정리해보았습니다. 아직 저도 공부를 계속 하는 중이라서 많이 알지는 못하지만, 저도 공부하면서 관련 자료(특히, 국내 자료)가 부족해서 시간이 꽤 걸렸던 부분들에 대해서는 가능하면 정리를 해볼 생각입니다.

DirectX11을 공부하면서 느낀 점은 DirectX9에 비해서 API가 참 정리가 잘 되었다는 점과, 자료 검색을 하면서 해외 포럼들을 보니까, 꾸준히 DX10을 보다가 DX11을 넘어와서 크게 어려움이 없어하는 반면에, 국내에는 DX10을 거의 관심을 많이 가지지 않았기 때문에 DX11을 바로 넘어가면서 더 많은 어려움을 느끼는 듯 합니다.   

그래픽스 API를 꾸준히 공부하시는 분들도 많을텐데, 이런 부분들도 좀 더 활발하게 공유가 되었으면 하는 바램입니다. 

참 공부할 게 많은 요즘이군요... ㅎㅎㅎ. 




반응형
,