Shaders Using integers instead of floats

kupo15

Member
I can't seem to find much documentation on using integers. I learned how to do a dynamic pallet swap system using floats that work fine but am having trouble converting it to an integer system since color data is typically 0-255 in paint programs. Here is my float point code

Code:
// create

red_value = 1.0;
green_value = 1.0;
blue_value = 1.0;
Code:
// draw

shader_set(shader0);

var ss = shader_get_uniform(shader0,"colors");
shader_set_uniform_f(ss,red_value,green_value,blue_value,1.0);

draw_sprite(sprite5,0,x,y);
shader_reset();
Code:
// shader0

//
// Simple passthrough fragment shader
//
varying vec2 v_vTexcoord;
varying vec4 v_vColour;
uniform vec4 colors;

void main()
{
    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;
   
    if (Color.r == 1.0 && green == 0.0 && blue == 0.0 && alph > 0.0)
    gl_FragColor = colors;
    else
    gl_FragColor = Color;
}

I tried simply changing all float numbers to integers and using shader_set_uniform_i instead...the program compiles but the alpha seems to be at 0 as the pixels its supposed to be affecting are transparent.

What am I missing?
 

kupo15

Member
If I understand you correctly,
can't you just divide your integer rgb values by 255?
Wow you are right, didn't think of that. Then what is the uniform_i used for and how do you use it? I assume its to do exactly this without dividing?
 

jo-thijs

Member
It is used to set the value of integer uniforms.
Do you see the "uniform vec4" in your shaders?
This defines a uniform vector with 4 float components.
You can also use "uniform ivec4" to have 4 integer components.
 

kupo15

Member
It is used to set the value of integer uniforms.
Do you see the "uniform vec4" in your shaders?
This defines a uniform vector with 4 float components.
You can also use "uniform ivec4" to have 4 integer components.
Cool so that probably was the missing thing I was trying to figure out, ivec4

Going back to this code, is there anyway I can use integers to target and exact color and shade that I want? I tried using those numbers/255 but in my one example it didn't work because I assume float points are not very accurate so the values didn't match up. I know I could use > < but I want to know if I could pinpoint any exact integer of RGB of values that aren't clean. For example 51 converts to 0.2 nicely

Code:
if (Color.r == 1.0 && green == 0.0 && blue == 0.0 && alph > 0.0) // example  (Color.r == (50/255) && green == (11/255) && blue == (114/255) && alph > 0.0) 
    gl_FragColor = colors;
 
I

icuurd12b42

Guest
You can't pass anything but floats or vectors (of floats) or arrays of afore mentioned types in Game Maker. Even if it looks like the type int and bool for example are supported. they are not.
 

kupo15

Member
You can't pass anything but floats or vectors (of floats) or arrays of afore mentioned types in Game Maker. Even if it looks like the type int and bool for example are supported. they are not.
Ok so I guess I am either limited to a small handful of values to specifically target or create a small range to account for the inaccuracies of float points. But just to double check, do shaders only have precision to one digit past the decimal or would it actually accept a number like 0.20161 as an exact value without rounding?
 

jo-thijs

Member
@kupo15, your test should have thrown compile errors at you.
The issue is you're doing 50/255, which is an intger divided by an integer.
This will perform an integer division, resulting in the integer 0.
You're then comparing a float with an integer, which is an invalid operation.
You should do 50.0/255.0 instead.

But then there might be an other problem.
You see, when shaders test on equality, they test exact equality.
GML uses fuzzy comparison.
It could be the 2 floats don't match in their last digit because of rounding errors.

You can definitely use ivec4 to solve this issue though.
You can convert an integer to a float by using float(...).
Or you can convert a bunch of integers to a float vector by using vec4(..., ..., ..., ...).

@icuurd12b42, I'm not sure what you're saying.
Are you saying GameMaker doesn't support storing integers in variables?
If you are not using any extensions, this is true, but quite irrelevant to this topic.
 
I

icuurd12b42

Guest
I'm saying you can't pass an integer or a bool to a shader uniform.
I'm saying you can only pass floats and vectors (which 2,3 or 4 floats)
then you asked for the precision of a float, hence the wiki post.
 

jo-thijs

Member
Ok, I just did some testing and I'm confused.
Does gamemaker change "uniform ivecn" to "uniform vecn" and "uniform int" to "uniform float" in shaders?

I just tested this vertex shader:
Code:
//
// Simple passthrough vertex shader
//
attribute vec3 in_Position;                  // (x,y,z)
//attribute vec3 in_Normal;                  // (x,y,z)     unused in this shader.
attribute vec4 in_Colour;                    // (r,g,b,a)
attribute vec2 in_TextureCoord;              // (u,v)

varying vec2 v_vTexcoord;
varying vec4 v_vColour;

uniform ivec2 test;

