• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!

Fake 3D, 2.5D, depth = -y Code Analysis

Kezarus

Endless Game Maker
TLDR version: I find no performance issues between "depth = -y" and other "improved" algorithms and I would like some opinions. Thanks! =]


Well, I made some performance analysis on 2 ways on making a Fake 3D effect.

Both are well contained in specific objects that handles it all.

You feed objects (not instances) to an array and they loop throught them and do all the setup.

The first, obj25D, create all layers and stores them in an array that is used to sort the object's depths.

The other, objSimpleFake3D, just do a "depth = - y" on all pertinent objects. I know that it can be improved by declaring an endObjectDepth to streamline things, but it's just a test.

I do not see any performance hit in neither of them, so I am tending towards the "depth = - y" because it's waaaay simpler.

Here is the full source code.
Fake 3D Project

@Yal, @Siolfor the Jackal, that's about that post we were talking about. Hop in if you feel like it.

I really would like some opinions about this matter. What does the good people of the forum think about it? =]
 

curato

Member
If you are managing the layers instead of letting the engine manage them then there is no reason not to use depth. More to the point I would think that would be the preferred method for what you are talking about.
 

Kezarus

Endless Game Maker
Hmmm, as I understand, layers could be fished out by layer_get_id_at_depth(depth), isn't it?

And, I am probably missing something, what could be done with layers besides shaders and panning it?
 

Yal

🐧 *penguin noises*
GMC Elder
TLDR version: I find no performance issues between "depth = -y" and other "improved" algorithms and I would like some opinions. Thanks! =]
Interesting result... I would've imagined there to be some sort of noticeable difference since the official word has been "stop using depth = -y, it's horribly inefficient now". Though managed layers that runs C++ probably will be faster than depth-sorting run in VM GML... Just to make sure, you've tested with a large number of objects at diverse depths, right?
 

Neptune

Member
IMO depth = -y has draw backs. I tried it once upon a time, and it didnt work too well because of the sprite origin being the render point, which is not ideal for many things.
Depth = -y also has no certainty that things will be rendered uniformly if at the same depth, so you get weird visual consistency... Say for a line of trees all at the same y value.
The "render control list" has a lot more flexibility, but is the same concept. I personally do something like render_y = y + x/10000 + render_add (if there is not some other forced render point taking over) -- this renders top to bottom, left to right uniformly, offering no inconsistency or sprite flickering etc.

Either way can probably be made to work, but playing around with thousands of depth layers and also trying to incorporate tile layers and whatever else is just asking for trouble...
Performance shouldnt really be note worthy for either approach.
 

Kezarus

Endless Game Maker
ust to make sure, you've tested with a large number of objects at diverse depths, right?
Define this "large" that you are talking about, @Yal. Lol. XD

I'm using this:
Room: 3200 x 3200px
Immovable Objects: 1250
Mobile Objects: 200

With managed layers:
1597179737026.png

With simple depth = -y
1597179783836.png

I left them running for a bout a minute. As you can see, nothing changed much.

I provided the source code too if anyone want to play around the solution.

As I could demonstrated in the example, and as I understand, there is no change in performance whatsoever. That's odd... o_O

Did I mess up?
 

Yal

🐧 *penguin noises*
GMC Elder
Define this "large" that you are talking about, @Yal. Lol. XD
Like, one hundred thousand instances spread evenly over a room 10000 pixels tall. A thousand-something objects is still so few that it's hard to notice any performance impact. If your code works flawlessly for a more extreme case than you ever expect to encounter in the wild, you can be pretty sure it works just as fine for actual use cases too. If it fails in spectacular ways, you can learn what the weak points of your code is relatively easily, and then either try to fix that (if it could happen for more modest situations) or just know what to avoid when using the code.
 

Kezarus

Endless Game Maker
Thanks @Yal! Like a stress test then! =D

I was aiming for a more down to earth (but extreme) example. I would never use so many objects in a room as I did usually. And sure that 100.000 is an over the top number.

