@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.
735 lines • 28.9 kB
JavaScript
import { EngineStore } from "../Engines/engineStore.js";
import { Mesh } from "../Meshes/mesh.js";
import { Observable } from "./observable.js";
/**
* Defines the root class used to create scene optimization to use with SceneOptimizer
* @description More details at https://doc.babylonjs.com/features/featuresDeepDive/scene/sceneOptimizer
*/
export class SceneOptimization {
/**
* Gets a string describing the action executed by the current optimization
* @returns description string
*/
getDescription() {
return "";
}
/**
* This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
* @param scene defines the current scene where to apply this optimization
* @param optimizer defines the current optimizer
* @returns true if everything that can be done was applied
*/
apply(scene, optimizer) {
return true;
}
/**
* Creates the SceneOptimization object
* @param priority defines the priority of this optimization (0 by default which means first in the list)
*/
constructor(
/**
* [0] Defines the priority of this optimization (0 by default which means first in the list)
*/
priority = 0) {
this.priority = priority;
}
}
/**
* Defines an optimization used to reduce the size of render target textures
* @see https://doc.babylonjs.com/features/featuresDeepDive/scene/sceneOptimizer
*/
export class TextureOptimization extends SceneOptimization {
/**
* Gets a string describing the action executed by the current optimization
* @returns description string
*/
getDescription() {
return "Reducing render target texture size to " + this.maximumSize;
}
/**
* Creates the TextureOptimization object
* @param priority defines the priority of this optimization (0 by default which means first in the list)
* @param maximumSize defines the maximum sized allowed for textures (1024 is the default value). If a texture is bigger, it will be scaled down using a factor defined by the step parameter
* @param step defines the factor (0.5 by default) used to scale down textures bigger than maximum sized allowed.
*/
constructor(
/**
* [0] Defines the priority of this optimization (0 by default which means first in the list)
*/
priority = 0,
/**
* [1024] Defines the maximum sized allowed for textures (1024 is the default value). If a texture is bigger, it will be scaled down using a factor defined by the step parameter
*/
maximumSize = 1024,
/**
* [0.5] Defines the factor (0.5 by default) used to scale down textures bigger than maximum sized allowed.
*/
step = 0.5) {
super(priority);
this.priority = priority;
this.maximumSize = maximumSize;
this.step = step;
}
/**
* This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
* @param scene defines the current scene where to apply this optimization
* @param optimizer defines the current optimizer
* @returns true if everything that can be done was applied
*/
apply(scene, optimizer) {
let allDone = true;
for (let index = 0; index < scene.textures.length; index++) {
const texture = scene.textures[index];
if (!texture.canRescale || texture.getContext) {
continue;
}
const currentSize = texture.getSize();
const maxDimension = Math.max(currentSize.width, currentSize.height);
if (maxDimension > this.maximumSize) {
texture.scale(this.step);
allDone = false;
}
}
return allDone;
}
}
/**
* Defines an optimization used to increase or decrease the rendering resolution
* @description More details at https://doc.babylonjs.com/features/featuresDeepDive/scene/sceneOptimizer
*/
export class HardwareScalingOptimization extends SceneOptimization {
/**
* Gets a string describing the action executed by the current optimization
* @returns description string
*/
getDescription() {
return "Setting hardware scaling level to " + this._currentScale;
}
/**
* Creates the HardwareScalingOptimization object
* @param priority defines the priority of this optimization (0 by default which means first in the list)
* @param maximumScale defines the maximum scale to use (2 by default)
* @param step defines the step to use between two passes (0.5 by default)
*/
constructor(
/**
* [0] Defines the priority of this optimization (0 by default which means first in the list)
*/
priority = 0,
/**
* [2] Defines the maximum scale to use (2 by default)
*/
maximumScale = 2,
/**
* [0.25] Defines the step to use between two passes (0.5 by default)
*/
step = 0.25) {
super(priority);
this.priority = priority;
this.maximumScale = maximumScale;
this.step = step;
this._currentScale = -1;
this._directionOffset = 1;
}
/**
* This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
* @param scene defines the current scene where to apply this optimization
* @param optimizer defines the current optimizer
* @returns true if everything that can be done was applied
*/
apply(scene, optimizer) {
if (this._currentScale === -1) {
this._currentScale = scene.getEngine().getHardwareScalingLevel();
if (this._currentScale > this.maximumScale) {
this._directionOffset = -1;
}
}
this._currentScale += this._directionOffset * this.step;
this._currentScale = Math.min(this.maximumScale, this._currentScale);
scene.getEngine().setHardwareScalingLevel(this._currentScale);
return this._directionOffset === 1 ? this._currentScale >= this.maximumScale : this._currentScale <= this.maximumScale;
}
}
/**
* Defines an optimization used to remove shadows
* @description More details at https://doc.babylonjs.com/features/featuresDeepDive/scene/sceneOptimizer
*/
export class ShadowsOptimization extends SceneOptimization {
/**
* Gets a string describing the action executed by the current optimization
* @returns description string
*/
getDescription() {
return "Turning shadows on/off";
}
/**
* This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
* @param scene defines the current scene where to apply this optimization
* @param optimizer defines the current optimizer
* @returns true if everything that can be done was applied
*/
apply(scene, optimizer) {
scene.shadowsEnabled = optimizer.isInImprovementMode;
return true;
}
}
/**
* Defines an optimization used to turn post-processes off
* @description More details at https://doc.babylonjs.com/features/featuresDeepDive/scene/sceneOptimizer
*/
export class PostProcessesOptimization extends SceneOptimization {
/**
* Gets a string describing the action executed by the current optimization
* @returns description string
*/
getDescription() {
return "Turning post-processes on/off";
}
/**
* This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
* @param scene defines the current scene where to apply this optimization
* @param optimizer defines the current optimizer
* @returns true if everything that can be done was applied
*/
apply(scene, optimizer) {
scene.postProcessesEnabled = optimizer.isInImprovementMode;
return true;
}
}
/**
* Defines an optimization used to turn lens flares off
* @description More details at https://doc.babylonjs.com/features/featuresDeepDive/scene/sceneOptimizer
*/
export class LensFlaresOptimization extends SceneOptimization {
/**
* Gets a string describing the action executed by the current optimization
* @returns description string
*/
getDescription() {
return "Turning lens flares on/off";
}
/**
* This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
* @param scene defines the current scene where to apply this optimization
* @param optimizer defines the current optimizer
* @returns true if everything that can be done was applied
*/
apply(scene, optimizer) {
scene.lensFlaresEnabled = optimizer.isInImprovementMode;
return true;
}
}
/**
* Defines an optimization based on user defined callback.
* @description More details at https://doc.babylonjs.com/features/featuresDeepDive/scene/sceneOptimizer
*/
export class CustomOptimization extends SceneOptimization {
/**
* Gets a string describing the action executed by the current optimization
* @returns description string
*/
getDescription() {
if (this.onGetDescription) {
return this.onGetDescription();
}
return "Running user defined callback";
}
/**
* This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
* @param scene defines the current scene where to apply this optimization
* @param optimizer defines the current optimizer
* @returns true if everything that can be done was applied
*/
apply(scene, optimizer) {
if (this.onApply) {
return this.onApply(scene, optimizer);
}
return true;
}
}
/**
* Defines an optimization used to turn particles off
* @description More details at https://doc.babylonjs.com/features/featuresDeepDive/scene/sceneOptimizer
*/
export class ParticlesOptimization extends SceneOptimization {
/**
* Gets a string describing the action executed by the current optimization
* @returns description string
*/
getDescription() {
return "Turning particles on/off";
}
/**
* This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
* @param scene defines the current scene where to apply this optimization
* @param optimizer defines the current optimizer
* @returns true if everything that can be done was applied
*/
apply(scene, optimizer) {
scene.particlesEnabled = optimizer.isInImprovementMode;
return true;
}
}
/**
* Defines an optimization used to turn render targets off
* @description More details at https://doc.babylonjs.com/features/featuresDeepDive/scene/sceneOptimizer
*/
export class RenderTargetsOptimization extends SceneOptimization {
/**
* Gets a string describing the action executed by the current optimization
* @returns description string
*/
getDescription() {
return "Turning render targets off";
}
/**
* This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
* @param scene defines the current scene where to apply this optimization
* @param optimizer defines the current optimizer
* @returns true if everything that can be done was applied
*/
apply(scene, optimizer) {
scene.renderTargetsEnabled = optimizer.isInImprovementMode;
return true;
}
}
/**
* Defines an optimization used to merge meshes with compatible materials
* @description More details at https://doc.babylonjs.com/features/featuresDeepDive/scene/sceneOptimizer
*/
export class MergeMeshesOptimization extends SceneOptimization {
constructor() {
super(...arguments);
this._canBeMerged = (abstractMesh) => {
if (!(abstractMesh instanceof Mesh)) {
return false;
}
const mesh = abstractMesh;
if (mesh.isDisposed()) {
return false;
}
if (!mesh.isVisible || !mesh.isEnabled()) {
return false;
}
if (mesh.instances.length > 0) {
return false;
}
if (mesh.skeleton || mesh.hasLODLevels) {
return false;
}
if (mesh.getTotalVertices() === 0) {
return false;
}
return true;
};
}
/**
* Gets or sets a boolean which defines if optimization octree has to be updated
*/
static get UpdateSelectionTree() {
return MergeMeshesOptimization._UpdateSelectionTree;
}
/**
* Gets or sets a boolean which defines if optimization octree has to be updated
*/
static set UpdateSelectionTree(value) {
MergeMeshesOptimization._UpdateSelectionTree = value;
}
/**
* Gets a string describing the action executed by the current optimization
* @returns description string
*/
getDescription() {
return "Merging similar meshes together";
}
/**
* This function will be called by the SceneOptimizer when its priority is reached in order to apply the change required by the current optimization
* @param scene defines the current scene where to apply this optimization
* @param optimizer defines the current optimizer
* @param updateSelectionTree defines that the selection octree has to be updated (false by default)
* @returns true if everything that can be done was applied
*/
apply(scene, optimizer, updateSelectionTree) {
const globalPool = scene.meshes.slice(0);
let globalLength = globalPool.length;
for (let index = 0; index < globalLength; index++) {
const currentPool = [];
const current = globalPool[index];
// Checks
if (!this._canBeMerged(current)) {
continue;
}
currentPool.push(current);
// Find compatible meshes
for (let subIndex = index + 1; subIndex < globalLength; subIndex++) {
const otherMesh = globalPool[subIndex];
if (!this._canBeMerged(otherMesh)) {
continue;
}
if (otherMesh.material !== current.material) {
continue;
}
if (otherMesh.checkCollisions !== current.checkCollisions) {
continue;
}
currentPool.push(otherMesh);
globalLength--;
globalPool.splice(subIndex, 1);
subIndex--;
}
if (currentPool.length < 2) {
continue;
}
// Merge meshes
Mesh.MergeMeshes(currentPool, undefined, true);
}
// Call the octree system optimization if it is defined.
const sceneAsAny = scene;
if (sceneAsAny.createOrUpdateSelectionOctree) {
if (updateSelectionTree != undefined) {
if (updateSelectionTree) {
sceneAsAny.createOrUpdateSelectionOctree();
}
}
else if (MergeMeshesOptimization.UpdateSelectionTree) {
sceneAsAny.createOrUpdateSelectionOctree();
}
}
return true;
}
}
MergeMeshesOptimization._UpdateSelectionTree = false;
/**
* Defines a list of options used by SceneOptimizer
* @description More details at https://doc.babylonjs.com/features/featuresDeepDive/scene/sceneOptimizer
*/
export class SceneOptimizerOptions {
/**
* Creates a new list of options used by SceneOptimizer
* @param targetFrameRate defines the target frame rate to reach (60 by default)
* @param trackerDuration defines the interval between two checks (2000ms by default)
*/
constructor(
/**
* [60] Defines the target frame rate to reach (60 by default)
*/
targetFrameRate = 60,
/**
* [2000] Defines the interval between two checks (2000ms by default)
*/
trackerDuration = 2000) {
this.targetFrameRate = targetFrameRate;
this.trackerDuration = trackerDuration;
/**
* Gets the list of optimizations to apply
*/
this.optimizations = [];
}
/**
* Add a new optimization
* @param optimization defines the SceneOptimization to add to the list of active optimizations
* @returns the current SceneOptimizerOptions
*/
addOptimization(optimization) {
this.optimizations.push(optimization);
return this;
}
/**
* Add a new custom optimization
* @param onApply defines the callback called to apply the custom optimization (true if everything that can be done was applied)
* @param onGetDescription defines the callback called to get the description attached with the optimization.
* @param priority defines the priority of this optimization (0 by default which means first in the list)
* @returns the current SceneOptimizerOptions
*/
addCustomOptimization(onApply, onGetDescription, priority = 0) {
const optimization = new CustomOptimization(priority);
optimization.onApply = onApply;
optimization.onGetDescription = onGetDescription;
this.optimizations.push(optimization);
return this;
}
/**
* Creates a list of pre-defined optimizations aimed to reduce the visual impact on the scene
* @param targetFrameRate defines the target frame rate (60 by default)
* @returns a SceneOptimizerOptions object
*/
static LowDegradationAllowed(targetFrameRate) {
const result = new SceneOptimizerOptions(targetFrameRate);
let priority = 0;
result.addOptimization(new MergeMeshesOptimization(priority));
result.addOptimization(new ShadowsOptimization(priority));
result.addOptimization(new LensFlaresOptimization(priority));
// Next priority
priority++;
result.addOptimization(new PostProcessesOptimization(priority));
result.addOptimization(new ParticlesOptimization(priority));
// Next priority
priority++;
result.addOptimization(new TextureOptimization(priority, 1024));
return result;
}
/**
* Creates a list of pre-defined optimizations aimed to have a moderate impact on the scene visual
* @param targetFrameRate defines the target frame rate (60 by default)
* @returns a SceneOptimizerOptions object
*/
static ModerateDegradationAllowed(targetFrameRate) {
const result = new SceneOptimizerOptions(targetFrameRate);
let priority = 0;
result.addOptimization(new MergeMeshesOptimization(priority));
result.addOptimization(new ShadowsOptimization(priority));
result.addOptimization(new LensFlaresOptimization(priority));
// Next priority
priority++;
result.addOptimization(new PostProcessesOptimization(priority));
result.addOptimization(new ParticlesOptimization(priority));
// Next priority
priority++;
result.addOptimization(new TextureOptimization(priority, 512));
// Next priority
priority++;
result.addOptimization(new RenderTargetsOptimization(priority));
// Next priority
priority++;
result.addOptimization(new HardwareScalingOptimization(priority, 2));
return result;
}
/**
* Creates a list of pre-defined optimizations aimed to have a big impact on the scene visual
* @param targetFrameRate defines the target frame rate (60 by default)
* @returns a SceneOptimizerOptions object
*/
static HighDegradationAllowed(targetFrameRate) {
const result = new SceneOptimizerOptions(targetFrameRate);
let priority = 0;
result.addOptimization(new MergeMeshesOptimization(priority));
result.addOptimization(new ShadowsOptimization(priority));
result.addOptimization(new LensFlaresOptimization(priority));
// Next priority
priority++;
result.addOptimization(new PostProcessesOptimization(priority));
result.addOptimization(new ParticlesOptimization(priority));
// Next priority
priority++;
result.addOptimization(new TextureOptimization(priority, 256));
// Next priority
priority++;
result.addOptimization(new RenderTargetsOptimization(priority));
// Next priority
priority++;
result.addOptimization(new HardwareScalingOptimization(priority, 4));
return result;
}
}
/**
* Class used to run optimizations in order to reach a target frame rate
* @description More details at https://doc.babylonjs.com/features/featuresDeepDive/scene/sceneOptimizer
*/
export class SceneOptimizer {
/**
* Gets or sets a boolean indicating if the optimizer is in improvement mode
*/
get isInImprovementMode() {
return this._improvementMode;
}
set isInImprovementMode(value) {
this._improvementMode = value;
}
/**
* Gets the current priority level (0 at start)
*/
get currentPriorityLevel() {
return this._currentPriorityLevel;
}
/**
* Gets the current frame rate checked by the SceneOptimizer
*/
get currentFrameRate() {
return this._currentFrameRate;
}
/**
* Gets or sets the current target frame rate (60 by default)
*/
get targetFrameRate() {
return this._targetFrameRate;
}
/**
* Gets or sets the current target frame rate (60 by default)
*/
set targetFrameRate(value) {
this._targetFrameRate = value;
}
/**
* Gets or sets the current interval between two checks (every 2000ms by default)
*/
get trackerDuration() {
return this._trackerDuration;
}
/**
* Gets or sets the current interval between two checks (every 2000ms by default)
*/
set trackerDuration(value) {
this._trackerDuration = value;
}
/**
* Gets the list of active optimizations
*/
get optimizations() {
return this._options.optimizations;
}
/**
* Creates a new SceneOptimizer
* @param scene defines the scene to work on
* @param options defines the options to use with the SceneOptimizer
* @param autoGeneratePriorities defines if priorities must be generated and not read from SceneOptimization property (true by default)
* @param improvementMode defines if the scene optimizer must run the maximum optimization while staying over a target frame instead of trying to reach the target framerate (false by default)
*/
constructor(scene, options, autoGeneratePriorities = true, improvementMode = false) {
this._isRunning = false;
this._currentPriorityLevel = 0;
this._targetFrameRate = 60;
this._trackerDuration = 2000;
this._currentFrameRate = 0;
this._improvementMode = false;
/**
* Defines an observable called when the optimizer reaches the target frame rate
*/
this.onSuccessObservable = new Observable();
/**
* Defines an observable called when the optimizer enables an optimization
*/
this.onNewOptimizationAppliedObservable = new Observable();
/**
* Defines an observable called when the optimizer is not able to reach the target frame rate
*/
this.onFailureObservable = new Observable();
if (!options) {
this._options = new SceneOptimizerOptions();
}
else {
this._options = options;
}
if (this._options.targetFrameRate) {
this._targetFrameRate = this._options.targetFrameRate;
}
if (this._options.trackerDuration) {
this._trackerDuration = this._options.trackerDuration;
}
if (autoGeneratePriorities) {
let priority = 0;
for (const optim of this._options.optimizations) {
optim.priority = priority++;
}
}
this._improvementMode = improvementMode;
this._scene = scene || EngineStore.LastCreatedScene;
this._sceneDisposeObserver = this._scene.onDisposeObservable.add(() => {
this._sceneDisposeObserver = null;
this.dispose();
});
}
/**
* Stops the current optimizer
*/
stop() {
this._isRunning = false;
}
/**
* Reset the optimizer to initial step (current priority level = 0)
*/
reset() {
this._currentPriorityLevel = 0;
}
/**
* Start the optimizer. By default it will try to reach a specific framerate
* but if the optimizer is set with improvementMode === true then it will run all optimization while frame rate is above the target frame rate
*/
start() {
if (this._isRunning) {
return;
}
this._isRunning = true;
// Let's wait for the scene to be ready before running our check
this._scene.executeWhenReady(() => {
setTimeout(() => {
this._checkCurrentState();
}, this._trackerDuration);
});
}
_checkCurrentState() {
if (!this._isRunning) {
return;
}
const scene = this._scene;
const options = this._options;
this._currentFrameRate = Math.round(scene.getEngine().getFps());
if ((this._improvementMode && this._currentFrameRate <= this._targetFrameRate) || (!this._improvementMode && this._currentFrameRate >= this._targetFrameRate)) {
this._isRunning = false;
this.onSuccessObservable.notifyObservers(this);
return;
}
// Apply current level of optimizations
let allDone = true;
let noOptimizationApplied = true;
for (let index = 0; index < options.optimizations.length; index++) {
const optimization = options.optimizations[index];
if (optimization.priority === this._currentPriorityLevel) {
noOptimizationApplied = false;
allDone = allDone && optimization.apply(scene, this);
this.onNewOptimizationAppliedObservable.notifyObservers(optimization);
}
}
// If no optimization was applied, this is a failure :(
if (noOptimizationApplied) {
this._isRunning = false;
this.onFailureObservable.notifyObservers(this);
return;
}
// If all optimizations were done, move to next level
if (allDone) {
this._currentPriorityLevel++;
}
// Let's the system running for a specific amount of time before checking FPS
scene.executeWhenReady(() => {
setTimeout(() => {
this._checkCurrentState();
}, this._trackerDuration);
});
}
/**
* Release all resources
*/
dispose() {
this.stop();
this.onSuccessObservable.clear();
this.onFailureObservable.clear();
this.onNewOptimizationAppliedObservable.clear();
if (this._sceneDisposeObserver) {
this._scene.onDisposeObservable.remove(this._sceneDisposeObserver);
}
}
/**
* Helper function to create a SceneOptimizer with one single line of code
* @param scene defines the scene to work on
* @param options defines the options to use with the SceneOptimizer
* @param onSuccess defines a callback to call on success
* @param onFailure defines a callback to call on failure
* @returns the new SceneOptimizer object
*/
static OptimizeAsync(scene, options, onSuccess, onFailure) {
const optimizer = new SceneOptimizer(scene, options || SceneOptimizerOptions.ModerateDegradationAllowed(), false);
if (onSuccess) {
optimizer.onSuccessObservable.add(() => {
onSuccess();
});
}
if (onFailure) {
optimizer.onFailureObservable.add(() => {
onFailure();
});
}
optimizer.start();
return optimizer;
}
}
//# sourceMappingURL=sceneOptimizer.js.map