Любой изящный способ справиться с полями массива в OpenGL Compute Shaders?
Есть ли какой-нибудь элегантный способ справиться с полями массива в Compute Shaders? (учитывая, что вы должны иметь размер рабочей группы, жестко закодированный в шейдере)
Рассмотрим следующий код шейдера, который вычисляет сумму префикса для массива 2048, если вызывается с помощью glDispatchCompute(1,1,1):
#version 430 core
layout (local_size_x = 1024) in;
layout (binding = 0) coherent readonly buffer block1
{
float input_data[gl_WorkGroupSize.x];
};
layout (binding = 1) coherent writeonly buffer block2
{
float output_data[gl_WorkGroupSize.x];
};
shared float shared_data[gl_WorkGroupSize.x * 2];
void main(void)
{
uint id = gl_LocalInvocationID.x;
uint rd_id;
uint wr_id;
uint mask;
const uint steps = uint(log2(gl_WorkGroupSize.x)) + 1;
uint step = 0;
shared_data[id * 2] = input_data[id * 2];
shared_data[id * 2 + 1] = input_data[id * 2 + 1];
barrier();
for (step = 0; step < steps; step++)
{
mask = (1 << step) - 1;
rd_id = ((id >> step) << (step + 1)) + mask;
wr_id = rd_id + 1 + (id & mask);
shared_data[wr_id] += shared_data[rd_id];
barrier();
}
output_data[id * 2] = shared_data[id * 2];
output_data[id * 2 + 1] = shared_data[id * 2 + 1];
}
Но что, если я хочу вычислить сумму префикса для массива из 3000 элементов?
1 ответ
Что касается работы с лишними, неиспользованными данными, это просто: выделите больше места. Диспетчерские вызовы работают на целых нескольких рабочих групп. Таким образом, вы должны убедиться, что есть достаточно места для того, что вы отправляете.
Просто оставьте его неинициализированным для входного буфера и игнорируйте его при чтении вывода в.
Но есть и другие проблемы с вашим шейдером, которые мешают им работать с диспетчерскими вызовами:
Вы специально разработали свой шейдер так, чтобы он работал только для одной отправки рабочей группы. То есть, независимо от того, сколько рабочих групп вы отправите, все они будут читать и записывать одни и те же данные.
Во-первых, как обсуждалось ранее, прекратите давать абсолютную длину буферным данным. Вы не знаете, сколько рабочих групп будет вызвано во время компиляции; это решение во время выполнения. Так что определите размер массива во время выполнения.
layout (binding = 0) readonly buffer block1
{
float input_data[];
};
layout (binding = 1) writeonly buffer block2
{
float output_data[];
};
Также обратите внимание на отсутствие coherent
, Вы не используете эти буферы никоим образом, который бы требовал этого квалификатора.
Ваш shared
данные все еще должны иметь размер.
Во-вторых, каждый рабочий элемент отвечает за чтение определенного значения из input_data
и писать конкретное значение для output_data
, В вашем текущем коде этот индекс id
, но ваш текущий код вычисляет его только на основе индекса рабочего элемента в рабочей группе. Чтобы вычислить его для всех рабочих элементов во всех рабочих группах, сделайте следующее:
const uint id = dot(gl_GlobalInvocationID,
vec3(1, gl_NumWorkGroups.x, gl_NumWorkGroups.y * gl_NumWorkGroups.x)
Точечный продукт - это просто модный способ умножения, а затем суммирования компонентов. gl_GlobalInvocationID
является 3D-местоположением в глобальном масштабе каждого рабочего элемента. Каждый рабочий элемент будет иметь уникальный gl_GlobalInvocationId
; точечный продукт просто превращает трехмерное местоположение в одномерный индекс.
В-третьих, в вашей реальной логике используйте gid
только для доступа к данным в ваших буферах. При доступе к данным в вашем общем хранилище, вы должны использовать gl_LocalInvocationIndex
(что по сути то, что id
раньше был):
const uint lid = gl_LocalInvocationIndex;
shared_data[lid * 2] = input_data[id * 2];
shared_data[lid * 2 + 1] = input_data[id * 2 + 1];
for (step = 0; step < steps; step++)
{
mask = (1 << step) - 1;
rd_id = ((lid >> step) << (step + 1)) + mask;
wr_id = rd_id + 1 + (lid & mask);
shared_data[wr_id] += shared_data[rd_id];
barrier();
}
output_data[id * 2] = shared_data[lid * 2];
output_data[id * 2 + 1] = shared_data[lid * 2 + 1];
Лучше использовать gl_LocalInvocationIndex
вместо gl_LocalInvocationID.x
потому что когда-нибудь вам может понадобиться больше рабочих элементов в рабочей группе, чем вы можете получить только с одним измерением локального размера. С gl_LocalInvocationIndex
Индекс всегда будет учитывать все размеры локального размера.