Does subpixel rendering exist in GM?

Robzoid

Member
I've been trying to learn about subpixel rendering in Gamemaker and I'm confused on where it actually draws objects to the screen for decimal values of x and y. I saw this thread on "How to avoid subpixel rending"

https://forum.yoyogames.com/index.php?threads/how-do-i-avoid-subpixel-rendering.49651/

which lead me to believe that subpixel rendering exists in gamemaker. From what I researched, there are 3 subpixels in a pixel. So, when I tested things, I expected something like x position of 0.0 - 0.3 to look the same, 0.4 - 0.6 to look the same and 0.7 to 0.9 to look the same. I got something much different upon testing.

I made two identical objects with two identically sized sprites - one red, one green for contrast. I set their x values to 480 plus some decimal value. I wanted to see when the red object was visible behind the green object based on the two objects' x values. Pics are below. I added the text in MS Paint. The yellow line at the bottom is one pixel thick for reference.

When the red and green objects' x values are both between 480.0 and 480.5, the two objects are in the same position (i.e. red object is invisible. See pics 3 and 4). I didn't include this in a pic, but when both objects are between 480.6 and 480.9, they are in the same position (i.e. red is invisible behind green). It is only when one is 480.0 - 480.5 and the other is 480.6 - 480.9 that they are in different positions (see pics 1 and 2). Also, see pic 5. The amount the red object sticks out is equal to the 1 pixel-thick reference sprite/object.

This tells me the following: if the decimal value of an x or y value is between 0.0 and 0.5, GM rounds it down (i.e. x = 480). If the decimal value of an x or y value is between 0.6 and 0.9, GM rounds it up (i.e. x = 481). The amount moved is never less than 1 pixel (pic 5).

This, to me, suggests that subpixel rendering does not exist in Gamemaker and that it simply rounds x and y values up or down at the end of each step. Maybe this is a hardware thing? I bought my monitor 2 years ago and my computer is about 4 years old.

Could you guys tell me if I'm off here? If i'm not, I don't understand why people bother with coding collision code that rounds decimal values, like floor(x), floor(y). Maybe it's because they don't like the way Gamemaker rounds? I really don't get it. In my game, I have decimal values all over. My x and y values are rarely integers and I haven't had any problems. Why create separate code to handle decimal values when Gamemaker, at least to my understanding, handle it for you?

PixelTest1.png

PixelTest2.png

PixelTest3.png

PixelTest4.png

upload_2019-4-14_1-44-46.png
 

YellowAfterlife

ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ
Forum Staff
Moderator
Direct your attention to the first screenshot in that topic and the last reply (Gift of Death) in that topic. The reason the author called it "subpixel" is because graphics appear at fractional game pixels, but not screen pixels (which are still whole).

Use of sub-pixel in your sense is mostly reserved to specific font rendering algorithms, and is less suitable for colored imagery.
 

NightFrost

Member
Coordinates you provide are rounded to integer values before draw. Further, GMS makes the odd decision of rounding halves (.5) towards even numbers: 2.5 rounds to 2.0 but 3.5 rounds to 4.0.
 

dannyjenn

Member
Further, GMS makes the odd decision of rounding halves (.5) towards even numbers: 2.5 rounds to 2.0 but 3.5 rounds to 4.0.
It is odd, but it's not just GameMaker. I think that's how they always do rounding in the Netherlands. That, or it's a carryover from Delphi or Pascal or whatever. There's probably some advantage to doing it that way, but I don't know the reasoning off the top of my head.
 

Robzoid

Member
Thanks for the replies.

Direct your attention to the first screenshot in that topic and the last reply (Gift of Death) in that topic. The reason the author called it "subpixel" is because graphics appear at fractional game pixels, but not screen pixels (which are still whole).
I'm not sure what you mean by fractional game pixels. By "fractional game pixels" are you referring to decimal values? For example, the game pixel position of x = 180.5 corresponds to a screen pixel position of x = 180?

Also, the round() function rounds 0.5 towards even numbers but that does not appear to be how it rounds when it draws decimal value x/y positions. See two pics below. When the red object's x value is set to 481.5, it remains hidden behind the green object. When its x value is changed to 481.6, its visible. This tells me that its rounding 0.5 down when it draws regardless of whether the number is odd or even.

2019-04-15_3-38-23.png



2019-04-15_3-39-40.png
 

YellowAfterlife

ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ
Forum Staff
Moderator
I'm not sure what you mean by fractional game pixels. By "fractional game pixels" are you referring to decimal values? For example, the game pixel position of x = 180.5 corresponds to a screen pixel position of x = 180?
If you look at that topic, their view is scaled up, so one pixel in-game (x += 1, for example) covers multiple pixels on screen. And they have resized application_surface accordingly, meaning that the difference between 180/180.25/180.50/180.75 is visible.

Otherwise rounding and rendering rules in general are entirely up to your GPU, and may vary by model/manufacturer.
 

TheouAegis

Member
Subpixel rendering is nasty for people that don't want it. It has been a concern for people since prior to Studio.

