OpenGL的着色器内存访问

着色器内存访问 Shader Memory Access

着色器在高度流水线化的系统中执行时,由于其读写操作的顺序在很大程度上未定义,可能会引发排序和同步问题。

  1. 着色器内存访问顺序

    • 对于顶点着色器和细分评估着色器,尽管对于应用程序指定的每个唯一顶点至少执行一次,但在某些情况下可能因实现相关的因素而多次执行。
    • 片段着色器的执行次数取决于多种因素,如像素所有权测试、剪裁测试结果及多重采样片段操作的影响;启用早期每片段测试时,若片段在早期测试阶段被丢弃,则不会执行片段着色器。当帧缓冲区不包含多重采样缓冲时,每个片段仅执行一次着色器调用;否则,根据覆盖的样本数,调用次数在1到N之间,如果声明了按样本着色,则精确执行N次。
    • 若片段着色器处理的是由几何着色器生成的但并未被当前正在光栅化处理的图元覆盖的片段或样本,那么存储操作、原子操作和原子计数器更新将无效。
    • 同一类型的着色器调用之间的相对顺序是未定义的,不同着色器类型的调用顺序也是如此。不过,一个着色器阶段产生的输出作为下一阶段输入时,保证前一阶段的所有着色器调用已经执行完毕并生成最终值。
  2. 着色器内存访问同步

    • 着色器调用间由于限制导致无法在单一图元集合内部实现某种形式的同步。例如,一个调用依赖另一个调用写入的内存时,不能确保另一个调用已经被启动且完成写入。
    • 在单个着色器调用内部针对不同内存位置发出的存储指令可能不会按照执行顺序对其他调用可见。
    • 内置函数memoryBarrier可用于提供单个着色器调用内读写操作的更强排序保证。调用memoryBarrier后,可以确保调用之前的所有内存事务在调用之后的事务之前完成。
    • 原子内存交易和原子计数器内置函数允许着色器以原子方式读写给定内存地址,并确保在这次读取和写入之间不会有其他内存事务对底层内存进行写入。
  3. 显式同步要求

    • 着色器对纹理或缓冲对象的写入可能被其他着色器调用、固定管线其他阶段或者应用程序读取。为了避免自动同步带来的性能开销,OpenGL实现不为着色器执行的缓冲区和纹理存储操作自动与其他使用相同内存的GL操作进行同步,而是需要显式同步来确保这些存储操作的效果对后续操作可见,且不会覆写先前请求的操作尚未读取的数据。
  4. MemoryBarrier命令

    • MemoryBarrier命令定义了一个屏障,用于规定命令之前和之后发出的内存事务之间的相对顺序。该命令接受一个标志位参数barriers,用于指定与着色器存储操作同步的操作集。不同的标志位分别影响特定类型的数据同步,如顶点属性数组、元素索引数组、统一变量、纹理获取、着色器内建图像访问、命令数据、像素缓冲、纹理更新、缓冲区更新、持久映射缓冲区、查询缓冲、帧缓冲、变换反馈、原子计数器和着色器存储等。
  5. Coherent内存访问

    • OpenGL着色语言中的图像变量可声明为coherent,以便独立的着色器调用通过读写共同内存地址进行通信。使用coherent声明的变量访问的缓冲对象或纹理图像内存将在任何其他着色器调用发出存储时自动更新缓存。
    • 使用coherent变量和MemoryBarrier命令的选择应基于数据共享的需求和场景,例如,细粒度共享数据应在生产者和消费者着色器中均使用coherent变量,而在多个渲染通道间传递数据则需在通道间调用带相应屏障标志的MemoryBarrier
  6. MemoryBarrierByRegion命令

    • 该命令提供了更细粒度的同步功能,它只考虑帧缓冲区较小区域内的片段着色器读写操作的完成和反映,并且只适用于可能被片段着色器读取或写入的内存交易。支持的屏障标志比特包括与片段着色器相关的部分屏障类型,相比MemoryBarrier而言,它对于散点/聚集类算法的读写操作的完成和反映并不一定适用,但在某些场景(如延迟着色)下,能够针对特定帧缓冲区地址上的前后执行进行更有效的同步。

你可能感兴趣的:(OpenGL,图形渲染)