97.ComputeShader多个入口函数

97.性能优化-GPU-着色器优化-ComputeShader-多个入口函数


97.1 知识点

多个入口函数的含义

一个 Compute Shader 文件可以定义多个 Kernel(内核),即多个入口函数。每个内核可以有独立的线程组配置([numthreads])。在 CPU 端(C#)需要为每个入口函数单独调用 Dispatch()

基本原理: 每声明一个入口函数(通过 #pragma kernel),都会被编译为一个独立的内核,相当于 GPU 上一个可单独调用的计算程序。

多个入口函数使用方式

主要步骤

  1. 在 Compute Shader 中定义多个内核入口函数

    • 入口函数不能重名(同一文件内)。
    • 多个入口函数可以共享该 Compute Shader 中声明的变量(如 RWTexture2D)。
  2. 在 C# 侧调用时,分别获取对应入口索引,通过多次 Dispatch 调用不同内核,用索引区分。

注意: Dispatch 调用类似异步,不会阻塞 CPU,只是把执行指令提交给 GPU。GPU 会按 Dispatch 的调用顺序依次执行:单个 Dispatch 内部是并行的;多个 Dispatch 之间是串行的

在 Compute Shader 中定义多个内核

// 每个 #kernel 指令声明一个可编译为 Compute Shader 入口点的函数
// 一个文件中可以声明多个内核,分别对应不同计算任务
#pragma kernel CSMain
#pragma kernel CSMain2

RWTexture2D<float4> Result;

// 第一个内核:8×8×1 线程组,适合二维图像
[numthreads(8,8,1)]
void CSMain(uint3 id : SV_DispatchThreadID,
            uint3 id2 : SV_GroupID,
            uint3 id3 : SV_GroupThreadID)
{
    Result[id.xy] = float4(id.x & id.y, (id.x & 15) / 15.0, (id.y & 15) / 15.0, 0.0);
}

// 第二个内核:1×1×1,适合单次或全局逻辑
[numthreads(1,1,1)]
void CSMain2(uint3 id : SV_DispatchThreadID)
{
    // 例如:初始化缓冲区、全局同步等
}

在 C# 中分别调用

int index = computeShader.FindKernel("CSMain");
int index2 = computeShader.FindKernel("CSMain2");

computeShader.Dispatch(index, 64, 64, 1);
computeShader.Dispatch(index2, 64, 64, 1);

多个入口函数的意义

  1. 分阶段执行:利用多入口函数串行执行的特点,实现类似流水线的分步处理。例如:第一个内核初始化/填充数据,第二个内核更新位置与速度,第三个内核将结果写入纹理。

  2. 逻辑模块化:不同内核负责不同功能,结构更清晰、易维护。

  3. 不同任务可使用不同线程组配置:每个内核可单独设置 [numthreads(X,Y,Z)],针对任务优化。

  4. 减少 Compute Shader 文件数量:一个文件中包含多个内核,C# 只需加载一次即可执行多个入口。

  5. 共享数据:同一文件内的内核共享声明的缓冲区与纹理,便于阶段间传递数据。

总结: 多入口函数相当于 GPU 并行计算的多阶段流水线,把复杂计算拆成多个独立、可组合的模块,提高性能、可读性和可维护性。


97.2 知识点代码

Lesson97_性能优化_GPU_着色器优化_ComputeShader_多个入口函数.cs

using UnityEngine;

public class Lesson97_性能优化_GPU_着色器优化_ComputeShader_多个入口函数 : MonoBehaviour
{
    public ComputeShader computeShader;

    private void Start()
    {
        #region 知识点一 多个入口函数的含义

        // 一个 Compute Shader 文件可以定义多个 Kernel(内核),即多个入口函数
        // 每个 Kernel 入口函数可以有独立的线程数量配置
        // 我们在 CPU 端(C# 侧)需要为每个入口函数单独调用 Dispatch()
        // 基本原理:每声明一个入口函数,都会被编译为一个独立的内核(Kernel),
        // 相当于 GPU 上的一个可单独调用的计算程序

        #endregion

        #region 知识点二 多个入口函数使用方式

        // 1. 在 Compute Shader 中定义多个内核入口函数(注意:入口不能重名,可共享该文件中声明的变量)
        // 2. 在 C# 侧调用时,获取对应入口索引,通过多次 Dispatch 用索引区分
        // 利用 API 找到入口函数索引:FindKernel("入口函数名")
        int index = computeShader.FindKernel("CSMain");
        int index2 = computeShader.FindKernel("CSMain2");

        // 启动计算:Dispatch(入口函数索引, x 线程组数, y 线程组数, z 线程组数)
        computeShader.Dispatch(index, 64, 64, 1);
        computeShader.Dispatch(index2, 64, 64, 1);
        // 注意:Dispatch 调用类似异步,不阻塞 CPU,只是把指令提交给 GPU;
        // GPU 按 Dispatch 顺序依次执行:单个 Dispatch 内部并行,多个 Dispatch 之间串行

        #endregion

        #region 知识点三 多个入口函数的意义

        // 1. 分阶段执行:将计算逻辑分步、分阶段处理,利用多入口串行特点实现类似流水线(如初始化→模拟→写纹理)
        // 2. 逻辑模块化:不同内核负责不同功能,结构清晰易维护
        // 3. 不同任务可使用不同线程组配置:各内核可单独设置 [numthreads]
        // 4. 减少 Compute Shader 文件数量:一个文件多内核,C# 只需加载一次
        // 5. 共享数据:同一文件内的内核可共享声明的缓冲区与纹理
        // 总结:多入口函数相当于 GPU 并行计算的多阶段流水线,可把复杂计算拆成多个独立、可组合的模块

        #endregion
    }
}

Lesson97_ComputeShader.compute

// 每个 #kernel 指令声明一个可编译为 Compute Shader 入口点的函数
// 一个文件中可以声明多个内核函数,分别对应不同的计算任务
#pragma kernel CSMain
#pragma kernel CSMain2

// 纹理缓冲区类型
RWTexture2D<float4> Result; // 可读写二维纹理(存储 float4,通常用于输出图像)

// 线程组配置:指定每个线程组包含的线程数量
// [numthreads(X, Y, Z)] 定义一个线程组包含 X*Y*Z 个线程
// 常见配置如 8x8x1=64 个线程,适合处理二维图像数据
[numthreads(8,8,1)]
void CSMain(uint3 id : SV_DispatchThreadID,  // 全局线程 ID(在整个 Dispatch 调用中唯一)
            uint3 id2 : SV_GroupID,          // 线程组 ID(标识当前线程组的位置)
            uint3 id3 : SV_GroupThreadID)    // 线程组内线程 ID(在线程组内唯一)
{
    // 核心计算逻辑:根据线程 ID 生成棋盘格图案
    // R 通道:id.x 和 id.y 的按位与结果
    // G 通道:id.x 的低 4 位映射到 [0,1] 范围
    // B 通道:id.y 的低 4 位映射到 [0,1] 范围
    // A 通道:固定为 0.0(完全透明)
    Result[id.xy] = float4(id.x & id.y, (id.x & 15) / 15.0, (id.y & 15) / 15.0, 0.0);
}

// 第二个计算着色器内核函数
// 使用 1x1x1 线程组配置,适合执行单次原子操作或全局控制逻辑
[numthreads(1,1,1)]
void CSMain2(uint3 id : SV_DispatchThreadID) // 仅需全局线程 ID
{
    // 此处可编写第二个内核的核心逻辑
    // 例如:初始化缓冲区、执行全局同步操作等
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