#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);
}
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 설명 )