void main()
{
    vec4 object_space_pos = vec4( in_Position.x, in_Position.y, in_Position.z, 1.0);
    object_space_pos = object_space_pos * vec4(test, 1.0, 1.0);
    gl_Position = gm_Matrices[MATRIX_WORLD_VIEW_PROJECTION] * object_space_pos;
   
    v_vColour = in_Colour;
    v_vTexcoord = in_TextureCoord;
}
And I set "test" using this code:
Code:
u = shader_get_uniform(shader0, "test");
shader_set_uniform_f(u, 1.5, 1.5);
And the shader behaves exactly as though test was of type vec2 with the values { 1.5, 1.5 }.

Is this normal?
 
I

icuurd12b42

Guest
Yes and no. as I said you can only pass floats and vec2,3,4... this implies the receiving end, the uniform, should be a float or a float based vector...

I should mention the fact you can't pass an int or bool is because it's actually a bug on the gml side.
only
shader_set_uniform_f
works

for your ivec or ints you should call
shader_set_uniform_i
but that dont work. if it does it's only recently so I would not trust it to work
 

jo-thijs

Member
It doesn't work indeed.
I'm guessing this is a bug in GM:S.

I'm thinking though it might not be the case that shader_set_uniform_i doesn't work,
but that the shaders get compiled wrongly when using integer uniforms.
Or perhaps shader_get_uniform just works really wierdly.
 

jo-thijs

Member
It will though, as I tested it with this result.
GM passing everything as floats (if we disregard strings, arrays and extension values) has nothing to do with that.
 
I

icuurd12b42

Guest
Just know that something is lost in the translation and not to trust anything but the float and vec types. I beg to ask, why did you pass 1.5 in the uniform when you know it's an int... I'm actually surprize it got through as 1.5 and not as some weird values as ints and floats are not memory compatible...
 

jo-thijs

Member
I passed it as a test, to see how it would be rounded.
To my surprise, it wasn't rounded at all.

I do get now that in the current versions of GM:S, only float and vecn types are to be trusted.
 

Xor

@XorDev
I would divide the color by 255 outside of the shader when you are setting the uniform. This doesn't need to be calculated for every pixel and so it can outside of the shader. Something like this:
Code:
///Create Event
uColor = shader_get_uniform(shader0,"colors")
Code:
///Draw Event
shader_set(shader0);
shader_set_uniform_f(uColor,color_get_red(Color)/255,color_get_green(Color)/255,color_get_blue(Color)/255,1);
//Draw stuff
shader_reset();
 
Last edited:

kupo15

Member
wow great to see a nice discussion about this and to find out GM:S is a little bugged with this

To back up icuurd12b42 - it's a float!

Even if your value was rounded to 0.20161 - if you can see the difference between 52 and 53 in a single colour channel, then you are a better man than I.
haha its not a matter of me seeing the differences (even with my brand new lasik eyes) but more of a matter of designating specific values to be swappable so that my artist doesn't use that value for normal things in the character that won't be swapped.

@kupo15, your test should have thrown compile errors at you.
The issue is you're doing 50/255, which is an intger divided by an integer.
This will perform an integer division, resulting in the integer 0.
You're then comparing a float with an integer, which is an invalid operation.
You should do 50.0/255.0 instead.
Right it did throw an error and I used float(50/255) instead and it compiled but didn't swap anything because the value was probably imprecise.

I'm saying you can't pass an integer or a bool to a shader uniform.
I'm saying you can only pass floats and vectors (which 2,3 or 4 floats)
then you asked for the precision of a float, hence the wiki post.
Yea a yes or no would have been helpful :p I only asked for clarification because GM's manual had a little blurb about floats saying that just putting a "1" is actually a float of 1.000000001 or something like that so I didn't know if there was actually more than 1 decimal or where GM rounded either.

Just know that something is lost in the translation and not to trust anything but the float and vec types. I beg to ask, why did you pass 1.5 in the uniform when you know it's an int... I'm actually surprize it got through as 1.5 and not as some weird values as ints and floats are not memory compatible...
Ok so it seems like I'm really only limited to multiples of 51 if I want exact pixels to swap out since that translates to multiples of 0.2 in float unless another multiple divides cleanly

I would divide the color by 255 outside of the shader when you are setting the uniform. This doesn't need to be calculated for every pixel and so it can outside of the shader. Something like this:
Code:
///Create Event
uColor = shader_get_uniform(chader0,"colors")
Code:
///Draw Event
shader_set(shader0);
shader_set_uniform_f(uColor,color_get_red(Color)/255,color_get_green(Color)/255,color_get_blue(Color)/255,1);
//Draw stuff
shader_reset();
Cool, yea recently switched to that and can get the full range of color outputs

Code:
shader_set(shd_pallete_swap);

