1. Hello Guest! It's with a heavy heart that we must announce the removal of the Legacy GMC Archive. If you wish to save anything from it, now's the time! Please see this topic for more information.
    Dismiss Notice

Does subpixel rendering exist in GM?

Discussion in 'Programming' started by Robzoid, Apr 14, 2019.

  1. Robzoid

    Robzoid Member

    Joined:
    Jul 4, 2018
    Posts:
    29
    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
     
  2. YellowAfterlife

    YellowAfterlife ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ Forum Staff Moderator

    Joined:
    Apr 21, 2016
    Posts:
    2,434
    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.
     
  3. NightFrost

    NightFrost Member

    Joined:
    Jun 24, 2016
    Posts:
    2,004
    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.
     
    Bentley likes this.
  4. dannyjenn

    dannyjenn Member

    Joined:
    Jul 29, 2017
    Posts:
    569
    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.
     
  5. Robzoid

    Robzoid Member

    Joined:
    Jul 4, 2018
    Posts:
    29
    Thanks for the replies.

    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
     
  6. YellowAfterlife

    YellowAfterlife ᴏɴʟɪɴᴇ ᴍᴜʟᴛɪᴘʟᴀʏᴇʀ Forum Staff Moderator

    Joined:
    Apr 21, 2016
    Posts:
    2,434
    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.
     
    Robzoid and Bentley like this.
  7. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    7,119
    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 and Bentley like this.
  8. Robzoid

    Robzoid Member

    Joined:
    Jul 4, 2018
    Posts:
    29
    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?

    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.
     
  9. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    7,119
    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;
     
    Robzoid likes this.
  10. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    7,119
    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);
     
  11. Ido-f

    Ido-f Member

    Joined:
    Feb 19, 2018
    Posts:
    134
    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.
     
  12. TheouAegis

    TheouAegis Member

    Joined:
    Jul 3, 2016
    Posts:
    7,119
    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: May 3, 2019

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice