Direct3D 11 with Direct2D Sample

프로그래머 민트찰떡·2024년 9월 25일
#include <Windows.h>
#include <cstdint>
#include <d3d11_1.h>
#include <dxgi1_2.h>
#include <d2d1.h>
#include <dwrite.h>

#pragma comment(lib, "d3d11.lib")
#pragma comment(lib, "d2d1.lib")
#pragma comment(lib, "dwrite.lib")

LRESULT WINAPI GlobalWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);

class IWindow
{
public:
	struct CreationDesc
	{
		CreationDesc() : CreationDesc(800, 600, L"Default Title") { __noop; }
		CreationDesc(uint32_t width, uint32_t height, const wchar_t* title) :_width{ width }, _height{ height }, _title{ title }{ __noop; }
		uint32_t _width;
		uint32_t _height;
		const wchar_t* _title;
	};
	virtual bool Create(const CreationDesc& creationDesc) = 0;
	virtual uint64_t GetNativeWindowHandle() const = 0;
	uint32_t GetWidth() const { return _creationDesc._width; }
	uint32_t GetHeight() const { return _creationDesc._height; }

protected:
	CreationDesc	_creationDesc;
};

class IGraphicDevice
{
public:
	IGraphicDevice(IWindow& window) : _window(window) { __noop; }
	~IGraphicDevice() = default;

public:
	virtual bool Create() = 0;
	virtual void Render() = 0;

protected:
	IWindow& _window;
};

template<typename T>
class SafeCOM
{
public:
	SafeCOM() :_ptr{} { __noop; }
	~SafeCOM() { Release(); }
	void Release() { if (_ptr) { _ptr->Release(); _ptr = nullptr; } }
	T** ReleaseAndGetAddress() { Release(); return (T**)&_ptr; }
	T* Get() { return (T*)_ptr; }
	T** GetAddress() const { return (T**)&_ptr; }
	const T* Get() const { return (const T*)_ptr; }
	T* operator->() { return (T*)_ptr; }
	const T* operator->() const { return (const T*)_ptr; }
private:
	IUnknown* _ptr;
};

class WindowsWindow : public IWindow
{
public:
	WindowsWindow(HINSTANCE hInstance)
		: _hInstance{ hInstance }
		, _windowClass{}
		, _hWnd{}
	{
		__noop;
	}
	~WindowsWindow() = default;

public:
	virtual bool Create(const CreationDesc& creationDesc) override final
	{
		_creationDesc = creationDesc;

		_windowClass.cbSize = sizeof(_windowClass);
		_windowClass.style = CS_HREDRAW | CS_VREDRAW;
		_windowClass.lpfnWndProc = ::GlobalWindowProc;
		_windowClass.cbClsExtra = 0;
		_windowClass.cbWndExtra = 0;
		_windowClass.hInstance = _hInstance;
		_windowClass.hIcon = nullptr;
		_windowClass.hCursor = ::LoadCursor(nullptr, IDC_ARROW);
		_windowClass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
		_windowClass.lpszMenuName = nullptr;
		_windowClass.lpszClassName = L"TutorialWindowClass";
		_windowClass.hIconSm = nullptr;

		// process 가 종료될 때 자동으로 Unregister 된다.
		if (::RegisterClassEx(&_windowClass) == 0)
			return false;

		const DWORD kWindowStyle = WS_OVERLAPPEDWINDOW;
		RECT rc = { 0, 0, (LONG)creationDesc._width, (LONG)creationDesc._height };
		if (::AdjustWindowRect(&rc, kWindowStyle, FALSE) == 0)
			return false;

		// WM_CLOSE 에서 ::DestroyWindow 를 호출해야 하는데, ::DefWindowProc 에서 이미 그렇게 하고 있음.
		_hWnd = ::CreateWindow(_windowClass.lpszClassName, creationDesc._title, kWindowStyle, CW_USEDEFAULT, CW_USEDEFAULT,
			rc.right - rc.left, rc.bottom - rc.top, nullptr, nullptr, _hInstance, nullptr);

		if (_hWnd == 0)
			return false;

		// return 값이 success/fail 이 아님...
		::ShowWindow(_hWnd, SW_NORMAL);

		::SetWindowLongPtr(_hWnd, GWLP_USERDATA, (LONG_PTR)this);
		return true;
	}

	LRESULT HandleMessage(UINT Msg, WPARAM wParam, LPARAM lParam)
	{
		PAINTSTRUCT paintStruct;
		HDC deviceContextHandle;
		switch (Msg)
		{
		case WM_PAINT:
			deviceContextHandle = ::BeginPaint(_hWnd, &paintStruct);
			::EndPaint(_hWnd, &paintStruct);
			break;
		case WM_DESTROY:
			::PostQuitMessage(0);
			break;
		}
		return ::DefWindowProc(_hWnd, Msg, wParam, lParam);
	}

