오늘은 DirectX 예시 프로젝트 수정 중 발생한 오류 원인을 찾아보았습니다.

 

결과적으로, 오류의 원인을 밝힐 수는 없었지만 한가지 오류는 찾았습니다.

https://docs.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgifactory-createswapchain

 

IDXGIFactory::CreateSwapChain (dxgi.h) - Win32 apps

Creates a swap chain.

docs.microsoft.com

https://gamedev.stackexchange.com/questions/149822/direct3d-12-cant-create-a-swap-chain

 

Direct3D 12 can't create a swap chain

I'm learning DirectX12 and I'm trying to create a simple application that clears the screen with a solid color, but I'm stuck in Direct3D initialization. I can't create the swap chain and the DXGI

gamedev.stackexchange.com

CreateSwapChain에서 문제가 발생했는데 최신 버전에서는 이 함수 사용을 지양한다는 내용입니다.

그래서 다른 방식으로 구현을 했는데, 여전히 문제가 발생하고 있습니다.

 

그래서 좀 더 근본적인 부분을 먼저 수정해보려 합니다.

바로 Window 생성 부분입니다.

DirectX 예시 코드와 Windows Template 예시 코드는 각각 다른 Main Window를 생성하고 있습니다.

우선 이 부분을 해소하기 위해 코드 분석을 해보았습니다.

 

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
                     _In_opt_ HINSTANCE hPrevInstance,
                     _In_ LPWSTR    lpCmdLine,
                     _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: Place code here.

    // Initialize global strings
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_DIRECTX12EXAMPLECODE, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // Perform application initialization:

    if (!InitInstance (hInstance, nCmdShow))
    {
        return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_DIRECTX12EXAMPLECODE));

    //D3DApp::Run
    MSG msg{ };

    // Main message loop:
    try
    {
        while (GetMessage(&msg, nullptr, 0, 0))
        {
            if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
        return (int)msg.wParam;
    }
    catch (DXException& e)
    {
        MessageBox(nullptr, e.ToString().c_str(), L"HR Failed", MB_OK);
        return 0;
    }
}

Main 함수에 해당하는 WinMain입니다.

LoadStringW를 통해 창 이름과 Class 이름을 지정합니다.

 

그리고 MyRegisterClass로 Window Class를 등록합니다.

//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;

    wcex.cbSize = sizeof(WNDCLASSEX);

    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_DIRECTX12EXAMPLECODE));
    wcex.hCursor        = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW+1);
    wcex.lpszMenuName   = MAKEINTRESOURCEW(IDC_DIRECTX12EXAMPLECODE);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    return RegisterClassExW(&wcex);
}

이중 WndProc는 자체적으로 선언한 함수입니다.

//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE: Processes messages for the main window.
//
//  WM_COMMAND  - process the application menu
//  WM_PAINT    - Paint the main window
//  WM_DESTROY  - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_COMMAND:
        {
            int wmId = LOWORD(wParam);
            // Parse the menu selections:
            switch (wmId)
            {
            case IDM_ABOUT:
                DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                break;
            case ID_EXAMPLE_CHAPTER6:

                break;
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, message, wParam, lParam);
            }
        }
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(hWnd, &ps);
            // TODO: Add any drawing code that uses hdc here...
            EndPaint(hWnd, &ps);
        }
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}

Class가 등록이 되면 그 다음은 Instance를 초기화 합니다.

//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
//   COMMENTS:
//
//        In this function, we save the instance handle in a global variable and
//        create and display the main program window.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
   hInst = hInstance; // Store instance handle in our global variable

   HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);

   if (!hWnd)
   {
      return FALSE;
   }

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   return TRUE;
}

그 아래가 Main Mesage Loop입니다.

 

다음은 DirectX 코드에서 해당 부분입니다.

bool D3DApp::InitMainWindow()
{
	//--------------RegisterClass
	WNDCLASS wc;
	wc.style = CS_HREDRAW | CS_VREDRAW;
	wc.lpfnWndProc = MainWndProc;
	wc.cbClsExtra = 0;
	wc.cbWndExtra = 0;
	wc.hInstance = mhAppInst;
	wc.hIcon = LoadIcon(0, IDI_APPLICATION);
	wc.hCursor = LoadCursor(0, IDC_ARROW);
	wc.hbrBackground = static_cast<HBRUSH>(GetStockObject(NULL_BRUSH));
	wc.lpszMenuName = 0;
	wc.lpszClassName = L"MainWnd";

	if (!RegisterClass(&wc))
	{
		MessageBox(0, L"RegisterClass Failed.", 0, 0);
		return false;
	}


	//--------------------------InitInstance
	RECT R = { 0, 0, mClientWidth, mClientHeight };
	AdjustWindowRect(&R, WS_OVERLAPPEDWINDOW, false);
	int width = R.right - R.left;
	int height = R.bottom - R.top;

	mhMainWnd = CreateWindow(L"MainWnd", mMainWndCaption.c_str(), 
		WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, width, height, 0, 0, mhAppInst, 0);
	if (!mhMainWnd)
	{
		MessageBox(0, L"CreateWindow Failed.", 0, 0);
	}

	ShowWindow(mhMainWnd, SW_SHOW);
	UpdateWindow(mhMainWnd);

	return true;

}

주석으로 대략 나눠놓았습니다.

InitMainWindow( 안에 RegisterClass와 InitInstance 부분이 같이 존재합니다.

이를 각각의 함수로 나누고, 양쪽 코드에서 필요로 하는 기능을 모두 갖추도록 하고자 합니다.

그리고 Main에서는 아무런 작업 없이, 메뉴의 Event에서 화면 출력을 교체하고자 합니다.

 

이 중 InitInstance는 다음 과정을 거쳐서 호출이 됩니다.

 

bool D3DApp::Initialize()
{
	if (!InitMainWindow())
	{
		return false;
	}
	if (!InitDirect3D())
	{
		return false;
	}

	OnResize();
	return true;
}

 

bool BoxApp::Initialize()
{
	if (!D3DApp::Initialize())
	{
		return false;
	}

	ThrowIfFailed(mCommandList->Reset(mDirectCmdListAlloc.Get(), nullptr));
	BuildDescriptorHeaps();
	BuildConstantBuffers();
	BuildRootSignature();
	BuildShadersAndInputLayout();
	BuildBoxGeometry();
	BuildPSO();

	ThrowIfFailed(mCommandList->Close());
	ID3D12CommandList* cmdLists[] = { mCommandList.Get() };
	mCommandQueue->ExecuteCommandLists(_countof(cmdLists), cmdLists);

	FlushCommandQueue();
	return true;
}

 

또한 WndProc와 대응하는 함수는 MsgProc가 있습니다.

LRESULT D3DApp::MsgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	//WndProc
	switch (msg)
	{
	case WM_ACTIVATE:
		if (LOWORD(wParam) == WA_INACTIVE)
		{
			mAppPaused = true;
			mTimer.Stop();
		}
		else
		{
			mAppPaused = false;
			mTimer.Start();
		}
		return 0;
	case WM_SIZE:
		mClientWidth = LOWORD(lParam);
		mClientHeight = HIWORD(lParam);
		if (md3dDevice)
		{
			if (wParam == SIZE_MINIMIZED)
			{
				mAppPaused = true;
				mMinimized = true;
				mMaximized = false;
			}
			else if (wParam == SIZE_MAXIMIZED)
			{
				mAppPaused = false;
				mMinimized = false;
				mMaximized = true;
				OnResize();
			}
			else if (wParam == SIZE_RESTORED)
			{
				if (mMinimized)
				{
					mAppPaused = false;
					mMinimized = false;
					OnResize();
				}
				else if (mMaximized)
				{
					mAppPaused = false;
					mMaximized = false;
					OnResize();
				}
				else if (mResizing)
				{

				}
				else
				{
					OnResize();
				}
			}
		}
		return 0;
	case WM_ENTERSIZEMOVE:
		mAppPaused = true;
		mResizing = true;
		mTimer.Stop();
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_MENUCHAR:
		return MAKELRESULT(0, MNC_CLOSE);
	case WM_GETMINMAXINFO:
		((MINMAXINFO*)lParam)->ptMinTrackSize.x = 200;
		((MINMAXINFO*)lParam)->ptMinTrackSize.y = 200;
		return 0;
	case WM_LBUTTONDOWN:
	case WM_MBUTTONDOWN:
	case WM_RBUTTONDOWN:
		OnMouseDown(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		return 0;
	case WM_LBUTTONUP:
	case WM_MBUTTONUP:
	case WM_RBUTTONUP:
		OnMouseUp(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		return 0;
	case WM_MOUSEMOVE:
		OnMouseMove(wParam, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam));
		return 0;
	case WM_KEYUP:
		if (wParam == VK_ESCAPE)
		{
			PostQuitMessage(0);
		}
		else if (static_cast<int>(wParam) == VK_F2)
		{
			Set4xMsaaState(!m4xMsaaState);
		}
		return 0;
	}
	return DefWindowProc(hwnd, msg, wParam, lParam);
}

 

다음에는 D3DApp의 상위 클래스를 두던가 하여 통합을 하고자 합니다.

'내용정리 > DirectX12' 카테고리의 다른 글

20.08.07 개발일지  (0) 2020.08.07
20.08.04 일지  (0) 2020.08.04
20.07.28 개발일지  (0) 2020.07.28
20.07.25 개발일지  (0) 2020.07.25
20.07.24 일지  (0) 2020.07.24

코드를 모두 작성하였는데 에러가 나서 에러를 수정하고 있습니다.

 

우선 디버깅으로 어느 위치에서 에러가 나는지 파악을 했는데, 라이브러리 코드라서 어떻게 뜯지를 못하고 있습니다.

 

그러다가 구글에 검색을 해보니 저와 같은 문제를 겪은 사람이 있는 것 같은데 글이 길어 읽다가 끝났습니다.

 

다음에는 좀 읽고 수정을 해보려 합니다.

'내용정리 > DirectX12' 카테고리의 다른 글

20.08.04 일지  (0) 2020.08.04
20.07.31 개발일지  (0) 2020.07.31
20.07.25 개발일지  (0) 2020.07.25
20.07.24 일지  (0) 2020.07.24
08. Direct3D의 초기화 - CPU와 GPU의 상호작용  (0) 2020.07.24

오늘은 코드를 보고 원하는 방향대로 어느정도 수정을 해보고 있습니다.

그런데 워낙에 양이 방대하여 그대로 작성하는 것만 해도 하루 종일 했음에도 시간이 부족했습니다.

https://github.com/d3dcoder/d3d12book/tree/master/Common

 

d3dcoder/d3d12book

Sample code for the book "Introduction to 3D Game Programming with DirectX 12" - d3dcoder/d3d12book

github.com

책의 예시에서는 예시 별 다른 Proejct를 지원하고 있습니다.

또한 공통 함수인 D3DApp을 상속받은 각 코드 안에서 WINMAIN이 작동되고 있습니다.

제가 하고자 하는 일은 이를 각 Class 단위로 분리하고,
Window 상에서 메뉴의 버튼 클릭을 통해 각 예시들을 교체 할 수 있도록 하는 것입니다.

여러 확장 코드들이 있지만 우선은 예시 코드를 보고 가능하면 이를 사용하지 않고 구현 중입니다.

 

공부 하면서 자체 엔진을 게발하는 회사들도 엄청나게 많은 기반 코드를 작성 해놓을 것만 같았습니다.

'내용정리 > DirectX12' 카테고리의 다른 글

20.07.31 개발일지  (0) 2020.07.31
20.07.28 개발일지  (0) 2020.07.28
20.07.24 일지  (0) 2020.07.24
08. Direct3D의 초기화 - CPU와 GPU의 상호작용  (0) 2020.07.24
08. Direct3D의 초기화 - 기본지식 2  (0) 2020.07.24

예전에는 내용 정리와 일지랑 섞어서 쓰긴 했는데 좀 분리를 해볼까 합니다.

어차피 내용 정리는 나중에 옮겨야 겠지만.

 

현재 4-3까지 읽었고, 5챕터를 읽은 상태입니다.

이제 여기서 선택지가 있어 조금 고민입니다.

 

하나는 4-4를 읽으면서 구조를 직접 짜보고 나중에 수정하는 것.

다른 하나는 5챕터 예시 코드를 보면서 우선 코드를 작성해놓고, 남은 4챕터와 6챕터를 읽고 코드 정리를 하는 것입니다.

 

아마 직접 해보는 전자가 더 정확히 익힐 수도 있겠지만,
잘 모르는건 예시를 우선 보는 것이 더 잘 이해하기에 후자로 진행하고자 합니다.

 

그래서 당장 다음 공부 날짜인 내일은 6챕터 코드를 제가 원하는 형태로 바꿔가면서 작성을 하려고 합니다.

아마 하루에 끝날 것 같지는 않은데, 이게 끝나면 4챕터 남은 것, 6챕터를 읽으면서 주석을 상세히 작성하고자 합니다.

'내용정리 > DirectX12' 카테고리의 다른 글

20.07.28 개발일지  (0) 2020.07.28
20.07.25 개발일지  (0) 2020.07.25
08. Direct3D의 초기화 - CPU와 GPU의 상호작용  (0) 2020.07.24
08. Direct3D의 초기화 - 기본지식 2  (0) 2020.07.24
09. 렌더링 파이프라인 2  (1) 2020.07.14
  • Graphic Programming에서는 GPU와 CPU. 두 Processor가 작동한다는 점을 이해할 필요가 있다.

  • 이 둘은 병렬로 작동하지만, 종종 동기화가 필요하다.

  • 최적의 성능을 위해서는 둘 모두 최대한 바쁘게 돌아가게 만들어야 하며, 동기화를 최소화 해야 한다.

    • 동기화는 한 Processor의 작업이 마칠 때까지 다른 Processor는 놀아야 한다는 것을 의미한다.

  • 한마디로, 동기화는 병렬성을 망친다.

명렬 대기열(Command Queue)과 명령 목록(Command List)

  • CPU는  Command List를 Direct3D API를 통해 GPU의 Command Queue에 제출한다.

    • 하지만 "Queue"에서 알 수 있듯이,
      Command Queue에 넘어갔다고 GPU가 그 즉시 실행하는 것은 아니다.

    • Command들은 GPU가 처리할 준비가 되어야 비로서 실행되기 시작한다.

    • 즉, GPU가 이전에 제출된 Command를 처리하는 동안에는 Queue에 남아 있다는 것이다.

  • Command Queue가 비면 GPU는 놀게 되고, 꽉 차면 Queue에 자리가 생길 때까지 CPU가 놀게 된다.

    • 게임 같은 High-Quality Application에서는 가용 Hardware Resource를 최대한 쓰는 것이 목표다.

    • 즉, CPU도 GPU도 항상 쉬지 않고 일하게 만들어야 한다.

  • Direct3D 12에서 Command Queue를 담당하는 Interface는 ID3D12CommandQueue이다.

    • 이를 생성하기 위해서는 D3D12_COMMAND_QUEUE_DESC 구조체를 채운 후 ID3D12Device::CreateCommandQueue를 호출해야 한다.

  • 다음은 Command Queue를 채우는 방식을 보여주는 예시 코드이다.

Microsoft::WRL::ComPtr<ID3D12CommandQueue> mCommandQueue;
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
queueDesc.Flags = D3D12_COMNAND_QUEUE_FLAG_NONE;
ThrowIfFailed(md3dDevice->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&mCommandQueue)));
  • 위 예시 코드에서 사용된 보조 메크로 IID_PPV_ARGS는 다음과 같이 정의되어 있다.

