GLSL: How to do switch-like statement - glsl

I would like to dynamically invoke an easing based on the data passed into the shader. So in pseudocode:
var easing = easings[easingId]
var value = easing(point)
I'm wondering the best way to accomplish this in GLSL. I could use a switch statement in some way, or could perhaps put the easings into an array and use them like that. Or maybe there is a way to create a hashtable and use it like the above example.
easingsArray = [
cubicIn,
cubicOut,
...
]
uniform easingId
main() {
easing = easingsArray[easingId]
value = easing(point)
}
That would be the potential array approach. Another approach is the switch statement. Maybe there are others. Wondering what the recommended way is of doing this. Maybe I could use a struct somehow...

If you need conditional branching in GLSL (in your case for selecting an easing function based on a variable) you'll need to use if or switch statements.
For example
if (easingId == 0) {
result = cubicIn();
} else if (easingId == 1) {
result = cubicOut();
}
or
switch (easingId) {
case 0:
result = cubicIn();
break;
case 1:
result = cubicOut();
break;
}
GLSL has no support for function pointers, so the kind of dynamic dispatch solutions you are considering (tables of function pointers etc.) will unfortunately not be possible.
Whilst your question was explicitly about data being passed into the shader, I would also like to point out that if the value controlling the branch is being passed into the shader as a uniform, then you could instead compile multiple variants of your shader, and then dynamically select the right one (i.e. the one using the right easing function) from the application itself. This would save the cost of branching in the shader.

Apart from conditional branching with if-else or switch, you may also want to have a look at the GLSL subroutines, this allows you to control the shader code branching on the C++ side, without touching the shader. Theoretically, this approach should be more efficient than using if-else, but the downside is that subroutines are not supported in SPIR-V, which is the future of shaders.
A subroutine is very much like a function pointer in C, you define multiple functions of the same "subroutine" type, and then use a subroutine uniform to control which function should be called at runtime. Here's an example of debug drawing a framebuffer using subroutines.
#version 460
// this shader is used by: `FBO::DebugDraw()`
////////////////////////////////////////////////////////////////////////////////
#ifdef vertex_shader
layout(location = 0) in vec3 position;
layout(location = 1) in vec3 normal;
layout(location = 2) in vec2 uv;
layout(location = 0) out vec2 _uv;
void main() {
_uv = uv;
gl_Position = vec4(position.xy, 0.0, 1.0);
}
#endif
////////////////////////////////////////////////////////////////////////////////
#ifdef fragment_shader
layout(location = 0) in vec2 _uv;
layout(location = 0) out vec4 color;
layout(binding = 0) uniform sampler2D color_depth_texture;
layout(binding = 1) uniform usampler2D stencil_texture;
const float near = 0.1;
const float far = 100.0;
subroutine vec4 draw_buffer(void); // typedef the subroutine (like a function pointer)
layout(location = 0) subroutine uniform draw_buffer buffer_switch;
layout(index = 0)
subroutine(draw_buffer)
vec4 DrawColorBuffer() {
return texture(color_depth_texture, _uv);
}
layout(index = 1)
subroutine(draw_buffer)
vec4 DrawDepthBuffer() {
// sampling the depth texture format should return a float
float depth = texture(color_depth_texture, _uv).r;
// depth in screen space is non-linear, the precision is high for small z-values
// and low for large z-values, we need to linearize depth values before drawing
float ndc_depth = depth * 2.0 - 1.0;
float z = (2.0 * near * far) / (far + near - ndc_depth * (far - near));
float linear_depth = z / far;
return vec4(vec3(linear_depth), 1.0);
}
layout(index = 2)
subroutine(draw_buffer)
vec4 DrawStencilBuffer() {
uint stencil = texture(stencil_texture, _uv).r;
return vec4(vec3(stencil), 1.0);
}
void main() {
color = buffer_switch();
}
#endif

Related

glsl conditionally assigning out_color

