2016-05-05 9 views
3

Есть ли элегантный способ справиться с полями массива в Compute Shaders? (учитывая, что вы должны иметь размер рабочей группы, жестко закодированной в шейдере)Любой элегантный способ справиться с полями массива в OpenGL 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 элементов?

ответ

2

Что касается работы с лишними, необычными данными, это легко: выделить больше места. Вызов диспетчера работает со всеми краткими рабочими группами. Таким образом, вы должны убедиться, что есть достаточное хранилище для того, что вы отправляете.

Просто оставьте его неинициализированным для входного буфера и игнорировать его, когда вы читаете вывод в

Но есть и другие проблемы, связанные с вашим шейдером, которые мешают им работать с диспетчерскими вызовами:.


Вы специально разработали свой шейдер, чтобы работать только для одной групповой отправки. То есть, независимо от того, сколько рабочих групп вы отправляете, все они будут читать и писать одни и те же данные.

Во-первых, as previously discussed, прекратите предоставление абсолютной длины буферным данным. Вы не знаете, сколько рабочих групп будет вызываться во время компиляции; это решение времени исполнения. Поэтому определите размер выполнения массива.

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; dot-продукт просто превращает 3D-местоположение в 1D-индекс.

В-третьих, в вашей фактической логике используйте 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 индекс всегда учитывает все размеры локального размера.

+0

Спасибо за ваши наблюдения. Но я не уверен, что понимаю ответ на мой вопрос. Имея массив из 3000 элементов, для которого я хочу вычислить сумму префикса, лучше всего сделать это, чтобы наложить его на 4096 элементов и просто игнорировать результаты, которые меня не волнуют (при условии, что я сохраняю local_size_x = 1024)? – markwalberg

+0

@BogdanPetcu: Это общая идея. Я не уверен, сколько перекрестных помех есть в ваших рабочих элементах, поэтому меньшие локальные размеры могут быть лучше. –