• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Shaders Converting gpu_fog flashing code to shader [solved]

kupo15

Member
I have a very inefficient and poorly written code that flashes the character that was written a while back when we didn't have shaders. I want to convert it to a shader version instead to make it better and not have to draw the character 3 times and clean it up but I really don't know what the fog code is doing to even know how to attempt to convert this.


GML:
function scr_flashing(scale) {

    fade += 0.3;
    fade_normalized = (sin(fade)+1)*0.5;

    draw_sprite_ext(sprite_index,floor(image_index),x,y,image_xscale*scale,image_yscale*scale,image_angle,image_blend,image_alpha);

// fog start
    gpu_set_fog(true,merge_color(c_black,flash_color,fade_normalized),0,0);

    draw_sprite_ext(sprite_index,floor(image_index),x,y,image_xscale*scale,image_yscale*scale,image_angle,c_white,0.3);   

    gpu_set_fog(0,0,0,0);

// fog end

// BM start
    gpu_set_blendmode(bm_add);

    draw_sprite_ext(sprite_index,floor(image_index),x,y,image_xscale*scale,image_yscale*scale,image_angle,c_white,0.5);   

    gpu_set_blendmode(bm_normal);


}
its even worse because I have an IF statement that runs this code instead of the normal drawing code for the character which I also think is probably unnecessary. I should be able to simple write the character draw code once and turn on the flashing shader when needed

Whenever I think of shaders, I think @The Reverend :)

If I could just understand what the fog code does, I could probably make the shader
 
Last edited:

kupo15

Member
I removed some code unnecessary to figuring this out so there isn't as much code to look through. Even if I were to not use shaders, is drawing the character 3 times necessary?
 

GMWolf

aka fel666
From the OpenGL Docs:
Fog blends a fog color with each rasterized pixel fragment's post-texturing color using a blending factor f. Factor f is computed in one of three ways, depending on the fog mode. Let c be either the distance in eye coordinate from the origin (in the case that the GL_FOG_COORD_SRC is GL_FRAGMENT_DEPTH) or the current fog coordinate (in the case that GL_FOG_COORD_SRC is GL_FOG_COORD). The equation for GL_LINEAR fog is

f = end - c end - start
The equation for GL_EXP fog is

f = e - density · c
The equation for GL_EXP2 fog is

f = e - density · c 2
Regardless of the fog mode, f is clamped to the range 0 1 after it is computed. Then, if the GL is in RGBA color mode, the fragment's red, green, and blue colors, represented by C r , are replaced by

C r ″ = f × C r + 1 - f × C f
Fog does not affect a fragment's alpha component.

In color index mode, the fragment's color index i r is replaced by

i r ″ = i r + 1 - f × i f

So it seems to me like you are first drawing your sprite, then drawing a sprite blended with a colour, with alpha blending. Finally adding the sprite back in top.

Code:
vec3 color = texture(...);
vec3 bcolor = blend( color, flash_color, fade_norm);

outColor = ( color *0.7) + (bcolor * 0.3) + color*0.5;

Actually not super sure about that last *0.5, I forget what GM means by additive blending and what it does with the alpha..



Btw, rather than conditionally switching between shaders, it might be worth making character shaders that can do all the effects at once (flashing, whatever) that you control with different uniforms.
Them you don't have an explosion of shader permutations.
It also means you don't have to switch shaders for each character (although you still need to change uniforms which sucks. GM doesn't offer much to solve that part though)
 

kupo15

Member
From the OpenGL Docs:



So it seems to me like you are first drawing your sprite, then drawing a sprite blended with a colour, with alpha blending. Finally adding the sprite back in top.

Code:
vec3 color = texture(...);
vec3 bcolor = blend( color, flash_color, fade_norm);

outColor = ( color *0.7) + (bcolor * 0.3) + color*0.5;

Actually not super sure about that last *0.5, I forget what GM means by additive blending and what it does with the alpha..



Btw, rather than conditionally switching between shaders, it might be worth making character shaders that can do all the effects at once (flashing, whatever) that you control with different uniforms.
Them you don't have an explosion of shader permutations.
It also means you don't have to switch shaders for each character (although you still need to change uniforms which sucks. GM doesn't offer much to solve that part though)
Yeah! Combining all the effects into one was going to be my next question, the next effect would be combining my palette swapping shader and how to do that with uniforms without branching if statements in the shader. But first, I'm having problems with your code

