ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [17] - Device Init
    Graphics 2021. 7. 20. 21:05

    참고 : (6) C++ 3D DirectX Tutorial [Device Init / Fill Screen / Present] - YouTube.

    hw3d/hw3d at T13-End · planetchili/hw3d (github.com)

    이제 Window를 만드는 기초적인 내용과 그래픽 파이프라인, 화면이 어떻게 구성되는지 등을 배웠으니 파이프 라인을 구축할 차례입니다. 전에도 보았던 그림을 보면 D3D는 대부분 Device가 관할합니다. 따라서 가장 먼저 해야 할 일은 Device를 만드는 일이고 D3D11CreateDeviceAndSwapChain으로 구현됩니다.

    보면 이 함수의 Parameter가 쭉 나옵니다. 필요한 오브젝트들을 확인할 수 있고 만들어 줄 필요가 있습니다. ID3D11Device, IDXGISwapChain ID3D11DeviceContext이 세 가지는 Graphics라는 Class를 만들어서 Member로 갖도록 구현합니다. 나머지 포인터들은 어쩌면 좋을까요.

     대체로 Default로 두기 위해(사실, 어떤 기능을 할 지 잘 모르기도 하고 사용할 필요도 없으니까) null pointer 또는 0을 줍니다.

     

    우측의 코드가 Device와 SwapChain을 만들어주는 Factory 함수 입니다. 첫 MSDN을 마음의 여유를 갖고 읽어보면 각 Parameter가 의미하는 바를 잘 알 수 있습니다. 하나씩 확인을 해보면 다음과 같습니다. 

    1. *pAdapter : Device를 만들 때 사용할 Device의 포인터입니다. Default Device를 사용할 거라면 NULL값을 넣어주라고 했으니 그렇게 합니다.

    2. DriverType : 생성할 Driver Type을 일컫습니다. 이건 Enumeration으로 다양한 종류가 있지만 특별한 상황이 아니라면 D3D_DRIVER_TYPE_HARDWARE를 사용하는 편이 좋습니다. 그 이유는 기본적으로 이 옵션이 가장 좋은 성능을 제공하기 때문입니다. HAREWARE DRIVER는 HARDWARE ACCELERATION 기능이 제공이 되면 쓸 수 있고 Hareware에서 제공하지 않는 그래픽 파이프 라인의 소프트웨어까지 사용한다고 합니다. 이 옵션은 HAL 또는 Hareware Abstraction Layer라고도 불립니다. 제가 만들어 낸 말이 아니고 MSDN에 그리 적혀 있습니다.

    3. SOFTWARE : DLL을 위한 핸들인데 Driver Type이 Software라면 반드시 NULL값을 제외한 값을 넘겨주어야 합니다. 그게 아니라면 NULL을 넘겨주라고 하니까 고분고분 따릅니다. 혹시 D3D_DRIVER_TYPE_SOFTWARE를 사용했다면 여기에 가서 확인하는 편이 좋습니다. HMODULE 부분에 자세히 기술되어 있습니다.

    4. FLAGS : Runtime Layer 중 활성화 할 것들을 적습니다. 여러 개의 값을 넣고 싶으면 bitwise OR(|)을 사용해서 넘겨줍니다. 어떤 값들이 있는지 MSDN의 문서를 참조하면 됩니다.

    5. pFeatureLevels : D3D_FEATURE_LEVEL의 배열에 대한 포인터를 말합니다. 문서를 읽어보면 GPU의 버전에 따라서 탑재된 DirectX가 다른데 이것을 제어하기 위해 도입된 개념입니다. GPU가 무슨 DX를 탑재했냐, 그것에 따라서 Shader의 버전도 다르기 때문에 여기에 GPU가 지원하는 DX를 위한 값을 넣는 것입니다. NULL값을 넘겨주면 자체적으로 정해진 값을 넣어주니 그렇게 하도록 합시다. 

    6. FeatureLevels : 5번의 Parameter로 넘겨준 원소의 개수를 여기에 적습니다.

    7. SDK Version : 설명을 할 것도 없이 D3D11_SDK_VERSION을 사용하라고 명시되어 있습니다.

    8. *pSwapChainDesc : SwapChain의 Description의 구조체에 대한 포인터를 요구합니다. 이 Description이라는 개념은 앞으로 계속 나올 것이며 D3D에서 무언가를 Setup하는데 자주 사용되는 방법입니다. 특히, HWND의 정보도 여기에 함께 묶여 보내질 것입니다. 

    이런 식으로 Description Struct를 구성합니다. 그리고 이 구조체를 &sd넘겨주는 겁니다. 각각의 요소에 대한 설명은 MSDN 문서에 잘 설명되어 있습니다. 

     

    기본적으로 좌측과 같은 형태로 이루어져 있습니다. 필요한 대로 설명서를 읽으며 채워나가면 됩니다. 

    DXGI_MODE_DESC는 Back Buffer의 Display mode에 대한 설정을 합니다. 

     

    WidthHeight는 Resolution에 대한 값입니다. 이걸 0으로 두면 활성화 된 Window 즉, 만들어 둔 Window의 값을 받아서 사용하게 됩니다. 

    RefreshRate는 구조체인데 Pixel과 Channel에 대한 설명이면서 hertz 단위를 실수[Rational Number]로 기입하기 위해서 이 구조체를 사용합니다. 

    Format은 Enumeration 구조체의 요소를 기입합니다. 내용이 방대하기 때문에 여기에 다 적을 수는 없으나 데이터의 Format을 넘겨줍니다. RGBA 8bits를 넘겨주겠다는 것이 느껴지십니까? 아님 말고.

    Scanline orderScan line - Wikipedia위키피디아를 조금 참고 했습니다. 여기서 말하는 것과 완전히 일치하는 내용은 아니지만 화면에 데이터를 표시하기 위해 논리적인 커서가 움직이는 형태를 정의한다고 이해하면 됩니다. 과거 CRT 모니터의 유산인 것 같습니다. Unspecified 해도 별 문제가 없어 보입니다. 

    Scaling은 전에 그래픽 이론을 할 때 다루었지만 느낌이 조금 다릅니다. 이건 화면을 확대하거나 줄였을 때, 모니터의 Resolution을 어떻게 처리할 것인지 묻는 겁니다. 당장에 우리 알 바는 아니니까 Unspecified하게 두기로 합니다. 이렇게 Buffer Description은 해결했습니다. Sample Description으로 넘어갑니다. 이 요소는 Multi-Sampling에 대한 요소입니다. Sample : Sample (graphics) - Wikipedia에 대한 짧은 설명이 있습니다. bit를 모아 놓은 걸 그냥 Sample이라고 이름을 붙였나 봅니다. 그리고 Multi-Sampling은 Aliasing을 줄여주는 과정이라고 설명되어 있습니다. 이 부분은 아예 따로 빼서 공부하는 편이 낫습니다.ㅌ

    DXGI_USAGE는 포괄적으로 이 버퍼를 어디에 어떻게 쓰겠는가를 결정합니다. 지금 여기에서는 Back Buffer에 대한 CPU의 접근 권한 옵션 등을 정의합니다. Back Buffer는 Shader의 Input으로도 쓰일 수 있고 Render Target의 Output으로도 쓰일 수 있다고 합니다. 

    BufferCount는 사용된 buffer의 개수를 적습니다. Back과 Front 두 개가 있는데 왜 2라고 안 적을까! 그냥 그렇다고 합니다. Chili도 "Little confusing... but anyway"라고 합니다. 

    그리고 마침내 OutputWindow가 HWND가 올 자리입니다. 이 모든 그래픽에 대한 정보를 출력할 Window를 넣어 줍니다. Windowed는 중요한 건 아니고 창 모드로 쓸 것 인지를 묻고 있는 겁니다. 그러고 싶으니까 True를 줍니다.

    이제 SwapEffect라는 요소가 등장하는데 화면에 뭔가를 출력하고 이 픽셀들을 어떻게 처리할지 결정하는 겁니다. 대부분의 상황에서 이 DXGI_SWAP_EFFECT_DISCARD가 준수한 성능을 내기 때문에 이것을 채택하도록 합니다. 마지막의 Flags는 추가적인 옵션인데 MSDN 문서를 참고 하는 편이 수월합니다. 

    기억을 더듬어 보면 ChiliWin.h에는 각종 Macro와 설정들이 있었습니다. 따라서 그것들을 끌어오고 d3d11 header file을 탑재합니다.

    Graphics는 각 Window마다 존재하긴 하는데 이걸 복사해서 쓰지는 않을 테니까 복사/할당 생성자들을 비활성화 합니다.

    그리고 Graphics의 Description을 Setup할 때, HWND가 Output이 될 Window라고 했으니 이 Class는 Window의 embedded되는 것이 합리적입니다. 

    그런데 단순히 다음과 같이 구성하면 문제가 발생합니다.

    Graphics는 Parameter로 반드시 HWND를 요구합니다. HWND는 어디서 Initialise되는가 하면 다음 부분입니다.

    애초에 Graphis는 Window Class의 HWND의 인스턴스인 hwnd를 무조건 받아야 합니다. 그런데 그렇게 하기 위해서는 member initialise list를 사용해야 하는데 위에서 보는 것처럼 HWND가 실제로 생성 되는 곳은 Ctor의 중앙 부분이기 때문에 이 방법은 사용할 수가 없습니다. 대신에 unique pointer와 Effective C++에서 배웠던 방법을 사용하면 이 문제를 극복할 수 있습니다.

    이제 Window의 Handle을 GFX에게 넘겨만 주면 마무리 됩니다.

    빌드 하면 문제가 없습니다. 혹시 Linking Error가 발생할 수 있는데 그건 <d3d11.h>을 include만 했기 때문에 발생했을 가능성이 높습니다. header엔 함수의 declaration들이 있는데 이들을 올바르게 쓰려면 include만 할 것이 아니고 project에 Link를 해주어야 합니다. 보통 Solution의 Property에서 설정할 수 있고 저는 이 방법을 가장 좋아하지만 코드 상에 #pragma commet (lib, "d3d11.lib")을 추가해주는 것 만으로도 해결이 됩니다.

    끝난 것이 아닙니다. 이걸 Build하고 실행하면 기존과 달라진 것이 아무것도 없습니다. Back Buffer를 Front Buffer에 Present하는 일을 해야 합니다. Flipping이라고도 합니다. 그걸 EndFrame이라는 함수에 작성하기로 합니다. 이 함수가 모든 것들의 전제[Prerequisite]입니다. 왜냐하면 어떤 픽셀이든 출력을 안 하면 없는 것과 마찬가지이기 때문입니다. 

    그리고 App에서 이 함수를 호출해주고 다시 Build해보면 마침내 새로운 현상을 목격할 수 있습니다.

    까만 화면을 볼 수 있습니다

    이처럼 즐거울 수가 없습니다. 새롭게 뭔가 만들었습니다. 근데 왜 하필 검은색일까요? 모르겠습니다. 일단은 마침내 작성한 코드로 그래픽 카드가 무슨 짓을 하고 있다는 것을 알 수 있게 되었습니다. 정말 그런가? 한 번 테스트를 해보도록 하겠습니다. 단순히 Back buffer를 비우거나 특정한 색으로 채우는 실험입니다. 이를 위해서 사용할 함수는 ClearRenderTargetView입니다.

    보아하니, RenderTargetView라는 것이 필요한 모양입니다. 이전에 SwapChain은 단순히 Texture나 Pixel, Frame Buffer 등등 Resource들의 집합체라고 했습니다. 당연한 수순이기도 합니다. SwapChain은 Device로부터 나왔습니다. 그런데 Device는 Resource를 분배하는 역할을 합니다. 그 분배한 Resource 중엔 RenderTargetView가 있고 이것은 SwapChain을 이루고 있는 요소이기 때문에 SwapChain으로부터 어떤 함수를 호출하여 이 RenderTargetView에 접근할 수 있습니다. 

    가장 먼저 해야 하는 일은 SwapChain의 Subresouce인 Texture에 대한 접근 권한을 얻는 것입니다. 그리고 그것이 Back Buffer입니다.

    데이터 타입이 ID3D11Resource입니다. 이렇게 SwapChain에 UUID를 인터페이스에 넘겨주면 Back Buffer에 대한 Handle을 받을 수 있습니다. 이렇게 ID3D11Resource의 포인터에 Back Buffer에 대한 Handle을 받으면 이 포인터를 RenderTargetView를 만들기 위해 사용할 수 있습니다.

    그래서 첫 번째 Parameter에 RenderTargetView를 만들 포인터를 넘겨 주고 추가적인 Configuration을 위한 Description을 넘겨줄 수도 있으나 Default로 설정할 수 있으니 nullptr를 넘겨줍니다. 그런데 이 RenderTargetView의 포인터를 Hold할 추가적인 포인터가 필요한데 그것을 만들어 줘야 합니다. 

    마지막으로 더 이상 pBackBuffer는 필요가 없으니 메모리를 해제해줍니다. 당연한 수순이지만 pTarget역시 GFX가 제거될 때 Release()를 호출하는 편이 좋습니다. 이제 Back Buffer를 비우거나 색을 채우는 함수를 만들면 됩니다.

     그리고 App에서 이 함수를 호출만 해주면 색이 변화하는 것을 확인할 수 있습니다. 값이 정적이면 실시간으로 되는지 알 방법이 없으니 주기 함수를 사용하여 변화하는 모습을 볼 수 있다면 좋을 것입니다.

    잘 됩니다.

    'Graphics' 카테고리의 다른 글

    [19] - ComPtr and Smart Pointer  (0) 2021.07.21
    [18] - Debug Layer Diagnostics  (0) 2021.07.21
    [16] - Swap Chain  (0) 2021.07.19
    [15] - COM object  (0) 2021.07.19
    [14] - Vertex processing - 2  (0) 2021.07.17
Designed by Tistory.