Windows 图形 API 之间的 Surface 共享

本主题提供有关在 Windows 图形 API(包括 Direct3D 11、Direct2D、DirectWrite、Direct3D 10 和 Direct3D 9Ex)之间使用图面共享的互操作性的技术概述。 如果你已经掌握了这些 API 的实践知识,本白皮书可帮助你使用多个 API 在专为 Windows 7 或 Windows Vista 操作系统设计的应用程序中呈现到同一表面。 本主题还提供最佳做法指南和指向其他资源的指针。

注意

对于 Direct2D 和 DirectWrite DirectX 11.1 运行时上的互操作性,可以使用 Direct2D 设备和设备上下文直接呈现到 Direct3D 11 设备。

 

本主题包含以下各节:

简介

在本文档中,Windows 图形 API 互操作性是指通过不同的 API 共享同一呈现图面。 这种互操作性使应用程序能够通过利用多个 Windows 图形 API 创建引人注目的显示器,并通过保持与现有 API 的兼容性来简化到新技术的迁移。

在具有 Windows 7 互操作包、Vista 7IP) 的 Windows 7 (和 Windows Vista SP2 中,图形呈现 API 为 Direct3D 11、Direct2D、Direct3D 10.1、Direct3D 10.0、Direct3D 9Ex、Direct3D 9c 和更早的 Direct3D API 以及 GDI 和 GDI+。 Windows 图像处理组件 (WIC) 和DirectWrite是图像处理的相关技术,Direct2D 执行文本呈现。 DirectX 视频加速 API (基于 Direct3D 9c 和 Direct3D 9Ex 的 DXVA) 用于视频处理。

随着 Windows 图形 API 向基于 Direct3D 的方向发展,Microsoft 在确保 API 之间的互操作性方面投入了更多精力。 新开发的 Direct3D API 和基于 Direct3D API 的更高级别的 API 也在需要时提供支持,以桥接与旧 API 的兼容性。 为了说明,Direct2D 应用程序可以通过共享 Direct3D 10.1 设备来使用 Direct3D 10.1。 此外,Direct3D 11、Direct2D 和 Direct3D 10.1 API 都可以利用 DirectX Graphics Infrastructure (DXGI) 1.1,它支持完全支持这些 API 之间的互操作性的同步共享图面。 基于 DXGI 1.1 的 API 通过从 DXGI 1.1 图面获取 GDI 设备上下文,通过关联与 GDI+ 互操作。 有关详细信息,请参阅 MSDN 上提供的 DXGI 和 GDI 互操作性文档。

Direct3D 9Ex 运行时支持未同步的图面共享。 基于 DXVA 的视频应用程序可以使用 Direct3D 9Ex 和 DXGI 互操作性帮助程序来实现基于 Direct3D 9Ex 的 DXVA 互操作性与计算着色器的 Direct3D 11 的互操作,也可以与 Direct2D 互操作以用于 2D 控件或文本呈现。 WIC 和 DirectWrite还与 GDI、Direct2D 以及通过关联的其他 Direct3D API 进行互操作。

Direct3D 10.0、Direct3D 9c 和较旧的 Direct3D 运行时不支持共享图面。 系统内存副本将继续用于与基于 GDI 或 DXGI 的 API 的互操作性。

请注意,本文档中的互操作性方案是指将多个图形 API 呈现到共享呈现图面,而不是呈现到同一应用程序窗口。 针对不同图面的单独 API 的同步,然后复合到同一窗口上,不在本文的范围内。

API 互操作性概述

可以通过 API 到 API 方案和相应的互操作性功能来描述 Windows 图形 API 的 Surface 共享互操作性。 从 Windows 7 开始,从具有 7IP 的 Windows Vista SP2 开始,新的 API 和相关运行时包括 Direct2D 和相关技术:Direct3D 11 和 DXGI 1.1。 在 Windows 7 中,GDI 性能也得到了改进。 Direct3D 10.1 是在 Windows Vista SP1 中引入的。 下图显示了 API 之间的互操作性支持。

Windows 图形 API 之间的互操作性支持的示意图

在此图中,箭头显示了互操作性方案,其中的同一图面可由连接的 API 访问。 蓝色箭头表示 Windows Vista 中引入的互操作性机制。 绿色箭头表示对新 API 或改进的互操作性支持,可帮助旧 API 与较新的 API 进行互操作。 例如,绿色箭头表示设备共享、同步共享图面支持、Direct3D 9Ex/DXGI 同步帮助程序,以及从兼容图面获取 GDI 设备上下文。

互操作性方案

从 Windows 7 和 Windows Vista 7IP 开始,Windows 图形 API 中的主流产品/服务支持将多个 API 呈现到同一 DXGI 1.1 图面。

Direct3D 11、Direct3D 10.1、Direct2D - 相互互操作性

direct3D 11、Direct3D 10.1 和 Direct2D API (及其相关 API(如 DirectWrite 和 WIC) )可以使用 Direct3D 10.1 设备共享或同步的共享图面相互互操作。

使用 Direct2D 共享 Direct3D 10.1 设备

在 Direct2D 和 Direct3D 10.1 之间共享设备允许应用程序使用这两个 API,使用同一基础 Direct3D 设备对象,无缝高效地呈现到同一 DXGI 1.1 图面上。 Direct2D 利用 Direct2D 基于 Direct3D 10.1 和 DXGI 1.1 运行时构建的事实,可以使用现有的 Direct3D 10.1 设备调用 Direct2D API。 以下代码片段演示 Direct2D 如何从与设备关联的 DXGI 1.1 图面获取 Direct3D 10.1 设备呈现目标。 Direct3D 10.1 设备呈现目标可以在 BeginDraw 和 EndDraw API 之间执行 Direct2D 绘图调用。

// Direct3D 10.1 Device and Swapchain creation
HRESULT hr = D3D10CreateDeviceandSwapChain1(
                pAdapter,
                DriverType,
                Software,
                D3D10_CREATE_DEVICE_BGRA_SUPPORT,
                featureLevel,
                D3D10_1_SDK_VERSION,
                pSwapChainDesc,
                &pSwapChain,
                &pDevice
                );

hr = pSwapChain->GetBuffer(
        0,
        __uuidof(IDXGISurface),
        (void **)&pDXGIBackBuffer
        ));

