Compute Shader次世代优化方案

栏目: 后端 · 发布时间: 5年前

内容简介:这是侑虎科技第498篇文章,感谢作者凯奥斯供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)作者主页:本文章标题来源于AMD在4C上的一个演讲: Compute Shaders: Optimize your engine using compute.

这是侑虎科技第498篇文章,感谢作者凯奥斯供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

作者主页: https://zhuanlan.zhihu.com/commentsofchaos ,作者也是U Sparkle活动参与者,UWA欢迎更多开发朋友加入U Sparkle开发者计划,这个舞台有你更精彩!

本文章标题来源于AMD在4C上的一个演讲: Compute Shaders: Optimize your engine using compute. 3

概念

Compute Shader是在GPU上运行的程序。虽然是老生常谈了,但是我们还是要先介绍一下GPU。 众所周知,CPU和GPU是两种不同的架构,那么他们之间的区别是什么?

1.CPU是基于低延迟的设计

Compute Shader次世代优化方案

CPU有很强大的算术逻辑单元,减少操作延迟;巨大的Cache,为了降低内存访问的延迟;复杂的控制器,使用分支预测来减少分支延迟,使用数据转发减少数据延迟。

我们可以这样说:CPU擅长逻辑控制和串行的运算 1

2.GPU是基于大吞吐量的设计

Compute Shader次世代优化方案

GPU有小的Cache,用来促进吞吐量;简单的控制,没有分支预测和数据转发;高效节能的ALU,很多延迟很长的ALU,但是为了高吞吐量被重度管线化;需要开启大量的线程才能降低延迟。

相应地,我们可以这样说:GPU适用于计算密集型和易于并发的程序 1 & 2

3.GPGPU

可以看出,CPU和GPU各有自己的擅长,那么我们可以将二者结合起来,使用CPU做串行,而使用GPU做并行。这种技术就叫做GPGPU,也就是利用GPU进行通用计算的技术(General Purpose Computing on GPU) 1

Compute Shader次世代优化方案

但是,我们知道,通常来讲,GPU是用来执行图形渲染的。那么,为了执行通用计算,NV推出了CUDA,Khronos推出了OpenCL,Microsoft推出了DirectCompute,也就是后来的Compute Shader,然后,各种图形API也相继推出了CS。 25

4.支持Compute Shader的图形API

Compute Shader次世代优化方案

DX虽然从10开始支持Compute Shader/Direct Compute,但是限制比较大。DX11的Compute Shader拥有更强大的功能(当然肯定还有DX12) 6 。所以我们一般在Unity中使用CS,还是要求Shader Target4.5(也就是Shader Model 5) 19

OpenGL从4.3开始支持CS(但是MacOSX不支持4.3)。ES从3.1开始支持CS 5

Metal和Vulkan都支持CS 4 & 7

另外PS4和Xbox one(DX11.2)也支持CS 19

5.Compute管线与图形管线的对比

我们通过几张图,来简单对比一下计算管线与传统图形管线有什么不同。

Compute Shader次世代优化方案

我们可以看到,计算管线变得很简单 3

(关于GPU Rendering Pipeline,可以参考这张图 14 :

http://t.cn/E5RqIWp

从硬件端来看:

Compute Shader次世代优化方案

上图是图形管线在硬件端的工作流程 3

Compute Shader次世代优化方案

上图是计算管线在硬件端的工作流程 3 。通过对比,我们可以看出:Compute Shader可以在不通过渲染管线的情况下,利用GPU完成一些与图形渲染不直接相关的工作,从而降低硬件的Overhead。

这就是Compute Shader的优势。

语法

1.如何在Unity里使用Compute Shader?

上文中介绍了,目前有很多图形API支持CS,但是各种API的Shading Language语法和API各不相同。Unity的ShaderLab采用了跟HLSL接近的API,方便我们编写Shader。

2.Kernel

如果我们在Unity里面新建一个CS,便是如下的代码(稍作修改)。

1// test.compute
 2#pragma kernel FillWithRed // 1
 3
 4RWTexture2D<float4> res;   // 2
 5
 6[numthreads(8,8,1)]       // 3
 7void FillWithRed (uint3 dtid : SV_DispatchThreadID) // 4
 8{
 9    res[dtid.xy] = float4(1,0,0,1);                 // 5
10}

这是一个简单的Compute Shader示例,将一个RT填充成红色。

1)首先声明了一个Kernel,Kernel相当于一个main函数,是CS的入口。这应该是来源于Metal的思路 7 ,可以在一个资源文件里定义不同的Kernel方法,公用一些代码,同时也可以做到相对独立。

