Controlling Moai Rendering Order - Part 2

This is the second of two posts looking at ways to control object rendering order in Moai. That is, managing which objects appear on top of which other objects in your game.

The first post looked at the more straight-forward techniques of using insertProp() ordering and pushRenderPass() layering. This post looks at two more techniques: Partition priority and render tables. It requires digging a little bit under the hood to see first: what the code is doing when you insertProp, and, second: what happens when you pushRenderPass. Both techniques are very handy, so without further ado...

Partition Priority

The documentation for insertProp says only: "Adds a prop to the layer's partition.", and indeed the code for insert prop is just:
self->AffirmPartition ();
self->mPartition->InsertProp ( *prop );
prop->ScheduleUpdate ();
So, what then is a partition? There's much documentation for them in the SDK itself, but there is info available on the forums.  A partition is used by a layer to manage its list of objects. They play several important roles for layers including clipping and picking, but the aspect we're most interested in here is sorting.

According to the Moai folks ( here ):
The partition is the object container... the layer just combines a camera, partition and viewport...
  • MOAIPartition: stage where all of your actors are performing.
  • MOAIViewport: the frame in which the stage is displayed.
  • MOAICamera: the vantage from which to view the actors.
  • MOAILayer: a stage as viewed by a camera and drawn into a viewport.
By default, partitions behave as a list of objects sorted in priority order. When the layer goes to draw its objects, it asks the partition for the relevant objects, and objects with a lower priority number wind up rendering before ( and, therefore, underneath ) objects with higher priority number.

The reason insertProp() works as discussed in the first post -- rendering in the order you inserted them -- is actually due to priority sorting.  For each prop you insert, if the prop has not been previously assigned a priority, the prop is automatically assigned a priority number one higher than the previously assigned priority number.

On the C++ side, inserting first a red prop, then a blue prop, looks something like this:
// layer:insertProp( red ) 
if (red.priority isn't set) { 
 red.priority = ++layer.priorityCounter; 
} 

// layer:insertProp( blue ) 
if (blue.priority isn't set) { 
  blue.priority = ++layer.priorityCounter; 
}
By controlling priority, therefore, you can control the order of rendering. Here's a rework of the sample code from the previous post:
local red= MOAIProp2D.new ()
red:setDeck ( deck )
red:setColor( 1,0,0 )
layer:insertProp ( red )

local blue= MOAIProp2D.new ()
blue:setDeck ( deck )
blue:setColor( 0,0,1 )
blue:setLoc( 16,16 )
layer:insertProp ( blue )

blue:setPriority(50) -- render blue early
red:setPriority(60)  - render red late
Even though red was inserted before blue, in this sample, both are manually assigned priority numbers. Blue was given a lower priority, so it renders first. You can, at any time, change the priority of any prop, and therefore you can easily change rendering order.
-- now make red render before blue
red:setPriority(40)
Playing with priority is extremely useful, but it is a little like programming Basic circa 1985. If you might ever want to insert an object between red and blue, you have to have left enough numbers to do so. For example, if I needed a white prop in between the red and blue, I could have given the white prop a priority of 45 and all else could have stayed the same.

Priority Groups


Nothing will crash if you set two objects to the same priority. Which object renders first is undefined, but consistent from frame to frame. Sometimes this behavior is perfectly fine, even perhaps desirable.

groups and layers can be emulated via setPriority.
In Dawn, for instance, every town is its own sprite, but since towns never overlap each other, it'd be fine to give them all the same priority number.

Using this trick, we can create groups of objects without the overhead of having multiple MOAILayer(s). All we need to do is give each group of objects its own unique priority. All map tiles could have a priority of 10, towns a priority of 20, paths of 30, and so on.

In fact, even for character sprites, since there is no "correct rendering order" -- should the mercenary render over the assassin? does it matter? -- all characters could likewise share a single priority, so long as it's larger than the other map elements.

One last note: once a priority is set, it "sticks" with the prop until manually cleared, or set to something different. This, then explains the bug mentioned from the previous post. Simply removing a prop doesn't clear priority, so simply removing and re-inserting a prop doesn't change the order it gets rendered in.

Other Partition Modes

As far as I can tell, there is no attribute for priority to allow you to tie priority to curves or other run-time evaluations, but there are modes other than priority that you can use for sorting props. In Lua, the options are part of  the MOAILayer class, and the complete list is:
  • priority sorted by ascending ( the default ), or descending order.
  • world x,y, or z value; sorted by ascending or descending value.
  • distance along an arbitrary vector, sorted by ascending or descending distance (good for sorting objects as viewed from a camera.)
  • sorting for isometric rendering.
  • sort none - arbitrary but, i believe, frame-to-frame consistent ordering.
As a quick example, the following would render the blue on top of red because the blue prop sits to the right of the red block.
layer:setSortMode( MOAILayer.SORT_X_ASCENDING )

 

Render Tables

Just as insertProp is used to put objects in layers, pushRenderPass is used to add layers to the scene. As mentioned previously, it's an interesting function because, even though it's used in all of Moai's samples, it's marked as deprecated. There's no recommended replacement for the deprecated function, but taking a look at how its implemented ( in lua-headers/moai.lua ) is revealing.
MOAIRenderMgr.extend (
  'MOAIRenderMgr',
  ...
  function class.pushRenderPass ( pass )
    local renderTable = class.affirmRenderTable ()
    table.insert ( renderTable, pass )
  end

What is this strange render table you speak of?

Render tables are the core element of rendering in Moai. They are normal Lua tables, treated as arrays.

Moai ( via the MOAIRenderMgr class ) holds a single top level render table, and once per frame Moai traverses the table in order, and attempts to render each element. If the element is a piece of user data, and it derives from MOAIRenderable (ex. a prop or a layer ), then it draws that object. If the element is another render table, then it renders the elements of that table. Otherwise, it stops rendering the table.

We can therefore achieve the same results as pushRenderPass with a render table:
local myRenderTable= {}
MOAIRenderMgr:setRenderTable( myRenderTable )

-- create a viewport 
-- 0,0 will be at upper-left corner of the screen
local viewport = MOAIViewport.new ()
viewport:setSize ( 128, 128 )
viewport:setScale ( 128, -128 )
viewport:setOffset ( -1, 1 )

-- create a layer associated with that viewport
local layer = MOAILayer2D.new ()
layer:setViewport ( viewport )

-- dont add the layer directly to the rendermgr
-- MOAIRenderMgr.pushRenderPass ( layer )

-- add it to the render table instead
myRenderTable[ #myRenderTable + 1 ]= layer

-- create a 'deck', the visual appearance for a 'prop'
local deck = MOAIGfxQuad2D.new ()
deck:setTexture ( "white.png" )
deck:setRect ( 0, 0, 32, 32 )

-- create the prop
local prop = MOAIProp2D.new ()
prop:setDeck ( deck )

-- add the prop to the layer
layer:insertProp ( prop )
This results in the following behavior:
Moai rendering ->
 RenderMgr::Render ->
   render all elements in myRenderTable:
     element[1] is a layer so:
       device.reset
       set viewport and rendering transforms 
       layer.partition sort all props by priority:
         for all props:
            prop.render
At first, this may seem not very much more useful than what we already had. You need a layer to setup the device ( aka. the rendering hardware ), the viewport ( aka. the screen ), and transforms ( aka. the camera, etc. ) so what new technique have we gained to control rendering order? 

The key here is noting: the layer doesn't clear the rendering state when it's done. This is a very common behavior in rendering. Systems often try to ensure they set up all the bits and pieces of rendering they needs before rendering, because they can't otherwise guarantee objects will get rendered correctly. However, since they do this, it's not really necessary to *restore* everything, because the very next system that's going to draw is going to reconfigure everything anyways.

At any rate, what this means we don't need to use a layer to *contain* props, we just need to have a layer render *before* props.
local myRenderTable= {}
MOAIRenderMgr:setRenderTable( myRenderTable )

-- create the viewport and layer
local layer= ....

-- add it to the render table
myRenderTable[ #myRenderTable + 1 ]= layer

-- create a deck and prop...
local prop = ...

-- don't add the prop to the layer
-- layer:insertProp ( prop )

-- add it to the render table
myRenderTable[ #myRenderTable + 1]= prop
Now,  we can control rendering order by simply moving props around in the render table.
local red= MOAIProp2D.new ()
red:setDeck ( deck )
red:setColor( 1,0,0 )

local blue= MOAIProp2D.new ()
blue:setDeck ( deck )
blue:setColor( 0,0,1 )
blue:setLoc( 16,16 )

myRenderTable[ #myRenderTable+1 ]= blue
myRenderTable[ #myRenderTable+1 ]= red
Even more interesting, because render tables can contain other render tables, we can create a hierarchical system of rendering, similar to how Flash works.

To use Dawn's layers as an example again, it's easy to setup the following scenario:
local mapTiles= {}
local towns= [}
local paths= {}
local sprites= {}
local world= { mapTiles, towns, paths, sprites }

local layer= ...
MOAIRenderMgr:setRenderTable{ layer, world }
If the Moai folks were to include Lua function callbacks in the list of acceptable render table elements, we could have a really powerful system indeed!

Now, whether this will work for you or not, depends on the nature of your project. You don't get partitions, which means you don't get automatic clipping of props to the screen, but -- if you know in advance the visibility of your objects ( as many games do ) -- this can actually be a benefit because it means your game isn't weighed down by partition sorting.

Final Thoughts

I've presented four techniques now for controlling rendering order with Moai: simple insertProp, layering via inserRenderPass, low level prop priority sorting, and even lower level render table management. It's worthwhile, in parting, to note your game doesn't have to use just one technique or another. You can mix and match to get the results you need.

You might, for instance, have two layers: one layer that contains all of your game objects via insertProp, and another layer that precedes all the elements of your user interface in a render table. You could use priority sorting for your game objects, giving background objects a low priority number, sprites a slightly higher number, and particle effects the highest number. On the other hand, since your user interface elements are more or less guaranteed to be entirely on screen, and since UI is essentially just a set of components nested inside one another -- render tables will make control easy, and keep rendering lightweight.

3 comments:

Unknown said...

Wow!
This is by far the best series of Moai tutorials I've seen so far! Thanks so much for such a great work peeking under the hood in all the right places.
And keep'em coming please :)
Cheers
Nenad

ionous said...

Thanks for the encouragement!

I'm still casting around for what to look at next. So far, it's all been research done while porting Dawn. Right now, though, I'm just going through the slog of implementing UI screens so nothing super interesting has been popping up.
No guarantees, but, if you have any requests, let me know!

Ben D. said...

I agree, this is a very useful post. Lots of great insight! I've been using render tables but never realized their power until reading this. Keep it up!