Vulkan计算着色器缓存和屏障

4

我试图理解整个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
2个回答

5
Vulkan内存模型不关心缓存是否存在。它的模型是建立在可用性和可见性的概念上的。如果GPU命令/阶段A生成的值与GPU命令/阶段B有执行依赖关系,则该值对GPU命令/阶段B是“可用的”。如果GPU命令/阶段A与GPU命令/阶段B就特定内存及其访问模式产生内存依赖关系,那么GPU命令/阶段A生成的值对GPU命令/阶段B是“可见的”,并且通过尝试访问未同时满足可用性和可见性的值会导致未定义行为。
可用性和可见性的实现将涉及清除缓存等操作。但就Vulkan内存模型而言,这是一个它不关心的实现细节。您也不应该关心此类细节,而应了解Vulkan内存模型并编写在其中工作的代码。
您的管线障碍创建了执行依赖性,但没有创建内存依赖性。因此,在障碍之前由CS进程写入的值对障碍之后的CS进程是可用的,但对它们不可见。您需要有内存依赖关系才能建立可见性。
然而,如果您想要GPU层面的理解......所有这些都取决于GPU。GPU是否具有缓存层次结构、L1/L2分裂?也许有些是,也许没有。
这种问题有点无关紧要,因为仅仅向内存地址写入一个值并不相当于刷新该内存周围的适当缓存。即使使用“coherent”限定符,也只能对在同一调度调用中执行的计算着色器操作进行刷新。它不能保证影响稍后的调度调用。

2

实现相关。我们所知道的是,某些设备可能根本没有缓存,或者在未来可能会有一些量子魔法。

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

完全摆脱缓存相关的所有思维包袱。

重要的是当你读取某个东西时,那个东西需要对于读取代理(无论你使用什么设备,以及它可能具有的奇怪内存架构)“可见”。如果它不“可见”,那么你可能会读到垃圾。

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

首先,您需要将您的写入放入内存依赖关系的src*部分(例如通过管线障碍)。这将使您的写入“可用于”。

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

如果您真的想把它塞进缓存系统的概念中,不要把它看作缓存级别。把它看作是独立的缓存。某些东西已经在某个缓存中并不意味着它在消费者需要的特定缓存中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接