While a rectangle may appear normal at all coordinates, a complex sprite pattern will not necessarily look the same at all positions. A complex sprite pattern drawn at various subpixel coordinates will have various pixels stretched, while others stay the same.For people focusing on pixel art, this is a major issue of contention. If you force the program to ignore subpixels, you can ensure a sprite will look the same no matter where it is on the screen.

The issue with handling subpixel rendering in GM is it depends on a couple things.

1) View/Camera Position. If the view is at 10.4 and a subpixel at 120.4 is rendered, the subpixel is positioned relative to the view first so that the 120.4 becomes 110.0 and thus there is no visible subpixel rendering. But if the view is at 10.4 and the subpixel is at 120.6, it will be rendered at 110.2 instead.

2) View Size. After the subpixel has been repositioned relative to the view, it then undergoes a transformation if the view needs to be scaled at all to fit in the port. If the view is scaled up 2x and the subpixel is at 110.2 as above, then the subpixel will be transformed to 220.4 before rendering.

GM's own arbitrary handling of subpixels can also overcomplicate collision handling for a lot of people. Ignoring subpixels can alleviate such problems. Sometimes it rounds off, sometimes it doesn't. Personally, I want it to floor toward 0 all the time, not some gaussian rounding.
 

Robzoid

Member
Otherwise rounding and rendering rules in general are entirely up to your GPU, and may vary by model/manufacturer.
OK, so in my posts where it only rounds up when the decimal value is between 0.6 and 0.9, that rounding is specific to my computer's GPU model?

GM's own arbitrary handling of subpixels can also overcomplicate collision handling for a lot of people. Ignoring subpixels can alleviate such problems. Sometimes it rounds off, sometimes it doesn't. Personally, I want it to floor toward 0 all the time, not some gaussian rounding.
So do you use subpixels in your games but floor values before assigning them x or y and floor them before inputting them into collision functions like place_meeting, instance_place, etc.?

Thanks again for all the help on this.
 

TheouAegis

Member
That's the simpler way. Personally, i use a second variable and add hspd (or vspd for the vertical) to the variable each step, adding to x the integral values and subtracting them so only the fraction remains.

xfrac ±= hsp;
x += xfrac div 1;
xfrac = xfrac mod 1;
 

TheouAegis

Member
Necroing this because I just found an issue with my code. Apparently GM still does some rounding when using div 1 and mod 1. I was getting weird results in one of my tests trying to resolve another issue I was having and I traced it back to this code. It seems you'd have to use floor(xfrac) instead of div and mod.

Code:
xfrac += hsp;
var f = floor(xfrac);
x += f;
xfrac -= f;
I was expecting to get a steady range of integers from a code I was running, but the range kept growing. Once I deduced div 1 and mod 1 were not behaving as I was expecting them to, I got the desired results. So following up from that, a pixel-perfect Medusa Head wave motion would be
Code:
accel = (ystart-y)/256;
vsp += accel;
yfrac += vsp;
y += floor(yfrac);
yfrac -= floor(yfrac);
 

Ido-f

Member
Necroing this because I just found an issue with my code. Apparently GM still does some rounding when using div 1 and mod 1. I was getting weird results in one of my tests trying to resolve another issue I was having and I traced it back to this code. It seems you'd have to use floor(xfrac) instead of div and mod.

Code:
xfrac += hsp;
var f = floor(xfrac);
x += f;
xfrac -= f;
I was expecting to get a steady range of integers from a code I was running, but the range kept growing. Once I deduced div 1 and mod 1 were not behaving as I was expecting them to, I got the desired results. So following up from that, a pixel-perfect Medusa Head wave motion would be
Code:
accel = (ystart-y)/256;
vsp += accel;
yfrac += vsp;
y += floor(yfrac);
yfrac -= floor(yfrac);
That's taking into account that floor(-1.01) returns -2 rather than -1? I'm used to working with frac() instead for that reason.
 

TheouAegis

Member
I got the values I was expecting using floor so, yes? LOL

I had a starting coordinate of 164. I expected a minimum coordinate of 132 and a maximum of 196. floor() yielded those results. I concur the overall results may be particular to my case and other disparities may arise for other people, but... I just tested it as soon as I got out of bed after you pointed that out and I literally got the exact same results pixel by pixel for every single step as I was expecting. So... Yeah, even with negatives!

Edit: As I let on in the previous post, I was debugging my Medusa Head code -- which still holds up properly -- giving a minimax range of 64 pixels when the starting vspeed is 2. The problem I was having was my code as is what is yielding fractional pixels, in spite of the fact that I used a power of 2 as the divisor. Like, WTF GameMaker?! Tell I was trying to fix that and like I said, my attempts were causing a weird bug that I was scratching my head over for days. Then I finally realized it was a rounding issue. So to make sure my code was actually working properly - with the exact intended results - I opened up Castlevania and watched the RAM values for a Medusa Head, step by step, comparing each pixel displacement in the actual game to inside my test project. Using floor() was what yielded the best results with no noticeable pixel displacement disparity perceived.
 
Last edited:
Top