Summary
Text and gradient surfaces round their offscreen canvas up to the next power of two. WebGL 2 supports non-power-of-two textures fully (including REPEAT wrap and mipmaps), so the rounding is pure waste since #1509 made the renderer WebGL 2 only.
Where
src/renderable/text/text.js:365-367 — nextPowerOfTwo(this.metrics.width / .height), with the comment already admitting "required for WebGL1, harmless for WebGL2/Canvas"
src/video/gradient.js:166-167 — same rounding for gradient textures
Impact
A 300×50 text label currently allocates a 512×64 canvas — about 2.2× the pixels. And because Text.draw() blits the whole canvas (renderer.drawImage(this.canvasTexture.canvas, x, y), text.js:533) rather than a metrics-sized sub-rect, the padded region is drawn too (transparent, so it looks correct). The saving is real for text-heavy UIs, and it is not only memory.
⚠️ The trap: POT rounding is also acting as allocation hysteresis
Do not just delete the two nextPowerOfTwo() calls. The resize is guarded by a grow-only check and CanvasRenderTarget.resize() never shrinks:
if (this.canvasTexture.width < width || this.canvasTexture.height < height) {
this.canvasTexture.resize(width, height);
}
Power-of-two bucketing means a score counter going "9" → "10" → "11" usually stays inside the same bucket and never reallocates. Remove the rounding and every width change crosses the threshold, so dynamic text reallocates its backing canvas on essentially every update — a per-frame allocation regression on exactly the widgets that change most often.
So the work is to replace the growth strategy, not remove it:
- round up to a modest multiple (e.g. 16 or 32 px), or
- grow geometrically (e.g. ×1.25 with a floor), or
- keep exact sizing but add explicit hysteresis (only resize when the delta exceeds a threshold)
Any of those keeps the reallocation behaviour while recovering most of the wasted pixels.
Scope
Unblocked by #1509 (WebGL 2 only). Related: #1553.
Summary
Textand gradient surfaces round their offscreen canvas up to the next power of two. WebGL 2 supports non-power-of-two textures fully (includingREPEATwrap and mipmaps), so the rounding is pure waste since #1509 made the renderer WebGL 2 only.Where
src/renderable/text/text.js:365-367—nextPowerOfTwo(this.metrics.width / .height), with the comment already admitting "required for WebGL1, harmless for WebGL2/Canvas"src/video/gradient.js:166-167— same rounding for gradient texturesImpact
A 300×50 text label currently allocates a 512×64 canvas — about 2.2× the pixels. And because
Text.draw()blits the whole canvas (renderer.drawImage(this.canvasTexture.canvas, x, y),text.js:533) rather than a metrics-sized sub-rect, the padded region is drawn too (transparent, so it looks correct). The saving is real for text-heavy UIs, and it is not only memory.Do not just delete the two
nextPowerOfTwo()calls. The resize is guarded by a grow-only check andCanvasRenderTarget.resize()never shrinks:Power-of-two bucketing means a score counter going
"9"→"10"→"11"usually stays inside the same bucket and never reallocates. Remove the rounding and every width change crosses the threshold, so dynamic text reallocates its backing canvas on essentially every update — a per-frame allocation regression on exactly the widgets that change most often.So the work is to replace the growth strategy, not remove it:
Any of those keeps the reallocation behaviour while recovering most of the wasted pixels.
Scope
text.jsandgradient.jswith a hysteresis-preserving growth strategyisPowerOfTwo/nextPowerOfTworemain inmath.tsas public utilities regardlessUnblocked by #1509 (WebGL 2 only). Related: #1553.