Drawing using Integer/Float

Started by Steve Elliott, March 27, 2019, 09:34:15

Previous topic - Next topic

Steve Elliott

At the moment I'm drawing using delta timing and floating point positions for my sprites, but I've heard that can cause some 'smearing' at times so it's best to convert to integers just before positioning a sprite at x, y.

Your thoughts on this? And if you do convert to an integer, do you round up or down or simply pass the floats to integer variables and let the data type lose the remainder?
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb

Qube

I use floats + delta time and for the perfect pixel sharpness ( like the current game comp ) in AGK I set the image min / max filter to 0. AGK has very good sub pixel rendering :)
Mac Studio M1 Max ( 10 core CPU - 24 core GPU ), 32GB LPDDR5, 512GB SSD,
Beelink SER7 Mini Gaming PC, Ryzen 7 7840HS 8-Core 16-Thread 5.1GHz Processor, 32G DDR5 RAM 1T PCIe 4.0 SSD
MSI MEG 342C 34" QD-OLED Monitor

Until the next time.

Steve Elliott

Quote
I use floats + delta time and for the perfect pixel sharpness ( like the current game comp ) in AGK I set the image min / max filter to 0. AGK has very good sub pixel rendering :)

You're talking about SetDefaultMinFilter( 0 ) and SetDefaultMagFilter( 0 )?  Yes I use those, and yes sub pixel rendering is great, but if I were to switch to another engine I wonder if non AGK people convert to integers?
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb

Derron

If you need to have it "crisp" and "pixel perfect" then just render them the simple way:
DrawImage(img, int(x), int(y))

BUT - at least in BlitzMax that means you just truncate the fractional part (2.9 => 2, 2.1 => 2). To avoid that, do a simple "round as you learned in school":
DrawImage(img, int(x + 0.5), int(y + 0.5))
This rounds up for >= x.5 and down for < 0.5 (2.9 + 0.5 = 3.4 => 3, 2.1 + 0.5 = 2.6 => 2).
Think you can still do the simple way without much visual issues. Just make sure that you move moving entities to their final target position. Why? If the entity is at x=2.999995 and the target is 3.0 your logic might already think "it reached" and stops moving. When you now render that entity, it gets rendered at eg. "int(2.999995)" so at ... "2" - which is of course not the right position.


Rendering at subpixel positions would lead to blended/anti-aliased sprites if not handled properly (dunno if that MinFilter/MagFilter thing in AGK does disable alpha values).
As you only do this for rendering and not physics, the rounding of numbers is not affecting the movement of your entities.



bye
Ron

Steve Elliott

#4
Thanks Derron, that makes sense.  But anything over x.5 is going to always round up, surely?
QuoteThis rounds up for >= x.5 and down for < 0.5 (2.9 + 0.5 = 3.4 => 3, 2.1 + 0.5 = 2.6 => 2).
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb

Qube

#5
Rounding to integers before drawing sprites will lead to a more jerky and less smooth scrolling 2D experience. If your game is designed around 60hz monitors and sprites move at integer speeds then you can get away with it but as soon as you use delta time and your game is played on different monitor refresh rates then on 2D games you'll notice that rounding to integers before drawing will result in a less smooth experience.

*edit* sub-pixel rendering is the only way for 2D smooth scrolling on all monitor refresh rates.
Mac Studio M1 Max ( 10 core CPU - 24 core GPU ), 32GB LPDDR5, 512GB SSD,
Beelink SER7 Mini Gaming PC, Ryzen 7 7840HS 8-Core 16-Thread 5.1GHz Processor, 32G DDR5 RAM 1T PCIe 4.0 SSD
MSI MEG 342C 34" QD-OLED Monitor

Until the next time.

Steve Elliott

Quote
*edit* sub-pixel rendering is the only way for 2D smooth scrolling on all monitor refresh rates.

Hmm, I'll probably leave it as it is then.  However SDL 2 only offers integer values for a start.
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb

Derron

#7
Quote from: Qube on March 27, 2019, 14:01:20
*edit* sub-pixel rendering is the only way for 2D smooth scrolling on all monitor refresh rates.

Can you explain why?
If we cannot use anti-aliasing (16 color limit) we cannot draw "between pixels" without loosing crispyness (as all pixels would be "opacque" and so sometimes a sprite's pixel would be drawn twice to do the "sub-pixel rendering").

1st) No sub-pixel rendering,
2nd) sub-pixel rendering with "Clear-Type"

source: wikipedia, unknown author, PD, https://de.wikipedia.org/w/index.php?curid=1398913


Quote from: Qube on March 27, 2019, 14:01:20
Rounding to integers before drawing sprites will lead to a more jerky and less smooth scrolling 2D experience
If you could only draw a pixel once (see above) then this is the limitation we have to live with - drawing them at a floating point position will lead to "smudged pixels" or "doubled pixels" which is what will either break the 16color rule or look "odd".


Remember: the movement logic is done with floating point precision only for rendering the rounding is done (so it is "temporary").




Quote from: Steve Elliott on March 27, 2019, 13:43:59
Thanks Derron, that makes sense.  But anything over x.5 is going to always round up, surely?

If you cast a float to an integer it just truncates what comes after the comma/dot.
1.100 ➡ 1.100
1.200 ➡ 1.200
...
1.500 ➡ 1
...
1.800 ➡ 1
1.900 ➡ 1

