From 37c55fb7a21c2c49ff16de720ded3556e0453e0a Mon Sep 17 00:00:00 2001 From: Hunter Kvalevog Date: Sun, 30 Nov 2025 14:48:24 -0600 Subject: d3d11-resize-draw --- .gitignore | 13 +- d3d11-resize-draw/d3d11-resize-draw.slnx | 7 + d3d11-resize-draw/d3d11-resize-draw.vcxproj | 146 +++++++ .../d3d11-resize-draw.vcxproj.filters | 27 ++ d3d11-resize-draw/main.cpp | 469 +++++++++++++++++++++ d3d11-resize-draw/shader.hlsl | 30 ++ 6 files changed, 688 insertions(+), 4 deletions(-) create mode 100644 d3d11-resize-draw/d3d11-resize-draw.slnx create mode 100644 d3d11-resize-draw/d3d11-resize-draw.vcxproj create mode 100644 d3d11-resize-draw/d3d11-resize-draw.vcxproj.filters create mode 100644 d3d11-resize-draw/main.cpp create mode 100644 d3d11-resize-draw/shader.hlsl diff --git a/.gitignore b/.gitignore index b6d696d..784daa5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ -.DS_Store -.cache -.vs +*.DS_Store +*.cache +*.cso +*.exe +*.pdb +*.vcxproj.user +*.vs bin -build \ No newline at end of file +build +d3d11-re.* \ No newline at end of file diff --git a/d3d11-resize-draw/d3d11-resize-draw.slnx b/d3d11-resize-draw/d3d11-resize-draw.slnx new file mode 100644 index 0000000..49bfd17 --- /dev/null +++ b/d3d11-resize-draw/d3d11-resize-draw.slnx @@ -0,0 +1,7 @@ + + + + + + + diff --git a/d3d11-resize-draw/d3d11-resize-draw.vcxproj b/d3d11-resize-draw/d3d11-resize-draw.vcxproj new file mode 100644 index 0000000..8da36ae --- /dev/null +++ b/d3d11-resize-draw/d3d11-resize-draw.vcxproj @@ -0,0 +1,146 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 18.0 + Win32Proj + {53c908fe-a2be-4c82-8088-06bda7620da6} + d3d11resizedraw + 10.0 + + + + Application + true + v145 + Unicode + + + Application + false + v145 + true + Unicode + + + Application + true + v145 + Unicode + + + Application + false + v145 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Console + true + + + + + Level4 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Windows + true + %(AdditionalDependencies) + + + + + Level4 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + stdcpp20 + + + Windows + true + %(AdditionalDependencies) + + + + + + + + Pixel + Pixel + Pixel + Pixel + Document + + + + + + \ No newline at end of file diff --git a/d3d11-resize-draw/d3d11-resize-draw.vcxproj.filters b/d3d11-resize-draw/d3d11-resize-draw.vcxproj.filters new file mode 100644 index 0000000..887115c --- /dev/null +++ b/d3d11-resize-draw/d3d11-resize-draw.vcxproj.filters @@ -0,0 +1,27 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + + + Source Files + + + \ No newline at end of file diff --git a/d3d11-resize-draw/main.cpp b/d3d11-resize-draw/main.cpp new file mode 100644 index 0000000..aa5ff88 --- /dev/null +++ b/d3d11-resize-draw/main.cpp @@ -0,0 +1,469 @@ +// -------------------------------------------------------------------------------- +// Direct3D 11 demo comparing various methods of drawing while resizing +// +// ref: https://gist.github.com/d7samurai/261c69490cce0620d0bfc93003cd1052 +// +// SPDX-License-Identifier: 0BSD +// -------------------------------------------------------------------------------- + +#define ACTIVE_METHOD 3 + +// -------------------------------------------------------------------------------- +// Method 1: Don't do anything special. This is just to highlight the problem. +// -------------------------------------------------------------------------------- +// #define ACTIVE_METHOD 1 + +// -------------------------------------------------------------------------------- +// Method 2: Set CS_VREDRAW | CS_HREDRAW and render in WM_PAINT. +// +// This is visually perfect, but some functions can't be called from inside +// WM_PAINT. For example, creating a modal dialog box with MessageBoxW() or +// assert() will block the message loop. When this happens, the app will hang or +// crash. +// -------------------------------------------------------------------------------- + +// -------------------------------------------------------------------------------- +// Method 3: Render during resize with WM_TIMER. +// +// This is a well-known workaround that has been generally proven safe. This is +// what sokol_app.h does. +// +// If you use WM_TIMER alone, there is a small gap at the side of the window when +// resizing, but it's barely visible. +// +// If you draw on WM_TIMER and WM_SIZE, there is no bar, but there may be redundant +// draw calls. +// +// Creating dialogs in WM_TIMER and WM_SIZE doesn't seem to be an issue. +// -------------------------------------------------------------------------------- + +#if !defined(ACTIVE_METHOD) || ACTIVE_METHOD < 1 || ACTIVE_METHOD > 3 +# error ACTIVE_METHOD not set +#endif + +#include +#include +#include +#include + +#pragma comment(lib, "d3d11.lib") +#pragma comment(lib, "d3dcompiler.lib") + +// -------------------------------------------------------------------------------- +// Shared code +// -------------------------------------------------------------------------------- + +#define Assert(expr) do { if (!(expr)) { __debugbreak(); } } while (0); +#define HR(hr) do { HRESULT hr_ = (hr); Assert(SUCCEEDED(hr_)); } while (0); + +struct CBuffer +{ + FLOAT color[4]; + FLOAT rot[16]; +}; + +static struct +{ + HWND hwnd; + IDXGISwapChain* d3dswp; + ID3D11Device* d3ddev; + ID3D11DeviceContext* d3dctx; + ID3D11RenderTargetView* d3drtv; + UINT vstride; + UINT voffset; + ID3D11Buffer* vbuf_quad; + ID3D11Buffer* ibuf_quad; + UINT ibuf_quad_count; + ID3D11VertexShader* vs; + ID3D11PixelShader* ps; + ID3D11InputLayout* vlayout; + ID3D11Buffer* cbuf; + FLOAT vp_w; + FLOAT vp_h; +} G = { 0 }; + +static void CreateDemoWindow(UINT class_style, WNDPROC wndproc) +{ + WNDCLASSW wcl = { .style = class_style, .lpfnWndProc = wndproc, .hInstance = GetModuleHandleW(0), .lpszClassName = L"d3d11-resize-draw"}; + RegisterClassW(&wcl); + + G.hwnd = CreateWindowExW(0, wcl.lpszClassName, wcl.lpszClassName, WS_OVERLAPPEDWINDOW, 0, 0, 1024, 768, 0, 0, 0, 0); + Assert(G.hwnd); +} + +static void DestroyRenderTarget() +{ + if (G.d3drtv) { + G.d3drtv->Release(); + G.d3drtv = 0; + } +} + +static void CreateRenderTarget() +{ + Assert(!G.d3drtv); + ID3D11Texture2D* tex; + HR(G.d3dswp->GetBuffer(0, IID_PPV_ARGS(&tex))); + HR(G.d3ddev->CreateRenderTargetView(tex, 0, &G.d3drtv)); + tex->Release(); +} + +static void SetupD3D11() +{ + DXGI_SWAP_CHAIN_DESC swap_chain_desc = { + .BufferDesc = { .Format = DXGI_FORMAT_R8G8B8A8_UNORM, }, + .SampleDesc = { .Count = 1, }, + .BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT, + .BufferCount = 2, + .OutputWindow = G.hwnd, + .Windowed = TRUE, + .SwapEffect = DXGI_SWAP_EFFECT_DISCARD, + }; + D3D_FEATURE_LEVEL feature_levels[] = { D3D_FEATURE_LEVEL_11_0 }; + + HR(D3D11CreateDeviceAndSwapChain(0, D3D_DRIVER_TYPE_HARDWARE, 0, D3D11_CREATE_DEVICE_DEBUG, feature_levels, ARRAYSIZE(feature_levels), D3D11_SDK_VERSION, &swap_chain_desc, &G.d3dswp, &G.d3ddev, 0, &G.d3dctx)); + + // Vertex shader + { + ID3DBlob* blob; + HR(D3DCompileFromFile(L"shader.hlsl", 0, 0, "VsMain", "vs_5_0", 0, 0, &blob, nullptr)); + HR(G.d3ddev->CreateVertexShader(blob->GetBufferPointer(), blob->GetBufferSize(), 0, &G.vs)); + + D3D11_INPUT_ELEMENT_DESC idesc[] = { + { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 }, + }; + HR(G.d3ddev->CreateInputLayout(idesc, ARRAYSIZE(idesc), blob->GetBufferPointer(), blob->GetBufferSize(), &G.vlayout)); + blob->Release(); + } + // Pixel shader + { + ID3DBlob* blob; + HR(D3DCompileFromFile(L"shader.hlsl", 0, 0, "PsMain", "ps_5_0", 0, 0, &blob, nullptr)); + HR(G.d3ddev->CreatePixelShader(blob->GetBufferPointer(), blob->GetBufferSize(), 0, &G.ps)); + blob->Release(); + } + + struct Vertex { FLOAT p[2]; FLOAT t[2]; }; + G.vstride = sizeof(Vertex); + G.voffset = 0; + + // Quad vertex buffer + { + Vertex vdata[] = { + { { -1.0f, +1.0f }, { 0.0f, 0.0f } }, + { { +1.0f, +1.0f }, { 1.0f, 0.0f } }, + { { +1.0f, -1.0f }, { 1.0f, 1.0f } }, + { { -1.0f, -1.0f }, { 0.0f, 1.0f } }, + }; + D3D11_BUFFER_DESC bdesc = { .ByteWidth = sizeof(vdata), .Usage = D3D11_USAGE_IMMUTABLE, .BindFlags = D3D11_BIND_VERTEX_BUFFER }; + D3D11_SUBRESOURCE_DATA sdata = { .pSysMem = vdata }; + HR(G.d3ddev->CreateBuffer(&bdesc, &sdata, &G.vbuf_quad)); + } + // Quad index buffer + { + UINT16 idata[] = { + 0, 1, 2, + 0, 2, 3, + }; + D3D11_BUFFER_DESC bdesc = { .ByteWidth = sizeof(idata), .Usage = D3D11_USAGE_IMMUTABLE, .BindFlags = D3D11_BIND_INDEX_BUFFER }; + D3D11_SUBRESOURCE_DATA sdata = { .pSysMem = idata }; + HR(G.d3ddev->CreateBuffer(&bdesc, &sdata, &G.ibuf_quad)); + G.ibuf_quad_count = ARRAYSIZE(idata); + } + + // Constant buffer + { + D3D11_BUFFER_DESC bdesc = { .ByteWidth = sizeof(CBuffer), .Usage = D3D11_USAGE_DYNAMIC, .BindFlags = D3D11_BIND_CONSTANT_BUFFER , .CPUAccessFlags = D3D11_CPU_ACCESS_WRITE }; + HR(G.d3ddev->CreateBuffer(&bdesc, 0, &G.cbuf)); + } +} + +static void MakeRotationZ(FLOAT angle, FLOAT* out) +{ + FLOAT c = cosf(angle); + FLOAT s = sinf(angle); + + out[ 0] = c; out[ 1] = -s; out[ 2] = 0; out[ 3] = 0; + out[ 4] = s; out[ 5] = c; out[ 6] = 0; out[ 7] = 0; + out[ 8] = 0; out[ 9] = 0; out[10] = 1; out[11] = 0; + out[12] = 0; out[13] = 0; out[14] = 0; out[15] = 1; +} + +static void MakeScale(FLOAT s, FLOAT* out) +{ + ZeroMemory(out, sizeof(FLOAT) * 16); + out[ 0] = s; + out[ 5] = s; + out[10] = 1.0f; + out[15] = 1.0f; +} + +static void MulMat4(const FLOAT* A, const FLOAT* B, FLOAT* out) +{ + FLOAT r[16]; + for (UINT row = 0; row < 4; row++) { + for (UINT col = 0; col < 4; col++) { + r[row * 4 + col] = A[row * 4 + 0] * B[0 * 4 + col] + A[row * 4 + 1] * B[1 * 4 + col] + A[row * 4 + 2] * B[2 * 4 + col] + A[row * 4 + 3] * B[3 * 4 + col]; + } + } + memcpy(out, r, sizeof(r)); +} + +static void MakeTransform(FLOAT r, FLOAT s, FLOAT* out) +{ + FLOAT ms[16]; + FLOAT mr[16]; + MakeScale(s, ms); + MakeRotationZ(r, mr); + MulMat4(mr, ms, out); +} + +static void DrawD3D11() +{ + RECT rc; GetClientRect(G.hwnd, &rc); + D3D11_VIEWPORT vp = { .Width = (FLOAT)(rc.right - rc.left), .Height = (FLOAT)(rc.bottom - rc.top), .MaxDepth = 1.0f }; + G.d3dctx->RSSetViewports(1, &vp); + + FLOAT clear[4] = { 0.1f, 0.1f, 0.1f, 1.0f }; + G.d3dctx->OMSetRenderTargets(1, &G.d3drtv, 0); + G.d3dctx->ClearRenderTargetView(G.d3drtv, clear); + + G.d3dctx->VSSetShader(G.vs, 0, 0); + G.d3dctx->PSSetShader(G.ps, 0, 0); + G.d3dctx->VSSetConstantBuffers(0, 1, &G.cbuf); + G.d3dctx->PSSetConstantBuffers(0, 1, &G.cbuf); + + G.d3dctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + G.d3dctx->IASetInputLayout(G.vlayout); + G.d3dctx->IASetVertexBuffers(0, 1, &G.vbuf_quad, &G.vstride, &G.voffset); + G.d3dctx->IASetIndexBuffer(G.ibuf_quad, DXGI_FORMAT_R16_UINT, 0); + + // Quad 1 + { + D3D11_MAPPED_SUBRESOURCE mapped; + HR(G.d3dctx->Map(G.cbuf, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped)); + CBuffer* cb = (CBuffer*)mapped.pData; + + cb->color[0] = 1.0f; + cb->color[1] = 0.0f; + cb->color[2] = 0.0f; + cb->color[3] = 1.0f; + + MakeTransform(0.0f, 1.0f, cb->rot); + + G.d3dctx->Unmap(G.cbuf, 0); + G.d3dctx->DrawIndexed(G.ibuf_quad_count, 0, 0); + } + + // Quad 2 + { + D3D11_MAPPED_SUBRESOURCE mapped; + HR(G.d3dctx->Map(G.cbuf, 0, D3D11_MAP_WRITE_DISCARD, 0, &mapped)); + CBuffer* cb = (CBuffer*)mapped.pData; + + cb->color[0] = 0.0f; + cb->color[1] = 1.0f; + cb->color[2] = 0.0f; + cb->color[3] = 1.0f; + + MakeTransform((FLOAT)GetTickCount64() / 1e3f, 0.5f, cb->rot); + + G.d3dctx->Unmap(G.cbuf, 0); + G.d3dctx->DrawIndexed(G.ibuf_quad_count, 0, 0); + } + + HR(G.d3dswp->Present(1, 0)); + + SHORT key = GetAsyncKeyState('A'); + if ((key & 0x8000) && (key & 0x1)) { + MessageBoxW(G.hwnd, L"This is an example dialog", L"d3d11-resize-draw", MB_OK); + } +} + +static void ResizeD3D11(LPARAM lparam) +{ + DestroyRenderTarget(); + HR(G.d3dswp->ResizeBuffers(0, LOWORD(lparam), HIWORD(lparam), DXGI_FORMAT_UNKNOWN, 0)); + CreateRenderTarget(); +} + +static void SpewMsg(const char* fmt, ...) +{ + char buf[512]; + va_list va; + va_start(va, fmt); + vsnprintf(buf, sizeof(buf), fmt, va); + va_end(va); + + OutputDebugStringA(buf); OutputDebugStringA("\n"); +} + +static void SpewWndProc(HWND hwnd, UINT msg, WPARAM, LPARAM) +{ + switch (msg) { + case WM_SIZE: { + SpewMsg("WM_SIZE hwnd=%p", hwnd); + } break; + case WM_PAINT: { + SpewMsg("WM_PAINT hwnd=%p", hwnd); + } break; + } +} +#define SPEW_WNDPROC() SpewWndProc(hwnd, msg, wparam, lparam); + +// -------------------------------------------------------------------------------- +// Method 1: Don't do anything special. This is just to highlight the problem. +// -------------------------------------------------------------------------------- +#if ACTIVE_METHOD == 1 + +static LRESULT WINAPI Method1_WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + SPEW_WNDPROC(); + switch (msg) { + case WM_SIZE: { + if (wparam != SIZE_MINIMIZED) { + ResizeD3D11(lparam); + } + DrawD3D11(); + return 0; + } break; + case WM_DESTROY: { + PostQuitMessage(0); + return 0; + } break; + } + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + +int WINAPI WinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ LPSTR, _In_ int) +{ + CreateDemoWindow(CS_CLASSDC, Method1_WndProc); + SetupD3D11(); + CreateRenderTarget(); + ShowWindow(G.hwnd, SW_SHOW); + + BOOL running = TRUE; + while (running) { + MSG msg; + while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + if (msg.message == WM_QUIT) { + running = FALSE; + } + } + + DrawD3D11(); + } +} + +#endif // ACTIVE_METHOD == 1 + +// -------------------------------------------------------------------------------- +// Method 2: Set CS_VREDRAW | CS_HREDRAW and render in WM_PAINT. +// -------------------------------------------------------------------------------- +#if ACTIVE_METHOD == 2 + +static LRESULT WINAPI Method1_WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + SPEW_WNDPROC(); + switch (msg) { + case WM_SIZE: { + if (wparam != SIZE_MINIMIZED) { + ResizeD3D11(lparam); + } + return 0; + } break; + case WM_DESTROY: { + PostQuitMessage(0); + return 0; + } break; + case WM_PAINT: { + DrawD3D11(); + return 0; + } break; + } + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + +int WINAPI WinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ LPSTR, _In_ int) +{ + CreateDemoWindow(CS_CLASSDC | CS_HREDRAW | CS_VREDRAW, Method1_WndProc); + SetupD3D11(); + CreateRenderTarget(); + ShowWindow(G.hwnd, SW_SHOW); + + BOOL running = TRUE; + while (running) { + MSG msg; + while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + if (msg.message == WM_QUIT) { + running = FALSE; + } + } + } +} + +#endif // ACTIVE_METHOD == 2 + +// -------------------------------------------------------------------------------- +// Method 3: Render during resize with WM_TIMER. +// -------------------------------------------------------------------------------- +#if ACTIVE_METHOD == 3 + +static LRESULT WINAPI Method1_WndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) +{ + SPEW_WNDPROC(); + switch (msg) { + case WM_SIZE: { + if (wparam != SIZE_MINIMIZED) { + ResizeD3D11(lparam); + } +#if 1 + DrawD3D11(); +#endif + return 0; + } break; + case WM_DESTROY: { + PostQuitMessage(0); + return 0; + } break; + case WM_ENTERSIZEMOVE: { + SetTimer(hwnd, 1, USER_TIMER_MINIMUM, 0); + } break; + case WM_EXITSIZEMOVE: { + KillTimer(hwnd, 1); + } break; + case WM_TIMER: { + DrawD3D11(); + } break; + } + return DefWindowProcW(hwnd, msg, wparam, lparam); +} + +int WINAPI WinMain(_In_ HINSTANCE, _In_opt_ HINSTANCE, _In_ LPSTR, _In_ int) +{ + CreateDemoWindow(CS_CLASSDC, Method1_WndProc); + SetupD3D11(); + CreateRenderTarget(); + ShowWindow(G.hwnd, SW_SHOW); + + BOOL running = TRUE; + while (running) { + MSG msg; + while (PeekMessageW(&msg, 0, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + if (msg.message == WM_QUIT) { + running = FALSE; + } + } + + DrawD3D11(); + } +} + +#endif // ACTIVE_METHOD == 3 diff --git a/d3d11-resize-draw/shader.hlsl b/d3d11-resize-draw/shader.hlsl new file mode 100644 index 0000000..8a942e8 --- /dev/null +++ b/d3d11-resize-draw/shader.hlsl @@ -0,0 +1,30 @@ +struct VertexDesc +{ + float2 p : POSITION; + float2 t : TEXCOORD0; +}; + +struct PixelDesc +{ + float4 p : SV_POSITION; + float2 t : TEXCOORD0; +}; + +cbuffer Params : register(b0) +{ + float4 u_color; + float4x4 u_rot; +}; + +PixelDesc VsMain(VertexDesc v) +{ + PixelDesc p; + p.p = mul(u_rot, float4(v.p.x, v.p.y, 0.0f, 1.0f)); + p.t = v.t; + return p; +} + +float4 PsMain(PixelDesc p) : SV_Target +{ + return u_color; +} -- cgit v1.2.3