I'm trying to achieve blending by using a shader to conditionally assign the fragment color.
Full shader code...
#version 450
#pragma shader_stage(fragment)
layout(binding = 1) uniform sampler2D texture_sampler;
layout(location = 0) in vec2 tex_coord;
layout(location = 1) in vec4 _rgba;
layout(location = 0) out vec4 out_color;
void main() {
vec4 oc = textureLod(texture_sampler, tex_coord, 0);
if (oc.r > _rgba.r)
oc.r = _rgba.r;
if (oc.g > _rgba.g)
oc.g = _rgba.g;
if (oc.b > _rgba.b)
oc.b = _rgba.b;
if (oc.a > _rgba.a)
oc.a = _rgba.a;
float threshhold = .5;
if (oc.a > threshhold)
out_color = oc;
}
(this flickers at run time btw...)
changing the last bit of code to...
float threshhold = .5;
//if (oc.a > threshhold)
out_color = oc;
does this...
If I remove the assignment completely, the text just dissappears. It seems that having the assignment there makes the driver expect it to always be assigned. Does anyone know why this is happening? Why can't I conditionally leave the pixel unmodified? Is there something I have to change in my Vulkan code for the pipeline?
This declaration in a shader
layout(location = 0) out vec4 out_color;
is basically telling the pipeline that whatever value out_color ends up with at the end of execution is what should go in the framebuffer. You're not allowed to just not assign a value. If you fail to write the value it's the same as having an uninitialized variable in C. The contents will be random based on whatever happened to be in that memory address at the start of execution.
From the GL wiki (but still applicable to GLSL shaders for Vulkan)
The shader must set all output variables at some point in its execution
The only two exceptions this. The first is if the output variable isn't being read by subsequent stages. In this case, there are no subsequent stages, and the variable is the output going to the framebuffer, so that's not applicable.
The second is in fragment shaders where you use the discard keyword. You're not using discard in your shader, but most likely that would fix your problem. Try changing your code to
if (oc.a > threshhold)
out_color = oc;
else
discard;
However, bear in mind that discard as a keyword can impact performance of the entire pipeline because it can no longer assume that every evaluation of the shader will produce output. See this other question

OpenGL: won't render color (from uniform) unless printing to the console