#define IID_PPV_ARGS(ppType) __uuidof(**(ppType)), IID_PPV_ARGS_Helper(ppType)
  • 여기서 __uuidof(**(ppType))은 (**(ppType))의 COM Interface ID로 평가 된다.

    • 위 예에서의 ID는 ID3D12CommandQueue이다.

  • 보조함수 IID_PPV_ARGS_Helper는 ppType을 void**로 Cast한다.

  • Direct3D 12 API에는 생성하고자 하는 Interface의 COM ID와 void**를 받는 함수들이 많기 때문에

    책에서 이 매크로를 선언하고, 책 내의 예시코드 전반적으로 사용되고 있다.

ExcuteCommandLists

  • ExcuteCommandLists는 Command List에 있는 Command들을 Queue에 추가하는 메서드이다.

void ID3D12CommandQueue::ExecuteCommandLists(
  UINT              NumCommandLists,
  ID3D12CommandList * const *ppCommandLists
);

https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12commandqueue-executecommandlists

 

ID3D12CommandQueue::ExecuteCommandLists (d3d12.h) - Win32 apps

Submits an array of command lists for execution.

docs.microsoft.com

  • 선언으로 보면 ID3D12CommandList가 Command List를 담당하는 것 같지만,

    실제 그래픽 작업을 위한 Command List는 이를 상속하는 ID3D12GraphicsCommandList라는 Interface가 담당한다.

HRESULT Close();

https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12graphicscommandlist-close

 

ID3D12GraphicsCommandList::Close (d3d12.h) - Win32 apps

Indicates that recording to the command list has finished.

docs.microsoft.com

  • ExcuteCommandLists로 Command List를 제출하기 전에

    반드시 이 메서드를 이용해 Command List를 닫아야 함을 기억하자.

CreateCommandAllocator

  • Command List에는 ID3D12CommandAllocator가 하나 연관된다.

    • Command List에 추가된 Command들은 이 Allocator의 메모리에 저장된다.

  • ExcuteCommandLists로 Command List를 제출하면 Command Queue는 Allocator에 담긴 Command들을 참조한다.

  • Command Memory Allocator는 다음 메서드를 이용해 생성한다.

HRESULT CreateCommandAllocator(
  D3D12_COMMAND_LIST_TYPE type,
  REFIID                  riid,
  void                    **ppCommandAllocator
);

https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-createcommandallocator

 

ID3D12Device::CreateCommandAllocator (d3d12.h) - Win32 apps

Creates a command allocator object.

docs.microsoft.com

  • type은 이 Allocator와 연관시킬 수 있는 Command List 종류이다.

  • 책에서 주로 쓰이는 Type은 다음과 같다.

D3D12_COMMAND_LIST_TYPE_DIRECT

  • GPU가 직접 실행 시킬 수 있는 Command List

  • 지금까지 설명한 Command List들이 이에 포함됨.

D3D12_COMMAND_LIST_TYPE_BUNDLE

  • Bundle을 나타내는 Command List

  • Command List를 만드는 데에 CPU의 부담이 어느정도 따른다.

    • 때문에 Direct3D 12는 일련의 Command들을 Bundle 단위로 기록할 수 있는 최적화 수단을 제공한다.

  • Bundle을 추가하면 Driver는 Rendering 도중에 실행이 최적화 되도록 Bundle의 Command들을 PreCompile한다.

  • Application이 특정 Command List들을 구축하는데 시간이 오래 걸린다면, 이를 고려할 필요가 있다.

    • 단, Direct3D 12 API는 이미 아주 효율적이라 이런 경우가 자주 생기지 않는다.

    • 때문에 Bundle 사용이 성능상 이득을 가져오는 명백한 경우에만 사용한다.

  • 즉, Bundle을 무조건 사용하지는 말아야 한다.

riid

  • 생성하고자 하는 ID3D12CommandAllocator의 COM ID

ppCommandAllocator

  • 생성된 Command Allocator를 가리키는 포인터

  • 출력되는 매개변수

typedef enum D3D12_COMMAND_LIST_TYPE {
  D3D12_COMMAND_LIST_TYPE_DIRECT,
  D3D12_COMMAND_LIST_TYPE_BUNDLE,
  D3D12_COMMAND_LIST_TYPE_COMPUTE,
  D3D12_COMMAND_LIST_TYPE_COPY,
  D3D12_COMMAND_LIST_TYPE_VIDEO_DECODE,
  D3D12_COMMAND_LIST_TYPE_VIDEO_PROCESS,
  D3D12_COMMAND_LIST_TYPE_VIDEO_ENCODE
} ;

https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-createcommandallocator

 

ID3D12Device::CreateCommandAllocator (d3d12.h) - Win32 apps

Creates a command allocator object.

docs.microsoft.com

CreateCommandList

HRESULT ID3D12Device::CreateCommandList(
  UINT                    nodeMask,
  D3D12_COMMAND_LIST_TYPE type,
  ID3D12CommandAllocator  *pCommandAllocator,
  ID3D12PipelineState     *pInitialState,
  REFIID                  riid,
  void                    **ppCommandList
);

https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-createcommandlist

 

ID3D12Device::CreateCommandList - Win32 apps

Creates a command list.

docs.microsoft.com

nodeMask

  • GPU가 여러개일 때에는 이 Command List와 연관시킬

    물리적 GPU Adpater Node들을 지정하는 비트마스크 값을 설정한다.

  • GPU가 하나인 겨우에는 0으로 설정하면 된다.

  • System의 GPU Adapter node 개수는 다음 메서드로 알아낼 수 있다.

UINT ID3D12DeviceGetNodeCount();

https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-getnodecount

 

ID3D12Device::GetNodeCount (d3d12.h) - Win32 apps

Reports the number of physical adapters (nodes) that are associated with this device.

docs.microsoft.com

type

  • Command List의 종류

  • pCommandAllocator

    • 생성된 Command List와 연관시킬 Allocator

    • Command Allocator의 type은 Command List의 type와 일치해야 한다.

pInitialState

  • Command List의 초기 Pipeline 상태를 지정한다.

  • Bundle이거나 초기화 목적으로 쓰이고 실제 그리기 명령이 없는 Command List의 경우 null을 지정한다.

  • 자세한 내용은 나중에 추가로 정리 할 예정이다.

riid

  • 생성하고자 하는 Command List에 해당하는 ID3D12CommandList Interface의 COM ID

ppCommandList

  • 생성된 Command List를 가르키는 포인터

  • 출력 매개변수

  • 한 Allocator를 여러 Command List에 연관시켜도 되지만,
    Command를 여러 Command List에 동시에 기록할 수는 없다.

    • 바꿔 말하면, 현재 Command를 추가하는 Command List를 제외한 모든 Command List들은 닫혀 있어야 한다.

    • 그래야 Command List의 모든 Command가 Allocator 안에 인접해서 저장된다.

  • Command List를 Create 하거나 Reset하면 Command List가 열린 상태가 된다.

    • 때문에 같은 Allocator로 두 Command List를 연달아 Create하면 오류가 발생한다.

Reset

HRESULT ID3D12GraphicsCommandList::Reset(
  ID3D12CommandAllocator *pAllocator,
  ID3D12PipelineState    *pInitialState
);

https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12graphicscommandlist-reset

 

ID3D12GraphicsCommandList::Reset (d3d12.h) - Win32 apps

Resets a command list back to its initial state as if a new command list was just created.

docs.microsoft.com

  • Reset 함수는 Command List뿐만 아니라 Command Allocator에서도 제공한다.

    • 예를 들어, 하나의 Frame을 완성하는데 필요한 Rendering 명령들을 모두 GPU에 제출 한 후에는

      Command Allocator의 Memory를 다음 Frame을 위해 재사용해야 할 것이다.

    • ID3D12CommandAllocator::Reset 메서드는 이 때 사용된다.

HRESULT ID3D12CommandAllocator::Reset();

https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12commandallocator-reset

 

ID3D12CommandAllocator::Reset (d3d12.h) - Win32 apps

Indicates to re-use the memory that is associated with the command allocator.

docs.microsoft.com

  • 하지만 Command Queue가 Allocator 안의 자료를 참조하고 있을수도 있다.

    • 때문에 GPU가 Command Allocator에 담긴 모든 Command를 실행했음이 확실해지기 전까지
      Command Allocator를 재설정하지 말아야 한다.

