Hello,
I have a Win32 window with WS_POPUP, WS_EX_NOREDIRECTIONBITMAP, and a custom title bar. I render using Direct3D 11 with a swap chain created by CreateSwapChainForComposition, combined with Windows.UI.Composition (WUC). The window has an acrylic backdrop blur effect (Compositor BackdropBrush + GaussianBlurEffect + mask/root clip for rounded corners). The swap chain is used with DXGI_ALPHA_MODE_PREMULTIPLIED, and I clear with D2D1::ColorF(0,0,0,0) to keep the window transparent.
The Problem is When resizing by dragging the left or top edges, the UI (buttons, shapes) visibly stretches or shrinks for a frame before correcting itself. Right/bottom edge resizing is fine. The stretching happens in the swap chain content (not the blur). I’ve already:
Set hbrBackground = NULL and handled WM_ERASEBKGND (returns 1) – no classic flicker.
Called DwmFlush() after Present1 during resize to prevent black flashes (helped for flicker but not stretch).
Rebuilt the blur mask geometry on every resize (even switched to a root clip) – no change.
Tried clearing the swap chain with an opaque colour before drawing – stops the stretch but kills the acrylic transparency.
Disabled CS_HREDRAW/CS_VREDRAW.
The stretch occurs because the compositor updates the visual sizes (root, content, blur) immediately on resize, but the swap chain still holds the old buffer until I call Present. The compositor then stretches that old buffer to fit the new visual size, causing a single distorted frame.
So my Question is How can I prevent the swap chain buffer from being stretched during live resize, while preserving the live acrylic blur and full transparency? I don’t want to switch to opaque clears, snapshots, or disable live resizing. Is there a way to temporarily detach the swap chain from the visual or freeze the content visual size until the new frame is ready, without affecting the blur layer? Or any other composition trick?
I’m using C++/WinRT, D3D11, D2D1, and WUC (no XAML). Any guidance or working pattern would be greatly appreciated.
#include "VX_WindowsWindow.h"
#include "VX_WindowsWindow_P.h"
#include "VX_ToWideChar.h"
#include "VX_Window.h"
#include "VX_Application.h"
#include "VX_WindowApplication.h"
#include <windowsx.h>
#include <dwmapi.h>
#pragma comment(lib, "dwmapi.lib")
const wchar_t* VX::VX_WindowsWindowPrivate::windowClassName = L"VXWINDOWCLASS";
bool VX::VX_WindowsWindowPrivate::isWindowClassRegistered = false;
LRESULT VX::WindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
LRESULT result = 0;
VX_WindowsWindowPrivate* ptr = nullptr;
if (msg == WM_NCCREATE) {
CREATESTRUCTW* pCreate = reinterpret_cast<CREATESTRUCTW*>(lParam);
ptr = reinterpret_cast<VX_WindowsWindowPrivate*>(pCreate->lpCreateParams);
SetWindowLongPtrW(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(ptr));
}
else {
ptr = reinterpret_cast<VX_WindowsWindowPrivate*>(static_cast<LONG_PTR>(GetWindowLongPtrW(hwnd, GWLP_USERDATA)));
}
if (!ptr) return DefWindowProcW(hwnd, msg, wParam, lParam);
bool wasHandled = false;
switch (msg)
{
case WM_ERASEBKGND:
return 1;
case WM_MOVE:
{
int newX = (int)(short)LOWORD(lParam);
int newY = (int)(short)HIWORD(lParam);
ptr->q_ptr->x = newX;
ptr->q_ptr->y = newY;
if (ptr->shadowWindow)
ptr->shadowWindow->TrackOwner(newX, newY, ptr->q_ptr->width, ptr->q_ptr->height);
result = 0;
wasHandled = true;
break;
}
case WM_SIZING:
{
RECT* rect = reinterpret_cast<RECT*>(lParam);
unsigned int newWidth = rect->right - rect->left;
unsigned int newHeight = rect->bottom - rect->top;
auto t0 = std::chrono::high_resolution_clock::now();
if (newWidth > 0 && newHeight > 0) {
ptr->q_func()->width = newWidth;
ptr->q_func()->height = newHeight;
if (ptr->directX11) {
ptr->directX11->SetResizing(true);
ptr->directX11->Resize(newWidth, newHeight, ptr->q_ptr->isMaximized);
auto t1 = std::chrono::high_resolution_clock::now();
ptr->q_func()->Render();
auto t2 = std::chrono::high_resolution_clock::now();
ptr->directX11->SetResizing(false);
auto resizeMs = std::chrono::duration<double, std::milli>(t1 - t0).count();
auto renderMs = std::chrono::duration<double, std::milli>(t2 - t1).count();
wchar_t buf[256];
swprintf_s(buf, L"[SIZING] w=%u h=%u resize=%.2fms render=%.2fms total=%.2fms\n",
newWidth, newHeight, resizeMs, renderMs, resizeMs + renderMs);
OutputDebugStringW(buf);
}
}
result = TRUE;
wasHandled = true;
break;
}
case WM_SIZE:
{
if (wParam == SIZE_MINIMIZED) {
result = 0;
wasHandled = true;
break;
}
unsigned int newWidth = LOWORD(lParam);
unsigned int newHeight = HIWORD(lParam);
if (newWidth > 0 && newHeight > 0) {
ptr->q_func()->width = newWidth;
ptr->q_func()->height = newHeight;
if (ptr->directX11) {
ptr->directX11->Resize(newWidth, newHeight, ptr->q_ptr->isMaximized);
if (ptr->shadowWindow)
ptr->shadowWindow->TrackOwner(ptr->q_ptr->x, ptr->q_ptr->y, newWidth, newHeight);
ptr->q_func()->Render();
}
}
if (ptr->shadowWindow) {
if (ptr->shadowWindow->IsInitialized()) {
if (wParam == SIZE_RESTORED) {
ptr->shadowWindow->Show();
ptr->shadowWindow->TrackOwner(ptr->q_ptr->x, ptr->q_ptr->y, newWidth, newHeight);
}
}
}
result = 0;
wasHandled = true;
break;
}
//case WM_NCCALCSIZE:
//{
// if (wParam == TRUE)
// {
// result = 0;
// wasHandled = true;
// break;
// }
// result = DefWindowProcW(hwnd, msg, wParam, lParam);
// wasHandled = true;
// break;
//}
case WM_NCHITTEST:
{
POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
ScreenToClient(hwnd, &pt);
RECT rc;
GetClientRect(hwnd, &rc);
const int p = ptr->q_ptr->isMaximized ? 0 : 0;
const int border = 8;
const int titleBarHeight = 32;
bool left = pt.x < p + border;
bool right = pt.x >= rc.right - p - border;
bool top = pt.y < p + border;
bool bottom = pt.y >= rc.bottom - p - border;
if (left && top && !ptr->q_ptr->isMaximized) return HTTOPLEFT;
if (right && top && !ptr->q_ptr->isMaximized) return HTTOPRIGHT;
if (left && bottom && !ptr->q_ptr->isMaximized) return HTBOTTOMLEFT;
if (right && bottom && !ptr->q_ptr->isMaximized) return HTBOTTOMRIGHT;
if (top && !ptr->q_ptr->isMaximized) return HTTOP;
if (left && !ptr->q_ptr->isMaximized) return HTLEFT;
if (right && !ptr->q_ptr->isMaximized) return HTRIGHT;
if (bottom && !ptr->q_ptr->isMaximized) return HTBOTTOM;
RECT closeRect{
(LONG)ptr->q_ptr->width - p - border - 32,
(LONG)p + border,
(LONG)ptr->q_ptr->width - p - border,
(LONG)p + border + 32
};
RECT minimizeRect{
(LONG)ptr->q_ptr->width - p - border * 2 - 32 * 2,
(LONG)p + border,
(LONG)ptr->q_ptr->width - p - border * 2,
(LONG)p + border + 32
};
RECT maximizeRect{
(LONG)ptr->q_ptr->width - p - border * 3 - 32 * 3,
(LONG)p + border,
(LONG)ptr->q_ptr->width - p - border * 3,
(LONG)p + border + 32
};
if (PtInRect(&closeRect, pt)) return HTCLIENT;
if (PtInRect(&maximizeRect, pt)) return HTCLIENT;
if (PtInRect(&minimizeRect, pt)) return HTCLIENT;
if (pt.y <= titleBarHeight + p) return HTCAPTION;
return HTCLIENT;
}
case WM_LBUTTONDOWN:
{
POINT pt;
GetCursorPos(&pt);
ScreenToClient(hwnd, &pt);
const int p = ptr->q_ptr->isMaximized ? 0 : 0;
const int border = 8;
if (pt.x >= float(ptr->q_ptr->width - p - border - 32)
&& pt.x <= float(ptr->q_ptr->width - p - border)
&& pt.y >= float(p + border)
&& pt.y <= float(p + border) + 32) {
ptr->q_ptr->Close();
}if (pt.x >= float(ptr->q_ptr->width - p - border * 2 - 32 * 2)
&& pt.x <= float(ptr->q_ptr->width - p - border * 2)
&& pt.y >= float(border)
&& pt.y <= float(border) + 32) {
if (!ptr->q_ptr->isMaximized)
ptr->q_ptr->Maximize();
else
ptr->q_ptr->Restore();
}if (pt.x >= float(ptr->q_ptr->width - border * 3 - 32 * 3)
&& pt.x <= float(ptr->q_ptr->width - border * 3)
&& pt.y >= float(border)
&& pt.y <= float(border) + 32) {
ptr->q_ptr->Minimize();
}
result = 0;
wasHandled = true;
break;
}
case WM_NCLBUTTONDBLCLK:
{
POINT pt;
GetCursorPos(&pt);
ScreenToClient(hwnd, &pt);
const int p = ptr->q_ptr->isMaximized ? 0 : 0;
const int border = 8;
if (wParam == HTCAPTION) {
if (ptr->q_ptr->isMaximized) {
ptr->q_ptr->Restore();
}
else {
ptr->q_ptr->Maximize();
}
}
result = 0;
wasHandled = true;
break;
}
case WM_CLOSE:
ptr->q_func()->isInitialized = false;
ptr->q_func()->isFocused = false;
ptr->q_func()->isVisible = false;
ptr->q_func()->isActivated = false;
DestroyWindow(hwnd);
result = 0;
wasHandled = true;
break;
case WM_DESTROY:
{
bool hasAliveWindows = false;
if (VX_Application::Current()) {
auto& windows = VX_Application::Current()->GetWindows();
for (auto& w : windows) {
if (w->IsInitialized()) hasAliveWindows = true;
}
}
if (!hasAliveWindows) PostQuitMessage(0);
result = 0;
wasHandled = true;
break;
}
default:
break;
}
if (!wasHandled) result = DefWindowProcW(hwnd, msg, wParam, lParam);
return result;
}
VX::VX_WindowsWindow::VX_WindowsWindow() : VX_Object(*new VX_WindowsWindowPrivate) {
VX_D;
d->q_ptr = this;
d->hInstance = GetModuleHandle(nullptr);
d->directX11 = std::make_unique<VX_DirectX11>();
if (VX_WindowsApplication::Current()) VX_WindowsApplication::Current()->RegisterWindow(this);
}
VX::VX_WindowsWindow::~VX_WindowsWindow() {
if (VX_WindowsApplication::Current()) VX_WindowsApplication::Current()->UnregisterWindow(this);
}
bool VX::VX_WindowsWindow::Initialize() {
VX_D;
if (!VX_WindowsWindowPrivate::isWindowClassRegistered) {
WNDCLASSEXW wcex{};
wcex.cbSize = sizeof(WNDCLASSEXW);
wcex.hInstance = d->hInstance;
wcex.lpszClassName = VX_WindowsWindowPrivate::windowClassName;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hbrBackground = nullptr;
wcex.hCursor = LoadCursorW(nullptr, IDC_ARROW);
wcex.hIcon = LoadIconW(nullptr, IDI_APPLICATION);
wcex.hIconSm = LoadIconW(nullptr, IDI_APPLICATION);
wcex.lpfnWndProc = WindowProc;
wcex.lpszMenuName = nullptr;
wcex.style = 0;
if (!RegisterClassExW(&wcex)) return false;
VX_WindowsWindowPrivate::isWindowClassRegistered = true;
}
const wchar_t* wCharTitle = VX_ToWideChar(title);
d->hwnd = CreateWindowExW(
WS_EX_NOREDIRECTIONBITMAP,
d->windowClassName,
wCharTitle,
WS_POPUP,
x, y,
width, height,
nullptr, nullptr,
d->hInstance,
d
);
delete[] wCharTitle;
if (!d->hwnd) return false;
#ifndef DWMWA_SYSTEMBACKDROP_TYPE
#define DWMWA_SYSTEMBACKDROP_TYPE 38
#endif
#ifndef DWMSBT_NONE
#define DWMSBT_NONE 1
#endif
DWORD backdropType = DWMSBT_NONE;
::DwmSetWindowAttribute(d->hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &backdropType, sizeof(backdropType));
MARGINS margins = { -1, -1, -1, -1 };
DwmExtendFrameIntoClientArea(d->hwnd, &margins);
if (!d->directX11->Initialize(this)) return false;
d->shadowWindow = std::make_unique<VX_ShadowWindowsWindow>();
d->shadowWindow->Initialize(this, d->directX11->GetCompositor());
isInitialized = true;
return true;
}
void VX::VX_WindowsWindow::SetTitle(const char* title) {
VX_D;
const wchar_t* wCharTitle = VX_ToWideChar(title);
SetWindowTextW(d->hwnd, wCharTitle);
delete[] wCharTitle;
}
void VX::VX_WindowsWindow::SetPosition(int x, int y) {
VX_D;
SetWindowPos(d->hwnd, nullptr, x, y, 0, 0, SWP_NOSIZE | SWP_NOZORDER);
if (d->shadowWindow)
d->shadowWindow->TrackOwner(x, y, width, height);
}
void VX::VX_WindowsWindow::SetSize(unsigned int width, unsigned int height) {
VX_D;
SetWindowPos(d->hwnd, nullptr, 0, 0, width, height, SWP_NOMOVE | SWP_NOZORDER);
}
void VX::VX_WindowsWindow::Show() {
VX_D;
ShowWindow(d->hwnd, SW_SHOW);
UpdateWindow(d->hwnd);
if (d->shadowWindow) d->shadowWindow->Show();
}
void VX::VX_WindowsWindow::Hide() {
VX_D;
ShowWindow(d->hwnd, SW_HIDE);
if (d->shadowWindow) d->shadowWindow->Hide();
}
void VX::VX_WindowsWindow::Activate() {
VX_D;
if (!isVisible) Show();
SetForegroundWindow(d->hwnd);
::SetFocus(d->hwnd);
}
void VX::VX_WindowsWindow::Close() {
VX_D;
SendMessageW(d->hwnd, WM_CLOSE, 0, 0);
if (d->shadowWindow) d->shadowWindow->Destroy();
}
void VX::VX_WindowsWindow::SetFocus() {
VX_D;
::SetFocus(d->hwnd);
}
void VX::VX_WindowsWindow::Minimize() {
VX_D;
isMinimized = true;
isMaximized = false;
isRestored = false;
if (d->shadowWindow) d->shadowWindow->Hide();
ShowWindow(d->hwnd, SW_MINIMIZE);
}
void VX::VX_WindowsWindow::Restore() {
VX_D;
isRestored = true;
isMaximized = false;
isMinimized = false;
ShowWindow(d->hwnd, SW_RESTORE);
if (d->shadowWindow) d->shadowWindow->Show();
}
void VX::VX_WindowsWindow::Maximize() {
VX_D;
isMaximized = true;
isMinimized = false;
isRestored = false;
if (d->shadowWindow) d->shadowWindow->Hide(); // no shadow when maximized
ShowWindow(d->hwnd, SW_MAXIMIZE);
}
void VX::VX_WindowsWindow::DrawWindow() {
VX_D;
d->directX11->Clear(VX_Colors::VX_Transparent);
float p = isMaximized ? 0.0f : 0.0f;
d->directX11->FillRoundedRectangle({ 0.0f,0.0f,0.0f,0.1f }, { p, p, float(width) - 1, float(height) - 1 }, { 8, 8 });
unsigned int border = 8;
float BtnTop = float(border);
float BtnBottom = float(border + 32);
// Close Button.
float closeBtnLeft = float(width - border - 32);
float closeBtnRight = float(width - border);
d->directX11->FillRoundedRectangle(VX_Colors::VX_Red, { closeBtnLeft, BtnTop, 32, 32 }, { 4, 4 });
d->directX11->DrawLine({ closeBtnLeft + border, BtnTop + border }, { closeBtnRight - border, BtnBottom - border }, VX_Colors::VX_Black, 1.5f);
d->directX11->DrawLine({ closeBtnLeft + border, BtnBottom - border }, { closeBtnRight - border, BtnTop + border }, VX_Colors::VX_Black, 1.5f);
// Maximize Button.
float maximizeBtnLeft = closeBtnLeft - (32 + border);
float maximizeBtnRight = closeBtnRight - (32 + border);
float innerGap = 0.0f;
float outerTip = 9.0f;
float wingSize = 6.0f;
d->directX11->FillRoundedRectangle(VX_Colors::VX_Green, { maximizeBtnLeft, BtnTop, 32, 32 }, { 4, 4 });
d->directX11->FillTriangle({ maximizeBtnLeft + border * 2.0f + outerTip, BtnTop + border * 2.0f - outerTip },
{ maximizeBtnLeft + border * 2.0f + innerGap, BtnTop + border * 2.0f - innerGap - wingSize },
{ maximizeBtnLeft + border * 2.0f + innerGap + wingSize, BtnTop + border * 2.0f - innerGap },
VX_Colors::VX_Black
);
d->directX11->FillTriangle({ maximizeBtnLeft + border * 2.0f - outerTip, BtnTop + border * 2.0f + outerTip },
{ maximizeBtnLeft + border * 2.0f - innerGap - wingSize, BtnTop + border * 2.0f + innerGap },
{ maximizeBtnLeft + border * 2.0f - innerGap, BtnTop + border * 2.0f + innerGap + wingSize },
VX_Colors::VX_Black
);
// Minimize Button.
float minimizeBtnLeft = closeBtnLeft - (64 + 2 * border);
float minimizeBtnRight = closeBtnRight - (64 + 2 * border);
d->directX11->FillRoundedRectangle(VX_Colors::VX_Orange, { minimizeBtnLeft, BtnTop, 32, 32 }, { 4, 4 });
d->directX11->DrawLine({ minimizeBtnLeft + border, BtnTop + border * 2.0f }, { minimizeBtnRight - border, BtnBottom - border * 2.0f }, VX_Colors::VX_Black, 1.05f);
d->directX11->DrawRoundedRectangle({ 0.235f, 0.235f, 0.235f, 1.0f }, { p, p, float(width) - p * 2.0f, float(height) - p * 2.0f }, { 8, 8 });
}
void VX::VX_WindowsWindow::Render() {
VX_D;
if (!d->directX11->IsInitialized()) return;
d->directX11->BeginDraw();
DrawWindow();
d->directX11->EndDraw(width, height);
}
VX::VX_NativeHandle VX::VX_WindowsWindow::GetHandle() {
VX_D;
return d->hwnd;
}
#include "VX_DirectX11.h"
#include "VX_DirectX11_P.h"
#include "VX_WindowsWindow.h"
#include <dwmapi.h>
VX::VX_DirectX11::VX_DirectX11() : VX_Object(*new VX_DirectX11Private) {}
VX::VX_DirectX11::~VX_DirectX11() { Shutdown(); }
bool VX::VX_DirectX11::Initialize(VX_WindowsWindow* window) {
VX_D;
d->currentWidth = window->width;
d->currentHeight = window->height;
if (FAILED(CreateD3DDeviceAndDeviceContext())) return false;
if (FAILED(CreateSwapChain(window->width, window->height))) return false;
if (FAILED(CreateD2DDeviceAndDeviceContext())) return false;
d->d2dDeviceContext->CreateSolidColorBrush(D2D1::ColorF(VX_Colors::VX_Black.r, VX_Colors::VX_Black.g, VX_Colors::VX_Black.b, VX_Colors::VX_Black.a), d->d2dSolidColorBrush.GetAddressOf());
EnsureDispatcherQueue();
InitializeWUC(window);
d->isInitialized = true;
return true;
}
void VX::VX_DirectX11::Shutdown() {
VX_D;
d->d3d11DeviceContext.Reset();
d->d3d11Device.Reset();
}
HRESULT VX::VX_DirectX11::CreateD3DDeviceAndDeviceContext() {
VX_D;
HRESULT hr = S_OK;
Microsoft::WRL::ComPtr<ID3D11Device> baseDevice;
Microsoft::WRL::ComPtr<ID3D11DeviceContext> baseDeviceContext;
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 flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
#if defined(VX_BUILD_DLL_DEBUG) || defined(VX_BUILD_STATIC_DEBUG)
flags |= D3D11_CREATE_DEVICE_DEBUG;
#endif
D3D_FEATURE_LEVEL featureLevel;
hr = D3D11CreateDevice(
nullptr,
D3D_DRIVER_TYPE_HARDWARE,
nullptr,
flags,
featureLevels,
ARRAYSIZE(featureLevels),
D3D11_SDK_VERSION,
baseDevice.GetAddressOf(),
&featureLevel,
baseDeviceContext.GetAddressOf()
);
if (FAILED(hr)) {
hr = D3D11CreateDevice(
nullptr,
D3D_DRIVER_TYPE_WARP,
nullptr,
flags,
featureLevels,
ARRAYSIZE(featureLevels),
D3D11_SDK_VERSION,
baseDevice.GetAddressOf(),
&featureLevel,
baseDeviceContext.GetAddressOf()
);
if (FAILED(hr)) return hr;
}
hr = baseDevice.As(&d->d3d11Device);
hr = baseDeviceContext.As(&d->d3d11DeviceContext);
if (FAILED(hr)) return hr;
return S_OK;
}
Microsoft::WRL::ComPtr<IDXGIFactory2> VX::VX_DirectX11::GetDXGIFactory() {
VX_D;
HRESULT hr = S_OK;
Microsoft::WRL::ComPtr<IDXGIDevice1> dxgiDevcie;
Microsoft::WRL::ComPtr<IDXGIAdapter> dxgiAdapter;
Microsoft::WRL::ComPtr<IDXGIFactory2> dxgiFactory;
hr = d->d3d11Device.As(&dxgiDevcie);
if (SUCCEEDED(hr)) hr = dxgiDevcie->GetAdapter(&dxgiAdapter);
if (SUCCEEDED(hr)) hr = dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory));
return dxgiFactory;
}
HRESULT VX::VX_DirectX11::CreateSwapChain(unsigned int width, unsigned int height) {
VX_D;
HRESULT hr = S_OK;
d->dxgiFactory = GetDXGIFactory();
DXGI_SWAP_CHAIN_DESC1 desc{};
desc.Width = width;
desc.Height = height;
desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Stereo = FALSE;
desc.Scaling = DXGI_SCALING_STRETCH;
desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
desc.Flags = 0;
desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
desc.BufferCount = 2;
desc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED;
hr = d->dxgiFactory->CreateSwapChainForComposition(
d->d3d11Device.Get(),
&desc,
nullptr,
d->dxgiSwapChain.GetAddressOf()
);
if (FAILED(hr)) return hr;
hr = d->dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(d->backBuffer.GetAddressOf()));
if (FAILED(hr)) return hr;
d->currentWidth = width;
d->currentHeight = height;
return S_OK;
}
HRESULT VX::VX_DirectX11::CreateD2DDeviceAndDeviceContext() {
VX_D;
HRESULT hr = S_OK;
Microsoft::WRL::ComPtr<IDXGIDevice1> dxgiDevice;
hr = d->d3d11Device.As(&dxgiDevice);
if (FAILED(hr)) return hr;
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, IID_PPV_ARGS(&d->d2dFactory));
if (FAILED(hr)) return hr;
hr = d->d2dFactory->CreateDevice(dxgiDevice.Get(), d->d2dDevice.GetAddressOf());
if (FAILED(hr)) return hr;
hr = d->d2dDevice->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_NONE, d->d2dDeviceContext.GetAddressOf());
if (FAILED(hr)) return hr;
Microsoft::WRL::ComPtr<IDXGISurface> dxgiSurface;
hr = d->dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiSurface));
if (FAILED(hr)) return hr;
D2D1_BITMAP_PROPERTIES1 props = D2D1::BitmapProperties1(
D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)
);
hr = d->d2dDeviceContext->CreateBitmapFromDxgiSurface(dxgiSurface.Get(), &props, d->d2dBitmapRenderTarget.GetAddressOf());
if (FAILED(hr)) return hr;
d->d2dDeviceContext->SetTarget(d->d2dBitmapRenderTarget.Get());
return S_OK;
}
void VX::VX_DirectX11::EnsureDispatcherQueue() {
VX_D;
if (winrt::Windows::System::DispatcherQueue::GetForCurrentThread() != nullptr)
return;
DispatcherQueueOptions options{
sizeof(DispatcherQueueOptions),
DQTYPE_THREAD_CURRENT,
DQTAT_COM_STA
};
ABI::Windows::System::IDispatcherQueueController* rawController;
winrt::check_hresult(CreateDispatcherQueueController(options, &rawController));
d->dispatcherQueueController = { rawController, winrt::take_ownership_from_abi };
}
void VX::VX_DirectX11::InitializeWUC(VX_WindowsWindow* win) {
VX_D;
d->compositor = winrt::Windows::UI::Composition::Compositor();
auto interop = d->compositor
.as<ABI::Windows::UI::Composition::Desktop::ICompositorDesktopInterop>();
winrt::check_hresult(interop->CreateDesktopWindowTarget(
win->GetHandle(), false,
reinterpret_cast<ABI::Windows::UI::Composition::Desktop::IDesktopWindowTarget**>(
winrt::put_abi(d->desktopTarget))
));
float w = float(win->width);
float h = float(win->height);
// Root is HWND size (already expanded by padding in CreateWindowEx)
d->rootVisual = d->compositor.CreateContainerVisual();
d->rootVisual.Size({ w, h });
d->rootVisual.Offset({ 0.0f, 0.0f, 0.0f });
d->desktopTarget.Root(d->rootVisual);
auto roundedRectGeom = d->compositor.CreateRoundedRectangleGeometry();
roundedRectGeom.Size({ w, h });
roundedRectGeom.CornerRadius({ 8.0f, 8.0f });
auto shape = d->compositor.CreateSpriteShape(roundedRectGeom);
shape.FillBrush(d->compositor.CreateColorBrush({ 255,255,255,255 }));
shape.Offset({ 0.0f, 0.0f });
auto shapeVisual = d->compositor.CreateShapeVisual();
shapeVisual.Shapes().Append(shape);
shapeVisual.Size({ w, h });
auto backdropBrush = d->compositor.CreateBackdropBrush();
HMODULE hWin2D = LoadLibraryW(L"Microsoft.Graphics.Canvas.dll");
winrt::Microsoft::Graphics::Canvas::Effects::GaussianBlurEffect blurEffect{ nullptr };
if (hWin2D) {
typedef HRESULT(WINAPI* DllGetActivationFactoryFunc)(HSTRING, ::IActivationFactory**);
auto pfnDllGetActivationFactory = (DllGetActivationFactoryFunc)GetProcAddress(hWin2D, "DllGetActivationFactory");
if (pfnDllGetActivationFactory) {
winrt::hstring className{ L"Microsoft.Graphics.Canvas.Effects.GaussianBlurEffect" };
winrt::com_ptr<::IActivationFactory> factory;
if (SUCCEEDED(pfnDllGetActivationFactory((HSTRING)winrt::get_abi(className), factory.put()))) {
blurEffect = factory.as<winrt::Windows::Foundation::IActivationFactory>()
.ActivateInstance<winrt::Microsoft::Graphics::Canvas::Effects::GaussianBlurEffect>();
}
}
}
if (blurEffect == nullptr) {
return;
}
blurEffect.BlurAmount(30.0f);
winrt::Windows::UI::Composition::CompositionEffectSourceParameter sourceParameter{ L"Background" };
blurEffect.Source(sourceParameter);
blurEffect.Name(L"Blur");
auto effectFactory = d->compositor.CreateEffectFactory(
blurEffect,
{ L"Blur.BlurAmount" } // Allows dynamic changes or animations
);
auto visualSurface2 = d->compositor.CreateVisualSurface();
visualSurface2.SourceVisual(shapeVisual);
visualSurface2.SourceSize({ w, h });
auto blurMaskBrushSurface = d->compositor.CreateSurfaceBrush(visualSurface2);
d->effectBrush = effectFactory.CreateBrush();
d->effectBrush.SetSourceParameter(L"Background", backdropBrush);
auto blurMaskBrush = d->compositor.CreateMaskBrush();
blurMaskBrush.Source(d->effectBrush); // the blurred backdrop
blurMaskBrush.Mask(blurMaskBrushSurface);
d->blurVisual = d->compositor.CreateSpriteVisual();
d->blurVisual.Size({ w, h });
d->blurVisual.Offset({ 0.0f, 0.0f, 0.0f });
d->blurVisual.Brush(blurMaskBrush);
// ── STEP 5: Content visual ────────────────────────────────────────
d->contentVisual = d->compositor.CreateSpriteVisual();
d->contentVisual.Size({ w, h });
d->contentVisual.Offset({ 0.0f, 0.0f, 0.0f }); // offset by padding
// Bind swap chain
auto compositorInterop = d->compositor
.as<ABI::Windows::UI::Composition::ICompositorInterop>();
winrt::Windows::UI::Composition::ICompositionSurface surface{ nullptr };
winrt::check_hresult(
compositorInterop->CreateCompositionSurfaceForSwapChain(
d->dxgiSwapChain.Get(),
reinterpret_cast<ABI::Windows::UI::Composition::ICompositionSurface**>(
winrt::put_abi(surface))
)
);
auto swapChainBrush = d->compositor.CreateSurfaceBrush(surface);
swapChainBrush.Stretch(
winrt::Windows::UI::Composition::CompositionStretch::Fill);
d->contentVisual.Brush(swapChainBrush);
// ── STEP 6: Insert order — CRITICAL ───────────────────────────────
d->rootVisual.Children().InsertAtTop(d->blurVisual); // 2. blur
d->rootVisual.Children().InsertAtTop(d->contentVisual); // 3. content
}
void VX::VX_DirectX11::BeginDraw() {
VX_D;
d->isDrawing = true;
d->d2dDeviceContext->BeginDraw();
}
void VX::VX_DirectX11::EndDraw(unsigned int width, unsigned int height) {
VX_D;
if (!d->isDrawing) return;
HRESULT hr = S_OK;
hr = d->d2dDeviceContext->EndDraw();
d->isDrawing = false;
d->hadAtLeastDrawnOnce = true;
if (hr == D2DERR_RECREATE_TARGET) {
// In that case we will need to recreate all the independent resources
HandleDeviceLose(width, height);
return;
}
DXGI_PRESENT_PARAMETERS params{};
UINT syncInterval = d->isResizing ? 0 : 1;
hr = d->dxgiSwapChain->Present1(syncInterval, 0, ¶ms);
if (d->isResizing) {
DwmFlush(); // force-sync this present with DWM's compositor before returning
}
if (hr == DXGI_ERROR_DEVICE_REMOVED ||
hr == DXGI_ERROR_DEVICE_RESET) {
HandleDeviceLose(width, height);
}
}
void VX::VX_DirectX11::Resize(unsigned int width, unsigned int height, bool isMaximized) {
VX_D;
if (!d->isInitialized) return;
if (!d->d2dDeviceContext) return;
if (!d->dxgiSwapChain) return;
if (width == 0 || height == 0) return;
if (width == d->currentWidth && height == d->currentHeight) return;
if (d->isDrawing) {
d->d2dDeviceContext->EndDraw();
d->isDrawing = false;
}
// Rebuild D2D render target
d->d2dDeviceContext->SetTarget(nullptr);
d->d2dBitmapRenderTarget.Reset();
if (d->hadAtLeastDrawnOnce) d->d2dDeviceContext->Flush();
d->backBuffer.Reset();
///DwmFlush();
HRESULT hr = d->dxgiSwapChain->ResizeBuffers(0, width, height, DXGI_FORMAT_UNKNOWN, 0);
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET) {
HandleDeviceLose(width, height);
return;
}
Microsoft::WRL::ComPtr<IDXGISurface> dxgiSurface;
d->dxgiSwapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiSurface));
D2D1_BITMAP_PROPERTIES1 props = D2D1::BitmapProperties1(
D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW,
D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED)
);
d->d2dDeviceContext->CreateBitmapFromDxgiSurface(dxgiSurface.Get(), &props, d->d2dBitmapRenderTarget.GetAddressOf());
d->d2dDeviceContext->SetTarget(d->d2dBitmapRenderTarget.Get());
float w = float(width);
float h = float(height);
if (w <= 0 || h <= 0) return;
// Fast mutations: Simply change sizes instead of calling .CreateXXXX() allocation functions!
d->rootVisual.Size({ w, h });
d->contentVisual.Size({ w, h });
d->blurVisual.Size({ w, h });
// Rebuild the blur mask surface with the new size
auto roundedRectGeom = d->compositor.CreateRoundedRectangleGeometry();
roundedRectGeom.Size({ w, h });
roundedRectGeom.CornerRadius({ 8.0f, 8.0f });
auto shape = d->compositor.CreateSpriteShape(roundedRectGeom);
shape.FillBrush(d->compositor.CreateColorBrush({ 255,255,255,255 })); // white
auto shapeVisual = d->compositor.CreateShapeVisual();
shapeVisual.Shapes().Append(shape);
shapeVisual.Size({ w, h });
auto maskSurface = d->compositor.CreateVisualSurface();
maskSurface.SourceVisual(shapeVisual);
maskSurface.SourceSize({ w, h });
auto maskSurfaceBrush = d->compositor.CreateSurfaceBrush(maskSurface);
// Stretch must be None (default) to keep the mask sharp
maskSurfaceBrush.Stretch(winrt::Windows::UI::Composition::CompositionStretch::None);
// Create a new mask brush that uses the existing effect brush (blur) as source
auto blurMaskBrush = d->compositor.CreateMaskBrush();
blurMaskBrush.Source(d->effectBrush); // reuse your existing blur effect brush
blurMaskBrush.Mask(maskSurfaceBrush);
// Assign the new mask brush to the blur visual
d->blurVisual.Brush(blurMaskBrush);
d->currentWidth = width;
d->currentHeight = height;
}
void VX::VX_DirectX11::Clear(VX_Color color) {
VX_D;
d->d2dDeviceContext->Clear(D2D1::ColorF(color.r, color.g, color.b, color.a));
}
void VX::VX_DirectX11::FillRoundedRectangle(VX_Color color, VX_Coordinates c, VX_CornerRadius radius) {
VX_D;
d->d2dSolidColorBrush->SetColor(D2D1::ColorF(color.r, color.g, color.b, color.a));
D2D1_ROUNDED_RECT rect = {
c.x, c.y, c.x + c.w, c.y + c.h,
radius.x, radius.y
};
d->d2dDeviceContext->FillRoundedRectangle(rect, d->d2dSolidColorBrush.Get());
}
void VX::VX_DirectX11::DrawRoundedRectangle(VX_Color color, VX_Coordinates c, VX_CornerRadius radius) {
VX_D;
d->d2dSolidColorBrush->SetColor(D2D1::ColorF(color.r, color.g, color.b, color.a));
D2D1_ROUNDED_RECT rect = {
c.x, c.y, c.x + c.w, c.y + c.h,
radius.x, radius.y
};
d->d2dDeviceContext->DrawRoundedRectangle(rect, d->d2dSolidColorBrush.Get());
}
void VX::VX_DirectX11::DrawLine(VX_Point pt1, VX_Point pt2, VX_Color color, float strokeWidth) {
VX_D;
d->d2dSolidColorBrush->SetColor(D2D1::ColorF(color.r, color.g, color.b, color.a));
d->d2dDeviceContext->DrawLine({ pt1.x, pt1.y }, { pt2.x, pt2.y }, d->d2dSolidColorBrush.Get(), strokeWidth);
}
void VX::VX_DirectX11::FillTriangle(VX_Point p1, VX_Point p2, VX_Point p3, const VX_Color& color) {
VX_D;
ID2D1PathGeometry* pGeometry = nullptr;
if (SUCCEEDED(d->d2dFactory->CreatePathGeometry(&pGeometry))) {
ID2D1GeometrySink* pSink = nullptr;
if (SUCCEEDED(pGeometry->Open(&pSink))) {
pSink->BeginFigure({ p1.x, p1.y }, D2D1_FIGURE_BEGIN_FILLED);
pSink->AddLine({ p2.x, p2.y });
pSink->AddLine({ p3.x, p3.y });
pSink->EndFigure(D2D1_FIGURE_END_CLOSED);
pSink->Close();
pSink->Release();
}
// Retrieve or create your solid brush wrapper matching the color target
d->d2dSolidColorBrush->SetColor({ color.r, color.g, color.b, color.a });
d->d2dDeviceContext->FillGeometry(pGeometry, d->d2dSolidColorBrush.Get());
pGeometry->Release();
}
}
void VX::VX_DirectX11::SetResizing(bool resizing) {
VX_D;
d->isResizing = resizing;
}
winrt::Windows::UI::Composition::Compositor VX::VX_DirectX11::GetCompositor() const {
VX_D;
return d->compositor;
}
bool VX::VX_DirectX11::IsInitialized() {
VX_D;
return d->isInitialized;
}
void VX::VX_DirectX11::HandleDeviceLose(unsigned int width, unsigned int height) {
ReleaseDeviceResources();
CreateD3DDeviceAndDeviceContext();
CreateSwapChain(width, height);
CreateD2DDeviceAndDeviceContext();
}
void VX::VX_DirectX11::ReleaseDeviceResources() {
VX_D;
d->d2dBitmapRenderTarget.Reset();
d->d2dDeviceContext.Reset();
d->d2dDevice.Reset();
d->dxgiSwapChain.Reset();
d->d3d11DeviceContext.Reset();
d->d3d11Device.Reset();
}
#include "VX_WindowApplication.h"
#include "VX_WindowsApplication_P.h"
#include <Windows.h>
VX::VX_WindowsApplication* VX::VX_WindowsApplicationPrivate::current = nullptr;
VX::VX_WindowsApplication::VX_WindowsApplication() : VX_Object(*new VX_WindowsApplicationPrivate) {
if (!VX_WindowsApplicationPrivate::current) VX_WindowsApplicationPrivate::current = this;
}
VX::VX_WindowsApplication::~VX_WindowsApplication() {
if (VX_WindowsApplicationPrivate::current) VX_WindowsApplicationPrivate::current = nullptr;
}
void VX::VX_WindowsApplication::Run() {
VX_D;
MSG msg{};
while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) {
if (msg.message == WM_QUIT) {
isRunning = false;
return;
}
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
auto& windows = d->windowsWindows;
for (auto& win : windows) win->Render();
}
void VX::VX_WindowsApplication::RegisterWindow(VX_WindowsWindow* win) {
VX_D;
d->windowsWindows.push_back(win);
}
void VX::VX_WindowsApplication::UnregisterWindow(VX_WindowsWindow* win) {
VX_D;
auto it = std::find(d->windowsWindows.begin(), d->windowsWindows.end(), win);
if (it != d->windowsWindows.end()) d->windowsWindows.erase(it);
}
VX::VX_WindowsApplication* VX::VX_WindowsApplication::Current() {
return VX_WindowsApplicationPrivate::current;
}