// Direct3D 10.1 API rendering calls
...

hr = D2D1CreateFactory(
        D2D1_FACTORY_TYPE_SINGLE_THREADED,
        &m_spD2DFactory
        ));

pD2DFactory->CreateDxgiSurfaceRenderTarget(
        pDXGIBackBuffer,
        &renderTargetProperties,
        &pD2DBackBufferRenderTarget
        ));
...

pD2DBackBufferRenderTarget->BeginDraw();
//Direct2D API rendering calls
...

pD2DBackBufferRenderTarget->EndDraw();

pSwapChain->Present(0, 0);

备注

  • 关联的 Direct3D 10.1 设备必须支持 BGRA 格式。 该设备是通过使用参数D3D10_CREATE_DEVICE_BGRA_SUPPORT调用 D3D10CreateDevice1 创建的。 从 Direct3D 10 功能级别 9.1 开始支持 BGRA 格式。
  • 应用程序不应创建多个关联到同一 Direct3D10.1 设备的 ID2D1RenderTarget。
  • 为了获得最佳性能,请始终保留至少一个资源,例如与设备关联的纹理或表面。

设备共享适用于在进程内单线程使用由 Direct3D 10.1 和 Direct2D 呈现 API 共享的一个呈现设备。 通过同步的共享图面,可以在多线程、进程内和进程外使用 Direct3D 10.1、Direct2D 和 Direct3D 11 API 使用的多个渲染设备。

Direct3D 10.1 和 Direct2D 互操作性的另一种方法是使用 ID3D1RenderTarget::CreateSharedBitmap,它从 IDXGISurface 创建 ID2D1Bitmap 对象。 可以将 Direct3D10.1 场景写入位图并使用 Direct2D 呈现。 有关详细信息,请参阅 ID2D1RenderTarget::CreateSharedBitmap 方法

Direct2D 软件光栅化

使用 Direct2D 软件呈现器时,不支持使用 Direct3D 10.1 共享设备,例如,在创建 Direct2D 呈现目标时在 D2D1_RENDER_TARGET_USAGE 中指定D2D1_RENDER_TARGET_USAGE_FORCE_SOFTWARE_RENDERING。

Direct2D 可以使用 WARP10 软件光栅器与 Direct3D 10 或 Direct3D 11 共享设备,但性能明显下降。

DXGI 1.1 同步共享图面

Direct3D 11、Direct3D 10.1 和 Direct2D API 都使用 DXGI 1.1,它提供的功能是将读取和写入到同一个视频内存图面同步 (DXGISurface1) 由两个或更多个 Direct3D 设备。 使用同步共享图面的渲染设备可以是 Direct3D 10.1 或 Direct3D 11 设备,每个设备在同一进程或跨进程运行。

应用程序可以通过从 Direct2D 呈现目标对象获取 Direct3D 10.1 设备,使用同步的共享图面在任何基于 DXGI 1.1 的设备(如 Direct3D 11 和 Direct3D 10.1)之间或在 Direct3D 11 和 Direct2D 之间互操作。

在 Direct3D 10.1 及更高版本的 API 中,若要使用 DXGI 1.1,请确保使用 DXGI 1.1 适配器对象(从 DXGI 1.1 工厂对象枚举)创建 Direct3D 设备。 调用 CreateDXGIFactory1 创建 IDXGIFactory1 对象,调用 EnumAdapters1 枚举 IDXGIAdapter1 对象。 IDXGIAdapter1 对象需要作为 D3D10CreateDevice 或 D3D10CreateDeviceAndSwapChain 调用的一部分传入。 有关 DXGI 1.1 API 的详细信息,请参阅 DXGI 编程指南

API

D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX
创建同步的共享资源时,请在 D3D10_RESOURCE_MISC_FLAG 中设置D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX。

typedef enum D3D10_RESOURCE_MISC_FLAG {
    D3D10_RESOURCE_MISC_GENERATE_MIPS      = 0x1L,
    D3D10_RESOURCE_MISC_SHARED             = 0x2L,
    D3D10_RESOURCE_MISC_TEXTURECUBE        = 0x4L,
    D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX  = 0x10L,
    D3D10_RESOURCE_MISC_GDI_COMPATIBLE     = 0x20L,
}   D3D10_RESOURCE_MISC_FLAG;

D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX
允许使用 IDXGIKeyedMutex::AcquireSync 和 ReleaseSync API 同步创建的资源。 以下资源创建 Direct3D 10.1 API 均采用 D3D10_RESOURCE_MISC_FLAG 参数,已扩展以支持新标志。

  • ID3D10Device1::CreateTexture1D
  • ID3D10Device1::CreateTexture2D
  • ID3D10Device1::CreateTexture3D
  • ID3D10Device1::CreateBuffer

