项目奥斯丁第4部分6:C++加速

大家好,我是Amit Agarwal,是C++ AMP团队的开发人员。 C++ AMP VisualStudio 2012中提供了一种新技术,它使C++开发人员能够从同一C++源和用于编程CPU的VS IDE中充分利用它们的应用程序中可用的异构计算资源。 奥斯丁 是一款适用于windows8的数字笔记应用程序,以及与之相关的视觉上引人入胜的3D效果 奥斯汀应用程序中的翻页 是用C++来驱动的。

null

页面曲面被建模为一个三维网格,由一组三角形组成,每个三角形由其顶点在三维中的位置定义。翻页动画涉及计算密集型的卷页算法,包括两个主要步骤:

  1. 页面表面网格的变形,用于计算每个帧的顶点位置。
  2. 计算顶点法线,随后用于对页面曲面应用着色。

这两个步骤本质上是高度数据并行的,并且可以利用C++的AMP来加速利用现代GPU的浮点运算能力,从而提高翻页动画的整体帧速率。页面变形步骤目前在CPU上实现;使用C++ AMP加速这一步的努力正在进行中,我们将在以后的帖子中讨论它。

在这篇博客文章中,我们将讨论使用C++ AMP加速顶点法线的计算,这已经是奥斯丁的当前版本的一部分。但是在深入研究细节之前,这里有一张图片描述了奥斯丁的页面翻转动画,它是用C++的AMP加速的。

image

介绍

顶点法线 通常计算为 标准化 包含顶点的所有三角形的曲面法线的平均值。使用这种方法,计算CPU上的顶点法线只需迭代描述页面表面的所有三角形,并在各个顶点的法线中累积三角形法线。

在伪代码中:

for each triangle

{

    Position vertex1Pos = triangle.vertex1.position;

    Position vertex2Pos = triangle.vertex2.position;

    Position vertex3Pos = triangle.vertex3.position;

 

    Normal triangleNormal = cross(vertex2Pos – vertex1Pos, vertex3Pos – vertex1Pos);

 

    triangleNormal.normalize();

 

    vertex1.normal += triangleNormal;

    vertex2.normal += triangleNormal;

    vertex3.normal += triangleNormal;

}

用C++ AMP加速顶点法线计算

如前所述,顶点法线的计算由于其数据并行和计算密集性而非常适合于C++加速。

一个简单的出发点是取代 对于每个三角形 使用C++ AMP实现CPU的循环 每个平行u 打电话。的计算域 每个平行u call是描述页面的三角形数,指定为 程度 争论。简单地说,这可以被认为是在服务器上启动尽可能多的线程 加速器 作为每个线程负责计算三角形的曲面法线并累积三角形顶点法线中的值的三角形数(通常为几千)。但是,顶点是多个三角形的一部分 每个平行u 线程并发执行时,多个线程可能会尝试将各自的三角形法线累积到同一顶点,从而导致争用。解决这个问题的一种方法是使用 C++ AMP原子操作 . 不幸的是,原子操作在GPU加速器上是昂贵的,并且会严重损害内核的性能。

更好的替代方法是将顶点法线的计算分为两个步骤:

  1. 计算每个三角形的法线。
  2. 对于每个顶点,累积该顶点所属的所有三角形的法线,并在规格化累积值后更新顶点法线。

这种方法包括两个方面 每个平行u 调用。第一个启动尽可能多的GPU加速器线程有三角形,每个线程计算一个三角形的法线从三角形的顶点的位置。三角形法向值存储在临时中间值中 并发::数组视图 随后在第二阶段中用于累积每个顶点的法线。

在伪代码中:

parallel_for_each(extent<1>(triangleCount), [=](index<1> idx) restrict(amp) 

{

    Position vertex1Pos = triangle.vertex1.position;

    Position vertex2Pos = triangle.vertex2.position;

    Position vertex3Pos = triangle.vertex3.position;

 

    Normal triangleNormal = cross(vertex2Pos – vertex1Pos, vertex3Pos – vertex1Pos);

 

    triangleNormal.normalize();

 

    tempTriangleNormals[idx] = triangleNormal;

});

第二个 每个平行u 启动与页面上顶点数量相同的线程,每个线程从临时线程中累积顶点所属三角形的法线 阵列视图 用于存储三角形法线的第一个 每个平行u . 此后,累积的顶点法线被规格化并存储在顶点法线中 阵列视图 .

在伪代码中:

parallel_for_each(extent<2>(vertexCountY, vertexCountX), [=](index<2> idx) restrict(amp)

{

    // First get the existing vertex normal value

    Normal vertexNormal = vertexNormalView(idx);

 

    // Each vertex is part of 4 quads with each quad comprising of 2 triangles

    // Based on the vertex position, it is determined which triangles the vertex is

    // part of and whether that triangle's normal should be accumulated in the vertex normal

    if (isVertexOfQuad1Triangle1) {

        vertexNormal += tempTriangleNormals(quad1Triangle1_index);

    }

 

    if (isVertexOfQuad1Triangle2) {

        vertexNormal += tempTriangleNormals(quad1Triangle2_index);

    }

 

    if (isVertexOfQuad2Triangle1) {

        vertexNormal += tempTriangleNormals(quad2Triangle1_index);

    }

 

    if (isVertexOfQuad2Triangle2) {

        vertexNormal += tempTriangleNormals(quad2Triangle2_index);

    }

 

    ...

 

    vertexNormal.normalize();

 

    vertexNormalView(idx) = vertexNormal;

});

