Skip to content

renderer.toFrameTexture(): GPU frame capture as a Texture2d (screen-space refraction & co.) #1544

Description

@obiot

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions