-
Notifications
You must be signed in to change notification settings - Fork 111
Batch Rendering Guide
The 2D rendering pipeline in Torque 2D is based upon a scene render-request setup where potentially visible objects get to push render requests out to a batch renderer. These are then processed (sorted if appropriate and many other things) and then each request is processed for rendering. This unifies and centralizes all the 2D rendering and provides a batching system to boot. It enables features like setting the global rendering mode i.e. wireframe, etc, in a central location.
It also enables useful things like render requests being sorted amongst other render requests from various other objects i.e. all the render requests get put into a render queue and can be sorted together independent of where they came from.
A concrete example of this can be found in the CompositeSprite object. CompositeSprites can push render requests for the sprites it contains. Any render request can be placed anywhere in 2D space, have an age (serial-Id), have a scene-layer and a scene-layer-depth (layer sub-depth), etc. The sort modes in T2D allow you to not only sort on a per-layer basis but also on a per-object basis. But what does a sort on a per-object basis mean? Well imagine a CompositeSprite that has a bunch of isometric tiles and it has positioned each tile appropriately but also each tile has a scene-layer-depth set.
CompositeSprite.setSpriteDepth(%layerNumber);
You can tell the CompositeSprite to sort by scene-layer-depth and it will have all its sprites rendered in that order. The thing is, that’s okay for a set of sprites that work as some kind of complex background but what about other sprites moving “within” that scene-layer-depth? In this case we want other sprites to move within the isometric sprites and be occluded by them if appropriate. Well, you can tell the sprite to NOT sort (which is the default) but have its sprites sorted as part of the main layer-sort. If you were to then add separate sprites in the scene then they will get sorted alongside the sprites from within the CompositeSprite. If you move the sprites then they move within the composite sprite (as a tile-layer in this example).
This sounds complex but it’s really simple and it’s controlled by two methods: one controls whether the object is batch-isolated i.e. it is batched, sorted and rendered in an isolated way or if it’s not isolated and is batched, sorted and rendered with the rest of the scene layer.
Essentially then, SceneObjects don’t render themselves, it’s scene-render-requests that are the mechanism to render. This means that anything can submit such a request and not just SceneObjects. A SceneObject is convenient as it is owned by the Scene but it has no special capabilities or exclusive domination on scene rendering.
In addition to sorting each scene layer individually, there are the following sort modes to choose from:
- Off - No sorting.
- Newest - The newest render request (typically the age of the SceneObject) is rendered in front.
- Oldest - The oldest render request (typically the age of the SceneObject) is rendered in front.
- Batch - Sorted so that the absolute minimum number of draw-calls are required.
- Group - Sorted by each objects’ assigned render group name.
- X - Sort so that things further along the X-Axis are rendered in front.
- Y - Sort so that things further along the Y-Axis are rendered in front.
- Z - Sort so that things further along the Z-Axis are rendered in front.
- -X - Sort so that things further along the X-Axis are rendered at the back.
- -Y - Sort so that things further along the Y-Axis are rendered at the back.
- -Z - Sort so that things further along the Z-Axis are rendered at the back.
You may have noticed something there: Batch. This might seem slightly odd when you consider that batching is always on. To elaborate on this: when objects render, they don’t actually render directly. No 2D code uses draw-calls, everything just pushes what it wants to render to the batcher via the render requests. It’s the batcher that decides what to render and when. This is the centralization mentioned in a previos section. As an example, we can turn off the batcher completely in which case everything that is submitted to it is immediately rendered, massively increasing the number of draw calls. This is exactly how the legacy T2D worked and it was why things like tile-layers and particle-emitters were so costly.
So with batching on (this is obviously the default), the batcher will consume the requests until such a time that it decides that something that has been requested cannot be pushed as part of the current draw-call. In that case then it flushes it (draws it) and continues. The sorting outlined above controls the order that these render requests are pushed to the batcher.
So for instance, in “Newest” sort, the scene layer is sorted appropriately resulting in the oldest items being pushed all the way to the newest ones in that order so that newests are rendered last i.e. are in "front". The batcher will continue to consume/flush as appropriate, trying its best to reduce the number of draw calls as it goes. Internally the batcher keeps track of render states like blending, texture-handles etc. For instance, if all the render requests pushed when in “Newest” mode use the same texture, blending, etc then it will result in a single draw call! It's a nice bonus, you just told it to sort by age but also managed to get a single draw call.
Here is an example on how to set this up in TorqueScript:
Scene.layerSortMode0 = "Newest";
or with:
Scene.setLayerSortMode(%layerNumber, "Newest");
Now obviously, the age of the render request (age of the SceneObject in most cases) does not relate to the most optimal way to draw but the batcher will do its best. In this mode, the batcher operates in what is termed a “strict order” mode i.e. the batcher can do whatever it likes to optimize the drawing but it cannot change the order. This makes sense because the sort mode explicit wants the draw order to be by the objects age and it has to assume that some of these object overlap otherwise why would sort by age have been selected?
This is what the “Batch” sort-mode is for. It’s for when you don’t care about the order i.e. you don’t want a “strict” order but you want the best batching performance. When this is selected, the sort order is done so that contiguous render requests allow the batcher to keep the draw calls to a minimum.
Let's highlight a couple more interesting sort modes. The “Group” sort mode allows you to sort the objects by an assigned group name. This is exposed by anything that uses render requests such as the SceneObject or CompositeSprite (sub-sprites). It allows you to specify an arbitrary render name and the sorting will be done via this. This allows you to sort by (say) “Projectiles” or “Aliens” or some other logical grouping of your choice.
A simple TorqueScript example:
Sprite.setRenderGroup("Projectiles");
Finally it would be good to mention the Z-Axis which may interest some of you reading. Torque 2D does not have a continuous Z-Axis in 2D as that would almost make it a 3D engine! That would also cause the engine to lose the nice feature of controlling which discrete layers are rendered.
Instead, what the engine has is the ability to control the ordering within a layer. You can specify scene-depth explicitly or leave it at its default of zero. This has nothing to do with age, mounting etc. This allows you to move objects back/forward relative to other objects in the same layer. This is a continuous value with no bounds i.e. you can use any floating-point value, positive or negative. There are helper methods that allow you to also move an object forward/backward/to-back/to-front within the layer and they manipulate the scene-layer-depth exclusively and it doesn’t affect any other state.
This is what the Z-Axis & Inverse-Z-Axis are all about. You can choose whether to sort by that or not. It’s essentially layer “depth”.