高效的 Direct2D 多线程
Efficient Direct2D multithreading
我正在为 Windows 商店编写电子书 reader 应用程序。我正在使用 Direct2D + DXGI 交换链在屏幕上呈现书页。
我的书内容有时非常复杂(几何图形、位图、遮罩等),因此渲染它最多可能需要 100 毫秒。所以我试图在一个单独的线程中对位图进行离屏渲染,然后只在主线程中显示该位图。
但是,我想不出如何有效地做到这一点。
到目前为止,我已经尝试了两种方法:
使用带有 D2D1_FACTORY_TYPE_MULTI_THREADED 标志的单个 ID2D1Factory
,创建 ID2D1BitmapRenderTarget
并在后台线程中使用它进行屏幕外渲染。 (这还需要对 IDXGISwapChain::Present
操作进行 ID2D1Multithread::Enter/Leave
)。问题是,后台线程中的 ID2D1RenderTarget::EndDraw
操作有时需要长达 100 毫秒,并且由于内部 Direct2D 锁定,主线程渲染在此期间被阻塞。
在后台线程中使用单独的 ID2D1Factory
(如 http://www.sdknews.com/ios/using-direct2d-for-server-side-rendering 中所述)并关闭内部 Direct2D 同步。在这种情况下,两个线程之间没有交叉锁定。不幸的是,在这种情况下,我不能直接在 main ID2D1Factory
中使用生成的位图,因为它属于不同的工厂。我必须将位图数据移动到 CPU 内存,然后将其复制到主 ID2D1Factory
的 GPU 内存中。此操作还引入了显着的滞后(我认为这是由于大量内存访问,但我不确定)。
有没有办法有效地做到这一点?
P.S。此处的所有时间均针对 Acer Switch 10 平板电脑。在普通的 Core i7 PC 上,这两种方法都没有任何明显的延迟。
tl;dr:在软件模式下在后台线程上渲染位图。在 UI 硬件模式线程上从位图绘制到渲染目标。
到目前为止,我能找到的最佳方法是使用后台线程和 软件 渲染(IWICImagingFactory::CreateBitmap
和 ID2D1Factory::CreateWicBitmapRenderTarget
),然后通过 ID2D1RenderTarget::CreateBitmapFromWicBitmap
将其复制到硬件位图,返回到具有硬件渲染目标的线程上。然后使用 ID2D1RenderTarget::DrawBitmap
.
blit
这就是paint.net 4.0 进行选区渲染的方式。当您使用套索工具绘制选区时,它将使用后台线程异步绘制选区轮廓(UI 线程不会等待此完成)。由于笔触样式和动画,您最终可能会得到一个非常复杂的多边形。我渲染了 4 次,其中每个动画帧的虚线笔触样式的偏移量略有不同。
显然,随着多边形变得更加复杂(也就是说,如果您继续涂鸦一段时间),此渲染可能需要一段时间。当您使用允许您进行变换(旋转、平移、缩放)的移动选择工具时,我还有一些其他特殊优化:如果后台线程尚未使用新变换重新渲染当前多边形,那么我将应用新变换渲染旧位图(使用当前多边形和旧变换)。当背景线程赶上时,选择轮廓可能会被扭曲(缩放)或被剪裁(翻译到可视区域之外),但这对于 60fps 的响应速度来说是一个很小的代价。此优化非常有效,因为您不能同时修改选区的多边形 和 变换。
好的,我找到了解决方案。
基本上,我只需要修改方法 2 以在两个 DirectX 工厂集之间使用 DXGI 资源共享。我将跳过所有血淋淋的细节(它们可以在这里找到:http://xboxforums.create.msdn.com/forums/t/66208.aspx),但基本步骤是:
- 创建两组 DirectX 资源:main(将用于屏幕渲染)和 secondary(用于屏幕外渲染)。
- 使用主要资源集中的
ID3D11Device2
,通过 CreateTexture2D
D3D11_BIND_RENDER_TARGET
、D3D11_BIND_SHADER_RESOURCE
、D3D11_RESOURCE_MISC_SHARED_NTHANDLE
和 D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX
标志创建 D3D 2D 纹理.
- 通过将其转换为
IDXGIResource1
并使用 XGI_SHARED_RESOURCE_READ
和 DXGI_SHARED_RESOURCE_WRITE
从中调用 CreateSharedHandle
来获取共享句柄。
- 通过调用
ID3D11Device2::OpenSharedResource1
. 在后台线程的辅助资源集中打开此共享纹理
- 获取此纹理的键控互斥量 (
IDXGIKeyedMutex::AcquireSync
),从中创建渲染目标 (ID2D1Factory2::CreateDxgiSurfaceRenderTarget
),在其上绘制并释放互斥量 (IDXGIKeyedMutex::ReleaseSync
)。
- 在主线程上,在主资源集中,获取互斥量并从步骤 2 中创建的纹理创建共享位图,绘制此位图,然后释放互斥量。
请注意,互斥锁是必需的。不这样做会导致一些神秘的 DirectX 调试错误消息、错误操作甚至崩溃。
我正在为 Windows 商店编写电子书 reader 应用程序。我正在使用 Direct2D + DXGI 交换链在屏幕上呈现书页。
我的书内容有时非常复杂(几何图形、位图、遮罩等),因此渲染它最多可能需要 100 毫秒。所以我试图在一个单独的线程中对位图进行离屏渲染,然后只在主线程中显示该位图。
但是,我想不出如何有效地做到这一点。
到目前为止,我已经尝试了两种方法:
使用带有 D2D1_FACTORY_TYPE_MULTI_THREADED 标志的单个
ID2D1Factory
,创建ID2D1BitmapRenderTarget
并在后台线程中使用它进行屏幕外渲染。 (这还需要对IDXGISwapChain::Present
操作进行ID2D1Multithread::Enter/Leave
)。问题是,后台线程中的ID2D1RenderTarget::EndDraw
操作有时需要长达 100 毫秒,并且由于内部 Direct2D 锁定,主线程渲染在此期间被阻塞。在后台线程中使用单独的
ID2D1Factory
(如 http://www.sdknews.com/ios/using-direct2d-for-server-side-rendering 中所述)并关闭内部 Direct2D 同步。在这种情况下,两个线程之间没有交叉锁定。不幸的是,在这种情况下,我不能直接在 mainID2D1Factory
中使用生成的位图,因为它属于不同的工厂。我必须将位图数据移动到 CPU 内存,然后将其复制到主ID2D1Factory
的 GPU 内存中。此操作还引入了显着的滞后(我认为这是由于大量内存访问,但我不确定)。
有没有办法有效地做到这一点?
P.S。此处的所有时间均针对 Acer Switch 10 平板电脑。在普通的 Core i7 PC 上,这两种方法都没有任何明显的延迟。
tl;dr:在软件模式下在后台线程上渲染位图。在 UI 硬件模式线程上从位图绘制到渲染目标。
到目前为止,我能找到的最佳方法是使用后台线程和 软件 渲染(IWICImagingFactory::CreateBitmap
和 ID2D1Factory::CreateWicBitmapRenderTarget
),然后通过 ID2D1RenderTarget::CreateBitmapFromWicBitmap
将其复制到硬件位图,返回到具有硬件渲染目标的线程上。然后使用 ID2D1RenderTarget::DrawBitmap
.
这就是paint.net 4.0 进行选区渲染的方式。当您使用套索工具绘制选区时,它将使用后台线程异步绘制选区轮廓(UI 线程不会等待此完成)。由于笔触样式和动画,您最终可能会得到一个非常复杂的多边形。我渲染了 4 次,其中每个动画帧的虚线笔触样式的偏移量略有不同。
显然,随着多边形变得更加复杂(也就是说,如果您继续涂鸦一段时间),此渲染可能需要一段时间。当您使用允许您进行变换(旋转、平移、缩放)的移动选择工具时,我还有一些其他特殊优化:如果后台线程尚未使用新变换重新渲染当前多边形,那么我将应用新变换渲染旧位图(使用当前多边形和旧变换)。当背景线程赶上时,选择轮廓可能会被扭曲(缩放)或被剪裁(翻译到可视区域之外),但这对于 60fps 的响应速度来说是一个很小的代价。此优化非常有效,因为您不能同时修改选区的多边形 和 变换。
好的,我找到了解决方案。
基本上,我只需要修改方法 2 以在两个 DirectX 工厂集之间使用 DXGI 资源共享。我将跳过所有血淋淋的细节(它们可以在这里找到:http://xboxforums.create.msdn.com/forums/t/66208.aspx),但基本步骤是:
- 创建两组 DirectX 资源:main(将用于屏幕渲染)和 secondary(用于屏幕外渲染)。
- 使用主要资源集中的
ID3D11Device2
,通过CreateTexture2D
D3D11_BIND_RENDER_TARGET
、D3D11_BIND_SHADER_RESOURCE
、D3D11_RESOURCE_MISC_SHARED_NTHANDLE
和D3D11_RESOURCE_MISC_SHARED_KEYEDMUTEX
标志创建 D3D 2D 纹理. - 通过将其转换为
IDXGIResource1
并使用XGI_SHARED_RESOURCE_READ
和DXGI_SHARED_RESOURCE_WRITE
从中调用CreateSharedHandle
来获取共享句柄。 - 通过调用
ID3D11Device2::OpenSharedResource1
. 在后台线程的辅助资源集中打开此共享纹理
- 获取此纹理的键控互斥量 (
IDXGIKeyedMutex::AcquireSync
),从中创建渲染目标 (ID2D1Factory2::CreateDxgiSurfaceRenderTarget
),在其上绘制并释放互斥量 (IDXGIKeyedMutex::ReleaseSync
)。 - 在主线程上,在主资源集中,获取互斥量并从步骤 2 中创建的纹理创建共享位图,绘制此位图,然后释放互斥量。
请注意,互斥锁是必需的。不这样做会导致一些神秘的 DirectX 调试错误消息、错误操作甚至崩溃。