Windows 图形 API 之间的图面共享

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

注意

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

 

本主题包含以下各节:

介绍

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

在 Windows 7(以及包含 Windows 7 Interop Pack 的 Windows Vista SP2,Vista 7IP)中,图形呈现 API 包括 Direct3D 11、Direct2D、Direct3D 10.1、Direct3D 10.0、Direct3D 9Ex、Direct3D 9c 和早期的 Direct3D API,以及 GDI 和 GDI+。 Windows 图像处理组件 (WIC) 和 DirectWrite 是用于图像处理的相关技术,而 Direct2D 则用于文本呈现。 基于 Direct3D 9c 和 Direct3D 9Ex 的 DirectX 视频加速 API (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 图形基础结构 (DXGI) 1.1,它可以实现完全支持这些 API 之间的互操作性的同步共享图面。 通过从 DXGI 1.1 图面获取 GDI 设备上下文,基于 DXGI 1.1 的 API 可与 GDI 以及 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 互操作性概述

Windows 图形 API 的图面共享互操作性可以通过 API 到 API 方案和相应的互操作性功能来描述。 从 Windows 7 和包含 7IP 的 Windows Vista SP2 开始,新的 API 和相关运行时包括 Direct2D 和相关技术:Direct3D 11 和 DXGI 1.1。 Windows 7 中的 GDI 性能也得到了提升。 Windows Vista SP1 中已经引入了 Direct3D 10.1。 下图显示了 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,它提供了通过两个或多个 Direct3D 设备同步读取和写入同一视频内存图面 (DXGISurface1) 的功能。 使用同步共享图面的呈现设备可以是 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 适配器对象来创建 Direct3D 设备,该对象是从 DXGI 1.1 工厂对象中枚举出来的。 调用 CreateDXGIFactory1 以创建 IDXGIFactory1 对象,并调用 EnumAdapters1 枚举 IDXGIAdapter1 对象。 IDXGIAdapter1 对象需要作为 D3D10CreateDevice 或 D3D10CreateDeviceAndSwapChain 调用的一部分传入。 有关 DXGI 1.1 API 的详细信息,请参阅 DXGI 编程指南

API

D3D10_RESOURCE_MISC_SHARED_KEYEDMUTEX
在创建同步共享资源时,在 D3D10_RESOURCE_MISC_FLAG 中设置 D3D10_RESOURCE_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 同步所创建的资源。 以下所有使用 D3D10_RESOURCE_MISC_FLAG 参数的资源创建 Direct3D 10.1 API 已被扩展,以便支持新标志。

  • 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 设备上创建带有此标记的资源时,创建方法将返回 E_OUTOFMEMORY 错误代码。
IDXGIKEYEDMUTEX INTERFACE
DXGI 1.1 中的一个新接口 IDXGIKeyedMutex 表示一个键式互斥体,它允许独占访问多个设备使用的共享资源。 有关此接口及其两种方法 AcquireSync 和 ReleaseSync 的参考文档,请参阅 IDXGIKeyedMutex

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

下面的示例展示了两个 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);
...

请注意,现实世界中的应用程序可能总是呈现到一个中间图面,然后将其复制到共享图面,以防止任何一个设备等待共享图面的另一个设备。

将同步共享图面与 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 Shared Surfaces Plus 手动同步帮助程序

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

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

帮助程序的高级概述

ISurfaceQueue 对象是使用共享图面的构建基块。 它是通过一个初始化的 Direct3D 设备和一个创建固定数量共享图面的说明创建的。 队列对象负责管理资源的创建和代码的打开。 图面的数量和类型是固定的;一旦创建了图面,应用程序就不能再添加或删除它们。

ISurfaceQueue 对象的每个实例都提供了一种单行道,可用于将图面从生成设备发送到消耗设备。 多个这样的单行道可用于在特定应用程序的设备之间启用图面共享方案。

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

API 详细信息

IsurfaceQueue

队列负责创建和维护共享资源。 它还提供了使用克隆功能链接多个队列的功能。 队列具有打开生成设备和消耗设备的方法。 任何时候只能打开一个。

队列公开以下 API:

API 说明
CreateSurfaceQueue 创建一个 ISurfaceQueue 对象(“root”队列)。
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;

成员

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

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

Parameters

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 标志(或不同运行时的相应标志)。

开发人员可以指定一个标志,用以指明队列是否会被多个线程访问。 如果未设置标记 (Flags == 0),则队列将被多个线程使用。 开发人员可以指定单线程访问,从而关闭同步代码,提高在这些情况下的性能。 每个克隆队列都有自己的标记,因此系统中的不同队列有可能具有不同的同步控制。

打开生成者

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

Parameters

pDevice [in]

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

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

返回值

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

打开消耗者

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

Parameters
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;

Members MetaDataSizeFlags 的行为与 CreateSurfaceQueue 的行为相同。

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

Parameters

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

备注

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

IDXGIXSurfaceConsumer

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

Parameters
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
);

Parameters
pSurface [in]
需要排队的生成设备的图面。 此图面必须是来自同一队列网络的已取消队列的图面。 pBuffer [in] 一个用于传入元数据的可选参数。 它应指向将传递给取消排队调用的数据。
BufferSize [in] pBuffer 的大小,以字节为单位。
Flags [in] 一个可选参数,用于控制该函数的行为。 唯一的标志是 SURFACE_QUEUE_FLAG_DO_NOT_WAIT。 请参阅“刷新”的备注。 如果没有传递标志 (Flags == 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
);

Parameters
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 标志,Flush 才有意义;否则,它将不起作用。 如果调用排队时使用了 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 是生成者,哪个是消耗者:m_producertoconsumerQueue。 因此,m_11to9Queue 表示一个队列,Direct3D 11 设备为该队列生成图面,而 Direct3D 9 设备则消耗这些图面。 同样,m_9to11Queue 表示一个队列,Direct3D 9 为该队列生成图面,而 Direct3D 11 则消耗这些图面。
根队列最初已满,所有克隆的队列最初均为空。 除了第一周期的 Enqueues 和 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);
    

主循环
队列的使用以经典的生成者/消耗者问题为模型。 从每个设备的角度来考虑这一问题。 每个设备都必须执行以下步骤:取消排队才能从其消耗队列中获取图面,在图面上进行处理,然后排队到其生成队列。 对于 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);

如前所述,无论克隆哪个队列,克隆的工作方式都是一样的。 例如,第二个克隆调用可能来自 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 代码库 Microsoft 示例存储库中的旧版 Win32 示例应用程序中提供的同步机制所示。