Shaders Depth ordered with statement

kraifpatrik

Member
Hey everyone,

throughout the time working with shaders in GM, I've found it convenient to set the visible property of all objects that I want to apply a shader on to false and then simply draw the objects in a single renderer object with

Code:
shader_set(myShader);
with (myObject) {
  // draw instances here...
}
shader_reset();
The problem with this is that the with statement goes through all instances based on their order of creation, not their depth, so the instances end up overlapping each other in a different order than they normally would. One way to solve this is to place the instances into a list, sort them by their depth and them draw them in a for cycle, but that is much slower compared to the with statement. And since GM internally already sorts the instances for the draw event (or at least I think it does), I thought it would be cool if our dear YoYoGames somehow exposed this order to be used in GML, maybe by the with statement being sorted by depth by default, or creating a new keyword, that would behave basically the same as with, except it would go through instances based on their depth.

So what are your thoughts on this guys? Is this something that you deal with too? Would this be a feature that you would welcome?

Patrik

EDIT: Also I'm sorry if this is a wrong forum section again.
EDIT2: Wrong forum section confirmed. God dammit!
 
Last edited:
C

Chungsie

Guest
we are all allowed to create our own custom functions. with enough time and thoughts, I'm sure you could create a great function that did what you needed. All the building blocks are in place, it just takes some engineering to construct something desired.
 

andev

Member
we are all allowed to create our own custom functions.
Of course we can :D But the thing is, it's already been calculated internally. Having access to that would just make things less cluttered. For example, something like this would be really useful, only instead of "active" instances, it would be "visible" instances, in order of being drawn.

Re-calculating it through GML is less efficient, but you could do it by making a global array, then making each instance append their id to it during the draw event (making sure you reset it each step).
 

kraifpatrik

Member
(...)but you could do it by making a global array, then making each instance append their id to it during the draw event (making sure you reset it each step)
Well actually, this quite does the job (maybe a ds_queue is more suitable for this). It's not that fast as a depth ordered with could be, but it's definitely faster than sorting the list in step or something. I think I will use this as a solution, but I still think that the "enhanced" with would be cool.
 
Last edited:

Surgeon_

Symbian Curator
1st Solution: There's one easy solution which can work if the depths span of all the objects being drawn this way isn't too big: Make an array of surfaces, then in the with statement each object draws itself onto the surface corresponding to its depth, then the rederer would overlay the surfaces in the correct order onto the screen.
Code:
for (var i = 0; i < DEPTH_SPAN; i++)
    surface_clear(surface[i]);

shader_set(myShader);

with (myObject) {
  surface_set_target(other.surfaces[depth-DEPTH_SPAN]);
  // Draw ...
}

shader_reset();

//Draw surfaces onto the screen in correct order ...
Now, a few disclaimers: I didn't test this code so I'm not sure if it's 100% correct (for example, I don't know if it would work like this for negative depths but it could be tweaked if not). I'm also not sure about performance but you could benchmark it if you like the idea. You'd also need to take care of offsetting the draw coordinated because user-made surfaces are always indexed from (0,0).
 

kraifpatrik

Member
I think I've never compared a queue to a list in terms of speed, but I thought queue would be more suitable here, because it more fits the needs. There won't be a situtation, where I would need to access it's items in some random order, but always sequentially without even caring about index.

With and for perform quite similarly, but I think if there was the "special" with, it would be faster overall than first every instance putting it's id somewhere and then a controller basically going through all the instances again.

@Surgeon_ That would really cause my pc to explode, since I'm already using a few fullscreen sized surfaces :D
 

andev

Member
I thought queue would be more suitable here, because it more fits the needs
I agree, its functionality lines up perfectly. But you can do exactly the same thing with:
Code:
ds_list_find_value(list, ds_list_size(list)-1);
and if you have a lot of instances to iterate though, it's quite important to maximise efficiency.

