Skip to content

fix(tilemap): carry tileset textures across surface loss/restore (#847) - #907

Merged
apotema merged 1 commit into
mainfrom
fix/847-tilemap-surface-restore
Sep 28, 2026
Merged

apotema merged 1 commit into
mainfrom
fix/847-tilemap-surface-restore

Conversation

@apotema

@apotema apotema commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Closes #847.

Problem

Tilemap tileset textures — the per-tileset sheet and the per-tile images of a collection tileset — are uploaded by tilemap_runtime.initInPlace directly through RenderImpl.loadTextureFromMemory. Only Game.loadTextureFromMemoryU32 keeps the bytes in World.direct_textures, and the #820 surface re-arm handles only that store. So after an Android TERM_WINDOW/INIT_WINDOW cycle every tilemap drew blank. On current main, surfaceLost/surfaceRestored never touched world.tilemaps.

Fix

The runtime now handles its own surface lifecycle, using gfx's minted-key re-arm seam (invalidateTexture + reuploadTextureFromMemory, the same both-or-neither gate as tracks_direct_uploads):

On the issue's options: this is neither A nor B. Unlike A, the TMX is not decoded again, so runtime edits are not lost. Unlike B, no second copy of the bytes is kept. It uses B's same-id re-upload plus a renderer rebind, which B would also have needed.

Tests

  • New test/tilemap_surface_test.zig drives the real Game.surfaceLost/surfaceRestored against a renderer that records every call to gfx's registry-style seam. It asserts:
    • each owned id (sheet + per-tile, with a shared source) is invalidated once and never unloaded
    • draws are skipped while the surface is lost
    • on restore, each id is re-uploaded under the same id with that image's bytes, and no new ids are created
    • gfx's renderer now holds the new-generation backend textures
    • tile edits are kept
    • teardown frees each id exactly once through a live handle
    • releasing a tilemap while the surface is lost frees no dead handle
    • a partial re-upload failure blanks only that texture
    • a restore with no prior loss does nothing
  • test/tilemap_mutation_test.zig adds the same cycle against the real gfx TileMapRendererWith(MockBackend) (the test renderer gets the seam). After restore, tm.textures holds the new backend handle, and the next frame draws 5/5 tiles from it and none from the old one.
  • test/tilemap_collection_test.zig covers the renderer without the seam: nothing is invalidated, unloaded or re-uploaded.
  • FakeGfx + parseTmx move to test/tilemap_fake_gfx.zig, so both suites share them. This also brings the collection suite under 1000 lines.

I checked that the tests catch a regression: with the two lifecycle calls commented out, 3 of the 6 surface tests fail.

Locally (Windows) the tilemap suites all pass. zig build test still fails there on the usual Windows-only errors (std.c.timespec, preview sockets, storage), which don't involve the files changed here; CI (Linux) is the gate.

🤖 Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Tileset textures (the per-tileset sheet AND the per-tile images of a
collection tileset) are uploaded by the tilemap runtime straight through
the renderer, so the direct-upload re-arm (#820) never covered them and a
tilemap drew blank after an Android TERM_WINDOW/INIT_WINDOW cycle.

The runtime now owns its own surface lifecycle on gfx's minted-key re-arm
seam (invalidateTexture + reuploadTextureFromMemory):

- surfaceLost invalidates (never frees) every owned id and suppresses the
  map's draw pass.
- surfaceRestored re-decodes each id's image from the embedded provider
  (program-lifetime bytes, nothing duplicated) and re-uploads it under its
  ORIGINAL id, then rebinds gfx's TileMapRenderer, which caches backend
  texture values rather than ids. The decoded map (runtime setTile edits)
  and the per-tile animation clock are kept.

Game.surfaceLost/surfaceRestored drive it via the tilemap mixin. Without
the seam both are no-ops (pre-#847 behaviour).

Tests: new test/tilemap_surface_test.zig (sheet + per-tile, same-id
re-upload with the right bytes, rebind to fresh backend textures, draw
suppression, edits kept, teardown/free-while-lost, partial failure), a
real-gfx TileMapRenderer rebind case in tilemap_mutation_test.zig, and the
no-seam degrade in tilemap_collection_test.zig. The FakeGfx stand-in moves
to test/tilemap_fake_gfx.zig so both suites share it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Caqyi5Snr6yKssztYFztzZ
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-28T23:24:41.878904Z 1327ce1 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 6cff3695-9d8e-4898-a071-14960c4e3e6d

📥 Commits

Reviewing files that changed from the base of the PR and between 28dd1d7 and 1327ce1.

📒 Files selected for processing (10)
  • build.zig
  • src/game.zig
  • src/game/lifecycle_mixin.zig
  • src/game/tilemap_mixin.zig
  • src/tilemap_runtime.zig
  • test/tilemap_collection_test.zig
  • test/tilemap_fake_gfx.zig
  • test/tilemap_interleave_support.zig
  • test/tilemap_mutation_test.zig
  • test/tilemap_surface_test.zig

Included review availability: This review used your included allowance. 0 included reviews remain after this review. Your included PR review attempts over the past 7 days set your current allowance at 1 review per hour.


📝 Walkthrough

Walkthrough

The change adds tilemap texture invalidation and restoration to the game’s GPU surface lifecycle. Runtime retains texture sources, re-uploads textures under their existing IDs when supported, and rebinds renderers. Tests cover surface cycles, partial failures, renderer compatibility, and teardown.

Changes

Tilemap surface lifecycle

Layer / File(s) Summary
Runtime texture lifecycle
src/tilemap_runtime.zig
Runtime retains sources for uploaded textures. When the renderer provides both lifecycle methods, runtime invalidates owned texture IDs on surface loss and re-uploads them on restoration. Runtime rebinds the renderer and suppresses drawing while the surface is lost.
Game surface lifecycle wiring
src/game.zig, src/game/tilemap_mixin.zig, src/game/lifecycle_mixin.zig
Game exposes tilemap texture invalidation and reload methods. Surface loss invokes invalidation. Surface restoration reloads tilemap textures and rebinds each map renderer before emitting the restored event.
Surface-cycle tests and renderer support
test/tilemap_fake_gfx.zig, test/tilemap_collection_test.zig, test/tilemap_interleave_support.zig, test/tilemap_mutation_test.zig, test/tilemap_surface_test.zig, build.zig
Shared fake renderer and TMX parsing support serve the tests. Tests cover renderer compatibility, re-upload under existing IDs, renderer rebinding, edits across a surface cycle, partial upload failure, and texture teardown. The surface lifecycle test is added to the test suite.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix · Severity of issue fixed: Medium

Sequence Diagram(s)

sequenceDiagram
  participant Game
  participant TilemapMixin
  participant Runtime
  participant ImageProvider
  participant RenderImpl
  Game->>TilemapMixin: invalidateTilemapTextures
  TilemapMixin->>Runtime: surfaceLost
  Runtime->>RenderImpl: invalidateTexture for owned IDs
  Game->>TilemapMixin: reloadTilemapTextures
  TilemapMixin->>Runtime: surfaceRestored with image provider
  Runtime->>ImageProvider: fetch retained image sources
  Runtime->>RenderImpl: re-upload textures under existing IDs
  Runtime->>RenderImpl: rebind tilemap renderer
Loading

Merge Risk: ⚪ Minimal · up to 1327c

Tilemap sheet and per-tile textures are now invalidated when the surface is lost and re-uploaded under their original IDs when it is restored. Runtime tile edits are preserved, and renderers without re-arm support keep their existing behavior. No merge-blocking issues were found, and tests cover the lifecycle, partial failures, and teardown.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description check ✅ Passed The description clearly explains the tilemap texture surface-loss problem, the lifecycle fix, and the related tests. It directly matches the changeset objectives.
Title check ✅ Passed The title clearly and concisely identifies the main change: preserving tilemap tileset textures across GPU surface loss and restoration.
Linked Issues check ✅ Passed Issue #847 requires sheet textures and collection-tileset per-tile images to survive surface loss and restore. Runtime records each uploaded source, invalidates owned IDs without freeing stale backe…
Out of Scope Changes check ✅ Passed The changes stay within issue #847. Production changes implement tilemap texture lifecycle handling and its Game lifecycle integration. The shared fake renderer, test refactoring, lifecycle tests, and…
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0…
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

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

@apotema
apotema merged commit 6a4bd64 into main Sep 28, 2026
4 checks passed
@apotema
apotema deleted the fix/847-tilemap-surface-restore branch September 28, 2026 23:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tilemap tileset textures do not survive surface loss/restore (sheet AND per-tile) — uploads bypass World.direct_textures

1 participant