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>
352 lines
11 KiB
C++
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
|