CPU/GPU 동기화

  • 한 System에서 두 개의 Processor가 병렬로 실행되다 보니 여러 동기화문제가 발생한다.

    • 이에 대한 해결법 중 하나는 GPU가 Command Queue의 Command들 중

      특정 지점까지의 모든 Command를 다 처리할 때까지 CPU를 기다리게 하는 것이다.

  • Queue의 모든 Command를 처리하는 것을 Flush라고 한다.

  • 이 때 필요한 것은 Fence라는 객체이다.

    • Fence는 ID3D12Fence Interface로 구현하며 GPU와 CPU의 동기화를 위한 수단으로 쓰인다.

    • Fence를 생성하는 메서드는 다음과 같다.

HRESULT ID3D12Device::CreateFence(
  UINT64            InitialValue,
  D3D12_FENCE_FLAGS Flags,
  REFIID            riid,
  void              **ppFence
);

https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-createfence

 

ID3D12Device::CreateFence (d3d12.h) - Win32 apps

Creates a fence object.

docs.microsoft.com

  • Fence 객체는 UINT64 값 하나를 관리한다.

    • 이는 그냥 시간상의 특정 Fence 지점을 식별하는 정수이다.

  • 예를 들어, Fence가 하나도 없을 때에는 이 값을 0으로 둔다.

    • 새 Fence를 만들 때에는 이 값이 1씩 증가한다.

  • 이를 아래 예시 코드를 보며 더 자세히 설명을 해보겠다.

UINT64 mCurrentFence = 0;
void D3DApp::FlushCommandQueue()
{
	// 현재 Fence 지점까지의 명령들을 표시하도록 Fence 값을 전진시킨다.
    mCurrentFence++;
    
    // 새 Fence 지점을 설정하는 Signal을 Command Queue에 추가한다.
    // 지금 우리는 GPU timeline상에 있으므로 새 Fence 지점은 
    // GPU가 이 Signal() Command까지의 모든 명령을 처리하기 전까지는 설정되지 않는다.
    ThrowIfFailed(mCommandQueue->Signal(mFence.Get(), mCurrentFence));
    
    // GPU가 이 Fence 지점까지의 Command들을 완료할 때까지 기다린다.
    if(mFence->GetCompletedValue() < mCurrentFence)
    {
    	HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
        
        // GPU가 현재 Fence 지점에 도달했으면 이벤트를 발동한다.
        ThrowIfFailed(mFence->SetEventOnCompletion(mCurrentFence, eventHandle));
        
        // GPU가 현재 Fence 지점에 도달했음을 뜻하는 이벤트를 기다린다.
        WaitForSingleObject(eventHandle, INFINITE);
        CloseHandle(eventHandle);
    }
}
  • 하지만 이것은 이상적인 해결책은 아니다.

    • GPU의 작업이 끝날 때까지 CPU가 기다려야하기 때문이다.

  • 하지만 충분히 간단한 해결책이므로 당분간은 이 방법을 이용한다.

  • Command Queue를 비우는 시점에는 제약이 거의 없다.

    • 특히 한 Frame에서 1번만 비워야 하는 것도 아니다.

  • 예를 들어 초기화를 위한 GPU Command들이 있다고 하자.

    • 먼저 그 Command가 실행되었음이 확실해진 후 Command Allocator를 재설정 하려면

      Command Queue를 비운 후 Command Allocator를 Reset하면 된다.

  • Resource hazard를 막기 위해 Direct3D는 Resource들에게 State를 부여한다.

    • 새로 생성된 Resource는 Default State로 시작한다.

  • 임의의 State Transition를 Direct3D에게 보고하는 것은 전적으로 Application의 몫이다.

    • 덕분에 GPU는 State를 전이하고 Resourece Hazard를 방지하는데 필요한 일들을 자유롭게 진행할 수 있다.

  • 예를 들어, Texture Resource에 자료를 기록해야 할 때에는 그 Texture의 State를 Render Object State로 설정한다.

    • 이후 Texture의 자료를 읽어야 할 때가 되면 State를 Shader Resource State로 Transition 한다.

  • Application이 State Transition을 Direct3D에게 보고 함으로써,
    GPU는 Resource Hazard를 피하는데 필요한 조치를 할 수 있다.

    • 쓰기 연산이 완료되길 기다린 후 읽기를 시도 하는 등

  • Resource Transition을 보고하는 부담을 Application이 담당하는 것은 성능 때문이다.

    • GPU 입장에서는 Transition이 언제 발생하는지 몰라 항상 자동으로 추적하게 해야 한다.

    • Programmer는 이러한 Transition이 언제 일어아는지 미리 알고 있기에 필요할 때 추적할 수 있다.

전이 자원 장벽(Transition Resource Barrier)

  • Resource State Transition은 Transition Resource Barrier들의 Array를 설정해서 지정한다.

    • Array를한번의 API 호출로 여러 개의 Resource를 Transition 할 수 있다.

  • 코드 상에서는 Resource Barrier는 D3D12_RESOURCE_DESC 구조체로 서술된다.

typedef struct D3D12_RESOURCE_BARRIER {
  D3D12_RESOURCE_BARRIER_TYPE  Type;
  D3D12_RESOURCE_BARRIER_FLAGS Flags;
  union {
    D3D12_RESOURCE_TRANSITION_BARRIER Transition;
    D3D12_RESOURCE_ALIASING_BARRIER   Aliasing;
    D3D12_RESOURCE_UAV_BARRIER        UAV;
  };
} D3D12_RESOURCE_BARRIER;

https://docs.microsoft.com/en-us/windows/win32/api/d3d12/ns-d3d12-d3d12_resource_barrier

 

D3D12_RESOURCE_BARRIER (d3d12.h) - Win32 apps

Describes a resource barrier (transition in resource use).

docs.microsoft.com

  • 이 책에서는 Direct3D 12의 구조체들에 여러 편의용 메서드들을 추가한 확장 버전을 사용한다.

    • 대부분의 구조체들은 이런 편의용 확장 버전들이 존재하며,
      이 책에서 사용하는 것들은 Microsoft 사이트에서 내려받을 수 있다.

https://docs.microsoft.com/en-us/windows/win32/direct3d12/helper-structures-for-d3d12

 

Helper Structures for D3D12 - Win32 apps

These helper structures help initialize many of the Direct3D 12 structures, and are declared in d3dx12.h.

docs.microsoft.com

https://github.com/microsoft/DirectX-Graphics-Samples

 

microsoft/DirectX-Graphics-Samples

This repo contains the DirectX Graphics samples that demonstrate how to build graphics intensive applications on Windows. - microsoft/DirectX-Graphics-Samples

github.com

  • Transition Resource Barrier는 GPU에게 Resource의 State가 Transition됨을 알려주는
    하나의 Command라고 생각할 수 있다.

    • Comand를 통해 Resource State Transition를 알려주는 덕분에,
      GPU는 이후 Command들을 실행 할 때 Resource Hazard를 피하는데 필요한 단계를 밟을 수 있다.

Command List를 이용한 Multi Thread 활용

  • Direct3D 12는 Multi Thread를 효율적으로 활용할 수 있도록 설계되었다.

    • Command List의 서러계는 Direct3D가 Multi Thread 적용의 장점을 취하는 대표적인 한 방법이다.

  • 물체가 많은 큰 장면을 다룰 때,
    장면 전체를 하나의 Command List로 그리려 하면 구축하는데 CPU 시간이 오래 걸린다.

    • 이에 대한 자명한 해결책은 여러 개의 Command List를 병렬로 구축하는 것이다.

  • Command List 구축에 Multi Thread를 적용 할 때 주의해야 할 점이 쳐가지 있다.

    1. Command List와 Command Allocator는 Free-Threaded 하지 않는다.

      보통의 경우 여러 Thread가 같은 Command List나 Allocator를 공유하지도 않고,
      그 메서드들을 동시에 호출하지도 않는다.

      따라서, 일반적으로 각 Thread는 각자 자신만의 Command List와 Allocator를 가지게 된다.

    2. Command Queue는 Free-Threaded하다.

      즉, 여러 Thread가 같은 Command Queue에 접근해서 그 메서드들을 동시에 호출할 수 있다.

      특히, Thread들이 각자 생성한 Command List를 동시에 Command Queue에 제출할 수 있다.

    3. 성능상의 이유로,
      Application은 동시에 기록할 수 있는
      Command List의 최대 개수를 반드시 초기화 시점에서 설정해야 한다.

  • 단순함을 위해, 이 책에서는 Multi Thread를 사용하지 않는다.

    • 하지만 System Resource 사용을 극대화 하려면 Application이

      반드시 Multi Thread를 이용해 Multi Core의 장점을 최대한 활용할 필요가 있다.

'내용정리 > DirectX12' 카테고리의 다른 글

20.07.25 개발일지  (0) 2020.07.25
20.07.24 일지  (0) 2020.07.24
08. Direct3D의 초기화 - 기본지식 2  (0) 2020.07.24
09. 렌더링 파이프라인 2  (1) 2020.07.14
09. 렌더링 파이프라인 1  (0) 2020.07.14

이전 내용은 DirectX11 기반 내용을 현재 DirectX12에 맞게 검색한 뒤 작성한 내용입니다.

이후 DirectX12 책을 구매하였고, 몇가지 내용이 추가되어 그 내용을 우선 작성합니다.

 

---------------------------------------------------------------------------------------------------

 

DXGI(DirectX Graphics Infrastructure)

  • DirectX3D와 함께 쓰이는 API로 여러 그래픽 API들에 공통인 그래픽 관련 작업들이 존재한다.

    • 예를 들어 SwapChain을 위한 대표적인 Interface인 IDXGISwapShain은 사실 DXGI API일부이다.

    • 그 밖에 Graphic System Data의 Enumeration. 그리고 지원되는 표현 형식들도 DXGI가 제공한다.

IDXGIFactory

  • 주로 IDXGISwapChain Interface 생성과 Display Adapter Enumeration에 쓰인다.

IDXGIAdapter

  • Display Adapter를 대표하는 Interface

Display Adapter

  • 그래픽 기능성을 구현하며, 일반적으로 하드웨어 장치(ex. Graphic Card)이다.

    • 하지만 하드웨어 그래픽 기능성을 흉내내는 소프트웨어 디스플레이 기능성도 존재한다.

  • 하나의 시스템은 여러개의 Adpater를 가질 수 있다.

IDXGIOutput

  • Display Output을 담당하는 Interface.

  • DXGI_MODE_DESC 구조체에는 하나의 Display Mode를 서술하는 여러 멤버들이 있다.

typedef struct DXGI_MODE_DESC {
  UINT                     Width;
  UINT                     Height;
  DXGI_RATIONAL            RefreshRate;
  DXGI_FORMAT              Format;
  DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;
  DXGI_MODE_SCALING        Scaling;
} DXGI_MODE_DESC;

typedef struct DXGI_RATIONAL {
  UINT Numerator;
  UINT Denominator;
} DXGI_RATIONAL;

typedef enum DXGI_MODE_SCANLINE_ORDER { 
  DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED        = 0,
  DXGI_MODE_SCANLINE_ORDER_PROGRESSIVE        = 1,
  DXGI_MODE_SCANLINE_ORDER_UPPER_FIELD_FIRST  = 2,
  DXGI_MODE_SCANLINE_ORDER_LOWER_FIELD_FIRST  = 3
} DXGI_MODE_SCANLINE_ORDER;

typedef enum DXGI_MODE_SCALING { 
  DXGI_MODE_SCALING_UNSPECIFIED  = 0,
  DXGI_MODE_SCALING_CENTERED     = 1,
  DXGI_MODE_SCALING_STRETCHED    = 2
} DXGI_MODE_SCALING;

https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/bb173064(v=vs.85)

 

DXGI_MODE_DESC structure (Windows)