Are your vec types not correct? I tried changing them bc the code didn't work with v3. Isn't color a vec4 since its the base color? Also, blend isn't a function, I tried replacing it with mix and it still errored
 
Last edited:

GMWolf

aka fel666
Yeah! Combining all the effects into one was going to be my next question, the next effect would be combining my palette swapping shader and how to do that with uniforms without branching if statements in the shader. But first, I'm having problems with your code
Actually branching isn't usually a huge issue.
If you are branching over uniform values, then the cost is basically insignificant, since every thread is following the same branch.
You would be branching using uniforms as control so no issues at all.

If you branch over dynamic values, then it's more expensive, but not prohibitively so.
The reason dynamic branching is expensive is because each "thread" needs to execute in lockstep, if they is branching they essentially go over both branches.
(But if each thread in the same wave is following branch then the GPU can figure that out and it's fast again).
So there is no point replacing a small branch with large complex logic instead, if the logic ends up twice the size as the branching logic. Especially since waves tend to be pixels near each other that usually follow the same path anyways.


Tldr; don't worry about branching: with uniforms it's practically free, and in other cases it won't matter anyways.

Are your vec types not correct? I tried changing them bc the code didn't work with v3. Isn't color a vec4 since its the base color? Also, blend isn't a function, I tried replacing it with mix and it still errored
Yeah I just quickly typed that on my phone. Texture returns a vec4, but you could do texture(...).rgb to get a vec3 instead too.

Yup, it should be mix, not blend
What error do you get?

Also you are.multiplying by aloha at the end. That's only correct if you are doing premultiplied alpha. What is your blend mode?
 

kupo15

Member
Tldr; don't worry about branching: with uniforms it's practically free, and in other cases it won't matter anyways.
Awesome thanks! I'll probably need a double check when I get there

Yeah I just quickly typed that on my phone. Texture returns a vec4, but you could do texture(...).rgb to get a vec3 instead too.

Yup, it should be mix, not blend
What error do you get?

Also you are.multiplying by aloha at the end. That's only correct if you are doing premultiplied alpha. What is your blend mode?
My error was labeling the flashing_color uniform as a float instead of a vec3. So now I got something working, however the result is not correct and I'm not seeing the flashing color change when I change it either. I currently don't have any blendmodes anywhere on this


Code:
//
// Simple passthrough fragment shader
//
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform vec3 flash_color;
uniform float fade_norm;

void main()
{
    vec4 color = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord);
    vec3 bcolor = mix(color.rgb,flash_color,fade_norm);
 
    vec4 outColor = vec4((color.rgb *0.7) + (bcolor * 0.3) + color.rgb*0.5,color.a);
 
    gl_FragColor = outColor;
}
 

Yal

🐧 *penguin noises*
GMC Elder
GLSL, GLSL ES and HLSL are three different languages (GLSL ES is the "embedded systems" version and is more compatible but with lesser features, GM uses a ridiculously old version of it) so make sure the shader is the correct language when copypasting code.

You can define functions yourself in shaders, this can help making some operations more clean to write:
GML:
vec4 blend(vec4 one, vec4 two, float factor)
{
   return factor*two + (1.0 - factor)*one;
}
I'm not sure if compilers are one-pass or two-pass for shaders these days, so place all function definitions before the first place they're used (e.g. before main) just in case.


In GM's GL ES there's some quirks with branching and sampling (you aren't allowed to sample a texture based on a branching) and loops based on variables aren't allowed (they are unrolled compile-time and the depth needs to be known), not sure if these apply to GLSL and HLSL too. You can get around those issues by...
  • Sampling non-conditionally, and then just don't use the value if the branching told you you didn't need it.
  • Loop over the max allowed loop size, but wrap the entire loop body in an if statement that checks if the loop counter < the number of loops you wanted.
Be prepared to learn workarounds like this... shaders are quirky and kinda messy to deal with. They're kinda like eldritch lore in that they'll drive you insane if you know too much about them, but they still beckon you to keep going.
 

kupo15

Member
GLSL, GLSL ES and HLSL are three different languages (GLSL ES is the "embedded systems" version and is more compatible but with lesser features, GM uses a ridiculously old version of it) so make sure the shader is the correct language when copypasting code.

You can define functions yourself in shaders, this can help making some operations more clean to write:
GML:
vec4 blend(vec4 one, vec4 two, float factor)
{
   return factor*two + (1.0 - factor)*one;
}
I'm not sure if compilers are one-pass or two-pass for shaders these days, so place all function definitions before the first place they're used (e.g. before main) just in case.


In GM's GL ES there's some quirks with branching and sampling (you aren't allowed to sample a texture based on a branching) and loops based on variables aren't allowed (they are unrolled compile-time and the depth needs to be known), not sure if these apply to GLSL and HLSL too. You can get around those issues by...
  • Sampling non-conditionally, and then just don't use the value if the branching told you you didn't need it.
  • Loop over the max allowed loop size, but wrap the entire loop body in an if statement that checks if the loop counter < the number of loops you wanted.
Be prepared to learn workarounds like this... shaders are quirky and kinda messy to deal with. They're kinda like eldritch lore in that they'll drive you insane if you know too much about them, but they still beckon you to keep going.
Thanks for all that info! I'm using the default shader language, its good to know about functions, that'll probably help when it comes time to combine multiple effects into one shader.

At the moment I'm trying to figure out why the shader isn't matching the output of the old fog code. (its bothering me that the flashes aren't in sync when they are using the same time variable)

Here's a gif of the shader (left) and the fog code (right) broken down into each component and then the final version of it

Current Shader Code
Code:
//
// Simple passthrough fragment shader
//
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform vec3 flash_color;
uniform float fade_norm;

void main()
{
    vec4 ref = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord);
    vec3 bcolor = mix(ref.rgb,flash_color,fade_norm);
 
    vec4 outColor = vec4((ref.rgb *0.7) + (bcolor * 0.3) + ref.rgb*0.5,ref.a);
 
    gl_FragColor = outColor;
}

Fog Code
GML:
   draw_sprite_ext(sprite_index,floor(image_index),x,y,image_xscale*scale,image_yscale*scale,image_angle,image_blend,image_alpha);

// fog start
    gpu_set_fog(true,merge_color(c_black,flash_color,fade_normalized),0,0);

    draw_sprite_ext(sprite_index,floor(image_index),x,y,image_xscale*scale,image_yscale*scale,image_angle,c_white,0.3);

    gpu_set_fog(0,0,0,0);

// fog end

// BM start
    gpu_set_blendmode(bm_add);

    draw_sprite_ext(sprite_index,floor(image_index),x,y,image_xscale*scale,image_yscale*scale,image_angle,c_white,0.5);

    gpu_set_blendmode(bm_normal);
 
Last edited:

GMWolf

aka fel666
I guess a good first step would be recreating the fog pass as a shader.

Then combining them according to the blend modes, but in pixel shader.

[EDIT]

OH! right, I didn't include the merge_color(black, ...) In my snippets. You will want to include that too.
 

RujiK

Member
I only skimmed the thread so I'm probably missing some information, but I gave it a quick try with a basic color blending shader:


And then I tweaked it a little so darker colors didn't blend as much:


And with here it is with a lighter destination color. (same code)


Again, I only skimmed the thread so I probably missed something on your wishlist, but maybe it will help.

Code:
shader_set(sh_flash_shader)
shader_set_uniform_f(shader_get_uniform(sh_flash_shader,"flash_color"),1,0.5,0.5); //rgb in (1.0,1.0,1.0) format
shader_set_uniform_f(shader_get_uniform(sh_flash,"fade_norm"),mouse_x/512); //fade amount
draw_stuff();
shader_reset();
and fragment shader:
Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform vec3 flash_color;
uniform float fade_norm;
void main()
{
    vec4 color = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord);
    //color.rgb = ((color.rgb - 0.5)*max(fade_norm*2.0,1.0)) + 0.5; //optional code to increase contrast.
    vec3 bcolor = color.rgb*(1.0 - fade_norm) + mix(color.rgb,flash_color.rgb,fade_norm)*(max(max(color.r,color.g),color.b))*fade_norm;
    gl_FragColor = vec4(bcolor.rgb, color.a);
}
 
Last edited:

kupo15

Member
I only skimmed the thread so I'm probably missing some information, but I gave it a quick try with a basic color blending shader:


And then I tweaked it a little so darker colors didn't blend as much:


And with here it is with a lighter destination color. (same code)


Again, I only skimmed the thread so I probably missed something on your wishlist, but maybe it will help.

Code:
shader_set(sh_flash_shader)
shader_set_uniform_f(shader_get_uniform(sh_flash_shader,"flash_color"),1,0.5,0.5); //rgb in (1.0,1.0,1.0) format
draw_stuff();
shader_reset();
and fragment shader:
Code:
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform vec3 flash_color;
uniform float fade_norm;
void main()
{
    vec4 color = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord);
    //color.rgb = ((color.rgb - 0.5)*max(fade_norm*2.0,1.0)) + 0.5; //optional code to increase contrast.
    vec3 bcolor = color.rgb*(1.0 - fade_norm) + mix(color.rgb,flash_color.rgb,fade_norm)*(max(max(color.r,color.g),color.b))*fade_norm;
    gl_FragColor = vec4(bcolor.rgb, color.a);
}
Thanks so much for that! You made me realize that I treated my color uniform wrongly. I essentially passed in c_yellow as the color value which doesn't have the breakdown of the rgb and used that id as color information. Silly me.