2)然后声明了一个RWTexture2D,对应于C#,是RenderTexture。

3)在函数名上面还有一个numThreads的attribute,这个我们后面会讲到。

4)函数的参数后面带有一个Semantic(SV_DispatchThreadID),这个我们后面也会讲到。我们暂时可以把它当作一个坐标值。

5)最后是函数体,是将RT中的像素设置成红色。

3.Dispatch

如何执行这样一个CS代码?在C#里,调用如下代码。

1public void Dispatch(int kernelIndex,
2    int threadGroupsX,
3    int threadGroupsY,
4    int threadGroupsZ);

在CPU端,我们可以通过这个接口,将CS Dispatch出去。Dispatch就相当于Drawcall,但是没有Draw。 其中KernelIndex可以通过ComputeShader.FindKernel来获取。而ThreadGroupsXYZ代表线程组的数量。 那么什么又是线程组?

4.线程组

Compute Shader次世代优化方案

在CS里面,线程可以分为三个维度 2

上图中,最右边的表示单个线程,最左边的表示一个Dispatch,而图中间的,表示一个Thread Group。

Thread Group是指将多个线程组合成为一个Group,在这个Group里面,每个线程有自己的相对位置。Group内,还可以使用共享变量,相互通信。将numThreads这个attribute声明在Kernel函数的前面,就表示一个Thread Group中有多少个Thread。

如图所示一个Dispatch中有3x2x3个Thread Groups,而一个Group中有4x4x2个Thread。

这样做的好处一个是可以利用GPU的warp/wavefront/EU-thread 2 & 3

另外,举个例子,现在很多图像压缩算法都是基于Block的,而Thread Group(OpenGL里叫做local size)可以为图像数据的一个Block的大小(例如8x8),Group数量可以是图像的尺寸除以块的尺寸。每个块被当作一个单独的Work Group来处理,并且Group内可以共享一些信息 5

更进一步的,我们可以看下图 6

Compute Shader次世代优化方案

上半图代表了一个5x3x2的Dispatch,图中的坐标代表一个Thread Group。接着,将2,1,0的Thread Group打开,我们可以看到下半图。这张图代表了一个10x8x3的Thread Group,图中的坐标代表了一个Thread。

如图所示,我们可以根据这些坐标算出GroupThreadID,GroupID,DispatchThreadID和GroupIndex。

这些ID一般是用来作为索引来获取Buffer、Texture或者Thread Group Shared Memory里的数据。

例如上面举的例子,GroupThreadID就是图像的Block内的坐标,GroupID是图像按块划分的坐标(图像的尺寸除以块的尺寸),而DispatchThreadID是像素的坐标。

5.Buffer & Texture

CS可以使用一些常规的类型,标量、向量、矩阵、纹理、数组等。

除此之外,为了更灵活的使用CS,还推出了StructuredBuffer,简称SBuffer。

Compute Shader次世代优化方案

(SBuffer在FS里也可以使用,在其他Shader里也可能可以使用。)

StructuredBuffer还包括:

RWStructuredBuffer

RWStructuredBuffer with counter

(RW)ByteAddressBuffer

AppendStructuredBuffer

ConsumeStructuredBuffer

StructuredBuffer除了可以包含各种内置的类型之外,还可以包含自定义的Struct。

6.GroupShared

使用GroupShared可以将一个变量标记为组内共享(又叫TGSM 2 )。

使用这种变量,就可以在Thread Group内进行通讯。

例如,我们可以在Forward+/Deferred管线里使用Compute Shader对点光源进行剔除。这个是在战地3中使用的技术 16 & 21