DXGI_MODE_DESC structure 05/18/2018 2 minutes to read In this article --> Describes a display mode. Syntax typedef struct DXGI_MODE_DESC { UINT                     Width; UINT                     Height; DXGI_RATIONA

docs.microsoft.com

  • 이러한 Display Mode Enumeration은 전체 화면 모드로 갈 때 특히 중요하다.

    • 성능을 극대화 하려면 지정된 Display Mode가 반드시 모니터가 지원하는 Display Mode와 일치해야 한다.

    • 모니터가 지원하는 Display Mode들을 열거해 그 중 하나를 지정하면 이러한 일치성이 보장된다.

Display Output

  • 모니터와 같이 화면을 출력하는 장치.

기능 지원 점검

  • ID3D12Device::CheckFeatureSupport 메서드는 Graphic Driver의 MultiSampling 지원 여부를

    점검할 수 있다고 소개했다.

    • 하지만 이는 CheckFeatureSupport가 확인할 수 있는 수 많은 기능들 중 하나일 뿐이다.

  • CheckFeatureSupport의 서명은 다음과 같다.

HRESULT CheckFeatureSupport(
  D3D12_FEATURE Feature,
  void          *pFeatureSupportData,
  UINT          FeatureSupportDataSize
);
  • Feature는 이 메서드로 지원 여부를 점검할 기능들의 종류를 나타낸다.

  • pFeatureSupportData는 기능 지원 정보가 설정될 구조체를 가르키는 포인터이다.

    • 이는 Feature 값에 따라 구체적인 형식이 다르다.

  • FeatureSupportDataSize는 pFeatureSupportData에 전달한 구조체의 크기를 나타낸다.

D3D12_FEATURE_D3D12_OPTIONS

  • Direct3D 12의 여러 기능들을 점검한다.

  • pFeatureSupportData에는 D3D12_FEATURE_DATA_D3D12_OPTIONS 인스턴스를 가르키는 포인터를 넣어야 한다.

DED12_FEATURE_ARCHITECTURE

  • Hardware Architecture 기능들을 점검한다.

  • pFeatureSupportData에 D3D12_FEATURE_DATA_ARCHITECTURE 인스턴스를 가르키는 포인터를 넣어야 한다.

D3D12_FEATURE_FEATURE_LEVELS

  • 기능 수준들을 점검한다.

  • D3D12_FEATURE_DATA_FEATURE_LEVELS 인스턴스를 가리키는 포인터를 넣어야 한다.

D3D12_FEATURE_FORMAT_SUPPORT

  • 주어진 Texture 형식에 대한 기능들을 점검한다.

  • D3D12_FEATURE_DATA_FORMAT_SUPPORT 인스턴스를 가르키는 포인터를 넣어야 한다.

D3D12_FEATURE_MULTICAMPLE_QUALITY_LEVELS

  • MultiSampling 기능들을 점검한다.

  • D3D12_FEATURE_DATA_MULTISAMPLING_QUALITY_LEVELS 인스턴스를 가르키는 포인터를 넣어야 한다.

상주성(Residency)

  • 게임에 사용되는 모든 자원이 GPU를 필요로 하는 것은 아니다.

  • 때문에 Direct3D 12의 Application은 Resource를 GPU 메모리로부터 내리거나 올려서
    Residency를 관리한다.

    • 이러한 작업의 핵심은 GPU Memory 점유율을 최소화 하는 것이다.

  • 성능적 측면에서 한가지 주의해야 할 점이 있다.

    • Application은 같은 자원을 짧은 시간에 GPU Memory에 넣었다 뺐다 하는 상황을 피해야 한다.

    • 이러한 활동에는 비용이 따르기 때문이다.

  • 이상적으로, 한동안 사용하지 않는 Resource들만 GPU Memory에서 내려야 한다.

  • Residency을 변경하는 대표적인 예로는 Level이나 Area가 바뀌는 시점을 들 수 있다.

  • 보통은 Resource를 생성하면 GPU Memory에 올라가고, 파괴되면 Memory에서 내려간다.

    • 하지만 다음 메서드들을 이용해 Application이 직접 Residency를 제어할 수 있다.

HRESULT ID3D12Device::MakeResident(
  UINT           NumObjects,
  ID3D12Pageable * const *ppObjects
);

HRESULT ID3D12Device::Evict(
  UINT           NumObjects,
  ID3D12Pageable * const *ppObjects
);

https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-makeresident

 

ID3D12Device::MakeResident (d3d12.h) - Win32 apps

Makes objects resident for the device.

docs.microsoft.com

https://docs.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12device-evict

 

ID3D12Device::Evict (d3d12.h) - Win32 apps

Enables the page-out of data, which precludes GPU access of that data.

docs.microsoft.com

 

DirectX12의 Rendering Pipeline (출처: https://docs.microsoft.com/ko-kr/windows/win32/direct3d12/pipelines-and-shaders-with-directx-12)

Input Assembler

  • Input Assembler 단계는 메모리에서 기하 자료를 읽어 기하학적 기본 도형을 조립한다.

Vertex

  • 원래 Vertex는 두 변이 만나는 점이다. 때문에 "기하학적 기본 도형의 꼭짓점"이라는 인상을 받을 것이다.
    • 그러나 Direct3D의 정점은 그보다 훨씬 일반적이다.
  • Direct3D의 Vertex는 본질적으로 공간적 위치 이외의 정보도 담을 수 있다.
    • 그리고 이를 이용해 더 복잡한 Redering 효과를 낼 수 있다.
      • 예를 들어 조명을 구현하기 위해 Vertex에 Normal Vector를 추가할 수 있다.
      • 또는 Texture를 적용하기 위해 Vertex에 Texture 좌표를 추가할 수도 있다.
  • Direct3D에서는 Application이 자신만의 Vertex type을 정의할 수 있는 유연성을 제공한다.

기본도형 위상구조(Primitive Topology)

  • Vertex들은 Vertex Buffer라는 특별한 Direct3D Data Structure 안에 담겨서 Rendering Pipeline에 묶인다.
    • 이는 그저 Vertex들을 연속적인 Memory에 저장하는 Data Structure일 뿐이다.
  • Vertex Buffer 그 자체는 Vertex들이 어떤 식으로 조합해서 기본도형을 형성할 것인지 알려주지 않는다.
    • 이를 알려주는 것은 Primitive Topology가 맡는다.
  • 다음은 Primitive Topology를 지정하는 Method와 이를 정의한 Enumeration이다.
virtual void STDMETHODCALLTYPE IASetPrimitiveTopology( 
            _In_  D3D12_PRIMITIVE_TOPOLOGY PrimitiveTopology) = 0;
            
typedef 
enum D3D12_PRIMITIVE_TOPOLOGY_TYPE
    {
        D3D12_PRIMITIVE_TOPOLOGY_TYPE_UNDEFINED	= 0,
        D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT	= 1,
        D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE	= 2,
        D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE	= 3,
        D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH	= 4
    } 	D3D12_PRIMITIVE_TOPOLOGY_TYPE;
  • 한번 Method를 호출해서 Primitive Topology를 설정하면, 다음 호출이 있기 전까지 모든 그리기 호출에서 이 Primitive Topology가 적용된다.
  • 각 Primitive Topology의 방식은 아래 링크로 대체한다.

https://docs.microsoft.com/ko-kr/windows/uwp/graphics-concepts/primitive-topologies

 

기본 토폴로지 - UWP applications

Direct3D는 여러 개의 기본 토폴로지를 지원하는데, 이 토폴로지는 점 목록, 선 목록, 삼각형 스트립 등 파이프라인이 꼭짓점을 해석 및 렌더링하는 방식을 정의합니다.

docs.microsoft.com

색인(Index)

  • 앞서 이야기 했듯이, 3차원 Solid Object의 기본 구축 요소는 삼각형이다.
  • 다른 다각형을 표현할 때에도 여러 개의 삼각형으로 나누어서 표현을 한다.
    • 때문에 다각형을 여러 개의 삼각형으로 표현하기 위해서는 다음과 같이 Vertex Array를 사용한다.
Vertex quad[6] = {
	v0, v1, v2, 	// 삼각형 0
    	v0, v2, v3	// 삼각형 1
};

Vertex octagon[24] = {
	v0, v1, v2, 	// 삼각형 0
	v0, v2, v3, 	// 삼각형 1
	v0, v3, v4, 	// 삼각형 2
	v0, v4, v5, 	// 삼각형 3
	v0, v5, v6, 	// 삼각형 4
	v0, v6, v7, 	// 삼각형 5
	v0, v7, v8, 	// 삼각형 6
	v0, v8, v1 	// 삼각형 7
};
  • 예시에서 보이듯이, 각 삼각형들은 다 수의 Vertex들을 공유한다.
    • 한 두개라면 상관이 없지만, 팔각형처럼 공유하는 Vertex가 많아지면 문제가 심각해진다.
      • 같은 Vertex를 여러번 저장하면서 Memory 요구량이 증가한다.
      • 마찬가지로 같은 Vertex를 여러번 처리하면서 Graphic Hardware의 처리량이 증가한다.
  • 물론 다른 방식의 Primitive Topology를 사용하는 방법도 있지만, 삼각형 방식만큼 유연성이 높은 방식이 없다.
  • 때문에 우리는 삼각형 목록에서 중복 Vertex를 제거하기 위해 Index를 사용한다.
    • 예를 들어 사각형의 Vertex Array는 다음과 같이 표현한다.
Vertex v[4] = { v0, v1, v2, v3 };
UING indexList[6] = { 0, 1, 2, 	// 삼각형 0
		      0, 2, 3};	// 삼각형 1
  • Graphic Hardware는 Vertex Array의 고유한 Vertex들을 처리한 후, Index를 이용해 Vertex들을 조합한다.
  • Vertex의 중복이 없어지는 대신 Index의 중복이 발생하지만, 이는 큰 문제가 없다.
    • Index는 그냥 정수이므로 Vertex Structure보다 메모리 점유양이 적다.
    • Vertex Cache 순서가 좋은 경우 Graphic Hardware가 중복된 Vertex를 처리하지 않아도 된다.

Vertex Shader

  • Input Assembler에서 기본 도형들을 조립 한 후, Vertex들이 Vertex Shader(VS)로 입력된다.
    • 이는 Vertex 하나를 입력 받아 Vertex 출력하는 함수로 간주해도 될 것이다.
    • 화면에 그려질 모든 Vertex는 이 Vertex Shader를 거쳐간다.
  • Vertex Shader Function의 구체적인 내용은 Programmer가 구현해서 GPU에 제출한다.
    • 이 Function은 각 Vertex에 대해 GPU에서 실행되기 때문에 아주 빠르다.
  • Transform, Illuminate, Conversion Mapping 등 수 많은 특수효과를 GS에서 수행할 수 있다.
    • Vertex Shader에서 Vertex data는 물론 Texture, Transform Matrix등 GPU Memory에 담긴 다른 자료에도 접근할 수 있기 때문이다.

Local Space와 World Space

  • 3D 물체를 만들 때에는 장면 전역의 좌표계가 아니라 물체 자신의 국소 좌표계를 기준으로 구축한다.
    • 전자의 좌표계를 World Space라고 부른다.
    • 후자의 좌표게는 Local Space라고 부른다.
  • Local Space에서 3차원 물체의 Vetex들을 정의 했다면, 이를 World Space에 적절히 배치해야 한다.
  • 이를 위해서는 Local Space와 World Space의 관계를 정의할 수 있어야한다.
    • 정확히는 World Coordinate를 기준으로 한 Local Coordinate의 원점과 Axis의 위치, 방향을 지정.
    • 그리고 이에 해당하는 Coordinate Transformation을 수행해야 한다.
  • 이 Coordinate Transformation을 세계 변환(World Transform)이라 부른다.
    • 이때 Transform 되는 Matrix를 세계 행렬(World Matrix)라고 부른다.
  • World Matrix를 만들기 위해서는 World Space를 기준으로 한 Local Space의 원점과 Axis의 좌표가 필요하다.
  • 더 흔한 접근 방식은 World Transform W를 SRT 형태로 정의하는 것이다.
    • S는 Scaling Matrix
    • R은 Rotation Matrix
    • T는 Translate Matrix
  • 이와 같이 Local Space에서 물체를 정의하는 방식을 사용하는 이유는 다음과 같다.
    • 더 쉽다. 
      • 보통 Local Space에서 원점과 물체의 중심이 일치하고, Axis 중 하나와 대칭한다.
    • 더 합리적이다.
      • World Space에서 모양은 같으니 크기나 방향이 다른 물체를 여러번 만드는 것은 비합리적이다.
      • 이를 Local Space에서 생성하여 다양한 크기와 방향에 맞춰 배치하는 것이 더 합리적이다.
    • 같은 물체를 반복적으로 배치해야 할 경우, Vertex나 Index으로 인한 낭비를 막아준다.
  • 이런 방식을 Instancing이라고 부른다.

시야 공간(View Space)

  • 최종적으로 화면에 표시 될 2차원 이미지를 만들기 위해서는 가상의 카메라를 배치해야 한다.
    • 이느 World에 2차원 이미지를 생성해야 하는 영역을 결정하는 것이다.
  • 이 때 카메라에 Local Coordinate를 부여했을 때, 이 Coordinate를 View Space라 한다.
    • View Space는 시점 공간(Eye Space) 또는 카메라 공간(Camera Space)라고 부르기도 한다.
  • 카메라는 View Space에서 +Z축을 바라보고, 카메라의 오른쪽이 +X축, 위쪽이 +y축이다.
    • 이 때 World Space에서 Local Space로의 Transform 하는 것을 시야 변환(View Transform)라 한다.
    • Transform 되는 Matrix는 시야 행렬(View Matrix)라고 부른다.
  • 이는 World Space에서 카메라의 좌표에 대한 World Matrix W의 반대되는 역할을 한다.
    • 즉 W의 역행렬이 View Matrix이다.
  • 이에 대해 DirectXMath.h는 다음과 같은 함수들을 제공하고 있다.
XMMATRIX    XM_CALLCONV     XMMatrixLookAtLH(FXMVECTOR EyePosition, FXMVECTOR FocusPosition, FXMVECTOR UpDirection);
XMMATRIX    XM_CALLCONV     XMMatrixLookAtRH(FXMVECTOR EyePosition, FXMVECTOR FocusPosition, FXMVECTOR UpDirection);
XMMATRIX    XM_CALLCONV     XMMatrixLookToLH(FXMVECTOR EyePosition, FXMVECTOR EyeDirection, FXMVECTOR UpDirection);
XMMATRIX    XM_CALLCONV     XMMatrixLookToRH(FXMVECTOR EyePosition, FXMVECTOR EyeDirection, FXMVECTOR UpDirection);

투영과 동차 절단 공간

  • 카메라를 서술하는 요소가 하나 더 있다.
    • 카메라가 바라보는 절두체(Frustum)의 공간 영역이다.
  • 이제 할 일은 이 절두체 안의 3차원 기하 구조를 2차원 투영 창으로 Projection하는 것이다.
    • 이를 위해서는 반드시 평행선들이 하나의 소실점으로 수렴하는 방식으로 이루어져야 한다.
    • 그래야 3차원 Dpeth가 증가함에 따라 Project 된 결과가 줄어드는 현상이 나타나다.
    • 즉 이는 원근투영(Perspective Projection)을 따른다는 것이다.
    • 정점에서 시점으로의 직선을 정점의 투영선(line of projection)이라 부른다.
  • 원근투영 변환(Perspective Projection Transform)은 하나의 3차원 Vertex v를 Line of Projection이 2차원 투영 평면과 만나는 점 v'로 변환하는 Transform이다.
    • 이 v'를 v의 Projection이라 부른다.

Frustum의 정의

  • View Space에서 Center of Projection을 원점에 두고 +Z축을 바라보는 Frustum을 4가지 수량으로 정의할 수 있다.
    • 원점과 가까운 평면 사이의 거리 n
    • 원점과 먼 평면 사이의 거리 f
    • 수직 시야각 a
    • 종횡비(Aspect Ratio) r
  • 여기서 평면이 XY 평면과 평행임을 주목하자.
    • 이로 인해 평면과 원점사이의 거리는 Z축 상의 거리가 된다.
  • Aspect Ratio r = w / h로 정의 된다.
    • w: Projection 창의 너비
    • h: Projection 창의 높이
  • Projection 창은 본질적으로 View Space 안 장면의 2차원 이미지이고, 이는 결국 Back Buffer에 사상된다.
    • 때문에 Projection Window의 Aspect Ratio는 Back Buffer의 Aspect Ratio와 같게 만드는 것이 바람직하다.
  • 그래서 일반적으로 Back Buffer에 맞춰서 설정한다.

정규화된 장치 좌표(Normalized Device Coordinates)

  • Vertex를 Aspect Ratio을 이용해 Projection하면 Aspect Ratio에 의존한다는 문제가 발생한다.
    • 이는 Hardware가 Projection 창의 크기가 관여하는 연산을 수행할 수 있으려면
      Hardware에 Asprect Ratio를 알려줘야 한다는 것이다.
  • 이 문제를 해결하기 위해 우리는 X 좌표 성분을 [-r, r]에서 [-1, 1]로 비례시킨다.
    • 이러한 작업이 수행된 후의 좌표를 Normalized Device Coordinates(NDC)라 부른다.
  • Space Area에서 NDC Space로의 변환은 일종의 단위 변환(unit conversion)으로 볼 수 있다.
  • x 축에서 NDC의 한 단위는 View Area의 r 단위와 같다.
    • 1 ndc = r vs
    • 즉, NDC 좌표에서는 Projection 창의 너비도, 높이도 2이다.
  • 위와 같이 Projection 창의 크기가 조정되었으므로, Hardware는 Aspect Ratio를 몰라도 된다.
    • 뒤집어 말하면, Graphic Hardware는 Project된 Coordinate들이 NDC Space라고 가정하므로,
      프로그래머는 항상 실제로 NDC Space를 기준으로 한 Projection Coordinate를 공급하는 것에
      신경을 써야 한다.

Projection Transform을 Matrix로 표현

  • 일관성을 위해서는 Projection Trnasform을 하나의 Matrix로 표현하는 것이 좋다.
    • 그러나 식이 비선형적이라 Matrix Expression이 존재하지 않는다.
  • 그래서 우리는 한가지 요령으로 이 문제를 피해간다.
    • 바로 선형 부분과 비선형 부분을 분리하는 것이다.
  • Projection Transform에서 비선형적인 연산은 z로 나누는 연산이다.
    • 임의의 Vertex V = ( x, y, z, w )에 대한 Transform 연산을 하면 w = z가 되기 때문이다.
  • 그렇기에 Transform 연산을 한 후에 z로 나누어 w = 1로 만들어준다.
    • z = 0인 경우를 걱정 할 수 있으나, 아무리 평면과 가까워도 z는 0보다 크다.
  • 이와 같이 w로 나누는 것을 원근 나누기(Perspective Divide), 또는 동차 나누기(Homogeneous Divide)라 부른다.

Normalized Depth

  • Projection이 끝났다면 3차원의 z 성분은 이제 버려도 되지 않을까 하는 생각이 들 수 있다.
    • 하지만 Depth Buffering을 위해 여전히 Depth 정보가 필요하다.
  • 앞서 보았듯이, Direct3D는 투영된 x, y 좌표가 Normalized 되어 있기를 요구한다.
    • Depth 역시 마찬가지로 [0, 1] 구간으로 Normalized 되기를 요구한다.
  • 이를 위해 구간 [n, f]를 [0, 1]로 사상하는 순서 보존 함수 g(z)를 구축할 필요가 있다.
    • 이는 Depth 값들이 Transform 되어도 상대적 관계는 유지 시키는 함수이다.
  • Depth Buffering에는 Dpth의 값이 아니라 상대적 관계가 필요한 것임을 기억하자.
  • Depth를 Normalize 하기 위해서는 한번의 Scaling과 한번의 Translate 연산이 필요하다.
    • 하지만 실제로 적용을 해본다면, 잘 먹히지 않을 것이다.

  • 이제 여기서 두 가지 구속조건을 만족하는 A와 B를 선택해야 한다.
    • 조건 1) g(n) = A + B/n = 0 (가까운 평면이 0으로 사상됨)
    • 조건 2) g(f) = A + B/f = 1 (먼 평면이 1로 사상됨)
  • 여기서 조건 1을 풀고, 조건 2를 푼다면 함수 g의 그래프가 순증가(strictly increasing)이고, 비선형임을 알 수 있다.
    • 또한 치역의 대부분을 가까운 평면에 가까운 Depth이 차지하고 있음도 알 수 있다.
      • Depth들이 치역의 작은 부분에 몰려 있는 것이다.
    • 때문에 Depth Buffer에서 정밀도 문제가 발생 할 수 있다.
  • 이를 해결하기 위한 일반적인 조언은, "가까운 평면과 먼 평면을 최대한 가깝게 하라"는 것이다.
    • 값이 작으면 작을수록 기울기가 늘어나면서 치역이 분포가 더 퍼지게 된다.
  • 이 Proejction Matrix를 곱하고, Perspective Divide를 하기 전의 기하 구조를
    동차 절단 공간(Homogeneous Clip Space) 또는 투영 공간(Projection Area)에 있다고 한다.
  • Perspective Divide를 수행한 후의 기하 구조를 가리켜 Normalized Device Coordinate Area에 있다고 말한다.

