diff --git a/src/client/graphics/layers/TerritoryLayer.ts b/src/client/graphics/layers/TerritoryLayer.ts index 979bbc22f..22326c23a 100644 --- a/src/client/graphics/layers/TerritoryLayer.ts +++ b/src/client/graphics/layers/TerritoryLayer.ts @@ -21,6 +21,7 @@ const CONTEST_ID_MASK = 0x7fff; const CONTEST_ATTACKER_EVER_BIT = 0x8000; const CONTEST_TIME_WRAP = 32768; const DEFAULT_CONTEST_DURATION_TICKS = 2; +const ENABLE_CONTEST_TRACKING = false; const CONTEST_STRENGTH_EMA_ALPHA = 0.8; const CONTEST_STRENGTH_MIN = 0.01; const CONTEST_STRENGTH_MAX = 0.95; @@ -70,6 +71,7 @@ export class TerritoryLayer implements Layer { private contestTileIndices: Int32Array | null = null; private contestComponents = new Map(); private contestTileCount = 0; + private contestEnabled = ENABLE_CONTEST_TRACKING; private tickSnapshotPending = false; private tickTimeMsCurrent = 0; private tickTimeMsPrev = 0; @@ -131,16 +133,21 @@ export class TerritoryLayer implements Layer { } this.game.recentlyUpdatedTiles().forEach((t) => this.markTile(t)); - const ownerUpdates = this.game.recentlyUpdatedOwnerTiles(); - const nowTickPacked = this.packContestTick(this.game.ticks()); - this.applyContestChanges(ownerUpdates, nowTickPacked); - this.updateContestState(nowTickPacked); - this.updateContestStrengths(); - let tileCount = 0; - for (const component of this.contestComponents.values()) { - tileCount += component.tiles.length; + if (this.contestEnabled) { + const ownerUpdates = this.game.recentlyUpdatedOwnerTiles(); + const nowTickPacked = this.packContestTick(this.game.ticks()); + this.applyContestChanges(ownerUpdates, nowTickPacked); + this.updateContestState(nowTickPacked); + this.updateContestStrengths(); + let tileCount = 0; + for (const component of this.contestComponents.values()) { + tileCount += component.tiles.length; + } + this.contestTileCount = tileCount; + } else { + this.contestTileCount = 0; + this.contestActive = false; } - this.contestTileCount = tileCount; const updates = this.game.updatesSinceLastTick(); // Detect alliance mutations @@ -405,8 +412,15 @@ export class TerritoryLayer implements Layer { this.lastMyPlayerSmallId = this.game.myPlayer()?.smallID() ?? null; this.cachedTerritoryPatternsEnabled = this.userSettings.territoryPatterns(); this.configureRenderers(); - this.ensureContestScratch(); - this.syncContestStateToRenderer(); + if (this.contestEnabled) { + this.ensureContestScratch(); + this.syncContestStateToRenderer(); + } else { + this.contestActive = false; + this.contestComponents.clear(); + this.contestFreeIds = []; + this.contestNextId = 1; + } // Add a second canvas for highlights this.highlightCanvas = document.createElement("canvas"); @@ -429,6 +443,7 @@ export class TerritoryLayer implements Layer { } this.territoryRenderer = renderer; + this.territoryRenderer.setContestEnabled(this.contestEnabled); this.territoryRenderer.setAlternativeView(this.alternativeView); this.territoryRenderer.markAllDirty(); this.territoryRenderer.refreshPalette(); @@ -1146,7 +1161,7 @@ export class TerritoryLayer implements Layer { `delayMs: ${this.interpolationDelayMs.toFixed(0)}`, `smoothPrereq: prevCopy ${stats.prevStateCopySupported ? "yes" : "no"}`, `jfa: ${jfaStatus} dirty ${stats.jfaDirty ? "yes" : "no"}`, - `contest: ${this.contestActive ? "on" : "off"} comps ${this.contestComponents.size}`, + `contests: ${this.contestEnabled ? "on" : "off"} comps ${this.contestComponents.size}`, `contestTiles: ${this.contestTileCount}`, `contestTicks: ${this.contestDurationTicks}`, `hovered: ${stats.hoveredPlayerId}`, diff --git a/src/client/graphics/layers/TerritoryWebGLRenderer.ts b/src/client/graphics/layers/TerritoryWebGLRenderer.ts index a81a1c5fa..b8d50a35b 100644 --- a/src/client/graphics/layers/TerritoryWebGLRenderer.ts +++ b/src/client/graphics/layers/TerritoryWebGLRenderer.ts @@ -27,6 +27,8 @@ const PATTERN_STRIDE_BYTES = 1052; export class TerritoryWebGLRenderer { public readonly canvas: HTMLCanvasElement; + private contestEnabled = false; + private readonly gl: WebGL2RenderingContext | null; private readonly program: WebGLProgram | null; private readonly vao: WebGLVertexArrayObject | null; @@ -89,6 +91,7 @@ export class TerritoryWebGLRenderer { palette: WebGLUniformLocation | null; relations: WebGLUniformLocation | null; patterns: WebGLUniformLocation | null; + contestEnabled: WebGLUniformLocation | null; contestOwners: WebGLUniformLocation | null; contestIds: WebGLUniformLocation | null; contestTimes: WebGLUniformLocation | null; @@ -247,6 +250,7 @@ export class TerritoryWebGLRenderer { palette: null, relations: null, patterns: null, + contestEnabled: null, contestOwners: null, contestIds: null, contestTimes: null, @@ -336,6 +340,7 @@ export class TerritoryWebGLRenderer { palette: null, relations: null, patterns: null, + contestEnabled: null, contestOwners: null, contestIds: null, contestTimes: null, @@ -427,6 +432,7 @@ export class TerritoryWebGLRenderer { palette: gl.getUniformLocation(this.program, "u_palette"), relations: gl.getUniformLocation(this.program, "u_relations"), patterns: gl.getUniformLocation(this.program, "u_patterns"), + contestEnabled: gl.getUniformLocation(this.program, "u_contestEnabled"), contestOwners: gl.getUniformLocation(this.program, "u_contestOwners"), contestIds: gl.getUniformLocation(this.program, "u_contestIds"), contestTimes: gl.getUniformLocation(this.program, "u_contestTimes"), @@ -1224,6 +1230,20 @@ export class TerritoryWebGLRenderer { } } + setContestEnabled(enabled: boolean) { + if (this.contestEnabled === enabled) { + return; + } + this.contestEnabled = enabled; + if (this.contestEnabled) { + this.needsContestFullUpload = true; + this.needsContestTimesUpload = true; + this.needsContestStrengthsUpload = true; + } else { + this.contestDirtyRows.clear(); + } + } + markTile(tile: TileRef) { if (this.needsFullUpload) { return; @@ -1246,6 +1266,9 @@ export class TerritoryWebGLRenderer { componentId: number, attackerEver: boolean, ) { + if (!this.contestEnabled) { + return; + } const offset = tile * 2; const defenderValue = defenderOwner & 0xffff; const attackerValue = attackerOwner & 0xffff; @@ -1279,6 +1302,9 @@ export class TerritoryWebGLRenderer { } setContestTime(componentId: number, nowPacked: number) { + if (!this.contestEnabled) { + return; + } if (componentId <= 0) { return; } @@ -1306,6 +1332,9 @@ export class TerritoryWebGLRenderer { } setContestStrength(componentId: number, strength: number) { + if (!this.contestEnabled) { + return; + } if (componentId <= 0) { return; } @@ -1334,6 +1363,9 @@ export class TerritoryWebGLRenderer { } setContestNow(nowPacked: number, durationTicks: number) { + if (!this.contestEnabled) { + return; + } this.contestNow = nowPacked | 0; this.contestDurationTicks = Math.max(0, durationTicks); } @@ -1508,26 +1540,28 @@ export class TerritoryWebGLRenderer { this.uploadStateTexture(); FrameProfiler.end("TerritoryWebGLRenderer:uploadState", uploadStateSpan); - const uploadContestSpan = FrameProfiler.start(); - this.uploadContestTexture(); - FrameProfiler.end( - "TerritoryWebGLRenderer:uploadContests", - uploadContestSpan, - ); + if (this.contestEnabled) { + const uploadContestSpan = FrameProfiler.start(); + this.uploadContestTexture(); + FrameProfiler.end( + "TerritoryWebGLRenderer:uploadContests", + uploadContestSpan, + ); - const uploadContestTimesSpan = FrameProfiler.start(); - this.uploadContestTimesTexture(); - FrameProfiler.end( - "TerritoryWebGLRenderer:uploadContestTimes", - uploadContestTimesSpan, - ); + const uploadContestTimesSpan = FrameProfiler.start(); + this.uploadContestTimesTexture(); + FrameProfiler.end( + "TerritoryWebGLRenderer:uploadContestTimes", + uploadContestTimesSpan, + ); - const uploadContestStrengthsSpan = FrameProfiler.start(); - this.uploadContestStrengthsTexture(); - FrameProfiler.end( - "TerritoryWebGLRenderer:uploadContestStrengths", - uploadContestStrengthsSpan, - ); + const uploadContestStrengthsSpan = FrameProfiler.start(); + this.uploadContestStrengthsTexture(); + FrameProfiler.end( + "TerritoryWebGLRenderer:uploadContestStrengths", + uploadContestStrengthsSpan, + ); + } if (this.jfaSupported) { this.updateChangeMask(); @@ -1668,6 +1702,9 @@ export class TerritoryWebGLRenderer { const viewerId = this.game.myPlayer()?.smallID() ?? 0; gl.uniform1i(this.uniforms.viewerId, viewerId); } + if (this.uniforms.contestEnabled) { + gl.uniform1i(this.uniforms.contestEnabled, this.contestEnabled ? 1 : 0); + } if (this.uniforms.contestNow) { gl.uniform1i(this.uniforms.contestNow, this.contestNow); } @@ -2602,19 +2639,20 @@ export class TerritoryWebGLRenderer { `; const fragmentShaderSource = `#version 300 es - precision highp float; - precision highp usampler2D; + precision highp float; + precision highp usampler2D; - uniform usampler2D u_state; - uniform usampler2D u_latestState; - uniform sampler2D u_palette; - uniform usampler2D u_relations; - uniform usampler2D u_patterns; - uniform usampler2D u_contestOwners; - uniform usampler2D u_contestIds; - uniform usampler2D u_contestTimes; - uniform usampler2D u_contestStrengths; - uniform bool u_jfaAvailable; + uniform usampler2D u_state; + uniform usampler2D u_latestState; + uniform sampler2D u_palette; + uniform usampler2D u_relations; + uniform usampler2D u_patterns; + uniform bool u_contestEnabled; + uniform usampler2D u_contestOwners; + uniform usampler2D u_contestIds; + uniform usampler2D u_contestTimes; + uniform usampler2D u_contestStrengths; + uniform bool u_jfaAvailable; uniform int u_contestNow; uniform float u_contestDurationTicks; uniform usampler2D u_prevOwner; @@ -2805,28 +2843,34 @@ export class TerritoryWebGLRenderer { uint latestOwner = latestState & 0xFFFu; uint oldOwner = prevOwnerAtTex(texCoord); uint changeMask = texelFetch(u_changeMask, texCoord, 0).r; - bool smoothActive = u_smoothEnabled && - u_smoothProgress < 1.0 && - !u_alternativeView && - u_jfaAvailable && - changeMask != 0u; + bool smoothActive = u_smoothEnabled && + u_smoothProgress < 1.0 && + !u_alternativeView && + u_jfaAvailable && + changeMask != 0u; - uint contestIdRaw = contestIdRawAtTex(texCoord); - const uint CONTEST_ID_MASK = 0x7FFFu; - uint contestId = contestIdRaw & CONTEST_ID_MASK; - uvec2 contestOwners = contestOwnersAtTex(texCoord); - uint defender = contestOwners.r & 0xFFFu; + uint contestIdRaw = 0u; + const uint CONTEST_ID_MASK = 0x7FFFu; + uint contestId = 0u; + uvec2 contestOwners = uvec2(0u); + uint defender = 0u; + bool contested = false; + if (u_contestEnabled) { + contestIdRaw = contestIdRawAtTex(texCoord); + contestId = contestIdRaw & CONTEST_ID_MASK; + contestOwners = contestOwnersAtTex(texCoord); + defender = contestOwners.r & 0xFFFu; - bool contested = false; - if (contestId != 0u) { - uint lastTime = texelFetch(u_contestTimes, ivec2(int(contestId), 0), 0).r; - const uint CONTEST_WRAP = 32768u; - uint nowTime = uint(u_contestNow); - uint elapsed = nowTime >= lastTime - ? (nowTime - lastTime) - : (CONTEST_WRAP - lastTime + nowTime); - contested = float(elapsed) < u_contestDurationTicks; - } + if (contestId != 0u) { + uint lastTime = texelFetch(u_contestTimes, ivec2(int(contestId), 0), 0).r; + const uint CONTEST_WRAP = 32768u; + uint nowTime = uint(u_contestNow); + uint elapsed = nowTime >= lastTime + ? (nowTime - lastTime) + : (CONTEST_WRAP - lastTime + nowTime); + contested = float(elapsed) < u_contestDurationTicks; + } + } bool isBorder = false; bool hasFriendlyRelation = false;