Skip to content

Fix/refactor/optim(StructureIconsLayer): restore structure icons after context loss, use WebGL/WebGPU/Canvas, and some improvements#3654

Open
VariableVince wants to merge 6 commits intomainfrom
structures-restoreon-contextloss
Open

Fix/refactor/optim(StructureIconsLayer): restore structure icons after context loss, use WebGL/WebGPU/Canvas, and some improvements#3654
VariableVince wants to merge 6 commits intomainfrom
structures-restoreon-contextloss

Conversation

@VariableVince
Copy link
Copy Markdown
Contributor

@VariableVince VariableVince commented Apr 12, 2026

Description:

StructureIconsLayer and StructureDrawingUtils fixes and improvements. Most notably have it restore structure icons after webGL context loss.

Inspired by @Skigim's #3339, #3480. Fixes his #3207, contains only those fixes from the Issue that are actually valid and needed fixes, on top of his earlier merged PR.

CONTAINS (partly written by AI, excuse the exaggerated language)

1.

  • ** AutoDetectRenderer: ** now, if Hardware Acceleration is unavailable or disabled, Structure Icons will be displayed using Canvas renderer. Otherwise it will use either WebGL or WebGPU, depeding on which is available. PixiJS currently prefers WebGL but it will switch this to WebGPU at one point. We can also force it to WebGPU as explained in the comment.
  • ** Canvas: ** on Canvas, what doesn't work is gracefully skipped. The non-working parts will be fixed, see this issue in their repo, but until then it will work fine for us anyway: Bug: new canvas 2d mode fails with filters when build with vite pixijs/pixijs#11981
  • WebGPU Context Loss: PixiJS doesn't restore this context itself. Instead we do it by calling setupRenderer again on device loss.
  • WebGL Context Loss: To know when we need to restore the layer, don't use native event (webglcontextrestored) but use PixiJS's internal hook (this.renderer.runners.contextChange). This prevents our cache-clearing commands from interrupting Pixi while it's still busy rebuilding its internal GL State Machine buffer. With links severed, we need to clear and rebuild all icons to restore them.
  • WebGL Context existance Check (this.renderer.context?.isLost): This prevents a crash in PixiJS. Fixes black map background and all graphics frozen, which has been reported a few times. Issue created in their repo: WebGL: error thrown on logPrettyShaderError when getShaderSource returns null pixijs/pixijs#12032.
  • Redraw: for Canvas context restore or on Alt-R, a call from GameRenderer now actually restores icons. Also called for WebGPU device loss and after contextChange WebGL restoration. Checks for WebGL context.isLost so a calls from Alt-R etc won't meddle while GL context is lost.
  • Orphaned Object Leaks: In PixiJS v8, Container.destroy() does not recursively destroy its children. This PR explicitly adds .destroy({ children: true }) inside icon deletion states. This stops thousands of PIXI.Sprite and PIXI.BitmapText child nodes from leaking and choking Pixi when it attempts a WebGL restore.
  • Texture Lifecycle: Invalidate caching logic in SpriteFactory now correctly executes .destroy(true) on PIXI.Texture objects. Previously, they were only deleted from the textureCache Map, retaining a phantom grip on GPU memory buffers.
  • **Don't remove PIXI.Texture.EMPTY from textureCache: createTexture() in SpriteFactory stores PIXI.Texture.EMPTY (a singleton) in textureCache when a structure type has no known shape. When not preventing removal of the EMPTY texture, clearCache() would call texture.destroy(true) on PixiJS's shared global empty texture, breaking all sprites in the renderer that fall back to it.

2. Small Memory/Perf Optimizations

  • The Shared 2D Canvas Optimization: To prevent allocating endless tiny <canvas> elements every time a structure color is loaded, SpriteFactory now utilizes a cleanly shared colorCanvas singleton. To keep this safe from hardware acceleration crashes (where the 2D context dies alongside WebGL), it accurately nullifies itself in clearCache() and lazily instantiates on the next call (getImageColored()).
  • Bypassing Inefficient Textures Cache: Now passing the skipCache: true argument implicitly to dynamic UI elements via PIXI.Texture.from(structureCanvas, true).
  • Zero-Allocation Filters (No more GC Stutters): renderGhost() previously spawned numerous new OutlineFilter(...) WebGL shaders when hovering over invalid tiles, compounding to many leaked Shader Programs. We hoisted these filters to static class properties initialized once, and went a step further: hoisted the wrapping Array structures too (filterRedArray, filterGreenArray). This eliminates many pointless micro-allocations and GC sweeps entirely.

