UPL9-17-a——多个入口函数

本章代码关键字

1
#pragma kernel        // 通过 #pragma kernel 声明入口函数,可以有多个(请注意在 #pragma kernel 指令的同一行上不允许 "// text" 样式的注释)

多个入口函数的含义

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

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

多个入口函数声明和使用方式

  1. 在 Compute Shader 中使用 #pragma kernel 定义多个内核入口函数

    注意:

    1. 入口函数不能重名(在一个 Compute Shader 文件中)
    2. 入口函数可以共享该 Compute Shader 中声明的变量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 通过 #pragma kernel 声明入口函数,可以有多个
    #pragma kernel CSMain
    #pragma kernel CSMain2

    // 所有入口函数共享的变量
    RWTexture2D<float4> Result;
    float f;

    [numthreads(8,8,1)]
    void CSMain (uint3 id : SV_DispatchThreadID)
    {
    // 书写核心计算逻辑
    Result[id.xy] = float4(id.x & id.y, (id.x & 15)/15.0, (id.y & 15)/15.0, 0.0);
    }

    [numthreads(8, 8, 1)]
    void CSMain2 (uint3 id : SV_DispatchThreadID)
    {
    // 书写核心计算逻辑
    }
  2. 在 CPU(C# 侧)调用时,需要获取对应入口索引

    通过多个 Dispatch()​ 函数进行调用,用索引进行区分,使用 FindKernel() 传入入口函数名即可获取索引

    注意:Dispatch()​ 函数的调用类似异步,不会对 CPU 造成阻塞
    只是提交执行指令给到 GPU,GPU 侧会按照 Dispatch()​ 顺序,依次执行内核入口函数
    在一个 Dispatch()内部,GPU 执行是并行的;多个 Dispatch()调用之间,是串行的,需要按顺序执行

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public ComputeShader computeShader;     // 声明 computeShader 变量,可以通过 Inspector 窗口关联,也可以动态加载

    void Start()
    {
    int index = computeShader.FindKernel("CSMain"); // 获取 computeShader 中名为 CSMain 的函数索引
    int index2 = computeShader.FindKernel("CSMain2"); // 获取 computeShader 中名为 CSMain2 的函数索引
    computeShader.SetFloat("f", 10.0f); // 传递相关参数
    computeShader.Dispatch(index, 64, 64, 0); // 启动 CSMain 的计算
    computeShader.Dispatch(index2, 64, 64, 0); // 启动 CSMain2 的计算,它会在 CSMain 计算完成后执行
    // 使用GPU计算完成输出的数据渲染或者传回CPU
    // 释放资源
    }

多个入口函数对于我们的意义

  1. 分阶段执行

    我们可以将部分计算逻辑分步执行,分阶段处理,我们可以利用多入口函数串行执行的这一特点。实现出类似流水线一样的分布执行,比如:

    1. 初始化阶段

      在第一个内核入口函数中分配、填充初始数据

    2. 模拟阶段

      在第二个内核入口函数中更新位置、速度等数据

    3. 渲染阶段

      在第三个内核入口函数中把结果写入纹理

  2. 逻辑模块化

    我们可以让不同的内核入口函数负责不同的功能,这样从设计上更加清晰易维护

  3. 不同任务可以使用不同线程组配置

    不同内核入口函数的线程组配置可以不同
    可以针对性的设置线程组配置

  4. 减少 ComputeShader 文件数量

    在一个 ComputeShader 文件中可以包含多个内核入口函数
    CPU(C# 侧)只需要加载一次 ComputeShader 文件,就可以执行多个内核入口函数

  5. 共享数据

    多个内核入口函数由于在一个 ComputeShader 文件中,因此他们可以共享数据

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