	virtual uint64_t GetNativeWindowHandle() const override final { return reinterpret_cast<uint64_t>(_hWnd); }

private:
	HINSTANCE _hInstance;
	WNDCLASSEX _windowClass;
	HWND _hWnd;
};

class D3DGraphicDevice : public IGraphicDevice
{
public:
	D3DGraphicDevice(IWindow& window) : IGraphicDevice(window) { __noop; }
	~D3DGraphicDevice() = default;

public:
	virtual bool Create() override final
	{
		UINT createDeviceFlags = 0;
#if defined(_DEBUG)
		createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif // defined(_DEBUG)
		createDeviceFlags |= D3D11_CREATE_DEVICE_BGRA_SUPPORT;

		D3D_DRIVER_TYPE driverTypes[] =
		{
			D3D_DRIVER_TYPE_HARDWARE,
			D3D_DRIVER_TYPE_WARP,
			D3D_DRIVER_TYPE_REFERENCE,
		};
		size_t driverTypeCount = ARRAYSIZE(driverTypes);

		D3D_FEATURE_LEVEL featureLevels[] =
		{
			D3D_FEATURE_LEVEL_11_1,
			D3D_FEATURE_LEVEL_11_0,
			D3D_FEATURE_LEVEL_10_1,
			D3D_FEATURE_LEVEL_10_0,
		};
		UINT featureLevelCount = ARRAYSIZE(featureLevels);

		HRESULT hr{};
		for (size_t i = 0; i < driverTypeCount; i++)
		{
			_driverType = driverTypes[i];

			hr = ::D3D11CreateDevice(nullptr, _driverType, nullptr, createDeviceFlags, featureLevels, featureLevelCount,
				D3D11_SDK_VERSION, _d3dDevice.ReleaseAndGetAddress(), &_featureLevel, _d3dImmediateContext.ReleaseAndGetAddress());

			if (hr == E_INVALIDARG)
			{
				// DirectX 11.0 platforms will not recognize D3D_FEATURE_LEVEL_11_1 so we need to retry without it
				hr = ::D3D11CreateDevice(nullptr, _driverType, nullptr, createDeviceFlags, &featureLevels[1], featureLevelCount - 1,
					D3D11_SDK_VERSION, _d3dDevice.ReleaseAndGetAddress(), &_featureLevel, _d3dImmediateContext.ReleaseAndGetAddress());
			}

			if (SUCCEEDED(hr))
				break;
		}
		if (FAILED(hr))
			return false;

		// Obtain DXGI factory from device (since we used nullptr for pAdapter above)
		SafeCOM<IDXGIFactory1> dxgiFactory;
		{
			SafeCOM<IDXGIDevice> dxgiDevice;
			hr = _d3dDevice->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(dxgiDevice.ReleaseAndGetAddress()));
			if (SUCCEEDED(hr))
			{
				SafeCOM<IDXGIAdapter> adapter;
				hr = dxgiDevice->GetAdapter(adapter.ReleaseAndGetAddress());
				if (SUCCEEDED(hr))
				{
					hr = adapter->GetParent(__uuidof(IDXGIFactory1), reinterpret_cast<void**>(dxgiFactory.ReleaseAndGetAddress()));
				}
			}
		}
		if (FAILED(hr))
			return false;

		// Create swap chain
		SafeCOM<IDXGIFactory2> dxgiFactory2;
		hr = dxgiFactory->QueryInterface(__uuidof(IDXGIFactory2), reinterpret_cast<void**>(dxgiFactory2.ReleaseAndGetAddress()));
		if (dxgiFactory2.Get())
		{
			// DirectX 11.1 or later
			hr = _d3dDevice->QueryInterface(__uuidof(ID3D11Device1), reinterpret_cast<void**>(_d3dDevice1.ReleaseAndGetAddress()));
			if (SUCCEEDED(hr))
			{
				(void)_d3dImmediateContext->QueryInterface(__uuidof(ID3D11DeviceContext1), reinterpret_cast<void**>(_d3dImmediateContext1.ReleaseAndGetAddress()));
			}

			DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
			swapChainDesc.Width = _window.GetWidth();
			swapChainDesc.Height = _window.GetHeight();
			swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
			swapChainDesc.SampleDesc.Count = 1;
			swapChainDesc.SampleDesc.Quality = 0;
			swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
			swapChainDesc.BufferCount = 1;
			//swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT::DXGI_SWAP_EFFECT_FLIP_DISCARD;

			hr = dxgiFactory2->CreateSwapChainForHwnd(_d3dDevice.Get(), (HWND)_window.GetNativeWindowHandle(), &swapChainDesc, nullptr, nullptr, _dxgiSwapChain1.ReleaseAndGetAddress());
			if (SUCCEEDED(hr))
			{
				hr = _dxgiSwapChain1->QueryInterface(__uuidof(IDXGISwapChain), reinterpret_cast<void**>(_dxgiSwapChain.ReleaseAndGetAddress()));
			}
		}
		else
		{
			// DirectX 11.0 systems
			DXGI_SWAP_CHAIN_DESC swapChainDesc = {};
			swapChainDesc.BufferCount = 1;
			swapChainDesc.BufferDesc.Width = _window.GetWidth();
			swapChainDesc.BufferDesc.Height = _window.GetHeight();
			swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
			swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;
			swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;
			swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
			swapChainDesc.OutputWindow = (HWND)_window.GetNativeWindowHandle();
			swapChainDesc.SampleDesc.Count = 1;
			swapChainDesc.SampleDesc.Quality = 0;
			swapChainDesc.Windowed = TRUE;
			swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT::DXGI_SWAP_EFFECT_FLIP_DISCARD;

			hr = dxgiFactory->CreateSwapChain(_d3dDevice.Get(), &swapChainDesc, _dxgiSwapChain.ReleaseAndGetAddress());
		}