BEFORE, for webGL:
https://youtu.be/durJxNFNePs

AFTER, for WebGL:
https://youtu.be/VnYEFMx4gfM

AFTER, for Canvas:
https://youtu.be/zT720oKxcaI

AFTER, for WebGPU:
https://youtu.be/J09Wee2qTs8

The performance optimizations weren't well measurable in my tests but there's no downgrade at least. WebGPU should bee better than WebGL when we would force it but again, currently PixiJS prefers WebGL hardcoded so only if we disallow WebGL will it use WebGPU if it is available, otherwise fallback gracefully to Canvas still.

Canvas skips parts gracefully, as long as the non-breaking issue exists in PixiJS (as explained above):
AFTER Canvas in Firefox skips non-supported gracefully

Please complete the following:

  • I have added screenshots for all UI updates
  • I process any text displayed to the user through translateText() and I've added it to the en.json file
  • I have added relevant tests to the test directory
  • I confirm I have thoroughly tested these changes and take full responsibility for any bugs introduced

Please put your Discord username so you can be contacted if a bug or regression is found:

tryout33

… Most notably have it restore structure icons after webGL context loss.
@VariableVince VariableVince self-assigned this Apr 12, 2026
@VariableVince VariableVince added Performance Performance optimization Refactor Code cleanup, technical debt, refactoring, and architecture improvements. Bugfix Fixes a bug labels Apr 12, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 12, 2026

Walkthrough

These changes optimize graphics rendering performance and enhance WebGL reliability in a PIXI-based rendering system. Key updates include canvas and texture caching in sprite creation, WebGL context loss detection and recovery, filter array reuse for visual effects, and improved resource cleanup with explicit child destruction.

Changes

