在 Intel® 处理器图形上共享 OpenCL™ 和 DirectX 11 之间的表面
本教程演示了如何在 Microsoft Windows 上,使用 OpenCL 中的表面共享扩展,在 Intel® 处理器图形上共享 OpenCL™ 和 DirectX 11 表面。
Intel® Developer Zone 提供跨平台应用程序开发工具和操作指南、平台和技术信息、代码示例以及同行专业知识,帮助开发人员创新并取得成功。加入我们的社区,了解物联网、Android*、Intel® RealSense™ 技术和Windows*,下载工具、获取开发套件、与志同道合的开发人员分享想法,并参与黑客马拉松、竞赛、路演和本地活动。
Content
- 引言
- 具有共享物理内存的 Intel® 处理器图形
- OpenCL 和 DirectX 11 之间的同步
- OpenCL 和 DirectX 11 表面共享概述
- OpenCL 和 DirectX 11 表面共享的详细信息
- 未来工作
- 表面共享示例
- 致谢
- 关于作者
- 参考文献
- 定义
介绍
本教程演示了如何在 Microsoft Windows 上的 Intel® 处理器图形上,使用 OpenCL 中的表面共享扩展来共享 OpenCL™ 和 DirectX* 11 表面。目标是提供对 OpenCL C 内核所实现的表达能力以及 DirectX11 API 的渲染能力的访问。一个可能的应用场景是实时计算机视觉应用,它在 OpenCL 中对图像运行特征检测器,然后使用 DirectX 11 将最终输出实时渲染到屏幕上,并清楚地标记出特征。另一个应用场景是使用 OpenCL 在渲染场景中的 3D 对象时,动态生成程序化纹理。最后,可以想象在通过 3D 流水线渲染场景后,使用 OpenCL 对图像进行后处理。在某些情况下,这对于颜色转换、重采样或执行压缩非常有用。
为了帮助您开始使用表面共享,我们将展示如何使用 OpenCL 更新通过 DirectX 11 创建的纹理。相同的过程适用于对顶点缓冲区或可能在非交互式离线图像处理流水线中使用的离屏帧缓冲区对象的更新。
表面共享扩展在 OpenCL 扩展规范中定义,字符串为 cl_khr_dx11_sharing
。我们还利用了上下文属性 CL_CONTEXT_INTEROP_USER_SYNC
,它在 Intel 处理器图形上受支持,并设置为默认值 CL_FALSE
。
我们偶尔使用 DX11 作为 DirectX 11 的简写。
动机
本教程将向您展示如何创建 OpenCL 和 DirectX 11 之间的共享表面。
关键要点
本教程向您展示了如何使用 cl_khr_dx11_sharing
扩展在 OpenCL 和 DirectX11 之间共享表面,并利用 Intel 处理器图形上的 CL_CONTEXT_INTEROP_USER_SYNC
。在创建 DX11 纹理描述时,将 D3D11_TEXTURE2D_DESC
对象中的 MiscFlags
字段设置为 D3D11_RESOURCE_MISC_SHARED
,以确保 OpenCL 和 DirectX 11 之间不发生复制。请注意,在使用带有 D3D11_CPU_ACCESS
标志创建资源时,不能使用 D3D11_RESOURCE_MISC_FLAG
。有关最后一点的更多详细信息,请参阅文章末尾的 MSDN 参考。
具有共享物理内存的 Intel® 处理器图形
Intel 处理器图形与 CPU 共享内存。图 1 显示了它们的关系。虽然此图中未显示,但存在多项增强内存子系统的架构功能。例如,缓存层次结构、采样器、原子操作支持以及读写队列都用于充分发挥内存子系统的性能。
OpenCL 和 DirectX 11 之间的同步
在 OpenCL 和 DirectX 11 之间共享表面时,重要的是要处理程序可能同时尝试更新同一表面或表面位置的可能性。有两种方法可以处理这种情况。CL_CONTEXT_INTEROP_USER_SYNC
标志用于确定开发人员是否负责处理 API 之间的同步。如果将此标志设置为 CL_FALSE
(在本示例中即为如此),则驱动程序将负责确保在 clEnqueueAcquireD3D11ObjectsKHR
之前发出的 DirectX 11 对表面的操作已将其结果写入内存,然后再由 OpenCL 操作该表面。同样,此标志可确保在允许任何 DX11 操作在 clEneuqueReleaseD3D11ObjectsKHR
之后操作该表面之前,OpenCL 操作已完成。此标志及其语义使得两个 API 之间的表面共享非常容易。
OpenCL 和 DirectX 11 表面共享概述
以下是示例代码执行表面共享逻辑的摘要。下一节将详细解释每个步骤。
初始化
- OpenCL
- 查询以确定是否支持
cl_khr_dx11_sharing
扩展;如果不支持则退出。 - 创建上下文,传递适当的设备选项。
- 在支持 OpenCL 和 DX11 之间共享的设备和上下文上创建队列。
- 查询以确定是否支持
- DirectX:创建将与 OpenCL 共享的 DX11 纹理;请务必设置 D3D 纹理描述符的 MiscFlags 字段。
- OpenCL:使用 2 中创建的 DX11 句柄,通过 OpenCL 扩展创建共享表面。
步骤 1 和 2 可以互换。步骤 3 必须在步骤 1 和 2 之后执行。
写入共享表面
- 锁定表面以供 OpenCL 独占访问。
- 通过 OpenCL C 内核写入表面。对于纹理数据,请务必使用图像读取和/或写入函数,并适当地传递图像。
- 解锁表面,以便 DirectX 现在可以读取或写入表面。
渲染循环
渲染循环使用简单的直通式可编程顶点和像素着色器,对两个屏幕定向三角形进行纹理映射,这些三角形构成一个四边形用于显示结果。该四边形仅使用屏幕的一部分来显示渲染背景的清晰颜色,这对于调试更复杂的场景很有用。
关闭
- 清理状态对象。
OpenCL 和 DirectX 11 表面共享的详细信息
初始化
1. OpenCL
a. 查询以确定是否支持 cl_khr_dx11_sharing
扩展;如果不支持则退出。
并非所有 OpenCL 实现都支持 OpenCL 和 DirectX 11 之间的表面共享,因此第一步是确定该扩展是否存在于系统中。我们遍历平台,查看支持表面共享的平台的扩展字符串。仔细阅读规范会发现这是一个平台扩展或设备扩展,因此我们实际上必须同时检查两者!稍后,当我们创建上下文时,我们将不得不查询以确定上下文中哪些设备可以与 DX11 上下文共享。
本示例仅支持 Intel 处理器图形,但将其范围扩展到其他 GPU 应该非常容易。我们要查找的扩展字符串是 cl_khr_dx11_sharing
。相关代码片段是
char extension_string[1024];
memset(extension_string, '', 1024);
status = clGetPlatformInfo( platforms[i],
CL_PLATFORM_EXTENSIONS,
sizeof(extension_string),
extension_string,
NULL);
char *extStringStart = NULL;
extStringStart = strstr(extension_string, "cl_khr_dx11_sharing");
if(extStringStart != 0){
printf("Platform does support cl_khr_dx11_sharing\n");
...
}
请注意,类似的序列也适用于设备查询,并且包含在示例代码中,以防您想修改代码以在其他平台上工作。
b. 创建上下文,传递适当的设备选项。
cl_context_properties cps[] = { CL_CONTEXT_PLATFORM, (cl_context_properties)g_platformToUse, CL_CONTEXT_D3D11_DEVICE_KHR, (intptr_t)g_pd3dDevice, CL_CONTEXT_INTEROP_USER_SYNC, CL_FALSE, 0 };
在支持 OpenCL 和 DX11 之间共享的设备和上下文上创建队列。请注意,在上下文中我们指定了 CL_CONTEXT_INTEROP_USER_SYNC
标志并将其设置为 CL_FALSE
,这是默认值。使用此标志来指定您将管理 OpenCL 和 DX11 之间的同步,还是让运行时处理它。
我们首先查询平台上满足我们标准的设备数量。请注意,因为这是一个扩展,并且我们已经验证了平台支持此扩展,所以我们必须为下一步使用的扩展函数创建一个指针。
clGetDeviceIDsFromD3D11KHR_fn ptrToFunction_clGetDeviceIDsFromD3D11KHR = NULL;
ptrToFunction_clGetDeviceIDsFromD3D11KHR = (clGetDeviceIDsFromD3D11KHR_fn) clGetExtensionFunctionAddressForPlatform(g_platformToUse, "clGetDeviceIDsFromD3D11KHR");
cl_uint numDevs = 0;
//careful with the g_pd3DDevice
status = ptrToFunction_clGetDeviceIDsFromD3D11KHR(g_platformToUse, CL_D3D11_DEVICE_KHR, (void *)g_pd3dDevice, CL_PREFERRED_DEVICES_FOR_D3D11_KHR, 0, NULL, &numDevs);
testStatus(status, "Failed on clGetDeviceIDsFromD3D11KHR");
利用此信息,我们创建一个仅包含我们请求共享的 DX11 上下文的设备。
cl_device_id *devID = NULL;
g_clDevices = (cl_device_id *)malloc(sizeof(cl_device_id) * numDevs);
ptrToFunction_clGetDeviceIDsFromD3D11KHR(g_platformToUse, CL_D3D11_DEVICE_KHR, (void *)g_pd3dDevice, CL_PREFERRED_DEVICES_FOR_D3D11_KHR, numDevs, g_clDevices, NULL);
testStatus(status, "Failed on clGetDeviceIDsFromD3D11KHR");
//create an OCL context from the device we are using as our DX11 rendering device
g_clContext = clCreateContext(cps, 1, g_clDevices, NULL, NULL, &status);
testStatus(status, "clCreateContext error");
最后,在此设备上为应用程序创建我们的命令队列。请注意,这侧重于让 Intel 处理器图形上的表面共享运行良好,因此我们将省略一些使平台可移植代码进行设备选择的复杂性。
//create an openCL commandqueue
g_clCommandQueue = clCreateCommandQueue(g_clContext, devID, 0, &status);
testStatus(status, "clCreateCommandQueue error");
2. DirectX:创建我们将与 OpenCL 共享的 DX11 纹理。在这里,我们使用 D3D11_RESOURCE_MISC_SHARED
标志来确保与 Intel 处理器图形创建最佳共享方案。
void CreateTextureDX11()
{
unsigned char *texture = NULL;
texture = (unsigned char *)malloc(sizeof(unsigned char) * NUM_IMAGE_CHANNELS * SHARED_IMAGE_HEIGHT * SHARED_IMAGE_WIDTH);
if(texture == nullptr)
{
printf("error creating texture\n");
}
for(unsigned int i=0;i<NUM_IMAGE_CHANNELS * SHARED_IMAGE_HEIGHT * SHARED_IMAGE_WIDTH;)
{
texture[i++] = 255;
texture[i++] = 0;
texture[i++] = 0;
texture[i++] = 255;
}
D3D11_TEXTURE2D_DESC desc;
ZeroMemory(&desc, sizeof(D3D11_TEXTURE2D_DESC));
desc.Width = SHARED_IMAGE_WIDTH;
desc.Height = SHARED_IMAGE_HEIGHT;
desc.MipLevels = 1;
desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; desc.SampleDesc.Count = 1;
desc.SampleDesc.Quality = 0;
desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
desc.CPUAccessFlags = 0;
if(g_UseD3D11_RESOURCE_MISC_SHAREDflag == true)
{
printf("Using the D3D11_RESOURCE_MISC_SHARED flag\n");
desc.MiscFlags = D3D11_RESOURCE_MISC_SHARED;
}
else
{
desc.MiscFlags = 0;
}
D3D11_SUBRESOURCE_DATA tbsd;
ZeroMemory(&tbsd, sizeof(D3D11_SUBRESOURCE_DATA));
tbsd.pSysMem = (void *)texture;
tbsd.SysMemPitch = SHARED_IMAGE_WIDTH * NUM_IMAGE_CHANNELS;
tbsd.SysMemSlicePitch = SHARED_IMAGE_WIDTH * SHARED_IMAGE_HEIGHT * NUM_IMAGE_CHANNELS;
g_pd3dDevice->CreateTexture2D(&desc, &tbsd, &g_pSharedDX11Texture2D);
//still need to bind
free(texture);
}
3. OpenCL:使用 2 中创建的 DX11 句柄,通过 OpenCL 扩展创建共享表面。
这是我们表面共享 API 的核心。只有在以下调用成功时,共享才能正常工作
int ShareDX11BufferWithCL()
{
int status = 0;
g_SharedRGBAimageCLMemObject = ptrToFunction_clCreateFromD3D11Texture2DKHR(g_clContext, CL_MEM_WRITE_ONLY, g_pSharedDX11Texture2D, 0, &status);
if(status == 0)
{
printf("Successfully shared!\n");
status = SUCCESS;
}
else
{
printf("Sharing failed\n");
status = FAIL;
}
return status;
}
步骤 1 和 2 可以互换。步骤 3 必须在步骤 1 和 2 之后执行。
写入共享表面
我们需要确保当 OpenCL 读取或写入表面时,DX11 不在使用该表面,反之亦然。为此,扩展支持用于获取和释放对表面的独占访问的函数。此处我们描述使用这些 API 的步骤。有两种方法可以做到这一点。首先,我们可以在代码中使用同步对象。第二种方法(在本示例中我们采用的方式)是利用使用 CL_CONTEXT_INTEROP_USER_SYNC
设置为 CL_FALSE
标志时的行为。当设置此标志时,同步在 clEnqueueAcquireD3D11ObjectsKHR()
和 clENqueueReleaseD3D11ObjectsKHR()
API 调用端是隐式的。
4. 锁定表面以供 OpenCL 独占访问。
status = ptrToFunction_clEnqueueAcquireD3D11ObjectsKHR(g_clCommandQueue, 1, &g_SharedRGBAimageCLMemObject, 0, 0, 0);
5. 通过 OpenCL C 内核写入表面。对于纹理数据,请务必使用图像读取和/或写入函数,并适当地传递图像。内核只是写入纹理的 texel 子集。
kernel void drawBox(__write_only image2d_t output, float fDimmerSwitch)
{
int x = get_global_id(0);
int y = get_global_id(1);
int xMin = 0, xMax = 1, yMin = 0, yMax = 1;
if((x >= xMin) && (x <= xMax) && (y >= yMin) && (y <= yMax))
{
write_imagef(output, (int2)(x, y),
(float4)(0.f, 0.f, fDimmerSwitch, 1.f));
}
}
6. 解锁表面,以便 OpenCL 现在可以读取或写入表面。
status = ptrToFunction_clEnqueueReleaseD3D11ObjectsKHR(g_clCommandQueue, 1, &g_SharedRGBAimageCLMemObject, 0, NULL, NULL);
渲染循环
渲染循环使用简单的直通式可编程顶点和像素着色器,对两个屏幕定向三角形进行纹理映射,以显示结果。
关闭
1. 清理状态对象。
此示例在清理方面不需要太多工作。一个对象是共享表面的 cl_mem
对象。在 DX11 端,我们必须释放我们创建用于共享的纹理、纹理的采样器以及纹理上的视图。
未来工作
本教程涵盖了表面共享的基础知识。有更多时间的话,我们希望将教程的范围扩展到涵盖此处提到的其他用例。
在 OpenCL 和 DirectX 11 之间共享显式同步事件
除了本文中的隐式同步外,OpenCL 和 DirectX 11 还可以使用设置为 CL_TRUE
的 CL_CONTEXT_INTEROP_USER_SYNC
标志,并通过 Windows 同步对象显式处理同步。比较两者的性能会很有趣。我们与之交谈过的驱动程序架构师发现两者的差异很小甚至没有,因此这是目前的建议,但进行验证将很有启发性。
共享帧缓冲区、深度、模板和 MSAA 表面
OpenCL 和 DirectX 11 在许多有用的表面方面缺乏表面共享功能。如果它们能像 OpenGL 和 DirectX 11 之间的表面共享那样得到支持,将会很有用。此外,虽然扩展和文档对此没有明确说明,但本扩展仅要求支持非 mip 贴图表面共享。
双缓冲
我们可以探讨使用双缓冲方案带来的复杂性和性能方面的权衡。在本教程中,我们侧重于功能和表面共享的基础知识。
当不支持表面共享时该怎么办?
Maxim Shevtsov 在参考文献中引用了一篇关于 OpenCL 和 OpenGL 之间必须发生复制的情况的文章。虽然这些建议侧重于 OpenGL,但许多建议也适用于与 DirectX 11 的共享。
表面共享示例
依赖项
表面共享示例具有以下依赖项:
- Windows 8 或更高版本的 SDK;请注意,DirectX SDK 已被弃用,Windows SDK 用于 Direct3D* 开发。
- Microsoft Visual Studio* 2012 或 2013
- 利用 SDK 中特定于 DirectX 的部分,包括 d3dcompiler_46.dll。
- D3dcompiler_46.dll 在解决方案(而不是项目)级别复制到可执行文件所在的相应 Debug 或 Release 目录。还有其他方法可以处理此依赖项。
- 此示例应该适用于其他版本的 Visual Studio,但尚未经过测试。
- Intel® INDE(Intel® OpenCL™ Code Builder 现在是其中的一部分)
- 使用
cl.h, cl_d3d11.h
和opencl.lib
文件。
- 使用
示例中使用了以下设置,但您的可能略有不同:
- 将
d3dcompiler_46.dll
复制到可执行文件将在解决方案(而不是项目)级别构建到的调试器发布目录。 - 将
cl.h
和cl_d3d11.h
的位置添加到您的 include 路径。例如,C:\Program Files (x86)\Intel\OpenCL SDK\3.0\include\CL
- 将 OpenCL 库的位置添加到您的库路径:例如,
C:\Program Files (x86)\Intel\OpenCL SDK\3.0\lib\x86
- 将 OpenCL.lib 添加到您的静态链接库集中。
示例文件和目录结构
解决方案位于 CL_20_DX11_surface_sharing
目录中。同名的目录是项目和源文件所在的目录。此示例代码侧重于演示 OpenCL 和 DirectX 11 之间的表面共享,不旨在提供产品质量的实现。
文件组织如下:
Main.cpp
– 大部分是窗口代码,窗口消息泵循环,将事件分派给相应的 OpenCL 和 DX11 函数,以及一个键盘处理程序。Common_CL_20_DX11.h
– 用于 OpenCL 和 DX11 的标志。DX11.h, DX11.cpp
– 特定于 DX11 的函数,包括初始化、关闭和渲染。OCL.h, OCL.cpp
– 特定于 OpenCL 的函数。OpenCLRGBAFile.cl
– OpenCL 内核的源代码。PixelShader.hlsl, VertexShader.hlsl
– DX11 的简单顶点和像素着色器。
构建和运行示例
通过从主菜单中选择构建->构建解决方案来构建此示例代码。所有可执行文件都应生成。您可以直接在 Visual Studio 中运行它们,或者转到与 CL_20_DX11_surface_sharing
解决方案文件位于同一位置的 Debug 和/或 Release 目录。
要运行示例,请在 Visual Studio IDE 中按 F5。三个相关的内核和着色器文件是 OpenCLRGBAFile.cl, PixelShader.hlsl, and VertexShader.hlsl
。
了解更多
您可以在 OpenCL 和 DirectX 11 之间共享其他类型的缓冲区。此外,还有其他同步机制可供利用。这些在 OpenCL 扩展规范中进行了详细说明,您可以参考其中获取更多详细信息。
致谢
感谢 Murali Sundarasen、Aaron Kunze、Allen Hux、Pavan Lanka、Maxim Shevtsov、Michal Mrozek、Piotr Uminski、Stephen Junkins、Dan Petre 和 Ben Ashbaugh。他们都提供了技术讨论、澄清或审阅。
关于作者
Adam Lake 在 Visual Products Group 担任高级图形架构师和 Khronos OpenCL 标准委员会的投票代表。他从事 GPGPU 编程已有 12 年以上。在此之前,他曾在 VR、3D、图形和流编程语言编译器领域工作。
Robert Ioffe 是 Intel 软件与解决方案事业部的一名技术咨询工程师。他深度参与了 Khronos 标准工作,专注于最新功能的原型制作,并确保它们能在 Intel 架构上良好运行。
参考文献
- OpenCL 1.2 规范:https://www.khronos.org/registry/cl/
- OpenCL 2.0 规范,包含三个卷:OpenCL C 语言规范、OpenCL 运行时 API 和 OpenCL 扩展:https://www.khronos.org/registry/cl/
- Stephen Junkins 的白皮书:《Intel® 处理器图形 Gen 7.5 的计算架构》:https://software.intel.com/sites/default/files/managed/f3/13/Compute_Architecture_of_Intel_Processor_Graphics_Gen7dot5_Aug2014.pdf。对于任何在 Intel® 处理器图形平台上使用 OpenCL 的人来说,这都是必读的。
- Adam Lake 关于在 Intel 处理器图形上最小化缓冲区复制的教程:https://software.intel.com/en-us/articles/getting-the-most-from-opencl-12-how-to-increase-performance-by-minimizing-buffer-copies-on-intel-processor-graphics
- Windows 中的同步对象:http://msdn.microsoft.com/en-us/library/windows/desktop/ms686364(v=vs.85).aspx
D3D11_RESOURCE_MISC_FLAG
与D3D11_CPU_ACCESS
标志使用的限制:http://msdn.microsoft.com/en-us/library/windows/desktop/ff476203(v=vs.85).aspx- 表面共享支持的表面类型限制:http://msdn.microsoft.com/en-us/library/windows/desktop/hh404562(v=vs.85).aspx#direct3d_device_sharing
定义
下面包含了本教程中使用的一些术语的定义。有关更多详细信息,请参阅参考文献。
- 缓冲区:OpenCL 区分缓冲区和图像。OpenCL 缓冲区在内存中是线性布局的——可以认为缓冲区是一个数组。
- 纹理:指以平铺格式布局的数据缓冲区,并通过 OpenGL 或 DX11 的片上采样器读取。这种内存布局通过纹理采样器提高了性能,这些采样器通过预定义的滤波核对从内存读取的输入像素进行滤波。
- 表面:指缓冲区、纹理或图像。它是内存中数据的通用术语,其布局可以是平铺的或线性的。在某些情况下,表面还包含额外的数据,如维度、高度、宽度和数据布局属性。这些属性通过 API(OpenCL、OpenGL、DirectX 等)进行管理。
- 采样器:用于在 OpenCL 中读取图像,在 OpenGL 或 DX11 中读取纹理。采样器利用内部缓存和图像或纹理在内存中的平铺布局,在滤波时提高性能。采样器包含缓存和逻辑,可以同时从多个 texel(可能还有 mipmap 级别)进行采样,并为单个请求输出一个 texel 值。
- 图像:指以平铺格式布局的数据缓冲区,并通过 OpenCL 的片上采样器读取。它们相当于 OpenGL 或 DX11 纹理。可以共享或支持的图像格式和纹理格式取决于具体实现。
- 表面共享:跨 API 表面共享的简写,用于指在一个 API 中创建表面并在另一个 API 中使用数据。其动机是最大限度地减少同一表面的多个副本的创建,但这并不严格如此,除非我们遵循一组特定于设备的限制。本教程描述了 Intel 处理器图形的这些限制。
- 纹理映射:内存中的像素与图形流水线中的多边形之间的关联。在本示例中,我们将 OpenGL 或 DX11 纹理映射到两个屏幕定向多边形上进行显示。
- 共享物理内存:主机和设备共享相同的物理 DRAM。这与共享虚拟内存不同,共享虚拟内存是指主机和设备共享相同的虚拟地址,而这也不是本文的重点。支持表面共享的关键硬件特性是 CPU 和 GPU 共享物理内存的事实。共享物理内存和共享虚拟内存并非互斥。设备可能无法看到整个物理内存来支持共享物理内存。
- Intel® 处理器图形:指当前的 Intel 图形解决方案。集成在 SoC 中的 Intel GPU 的产品名称包括 Intel® Iris™ Graphics、Intel® Iris™ Pro Graphics 或 Intel® HD Graphics,具体取决于具体的 SoC。有关其他硬件架构的详细信息,请参阅本文档参考文献部分列出的《Intel® 处理器图形 Gen 7.5 的计算架构》文档或 http://ark.intel.com/。
法律信息
* 其他名称和品牌可能被声明为他人财产。
OpenCL 和 OpenCL 徽标是 Apple Inc. 的商标,经 Khronos 许可使用。
版权所有 © 2015,Intel 公司。保留所有权利。