@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
375 lines • 15.5 kB
JavaScript
import { FrameGraphPass } from "./Passes/pass.js";
import { FrameGraphRenderPass } from "./Passes/renderPass.js";
import { FrameGraphObjectListPass } from "./Passes/objectListPass.js";
import { FrameGraphRenderContext } from "./frameGraphRenderContext.js";
import { FrameGraphContext } from "./frameGraphContext.js";
import { FrameGraphTextureManager } from "./frameGraphTextureManager.js";
import { Observable } from "../Misc/observable.js";
import { _RetryWithInterval } from "../Misc/timingTools.js";
import { Logger } from "../Misc/logger.js";
import { UniqueIdGenerator } from "../Misc/uniqueIdGenerator.js";
var FrameGraphPassType;
(function (FrameGraphPassType) {
FrameGraphPassType[FrameGraphPassType["Normal"] = 0] = "Normal";
FrameGraphPassType[FrameGraphPassType["Render"] = 1] = "Render";
FrameGraphPassType[FrameGraphPassType["ObjectList"] = 2] = "ObjectList";
})(FrameGraphPassType || (FrameGraphPassType = {}));
/**
* Class used to implement a frame graph
*/
export class FrameGraph {
/**
* Gets the engine used by the frame graph
*/
get engine() {
return this._engine;
}
/**
* Gets the scene used by the frame graph
*/
get scene() {
return this._scene;
}
/**
* Gets the list of tasks in the frame graph
*/
get tasks() {
return this._tasks;
}
/**
* Gets the node render graph linked to the frame graph (if any)
* @returns the linked node render graph or null if none
*/
getLinkedNodeRenderGraph() {
return this._linkedNodeRenderGraph;
}
/**
* Constructs the frame graph
* @param scene defines the scene the frame graph is associated with
* @param debugTextures defines a boolean indicating that textures created by the frame graph should be visible in the inspector (default is false)
* @param _linkedNodeRenderGraph defines the linked node render graph (if any)
*/
constructor(scene, debugTextures = false, _linkedNodeRenderGraph = null) {
this._linkedNodeRenderGraph = _linkedNodeRenderGraph;
this._tasks = [];
this._initAsyncPromises = [];
this._currentProcessedTask = null;
this._whenReadyAsyncCancel = null;
/**
* Name of the frame graph
*/
this.name = "Frame Graph";
/**
* Gets the unique id of the frame graph
*/
this.uniqueId = UniqueIdGenerator.UniqueId;
/**
* Gets or sets a boolean indicating that texture allocation should be optimized (that is, reuse existing textures when possible to limit GPU memory usage) (default: true)
*/
this.optimizeTextureAllocation = true;
/**
* Observable raised when the node render graph is built
*/
this.onBuildObservable = new Observable();
/**
* Indicates whether the execution of the frame graph is paused (default is false)
*/
this.pausedExecution = false;
this._scene = scene;
this._engine = scene.getEngine();
this._importPromise = this._engine.isWebGPU ? import("../Engines/WebGPU/Extensions/engine.multiRender.js") : import("../Engines/Extensions/engine.multiRender.js");
this.textureManager = new FrameGraphTextureManager(this._engine, debugTextures, scene);
this._passContext = new FrameGraphContext(this._engine, this.textureManager, scene);
this._renderContext = new FrameGraphRenderContext(this._engine, this.textureManager, scene);
this._scene.addFrameGraph(this);
}
/**
* Gets the class name of the frame graph
* @returns the class name
*/
getClassName() {
return "FrameGraph";
}
/**
* Gets a task by name
* @param name Name of the task to get
* @returns The task or undefined if not found
*/
getTaskByName(name) {
return this._tasks.find((t) => t.name === name);
}
/**
* Gets all tasks of a specific class name(s)
* @param name Class name(s) of the task to get
* @returns The list of tasks or an empty array if none found
*/
getTasksByClassNames(name) {
return this._tasks.filter((t) => (Array.isArray(name) ? name.includes(t.getClassName()) : t.getClassName() === name));
}
/**
* Gets all tasks of a specific type
* @param taskType Type of the task(s) to get
* @returns The list of tasks of the specified type
*/
getTasksByType(taskType) {
return this._tasks.filter((t) => t instanceof taskType);
}
/**
* Gets all tasks of a specific type, based on their class name
* @param taskClassName Class name(s) of the task(s) to get
* @returns The list of tasks of the specified type
*/
getTasksByClassName(taskClassName) {
return Array.isArray(taskClassName)
? this._tasks.filter((t) => taskClassName.includes(t.getClassName()))
: this._tasks.filter((t) => t.getClassName() === taskClassName);
}
/**
* Adds a task to the frame graph
* @param task Task to add
*/
addTask(task) {
if (this._currentProcessedTask !== null) {
throw new Error(`FrameGraph.addTask: Can't add the task "${task.name}" while another task is currently building (task: ${this._currentProcessedTask.name}).`);
}
if (this._tasks.includes(task)) {
return;
}
this._tasks.push(task);
this._initAsyncPromises.push(task.initAsync());
}
/**
* Adds a pass to a task. This method can only be called during a Task.record execution.
* @param name The name of the pass
* @param whenTaskDisabled If true, the pass will be added to the list of passes to execute when the task is disabled (default is false)
* @returns The render pass created
*/
addPass(name, whenTaskDisabled = false) {
return this._addPass(name, FrameGraphPassType.Normal, whenTaskDisabled);
}
/**
* Adds a render pass to a task. This method can only be called during a Task.record execution.
* @param name The name of the pass
* @param whenTaskDisabled If true, the pass will be added to the list of passes to execute when the task is disabled (default is false)
* @returns The render pass created
*/
addRenderPass(name, whenTaskDisabled = false) {
return this._addPass(name, FrameGraphPassType.Render, whenTaskDisabled);
}
/**
* Adds an object list pass to a task. This method can only be called during a Task.record execution.
* @param name The name of the pass
* @param whenTaskDisabled If true, the pass will be added to the list of passes to execute when the task is disabled (default is false)
* @returns The object list pass created
*/
addObjectListPass(name, whenTaskDisabled = false) {
return this._addPass(name, FrameGraphPassType.ObjectList, whenTaskDisabled);
}
_addPass(name, passType, whenTaskDisabled = false) {
if (!this._currentProcessedTask) {
throw new Error("FrameGraph: A pass must be created during a Task.record execution only.");
}
let pass;
switch (passType) {
case FrameGraphPassType.Render:
pass = new FrameGraphRenderPass(name, this._currentProcessedTask, this._renderContext, this._engine);
break;
case FrameGraphPassType.ObjectList:
pass = new FrameGraphObjectListPass(name, this._currentProcessedTask, this._passContext, this._engine);
break;
default:
pass = new FrameGraphPass(name, this._currentProcessedTask, this._passContext);
break;
}
this._currentProcessedTask._addPass(pass, whenTaskDisabled);
return pass;
}
/** @internal */
async _whenAsynchronousInitializationDoneAsync() {
if (this._initAsyncPromises.length > 0) {
await Promise.all(this._initAsyncPromises);
this._initAsyncPromises.length = 0;
}
}
/**
* Builds the frame graph.
* This method should be called after all tasks have been added to the frame graph (FrameGraph.addTask) and before the graph is executed (FrameGraph.execute).
* @param waitForReadiness If true, the method will wait for the frame graph to be ready before returning (default is true)
*/
async buildAsync(waitForReadiness = true) {
this.textureManager._releaseTextures(false);
this.pausedExecution = true;
try {
await this._importPromise;
await this._whenAsynchronousInitializationDoneAsync();
for (const task of this._tasks) {
task._reset();
this._currentProcessedTask = task;
this.textureManager._isRecordingTask = true;
task.record();
this.textureManager._isRecordingTask = false;
this._currentProcessedTask = null;
}
this.textureManager._allocateTextures(this.optimizeTextureAllocation ? this._tasks : undefined);
for (const task of this._tasks) {
task._checkTask();
}
for (const task of this._tasks) {
task.onTexturesAllocatedObservable.notifyObservers(this._renderContext);
}
for (const task of this._tasks) {
task._initializePasses();
}
this.onBuildObservable.notifyObservers(this);
if (waitForReadiness) {
await this.whenReadyAsync();
}
}
catch (e) {
this._tasks.length = 0;
this._currentProcessedTask = null;
this.textureManager._isRecordingTask = false;
throw e;
}
finally {
this.pausedExecution = false;
}
}
/**
* Checks if the frame graph is ready to be executed.
* Note that you can use the whenReadyAsync method to wait for the frame graph to be ready.
* @returns True if the frame graph is ready to be executed, else false
*/
isReady() {
let ready = this._renderContext._isReady();
for (const task of this._tasks) {
ready && (ready = task.isReady());
}
return ready;
}
/**
* Returns a promise that resolves when the frame graph is ready to be executed.
* In general, calling “await buildAsync()” should suffice, as this function also waits for readiness by default.
* @param timeStep Time step in ms between retries (default is 16)
* @param maxTimeout Maximum time in ms to wait for the graph to be ready (default is 10000)
* @returns The promise that resolves when the graph is ready
*/
async whenReadyAsync(timeStep = 16, maxTimeout = 10000) {
let firstNotReadyTask = null;
return await new Promise((resolve, reject) => {
this._whenReadyAsyncCancel = _RetryWithInterval(() => {
let ready = this._renderContext._isReady();
for (const task of this._tasks) {
const taskIsReady = task.isReady();
if (!taskIsReady && !firstNotReadyTask) {
firstNotReadyTask = task;
}
ready && (ready = taskIsReady);
}
return ready;
}, () => {
this._whenReadyAsyncCancel = null;
resolve();
}, (err, isTimeout) => {
this._whenReadyAsyncCancel = null;
if (!isTimeout) {
Logger.Error("FrameGraph: An unexpected error occurred while waiting for the frame graph to be ready.");
if (err) {
Logger.Error(err);
if (err.stack) {
Logger.Error(err.stack);
}
}
}
else {
Logger.Error(`FrameGraph: Timeout while waiting for the frame graph to be ready.${firstNotReadyTask ? ` First task not ready: ${firstNotReadyTask.name}` : ""}`);
if (err) {
Logger.Error(err);
}
}
reject(new Error(err));
}, timeStep, maxTimeout);
});
}
/**
* Executes the frame graph.
*/
execute() {
if (this.pausedExecution) {
return;
}
this._renderContext.restoreDefaultFramebuffer();
this.textureManager._updateHistoryTextures();
for (const task of this._tasks) {
task._execute();
}
this._renderContext.restoreDefaultFramebuffer();
}
/**
* Clears the frame graph (remove the tasks and release the textures).
* The frame graph can be built again after this method is called.
*/
clear() {
this._whenReadyAsyncCancel?.();
this._whenReadyAsyncCancel = null;
for (const task of this._tasks) {
task._reset();
}
this._tasks.length = 0;
this.textureManager._releaseTextures();
this._currentProcessedTask = null;
}
/**
* Looks for the main camera used by the frame graph.
* By default, this is the camera used by the main object renderer task.
* If no such task, we try to find a camera in a utility layer renderer tasks.
* @returns The main camera used by the frame graph, or null if not found
*/
findMainCamera() {
const mainObjectRenderer = this.findMainObjectRenderer();
if (mainObjectRenderer) {
return mainObjectRenderer.camera;
}
// Try to find a camera in the utility layer renderer tasks
const tasks = this.tasks;
for (let i = tasks.length - 1; i >= 0; i--) {
const task = tasks[i];
if (task.getClassName() === "FrameGraphUtilityLayerRendererTask") {
return task.camera;
}
}
return null;
}
/**
* Looks for the main object renderer task in the frame graph.
* By default, this is the object/geometry renderer task with isMainObjectRenderer set to true.
* If no such task, we return the last object/geometry renderer task that has an object list with meshes (or null if none found).
* @returns The main object renderer of the frame graph, or null if not found
*/
findMainObjectRenderer() {
const objectRenderers = this.getTasksByClassNames(["FrameGraphObjectRendererTask", "FrameGraphGeometryRendererTask"]);
let fallbackRenderer = null;
for (let i = objectRenderers.length - 1; i >= 0; --i) {
const meshes = objectRenderers[i].objectList.meshes;
if (objectRenderers[i].isMainObjectRenderer) {
return objectRenderers[i];
}
if ((!meshes || meshes.length > 0) && !fallbackRenderer) {
fallbackRenderer = objectRenderers[i];
}
}
return fallbackRenderer;
}
/**
* Disposes the frame graph
*/
dispose() {
this._whenReadyAsyncCancel?.();
this._whenReadyAsyncCancel = null;
this.clear();
this.textureManager._dispose();
this._renderContext._dispose();
this._scene.removeFrameGraph(this);
}
}
//# sourceMappingURL=frameGraph.js.map