Anyone ever get smooth scrolling tiles working?

BlitzMax Forums/BlitzMax Programming/Anyone ever get smooth scrolling tiles working?

Grey Alien(Posted 2015) [#1]
See this old thread: http://www.blitzbasic.com/Community/posts.php?topic=86549

Basically I'm talking about the fact that if you draw a bunch of tiles in BlitzMax and then scroll them at floating point coords (or zoom in on them), the joins will look jerky as the move across pixel boundaries. OR if you add a 1 pixel blank border (popular technique with sprites to avoid jerking edges) you'll get dark lines showing up in between then. (see my code sample about halfway down that thread above)

[edit]Found an even older thread of mine with images. http://www.blitzbasic.com/Community/posts.php?topic=66157#739230

This is because each tile doesn't know about its neighbours and so their edges are antialiased to the background instead of the neighbouring tiles.

If you were able to draw all the tiles at integer coords and *then* draw the whole lot in one go (as if it was a texture) at floating point coords/zooomed in, that would work fine because the antialiasing would be applied to the image as a whole. However, it's too slow to render to a texture first before outputting, so that solution is out.

Back in the day indiepath kept going on about using a mesh (like in 3D programming) to do it but none ever materialised in BMax.

Or is there a way to use something like a viewport to show a portion of a bigger tiled area grabbed from a floating point source coordinate? Or a projection matrix solution using something like: D3D7GraphicsDriver().Direct3DDevice7().SetTransform(D3DTS_PROJECTION,matrix)

Then there's TileMax. Did _Skully ever solve this issue (can't see it mentioned in the feature list).

Any info/suggestions you may have are most welcome.


