Ручной рендеринг глубины: случайные результаты, несмотря на использование атомарных операций

Я визуализирую однопиксельные точки в текстуру uint32 с помощью вычислительного шейдера. текстура представляет собой трехмерную текстуру, x и y являются координатами области просмотра, z имеет информацию о глубине по координате 0 и дополнительные атрибуты по координате 1. Таким образом, если хотите, две созданные вручную цели рендеринга. код выглядит так:

layout (r32ui, binding = 0) coherent volatile uniform uimage3D renderBuffer;
layout (rgba32f, binding = 1) restrict readonly uniform imageBuffer pointBuffer;

for(int j = 0; j < numPoints / gl_WorkGroupSize.x + 1; j++)
{
    vec4 point = imageLoad(pointBuffer, ...)
    // ... transform point ...
    uint originalDepth = imageAtomicMin(renderBuffer, ivec3(imageCoords, 0), point.depth);
    if (originalDepth >= point.depth)
    {
        // write happened, store the attributes
        imageStore(renderBuffer, ivec3(imageCoords, 1), point.attributes);
    }
}

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

порядок точек в pointBuffer является случайным (но я убедился, что набор всех точек всегда один и тот же), поэтому моей первой мыслью было, что два одинаковых значения глубины могут изменить вывод, в зависимости от того, какое из них будет первым. поэтому я сделал так: if originalDepth == point.depth он использует imageAtomicMax, чтобы всегда иметь один и тот же из двух альтернативных атрибутов, но это ничего не изменило.

я разбросал barrier() и memoryBarrier() повсюду, но это ничего не изменило. Я также удалил все расходящиеся потоки управления для этого, ничего не изменив.

уменьшение размера локальной работы до 32 убирает 90% мерцания, но часть все равно остается.

Любые идеи очень приветствуются.

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


person karyon    schedule 02.08.2016    source источник


Ответы (1)


Проблема в следующем: у вас есть состояние гонки при записи в renderBuffer. Если два разных вызова CS сопоставляются с одним и тем же пикселем, и оба они решают записать значение, то в вашем вызове imageStore возникает гонка. Одно может быть перезаписано другим, это может быть частичная перезапись или что-то совсем другое. Но в любом случае не факт, что это сработает.

Это лучше всего решить, сделав то, что делают растеризаторы: разбейте процесс на две отдельные фазы. Первая фаза выполняет часть ... transform point ..., записывая эти данные в буфер. Затем вторая фаза проходит по точкам и записывает их в окончательное изображение.

На этапе 2 каждый вызов CS выполняет все обработку для определенного выходного пикселя. Таким образом, нет никаких условий гонки. Конечно, для этого требуется, чтобы на этапе 1 данные производились таким образом, чтобы их можно было упорядочить для каждого пикселя.

Есть несколько способов сделать последнее. Вы можете использовать связанный список со списком для каждого пикселя. Или вы можете использовать список для каждой рабочей группы, где рабочая группа представляет некоторую область X/Y пространства пикселей. В этом случае вы должны использовать локальную разделяемую память в качестве локального буфера глубины, при этом все вызовы CS будут читать/записывать в эту область. После того, как все они обработают пиксели, вы записываете их в реальную память. По сути, вы будете реализовывать рендеринг на основе тайлов вручную.

В самом деле, если у вас есть много таких точек, решение на основе тайлов позволит вам включить конвейерную обработку, чтобы вам не приходилось ждать завершения всего этапа 1 перед тем, как приступить к некоторым фазы 2. Вы можете разбить фазу 1 на части. Вы запускаете пару фрагментов фазы 1, затем фрагмент фазы 2, который считывается из первой фазы 1, затем еще один фрагмент фазы 1 и так далее.

Vulkan с его системой событий имеет лучшие инструменты для построения такой эффективной цепочки зависимостей, чем OpenGL.

person Nicol Bolas    schedule 02.08.2016
comment
Я сам написал в чертовом комментарии прямо над этим магазином изображений, указав здесь потенциальное состояние гонки, но по какой-то причине решил, что он не может быть ответственным. Вероятно, я слишком сосредоточился на этом... В любом случае, спасибо :) Я визуализирую в атлас карт теней 64x64 и уже имею одну рабочую группу на SM, но это слишком много, чтобы иметь буфер глубины в общей памяти. Два вопроса: разве список для каждой рабочей группы не просто перемещает проблему из глобальной в общую память, и мне также нужно решить проблему гонки? Во-вторых, для конвейерной обработки потребуется несколько диспетчерских вызовов, не так ли? - person karyon; 02.08.2016
comment
Разве список для каждой рабочей группы не просто перемещает проблему из глобальной в разделяемую память, и мне также нужно решить проблему гонки? Да, но есть вещи, которые вы можете решить это там, которые относительно дешевы. - person Nicol Bolas; 02.08.2016
comment
Было бы неплохо указать, с чего начать, так как мои знания здесь заканчиваются, и этот материал вряд ли можно найти в Google... - person karyon; 02.08.2016
comment
мое исправление состояло в том, чтобы прочитать записанное значение после imageAtomicMin (и добавленного groupMemoryBarrier) и проверить, действительно ли оно является записанным. Это работает до тех пор, пока ни одна другая рабочая группа не получит доступ к тем же пикселям. При работе в общей памяти можно просто записывать в общую память в цикле до тех пор, пока запись не завершится успешно, см. «Растеризация высокопроизводительного программного обеспечения на графических процессорах» Лэйна и Карраса. - person karyon; 03.08.2016