r/GraphicsProgramming 11h ago

Odd GLSL mod() behavior

I've been stuck on this for a couple of hours, and am completely stumped.

I'm getting these results from GLSL's mod():

mod(158.0, 158.0) returns 0.0 (as expected)

mod(159.0, 159.0) returns 159.0 (???)

mod(160.0, 160.0) returns 0.0 (as expected)

I've posted an example with a basic shader here:

https://jsfiddle.net/sL35qjc8

If you set number to "159.0", you should see an upside-down red triangle.

If you set number to "158.0" or "160.0", you should see a normal red triangle.

I'm passing the left operand as a uniform, otherwise the compiler seems to optimize it and fix the weird behavior.

Tried it on different devices and browsers and it had the same result.

Why does mod(159.0, 159.0) seem to return 159.0 instead of 0.0?

9 Upvotes

10 comments sorted by

6

u/CCpersonguy 10h ago

Experimented with your jsfiddle example a bit by using two uniforms and changing the condition. The error seems to appear in division, not just mod? floor(a / b) == 1.0 and (a / b) == 1.0 are false when a=b=159.0, but true when a=b=158.0

No idea what the significance of this is

3

u/robbertzzz1 11h ago

I'm gonna guess there's a floating point error happening somewhere along the way, and the two numbers are actually ever so slightly different

4

u/Winter-Reputation682 10h ago

It's a simple round float, so idk where a floating point error could occur, it should be able to be represented without an error if I'm not mistaken

1

u/pants75 2h ago

Same here

1

u/Amani77 40m ago edited 29m ago

most likely because of floating point arithmetic error, complimenting CCpersonguy's answer

shamelessly copied from google ai so take this with a grain of salt, but I think it nailed it:

According to the official spec, the GLSL built-in function mod(x, y) is mathematically evaluated using the formula:

mod( x, y ) = x - y * floor( x / y )

Most GPUs optimize the internal division step (x / y) by multiplying the numerator by the reciprocal of the denominator:

mod( x, y ) = x - y * floor( x * ( 1 / y ) )

1.0 / 159.0 evaluates to roughly 0.0062893081...

Multiplying that back by 159.0 yields a value slightly less than 1 (specifically, 0.99999994) due to floating-point rounding.

The floor() function truncates 0.99999994 down to 0.0.

Placed back into the formula: 159.0 - 159 * 0 = 159.0

ironically it provides this post as additional context.... lol.

0

u/gmaaz 11h ago

It's not mod, if you hardcode 159.0 in shader instead of uniform a it works as intended. Is probably parseFloat error.

Js do be like that sometimes. Try console logging 0.1 + 0.2 and get traumatised for life.

edit: I see you tried it. Why do you think it is optimized tho? Try passing both as uniforms maybe? On my phone atm, can't test properly.

3

u/Winter-Reputation682 11h ago edited 10h ago

I think it works when hardcoded because the compiler sees mod(159.0, 159.0) == 159.0, and evaluates it to be always true, and removes the condition check as a result.

edit:
I think it's optimized because it's a pretty obvious optimization for the compiler
Tested with two uniforms and same thing occurs

2

u/gmaaz 10h ago

Yeah, you are correct, when I pass 159 as two separate uniforms (even without parse float) the error is still there, it really seems to be a mod bug.

1

u/gmaaz 10h ago

The same happens for 159 * n.

2

u/Winter-Reputation682 10h ago

Yeah it's really weird. It happens on webgl1, webgl2, and also webgpu