XMMatrixPerspectiveFovLH

  • DirectXMath.h에는 이에 대해 다음 함수를 제공한다.
XMMATRIX    XM_CALLCONV     XMMatrixPerspectiveFovLH(float FovAngleY, float AspectRatio, float NearZ, float FarZ);
XMMATRIX    XM_CALLCONV     XMMatrixPerspectiveFovRH(float FovAngleY, float AspectRatio, float NearZ, float FarZ);

 

Hull Shader

  • Hull Shader에 대한 자세한 설명은 따로 찾을 수 없었다. 우선은 관련 링크로 대체를 한다.

https://docs.microsoft.com/ko-kr/windows/uwp/graphics-concepts/hull-shader-stage--hs-

 

HS(헐 셰이더) 단계 - UWP applications

HS(헐 셰이더) 단계는 공간 분할 단계 중 하나로, 모델의 한 표면을 효율적으로 많은 삼각형으로 나눕니다.

docs.microsoft.com

Tessellator

  • Tessellation은 한 mesh의 삼각형들을 더 잘게 쪼개서 새로운 삼각형들을 만드는 과정을 나타낸다.
    • 이렇게 생성된 새로운 삼각형들을 새로운 위치로 이동함으로써 좀 더 세밀한 메시를 만들어낼 수 있다.
  • Tessellation의 장점은 다음과 같다.
    • 계산 리소스를 더 중요한 곳에 투자할 수 있다.
      • 카메라에 가까운 삼각형들에는 세부도를 높이고,
        먼 삼각형들에는 Tessellation을 적용하지 않으면서 세부도를 낮춘다.
      • 이러한 방식의 세부수준(LOD) 메커니즘은 눈에 띄는 부분에 삼각형을 더 투자할 수 있게 한다.
    • 메모리 소모를 줄일 수 있다.
      • 메모리에는 삼각형이 적은 Mesh를 담아두고 즉석에서 삼각형을 추가함으로써 메모리를 적용할 수 있다.
    • 계산량을 줄일 수 있다.
      • Animation이나 물리 처리 같은 연산들을 저다각형 Mesh로 수행하고,
        Tessellation된 고다각형 Mesh는 Rendering에만 사용한다.
  • Direct3D 11에서 추가된 Tessellation Step은 GPU에서 기하구조를 그 수단을 제공한다.
    • 이전에는 CPU에서 새로운 기하구조를 만들고, 이를 GPU에 올려 Rendering해야 했다.
      • 하지만 CPU 메모리에서 GPU 메모리로 올리는 것은 매우 느린 연산이다.
      • 게다가 CPU에서 Tessellation 하는 것도 큰 부담이 된다.
  • 그래서 Direct3D 11 이전에는 실시간 그래픽에서 Tessellation이 별로 인기가 없었다.
  • 하지만 Direct3D 11에서는 이를 전적으로 Hardware에서 수행하는 API를 제공한다.
    • 이 덕분에 Tessellation은 매우 매력적인 기법이 되었다.
  • Tessellation 단계는 생략이 가능한 단계이다.
  • Tessellation에 대한 자세한 이론은 나중에 뒤에서 좀 더 자세히 다루겠다.

