Vullkan 计算着色器缓存和屏障

ala*_*ris 3 memory-barriers barrier compute-shader vulkan

我试图了解整个 L1/L2 冲洗是如何工作的。假设我有一个像这样的计算着色器

layout(std430, set = 0, binding = 2) buffer Particles{
    Particle particles[];
};


layout(std430, set = 0, binding = 4) buffer Constraints{
    Constraint constraints[];
};


void main(){
    const uint gID = gl_GlobalInvocationID.x;
    for (int pass=0;pass<GAUSS_SEIDEL_PASSES;pass++){
        // first query the constraint, which contains particle_id_1 and particle_id_1
        const Constraint c = constraints[gID*GAUSS_SEIDEL_PASSES+pass]; 
        // read newest positions
        vec3 position1 = particles[c.particle_id_1].position; 
        vec3 position2 = particles[c.particle_id_2].position;
        // modify position1 and position2
        position1 += something;
        position2 -= something;
        // update positions
        particles[c.particle_id_1].position = position1;
        particles[c.particle_id_2].position = position2;
        // in the next iteration, different constraints may use the updated positions
    }
}

Run Code Online (Sandbox Code Playgroud)

据我了解,最初所有数据都驻留在 L2 中。当我阅读时,particles[c.particle_id_1].position我将一些数据从 L2 复制到 L1(或直接复制到寄存器)。然后在position1 += something我修改 L1(或寄存器)。最后particles[c.particle_id_2].position = position1,我将数据从 L1(或寄存器)刷新回 L2,对吗?因此,如果我想在此之后运行第二个计算着色器,并且第二个着色器将读取粒子的位置,则我不需要同步Particles。只放置一个执行屏障就足够了,没有内存屏障

void vkCmdPipelineBarrier(
    VkCommandBuffer                             commandBuffer,  
    VkPipelineStageFlags                        srcStageMask, // here I put VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
    VkPipelineStageFlags                        dstStageMask, // here I put VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
    VkDependencyFlags                           dependencyFlags, // here nothing
    uint32_t                                    memoryBarrierCount, // here 0
    const VkMemoryBarrier*                      pMemoryBarriers, // nullptr
    uint32_t                                    bufferMemoryBarrierCount, // 0
    const VkBufferMemoryBarrier*                pBufferMemoryBarriers,  // nullptr
    uint32_t                                    imageMemoryBarrierCount, // 0
    const VkImageMemoryBarrier*                 pImageMemoryBarriers);  // nullptr
Run Code Online (Sandbox Code Playgroud)

Nic*_*las 5

Vulkan 的内存模型并不将“缓存”视为缓存。它的模型建立在可用性和可见性的概念之上。如果命令/阶段 A 与命令/阶段 B 具有执行依赖性,则 GPU 命令/阶段 A 产生的值对 GPU 命令/阶段 B“可用”。 GPU 命令/阶段 A 产生的值对 GPU“可见”命令/阶段 B 如果命令/阶段 A 与命令/阶段 B 具有内存依赖关系,就相关的特定内存以及 A 写入它和 B 将访问它的访问模式。

如果某个值对命令/阶段既不可用也不可见,则尝试访问它会产生未定义的行为。

可用性和可见性的实现将涉及清除缓存等。但就 Vulkan 内存模型而言,这是一个它并不关心的实现细节。您也不应该:了解 Vulkan 内存模型并编写在其中运行的代码。

您的管道屏障会创建执行依赖项,但不会创建内存依赖项。因此,在屏障之前由 CS 进程写入的值对之后的 CS 进程可用,但对它们不可见。您需要具有内存依赖性才能建立可见性。


但是,如果您想要 GPU 级别的理解……这完全取决于 GPU。GPU 是否具有缓存层次结构、L1/L2 拆分?也许有些人会,也许不会。

无论如何,这都无关紧要,因为仅将值写入内存中的地址并不等同于“刷新”该内存周围的适当缓存。即使使用coherent限定符也只会导致在同一调度调用中执行的计算着色器操作刷新。不能保证会影响以后的调度调用。