Skip to content

[DEV] GPU 硬件加速渲染下更新分层窗口的性能优化 #79

@GPBeta

Description

@GPBeta

使用 OpenGL, D3D12, Vulkan 图形接口进行离屏渲染,是否存在某种“捷径”可以减少在使用 UpdateLayeredWindow() 更新分层窗口时位图内存的复制次数?

背景

对于 GPU 硬件加速的分层窗口,可以直接在该窗口上创建 Swap Chain, 系统会把 GPU 绘制的内容按照不同的交换模型直接传输到各自的由 DWM 管理的 Surface 上。但该技术有一个限制:无法利用分层窗口的逐像素鼠标拾取检测特性

因此 SAO Utils 2 在渲染桌面模式下的异形视图时 (View.sold = false), 会把 GPU 离屏渲染的纹理传送到 CPU 可以访问的内存,然后使用 UpdateLayeredWindow() 更新到对应的分层窗口。
这不可避免地会占用总线带宽和额外的缓冲区内存——更糟糕的是,由于 API 的设计 UpdateLayeredWindow() 必须通过 Memory DC 更新分层窗口,这又会引入另一个额外的的 从图形接口管理的内存缓冲区到 GDI Bitmap 内存的复制

参考

Direct3D 9

对于 D3D9 图形接口,可以通过 IDirect3DSurface9::GetDC() 获得一个 UpdateLayeredWindow() 直接用于更新分层窗口的 HDC.

Direct3D 11

虽然 D3D11 图形接口没有像 D3D9 那样直接给出 GetDC() 方法,但可以通过 ID3D11Texture2D::QueryInterface() 查询一个 IDXGISurface1 接口,最后通过 IDXGISurface1::GetDC() 获得一个和 D3D9 方案一样的可用 HDC.

以上捷径可能仍然无法避免 VRAM -> RAM 的传输,但至少这些内部实现应该可以减少一次额外的 GDI 位图转换或传输。

想法

其中一个突破口是,用于创建中转位图的 CreateDIBSection() 函数支持传入一个 CreateFileMapping() 所创建的 NT 句柄。
这使得 CreateDIBSection() 虽然不支持为创建的位图指定内存地址,但是至少可以指定一个共享内存对象。

OpenGL

对于 OpenGL 图形接口,应该可以通过 EXT_external_objects_win32 扩展减少额外的内存复制:

  • 创建一个共享内存对象;
  • 把该内存对象的句柄作为 Memory Object 导入到 OpenGL;
  • 把该 Memory Object 绑定到 Pixel Buffer Object;
  • 把渲染结果异步读取到该 PBO;
  • 使用该内存对象的句柄创建 DIB 位图;
  • 绑定该位图到 HDC 后更新分层窗口。

这个方案最大的挑战可能是 驱动的支持程度可能很低,大部分软硬件环境可能需要回退到内存对拷的实现。

Vulkan

相似地,Vulkan 存在 VK_KHR_external_memory_win32 扩展可以实现类似以上 OpenGL 的内存共享操作。
不同的是,Vulkan 对该扩展的支持度可能会比 OpenGL 不知道高到哪里去了。

Direct3D 12

D3D12 对其他图形接口的互操作似乎精简得只剩下 ID3D12Device::CreateSharedHandle() 了,然而该方法创建的 NT 句柄并非共享内存对象,不能用于 CreateDIBSection(), 纹理的 ID3D12Resource 接口也不提供 DXGI 互操作。

Metadata

Metadata

Assignees

No one assigned

    Labels

    discussion我们是兄弟,我怎么会鸽你呢

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions