Version: ghostty-web 0.4.0-next.20.g1858a59 (also current main, 1858a59)
What happens
With a non-integer window.devicePixelRatio (browser zoom other than 100%, a scaled display, e.g.
1.36 or 2.2), the canvas renderer calls resize() and does a full redraw on every animation
frame, about 60 times a second while nothing changes. The caret and output look "busy", and CPU
use goes up. Cell backgrounds are also drawn at fractional device-pixel edges, which leaves
hairline seams (a visible grid) over coloured backgrounds.
Cause
CanvasRenderer.render() checks
this.canvas.width !== D.cols * this.metrics.width * this.devicePixelRatio
canvas.width is always an integer and the right-hand side is fractional at a fractional DPR, so
the check is true on every frame and resize() runs again, forcing a full redraw.
Repro
Open the demo, set browser zoom to 110% (or run Chrome with --force-device-scale-factor=1.36),
then wrap term.renderer.resize to count calls: about 60/s while idle. At DPR 1 or 2: 0/s.
Fix suggestion
Compare against Math.round(cols * width * dpr) (as resize() sets it), or render at
Math.ceil(devicePixelRatio) and let the browser scale the canvas down. The second also removes
the seams. We work around it by setting renderer.devicePixelRatio = Math.ceil(window.devicePixelRatio)
after open(), re-applied on a resolution media-query change. That took idle resizes from 60/s
to 0 and removed the seams.
Version: ghostty-web 0.4.0-next.20.g1858a59 (also current main, 1858a59)
What happens
With a non-integer
window.devicePixelRatio(browser zoom other than 100%, a scaled display, e.g.1.36 or 2.2), the canvas renderer calls
resize()and does a full redraw on every animationframe, about 60 times a second while nothing changes. The caret and output look "busy", and CPU
use goes up. Cell backgrounds are also drawn at fractional device-pixel edges, which leaves
hairline seams (a visible grid) over coloured backgrounds.
Cause
CanvasRenderer.render()checkscanvas.widthis always an integer and the right-hand side is fractional at a fractional DPR, sothe check is true on every frame and
resize()runs again, forcing a full redraw.Repro
Open the demo, set browser zoom to 110% (or run Chrome with
--force-device-scale-factor=1.36),then wrap
term.renderer.resizeto count calls: about 60/s while idle. At DPR 1 or 2: 0/s.Fix suggestion
Compare against
Math.round(cols * width * dpr)(asresize()sets it), or render atMath.ceil(devicePixelRatio)and let the browser scale the canvas down. The second also removesthe seams. We work around it by setting
renderer.devicePixelRatio = Math.ceil(window.devicePixelRatio)after
open(), re-applied on aresolutionmedia-query change. That took idle resizes from 60/sto 0 and removed the seams.