Какой-нибудь элегантный способ работы с полями массива в вычислительных шейдерах OpenGL?

Есть ли какой-нибудь элегантный способ работы с полями массива в вычислительных шейдерах? (учитывая, что вы должны иметь размер рабочей группы, жестко запрограммированный в шейдере)

Рассмотрим следующий код шейдера, который вычисляет сумму префиксов для массива 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 элементов?


person markwalberg    schedule 05.05.2016    source источник


Ответы (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 — глобальное трехмерное местоположение каждого рабочего элемента. Каждый рабочий элемент будет иметь уникальный 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 индекс всегда будет учитывать все измерения локального размера.

person Nicol Bolas    schedule 05.05.2016
comment
Спасибо за ваши наблюдения. Но я не уверен, что понимаю ответ на свой вопрос. Имея массив из 3000 элементов, для которого я хочу вычислить сумму префикса, лучшее, что я могу сделать, это дополнить его, чтобы он имел 4096 элементов, и просто игнорировать результаты, которые мне не нужны (при условии, что я сохраняю local_size_x = 1024)? - person markwalberg; 05.05.2016
comment
@BogdanPetcu: Это общая идея. Я не уверен, сколько перекрестных помех есть в ваших рабочих элементах, поэтому меньшие локальные размеры могут быть лучше. - person Nicol Bolas; 05.05.2016