Domain Shader

  • 역시 자세한 설명을 찾지 못해 링크로 대체한다.

https://docs.microsoft.com/ko-kr/windows/uwp/graphics-concepts/domain-shader-stage--ds-

 

DS(도메인 셰이더) 단계 - UWP applications

DS(도메인 셰이더) 단계는 출력 패치에서 세분화된 지점의 꼭짓점 위치를 계산합니다. 이 단계에서 각 도메인 샘플에 해당하는 꼭짓점 위치도 계산합니다.

docs.microsoft.com

Geometry Shader

  • Geometry Shader(GS) 단계는 생략 가능한 단계이며, 한참 뒤에 사용할 것이기에 간단히 설명만 하겠다.
  • Geometry Shader는 하나의 온전한 기본도형을 입력받아 임의로 변경한다.
    • 예를 들어 삼각형 목록을 그리는 경우, Geometry Shader에는 삼각형을 정의하는 Vertex 3개가 입력된다.
      • 이 Vertex들은 이미 Vertex Shader 처리를 거친 것들이다.
  • Geometry Shader가장 큰 장점은 기하구조를 생성하거나 파괴 할 수 있다는 것이다.
    • 예를 들어 Geometry Shader는 입력 기본도형을 다른 여러 기본 도형으로 확장할 수 있다.
    • 또 특정 조건에 따라서는 출력하지 않고 폐기할 수도 있다.
  • 이는 Vertex Shader와 대조되는 특징이다.
    • Vertex Shader는 항상 Vertex 하나를 받아 Vertex 하나를 출력한다.
    • 하지만 Geometry Shader의 흔한 용도는 하나의 점이나 선분을 하나의 사각형으로 확장하는 것이다.
  • Rendering Pipeline 사진에서 Geometry Shader로부터 Stream Output으로 화살표가 이어지는 점도 주목하자.
    • GS의 Output Vertex Data는 Stream Output 단계를 통해
      Memory의 Buffer에 저장해두고 나중에 활용하는 것이 가능하다.
      • 이는 고급 기법이므로 뒤에서 자세히 설명하겠다.

Clipping

  • Frustum을 벗어난 기하 구조를 폐기하고, 교차한 것은 Frustum 내부에 있는 것만 남도록 잘라내는 연산이다.
  • 이 연산은 Hardware가 수행해주므로 이 이상 설명은 생략한다.
    • 관심이 있다면 Sutherland-Hodgeman Clipping Algorith을 살펴보기 바란다.

Rasterizer

  • Rasterization Stage라고도 하며, 주 임무는 투영된 3차원 삼각형으로부터 Pixel 색상들을 계산하는 것이다.

Viewport Transform

  • Clipping을 마치고 나면 Hardware는 Prespective Divide를 수행해서 Homogeneous Clip Space Coordinate를
    Normalized Device Coordinate로 변환한다.
  • Vertex들이 NDC Space에 들어왔다면, 2차원 이미지를 형성하는 x, y 성분들을
    Back Buffer의 한 직사각형 영역으로 변환한다. 
    • 이 직사각형 영역이 Viewport이다.
  • 이 Transform을 마치면 x, y 성분은 Pixel 단위의 값이 된다.
    • 일반적으로 Viewport Transform은 z성분은 변경하지 않는다.
      • 이 성분을 Depth Buffering에 사용해야하기 때문이다.
    • 하지만 D3D12_VIEWPORT의 MinDepth와 MaxDepth의 값은 변경할 수 있다.
      • 단, 반드시 [0, 1] 범위 안에 들어와야 한다.

Backface Culling

  • 하나의 삼각형에는 두 개의 면이 있는데, 이를 구분하기 위해서는 다음 관례를 사용한다.
    • 삼각형의 한 Vertex를 기준으로, 다른 Vertex로 향하는 Vector e1, e2를 생성한다.
    • e1과 e2를 Cross Product 한 후 Normalize 한 결과를 n이라 하자.
    • Vector n과 같은 방향인 면을 Front Side, 반대 쪽을 Back Side라 한다.
  • 관찰자가 삼각형의 앞면을 보고 있는 경우, 그 삼각형을 Front-Facing 삼각형이라 한다.
    • 반대로 뒷면을 보고 있으면 Back-Facing 삼각형이라 한다.
  • 또한 DirectX의 관례를 통해 Vertex가 시계방향으로 나열된 삼각형을 Front-Facing 삼각형으로 판단 가능하다.
  • 3차원 장면에서 대부분의 물체는 Closed Solid Object이다.
  • 각 물체의 삼각형들을 항상 Normal Vector가 물체 바깥쪽으로 향하도록 구성한다고 하자.
    • 그럼 카메라에는 Solid Object의 Back-Facing 삼각형이 보이지 않는다.
      • Back-Facing 삼각형은 모두 Front-Facing 삼각형에 가려지기 때문이다.
    • 때문에 Back-Facing 삼각형은 화면에 그릴 필요가 전혀 없어진다.
  • 이러한 Backface Culling은 Pipeline에서 이러한 삼각형을 골라서 폐기하는 공정이다.
  • 이 과정을 통해 처리해야 할 삼각형의 수가 거의 절반으로 줄어들 수 있다.
  • Direct3D는 일반적으로 시계방향으로 감긴 삼각형을 Front-Facing 삼각형으로 간주한다.
    • 하지만 Render State의 설정에 따라 그 반대도 가능하다.

Vertex 특성 Interpolation

  • 이전에 한번 삼각형은 Vertex들로 정의된다고 말한 적이 있을 것이다.
    • 또한 Vertex Data에는 위치 정보뿐만 아니라 색상, Normal Vector, Texture Coordinates 같은 
      추가적인 특성을 붙일 수 있다는 점도 언급했을 것이다.
  • Viewport Transformation을 거친 후에는 Vertex의 그러한 특성들을 Pixel들에게 Interpolation 해야한다.
    • Vertex 뿐만 아니라 Depth value도 Interpolation 해야 한다.
  • 그럼 각 Pixel마다 Depth Buffering Algorithm을 위한 값이 생성된다.
  • 이러한 Vertex 특성들은 View Space에서 Interpolation되는데, 
    3차원 공간에서는 삼각형의 면을 따라 Linear하게 Interpolation 된다.
    • 이를 위해서는 원근 보정 보간(Perspective Correct Interpolation)이 필요하다.
  • Perspective Correct Interpolation은 Hardware가 수행하므로 수학적 세부사항을 굳이 알 필요는 없다.

Pixel Shader

  • Pixel Shader(PS)는 프로그래머가 작성해서 GPU에서 실행하는 프로그램으로, 각 Pixel Fragment마다 실행된다.
  • PS는 기본적으로 Interpolation 된 Vertex 특성들을 입력받아 하나의 색상을 출력한다.
    • 이는 고정된 상수 색을 돌려주는 간단한 형태서부터
      Pixel별 조명, 반사, 그림자 효과를 수행하는 복잡한 형태까지 다양하다.

Output Merger

  • Pixel Shder가 생성한 Pixel Fragment들은 Output Merger(OM)단계로 입력된다.
  • Output Merger에서는 일부 Pixel Fragment들이 폐기된다.
    • Depth나 Stencil 판정으로 인해
  • 기각되지 않은 Pixel Fragment는 Back Buffer에 기록된다.
  • Blending도 이 단계에서 일어난다.

Blending

  • Blending은 새 Pixel이 Back Buffer의 기존 Pixel을 덮어쓰는 것이 아니라
    두 Pixel을 일정한 공식에 따라 혼합된 결과를 기록하는 것을 말한다.
  • Blending은 주로 반투명과 같은 특수효과를 내는데 쓰인다.
  • 이에 대해서는 뒤에서 더 자세히 다룰 예정이다.
  • 이번 챕터에서는 총 두가지 이론을 다룰 것이다.
    • 평평한 2차원 모니터 화면을 통해서 3차원 세상을 보고 있다는 환상의 주요 요소
    • 색상을 수학적으로, 그리고 Direct3D 코드 안에서 표현하고 다루는 방법

3차원의 환상

  • 3차원 세계의 깊이와 부피를 2차원 평면으로 나타내는 방법은 이미 충분히 연구되어 왔다.
    • 소실점(Vanishing Point)
      • 시각 평행선들이 수렴하는 점.
    • 물체 겹침(Object Overlay)
      • 불투명한 물체가 그 뒤에 있는 물체의 일부 또는 전체를 가리는 현상.
    • 조명, 셰이딩
      • 광원으로 인한 밝기 변화를 적용함으로써 부피를 표현
    • 그림자
      • 그림자는 장면에서 광원이 있는 위치를 말해준다.
      • 또한 물체의 위치를 대략적으로 제시하기도 한다.

모형의 표현

  • Direct3D Application에서는 일반적으로 Solid의 3차원 물체를 삼각형 Mesh로 Approximation한다.
  • 실제 세상의 그 어떤 3차원 물체라도 삼각형 mesh로 표현이 가능하다.
  • 일반적으로, 근사하는데 사용한 삼각형이 많을수록 요구되는 처리 능력이 높아진다.
    • 따라서 사용자의 Hardware 성능에 근거하여 적절한 균형점을 찾아야 한다.
  • 삼각형 외에 섬이나 점도 물체를 근사하는데 유용하다.
    • 예를 들어 곡선은 한 Pixel 굵기의 짧은 선분들로 근사가 가능하다.
  • 모든 3차원 물체를 삼각형 mesh로 표현이 가능하지만, 이를 일일이 나열하는 작업은 매우 성가시다.
    • 그래서 간단한 모형이 아닌 이상 모형을 생성, 조작할 때에는 3D Modeler를 사용한다.
    • 3D Studio Max, LightWave 3D, Maya, Softimage, Blender 등

색상 표현의 기초

  • 컴퓨터 모니터는 Pixel마다 Red, Green, Blue 색의 빛을 섞어서 방출한다.
  • 모니터가 방출하는 RGB의 세기에는 상한과 하한이 존재한다.
    • 보통 [0, 1]의 정규화된 값을 사용한다.
    • 0은 빛이 전혀 ㅇ벗는 것이고, 1은 세기가 최대인 것이다.
    • 예를 들어 (0.25, 0.67, 1.0)은 25%의 Red, 67%의 Green, 100%의 Blue가 혼합되었다는 의미이다.
  • 이는 하나의 색상을 하나의 3차원 색상 Vector (r, g, b)로 나타낼 수 있음을 의미한다.

색상 연산

  • Vector 연산 중 몇가지 연산은 색상 Vector에도 그대로 적용이 가능하다.
    • 덧셈, 뺄셈, 스칼라 곱 등은 그대로 적용이 된다.
  • 반면 Dot Product, Cross Product와 같은 연산은 의미를 퇴색한다.
  • 색상 Vector만의 연산도 존재한다.
    • 변조(Modulation) 혹은 성분별 곱셈(Componentwise multiplication)이라고도 한다.

Modulation

  • Modulation은 주로 조명 공식에 쓰인다.
  • 색상 연산은 성분이 [0, 1] 범위 바깥으로 나갈 수 있다.
    • 하지만 1.0이 최대 세기이므로 이보다 큰 성분은 1.0으로 clamping 해야 한다.
    • 마찬가지로 음의 세기도 0.0으로 clamping 해야 한다.