it would be faster overall than first every instance putting it's id somewhere and then a controller basically going through all the instances again.
If the controller was using a "with" to access every single instance then yes, for sure it would be really slow :D But I'd have to check the speed of "with" vs other methods.
 

TheouAegis

Member
We should be discussing priority queues here, not normal queues. He has to add presorted values to a list. I suspect a priority queue could do that faster than him. You all act like you just throw values into a list and use ds_list_sort(listID,depth), but it diesn't work that way. He would have to loop through the entire list, peek each stored id's depth until it finds one lower than the current one's depth, then add it into the list.

Or if we're dealing with Studio here,use the Begin Draw event to let each instance fill the list on its own.
 
Last edited:

kupo15

Member
Or, like, you know, if we're dealing with Studio here,use the Begin Draw event to let each instance fill the list on its own.
hmm really clever there! ;)

I'm curious, whats the big advantage to having a controller object draw everything? Is it for organization or would it help batch things better or some other technical optimization?
 
Last edited:
you can use a priority queue, and then copy the instance id's into a list. You then could avoid rebuilding the queue until the number of instances change. Actually, if instances are being added at a slow rate, it might be easiest just to insert into the list and skip the priority queue.
 

kupo15

Member
you can use a priority queue, and then copy the instance id's into a list. You then could avoid rebuilding the queue until the number of instances change. Actually, if instances are being added at a slow rate, it might be easiest just to insert into the list and skip the priority queue.
I like Theou's method better. Since objects are drawn in order according to their depths under the hood by default simply putting

ds_queue_enqueue(depth_order,id) in the begin draw of every object already orders them by depth without additional sorting. Any object that isn't visible will skip the begin draw and therefore not be added to the queue.Then you the controller object can easily dequeue it in the Normal Draw Event
 
I like Theou's method better. Since objects are drawn in order according to their depths under the hood by default simply putting

ds_queue_enqueue(depth_order,id) in the begin draw of every object already orders them by depth without additional sorting. Any object that isn't visible will skip the begin draw and therefore not be added to the queue.Then you the controller object can easily dequeue it in the Normal Draw Event
It just occurs to me that the number of isntances probably wont change much between frames, so rebuilding the queue every frame is probably unecessary. You might have one, or zero new instances added, and inserting them into the list should be rather efficient. If the depth of instances changes, like it might with an isometric game, then this wouldn't work well.
Code:
    //insert instance into list (depth sorted greatest to least)
    var _id = argument0;
    var _list = argument1;
    var _n = ds_list_size(_list) * 0.5;
    //------------------------------------------------------------------------------
        var _m = _n;
        var _d = _id.depth;
        //binary search
        while (_n >= 0.5) {
            _n *= 0.5;
            if (_d > _list[|_m].depth) { _m -= _n; }
            else { _m += _n; }
        }
        ds_list_insert( _list, round(_m), _id );
    //------------------------------------------------------------------------------
 

Paskaler

Member
Here's code that I use in one of my games. Do note that I never had more than 60 or so instances that did this.
Like previously mentioned, I had a global drawing object, too, and then all of the instances that had to be drawn ordered had this code in the end step event:

Code:
ds_priority_add(Drawer.queue, id, y);
Another thing to note is that I'm on GMS 1 and that I had to order them by Y coord because of the perspective.

In the draw event of the Drawe object I did:

Code:
// The docs say repeat is faster than for and I don't need i, so, why not
repeat ds_priority_size(queue) {
    with ds_priority_delete_min(queue) {
        event_perform(ev_draw, 0);
    }
}
 

andev

Member
You all act like you just throw values into a list and use ds_list_sort(listID,depth), but it diesn't work that way.
Well that would just put the instances back into creation order :D As you have also since said, I was suggesting putting the instances in that list from the draw event.

whats the big advantage to having a controller object draw everything? Is it for organization or would it help batch things better or some other technical optimization?
I personally use it for organizing, it puts all the code in once place, and allows more control over what order things happen. I doubt it's faster though, as accessing instance variables from another object is slower than accessing them locally.