I did this before
shader_set_uniform_f(u_color,flash_col[flash_index]); // flash color wrong code


Now the color isn't red anymore, thought I'm not sure why the fade_norm isn't working and she has a brief flick. I want to recreate the fog code like @GMWolf mentioned first before continuing things, then I'll check out yours!

EDIT: Its not the fade_norm that is the problem, it appears to be the flash_color still because this works to produce yellow
vec3 fog_pass = mix(vec3(0.0),vec3(1.0,1.0,0.0),fade_norm);
 
Last edited:

kupo15

Member
Code:
vec3 color = texture(...);
vec3 bcolor = blend( color, flash_color, fade_norm);

outColor = ( color *0.7) + (bcolor * 0.3) + color*0.5;
Turns out this was pretty much correct haha I finally got it to work with one small issue. It only worked if I hardcoded the flash color in the shader which means something is wrong with how I pass and get my uniform into the shader. The color values for the flash color have to be what is giving me the wrong result. Any idea what I'm doing wrong with the uniform?


EDIT: Found my error, being an idiot and passed through the 0-255 values instead of floats. Simple mistakes.

Btw, rather than conditionally switching between shaders, it might be worth making character shaders that can do all the effects at once (flashing, whatever) that you control with different uniforms.
Them you don't have an explosion of shader permutations.
It also means you don't have to switch shaders for each character (although you still need to change uniforms which sucks. GM doesn't offer much to solve that part though)
Last question for this thread! Is this how I would do the branching below? It does work, just wanting to make sure I did it the correct way

GML:
/// CREATE
// assign shader
var shader = shader_flashing;

// assign uniforms
u_time = shader_get_uniform(shader,"fade_norm");
u_color = shader_get_uniform(shader,"flash_color");
u_flashing = shader_get_uniform(shader,"flashing");

/// DRAW
shader_set(shader_flashing);

// set shader uniforms
var r = color_get_red(flash_col[flash_index]);
var g = color_get_green(flash_col[flash_index]);
var b = color_get_blue(flash_col[flash_index]);

shader_set_uniform_f(u_color,r/255.0,g/255.0,b/255.0); // flash color
shader_set_uniform_f(u_time,h_time_normalized); // normalize x value
shader_set_uniform_f(u_flashing,flashing); // if flashing or not

// draw shader
draw_sprite(sprite_index,image_index,x,y-100); // draw animation
shader_reset();

Code:
//
// Simple passthrough fragment shader
//
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform vec3 flash_color;
uniform float fade_norm;
uniform bool flashing;

void main()
{
    vec4 ref = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord);
    
    vec3 fog_color = mix(vec3(0.0),flash_color,fade_norm);
    
    vec3 outputCol = vec3((ref.rgb*0.7)+(fog_color*0.3)+(ref.rgb*0.5));

    if (flashing == false)
    outputCol = ref.rgb;
    
    gl_FragColor = vec4(outputCol,ref.a);


    
    // vec4 base_pass = vec4(ref.rgb*0.7,ref.a);
    // vec4 fog_pass = vec4(fog_color*0.3,ref.a);
    // vec4 blend_pass = vec4(ref.rgb*0.5,ref.a);
    // gl_FragColor = base_pass+fog_pass+blend_pass; // base_pass*0.1; // vec4(fog_pass,ref.a);

}
 
Last edited:

Yal

🐧 *penguin noises*
GMC Elder
Turns out this was pretty much correct haha I finally got it to work with one small issue. It only worked if I hardcoded the flash color in the shader which means something is wrong with how I pass and get my uniform into the shader. The color values for the flash color have to be what is giving me the wrong result. Any idea what I'm doing wrong with the uniform?


EDIT: Found my error, being an idiot and passed through the 0-255 values instead of floats. Simple mistakes.



Last question for this thread! Is this how I would do the branching below? It does work, just wanting to make sure I did it the correct way

GML:
/// CREATE
// assign shader
var shader = shader_flashing;

// assign uniforms
u_time = shader_get_uniform(shader,"fade_norm");
u_color = shader_get_uniform(shader,"flash_color");
u_flashing = shader_get_uniform(shader,"flashing");

/// DRAW
shader_set(shader_flashing);

// set shader uniforms
var r = color_get_red(flash_col[flash_index]);
var g = color_get_green(flash_col[flash_index]);
var b = color_get_blue(flash_col[flash_index]);

shader_set_uniform_f(u_color,r/255.0,g/255.0,b/255.0); // flash color
shader_set_uniform_f(u_time,h_time_normalized); // normalize x value
shader_set_uniform_f(u_flashing,flashing); // if flashing or not

// draw shader
draw_sprite(sprite_index,image_index,x,y-100); // draw animation
shader_reset();

Code:
//
// Simple passthrough fragment shader
//
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform vec3 flash_color;
uniform float fade_norm;
uniform bool flashing;

void main()
{
    vec4 ref = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord);
   
    vec3 fog_color = mix(vec3(0.0),flash_color,fade_norm);
   
    vec3 outputCol = vec3((ref.rgb*0.7)+(fog_color*0.3)+(ref.rgb*0.5));

    if (flashing == false)
    outputCol = ref.rgb;
   
    gl_FragColor = vec4(outputCol,ref.a);


   
    // vec4 base_pass = vec4(ref.rgb*0.7,ref.a);
    // vec4 fog_pass = vec4(fog_color*0.3,ref.a);
    // vec4 blend_pass = vec4(ref.rgb*0.5,ref.a);
    // gl_FragColor = base_pass+fog_pass+blend_pass; // base_pass*0.1; // vec4(fog_pass,ref.a);

}
I'd guess it's because GM has a different color format than the shader language (GM has traditionally put blue first, GL ES - and thus most likely GLSL - put red first. Both formats are surprisingly common according to my research).

So pass in a hex color code like $RRGGBB (e.g. $FFFF00 for yellow) instead of the GM-standard $BBGGRR (where it'd be $00FFFF) and it should work?
 

kupo15

Member
I'd guess it's because GM has a different color format than the shader language (GM has traditionally put blue first, GL ES - and thus most likely GLSL - put red first. Both formats are surprisingly common according to my research).

So pass in a hex color code like $RRGGBB (e.g. $FFFF00 for yellow) instead of the GM-standard $BBGGRR (where it'd be $00FFFF) and it should work?
I figured out my mistake, it had to do with how I passed in my uniform. First error was simply passing in c_blue etc... as a vec3, but that passed in the color ID. Then I passed in the uniform with the 3 color components using color__get_rgb. The problem was I was using values from 0-255 into the shader and forgot to /255 to convert it to float. So now everything works!

Last question in regards to branching and turning things on and off, is this the right way to do it? This works btw

Code:
//
// Simple passthrough fragment shader
//
varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform vec3 flash_color;
uniform float fade_norm;
uniform bool flashing;

void main()
{
    vec4 ref = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord);
    
    vec3 fog_color = mix(vec3(0.0),flash_color,fade_norm);
    
    vec3 outputCol = vec3((ref.rgb*0.7)+(fog_color*0.3)+(ref.rgb*0.5));

    if (flashing == false)
    outputCol = ref.rgb;
    
    gl_FragColor = vec4(outputCol,ref.a);


    
    // vec4 base_pass = vec4(ref.rgb*0.7,ref.a);
    // vec4 fog_pass = vec4(fog_color*0.3,ref.a);
    // vec4 blend_pass = vec4(ref.rgb*0.5,ref.a);
    // gl_FragColor = base_pass+fog_pass+blend_pass; // base_pass*0.1; // vec4(fog_pass,ref.a);

}
 

Yal

🐧 *penguin noises*
GMC Elder
If I'm reading the documentation correctly, only sampling textures has caveats with branching. (It's done in a previous step and then reused as much as possible for several fragments, or something to that effect). It could be other gradient operations as well (I remember having to scrap one shader implementation because it depended on 2-dimensional derivatives of the fragment's parent vertices and I couldn't figure out how to make them work properly - doing them nonconditionally lead to weird edge cases at the edges where they were infinite) but there's not a whole lot of those operations available in GM's GL ES 1.0 to begin with, so you probably won't run into them any time soon.
 

kupo15

Member
If I'm reading the documentation correctly, only sampling textures has caveats with branching. (It's done in a previous step and then reused as much as possible for several fragments, or something to that effect). It could be other gradient operations as well (I remember having to scrap one shader implementation because it depended on 2-dimensional derivatives of the fragment's parent vertices and I couldn't figure out how to make them work properly - doing them nonconditionally lead to weird edge cases at the edges where they were infinite) but there's not a whole lot of those operations available in GM's GL ES 1.0 to begin with, so you probably won't run into them any time soon.
Got it, so what I'm doing which that conditional is good then. And when you say sampling compare you mean doing something like this is bad?

Code:
  // get original color of pixel
    vec4 Color = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord );  
   
    // get the pixel color data
    float red = Color.r;
    float green = Color.g;
    float blue = Color.b;
    float alph = Color.a;   
   
    // layer0
    if (red == u_red1 && green == u_green1 && blue == u_blue1 && alph == 1.0) // straight red
    gl_FragColor = colors0;
    // layer1
    else if (red == u_red2 && green == u_green2 && blue == u_blue2 && alph == 1.0) // straight green
    gl_FragColor = colors1;
    // layer2
    else if (red == u_red3 && green == u_green3 && blue == u_blue3 && alph == 1.0) // straight blue
    gl_FragColor = colors2;
    // layer3
    else if (red == u_red4 && green == u_green4 && blue == u_blue4 && alph == 1.0)  // bright pink
    gl_FragColor = colors3;
    // output original color
    else
    gl_FragColor = Color;
I want to get away from this palette swap method and want to instead assign the pixel color based on a texture. I know how to do this if you draw your sprite purposely where red value 0 is first palette color, r1 is second etc... but wondering if there is a way to assign values based on a base color palette. I assume you have to get the rgb of the color, find the uv coor on the base palette using that rgb, get the output color rgb on a new palette using those coor and apply it?


I get the feeling the best way to do this is simply using the red channel as the color index and use that to swap out the new color
 

RujiK

Member
if statements are for slow losers. Ditch that conditional weasel for some fast math.

First, change flashing to a float instead of a bool:
Code:
uniform bool flashing; -> uniform float flashing;
and then change this:
Code:
   if (flashing == false)
    outputCol = ref.rgb;
to this:
Code:
outputCol = ref.rgb*(1.0 - flashing) + outputCol*(flashing);
Boom, no conditions at all. Your game will now run 0.1 FPS faster! Oh boy!
 

kupo15

Member
Wow, I really feel the difference in the speed now. Thanks!! :D But yeah, I love getting rid of IFs where ever I can in normal code so this is something Id prefer doing. Thanks for the tip and for the help!

And thanks everyone for all the help in this thread! You guys are awesome!
 

GMWolf

aka fel666
if statements are for slow losers. Ditch that conditional weasel for some fast math.

First, change flashing to a float instead of a bool:
Code:
uniform bool flashing; -> uniform float flashing;
and then change this:
Code:
   if (flashing == false)
    outputCol = ref.rgb;
to this:
Code:
outputCol = ref.rgb*(1.0 - flashing) + outputCol*(flashing);
Boom, no conditions at all. Your game will now run 0.1 FPS faster! Oh boy!
Nope
It's a myth. Kinda..

Branching on a dynamically uniform value is fast, practically free. Drivers can do a lot of optimizations. That's cheaper than the fast math.(So that's uniforms in GM, idk if GM has any other kind of dynamically uniform values)

Branching on dynamic values is more complicated:
If every thread in a wave/warp/EU (a SIMD unit) does the same branch (rather common) then that's cheap as well. Cheaper than the "fast math".

If every thread does something different, like in the pallet swap example, then it's usually expensive. Basically it's the cost of both branches(although texture lookups etc are only performed in active threads). So "fast maths" is only faster if it's fewer instructions than both branches.


If you have a mixture, like waves mostly do the same thing, but sometimes do both branches, then branching might still be cheaper, because you only pay the price of one branch in many waves, but you would pay the price of the fat fast maths in all waves.
This is relevant when branching on, say, flat vertex attributes. Most waves will be processing pixels from the same triangle, so you will only get the expensive path when a wave processes pixels from different triangles/sprites.

Shaders also optimize really well, so if you don't use a value, it won't get computed (usually). So the way you setup your condition is mostly fine.


The way I would rewrite it though is by thinking in terms of stages.
You keep modifying a colour value with multiple effects.

Code:
colour = texture...
if (effect1)
  colour = applyEffect1(colour);
If (effect2)
  colour = applyEffect2(colour)

Etc
I combined a series of expensive shaders like that in MW and it was a perf gain overall (mostly from combining passes actually). It was also not significantly more expensive than compiling different permutations for each possibility, there was a difference but it wasn't deemed worth it (in a game where we go to crazy lengths otherwise for tiny perf gains)
 
Last edited:

GMWolf

aka fel666
But yeah, I love getting rid of IFs where ever I can in normal code so this is something Id prefer doing.
Be careful with that!
Getting rid of ifs might be a good idea if your branches are short, and have a ~50-50 probability.
But if one branch is significantly more likely than the other, then a branch is quite efficient thanks to branch prediction.
Get that CPU pipelining!


Disclaimer: all my knowledge comes from c++/OpenGL/etc. GM might behave differently.
 
Last edited:

kupo15

Member
Be careful with that!
Getting rid of ifs might be a good idea if your branches are short, and have a ~50-50 probability.
But if one branch is significantly more likely than the other, then a branch is quite efficient thanks to branch prediction.
Get that CPU pipelining!


Disclaimer: all my knowledge comes from c++/OpenGL/etc. GM might behave differently.
That's some really great information. Thanks for that! And your apply effects incrementally seems like a much cleaner approach from an organization effect. So now I have this, how do I turn the flashing code into a function like you did when functions don't accept shader variables?

Code:
void main()
{
    vec4 ref = v_vColour * texture2D( gm_BaseTexture, v_vTexcoord);
    vec3 outputCol = ref.rgb; // normal color
    
    // flashing color
    if (is_flashing)
        {
        vec3 fog_color = mix(vec3(0.0),flash_color/255.0,fade_norm);
        
        outputCol = vec3((outputCol*0.7)+(fog_color*0.3)+(outputCol*0.5));
        }
        
    gl_FragColor = vec4(outputCol,ref.a);

}
 
Last edited:

GMWolf

aka fel666
when functions don't accept shader variables
what do you mean by that?

i would do something like this:
C:
vec3 flashingEffect( vec3 color, vec3 flashColor, float fade)
{
    vec3 fog_color = mix(vec3(0.0), flashColor, fade);
    return (color * 0.7) + (fog_color * 0.3) + (color * 0.5);
}

void main()
{
    vec4 ref = (v_vColour * texture2D(gm_BaseTexture, v_vTexcoord);
    vec3 color = ref.rgb;
    if (is_flashing)
    {
        color = flashingEffect( color, flash_color/255, fade_norm );
    }

    gl_FragColor = vec4(color, ref.a);
}
Alternatively you dont have to pass in the uniforms:
C:
vec3 flashingEffect( vec3 color )
{
    vec3 fog_color = mix(vec3(0.0), flash_color/255 , fade_norm);
    return (color * 0.7) + (fog_color * 0.3) + (color * 0.5);
}

void main()
{
    vec4 ref = (v_vColour * texture2D(gm_BaseTexture, v_vTexcoord);
    vec3 color = ref.rgb;
    if (is_flashing)
    {
        color = flashingEffect( color  );
    }

    gl_FragColor = vec4(color, ref.a);
}
 

kupo15

Member
what do you mean by that?
I did this originally and it didn't recognize any of the shader functions which I guess is understandable


the only way I can get that flashingEffect to colorize as if its a function is if I have a script created with that name but then I'll have a blank script otherwise it'll be
 

GMWolf

aka fel666
shaders cannot use GML functions/scripts.
All the code for a shader has to be written in that shader file.
So in this case the 'flashingEffect' function would be right above the main() in the shader.
 

kupo15

Member
Got it, it seems within the shader it doesn't highlight the function names at all like I was expecting. That threw me off. I like the not having to pass in the uniforms method and it works. Thanks a lot!
 

RujiK

Member
@GMWolf

So I tried the optimizations with a math "condition", a bool condition based on a uniform, and a slow color condition.

These were my four shaders:
1.) GMS's default shader (No shader set)
2.) Math "condition" ( color1*uniform + color2*(1 - uniform) );
3.) uniform Bool condition ( if (uniform_bool) {color1} else {color2} )
4.) Slow color condition ( if (color.r > 0.5) {color1} else {color2} )

