【问题标题】:Vullkan compute shader caches and barriersVullkan 计算着色器缓存和屏障
【发布时间】:2021-10-10 06:58:19
【问题描述】:

我试图了解整个 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
    }
}

据我了解,最初所有数据都驻留在 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

【问题讨论】:

    标签: vulkan memory-barriers compute-shader barrier


    【解决方案1】:

    依赖于实现。据我们所知,一个设备可能根本没有缓存,或者将来它可能是某种量子魔法。

    着色器分配操作并不意味着任何事情。 Vulkan 规范中的任何地方都没有提到“L1”或“L2”。这是一个不存在的概念。

    完全摆脱缓存的东西,以及随之而来的所有心理负担。

    这里重要的是,当您阅读某些内容时,该内容需要对阅读代理“可见”(无论您使用何种设备,以及它可能具有何种模糊的内存架构)。如果它不是“可见的”,那么您可能正在阅读垃圾。

    当你写东西时,这不会自动发生。这些写入对任何人都不“可见”。

    首先,你将你的写入放入内存依赖的src* 部分(例如,通过管道屏障)。这将使您的作品“可从”获得。

    然后您将阅读器放入dst*,它将获取所有“可用”的引用写入,并使它们“对”第二个同步范围“可见”。

    如果你真的想把它硬塞进缓存系统的概念中,不要把它当作缓存级别。将其视为单独的缓存。某些东西已经在某个缓存中并不意味着它在消费者需要的特定缓存中。

    【讨论】:

      【解决方案2】:

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

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

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

      你的管道屏障创建了一个执行依赖,但不是一个内存依赖。因此,在屏障之前由 CS 进程写入的值可供之后的 CS 进程使用,但对它们不可见。你需要有一个内存依赖来建立可见性。


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

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

      【讨论】:

        猜你喜欢
        • 2020-02-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-07-02
        • 1970-01-01
        • 1970-01-01
        • 2017-02-24
        相关资源
        最近更新 更多