128비트 색상

  • 보통 RGB 외에 A(Alpha)라는 성분을 색상 Vector에 추가하는 경우가 많다.
  • Alpha는 색상의 불투명도를 나타내는데, 이는 색상 혼합에 유용하다.
  • Alpha를 색상 Vector에 추가함으로서 하나의 색상은 각 성분 별 32bit float형으로 총 128bit로 표현된다.
    • 수학적으로는 하나의 4차원 Vector이므로, 코드상에서도 그냥 XMVector를 이용한다.
    • 그러면 XNA Math 라이브러리의 Vector 함수들로 연산 할 때 SIMD의 이득을 취할 수 있다
  • 색상 Vector의 Modulate에 대해서는 XNA Math 라이브러리의 다음 함수를 사용한다.
XMVECTOR    XM_CALLCONV     XMColorModulate(FXMVECTOR C1, FXMVECTOR C2);

32비트 색상

  • 각 성분당 1Byte를 할당해서 32bit로 하나의 색상을 표현할 수도 있다.
    • 성분 당 0에서 255로 16,777,216가지의 색을 표현하는 것이 가능하다.
  • XNA Math 라이브러리는 이를 위해 다음 구조체를 제공한다.
struct XMCOLOR {
  union {
    struct {
      uint8_t b;
      uint8_t g;
      uint8_t r;
      uint8_t a;
    };
    uint32_t c;
  };
  void         XMCOLOR();
  void         XMCOLOR(
    const XMCOLOR & 
  );
  XMCOLOR &    operator=(
    const XMCOLOR & 
  );
  void         XMCOLOR(
    XMCOLOR && 
  );
  XMCOLOR &    operator=(
    XMCOLOR && 
  );
  XM_CONSTEXPR XMCOLOR(
    uint32_t Color
  );
  void         XMCOLOR(
    float _r,
    float _g,
    float _b,
    float _a
  );
  void         XMCOLOR(
    const float *pArray
  );
  void         operator uint32_t();
  XMCOLOR &    operator=(
    const uint32_t Color
  );
};

https://docs.microsoft.com/en-us/windows/win32/api/directxpackedvector/ns-directxpackedvector-xmcolor

 

XMCOLOR (directxpackedvector.h) - Win32 apps

A 32-bit Alpha Red Green Blue (ARGB) color vector, where each color channel is specified as an unsigned 8 bit integer.

docs.microsoft.com

  • 32bit 색상을 128bit 색상으로 바꿀 때는 각 성분을 255로 나누면 된다.
  • 반대로 128bit 색상을 32bit 색상으로 바꿀 때는 각 성분을 255를 곱하면 된다.
    • 단, 32bit 색상은 4개의 8bit 색상 성분을 하나의 32bit 정수형 자료형(ex. unsigned int)에 넣은 형태이다.
    • 때문에 추가적인 비트 연산이 필요하다. 이는 XMCOLOR도 해당된다.
  • 이에 대해서 XNA Math 라이브러리는 다음 함수를 제공한다.
XMVECTOR XM_CALLCONV XMLoadColor(
  const XMCOLOR *pSource
);
  • 반대로 XMVECTOR 색상을 XMCOLOR로 변환하는 함수도 제공한다.
void XM_CALLCONV XMStoreColor(
  XMCOLOR   *pDestination,
  FXMVECTOR V
);
  • 하지만 이 함수들과 XMColor는 DirectX11이나 DirectXPackedVector.h에 존재한다.
  • DirectX12 예시에서는 XNA Math가 아니라 DirectXMath.h를 사용하는데, 이 안에 흔적을 찾을 수 없었다.
    • 아무래도 DirectX12에 와서는 일반 XMUInt4를 사용하는 것 같다.
struct XMUINT4
{
    uint32_t x;
    uint32_t y;
    uint32_t z;
    uint32_t w;

    XMUINT4() = default;

    XMUINT4(const XMUINT4&) = default;
    XMUINT4& operator=(const XMUINT4&) = default;

    XMUINT4(XMUINT4&&) = default;
    XMUINT4& operator=(XMUINT4&&) = default;

    XM_CONSTEXPR XMUINT4(uint32_t _x, uint32_t _y, uint32_t _z, uint32_t _w) : x(_x), y(_y), z(_z), w(_w) {}
    explicit XMUINT4(_In_reads_(4) const uint32_t *pArray) : x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {}
};

XMVECTOR    XM_CALLCONV     XMLoadUInt4(_In_ const XMUINT4* pSource);
void        XM_CALLCONV     XMStoreUInt4(_Out_ XMUINT4* pDestination, _In_ FXMVECTOR V);
  • 다시 돌아와, 128bit 색상은 Pixel Shader와 같이 다수의 색상 연산이 진행되는 곳에 쓰인다.
    • 정밀도를 위한 비트가 많으므로 산술 오차가 과도하게 누적되는 일이 없기 때문이다.
  • 하지만 최종적인 Pixel 색상은 일반적으로 Back Buffer에 32bit로 저장된다.
  • 현재의 물리적 디스플레이 장치들은 고해상도 색상의 장점을 취하지 못한다.

Direct3D의 초기화 작업에 필요한 기본 형식 및 몇 가지 기본적인 그래픽 개념을 다뤄보겠다.

 

Direct3D의 개요

  • Direct3D는 그래픽 하드웨어를 제어할 수 있는 소프트웨어 인터페이스를 제공한다.
  • Graphic Hardware가 Direct3D capable device이기만 하다면 Hardware의 구체적인 세부사항을 걱정할 필요가 없다.
    • 물론 다중 표본화 횟수 등 Hardware마다 다를 수 있는 항목들도 존재한다.
    • 그래도 Direct3D capable device는 반드시 Direct3D capability set 전체를 지원해야 한다.
  • 이는 Direct3D 9와 비교해서 가장 두드러지는 특징이다

COM(Component Object Model)

  • COM은 DirectX의 프로그래밍 언어 독립성과 하위 호환성을 가능하게 하는 기술이다.
  • C++로 DirectX Application을 개발 할  때에 세부 사항 대부분이 드러나지 않는다.
    • 프로그래머가 알아야 할 것은 COM Interface로의 포인터를 얻는 방법 뿐이다.
  • COM Interface는 C++의 new 키워드로 생성하지 않는다.
    • 또한 삭제 할 때도 delete가 아니라 Interface의 Release 메서드를 호출한다.
    • 이는 COM Object들이 자신만의 고유한 방식으로 메모리를 관리하기 때문이다.
  • COM에 관한 이야기는 이보다 훨씬 많지만, DirectX를 사용하는데 이 이상은 불필요하다.
  • 마지막으로 COM Interface들은 Class 이름이 I로 시작한다.
    • 예를 들어 DirectX11의 2차원 Texture를 나타내는 COM Interface는 ID3D11Texture2D이다.

텍스처 및 자료 자원 형식

  • 2차원 Texture는 2차원 배열로 원소 당 2차원 이미지 한 Pixel의 색상을 담는다.
  • 하지만 이것이 2차원 Texture의 유일한 용도는 아니다.
    • 예를 들어, 법선 매핑에서는 Texture의 각 원소가 색상이 아니라 3차원 Vector를 담는다.
  • Texture라고 하면 이미지 자료 저장을 떠올리지만, 실제로는 그보다 훨씬 범용적이다.
    • Texture에는 밉맵 수준들이 존재할 수도 있다.
    • GPU Filtering, 다중 표본화 등의 특별한 연산도 적용할 수 있따.
    • 더 나아가 아무 자료나 아무 자료나 담을 수 있는 것은 아니라는 점도 중요하다.
  • Texture는 DXGI_FORMAT이라는 Enumeration으로 지정된 format을 따르는 자료만 담을 수 있다.

https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format

 

DXGI_FORMAT (dxgiformat.h) - Win32 apps

Resource data formats, including fully-typed and typeless formats. A list of modifiers at the bottom of the page more fully describes each format type.

docs.microsoft.com

  • 몇가지만 예시를 들어보겠다.
    • DXGI_FORMAT_R32G32B32_FLOAT
      • 각 원소는 32비트 float 3개로 이루어진다.
    • DXGI_FORMAT_R16G16B16_UNORM
      • 각 원소는 [0, 1] 구가능로 사상되는 16비트 성분 4개로 이루어진다.
    • DXGI_FORMAT_R8G8B8A8_SINT
      • 각 원소는 [-128, 127] 구간으로 사상되는 8비트 signed int 성분 4개로 이루어진다.
  • 여기서 R, G, B, A는 빛의 삼원색과 알파(Alpha, 투명도) 성분을 뜻한다.
  • 앞서 얘기 했듯이, RGBA라 하여 반드시 Texture에 색상을 담아야 하는 것은 아니다.
    • 예를 들어 DXGI_FORMAT_R32G32B32_FLOAT에는 float로 이루어진 3차원 Vector를 담을 수 있다.
  • 또한 typeless한 Texture도 존재한다.
    • 이는 메모리만 확보해두고 자료의 구체적인 해석 방식은 나중에 Texture를 Pipeline에 묶을 때 지정한다.
    • C++의 reinterpret_cast와 비슷하다고 생각하면 된다.
    • 예를 들어, 8비트 성분 4개를 할당하되, 구체적인 자료 형식을 지정하지 않으려면 DXGI_FORMAT_R8G8B8A8_TYPELESS를 지정하면 된다.

교환 사실과 페이지 전환

  • 페이지 전환은 애니메이션이 깜빡대는 현상을 방지하기 위한 최선의 방법은 버퍼링이다.
    • 애니메이션 한 프레임 전체를 후면 버퍼에 그린 뒤, 하나의 프레임으로 화면에 표시를 한다.
    • 이는 사용자에게 프레임이 그려지는 과정을 나타내지 않는다는 장점이 있다.
  • 버퍼링을 구현 할 때에는 Hardware로 두 개의 Texture Buffer를 관리한다.
    • 화면 모니터에 표시되는 이미지 자료를 담는 것은 전면 버퍼(front buffer)
    • 화면 밖에서 현재 화면에 표시되고 있는 프레임 다음 것을 담는 것은 후면 버퍼(back buffer)
    • Front Buffer가 화면에 표시되면 Back Buffer와 역할을 맞바꾼다.
  • Back Buffer를 Front Buffer와 교환하여 화면에 표시되게 하는 것을 제시(Presenting)이라 부른다.
  • Presenting은 Buffer의 내용이 아니라 각 Buffer의 Pointer를 맞바꾸는 매우 효율적인 연산이다.
  • Front Buffer와 Back Buffer는 하나의 교환 사슬(Swap Chain)을 형성한다.
    • 이는 IDXGISwapChain이라는 Interface로 대표된다.
    • 이 Interface는 Front Buffer와 Back Buffer를 담을 수 있다.
    • 또한 Buffer 크기 변경을 위한 ResizeBuffers와 Presenting을 위한 Present 메소드를 제공한다.
  • 물론 2개 이상의 Buffer를 사용할 수도 있다. 하지만 일반적으로 2개면 충분하다.

깊이 버퍼링(Depth Buffer)

  • Depth Buffer는 이미지 자료를 담지 않는 Texture의 한 예로 각 Pixel의 깊이 정보를 담는다.
    • Pixel의 깊이는 [0.0, 1.0]으로 값이 작을수록 관찰자와 가깝다.
  • Depth Buffer는 Back Buffer의 Pixel들과 일대일 대응 관계이다.
    • 구체적으로 Buffer의 크기와 대응하는 Pixel들의 Index까지 동일하다.
  • Direct3D에서는 한 물체의 Pixel들이 다른 물체 앞에 있는지 판정 하기 위해 Depth Buffering 혹은 Z-Buffering을 사용한다.
  • Depth Buffering에서는 그리는 순서는 중요하지 않다.
    • 실제로 먼 물체부터 그리는 방법으로 깊이 문제를 해결하기도 한다.
    • 하지만 이는 크게 두가지 문제가 발생한다.
      • 하나는 커다란 자료 집합을 정렬 해야 한다는 것.
      • 다른 하나는 맞물린 형태의 물체들은 제대로 처리하지 못한다는 것.
    • 이에 반해 Depth Buffering은 Graphic Hardware 안에서 공짜로 일어나며, 맞물린 물체도 제대로 처리해준다.