And the results after drawing a 256x256 sprite 500 times:
1) GM Default: 49-50 FPS.
2) Math "Condition: 51-52 FPS
3) Bool Condition: 51-52 FPS
4.) "Slow" color condition: 51-52 FPS

I also tried drawing the sprite 1000 times but the FPS was 27 for all shaders.

So in conclusion: The GPU is a beast and doesn't care about silly optimizations. Weird. I was expecting the color condition to at least be noticeably slower.
 

GMWolf

aka fel666
@GMWolf

So I tried the optimizations with a math "condition", a bool condition based on a uniform, and a slow color condition.

These were my four shaders:
1.) GMS's default shader (No shader set)
2.) Math "condition" ( color1*uniform + color2*(1 - uniform) );
3.) uniform Bool condition ( if (uniform_bool) {color1} else {color2} )
4.) Slow color condition ( if (color.r > 0.5) {color1} else {color2} )

And the results after drawing a 256x256 sprite 500 times:
1) GM Default: 49-50 FPS.
2) Math "Condition: 51-52 FPS
3) Bool Condition: 51-52 FPS
4.) "Slow" color condition: 51-52 FPS

I also tried drawing the sprite 1000 times but the FPS was 27 for all shaders.

So in conclusion: The GPU is a beast and doesn't care about silly optimizations. Weird. I was expecting the color condition to at least be noticeably slower.
Yeah then you have to see where the bottle neck is.
Most probably it's going to be vertex upload.
GM has to build vertex buffers on the CPu and upload them to the GPU.
The GPU can render a whole lot more geometry that what you could ever hope to upload to it every frame.
(Looking at captures it doesn't look like GM is super efficient with uploads either, but my DX11 isn't super good so idk)

It might be worth trying with very large vertex buffers, and very high resolutions.


Edit:
Only 500 sprites and below 60? What machine are you running on?
256*256?
Maybe you are limited by fill rate.
Actually drawing pixels to the screen is rather expensive. That's why combining passes, and avoiding overdraw (drawing on stuff you already drawn) is so important when optimizing.

It could also be bandwidth if you are using integrated GPU: those use system ram I think.
 
Last edited:

GMWolf

aka fel666
Update on the whole dynamically uniform value thing:
I have recently run into a compiler bug where branches on dynamically uniform values costs both branches.
So make sure to profile your shader to make sure it's compiling correctly.

In this case, the code went as follows:
C:
vec3 color;
if (time == 0)
{
  color = somethingExpensive();
}
else
{
 color = somethingElse();
}

fragColor = vec4(color, 1);
The issue seemed to be in the glsl to hlsl conversion: it would flatten the branch; not what you want with dynamically uniform values.

Idk if there is a way to check the generated HLSL but if it's giving you trouble that might be a good place to start looking.
 

kupo15

Member
Update on the whole dynamically uniform value thing:
I have recently run into a compiler bug where branches on dynamically uniform values costs both branches.
So make sure to profile your shader to make sure it's compiling correctly.

In this case, the code went as follows:
C:
vec3 color;
if (time == 0)
{
  color = somethingExpensive();
}
else
{
color = somethingElse();
}

fragColor = vec4(color, 1);
The issue seemed to be in the glsl to hlsl conversion: it would flatten the branch; not what you want with dynamically uniform values.

Idk if there is a way to check the generated HLSL but if it's giving you trouble that might be a good place to start looking.
I wouldn't know how to do that so I hope I don't run into any performance issues with the shader 😅
 

GMWolf

aka fel666
I wouldn't know how to do that so I hope I don't run into any performance issues with the shader 😅
Assuming YYG have their stuff together then you should be fine.
If not it's a bug.
The driver should correctly optimize the shader. So if there is an issue it is most definitely a bug on yoyos side converting glsl to hlsl.
 
Top