Target: 19.10 — design converged with the maintainer (2026-07-08); #1543 lands the 19.9 groundwork (generalized Texture2d contract + setTexture(Texture2d)).
Motivation
A user needed the current camera view as a shader input (screen-space water refraction) and resorted to gl.readPixels + texImage2D on a fork — a full pipeline stall plus a double bus-crossing of the frame, per object, per frame (multi-ms; frame-budget-destroying on mobile). The engine has no sanctioned way to sample "what's on screen behind me".
API
renderer.toFrameTexture(options?: {
target?: Texture2d, // reuse a previous capture (two-per-frame case)
region?: Rect | Bounds // sub-region = proportionally cheaper copy
}) → Texture2d
- Name: the fourth member of the
toDataURL / toBlob / toImageBitmap family ("the current frame as X") — the only one that never leaves the GPU. Deliberately not snapshot (Phaser attached that to the slow CPU path).
- Returns the public
Texture2d (concrete class is a small unexported subclass; its GPU backing implements the internal TextureResource upload/bind duck-type). One public texture taxonomy — TextureResource stays internal.
- Consumption:
effect.setTexture("screenTex", renderer.toFrameTexture()) — direct. setTexture gains a live-bind branch for GPU-resident sources (today it uploads static copies; a capture must be referenced, not copied — the renderer refreshes one shared GL texture).
Semantics
- One
gl.copyTexSubImage2D from the currently bound framebuffer — backbuffer normally, the camera's view FBO during a camera post-effect pass — after a flush(), so the capture holds everything drawn so far this frame at the call site (Godot BackBufferCopy placement, as a method call). Works under Camera2d and Camera3d.
- ~0.1–0.5 ms of enqueued GPU time full-screen vs multi-ms + stall for
readPixels: 10–100× cheaper and, decisively, zero CPU/GPU serialization.
- Shared renderer-owned slot by default (valid until the next parameterless call); caller-owned via
target. RGBA storage (format-matching fast path). Binding goes through the texture cache (uploadTexture), so unit bookkeeping stays consistent by construction and context-loss recovery rides the existing cache-reset machinery.
- Canvas renderer: offscreen-canvas self-copy variant, keeping the family renderer-complete.
Prior art
Three.js copyFramebufferToTexture (closest 1:1) · Godot BackBufferCopy / hint_screen_texture (placement model) · Unity _CameraOpaqueTexture · Phaser/Pixi only offer render-to-texture or CPU snapshot paths.
Caveats (documented, not hidden)
- Color-only; a samplable depth texture is a separate future feature (FBOs currently attach
DEPTH_STENCIL renderbuffers).
- Under Camera3d, "drawn-so-far" ≠ depth order — guidance mirrors Unity/Godot: capture after opaque content, right before the refracting surface draws.
- Y-orientation of the FBO-source case pinned by pixel test at implementation, not assumed.
Acceptance
Red-first tests: backdrop-through-passthrough shader, painter-order semantics, region, capture inside a camera post-effect pass, context-loss revival, shared-slot resize. End-to-end validation: port of the user's water-refraction shader (noise flow-map × captured backdrop).
Related / future
Target: 19.10 — design converged with the maintainer (2026-07-08); #1543 lands the 19.9 groundwork (generalized
Texture2dcontract +setTexture(Texture2d)).Motivation
A user needed the current camera view as a shader input (screen-space water refraction) and resorted to
gl.readPixels+texImage2Don a fork — a full pipeline stall plus a double bus-crossing of the frame, per object, per frame (multi-ms; frame-budget-destroying on mobile). The engine has no sanctioned way to sample "what's on screen behind me".API
toDataURL/toBlob/toImageBitmapfamily ("the current frame as X") — the only one that never leaves the GPU. Deliberately notsnapshot(Phaser attached that to the slow CPU path).Texture2d(concrete class is a small unexported subclass; its GPU backing implements the internalTextureResourceupload/bind duck-type). One public texture taxonomy —TextureResourcestays internal.effect.setTexture("screenTex", renderer.toFrameTexture())— direct.setTexturegains a live-bind branch for GPU-resident sources (today it uploads static copies; a capture must be referenced, not copied — the renderer refreshes one shared GL texture).Semantics
gl.copyTexSubImage2Dfrom the currently bound framebuffer — backbuffer normally, the camera's view FBO during a camera post-effect pass — after aflush(), so the capture holds everything drawn so far this frame at the call site (GodotBackBufferCopyplacement, as a method call). Works under Camera2d and Camera3d.readPixels: 10–100× cheaper and, decisively, zero CPU/GPU serialization.target. RGBA storage (format-matching fast path). Binding goes through the texture cache (uploadTexture), so unit bookkeeping stays consistent by construction and context-loss recovery rides the existing cache-reset machinery.Prior art
Three.js
copyFramebufferToTexture(closest 1:1) · GodotBackBufferCopy/hint_screen_texture(placement model) · Unity_CameraOpaqueTexture· Phaser/Pixi only offer render-to-texture or CPU snapshot paths.Caveats (documented, not hidden)
DEPTH_STENCILrenderbuffers).Acceptance
Red-first tests: backdrop-through-passthrough shader, painter-order semantics,
region, capture inside a camera post-effect pass, context-loss revival, shared-slot resize. End-to-end validation: port of the user's water-refraction shader (noise flow-map × captured backdrop).Related / future
RenderTexturewith an honestgetTexture()(GodotViewportTextureshape) — render-to-texture sibling.Texture2d/GPU-resource taxonomy unification beyond this: Refactor TextureCache and Batcher base class for renderer-agnostic abstraction #1410.readPixelsv1,copyTexSubImage2D-with-manual-units v2) available in the design discussion.