Using OpenGL i've displayed a simple square with some color in it. Today I tried to set the color (actually just the green value) of it using a uniform, which contains some sort of sinus from the current time. It looks the value of the uniform is just 0.0, as it shows no green (black when setting the others colours to 0.0), unless I add a print statement to the loop (I can place it anywhere). If I do so, it displays a square that nicely changes in colour.
What's going on?!
This is the main source:
// MAIN LOOP
while !window.should_close() {
// UPDATE STUFF
let time_value = glfwGetTime();
let green_value = ((time_value.sin() / 2.0) + 0.5) as GLfloat;
program.set_uniform1f("uGreenValue", green_value);
println!("yoo"); // it only works when this is somewhere in the loop
// RENDER STUFF
gl::Clear(gl::COLOR_BUFFER_BIT);
gl::DrawElements(gl::TRIANGLES, 6, gl::UNSIGNED_INT, 0 as *const GLvoid);
window.swap_buffers();
This is the vertex shader:
#version 330 core
layout (location = 0) in vec2 aPosition;
layout (location = 1) in float aRedValue;
uniform float uGreenValue;
out float redValue;
out float greenValue;
void main()
{
gl_Position = vec4(aPosition, 0.0, 1.0);
redValue = aRedValue;
greenValue = uGreenValue;
}
and this the fragment shader:
#version 330 core
out vec4 Color;
in float redValue;
in float greenValue;
void main()
{
Color = vec4(redValue, 0.0f, greenValue, 1.0f);
}
I think I found the problem! In the set_uniform functions I provided a non null terminated &str to the glGetUniFormLocation function, which did not work in every occasion. Using a &cstr solved it. I still have no idea what the print statements (and other functions calls, even empty functions) had to do with it though...

Banding Problem in Multi Step Shader with Ping Pong Buffers, does not happen in ShaderToy

I am trying to implement a Streak shader, which is described here:
http://www.chrisoat.com/papers/Oat-SteerableStreakFilter.pdf
Short explanation: Samples a point with a 1d kernel in a given direction. The kernel size grows exponentially in each step. Color values are weighted based on distance to sampled point and summed. The result is a smooth tail/smear/light streak effect on that direction. Here is the frag shader:
precision highp float;
uniform sampler2D u_texture;
varying vec2 v_texCoord;
uniform float u_Pass;
const float kernelSize = 4.0;
const float atten = 0.95;
vec4 streak(in float pass, in vec2 texCoord, in vec2 dir, in vec2 pixelStep) {
float kernelStep = pow(kernelSize, pass - 1.0);
vec4 color = vec4(0.0);
for(int i = 0; i < 4; i++) {
float sampleNum = float(i);
float weight = pow(atten, kernelStep * sampleNum);
vec2 sampleTexCoord = texCoord + ((sampleNum * kernelStep) * (dir * pixelStep));
vec4 texColor = texture2D(u_texture, sampleTexCoord) * weight;
color += texColor;
}
return color;
}
void main() {
vec2 iResolution = vec2(512.0, 512.0);
vec2 pixelStep = vec2(1.0, 1.0) / iResolution.xy;
vec2 dir = vec2(1.0, 0.0);
float pass = u_Pass;
vec4 streakColor = streak(pass, v_texCoord, dir, pixelStep);
gl_FragColor = vec4(streakColor.rgb, 1.0);
}
It was going to be used for a starfield type of effect. And here is the implementation on ShaderToy which works fine:
https://www.shadertoy.com/view/ll2BRG
(Note: Disregard the first shader in Buffer A, it just filters out the dim colors in the input texture to emulate a star field since afaik ShaderToy doesn't allow uploading custom textures)
But when I use the same shader in my own code and render using ping-pong FrameBuffers, it looks different. Here is my own implementation ported over to WebGL:
https://jsfiddle.net/1b68eLdr/87755/
I basically create 2 512x512 buffers, ping-pong the shader 4 times increasing kernel size at each iteration according to the algorithm and render the final iteration on the screen.
The problem is visible banding, and my streaks/tails seem to be losing brightness a lot faster: (Note: the image is somewhat inaccurate, the lengths of the streaks are same/correct, its color values that are wrong)
I have been struggling with this for a while in Desktop OpenGl / LWJGL, I ported it over to WebGL/Javascript and uploaded on JSFiddle in hopes someone can spot what the problem is. I suspect it's either about texture coordinates or FrameBuffer configuration since shaders are exactly the same.
The reason it works on Shadertoys is because it uses a floating-point render target.
Simply use gl.FLOAT as the type of your framebuffer texture and the issue is fixed (I could verify it with the said modification on your JSFiddle).
So do this in your createBackingTexture():
// Just request the extension (MUST be done).
gl.getExtension('OES_texture_float');
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, this._width, this._height, 0, gl.RGBA, gl.FLOAT, null);

How to access automatic mipmap level in GLSL fragment shader texture?

How do I determine what mipmap level was used when sampling a texture in a GLSL fragment shader?
I understand that I can manually sample a particular mipmap level of a texture using the textureLod(...) method:
uniform sampler2D myTexture;
void main()
{
float mipmapLevel = 1;
vec2 textureCoord = vec2(0.5, 0.5);
gl_FragColor = textureLod(myTexture, textureCoord, mipmapLevel);
}
Or I could allow the mipmap level to be selected automatically using texture(...) like
uniform sampler2D myTexture;
void main()
{
vec2 textureCoord = vec2(0.5, 0.5);
gl_FragColor = texture(myTexture, textureCoord);
}
I prefer the latter, because I trust the driver's judgment about appropriate mipmap level more than I do my own.
But I'd like to know what mipmap level was used in the automatic sampling process, to help me rationally sample nearby pixels. Is there a way in GLSL to access the information about what mipmap level was used for an automatic texture sample?
Below are three distinct approaches to this problem, depending on which OpenGL features are available to you:
As pointed out by Andon M. Coleman in the comments, the solution in OpenGL version 4.00 and above is simple; just use the textureQueryLod function:
#version 400
uniform sampler2D myTexture;
in vec2 textureCoord; // in normalized units
out vec4 fragColor;
void main()
{
float mipmapLevel = textureQueryLod(myTexture, textureCoord).x;
fragColor = textureLod(myTexture, textureCoord, mipmapLevel);
}
In earlier versions of OpenGL (2.0+?), you might be able to load an extension, to similar effect. This approach worked for my case. NOTE: the method call is capitalized differently in the extension, vs. the built-in (queryTextureLod vs queryTextureLOD).
#version 330
#extension GL_ARB_texture_query_lod : enable
uniform sampler2D myTexture;
in vec2 textureCoord; // in normalized units
out vec4 fragColor;
void main()
{
float mipmapLevel = 3; // default in case extension is unavailable...
#ifdef GL_ARB_texture_query_lod
mipmapLevel = textureQueryLOD(myTexture, textureCoord).x; // NOTE CAPITALIZATION
#endif
fragColor = textureLod(myTexture, textureCoord, mipmapLevel);
}
If loading the extension does not work, you could estimate the automatic level of detail using the approach contributed by genpfault:
#version 330
uniform sampler2D myTexture;
in vec2 textureCoord; // in normalized units
out vec4 fragColor;
// Does not take into account GL_TEXTURE_MIN_LOD/GL_TEXTURE_MAX_LOD/GL_TEXTURE_LOD_BIAS,
// nor implementation-specific flexibility allowed by OpenGL spec
float mip_map_level(in vec2 texture_coordinate) // in texel units
{
vec2 dx_vtc = dFdx(texture_coordinate);
vec2 dy_vtc = dFdy(texture_coordinate);
float delta_max_sqr = max(dot(dx_vtc, dx_vtc), dot(dy_vtc, dy_vtc));
float mml = 0.5 * log2(delta_max_sqr);
return max( 0, mml ); // Thanks #Nims
}
void main()
{
// convert normalized texture coordinates to texel units before calling mip_map_level
float mipmapLevel = mip_map_level(textureCoord * textureSize(myTexture, 0));
fragColor = textureLod(myTexture, textureCoord, mipmapLevel);
}
In any case, for my particular application, I ended up just computing the mipmap level on the host side, and passing it to the shader, because the automatic level-of-detail turned out to be not exactly what I needed.
From here:
take a look at the OpenGL 4.2 spec chapter 3.9.11 equation 3.21. The mip map level is calculated based on the lengths of the derivative vectors:
float mip_map_level(in vec2 texture_coordinate)
{
vec2 dx_vtc = dFdx(texture_coordinate);
vec2 dy_vtc = dFdy(texture_coordinate);
float delta_max_sqr = max(dot(dx_vtc, dx_vtc), dot(dy_vtc, dy_vtc));
return 0.5 * log2(delta_max_sqr);
}

memoryBarrier() behaving unexpectedly in Geometry Shader

I am trying to get a hold of how memoryBarrier() works in OpenGL 4.4
I tried the following once with a texture image and once with Shader Storage Buffer Object (SSBO).
The basic idea is to create an array of flags for however many objects that need to be rendered in my scene and then perform a simple test in the geometry shader.
For each primitive in GS, if at least one vertex passes the test, it
sets the corresponding flag in the array at the location specified
by this primitive's object ID (Object IDs are passed to GS as vertex
attributes).
I then perform a memoryBarrier() to make sure all threads have written their values.
Next, I have all primitives read from the flags array and only emit a vertex if the flag is set.
Here is some code from my shaders to explain:
// Vertex Shader:
#version 440
uniform mat4 model_view;
uniform mat4 projection;
layout(location = 0) in vec3 in_pos;
layout(location = 1) in vec3 in_color;
layout(location = 2) in int lineID;
out VS_GS_INTERFACE
{
vec4 position;
vec4 color;
int lineID;
} vs_out;
void main(void) {
vec4 pos = vec4(in_pos, 1.0);
vs_out.position = pos;
vs_out.color = vec4(in_colo, 1.0);
vs_out.lineID = lineID;
gl_Position = projection * model_view * pos;
}
and here is a simple Geometry shader in which I use only a simple test based on lineID ( I realize this test doesn't need a shared data structure but this is just to test program behavior)
#version 440
layout (lines) in;
layout (line_strip, max_vertices = 2) out;
layout (std430, binding = 0) buffer BO {
int IDs[];
};
in VS_GS_INTERFACE
{
vec4 position;
vec4 color;
int lineID;
} gs_in[];
out vec4 f_color;
void main()
{
if(gs_in[0].lineID < 500)
{
IDs[gs_in[0].lineID] = 1;
}
else
{
IDs[gs_in[0].lineID] = -1;
}
memoryBarrier();
// read back the flag value
int flag = IDs[gs_in[0].lineID];
if ( flag > 0)
{
int n;
for( n = 0; n < gl_in.length(), n++)
{
f_color = gs_in[n].color;
gl_Position = gl_in[n].gl_Position;
emitVertex();
}
}
}
No matter what value I put instead of 500, this code always renders only 2 objects. If I change the condition for rendering in the GS to if( flag > = 0) it seems to me that all objects are rendered which means the -1 is never written by the time these IDs are read back by the shader.
Can someone please explain why the writes are not coherently visible to all shader invocations despite the memoryBarrier() and what would be the most efficient work around to get this to work?
Thanks.