Files
ObsidianDragon/src/platform/dx11_context.cpp
T
DanSandClaude Opus 4.8 49240facff perf(resize): dedupe the live-resize watcher, early-out DX11 resize
The live-resize event watcher redrew for every resize event, and each
resize tore down and recreated the DX11 render target even when the
pixel size had not actually changed. A single drag fires both a RESIZED
and a PIXEL_SIZE_CHANGED event reporting the same final size, so the
work ran twice.

- The watcher keys off the real pixel size and returns early when it has
  not moved since the last pass, collapsing the duplicate event pair into
  one redraw. It also bails out when called from inside an open ImGui
  frame, which the caption buttons can trigger.
- The DX11 resize now checks the swap chain's current dimensions up
  front and does nothing when the size already matches and the target is
  live, skipping the destroy/recreate churn.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-10-13 18:16:50 -05:00

352 lines
11 KiB
C++

// DragonX Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "dx11_context.h"
#ifdef _WIN32
#include <SDL3/SDL.h>
#include <dcomp.h>
#include "../util/logger.h"
// Not defined in older MinGW SDK headers
#ifndef WS_EX_NOREDIRECTIONBITMAP
#define WS_EX_NOREDIRECTIONBITMAP 0x00200000L
#endif
namespace dragonx {
namespace platform {
// Helper to get HWND from SDL window
static HWND getHWND(SDL_Window* window) {
if (!window) return nullptr;
SDL_PropertiesID props = SDL_GetWindowProperties(window);
if (props == 0) return nullptr;
return (HWND)SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
}
bool DX11Context::init(SDL_Window* window)
{
HWND hwnd = getHWND(window);
if (!hwnd) {
DEBUG_LOGF("DX11: Failed to get HWND from SDL window\n");
return false;
}
hwnd_ = hwnd;
// Ensure WS_EX_NOREDIRECTIONBITMAP is set. The main code creates the
// HWND with this style, but if someone passes a different window we try
// to set it here as a fallback. We also call SetWindowPos with
// SWP_FRAMECHANGED so the DWM re-evaluates the extended style.
LONG_PTR exStyle = GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
if (!(exStyle & WS_EX_NOREDIRECTIONBITMAP)) {
SetWindowLongPtrW(hwnd, GWL_EXSTYLE, exStyle | WS_EX_NOREDIRECTIONBITMAP);
SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
LONG_PTR verify = GetWindowLongPtrW(hwnd, GWL_EXSTYLE);
if (verify & WS_EX_NOREDIRECTIONBITMAP)
DEBUG_LOGF("DX11: WS_EX_NOREDIRECTIONBITMAP set via fallback path\n");
else
DEBUG_LOGF("DX11: WARNING - WS_EX_NOREDIRECTIONBITMAP could NOT be set!\n");
} else {
DEBUG_LOGF("DX11: WS_EX_NOREDIRECTIONBITMAP already present (good)\n");
}
fflush(stdout);
// Create D3D11 device and context
D3D_FEATURE_LEVEL featureLevel;
const D3D_FEATURE_LEVEL featureLevelArray[] = {
D3D_FEATURE_LEVEL_11_0,
D3D_FEATURE_LEVEL_10_0,
};
// Need BGRA support for DirectComposition
UINT createDeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
HRESULT hr = D3D11CreateDevice(
nullptr, // Default adapter
D3D_DRIVER_TYPE_HARDWARE, // Hardware rendering
nullptr, // No software module
createDeviceFlags,
featureLevelArray,
2,
D3D11_SDK_VERSION,
&device_,
&featureLevel,
&deviceContext_
);
if (FAILED(hr)) {
DEBUG_LOGF("DX11: D3D11CreateDevice failed (HRESULT 0x%08lx)\n", hr);
return false;
}
DEBUG_LOGF("DX11: Device created (feature level %d.%d)\n",
(featureLevel >> 12) & 0xF, (featureLevel >> 8) & 0xF);
// Get DXGI device for factory access and DirectComposition
IDXGIDevice* dxgiDevice = nullptr;
hr = device_->QueryInterface(IID_PPV_ARGS(&dxgiDevice));
if (FAILED(hr)) {
DEBUG_LOGF("DX11: QueryInterface IDXGIDevice failed\n");
shutdown();
return false;
}
IDXGIAdapter* dxgiAdapter = nullptr;
hr = dxgiDevice->GetAdapter(&dxgiAdapter);
if (FAILED(hr)) {
dxgiDevice->Release();
DEBUG_LOGF("DX11: GetAdapter failed\n");
shutdown();
return false;
}
IDXGIFactory2* dxgiFactory = nullptr;
hr = dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory));
dxgiAdapter->Release();
if (FAILED(hr)) {
dxgiDevice->Release();
DEBUG_LOGF("DX11: GetParent IDXGIFactory2 failed\n");
shutdown();
return false;
}
// ---------------------------------------------------------------
// Strategy: CreateSwapChainForComposition + DirectComposition
//
// DXGI_ALPHA_MODE_PREMULTIPLIED only works with composition swap chains.
// CreateSwapChainForHwnd does NOT support premultiplied alpha.
// We must use DirectComposition to bind the composition swap chain
// to the window. This is how Windows Terminal achieves transparency.
// ---------------------------------------------------------------
DXGI_SWAP_CHAIN_DESC1 sd = {};
sd.Width = 0; // Auto-detect from HWND
sd.Height = 0;
sd.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
sd.Stereo = FALSE;
sd.SampleDesc.Count = 1;
sd.SampleDesc.Quality = 0;
sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
sd.BufferCount = 2;
sd.Scaling = DXGI_SCALING_STRETCH;
sd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
sd.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
sd.Flags = 0;
// CreateSwapChainForComposition needs explicit width/height
RECT clientRect;
GetClientRect(hwnd, &clientRect);
sd.Width = clientRect.right - clientRect.left;
sd.Height = clientRect.bottom - clientRect.top;
if (sd.Width == 0) sd.Width = 1400;
if (sd.Height == 0) sd.Height = 900;
hr = dxgiFactory->CreateSwapChainForComposition(
device_,
&sd,
nullptr,
&swapChain_
);
if (SUCCEEDED(hr)) {
DEBUG_LOGF("DX11: Composition swap chain created (%ux%u, PREMULTIPLIED alpha)\n",
sd.Width, sd.Height);
// Create DirectComposition device and bind swap chain to HWND
hr = DCompositionCreateDevice(dxgiDevice, IID_PPV_ARGS(&dcompDevice_));
if (SUCCEEDED(hr)) {
hr = dcompDevice_->CreateTargetForHwnd(hwnd, TRUE, &dcompTarget_);
}
if (SUCCEEDED(hr)) {
hr = dcompDevice_->CreateVisual(&dcompVisual_);
}
if (SUCCEEDED(hr)) {
hr = dcompVisual_->SetContent(swapChain_);
}
if (SUCCEEDED(hr)) {
hr = dcompTarget_->SetRoot(dcompVisual_);
}
if (SUCCEEDED(hr)) {
hr = dcompDevice_->Commit();
}
if (SUCCEEDED(hr)) {
hasAlpha_ = true;
DEBUG_LOGF("DX11: DirectComposition bound to HWND (alpha compositing active)\n");
fflush(stdout);
} else {
DEBUG_LOGF("DX11: DirectComposition setup failed (0x%08lx), falling back\n", hr);
fflush(stderr);
// Clean up the composition objects and swap chain
if (dcompVisual_) { dcompVisual_->Release(); dcompVisual_ = nullptr; }
if (dcompTarget_) { dcompTarget_->Release(); dcompTarget_ = nullptr; }
if (dcompDevice_) { dcompDevice_->Release(); dcompDevice_ = nullptr; }
swapChain_->Release();
swapChain_ = nullptr;
}
} else {
DEBUG_LOGF("DX11: CreateSwapChainForComposition failed (0x%08lx)\n", hr);
}
// Fallback: standard HWND swap chain (no alpha transparency)
if (!swapChain_) {
sd.AlphaMode = DXGI_ALPHA_MODE_UNSPECIFIED;
sd.Width = 0; // Auto-detect
sd.Height = 0;
hr = dxgiFactory->CreateSwapChainForHwnd(
device_,
hwnd,
&sd,
nullptr,
nullptr,
&swapChain_
);
if (SUCCEEDED(hr)) {
DEBUG_LOGF("DX11: HWND swap chain created (opaque, no alpha)\n");
}
}
dxgiFactory->Release();
dxgiDevice->Release();
if (FAILED(hr) || !swapChain_) {
DEBUG_LOGF("DX11: All swap chain creation paths failed\n");
shutdown();
return false;
}
createRenderTarget();
return true;
}
void DX11Context::shutdown()
{
cleanupRenderTarget();
// Release DirectComposition objects
if (dcompVisual_) { dcompVisual_->Release(); dcompVisual_ = nullptr; }
if (dcompTarget_) { dcompTarget_->Release(); dcompTarget_ = nullptr; }
if (dcompDevice_) { dcompDevice_->Release(); dcompDevice_ = nullptr; }
if (swapChain_) {
swapChain_->Release();
swapChain_ = nullptr;
}
if (deviceContext_) {
deviceContext_->Release();
deviceContext_ = nullptr;
}
if (device_) {
device_->Release();
device_ = nullptr;
}
hasAlpha_ = false;
}
void DX11Context::resize(int width, int height)
{
(void)width;
(void)height;
if (!swapChain_ || !deviceContext_) return;
// For composition swap chains (CreateSwapChainForComposition),
// passing 0,0 means "keep current size" — NOT auto-detect from HWND.
// We must pass the actual pixel dimensions from GetClientRect.
UINT w = 0, h = 0;
if (hwnd_) {
RECT rc;
GetClientRect(hwnd_, &rc);
w = static_cast<UINT>(rc.right - rc.left);
h = static_cast<UINT>(rc.bottom - rc.top);
}
if (w == 0 || h == 0) return; // minimized / zero-area: keep the current RTV, do nothing
// Already this size with a live RTV: nothing to do (skips the destroy/recreate churn).
DXGI_SWAP_CHAIN_DESC1 d;
if (SUCCEEDED(swapChain_->GetDesc1(&d)) && d.Width == w && d.Height == h && renderTargetView_)
return;
cleanupRenderTarget();
// Unbind the render target from the pipeline — ResizeBuffers requires
// ALL outstanding references to back-buffer resources to be released.
// Without this, ResizeBuffers fails with DXGI_ERROR_INVALID_CALL when
// growing the window (shrinking appears fine because the old larger
// buffer simply gets cropped by DWM).
deviceContext_->OMSetRenderTargets(0, nullptr, nullptr);
deviceContext_->Flush();
HRESULT hr = swapChain_->ResizeBuffers(0, w, h, DXGI_FORMAT_UNKNOWN, 0);
if (FAILED(hr)) {
DEBUG_LOGF("DX11: ResizeBuffers(%u x %u) failed (0x%08lx)\n", w, h, hr);
}
createRenderTarget();
// Commit DirectComposition so it picks up the new buffer size
if (dcompDevice_) {
dcompDevice_->Commit();
}
}
void DX11Context::ensureSize()
{
if (!swapChain_ || !hwnd_) return;
// Query current swap chain buffer dimensions
DXGI_SWAP_CHAIN_DESC1 desc;
if (FAILED(swapChain_->GetDesc1(&desc))) return;
// Query actual HWND client area
RECT rc;
GetClientRect(hwnd_, &rc);
UINT clientW = static_cast<UINT>(rc.right - rc.left);
UINT clientH = static_cast<UINT>(rc.bottom - rc.top);
// Resize only when there's a mismatch
if (clientW > 0 && clientH > 0 &&
(desc.Width != clientW || desc.Height != clientH)) {
resize(static_cast<int>(clientW), static_cast<int>(clientH));
}
}
void DX11Context::clear(float r, float g, float b, float a)
{
if (!deviceContext_ || !renderTargetView_) return;
const float clearColor[4] = { r, g, b, a };
deviceContext_->ClearRenderTargetView(renderTargetView_, clearColor);
}
void DX11Context::present(int vsync)
{
if (!swapChain_) return;
swapChain_->Present(vsync ? 1 : 0, 0);
}
void DX11Context::createRenderTarget()
{
if (!swapChain_ || !device_) return;
ID3D11Texture2D* backBuffer = nullptr;
swapChain_->GetBuffer(0, IID_PPV_ARGS(&backBuffer));
if (backBuffer) {
device_->CreateRenderTargetView(backBuffer, nullptr, &renderTargetView_);
backBuffer->Release();
}
}
void DX11Context::cleanupRenderTarget()
{
if (renderTargetView_) {
renderTargetView_->Release();
renderTargetView_ = nullptr;
}
}
} // namespace platform
} // namespace dragonx
#endif // _WIN32