Share via

Live resize of transparent D3D11/WUC window causes swap chain content to stretch/shrink from left/top edges

Omar_Voltex_M_O 0 Reputation points
2026-06-17T18:02:16.4233333+00:00

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, &params);
	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;
}

Adobe Express - Screen Recording 2026-06-17 204410

Windows development | Windows API - Win32
0 comments No comments

1 answer

Sort by: Most helpful
  1. Taki Ly (WICLOUD CORPORATION) 2,225 Reputation points Microsoft External Staff Moderator
    2026-06-18T03:05:40.56+00:00

    Hello @Omar_Voltex_M_O ,

    I was looking at your description, and it seems like the stretching behavior you're experiencing when resizing from the top or left edges might be related to how the compositor handles the CompositionSurfaceBrush during a resize event before the newly sized frame is fully presented.

    Since you're aiming to preserve the live acrylic blur and transparency without resorting to opaque clears or snapshots, a potential approach I thought you could look into involves tweaking the Stretch and Alignment properties of your swap chain brush, along with explicitly updating the swap chain's source region size.

    Below are a couple of small adjustments you might want to try in your implementation:

    1. Adjusting the CompositionSurfaceBrush stretch and alignment:

    In your InitializeWUC method, around STEP 5: Content visual, the brush is currently set to Stretch::Fill. Changing this to None and explicitly anchoring it to the top-left could potentially prevent the compositor from automatically pulling the old buffer out of proportion to fill the new visual bounds.

    auto swapChainBrush = d->compositor.CreateSurfaceBrush(surface);
    // You might want to try disabling stretch to maintain the 1:1 pixel mapping during resize
    swapChainBrush.Stretch(winrt::Windows::UI::Composition::CompositionStretch::None);
    // Anchoring to Top-Left might help keep the buffer in place when the left/top edges move
    swapChainBrush.HorizontalAlignmentRatio(0.0f);
    swapChainBrush.VerticalAlignmentRatio(0.0f);
    d->contentVisual.Brush(swapChainBrush);
    

    Official reference:

    2. Updating the SwapChain's Source Size:

    After you call ResizeBuffers in your Resize method, you might also consider casting your swap chain to IDXGISwapChain2 and calling SetSourceSize. This is often recommended when dealing with composition swapchains so the OS understands the exact valid dimensions of the source content before the next Present executes.

    HRESULT hr = d->dxgiSwapChain->ResizeBuffers(0, width, height, DXGI_FORMAT_UNKNOWN, 0);
    if (SUCCEEDED(hr)) {
        // Optionally informing the swap chain about the new dimensions
        Microsoft::WRL::ComPtr<IDXGISwapChain2> swapChain2;
        if (SUCCEEDED(d->dxgiSwapChain.As(&swapChain2))) {
            swapChain2->SetSourceSize(width, height);
        }
    }
    

    Official reference:

    Since you already have DwmFlush(); implemented during the resize phase, combining it with Stretch::None could potentially yield a smoother resize experience without distorting your transparent visual.

    Hopefully, these documentation pointers and suggestions might help you find a workaround for the visual glitch. Let me know if it makes any difference in your setup! If you found my response helpful or informative, I would greatly appreciate it if you could follow this guide for your confirmation.

    Thank you.

    Was this answer helpful?


Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.