Skip to content

Commit

Permalink
Add basic profiler to the scene editor
Browse files Browse the repository at this point in the history
  • Loading branch information
4ian committed Sep 23, 2024
1 parent faa506c commit 73571dc
Show file tree
Hide file tree
Showing 10 changed files with 314 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,7 @@ const FullSizeInstancesEditorWithScrollbars = (props: Props) => {
}
}}
showObjectInstancesIn3D={values.use3DEditor}
showBasicProfilingCounters={values.showBasicProfilingCounters}
{...otherProps}
/>
</ErrorBoundary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// @flow

type InstanceCounter = {
updateCount: number,
totalUpdateTime: number,
};

export type BasicProfilingCounters = {
instanceCounters: { [string]: InstanceCounter },
totalInstancesUpdateCount: number,
totalInstancesUpdateTime: number,
totalPixiRenderingTime: number,
totalPixiUiRenderingTime: number,
totalThreeRenderingTime: number,
};

export const makeBasicProfilingCounters = (): BasicProfilingCounters => {
return {
instanceCounters: {},
totalInstancesUpdateCount: 0,
totalInstancesUpdateTime: 0,
totalPixiRenderingTime: 0,
totalPixiUiRenderingTime: 0,
totalThreeRenderingTime: 0,
};
};

export const resetBasicProfilingCounters = (
basicProfilingCounters: BasicProfilingCounters
): BasicProfilingCounters => {
basicProfilingCounters.instanceCounters = {};
basicProfilingCounters.totalInstancesUpdateCount = 0;
basicProfilingCounters.totalInstancesUpdateTime = 0;
basicProfilingCounters.totalPixiRenderingTime = 0;
basicProfilingCounters.totalPixiUiRenderingTime = 0;
basicProfilingCounters.totalThreeRenderingTime = 0;
return basicProfilingCounters;
};

export const increaseInstanceUpdate = (
basicProfilingCounters: BasicProfilingCounters,
objectName: string,
updateDuration: number
) => {
let instanceCounter = basicProfilingCounters.instanceCounters[objectName];
if (!instanceCounter) {
basicProfilingCounters.instanceCounters[objectName] = {
updateCount: 1,
totalUpdateTime: updateDuration,
};
} else {
instanceCounter.updateCount++;
instanceCounter.totalUpdateTime += updateDuration;
}
basicProfilingCounters.totalInstancesUpdateCount++;
basicProfilingCounters.totalInstancesUpdateTime += updateDuration;
};

export const increasePixiRenderingTime = (
basicProfilingCounters: BasicProfilingCounters,
pixiRenderingTime: number
) => {
basicProfilingCounters.totalPixiRenderingTime += pixiRenderingTime;
};

export const increasePixiUiRenderingTime = (
basicProfilingCounters: BasicProfilingCounters,
pixiUiRenderingTime: number
) => {
basicProfilingCounters.totalPixiUiRenderingTime += pixiUiRenderingTime;
};

export const increaseThreeRenderingTime = (
basicProfilingCounters: BasicProfilingCounters,
threeRenderingTime: number
) => {
basicProfilingCounters.totalThreeRenderingTime += threeRenderingTime;
};

export const mergeBasicProfilingCounters = (
destination: BasicProfilingCounters,
source: BasicProfilingCounters
): BasicProfilingCounters => {
for (const objectName in source.instanceCounters) {
if (source.instanceCounters.hasOwnProperty(objectName)) {
const instanceCounter = source.instanceCounters[objectName];
let destinationInstanceCounter = destination.instanceCounters[objectName];
if (!destinationInstanceCounter) {
destinationInstanceCounter = destination.instanceCounters[
objectName
] = {
updateCount: 0,
totalUpdateTime: 0,
};
}
destinationInstanceCounter.updateCount += instanceCounter.updateCount;
destinationInstanceCounter.totalUpdateTime +=
instanceCounter.totalUpdateTime;
}
}
destination.totalInstancesUpdateCount += source.totalInstancesUpdateCount;
destination.totalInstancesUpdateTime += source.totalInstancesUpdateTime;
destination.totalPixiRenderingTime += source.totalPixiRenderingTime;
destination.totalPixiUiRenderingTime += source.totalPixiUiRenderingTime;
destination.totalThreeRenderingTime += source.totalThreeRenderingTime;
return destination;
};