GW(Posted 2015) [#2]
I didn't know this was a problem. I've never encountered it myself.
Are you talking about the little black lines that can show up between tiles when they are drawn at float coords?

It seems the simplest solution would be to use the center handle on your tiles and set the scale to small amount over 1.0.


Grey Alien(Posted 2015) [#3]
Yes I am talking about those black lines when drawn at floating point coords (if using a filtered image with a 1 pixel border - I updated the original post to add more detail)

I haven't tried boosting the scale but I'm not sure that's entirely viable.


Kryzon(Posted 2015) [#4]
Each DrawImage has a glBegin\glEnd or BeginScene\EndScene pair. This means that each tile drawn costs one draw call. Imagine a full map.

The "right" way to render a tile map would be to use a canvas texture and generating a single mesh for all the tiles, then rendering this mesh with a single call.
Since the vertices are also joined (they're separate vertices, but at the same position), this should also solve the rasterisation problem.

Can you implement this? Do you need example code?


Grey Alien(Posted 2015) [#5]
No I cannot implement it, it's beyond my current knowledge. Anyone done that sort of thing in BlitzMax?


zoqfotpik(Posted 2015) [#6]
I never had a problem with this but when I view your example I do see what you're saying.

A couple of other solutions might be rounding your FP coordinates down to integer in software, or batching the tilemap into one or more larger images and then moving and drawing that as you wish. That's obviously going to be much faster than 64x64 or however many individual draw calls.

Not to minimize your concern or anything like that but I'm questioning whether this would even be noticeable to the vast majority of users. When I ran your example I couldn't see what you were talking about even though it had been pointed out to me, until I looked very closely. The example that flashes a little bit is more noticeable.

This isn't fixing the underlying problem but my gut feeling is that if you're drawing a large number of tiles every frame, you might want to find ways of not doing that, for performance reasons irrespective of anything else. How exactly to do that is something I'm still kicking around but I'm thinking for me it's going to involve rendering to pixmaps-- lots of blast scars and that sort of thing and when you are drawing all those every frame things can get real slow real fast.


Grey Alien(Posted 2015) [#7]
I don't want to round the FP down to int, that's what most people do and it looks jerky. Yes I do want to batch the tilemap into a larger image but I don't know how to do that with a mesh or whatever. There's no point in using render to texture stuff as far as I know because it's supposed to be very slow. Also, drawing a screen of tiles on a modern PC is extremely trivial in terms of GPU power. Drawing thousands of particles is the sort of thing that needs to be optimised.

As for if people will notice, yeah that's valid for sure. There are lots of successful indie games with awful scrolling/framerate that sell fine. But I'm still interested to know if it's possible in BMax for my own satisfaction at least. I know it's possible in Unity and other engines where it's all drawn as 3D geometry for example.


therevills(Posted 2015) [#8]
When I perform scrolling I keep the logic as floating points but only render as integers.


Grey Alien(Posted 2015) [#9]
Yeah for sure I'd do that if I wanted to render at int coords but I want to try and render at float coords and sort this out once and for all.

Considering another approach. Using a projection maxtrix. Draw the tiles at int coords, then project the whole lot at a floating point end coord.

I've currently got this projection matrix code that I was using for zooming in on a screen (works well), but do you know if it can be modified to apply an x,y coord (using floats) somewhere in that matrix?

		If D3D7GraphicsDriver().Direct3DDevice7() = Null
			glMatrixMode GL_PROJECTION
			glLoadIdentity
			glOrtho 0, Self.width, Self.height, 0, -1, 1
			glMatrixMode GL_MODELVIEW
		Else
		     Local matrix:Float[] = ..
		     [..
		         2.0 / Self.width ,    0.0,    0.0,    0.0, ..
		         0.0,   -2.0 / Self.height,    0.0,    0.0, ..
		         0.0,   0.0,    1.0,    0.0, ..
		         -1-( 1.0 / Self.width ), 1+( 1.0 / Self.height ), 1.0, 1.0 ..
		     ]
		     D3D7GraphicsDriver().Direct3DDevice7().SetTransform(D3DTS_PROJECTION,matrix)
		EndIf


Though admitedly if I do that I have to make sure I can revert the matrix back to normal and draw the HUD over the top, if that's possible...

Hmm scratch that because of course you need to call SetTransform before you start drawing the tiles, so they'll no longer be at int coords as the matrix will have changed them.


Grey Alien(Posted 2015) [#10]
Well then it seems the only way is to use a 3D mesh, whatever that is, and make sure the graphics card knows to render all the tiles at once and thus calculate the join anti-aliasing correctly.

There's nothing to do that in BlitzMax, but it does have access to certain DX/GL commands and it's possible to add more commands (via C++) so it's gotta be doable. Something like this:

http://www.riemers.net/eng/Tutorials/DirectX/Csharp/Series1/tut13.php

and this (it's gotta work with DX9 and not use DX11 shaders to render the mesh)

https://msdn.microsoft.com/en-us/library/windows/desktop/bb153263%28v=vs.85%29.aspx


zoqfotpik(Posted 2015) [#11]
How jerky can it possibly be if you are rounding to int? How large is your screen resolution?

Drawing screen-aligned textured quads as a display list should be trivial but I don't know if I'm buying it that rounding to int would not be smooth, or at any rate that the lack of smoothness would be noticeable at all unless you're looking at the screen with an electron microscope or something. Pixels these days are easily sub-millimeter, it looks on my screen like there are 2 per millimeter.


therevills(Posted 2015) [#12]
Just checked some of my previous games scrolling code.

For The Bloobles I did use Ints and did the following:
		For Local x% = xstart To xend
			For Local y% = ystart To yend
				c = tiles[x, y]
				tile = c.CellType
				If tile > 0 Then DrawImage TilesImage.Image, Int(x * TILE_SIZE - Game.ScrollX), Int(y * TILE_SIZE - Game.ScrollY), tile
			Next
		Next	

But the movement was always at a set speed.

For the map in LoS:COTD, I used floats as it looked a lot better.

Heres a cut-down version of the map code and I've made so that you can switch between Ints and Floats using the spacebar:

https://dl.dropboxusercontent.com/u/35103024/bmx/Scrolling.zip




Grey Alien(Posted 2015) [#13]
Hi therevills, thanks for sharing that code and exe. Yeah the floats do look a lot better in it (jerkiness is gone as expected). However, you are still getting artefacts at the 128pixel joins, although they are quite minor due to the image type you've used (more on that below).

For example, find the 3rd vertical blank line in from the left edge of the map, then scroll it slowly. You'll see that black line "shimmers" even though the other lines do not. That's because it falls on a 128 pixel boundary and the issue I've been discussing crops up. There are a few other smaller examples but that's the most obvious.

So if I'm making a tile based games which has lots of square block graphics e.g. a castle, they will often have vertical and horizontal lines on the edges of the textures. When I draw the tilemap at a subpixel coordinate, I'll get loads of those shimmering lines everywhere, which I want to avoid. You are lucky that for that map the tile size you chose doesn't fall on very many lines as this issue is less pronounced on more "organic" looking textures.

In fact I modified your image to move it left 10 pixels and changed the tile size to 82 pixels and now I get shimmering joins on many of the vertical lines.


Grey Alien(Posted 2015) [#14]
Anyway after discussion with some other devs, I think the solution must be to use some render to texture (render target) code like this:

http://www.blitzmax.com/codearcs/codearcs.php?code=2222

That's only OpenGL though and I haven't tested it yet for a tile engine. I'll need a DX version but I just can't find one on these forums (there are references to one by indiepath that doesn't with new BMax).

Also I found an old thread elsewhere from TonyG saying he couldn't get a DX8+ render to texture working because "BlitzMax uses the TEXTUREMANAGE flag which means I couldn't set videomemory. Took out the texturemanage and it all worked OK." However, I'm pretty wary of doing that. Like where does BlitzMax use that flag? Just on creating textures, or when setting up DX? It could be pretty important! Like what if it causes alt+tab issues, or maybe I'm just being paranoid - but I've learned not to assume stuff will just work and be safe after all these years coding :-)


zoqfotpik(Posted 2015) [#15]
IMO the entire graphics engine should be doing this. I thought it was already.

You could also do it with fixed pipeline quads rendered in 3d but orthographic, with or without even using displaylists. That would be extremely simple and supported on everything and if you were doing, say, 32x32 tiles the speed would be perfectly fine.


Grey Alien(Posted 2015) [#16]
Yeah if the whole of BMax's backbuffer was a texture, you rendered on that, and then it was copied onto the main backbuffer (scaled as needed and at floating point coords if required) that would make all the projection matrix and virtual resolution stuff way simpler and would anti-alias/filter the entire texture nicely. Like building up a single image in photoshop and scaling it afterwards.

Currently you have to draw each sprite individually and if you've set up a projection matrix at the start, it means each sprite is scaled and drawn at floating point coords which yields a less cohesive final image.


zoqfotpik(Posted 2015) [#17]
Think of all the other stuff you could do as well, like postprocessing and color cycling effects in shaders. Seems like it would offer a gigantic speed boost as well as all the other benefits.

I would be willing to help with any sort of development along those lines.


Grey Alien(Posted 2015) [#18]
Yep, you could do all sorts of cool stuff afterwards. Sorta fancy effects I see in other games.

Well the main thing is to convert that openGL render to texture stuff (link above) to DirectX. Then we can just make a texture and swap the drawing code to it (though that might be tricky, dunno), then draw that whole thing on the backbuffer at the end.

Though I wonder if any of Brucey's mods has a DX version? (hmm doesn't look like it: https://code.google.com/p/maxmods/)

I'm also in contact with Indiepath who made a DX version years ago that stopped working with BMax updates, so I'll see if anything comes of that.


zoqfotpik(Posted 2015) [#19]
Why do render to texture at all? If it's a tilemap, why not just texture your bitmaps to individual quads in a mesh? Render to texture would probably be better and easier because it would be more transparent for people used to 2D but weren't you saying it was slow?

If it was just individual quads, you could have some of them missing to let a background show through. You could also use the OpenGL vertex lighting and coloring.


Grey Alien(Posted 2015) [#20]
No I was wrong about it being slow. I got confused with pixmaps.

A mesh might work, but it might also still have the join lines issue. Can't tell until I see it. If I'm going to spend my time working on one, I'd rather do the render target one as I know that will work.


zoqfotpik(Posted 2015) [#21]
A mesh might work, but it might also still have the join lines issue.

That's conceivably possible but wouldn't someone have noticed in 25 years of OpenGL?

I will see if I can throw a demo together this week, been about 3 years since I touched OpenGL to any extent but I've been meaning to get back at it... my tendency to be a retro-luddite is bad.


Pingus(Posted 2015) [#22]
Maybe it could work with textured polygons :
ex(there many threads about textured poly)

[a /posts.php?topic=100299#1183143[/a]

If each tile is drawn on a polygon, the antialiasing should be 'cut' on polygon's limits. There could be artifacts but no dark lines. Quite tedious to do tough. And I wonder about the performance with many polygons drawn 'by hand'.


zoqfotpik(Posted 2015) [#23]
It's certainly not too bad for say 32x32 and that's a pretty large tilegrid.


*(Posted 2015) [#24]
On monkey with a atlas sheet it would give the desired effect as it uses one texture for tile drawing operations and changes the UV coordinates of the image you want to draw, if you want to get rid of it you have to turn it off but it can be useful for these sort of problems.


TomToad(Posted 2015) [#25]
Do you really need anti-aliasing on tiles? Here is the example from the other thread, but with FILTEREDIMAGE disabled on the second image.
Strict

Graphics 800,600,0

Local x# = 0
Local y#=50
Local speed#=0.23
Local size=64

Local tiles1:TImage = LoadImage("tiles1.png")
Local tiles2:TImage = LoadImage("tiles1.png",MASKEDIMAGE)


While Not KeyHit(KEY_Escape) And Not AppTerminate()
	SetBlend AlphaBlend
	Cls
	'rectangles
	SetColor 200,200,200
	DrawRect x,y,size,size
	SetColor 200,100,100
	DrawRect x+20,y+20,20,20
	SetColor 100,100,100
	DrawRect x+size,y,size,size
	'two images with FILTEREDIMAGE set
	SetColor 255,255,255
	DrawImage tiles1,x,y+100
	DrawImage tiles1,x+size,y+100
	'two images with FILTEREDIMAGE removed
	DrawImage tiles2,x,y+200
	DrawImage tiles2,x+size,y+200
	
	'move
	x:+speed
	DrawText x,0,0
	Flip 1
Wend



Grey Alien(Posted 2015) [#26]
@TomToad: Try this and you'll see the issue (note I've dropped size to 32 pixels so modify if need be). The joins (and edges) are perfect but that's because they are clamped to integer coords by the graphics driver, and so when moving slowly they all jiggle badly.

If you add a 1 pixel blank border to those images (so they are 34x34) and draw those (but keep size variable at 32), you'll see the middle set (the filtered set) has nice smooth outer lines (no jiggling), but there's a black join line in the middle. The unfiltered set still jiggle badly.


Strict

Graphics 800,600,0

Local x# = 0
Local y#=50
Local speed#=0.23
Local size=32

Local tiles1:TImage = LoadImage("testimage1.png")
Local tiles2:TImage = LoadImage("testimage2.png")
Local tiles3:TImage = LoadImage("testimage1.png",MASKEDIMAGE)
Local tiles4:TImage = LoadImage("testimage2.png",MASKEDIMAGE)


While Not KeyHit(KEY_Escape) And Not AppTerminate()
	SetBlend AlphaBlend
	Cls
	'rectangles
	SetColor 200,200,200
	DrawRect x,y,size,size
	SetColor 200,100,100
	DrawRect x+20,y+20,20,20
	SetColor 100,100,100
	DrawRect x+size,y,size,size
	'two images with FILTEREDIMAGE set
	SetColor 255,255,255
	DrawImage tiles1,x,y+100
	DrawImage tiles2,x+size,y+100
	'two images with FILTEREDIMAGE removed
	DrawImage tiles3,x,y+200
	DrawImage tiles4,x+size,y+200
	
	'move
	x:+speed
	DrawText x,0,0
	Flip 1
Wend



Grey Alien(Posted 2015) [#27]
@zoqfotpik someone sent me a demo using a mesh but it exhibited join lines. So until I see one where it works, I'm ruling out that as a possible solution.

@Pingus I don't think that will solve it. The issue is that we want both adjacent tiles to know about each other and anti-alias along the join as if they were actually one large image. Drawing them separately (at floating point coords), they won't know about each other, which is why there's a join line in the first place. Also cutting anti-aliasing at polygon edge will result in jiggling on integer boundaries. Unless I'm misunderstanding you.

@EdzUp A texture atlas won't solve the issue I'm talking about. Whether you render a filtered tile from a single image, or an atlas it doesn't matter. The graphics driver will still sample the pixels around the tile you are using for the anti-aliasing. In a single tile, it'll sample blank space (result in a black join), in an atlas it'll sample the adjacent tile and use its colours as part of the anti-alias result. I've tested this and seen it in action.

Basically folks, we need render to texture for DX and OpenGL in BMax and it'll make this problem go away and allow us to do totally rad things.


Pingus(Posted 2015) [#28]
In fact dmaz already suggested the same thing... 8 years ago :-)

http://www.blitzbasic.com/Community/posts.php?topic=66157#739279

IF the drawpoly can be drawn at floating coordinates (antialiased subpixel), this should work.


zoqfotpik(Posted 2015) [#29]
Yeah GA it is way below my threshold of detail that I notice but I would love such a thing for other reasons, like the postprocessing thing. It would be easy to add in hooks for postprocessing shaders.


Grey Alien(Posted 2015) [#30]
@Pingus Nope the mesh doesn't work (based on my limited test). It doesn't anti-alias the joins properly. Like I said, someone sent me a demo and it didn't work. It's because when the mesh is drawn at a floating point coord the edge anti-aliasing looks at the pixels OUTSIDE the original tile size (say 32x32) and uses those (which come from the neighboring tile in the atlas). I proved this in the demo I was sent by drawing over the neighboring tile in the atlas with bright red and I could see that was what the mesh was using for anti-aliasing.

What all that means is maybe it's possible to make a 34x34 tile where the edge of the 32x32 tile is repeated in the outer border. Then when drawing in the mesh, use UV coords of the inner 32x32 square and it'll use a better colour for the anti-aliased join. Still not ideal though as the destination background colour will come through due to the join having an alpha value, and then you draw the adjacent tile on top of that (again with some alpha on the join) which results in a 3 colour blend. So it's not as accurate as if you just got the 2 adjacent tiles and blended those two colours for the join (based on their floating point coords used as weight).

However, I'm willing to be proved wrong! :-)

Still I say, render to texture is the way to go.


zoqfotpik(Posted 2015) [#31]
What if there was a 1 pixel transparent border around the texture?

You said this " if you add a 1 pixel blank border "
but it's not clear if you meant transparent. I presume you did.


Grey Alien(Posted 2015) [#32]
Yeah sorry transparent is what I meant. However, you can still run into issues because paint programs like photoshop give the transparent pixels a white colour which can be taken into account during rendering edges scaled or at sub-pixel coords. You can definge images like that (to remove the colouring) but I've found that just goes back to jiggly floating point scrolling as if it were at int coords, very strange.


*(Posted 2015) [#33]
Maybe rendering to a pixmap then drawing that might be the only way, I would have thought a quad sheet would have worked where each quad is a image then when rendered it would use its neighbours but I haven't run tests on that.


Grey Alien(Posted 2015) [#34]
Well you can't do drawimage on a pixmap can you? Only poke and peek pixels as far as I knew (after you've called LockImage() on it)


John G(Posted 2015) [#35]
@ Grey Alien -- One brute-force method I've used is to tape the joints between 2 tiles -- like a builder tapes the ugly seam between 2 sheets of dry-wall. Offline or during startup, place 2 adjacent tiles side-by-side using integer coordinates and GrabImage of several pixels from each tile. During dynamic floating-point run, tape over the gaps. Seems to mask flicker at different zoom levels.



I've used this for side-scrolling of long backgrounds sliced into 512 pixel chunks where drawing 4 pixel tapes doesn't effect overhead much.


Grey Alien(Posted 2015) [#36]
Ha that's a novel solution. Thought I'm still looking for a render to texture solution... Pretty sure I've searched these whole forums now. Found bits but nothing 100%