Texture bleeding - Blitzmax bug with virtual resolution

Started by Derron, October 13, 2017, 21:59:44

Previous topic - Next topic

Derron

So you pointing me to "x1=0 and x2=22" means 23 pixels, not 22 pixels as in the difference. Means one should not do "from X1 to X1+Width" in that case?


(not able to try it out now - outdoor fun with sun is starting _now_)




bye
Ron

col

#46
It certainly looks that way in this case.

For testing I'd turn off the linear sampling and have a go in DrawSubImageRect or make my own for testing that doesn't involve the DrawImage command.

Also you could write your own UVs mapping to map the pixel positions from 0.5/dimension to 1-0.5/dimension, remembering that its the edges of your smaller image that you want to map to .5 texel positions from the larger image. This should guarantee that the edge of your smaller image UVs are lined up to the dead centres of the larger image texels. You would probably keep the filtering on if you do that.

Another idea would be to position the smaller 'tiles' at positions in the atlas so that the math doesn't produce numbers such as 216.999916?
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."

Derron

These numbers like "216.9999997" are based on floating point inaccuracy. So even if I pass them as int (217) they might get passed to the next call as "216.999997"

This is a problem I see with the 0-1.0 way of OpenGL as a "integer position" cannot be passed that way (it is always inaccurate).


@ custom uv mapping
This is something I would not like to tackle. I am ok with coding custom "atlas stuff" or so, but as soon as it opens a can of new "knowledge areas" I am afraid of not catching on to it. It is one of the reasons I use BlitzMax with a premade "Max2D-engine" rather than C++ with an own engine.


@ linear sampling
Yes, this is on my plan already (still have to work on other areas, so for now it is "todo").


bye
Ron

Derron

Just saw another video of the guy ("Part 11") - and there he went into the settings. He is playing it with a virtual resolution of "1600x900" (game is designed for 800x600). So Blitzmax is scaling it up to that resolution (and adding the black borders left and right). As it scales up it might explain a bit why things might differ on there compared to our results... ?




bye
Ron

col

#49
Hmm,
I think that would be a long shot, although... anywhere where there is scaling can introduce an issue if its not accurate. Its worth doing some tests for that for the sake of ruling it out.

What I meant with 'position the smaller 'tiles' at positions in the atlas so that the math doesn't produce numbers such as 216.999916' is placing tiles at positions that will divide more evenly with the atlas dimensions. For example working with numbers such as finding the normalised position ( 0.0 to 1.0 ) of 217 within 512 will could introduce the 'possibility' of rounding errors when combined with an already scaled screen. I mean it shouldn't right? But there is something wrong. These are just suggestions to try for testing only.

Going back to basics... is it worth installing older drivers to see if the problem shows up?

I just googled for some more information and NVidia did a paper on texture atlassing.
It may be worth studying it, together with the debug tools it may help shed more light on things:
http://download.nvidia.com/developer/NVTextureSuite/Atlas_Tools/Texture_Atlas_Whitepaper.pdf
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."

Derron

#50
Thanks for the link to the paper.

