CreateSwapChainForHwnd多子窗口下渲染区域裁剪显示异常
问题现象
从HwndRenderTarget迁移到DeviceContext+SwapChain渲染架构后,所有接口HRESULT返回值均为S_OK,交换链预设背景色可铺满整个窗口客户区,但D2D绘制的内容被裁剪,仅在窗口左上角部分区域显示,排查发现渲染目标的DIP单位尺寸和客户区像素尺寸数值完全相等。
问题根因
核心是DPI配置不匹配,观察到的「渲染目标DIP尺寸与像素尺寸数值相等」就是最直接的故障表征:
applySwapChain方法中虽然调用GetDpiForWindow拿到了窗口实际DPI,但没有将该值同步给ID2D1DeviceContext,创建绑定交换链的目标位图时,用的是DeviceContext默认的96DPI。- 当系统显示缩放不是100%(比如常见的125%、150%缩放),96DPI下1个DIP单位对应1个物理像素,但窗口实际的逻辑尺寸(DIP单位)应该是物理像素值除以DPI缩放系数,最终表现为D2D仅在窗口左上角对应96DPI的区域渲染,剩余区域直接露出交换链预设的蓝色背景。
- 额外隐患:如果进程未开启Per-Monitor DPI感知,
GetClientRect拿到的是缩放后的逻辑像素值,和交换链需要的物理像素尺寸不匹配,会进一步放大裁剪问题。
修复方案
按顺序完成以下修改即可:
- 程序启动阶段开启Per-Monitor V2 DPI感知,调用
SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2),或者在程序manifest中添加对应DPI感知配置,保证GetClientRect返回窗口客户区的真实物理像素尺寸。 - 修正
applySwapChain的DPI处理逻辑,拿到窗口DPI后先设置给DeviceContext,再创建绑定到交换链缓冲的目标位图,修正后的代码如下:
void direct2dWindow::applySwapChain() { HRESULT hr; float dpiX, dpiY; UINT windowDpi = ::GetDpiForWindow(hwnd); dpiX = dpiY = static_cast<FLOAT>(windowDpi); // 关键修正:同步DeviceContext DPI和窗口DPI,不要沿用默认96DPI renderTarget->SetDpi(dpiX, dpiY); D2D1_BITMAP_PROPERTIES1 bitmapProps = D2D1::BitmapProperties1( D2D1_BITMAP_OPTIONS_TARGET | D2D1_BITMAP_OPTIONS_CANNOT_DRAW, D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE), dpiX, dpiY ); // 释放旧资源避免泄漏 if (bitmap) { bitmap->Release(); bitmap = nullptr; } if (surface) { surface->Release(); surface = nullptr; } hr = swapChain->GetBuffer(0, IID_IDXGISurface, reinterpret_cast<void**>(&surface)); throwOnFail(hr, "Failed to get swap chain back buffer"); hr = renderTarget->CreateBitmapFromDxgiSurface(surface, &bitmapProps, &bitmap); throwOnFail(hr, "Failed to create D2D target from back buffer"); renderTarget->SetTarget(bitmap); }
- 响应窗口的
WM_DPICHANGED消息,按照消息参数给出的建议矩形调整窗口位置后,主动调用resize方法用新的客户区尺寸重建交换链缓冲,重新执行applySwapChain更新DPI和渲染目标。 - 校验交换链尺寸:由于配置了
DXGI_SCALING_NONE参数,必须保证ResizeBuffers和创建交换链时传入的宽高,与窗口客户区物理像素尺寸完全一致,否则也会出现裁剪、错位问题。
验证标准
修复后调用renderTarget->GetSize()拿到的DIP尺寸,应该等于「客户区物理宽度 * 96 / 窗口DPI」「客户区物理高度 * 96 / 窗口DPI」,此时绘制和DIP尺寸等大的矩形即可完整覆盖整个窗口客户区,不会再出现裁剪问题。
补充:旧版HwndRenderTarget内部会自动完成窗口DPI同步、目标尺寸匹配的工作,迁移到手动管理DeviceContext+SwapChain的模式后,这部分逻辑需要自行实现,是这类迁移场景的高频踩坑点。
内容的提问来源于stack exchange,提问作者TJ Bandrowsky
相关产品推荐
相关产品推荐

