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) {
const BASE_FPS = 60;
const MIN_FPS = 10;
const MIN_FPS = 30;
const BACKLOG_MAX_TURNS = 50;
const scale = Math.min(1, this.backlogTurns / BACKLOG_MAX_TURNS);
@@ -426,8 +426,11 @@ export class GameRenderer {
const layerStart = FrameProfiler.start();
layer.renderLayer?.(this.context);
const name = layer.constructor?.name ?? "UnknownLayer";
FrameProfiler.end(name, layerStart);
const profileName =
(layer as any).profileName?.() ??
layer.constructor?.name ??
"UnknownLayer";
FrameProfiler.end(profileName, layerStart);
}
handleTransformState(false, isTransformActive); // Ensure context is clean after rendering
this.transformHandler.resetChanged();
+1
View File
@@ -4,4 +4,5 @@ export interface Layer {
renderLayer?: (context: CanvasRenderingContext2D) => void;
shouldTransform?: () => boolean;
redraw?: () => void;
profileName?: () => string;
}
+30 -2
View File
@@ -33,6 +33,10 @@ import {
import { TerritoryWebGLRenderer } from "./TerritoryWebGLRenderer";
export class TerritoryLayer implements Layer {
profileName(): string {
return "TerritoryLayer:renderLayer";
}
private userSettings: UserSettings;
private borderAnimTime = 0;
@@ -92,6 +96,7 @@ export class TerritoryLayer implements Layer {
}
tick() {
const tickProfile = FrameProfiler.start();
if (this.game.inSpawnPhase()) {
this.spawnHighlight();
}
@@ -188,6 +193,7 @@ export class TerritoryLayer implements Layer {
if (currentMyPlayer !== this.lastMyPlayerSmallId) {
this.redraw();
}
FrameProfiler.end("TerritoryLayer:tick", tickProfile);
}
private spawnHighlight() {
@@ -537,10 +543,17 @@ export class TerritoryLayer implements Layer {
return;
}
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();
}
const useNeighborPaint = !(this.territoryRenderer?.isWebGL() ?? false);
const neighborsToPaint: TileRef[] = [];
const mainSpan = FrameProfiler.start();
while (numToRender > 0) {
numToRender--;
@@ -551,9 +564,24 @@ export class TerritoryLayer implements Layer {
const tile = entry.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
}
FrameProfiler.end(
"TerritoryLayer:renderTerritory.neighborPaint",
neighborSpan,
);
}
}
@@ -110,7 +110,7 @@ export class CanvasTerritoryRenderer implements TerritoryRendererStrategy {
} else {
this.clearTile(tile);
}
FrameProfiler.end("TerritoryLayer:paintTerritory.cpu", cpuStart);
FrameProfiler.end("CanvasTerritoryRenderer:paintTile", cpuStart);
return;
}
@@ -142,7 +142,7 @@ export class CanvasTerritoryRenderer implements TerritoryRendererStrategy {
150,
);
}
FrameProfiler.end("TerritoryLayer:paintTerritory.cpu", cpuStart);
FrameProfiler.end("CanvasTerritoryRenderer:paintTile", cpuStart);
}
render(
@@ -165,7 +165,7 @@ export class CanvasTerritoryRenderer implements TerritoryRendererStrategy {
width,
height,
);
FrameProfiler.end("TerritoryLayer:putImageData", putImageStart);
FrameProfiler.end("CanvasTerritoryRenderer:putImageData", putImageStart);
}
const drawCanvasStart = FrameProfiler.start();
@@ -176,7 +176,7 @@ export class CanvasTerritoryRenderer implements TerritoryRendererStrategy {
this.game.width(),
this.game.height(),
);
FrameProfiler.end("TerritoryLayer:drawCanvas", drawCanvasStart);
FrameProfiler.end("CanvasTerritoryRenderer:drawCanvas", drawCanvasStart);
}
setAlternativeView(enabled: boolean): void {
@@ -304,7 +304,7 @@ export class WebglTerritoryRenderer implements TerritoryRendererStrategy {
): void {
const webglRenderStart = FrameProfiler.start();
this.renderer.render();
FrameProfiler.end("TerritoryLayer:territoryWebGL.render", webglRenderStart);
FrameProfiler.end("WebglTerritoryRenderer:render", webglRenderStart);
const drawCanvasStart = FrameProfiler.start();
context.drawImage(
@@ -314,10 +314,7 @@ export class WebglTerritoryRenderer implements TerritoryRendererStrategy {
this.game.width(),
this.game.height(),
);
FrameProfiler.end(
"TerritoryLayer:territoryWebGL.drawImage",
drawCanvasStart,
);
FrameProfiler.end("WebglTerritoryRenderer:drawImage", drawCanvasStart);
}
setAlternativeView(enabled: boolean): void {
@@ -481,10 +481,13 @@ export class TerritoryWebGLRenderer {
}
const gl = this.gl;
const uploadSpan = FrameProfiler.start();
const uploadStateSpan = FrameProfiler.start();
this.uploadStateTexture();
FrameProfiler.end("TerritoryWebGLRenderer:uploadState", uploadStateSpan);
const uploadBorderSpan = FrameProfiler.start();
this.uploadBorderTexture();
FrameProfiler.end("TerritoryWebGLRenderer:uploadState", uploadSpan);
FrameProfiler.end("TerritoryWebGLRenderer:uploadBorder", uploadBorderSpan);
const renderSpan = FrameProfiler.start();
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
@@ -524,12 +527,16 @@ export class TerritoryWebGLRenderer {
FrameProfiler.end("TerritoryWebGLRenderer:draw", renderSpan);
}
private uploadStateTexture() {
if (!this.gl || !this.stateTexture) return;
private uploadStateTexture(): { rows: number; bytes: number } {
if (!this.gl || !this.stateTexture) return { rows: 0, bytes: 0 };
const gl = this.gl;
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.stateTexture);
const bytesPerPixel = Uint16Array.BYTES_PER_ELEMENT;
let rowsUploaded = 0;
let bytesUploaded = 0;
if (this.needsFullUpload) {
gl.texImage2D(
gl.TEXTURE_2D,
@@ -544,11 +551,13 @@ export class TerritoryWebGLRenderer {
);
this.needsFullUpload = false;
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) {
return;
return { rows: 0, bytes: 0 };
}
for (const [y, span] of this.dirtyRows) {
@@ -566,16 +575,23 @@ export class TerritoryWebGLRenderer {
gl.UNSIGNED_SHORT,
rowSlice,
);
rowsUploaded++;
bytesUploaded += width * bytesPerPixel;
}
this.dirtyRows.clear();
return { rows: rowsUploaded, bytes: bytesUploaded };
}
private uploadBorderTexture() {
if (!this.gl || !this.borderColorTexture) return;
private uploadBorderTexture(): { rows: number; bytes: number } {
if (!this.gl || !this.borderColorTexture) return { rows: 0, bytes: 0 };
const gl = this.gl;
gl.activeTexture(gl.TEXTURE3);
gl.bindTexture(gl.TEXTURE_2D, this.borderColorTexture);
const bytesPerPixel = Uint8Array.BYTES_PER_ELEMENT * 4; // RGBA8
let rowsUploaded = 0;
let bytesUploaded = 0;
if (this.borderNeedsFullUpload) {
gl.texImage2D(
gl.TEXTURE_2D,
@@ -590,11 +606,13 @@ export class TerritoryWebGLRenderer {
);
this.borderNeedsFullUpload = false;
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) {
return;
return { rows: 0, bytes: 0 };
}
for (const [y, span] of this.borderDirtyRows) {
@@ -615,8 +633,27 @@ export class TerritoryWebGLRenderer {
gl.UNSIGNED_BYTE,
rowSlice,
);
rowsUploaded++;
bytesUploaded += width * bytesPerPixel;
}
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() {