Skip to content

fix: Renderer.drawScene() calls Finish() on its destination (issue #211) - #214

Merged
markwpearce merged 4 commits into
mainfrom
spike/issue-211-large-image-render
Sep 12, 2026
Merged

markwpearce merged 4 commits into
mainfrom
spike/issue-211-large-image-render

Conversation

@markwpearce

Copy link
Copy Markdown
Owner

Summary

Renderer.drawScene() never called .Finish() on its destination surface before the frame proceeded to compositing/SwapBuffers() - unlike every other engine path that draws into a scratch/temp bitmap and reads it back afterward (triangle warps, oriented-mode temp bitmaps - see Renderer.forceDraw()). That asymmetry is the root cause of issue #211: a baked tilemap chunk (examples/platformer's ground/platform, via BGE.TileMap.bakeTileMapImages()) could silently fail to render above ~192px, even with provably-correct content, on both the BrightScript Simulator and real hardware.

  • src/source/engine/renderer/Renderer.bs: one m.draw2d.Finish() call added at the end of drawScene().
  • examples/platformer/src/source/Entities/Level.bs: chunkSize raised 128 -> 512 (colliders 24->9, scene objects 34->10 on this level), doc comment updated with the measurements below.
  • examples/platformer/src/source/Rooms/MainRoom.bs: the existing debug-overlay toggle (options/info) now also calls debugDrawColliders() - useful for diagnosing this, and future issues.
  • src/source/utils/tilemap/TileMapBaking.bs: doc comment updated to match.

Testing

  • npm run check (lint + validate + headless Rooibos suite): 1105 passed.
  • Real hardware (not the simulator - confirmed via device-info model check after an earlier mixup): automated in-app GetByteArray checks, oscillation across the level's pit/platform boundary, and multi-copy stress tests (up to 16 simultaneous 320px blits/frame, ~500+ sustained frames) all show zero failures at chunkSize=512, versus reliable reproduction of the original failure at 256+ before this fix.
  • Manual play-testing on real hardware, confirmed by @markwpearce mid-session.

Known remaining gap (not blocking this fix)

Pushing chunkSize further (768+) reproduces a different, deterministic failure: a merged chunk spanning a large mostly-empty vertical gap can still fail to render inside the full running game, even though identical dimensions are reliable in an isolated stress test. Filed as #213 rather than chased further here, since 512 already represents a solid, thoroughly-verified 4x improvement over the original 128 cap.

Related

🤖 Generated with Claude Code

markwpearce and others added 4 commits September 12, 2026 10:23
Adds a rendererTest diagnostic (LargeImageRenderTest, id
large-image-render) that draws a controllable size/source/finish
combo directly onto the real roScreen every frame and auto-verifies
via GetByteArray, to test whether an isolated large blit reproduces
the silent-render-failure documented in TileMapBaking.bs.

Findings (full writeup in specs/2026-09-12-large-image-render-spike.md):
- No hardcoded size cap exists anywhere in brs-engine's draw path.
- This exact symptom was already filed upstream
  (lvcabral/brs-engine#1198) and the maintainer couldn't reproduce it
  in an isolated repro either.
- Renderer.forceDraw()'s Finish()/dummy-screen-flush pattern is never
  applied to the plain single-blit path SceneObjectImage's
  matchCamera/directToCamera mode uses (a real asymmetry vs. every
  other scratch-bitmap path) - a plausible but, per this spike's
  empirical test, NOT sufficient explanation on its own.
- Ran the new demo at sizes up to 640px, memory- and disk-sourced,
  with no Finish() call (worst case), on both the BrightScript
  Simulator and real Roku hardware: zero failures across ~500
  sustained frames on each. The isolated repro does not reproduce the
  original failure on either target.
- roTextureManager is a load/cache wrapper only; it can't affect
  render-time completion and isn't a fix for this.

Next steps recorded in the spec doc: re-test embedded in a real
BGE.Game/Room under realistic entity/collider load, add the missing
Finish()/forceDraw() call defensively regardless, and test under
deliberate GC pressure.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The first "real hardware" pass in the previous commit actually re-hit
the local BrightScript Simulator (.env's default ROKU_HOST resolves
there too) - re-ran against an explicit --host pointed at an actual
physical Roku, confirmed via device-info model check.

Along the way found roScreen.GetByteArray() returns Invalid on real
hardware (crashed the demo) but returns real pixel data on both the
simulator GUI app and headless brs-cli - a genuine, previously
undocumented platform difference, worth its own upstream report.
roBitmap.GetByteArray() is unaffected on both platforms. The demo now
detects and falls back to screenshot-only verification where
unsupported.

Also added a `count` dimension (1/4/8/12/16 simultaneous copies per
frame, rev/fwd to cycle) per the idea that a single isolated blit may
not be representative of a real level baking many chunks at once.
Re-verified on the real device: up to 16 simultaneous 320px blits/
frame (memory- and disk-sourced), sustained ~9.5s, zero failures.

Findings and corrected results folded into
specs/2026-09-12-large-image-render-spike.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Every other engine path that draws into a scratch/temp bitmap and
reads it back afterward (triangle warps, oriented-mode temp bitmaps)
already forces completion via Renderer.forceDraw()'s Finish() call -
but drawScene() itself never did, for the plain single-blit path
every screen-aligned Image (matchCamera/directToCamera) actually
uses. Added one Finish() call per frame, at the end of drawScene().

Confirmed on real hardware (Mark Pearce's Roku device): a baked
tilemap chunk that previously silently failed to render above
~192px now renders reliably up to chunkSize=512 (colliders 24->9,
scene objects 34->10 on examples/platformer's level) - verified via
automated in-app GetByteArray checks, oscillation across the level's
pit/platform boundary, multi-copy stress tests (up to 16 simultaneous
320px blits/frame), and manual play-testing, all with zero failures
across ~500+ sustained real-device frames.

Raised examples/platformer's Level.bs chunkSize from 128 to 512 and
updated its and TileMapBaking.bs's doc comments accordingly. Also
wires MainRoom's existing debug-overlay toggle (options/info button)
to debugDrawColliders(), which was useful for diagnosing this and
will be for future issues too.

A separate, smaller residual gap was found while pushing chunkSize
further (768+): a merged chunk spanning a large mostly-empty vertical
gap can still occasionally fail to render *inside the full running
game* even though identical dimensions are reliable in isolation.
Filed as issue #213 rather than chased further here, since 512
already represents a solid, thoroughly-verified 4x improvement.

Also filed lvcabral/brs-engine#1229 for a platform difference found
along the way: roScreen.GetByteArray() returns Invalid on real
hardware but real pixel data on the simulator/brs-node.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@markwpearce
markwpearce merged commit 0460797 into main Sep 12, 2026
4 checks passed
@markwpearce
markwpearce deleted the spike/issue-211-large-image-render branch September 12, 2026 15:39
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.

Support multi-frame loading for Room setup (spread heavy onCreate work like tile baking across frames)

1 participant