7.Barrier

当我们在不同线程访问同一个资源的时候,我们需要使用Barrier来进行阻塞和同步。

分为以下两种:

GroupMemoryBarrier 
DeviceMemoryBarrier
AllMemoryBarrier
DeviceMemoryBarrierWithGroupSync 
GroupMemoryBarrierWithGroupSync  
AllMemoryBarrierWithGroupSync

GroupMemoryBarrier是等待对GroupShared变量的访问。

DeviceMemoryBarrier是等待对Texture或Buffer的访问。

AllMemoryBarrier是以上两者的和。

*WithGroupSync版本是需要同步到当前指令

8.Interlocked

原子操作,不会被线程调度机制打断。

InterlockedAdd
InterlockedAnd
InterlockedCompareExchange
InterlockedCompareStore
InterlockedExchange
InterlockedMax
InterlockedMin
InterlockedOr
InterlockedXor

但是只能用于int/uint。

例如可以用于计算灰度直方图,用于Tonemapping\Auto Exposure等效果 19

9.平台差异

虽然Unity帮我们做了跨平台的工作,但是我们仍然需要面对一些平台差异。

1) 数组越界 ,DX上会返回0,其它平台会出错。

2)变量名与关键字/内置库函数 重名 ,DX无影响,其他平台会出错。

3)如果SBuffer内结构的显存布局要与 内存布局不一致 ,DX可能会转换,其他平台会出错。

4) 未初始化 的SBuffer或Texture,在某些平台上会全部是0,但是另外一些可能是任意值,甚至是NaN。

5)Metal不支持对纹理的原子操作,不支持对SBuffer调用 GetDimensions

6)ES 3.1在一个CS里 至少支持4个SBuffer (所以,我们需要将相关联的数据定义为struct)。

7)在渲染管线中,部分号称支持es3.1+的Android手机 只支持在片元着色器内访问StructuredBuffer

10.性能优化

另外,在使用CS的时候,我们还需要知道一些性能优化点。

1)尽量减少Group之间的交互:硬件不支持全局同步 2 ,不同步的话容易导致错误和崩溃 3

2)GPU一次Dispatch会调用64(AMD成为wavefront)或32(NVIDIA称为warp)个线程(这实际上是一种SIMD技术),所以,numThreads的乘积最好是这个值的整数倍。但是Mali不需要这种优化 8 。此外,Metal可以通过API获取这个值 7

3)避免回读:回读操作在渲染管线中使用的比较少,而在CS中可能会被用到,所以重点提一下 20

4)避免分支,重点避免在Thread Group中间的分支,这其实跟第二点是相关的,如果在wavefront/warp整数倍的地方发生分支,消耗就会小很多 2 & 26

5)尽量保证内存连续性 2

6)使用[unroll]来打开循环,有些时候需要手动unroll 22

还有一些在渲染管线中适用的Tips这里没有列举出来。

应用

那么介绍过CS之后,我们看看,目前都有哪些应用。

1.GPU Particle System

Compute Shader次世代优化方案

图为用CS实现的GPU粒子系统,这个功能中使用CS计算粒子的运动轨迹 10

2.GPU Simulation

Compute Shader次世代优化方案

图为布料模拟,使用了CS进行布料粒子的受力运动计算、碰撞检测和反馈,以及约束计算。类似的还有头发模拟和海水模拟 11

3.Image Processing

Compute Shader次世代优化方案

图为一个简单的去色的图像处理 12 ,将RGB与(0.299,0.587,0.114)进行Dot,获得灰度值 24 。类似的还有eye adaptation, color grading等等 3

Unity的PPS2中使用的Histogram就是一个很好的例子,几乎用到了CS的所有Feature 23

4.Image Compression

Compute Shader次世代优化方案

图为ASTC算法压缩过的图像(4x4 6x6 8x8) 13 。 上面提到过,我们可以使用CS来实现基于Block的纹理压缩算法。

5.Tessellation

Compute Shader次世代优化方案

曲面细分 15 :默认管线中的Tessellation比较受限,虽然可以使用Displacement Mapping来提升它的效果,但是仍然不够动态。我们配合CS一起使用,我们可以配合一些逻辑更自由更动态的生成细分顶点 14 & 3

6.Local lights culling

Compute Shader次世代优化方案

战地3中,使用的是Deffered Shading Pipeline,通过CS对点光源、探照灯等光源进行剔除 16

7.Occlusion culling

Compute Shader次世代优化方案

图片来源,知乎大V MaxwellGeng实现的GPU Occlusiong Culling,他使用了Hiz的方法,对Cluster进行遮挡剔除 17 。而这种思想就是GPUDRP。

8.GPU Driven Rendering Pipeline

Compute Shader次世代优化方案

图为刺客信条大革命,在这部游戏中使用了GPUDRP技术,并在Siggraph 2015: Advances in Real-Time Rendering in Games course中发表 18

还有很多很多……

Simple, but not easy.

“Simple, but not easy”是我对Compute Shader的认识,也是对本文的总结。ES从3.1开始支持CS,也就是说,在手机上的支持率并不是很高。

另外,手机算力还是很低。GTX 1050 Ti的算力是1.9k~2.9k Gflops(floating point operations per second),有768个core。华为P20的Mali-G72 MP12的算力是300+ Gflops,只有12个core [28]

所以,CS在手机上的使用,是困难的。但是,我认为它是有巨大潜力的,随着手机硬件的高速发展,我相信,用不了多久,Compute Shader的使用就可以在手机上普及。

Compute Shader次世代优化方案

引用

[1] Graphic Processing Processors (GPUs) Parallel Programming

[2] DirectCompute Optimizations and Best Practices

[3] Compute Shaders: Optimize your engine using compute / Lou Kramer, AMD (video)

[4] Introduction to Compute Shaders in Vulkan

[5] Compute Shader(OpenGL)

[6] Compute Shader Overview(Direct3D 11)

[7] About Threads and Threadgroups(Metal)

[8] ARM® Mali™ GPU OpenCL Developer Guide(Version 3.2)

[9]Real-Time Rendering 3rd Edition. Chapter 18

[10] GPU Particles (Github)

[11] GPU Cloth Tool

[12] Compute Shader Filters

[13]Adaptive Scalable Texture Compression

[14]Introduction to 3D Game Programming with DirectX 11

[15] DirectX 11 Tessellation (NVIDIA)

[16]DirectX 11 Rendering in Battlefield 3

[17] Hi-Z GPU Occlusion Culling

[18] GPU-Driven Rendering Pipelines

[19] https://docs.unity3d.com

[20] Problems with ComputeBuffer Readback

[21] Volume Tiled Forward Shading (Github)

[22]Low-level Shader Optimization for Next-Gen and DX11 (ppt) (video)

[23] Post-processing Stack v2 (Github)

[24]数字图像处理(冈萨雷斯)

[25] General-purpose computing on graphics processing units (Wikipedia)

[26]全局光照技术:从离线到实时渲染

[27] Mythbusters Demo GPU versus CPU ( NVIDIA )

[28] Glops

文末,再次感谢凯奥斯的分享,如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

也欢迎大家来积极参与U Sparkle开发者计划,简称“US”,代表你和我,代表UWA和开发者在一起!


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

测试驱动的JavaScript开发

测试驱动的JavaScript开发

Christian Johansen / 赵勇、程德、凌杰、高博 / 机械工业出版社 / 2012-2-9 / 69.00元

本书是一本完整的、基于最佳实践的JavaScript敏捷测试指南,同时又有着测试驱动开发方法(TDD)所带来的质量保证。领先一步的JavaScript敏捷开发者Christian Johansen的讨论涵盖了将最先进的自动化测试用于JavaScript开发环境的方方面面,带领读者走查整个开发的生命周期,从项目启动到应用程序部署。本书的主要内容包括:掌握自动化测试和TDD;构建有效的自动化测试工作流......一起来看看 《测试驱动的JavaScript开发》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

SHA 加密
SHA 加密

SHA 加密工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换