如果使用设置D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX标志调用列出的任何函数,则可以查询返回的接口以获取 IDXGIKeyedMutex 接口,该接口实现 AcquireSync 和 ReleaseSync API 以同步对图面的访问。 创建图面的设备以及使用 OpenSharedResource () 打开图面的任何其他设备需要先调用 IDXGIKeyedMutex::AcquireSync,然后才能向图面调用任何呈现命令,在呈现完成后调用 IDXGIKeyedMutex::ReleaseSync。
WARP 和 REF 设备不支持共享资源。 尝试在 WARP 或 REF 设备上使用此标志创建资源将导致 create 方法返回E_OUTOFMEMORY错误代码。
IDXGIKEYEDMUTEX 接口
DXGI 1.1 中的新接口 IDXGIKeyedMutex 表示键锁互斥体,允许独占访问由多个设备使用的共享资源。 有关此接口及其两种方法 AcquireSync 和 ReleaseSync 的参考文档,请参阅 IDXGIKeyedMutex

示例:两个 Direct3D 10.1 设备之间的同步 Surface 共享

下面的示例演示在两个 Direct3D 10.1 设备之间共享图面。 同步的共享图面由 Direct3D10.1 设备创建。

// Create Sync Shared Surface using Direct3D10.1 Device 1.
D3D10_TEXTURE2D_DESC desc;
ZeroMemory( &desc, sizeof(desc) );
desc.Width = width;
desc.Height = height;
desc.MipLevels = 1;
desc.ArraySize = 1;
// must match swapchain format in order to CopySubresourceRegion.
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
desc.SampleDesc.Count = 1;
desc.Usage = D3D10_USAGE_DEFAULT;
// creates 2D texture as a Synchronized Shared Surface.
desc.MiscFlags = D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX;
desc.BindFlags = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE;
ID3D10Texture2D* g_pShared = NULL;
g_pd3dDevice1->CreateTexture2D( &desc, NULL, &g_pShared );

// QI IDXGIResource interface to synchronized shared surface.
IDXGIResource* pDXGIResource = NULL;
g_pShared->QueryInterface(__uuidof(IDXGIResource), (LPVOID*) &pDXGIResource);

// obtain handle to IDXGIResource object.
pDXGIResource->GetSharedHandle(&g_hsharedHandle);
pDXGIResource->Release();
if ( !g_hsharedHandle )
    return E_FAIL;

// QI IDXGIKeyedMutex interface of synchronized shared surface's resource handle.
hr = g_pShared->QueryInterface( __uuidof(IDXGIKeyedMutex),
    (LPVOID*)&g_pDXGIKeyedMutex_dev1 );
If ( FAILED( hr ) || ( g_pDXGIKeyedMutex_dev1 == NULL ) )
    return E_FAIL;

同一 Direct3D10.1 设备可以通过调用 AcquireSync 获取用于呈现的同步共享图面,然后通过调用 ReleaseSync 为另一台设备的呈现释放图面。 当未与任何其他 Direct3D 设备共享同步的共享图面时,创建者可以获取并释放同步的共享图面, (通过使用相同的键值获取和释放来开始和结束呈现) 。

// Obtain handle to Sync Shared Surface created by Direct3D10.1 Device 1.
hr = g_pd3dDevice2->OpenSharedResource( g_hsharedHandle,__uuidof(ID3D10Texture2D),
                                        (LPVOID*) &g_pdev2Shared);
if (FAILED (hr))
    return hr;
hr = g_pdev2Shared->QueryInterface( __uuidof(IDXGIKeyedMutex),
                                    (LPVOID*) &g_pDXGIKeyedMutex_dev2);
if( FAILED( hr ) || ( g_pDXGIKeyedMutex_dev2 == NULL ) )
    return E_FAIL;

// Rendering onto Sync Shared Surface from D3D10.1 Device 1 using D3D10.1 Device 2.
UINT acqKey = 1;
UINT relKey = 0;
DWORD timeOut = 5;
DWORD result = g_pDXGIKeyedMutex_dev2->AcquireSync(acqKey, timeOut);
if ( result == WAIT_OBJECT_0 )
    // Rendering calls using Device 2.
else
    // Handle unable to acquire shared surface error.
result = g_pDXGIKeyedMutex_dev2->ReleaseSync(relKey));
if (result == WAIT_OBJECT_0)
    return S_OK;

第二个 Direct3D10.1 设备可以通过调用 AcquireSync 来获取要呈现的同步共享图面,然后通过调用 ReleaseSync 为第一个设备的呈现释放图面。 请注意,设备 2 能够使用与设备 1 在 ReleaseSync 调用中指定的键值相同的键值来获取同步的共享图面。

// Rendering onto Sync Shared Surface from D3D10.1 Device 1 using D3D10.1 Device 1.
UINT acqKey = 0;
UINT relKey = 1;
DWORD timeOut = 5;
DWORD result = g_pDXGIKeyedMutex_dev1->AcquireSync(acqKey, timeOut);
if (result == WAIT_OBJECT_0)
    // Rendering calls using Device 1.
else
    // Handle unable to acquire shared surface error.
result = g_pDXGIKeyedMutex_dev1->ReleaseSync(relKey));
if ( result == WAIT_OBJECT_0 )
    return S_OK;

共享同一表面的其他设备可以使用其他密钥轮流获取和释放图面,如以下调用所示。