But that will surely expose any flaws it might have. Gonna try that (it's VERY easy to do). Will do just that after work and other things (in about 10h). Thanks! =D
 
  • Like
Reactions: Yal

Yal

🐧 *penguin noises*
GMC Elder
Sounds great!

Oh yeah, and one thing I forgot to bring up: since you probably will get slow performance for both versions during the stress test, running the profiler to figure out what's using the most resources probably will be necessary.
 

Kezarus

Endless Game Maker
Nice reminder! You're the best!

I am so amped to do it. Gonna try to slip this really quickly on my schedule. 🤩
 

Kezarus

Endless Game Maker
These were my findings.

I'm using this:
Room: 128k x 128k px
Immovable Objects: 90.000
Mobile Objects: 10.000
Time: ~1 minute

With managed layers:
1597231509021.png

With simple depth = -y
1597231963445.png

Hmmm, that's odd, "depth -y" fps' went rock bottom. But I have no idea why. o_Ô?

Well, gotta run! See ya!
 

drandula

Member
(edit. I was writing this before your results)

To toss my coin here abot "depth = -y", I had tested this about year ago myself, this is what I found:
There is no real performance hit when instances stay on the depth they are, which is case for static instances and characters which don't move in y-axis.
If there is few instances, which move in y-axis, there are no noticeable performance issues.
Now when there are considerable amount of instances which move in y-axis (or change depth any way), performance takes a toll.

What I have understood why this is case (not necessarely how it is):
- When instance is not assigned to specific layer, it creates temporal layer at the depth where it is.
- When instance depth is constant, this temporal layer stays intact and same.
- When instance changes depth, it removes old temporal layer and creates new one. This causes performance hit

I don't remember do instances at same depth share same temporal layer, or do they all have individual ones. (If it is individual, it makes depth = -y even worse)
 
Last edited:

drandula

Member
Now when using "depth = -y" (or similiar method, where depth varies depent on position), it will basically make performance to vary with amount depth's vary. It is linked to amount of instances, which depths are changed.
I think with other depth-sorting methods you can avoid 'movement of instances' affecting your performance, as removing and creating temporal layers is costly.
 

GMWolf

aka fel666
Isn't the point of depth = -y that having many layers is slow?
I haven't investigated this but I wouldn't be surprised if many layers breaks the sprite batching system.

So just managing layers yourself is also going to be equally as slow.

Have you tried using sorting a custom drawing code, but still just one layer?
 

Kezarus

Endless Game Maker
Hm, I remember your picture from some post related to performance. I should have bumped that in my searchs. Nice to have you around, @drandula!

About the performance: as I can see "depth -y" is not the best solution ever, but neither is "managed layers" a silver bullet. I can perceive that something is frantically trying to clean and reform something in the memory. But it seems that the objects are sharing the layers, if not, the memory consumption would be a LOT higher.
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
I don't remember do instances at same depth share same temporal layer, or do they all have individual ones.
All instances on managed layers that share the same Y will be on the same layer.

The best method is to use a depth ordering system and do it yourself, although I also found that creating layers yourself at room start and then using them was also pretty fast. For example, if your game is tile based, you would (at the start of the room) create a layer for each Y cell of the tile, so if the room is (for example) 480px in height and tiles are 32x32, then you'd create 15 layers. You store these layer IDs in a global array and then when the instance y (rounded to the cell) goes to a new Y cell, simply set it's layer to be that of the corresponding layer array position. EG:

GML:
var _y = y div 32;
if layer != global.layer_array[_y] layer = global.layer_array[_y];
Something like that is actually really good on performance as you are not creating/destroying/creating layers all the time and it's a bit of a cross between letting GM handle things and doing it all yourself.
 

Kezarus

Endless Game Maker
Isn't the point of depth = -y that having many layers is slow?
I haven't investigated this but I wouldn't be surprised if many layers breaks the sprite batching system.

So just managing layers yourself is also going to be equally as slow.

Have you tried using sorting a custom drawing code, but still just one layer?
Hmmm, I would rather prefer a not so dramatic solution as kicking out the draw event of everyone. But probably, for a very draw intensive, this is a way to go.

The best method is to use a depth ordering system and do it yourself, although I also found that creating layers yourself at room start and then using them was also pretty fast. For example, if your game is tile based, you would (at the start of the room) create a layer for each Y cell of the tile, so if the room is (for example) 480px in height and tiles are 32x32, then you'd create 15 layers. You store these layer IDs in a global array and then when the instance y (rounded to the cell) goes to a new Y cell, simply set it's layer to be that of the corresponding layer array position. EG:
I tried to use a "cell height" solution, but had appalling results concerning visuals. My solution provided above could easily be changed to that just changing the tolerance of the layers in the object. Hmmm, did I do something wrong, @Nocturne?
 

GMWolf

aka fel666
Hmmm, I would rather prefer a not so dramatic solution as kicking out the draw event of everyone. But probably, for a very draw intensive, this is a way to go.
You can just invoke their draw event.

So first make all "depth managed" objects invisible.
Then call their draw event in a loop in their depth order.

 

Kezarus

Endless Game Maker
You can just invoke their draw event.

So first make all "depth managed" objects invisible.
Then call their draw event in a loop in their depth order.

event_perform
Yep, like a "Master Object" that does the drawing. I understand that.

What I was not aware of... is that you could actually CALL the Draw event of an invisible object. Hmmm, nice! Thanks @GMWolf! =D
 

Nocturne

Friendly Tyrant
Forum Staff
Admin
I tried to use a "cell height" solution, but had appalling results concerning visuals
Yes, if your game isn't absolutely tile based (or for many other reasons), then you'll get z-fighting doing it the way I suggest, as instances on the same layer will not have a guaranteed draw order. :(
 

GMWolf

aka fel666
A trick with the pigeon hole sorting method (what nocturne suggested) is that you dont actually need the full set of slots for the entire level, just what is on screen.

So you can totally use it with one list per vertical pixel. Just offset it by your screen position first. (You will need to add padding to account for long objects)
 

Kezarus

Endless Game Maker
A trick with the pigeon hole sorting method (what nocturne suggested) is that you dont actually need the full set of slots for the entire level, just what is on screen.

So you can totally use it with one list per vertical pixel. Just offset it by your screen position first. (You will need to add padding to account for long objects)
As I understand it have something to do with the x y offset of the sprite. The padding var should do the trick... hmmm
 

Neptune

Member
If you use a draw-order system, as I or a few others suggested, you do not have to get rid of the draw event.
There are a few ways, but I personally just inherit the code if render {render = false;} else {exit;} in my draw events that use "depth rendering".
And then your list control code will just set render = true; for each instance as it goes about its ordering business :)

So basically, your objects' draw events are holding back drawing (from the exit) until their respective boolean becomes true each step.
This way is even a tad clunky, but performance is so far from being an issue.
The actually drawing of 10,000 objects is going to magnitude out weigh the processing to organize a render list, and initiate the rendering.
 

Kezarus

Endless Game Maker
Thanks everyone for all your thoughts about this topic! I think I learn a thing or two with this. =]

For one, the "depth=-y" solution is not as harmful as I thought. I think a simple performatic solution would be to make all the managed objects invisible, put a specific Draw Event (if specifics are needed like some yoffset to change depths) and all managed objects be drawn by a Controller Object that sorts out the layers by a "depth=-y".

If more performance is needed, then it's just a matter of putting the code for creating layers and sort them out by "layer = arrLayers[aaa]".

Seems easy and practical enough for me. =]


Thanks again!
Kezarus
 
Top