Especially the "Using Mip-Maps with Atlases"-part is interesting. They suggest to have the "sub-images" to be power-of-two too! Maybe this is also to avoid rounding problems (as except for "1x1" all dimensions would be "even".

Why isn't this technique used by the various texture packers around? They just optimize the placement to occupy as less space as possible.
I assume it is because they do not generate mipmaps - and therefor also the aspect of "larger textures in the atlas have define the maximum mipmap level" is not important.

This might be of interest for us too:
Quote
Most applications, however, use texture coordinates ranging from zero to one, inclusive, nonetheless. While such coordinates actually wrap, clamp, or mirror a texture by exactly one texel, the benefit of being texture-dimension independent outweighs the slight image-quality reduction. Because texture coordinates in the inclusive [0, 1] interval thus address an area larger than the actual texture, directly re-mapping these coordinates to atlas coordinates also accesses an area larger then the texture's assigned sub-rectangle. The Atlas Creation Tool [CreationTool2004] offers several solutions to this problem.

But wouldn't that - if it was done that way in Max2D/BlitzMax - be a little bug? I mean it would access a 512x512 texture as if it was 513x513 (or so ?) leading to calculation "offsets" (adding a bit from left/top of a texture). It slightly reads as if it was connected to the "x+width=wrongValue" thing we just wrote about some posts ago.
 
bye
Ron

Derron

#51
I am not sure whether I was able to replicate it...


SuperStrict

'keep it small
Framework BRL.standardIO
Import Brl.Max2D
Import Brl.Graphics
Import "../../base.util.registry.imageloader.bmx"
Import "../../base.util.registry.spriteloader.bmx"
Import "../../base.util.registry.bitmapfontloader.bmx"
Import "../../base.gfx.gui.input.bmx"
Import "../../base.util.virtualgraphics.bmx"

SetGraphicsDriver GLMax2DDriver()
TVirtualGfx.getInstance().Init()
Local g:TGraphics = Graphics(1000, 750, 0, 60, 0 )
TVirtualGfx.getInstance().SetVirtualGraphics(800,600)

Local guiState:TLowerString = TLowerString.Create("test")

Local registryLoader:TRegistryLoader = New TRegistryLoader
registryLoader.LoadFromXML("res/config/startup.xml", True)
registryLoader.LoadFromXML("res/config/resources.xml", True)

GuiManager.SetDefaultFont( GetBitmapFontManager().Get("Default", 14) )

Local widget:TGUIInput = New TGUIInput.Create(New TVec2D.Init(70, 20), New TVec2D.Init(130,-1), "Sample Input", 50, guiState.ToString())
widget.SetTooltip( New TGUITooltipBase.Initialize("An input field", "Entering text does not make you an author!", New TRectangle.Init(0,0,250,-1)) )
widget.SetOverlay(GetSpriteFromRegistry("gfx_gui_overlay_player"))

Repeat
   Cls

   'fetch and cache mouse and keyboard states for this cycle
   GUIManager.StartUpdates()
   'system wide gui elements
   GuiManager.Update( guiState )
   'reset modal window states
   GUIManager.EndUpdates()

   KeyManager.Update()


   'draw a background
   SetClsColor(255,230,200); Cls
   'GetSpriteFromRegistry("gfx_startscreen").Draw(0,0)

   GuiManager.Draw( guiState )

   Flip 0
Until KeyManager.IsHit(KEY_ESCAPE) Or AppTerminate()


What I do is to create a bigger application window and let it scale the 800x600 via "TVirtualGFX" (think the base of this was posted somewhen on the blitzmax.com website).
When having a window of "1200x900" (so scale of 1.5) I did not see any changes (except pixelation) but when running it with "1000x750" (so *1.25) I got a pink bar...

1000x750


1200x900


Would this be of use (regarding debugging) or did I mix things up?

In my game I use a similar approach and allow "windowed" but with a "screen resolution" (so you decide how big the window of the game gets). The Let's player used (if I remember correctly - seeing it once in his videos) that he has used a height of 900 - maybe this "scrambles" it on his computer ?


When running my "gui sample" in his resolution (had to search his videos ... when he went to the main menu to re-enable the music he found "annoying" at the beginning ;-)) I get a similar result: so 1600x900 is doing this here:



Seems I finally got something to fix without relying on his computer.


bye
Ron

Derron

So I tried to adjust the "source rectangles" a bit.



DrawSubImageRect(parent.image,..
          x,..
          y,..
          area.GetW(),..
          area.GetH(),..
          area.GetX()+0.1,..
          area.GetY(),..
          area.GetW(),..
          area.GetH(),..
          offsetX,..
          offsetY,..
          0)


And it removed the black line on the left (of that "arrow left"-button) - but I only was able to do this for the "none-nine-patch-sprites". For nine-patch I tried the opposite - and subtracted 0.1 from "y2" (y+height-0.1). It worked there too.

So I assume it has to do with the "rounding" into another texel. Dunno how to avoid that, means, how to pass some kind of "integer" defining an exact pixel coordinate in the texture.


bye
Ron

col

#53
QuoteWhy isn't this technique used by the various texture packers around? They just optimize the placement to occupy as less space as possible.
A texture packer will all store the correct UVs though yes? as opposed to doing a calculation to get them. I would think that the new UV will be dead centre of the texel for edges of the smaller tile.

QuoteWould this be of use (regarding debugging) or did I mix things up?
Aside from seeing the artifact you would also see the uv numbers that are being sampled. More information is better ;)

Phew! It's much better that you can reproduce the issue. That alone can make the whole debug process so much easier. It can be a painstakingly slow process I know.
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."

Derron

#54
I found the culprit (partially, maybe ;-)...

