Windows图形 API 之间的图面共享

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

注意

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

 

本主题包含以下各节:

简介

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

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

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

Direct3D 9Ex 运行时支持未同步的表面共享。 基于 DXVA 的视频应用程序可以使用 Direct3D 9Ex 和 DXGI 互操作性帮助程序实现 Direct3D 9Ex 的 DXVA 与 Direct3D 11 的计算着色器的 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 的图面共享互操作性。 从 Windows 7 开始,从具有 7IP 的 Windows Vista SP2 开始,新的 API 和相关运行时包括 Direct2D 和相关技术:Direct3D 11 和 DXGI 1.1。 Windows 7 中,GDI 性能也得到了改进。 Direct3D 10.1 在 Windows Vista SP1 中引入。 下图显示了 API 之间的互操作性支持。

diagram of interoperability support between windows graphics apis

在此关系图中,箭头显示可由连接的 API 访问相同图面的互操作性方案。 蓝色箭头表示Windows Vista 中引入的互操作性机制。 绿色箭头表示对新 API 或改进的互操作性支持,这些 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 设备共享或同步共享图面相互互操作。

Direct3D 10.1 设备共享与 Direct2D

Direct2D 和 Direct3D 10.1 之间的设备共享允许应用程序使用同一个基础 Direct3D 设备对象无缝高效地呈现到同一 DXGI 1.1 图面上。 Direct2D 提供使用现有 Direct3D 10.1 设备调用 Direct2D API 的功能,并利用 Direct2D 基于 Direct3D 10.1 和 DXGI 1.1 运行时构建的事实。 以下代码片段演示 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 设备关联的 ID2D1RenderTargets。
  • 为了获得最佳性能,请始终至少保留一个资源,例如与设备关联的纹理或图面。

设备共享适用于由 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,它提供功能,以便通过两台或更多个 Direct3D 设备将读取和写入同步到同一个视频内存图面 (DXGISurface1) 。 使用同步共享图面的呈现设备可以是 Direct3D 10.1 或 Direct3D 11 设备,每个设备在同一进程或跨进程中运行。

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

在 Direct3D 10.1 及更高版本中,若要使用 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,这些 API 均已扩展D3D10_RESOURCE_MISC_FLAG参数以支持新标志。

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

如果使用D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX标志集调用任何列出的函数,则可以针对 IDXGIKeyedMutex 接口查询返回的接口,该接口实现 AcquireSync 和 ReleaseSync API 以同步对图面的访问。 使用 OpenSharedResource () 创建图面的设备以及使用 OpenSharedResource 打开图面的任何其他设备都需要调用 IDXGIKeyedMutex::AcquireSync,然后才能将任何呈现命令发送到图面,并在呈现完成后调用 IDXGIKeyedMutex::ReleaseSync。
WARP 和 REF 设备不支持共享资源。 尝试在 WARP 或 REF 设备上使用此标志创建资源将导致创建方法返回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 包括图面共享的概念,以允许其他 API 从共享图面读取。 若要共享对 Direct3D 9Ex 共享图面的读取和写入,必须将手动同步添加到应用程序本身。

Direct3D 9Ex 共享 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 保证任何已取消排队的图面都已准备好使用。
元数据
该 API 支持将元数据与共享图面相关联。
排队 () 可以选择指定将传递给使用设备的其他元数据。 元数据在创建时必须小于最大已知值。
Dequeue () 可以选择传递缓冲区和指向缓冲区大小的指针。 队列使用相应排队调用中的元数据填充缓冲区。
克隆
每个 ISurfaceQueue 对象都解决了单向同步。 我们假设使用此 API 的绝大多数应用程序将使用封闭的系统。 使用两个设备来回发送图面的最简单封闭系统需要两个队列。 ISurfaceQueue 对象具有 Clone () 方法,可用于创建属于同一较大管道的多个队列。
克隆从现有对象创建新的 ISurfaceQueue 对象,并共享它们之间打开的所有资源。 生成的对象具有与源队列完全相同的图面。 克隆的队列可以具有彼此不同的元数据大小。
表面
ISurfaceQueue 负责创建和管理其表面。 排队任意图面无效。 此外,图面应只有一个活动的“所有者”。它应位于特定队列中,或者由特定设备使用。 在排队后,在多个队列上或让设备继续使用图面是无效的。

API 详细信息

IsurfaceQueue

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

队列公开以下 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]

排队到图面队列上的生成者设备。

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

返回值

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

打开使用者

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

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

返回值

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

注释

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

克隆 IDXGIXSurfaceQueue

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

MembersMetaDataSizeFlags 的行为与 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 () ,因此应用程序不需要执行此操作。 调用排队后,应用程序必须释放图面,因为它们不再使用它。

刷新

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标志时,刷新才有意义;否则,这将是一个不操作。 如果调用排队使用了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 存在的一些元数据。 本部分介绍三个对应于此序列的帮助程序用法:初始化、主Loop和清理。

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

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

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

  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. 打开生成者和使用者设备。
    应用程序必须在调用 Enqueue 和 Dequeue 之前执行此步骤。 打开生成者和使用者返回包含排队/取消排队 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);
    

主Loop
队列的使用是在经典生成者/使用者问题之后建模的。 从每个设备的角度来看,请考虑这一点。 每个设备必须执行以下步骤:取消排队才能从其消耗队列中获取图面,在表面上处理,然后排队到其生成队列中。 对于 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 互操作性现在提供了一个常见的表面管理运行时 DXGI 1.1。 此运行时支持新开发的 API(例如 Direct3D 11、Direct3D 10.1 和 Direct2D)中的同步图面共享支持。 新 API 与现有 API 之间的互操作性改进有助于应用程序迁移和向后兼容性。 Direct3D 9Ex 和 DXGI 1.1 使用者 API 可以互操作,如 MSDN 代码库中的示例帮助程序代码提供的同步机制所示。