class
#include <esp/gfx_batch/Renderer.h>
Renderer Batch renderer.
A renderer optimized for drawing multiple scenes at once, applying multi-draw optimizations where possible to reduce draw overhead. While all scenes are rendered at once, each scene contains an independent set of rendered objects.
This class expects an active Magnum::
Usage
The renderer gets constructed using a RendererConfiguration with desired tile size and count set via RendererConfiguration::
First, files with meshes, materials and textures that are meant to be rendered from should get added via addFile(). The file itself isn't directly rendered, instead it's treated as a composite file with named root scene nodes being node hierarchy templates to be selectively added to particular scenes. Apart from picking particular root nodes, it's also possible to treat the whole file as a single hierarchy using RendererFileFlag::
Then, particular scenes are populated using addNodeHierarchy(), referencing the node hierarchy templates via their names. The function takes an initial transformation and returns an ID of the node added into the scene. The ID can then be used to subsequently update its transformation via transformations(), for example in respose to a physics simulation or an animation. Each scene also has an associated camera and its combined projection and transformation matrix can be updated using updateCamera().
Finally, the draw() function renders the grid of scenes into a provided framebuffer.
Inner workflow of the batch renderer
The goal of the renderer is to do as much as possible using the least amount of draw calls. For that, it relies heavily on multi-draw and texture array support implemented in Magnum shaders.
- At the beginning, all data from files added by addFile() get uploaded to the GPU. The data consists of meshes, texture image levels and packed material uniforms. Such data are uploaded only once and treated as immutable for the rest of the renderer lifetime.
- On each addNodeHierarchy() call, a list of mesh views each together with material association, texture layer and transform and node assignment is added to a draw list. The draw list is further detailed below.
- For each draw() and each non-empty scene, the following is done:
- The transformation passed to updateCamera() is uploaded to a uniform buffer.
- The renderer calculates hierarchical transformations for all nodes based on the matrices supplied via transformations(). Each item in the draw list is then assigned a corresponding calculated absolute transformation, and the list of transformations corresponding to all draws is uploaded to a uniform buffer.
- Then the draw list is processed, resulting in one or more multi-draw calls as described below.
Draw list and multi-draw call submission
In the ideal (and often impossible) case, there would be just a single file added with addFile(), containing exactly one mesh and exactly one texture array, with scene nodes referring to sub-views of them — i.e., mesh index ranges, texture layer indices and texture transformation matrices. Then, no matter how many times addNodeHierarchy() gets called, the whole draw list populated by it can be drawn with a single multi-draw call.
In practice however, the files may contain meshes with different vertex layouts (such as some having vertex colors and some not), textures of different formats and sizes, and different materials requiring different shaders (such as vertex-colored, alpha-masked etc.). Draw lists populated with addNodeHierarchy() are then partitioned into draw batches, where a particular draw batch contains all draws with a particular shader, from a particular mesh and with a particular texture. Each draw batch then corresponds to a single multi-draw call issued on the GPU.
Performance tradeoffs
The optimization goal here is to pick a balance between minimizing driver overhead from submitting many draw calls and paying extra cost for shader features, texture fetches and vertex attributes present also for draws that don't need them.
For example, if only some meshes use vertex colors, it's possible to add white vertex colors to the remaining meshes, unifying their layout and making it possible to draw them together in a single draw call. The cost of extra vertex processing bandwidth would probably be rather minimal. On the other hand, if some materials need alpha masking and some not, attempting to draw everything with alpha masking enabled may have a much larger cost than the additional draw call saved by this change.
Here it's important to also take into account differences on the web — there the driver overhead is significantly higher and it might be beneficial to put extra effort into reducing draw batch count even though it may result in slightly worse performance natively.
Creating batch-optimized files
While addFile() can consume any count of any regular scene/model files supported by Magnum, in order to properly take advantage of all performance features it's needed to combine the files into batch-optimized composite files containing mesh views and texture arrays.
Batch-optimized files can be stored in any file format that has sufficient flexibility for Magnum::
Meshes and mesh views
Composite meshes, which concatenate several meshes of the same layout together, can be added with the general Magnum::Trade::AbstractSceneConverter::add(const MeshData&, Containers::StringView) API and referenced from scene nodes via Magnum::Trade::SceneField::Mesh. Mesh views, referencing sub-ranges of the composite mesh, aren't a builtin Magnum::
meshViewIndexOffset
of type Magnum::Trade::SceneFieldType::UnsignedInt, containing offset of the mesh view in the index buffer in bytes,meshViewIndexCount
of type Magnum::Trade::SceneFieldType::UnsignedInt, containing index count, andmeshViewMaterial
of type Magnum::Trade::SceneFieldType::Int containing the corresponding material ID. The builtin Magnum::Trade::SceneField::MeshMaterial is not used, because glTF bakes the material reference into the mesh itself, which means referencing the same mesh multiple times with different materials would cause it to be unnecessarily duplicated in the glTF.
The custom field IDs can be arbitrary, what's important is that they are associated with corresponding names using Magnum::MAGNUMX_mesh_views
extension should be added as both extensionUsed
and extensionRequired
in the plugin configuration. Inspecting the resulting file with magnum-sceneconverter may look similarly to this:
magnum-sceneconverter --info-scenes -i ignoreRequiredExtensions batch.gltf
Scene 0: Bound: 11 objects @ UnsignedInt (0.7 kB) Fields: Parent @ Int, 11 entries ImporterState @ Pointer, 11 entries Transformation @ Matrix4x4, 4 entries Mesh @ UnsignedInt, 7 entries Custom(0:meshViewIndexOffset) @ Float, 7 entries Custom(1:meshViewIndexCount) @ Float, 7 entries Custom(2:meshViewMaterial) @ Float, 7 entries
Texture arrays
For 2D texture arrays the KHR_experimentalKhrTextureKtx
configuration option enabled in the converter plugin. Materials should reference layers of it via Magnum::Trade::MaterialAttribute::BaseColorTextureLayer and related attributes, together with supplying BaseColorTextureMatrix describing a sub-image of a particular layer. Inspecting the resulting file with magnum-sceneconverter may look similarly to this:
magnum-sceneconverter --info-textures --info-images --info-materials \ -i experimentalKhrTextureKtx,ignoreRequiredExtensions batch.gltf
… Material 3: yellow Type: PbrMetallicRoughness Base layer: BaseColor @ Vector4: ██ {1, 1, 0, 1} BaseColorTexture @ UnsignedInt: 0 BaseColorTextureLayer @ UnsignedInt: 1 BaseColorTextureMatrix @ Matrix3x3: {0.5, 0, 0, 0, 0.5, 0.5, 0, 0, 1} Texture 0 (referenced by 4 material attributes): Type: Texture2DArray, image 0 Minification, mipmap and magnification: Nearest, Nearest, Nearest Wrapping: {Repeat, Repeat, Repeat} 3D image 0 (referenced by 1 textures): Level 0: Array {4, 4, 2} @ RGB8Unorm (0.1 kB)
Derived classes
- class RendererStandalone
- Standalone batch renderer.
Constructors, destructors, conversion operators
- Renderer(const RendererConfiguration& configuration) explicit
- Constructor.
- ~Renderer() virtual
Public functions
- auto flags() const -> RendererFlags
- Global renderer flags.
-
auto tileSize() const -> Magnum::
Vector2i - Tile size.
-
auto tileCount() const -> Magnum::
Vector2i - Tile count.
-
auto sceneCount() const -> std::
size_t - Scene count.
-
auto maxLightCount() const -> Magnum::
UnsignedInt - Max light count.
-
auto addFile(Corrade::
Containers:: StringView filename, RendererFileFlags flags = {}, Corrade:: Containers:: StringView name = {}) -> bool - Add a file.
-
auto addFile(Corrade::
Containers:: StringView filename, Corrade:: Containers:: StringView importerPlugin, RendererFileFlags flags = {}, Corrade:: Containers:: StringView name = {}) -> bool - Add a file using a custom importer plugin.
-
auto hasNodeHierarchy(Corrade::
Containers:: StringView name) const -> bool - If given mesh hierarchy exists.
-
auto addNodeHierarchy(Magnum::
UnsignedInt sceneId, Corrade:: Containers:: StringView name, const Magnum:: Matrix4& bakeTransformation = {}) -> std:: size_t - Add a mesh hierarchy.
-
auto addEmptyNode(Magnum::
UnsignedInt sceneId) -> std:: size_t -
auto addLight(Magnum::
UnsignedInt sceneId, std:: size_t nodeId, RendererLightType type) -> std:: size_t - Add a light.
-
void clear(Magnum::
UnsignedInt sceneId) - Clear a scene.
-
void clearLights(Magnum::
UnsignedInt sceneId) - Clear lights in a scene.
-
auto camera(Magnum::
UnsignedInt sceneId) const -> Magnum:: Matrix4 - Get the combined projection and view matrices of a camera (read-only)
-
auto cameraDepthUnprojection(Magnum::
UnsignedInt sceneId) const -> Magnum:: Vector2 - Get the depth unprojection parameters of a camera (read-only)
-
void updateCamera(Magnum::
UnsignedInt sceneId, const Magnum:: Matrix4& projection, const Magnum:: Matrix4& view) - Set the camera projection and view matrices.
-
auto transformations(Magnum::
UnsignedInt sceneId) -> Corrade:: Containers:: StridedArrayView1D<Magnum:: Matrix4> - Transformations of all nodes in the scene.
-
auto lightColors(Magnum::
UnsignedInt sceneId) -> Corrade:: Containers:: StridedArrayView1D<Magnum:: Color3> - Colors of all lights in the scene.
-
auto lightRanges(Magnum::
UnsignedInt sceneId) -> Corrade:: Containers:: StridedArrayView1D<Magnum:: Float> - Ranges of all lights in the scene.
-
void draw(Magnum::
GL:: AbstractFramebuffer& framebuffer) - Draw all scenes into provided framebuffer.
-
auto sceneStats(Magnum::
UnsignedInt sceneId) const -> SceneStats - Scene stats.
Function documentation
esp:: gfx_batch:: Renderer:: Renderer(const RendererConfiguration& configuration) explicit
Constructor.
Parameters | |
---|---|
configuration | Renderer configuration |
Expects an active Magnum::
RendererFlags esp:: gfx_batch:: Renderer:: flags() const
Global renderer flags.
By default, no flags are set.
Magnum:: Vector2i esp:: gfx_batch:: Renderer:: tileSize() const
Tile size.
The default tile size is {128, 128}
.
Magnum:: Vector2i esp:: gfx_batch:: Renderer:: tileCount() const
Tile count.
By default there's a single tile.
std:: size_t esp:: gfx_batch:: Renderer:: sceneCount() const
Scene count.
Same as the product() of tileCount(). Empty scenes are not rendered, they only occupy space in the output framebuffer.
Magnum:: UnsignedInt esp:: gfx_batch:: Renderer:: maxLightCount() const
Max light count.
By default there's zero lights, i.e. flat-shaded rendering.
bool esp:: gfx_batch:: Renderer:: addFile(Corrade:: Containers:: StringView filename,
RendererFileFlags flags = {},
Corrade:: Containers:: StringView name = {})
Add a file.
Parameters | |
---|---|
filename | File to add |
flags | File flags |
name | Hierarchy name. Ignored if RendererFileFlag:: |
By default a file is treated as a so-called composite — a collection of node hierarchy templates, which you then add with Renderer::
If RendererFileFlag::name
, or if name
is empty with the filename
used as a name.
Returns true
on success. Prints a message to Magnum::false
if the file can't be imported or the names are conflicting.
bool esp:: gfx_batch:: Renderer:: addFile(Corrade:: Containers:: StringView filename,
Corrade:: Containers:: StringView importerPlugin,
RendererFileFlags flags = {},
Corrade:: Containers:: StringView name = {})
Add a file using a custom importer plugin.
Parameters | |
---|---|
filename | File to add |
importerPlugin | Importer plugin name or path |
flags | File flags |
name | Hierarchy name. Ignored if RendererFileFlag:: |
See addFile(Corrade::
bool esp:: gfx_batch:: Renderer:: hasNodeHierarchy(Corrade:: Containers:: StringView name) const
If given mesh hierarchy exists.
Returns true
if name
is a mesh hierarchy name added by any previous addFile() call, false
otherwise.
std:: size_t esp:: gfx_batch:: Renderer:: addNodeHierarchy(Magnum:: UnsignedInt sceneId,
Corrade:: Containers:: StringView name,
const Magnum:: Matrix4& bakeTransformation = {})
Add a mesh hierarchy.
Parameters | |
---|---|
sceneId | Scene ID, expected to be less than sceneCount() |
name | Node hierarchy template name, added with addFile() earlier |
bakeTransformation | Transformation to bake into the hierarchy |
Returns | ID of the newly added node |
The returned ID can be subsequently used to update transformations via transformations(). The returned IDs are not contiguous, the gaps correspond to number of child nodes in the hierarchy.
The bakeTransformation
is baked into the added hierarchy, i.e. transformations() at the returned ID is kept as an identity transform and writing to it will not overwrite the baked transformation. This parameter is useful for correcting orientation/scale of the imported mesh.
std:: size_t esp:: gfx_batch:: Renderer:: addLight(Magnum:: UnsignedInt sceneId,
std:: size_t nodeId,
RendererLightType type)
Add a light.
Parameters | |
---|---|
sceneId | Scene ID, expected to be less than sceneCount() |
nodeId | Node ID to inherit transformation from, returned from addNodeHierarchy() or addEmptyNode() earlier |
type | Light type. Can't be changed after adding the light. |
Expects that maxLightCount() isn't 0
. If type
is RendererLightType::nodeId
transformation as the direction, if type
is RendererLightType::nodeId
transformation as the position. The ID returned from this function can be subsequently used to update light properties via lightColors() and lightRanges().
void esp:: gfx_batch:: Renderer:: clear(Magnum:: UnsignedInt sceneId)
Clear a scene.
Parameters | |
---|---|
sceneId | Scene ID, expected to be less than sceneCount() |
Clears everything added by addNodeHierarchy(), addEmptyNode() and addLight().
void esp:: gfx_batch:: Renderer:: clearLights(Magnum:: UnsignedInt sceneId)
Clear lights in a scene.
Parameters | |
---|---|
sceneId | Scene ID, expected to be less than sceneCount() |
Clears everything added by addLight().
Magnum:: Matrix4 esp:: gfx_batch:: Renderer:: camera(Magnum:: UnsignedInt sceneId) const
Get the combined projection and view matrices of a camera (read-only)
Parameters | |
---|---|
sceneId | Scene ID, expected to be less than sceneCount() |
Magnum:: Vector2 esp:: gfx_batch:: Renderer:: cameraDepthUnprojection(Magnum:: UnsignedInt sceneId) const
Get the depth unprojection parameters of a camera (read-only)
Parameters | |
---|---|
sceneId | Scene ID, expected to be less than sceneCount() |
void esp:: gfx_batch:: Renderer:: updateCamera(Magnum:: UnsignedInt sceneId,
const Magnum:: Matrix4& projection,
const Magnum:: Matrix4& view)
Set the camera projection and view matrices.
Parameters | |
---|---|
sceneId | Scene ID, expected to be less than sceneCount() |
projection | Projection matrix of the camera |
view | View matrix of the camera (inverse transform) |
Also computes the camera unprojection. Modifications to the transformation are taken into account in the next draw().
Corrade:: Containers:: StridedArrayView1D<Magnum:: Matrix4> esp:: gfx_batch:: Renderer:: transformations(Magnum:: UnsignedInt sceneId)
Transformations of all nodes in the scene.
Parameters | |
---|---|
sceneId | Scene ID, expected to be less than sceneCount() |
Returns a view on all node transformations in given scene. Desired usage is to update only transformations at indices returned by addNodeHierarchy() and addEmptyNode(), updating transformations at other indices is possible but could have unintended consequences. Modifications to the transformations are taken into account in the next draw(). By default, transformations of root nodes (those which IDs were returned from addNodeHierarchy() or addEmptyNode()) are identities, transformations of other nodes are unspecified.
Corrade:: Containers:: StridedArrayView1D<Magnum:: Color3> esp:: gfx_batch:: Renderer:: lightColors(Magnum:: UnsignedInt sceneId)
Colors of all lights in the scene.
Parameters | |
---|---|
sceneId | Scene ID, expected to be less than sceneCount() |
Returns a view on colors of all lights in given scene. Desired usage is to update only colors at indices returned by addLight(), updating colors at other indices is possible but could have unintended consequences. Modifications to the lights are taken into account in the next draw(). By default, colors of root lights (those which IDs were returned from addLight()) are 0xffffff_rgbf
, colors of other lights are unspecified.
Corrade:: Containers:: StridedArrayView1D<Magnum:: Float> esp:: gfx_batch:: Renderer:: lightRanges(Magnum:: UnsignedInt sceneId)
Ranges of all lights in the scene.
Parameters | |
---|---|
sceneId | Scene ID, expected to be less than sceneCount() |
Returns a view on ranges of all lights in given scene. Desired usage is to update only ranges at indices returned by addLight(), updating ranges at other indices is possible but could have unintended consequences. Modifications to the lights are taken into account in the next draw(). The value range is ignored for RendererLightType::
void esp:: gfx_batch:: Renderer:: draw(Magnum:: GL:: AbstractFramebuffer& framebuffer)
Draw all scenes into provided framebuffer.
The framebuffer
is expected to have a size at least as larger as the product of tileSize() and tileCount().
SceneStats esp:: gfx_batch:: Renderer:: sceneStats(Magnum:: UnsignedInt sceneId) const
Scene stats.
Mainly for testing and introspection purposes. The returned info is up-to-date only if draw() has been called before.