# Rendering
The Render Graph is a system used by the small game framework to manage the entire rendering process, and it can also be considered as an abstraction of the rendering pipeline.
The basis of the rendering graph is the rendering node. These rendering nodes are organized to form a graph. In actual operation, these nodes will be topologically sorted and then executed in order.
Note that the current rendering is not the final version, only a mode of code construction is provided, and it will become an important resource in the future.
# Node
The node is the basis of the rendering graph. Its base class is engine.RGNode
. An RGNode is mainly composed of input, processing, and output. Let's take a look at the implementation of a typical node:
interface IRGCullNodeOptions {
lightMode: string;
initSize?: number;
}
class RGCullNode extends RGNode<{camera:'Camera'},'MeshList', IRGCullNodeOptions> {
public inputTypes = {camera:'Camera' as'Camera'};
public outputType:'MeshList' ='MeshList';
public onActive(context: RenderSystem, options: IRGCullNodeOptions) {
this._output = new Kanata.ScalableList(options.initSize || 2048);
}
public onExecute(context: RenderSystem, options: IRGCullNodeOptions) {
this.getInput('camera').cull(this._output!, options.lightMode);
}
public onDisable(context: RenderSystem, options: IRGCullNodeOptions) {
this._output = undefined;
}
}
This is a culling node, and its composition is divided into three parts: input and output, initialization and life cycle.
# input Output
The type of input and output affects its connection with other nodes. In the above culling node, these parts are embodied as:
The first is the input type, which needs to be specified in the first type parameter and member variable at the same time, here is {camera:'Camera'}
and public inputTypes = {camera:'Camera' as'Camera'};
, The reason why it seems to be written twice is because TS's type system cannot guarantee runtime checking, and this check can be bypassed if it is not strictly required. In this definition, we can see that we have specified the input type Camera
for the input of camera
.
The second is the output type, which is the second type parameter and member variable, here MeshList
and public outputType:'MeshList' ='MeshList';
.
The input and output types are defined in engine.IRGData
, and developers can extend them by themselves. Currently, the built-in types are:
Type Key | Corresponding Type | Description |
---|---|---|
None | void | Empty type |
Camera | BaseCamera | Camera |
RenderTarget | RenderTexture/Screen | Render Target |
MeshList | ScalableList | Cull List or Draw List |
LightList | BaseLight[] | Light Source List |
# Initialization
In addition to input and output, there are initialization type parameters. Each node can have its own initialization parameters to customize this node to achieve different functions.
The initialization type parameter is the third type parameter of RGNode. In this example, it is IRGCullNodeOptions
, that is, {lightMode: string, initSize?: number}
, indicating that this type of object can be passed in when creating this node Initialize this node.
And this initialization parameter will also be passed into each life cycle for developers to use.
# The life cycle
The type parameter determines the external interface and initialization of the node, and the life cycle determines the internal operation of the node. It can be seen from this culling node that there are a total of three life cycles, and they all have the same parameters.
- onInit: Triggered when a node is just created.
- onActive: when the node is in a rendering graph, and it is triggered when the graph start.
- onExecute: Triggered when every frame of rendering is executed.
- onDisable: Triggered when a node is in a rendering graph and is disabled or removed out of the graph.
For example, in the culling node, the output _output
is created when the graph is started, and the cull
method of the input camera
is used when the graph is executed, and the _output
is destroyed when the graph is deactivated to prevent memory leaks. .
In the preparation of the life cycle, you can also see the role of its parameters:
- Context: It is actually the
renderSystem
mentioned in the previous chapter, which can be used to obtain some information about the rendering system. - options: is the initialization parameter of the node.
# picture
With nodes, you can connect these nodes into a graph. The rendering graph is engine.RenderGraph
in the small game framework, which provides some methods to manage and drive all the rendering nodes.
In order to customize a rendering, the developer must first define his own class:
export default class CustomRG extends RenderGraph {
public onActive(context: RenderSystem) {}
public onCamerasChange(cameras: BaseCamera[]) {}
public onExecuteBegin(context: RenderSystem) {}
public onExecuteDone(context: RenderSystem) {}
public onDisable(context: RenderSystem) {
}
It can be seen that RenderGraph also has a life cycle mechanism. But before we understand the life cycle, we first need to understand the relationship between graphs and nodes.
# Create and destroy nodes
Careful developers should have noticed that when we introduced nodes, we did not discuss how to create and destroy nodes. This is because the creation and destruction of nodes depends on RenderGraph and cannot be performed independently. This is to ensure that the graph can manage the entire life of the node. cycle. In order to create and destroy nodes, RenderGraph provides two methods:
// Create
const rgNode = rg.createNode(name, clz, options);
// destroy
rg.destroyNode(rgNode);
To create a node, you must provide a name name
, which is to facilitate debugging, then the node class clz
, such as the above-mentioned RGCullNode
, and finally the initialization parameter options
corresponding to this class.
# Connect nodes and compile
After the nodes are created, they need to be connected to form a graph:
// connect two nodes
rg.connect(from, to, inputKey);
// Disconnect the two nodes
rg.disconnect(from, node);
The connection and disconnection methods both need to provide two nodes from
and to
, as the name suggests is to connect from the from
node to the to
node, and for the connection, you may also need to provide the inputKey
, which is due to one The node may have multiple inputs, so a key is needed to determine which input of the to
node this from
node needs to connect to.
Of course, it is not necessary to provide inputKey
, which means that there is no need to use the output of the from
node as the input of the to
node. In this case, the order of execution of the two nodes is actually guaranteed. But once inputKey
is provided, you must ensure that the output type of from
is the same as the input type of this key of to
, otherwise an error will be reported during compilation.
After the node connection is completed, when the graph is run for the first time, the compilation process will be executed. The essence of the compilation process is to topologically sort all the nodes of the entire graph, verify the connection relationship of each node, and execute the onActive
life cycle at the same time.
# The life cycle
After understanding how nodes are created and connected, you can further clarify the life cycle of RenderGraph:
- onActive: Triggered when the graph is used, see the next section for use.
- onCamerasChange: Triggered when the camera list in the scene changes. Developers can use this method to build the RenderGraph with the camera dimension, which will be in the subsequent builtin pipeline ) Said.
- onExecuteBegin: Triggered before the start of each frame rendering, developers can set some global uniforms here.
- onExecuteDone: Triggered after the rendering of each frame.
- onDisable: Triggered when the rendered image is not in use.
# Use and debug
To use RenderGraph, you only need to create it and use it:
const rg = new CustomRG();
game.renderSystem.useRenderGraph(rg);
If you need to know the running status, developers can use rg.showDebugInfo()
to see the order of nodes after topological sorting.