		// Note this tutorial doesn't handle full-screen swapchains so we block the ALT+ENTER shortcut
		dxgiFactory->MakeWindowAssociation((HWND)_window.GetNativeWindowHandle(), DXGI_MWA_NO_ALT_ENTER);

		if (FAILED(hr))
			return false;

		// Create a render target view
		SafeCOM<ID3D11Texture2D> d3dBackBuffer;
		hr = _dxgiSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(d3dBackBuffer.ReleaseAndGetAddress()));
		if (FAILED(hr))
			return false;


		hr = _d3dDevice->CreateRenderTargetView(d3dBackBuffer.Get(), nullptr, _d3dRenderTargetView.ReleaseAndGetAddress());
		if (FAILED(hr))
			return false;

		_d3dImmediateContext->OMSetRenderTargets(1, _d3dRenderTargetView.GetAddress(), nullptr);

		// Setup the viewport
		D3D11_VIEWPORT viewport;
		viewport.Width = (FLOAT)_window.GetWidth();
		viewport.Height = (FLOAT)_window.GetHeight();
		viewport.MinDepth = 0.0f;
		viewport.MaxDepth = 1.0f;
		viewport.TopLeftX = 0;
		viewport.TopLeftY = 0;
		_d3dImmediateContext->RSSetViewports(1, &viewport);


		{
			SafeCOM<IDXGISurface> dxgiBackBuffer;
			HRESULT hr = _dxgiSwapChain->GetBuffer(0, __uuidof(IDXGISurface), reinterpret_cast<void**>(dxgiBackBuffer.ReleaseAndGetAddress()));

			D2D1_FACTORY_OPTIONS factoryOptions{};
#if defined(_DEBUG)
			factoryOptions.debugLevel = D2D1_DEBUG_LEVEL::D2D1_DEBUG_LEVEL_INFORMATION;
#endif // defined(_DEBUG)
			hr = ::D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, factoryOptions, _d2dFactory.ReleaseAndGetAddress());
			const float dpi = (float)::GetDpiForWindow((HWND)_window.GetNativeWindowHandle());
			D2D1_RENDER_TARGET_PROPERTIES props =
				D2D1::RenderTargetProperties(
					D2D1_RENDER_TARGET_TYPE_DEFAULT,
					D2D1::PixelFormat(DXGI_FORMAT_UNKNOWN, D2D1_ALPHA_MODE_PREMULTIPLIED),
					dpi,
					dpi
				);
			HRESULT r = _d2dFactory->CreateDxgiSurfaceRenderTarget(dxgiBackBuffer.Get(), &props, _d2dBackBufferRenderTarget.ReleaseAndGetAddress());
			if (FAILED(r))
				return false;

			D2D1_COLOR_F brushColor{};
			brushColor.r = 0.0f;
			brushColor.g = 0.0f;
			brushColor.b = 0.125f;
			brushColor.a = 1.0f;
			_d2dBackBufferRenderTarget->CreateSolidColorBrush(brushColor, _d2dSolidColorBrush.ReleaseAndGetAddress());

			::GetSystemDefaultLocaleName(_localName, 128);
			::DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown**>(&_dwriteFactory));
			_dwriteFactory->CreateTextFormat(L"Consolas", nullptr, DWRITE_FONT_WEIGHT::DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE::DWRITE_FONT_STYLE_NORMAL,
				DWRITE_FONT_STRETCH::DWRITE_FONT_STRETCH_NORMAL, kFontSize, _localName, _dwriteTextFormat.ReleaseAndGetAddress());
		}

		return true;
	}

	virtual void Render() override final
	{
		// Just clear the backbuffer
		const float kClearColor[4]{ 0.25f, 0.75f, 1.0f, 1.0f };
		_d3dImmediateContext->ClearRenderTargetView(_d3dRenderTargetView.Get(), kClearColor);

		_d2dBackBufferRenderTarget->BeginDraw();
		{
			D2D1_RECT_F layoutRect;
			layoutRect.left = 10.0f;
			layoutRect.right = 100.0f;
			layoutRect.top = 10.0f;
			layoutRect.bottom = 100.0f;
			_d2dBackBufferRenderTarget->DrawTextW(L"Hello!", static_cast<UINT32>(::wcslen(L"Hello!")), _dwriteTextFormat.Get(), layoutRect, _d2dSolidColorBrush.Get());
		}
		_d2dBackBufferRenderTarget->EndDraw();

		_dxgiSwapChain->Present(0, 0);
	}

