perf: reuse clear options object in Renderer.clear#8776
Merged
Conversation
`Renderer.clear` was allocating a fresh `{ color, depth, stencil, flags }`
object — and a fresh `[r, g, b, a]` color array — on every call. The
method runs per camera per layer per frame, so this added up to a
sustained per-frame allocation.
Hoist both to module-scope scratch buffers and write the field values in
place. Behaviour is unchanged: the WebGL, WebGPU, and Null device `clear`
implementations only read from the options object, they never store it.
Measured impact on a typical-scene allocation profile (1800 frames,
NullGraphicsDevice, V8 sampling heap profiler @ 128 B interval):
- Renderer.clear attribution: 48 B/iter -> 0 B/iter (in a tight call loop)
- Engine-wide allocation sampled per frame on the same fixture:
201 B/frame -> 164 B/frame (-18%)
The scratch variables in this file all use the _temp* prefix (_tempLightSet, _tempLayerSet, _tempProjMat0..5, _tempSet, _tempMeshInstances). Match that rather than introducing a new Scratch suffix.
Contributor
There was a problem hiding this comment.
Pull request overview
Reduces per-frame GC pressure in the scene renderer by reusing scratch buffers for GraphicsDevice.clear options, avoiding repeated allocations in a hot path (Renderer.clear, invoked per camera/layer/frame).
Changes:
- Hoists a reusable clear color array to module scope and updates its RGBA values in-place per call.
- Hoists a reusable clear options object to module scope and updates depth/stencil/flags in-place per call before invoking
device.clear(...).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
mvaligursky
approved these changes
May 26, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Renderer.clearwas allocating a fresh{ color, depth, stencil, flags }object — and a fresh[r, g, b, a]color array — on every call. The method runs per camera per layer per frame, so this added up to sustained per-frame GC pressure.Hoist both to module-scope scratch buffers (matching the
_temp*naming used elsewhere in this file) and write field values in place. Behaviour is unchanged: the WebGL, WebGPU, and Nullclearimplementations only read from the options object, they never store it.Technical details
Verified that all three
clear(options)implementations only read fields and do not retain a reference:Measured impact
Captured via V8 sampling heap profiler (`node:inspector` `HeapProfiler.startSampling`) on a representative scene rendered against `NullGraphicsDevice`:
Public API changes
None. The options shape passed to `GraphicsDevice.clear` is unchanged; only the underlying memory is now reused.
Test plan