DRAGONX_DEBUG was defined unconditionally, causing D3D11CreateDevice() to request the debug layer via D3D11_CREATE_DEVICE_DEBUG. This layer is only available on machines with the Windows SDK or Graphics Tools installed, so the call fails with DXGI_ERROR_SDK_COMPONENT_MISSING on regular user machines — causing the app to silently exit.
347 lines
11 KiB
C++
347 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;
|
|
|
|
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();
|
|
|
|
// 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;
|
|
|
|
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
|