Cohort / File(s) Summary
Canvas & Texture Caching
src/client/graphics/layers/StructureDrawingUtils.ts
Added reusable colorCanvas and colorCtx fields to cache and resize a single canvas instead of allocating new ones per call. Introduced clearCache() public method to destroy cached textures and reset cache fields. Updated texture creation to pass second parameter to PIXI.Texture.from() and added explicit texture destruction during cache invalidation.
WebGL Context & Layer Rendering
src/client/graphics/layers/StructureIconsLayer.ts
Narrowed renderer type to PIXI.WebGLRenderer and added context-loss awareness with early returns when WebGL context is lost. Added RefreshGraphicsEvent and contextChange event handlers to rebuild icons after graphics updates or context restoration. Introduced reusable outline filter arrays to avoid repeated filter instantiation. Updated destroy() calls to include { children: true } for proper child cleanup and simplified renderer initialization flow.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🎨 Canvas whispers cache their secrets neat,
While WebGL wakes from context's brief retreat—
Textures bloom where filters dance in place,
Resources freed with gentle, purposeful grace.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title contains multiple concepts (fixes, refactor, performance optimization, and context loss) but does not clearly identify the primary change as the main focus. Simplify the title to focus on the primary change: restore structure icons after WebGL context loss. Secondary improvements can be mentioned in the PR description instead.
✅ Passed checks (2 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The pull request description clearly relates to the changeset, detailing fixes for structure icon restoration after WebGL context loss and performance optimizations in StructureIconsLayer and StructureDrawingUtils.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/client/graphics/layers/StructureIconsLayer.ts (1)

239-246: Coalesce manual refresh with the restore rebuild path.

RefreshGraphicsEvent can fire while a context-restore rebuild is already queued. In that case this does the full destroy/recreate pass twice. Reuse the same rebuildPending + isLost guard here so recovery stays single-shot.

♻️ Proposed change
     this.eventBus.on(RefreshGraphicsEvent, () => {
-      // No redraw() here to be called from GameRenderer, because it triggers on
-      // on "contextrestored" event while we're a WebGL context. It also triggers
-      // on Alt-R, but we can listen to that event ourselves here
-      if (this.rendererInitialized) {
-        this.rebuildAllIcons();
-      }
+      if (
+        this.rendererInitialized &&
+        !this.rebuildPending &&
+        !this.renderer?.context?.isLost
+      ) {
+        this.rebuildPending = true;
+        requestAnimationFrame(() => {
+          this.rebuildPending = false;
+          if (!this.renderer?.context?.isLost) {
+            this.rebuildAllIcons();
+          }
+        });
+      }
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/client/graphics/layers/StructureIconsLayer.ts` around lines 239 - 246,
The RefreshGraphicsEvent handler can trigger a duplicate destroy/recreate when a
context-restore rebuild is already queued; update the
eventBus.on(RefreshGraphicsEvent, ...) callback to guard against that by
checking the same rebuildPending and isLost flags used in the restore path
before calling rebuildAllIcons; i.e., only call this.rebuildAllIcons() when
this.rendererInitialized is true AND rebuildPending is false AND isLost is false
so the recovery path remains single-shot.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/client/graphics/layers/StructureIconsLayer.ts`:
- Around line 239-246: The RefreshGraphicsEvent handler can trigger a duplicate
destroy/recreate when a context-restore rebuild is already queued; update the
eventBus.on(RefreshGraphicsEvent, ...) callback to guard against that by
checking the same rebuildPending and isLost flags used in the restore path
before calling rebuildAllIcons; i.e., only call this.rebuildAllIcons() when
this.rendererInitialized is true AND rebuildPending is false AND isLost is false
so the recovery path remains single-shot.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2b9b76e4-cfe0-4260-a5f4-57c93eca19bc

📥 Commits

Reviewing files that changed from the base of the PR and between 95d7895 and 05547ad.

📒 Files selected for processing (2)
  • src/client/graphics/layers/StructureDrawingUtils.ts
  • src/client/graphics/layers/StructureIconsLayer.ts

coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 12, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the Pixi-based structure icon rendering path to better survive WebGL context loss and reduce per-frame allocations in the structure icon layer.

Changes:

  • Hook StructureIconsLayer into Pixi’s renderer.runners.contextChange to rebuild icons after context restoration and guard rendering/resizing when the context is lost.
  • Reduce allocations by reusing OutlineFilter instances/arrays and ensuring containers are destroyed with { children: true }.
  • Improve SpriteFactory texture/canvas lifecycle: add cache clearing, destroy textures on invalidation, reuse a shared colorization canvas, and avoid destroying PIXI.Texture.EMPTY.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
src/client/graphics/layers/StructureIconsLayer.ts Adds context-change-driven rebuilds, lost-context guards, filter reuse, and more thorough container destruction.
src/client/graphics/layers/StructureDrawingUtils.ts Adds explicit texture cache clearing/invalidation cleanup and reuses a shared offscreen canvas for colorization.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/client/graphics/layers/StructureIconsLayer.ts Outdated
Comment thread src/client/graphics/layers/StructureIconsLayer.ts Outdated
Comment thread src/client/graphics/layers/StructureIconsLayer.ts Outdated
Comment thread src/client/graphics/layers/StructureIconsLayer.ts
Comment thread src/client/graphics/layers/StructureDrawingUtils.ts Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 29, 2026
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 29, 2026
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Apr 29, 2026
Comment thread src/client/graphics/layers/StructureIconsLayer.ts Outdated
…o canvas. Can be restricted later on.

Also created restoration paths after context loss for each.
@VariableVince VariableVince changed the title Fix/perf(StructureIconsLayer): restore structure icons after context loss and some improvements Fix/refactor/optim(StructureIconsLayer): restore structure icons after context loss, use WebGL/WebGPU/Canvas, and some improvements Apr 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Bugfix Fixes a bug Performance Performance optimization Refactor Code cleanup, technical debt, refactoring, and architecture improvements.

Projects

Status: Triage

Development

Successfully merging this pull request may close these issues.

Handle graphics context loss for PixiJS (buildings/structure icons dissapear or never appear)

3 participants