我有以下计算着色器:
#version 450
layout (local_size_x = 128, local_size_y = 1, local_size_z = 1) in;
layout(push_constant) uniform PushConstant
{
vec2 topLeft;
vec2 bottomRight;
};
struct Position {
float x, y, z;
};
layout (set=0, binding=0) buffer PositionBuffer
{
Position positions[];
};
layout (set=0, binding=1) buffer SelectionBuffer
{
uint selected[];
};
void main()
{
uint ind = gl_GlobalInvocationID.z * (gl_WorkGroupSize.x * gl_NumWorkGroups.x) * (gl_WorkGroupSize.y * gl_NumWorkGroups.y)
+ gl_GlobalInvocationID.y * (gl_WorkGroupSize.x * gl_NumWorkGroups.x)
+ gl_GlobalInvocationID.x;
Position pos = positions[ind];
selected[ind] = 0;
if(pos.x > topLeft.x && pos.x < bottomRight.x && pos.y > topLeft.y && pos.y < bottomRight.y)
{
selected[ind] = 1;
}
}
它的作用是检查一个点(来自 positions
缓冲区)是否位于用户提供的矩形(来自 PushConstant
)内。如果是 - 着色器通过将 1
写入选定的
缓冲区来标记该点。
这段代码工作正常。但由于我没有计算经验,所以我正在寻找让它变得更好的方法。我知道整个组都可以访问共享变量。这个想法是创建一个共享位置数组并将其填充到一个线程中,假设线程号为 0。那么,理论上,其他线程不需要读取缓冲内存,而是需要读取速度更快的共享内存。
值得吗?
如何正确同步?
我可以做类似的事情来将数据写入 selected
数组吗?
最佳答案
从你整体运营的角度来看。按照顺序,您:
- 读取单个连续的内存块。
- 对该内存的每个值执行一次操作。
- 将该操作的结果写入另一个内存块。
您的代码在任何时候都不需要多次读取该值。虽然所编写的代码确实可能会两次写入一个值,但没有理由必须这样做。您可以轻松地根据条件计算一个值,然后将该值写入内存。我假设一个好的编译器会将您的代码准确地翻译成这样。
因为没有线程同时读取或写入多个位置,所以对内存的缓存访问只会有帮助,因为它允许将“读取 X 字节”转换为更有效的“读取缓存行字节”读取。尝试从恰好位于同一高速缓存行中的地址读取的两个调用应该只执行一次内存提取。写作也是如此;写入同一缓存行的多次调用应聚合为一次写入。
当然,这假设硬件合理。
假设这样的系统仍然有可能调用同一内存的多次读/写。这与扭曲/波前的调用次数有关(即:以锁步执行的着色器的调用次数)。如果每个 warp 读取的数据大小未与缓存对齐,则两个 warp 可能会向同一缓存行发出读取,因为不同的 warp 可能会同时执行。写入也是如此。但即使如此,也假设缓存和执行内存获取的决策是基于每个 warp 进行的。
无论如何,如果确定是这种情况,正确的解决方案是尽可能地调整您的阅读,而不是尝试为其完成缓存的工作。
有时预缓存数据会很有用,但这主要是在调用频繁从相同地址读取的情况下,并且通常是在它们从彼此的内存中读取时。即使如此,这也是您应该分析的内容,而不是尝试先验地以这种方式进行编码。关于glsl - 将 "preload"数据传输到计算着色器的共享存储以实现更快的读取访问是否有意义?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54673799/