render_PerformanceRender.js
const RenderMethods = require("../render/RenderMethods");
const vec = require("../geometry/vec");
/**
* Handles rendering performance stats. Creates a graph in the top corner of the screen.
*/
class PerformanceRender {
/**
* If the graph is enabled
* @type {boolean}
*/
enabled = false;
canvas;
ctx;
position = new vec(20, 20);
/**
*
* @param {Performance} Performance - [Performance](./Performance.html)
* @param {Render} Render - [Render](./Render.html)
*/
constructor(Performance, Render) {
this.Performance = Performance;
// Create canvas
let baseCanvas = Render.app.view;
const width = this.width = 100;
const height = this.height = 50;
let scale = this.scale = devicePixelRatio ?? 1;
let canvas = this.canvas = document.createElement("canvas");
this.ctx = canvas.getContext("2d");
canvas.style.position = "absolute";
canvas.style.zIndex = "2";
canvas.style.top = "20px";
canvas.style.right = "0px";
canvas.style.left = "unset";
canvas.width = scale * width;
canvas.height = scale * height;
canvas.style.background = "transparent";
canvas.style.pointerEvents = "none";
canvas.style.transformOrigin = "top left";
canvas.style.transform = `scale(${1 / scale}, ${1 / scale})`;
baseCanvas.parentNode.appendChild(canvas);
// Set up rendering
this.update = this.update.bind(this);
Render.app.ticker.add(this.update);
}
update() {
let { canvas, ctx, enabled, Performance, scale, width, height } = this;
let { history } = Performance;
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (enabled) {
ctx.save();
ctx.scale(scale, scale);
// background
ctx.beginPath();
RenderMethods.roundedRect(width, height, new vec(width/2, height/2), 5, ctx);
ctx.fillStyle = "#0D0D0De6";
ctx.fill();
// get fps stats
let maxFps = 0;
let minFps = Infinity;
let avgFps = (() => {
let v = 0;
for (let i = 0; i < history.fps.length; i++) {
let cur = history.fps[i];
v += cur;
maxFps = Math.max(maxFps, cur);
minFps = Math.min(minFps, cur);
}
return v / history.fps.length;
})();
let nearAvgFps = (() => {
let v = 0;
let n = Math.min(history.fps.length, 20);
for (let i = 0; i < n; i++) {
let cur = history.fps[i];
v += cur;
}
return v / n;
})();
// fps text
ctx.beginPath();
ctx.fillStyle = "white";
ctx.textAlign = "right";
ctx.font = `400 ${12}px Arial`;
ctx.fillText(`${Math.round(nearAvgFps)} fps`, width - 12, 5 + 12);
if (history.fps.length > 10) { // fps graph
let range = 100;
let fpsRanges = {
min: Math.max(0, Math.min(minFps, avgFps - range)),
max: Math.max(maxFps, avgFps + range, 60),
}
const fpsRange = fpsRanges.max - fpsRanges.min;
let bounds = {
min: new vec(10, 18),
max: new vec(width - 10, height - 4),
};
ctx.beginPath();
function getPosition(point, i) {
let x = bounds.max.x - (i / history.fps.length) * (bounds.max.x - bounds.min.x);
let y = bounds.max.y - ((point - fpsRanges.min) / fpsRange) * (bounds.max.y - bounds.min.y);
return [x, y];
}
ctx.moveTo(...getPosition(history.fps[0], 0))
for (let i = 1; i < history.fps.length; i++) {
ctx.lineTo(...getPosition(history.fps[i], i));
}
ctx.lineWidth = 1;
ctx.lineJoin = "bevel";
ctx.strokeStyle = "#9C9C9C";
ctx.stroke();
}
// colored rect
ctx.beginPath();
let colors = [[0.75, "#3FF151"], [0.5, "#F5ED32"], [0.25, "#F89A2C"], [0, "#F74D4D"]];
let boundMax = 60;
ctx.fillStyle = "#808080";
for (let color of colors) {
if (avgFps >= color[0] * boundMax) {
ctx.fillStyle = color[1];
break;
}
}
RenderMethods.roundedRect(6, 6, new vec(15, 13), 2, ctx);
ctx.fill();
ctx.restore();
}
}
}
module.exports = PerformanceRender;