var rgb0 = shader_get_uniform(shd_pallete_swap,"colors0");
var rgb1 = shader_get_uniform(shd_pallete_swap,"colors1");
var rgb2 = shader_get_uniform(shd_pallete_swap,"colors2");
var rgb3 = shader_get_uniform(shd_pallete_swap,"colors3");

shader_set_uniform_f(rgb0,obj_camera.red_value[0]/255,obj_camera.green_value[0]/255,obj_camera.blue_value[0]/255,1.0);
shader_set_uniform_f(rgb1,obj_camera.red_value[1]/255,obj_camera.green_value[1]/255,obj_camera.blue_value[1]/255,1.0);
shader_set_uniform_f(rgb2,obj_camera.red_value[2]/255,obj_camera.green_value[2]/255,obj_camera.blue_value[2]/255,1.0);
shader_set_uniform_f(rgb3,obj_camera.red_value[3]/255,obj_camera.green_value[3]/255,obj_camera.blue_value[3]/255,1.0);
Code:
// shader

uniform vec4 colors0;
uniform vec4 colors1;
uniform vec4 colors2;
uniform vec4 colors3;

void main()
{
    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 (Color.r == 1.0 && green == 0.0 && blue == 0.0 && alph > 0.0) // straight red
    gl_FragColor = colors0;
    // layer1
    else if (Color.r == 0.0 && green == 1.0 && blue == 0.0 && alph > 0.0) // straight green
    gl_FragColor = colors1;
    // layer2
    else if (Color.r == 0.0 && green == 0.0 && blue == 1.0 && alph > 0.0) // straight blue
    gl_FragColor = colors2;
    // layer3
    else if (Color.r == 1.0 && green == 0.2 && blue == 1.0 && alph > 0.0)  // bright pink
    gl_FragColor = colors3;
    // output original color
    else
    gl_FragColor = Color;
}
Are you saying though I could improve the efficiency of my shader through a different method or is what I did ok? It seems like the only difference is I should move my vars from the draw event to the create event like you did? Is that an improvement?
 

jo-thijs

Member
Right it did throw an error and I used float(50/255) instead and it compiled but didn't swap anything because the value was probably imprecise.
You didn't do what I said, you still did an integer division 50/255 instead of a float division 50.0/255.0.

Yea a yes or no would have been helpful :p I only asked for clarification because GM's manual had a little blurb about floats saying that just putting a "1" is actually a float of 1.000000001 or something like that so I didn't know if there was actually more than 1 decimal or where GM rounded either.
No, 1 would be stored 100% exactly as a float.
0.1 however would not, as it goes on infinitely in binary.

Ok so it seems like I'm really only limited to multiples of 51 if I want exact pixels to swap out since that translates to multiples of 0.2 in float unless another multiple divides cleanly
You're not limited to multiples of 51 and 0.2 cannot be accurately represented in floats.
Only integer multiples of integer powers of 2 can be represented exactly in floats if the number has a correct amount of binary digits and a good enough exponent.

Are you saying though I could improve the efficiency of my shader through a different method or is what I did ok? It seems like the only difference is I should move my vars from the draw event to the create event like you did? Is that an improvement?
You are doing there what he suggested to do.
He said to divide by 255 in GML and not in the shaders, as the GML code is executed way less often.
 

Xor

@XorDev
Are you saying though I could improve the efficiency of my shader through a different method or is what I did ok? It seems like the only difference is I should move my vars from the draw event to the create event like you did? Is that an improvement?
First is to divide outside of the shader (in GML) and the other thing is define the uniform variable in GML in the create event. Both are slight improvements because they compute less often. Dividing in the draw event means only doing it per object rather than per pixel and defining a uniform only needs to be done once. This would make more of a difference if you continue to add uniforms.
 

kupo15

Member
You didn't do what I said, you still did an integer division 50/255 instead of a float division 50.0/255.0.
wow it works! I guess that is different than using the float(), I didn't think it would. Thanks! :D

You're not limited to multiples of 51 and 0.2 cannot be accurately represented in floats.
Only integer multiples of integer powers of 2 can be represented exactly in floats if the number has a correct amount of binary digits and a good enough exponent.
From the above quote now I see I'm not. At first I only thought I was because of rounding and the float() wasn't working but now I know I'm not!

You are doing there what he suggested to do.
He said to divide by 255 in GML and not in the shaders, as the GML code is executed way less often.
First is to divide outside of the shader (in GML) and the other thing is define the uniform variable in GML in the create event. Both are slight improvements because they compute less often. Dividing in the draw event means only doing it per object rather than per pixel and defining a uniform only needs to be done once. This would make more of a difference if you continue to add uniforms.
Perfect. So all I need to do is just move the shader_get_uniforms to the create event and I'm good for efficiency. Thanks for the tip!
 
Top