// Within Device 1's process/thread:
// Rendering onto Sync Shared Surface from D3D10.1 Device 1 using D3D10.1 Device 1
result = g_pDXGIKeyedMutex_dev1->AcquireSync(0, timeOut);
// Rendering calls using Device 1
...
result = g_pDXGIKeyedMutex_dev1->ReleaseSync(1);
...
////////////////////////////////////////////////////////////////////////////
// Within Device 2's process/thread:
// Rendering onto Sync Shared Surface from D3D10.1 Device 1 using D3D10.1 Device 2
result = g_pDXGIKeyedMutex_dev2->AcquireSync(1, timeOut);
// Rendering calls using Device 2
...
result = g_pDXGIKeyedMutex_dev1->ReleaseSync(2);

////////////////////////////////////////////////////////////////////////////
// Within Device 3's process/thread:
// Rendering onto Sync Shared Surface from D3D10.1 Device 1 using D3D10.1 Device 3
result = g_pDXGIKeyedMutex_dev1->AcquireSync(2, timeOut);
// Rendering calls using Device 3
...
result = g_pDXGIKeyedMutex_dev1->ReleaseSync(0);
...

请注意,实际应用程序可能始终呈现到中间图面,然后复制到共享图面中,以防止任何设备在共享表面的另一台设备上等待。

将同步共享 Surface 与 Direct2D 和 Direct3D 11 配合使用

同样,对于 Direct3D 11 和 Direct3D 10.1 API 之间的共享,可以从任一 API 设备创建同步共享图面,并与其他 API 设备 () 、进程内或进程外共享。

使用 Direct2D 的应用程序可以共享 Direct3D 10.1 设备,并使用同步的共享图面与 Direct3D 11 或其他 Direct3D 10.1 设备互操作,无论它们属于同一进程还是不同进程。 但是,对于单进程、单线程应用程序,设备共享是 Direct2D 与 Direct3D 10 或 Direct3D 11 之间最高性能、最高效的互操作性方法。

软件光栅器

当应用程序使用 Direct3D 或 Direct2D 软件光栅器(包括参考光栅器和 WARP)而不是使用图形硬件加速时,不支持同步的共享图面。

Direct3D 9Ex 与基于 DXGI 的 API 之间的互操作性

Direct3D 9Ex API 包括 Surface 共享的概念,以允许其他 API 从共享图面读取。 若要共享对 Direct3D 9Ex 共享图面的读取和写入,必须将手动同步添加到应用程序本身。

Direct3D 9Ex Shared Surfaces Plus 手动同步帮助程序