export const getBasicProfilingCountersText = (
basicProfilingCounters: BasicProfilingCounters
): string => {
const texts = [];
texts.push(
`Instances update count: ${
basicProfilingCounters.totalInstancesUpdateCount
}`
);
texts.push(
`Instances update time: ${basicProfilingCounters.totalInstancesUpdateTime.toFixed(
2
)}ms`
);
texts.push(
`Pixi rendering time: ${basicProfilingCounters.totalPixiRenderingTime.toFixed(
2
)}ms`
);
texts.push(
`Three rendering time: ${basicProfilingCounters.totalThreeRenderingTime.toFixed(
2
)}ms`
);
texts.push(
`Pixi UI rendering time: ${basicProfilingCounters.totalPixiUiRenderingTime.toFixed(
2
)}ms`
);
texts.push(' ');
for (const objectName in basicProfilingCounters.instanceCounters) {
const instanceCounters =
basicProfilingCounters.instanceCounters[objectName];
texts.push(
`${objectName}: ${
instanceCounters.updateCount
} updates, ${instanceCounters.totalUpdateTime.toFixed(2)}ms`
);
}

return texts.join('\n');
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ import {
type Polygon,
} from '../../Utils/PolygonHelper';
import Rendered3DInstance from '../../ObjectsRendering/Renderers/Rendered3DInstance';
import {
type BasicProfilingCounters,
increaseInstanceUpdate,
makeBasicProfilingCounters,
resetBasicProfilingCounters,
} from './BasicProfilingCounters';
const gd: libGDevelop = global.gd;

export default class LayerRenderer {
Expand Down Expand Up @@ -83,6 +89,8 @@ export default class LayerRenderer {

_showObjectInstancesIn3D: boolean;

_basicProfilingCounters = makeBasicProfilingCounters();

constructor({
project,
globalObjectsContainer,
Expand Down Expand Up @@ -186,7 +194,18 @@ export default class LayerRenderer {
? 'auto'
: 'static';
}
if (isVisible) renderedInstance.update();
if (isVisible) {
const objectName = instance.getObjectName();
const time = performance.now();
renderedInstance.update();
const duration = performance.now() - time;

increaseInstanceUpdate(
this._basicProfilingCounters,
objectName,
duration
);
}

if (renderedInstance instanceof Rendered3DInstance) {
const threeObject = renderedInstance.getThreeObject();
Expand Down Expand Up @@ -555,6 +574,8 @@ export default class LayerRenderer {
}

render() {
resetBasicProfilingCounters(this._basicProfilingCounters);

this._computeViewBounds();
this.instances.iterateOverInstancesWithZOrdering(
// $FlowFixMe - gd.castObject is not supporting typings.
Expand All @@ -566,6 +587,10 @@ export default class LayerRenderer {
this._destroyUnusedInstanceRenderers();
}

getBasicProfilingCounters(): BasicProfilingCounters {
return this._basicProfilingCounters;
}

/**
* Create Three.js objects for 3D rendering of this layer.
*/
Expand Down
28 changes: 28 additions & 0 deletions newIDE/app/src/InstancesEditor/InstancesRenderer/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import * as PIXI from 'pixi.js-legacy';
import * as THREE from 'three';
import { rgbToHexNumber } from '../../Utils/ColorTransformer';
import Rectangle from '../../Utils/Rectangle';
import {
type BasicProfilingCounters,
makeBasicProfilingCounters,
mergeBasicProfilingCounters,
resetBasicProfilingCounters,
increasePixiRenderingTime,
increaseThreeRenderingTime,
increasePixiUiRenderingTime,
} from './BasicProfilingCounters';

export type InstanceMeasurer = {|
getInstanceAABB: (gdInitialInstance, Rectangle) => Rectangle,
Expand Down Expand Up @@ -49,6 +58,8 @@ export default class InstancesRenderer {
temporaryRectangle: Rectangle;
instanceMeasurer: InstanceMeasurer;

_basicProfilingCounters = makeBasicProfilingCounters();

constructor({
project,
layersContainer,
Expand Down Expand Up @@ -176,13 +187,19 @@ export default class InstancesRenderer {
return this.instanceMeasurer;
}

getBasicProfilingCounters(): BasicProfilingCounters {
return this._basicProfilingCounters;
}

render(
pixiRenderer: PIXI.Renderer,
threeRenderer: THREE.WebGLRenderer | null,
viewPosition: ViewPosition,
uiPixiContainer: PIXI.Container,
backgroundPixiContainer: PIXI.Container
) {
resetBasicProfilingCounters(this._basicProfilingCounters);

// Even if no rendering at all has been made already, setting up the Three.js/PixiJS renderers
// might have changed some WebGL states already. Reset the state for the very first frame.
// And, out of caution, keep doing it for every frame.
Expand Down Expand Up @@ -244,6 +261,8 @@ export default class InstancesRenderer {
layerRenderer.wasUsed = true;
layerRenderer.getPixiContainer().zOrder = i;
layerRenderer.render();
mergeBasicProfilingCounters(this._basicProfilingCounters, layerRenderer.getBasicProfilingCounters());

const layerContainer = layerRenderer.getPixiContainer();
viewPosition.applyTransformationToPixi(layerContainer);

Expand All @@ -256,7 +275,9 @@ export default class InstancesRenderer {

if (!threeRenderer) {
// Render a layer with 2D rendering (PixiJS) only.
const time = performance.now();
pixiRenderer.render(layerContainer, { clear: false });
increasePixiRenderingTime(this._basicProfilingCounters, performance.now() - time);
} else {
// Render a layer with 3D rendering, and possibly some 2D rendering too.
const threeScene = layerRenderer.getThreeScene();
Expand All @@ -272,12 +293,14 @@ export default class InstancesRenderer {
// Do the rendering of the PixiJS objects of the layer on the render texture.
// Then, update the texture of the plane showing the PixiJS rendering,
// so that the 2D rendering made by PixiJS can be shown in the 3D world.
const pixiStartTime = performance.now();
layerRenderer.renderOnPixiRenderTexture(pixiRenderer);
layerRenderer.updateThreePlaneTextureFromPixiRenderTexture(
// The renderers are needed to find the internal WebGL texture.
threeRenderer,
pixiRenderer
);
increasePixiRenderingTime(this._basicProfilingCounters, performance.now() - pixiStartTime);

// It's important to reset the internal WebGL state of PixiJS, then Three.js
// to ensure the 3D rendering is made properly by Three.js
Expand All @@ -287,7 +310,10 @@ export default class InstancesRenderer {
// Clear the depth as each layer is independent and display on top of the previous one,
// even 3D objects.
threeRenderer.clearDepth();

const threeStartTime = performance.now();
threeRenderer.render(threeScene, threeCamera);
increaseThreeRenderingTime(this._basicProfilingCounters, performance.now() - threeStartTime);
}
}
}
Expand All @@ -300,7 +326,9 @@ export default class InstancesRenderer {
pixiRenderer.reset();
}

const time = performance.now();
pixiRenderer.render(uiPixiContainer);
increasePixiUiRenderingTime(this._basicProfilingCounters, performance.now() - time);

if (threeRenderer) {
// It's important to reset the internal WebGL state of PixiJS, then Three.js
Expand Down
72 changes: 72 additions & 0 deletions newIDE/app/src/InstancesEditor/ProfilerBar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// @flow
import * as PIXI from 'pixi.js-legacy';
import {
getBasicProfilingCountersText,
type BasicProfilingCounters,
} from './InstancesRenderer/BasicProfilingCounters';

export default class ProfilerBar {
_profilerBarContainer: PIXI.Container;
_profilerBarBackground: PIXI.Graphics;
_profilerBarText: PIXI.Text;

constructor() {
this._profilerBarContainer = new PIXI.Container();
this._profilerBarContainer.alpha = 0.8;
this._profilerBarContainer.hitArea = new PIXI.Rectangle(0, 0, 0, 0);
this._profilerBarBackground = new PIXI.Graphics();
this._profilerBarText = new PIXI.Text('', {
fontSize: 12,
fill: 0xffffff,
align: 'left',
});
this._profilerBarContainer.addChild(this._profilerBarBackground);
this._profilerBarContainer.addChild(this._profilerBarText);
}

getPixiObject(): PIXI.Container {
return this._profilerBarContainer;
}

render({
basicProfilingCounters,
display,
}: {|
basicProfilingCounters: BasicProfilingCounters,
display: boolean,
|}) {
if (!display) {
this._profilerBarContainer.visible = false;
return;
}

this._profilerBarContainer.visible = true;
const textPadding = 5;
const profilerBarPadding = 15;
const borderRadius = 6;
const textXPosition = profilerBarPadding + textPadding;
const textYPosition = profilerBarPadding + textPadding;

this._profilerBarText.text = getBasicProfilingCountersText(
basicProfilingCounters
);
this._profilerBarText.position.x = textXPosition;
this._profilerBarText.position.y = textYPosition;

const profilerBarXPosition = profilerBarPadding;
const profilerBarYPosition = profilerBarPadding;
const profilerBarWidth = this._profilerBarText.width + textPadding * 2;
const profilerBarHeight = this._profilerBarText.height + textPadding * 2;

this._profilerBarBackground.clear();
this._profilerBarBackground.beginFill(0x000000, 0.8);
this._profilerBarBackground.drawRoundedRect(
profilerBarXPosition,
profilerBarYPosition,
profilerBarWidth,
profilerBarHeight,
borderRadius
);
this._profilerBarBackground.endFill();
}
}
Loading

0 comments on commit 73571dc

Please sign in to comment.