4

我正在使用现有的图形库,其中一个实现使用 Direct2D。(它也可以使用 GDI+、Quartz 等。)它有位图对象、钢笔、画笔等,所有这些都作为 Direct2D 对象的包装代码实现。该库是封闭源代码,不幸的是我可能无法在此处发布大量内容。

我发现当使用多个线程时 - 一个线程生产者,创建位图并绘制到它们,一个线程消费者,在屏幕上绘制它们 - 有时生产者线程产生的位图是完全空白的:图像数据(内部纹理)全为零. (位图是纹理周围的包装器对象,但表示位图就像您可能会想到的 GDI+ 位图对象一样。我相信您熟悉一般范例。)请注意,它永远不会崩溃,只是有时默默地失败。一个典型的症状是绘制位图数据,调用Unmap从缓冲区复制回纹理,然后当您稍后尝试绘制它时发现纹理是空白的。

我认为正在发生的是:

  • 为了直接写入像素数据,正如我的生产者线程所做的那样,库提供了直接访问像素数据的函数MapUnmap有一个 DirectX10 设备,一个“共享设备”,它用于调用CopyResource从内部纹理复制到缓冲区,然后可以自由写入,然后以另一种方式取消映射复制回来。我认为有时这会默默地失败。
  • 共享设备的这种做法在整个库中都有使用:有一个共享渲染目标(ID2D1RenderTarget)用于创建共享位图(对它们的操作是通过一个设备、一个渲染目标等完成的。ID2D1Bitmap
  • 当多个线程同时使用“位图”时,几个操作可能会失败:(CopyResource上面提到的)返回voidnot HRESULT,有时会失败;ID2D1RenderTarget::DrawBitmap也默默地失败了。

所以,我想解决这个问题并侵入库足够的线程安全性,我可以可靠地实现我的生产者 - 消费者线程系统。我该怎么做呢?

我一直在阅读有关从多个线程使用 Direct2D 的正确方法的大量内容。

  • 所有工厂都已使用D2D1_FACTORY_TYPE_MULTI_THREADEDflag创建。

  • 我尝试使用ID2D1MultiThread接口通过工厂进行同步。但是,这似乎只在 Windows 7 及更高版本上可用:我需要使用 DirectX10 级别的 Vista+ API。

  • 我也尝试过使用ID3D10Multithread 接口进行同步。我遇到了一些问题:

    我不确定我应该在哪里同步,或者围绕什么同步。如果是细粒度的,比如aroundMapUnmapcalls,或者PresentMSDN指示的需要round,同步是没有效果的:上面的方法调用还是失败。如果它是粗粒度的,在调用ID2D1RenderTarget::BeginDraw时进入锁,在所有绘图中都持有它,然后在和/或被调用之后离开锁ID2D1RenderTarget::EndDraw,它似乎工作得很好......直到几秒钟后我进入僵局。MSDN 对此进行了描述,ID2D1RenderTarget::FlushIDXGISwapChain::Present

    请注意,当您使用全屏交换链时,您永远不会让消息泵线程在渲染线程上等待。例如,调用 IDXGISwapChain1::Present1(从渲染线程)可能会导致渲染线程在消息泵线程上等待。发生模式更改时,如果 Present1 调用 ::SetWindowPos() 或 ::SetWindowStyle() 并且这些方法中的任何一个调用 ::SendMessage(),则这种情况是可能的。在这种情况下,如果消息泵线程有一个临界区保护它,或者如果渲染线程被阻塞,那么这两个线程将死锁。-来源

    ...但没有关于如何避免它的指导。我的死锁似乎是由两个线程同时尝试访问图形(例如调用BeginDraw)引起的。似乎有第二个锁在起作用,因为如果只有一个锁,那么就不会有死锁。

  • 我已经尝试保留“共享”设备、渲染目标等的每个线程实例。也就是说,每种类型的对象的每个线程都有一个实例,然后由该线程中运行的所有代码使用。这很好用,除了纹理:似乎访问在另一个线程上下文(由另一个设备)中创建的纹理不起作用 - 根本没有。在编码这种技术时,我经常遇到方法失败的问题 - 最常见的是CreateSharedBitmap失败D2DERR_UNSUPPORTED_OPERATION,其中纹理来自另一个线程(因此,另一个设备)。这是关键位,因为位图对象(包装在一个线程中创建的纹理)需要能够在另一个线程中绘制。根据其编码方式,线程和设备、渲染目标等之间可能存在也可能不存在 1:1 关系。

    如果我能解决这个问题,我认为我编写的其余代码足以让我实现所需的目标。有可能 - 以及如何?ID3D10Texture2D- 在s 之间交叉ID3D10Device1s?是否需要跨线程使用单独的设备?

总之,我有点难过,正在向那些熟悉 Direct3D 10 和 Direct2D 的人寻求关于最佳方法的建议。我认为有两种可行的可能性:

  1. 找出ID3D10Multithread 接口锁定。Google 上没有太多关于避免 MSDN 死锁的建议。

  2. 继续使用每个线程都有自己的设备、共享渲染目标等的机制。(这有多少是必要的 - 有没有可能,比如说,只有一个设备,但每个线程有单独的渲染目标?)如果是这样,纹理资源如何从一个线程交叉到另一个线程,这可能意味着从一个设备到另一个设备?基本上,使用在一个线程中创建的内部纹理的包装位图对象需要在第二个线程上可用。

但我也很想知道其他任何可能性。任何关于正确 Direct2D 线程实现的建议都将非常感激地听到。

4

1 回答 1

3

适合我的模型是单个 D2D 和 D3D 工厂以及每线程 D2D 渲染目标。同步是通过我自己的关键部分完成的。我不需要在线程之间共享位图。

在您的情况下应该工作的是每个线程都有单独的所有内容(工厂、渲染目标等)并使用共享资源。这也回答了您关于如何在ID3D10Texture2Ds 之间跨越 s的问题ID3D10Device1。您将需要这些功能: IDXGIResource::GetSharedHandle ID3D10Device::OpenSharedResource IDXGIKeyedMutex::AcquireSync

或者每个进程只创建一个工厂、一个渲染目标等,并通过您自己的关键部分进行同步。在这种情况下,需要围绕每个触及BeginDraw-EndDraw使用另一个线程位图的批处理 () 的 D2D 或 D3D 调用进行同步。

我发现同时启用 D2D 和 D3D 调试层并使用 Windows 8.1 SDK 在处理多线程问题时非常有用。新的 Windows 8.1 SDK 包含多线程问题的新消息。您无需在 Windows 8.1 上使用 8.1 SDK。

于 2014-01-27T17:15:52.413 回答