Depth Buffer의 작동 방식

  • 먼저 Back Buffer를 흰색이나 검은색과 같은 기본 색으로 지운다.
    • 이 때 Depth Buffer의 기본 값인 1.0으로 초기화 된다.
  • 이후 Rendering 할 물체의 Depth Buffer 값을 비교한다.
    • 물체의 Depth 값이 Depth Buffer 값보다 작을 경우, Depth Buffer와 함께 Back Buffer 값이 갱신된다.
  • Depth 값이 작을수록 관찰자와 가까운 곳에 위치한다는 점을 생각한다면 실로 합당한 알고리즘이다.
  • Depth Buffer도 하나의 Texture이므로 특정한 자료 형식을 지정해서 생성해야 한다.
    • 이 또한 DXGI_FORMAT이란 Enumeration을 따른다.
  • 몇가지 예시를 들어보자.
    • DXGI_FORMAT_D32_FLOAT_S8X24_UINT
      • 각 Texel은 32bit의 float형의 depth 값을 가진다.
      • Stencil은 [0, 255] 구간으로 사상되는 8bit의 unsigned int형의 값을 가진다.
      • 마지막으로 용도 없이 채우는 용으로만 쓰이는 24bit의 padding으로 구성된다.
    • DXGI_FORMAT_D32_FLOAT
      • 각 Texel은 하나의 32bit float형의 depth 값을 가진다.
    • DXGI_FORMAT_D24_UNORM_S8_UINT
      • 각 Texel은 [0, 1] 구간으로 사사오디는 unsigned 24bit의 depth 값을 가진다.
      • Stencil은 [0, 255]구간으로 사상되는 8bit unsigned int형의 값을 가진다.
  • 사실 Depth Buffer는 Depth-Stencil Buffer이라 부르는 것이 더 정확하다.
    • Stencil Buffer를 사용하려면 반드시 Depth Buffer에 부착해야 하기 때문이다.

텍스처 자원 뷰

  • Rendering Pipeline에 Texture를 bind 할 수 있는 단계는 여럿 있따.
  • 흔한 용도는 다음 두가지이다.
    • Texture를 Render 대상으로 묶는 것.
      • Direct3D가 Texture에 Rendering하는 경우.
    • Shader Resource로서 묶는 것
      • Shader 안에서 Texture를 추출하는 경우
  • 이 두가지 용도의 Texture를 생성할 때에는 다음과 같이 Texture를 bind 할 단계를 지정하는 bind flag를 사용한다.
D3D11_BIND_RENDER_TARET | D3D11_BIND_SHADER_RESOURCE

 

 

https://docs.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_bind_flag

 

D3D11_BIND_FLAG (d3d11.h) - Win32 apps

Identifies how to bind a resource to the pipeline.

docs.microsoft.com

  • 사실 Texture Resource가 Pipeline이 단계에 직접 묶이는 것은 아니다.
    • 실제로는 Texture가 연고나된 Resource View를 Pipeline 단계에 묶는다.
  • 어떤 용도이든 Direct3D에서 Texture를 사용하려면 초기화 시점에서 그 Texture의 Resource View를 생성해야 한다.
  • 이러한 절차는 효율성을 위한 것으로, SDK에는 다음과 같이 나와 있다.
    • 이렇게 하면 실행시점 Runtime과 Driver가 유효성 점검과 매핑을 뷰 생성 시점에서 수행할 수 있기 때문에 결속 시점에서의 형식 점검이 최소화 된다.
  • 따라서 Texture를 Render 대상과 Shader Resource로 사용하기 위해서는 두 개의 View를 생성할 필요가 있다.
    • 하나는 Render 대상 View인 ID3D11RenderTargetView
    • 다른 하나는 Shader Resource View인 ID3D11ShaderResourceView
  • Resource View는 본질적으로 두가지 일을 한다.
    • 하나는 DirectEd에게 Resource의 사용 방식(Pipeline의 어떤 단계에 묶을 것인지)을 알려주는 것.
    • 다른 하나는 생성 시점에서 Typeless Resource의 Type을 지정하는 것.
      • 이 경우, Texture 원소를 한 Pipeline 단계에서는 float로 사용하고, 다른 단계에서는 int로 사용할 수 있다.
  • 어떤 Resource에 대한 View를 생성할 수 있으려면 애초에 해당 Resource을 생성 할 때 Bind Flag를 지정해야 한다.
    • 예를 들어 D3D11_BIND_DEPTH_STENCIL flag를 지정하지 않고 생성한 자원은 ID3D11DepthStencilView를 생성할 수 없다.
    • 무작정 생성하려 하면 다음 오류 메시지가 뜬다
D3D11: ERROR: ID3D11Device::CreateDepthStencilView: A DepthStencilView 
cannot be created of a Resource that did not specify 
D3D11_BIND_DEPTH_STENCIL.

다중표본화의 이론

  • 모니터의 Pixel들은 무한히 작지 않기 위해, 임의의 선을 완벽하게 나타내는 것은 불가능하다.
    • 때문에 Aliasing 효과가 나타나기도 한다.
  • 모니터 해상도를 키워 Pixel 크기를 줄이면 문제를 완화할 수도 있다.
  • 그러나 모니터 해상도를 키우는 것으로 해결 못할 때도 있는데, 이를 위해 Anti Aliasing 기법이 존재한다.
    • 다중표본화(Super Sampling)도 그 중 하나이다.
  • Super Sampling에서는 Back Buffer와 Depth Buffer를 화면 해상도의 4배로 크게 잡는다.
  • 3차원 장면을 4배 크기의 해상도에서 Back Buffer에 Rendering한다.
  • 이를 화면에 출력 할 때는 원래 크기로 Resolving(혹은 down sampling)한다.
  • 이 Resolving 공정은 4개의 Pixel 블록의 각 색상의 평균을 최종 색상으로 사용한다.
    • 간단히 말해서, Super Sampling은 화면 해상도를 Software 상에서 증가하는 것이라 할 수 있다.
  • Super Sampling은 Pixle 처리량과 메모리 소비량이 4배라서 비용이 크다.
    • Direct3D는 이를 대신해 다중표본화(Multi Sampling)라는 절충안을 지원한다.
  • Multi Sampling은 일부 계산 결과를 부분 Pixel들 사이에서 공유하기 때문이 Super Sampling보다 비용이 적다.
    • 4X Multi Sampling의 경우 Super Sampling처럼 해상도 4배의 Depth Buffer와 Back Buffer를 사용한다.
    • 그러나 이미지 색상을 각 부분 Pixel마다 계산하는 것이 아니다.
      • Pixel마다 1번씩만 계산하고, 그 색상과 부분 Pixel들의 가시성과 포괄도를 이용해 최종 색을 결정한다.
        • 가시성: 이를 위해 부분 Pixel당 Depth, Stencil 판정이 일어남
        • 포괄도: 부분 Pixel을 다각형이 어느정도나 덮고 있는지를 뜻하는 값
  • Multi Sampling과 Super Sampling의 차이를 잘 파악하자.
    • Super Sampling은 Pixel별로 계산되므로 한 Pixel당 부분 Pixel의 값이 다를 수 있다.
    • 반면 Multi Sampling은 Pixel당 한번 계산되어 덮인 모든 가시 부분 Pixel에 계산된다.
  • 색을 계산하는 것은 매우 무거운 연산이다.
    • 때문에 Multi Sampling이 Super Sampling에 비해 상당한 비용 절감을 이룬 방식이다.
    • 하지만 더 정확하한 Texture와 Shader Aliasing 처리는 Super Sampling이 월등하다.
  • 하나의 Pixel을 부분 Pixel로 분할하는 패턴은 Hardware 제조사마다 다를 수 있다.

Direct3D의 Multi Sampling

  • Multi Sampling을 위해서는 DXGI_SAMPLE_DESC라는 구조체를 채워야 한다.
typedef struct DXGI_SAMPLE_DESC
{
    UINT Count;
    UINT Quality;
} DXGI_SAMPLE_DESC;
  • Count
    • Pixel당 추출할 표본의 개수
  • Quality
    • 원하는 품질 수준을 지정한다.
      • "품질 수준"에 대한 구체적인 의미는 Hardware 제조사마다 다를 수 있다.
  • 표본 개수가 많을수록, 품질 수준이 높을수록 렌더링 비용이 증가한다.
    • 때문에 비용과 속도 사이에 절충선을 잘 잡아야 한다.
  • 품질 수준들의 범위는 Texture type과 Pixel당 표본 개수에 의존한다.
  • 주어진 Texture type과 표본 개수의 조합에 대한 품질 수준들의 개수를 알고 싶을때는 다음 메소드를 사용한다.
    • 주어진 Texture type과 표본 개수의 조합을 장치가 지원하지 않으면 0을 반환한다.
    • 그 외에는 주어진 조합에 대한 품질 수준 개수가 pNumQualityLevels에 저장된다.
      • 하나의 Texture type과 표본 개수 조합에 대한 유효한 품질 수준은 [0, *pNumQualityLevels - 1]이다.
HRESULT ID3D11Device::CheckMultisampleQualityLevels(
  DXGI_FORMAT Format,
  UINT        SampleCount,
  UINT        *pNumQualityLevels
);
  • D3D12에서는 해당 객체에 이 메서드를 찾을 수 없었다. 대신 다음 구조체를 발견할 수 있었다.
typedef struct D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS {
  DXGI_FORMAT                           Format;
  UINT                                  SampleCount;
  D3D12_MULTISAMPLE_QUALITY_LEVEL_FLAGS Flags;
  UINT                                  NumQualityLevels;
} D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS;
  • 한 Pixel당 추출할 수 있는 최대 표본 개수는 다음과 같이 정의된다.
#define	D3D12_MAX_MULTISAMPLE_SAMPLE_COUNT	( 32 )
  • 그러나 실제로는 Multi Sampling의 성능과 메모리 비용을 합리적인 수준으로 유지하기 위해 4개나 8개만 추출한다.
  • Multi Sampling을 하고 싶지 않으면 표본 개수를 1로, 품질 수준을 0으로 설정하면 된다.

기능 수준(Feature Level)

  • Direct3D 11에서 도입된 Feature Level은 엄밀한 기능성 집합을 정의한다.
typedef 
enum D3D_FEATURE_LEVEL
    {
        D3D_FEATURE_LEVEL_1_0_CORE	= 0x1000,
        D3D_FEATURE_LEVEL_9_1	= 0x9100,
        D3D_FEATURE_LEVEL_9_2	= 0x9200,
        D3D_FEATURE_LEVEL_9_3	= 0x9300,
        D3D_FEATURE_LEVEL_10_0	= 0xa000,
        D3D_FEATURE_LEVEL_10_1	= 0xa100,
        D3D_FEATURE_LEVEL_11_0	= 0xb000,
        D3D_FEATURE_LEVEL_11_1	= 0xb100,
        D3D_FEATURE_LEVEL_12_0	= 0xc000,
        D3D_FEATURE_LEVEL_12_1	= 0xc100
    } 	D3D_FEATURE_LEVEL;
  • 이 기능의 핵심은, 사용자의 Hardware가 특정 Feature Level을 지원하지 않는다면 그보다 더 낮은(오래된) Feature Level로 내려가서 Application을 실행한다는 것이다.
    • 이를 위해 Application은 Feature Level 지원 여부를 최신서부터 오래된 순서로 하나씩 점검해나간다.

 

------------------------------------------------

책에서 4-1챕터에 해당하는 내용들입니다.

이론적인 부분과 코드 부분이 많아서 분리해서 올리고자 합니다.

다음에는 5챕터에 해당하는 내용을 정리하고, 그 다음에는 4-2부터 남은 4챕터의 내용을 코드와 함께 정리하겠습니다.

지원한 회사에서 과제를 제출하라는 연락을 받아 2주 가량 쉽니다.

+ Recent posts