max2d.mod/max2d.bmx: Function SetVirtualResolution( width#,height# )
So the virtual resolution function accepts floats... this, together with the "TVirtualGFX"-Class leads to the experienced inaccuracy.

I always assumed that the resolution-params are "integers" - so I never passed a "rounded" variant to them.

So this means I need to update DIGs "virtual graphics" to properly round down the x/y-offsets so it starts at "nice" coordinates when borders have to get added.
And I while doing, I replace these odd lines:

         ' Now go crazy with trial-and-error... ooh, it works! This tiny bit of code took FOREVER.
         Local pixels:Float = Float (GetInstance().vwidth) / (1.0 / GetInstance().vscale) ' Width after scaling
         Local half_scale:Float = (1.0 / GetInstance().vscale) / 2.0

with

         Local pixels:Int = Int(GetInstance().vwidth * GetInstance().vscale) ' Width after scaling
         Local half_scale:Float = 0.5 / GetInstance().vscale


PS: source of original "TVirtualGFX" is http://www.syntaxbomb.com/index.php?topic=2897.0

PPS: Even if this were not the source of the issue, then I would really really thank you as I would surely have stopped bug hunting already if there weren't your replies keeping me motivated.

PPPS: Will have to check formulas again, as for now it only fixes "X-axis" while the "stretched" ones are still showing that bottom-pixel. But for now it is family time until this evening is "computer time" again ;-)


bye
Ron

col

Thats awesome! And a great find too.
I'm glad you kept at it as there was clearly a discrepancy in there somewhere, verified by the debug tool and with your own latest findings.

Fingers crossed that it is the problem 8)
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."

Derron

#56
Hmm, there is something ... interesting...




First is the arrow-sprite which does no longer contain the "left side black border".
Second the upper widget: it is positioned at a "even" y-coordinate
Third the widget below: it is located at an "odd" y-coordinate
Fourth: the nine patch which moves on the right side... it shows how position adjusts the "scaled image result" which surely leads to the bottom borders of overlay (person) and widget

BUT ... what the really interesting thing is: the BOTTOM of the whole app. There is a black line... but why?

I use this code (hard coded values for this test... but calculated in the real app):
Code (blitzmax) Select

Local g:TGraphics = Graphics(800, 300, 0, 60, 0 )
SetVirtualResolution( 533, 200 )
SetViewport( 67, 0, 400, 200 )
SetOrigin( 67, 0 )

...
setClsColor ...
cls

But as you see it keeps a "1 pixel black border" at the bottom.


Ok, so this happens the same in this simple (standalone) sample:
Code (blitzmax) Select

SuperStrict

'keep it small
Framework BRL.standardIO
Import Brl.GlMax2D

SetGraphicsDriver GLMax2DDriver()
Local g:TGraphics = Graphics(800, 300, 0, 60, 0 )
SetVirtualResolution( 533, 200 )
SetViewport( 67, 0, 400, 200 )
SetOrigin( 67, 0 )

Repeat
   SetClsColor(255,230,200); Cls
   Flip 0
Until AppTerminate()
end


Is this black line happening for you too? If so, I assume this is an issue with Max2D and not my specific code.


The ApiTrace has this scissor-command there:

Editing this line (right click - edit) and change it to "100, 0, 600, 300" removes the black line in the following frames...

Digging around WHY this happens - leads me to "max2d.bmx : SetViewPort". There coordinates got always rounded _down_. Leading to the 299.

For now I replaced SetViewPort in max2d.bmx with the following code:
Code (blitzmax) Select

Function SetViewport( x,y,width,height )
   gc.viewport_x=x
   gc.viewport_y=y
   gc.viewport_w=width
   gc.viewport_h=height
   'round coordinates "correctly" to avoid values like 49.9997 to get rounded down
   'as the old floor() did
   Local x0=int( x / gc.vres_mousexscale  +0.5 )
   Local y0=int( y / gc.vres_mouseyscale  +0.5 )
   Local x1=int( (x+width) / gc.vres_mousexscale  +0.5 )
   Local y1=int( (y+height) / gc.vres_mouseyscale  +0.5 )
   _max2dDriver.SetViewport x0,y0,(x1-x0),(y1-y0)
End Function

And the black line is gone. The Error with scaled-up images is still existing. There might be similar issues left.


bye
Ron

Derron

#57
Ok, now I hope to get your help, as you will surely have a bit more experience regarding matrix modes, viewports...


As you can see the (uv) coordinates are exactly the same for left and right figure. Only difference is the glVertex2f-calls (the "draw positions"). Left one is rendered from "10,10 - 34,33" and the second one from "60,11 - 84,34". 77,10 is there because of the letterbox on the left (and right) side.

I assume that something like the projection matrix (excuse my missing knowledge here) leads to that issue.


To reproduce it on your computer:
- load this image: https://raw.githubusercontent.com/GWRon/Dig/master/samples/__res/gfx/gui.png
- copy the following code:


Code (blitzmax) Select

SuperStrict

'keep it small
Framework BRL.standardIO
Import Brl.GlMax2D
Import Brl.PNGLoader

SetGraphicsDriver GLMax2DDriver()
Local g:TGraphics = Graphics(800, 300, 0, 60, 0 )
SetVirtualResolution( 533, 200 )
SetViewport( 67, 0, 400, 200 )
SetOrigin( 67, 0 )
SetAlpha ALPHABLEND

