From cb10aadb0623c998d7d0a36e7b4026cd1080be34 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 23 Jul 2022 19:18:26 -0700 Subject: [PATCH 1/6] Fix device pixel rounding errors in webgl renderer This uses the ResizeObserver devicePixelContentBoxSize API in order to fetch the exact device pixel dimensions from the browser. The old possibly blurry behavior is used as a fallback if that API is not available. Part of #2662 Part of microsoft/vscode#85154 --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 95 +++++++++++-------- 1 file changed, 57 insertions(+), 38 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 1e385b3399..2c885ef666 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -11,7 +11,7 @@ import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; -import { Disposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; @@ -96,6 +96,31 @@ export class WebglRenderer extends Disposable implements IRenderer { this.register(addDisposableDomListener(this._canvas, 'webglcontextlost', (e) => { this._onContextLoss.fire(e); })); + // Observe any resizes to the canvas and extract the actual pixel size of the canvas if the + // devicePixelContentBoxSize API is supported. This allows correcting rounding errors when + // converting between CSS pixels and device pixels which causes blurry rendering when device + // pixel ratio is not a round number. + let observer: ResizeObserver | undefined = new ResizeObserver((entries) => { + const entry = entries.find((entry) => entry.target === this._canvas); + if (!entry) { + return; + } + + // Disconnect if devicePixelContentBoxSize isn't supported by the browser + if (!('devicePixelContentBoxSize' in entry)) { + observer?.disconnect(); + observer = undefined; + return; + } + + this._setCanvasDevicePixelDimensions( + entry.devicePixelContentBoxSize[0].inlineSize, + entry.devicePixelContentBoxSize[0].blockSize + ); + }); + observer.observe(this._canvas, { box: ['device-pixel-content-box'] } as any); + this.register(toDisposable(() => observer?.disconnect())); + this._core.screenElement!.appendChild(this._canvas); this._rectangleRenderer = this.register(new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions)); @@ -516,67 +541,61 @@ export class WebglRenderer extends Disposable implements IRenderer { return; } - // Calculate the scaled character width. Width is floored as it must be - // drawn to an integer grid in order for the CharAtlas "stamps" to not be - // blurry. When text is drawn to the grid not using the CharAtlas, it is - // clipped to ensure there is no overlap with the next cell. - - // NOTE: ceil fixes sometime, floor does others :s - + // Calculate the scaled character width. Width is floored as it must be drawn to an integer grid + // in order for the char atlas glyphs to not be blurry. this.dimensions.scaledCharWidth = Math.floor((this._core as any)._charSizeService.width * this._devicePixelRatio); - // Calculate the scaled character height. Height is ceiled in case - // devicePixelRatio is a floating point number in order to ensure there is - // enough space to draw the character to the cell. + // Calculate the scaled character height. Height is ceiled in case devicePixelRatio is a + // floating point number in order to ensure there is enough space to draw the character to the + // cell. this.dimensions.scaledCharHeight = Math.ceil((this._core as any)._charSizeService.height * this._devicePixelRatio); - // Calculate the scaled cell height, if lineHeight is not 1 then the value - // will be floored because since lineHeight can never be lower then 1, there - // is a guarentee that the scaled line height will always be larger than - // scaled char height. + // Calculate the scaled cell height, if lineHeight is _not_ 1, the resulting value will be + // floored since lineHeight can never be lower then 1, this guarentees the scaled cell height + // will always be larger than scaled char height. this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight!); - // Calculate the y coordinate within a cell that text should draw from in - // order to draw in the center of a cell. + // Calculate the y offset within a cell that glyph should draw at in order for it to be centered + // correctly within the cell. this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); // Calculate the scaled cell width, taking the letterSpacing into account. this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing!); - // Calculate the x coordinate with a cell that text should draw from in - // order to draw in the center of a cell. + // Calculate the x offset with a cell that text should draw from in order for it to be centered + // correctly within the cell. this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing! / 2); - // Recalculate the canvas dimensions; scaled* define the actual number of - // pixel in the canvas + // Recalculate the canvas dimensions, the scaled dimensions define the actual number of pixel in + // the canvas this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight; this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth; - // The the size of the canvas on the page. It's very important that this - // rounds to nearest integer and not ceils as browsers often set - // window.devicePixelRatio as something like 1.100000023841858, when it's - // actually 1.1. Ceiling causes blurriness as the backing canvas image is 1 - // pixel too large for the canvas element size. + // The the size of the canvas on the page. It's important that this rounds to nearest integer + // and not ceils as browsers often have floating point precision issues where + // `window.devicePixelRatio` ends up being something like `1.100000023841858` for example, when + // it's actually 1.1. Ceiling may causes blurriness as the backing canvas image is 1 pixel too + // large for the canvas element size. this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / this._devicePixelRatio); this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / this._devicePixelRatio); - // this.dimensions.scaledCanvasHeight = this.dimensions.canvasHeight * devicePixelRatio; - // this.dimensions.scaledCanvasWidth = this.dimensions.canvasWidth * devicePixelRatio; - - // Get the _actual_ dimensions of an individual cell. This needs to be - // derived from the canvasWidth/Height calculated above which takes into - // account window.devicePixelRatio. CharMeasure.width/height by itself is - // insufficient when the page is not at 100% zoom level as CharMeasure is - // measured in CSS pixels, but the actual char size on the canvas can + // Get the _actual_ dimensions of an individual cell. This needs to be derived from the + // canvas CSS dimensions calculated above which takes into account device pixel ratio. + // `CharMeasure.width`/`height` by itself is insufficient when device pixel ratio is not a round + // number as CharMeasure is measured in CSS pixels, since the actual device pixel char size will // differ. - // this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows; - // this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols; - - // This fixes 110% and 125%, not 150% or 175% though this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / this._devicePixelRatio; this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio; } + private _setCanvasDevicePixelDimensions(width: number, height: number): void { + this.dimensions.scaledCanvasHeight = width; + this.dimensions.scaledCanvasWidth = height; + this._canvas.width = width; + this._canvas.height = height; + this._requestRedrawViewport(); + } + private _requestRedrawViewport(): void { this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); } From e25471361f127f534551f6ec78ebb16a71e6ca32 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 03:41:25 -0700 Subject: [PATCH 2/6] Fix device pixel value in canvas renderer --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 10 +-- src/browser/renderer/BaseRenderLayer.ts | 2 + src/browser/renderer/Renderer.ts | 80 ++++++++++--------- src/browser/renderer/Types.d.ts | 2 + 4 files changed, 50 insertions(+), 44 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 2c885ef666..b081fb1f84 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -579,13 +579,9 @@ export class WebglRenderer extends Disposable implements IRenderer { this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / this._devicePixelRatio); this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / this._devicePixelRatio); - // Get the _actual_ dimensions of an individual cell. This needs to be derived from the - // canvas CSS dimensions calculated above which takes into account device pixel ratio. - // `CharMeasure.width`/`height` by itself is insufficient when device pixel ratio is not a round - // number as CharMeasure is measured in CSS pixels, since the actual device pixel char size will - // differ. - this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / this._devicePixelRatio; - this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio; + // Get the CSS dimensions of an individual cell. + this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._devicePixelRatio; + this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._devicePixelRatio; } private _setCanvasDevicePixelDimensions(width: number, height: number): void { diff --git a/src/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 0a9b805701..af3aabb42f 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -48,6 +48,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { italic: false }; + public get canvas(): HTMLCanvasElement { return this._canvas; } + constructor( private _container: HTMLElement, id: string, diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index 8bc3227828..04fdec7717 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -8,7 +8,7 @@ import { SelectionRenderLayer } from 'browser/renderer/SelectionRenderLayer'; import { CursorRenderLayer } from 'browser/renderer/CursorRenderLayer'; import { IRenderLayer, IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; import { LinkRenderLayer } from 'browser/renderer/LinkRenderLayer'; -import { Disposable } from 'common/Lifecycle'; +import { Disposable, toDisposable } from 'common/Lifecycle'; import { IColorSet, ILinkifier, ILinkifier2 } from 'browser/Types'; import { ICharSizeService } from 'browser/services/Services'; import { IBufferService, IOptionsService, IInstantiationService } from 'common/services/Services'; @@ -62,6 +62,33 @@ export class Renderer extends Disposable implements IRenderer { }; this._devicePixelRatio = window.devicePixelRatio; this._updateDimensions(); + + // Observe any resizes to the canvas and extract the actual pixel size of the canvas if the + // devicePixelContentBoxSize API is supported. This allows correcting rounding errors when + // converting between CSS pixels and device pixels which causes blurry rendering when device + // pixel ratio is not a round number. + const observedCanvas = this._renderLayers[0].canvas; + let observer: ResizeObserver | undefined = new ResizeObserver((entries) => { + const entry = entries.find((entry) => entry.target === observedCanvas); + if (!entry) { + return; + } + + // Disconnect if devicePixelContentBoxSize isn't supported by the browser + if (!('devicePixelContentBoxSize' in entry)) { + observer?.disconnect(); + observer = undefined; + return; + } + + this._setCanvasDevicePixelDimensions( + entry.devicePixelContentBoxSize[0].inlineSize, + entry.devicePixelContentBoxSize[0].blockSize + ); + }); + observer.observe(observedCanvas, { box: ['device-pixel-content-box'] } as any); + this.register(toDisposable(() => observer?.disconnect())); + this.onOptionsChanged(); } @@ -167,53 +194,32 @@ export class Renderer extends Disposable implements IRenderer { return; } - // Calculate the scaled character width. Width is floored as it must be - // drawn to an integer grid in order for the CharAtlas "stamps" to not be - // blurry. When text is drawn to the grid not using the CharAtlas, it is - // clipped to ensure there is no overlap with the next cell. + // See the WebGL renderer for an explanation of this section. this.dimensions.scaledCharWidth = Math.floor(this._charSizeService.width * window.devicePixelRatio); - - // Calculate the scaled character height. Height is ceiled in case - // devicePixelRatio is a floating point number in order to ensure there is - // enough space to draw the character to the cell. this.dimensions.scaledCharHeight = Math.ceil(this._charSizeService.height * window.devicePixelRatio); - - // Calculate the scaled cell height, if lineHeight is not 1 then the value - // will be floored because since lineHeight can never be lower then 1, there - // is a guarentee that the scaled line height will always be larger than - // scaled char height. this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._optionsService.rawOptions.lineHeight); - - // Calculate the y coordinate within a cell that text should draw from in - // order to draw in the center of a cell. this.dimensions.scaledCharTop = this._optionsService.rawOptions.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); - - // Calculate the scaled cell width, taking the letterSpacing into account. this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._optionsService.rawOptions.letterSpacing); - - // Calculate the x coordinate with a cell that text should draw from in - // order to draw in the center of a cell. this.dimensions.scaledCharLeft = Math.floor(this._optionsService.rawOptions.letterSpacing / 2); - - // Recalculate the canvas dimensions; scaled* define the actual number of - // pixel in the canvas this.dimensions.scaledCanvasHeight = this._bufferService.rows * this.dimensions.scaledCellHeight; this.dimensions.scaledCanvasWidth = this._bufferService.cols * this.dimensions.scaledCellWidth; - - // The the size of the canvas on the page. It's very important that this - // rounds to nearest integer and not ceils as browsers often set - // window.devicePixelRatio as something like 1.100000023841858, when it's - // actually 1.1. Ceiling causes blurriness as the backing canvas image is 1 - // pixel too large for the canvas element size. this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio); this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio); - - // Get the _actual_ dimensions of an individual cell. This needs to be - // derived from the canvasWidth/Height calculated above which takes into - // account window.devicePixelRatio. ICharSizeService.width/height by itself - // is insufficient when the page is not at 100% zoom level as it's measured - // in CSS pixels, but the actual char size on the canvas can differ. this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._bufferService.rows; this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._bufferService.cols; } + + private _setCanvasDevicePixelDimensions(width: number, height: number): void { + this.dimensions.scaledCanvasHeight = width; + this.dimensions.scaledCanvasWidth = height; + // Resize all render layers + for (const l of this._renderLayers) { + l.resize(this.dimensions); + } + this._requestRedrawViewport(); + } + + private _requestRedrawViewport(): void { + this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 }); + } } diff --git a/src/browser/renderer/Types.d.ts b/src/browser/renderer/Types.d.ts index 6818a92673..7410d4813e 100644 --- a/src/browser/renderer/Types.d.ts +++ b/src/browser/renderer/Types.d.ts @@ -56,6 +56,8 @@ export interface IRenderer extends IDisposable { } export interface IRenderLayer extends IDisposable { + readonly canvas: HTMLCanvasElement; + /** * Called when the terminal loses focus. */ From 3d6c25bf2de0a9ee625e61350ccb432c1a98f269 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 03:43:28 -0700 Subject: [PATCH 3/6] typescript@4.7 Required for devicePixelContentBoxSize ResizeObserver API --- package.json | 2 +- yarn.lock | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index cb0c1919df..9db4d16f1b 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "source-map-loader": "^3.0.0", "source-map-support": "^0.5.20", "ts-loader": "^9.1.2", - "typescript": "^4.4.4", + "typescript": "4.7", "utf8": "^3.0.0", "webpack": "^5.61.0", "webpack-cli": "^4.9.1", diff --git a/yarn.lock b/yarn.lock index 7cb970bbc1..2a5c02bbcc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3817,16 +3817,16 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" +typescript@4.7: + version "4.7.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" + integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== + typescript@^4.2.3: version "4.6.4" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9" integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg== -typescript@^4.4.4: - version "4.4.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.4.tgz#2cd01a1a1f160704d3101fd5a58ff0f9fcb8030c" - integrity sha512-DqGhF5IKoBl8WNf8C1gu8q0xZSInh9j1kJJMqT3a94w1JzVaBU4EXOSMrz9yDqMT0xt3selp83fuFMQ0uzv6qA== - unbox-primitive@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" From ad8404417edb072db61f0a064e4a66ec0ae24614 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 03:57:22 -0700 Subject: [PATCH 4/6] ts-loader update, correct css cell dimension error --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 16 +++++++++++----- package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index b081fb1f84..8d6a4f4958 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -579,14 +579,20 @@ export class WebglRenderer extends Disposable implements IRenderer { this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / this._devicePixelRatio); this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / this._devicePixelRatio); - // Get the CSS dimensions of an individual cell. - this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._devicePixelRatio; - this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._devicePixelRatio; + // Get the CSS dimensions of an individual cell. This needs to be derived from the calculated + // device pixel canvas value above. CharMeasure.width/height by itself is insufficient when the + // page is not at 100% zoom level as CharMeasure is measured in CSS pixels, but the actual char + // size on the canvas can differ. + this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / this._devicePixelRatio; + this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio; } private _setCanvasDevicePixelDimensions(width: number, height: number): void { - this.dimensions.scaledCanvasHeight = width; - this.dimensions.scaledCanvasWidth = height; + if (this.dimensions.scaledCanvasWidth === width && this.dimensions.scaledCanvasHeight === height) { + return; + } + this.dimensions.scaledCanvasWidth = width; + this.dimensions.scaledCanvasHeight = height; this._canvas.width = width; this._canvas.height = height; this._requestRedrawViewport(); diff --git a/package.json b/package.json index 9db4d16f1b..a270a52f23 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,7 @@ "playwright": "^1.22.1", "source-map-loader": "^3.0.0", "source-map-support": "^0.5.20", - "ts-loader": "^9.1.2", + "ts-loader": "^9.3.1", "typescript": "4.7", "utf8": "^3.0.0", "webpack": "^5.61.0", diff --git a/yarn.lock b/yarn.lock index 2a5c02bbcc..a1e6391c30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3746,10 +3746,10 @@ tr46@^3.0.0: dependencies: punycode "^2.1.1" -ts-loader@^9.1.2: - version "9.2.6" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.6.tgz#9937c4dd0a1e3dbbb5e433f8102a6601c6615d74" - integrity sha512-QMTC4UFzHmu9wU2VHZEmWWE9cUajjfcdcws+Gh7FhiO+Dy0RnR1bNz0YCHqhI0yRowCE9arVnNxYHqELOy9Hjw== +ts-loader@^9.3.1: + version "9.3.1" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.3.1.tgz#fe25cca56e3e71c1087fe48dc67f4df8c59b22d4" + integrity sha512-OkyShkcZTsTwyS3Kt7a4rsT/t2qvEVQuKCTg4LJmpj9fhFR7ukGdZwV6Qq3tRUkqcXtfGpPR7+hFKHCG/0d3Lw== dependencies: chalk "^4.1.0" enhanced-resolve "^5.0.0" From 47b1fdd14725811c85a9e52f67c9dd6aba362093 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 04:09:34 -0700 Subject: [PATCH 5/6] Refactor to share observeDevicePixelDimensions --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 27 ++------------- src/browser/renderer/DevicePixelObserver.ts | 34 +++++++++++++++++++ src/browser/renderer/Renderer.ts | 27 ++------------- 3 files changed, 38 insertions(+), 50 deletions(-) create mode 100644 src/browser/renderer/DevicePixelObserver.ts diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 8d6a4f4958..b21f8c2f25 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -16,6 +16,7 @@ import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'co import { Terminal, IEvent } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver'; import { ITerminal, IColorSet } from 'browser/Types'; import { EventEmitter } from 'common/EventEmitter'; import { CellData } from 'common/buffer/CellData'; @@ -95,31 +96,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } this.register(addDisposableDomListener(this._canvas, 'webglcontextlost', (e) => { this._onContextLoss.fire(e); })); - - // Observe any resizes to the canvas and extract the actual pixel size of the canvas if the - // devicePixelContentBoxSize API is supported. This allows correcting rounding errors when - // converting between CSS pixels and device pixels which causes blurry rendering when device - // pixel ratio is not a round number. - let observer: ResizeObserver | undefined = new ResizeObserver((entries) => { - const entry = entries.find((entry) => entry.target === this._canvas); - if (!entry) { - return; - } - - // Disconnect if devicePixelContentBoxSize isn't supported by the browser - if (!('devicePixelContentBoxSize' in entry)) { - observer?.disconnect(); - observer = undefined; - return; - } - - this._setCanvasDevicePixelDimensions( - entry.devicePixelContentBoxSize[0].inlineSize, - entry.devicePixelContentBoxSize[0].blockSize - ); - }); - observer.observe(this._canvas, { box: ['device-pixel-content-box'] } as any); - this.register(toDisposable(() => observer?.disconnect())); + this.register(observeDevicePixelDimensions(this._canvas, (w, h) => this._setCanvasDevicePixelDimensions(w, h))); this._core.screenElement!.appendChild(this._canvas); diff --git a/src/browser/renderer/DevicePixelObserver.ts b/src/browser/renderer/DevicePixelObserver.ts new file mode 100644 index 0000000000..611eb91023 --- /dev/null +++ b/src/browser/renderer/DevicePixelObserver.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2022 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { toDisposable } from 'common/Lifecycle'; +import { IDisposable } from 'common/Types'; + +export function observeDevicePixelDimensions(element: HTMLElement, callback: (deviceWidth: number, deviceHeight: number) => void): IDisposable { + // Observe any resizes to the element and extract the actual pixel size of the element if the + // devicePixelContentBoxSize API is supported. This allows correcting rounding errors when + // converting between CSS pixels and device pixels which causes blurry rendering when device + // pixel ratio is not a round number. + let observer: ResizeObserver | undefined = new ResizeObserver((entries) => { + const entry = entries.find((entry) => entry.target === element); + if (!entry) { + return; + } + + // Disconnect if devicePixelContentBoxSize isn't supported by the browser + if (!('devicePixelContentBoxSize' in entry)) { + observer?.disconnect(); + observer = undefined; + return; + } + + callback( + entry.devicePixelContentBoxSize[0].inlineSize, + entry.devicePixelContentBoxSize[0].blockSize + ); + }); + observer.observe(element, { box: ['device-pixel-content-box'] } as any); + return toDisposable(() => observer?.disconnect()); +} diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index 04fdec7717..45b7f1c14f 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -14,6 +14,7 @@ import { ICharSizeService } from 'browser/services/Services'; import { IBufferService, IOptionsService, IInstantiationService } from 'common/services/Services'; import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver'; let nextRendererId = 1; @@ -63,31 +64,7 @@ export class Renderer extends Disposable implements IRenderer { this._devicePixelRatio = window.devicePixelRatio; this._updateDimensions(); - // Observe any resizes to the canvas and extract the actual pixel size of the canvas if the - // devicePixelContentBoxSize API is supported. This allows correcting rounding errors when - // converting between CSS pixels and device pixels which causes blurry rendering when device - // pixel ratio is not a round number. - const observedCanvas = this._renderLayers[0].canvas; - let observer: ResizeObserver | undefined = new ResizeObserver((entries) => { - const entry = entries.find((entry) => entry.target === observedCanvas); - if (!entry) { - return; - } - - // Disconnect if devicePixelContentBoxSize isn't supported by the browser - if (!('devicePixelContentBoxSize' in entry)) { - observer?.disconnect(); - observer = undefined; - return; - } - - this._setCanvasDevicePixelDimensions( - entry.devicePixelContentBoxSize[0].inlineSize, - entry.devicePixelContentBoxSize[0].blockSize - ); - }); - observer.observe(observedCanvas, { box: ['device-pixel-content-box'] } as any); - this.register(toDisposable(() => observer?.disconnect())); + this.register(observeDevicePixelDimensions(this._renderLayers[0].canvas, (w, h) => this._setCanvasDevicePixelDimensions(w, h))); this.onOptionsChanged(); } From 0165c3612dac9426607a1a70ab3899245dc65fc5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 24 Jul 2022 07:11:56 -0700 Subject: [PATCH 6/6] Fix canvas device pixel dimension setting --- src/browser/renderer/Renderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/browser/renderer/Renderer.ts b/src/browser/renderer/Renderer.ts index 45b7f1c14f..fe3c4c589c 100644 --- a/src/browser/renderer/Renderer.ts +++ b/src/browser/renderer/Renderer.ts @@ -187,8 +187,8 @@ export class Renderer extends Disposable implements IRenderer { } private _setCanvasDevicePixelDimensions(width: number, height: number): void { - this.dimensions.scaledCanvasHeight = width; - this.dimensions.scaledCanvasWidth = height; + this.dimensions.scaledCanvasHeight = height; + this.dimensions.scaledCanvasWidth = width; // Resize all render layers for (const l of this._renderLayers) { l.resize(this.dimensions);