If you now add "0.5" to all of them, you will have:
1.100 ➡ 1.600 ➡ 1
1.200 ➡ 1.700 ➡ 1
...
1.500 ➡ 2.000 ➡ 2
...
1.800 ➡ 2.300 ➡ 2
1.900 ➡ 2.400 ➡ 2


bye
Ron

Derron

#8
Just have read OP again - Steve, and he did not state he was talking about the 8bit compo - so maybe I am talking about something which isn't intended.

so if you have all the alpha channels you like and a true color palette, then of course you can draw at floating point positions!


There are exceptions in which I avoid floating point positions:
- gui widgets (except they are animated - like "swooshing out of the screen"))
- bitmap texts (if they look too "soft" to you - try to render glyphs at a integer position)
- most non-moving elements (just set the position to "integer" values once the movement is done)

Sometimes it might be needed to render at integer positions if your image comes from a texture atlas - at least I've seen some slight texture bleeding when used with certain images/texture atlases in TVTower (some "ad contract folder-images" bleeded with their surrounding border). Only happened on some of my computers - so it is for sure a driver+engine specific problem which might not happen in AGK or whatever toolset you use.

bye
Ron

Qube

Quote from: Derron on March 27, 2019, 14:53:22
Quote from: Qube on March 27, 2019, 14:01:20
*edit* sub-pixel rendering is the only way for 2D smooth scrolling on all monitor refresh rates.

Can you explain why?
Because rounding up / down floats to integers and drawing sprites at integers is less smooth than drawing at float values with sub-pixel rendering.

For example ( pseudo code ):

Jerkier version when rounding floats :

x = 0
Do
   DrawSprite ( gfx, round( x ), 100 )

   Inc x, 1.75 * deltaTime
   If x > 320 Then x = 0

   Flip / Swap / Sync ( whatever )
Loop

Smother when using sub-pixel rendering ( if your engine supports it ) :

x = 0
Do
   DrawSprite ( gfx,  x , 100 )

   Inc x, 1.75 * deltaTime
   If x > 320 Then x = 0

   Flip / Swap / Sync ( whatever )
Loop

QuoteThere are exceptions in which I avoid floating point positions:
- gui widgets (except they are animated - like "swooshing out of the screen"))
- bitmap texts (if they look too "soft" to you - try to render glyphs at a integer position)
- most non-moving elements (just set the position to "integer" values once the movement is done)
I agree with that and would say any static element should be at integer values to guarantee the best possible visual rendering.
Mac Studio M1 Max ( 10 core CPU - 24 core GPU ), 32GB LPDDR5, 512GB SSD,
Beelink SER7 Mini Gaming PC, Ryzen 7 7840HS 8-Core 16-Thread 5.1GHz Processor, 32G DDR5 RAM 1T PCIe 4.0 SSD
MSI MEG 342C 34" QD-OLED Monitor

Until the next time.

Derron

Should have striked my ask for explanation ;-)

As said I wrote under the misassumption that we talk about something within the 8bit compo-limitations. Without that limitation of course the rendering at floating points might lead to smoother visuals.


bye
Ron

Steve Elliott

 :)

Quote
drawing at float values with sub-pixel rendering.

So what are the AGK Guys doing that the SDL Guys aren't?   ;D
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb

Qube

Quote from: Steve Elliott on March 27, 2019, 15:41:47
So what are the AGK Guys doing that the SDL Guys aren't?   ;D
More than likely using OpenGL directly rather than via SDL. Quite surprised that SDL 2 doesn't support that :o
Mac Studio M1 Max ( 10 core CPU - 24 core GPU ), 32GB LPDDR5, 512GB SSD,
Beelink SER7 Mini Gaming PC, Ryzen 7 7840HS 8-Core 16-Thread 5.1GHz Processor, 32G DDR5 RAM 1T PCIe 4.0 SSD
MSI MEG 342C 34" QD-OLED Monitor

Until the next time.

Steve Elliott

Quote
If you cast a float to an integer it just truncates what comes after the comma/dot.

It doesn't work like that in AGK.  Using the Round() Function rounds to the nearest Integer, so 2.1 + 0.5 (2.6) = 3.  You can use functions like Ceil to round up or Floor to round down - so you have to choose, it's not done for you automatically.

Ah, now I tested that in C++ and int( float_num ) does give the results you say (rounding up and down correctly by adding 0.5).  Cool.   8)

Quote
More than likely using OpenGL directly rather than via SDL. Quite surprised that SDL 2 doesn't support that :o

Yeah, that's a real pain SDL 2 doesn't support it. 
Win11 64Gb 12th Gen Intel i9 12900K 3.2Ghz Nvidia RTX 3070Ti 8Gb
Win11 16Gb 12th Gen Intel i5 12450H 2Ghz Nvidia RTX 2050 8Gb
Win11  Pro 8Gb Celeron Intel UHD Graphics 600
Win10/Linux Mint 16Gb 4th Gen Intel i5 4570 3.2GHz, Nvidia GeForce GTX 1050 2Gb
macOS 32Gb Apple M2Max
pi5 8Gb
Spectrum Next 2Mb

Derron

Think we should agree on what exact discuss about.

If I draw a black rect-image on a white background using floats then I will see some grayish border around the rectangle when using SDL and AlphaBlend in BlitzMax.

Is this what you are talking about...or do you talk about something different?



@ AGK
What happens if you do not use round() (as this is a custom command) but just cast the float to an integer?

Bye
Ron