UPL9-17-g——Compute Shader 常用 API
UPL9-17-g——Compute Shader 常用 API
Compute Shader 常用 API
我们目前已经掌握了 Compute Shader 的核心知识点,已经能够使用 Compute Shader 来帮助我们来完成计算需求
这里整理 Compute Shader 中的常用 API (大部分学习过,补充部分新 API)
C# 常用 API
通过 ComputeShader 对象调用
-
查找入口函数索引
1
ComputeShader.FindKernel(string name)
示例可见:UPL9-17——Compute Shader
-
按线程组数量启用GPU执行
1
ComputeShader.Dispatch(int kernelIndex, int x, int y, int z)
示例可见:UPL9-17——Compute Shader
-
设置参数相关API
1
2
3
4
5
6
7
8ComputeShader.SetFloat(string name, float value) // 设置单个 float 参数
ComputeShader.SetInt(string name, int value) // 设置 int 参数
ComputeShader.SetBool(string name, bool value) // 设置 bool 参数
ComputeShader.SetVector(string name, Vector4 value) // 设置 float2/float3/float4 类型
ComputeShader.SetFloats(string name, params float[] values) // 设置 float2/float3 类型 或 数组
ComputeShader.SetMatrix(string name, Matrix4x4 value) // 设置矩阵类型(float3x3/float4x4)
ComputeShader.SetBuffer(int kernelIndex, string name, ComputeBuffer buffer) // 绑定结构化缓冲区
ComputeShader.SetTexture(int kernelIndex, string name, Texture tex) // 绑定纹理相关内容可见:UPL9-17-c——数据输入规则
设置常量缓冲区
1
ComputeShader.SetConstantBuffer(string name, ComputeBuffer buffer, int offset, int size)
获取
[numthreads(x,y,z)]的设置值1
ComputeShader.GetKernelThreadGroupSizes(int kernelIndex, out uint x, out uint y, out uint z)
-
ComputeBuffer相关API1
2
3
4
5new ComputeBuffer(count, stride) // 创建一个 GPU 缓冲区
ComputeBuffer.SetData(T[] data) // 向 GPU 写入数据
ComputeBuffer.GetData(T[] data) // 从 GPU 读取数据(同步操作)
ComputeBuffer.Release() // 释放 GPU 缓冲区资源
ComputeBuffer.CopyCount(buffer1,buffer2, 0) // 统计 GPU 动态生成的数据数量主要配合
AppendStructuredBuffer(动态追加元素)和ConsumeStructuredBuffer(动态消耗元素)使用相关内容可见:UPL9-17-c——数据输入规则,UPL9-17-f——获取 GPU 数据
注意:新版本中有一个
GraphicsBuffer 类,它的用法和ComputeBuffer类似,它可以和 SRP 等管线更好地配合 -
异步获取
1
AsyncGPUReadback.Request(buffer, callback)
相关内容可见:UPL9-17-f——获取 GPU 数据
-
判断设备是否支持
ComputeShader1
SystemInfo.supportsComputeShaders
-
动态启用/关闭编译关键字(可用于多功能
ComputeShader)1
2ComputeShader.enableKeyword
ComputeShader.DisableKeyword相关内容可参考:US5L3——Shader变体和关键字
Compoute Shader 内常用 API
-
入口函数参数的三个常用语义
1
2
3SV_DispatchThreadID // 调度线程 ID,每个线程的全局索引(系统自动传入)
SV_GroupThreadID // 组内线程 ID,当前线程在组内的局部索引
SV_GroupID // 线程组 ID,当前线程组在整个 Dispatch 中的索引相关内容可见:UPL9-17-e——参数的系统语义
-
HLSL 内置函数
1
2
3
4
5
6
7
8lerp(a,b,t) // 线性插值
step(edge,x) // 阶跃函数
saturate(x) // 将值限制在 [0,1]
smoothstep(min, max, x) // 平滑插值
dot(a,b) // 向量点积
cross(a,b) // 向量叉积
length(x) // 向量长度
normalize(x) // 向量单位化等等,具体可以参考:US2S3L12——CG内置函数
-
组内共享内存 -
groupshared关键字它可以让同一线程组内的所有线程共享一块高速、低延迟的片上内存,以便线程之间协作计算、减少显存访问
用groupshared 修饰的变量会被放入组内共享内存,组内所有线程都能访问、修改同一份数据
每个组有自己的那一份;不同组之间互不干扰,比如:1
groupshared float test[64];
假设一个线程组
[numthreads(8,8,1)],每组64个线程
那么每组线程都会有自己的一块共享内存 -
同步与内存屏障函数
先解释一些概念:
-
GPU 的 三层内存层级
- 寄存器 —— 每个线程独有,比如普通局部变量
- 组内共享内存 —— 每个线程组独立拥有,比如
groupshared变量 - 全局内存 —— 所有线程组共享,比如可读写数据流、纹理等
-
内存同步
让之前写入的内存在后续访问时保证最新可见,不被缓存或乱序,举例:
1
2
3
4
5
6
7
8
9
10
11groupshared int data;
[numthreads(2, 1, 1)]
void CSMain(uint3 tid : SV_GroupThreadID)
{
if (tid.x == 0)
data = 123;
// 线程 1 可能此时还没看到 data=123
// 因此我们可以在这等待内存同步完成(添加一些内存同步API)
int v = data;
} -
线程同步
让所有线程等一等,等大家都执行到同一位置后再继续执行
同步函数:
-
GroupMemoryBarrierWithGroupSync()线程同步 + 内存同步(组内共享内存,
groupshared、当前组写的RWBuffer /Texture等,不跨线程组)
让同一个线程组内部的所有线程在某个时刻进行汇合同步
最常用的同步函数,一般所有涉及groupshared 的协作计算都要用它
假设一个线程组[numthreads(8,8,1)],每组有 64 个线程,这些线程是并行执行的
但是在某些时刻,我们可能希望该组所有线程都完成了当前阶段后,再一起进行下一阶段
这时候我们就可以调用GroupMemoryBarrierWithGroupSync()函数1
2
3
4
5
6
7
8
9
10
11groupshared int data;
[numthreads(2, 1, 1)]
void CSMain(uint3 tid : SV_GroupThreadID)
{
if (tid.x == 0)
data = 123;
// 让所有线程都执行到这一步时,再执行继续并行执行后边的逻辑,并且此时内存也同步了
GroupMemoryBarrierWithGroupSync();
int v = data;
} -
GroupMemoryBarrier()内存同步(组内共享内存,
groupshared、当前组写的RWBuffer /Texture等,不跨线程组) -
DeviceMemoryBarrier()内存同步(全局内存同步,比如可读写的纹理和数据流,跨线程组),跨组的全局内存同步
-
AllMemoryBarrier()内存同步(组内共享内存 + 全局内存 都得同步,跨线程组),同步所有层级内存
-
AllMemoryBarrierWithGroupSync()最强版,线程同步 + 所有内存同步,等待组内所有线程汇合,确保所有类型内存(共享内存 + 全局内存)写入完成
-
-
原子操作
原子操作用于并行计算时防止多个线程同时修改同一份数据造成的竞态条件
指一个操作在 GPU 上执行时,不会被其他线程中断或同时修改同一内存地址举例:
1
2
3
4
5
6
7RWStructuredBuffer<int> Counter;
[numthreads(64, 1, 1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
Counter[0] += 1; // 64个线程同时执行时,结果可能 Counter[0] < 64 !
}-
对
dest执行原子加法,并返回加前的值1
InterlockedAdd(dest, value, out originalValue)
示例:
1
2
3
4
5
6
7
8RWStructuredBuffer<int> Counter;
[numthreads(64, 1, 1)]
void CSMain(uint3 id : SV_DispatchThreadID)
{
int i;
InterlockedAdd(Counter[0], 1, out i)
} -
原子地将
dest设为新值,并返回加前的值1
InterlockedExchange(dest, value, out originalValue)
-
比较后再交换 如果
dest == compareValue,则写入value1
InterlockedCompareExchange(dest, compareValue, value, out originalValue)
-
原子地取最小值
1
InterlockedMin(dest, value, out originalValue)
-
原子地取最大值
1
InterlockedMax(dest, value, out originalValue)
-
原子按位与
1
InterlockedAnd(dest, value, out originalValue)
-
原子按位或
1
InterlockedOr(dest, value, out originalValue)
-
原子按位异或
1
InterlockedXor(dest, value, out originalValue)
原子操作让对共享变量的 “读-改-写” 过程变成一个不可被打断的整体
注意:原子操作会破坏 GPU 的并行性,影响 GPU 性能,需要谨慎使用
-
