You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 10:24:16