private:
	D3D_DRIVER_TYPE _driverType{};
	D3D_FEATURE_LEVEL _featureLevel{};

	SafeCOM<ID3D11Device> _d3dDevice;
	SafeCOM<ID3D11Device1> _d3dDevice1;
	SafeCOM<ID3D11DeviceContext> _d3dImmediateContext;
	SafeCOM<ID3D11DeviceContext1> _d3dImmediateContext1;
	SafeCOM<IDXGISwapChain> _dxgiSwapChain;
	SafeCOM<IDXGISwapChain1> _dxgiSwapChain1;
	SafeCOM<ID3D11RenderTargetView> _d3dRenderTargetView;

	static constexpr float kFontSize = 16.0f;
	SafeCOM<ID2D1Factory> _d2dFactory;
	SafeCOM<ID2D1RenderTarget> _d2dBackBufferRenderTarget;
	SafeCOM<ID2D1SolidColorBrush> _d2dSolidColorBrush;

	wchar_t _localName[128]{};
	SafeCOM<IDWriteFactory> _dwriteFactory;
	SafeCOM<IDWriteTextFormat> _dwriteTextFormat;
};

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)
{
	WindowsWindow window{ hInstance };
	if (window.Create(IWindow::CreationDesc(800, 600, L"Hello")) == false)
		return -1;

	D3DGraphicDevice graphicDevice(window);
	if (graphicDevice.Create() == false)
		return -1;

	MSG msg{};
	while (msg.message != WM_QUIT)
	{
		if (::PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE) != 0)
		{
			::TranslateMessage(&msg);
			::DispatchMessage(&msg);
		}
		else
		{
			graphicDevice.Render();
		}
	}
	return 0;
}

LRESULT WINAPI GlobalWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
	const LONG_PTR window = ::GetWindowLongPtr(hWnd, GWLP_USERDATA);
	if (window != 0)
	{
		return reinterpret_cast<WindowsWindow*>(window)->HandleMessage(Msg, wParam, lParam);
	}
	return ::DefWindowProc(hWnd, Msg, wParam, lParam);
}

REFERENCE

https://learn.microsoft.com/en-us/windows/win32/learnwin32/your-first-windows-program ( Win32 API 기초 )
https://learn.microsoft.com/en-us/windows/win32/learnwin32/managing-application-state- ( Win32 API 를 Object-oriented 방식으로 사용하기 )
https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/d3d10-graphics-programming-guide-dxgi
https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-graphics-pipeline
https://learn.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-rasterizer-stage-getting-started
https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-devices-enum
https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-devices-intro
https://learn.microsoft.com/en-us/windows/win32/direct3d9/what-is-a-swap-chain-
https://learn.microsoft.com/en-us/windows/win32/direct3d11/overviews-direct3d-11-resources-intro#resource-views
https://learn.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgiswapchain-getbuffer
https://learn.microsoft.com/en-us/windows/win32/api/d3d11/nn-d3d11-id3d11texture2d
https://learn.microsoft.com/en-us/windows/win32/direct2d/direct2d-and-direct3d-interoperation-overview
https://learn.microsoft.com/en-us/windows/win32/direct3darticles/surface-sharing-between-windows-graphics-apis
https://github.com/walbourn/directx-sdk-samples/blob/main/Direct3D11Tutorials/Tutorial01/Tutorial01.cpp ( Direct3D 11 Tutorial 코드 )
https://learn.microsoft.com/en-us/windows/win32/api/d3d11/ne-d3d11-d3d11_create_device_flag ( D3D11_CREATE_DEVICE_DEBUG, D3D11_CREATE_DEVICE_BGRA_SUPPORT 설명 )
https://learn.microsoft.com/en-us/windows/win32/direct3d11/using-the-debug-layer-to-test-apps ( Debug Layer 설명 )
https://learn.microsoft.com/en-us/windows/win32/api/dxgi/nf-dxgi-idxgifactory-createswapchain ( CreateSwapChainForHwnd 설명 )

profile
Game Engine Programmer

0개의 댓글