Direct3D 9Ex 和 Direct3D 10 或 11 互操作性中最基本的任务是将单个表面从第一个设备 (设备 A) 传递到第二个 (设备 B) 这样,当设备 B 获取图面上的句柄时,保证设备 A 的呈现已完成。 因此,设备 B 可以毫不担心地使用此表面。 这与经典生成者-消费者问题非常相似,此讨论以这种方式模拟问题。 第一个使用表面的设备,然后放弃它是设备 A) (生成者,而最初等待的设备是使用者 (设备 B) 。 任何实际应用程序都比这更复杂,并且会将多个生成者-使用者构建基块链接在一起,以创建所需的功能。

生成者-使用者构建基块通过使用图面队列在帮助器中实现。 表面由生成者排队,使用者取消排队。 帮助程序引入了三个 COM 接口:ISurfaceQueue、ISurfaceProducer 和 ISurfaceConsumer。

High-Level帮助程序概述

ISurfaceQueue 对象是使用共享图面的构建基块。 它使用初始化的 Direct3D 设备和说明创建,以创建固定数量的共享图面。 队列对象管理资源的创建和代码的打开。 表面的数量和类型是固定的;创建图面后,应用程序无法添加或删除它们。

ISurfaceQueue 对象的每个实例都提供一种单向街道,可用于将表面从生产设备发送到使用设备。 多个此类单向街道可用于启用特定应用程序设备之间的图面共享方案。

创建/对象生存期
可通过两种方法创建队列对象:通过 CreateSurfaceQueue 或 ISurfaceQueue 的 Clone 方法。 由于接口是 COM 对象,因此应用标准 COM 生存期管理。
生成者/使用者模型
排队 () :生成者调用此函数以指示它已使用表面完成,该图面现在可供其他设备使用。 从此函数返回后,生成者设备不再具有对表面的权限,并且继续使用它是不安全的。
取消排队 () :使用设备调用此函数以获取共享图面。 API 保证任何取消排队的图面都已准备好使用。
Metadata
API 支持将元数据与共享图面相关联。
Enqueue () 可以选择指定将传递给使用设备的附加元数据。 元数据必须小于创建时已知的最大值。
Dequeue () 可以选择性地将缓冲区和指针传递给缓冲区的大小。 队列使用来自相应排队调用的元数据填充缓冲区。
克隆
每个 ISurfaceQueue 对象都求解单向同步。 我们假设绝大多数使用此 API 的应用程序都将使用封闭系统。 具有两个设备来回发送图面的最简单封闭系统需要两个队列。 ISurfaceQueue 对象具有 Clone () 方法,可以创建多个队列,这些队列都是同一较大管道的一部分。
Clone 从现有对象创建新的 ISurfaceQueue 对象,并在它们之间共享所有打开的资源。 生成的 对象具有与源队列完全相同的图面。 克隆的队列可以具有彼此不同的元数据大小。
表面
ISurfaceQueue 负责创建和管理其表面。 将任意图面排队无效。 此外,一个表面应只有一个活动的“所有者”。它应位于特定队列中,或由特定设备使用。 将它放在多个队列上或设备在排队后继续使用 Surface 是无效的。

API 详细信息

IsurfaceQueue

队列负责创建和维护共享资源。 它还提供使用克隆链接多个队列的功能。 队列具有打开生成设备和使用设备的方法。 每次只能打开其中一个。

队列公开以下 API:

API 说明
CreateSurfaceQueue (“根”队列) 创建 ISurfaceQueue 对象。
ISurfaceQueue::OpenConsumer 返回一个接口,供使用设备取消排队。
ISurfaceQueue::OpenProducer 返回要排队的生成设备的接口。
ISurfaceQueue::Clone 创建与根队列对象共享图面的 ISurfaceQueue 对象。

 

CreateSurfaceQueue

typedef struct SURFACE_QUEUE_DESC {
  UINT            Width;
  UINT            Height;
  DXGI_FORMAT     Format;
  UINT            NumSurfaces;
  UINT            MetaDataSize;
  DWORD           Flags;
} SURFACE_QUEUE_DESC;

成员

宽度高度 共享图面的尺寸。 所有共享图面必须具有相同的尺寸。
格式 共享图面的格式。 所有共享图面必须具有相同的格式。 有效格式取决于将使用的设备,因为不同的设备对可以共享不同的格式类型。
NumSurfaces 属于队列的图面数。 这是一个固定数字。
MetaDataSize 元数据缓冲区的最大大小。
标志 用于控制队列行为的标志。 请参阅“备注”。

HRESULT CreateSurfaceQueue(
  [in]   SURFACE_QUEUE_DESC *pDesc,
  [in]   IUnknown *pDevice,
  [out]  IDXGIXSurfaceQueue **ppQueue
);

参数

pDesc [in] 要创建的共享图面队列的说明。

pDevice [in] 应用于创建共享表面的设备。 这是一个显式参数,因为 Windows Vista 中的某个功能。 对于在 Direct3D 9 和 Direct3D 10 之间共享的图面,必须使用 Direct3D 9 创建表面。

ppQueue [out] 返回时,包含指向 ISurfaceQueue 对象的指针。

返回值

如果 pDevice 无法共享资源,则此函数返回DXGI_ERROR_INVALID_CALL。 此函数创建资源。 如果失败,则返回错误。 如果成功,则返回S_OK。

备注

创建队列对象还会创建所有图面。 假定所有图面都是 2D 呈现目标,并将使用设置 (D3D10_BIND_RENDER_TARGET和D3D10_BIND_SHADER_RESOURCE标志或不同运行时) 的等效标志来创建。

开发人员可以指定一个标志,指示队列是否将由多个线程访问。 如果未 (标志 == 0) 设置任何 标志 ,则队列将由多个线程使用。 开发人员可以指定单线程访问,这会关闭同步代码,并为这些情况提供性能改进。 每个克隆的队列都有自己的标志,因此系统中的不同队列可能具有不同的同步控件。

打开生成者

HRESULT OpenProducer(
  [in]   IUnknown *pDevice,
  [out]  IDXGIXSurfaceProducer **ppProducer
);

参数

pDevice [in]

将图面排入队列的生成者设备会进入 Surface 队列。

ppProducer [out] 将对象返回到生成者接口。

返回值

如果设备无法共享图面,则 返回DXGI_ERROR_INVALID_CALL。

打开使用者

HRESULT OpenConsumer(
  [in]   IUnknown *pDevice,
  [out]  IDXGIXSurfaceConsumer **ppConsumer
);

参数
pDevice [in]
将图面从 Surface 队列中取消排队的使用者设备。 ppConsumer [out] 将对象返回到使用者接口。

返回值

如果设备无法共享图面,则 返回DXGI_ERROR_INVALID_CALL。

备注

此函数打开输入设备的队列中的所有图面并缓存它们。 对 Dequeue 的后续调用将直接转到缓存,无需每次重新打开图面。

克隆 IDXGIXSurfaceQueue

typedef struct SHARED_SURFACE_QUEUE_CLONE_DESC {
  UINT         MetaDataSize;
  DWORD        Flags;
} SHARED_SURFACE_QUEUE_CLONE_DESC;

成员MetaDataSizeFlags 的行为与用于 CreateSurfaceQueue 的行为相同。

HRESULT Clone(
  [in]   SHARED_SURFACE_QUEUE_CLONE_DESC *pDesc,
  [out]  IDXGIXSurfaceQueue **ppQueue
);

参数

pDesc [in] 一个结构,它提供要创建的 Clone 对象的说明。 应初始化此参数。
ppQueue [out] 返回初始化的对象。

备注

可以从任何现有队列对象克隆,即使它不是根目录。

IDXGIXSurfaceConsumer

HRESULT Dequeue(
  [in]      REFIID    id,
  [out]     void      **ppSurface,
  [in,out]  void      *pBuffer,
  [in,out]  UINT      *pBufferSize,
  [in]      DWORD     dwTimeout
);

参数
id [in]
使用设备的 2D 表面的 REFIID。

  • 对于 IDirect3DDevice9,REFIID 应__uuidof (IDirect3DTexture9) 。
  • 对于 ID3D10Device,REFIID 应__uuidof (ID3D10Texture2D) 。
  • 对于 ID3D11Device,REFIID 应__uuidof (ID3D11Texture2D) 。

ppSurface [out] 返回指向图面的指针。
pBuffer [in, out] 可选参数(如果不是 NULL),返回时包含传入相应排队调用的元数据。
pBufferSize [in, out] pBuffer 的大小(以字节为单位)。 返回 pBuffer 中返回的字节数。 如果排队调用未提供元数据, 则 pBuffer 设置为 0。
dwTimeout [in] 指定超时值。 有关更多详细信息,请参阅备注。

返回值

如果指定了超时值,并且该函数未在超时值之前返回,则此函数可以返回WAIT_TIMEOUT。 请参阅“备注”。 如果没有可用的图面,则函数返回并将 ppSurface 设置为 NULLpBufferSize 设置为 0,返回值为 0x80070120 (WIN32_TO_HRESULT (WAIT_TIMEOUT) ) 。

备注

如果队列为空,此 API 可能会阻止。 dwTimeout 参数的工作方式与 Windows 同步 API 相同,例如 WaitForSingleObject。 对于非阻塞行为,请使用超时 0。

ISurfaceProducer

此接口提供两种方法,使应用能够将图面排入队列。 将图面排入队列后,图面指针不再有效,使用不安全。 应用程序应对指针执行的唯一操作是释放它。

方法 说明
ISurfaceProducer::Enqueue 将图面排入队列对象。 此调用完成后,生成者将完成表面,并且该表面已准备好用于其他设备。
ISurfaceProducer::Flush 在应用程序应具有非阻塞行为时使用。 有关详细信息,请参阅“备注”。

 

排队

HRESULT Enqueue(
  [in]  IUnknown *pSurface,
  [in]  void *pBuffer,
  [in]  UINT BufferSize,
  [in]  DWORD Flags
);

参数
pSurface [in]
需要排队的生产设备表面。 此图面必须是同一队列网络中已取消排队的图面。 pBuffer [in] 可选参数,用于传入元数据。 它应指向将传递给取消排队调用的数据。
BufferSize [in] pBuffer 的大小(以字节为单位)。
标志 [in] 控制此函数行为的可选参数。 唯一的标志是SURFACE_QUEUE_FLAG_ DO_NOT_WAIT。 请参阅 Flush 的备注。 如果未传递标志 (标志 == 0) ,则使用默认阻止行为。

返回值

如果使用SURFACE_QUEUE_FLAG_DO_NOT_WAIT标志,此函数可以返回DXGI_ERROR_WAS_STILL_DRAWING。

备注

  • 此函数将图面置于队列中。 如果应用程序未指定SURFACE_QUEUE_FLAG_DO_NOT_WAIT,则此函数将阻止并执行 GPU-CPU 同步,以确保排队图面上的所有呈现都已完成。 如果此函数成功,将有一个图面可用于取消排队。 如果需要非阻止行为,请使用 DO_NOT_WAIT 标志。 有关详细信息,请参阅 Flush () 。
  • 根据 COM 引用计数规则,Dequeue 返回的图面将为 AddRef () ,因此应用程序无需执行此操作。 调用 Enqueue 后,应用程序必须释放图面,因为它们不再使用它。

刷新

HRESULT Flush(
  [in]  DWORD Flags,
  [out] UINT *nSurfaces
);

参数
Flags [in]
唯一的标志是SURFACE_QUEUE_FLAG_ DO_NOT_WAIT。 请参阅“备注”。 nSurfaces [out] 返回仍处于挂起状态且未刷新的图面数。

返回值

如果使用SURFACE_QUEUE_FLAG_DO_NOT_WAIT标志,此函数可以返回DXGI_ERROR_WAS_STILL_DRAWING。 如果成功刷新任何图面,此函数将返回S_OK。 仅当未刷新表面时,此函数才会返回DXGI_ERROR_WAS_STILL_DRAWING。 返回值和 nSurfaces 一起向应用程序指示已完成的工作以及是否有任何工作要做。

备注

仅当对排队的上一次调用使用了 DO_NOT_WAIT 标志时,刷新才有意义;否则,它将为 no-op。 如果对排队的调用使用了 DO_NOT_WAIT 标志,则排队将立即返回,并且无法保证 GPU-CPU 同步。 表面仍被视为排队,生产设备不能继续使用它,但它不能取消排队。 若要尝试提交图面以取消排队,必须调用 Flush。 刷新尝试提交当前排队的所有图面。 如果未向 Flush 传递任何标志,它将阻止并清除整个队列,使其中的所有图面都准备好取消排队。 如果使用DO_NOT_WAIT标志,则队列将检查图面,以查看其中是否有任何一个已准备就绪;此步骤是非阻塞性的。 已完成 GPU-CPU 同步的图面可供使用者设备使用。 仍处于挂起状态的图面将不受影响。 函数返回仍需要刷新的图面数。

注意

刷新不会中断队列语义。 API 保证先排队的图面将在稍后将图面排队之前提交,无论 GPU-CPU 同步何时发生。

 

Direct3D 9Ex 和 DXGI 互操作帮助程序:如何使用

我们预计大多数用例都涉及两个共享多个图面的设备。 由于这也是最简单的方案,因此本文详细介绍了如何使用 API 来实现此目标,讨论了非阻塞性变体,最后以有关为三个设备初始化的简短部分结束。

两个设备

使用此帮助程序的示例应用程序可以同时使用 Direct3D 9Ex 和 Direct3D 11。 应用程序可以使用这两台设备处理内容,并使用 Direct3D 9 呈现内容。 处理可能意味着呈现内容、解码视频、运行计算着色器等。 对于每个帧,应用程序将首先使用 Direct3D 11 进行处理,然后使用 Direct3D 9 进行处理,最后使用 Direct3D 9 进行处理。 此外,使用 Direct3D 11 进行处理将生成 Direct3D 9 需要使用的一些元数据。 本部分介绍对应于此序列的三个部分的帮助程序用法:初始化、主循环和清理。

初始化
初始化涉及以下步骤:

  1. 初始化两个设备。
  2. 创建根队列:m_11to9Queue。
  3. 从根队列克隆:m_9to11Queue。
  4. 在两个队列上调用 OpenProducer/OpenConsumer。

队列名称使用数字 9 和 11 来指示哪个 API 是生成者,哪个 API 是使用者: m_生成者使用者队列。 因此,m_11to9Queue指示 Direct3D 11 设备为其生成 Direct3D 9 设备使用的图面的队列。 同样,m_9to11Queue指示 Direct3D 9 为其生成 Direct3D 11 使用的图面的队列。
根队列最初已满,所有克隆的队列最初都是空的。 这应该不会是应用程序的问题,除了排队和取消排队的第一个周期以及元数据的可用性。 如果取消排队请求元数据,但未 (设置任何元数据,因为最初没有任何内容,或者排队未) 设置任何内容,则取消排队将看到未收到任何元数据。

  1. 初始化两个设备。

    m_pD3D9Device = InitializeD3D9ExDevice();
    m_pD3D11Device = InitializeD3D11Device();
    
  2. 创建根队列。
    此步骤还会创建图面。 大小和格式限制与创建任何共享资源相同。 元数据缓冲区的大小在创建时是固定的,在本例中,我们将仅传递 UINT。
    必须使用固定数量的图面创建队列。 性能因方案而异。 拥有多个图面会增加设备繁忙的可能性。 例如,如果只有一个图面,则两个设备之间不会并行化。 另一方面,增加图面数量会增加内存占用,这可能会降低性能。 此示例使用两个图面。

    SURFACE_QUEUE_DESC Desc;
    Desc.Width        = 640;
    Desc.Height       = 480;
    Desc.Format       = DXGI_FORMAT_R16G16B16A16_FLOAT;
    Desc.NumSurfaces  = 2;
    Desc.MetaDataSize = sizeof(UINT);
    Desc.Flags        = 0;
    
    CreateSurfaceQueue(&Desc, m_pD3D9Device, &m_11to9Queue);
    
  3. 克隆根队列。
    每个克隆的队列必须使用相同的图面,但可以有不同的元数据缓冲区大小和不同的标志。 在这种情况下,没有从 Direct3D 9 到 Direct3D 11 的元数据。

    SURFACE_QUEUE_CLONE_DESC Desc;
    Desc.MetaDataSize = 0;
    Desc.Flags        = 0;
    
    m_11to9Queue->Clone(&Desc, &m_9to11Queue);
    
  4. 打开“生成者”和“使用者设备”。
    应用程序必须在调用“排队”和“取消排队”之前执行此步骤。 打开生成者和使用者将返回包含排队/取消排队 API 的接口。

    // Open for m_p9to11Queue.
    m_p9to11Queue->OpenProducer(m_pD3D9Device, &m_pD3D9Producer);
    m_p9to11Queue->OpenConsumer(m_pD3D11Device, &m_pD3D11Consumer);
    
    // Open for m_p11to9Queue.
    m_p11to9Queue->OpenProducer(m_pD3D11Device, &m_pD3D11Producer);
    m_p11to9Queue->OpenConsumer(m_pD3D9Device, &m_pD3D9Consumer);
    

主循环
队列的使用是在经典生成者/使用者问题之后建模的。 从每个设备的角度考虑这一点。 每台设备必须执行以下步骤:取消排队以从其消耗队列中获取图面,在图面上进行处理,然后排入其生产队列。 对于 Direct3D 11 设备,Direct3D 9 的用法几乎相同。

// Direct3D 9 Device.
IDirect3DTexture9* pTexture9 = NULL;
REFIID             surfaceID9 = _uuidof(IDirect3DTexture9);
UINT               metaData;
UINT               metaDataSize;
while (!done)
{
    // Dequeue surface.
    m_pD3D9Consumer->Dequeue(surfaceID9, (void**)&pSurface9,
                             &metaData, &metaDataSize, INFINITE);

    // Process the surface.
    ProcessD3D9(pSurface9);

    // Present the surface using the meta data.
    PresentD3D9(pSurface9, metaData, metaDataSize);

    // Enqueue surface.
    m_pD3D9Producer->Enqueue(pSurface9, NULL, 0, 0);
}

清理
此步骤非常简单。 除了清理 Direct3D API 的常规步骤外,应用程序还必须释放返回 COM 接口。

m_pD3D9Producer->Release();
m_pD3D9Consumer->Release();
m_pD3D11Producer->Release();
m_pD3D11Consumer->Release();
m_p9to11Queue->Release();
m_p11to9Queue->Release();

非阻止使用

前面的示例适用于多线程用例,其中每个设备都有自己的线程。 该示例使用 API 的阻止版本:INFINITE 表示超时,没有标记可排队。 如果要以非阻止方式使用帮助程序,只需进行一些更改。 本部分介绍如何在一个线程上对两个设备进行非阻塞性使用。

初始化
初始化是相同的,但标志除外。 由于应用程序是单线程的,因此请使用该标志进行创建。 这会关闭某些同步代码,这可能会提高性能。

SURFACE_QUEUE_DESC Desc;
Desc.Width        = 640;
Desc.Height       = 480;
Desc.Format       = DXGI_FORMAT_R16G16B16A16_FLOAT;
Desc.NumSurfaces  = 2;
Desc.MetaDataSize = sizeof(UINT);
Desc.Flags        = SURFACE_QUEUE_FLAG_SINGLE_THREADED;

CreateSurfaceQueue(&Desc, m_pD3D9Device, &m_11to9Queue);
SURFACE_QUEUE_CLONE_DESC Desc;
Desc.MetaDataSize = 0;
Desc.Flags        = SURFACE_QUEUE_FLAG_SINGLE_THREADED;

m_11to9Queue->Clone(&Desc, &m_9to11Queue);

打开生成者和使用者设备与阻止示例中的相同。
使用队列
有多种方法可以采用具有各种性能特征的非阻塞方式使用队列。 下面的示例很简单,但由于旋转和轮询过多,性能不佳。 尽管存在这些问题,该示例还是演示了如何使用 帮助程序。 方法是不断处于循环中,并取消排队、处理、排队和刷新。 如果任何步骤因资源不可用而失败,应用程序只需再次尝试下一个循环。

// Direct3D 11 Device.
ID3D11Texture2D* pSurface11 = NULL;
REFIID           surfaceID11 = __uuidof(ID3D11Texture2D);
UINT             metaData;
while (!done)
{
    //
    // D3D11 Portion.
    //

    // Dequeue surface.
    hr = m_pD3D11Consumer->Dequeue(surfaceID11,
                                   (void**)&pSurface11,
                                   NULL, 0, 0);
    // Only continue if we got a surface.
    if (SUCCEEDED(hr))
    {
        // Process the surface and return some meta data.
        ProcessD3D11(pSurface11, &metaData);

        // Enqueue surface.
        m_pD3D11Producer->Enqueue(pSurface11, &metaData,
                                  sizeof(UINT),
                                  SURFACE_QUEUE_FLAG_DO_NOT_WAIT);
    }
    // Flush the queue to check if any surfaces completed.
    m_pD3D11Producer->Flush(NULL,SURFACE_QUEUE_FLAG_DO_NOT_WAIT);

    //
    // Do the same with the Direct3D 9 Device.
    //

    // Dequeue surface.
    hr = m_pD3D9Consumer->Dequeue(surfaceID9,
                                  (void**)&pSurface9,
                                  &metaData,
                                  &metaDataSize, 0);
    // Only continue if we got a surface.
    if (SUCCEEDED(hr)))
    {
        // Process the surface.
        ProcessD3D9(pSurface9);

        // Present the surface using the meta data.
        PresentD3D9(pSurface9, metaData, metaDataSize);

        // Enqueue surface.
        m_pD3D9Producer->Enqueue(pSurface9, NULL, 0,
                                 SURFACE_QUEUE_FLAG_DO_NOT_WAIT);
    }
    // Flush the queue to check if any surfaces completed.
    m_pD3D9Producer->Flush(NULL,SURFACE_QUEUE_FLAG_DO_NOT_WAIT);
}

