Background
PR #3015 introduced per-frame cross-camera pool leasing for BasicRenderPipeline._internalColorTarget / _copyBackgroundTexture: each camera acquires the internal RT from RenderTargetPool at the start of render() and returns it at the end, so cameras rendering sequentially in the same frame reuse one RT instead of each holding its own full-screen MSAA target.
This pattern currently covers only the internal color target. Several other pipeline pass RTs are still self-held across frames (kept on the pass instance via recreateRenderTargetIfNeeded, released only when the pass is disabled), so in a multi-camera scene each camera pins its own copy.
This issue tracks evaluating whether to extend the same per-frame lease to those passes.
Memory payoff (4 cameras @ 1920×1080, estimated)
| RT |
~Size |
Status |
| internal RT (full-res MSAA color) |
~33 MB |
✅ done in #3015 (~47% of total achievable saving) |
| PostProcess swap / output |
~16.6 MB ×2 |
✅ already per-frame freed (_releaseSwapRenderTarget / _releaseOutputRenderTarget) |
FinalPass _srgbRenderTarget |
~8.3 MB |
⬜ self-held — best follow-up candidate |
| Bloom mip up/down chain |
~85 MB (largest!) |
⬜ self-held — high payoff, complex nested lifetimes |
| DepthOnly RT |
~8.3 MB |
❌ do not convert (see below) |
| SAO + blur pair |
~6–12 MB |
⬜ low payoff, needs lifetime re-check |
The internal RT is the single biggest line item but only ~47% of the total achievable cross-camera saving — the long tail of full-res non-MSAA targets collectively rivals it. It is not an 80/20 win.
Recommended scope (priority order)
- FinalPass
_srgbRenderTarget — cleanest: purely internal, never exposed to camera.shaderData, allocated/used/released within pass scope. Lowest risk, ~8 MB.
- Bloom mip chain — largest payoff (~85 MB) but nested/overlapping lifetimes; needs a careful release-timing audit before converting.
- SAO pair — small payoff; its texture is bound into
camera.shaderData (camera_AOTexture) but last sampled in the opaque pass (transparent doesn't read AO), so likely safe to release at render end — needs confirmation that nothing samples it after the proposed release point.
Explicitly out of scope
- DepthOnly RT must NOT be converted. Its depth texture is published to
camera.shaderData via Camera._cameraDepthTextureProperty (DepthOnlyPass.onRender) and that reference outlives render() (accessible to materials/scripts as the camera depth texture). Returning the underlying RT to a shared pool would let the next camera's lease overwrite live depth data → use-after-free-style corruption. Keep it per-camera self-held.
Caveats / risks
- Each conversion needs a per-RT release-timing audit: the release point must come strictly after the last same-frame sampler of that RT. The internal RT was the easy case (used-then-discarded within
render()); the tail RTs have interleaved lifetimes and some publish texture refs into camera.shaderData.
- No temporal/history dependencies were found in the current pipeline (no TAA/motion-vector history), so cross-frame content reuse is not a blocker today — but any future temporal effect would change this calculus.
Acceptance
Follow-up to #3015.
Background
PR #3015 introduced per-frame cross-camera pool leasing for
BasicRenderPipeline._internalColorTarget/_copyBackgroundTexture: each camera acquires the internal RT fromRenderTargetPoolat the start ofrender()and returns it at the end, so cameras rendering sequentially in the same frame reuse one RT instead of each holding its own full-screen MSAA target.This pattern currently covers only the internal color target. Several other pipeline pass RTs are still self-held across frames (kept on the pass instance via
recreateRenderTargetIfNeeded, released only when the pass is disabled), so in a multi-camera scene each camera pins its own copy.This issue tracks evaluating whether to extend the same per-frame lease to those passes.
Memory payoff (4 cameras @ 1920×1080, estimated)
_releaseSwapRenderTarget/_releaseOutputRenderTarget)_srgbRenderTargetThe internal RT is the single biggest line item but only ~47% of the total achievable cross-camera saving — the long tail of full-res non-MSAA targets collectively rivals it. It is not an 80/20 win.
Recommended scope (priority order)
_srgbRenderTarget— cleanest: purely internal, never exposed tocamera.shaderData, allocated/used/released within pass scope. Lowest risk, ~8 MB.camera.shaderData(camera_AOTexture) but last sampled in the opaque pass (transparent doesn't read AO), so likely safe to release at render end — needs confirmation that nothing samples it after the proposed release point.Explicitly out of scope
camera.shaderDataviaCamera._cameraDepthTextureProperty(DepthOnlyPass.onRender) and that reference outlivesrender()(accessible to materials/scripts as the camera depth texture). Returning the underlying RT to a shared pool would let the next camera's lease overwrite live depth data → use-after-free-style corruption. Keep it per-camera self-held.Caveats / risks
render()); the tail RTs have interleaved lifetimes and some publish texture refs intocamera.shaderData.Acceptance
_srgbRenderTargetto per-frame lease (or document why not)camera.shaderDatalifetime constraintFollow-up to #3015.