Share via

How to fix size flickering on My D2D application

Omar Mohamed 120 Reputation points
2026-04-30T15:15:20.31+00:00

Hello,

Right now I have a Direct2d application that basically aims to draw the window using D2D instead of GDI so I have used Direct3d on top of Direct 2d This is cool and it actually worked well The problem right now is that When I resize the Window a small flickering issue happens I know it is because I have set that line of code to be

desc.Scaling = DXGI_SCALING_STRETCH;

but if I set it as DXGI_SCALING_NONE it actually fails with an error message that says that CreateSwapChainForDirectComposition only supports DXG_SCALING_STRETCH and the reason I'm using CreateSwapChainForDirectComposition is that I'm also using Direct Composition for retained mode and for a proper animations This is the Code of the project right now


void VX::VX_WindowsWindow::DrawWindow()
{
	VX_D;

	d->directX11->ClearScreen({ 0.0f, 0.0f, 0.0f, 0.0f });

	d->directX11->FillRoundedRectangle(
		{ 0.0f, 0.0f, float(width), float(height) },
		{ 8, 8 },
		VX_Colors::VX_Black
	);
	
	float closeButtonWidth = 22;
	float closeButtonHeight = 22;
	float closeButtonX = width - closeButtonWidth - 8;
	float closeButtonY = 8;

	d->directX11->FillRoundedRectangle(
		{ closeButtonX, closeButtonY, closeButtonWidth, closeButtonHeight },
		{ 4, 4 },
		VX_Colors::VX_Red
	);
}

void VX::VX_WindowsWindow::Render()
{
	VX_D;

	d->directX11->BeginDraw();

	DrawWindow();

	d->directX11->EndDraw();
}
void VX::VX_DirectX11::Resize(unsigned int width, unsigned int height, DrawWindow drawWindow)
{
	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;
	}

	d->currentWidth = width;
	d->currentHeight = height;

	// Release all D2D references to the swap chain back buffer
	d->d2dDeviceContext->SetTarget(nullptr);
	d->d2dRenderTarget.Reset();

	if (d->hasDrawingAtLeastOnce)
		d->d2dDeviceContext->Flush();

	d->backBuffer.Reset();

	DwmFlush();

	// Resize — old buffers stay valid in FLIP_SEQUENTIAL model
	// DirectComposition keeps showing the last frame during this call
	HRESULT hr = d->dxgiSwapChain->ResizeBuffers(
		0, width, height, DXGI_FORMAT_UNKNOWN, 0
	);

	if (SUCCEEDED(hr))
	{
		CreateD2DBitmap();
		drawWindow();

		// Single commit after the new frame is ready
		if (d->dcompDevice)
			d->dcompDevice->Commit();
	}
}

case WM_NCCALCSIZE:
	if (wParam == TRUE)
	{
		result = 0;
		wasHandled = VX_TRUE;
	}
	break;
case WM_ERASEBKGND:
	result = 1;
	wasHandled = VX_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 border = 8;

	bool left = pt.x < border;
	bool right = pt.x >= rc.right - border;
	bool top = pt.y < border;
	bool bottom = pt.y >= rc.bottom - border;

	if (left && top) { result = HTTOPLEFT; wasHandled = VX_TRUE; break; }
	if (right && top) { result = HTTOPRIGHT; wasHandled = VX_TRUE; break; }
	if (left && bottom) { result = HTBOTTOMLEFT; wasHandled = VX_TRUE; break; }
	if (right && bottom) { result = HTBOTTOMRIGHT; wasHandled = VX_TRUE; break; }
	if (top) { result = HTTOP; wasHandled = VX_TRUE; break; }
	if (left) { result = HTLEFT; wasHandled = VX_TRUE; break; }
	if (right) { result = HTRIGHT; wasHandled = VX_TRUE; break; }
	if (bottom) { result = HTBOTTOM; wasHandled = VX_TRUE; break; }

	result = HTCAPTION;
	wasHandled = VX_TRUE;
	break;
}
case WM_SIZE:
{
	VX_UNSIGNEDINT newWidth = LOWORD(lParam);
	VX_UNSIGNEDINT newHeight = HIWORD(lParam);

	if (pThis && newWidth > 0 && newHeight > 0)
	{
		pThis->q_ptr->width = newWidth;
		pThis->q_ptr->height = newHeight;

		if (pThis->q_ptr->isInitialized)
		{
			pThis->directX11->Resize(newWidth, newHeight, [pThis]() {pThis->q_ptr->DrawWindow(); });
			pThis->q_ptr->window->SizeChanged(pThis->q_ptr->window, { newWidth, newHeight });
			VX_Application::Current()->Invalidate();
		}
	}

	result = 0;
	wasHandled = VX_TRUE;
	break;
}

Thanks for everyone

Windows development | Windows API - Win32
0 comments No comments

2 answers