更复杂的解决方案可以检查排队和刷新的返回值,以确定是否需要刷新。

三个设备

扩展前面的示例以涵盖多个设备非常简单。 以下代码执行初始化。 创建生成者/使用者对象后,使用它们的代码是相同的。 此示例有三个设备,因此有三个队列。 图面从 Direct3D 9 流向 Direct3D 10 到 Direct3D 11。

SURFACE_QUEUE_DESC Desc;
Desc.Width        = 640;
Desc.Height       = 480;
Desc.Format       = DXGI_FORMAT_R16G16B16A16_FLOAT;
Desc.NumSurfaces  = 2;
Desc.MetaDataSize = sizeof(UINT);
Desc.Flags        = 0;

SURFACE_QUEUE_CLONE_DESC Desc;
Desc.MetaDataSize = 0;
Desc.Flags        = 0;

CreateSurfaceQueue(&Desc, m_pD3D9Device, &m_11to9Queue);
m_11to9Queue->Clone(&Desc, &m_9to10Queue);
m_11to9Queue->Clone(&Desc, &m_10to11Queue);

如前所述,克隆的工作方式相同,无论克隆哪个队列。 例如,第二个 Clone 调用可能已关闭 m_9to10Queue 对象。

// Open for m_p9to10Queue.
m_p9to10Queue->OpenProducer(m_pD3D9Device, &m_pD3D9Producer);
m_p9to10Queue->OpenConsumer(m_pD3D10Device, &m_pD3D10Consumer);

// Open for m_p10to11Queue.
m_p10to11Queue->OpenProducer(m_pD3D10Device, &m_pD3D10Producer);
m_p10to11Queue->OpenConsumer(m_pD3D11Device, &m_pD3D11Consumer);

// Open for m_p11to9Queue.
m_p11to9Queue->OpenProducer(m_pD3D11Device, &m_pD3D11Producer);
m_p11to9Queue->OpenConsumer(m_pD3D9Device, &m_pD3D9Consumer);

结论

可以创建使用互操作性的解决方案,以利用多个 DirectX API 的强大功能。 Windows 图形 API 互操作性现在提供常见的 Surface 管理运行时 DXGI 1.1。 此运行时在新开发的 API(如 Direct3D 11、Direct3D 10.1 和 Direct2D)中启用同步图面共享支持。 新 API 与现有 API 之间的互操作性改进有助于应用程序迁移和向后兼容性。 Direct3D 9Ex 和 DXGI 1.1 使用者 API 可以互操作,如 MSDN 代码库中通过示例帮助程序代码提供的同步机制所示。