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

#opengl-es #glsl #framebuffer #render-to-texture

#opengl-es #glsl #фреймбуфер #рендеринг в текстуру

Вопрос:

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

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

РЕДАКТИРОВАТЬ — Подробности, чтобы ответить на комментарий / вопрос:

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

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

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

Пример простого жидкостного решателя от 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;
}
  

Комментарии:

1. Возможно, EXT_shader_framebuffer_fetch ( gl_LastFragData )

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

Ответ №1:

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

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

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

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

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

Комментарии:

1. Спасибо. Да, это не увеличивает стоимость производительности (если я разделяю четные и нечетные кадры), но это добавляет некоторую сложность программе