Skip to content

Highway viz renderers keep rendering full-screen when #highway is display:none (leaks behind splitscreen + wasted GPU) #418

Description

@mogul

Restored from slopsmith/slopsmith#246 — original issue, opened by @topkoa on 2026-05-10.
[restored-from: slopsmith/slopsmith#246]

Summary

When the highway <canvas> is hidden via display:none but the createHighway() instance is not stopped, the active renderer keeps running: the rAF loop keeps firing, renderer.draw(bundle) keeps being called, and ren.render() keeps drawing a full WebGL scene that nobody can see. For renderers like 3D Highway that mount their own DOM as a sibling of #highway (a <div class="h3d-wrap"> overlay holding the WebGL canvas), display:none on #highway doesn't even hide the visible output — the overlay keeps painting full-screen.

Where it bites today: splitscreen

The splitscreen plugin, on activate, does document.getElementById('highway').style.display = 'none' and builds its own per-panel highways on top in #splitscreen-wrap (z-index 3). But:

  • It never stops the main window.highway. With the 3D Highway viz selected for the main player, that renderer's .h3d-wrap (z-index 2) keeps rendering full-screen behind the panels and bleeds through the panels' (slightly translucent) bottom bars — a visible "sliver of a full-screen 3D highway behind the panels."
  • Even after the splitscreen plugin works around the visual leak (hiding the sibling .h3d-wrap), the main highway's renderer is still running an entire WebGL render loop every frame for nothing. With N panels each running their own 3D renderer, that's N+1 WebGL contexts churning while only N are visible — ~33% wasted GPU/CPU at N=2, more for quad. On weak GPUs that's real.

(The splitscreen plugin is shipping a local workaround for the visual part — hiding #highway's sibling overlays on activate and re-binding panel.canvas after canvas swaps — but the render-loop waste can only be fixed in core.)

Proposed fix (core-level, transparent to all consumers)

In createHighway()'s rAF loop:

  1. Cheap per-frame visibility check: canvas.offsetParent === null (catches display:none on the canvas or any ancestor — exactly the splitscreen case). When not displayed, skip renderer.draw(bundle) and the default 2D draw that frame.
  2. When the displayed-state flips, emit a highway:visibility event ({ visible: boolean }) on window.slopsmith so renderers that mount their own DOM (3D Highway's .h3d-wrap) can hide/show it — same listener pattern they already use for highway:canvas-replaced. A renderer can also use this to drop its own per-frame work, free GPU buffers, etc.

Why this shape:

  • Transparent. No consumer changes needed — splitscreen (and the popup-follower window, and any future host that hides the highway) gets render-loop pausing + overlay hiding for free. No follow-up PR in the splitscreen plugin.
  • One place. Visibility logic lives in core, not duplicated (and subtly wrong) across every viz plugin.
  • offsetParent === null won't catch visibility:hidden / opacity:0 / "occluded by an opaque sibling" — those are rare; an optional explicit highway.setVisible(bool) override could cover them later if anyone needs it, but it's not required for v1.

Notes / docs

  • Update the setRenderer-contract section in CLAUDE.md: renderers that mount their own DOM should listen for highway:visibility and mirror the canvas's displayed state (just like the existing highway:canvas-replaced guidance).
  • draw(bundle) already isn't called during the loading/reconnect window; this just extends "don't draw" to "and don't draw while not displayed."

🤖 Filed via Claude Code while debugging the splitscreen sliver.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions