Проблема барьера сжатия параллельного буфера OpenCL
Как школьный проект, мы работаем над параллельным raytracer с OpenCL. Это наш первый проект, использующий OpenCL, поэтому у нас могут быть некоторые непонимания по этому поводу.
Мы пытаемся реализовать параллельное сжатие буфера для удаления готовых лучей или лучей, которые ни с чем не сталкивались, поэтому на следующей итерации будет обрабатываться меньше данных. В основном, у нас есть буфер как много s_ray_states
по мере необходимости для рендеринга, отслеживания их, получения данных о столкновениях, сжатия буфера таким образом, чтобы были только лучи, которые сталкивались с объектом внутри него, а затем затеняли их.
Итак, у нас есть буфер uint *prefix_sum
который содержит индексы, по которым каждый s_ray_state
должен быть перемещен в буфер s_ray_state *ray_states
чтобы уменьшить количество лучей, которые отправляются ядру шейдинга, и следующие итерации ядер трассировки / тени.
К сожалению, ray_sort
Ядро ниже, кажется, не работает правильно, мы проверили ввод prefix_sum
данные, которые на 100% верны, то же самое для ray_states
буфер, но мы получаем нежелательные данные в выводе.
Мы запускаем одну рабочую группу (глобальный размер работы = локальный размер работы), лучи всегда перемещаются в буфере с меньшим индексом, чем их оригинал. Мы поставили барьеры и используем s_ray_state *tmp
буфер для предотвращения параллельного выполнения для записи данных друг друга, но он, кажется, не работает, даже если убрать барьеры, мы получили бы тот же результат.
Мы оба работали над этим в течение 4 дней и уже обратились за помощью к другим студентам, но, похоже, никто не может понять, в чем дело. Возможно, мы недостаточно понимаем барьеры / барьеры, чтобы быть уверенными, что это действительно работает.
Мы уже пытались сделать один рабочий элемент в одной рабочей группе, отсортировав весь массив, который работает и даже дает лучшую производительность.
Код ниже должен работать? С нашим пониманием OpenCL, оно должно работать, и мы провели много исследований, но так и не получили четкого ответа.
kernel void ray_sort(
global read_only uint *prefix_sum,
global read_write struct s_ray_state *ray_states,
global read_only uint *ray_states_size,
local read_write struct s_ray_state *tmp
)
{
int l_size = get_local_size(0);
int l_id = get_local_id(0);
int group_id = -1;
int group_nb = *ray_states_size / l_size;
int state_id;
while (++group_id < group_nb)
{
state_id = group_id * l_size + l_id;
tmp[l_id] = ray_states[state_id];
barrier(CLK_LOCAL_MEM_FENCE);
if (did_hit(tmp[l_id]))
ray_states[prefix_sum[state_id]] = tmp[l_id];
barrier(CLK_GLOBAL_MEM_FENCE);
}
}
ray_states
длина ray_states_size
prefix_sum
содержит индексы, по которым каждый ray_states
элемент должен быть перемещен в
tmp
это локальный буфер размера local_work_size
local_work_size
знак равно global_work_size
did_hit()
возвращает 1, если луч попадает на объект, 0 в противном случае
Мы ожидаем ray_states
элементы для перемещения в индексы, содержащиеся в prefix_sum
Пример: каждый ray_states[id]
переезжает в prefix_sum[id]
индекс в ray_states
prefix_sum: 0 | 0 | 1 | 1 | 2 | 3 | 3 | 3 | 4
did_hit(ray_states[id]): 0 | 1 | 0 | 1 | 1 | 0 | 0 | 1 | 0
did_hit(output[id]): 1 | 1 | 1 | 1 | X | X | X | X | X
X
может быть что угодно
1 ответ
Я мог бы быть здесь совсем, но мне кажется, did_hit(ray_states[state_id])
вы читаете тот же кусок глобальной памяти, который вы помещаете в локальный буфер памяти tmp, всего на 2 строки выше. Что не будет проблемой, за исключением того, что вы используете этот буфер для ввода и вывода.
То, как я это вижу, на самом деле происходит на оборудовании:
tmp[l_id] = ray_states[state_id];
tmp[l_id] = ray_states[state_id];
tmp[l_id] = ray_states[state_id];
tmp[l_id] = ray_states[state_id];
tmp[l_id] = ray_states[state_id];
... local-work-size times
barrier(CLK_LOCAL_MEM_FENCE);
if (did_hit(ray_states[state_id]))
ray_states[prefix_sum[state_id]] = tmp[l_id];
if (did_hit(ray_states[state_id]))
ray_states[prefix_sum[state_id]] = tmp[l_id];
if (did_hit(ray_states[state_id]))
ray_states[prefix_sum[state_id]] = tmp[l_id];
if (did_hit(ray_states[state_id]))
ray_states[prefix_sum[state_id]] = tmp[l_id];
... again local-work-size times
Учитывая, что порядок параллельного выполнения WItem не определен (аппаратное обеспечение может выбирать любой порядок, который он хочет), это приведет к случайным результатам. Можете ли вы попробовать это вместо этого:
if (did_hit(tmp[l_id]))
ray_states[prefix_sum[state_id]] = tmp[l_id];
Кстати, если ray_states_size
это просто целое число, вы можете передать его напрямую, сделав аргумент "uint ray_states_size". Не нужно дурачиться там с буферами.
EDIT1: мое предложение будет работать только если prefix_sum[state_id]
не имеет дубликатов в каждом идентификаторе локального размера, иначе все равно будет гонка данных. Так, например, если для обоих state_id
-s 1 и 3 prefix_sum[state_id]
массив имеет 0, и ваш локальный размер рабочей группы>= 4, будет гонка данных.
Кроме того, есть ли действительно веская причина, по которой вы должны использовать один и тот же буфер для ввода и вывода? мне кажется, было бы намного проще, если бы у вас были отдельные входные / выходные буферы.
EDIT2: я только что заметил, что вы сказали, что "лучи всегда перемещаются в буфере с меньшим индексом, чем их оригинал" (извините, я пропустил это). Это хорошо, но недостаточно - всегда ли они перемещаются к меньшему индексу, чем индекс любого другого луча в той же локальной рабочей группе? если да, хорошо, но есть еще другая гонка данных, которую я упомянул.