Update GameRenderer and TerritoryLayer for improved performance profiling; adjust minimum FPS and enhance layer profiling with new profileName method. Refactor rendering logic in Canvas and WebGL territory renderers .

This commit is contained in:
scamiv
2025-11-28 18:40:16 +01:00
parent 8077008ceb
commit a9c1c408ca
5 changed files with 90 additions and 24 deletions
+6 -3
View File
@@ -372,7 +372,7 @@ export class GameRenderer {
if (this.backlogTurns > 0) { if (this.backlogTurns > 0) {
const BASE_FPS = 60; const BASE_FPS = 60;
const MIN_FPS = 10; const MIN_FPS = 30;
const BACKLOG_MAX_TURNS = 50; const BACKLOG_MAX_TURNS = 50;
const scale = Math.min(1, this.backlogTurns / BACKLOG_MAX_TURNS); const scale = Math.min(1, this.backlogTurns / BACKLOG_MAX_TURNS);
@@ -426,8 +426,11 @@ export class GameRenderer {
const layerStart = FrameProfiler.start(); const layerStart = FrameProfiler.start();
layer.renderLayer?.(this.context); layer.renderLayer?.(this.context);
const name = layer.constructor?.name ?? "UnknownLayer"; const profileName =
FrameProfiler.end(name, layerStart); (layer as any).profileName?.() ??
layer.constructor?.name ??
"UnknownLayer";
FrameProfiler.end(profileName, layerStart);
} }
handleTransformState(false, isTransformActive); // Ensure context is clean after rendering handleTransformState(false, isTransformActive); // Ensure context is clean after rendering
this.transformHandler.resetChanged(); this.transformHandler.resetChanged();
+1
View File
@@ -4,4 +4,5 @@ export interface Layer {
renderLayer?: (context: CanvasRenderingContext2D) => void; renderLayer?: (context: CanvasRenderingContext2D) => void;
shouldTransform?: () => boolean; shouldTransform?: () => boolean;
redraw?: () => void; redraw?: () => void;
profileName?: () => string;
} }
+30 -2
View File
@@ -33,6 +33,10 @@ import {
import { TerritoryWebGLRenderer } from "./TerritoryWebGLRenderer"; import { TerritoryWebGLRenderer } from "./TerritoryWebGLRenderer";
export class TerritoryLayer implements Layer { export class TerritoryLayer implements Layer {
profileName(): string {
return "TerritoryLayer:renderLayer";
}
private userSettings: UserSettings; private userSettings: UserSettings;
private borderAnimTime = 0; private borderAnimTime = 0;
@@ -92,6 +96,7 @@ export class TerritoryLayer implements Layer {
} }
tick() { tick() {
const tickProfile = FrameProfiler.start();
if (this.game.inSpawnPhase()) { if (this.game.inSpawnPhase()) {
this.spawnHighlight(); this.spawnHighlight();
} }
@@ -188,6 +193,7 @@ export class TerritoryLayer implements Layer {
if (currentMyPlayer !== this.lastMyPlayerSmallId) { if (currentMyPlayer !== this.lastMyPlayerSmallId) {
this.redraw(); this.redraw();
} }
FrameProfiler.end("TerritoryLayer:tick", tickProfile);
} }
private spawnHighlight() { private spawnHighlight() {
@@ -537,10 +543,17 @@ export class TerritoryLayer implements Layer {
return; return;
} }
let numToRender = Math.floor(this.tileToRenderQueue.size() / 10); let numToRender = Math.floor(this.tileToRenderQueue.size() / 10);
if (numToRender === 0 || this.game.inSpawnPhase()) { if (
numToRender === 0 ||
this.game.inSpawnPhase() ||
this.territoryRenderer.isWebGL()
) {
numToRender = this.tileToRenderQueue.size(); numToRender = this.tileToRenderQueue.size();
} }
const useNeighborPaint = !(this.territoryRenderer?.isWebGL() ?? false);
const neighborsToPaint: TileRef[] = [];
const mainSpan = FrameProfiler.start();
while (numToRender > 0) { while (numToRender > 0) {
numToRender--; numToRender--;
@@ -551,9 +564,24 @@ export class TerritoryLayer implements Layer {
const tile = entry.tile; const tile = entry.tile;
this.paintTerritory(tile); this.paintTerritory(tile);
for (const neighbor of this.game.neighbors(tile)) {
if (useNeighborPaint) {
for (const neighbor of this.game.neighbors(tile)) {
neighborsToPaint.push(neighbor);
}
}
}
FrameProfiler.end("TerritoryLayer:renderTerritory.mainPaint", mainSpan);
if (useNeighborPaint && neighborsToPaint.length > 0) {
const neighborSpan = FrameProfiler.start();
for (const neighbor of neighborsToPaint) {
this.paintTerritory(neighbor, true); //this is a misuse of the _Border parameter, making it a maybe stale border this.paintTerritory(neighbor, true); //this is a misuse of the _Border parameter, making it a maybe stale border
} }
FrameProfiler.end(
"TerritoryLayer:renderTerritory.neighborPaint",
neighborSpan,
);
} }
} }
@@ -110,7 +110,7 @@ export class CanvasTerritoryRenderer implements TerritoryRendererStrategy {
} else { } else {
this.clearTile(tile); this.clearTile(tile);
} }
FrameProfiler.end("TerritoryLayer:paintTerritory.cpu", cpuStart); FrameProfiler.end("CanvasTerritoryRenderer:paintTile", cpuStart);
return; return;
} }
@@ -142,7 +142,7 @@ export class CanvasTerritoryRenderer implements TerritoryRendererStrategy {
150, 150,
); );
} }
FrameProfiler.end("TerritoryLayer:paintTerritory.cpu", cpuStart); FrameProfiler.end("CanvasTerritoryRenderer:paintTile", cpuStart);
} }
render( render(
@@ -165,7 +165,7 @@ export class CanvasTerritoryRenderer implements TerritoryRendererStrategy {
width, width,
height, height,
); );
FrameProfiler.end("TerritoryLayer:putImageData", putImageStart); FrameProfiler.end("CanvasTerritoryRenderer:putImageData", putImageStart);
} }
const drawCanvasStart = FrameProfiler.start(); const drawCanvasStart = FrameProfiler.start();
@@ -176,7 +176,7 @@ export class CanvasTerritoryRenderer implements TerritoryRendererStrategy {
this.game.width(), this.game.width(),
this.game.height(), this.game.height(),
); );
FrameProfiler.end("TerritoryLayer:drawCanvas", drawCanvasStart); FrameProfiler.end("CanvasTerritoryRenderer:drawCanvas", drawCanvasStart);
} }
setAlternativeView(enabled: boolean): void { setAlternativeView(enabled: boolean): void {
@@ -304,7 +304,7 @@ export class WebglTerritoryRenderer implements TerritoryRendererStrategy {
): void { ): void {
const webglRenderStart = FrameProfiler.start(); const webglRenderStart = FrameProfiler.start();
this.renderer.render(); this.renderer.render();
FrameProfiler.end("TerritoryLayer:territoryWebGL.render", webglRenderStart); FrameProfiler.end("WebglTerritoryRenderer:render", webglRenderStart);
const drawCanvasStart = FrameProfiler.start(); const drawCanvasStart = FrameProfiler.start();
context.drawImage( context.drawImage(
@@ -314,10 +314,7 @@ export class WebglTerritoryRenderer implements TerritoryRendererStrategy {
this.game.width(), this.game.width(),
this.game.height(), this.game.height(),
); );
FrameProfiler.end( FrameProfiler.end("WebglTerritoryRenderer:drawImage", drawCanvasStart);
"TerritoryLayer:territoryWebGL.drawImage",
drawCanvasStart,
);
} }
setAlternativeView(enabled: boolean): void { setAlternativeView(enabled: boolean): void {
@@ -481,10 +481,13 @@ export class TerritoryWebGLRenderer {
} }
const gl = this.gl; const gl = this.gl;
const uploadSpan = FrameProfiler.start(); const uploadStateSpan = FrameProfiler.start();
this.uploadStateTexture(); this.uploadStateTexture();
FrameProfiler.end("TerritoryWebGLRenderer:uploadState", uploadStateSpan);
const uploadBorderSpan = FrameProfiler.start();
this.uploadBorderTexture(); this.uploadBorderTexture();
FrameProfiler.end("TerritoryWebGLRenderer:uploadState", uploadSpan); FrameProfiler.end("TerritoryWebGLRenderer:uploadBorder", uploadBorderSpan);
const renderSpan = FrameProfiler.start(); const renderSpan = FrameProfiler.start();
gl.viewport(0, 0, this.canvas.width, this.canvas.height); gl.viewport(0, 0, this.canvas.width, this.canvas.height);
@@ -524,12 +527,16 @@ export class TerritoryWebGLRenderer {
FrameProfiler.end("TerritoryWebGLRenderer:draw", renderSpan); FrameProfiler.end("TerritoryWebGLRenderer:draw", renderSpan);
} }
private uploadStateTexture() { private uploadStateTexture(): { rows: number; bytes: number } {
if (!this.gl || !this.stateTexture) return; if (!this.gl || !this.stateTexture) return { rows: 0, bytes: 0 };
const gl = this.gl; const gl = this.gl;
gl.activeTexture(gl.TEXTURE0); gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.stateTexture); gl.bindTexture(gl.TEXTURE_2D, this.stateTexture);
const bytesPerPixel = Uint16Array.BYTES_PER_ELEMENT;
let rowsUploaded = 0;
let bytesUploaded = 0;
if (this.needsFullUpload) { if (this.needsFullUpload) {
gl.texImage2D( gl.texImage2D(
gl.TEXTURE_2D, gl.TEXTURE_2D,
@@ -544,11 +551,13 @@ export class TerritoryWebGLRenderer {
); );
this.needsFullUpload = false; this.needsFullUpload = false;
this.dirtyRows.clear(); this.dirtyRows.clear();
return; rowsUploaded = this.canvas.height;
bytesUploaded = this.canvas.width * this.canvas.height * bytesPerPixel;
return { rows: rowsUploaded, bytes: bytesUploaded };
} }
if (this.dirtyRows.size === 0) { if (this.dirtyRows.size === 0) {
return; return { rows: 0, bytes: 0 };
} }
for (const [y, span] of this.dirtyRows) { for (const [y, span] of this.dirtyRows) {
@@ -566,16 +575,23 @@ export class TerritoryWebGLRenderer {
gl.UNSIGNED_SHORT, gl.UNSIGNED_SHORT,
rowSlice, rowSlice,
); );
rowsUploaded++;
bytesUploaded += width * bytesPerPixel;
} }
this.dirtyRows.clear(); this.dirtyRows.clear();
return { rows: rowsUploaded, bytes: bytesUploaded };
} }
private uploadBorderTexture() { private uploadBorderTexture(): { rows: number; bytes: number } {
if (!this.gl || !this.borderColorTexture) return; if (!this.gl || !this.borderColorTexture) return { rows: 0, bytes: 0 };
const gl = this.gl; const gl = this.gl;
gl.activeTexture(gl.TEXTURE3); gl.activeTexture(gl.TEXTURE3);
gl.bindTexture(gl.TEXTURE_2D, this.borderColorTexture); gl.bindTexture(gl.TEXTURE_2D, this.borderColorTexture);
const bytesPerPixel = Uint8Array.BYTES_PER_ELEMENT * 4; // RGBA8
let rowsUploaded = 0;
let bytesUploaded = 0;
if (this.borderNeedsFullUpload) { if (this.borderNeedsFullUpload) {
gl.texImage2D( gl.texImage2D(
gl.TEXTURE_2D, gl.TEXTURE_2D,
@@ -590,11 +606,13 @@ export class TerritoryWebGLRenderer {
); );
this.borderNeedsFullUpload = false; this.borderNeedsFullUpload = false;
this.borderDirtyRows.clear(); this.borderDirtyRows.clear();
return; rowsUploaded = this.canvas.height;
bytesUploaded = this.canvas.width * this.canvas.height * bytesPerPixel;
return { rows: rowsUploaded, bytes: bytesUploaded };
} }
if (this.borderDirtyRows.size === 0) { if (this.borderDirtyRows.size === 0) {
return; return { rows: 0, bytes: 0 };
} }
for (const [y, span] of this.borderDirtyRows) { for (const [y, span] of this.borderDirtyRows) {
@@ -615,8 +633,27 @@ export class TerritoryWebGLRenderer {
gl.UNSIGNED_BYTE, gl.UNSIGNED_BYTE,
rowSlice, rowSlice,
); );
rowsUploaded++;
bytesUploaded += width * bytesPerPixel;
} }
this.borderDirtyRows.clear(); this.borderDirtyRows.clear();
return { rows: rowsUploaded, bytes: bytesUploaded };
}
private labelUpload(
base: string,
metrics: { rows: number; bytes: number },
): string {
if (metrics.rows === 0 || metrics.bytes === 0) {
return `${base} (skip)`;
}
const rowBucket =
metrics.rows >= this.canvas.height
? "full"
: `${Math.ceil(metrics.rows / 50) * 50}`;
const kb = Math.max(1, Math.round(metrics.bytes / 1024));
const kbBucket = kb > 1024 ? `${Math.round(kb / 1024)}MB` : `${kb}KB`;
return `${base} rows:${rowBucket} bytes:${kbBucket}`;
} }
private uploadPalette() { private uploadPalette() {