Sort by: Most helpful
  1. Jack Dang (WICLOUD CORPORATION) 18,310 Reputation points Microsoft External Staff Moderator
    2026-05-01T06:23:40.1133333+00:00

    Hi @Omar Mohamed ,

    Thanks for reaching out.

    With a DirectComposition swap chain, that scaling mode is generally expected, so it probably makes sense to keep DXGI_SCALING_STRETCH here rather than trying to move to DXGI_SCALING_NONE. The flicker seems to happen because, during resize, the compositor can keep showing the previous frame stretched until a fully rendered frame at the new size is ready.

    What may help most here is making sure a complete new frame is produced as quickly as possible after ResizeBuffers(). In your current flow, after resizing you recreate the bitmap and call drawWindow(), but that alone does not look like a full render pass. Since your normal pipeline is BeginDraw(), then draw, then EndDraw(), it would probably be better to run that full sequence immediately after the resize so the new frame can replace the stretched one sooner.

    So the flow would likely be: release the old target references, call ResizeBuffers(), recreate the D2D bitmap target, then draw the new frame, end the draw, present it, and finally commit the DirectComposition device. If your EndDraw() wrapper already performs the present internally, then it would make sense to rely on that instead of presenting twice.

    I would also consider removing DwmFlush() from the resize path. In this case it may be working against you by forcing synchronization while the old frame is still visible, which can make the transition more noticeable rather than smoother.

    Something along these lines may work better, although the exact changes may need to be adjusted a bit to fit the way your project wraps drawing and presentation:

    HRESULT hr = d->dxgiSwapChain->ResizeBuffers(
        0,
        width,
        height,
        DXGI_FORMAT_UNKNOWN,
        0
    );
    
    if (SUCCEEDED(hr))
    {
        CreateD2DBitmap();
    
        d->d2dDeviceContext->BeginDraw();
        drawWindow();
        hr = d->d2dDeviceContext->EndDraw();
    
        if (SUCCEEDED(hr))
        {
            d->dxgiSwapChain->Present(1, 0);
    
            if (d->dcompDevice)
                d->dcompDevice->Commit();
        }
    }
    

    Your WM_ERASEBKGND handling already looks good, and releasing the old back-buffer references before ResizeBuffers() still seems like the correct approach. I would also double-check that the window class is not using CS_HREDRAW or CS_VREDRAW, and that the background brush is nullptr, because those can sometimes introduce extra redraws during resizing.

    Hope this helps! If my answer was helpful, I would greatly appreciate it if you could follow the instructions here so others with the same problem can benefit as well.

    Was this answer helpful?


  2. AI answer

    2026-04-30T15:15:54.5533333+00:00

    The flickering comes from how the DXGI swap chain and the Direct2D render target are resized and how scaling is applied, not from DXGI_SCALING_STRETCH itself. With DirectComposition + CreateSwapChainForComposition, DXGI_SCALING_STRETCH is required, so the fix is to make the resize path and resource lifetime match DXGI/D2D best practices.

    Key points from the platform guidance:

    1. Always resize swap chain buffers on WM_SIZE and keep front/back buffer sizes identical.
      • DXGI recommends handling WM_SIZE and calling IDXGISwapChain::ResizeBuffers with the new size so the swap chain can reallocate buffers optimally and keep flip-based presentation instead of falling back to blit.
      • This is exactly what the Resize method is doing; keep that pattern and ensure it runs for every size change.
    2. Release all references to the back buffer before ResizeBuffers.
      • Before calling ResizeBuffers, release the Direct2D target and any references to the swap chain back buffer. The code already does:
        • SetTarget(nullptr)
        • d2dRenderTarget.Reset()
        • backBuffer.Reset()
      • This matches the requirement that all references to the old buffers be released before ResizeBuffers.
    3. For Direct2D + DXGI surface render targets, recreate the render target on resize.
      • DXGI surface render targets cannot be resized in place; they must be released and recreated when the swap chain size changes.
      • The code calls CreateD2DBitmap() after ResizeBuffers, which should recreate the D2D bitmap/render target from the new back buffer. Ensure this function always uses the current swap chain buffer and does not cache old surfaces.
    4. Avoid unnecessary device destruction when recreating the render target.
      • When recreating the DXGI surface render target, keep at least one Direct2D resource (bitmap, layer, mesh) alive that was created from the same D3D device so the device is not destroyed and recreated on every resize. This reduces the chance of visible flicker during resize.
    5. Let DXGI handle window styles and full-screen/windowed transitions.
      • Avoid extra window-style changes during resize that can cause additional WM_SIZE/WM_NCCALCSIZE traffic and visual artifacts. DXGI already manages much of this.
    6. Keep scaling and mode changes minimal.
      • With DXGI_SCALING_STRETCH required by CreateSwapChainForDirectComposition, rely on DXGI’s scaling and ensure that the content is redrawn at the new size immediately after ResizeBuffers (as the code does by calling drawWindow() and then Commit). This minimizes the time the user sees a stretched old frame.

    In summary, keep DXGI_SCALING_STRETCH (since CreateSwapChainForDirectComposition requires it), ensure that ResizeBuffers is called on every WM_SIZE with all back-buffer references released, recreate the D2D render target from the new DXGI surface, and keep the D3D device alive across resizes. These steps align with DXGI and Direct2D guidance and reduce or eliminate resize flicker.


    References:

    AI-generated content may be incorrect. Read our transparency notes for more information.

    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.