最后,通过读取顶点法线的内容来更新DirectX顶点缓冲区中每个顶点的法线分量 阵列视图 在CPU上。DirectX顶点缓冲区现在可以进行渲染,顶点法线值用于对页面进行着色。

奥斯汀的源代码是免费的 可供下载 在CodePlex上 . 顶点法线计算中的C++ +AMP加速位位于类中。 纸张纸张节点 在源文件中 纸张纸张节点.hpp– 核心C++ AMP加速代码在函数中 更新NORMALSAMP 一些C++特定的初始化代码包含在函数中 已初始化。

C++ AMP性能考虑

查看了使用C++ AMP加速顶点正常计算的高级方法,现在让我们深入深入地研究从性能角度来看重要的C++ AMP实现的细节。

数组结构

首先,我们来讨论在C++中存取的输入和输出数据的布局。 每个平行u 果仁。输入第一个 每个平行u 调用是一种 阵列视图 对于顶点位置,每个位置由三个单精度浮点值(x、y、z分量)组成。输出是三角形法线的数组视图,其中每个法线又由三个浮点值(x、y、z分量)组成。2的输入和输出 每个内核都是法线的数组视图。

位置和法线数据作为结构数组存储在CPU上。但是,如果连续线程访问连续的内存位置(通常称为 记忆凝聚 用GPU计算术语来说。因此,为了确保最佳的内存访问行为,GPU上的位置和法线数据的布局被调整为三个数组的形式,分别保持x、y和z分量(顶点位置或法线)。请注意 这是不同的 从CPU开始,数据在内存中以结构数组的形式排列,每个结构由3个浮点值组成。

在加速器内存中持久化数据

每个帧中计算的顶点法向值用于计算第二帧中后续帧的顶点法向值 每个平行u 内核。因此,将顶点法线数据保存在加速器存储器中以用于下一帧中的顶点法线计算,而不是在每一帧中从CPU存储器传输数据是有益的。

使用暂存阵列在CPU和加速器内存之间传输数据

在每一帧中,顶点位置数据从CPU传输到加速器存储器。另外,在GPU加速器上计算顶点法线后,顶点法线将被传输回CPU内存中的顶点缓冲区,用于着色。此外,如前所述,加速器内存中的顶点位置和法线数据的布局是以数组结构的形式,而不是CPU内存中的数组结构布局。为了在CPU和加速器内存之间获得最佳的数据传输性能,我们采用了 暂存阵列 用于在CPU内存中暂存数据布局的更改。例如,顶点位置从顶点缓冲区复制到数组结构形式的暂存数组,然后复制到CPU。类似地,在GPU内存中作为数组结构布局的顶点法线数据被复制到一个暂存数组中,然后以结构数组的形式复制到CPU上的顶点缓冲区中。

未来改进

仔细看看这两个 每个平行u 内核包括顶点正常计算代码使用C++ AMP,揭示了这两个内核表现出2-D空间局部性的数据访问。例如,在第一个 每个平行u 在内核中,每个线程加载其三角形顶点的顶点位置数据,由于相邻三角形具有公共顶点,因此相邻线程独立于加速器全局内存读取相同的顶点位置数据。同样,在第二个 每个平行u 在内核中,每个顶点加载它所属三角形的三角形法向值,由于相邻顶点是相同三角形的一部分,因此相同的三角形法向值由相邻线程从加速器全局内存中独立读取。

加速器全局存储器的带宽有限,注意到这里的C++内核都可能是内存绑定的,如本帖中所描述的。 C++ AMP性能指南 . 因此,让多个线程从加速器全局内存多次读取相同的数据是浪费的。虽然GPU加速器的设计是为了通过大量的多线程和线程之间的快速切换来隐藏全局内存访问延迟,但通常建议通过利用通过快速片上处理器在相邻线程之间重用数据的机会来优化全局内存访问(以获得最佳的全局内存带宽利用率) 平铺u静态 加速器存储器。虽然当前的实现没有使用这种技术,但是使用 平铺u静态 这个实现中的内存—我们打算在将来做的事情。

这个 C++与纹理 类型是全局加速器内存的另一种形式,通常由为二维空间位置设计的缓存支持,可能是使用 平铺u静态 内存利用的空间2-D数据的本地固有的C++ AMP加速内核。

最后

在本文中,我们讨论了加速Austin应用程序中计算密集型部分之一的方法;即。顶点法线计算,用C++ +AMP。虽然从C++ +AMP加速获得的实际增益依赖于可用的GPU硬件,但是如果适当地使用,可能会产生比CPU密集型内核的性能提高的数量级。 您的应用程序 . 此外,在缺少DirectX 11可编程GPU硬件的情况下,C++ AMP采用 CPU回退 它使用CPU的多核和SSE功能来加速内核的执行。您可以在MSDN博客上了解更多关于C++的知识 本机代码中的并行编程 .

我们很乐意听到您的想法、评论、问题和反馈 本机代码msdnforum中的并行编程 .

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享