diff --git a/src/client/ClientGameRunner.ts b/src/client/ClientGameRunner.ts index de4510085..8a67b84a4 100644 --- a/src/client/ClientGameRunner.ts +++ b/src/client/ClientGameRunner.ts @@ -74,6 +74,7 @@ import { deepAssign, MapRenderer, preloadAtlasData, + type RenderSettings, } from "./render/gl"; import { ALL_UNIT_TYPES, UnitState } from "./render/types"; import { SoundManager } from "./sound/SoundManager"; @@ -256,6 +257,7 @@ export function joinLobby( function createWebGLView( terrainMap: TerrainMapData, config: Config, + settings: RenderSettings, ): { view: MapRenderer; glCanvas: HTMLCanvasElement; @@ -311,6 +313,7 @@ function createWebGLView( terrainBytes, palette, config, + settings, captureRaf, captureCaf, ); @@ -485,9 +488,21 @@ async function createClientGame( const soundManager = new SoundManager(eventBus, userSettings); try { + // Resolve render settings (defaults + user overrides) up front so the + // renderer is built with the final values — no construct-with-defaults, + // re-apply-overrides dance, and texture-baking passes (terrain) get the + // right colors on the first build. + const resolveRenderSettings = (): RenderSettings => { + const settings = createRenderSettings(); + applyGraphicsOverrides(settings, userSettings.graphicsOverrides()); + applyDarkModeOverride(settings, userSettings.darkMode()); + return settings; + }; + const { view, glCanvas, cachedWebGLFrameCallback } = createWebGLView( gameMap, config, + resolveRenderSettings(), ); view.setShowPatterns(userSettings.territoryPatterns()); @@ -497,11 +512,10 @@ async function createClientGame( ); const graphicsListenerAbort = new AbortController(); + // Re-resolve settings and copy them onto the renderer's live object in + // place (passes hold a reference to it, so they pick the change up). const regenerateRenderSettings = (): void => { - const live = view.getSettings(); - deepAssign(live, createRenderSettings()); - applyGraphicsOverrides(live, userSettings.graphicsOverrides()); - applyDarkModeOverride(live, userSettings.darkMode()); + deepAssign(view.getSettings(), resolveRenderSettings()); }; // Re-apply render settings, then re-theme and recolor players, on a // graphics-override change (covers a theme switch such as colorblind mode). @@ -513,7 +527,8 @@ async function createClientGame( gameView.refreshPlayerColors(); webglBuilder.refreshPalette(gameView); }; - regenerateRenderSettings(); + // No initial regenerate needed — the renderer was constructed with the + // resolved settings above. globalThis.addEventListener( `${USER_SETTINGS_CHANGED_EVENT}:${GRAPHICS_KEY}`, onGraphicsChanged, diff --git a/src/client/render/gl/MapRenderer.ts b/src/client/render/gl/MapRenderer.ts index 4ecc9c31b..423b9189a 100644 --- a/src/client/render/gl/MapRenderer.ts +++ b/src/client/render/gl/MapRenderer.ts @@ -50,6 +50,10 @@ export class MapRenderer { private terrainBytes: Uint8Array, private paletteData: Float32Array, private config: Config, + // Resolved render settings (defaults + overrides). Held so the same object + // is re-used when a GPURenderer is recreated after a context restore, + // preserving any user overrides that were applied to it. + private settings: RenderSettings, private raf?: typeof requestAnimationFrame, private caf?: typeof cancelAnimationFrame, ) { @@ -78,6 +82,7 @@ export class MapRenderer { this.terrainBytes, this.paletteData, this.config, + this.settings, this.raf, this.caf, ); diff --git a/src/client/render/gl/Renderer.ts b/src/client/render/gl/Renderer.ts index a5d651228..3231204b5 100644 --- a/src/client/render/gl/Renderer.ts +++ b/src/client/render/gl/Renderer.ts @@ -57,7 +57,7 @@ import { TerritoryPass } from "./passes/TerritoryPass"; import { TrailPass } from "./passes/TrailPass"; import { UnitPass } from "./passes/UnitPass"; import { WorldTextPass } from "./passes/WorldTextPass"; -import { createRenderSettings, type RenderSettings } from "./RenderSettings"; +import type { RenderSettings } from "./RenderSettings"; import { AffiliationPalette } from "./utils/Affiliation"; import { buildTerrainRGBA, getPaletteSize } from "./utils/ColorUtils"; import { @@ -180,11 +180,15 @@ export class GPURenderer { terrainBytes: Uint8Array, paletteData: Float32Array, config: Config, + settings: RenderSettings, raf: typeof requestAnimationFrame = requestAnimationFrame.bind(window), caf: typeof cancelAnimationFrame = cancelAnimationFrame.bind(window), ) { this.canvas = canvas; - this.settings = createRenderSettings(); + // Settings are resolved (defaults + user overrides) by the caller and + // passed in, so every pass — including texture-baking ones like terrain — + // is built with the final values. Live changes mutate this object in place. + this.settings = settings; this.raf = raf; this.caf = caf;