You then could avoid rebuilding the queue until the number of instances change
The queue would need rebuilding every time an instance changes y position. But the problem with priority/queues is you can only access the data at the ends. So if you want to "store" the data, you'd have to transfer it to a second list. Not efficient.
 

Micah_DS

Member
Here's code that I use in one of my games. Do note that I never had more than 60 or so instances that did this.
Like previously mentioned, I had a global drawing object, too, and then all of the instances that had to be drawn ordered had this code in the end step event:

Code:
ds_priority_add(Drawer.queue, id, y);
Another thing to note is that I'm on GMS 1 and that I had to order them by Y coord because of the perspective.

In the draw event of the Drawe object I did:

Code:
// The docs say repeat is faster than for and I don't need i, so, why not
repeat ds_priority_size(queue) {
    with ds_priority_delete_min(queue) {
        event_perform(ev_draw, 0);
    }
}
I've done exactly this before in a GMS2 test project, so I wanted to share an issue I discovered with it:

The depth order works for the most part, but when two or more instances are overlapping on the same y coordinate, they will change dominance randomly, creating a flickering effect. To be more specific, this only happens when at least one instance changes its y value.
If you want to test this, you could have two same-y'd instances which overlap on x, and another instance which is changing its y. The overlapping instances will flicker.
Depending on the game, it may not matter, but if you had something like large sprites that overlapped on the same y coordinate, it would be pretty nasty.

-
In any case, I'd argue that this is by far the best idea here:
if we're dealing with Studio here,use the Begin Draw event to let each instance fill the list on its own.
This would be fast and without the dominance issues that can cause flickering of overlapping instances. The priority queue is also pretty slow, especially for something done every step.
Since the draw order is already being sorted, it makes sense to ride off of that instead of doing it all over again.
 

Surgeon_

Symbian Curator
To solve the flickering effect, make a list that's sorted by depth, and sort sub-lists with the same depth by instance ID.
 

kraifpatrik

Member
@kraifpatrik did you say what kind of game you're talking about? Will the instance's have a constant depth, or will their depth change frequently?
It depends... Some of the objects are likely to have constant depth since their create event (but it should be possible to change if needed), but some can possibly change every frame. So it's rather unpredictable and so I need the system to account for that.

So far of all the solutions I like the most one where every instance puts their id into a list (or a queue, I still haven't compared their speed) in the Draw event and then I just draw them in a renderer object in the Draw End event. Anything else that includes auto sorting structures or requires the structure to be sorted in Step (or somewhere else) on change (which can be every frame for many instances) just adds too much overhead.

The solution needs to be as much in line with GM as possible, meaning that you shouldn't do work that GM internally already does for you.
 

Mike

nobody important
GMC Elder
Use the PRE-DRAW event to set up values that the DRAW even will pick up. The Pre-Draw is depth based and will be "like" doing a WITH() using depth.

OR.... use the PRE-DEAW event to add the instances ID into a list. In the Draw event this list will then be a depth ordered list of IDs you can iterate over...
 

kraifpatrik

Member
(...)use the PRE-DEAW event to add the instances ID into a list. In the Draw event this list will then be a depth ordered list of IDs you can iterate over...
Unlike for the draw event, when I put the code in the pre-draw event, the instances' sprites are still drawed by GM, which in my case is not desired. I want the instances to have a sprite set, visible set to true, but not actually draw anything, just add their id into the list. If this is not a bug, then I have to stick to the basic draw event.
 
Last edited:

Mike

nobody important
GMC Elder
This lets you build a list of items - in order (which is what you said you were after) BEFORE actually drawing them in the draw event.
 

kraifpatrik

Member
This lets you build a list of items - in order (which is what you said you were after) BEFORE actually drawing them in the draw event.
I've got that already working with draw + draw end, so I was thinking if there was some advantage of changing it to pre-draw + draw (if I have to add the empty draw event). Seems like it's not, but still thanks.
 
Top