Рендеринг в пользовательский буфер кадров с использованием одной и той же текстуры как на входе, так и на выходе

Некоторые шейдеры фрагментов в ShaderToy (например, гидродинамика, https://www.shadertoy.com/view/4tGfDW ) используют один и тот же буфер как для ввода, так и для вывода. Но когда я пытаюсь сделать это в своем коде C/C++, это не работает (я визуализирую странные артефакты шахматной доски, такие как непоследовательная зрительная память). Чтобы обойти эту проблему, я должен использовать два разных FrameBuffers A, B и перевернуть текстуры (сначала визуализировать A в B, а затем визуализировать B обратно в A)

Я понимаю, что OpenGL не позволяет использовать одну и ту же текстуру как для ввода, так и для вывода (?) из-за проблем с согласованностью памяти. Но нет ли более элегантного решения, чем использование двух FrameBuffers? Например. используя некоторую блокировку или временный кеш (я не знаю какой-либо флаг синхронизации, который позаботится об этом) ???

EDIT — Подробная информация для ответа на комментарий/вопрос:

OpenGL (в зависимости от версии GL) имеет некоторые очень специфические правила того, что можно и что нельзя делать, когда одна и та же текстура используется в качестве цели рендеринга и ввода сэмплера. Может ли ваш вариант использования быть реализован в рамках этого набора требований или нет, неясно, поскольку вы не объяснили, что именно вам нужно или вы хотите сделать здесь.

в основном я хочу реализовать решатель Fluid-Dynamics (например, из ShaderToy, указанный выше), а также другие решатели уравнений в частных производных. Это означает, что вывод каждого пикселя зависит от некоторой маски свертки (производной, лапласиана, среднего) соседних пикселей. Также может быть некоторое движение (адвекция), что означает, что значения считывания формируют удаленные пиксели.

В настоящее время я понял, что артефакты появляются в основном, когда я читаю/записываю пиксели, которые находятся в другом месте, т.е. это не локально (например, пиксель[100,100] зависит от пикселя[10,10])

Пример простого решения Fluid-Solver от Shadertoy:

vec4 solveFluid(sampler2D smp, vec2 uv, vec2 w, float time, vec3 mouse, vec3 lastMouse)
{
    const float K = 0.2;
    const float v = 0.55;
    
    vec4 data = textureLod(smp, uv, 0.0);
    vec4 tr = textureLod(smp, uv + vec2(w.x , 0), 0.0);
    vec4 tl = textureLod(smp, uv - vec2(w.x , 0), 0.0);
    vec4 tu = textureLod(smp, uv + vec2(0 , w.y), 0.0);
    vec4 td = textureLod(smp, uv - vec2(0 , w.y), 0.0);
    
    vec3 dx = (tr.xyz - tl.xyz)*0.5;
    vec3 dy = (tu.xyz - td.xyz)*0.5;
    vec2 densDif = vec2(dx.z ,dy.z);
    
    data.z -= dt*dot(vec3(densDif, dx.x + dy.y) ,data.xyz); //density
    vec2 laplacian = tu.xy + td.xy + tr.xy + tl.xy - 4.0*data.xy;
    vec2 viscForce = vec2(v)*laplacian;
    data.xyw = textureLod(smp, uv - dt*data.xy*w, 0.).xyw; //advection
    
    vec2 newForce = vec2(0);
    data.xy += dt*(viscForce.xy - K/dt*densDif + newForce); //update velocity
    data.xy = max(vec2(0), abs(data.xy)-1e-4)*sign(data.xy); //linear velocity decay
    
    #ifdef USE_VORTICITY_CONFINEMENT
    data.w = (tr.y - tl.y - tu.x + td.x);
    vec2 vort = vec2(abs(tu.w) - abs(td.w), abs(tl.w) - abs(tr.w));
    vort *= VORTICITY_AMOUNT/length(vort + 1e-9)*data.w;
    data.xy += vort;
    #endif
    
    data.y *= smoothstep(.5,.48,abs(uv.y-0.5)); //Boundaries
    
    data = clamp(data, vec4(vec2(-10), 0.5 , -10.), vec4(vec2(10), 3.0 , 10.));
    
    return data;
}

person Prokop Hapala    schedule 11.10.2020    source источник
comment
Возможно EXT_shader_framebuffer_fetch (gl_LastFragData)   -  person Rabbid76    schedule 11.10.2020
comment
OpenGL (в зависимости от версии GL) имеет некоторые очень специфические правила того, что можно и что нельзя делать, когда одна и та же текстура используется в качестве цели рендеринга и ввода сэмплера. Может ли ваш вариант использования быть реализован в рамках этого набора требований или нет, неясно, поскольку вы не объяснили, что именно вам нужно или вы хотите сделать здесь.   -  person derhass    schedule 11.10.2020


Ответы (1)


В настоящее время я понял, что артефакты появляются в основном, когда я читаю/записываю пиксели, которые находятся в другом месте, т.е. это не локально (например, пиксель[100,100] зависит от пикселя[10,10])

Да, это никогда не сработает на графических процессорах, поскольку нет никаких конкретных гарантий относительно порядка вызовов отдельных фрагментных шейдеров. Таким образом, если вызов, записывающий в пиксель [100,100], увидит результаты вызова, записывающего в [10,10], или исходные данные будут полностью случайными. В соответствии со спецификацией вы получаете неопределенные значения при чтении в таком одновременном сценарии чтения/записи, поэтому теоретически вы можете получить даже не одно или другое, а увидеть частичную запись или совершенно другие значения (хотя это вряд ли произойдет на реальном оборудовании).

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

Чтобы обойти эту проблему, я должен использовать два разных FrameBuffers A, B и перевернуть текстуры (сначала визуализировать A в B, а затем визуализировать B обратно в A)

Да, подход пинг-понга — это то, что вы должны сделать для этого варианта использования. И, честно говоря, в этом сценарии это не должно привести к значительному снижению производительности, поскольку вы все равно записываете в каждый выходной пиксель один раз, поэтому вам не нужна дополнительная копия нетронутых пикселей. Так что все, что стоит, это дополнительная память.

person derhass    schedule 12.10.2020
comment
благодаря. Да, это не увеличивает производительность (если я разделяю четные и нечетные кадры), но усложняет программу. - person Prokop Hapala; 13.10.2020