local img:TImage = LoadImage("gui.png", 0)

Repeat
    SetClsColor(255,230,200); Cls

    DrawSubImageRect(img,..
                     10,..  'x
                     10,..  'y
                     24,..      'width
                     23,..      'height
                     349,..     'atlas sprite x
                     1,..       'atlas sprite y
                     24,..      'atlas sprite w
                     23,..      'atlas sprite h
                     0,..       'offset x
                     0,..       'offset y
                     0)

    DrawSubImageRect(img,..
                     60,..  'x
                     11,..  'y
                     24,..      'width
                     23,..      'height
                     349,..     'atlas sprite x
                     1,..       'atlas sprite y
                     24,..      'atlas sprite w
                     23,..      'atlas sprite h
                     0,..       'offset x
                     0,..       'offset y
                     0)
    Flip 0
Until KeyHit(KEY_ESCAPE) or AppTerminate()
end





Hope you can help me to get rid of that nasty bug - again.




bye
Ron

Derron

#58
Ok, further digging:


glmax2d.bmx
Code (blitzmax) Select


   Method ResetGLContext( g:TGraphics )
      Local gw,gh,gd,gr,gf
      g.GetSettings gw,gh,gd,gr,gf
     
      state_blend=0
      state_boundtex=0
      state_texenabled=0
      glDisable GL_TEXTURE_2D
      glMatrixMode GL_PROJECTION
      glLoadIdentity
      glOrtho 0,gw,gh,0,-1,1
      glMatrixMode GL_MODELVIEW
      glLoadIdentity
      glViewport 0,0,gw,gh
   End Method

so it creates an orthographic view... for 800x600 it calls: glOrtho 0,800,600,0,-1,1. Isn't this saying: make an area of 801*601 pixels? Adjusting the command in QApiTrace (so it does "0,799,599,0,-1,1") removes my bars. But somehow I think it is still not perfectly correct and only "hiding" a bug.


Aside of that I found this:
https://msdn.microsoft.com/en-us/library/windows/desktop/dd374282(v=vs.85).aspx
and
https://www.opengl.org/archives/resources/faq/technical/transformations.htm  ("9.030 How do I draw 2D controls over my 3D rendering?")
and they suggested to use glTranslatef(0.375, 0.375, 0). So I added it to ResetGLContext():

glmax2d.bmx
Code (blitzmax) Select

   Method ResetGLContext( g:TGraphics )
      Local gw,gh,gd,gr,gf
      g.GetSettings gw,gh,gd,gr,gf
     
      state_blend=0
      state_boundtex=0
      state_texenabled=0
      glDisable GL_TEXTURE_2D
      glMatrixMode GL_PROJECTION
      glLoadIdentity
      glOrtho 0,gw,gh,0,-1,1
      glMatrixMode GL_MODELVIEW
      glLoadIdentity
      'make sure that primitives could be placed near the texel centers
      glTranslatef(0.375, 0.375, 0)
      glViewport 0,0,gw,gh
   End Method


Now all "lines" are gone - seems this fixed the bug for me. But I will wait for what you have to say about it.

I also undo'd my changes to the TVirtualGFX code - and still everything was rendered properly now.
I then undo'd my changes to max2d.bmx (the +0.5) and the line at the bottom of the window was back again.


So conclusion - for now - is, that only that glTranslate-call was needed to fix my issues (at least on my computer) and the max2d.bmx fix is "convenience" :-).

Happy? Nope... because adding this command makes all my fonts and sprites look "blurry" ...
So I moved that command from ResetGLContext() into the SetResolution()-function and the fonts/sprites kept being "crisp".


Now everything drawn is offset by 1 pixel (so top and left has 1px border of nothing). Seems there is still something to fix (and/or above thing isn't the right thing to fix the bug).


bye
Ron

col

OMG There are scales working from scales that are adjusting to scales then applied to other scales :P

I know its may not be possible, but rendering to a texture that's 1:1 pixels with your original images sizes and then rendering that to a screen size quad may actually be a better solution?

I remember Grey Alien and I talked about it in the past and he is using 'render to texture' to solve alpha transparency issues. I'm not sure how the final image quality comes out - I can't imagine the final image to suffer any quality loss compared to the original.

And, yep I get a black line at the bottom of the display with your latest example.

QuoteglOrtho 0,800,600,0,-1,
From MS I read that as if it means that the 800 and 600 are the *dimensions* that the normalized device coordinates ( -1 to +1 ) will be scaled to and not neccessariy that coords go from 0 to 800 inclusive.

From OpenGL it does read as you suggest  ???
gluOrtho2D
https://github.com/davecamp

"When you observe the world through social media, you lose your faith in it."