From 6fcc496547f7109d9aafe4f8e7362d58fa0885fd Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 17:36:16 -0700 Subject: [PATCH 01/23] Move cache utils to core shared/ folder Part of #4065 --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 5 +- addons/xterm-addon-canvas/src/CanvasAddon.ts | 2 +- .../xterm-addon-canvas/src/CanvasRenderer.ts | 12 +++-- .../src/CursorRenderLayer.ts | 5 +- .../xterm-addon-canvas/src/LinkRenderLayer.ts | 7 +-- .../src/SelectionRenderLayer.ts | 5 +- .../xterm-addon-canvas/src/TextRenderLayer.ts | 5 +- .../src/atlas/CharAtlasCache.ts | 25 ++++----- .../src/atlas/CharAtlasUtils.ts | 54 ------------------- .../src/atlas/DynamicCharAtlas.ts | 12 ++--- .../xterm-addon-canvas/src/atlas/Types.d.ts | 12 ----- .../src/atlas/CharAtlasCache.ts | 4 +- addons/xterm-addon-webgl/src/atlas/Types.d.ts | 22 -------- .../src/atlas/WebglCharAtlas.ts | 12 ++--- .../src/renderLayer/LinkRenderLayer.ts | 2 +- src/browser/ColorManager.ts | 10 ++-- .../renderer/shared}/CharAtlasUtils.ts | 13 ++--- src/browser/renderer/shared/README.md | 1 + src/browser/renderer/shared/Types.d.ts | 26 +++++++++ src/common/Color.ts | 5 ++ 20 files changed, 86 insertions(+), 153 deletions(-) delete mode 100644 addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts rename {addons/xterm-addon-webgl/src/atlas => src/browser/renderer/shared}/CharAtlasUtils.ts (92%) create mode 100644 src/browser/renderer/shared/README.md create mode 100644 src/browser/renderer/shared/Types.d.ts diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index f37ea1b8..641a5ca4 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -20,6 +20,7 @@ import { excludeFromContrastRatioDemands, throwIfFalsy } from 'browser/renderer/ import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; +import { Terminal } from 'xterm'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -53,12 +54,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { public get canvas(): HTMLCanvasElement { return this._canvas; } constructor( + private readonly _terminal: Terminal, private _container: HTMLElement, id: string, zIndex: number, private _alpha: boolean, protected _colors: IColorSet, - private _rendererId: number, protected readonly _bufferService: IBufferService, protected readonly _optionsService: IOptionsService, protected readonly _decorationService: IDecorationService, @@ -127,7 +128,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { return; } - this._charAtlas = acquireCharAtlas(this._optionsService.rawOptions, this._rendererId, colorSet, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); + this._charAtlas = acquireCharAtlas(this._terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); this._charAtlas.warmUp(); } diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 9fca00a6..372852b0 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -29,7 +29,7 @@ export class CanvasAddon implements ITerminalAddon { const colors: IColorSet = (terminal as any)._core._colorManager.colors; const screenElement: HTMLElement = (terminal as any)._core.screenElement; const linkifier = (terminal as any)._core.linkifier2; - this._renderer = new CanvasRenderer(colors, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService); + this._renderer = new CanvasRenderer(terminal, colors, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService); renderService.setRenderer(this._renderer); renderService.onResize(bufferService.cols, bufferService.rows); } diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index fd9629c0..77c2177e 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -16,6 +16,7 @@ import { IBufferService, IOptionsService, IDecorationService, ICoreService } fro import { removeTerminalFromCache } from './atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver'; +import { Terminal } from 'xterm'; let nextRendererId = 1; @@ -31,6 +32,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { public readonly onRequestRedraw = this._onRequestRedraw.event; constructor( + private readonly _terminal: Terminal, private _colors: IColorSet, private readonly _screenElement: HTMLElement, linkifier2: ILinkifier2, @@ -45,10 +47,10 @@ export class CanvasRenderer extends Disposable implements IRenderer { super(); const allowTransparency = this._optionsService.rawOptions.allowTransparency; this._renderLayers = [ - new TextRenderLayer(this._screenElement, 0, this._colors, allowTransparency, this._id, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService), - new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, this._coreBrowserService, decorationService, this._optionsService), - new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService), - new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService) + new TextRenderLayer(this._terminal, this._screenElement, 0, this._colors, allowTransparency, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService), + new SelectionRenderLayer(this._terminal, this._screenElement, 1, this._colors, this._bufferService, this._coreBrowserService, decorationService, this._optionsService), + new LinkRenderLayer(this._terminal, this._screenElement, 2, this._colors, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService), + new CursorRenderLayer(this._terminal, this._screenElement, 3, this._colors, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService) ]; this.dimensions = { scaledCharWidth: 0, @@ -77,7 +79,7 @@ export class CanvasRenderer extends Disposable implements IRenderer { l.dispose(); } super.dispose(); - removeTerminalFromCache(this._id); + removeTerminalFromCache(this._terminal); } public onDevicePixelRatioChange(): void { diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index 61e2d934..bddac195 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -11,6 +11,7 @@ import { IColorSet } from 'browser/Types'; import { IBufferService, IOptionsService, ICoreService, IDecorationService } from 'common/services/Services'; import { IEventEmitter } from 'common/EventEmitter'; import { ICoreBrowserService } from 'browser/services/Services'; +import { Terminal } from 'xterm'; interface ICursorState { x: number; @@ -32,10 +33,10 @@ export class CursorRenderLayer extends BaseRenderLayer { private _cell: ICellData = new CellData(); constructor( + terminal: Terminal, container: HTMLElement, zIndex: number, colors: IColorSet, - rendererId: number, private readonly _onRequestRedraw: IEventEmitter, bufferService: IBufferService, optionsService: IOptionsService, @@ -43,7 +44,7 @@ export class CursorRenderLayer extends BaseRenderLayer { coreBrowserService: ICoreBrowserService, decorationService: IDecorationService ) { - super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService, coreBrowserService); + super(terminal, container, 'cursor', zIndex, true, colors, bufferService, optionsService, decorationService, coreBrowserService); this._state = { x: 0, y: 0, diff --git a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts index c07329fd..5e1e7062 100644 --- a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts @@ -7,25 +7,26 @@ import { IRenderDimensions } from 'browser/renderer/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; import { ICoreBrowserService } from 'browser/services/Services'; -import { is256Color } from './atlas/CharAtlasUtils'; import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; +import { is256Color } from 'browser/renderer/shared/CharAtlasUtils'; +import { Terminal } from 'xterm'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent | undefined; constructor( + terminal: Terminal, container: HTMLElement, zIndex: number, colors: IColorSet, - rendererId: number, linkifier2: ILinkifier2, bufferService: IBufferService, optionsService: IOptionsService, decorationService: IDecorationService, coreBrowserService: ICoreBrowserService ) { - super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService, coreBrowserService); + super(terminal, container, 'link', zIndex, true, colors, bufferService, optionsService, decorationService, coreBrowserService); linkifier2.onShowLinkUnderline(e => this._onShowLinkUnderline(e)); linkifier2.onHideLinkUnderline(e => this._onHideLinkUnderline(e)); diff --git a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts index e90007b7..dc506bad 100644 --- a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts @@ -8,6 +8,7 @@ import { BaseRenderLayer } from './BaseRenderLayer'; import { IColorSet } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ICoreBrowserService } from 'browser/services/Services'; +import { Terminal } from 'xterm'; interface ISelectionState { start?: [number, number]; @@ -20,16 +21,16 @@ export class SelectionRenderLayer extends BaseRenderLayer { private _state!: ISelectionState; constructor( + terminal: Terminal, container: HTMLElement, zIndex: number, colors: IColorSet, - rendererId: number, bufferService: IBufferService, coreBrowserService: ICoreBrowserService, decorationService: IDecorationService, optionsService: IOptionsService ) { - super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService, decorationService, coreBrowserService); + super(terminal, container, 'selection', zIndex, true, colors, bufferService, optionsService, decorationService, coreBrowserService); this._clearState(); } diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index 95f22fce..eb004c9a 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -15,6 +15,7 @@ import { IOptionsService, IBufferService, IDecorationService } from 'common/serv import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; import { color, css } from 'common/Color'; +import { Terminal } from 'xterm'; /** * This CharData looks like a null character, which will forc a clear and render @@ -31,18 +32,18 @@ export class TextRenderLayer extends BaseRenderLayer { private _workCell = new CellData(); constructor( + terminal: Terminal, container: HTMLElement, zIndex: number, colors: IColorSet, alpha: boolean, - rendererId: number, bufferService: IBufferService, optionsService: IOptionsService, private readonly _characterJoinerService: ICharacterJoinerService, decorationService: IDecorationService, coreBrowserService: ICoreBrowserService ) { - super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService, decorationService, coreBrowserService); + super(terminal, container, 'text', zIndex, alpha, colors, bufferService, optionsService, decorationService, coreBrowserService); this._state = new GridCache(); } diff --git a/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts b/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts index d9349286..73d66f64 100644 --- a/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts +++ b/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts @@ -3,19 +3,19 @@ * @license MIT */ -import { generateConfig, configEquals } from './CharAtlasUtils'; +import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils'; import { BaseCharAtlas } from './BaseCharAtlas'; import { DynamicCharAtlas } from './DynamicCharAtlas'; -import { ICharAtlasConfig } from './Types'; import { IColorSet } from 'browser/Types'; -import { ITerminalOptions } from 'xterm'; +import { Terminal } from 'xterm'; +import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; interface ICharAtlasCacheEntry { atlas: BaseCharAtlas; config: ICharAtlasConfig; // N.B. This implementation potentially holds onto copies of the terminal forever, so // this may cause memory leaks. - ownedBy: number[]; + ownedBy: Terminal[]; } const charAtlasCache: ICharAtlasCacheEntry[] = []; @@ -25,19 +25,20 @@ const charAtlasCache: ICharAtlasCacheEntry[] = []; * one that is in use by another terminal. */ export function acquireCharAtlas( - options: Required, - rendererId: number, + terminal: Terminal, colors: IColorSet, + scaledCellWidth: number, + scaledCellHeight: number, scaledCharWidth: number, scaledCharHeight: number, devicePixelRatio: number ): BaseCharAtlas { - const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, options, colors, devicePixelRatio); + const newConfig = generateConfig(scaledCellWidth, scaledCellHeight, scaledCharWidth, scaledCharHeight, terminal, colors, devicePixelRatio); // Check to see if the renderer already owns this config for (let i = 0; i < charAtlasCache.length; i++) { const entry = charAtlasCache[i]; - const ownedByIndex = entry.ownedBy.indexOf(rendererId); + const ownedByIndex = entry.ownedBy.indexOf(terminal); if (ownedByIndex >= 0) { if (configEquals(entry.config, newConfig)) { return entry.atlas; @@ -58,7 +59,7 @@ export function acquireCharAtlas( const entry = charAtlasCache[i]; if (configEquals(entry.config, newConfig)) { // Add the renderer to the cache entry and return - entry.ownedBy.push(rendererId); + entry.ownedBy.push(terminal); return entry.atlas; } } @@ -69,7 +70,7 @@ export function acquireCharAtlas( newConfig ), config: newConfig, - ownedBy: [rendererId] + ownedBy: [terminal] }; charAtlasCache.push(newEntry); return newEntry.atlas; @@ -78,9 +79,9 @@ export function acquireCharAtlas( /** * Removes a terminal reference from the cache, allowing its memory to be freed. */ -export function removeTerminalFromCache(rendererId: number): void { +export function removeTerminalFromCache(terminal: Terminal): void { for (let i = 0; i < charAtlasCache.length; i++) { - const index = charAtlasCache[i].ownedBy.indexOf(rendererId); + const index = charAtlasCache[i].ownedBy.indexOf(terminal); if (index !== -1) { if (charAtlasCache[i].ownedBy.length === 1) { // Remove the cache entry if it's the only renderer diff --git a/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts b/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts deleted file mode 100644 index b0151e30..00000000 --- a/addons/xterm-addon-canvas/src/atlas/CharAtlasUtils.ts +++ /dev/null @@ -1,54 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { ICharAtlasConfig } from './Types'; -import { DEFAULT_COLOR } from 'common/buffer/Constants'; -import { IColorSet, IPartialColorSet } from 'browser/Types'; -import { ITerminalOptions } from 'xterm'; - -export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, options: Required, colors: IColorSet, devicePixelRatio: number): ICharAtlasConfig { - // null out some fields that don't matter - const clonedColors: IPartialColorSet = { - foreground: colors.foreground, - background: colors.background, - cursor: undefined, - cursorAccent: undefined, - selectionBackground: undefined, - ansi: colors.ansi.slice() - }; - return { - devicePixelRatio, - scaledCharWidth, - scaledCharHeight, - fontFamily: options.fontFamily, - fontSize: options.fontSize, - fontWeight: options.fontWeight, - fontWeightBold: options.fontWeightBold, - allowTransparency: options.allowTransparency, - colors: clonedColors - }; -} - -export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean { - for (let i = 0; i < a.colors.ansi.length; i++) { - if (a.colors.ansi[i].rgba !== b.colors.ansi[i].rgba) { - return false; - } - } - return a.devicePixelRatio === b.devicePixelRatio && - a.fontFamily === b.fontFamily && - a.fontSize === b.fontSize && - a.fontWeight === b.fontWeight && - a.fontWeightBold === b.fontWeightBold && - a.allowTransparency === b.allowTransparency && - a.scaledCharWidth === b.scaledCharWidth && - a.scaledCharHeight === b.scaledCharHeight && - a.colors.foreground === b.colors.foreground && - a.colors.background === b.colors.background; -} - -export function is256Color(colorCode: number): boolean { - return colorCode < DEFAULT_COLOR; -} diff --git a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts index 3098538d..49d384b7 100644 --- a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts +++ b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts @@ -4,25 +4,21 @@ */ import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/Constants'; -import { IGlyphIdentifier, ICharAtlasConfig } from './Types'; +import { IGlyphIdentifier } from './Types'; import { BaseCharAtlas } from './BaseCharAtlas'; import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager'; import { LRUMap } from './LRUMap'; import { isFirefox, isSafari } from 'common/Platform'; import { IColor } from 'common/Types'; import { throwIfFalsy } from 'browser/renderer/RendererUtils'; -import { color } from 'common/Color'; +import { color, NULL_COLOR } from 'common/Color'; +import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, // however, it can be useful to set this to a really tiny value, to verify that LRU eviction works. const TEXTURE_WIDTH = 1024; const TEXTURE_HEIGHT = 1024; -const TRANSPARENT_COLOR = { - css: 'rgba(0, 0, 0, 0)', - rgba: 0 -}; - // Drawing to the cache is expensive: If we have to draw more than this number of glyphs to the // cache in a single frame, give up on trying to cache anything else, and try to finish the current // frame ASAP. @@ -223,7 +219,7 @@ export class DynamicCharAtlas extends BaseCharAtlas { // The background color might have some transparency, so we need to render it as fully // transparent in the atlas. Otherwise we'd end up drawing the transparent background twice // around the anti-aliased edges of the glyph, and it would look too dark. - return TRANSPARENT_COLOR; + return NULL_COLOR; } let result: IColor; if (glyph.bg === INVERTED_DEFAULT_COLOR) { diff --git a/addons/xterm-addon-canvas/src/atlas/Types.d.ts b/addons/xterm-addon-canvas/src/atlas/Types.d.ts index d8bc54c1..cde8abec 100644 --- a/addons/xterm-addon-canvas/src/atlas/Types.d.ts +++ b/addons/xterm-addon-canvas/src/atlas/Types.d.ts @@ -15,15 +15,3 @@ export interface IGlyphIdentifier { dim: boolean; italic: boolean; } - -export interface ICharAtlasConfig { - devicePixelRatio: number; - fontSize: number; - fontFamily: string; - fontWeight: FontWeight; - fontWeightBold: FontWeight; - scaledCharWidth: number; - scaledCharHeight: number; - allowTransparency: boolean; - colors: IPartialColorSet; -} diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts index 893362c8..e70a4995 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts @@ -3,11 +3,11 @@ * @license MIT */ -import { generateConfig, configEquals } from './CharAtlasUtils'; import { WebglCharAtlas } from './WebglCharAtlas'; -import { ICharAtlasConfig } from './Types'; import { Terminal } from 'xterm'; import { IColorSet, ITerminal } from 'browser/Types'; +import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; +import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils'; interface ICharAtlasCacheEntry { atlas: WebglCharAtlas; diff --git a/addons/xterm-addon-webgl/src/atlas/Types.d.ts b/addons/xterm-addon-webgl/src/atlas/Types.d.ts index 8d2870cd..fd5b596c 100644 --- a/addons/xterm-addon-webgl/src/atlas/Types.d.ts +++ b/addons/xterm-addon-webgl/src/atlas/Types.d.ts @@ -3,9 +3,6 @@ * @license MIT */ -import { FontWeight } from 'xterm'; -import { IColorSet } from 'browser/Types'; - export interface IGlyphIdentifier { chars: string; code: number; @@ -15,22 +12,3 @@ export interface IGlyphIdentifier { dim: boolean; italic: boolean; } - -export interface ICharAtlasConfig { - customGlyphs: boolean; - devicePixelRatio: number; - letterSpacing: number; - lineHeight: number; - fontSize: number; - fontFamily: string; - fontWeight: FontWeight; - fontWeightBold: FontWeight; - scaledCellWidth: number; - scaledCellHeight: number; - scaledCharWidth: number; - scaledCharHeight: number; - allowTransparency: boolean; - drawBoldTextInBrightColors: boolean; - minimumContrastRatio: number; - colors: IColorSet; -} diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index dee1e02c..cb72f3bc 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -3,7 +3,6 @@ * @license MIT */ -import { ICharAtlasConfig } from './Types'; import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/Constants'; import { IRasterizedGlyph, IBoundingBox } from '../Types'; import { DEFAULT_COLOR, Attributes, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants'; @@ -11,12 +10,13 @@ import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'common/Types'; import { IDisposable } from 'xterm'; import { AttributeData } from 'common/buffer/AttributeData'; -import { color, rgba } from 'common/Color'; +import { color, NULL_COLOR, rgba } from 'common/Color'; import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph } from 'browser/renderer/RendererUtils'; import { IUnicodeService } from 'common/services/Services'; import { FourKeyMap } from 'common/MultiKeyMap'; import { IdleTaskQueue } from 'common/TaskQueue'; +import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; // For debugging purposes, it can be useful to set this to a really tiny value, // to verify that LRU eviction works. @@ -29,12 +29,6 @@ const TEXTURE_HEIGHT = 1024; * this prevent juggling multiple textures in the GL context. */ const TEXTURE_CAPACITY = Math.floor(TEXTURE_HEIGHT * 0.8); - -const TRANSPARENT_COLOR = { - css: 'rgba(0, 0, 0, 0)', - rgba: 0 -}; - /** * A shared object which is used to draw nothing for a particular cell. */ @@ -202,7 +196,7 @@ export class WebglCharAtlas implements IDisposable { // The background color might have some transparency, so we need to render it as fully // transparent in the atlas. Otherwise we'd end up drawing the transparent background twice // around the anti-aliased edges of the glyph, and it would look too dark. - return TRANSPARENT_COLOR; + return NULL_COLOR; } let result: IColor; diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index e1ff08d8..9b9f8e2a 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -6,10 +6,10 @@ import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; -import { is256Color } from '../atlas/CharAtlasUtils'; import { ITerminal, IColorSet, ILinkifierEvent } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; import { ICoreBrowserService } from 'browser/services/Services'; +import { is256Color } from 'browser/renderer/shared/CharAtlasUtils'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent | undefined; diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index a22a423f..0eb726a6 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -5,7 +5,7 @@ import { IColorManager, IColorSet, IColorContrastCache } from 'browser/Types'; import { ITheme } from 'common/services/Services'; -import { channels, color, css } from 'common/Color'; +import { channels, color, css, NULL_COLOR } from 'common/Color'; import { ColorContrastCache } from 'browser/ColorContrastCache'; import { ColorIndex, IColor } from 'common/Types'; @@ -124,12 +124,8 @@ export class ColorManager implements IColorManager { this.colors.selectionBackgroundOpaque = color.blend(this.colors.background, this.colors.selectionBackgroundTransparent); this.colors.selectionInactiveBackgroundTransparent = this._parseColor(theme.selectionInactiveBackground, this.colors.selectionBackgroundTransparent); this.colors.selectionInactiveBackgroundOpaque = color.blend(this.colors.background, this.colors.selectionInactiveBackgroundTransparent); - const nullColor: IColor = { - css: '', - rgba: 0 - }; - this.colors.selectionForeground = theme.selectionForeground ? this._parseColor(theme.selectionForeground, nullColor) : undefined; - if (this.colors.selectionForeground === nullColor) { + this.colors.selectionForeground = theme.selectionForeground ? this._parseColor(theme.selectionForeground, NULL_COLOR) : undefined; + if (this.colors.selectionForeground === NULL_COLOR) { this.colors.selectionForeground = undefined; } diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts b/src/browser/renderer/shared/CharAtlasUtils.ts similarity index 92% rename from addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts rename to src/browser/renderer/shared/CharAtlasUtils.ts index 83f82fa7..58c207e8 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts +++ b/src/browser/renderer/shared/CharAtlasUtils.ts @@ -5,14 +5,9 @@ import { ICharAtlasConfig } from './Types'; import { Attributes } from 'common/buffer/Constants'; -import { Terminal, FontWeight } from 'xterm'; +import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; -import { IColor } from 'common/Types'; - -const NULL_COLOR: IColor = { - css: '', - rgba: 0 -}; +import { NULL_COLOR } from 'common/Color'; export function generateConfig(scaledCellWidth: number, scaledCellHeight: number, scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: IColorSet, devicePixelRatio: number): ICharAtlasConfig { // null out some fields that don't matter @@ -70,8 +65,8 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean a.scaledCharHeight === b.scaledCharHeight && a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors && a.minimumContrastRatio === b.minimumContrastRatio && - a.colors.foreground === b.colors.foreground && - a.colors.background === b.colors.background; + a.colors.foreground.rgba === b.colors.foreground.rgba && + a.colors.background.rgba === b.colors.background.rgba; } export function is256Color(colorCode: number): boolean { diff --git a/src/browser/renderer/shared/README.md b/src/browser/renderer/shared/README.md new file mode 100644 index 00000000..58084235 --- /dev/null +++ b/src/browser/renderer/shared/README.md @@ -0,0 +1 @@ +This folder contains files that are shared between the renderer addons, but not necessarily bundled into the `xterm` module. diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts new file mode 100644 index 00000000..7559aa8f --- /dev/null +++ b/src/browser/renderer/shared/Types.d.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { FontWeight } from 'xterm'; +import { IColorSet } from 'browser/Types'; + +export interface ICharAtlasConfig { + customGlyphs: boolean; + devicePixelRatio: number; + letterSpacing: number; + lineHeight: number; + fontSize: number; + fontFamily: string; + fontWeight: FontWeight; + fontWeightBold: FontWeight; + scaledCellWidth: number; + scaledCellHeight: number; + scaledCharWidth: number; + scaledCharHeight: number; + allowTransparency: boolean; + drawBoldTextInBrightColors: boolean; + minimumContrastRatio: number; + colors: IColorSet; +} diff --git a/src/common/Color.ts b/src/common/Color.ts index 6e70671b..d009a978 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -11,6 +11,11 @@ let $g = 0; let $b = 0; let $a = 0; +export const NULL_COLOR: IColor = { + css: '#00000000', + rgba: 0 +}; + /** * Helper functions where the source type is "channels" (individual color channels as numbers). */ From c5f9eacbb11aacb51472f9b9fc9dd035c394e857 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 17:41:16 -0700 Subject: [PATCH 02/23] Move other render parts to shared folder --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 8 +-- .../xterm-addon-canvas/src/CanvasRenderer.ts | 4 +- .../src/CursorRenderLayer.ts | 2 +- .../xterm-addon-canvas/src/LinkRenderLayer.ts | 4 +- .../src/SelectionRenderLayer.ts | 2 +- .../xterm-addon-canvas/src/TextRenderLayer.ts | 2 +- .../src/atlas/DynamicCharAtlas.ts | 4 +- .../src/RectangleRenderer.ts | 4 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 4 +- .../src/atlas/WebglCharAtlas.ts | 6 +-- .../src/renderLayer/BaseRenderLayer.ts | 4 +- .../src/renderLayer/LinkRenderLayer.ts | 4 +- src/browser/Terminal.ts | 2 +- src/browser/TestUtils.test.ts | 2 +- src/browser/renderer/dom/DomRenderer.ts | 4 +- .../renderer/dom/DomRendererRowFactory.ts | 4 +- .../renderer/{ => shared}/Constants.ts | 0 .../renderer/{ => shared}/CustomGlyphs.ts | 2 +- .../{ => shared}/DevicePixelObserver.ts | 0 .../renderer/{ => shared}/RendererUtils.ts | 0 src/browser/renderer/shared/Types.d.ts | 50 +++++++++++++++++++ src/browser/services/RenderService.ts | 2 +- 22 files changed, 82 insertions(+), 32 deletions(-) rename src/browser/renderer/{ => shared}/Constants.ts (100%) rename src/browser/renderer/{ => shared}/CustomGlyphs.ts (99%) rename src/browser/renderer/{ => shared}/DevicePixelObserver.ts (100%) rename src/browser/renderer/{ => shared}/RendererUtils.ts (100%) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 641a5ca4..0a977d9c 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -3,12 +3,12 @@ * @license MIT */ -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { IRenderLayer } from './Types'; import { ICellData, IColor } from 'common/Types'; import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; import { IGlyphIdentifier } from './atlas/Types'; -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/Constants'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { BaseCharAtlas } from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { AttributeData } from 'common/buffer/AttributeData'; @@ -16,10 +16,10 @@ import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ICoreBrowserService } from 'browser/services/Services'; -import { excludeFromContrastRatioDemands, throwIfFalsy } from 'browser/renderer/RendererUtils'; +import { excludeFromContrastRatioDemands, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; -import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; +import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { Terminal } from 'xterm'; export abstract class BaseRenderLayer implements IRenderLayer { diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index 77c2177e..0a00f6f8 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -6,7 +6,7 @@ import { TextRenderLayer } from './TextRenderLayer'; import { SelectionRenderLayer } from './SelectionRenderLayer'; import { CursorRenderLayer } from './CursorRenderLayer'; -import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { IRenderLayer } from './Types'; import { LinkRenderLayer } from './LinkRenderLayer'; import { Disposable } from 'common/Lifecycle'; @@ -15,7 +15,7 @@ import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService } from ' import { IBufferService, IOptionsService, IDecorationService, ICoreService } from 'common/services/Services'; import { removeTerminalFromCache } from './atlas/CharAtlasCache'; import { EventEmitter, IEvent } from 'common/EventEmitter'; -import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver'; +import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver'; import { Terminal } from 'xterm'; let nextRendererId = 1; diff --git a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts index bddac195..78dc5432 100644 --- a/addons/xterm-addon-canvas/src/CursorRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/CursorRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; diff --git a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts index 5e1e7062..db622b3f 100644 --- a/addons/xterm-addon-canvas/src/LinkRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/LinkRenderLayer.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; import { ICoreBrowserService } from 'browser/services/Services'; import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; diff --git a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts index dc506bad..6a803342 100644 --- a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { IColorSet } from 'browser/Types'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index eb004c9a..b20765d8 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { CharData, ICellData } from 'common/Types'; import { GridCache } from './GridCache'; import { BaseRenderLayer } from './BaseRenderLayer'; diff --git a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts index 49d384b7..c06842a7 100644 --- a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts +++ b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts @@ -3,14 +3,14 @@ * @license MIT */ -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/Constants'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { IGlyphIdentifier } from './Types'; import { BaseCharAtlas } from './BaseCharAtlas'; import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager'; import { LRUMap } from './LRUMap'; import { isFirefox, isSafari } from 'common/Platform'; import { IColor } from 'common/Types'; -import { throwIfFalsy } from 'browser/renderer/RendererUtils'; +import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { color, NULL_COLOR } from 'common/Color'; import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index dccc7f6a..ea7c3836 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -9,10 +9,10 @@ import { Attributes, BgFlags, FgFlags } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColor } from 'common/Types'; import { IColorSet } from 'browser/Types'; -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { Disposable, toDisposable } from 'common/Lifecycle'; -import { DIM_OPACITY } from 'browser/renderer/Constants'; +import { DIM_OPACITY } from 'browser/renderer/shared/Constants'; const enum VertexAttribLocations { POSITION = 0, diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 58fbb6ed..34f247eb 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -15,8 +15,8 @@ import { Disposable } from 'common/Lifecycle'; import { Attributes, BgFlags, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; 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 { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; +import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver'; import { ITerminal, IColorSet } from 'browser/Types'; import { EventEmitter } from 'common/EventEmitter'; import { CellData } from 'common/buffer/CellData'; diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index cb72f3bc..b9a4088d 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/Constants'; +import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { IRasterizedGlyph, IBoundingBox } from '../Types'; import { DEFAULT_COLOR, Attributes, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants'; import { throwIfFalsy } from '../WebglUtils'; @@ -11,8 +11,8 @@ import { IColor } from 'common/Types'; import { IDisposable } from 'xterm'; import { AttributeData } from 'common/buffer/AttributeData'; import { color, NULL_COLOR, rgba } from 'common/Color'; -import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs'; -import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph } from 'browser/renderer/RendererUtils'; +import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; +import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph } from 'browser/renderer/shared/RendererUtils'; import { IUnicodeService } from 'common/services/Services'; import { FourKeyMap } from 'common/MultiKeyMap'; import { IdleTaskQueue } from 'common/TaskQueue'; diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index e9c0cf41..2cba49dd 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -7,9 +7,9 @@ import { IRenderLayer } from './Types'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; -import { TEXT_BASELINE } from 'browser/renderer/Constants'; +import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { ICoreBrowserService } from 'browser/services/Services'; -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { CellData } from 'common/buffer/CellData'; import { WebglCharAtlas } from 'atlas/WebglCharAtlas'; import { throwIfFalsy } from '../WebglUtils'; diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index 9b9f8e2a..dbf2fdcf 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -5,9 +5,9 @@ import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; import { ITerminal, IColorSet, ILinkifierEvent } from 'browser/Types'; -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { ICoreBrowserService } from 'browser/services/Services'; import { is256Color } from 'browser/renderer/shared/CharAtlasUtils'; diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 9c10fc16..be1f6777 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -22,7 +22,7 @@ */ import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, IViewport, ILinkifier2, CharacterJoinerHandler, IBufferRange } from 'browser/Types'; -import { IRenderer } from 'browser/renderer/Types'; +import { IRenderer } from 'browser/renderer/shared/Types'; import { CompositionHelper } from 'browser/input/CompositionHelper'; import { Viewport } from 'browser/Viewport'; import { rightClickHandler, moveTextAreaUnderMouseCursor, handlePasteEvent, copyHandler, paste } from 'browser/Clipboard'; diff --git a/src/browser/TestUtils.test.ts b/src/browser/TestUtils.test.ts index 0b5e00c1..1717c204 100644 --- a/src/browser/TestUtils.test.ts +++ b/src/browser/TestUtils.test.ts @@ -6,7 +6,7 @@ import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services'; -import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IBufferRange } from 'browser/Types'; import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types'; import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, ITerminalOptions } from 'common/Types'; diff --git a/src/browser/renderer/dom/DomRenderer.ts b/src/browser/renderer/dom/DomRenderer.ts index 223e2470..77e977ad 100644 --- a/src/browser/renderer/dom/DomRenderer.ts +++ b/src/browser/renderer/dom/DomRenderer.ts @@ -3,9 +3,9 @@ * @license MIT */ -import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { IRenderer, IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { BOLD_CLASS, ITALIC_CLASS, CURSOR_CLASS, CURSOR_STYLE_BLOCK_CLASS, CURSOR_BLINK_CLASS, CURSOR_STYLE_BAR_CLASS, CURSOR_STYLE_UNDERLINE_CLASS, DomRendererRowFactory } from 'browser/renderer/dom/DomRendererRowFactory'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; import { Disposable } from 'common/Lifecycle'; import { IColorSet, ILinkifierEvent, ILinkifier2 } from 'browser/Types'; import { ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index b6ee7bf1..bce72ec3 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -4,7 +4,7 @@ */ import { IBufferLine, ICellData, IColor } from 'common/Types'; -import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/Constants'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants'; import { NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Attributes } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; @@ -12,7 +12,7 @@ import { color, rgba } from 'common/Color'; import { IColorSet } from 'browser/Types'; import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; -import { excludeFromContrastRatioDemands } from 'browser/renderer/RendererUtils'; +import { excludeFromContrastRatioDemands } from 'browser/renderer/shared/RendererUtils'; import { AttributeData } from 'common/buffer/AttributeData'; export const BOLD_CLASS = 'xterm-bold'; diff --git a/src/browser/renderer/Constants.ts b/src/browser/renderer/shared/Constants.ts similarity index 100% rename from src/browser/renderer/Constants.ts rename to src/browser/renderer/shared/Constants.ts diff --git a/src/browser/renderer/CustomGlyphs.ts b/src/browser/renderer/shared/CustomGlyphs.ts similarity index 99% rename from src/browser/renderer/CustomGlyphs.ts rename to src/browser/renderer/shared/CustomGlyphs.ts index 32256cf4..b8725685 100644 --- a/src/browser/renderer/CustomGlyphs.ts +++ b/src/browser/renderer/shared/CustomGlyphs.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { throwIfFalsy } from 'browser/renderer/RendererUtils'; +import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; interface IBlockVector { x: number; diff --git a/src/browser/renderer/DevicePixelObserver.ts b/src/browser/renderer/shared/DevicePixelObserver.ts similarity index 100% rename from src/browser/renderer/DevicePixelObserver.ts rename to src/browser/renderer/shared/DevicePixelObserver.ts diff --git a/src/browser/renderer/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts similarity index 100% rename from src/browser/renderer/RendererUtils.ts rename to src/browser/renderer/shared/RendererUtils.ts diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 7559aa8f..ccfcae43 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -5,6 +5,8 @@ import { FontWeight } from 'xterm'; import { IColorSet } from 'browser/Types'; +import { IDisposable } from 'common/Types'; +import { IEvent } from 'common/EventEmitter'; export interface ICharAtlasConfig { customGlyphs: boolean; @@ -24,3 +26,51 @@ export interface ICharAtlasConfig { minimumContrastRatio: number; colors: IColorSet; } + +export interface IRenderDimensions { + scaledCharWidth: number; + scaledCharHeight: number; + scaledCellWidth: number; + scaledCellHeight: number; + scaledCharLeft: number; + scaledCharTop: number; + scaledCanvasWidth: number; + scaledCanvasHeight: number; + canvasWidth: number; + canvasHeight: number; + actualCellWidth: number; + actualCellHeight: number; +} + +export interface IRequestRedrawEvent { + start: number; + end: number; +} + +/** + * Note that IRenderer implementations should emit the refresh event after + * rendering rows to the screen. + */ +export interface IRenderer extends IDisposable { + readonly dimensions: IRenderDimensions; + + /** + * Fires when the renderer is requesting to be redrawn on the next animation + * frame but is _not_ a result of content changing (eg. selection changes). + */ + readonly onRequestRedraw: IEvent; + + dispose(): void; + setColors(colors: IColorSet): void; + onDevicePixelRatioChange(): void; + onResize(cols: number, rows: number): void; + onCharSizeChanged(): void; + onBlur(): void; + onFocus(): void; + onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; + onCursorMove(): void; + onOptionsChanged(): void; + clear(): void; + renderRows(start: number, end: number): void; + clearTextureAtlas?(): void; +} diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 66dbb7aa..c81357cd 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IRenderer, IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderer, IRenderDimensions } from 'browser/renderer/shared/Types'; import { RenderDebouncer } from 'browser/RenderDebouncer'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { Disposable } from 'common/Lifecycle'; From d669c6e9d62f6661c84cd2c6c9806c084f1b4e29 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sat, 1 Oct 2022 18:03:43 -0700 Subject: [PATCH 03/23] Fix remaining references --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 2 +- .../src/renderLayer/CursorRenderLayer.ts | 2 +- .../src/renderLayer/Types.ts | 2 +- src/browser/Viewport.ts | 2 +- src/browser/renderer/Types.d.ts | 56 ------------------- src/browser/services/Services.ts | 2 +- test/api/InputHandler.api.ts | 2 +- 7 files changed, 6 insertions(+), 62 deletions(-) delete mode 100644 src/browser/renderer/Types.d.ts diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 689b847a..b19c8cb1 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -10,7 +10,7 @@ import { fill } from 'common/TypedArrayUtils'; import { NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { Disposable, toDisposable } from 'common/Lifecycle'; interface IVertices { diff --git a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index 2b274516..522f6fb6 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -8,7 +8,7 @@ import { BaseRenderLayer } from './BaseRenderLayer'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { IColorSet } from 'browser/Types'; -import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/Types'; +import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; import { IEventEmitter } from 'common/EventEmitter'; import { ICoreBrowserService } from 'browser/services/Services'; import { ICoreService } from 'common/services/Services'; diff --git a/addons/xterm-addon-webgl/src/renderLayer/Types.ts b/addons/xterm-addon-webgl/src/renderLayer/Types.ts index 70acff34..993304a2 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/Types.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/Types.ts @@ -5,7 +5,7 @@ import { IDisposable, Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; export interface IRenderLayer extends IDisposable { /** diff --git a/src/browser/Viewport.ts b/src/browser/Viewport.ts index f95e0bb4..45373353 100644 --- a/src/browser/Viewport.ts +++ b/src/browser/Viewport.ts @@ -9,7 +9,7 @@ import { IColorSet, IViewport } from 'browser/Types'; import { ICharSizeService, ICoreBrowserService, IRenderService } from 'browser/services/Services'; import { IBufferService, IOptionsService } from 'common/services/Services'; import { IBuffer } from 'common/buffer/Types'; -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; const FALLBACK_SCROLL_BAR_WIDTH = 15; diff --git a/src/browser/renderer/Types.d.ts b/src/browser/renderer/Types.d.ts deleted file mode 100644 index cb1a85b4..00000000 --- a/src/browser/renderer/Types.d.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2019 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IDisposable } from 'common/Types'; -import { IColorSet } from 'browser/Types'; -import { IEvent } from 'common/EventEmitter'; - -export interface IRenderDimensions { - scaledCharWidth: number; - scaledCharHeight: number; - scaledCellWidth: number; - scaledCellHeight: number; - scaledCharLeft: number; - scaledCharTop: number; - scaledCanvasWidth: number; - scaledCanvasHeight: number; - canvasWidth: number; - canvasHeight: number; - actualCellWidth: number; - actualCellHeight: number; -} - -export interface IRequestRedrawEvent { - start: number; - end: number; -} - -/** - * Note that IRenderer implementations should emit the refresh event after - * rendering rows to the screen. - */ -export interface IRenderer extends IDisposable { - readonly dimensions: IRenderDimensions; - - /** - * Fires when the renderer is requesting to be redrawn on the next animation - * frame but is _not_ a result of content changing (eg. selection changes). - */ - readonly onRequestRedraw: IEvent; - - dispose(): void; - setColors(colors: IColorSet): void; - onDevicePixelRatioChange(): void; - onResize(cols: number, rows: number): void; - onCharSizeChanged(): void; - onBlur(): void; - onFocus(): void; - onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void; - onCursorMove(): void; - onOptionsChanged(): void; - clear(): void; - renderRows(start: number, end: number): void; - clearTextureAtlas?(): void; -} diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index ab91f8a3..9eea5c76 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -4,7 +4,7 @@ */ import { IEvent } from 'common/EventEmitter'; -import { IRenderDimensions, IRenderer } from 'browser/renderer/Types'; +import { IRenderDimensions, IRenderer } from 'browser/renderer/shared/Types'; import { IColorSet } from 'browser/Types'; import { ISelectionRedrawRequestEvent as ISelectionRequestRedrawEvent, ISelectionRequestScrollLinesEvent } from 'browser/selection/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; diff --git a/test/api/InputHandler.api.ts b/test/api/InputHandler.api.ts index c8cbbf66..8192bfed 100644 --- a/test/api/InputHandler.api.ts +++ b/test/api/InputHandler.api.ts @@ -6,7 +6,7 @@ import { assert } from 'chai'; import { pollFor, openTerminal, getBrowserType, launchBrowser, writeSync } from './TestUtils'; import { Browser, Page } from 'playwright'; -import { IRenderDimensions } from 'browser/renderer/Types'; +import { IRenderDimensions } from 'browser/renderer/shared/Types'; const APP = 'http://127.0.0.1:3001/test'; From af6f03600f9e4f7db56dddd6ec4ae856a44ba364 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 07:10:37 -0700 Subject: [PATCH 04/23] Show canvas texture atlas on demo --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 1 + addons/xterm-addon-canvas/src/CanvasAddon.ts | 9 +++++++++ addons/xterm-addon-canvas/src/CanvasRenderer.ts | 12 +++++++----- addons/xterm-addon-canvas/src/Types.d.ts | 1 + .../xterm-addon-canvas/src/atlas/BaseCharAtlas.ts | 1 + .../src/atlas/DynamicCharAtlas.ts | 8 ++++++++ .../typings/xterm-addon-canvas.d.ts | 5 +++++ demo/client.ts | 14 ++++++++++++-- 8 files changed, 44 insertions(+), 7 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 0a977d9c..7241347f 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -52,6 +52,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { }; public get canvas(): HTMLCanvasElement { return this._canvas; } + public get cacheCanvas(): HTMLCanvasElement { return this._charAtlas?.cacheCanvas!; } constructor( private readonly _terminal: Terminal, diff --git a/addons/xterm-addon-canvas/src/CanvasAddon.ts b/addons/xterm-addon-canvas/src/CanvasAddon.ts index 372852b0..10b8cdbb 100644 --- a/addons/xterm-addon-canvas/src/CanvasAddon.ts +++ b/addons/xterm-addon-canvas/src/CanvasAddon.ts @@ -8,11 +8,15 @@ import { IColorSet } from 'browser/Types'; import { CanvasRenderer } from './CanvasRenderer'; import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ITerminalAddon, Terminal } from 'xterm'; +import { EventEmitter, forwardEvent } from 'common/EventEmitter'; export class CanvasAddon implements ITerminalAddon { private _terminal?: Terminal; private _renderer?: CanvasRenderer; + private readonly _onChangeTextureAtlas = new EventEmitter(); + public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; + public activate(terminal: Terminal): void { if (!terminal.element) { throw new Error('Cannot activate CanvasAddon before Terminal.open'); @@ -30,6 +34,7 @@ export class CanvasAddon implements ITerminalAddon { const screenElement: HTMLElement = (terminal as any)._core.screenElement; const linkifier = (terminal as any)._core.linkifier2; this._renderer = new CanvasRenderer(terminal, colors, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService); + forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas); renderService.setRenderer(this._renderer); renderService.onResize(bufferService.cols, bufferService.rows); } @@ -44,4 +49,8 @@ export class CanvasAddon implements ITerminalAddon { this._renderer?.dispose(); this._renderer = undefined; } + + public get textureAtlas(): HTMLCanvasElement | undefined { + return this._renderer?.textureAtlas; + } } diff --git a/addons/xterm-addon-canvas/src/CanvasRenderer.ts b/addons/xterm-addon-canvas/src/CanvasRenderer.ts index 0a00f6f8..14f7faf6 100644 --- a/addons/xterm-addon-canvas/src/CanvasRenderer.ts +++ b/addons/xterm-addon-canvas/src/CanvasRenderer.ts @@ -14,15 +14,11 @@ import { IColorSet, ILinkifier2 } from 'browser/Types'; import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService } from 'browser/services/Services'; import { IBufferService, IOptionsService, IDecorationService, ICoreService } from 'common/services/Services'; import { removeTerminalFromCache } from './atlas/CharAtlasCache'; -import { EventEmitter, IEvent } from 'common/EventEmitter'; +import { EventEmitter } from 'common/EventEmitter'; import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver'; import { Terminal } from 'xterm'; -let nextRendererId = 1; - export class CanvasRenderer extends Disposable implements IRenderer { - private _id = nextRendererId++; - private _renderLayers: IRenderLayer[]; private _devicePixelRatio: number; @@ -30,6 +26,8 @@ export class CanvasRenderer extends Disposable implements IRenderer { private readonly _onRequestRedraw = new EventEmitter(); public readonly onRequestRedraw = this._onRequestRedraw.event; + private readonly _onChangeTextureAtlas = new EventEmitter(); + public readonly onChangeTextureAtlas = this._onChangeTextureAtlas.event; constructor( private readonly _terminal: Terminal, @@ -82,6 +80,10 @@ export class CanvasRenderer extends Disposable implements IRenderer { removeTerminalFromCache(this._terminal); } + public get textureAtlas(): HTMLCanvasElement | undefined { + return this._renderLayers[0].cacheCanvas; + } + public onDevicePixelRatioChange(): void { // If the device pixel ratio changed, the char atlas needs to be regenerated // and the terminal needs to refreshed diff --git a/addons/xterm-addon-canvas/src/Types.d.ts b/addons/xterm-addon-canvas/src/Types.d.ts index dda6052a..0527eae9 100644 --- a/addons/xterm-addon-canvas/src/Types.d.ts +++ b/addons/xterm-addon-canvas/src/Types.d.ts @@ -58,6 +58,7 @@ export interface IRenderer extends IDisposable { export interface IRenderLayer extends IDisposable { readonly canvas: HTMLCanvasElement; + readonly cacheCanvas: HTMLCanvasElement; /** * Called when the terminal loses focus. diff --git a/addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts index 03cf0285..466e2ec8 100644 --- a/addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts +++ b/addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts @@ -8,6 +8,7 @@ import { IDisposable } from 'common/Types'; export abstract class BaseCharAtlas implements IDisposable { private _didWarmUp: boolean = false; + public abstract readonly cacheCanvas: HTMLCanvasElement; public dispose(): void { } diff --git a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts index c06842a7..e52e3a40 100644 --- a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts +++ b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts @@ -111,6 +111,10 @@ export class DynamicCharAtlas extends BaseCharAtlas { } } + public override get cacheCanvas(): HTMLCanvasElement { + return this._cacheCanvas!; + } + public beginFrame(): void { this._drawToCacheCount = 0; } @@ -374,6 +378,10 @@ export class NoneCharAtlas extends BaseCharAtlas { super(); } + public override get cacheCanvas(): HTMLCanvasElement { + return null!; + } + public draw( ctx: CanvasRenderingContext2D, glyph: IGlyphIdentifier, diff --git a/addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts b/addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts index 7bbbd63d..6a2b98d4 100644 --- a/addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts +++ b/addons/xterm-addon-canvas/typings/xterm-addon-canvas.d.ts @@ -12,6 +12,11 @@ declare module 'xterm-addon-canvas' { export class CanvasAddon implements ITerminalAddon { public textureAtlas?: HTMLCanvasElement; + /** + * An event that is fired when the texture atlas of the renderer changes. + */ + public readonly onChangeTextureAtlas: IEvent; + constructor(); /** diff --git a/demo/client.ts b/demo/client.ts index 00b10b57..a1e3c97c 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -545,7 +545,15 @@ function initAddons(term: TerminalType): void { try { term.loadAddon(addon.instance); if (name === 'webgl') { - (addon.instance as WebglAddon).onChangeTextureAtlas(e => addTextureAtlas(e)); + setTimeout(() => { + addTextureAtlas(addons.webgl.instance.textureAtlas); + addons.webgl.instance.onChangeTextureAtlas(e => addTextureAtlas(e)); + }, 0); + } else if (name === 'canvas') { + setTimeout(() => { + addTextureAtlas(addons.canvas.instance.textureAtlas); + addons.canvas.instance.onChangeTextureAtlas(e => addTextureAtlas(e)); + }, 0); } else if (name === 'unicode11') { term.unicode.activeVersion = '11'; } else if (name === 'search') { @@ -559,7 +567,9 @@ function initAddons(term: TerminalType): void { } } else { if (name === 'webgl') { - (addon.instance as WebglAddon).textureAtlas.remove(); + addons.webgl.instance.textureAtlas.remove(); + } else if (name === 'canvas') { + addons.canvas.instance.textureAtlas.remove(); } else if (name === 'unicode11') { term.unicode.activeVersion = '6'; } From 53d029789802d0669ba8436d23e0b5925a50e68a Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 07:15:12 -0700 Subject: [PATCH 05/23] Lint client.ts --- .eslintrc.json | 1 + demo/client.ts | 124 +++++++++++++++++++++++++------------------------ 2 files changed, 64 insertions(+), 61 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 9510fc6b..aa6e6417 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,6 +7,7 @@ "parser": "@typescript-eslint/parser", "parserOptions": { "project": [ + "demo/tsconfig.json", "src/browser/tsconfig.json", "src/common/tsconfig.json", "src/headless/tsconfig.json", diff --git a/demo/client.ts b/demo/client.ts index a1e3c97c..8bff4690 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -36,15 +36,15 @@ import { Terminal as TerminalType, ITerminalOptions } from 'xterm'; export interface IWindowWithTerminal extends Window { term: TerminalType; - Terminal?: typeof TerminalType; - AttachAddon?: typeof AttachAddon; - FitAddon?: typeof FitAddon; - SearchAddon?: typeof SearchAddon; - SerializeAddon?: typeof SerializeAddon; - WebLinksAddon?: typeof WebLinksAddon; - WebglAddon?: typeof WebglAddon; - Unicode11Addon?: typeof Unicode11Addon; - LigaturesAddon?: typeof LigaturesAddon; + Terminal?: typeof TerminalType; // eslint-disable-line @typescript-eslint/naming-convention + AttachAddon?: typeof AttachAddon; // eslint-disable-line @typescript-eslint/naming-convention + FitAddon?: typeof FitAddon; // eslint-disable-line @typescript-eslint/naming-convention + SearchAddon?: typeof SearchAddon; // eslint-disable-line @typescript-eslint/naming-convention + SerializeAddon?: typeof SerializeAddon; // eslint-disable-line @typescript-eslint/naming-convention + WebLinksAddon?: typeof WebLinksAddon; // eslint-disable-line @typescript-eslint/naming-convention + WebglAddon?: typeof WebglAddon; // eslint-disable-line @typescript-eslint/naming-convention + Unicode11Addon?: typeof Unicode11Addon; // eslint-disable-line @typescript-eslint/naming-convention + LigaturesAddon?: typeof LigaturesAddon; // eslint-disable-line @typescript-eslint/naming-convention } declare let window: IWindowWithTerminal; @@ -59,27 +59,29 @@ type AddonType = 'attach' | 'canvas' | 'fit' | 'search' | 'serialize' | 'unicode interface IDemoAddon { name: T; canChange: boolean; - ctor: + ctor: ( T extends 'attach' ? typeof AttachAddon : - T extends 'canvas' ? typeof CanvasAddon : - T extends 'fit' ? typeof FitAddon : - T extends 'search' ? typeof SearchAddon : - T extends 'serialize' ? typeof SerializeAddon : - T extends 'web-links' ? typeof WebLinksAddon : - T extends 'unicode11' ? typeof Unicode11Addon : - T extends 'ligatures' ? typeof LigaturesAddon : - typeof WebglAddon; - instance?: + T extends 'canvas' ? typeof CanvasAddon : + T extends 'fit' ? typeof FitAddon : + T extends 'search' ? typeof SearchAddon : + T extends 'serialize' ? typeof SerializeAddon : + T extends 'web-links' ? typeof WebLinksAddon : + T extends 'unicode11' ? typeof Unicode11Addon : + T extends 'ligatures' ? typeof LigaturesAddon : + typeof WebglAddon + ); + instance?: ( T extends 'attach' ? AttachAddon : - T extends 'canvas' ? CanvasAddon : - T extends 'fit' ? FitAddon : - T extends 'search' ? SearchAddon : - T extends 'serialize' ? SerializeAddon : - T extends 'web-links' ? WebLinksAddon : - T extends 'webgl' ? WebglAddon : - T extends 'unicode11' ? typeof Unicode11Addon : - T extends 'ligatures' ? typeof LigaturesAddon : - never; + T extends 'canvas' ? CanvasAddon : + T extends 'fit' ? FitAddon : + T extends 'search' ? SearchAddon : + T extends 'serialize' ? SerializeAddon : + T extends 'web-links' ? WebLinksAddon : + T extends 'webgl' ? WebglAddon : + T extends 'unicode11' ? typeof Unicode11Addon : + T extends 'ligatures' ? typeof LigaturesAddon : + never + ); } const addons: { [T in AddonType]: IDemoAddon } = { @@ -96,12 +98,12 @@ const addons: { [T in AddonType]: IDemoAddon } = { let terminalContainer = document.getElementById('terminal-container'); const actionElements = { - find: document.querySelector('#find'), - findNext: document.querySelector('#find-next'), - findPrevious: document.querySelector('#find-previous'), + find: document.querySelector('#find') as HTMLInputElement, + findNext: document.querySelector('#find-next') as HTMLInputElement, + findPrevious: document.querySelector('#find-previous') as HTMLInputElement, findResults: document.querySelector('#find-results') }; -const paddingElement = document.getElementById('padding'); +const paddingElement = document.getElementById('padding') as HTMLInputElement; const xtermjsTheme = { foreground: '#F8F8F8', @@ -146,7 +148,7 @@ function getSearchOptions(e: KeyboardEvent): ISearchOptions { }; } -const disposeRecreateButtonHandler = () => { +const disposeRecreateButtonHandler: () => void = () => { // If the terminal exists dispose of it, otherwise recreate it if (term) { term.dispose(); @@ -169,7 +171,7 @@ const disposeRecreateButtonHandler = () => { } }; -const createNewWindowButtonHandler = () => { +const createNewWindowButtonHandler: () => void = () => { if (term) { disposeRecreateButtonHandler(); } @@ -196,7 +198,7 @@ const createNewWindowButtonHandler = () => { } }); } -} +}; if (document.location.pathname === '/test') { window.Terminal = Terminal; @@ -300,8 +302,8 @@ function createTerminal(): void { setTimeout(() => { initOptions(term); // TODO: Clean this up, opt-cols/rows doesn't exist anymore - (document.getElementById(`opt-cols`)).value = term.cols; - (document.getElementById(`opt-rows`)).value = term.rows; + (document.getElementById(`opt-cols`) as HTMLInputElement).value = term.cols; + (document.getElementById(`opt-rows`) as HTMLInputElement).value = term.rows; paddingElement.value = '0'; // Set terminal size again to set the specific dimensions on the demo @@ -427,14 +429,14 @@ function initOptions(term: TerminalType): void { // Attach listeners booleanOptions.forEach(o => { - const input = document.getElementById(`opt-${o}`); + const input = document.getElementById(`opt-${o}`) as HTMLInputElement; addDomListener(input, 'change', () => { console.log('change', o, input.checked); term.options[o] = input.checked; }); }); numberOptions.forEach(o => { - const input = document.getElementById(`opt-${o}`); + const input = document.getElementById(`opt-${o}`) as HTMLInputElement; addDomListener(input, 'change', () => { console.log('change', o, input.value); if (o === 'rows') { @@ -456,7 +458,7 @@ function initOptions(term: TerminalType): void { }); }); Object.keys(stringOptions).forEach(o => { - const input = document.getElementById(`opt-${o}`); + const input = document.getElementById(`opt-${o}`) as HTMLInputElement; addDomListener(input, 'change', () => { console.log('change', o, input.value); let value: any = input.value; @@ -489,7 +491,7 @@ function initOptions(term: TerminalType): void { magenta: '#b168df', red: '#da6771', white: '#efefef', - yellow: '#fff099', + yellow: '#fff099' }; break; case 'light': @@ -513,7 +515,7 @@ function initOptions(term: TerminalType): void { magenta: '#bc05bc', red: '#cd3131', white: '#555555', - yellow: '#949800', + yellow: '#949800' }; break; } @@ -594,7 +596,7 @@ function initAddons(term: TerminalType): void { container.appendChild(fragment); } -function updateFindResults(e: { resultIndex: number, resultCount: number } | undefined) { +function updateFindResults(e: { resultIndex: number, resultCount: number } | undefined): void { let content: string; if (e === undefined) { content = 'undefined'; @@ -610,8 +612,8 @@ function addDomListener(element: HTMLElement, type: string, handler: (...args: a } function updateTerminalSize(): void { - const cols = parseInt((document.getElementById(`opt-cols`)).value, 10); - const rows = parseInt((document.getElementById(`opt-rows`)).value, 10); + const cols = parseInt((document.getElementById(`opt-cols`) as HTMLInputElement).value, 10); + const rows = parseInt((document.getElementById(`opt-rows`) as HTMLInputElement).value, 10); const width = (cols * term._core._renderService.dimensions.actualCellWidth + term._core.viewport.scrollBarWidth).toString() + 'px'; const height = (rows * term._core._renderService.dimensions.actualCellHeight).toString() + 'px'; terminalContainer.style.width = width; @@ -635,21 +637,21 @@ function htmlSerializeButtonHandler(): void { document.getElementById('htmlserialize-output').innerText = output; // Deprecated, but the most supported for now. - function listener(e: any) { - e.clipboardData.setData("text/html", output); + function listener(e: any): void { + e.clipboardData.setData('text/html', output); e.preventDefault(); } - document.addEventListener("copy", listener); - document.execCommand("copy"); - document.removeEventListener("copy", listener); - document.getElementById("htmlserialize-output-result").innerText = "Copied to clipboard"; + document.addEventListener('copy', listener); + document.execCommand('copy'); + document.removeEventListener('copy', listener); + document.getElementById('htmlserialize-output-result').innerText = 'Copied to clipboard'; } -function addTextureAtlas(e: HTMLCanvasElement) { +function addTextureAtlas(e: HTMLCanvasElement): void { document.querySelector('#texture-atlas').replaceChildren(e); } -function writeCustomGlyphHandler() { +function writeCustomGlyphHandler(): void { term.write('\n\r'); term.write('\n\r'); term.write('Box styles: ┎┰┒┍┯┑╓╥╖╒╤╕ ┏┳┓┌┲┓┌┬┐┏┱┐\n\r'); @@ -694,7 +696,7 @@ function writeCustomGlyphHandler() { window.scrollTo(0, 0); } -function loadTest() { +function loadTest(): void { const rendererName = addons.webgl.instance ? 'webgl' : !!addons.canvas.instance ? 'canvas' : 'dom'; const testData = []; let byteCount = 0; @@ -727,7 +729,7 @@ function loadTest() { }); } -function powerlineSymbolTest() { +function powerlineSymbolTest(): void { function s(char: string): string { return `${char} \x1b[7m${char}\x1b[0m `; } @@ -801,7 +803,7 @@ function powerlineSymbolTest() { term.writeln('nf-mdi-github_face (\\uFbd9) \ufbd9'); } -function underlineTest() { +function underlineTest(): void { function u(style: number): string { return `\x1b[4:${style}m`; } @@ -811,7 +813,7 @@ function underlineTest() { term.write('\n\n\r'); term.writeln('Underline styles:'); term.writeln(''); - function showSequence(id: number, name: string) { + function showSequence(id: number, name: string): string { let alphabet = ''; for (let i = 97; i < 123; i++) { alphabet += String.fromCharCode(i); @@ -858,7 +860,7 @@ function underlineTest() { term.write('\x1b[0m\n\r'); } -function ansiColorsTest() { +function ansiColorsTest(): void { term.writeln(`\x1b[0m\n\n\rStandard colors: Bright colors:`); for (let i = 0; i < 16; i++) { term.write(`\x1b[48;5;${i}m ${i.toString().padEnd(2, ' ').padStart(3, ' ')} \x1b[0m`); @@ -883,7 +885,7 @@ function ansiColorsTest() { } } -function addAnsiHyperlink() { +function addAnsiHyperlink(): void { term.write('\n\n\r'); term.writeln(`Regular link with no id:`); term.writeln('\x1b]8;;https://github.com\x07GitHub\x1b]8;;\x07'); @@ -903,7 +905,7 @@ function addAnsiHyperlink() { term.write('\x1b[3A\x1b[1C\x1b]8;;https://xtermjs.org\x07xter\x1b[B\x1b[4Dm.js\x1b]8;;\x07\x1b[2B\x1b[5D'); } -function addDecoration() { +function addDecoration(): void { term.options['overviewRulerWidth'] = 15; const marker = term.registerMarker(1); const decoration = term.registerDecoration({ @@ -918,7 +920,7 @@ function addDecoration() { }); } -function addOverviewRuler() { +function addOverviewRuler(): void { term.options['overviewRulerWidth'] = 15; term.registerDecoration({ marker: term.registerMarker(1), overviewRulerOptions: { color: '#ef2929' } }); term.registerDecoration({ marker: term.registerMarker(3), overviewRulerOptions: { color: '#8ae234' } }); From 8752c4fa8ecd9628e58a35985afedbe90e78a03f Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 07:19:57 -0700 Subject: [PATCH 06/23] Remove canvas char atlas base class --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 4 +- .../src/atlas/BaseCharAtlas.ts | 59 ------------------- .../src/atlas/CharAtlasCache.ts | 5 +- .../src/atlas/DynamicCharAtlas.ts | 44 ++++++-------- 4 files changed, 22 insertions(+), 90 deletions(-) delete mode 100644 addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 7241347f..05200861 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -9,7 +9,6 @@ import { ICellData, IColor } from 'common/Types'; import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; import { IGlyphIdentifier } from './atlas/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; -import { BaseCharAtlas } from './atlas/BaseCharAtlas'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { AttributeData } from 'common/buffer/AttributeData'; import { IColorSet } from 'browser/Types'; @@ -21,6 +20,7 @@ import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { Terminal } from 'xterm'; +import { DynamicCharAtlas } from 'atlas/DynamicCharAtlas'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -36,7 +36,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _selectionEnd: [number, number] | undefined; protected _columnSelectMode: boolean = false; - protected _charAtlas: BaseCharAtlas | undefined; + protected _charAtlas: DynamicCharAtlas | undefined; /** * An object that's reused when drawing glyphs in order to reduce GC. diff --git a/addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts deleted file mode 100644 index 466e2ec8..00000000 --- a/addons/xterm-addon-canvas/src/atlas/BaseCharAtlas.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IGlyphIdentifier } from './Types'; -import { IDisposable } from 'common/Types'; - -export abstract class BaseCharAtlas implements IDisposable { - private _didWarmUp: boolean = false; - public abstract readonly cacheCanvas: HTMLCanvasElement; - - public dispose(): void { } - - /** - * Perform any work needed to warm the cache before it can be used. May be called multiple times. - * Implement _doWarmUp instead if you only want to get called once. - */ - public warmUp(): void { - if (!this._didWarmUp) { - this._doWarmUp(); - this._didWarmUp = true; - } - } - - /** - * Perform any work needed to warm the cache before it can be used. Used by the default - * implementation of warmUp(), and will only be called once. - */ - private _doWarmUp(): void { } - - public clear(): void { } - - /** - * Called when we start drawing a new frame. - * - * TODO: We rely on this getting called by TextRenderLayer. This should really be called by - * Renderer instead, but we need to make Renderer the source-of-truth for the char atlas, instead - * of BaseRenderLayer. - */ - public beginFrame(): void { } - - /** - * May be called before warmUp finishes, however it is okay for the implementation to - * do nothing and return false in that case. - * - * @param ctx Where to draw the character onto. - * @param glyph Information about what to draw - * @param x The position on the context to start drawing at - * @param y The position on the context to start drawing at - * @returns The success state. True if we drew the character. - */ - public abstract draw( - ctx: CanvasRenderingContext2D, - glyph: IGlyphIdentifier, - x: number, - y: number - ): boolean; -} diff --git a/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts b/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts index 73d66f64..f729b7f0 100644 --- a/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts +++ b/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts @@ -4,14 +4,13 @@ */ import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils'; -import { BaseCharAtlas } from './BaseCharAtlas'; import { DynamicCharAtlas } from './DynamicCharAtlas'; import { IColorSet } from 'browser/Types'; import { Terminal } from 'xterm'; import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; interface ICharAtlasCacheEntry { - atlas: BaseCharAtlas; + atlas: DynamicCharAtlas; config: ICharAtlasConfig; // N.B. This implementation potentially holds onto copies of the terminal forever, so // this may cause memory leaks. @@ -32,7 +31,7 @@ export function acquireCharAtlas( scaledCharWidth: number, scaledCharHeight: number, devicePixelRatio: number -): BaseCharAtlas { +): DynamicCharAtlas { const newConfig = generateConfig(scaledCellWidth, scaledCellHeight, scaledCharWidth, scaledCharHeight, terminal, colors, devicePixelRatio); // Check to see if the renderer already owns this config diff --git a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts index e52e3a40..54ad6f6e 100644 --- a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts +++ b/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts @@ -5,7 +5,6 @@ import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { IGlyphIdentifier } from './Types'; -import { BaseCharAtlas } from './BaseCharAtlas'; import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager'; import { LRUMap } from './LRUMap'; import { isFirefox, isSafari } from 'common/Platform'; @@ -52,14 +51,16 @@ export function getGlyphCacheKey(glyph: IGlyphIdentifier): number { return glyph.code << 21 | glyph.bg << 12 | glyph.fg << 3 | (glyph.bold ? 0 : 4) + (glyph.dim ? 0 : 2) + (glyph.italic ? 0 : 1); } -export class DynamicCharAtlas extends BaseCharAtlas { +export class DynamicCharAtlas { // An ordered map that we're using to keep track of where each glyph is in the atlas texture. // It's ordered so that we can determine when to remove the old entries. private _cacheMap: LRUMap; // The texture that the atlas is drawn to private _cacheCanvas: HTMLCanvasElement; + public get cacheCanvas(): HTMLCanvasElement { return this._cacheCanvas; } private _cacheCtx: CanvasRenderingContext2D; + private _didWarmUp: boolean = false; // A temporary context that glyphs are drawn to before being transfered to the atlas. private _tmpCtx: CanvasRenderingContext2D; @@ -80,7 +81,6 @@ export class DynamicCharAtlas extends BaseCharAtlas { private _bitmap: ImageBitmap | null = null; constructor(document: Document, private _config: ICharAtlasConfig) { - super(); this._cacheCanvas = document.createElement('canvas'); this._cacheCanvas.width = TEXTURE_WIDTH; this._cacheCanvas.height = TEXTURE_HEIGHT; @@ -111,10 +111,23 @@ export class DynamicCharAtlas extends BaseCharAtlas { } } - public override get cacheCanvas(): HTMLCanvasElement { - return this._cacheCanvas!; + /** + * Perform any work needed to warm the cache before it can be used. May be called multiple times. + * Implement _doWarmUp instead if you only want to get called once. + */ + public warmUp(): void { + if (!this._didWarmUp) { + this._doWarmUp(); + this._didWarmUp = true; + } } + /** + * Perform any work needed to warm the cache before it can be used. Used by the default + * implementation of warmUp(), and will only be called once. + */ + private _doWarmUp(): void { } + public beginFrame(): void { this._drawToCacheCount = 0; } @@ -371,27 +384,6 @@ export class DynamicCharAtlas extends BaseCharAtlas { } } -// This is used for debugging the renderer, just swap out `new DynamicCharAtlas` with -// `new NoneCharAtlas`. -export class NoneCharAtlas extends BaseCharAtlas { - constructor(document: Document, config: ICharAtlasConfig) { - super(); - } - - public override get cacheCanvas(): HTMLCanvasElement { - return null!; - } - - public draw( - ctx: CanvasRenderingContext2D, - glyph: IGlyphIdentifier, - x: number, - y: number - ): boolean { - return false; - } -} - /** * Makes a particular rgb color and colors that are nearly the same in an ImageData completely * transparent. From dece3cc41cd1106d2aad139ee93dbc9e99a5019c Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 07:20:55 -0700 Subject: [PATCH 07/23] DynamicCharAtlas -> CanvasCharAtlas --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 4 ++-- .../src/atlas/{DynamicCharAtlas.ts => CanvasCharAtlas.ts} | 2 +- addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) rename addons/xterm-addon-canvas/src/atlas/{DynamicCharAtlas.ts => CanvasCharAtlas.ts} (99%) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 05200861..d22088f2 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -20,7 +20,7 @@ import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { Terminal } from 'xterm'; -import { DynamicCharAtlas } from 'atlas/DynamicCharAtlas'; +import { CanvasCharAtlas } from 'atlas/CanvasCharAtlas'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -36,7 +36,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _selectionEnd: [number, number] | undefined; protected _columnSelectMode: boolean = false; - protected _charAtlas: DynamicCharAtlas | undefined; + protected _charAtlas: CanvasCharAtlas | undefined; /** * An object that's reused when drawing glyphs in order to reduce GC. diff --git a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts b/addons/xterm-addon-canvas/src/atlas/CanvasCharAtlas.ts similarity index 99% rename from addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts rename to addons/xterm-addon-canvas/src/atlas/CanvasCharAtlas.ts index 54ad6f6e..f3be7f46 100644 --- a/addons/xterm-addon-canvas/src/atlas/DynamicCharAtlas.ts +++ b/addons/xterm-addon-canvas/src/atlas/CanvasCharAtlas.ts @@ -51,7 +51,7 @@ export function getGlyphCacheKey(glyph: IGlyphIdentifier): number { return glyph.code << 21 | glyph.bg << 12 | glyph.fg << 3 | (glyph.bold ? 0 : 4) + (glyph.dim ? 0 : 2) + (glyph.italic ? 0 : 1); } -export class DynamicCharAtlas { +export class CanvasCharAtlas { // An ordered map that we're using to keep track of where each glyph is in the atlas texture. // It's ordered so that we can determine when to remove the old entries. private _cacheMap: LRUMap; diff --git a/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts b/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts index f729b7f0..f5995cf8 100644 --- a/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts +++ b/addons/xterm-addon-canvas/src/atlas/CharAtlasCache.ts @@ -4,13 +4,13 @@ */ import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils'; -import { DynamicCharAtlas } from './DynamicCharAtlas'; +import { CanvasCharAtlas } from './CanvasCharAtlas'; import { IColorSet } from 'browser/Types'; import { Terminal } from 'xterm'; import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; interface ICharAtlasCacheEntry { - atlas: DynamicCharAtlas; + atlas: CanvasCharAtlas; config: ICharAtlasConfig; // N.B. This implementation potentially holds onto copies of the terminal forever, so // this may cause memory leaks. @@ -31,7 +31,7 @@ export function acquireCharAtlas( scaledCharWidth: number, scaledCharHeight: number, devicePixelRatio: number -): DynamicCharAtlas { +): CanvasCharAtlas { const newConfig = generateConfig(scaledCellWidth, scaledCellHeight, scaledCharWidth, scaledCharHeight, terminal, colors, devicePixelRatio); // Check to see if the renderer already owns this config @@ -64,7 +64,7 @@ export function acquireCharAtlas( } const newEntry: ICharAtlasCacheEntry = { - atlas: new DynamicCharAtlas( + atlas: new CanvasCharAtlas( document, newConfig ), From 333b0e28ac7775299a56228dd375a096627728a5 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 07:48:43 -0700 Subject: [PATCH 08/23] Add ITextureAtlas and use that instead of WebglCharAtlas --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 7 +-- addons/xterm-addon-webgl/src/WebglRenderer.ts | 5 +- .../src/atlas/CharAtlasCache.ts | 6 +- .../src/atlas/WebglCharAtlas.ts | 4 +- .../src/renderLayer/BaseRenderLayer.ts | 5 +- src/browser/renderer/shared/Types.d.ts | 58 +++++++++++++++++++ 6 files changed, 70 insertions(+), 15 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index b19c8cb1..c198c9ba 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -4,13 +4,12 @@ */ import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; -import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { fill } from 'common/TypedArrayUtils'; import { NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; -import { IRenderDimensions } from 'browser/renderer/shared/Types'; +import { IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { Disposable, toDisposable } from 'common/Lifecycle'; interface IVertices { @@ -77,7 +76,7 @@ let $leftCellPadding = 0; let $clippedPixels = 0; export class GlyphRenderer extends Disposable { - private _atlas: WebglCharAtlas | undefined; + private _atlas: ITextureAtlas | undefined; private _program: WebGLProgram; private _vertexArrayObject: IWebGLVertexArrayObject; @@ -321,7 +320,7 @@ export class GlyphRenderer extends Disposable { gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, bufferLength / INDICES_PER_CELL); } - public setAtlas(atlas: WebglCharAtlas): void { + public setAtlas(atlas: ITextureAtlas): void { const gl = this._gl; this._atlas = atlas; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 34f247eb..b3a3ed02 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -7,7 +7,6 @@ import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; import { acquireCharAtlas, removeTerminalFromCache } from './atlas/CharAtlasCache'; -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_EXT_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; @@ -15,7 +14,7 @@ import { Disposable } from 'common/Lifecycle'; import { Attributes, BgFlags, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal, IEvent } from 'xterm'; import { IRenderLayer } from './renderLayer/Types'; -import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/shared/Types'; +import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ITextureAtlas } from 'browser/renderer/shared/Types'; import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver'; import { ITerminal, IColorSet } from 'browser/Types'; import { EventEmitter } from 'common/EventEmitter'; @@ -35,7 +34,7 @@ let $isSelected = false; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; - private _charAtlas: WebglCharAtlas | undefined; + private _charAtlas: ITextureAtlas | undefined; private _devicePixelRatio: number; private _model: RenderModel = new RenderModel(); diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts index e70a4995..741de255 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts @@ -6,11 +6,11 @@ import { WebglCharAtlas } from './WebglCharAtlas'; import { Terminal } from 'xterm'; import { IColorSet, ITerminal } from 'browser/Types'; -import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; +import { ICharAtlasConfig, ITextureAtlas } from 'browser/renderer/shared/Types'; import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils'; interface ICharAtlasCacheEntry { - atlas: WebglCharAtlas; + atlas: ITextureAtlas; config: ICharAtlasConfig; // N.B. This implementation potentially holds onto copies of the terminal forever, so // this may cause memory leaks. @@ -33,7 +33,7 @@ export function acquireCharAtlas( scaledCharWidth: number, scaledCharHeight: number, devicePixelRatio: number -): WebglCharAtlas { +): ITextureAtlas { const newConfig = generateConfig(scaledCellWidth, scaledCellHeight, scaledCharWidth, scaledCharHeight, terminal, colors, devicePixelRatio); // Check to see if the terminal already owns this config diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index b9a4088d..8782e123 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -16,7 +16,7 @@ import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlin import { IUnicodeService } from 'common/services/Services'; import { FourKeyMap } from 'common/MultiKeyMap'; import { IdleTaskQueue } from 'common/TaskQueue'; -import { ICharAtlasConfig } from 'browser/renderer/shared/Types'; +import { ICharAtlasConfig, ITextureAtlas } from 'browser/renderer/shared/Types'; // For debugging purposes, it can be useful to set this to a really tiny value, // to verify that LRU eviction works. @@ -51,7 +51,7 @@ interface ICharAtlasActiveRow { // Work variables to avoid garbage collection let $glyph = undefined; -export class WebglCharAtlas implements IDisposable { +export class WebglCharAtlas implements ITextureAtlas { private _didWarmUp: boolean = false; private _cacheMap: FourKeyMap = new FourKeyMap(); diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 2cba49dd..1321c246 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -9,9 +9,8 @@ import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { ICoreBrowserService } from 'browser/services/Services'; -import { IRenderDimensions } from 'browser/renderer/shared/Types'; +import { IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { CellData } from 'common/buffer/CellData'; -import { WebglCharAtlas } from 'atlas/WebglCharAtlas'; import { throwIfFalsy } from '../WebglUtils'; export abstract class BaseRenderLayer implements IRenderLayer { @@ -24,7 +23,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { private _scaledCharLeft: number = 0; private _scaledCharTop: number = 0; - protected _charAtlas: WebglCharAtlas | undefined; + protected _charAtlas: ITextureAtlas | undefined; constructor( private _container: HTMLElement, diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index ccfcae43..8fde3ae8 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -74,3 +74,61 @@ export interface IRenderer extends IDisposable { renderRows(start: number, end: number): void; clearTextureAtlas?(): void; } + +export interface ITextureAtlas extends IDisposable { + readonly cacheCanvas: HTMLCanvasElement; + // TODO: Add endFrame and make this readonly + hasCanvasChanged: boolean; + + /** + * Warm up the texture atlas, adding common glyphs to avoid slowing early frame. + */ + warmUp(): void; + + /** + * Call when a frame is being drawn, this will return true if the atlas was cleared to make room + * for a new set of glyphs. + */ + beginFrame(): boolean; + + /** + * Clear all glyphs from the texture atlas. + */ + clearTexture(): void; + getRasterizedGlyph(code: number, bg: number, fg: number, ext: number): IRasterizedGlyph; + getRasterizedGlyphCombinedChar(chars: string, bg: number, fg: number, ext: number): IRasterizedGlyph; +} + +/** + * Represents a rasterized glyph within a texture atlas. Some numbers are + * tracked in CSS pixels as well in order to reduce calculations during the + * render loop. + */ +export interface IRasterizedGlyph { + /** + * The x and y offset between the glyph's top/left and the top/left of a cell + * in pixels. + */ + offset: IVector; + /** + * the x and y position of the glyph in the texture in pixels. + */ + texturePosition: IVector; + /** + * the x and y position of the glyph in the texture in clip space coordinates. + */ + texturePositionClipSpace: IVector; + /** + * The width and height of the glyph in the texture in pixels. + */ + size: IVector; + /** + * The width and height of the glyph in the texture in clip space coordinates. + */ + sizeClipSpace: IVector; +} + +export interface IVector { + x: number; + y: number; +} From 26c974f9b62386a4e9ed8f39724e5780d63e3e59 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 08:36:24 -0700 Subject: [PATCH 09/23] Shuffle some utils --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 7 ++-- .../src/RectangleRenderer.ts | 3 +- addons/xterm-addon-webgl/src/Types.d.ts | 34 ------------------- addons/xterm-addon-webgl/src/WebglUtils.ts | 9 ++--- .../src/atlas/WebglCharAtlas.ts | 8 ++--- .../src/renderLayer/BaseRenderLayer.ts | 2 +- 6 files changed, 12 insertions(+), 51 deletions(-) diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index c198c9ba..f27ef29f 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -3,14 +3,15 @@ * @license MIT */ -import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; -import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; +import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; +import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; import { NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; -import { IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { Disposable, toDisposable } from 'common/Lifecycle'; +import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; interface IVertices { attributes: Float32Array; diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index ea7c3836..50a8cd58 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { createProgram, expandFloat32Array, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; +import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext } from './Types'; import { Attributes, BgFlags, FgFlags } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; @@ -13,6 +13,7 @@ import { IRenderDimensions } from 'browser/renderer/shared/Types'; import { RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; import { Disposable, toDisposable } from 'common/Lifecycle'; import { DIM_OPACITY } from 'browser/renderer/shared/Constants'; +import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; const enum VertexAttribLocations { POSITION = 0, diff --git a/addons/xterm-addon-webgl/src/Types.d.ts b/addons/xterm-addon-webgl/src/Types.d.ts index bcfa11c8..7add33a3 100644 --- a/addons/xterm-addon-webgl/src/Types.d.ts +++ b/addons/xterm-addon-webgl/src/Types.d.ts @@ -3,40 +3,6 @@ * @license MIT */ -/** - * Represents a rasterized glyph within a texture atlas. Some numbers are - * tracked in CSS pixels as well in order to reduce calculations during the - * render loop. - */ -export interface IRasterizedGlyph { - /** - * The x and y offset between the glyph's top/left and the top/left of a cell - * in pixels. - */ - offset: IVector; - /** - * the x and y position of the glyph in the texture in pixels. - */ - texturePosition: IVector; - /** - * the x and y position of the glyph in the texture in clip space coordinates. - */ - texturePositionClipSpace: IVector; - /** - * The width and height of the glyph in the texture in pixels. - */ - size: IVector; - /** - * The width and height of the glyph in the texture in clip space coordinates. - */ - sizeClipSpace: IVector; -} - -export interface IVector { - x: number; - y: number; -} - export interface IBoundingBox { top: number; left: number; diff --git a/addons/xterm-addon-webgl/src/WebglUtils.ts b/addons/xterm-addon-webgl/src/WebglUtils.ts index 841ad067..51a27377 100644 --- a/addons/xterm-addon-webgl/src/WebglUtils.ts +++ b/addons/xterm-addon-webgl/src/WebglUtils.ts @@ -3,6 +3,8 @@ * @license MIT */ +import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; + /** * A matrix that when multiplies will translate 0-1 coordinates (left to right, * top to bottom) to clip space. @@ -49,10 +51,3 @@ export function expandFloat32Array(source: Float32Array, max: number): Float32Ar } return newArray; } - -export function throwIfFalsy(value: T | undefined | null): T { - if (!value) { - throw new Error('value must not be falsy'); - } - return value; -} diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 8782e123..3d9bdf16 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -4,19 +4,17 @@ */ import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; -import { IRasterizedGlyph, IBoundingBox } from '../Types'; +import { IBoundingBox } from '../Types'; import { DEFAULT_COLOR, Attributes, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants'; -import { throwIfFalsy } from '../WebglUtils'; import { IColor } from 'common/Types'; -import { IDisposable } from 'xterm'; import { AttributeData } from 'common/buffer/AttributeData'; import { color, NULL_COLOR, rgba } from 'common/Color'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; -import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph } from 'browser/renderer/shared/RendererUtils'; +import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { IUnicodeService } from 'common/services/Services'; import { FourKeyMap } from 'common/MultiKeyMap'; import { IdleTaskQueue } from 'common/TaskQueue'; -import { ICharAtlasConfig, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types'; // For debugging purposes, it can be useful to set this to a really tiny value, // to verify that LRU eviction works. diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 1321c246..9290bb79 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -11,7 +11,7 @@ import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { ICoreBrowserService } from 'browser/services/Services'; import { IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { CellData } from 'common/buffer/CellData'; -import { throwIfFalsy } from '../WebglUtils'; +import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; From f38a49b4d57fc6d7d5f7ad644abddd03ac349bc3 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 08:48:55 -0700 Subject: [PATCH 10/23] Move remaining webgl atlas to shared --- addons/xterm-addon-webgl/src/Types.d.ts | 7 ------- addons/xterm-addon-webgl/src/WebglRenderer.ts | 4 ++-- addons/xterm-addon-webgl/src/atlas/Types.d.ts | 14 -------------- .../src/renderLayer/BaseRenderLayer.ts | 4 ++-- .../browser/renderer/shared}/CharAtlasCache.ts | 12 ++++++------ .../browser/renderer/shared/TextureAtlas.ts | 5 ++--- src/browser/renderer/shared/Types.d.ts | 7 +++++++ 7 files changed, 19 insertions(+), 34 deletions(-) delete mode 100644 addons/xterm-addon-webgl/src/atlas/Types.d.ts rename {addons/xterm-addon-webgl/src/atlas => src/browser/renderer/shared}/CharAtlasCache.ts (90%) rename addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts => src/browser/renderer/shared/TextureAtlas.ts (99%) diff --git a/addons/xterm-addon-webgl/src/Types.d.ts b/addons/xterm-addon-webgl/src/Types.d.ts index 7add33a3..d09f43e9 100644 --- a/addons/xterm-addon-webgl/src/Types.d.ts +++ b/addons/xterm-addon-webgl/src/Types.d.ts @@ -3,13 +3,6 @@ * @license MIT */ -export interface IBoundingBox { - top: number; - left: number; - right: number; - bottom: number; -} - export interface IRenderModel { cells: Uint32Array; lineLengths: Uint32Array; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index b3a3ed02..977d42d7 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -6,7 +6,7 @@ import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; -import { acquireCharAtlas, removeTerminalFromCache } from './atlas/CharAtlasCache'; +import { acquireTextureAtlas, removeTerminalFromCache } from '../../../src/browser/renderer/shared/CharAtlasCache'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; @@ -277,7 +277,7 @@ export class WebglRenderer extends Disposable implements IRenderer { return; } - const atlas = acquireCharAtlas(this._terminal, this._colors, this.dimensions.scaledCellWidth, this.dimensions.scaledCellHeight, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight, this._coreBrowserService.dpr); + const atlas = acquireTextureAtlas(this._terminal, this._colors, this.dimensions.scaledCellWidth, this.dimensions.scaledCellHeight, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight, this._coreBrowserService.dpr); if (!('getRasterizedGlyph' in atlas)) { throw new Error('The webgl renderer only works with the webgl char atlas'); } diff --git a/addons/xterm-addon-webgl/src/atlas/Types.d.ts b/addons/xterm-addon-webgl/src/atlas/Types.d.ts deleted file mode 100644 index fd5b596c..00000000 --- a/addons/xterm-addon-webgl/src/atlas/Types.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -export interface IGlyphIdentifier { - chars: string; - code: number; - bg: number; - fg: number; - bold: boolean; - dim: boolean; - italic: boolean; -} diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 9290bb79..25871c24 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -4,7 +4,7 @@ */ import { IRenderLayer } from './Types'; -import { acquireCharAtlas } from '../atlas/CharAtlasCache'; +import { acquireTextureAtlas } from '../../../../src/browser/renderer/shared/CharAtlasCache'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; @@ -94,7 +94,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { return; } - this._charAtlas = acquireCharAtlas(terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); + this._charAtlas = acquireTextureAtlas(terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); this._charAtlas.warmUp(); } diff --git a/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts b/src/browser/renderer/shared/CharAtlasCache.ts similarity index 90% rename from addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts rename to src/browser/renderer/shared/CharAtlasCache.ts index 741de255..ee88c708 100644 --- a/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts +++ b/src/browser/renderer/shared/CharAtlasCache.ts @@ -3,13 +3,13 @@ * @license MIT */ -import { WebglCharAtlas } from './WebglCharAtlas'; +import { TextureAtlas } from 'browser/renderer/shared/TextureAtlas'; import { Terminal } from 'xterm'; import { IColorSet, ITerminal } from 'browser/Types'; import { ICharAtlasConfig, ITextureAtlas } from 'browser/renderer/shared/Types'; import { generateConfig, configEquals } from 'browser/renderer/shared/CharAtlasUtils'; -interface ICharAtlasCacheEntry { +interface ITextureAtlasCacheEntry { atlas: ITextureAtlas; config: ICharAtlasConfig; // N.B. This implementation potentially holds onto copies of the terminal forever, so @@ -17,7 +17,7 @@ interface ICharAtlasCacheEntry { ownedBy: Terminal[]; } -const charAtlasCache: ICharAtlasCacheEntry[] = []; +const charAtlasCache: ITextureAtlasCacheEntry[] = []; /** * Acquires a char atlas, either generating a new one or returning an existing @@ -25,7 +25,7 @@ const charAtlasCache: ICharAtlasCacheEntry[] = []; * @param terminal The terminal. * @param colors The colors to use. */ -export function acquireCharAtlas( +export function acquireTextureAtlas( terminal: Terminal, colors: IColorSet, scaledCellWidth: number, @@ -66,8 +66,8 @@ export function acquireCharAtlas( } const core: ITerminal = (terminal as any)._core; - const newEntry: ICharAtlasCacheEntry = { - atlas: new WebglCharAtlas(document, newConfig, core.unicodeService), + const newEntry: ITextureAtlasCacheEntry = { + atlas: new TextureAtlas(document, newConfig, core.unicodeService), config: newConfig, ownedBy: [terminal] }; diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts similarity index 99% rename from addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts rename to src/browser/renderer/shared/TextureAtlas.ts index 3d9bdf16..0840b035 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -4,7 +4,6 @@ */ import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; -import { IBoundingBox } from '../Types'; import { DEFAULT_COLOR, Attributes, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants'; import { IColor } from 'common/Types'; import { AttributeData } from 'common/buffer/AttributeData'; @@ -14,7 +13,7 @@ import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlin import { IUnicodeService } from 'common/services/Services'; import { FourKeyMap } from 'common/MultiKeyMap'; import { IdleTaskQueue } from 'common/TaskQueue'; -import { ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types'; // For debugging purposes, it can be useful to set this to a really tiny value, // to verify that LRU eviction works. @@ -49,7 +48,7 @@ interface ICharAtlasActiveRow { // Work variables to avoid garbage collection let $glyph = undefined; -export class WebglCharAtlas implements ITextureAtlas { +export class TextureAtlas implements ITextureAtlas { private _didWarmUp: boolean = false; private _cacheMap: FourKeyMap = new FourKeyMap(); diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 8fde3ae8..20c1357a 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -132,3 +132,10 @@ export interface IVector { x: number; y: number; } + +export interface IBoundingBox { + top: number; + left: number; + right: number; + bottom: number; +} From 0bc5ca8f8052826380fd6f028c0f868f85985cce Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 11:08:06 -0700 Subject: [PATCH 11/23] Fix imports --- addons/xterm-addon-webgl/src/WebglRenderer.ts | 2 +- addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 977d42d7..43b15e0c 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -6,7 +6,7 @@ import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; -import { acquireTextureAtlas, removeTerminalFromCache } from '../../../src/browser/renderer/shared/CharAtlasCache'; +import { acquireTextureAtlas, removeTerminalFromCache } from 'browser/renderer/shared/CharAtlasCache'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 25871c24..07eca0bf 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -4,7 +4,7 @@ */ import { IRenderLayer } from './Types'; -import { acquireTextureAtlas } from '../../../../src/browser/renderer/shared/CharAtlasCache'; +import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; From c2bf87ee343820d3d1abb758b76d63662a8830eb Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 11:38:46 -0700 Subject: [PATCH 12/23] Initial working shared atlas --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 39 ++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index d22088f2..8752b55f 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -3,13 +3,12 @@ * @license MIT */ -import { IRenderDimensions } from 'browser/renderer/shared/Types'; +import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { IRenderLayer } from './Types'; import { ICellData, IColor } from 'common/Types'; import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; import { IGlyphIdentifier } from './atlas/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; -import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { AttributeData } from 'common/buffer/AttributeData'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; @@ -20,7 +19,7 @@ import { channels, color, rgba } from 'common/Color'; import { removeElementFromParent } from 'browser/Dom'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { Terminal } from 'xterm'; -import { CanvasCharAtlas } from 'atlas/CanvasCharAtlas'; +import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -36,7 +35,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _selectionEnd: [number, number] | undefined; protected _columnSelectMode: boolean = false; - protected _charAtlas: CanvasCharAtlas | undefined; + protected _charAtlas: ITextureAtlas | undefined; /** * An object that's reused when drawing glyphs in order to reduce GC. @@ -129,7 +128,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { return; } - this._charAtlas = acquireCharAtlas(this._terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); + // this._charAtlas = acquireCharAtlas(this._terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); + this._charAtlas = acquireTextureAtlas(this._terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); this._charAtlas.warmUp(); } @@ -156,7 +156,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { public abstract reset(): void; public clearTextureAtlas(): void { - this._charAtlas?.clear(); + this._charAtlas?.clearTexture(); } /** @@ -418,13 +418,34 @@ export abstract class BaseRenderLayer implements IRenderLayer { } }); - const atlasDidDraw = hasOverrides ? false : this._charAtlas?.draw(this._ctx, this._currentGlyphIdentifier, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); - - if (!atlasDidDraw) { + if (this._charAtlas) { + // const atlasDidDraw = hasOverrides ? false : this._charAtlas?.draw(this._ctx, this._currentGlyphIdentifier, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); + const glyph = this._charAtlas.getRasterizedGlyph(this._currentGlyphIdentifier.code, this._currentGlyphIdentifier.bg, this._currentGlyphIdentifier.fg, 0); + this._drawGlyph(glyph, x, y); + } else { this._drawUncachedChars(cell, x, y); } } + // Does fg override work? + private _drawGlyph(glyph: IRasterizedGlyph, x: number, y: number): void { + this._ctx.save(); + this._clipRow(y); + this._ctx.drawImage( + this._charAtlas!.cacheCanvas, + glyph.texturePosition.x, + glyph.texturePosition.y, + glyph.size.x, + glyph.size.y, + x * this._scaledCellWidth - glyph.offset.x, + y * this._scaledCellHeight - glyph.offset.y, + glyph.size.x, + glyph.size.y + ); + this._ctx.restore(); + // TODO: Bitmap optimizations? + } + /** * Draws one or more characters at one or more cells. The character(s) will be * clipped to ensure that they fit with the cell(s), including the cell to the From dbef85003912077ceab6ad564828e156773153e1 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 13:57:35 -0700 Subject: [PATCH 13/23] Hook up attributes to new texture atlas --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 298 ++---------------- .../xterm-addon-canvas/src/TextRenderLayer.ts | 73 +---- src/browser/renderer/shared/TextureAtlas.ts | 2 + 3 files changed, 21 insertions(+), 352 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index e0561494..3ab8ea5a 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -3,23 +3,21 @@ * @license MIT */ -import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; -import { IRenderLayer } from './Types'; -import { ICellData, IColor } from 'common/Types'; -import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; -import { IGlyphIdentifier } from './atlas/Types'; -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; -import { AttributeData } from 'common/buffer/AttributeData'; +import { removeElementFromParent } from 'browser/Dom'; +import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; +import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; +import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; +import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; +import { IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { ICoreBrowserService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; +import { BgFlags, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; -import { ICoreBrowserService } from 'browser/services/Services'; -import { excludeFromContrastRatioDemands, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; -import { channels, color, rgba } from 'common/Color'; -import { removeElementFromParent } from 'browser/Dom'; -import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; +import { ICellData } from 'common/Types'; import { Terminal } from 'xterm'; -import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; +import { IGlyphIdentifier } from './atlas/Types'; +import { IRenderLayer } from './Types'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; @@ -35,7 +33,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _selectionEnd: [number, number] | undefined; protected _columnSelectMode: boolean = false; - protected _charAtlas: ITextureAtlas | undefined; + protected _charAtlas!: ITextureAtlas; /** * An object that's reused when drawing glyphs in order to reduce GC. @@ -70,6 +68,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._canvas.style.zIndex = zIndex.toString(); this._initCanvas(); this._container.appendChild(this._canvas); + this._refreshCharAtlas(this._colors); } public dispose(): void { @@ -367,70 +366,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { /** * Draws one or more characters at a cell. If possible this will draw using * the character atlas to reduce draw time. - * @param chars The character or characters. - * @param code The character code. - * @param width The width of the characters. - * @param x The column to draw at. - * @param y The row to draw at. - * @param fg The foreground color, in the format stored within the attributes. - * @param bg The background color, in the format stored within the attributes. - * This is used to validate whether a cached image can be used. - * @param bold Whether the text is bold. */ protected _drawChars(cell: ICellData, x: number, y: number): void { - const contrastColor = this._getContrastColor(cell, x, y); - - // skip cache right away if we draw in RGB - // Note: to avoid bad runtime JoinedCellData will be skipped - // in the cache handler itself (atlasDidDraw == false) and - // fall through to uncached later down below - if (contrastColor || cell.isFgRGB() || cell.isBgRGB()) { - this._drawUncachedChars(cell, x, y, contrastColor); - return; - } - - let fg; - let bg; - if (cell.isInverse()) { - fg = (cell.isBgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getBgColor(); - bg = (cell.isFgDefault()) ? INVERTED_DEFAULT_COLOR : cell.getFgColor(); - } else { - bg = (cell.isBgDefault()) ? DEFAULT_COLOR : cell.getBgColor(); - fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor(); - } - - const drawInBrightColor = this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8; - - fg += drawInBrightColor ? 8 : 0; - this._currentGlyphIdentifier.chars = cell.getChars() || WHITESPACE_CELL_CHAR; - this._currentGlyphIdentifier.code = cell.getCode() || WHITESPACE_CELL_CODE; - this._currentGlyphIdentifier.bg = bg; - this._currentGlyphIdentifier.fg = fg; - this._currentGlyphIdentifier.bold = !!cell.isBold(); - this._currentGlyphIdentifier.dim = !!cell.isDim(); - this._currentGlyphIdentifier.italic = !!cell.isItalic(); - - // Don't try cache the glyph if it uses any decoration foreground/background override. - let hasOverrides = false; - this._decorationService.forEachDecorationAtCell(x, y, undefined, d => { - if (d.backgroundColorRGB || d.foregroundColorRGB) { - hasOverrides = true; - } - }); - - if (this._charAtlas) { - // const atlasDidDraw = hasOverrides ? false : this._charAtlas?.draw(this._ctx, this._currentGlyphIdentifier, x * this._scaledCellWidth + this._scaledCharLeft, y * this._scaledCellHeight + this._scaledCharTop); - const contrastColor = this._getContrastColor(cell, x, y); - console.log('contrast color', contrastColor); - const glyph = this._charAtlas.getRasterizedGlyph(this._currentGlyphIdentifier.code, this._currentGlyphIdentifier.bg, this._currentGlyphIdentifier.fg, 0); - this._drawGlyph(glyph, x, y); - } else { - this._drawUncachedChars(cell, x, y); - } - } - - // Does fg override work? - private _drawGlyph(glyph: IRasterizedGlyph, x: number, y: number): void { + const glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, cell.bg, cell.fg, cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0); this._ctx.save(); this._clipRow(y); this._ctx.drawImage( @@ -445,80 +383,12 @@ export abstract class BaseRenderLayer implements IRenderLayer { glyph.size.y ); this._ctx.restore(); - // TODO: Bitmap optimizations? - // TODO: Glyph background not correct? + // TODO: Verify selection + // TODO: Verify fg override + // TODO: Verify bg override + // TODO: Verify min contrast ratio } - /** - * Draws one or more characters at one or more cells. The character(s) will be - * clipped to ensure that they fit with the cell(s), including the cell to the - * right if the last character is a wide character. - * @param chars The character. - * @param width The width of the character. - * @param fg The foreground color, in the format stored within the attributes. - * @param x The column to draw at. - * @param y The row to draw at. - */ - private _drawUncachedChars(cell: ICellData, x: number, y: number, fgOverride?: IColor): void { - this._ctx.save(); - this._ctx.font = this._getFont(!!cell.isBold(), !!cell.isItalic()); - this._ctx.textBaseline = TEXT_BASELINE; - - if (cell.isInverse()) { - if (fgOverride) { - this._ctx.fillStyle = fgOverride.css; - } else if (cell.isBgDefault()) { - this._ctx.fillStyle = color.opaque(this._colors.background).css; - } else if (cell.isBgRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; - } else { - let bg = cell.getBgColor(); - if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && bg < 8) { - bg += 8; - } - this._ctx.fillStyle = this._colors.ansi[bg].css; - } - } else { - if (fgOverride) { - this._ctx.fillStyle = fgOverride.css; - } else if (cell.isFgDefault()) { - this._ctx.fillStyle = this._colors.foreground.css; - } else if (cell.isFgRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; - } else { - let fg = cell.getFgColor(); - if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { - fg += 8; - } - this._ctx.fillStyle = this._colors.ansi[fg].css; - } - } - - this._clipRow(y); - - // Apply alpha to dim the character - if (cell.isDim()) { - this._ctx.globalAlpha = DIM_OPACITY; - } - - // Draw custom characters if applicable - let drawSuccess = false; - if (this._optionsService.rawOptions.customGlyphs !== false) { - drawSuccess = tryDrawCustomChar(this._ctx, cell.getChars(), x * this._scaledCellWidth, y * this._scaledCellHeight, this._scaledCellWidth, this._scaledCellHeight, this._optionsService.rawOptions.fontSize, this._coreBrowserService.dpr); - } - - // Draw the character - if (!drawSuccess) { - this._ctx.fillText( - cell.getChars(), - x * this._scaledCellWidth + this._scaledCharLeft, - y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight); - } - - this._ctx.restore(); - } - - /** * Clips a row to ensure no pixels will be drawn outside the cells in the row. * @param y The row to clip. @@ -543,137 +413,5 @@ export abstract class BaseRenderLayer implements IRenderLayer { return `${fontStyle} ${fontWeight} ${this._optionsService.rawOptions.fontSize * this._coreBrowserService.dpr}px ${this._optionsService.rawOptions.fontFamily}`; } - - private _getContrastColor(cell: CellData, x: number, y: number): IColor | undefined { - // Get any decoration foreground/background overrides, this must be fetched before the early - // exist but applied after inverse - let bgOverride: number | undefined; - let fgOverride: number | undefined; - let isTop = false; - this._decorationService.forEachDecorationAtCell(x, y, undefined, d => { - if (d.options.layer !== 'top' && isTop) { - return; - } - if (d.backgroundColorRGB) { - bgOverride = d.backgroundColorRGB.rgba; - } - if (d.foregroundColorRGB) { - fgOverride = d.foregroundColorRGB.rgba; - } - isTop = d.options.layer === 'top'; - }); - - // Apply selection foreground if applicable - if (!isTop) { - if (this._colors.selectionForeground && this._isCellInSelection(x, y)) { - fgOverride = this._colors.selectionForeground.rgba; - } - } - - if (!bgOverride && !fgOverride && (this._optionsService.rawOptions.minimumContrastRatio === 1 || excludeFromContrastRatioDemands(cell.getCode()))) { - return undefined; - } - - if (!bgOverride && !fgOverride) { - // Try get from cache - const adjustedColor = this._colors.contrastCache.getColor(cell.bg, cell.fg); - if (adjustedColor !== undefined) { - return adjustedColor || undefined; - } - } - - let fgColor = cell.getFgColor(); - let fgColorMode = cell.getFgColorMode(); - let bgColor = cell.getBgColor(); - let bgColorMode = cell.getBgColorMode(); - const isInverse = !!cell.isInverse(); - const isBold = !!cell.isInverse(); - if (isInverse) { - const temp = fgColor; - fgColor = bgColor; - bgColor = temp; - const temp2 = fgColorMode; - fgColorMode = bgColorMode; - bgColorMode = temp2; - } - - const bgRgba = this._resolveBackgroundRgba(bgOverride !== undefined ? Attributes.CM_RGB : bgColorMode, bgOverride ?? bgColor, isInverse); - const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, isInverse, isBold); - let result = rgba.ensureContrastRatio(bgOverride ?? bgRgba, fgOverride ?? fgRgba, this._optionsService.rawOptions.minimumContrastRatio); - - if (!result) { - if (!fgOverride) { - this._colors.contrastCache.setColor(cell.bg, cell.fg, null); - return undefined; - } - // If it was an override and there was no contrast change, set as the result - result = fgOverride; - } - - const color: IColor = { - css: channels.toCss( - (result >> 24) & 0xFF, - (result >> 16) & 0xFF, - (result >> 8) & 0xFF - ), - rgba: result - }; - if (!bgOverride && !fgOverride) { - this._colors.contrastCache.setColor(cell.bg, cell.fg, color); - } - - return color; - } - - private _resolveBackgroundRgba(bgColorMode: number, bgColor: number, inverse: boolean): number { - switch (bgColorMode) { - case Attributes.CM_P16: - case Attributes.CM_P256: - return this._colors.ansi[bgColor].rgba; - case Attributes.CM_RGB: - return bgColor << 8; - case Attributes.CM_DEFAULT: - default: - if (inverse) { - return this._colors.foreground.rgba; - } - return this._colors.background.rgba; - } - } - - private _resolveForegroundRgba(fgColorMode: number, fgColor: number, inverse: boolean, bold: boolean): number { - switch (fgColorMode) { - case Attributes.CM_P16: - case Attributes.CM_P256: - if (this._optionsService.rawOptions.drawBoldTextInBrightColors && bold && fgColor < 8) { - fgColor += 8; - } - return this._colors.ansi[fgColor].rgba; - case Attributes.CM_RGB: - return fgColor << 8; - case Attributes.CM_DEFAULT: - default: - if (inverse) { - return this._colors.background.rgba; - } - return this._colors.foreground.rgba; - } - } - - private _isCellInSelection(x: number, y: number): boolean { - const start = this._selectionStart; - const end = this._selectionEnd; - if (!start || !end) { - return false; - } - if (this._columnSelectMode) { - return x >= start[0] && y >= start[1] && - x < end[0] && y < end[1]; - } - return (y > start[1] && y < end[1]) || - (start[1] === end[1] && y === start[1] && x >= start[0] && x < end[0]) || - (start[1] < end[1] && y === end[1] && x < end[0]) || - (start[1] < end[1] && y === start[1] && x >= start[0]); - } } diff --git a/addons/xterm-addon-canvas/src/TextRenderLayer.ts b/addons/xterm-addon-canvas/src/TextRenderLayer.ts index b20765d8..8d80514b 100644 --- a/addons/xterm-addon-canvas/src/TextRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/TextRenderLayer.ts @@ -233,78 +233,7 @@ export class TextRenderLayer extends BaseRenderLayer { } private _drawForeground(firstRow: number, lastRow: number): void { - this._forEachCell(firstRow, lastRow, (cell, x, y) => { - if (cell.isInvisible()) { - return; - } - this._drawChars(cell, x, y); - if (cell.isUnderline() || cell.isStrikethrough()) { - this._ctx.save(); - - if (cell.isInverse()) { - if (cell.isBgDefault()) { - this._ctx.fillStyle = this._colors.background.css; - } else if (cell.isBgRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getBgColor()).join(',')})`; - } else { - let bg = cell.getBgColor(); - if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && bg < 8) { - bg += 8; - } - this._ctx.fillStyle = this._colors.ansi[bg].css; - } - } else { - if (cell.isFgDefault()) { - this._ctx.fillStyle = this._colors.foreground.css; - } else if (cell.isFgRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; - } else { - let fg = cell.getFgColor(); - if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { - fg += 8; - } - this._ctx.fillStyle = this._colors.ansi[fg].css; - } - } - - if (cell.isStrikethrough()) { - this._fillMiddleLineAtCells(x, y, cell.getWidth()); - } - if (cell.isUnderline()) { - if (!cell.isUnderlineColorDefault()) { - if (cell.isUnderlineColorRGB()) { - this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getUnderlineColor()).join(',')})`; - } else { - let fg = cell.getUnderlineColor(); - if (this._optionsService.rawOptions.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { - fg += 8; - } - this._ctx.fillStyle = this._colors.ansi[fg].css; - } - } - switch (cell.extended.underlineStyle) { - case UnderlineStyle.DOUBLE: - this._fillBottomLineAtCells(x, y, cell.getWidth(), -this._coreBrowserService.dpr); - this._fillBottomLineAtCells(x, y, cell.getWidth(), this._coreBrowserService.dpr); - break; - case UnderlineStyle.CURLY: - this._curlyUnderlineAtCell(x, y, cell.getWidth()); - break; - case UnderlineStyle.DOTTED: - this._dottedUnderlineAtCell(x, y, cell.getWidth()); - break; - case UnderlineStyle.DASHED: - this._dashedUnderlineAtCell(x, y, cell.getWidth()); - break; - case UnderlineStyle.SINGLE: - default: - this._fillBottomLineAtCells(x, y, cell.getWidth()); - break; - } - } - this._ctx.restore(); - } - }); + this._forEachCell(firstRow, lastRow, (cell, x, y) => this._drawChars(cell, x, y)); } public onGridChanged(firstRow: number, lastRow: number): void { diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 0840b035..478254d7 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -336,6 +336,8 @@ export class TextureAtlas implements ITextureAtlas { private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number): IRasterizedGlyph { const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars; + console.log(`draw to cache "${chars}"`, bg, fg, ext); + this.hasCanvasChanged = true; // Allow 1 cell width per character, with a minimum of 2 (CJK), plus some padding. This is used From f2ff3a6a7ab72c4d8dcaa220d8bb0b6c0621af49 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 14:21:06 -0700 Subject: [PATCH 14/23] Add sgr attr test --- demo/client.ts | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ demo/index.html | 1 + 2 files changed, 61 insertions(+) diff --git a/demo/client.ts b/demo/client.ts index 8bff4690..4c3019a2 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -222,6 +222,7 @@ if (document.location.pathname === '/test') { document.getElementById('underline-test').addEventListener('click', underlineTest); document.getElementById('ansi-colors').addEventListener('click', ansiColorsTest); document.getElementById('osc-hyperlinks').addEventListener('click', addAnsiHyperlink); + document.getElementById('sgr-test').addEventListener('click', sgrTest); document.getElementById('add-decoration').addEventListener('click', addDecoration); document.getElementById('add-overview-ruler').addEventListener('click', addOverviewRuler); } @@ -885,6 +886,65 @@ function ansiColorsTest(): void { } } +function writeTestString(): string { + let alphabet = ''; + for (let i = 97; i < 123; i++) { + alphabet += String.fromCharCode(i); + } + let numbers = ''; + for (let i = 0; i < 10; i++) { + numbers += i.toString(); + } + return `${alphabet} ${numbers} 汉语 한국어 👽`; +} +const testString = writeTestString(); + +function sgrTest(): void { + term.write('\n\n\r'); + term.writeln(`Character Attributes (SGR, Select Graphic Rendition)`); + const entries: { ps: number, name: string }[] = [ + { ps: 0, name: 'Normal' }, + { ps: 1, name: 'Bold' }, + { ps: 2, name: 'Faint/dim' }, + { ps: 3, name: 'Italicized' }, + { ps: 4, name: 'Underlined' }, + { ps: 5, name: 'Blink' }, + { ps: 7, name: 'Inverse' }, + { ps: 8, name: 'Invisible' }, + { ps: 9, name: 'Crossed-out characters' }, + { ps: 21, name: 'Doubly-underlined' }, + { ps: 22, name: 'Normal' }, + { ps: 23, name: 'Not italicized' }, + { ps: 24, name: 'Not underlined' }, + { ps: 25, name: 'Steady (not blink)' }, + { ps: 27, name: 'Positive (not inverse)' }, + { ps: 28, name: 'Visible (not hidden)' }, + { ps: 29, name: 'Not crossed-out' }, + { ps: 30, name: 'Foreground Black' }, + { ps: 31, name: 'Foreground Red' }, + { ps: 32, name: 'Foreground Green' }, + { ps: 33, name: 'Foreground Yellow' }, + { ps: 34, name: 'Foreground Blue' }, + { ps: 35, name: 'Foreground Magenta' }, + { ps: 36, name: 'Foreground Cyan' }, + { ps: 37, name: 'Foreground White' }, + { ps: 39, name: 'Foreground default' }, + { ps: 40, name: 'Background Black' }, + { ps: 41, name: 'Background Red' }, + { ps: 42, name: 'Background Green' }, + { ps: 43, name: 'Background Yellow' }, + { ps: 44, name: 'Background Blue' }, + { ps: 45, name: 'Background Magenta' }, + { ps: 46, name: 'Background Cyan' }, + { ps: 47, name: 'Background White' }, + { ps: 49, name: 'Background default' } + ]; + const maxNameLength = entries.reduce((p, c) => Math.max(c.name.length, p), 0); + for (const e of entries) { + term.writeln(`\x1b[0m\x1b[${e.ps}m ${e.name.padEnd(maxNameLength, ' ')} - ${testString}\x1b[0m`); + } +} + function addAnsiHyperlink(): void { term.write('\n\n\r'); term.writeln(`Regular link with no id:`); diff --git a/demo/index.html b/demo/index.html index 41289221..a65b8bbc 100644 --- a/demo/index.html +++ b/demo/index.html @@ -79,6 +79,7 @@
+
From c741c13409246ce721130a5d8613d8fa890fb165 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 14:25:08 -0700 Subject: [PATCH 15/23] Add ps to output string --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 1 + demo/client.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 3ab8ea5a..4c92bca4 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -387,6 +387,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { // TODO: Verify fg override // TODO: Verify bg override // TODO: Verify min contrast ratio + // TODO: Verify emoji } /** diff --git a/demo/client.ts b/demo/client.ts index 4c3019a2..593b0da6 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -941,7 +941,7 @@ function sgrTest(): void { ]; const maxNameLength = entries.reduce((p, c) => Math.max(c.name.length, p), 0); for (const e of entries) { - term.writeln(`\x1b[0m\x1b[${e.ps}m ${e.name.padEnd(maxNameLength, ' ')} - ${testString}\x1b[0m`); + term.writeln(`\x1b[0m\x1b[${e.ps}m ${e.ps.toString().padEnd(2, ' ')} ${e.name.padEnd(maxNameLength, ' ')} - ${testString}\x1b[0m`); } } From 9259ec258fbbd90a025d2824780f8cae4f09b2a2 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 14:27:43 -0700 Subject: [PATCH 16/23] Fix combined chars in canvas --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 4c92bca4..42303f05 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -8,7 +8,7 @@ import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; -import { IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; import { ICoreBrowserService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; @@ -368,7 +368,13 @@ export abstract class BaseRenderLayer implements IRenderLayer { * the character atlas to reduce draw time. */ protected _drawChars(cell: ICellData, x: number, y: number): void { - const glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, cell.bg, cell.fg, cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0); + const chars = cell.getChars(); + let glyph: IRasterizedGlyph; + if (chars && chars.length > 1) { + glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, cell.bg, cell.fg, cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0); + } else { + glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, cell.bg, cell.fg, cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0); + } this._ctx.save(); this._clipRow(y); this._ctx.drawImage( @@ -387,7 +393,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { // TODO: Verify fg override // TODO: Verify bg override // TODO: Verify min contrast ratio - // TODO: Verify emoji } /** From 6316eee67296d7e2f7a21b53437c447051886e02 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Sun, 2 Oct 2022 21:32:24 -0700 Subject: [PATCH 17/23] Comment out debug log --- src/browser/renderer/shared/TextureAtlas.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 478254d7..c4bffe8d 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -336,7 +336,8 @@ export class TextureAtlas implements ITextureAtlas { private _drawToCache(codeOrChars: number | string, bg: number, fg: number, ext: number): IRasterizedGlyph { const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars; - console.log(`draw to cache "${chars}"`, bg, fg, ext); + // Uncomment for debugging + // console.log(`draw to cache "${chars}"`, bg, fg, ext); this.hasCanvasChanged = true; From 03e3105c55fb202939a2f81f59e707f0041d2130 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Oct 2022 14:10:27 -0700 Subject: [PATCH 18/23] Fix fg/bg overrides in canvas --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 207 ++++++++++++++++-- addons/xterm-addon-webgl/src/RenderModel.ts | 3 +- addons/xterm-addon-webgl/src/Types.d.ts | 13 +- addons/xterm-addon-webgl/src/WebglRenderer.ts | 3 - src/browser/renderer/shared/Types.d.ts | 11 + 5 files changed, 200 insertions(+), 37 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 42303f05..586be167 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -8,17 +8,24 @@ import { acquireTextureAtlas } from 'browser/renderer/shared/CharAtlasCache'; import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; -import { IRasterizedGlyph, IRenderDimensions, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types'; import { ICoreBrowserService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; -import { BgFlags, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; +import { Attributes, BgFlags, FgFlags, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ICellData } from 'common/Types'; import { Terminal } from 'xterm'; import { IGlyphIdentifier } from './atlas/Types'; import { IRenderLayer } from './Types'; +// Work variables to avoid garbage collection +let $fg = 0; +let $bg = 0; +let $hasFg = false; +let $hasBg = false; +let $isSelected = false; + export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; protected _ctx!: CanvasRenderingContext2D; @@ -32,22 +39,19 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _selectionStart: [number, number] | undefined; protected _selectionEnd: [number, number] | undefined; protected _columnSelectMode: boolean = false; + protected _selectionModel: ISelectionRenderModel = { + hasSelection: false, + columnSelectMode: false, + viewportStartRow: 0, + viewportEndRow: 0, + viewportCappedStartRow: 0, + viewportCappedEndRow: 0, + startCol: 0, + endCol: 0 + }; protected _charAtlas!: ITextureAtlas; - /** - * An object that's reused when drawing glyphs in order to reduce GC. - */ - private _currentGlyphIdentifier: IGlyphIdentifier = { - chars: '', - code: 0, - bg: 0, - fg: 0, - bold: false, - dim: false, - italic: false - }; - public get canvas(): HTMLCanvasElement { return this._canvas; } public get cacheCanvas(): HTMLCanvasElement { return this._charAtlas?.cacheCanvas!; } @@ -91,9 +95,11 @@ export abstract class BaseRenderLayer implements IRenderLayer { public onGridChanged(startRow: number, endRow: number): void {} public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { + // TODO: Remove these other variables in favor of the selection model this._selectionStart = start; this._selectionEnd = end; this._columnSelectMode = columnSelectMode; + this._updateSelectionModel(start, end, columnSelectMode); } public setColors(colorSet: IColorSet): void { @@ -127,7 +133,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { return; } - // this._charAtlas = acquireCharAtlas(this._terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); this._charAtlas = acquireTextureAtlas(this._terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); this._charAtlas.warmUp(); } @@ -363,17 +368,20 @@ export abstract class BaseRenderLayer implements IRenderLayer { } } + private _workColors: { fg: number, bg: number, ext: number } = { fg: 0, bg: 0, ext: 0 }; + /** * Draws one or more characters at a cell. If possible this will draw using * the character atlas to reduce draw time. */ protected _drawChars(cell: ICellData, x: number, y: number): void { const chars = cell.getChars(); + this._loadColorsForCell(x, y, cell, this._workColors); let glyph: IRasterizedGlyph; if (chars && chars.length > 1) { - glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, cell.bg, cell.fg, cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0); + glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._workColors.bg, this._workColors.fg, this._workColors.ext); } else { - glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, cell.bg, cell.fg, cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0); + glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._workColors.bg, this._workColors.fg, this._workColors.ext); } this._ctx.save(); this._clipRow(y); @@ -389,10 +397,165 @@ export abstract class BaseRenderLayer implements IRenderLayer { glyph.size.y ); this._ctx.restore(); - // TODO: Verify selection - // TODO: Verify fg override - // TODO: Verify bg override - // TODO: Verify min contrast ratio + // TODO: Move both renderers to use shared load color code + } + + /** + * Loads colors for the cell into the work colors object. This resolves overrides/inverse if + * necessary which is why the work cell object is not used. + */ + private _loadColorsForCell(x: number, y: number, cell: ICellData, workColors: { fg: number, bg: number, ext: number }): void { + workColors.bg = cell.bg; + workColors.fg = cell.fg; + workColors.ext = cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0; + // Get any foreground/background overrides, this happens on the model to avoid spreading + // override logic throughout the different sub-renderers + + // Reset overrides work variables + $bg = 0; + $fg = 0; + $hasBg = false; + $hasFg = false; + $isSelected = false; + + // Apply decorations on the bottom layer + this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => { + if (d.backgroundColorRGB) { + $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasBg = true; + } + if (d.foregroundColorRGB) { + $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasFg = true; + } + }); + + // TODO: Selection? + // Apply the selection color if needed + $isSelected = this._isCellSelected(x, y); + if ($isSelected) { + $bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; + $hasBg = true; + if (this._colors.selectionForeground) { + $fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; + $hasFg = true; + } + } + + // Apply decorations on the top layer + this._decorationService.forEachDecorationAtCell(x, y, 'top', d => { + if (d.backgroundColorRGB) { + $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasBg = true; + } + if (d.foregroundColorRGB) { + $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasFg = true; + } + }); + + // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag + // ahead of time in order to use the correct cache key + if ($hasBg) { + if ($isSelected) { + // Non-RGB attributes from model + force non-dim + override + force RGB color mode + $bg = (cell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | $bg | Attributes.CM_RGB; + } else { + // Non-RGB attributes from model + override + force RGB color mode + $bg = (cell.bg & ~Attributes.RGB_MASK) | $bg | Attributes.CM_RGB; + } + } + if ($hasFg) { + // Non-RGB attributes from model + force disable inverse + override + force RGB color mode + $fg = (cell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | $fg | Attributes.CM_RGB; + } + + // Handle case where inverse was specified by only one of bg override or fg override was set, + // resolving the other inverse color and setting the inverse flag if needed. + if (workColors.fg & FgFlags.INVERSE) { + if ($hasBg && !$hasFg) { + // Resolve bg color type (default color has a different meaning in fg vs bg) + if ((workColors.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { + $fg = (workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + } else { + $fg = (workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK); + } + $hasFg = true; + } + if (!$hasBg && $hasFg) { + // Resolve bg color type (default color has a different meaning in fg vs bg) + if ((workColors.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { + $bg = (workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + } else { + $bg = (workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK); + } + $hasBg = true; + } + } + + // Use the override if it exists + workColors.bg = $hasBg ? $bg : workColors.bg; + workColors.fg = $hasFg ? $fg : workColors.fg; + } + + private _isCellSelected(x: number, y: number): boolean { + if (!this._selectionStart) { + return false; + } + y -= this._terminal.buffer.active.viewportY; + if (this._selectionModel.columnSelectMode) { + if (this._selectionModel.startCol <= this._selectionModel.endCol) { + return x >= this._selectionModel.startCol && y >= this._selectionModel.viewportCappedStartRow && + x < this._selectionModel.endCol && y <= this._selectionModel.viewportCappedEndRow; + } + return x < this._selectionModel.startCol && y >= this._selectionModel.viewportCappedStartRow && + x >= this._selectionModel.endCol && y <= this._selectionModel.viewportCappedEndRow; + } + return (y > this._selectionModel.viewportStartRow && y < this._selectionModel.viewportEndRow) || + (this._selectionModel.viewportStartRow === this._selectionModel.viewportEndRow && y === this._selectionModel.viewportStartRow && x >= this._selectionModel.startCol && x < this._selectionModel.endCol) || + (this._selectionModel.viewportStartRow < this._selectionModel.viewportEndRow && y === this._selectionModel.viewportEndRow && x < this._selectionModel.endCol) || + (this._selectionModel.viewportStartRow < this._selectionModel.viewportEndRow && y === this._selectionModel.viewportStartRow && x >= this._selectionModel.startCol); + } + + private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { + const terminal = this._terminal; + + // Selection does not exist + if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { + this._clearSelectionMoidel(); + return; + } + + // Translate from buffer position to viewport position + const viewportStartRow = start[1] - terminal.buffer.active.viewportY; + const viewportEndRow = end[1] - terminal.buffer.active.viewportY; + const viewportCappedStartRow = Math.max(viewportStartRow, 0); + const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); + + // No need to draw the selection + if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { + this._clearSelectionMoidel(); + return; + } + + this._selectionModel.hasSelection = true; + this._selectionModel.columnSelectMode = columnSelectMode; + this._selectionModel.viewportStartRow = viewportStartRow; + this._selectionModel.viewportEndRow = viewportEndRow; + this._selectionModel.viewportCappedStartRow = viewportCappedStartRow; + this._selectionModel.viewportCappedEndRow = viewportCappedEndRow; + this._selectionModel.startCol = start[0]; + this._selectionModel.endCol = end[0]; + } + + private _clearSelectionMoidel(): void { + this._selectionModel.hasSelection = false; + this._selectionModel.viewportStartRow = 0; + this._selectionModel.viewportEndRow = 0; + this._selectionModel.viewportCappedStartRow = 0; + this._selectionModel.viewportCappedEndRow = 0; + this._selectionModel.startCol = 0; + this._selectionModel.endCol = 0; } /** diff --git a/addons/xterm-addon-webgl/src/RenderModel.ts b/addons/xterm-addon-webgl/src/RenderModel.ts index 2969a6d1..ecb25aa9 100644 --- a/addons/xterm-addon-webgl/src/RenderModel.ts +++ b/addons/xterm-addon-webgl/src/RenderModel.ts @@ -3,8 +3,9 @@ * @license MIT */ -import { IRenderModel, ISelectionRenderModel } from './Types'; +import { IRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; +import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; export const RENDER_MODEL_INDICIES_PER_CELL = 4; export const RENDER_MODEL_BG_OFFSET = 1; diff --git a/addons/xterm-addon-webgl/src/Types.d.ts b/addons/xterm-addon-webgl/src/Types.d.ts index d09f43e9..6e3a18fc 100644 --- a/addons/xterm-addon-webgl/src/Types.d.ts +++ b/addons/xterm-addon-webgl/src/Types.d.ts @@ -3,23 +3,14 @@ * @license MIT */ +import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; + export interface IRenderModel { cells: Uint32Array; lineLengths: Uint32Array; selection: ISelectionRenderModel; } -export interface ISelectionRenderModel { - hasSelection: boolean; - columnSelectMode: boolean; - viewportStartRow: number; - viewportEndRow: number; - viewportCappedStartRow: number; - viewportCappedEndRow: number; - startCol: number; - endCol: number; -} - export interface IWebGL2RenderingContext extends WebGLRenderingContext { vertexAttribDivisor(index: number, divisor: number): void; createVertexArray(): IWebGLVertexArrayObject; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index e3e38575..f0ee105c 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -275,9 +275,6 @@ export class WebglRenderer extends Disposable implements IRenderer { } const atlas = acquireTextureAtlas(this._terminal, this._colors, this.dimensions.scaledCellWidth, this.dimensions.scaledCellHeight, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight, this._coreBrowserService.dpr); - if (!('getRasterizedGlyph' in atlas)) { - throw new Error('The webgl renderer only works with the webgl char atlas'); - } if (this._charAtlas !== atlas) { this.onChangeTextureAtlas.fire(atlas.cacheCanvas); } diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 20c1357a..81a025f2 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -139,3 +139,14 @@ export interface IBoundingBox { right: number; bottom: number; } + +export interface ISelectionRenderModel { + hasSelection: boolean; + columnSelectMode: boolean; + viewportStartRow: number; + viewportEndRow: number; + viewportCappedStartRow: number; + viewportCappedEndRow: number; + startCol: number; + endCol: number; +} From 83c09c34dbdd4a9419031ae9daf82ff2eef2fec4 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Oct 2022 14:23:09 -0700 Subject: [PATCH 19/23] Share selection render model code --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 78 +---------------- addons/xterm-addon-webgl/src/RenderModel.ts | 22 +---- addons/xterm-addon-webgl/src/WebglRenderer.ts | 56 +----------- .../renderer/shared/SelectionRenderModel.ts | 85 +++++++++++++++++++ src/browser/renderer/shared/Types.d.ts | 21 +++-- 5 files changed, 106 insertions(+), 156 deletions(-) create mode 100644 src/browser/renderer/shared/SelectionRenderModel.ts diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 586be167..c0e20abd 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -9,6 +9,7 @@ import { TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; import { throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { IRasterizedGlyph, IRenderDimensions, ISelectionRenderModel, ITextureAtlas } from 'browser/renderer/shared/Types'; +import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel'; import { ICoreBrowserService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; @@ -16,7 +17,6 @@ import { Attributes, BgFlags, FgFlags, WHITESPACE_CELL_CODE } from 'common/buffe import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ICellData } from 'common/Types'; import { Terminal } from 'xterm'; -import { IGlyphIdentifier } from './atlas/Types'; import { IRenderLayer } from './Types'; // Work variables to avoid garbage collection @@ -39,16 +39,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _selectionStart: [number, number] | undefined; protected _selectionEnd: [number, number] | undefined; protected _columnSelectMode: boolean = false; - protected _selectionModel: ISelectionRenderModel = { - hasSelection: false, - columnSelectMode: false, - viewportStartRow: 0, - viewportEndRow: 0, - viewportCappedStartRow: 0, - viewportCappedEndRow: 0, - startCol: 0, - endCol: 0 - }; + protected _selectionModel: ISelectionRenderModel = createSelectionRenderModel(); protected _charAtlas!: ITextureAtlas; @@ -99,7 +90,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._selectionStart = start; this._selectionEnd = end; this._columnSelectMode = columnSelectMode; - this._updateSelectionModel(start, end, columnSelectMode); + this._selectionModel.update(this._terminal, start, end, columnSelectMode); } public setColors(colorSet: IColorSet): void { @@ -430,9 +421,8 @@ export abstract class BaseRenderLayer implements IRenderLayer { } }); - // TODO: Selection? // Apply the selection color if needed - $isSelected = this._isCellSelected(x, y); + $isSelected = this._selectionModel.isCellSelected(this._terminal, x, y); if ($isSelected) { $bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; $hasBg = true; @@ -498,66 +488,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { workColors.fg = $hasFg ? $fg : workColors.fg; } - private _isCellSelected(x: number, y: number): boolean { - if (!this._selectionStart) { - return false; - } - y -= this._terminal.buffer.active.viewportY; - if (this._selectionModel.columnSelectMode) { - if (this._selectionModel.startCol <= this._selectionModel.endCol) { - return x >= this._selectionModel.startCol && y >= this._selectionModel.viewportCappedStartRow && - x < this._selectionModel.endCol && y <= this._selectionModel.viewportCappedEndRow; - } - return x < this._selectionModel.startCol && y >= this._selectionModel.viewportCappedStartRow && - x >= this._selectionModel.endCol && y <= this._selectionModel.viewportCappedEndRow; - } - return (y > this._selectionModel.viewportStartRow && y < this._selectionModel.viewportEndRow) || - (this._selectionModel.viewportStartRow === this._selectionModel.viewportEndRow && y === this._selectionModel.viewportStartRow && x >= this._selectionModel.startCol && x < this._selectionModel.endCol) || - (this._selectionModel.viewportStartRow < this._selectionModel.viewportEndRow && y === this._selectionModel.viewportEndRow && x < this._selectionModel.endCol) || - (this._selectionModel.viewportStartRow < this._selectionModel.viewportEndRow && y === this._selectionModel.viewportStartRow && x >= this._selectionModel.startCol); - } - - private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { - const terminal = this._terminal; - - // Selection does not exist - if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { - this._clearSelectionMoidel(); - return; - } - - // Translate from buffer position to viewport position - const viewportStartRow = start[1] - terminal.buffer.active.viewportY; - const viewportEndRow = end[1] - terminal.buffer.active.viewportY; - const viewportCappedStartRow = Math.max(viewportStartRow, 0); - const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); - - // No need to draw the selection - if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { - this._clearSelectionMoidel(); - return; - } - - this._selectionModel.hasSelection = true; - this._selectionModel.columnSelectMode = columnSelectMode; - this._selectionModel.viewportStartRow = viewportStartRow; - this._selectionModel.viewportEndRow = viewportEndRow; - this._selectionModel.viewportCappedStartRow = viewportCappedStartRow; - this._selectionModel.viewportCappedEndRow = viewportCappedEndRow; - this._selectionModel.startCol = start[0]; - this._selectionModel.endCol = end[0]; - } - - private _clearSelectionMoidel(): void { - this._selectionModel.hasSelection = false; - this._selectionModel.viewportStartRow = 0; - this._selectionModel.viewportEndRow = 0; - this._selectionModel.viewportCappedStartRow = 0; - this._selectionModel.viewportCappedEndRow = 0; - this._selectionModel.startCol = 0; - this._selectionModel.endCol = 0; - } - /** * Clips a row to ensure no pixels will be drawn outside the cells in the row. * @param y The row to clip. diff --git a/addons/xterm-addon-webgl/src/RenderModel.ts b/addons/xterm-addon-webgl/src/RenderModel.ts index ecb25aa9..b1542d98 100644 --- a/addons/xterm-addon-webgl/src/RenderModel.ts +++ b/addons/xterm-addon-webgl/src/RenderModel.ts @@ -6,6 +6,7 @@ import { IRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; +import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel'; export const RENDER_MODEL_INDICIES_PER_CELL = 4; export const RENDER_MODEL_BG_OFFSET = 1; @@ -22,16 +23,7 @@ export class RenderModel implements IRenderModel { constructor() { this.cells = new Uint32Array(0); this.lineLengths = new Uint32Array(0); - this.selection = { - hasSelection: false, - columnSelectMode: false, - viewportStartRow: 0, - viewportEndRow: 0, - viewportCappedStartRow: 0, - viewportCappedEndRow: 0, - startCol: 0, - endCol: 0 - }; + this.selection = createSelectionRenderModel(); } public resize(cols: number, rows: number): void { @@ -46,14 +38,4 @@ export class RenderModel implements IRenderModel { fill(this.cells, 0, 0); fill(this.lineLengths, 0, 0); } - - public clearSelection(): void { - this.selection.hasSelection = false; - this.selection.viewportStartRow = 0; - this.selection.viewportEndRow = 0; - this.selection.viewportCappedStartRow = 0; - this.selection.viewportCappedEndRow = 0; - this.selection.startCol = 0; - this.selection.endCol = 0; - } } diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index f0ee105c..d7512908 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -229,7 +229,7 @@ export class WebglRenderer extends Disposable implements IRenderer { for (const l of this._renderLayers) { l.onSelectionChanged(this._terminal, start, end, columnSelectMode); } - this._updateSelectionModel(start, end, columnSelectMode); + this._model.selection.update(this._terminal, start, end, columnSelectMode); this._requestRedrawViewport(); } @@ -336,7 +336,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Tell renderer the frame is beginning if (this._glyphRenderer.beginFrame()) { this._clearModel(true); - this._updateSelectionModel(undefined, undefined); + this._model.selection.clear(); } // Update model to reflect what's drawn @@ -484,7 +484,7 @@ export class WebglRenderer extends Disposable implements IRenderer { }); // Apply the selection color if needed - $isSelected = this._isCellSelected(x, y); + $isSelected = this._model.selection.isCellSelected(this._terminal, x, y); if ($isSelected) { $bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; $hasBg = true; @@ -550,56 +550,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._workColors.fg = $hasFg ? $fg : this._workColors.fg; } - private _isCellSelected(x: number, y: number): boolean { - if (!this._model.selection.hasSelection) { - return false; - } - y -= this._terminal.buffer.active.viewportY; - if (this._model.selection.columnSelectMode) { - if (this._model.selection.startCol <= this._model.selection.endCol) { - return x >= this._model.selection.startCol && y >= this._model.selection.viewportCappedStartRow && - x < this._model.selection.endCol && y <= this._model.selection.viewportCappedEndRow; - } - return x < this._model.selection.startCol && y >= this._model.selection.viewportCappedStartRow && - x >= this._model.selection.endCol && y <= this._model.selection.viewportCappedEndRow; - } - return (y > this._model.selection.viewportStartRow && y < this._model.selection.viewportEndRow) || - (this._model.selection.viewportStartRow === this._model.selection.viewportEndRow && y === this._model.selection.viewportStartRow && x >= this._model.selection.startCol && x < this._model.selection.endCol) || - (this._model.selection.viewportStartRow < this._model.selection.viewportEndRow && y === this._model.selection.viewportEndRow && x < this._model.selection.endCol) || - (this._model.selection.viewportStartRow < this._model.selection.viewportEndRow && y === this._model.selection.viewportStartRow && x >= this._model.selection.startCol); - } - - private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { - const terminal = this._terminal; - - // Selection does not exist - if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { - this._model.clearSelection(); - return; - } - - // Translate from buffer position to viewport position - const viewportStartRow = start[1] - terminal.buffer.active.viewportY; - const viewportEndRow = end[1] - terminal.buffer.active.viewportY; - const viewportCappedStartRow = Math.max(viewportStartRow, 0); - const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); - - // No need to draw the selection - if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { - this._model.clearSelection(); - return; - } - - this._model.selection.hasSelection = true; - this._model.selection.columnSelectMode = columnSelectMode; - this._model.selection.viewportStartRow = viewportStartRow; - this._model.selection.viewportEndRow = viewportEndRow; - this._model.selection.viewportCappedStartRow = viewportCappedStartRow; - this._model.selection.viewportCappedEndRow = viewportCappedEndRow; - this._model.selection.startCol = start[0]; - this._model.selection.endCol = end[0]; - } - /** * Recalculates the character and canvas dimensions. */ diff --git a/src/browser/renderer/shared/SelectionRenderModel.ts b/src/browser/renderer/shared/SelectionRenderModel.ts new file mode 100644 index 00000000..8a6e8b85 --- /dev/null +++ b/src/browser/renderer/shared/SelectionRenderModel.ts @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2022 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; +import { Terminal } from 'xterm'; + +class SelectionRenderModel implements ISelectionRenderModel { + public hasSelection!: boolean; + public columnSelectMode!: boolean; + public viewportStartRow!: number; + public viewportEndRow!: number; + public viewportCappedStartRow!: number; + public viewportCappedEndRow!: number; + public startCol!: number; + public endCol!: number; + + constructor() { + this.clear(); + } + + public clear(): void { + this.hasSelection = false; + this.columnSelectMode = false; + this.viewportStartRow = 0; + this.viewportEndRow = 0; + this.viewportCappedStartRow = 0; + this.viewportCappedEndRow = 0; + this.startCol = 0; + this.endCol = 0; + } + + public update(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { + // Selection does not exist + if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { + this.clear(); + return; + } + + // Translate from buffer position to viewport position + const viewportStartRow = start[1] - terminal.buffer.active.viewportY; + const viewportEndRow = end[1] - terminal.buffer.active.viewportY; + const viewportCappedStartRow = Math.max(viewportStartRow, 0); + const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); + + // No need to draw the selection + if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { + this.clear(); + return; + } + + this.hasSelection = true; + this.columnSelectMode = columnSelectMode; + this.viewportStartRow = viewportStartRow; + this.viewportEndRow = viewportEndRow; + this.viewportCappedStartRow = viewportCappedStartRow; + this.viewportCappedEndRow = viewportCappedEndRow; + this.startCol = start[0]; + this.endCol = end[0]; + } + + public isCellSelected(terminal: Terminal, x: number, y: number): boolean { + if (!this.hasSelection) { + return false; + } + y -= terminal.buffer.active.viewportY; + if (this.columnSelectMode) { + if (this.startCol <= this.endCol) { + return x >= this.startCol && y >= this.viewportCappedStartRow && + x < this.endCol && y <= this.viewportCappedEndRow; + } + return x < this.startCol && y >= this.viewportCappedStartRow && + x >= this.endCol && y <= this.viewportCappedEndRow; + } + return (y > this.viewportStartRow && y < this.viewportEndRow) || + (this.viewportStartRow === this.viewportEndRow && y === this.viewportStartRow && x >= this.startCol && x < this.endCol) || + (this.viewportStartRow < this.viewportEndRow && y === this.viewportEndRow && x < this.endCol) || + (this.viewportStartRow < this.viewportEndRow && y === this.viewportStartRow && x >= this.startCol); + } +} + +export function createSelectionRenderModel(): ISelectionRenderModel { + return new SelectionRenderModel(); +} diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 81a025f2..5e29411a 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { FontWeight } from 'xterm'; +import { FontWeight, Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; import { IDisposable } from 'common/Types'; import { IEvent } from 'common/EventEmitter'; @@ -141,12 +141,15 @@ export interface IBoundingBox { } export interface ISelectionRenderModel { - hasSelection: boolean; - columnSelectMode: boolean; - viewportStartRow: number; - viewportEndRow: number; - viewportCappedStartRow: number; - viewportCappedEndRow: number; - startCol: number; - endCol: number; + readonly hasSelection: boolean; + readonly columnSelectMode: boolean; + readonly viewportStartRow: number; + readonly viewportEndRow: number; + readonly viewportCappedStartRow: number; + readonly viewportCappedEndRow: number; + readonly startCol: number; + readonly endCol: number; + clear(): void; + update(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode?: boolean): void; + isCellSelected(terminal: Terminal, x: number, y: number): boolean; } From 0d7e18d236a85df88b60e9b528863f1a4be5b4fc Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Oct 2022 14:41:27 -0700 Subject: [PATCH 20/23] Add CellColorResolver to share load color code --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 108 +---------- addons/xterm-addon-webgl/src/WebglRenderer.ts | 170 ++++-------------- .../renderer/shared/CellColorResolver.ts | 136 ++++++++++++++ 3 files changed, 177 insertions(+), 237 deletions(-) create mode 100644 src/browser/renderer/shared/CellColorResolver.ts diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index c0e20abd..ffea9f0e 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -18,6 +18,7 @@ import { IBufferService, IDecorationService, IOptionsService } from 'common/serv import { ICellData } from 'common/Types'; import { Terminal } from 'xterm'; import { IRenderLayer } from './Types'; +import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; // Work variables to avoid garbage collection let $fg = 0; @@ -40,6 +41,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _selectionEnd: [number, number] | undefined; protected _columnSelectMode: boolean = false; protected _selectionModel: ISelectionRenderModel = createSelectionRenderModel(); + private _cellColorResolver: CellColorResolver; protected _charAtlas!: ITextureAtlas; @@ -58,6 +60,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected readonly _decorationService: IDecorationService, protected readonly _coreBrowserService: ICoreBrowserService ) { + this._cellColorResolver = new CellColorResolver(this._terminal, this._colors, this._selectionModel, this._decorationService, this._coreBrowserService); this._canvas = document.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); this._canvas.style.zIndex = zIndex.toString(); @@ -359,20 +362,18 @@ export abstract class BaseRenderLayer implements IRenderLayer { } } - private _workColors: { fg: number, bg: number, ext: number } = { fg: 0, bg: 0, ext: 0 }; - /** * Draws one or more characters at a cell. If possible this will draw using * the character atlas to reduce draw time. */ protected _drawChars(cell: ICellData, x: number, y: number): void { const chars = cell.getChars(); - this._loadColorsForCell(x, y, cell, this._workColors); + this._cellColorResolver.resolve(cell, x, y); let glyph: IRasterizedGlyph; if (chars && chars.length > 1) { - glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._workColors.bg, this._workColors.fg, this._workColors.ext); + glyph = this._charAtlas.getRasterizedGlyphCombinedChar(chars, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext); } else { - glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._workColors.bg, this._workColors.fg, this._workColors.ext); + glyph = this._charAtlas.getRasterizedGlyph(cell.getCode() || WHITESPACE_CELL_CODE, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext); } this._ctx.save(); this._clipRow(y); @@ -391,103 +392,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { // TODO: Move both renderers to use shared load color code } - /** - * Loads colors for the cell into the work colors object. This resolves overrides/inverse if - * necessary which is why the work cell object is not used. - */ - private _loadColorsForCell(x: number, y: number, cell: ICellData, workColors: { fg: number, bg: number, ext: number }): void { - workColors.bg = cell.bg; - workColors.fg = cell.fg; - workColors.ext = cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0; - // Get any foreground/background overrides, this happens on the model to avoid spreading - // override logic throughout the different sub-renderers - - // Reset overrides work variables - $bg = 0; - $fg = 0; - $hasBg = false; - $hasFg = false; - $isSelected = false; - - // Apply decorations on the bottom layer - this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => { - if (d.backgroundColorRGB) { - $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; - $hasBg = true; - } - if (d.foregroundColorRGB) { - $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; - $hasFg = true; - } - }); - - // Apply the selection color if needed - $isSelected = this._selectionModel.isCellSelected(this._terminal, x, y); - if ($isSelected) { - $bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; - $hasBg = true; - if (this._colors.selectionForeground) { - $fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; - $hasFg = true; - } - } - - // Apply decorations on the top layer - this._decorationService.forEachDecorationAtCell(x, y, 'top', d => { - if (d.backgroundColorRGB) { - $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; - $hasBg = true; - } - if (d.foregroundColorRGB) { - $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; - $hasFg = true; - } - }); - - // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag - // ahead of time in order to use the correct cache key - if ($hasBg) { - if ($isSelected) { - // Non-RGB attributes from model + force non-dim + override + force RGB color mode - $bg = (cell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | $bg | Attributes.CM_RGB; - } else { - // Non-RGB attributes from model + override + force RGB color mode - $bg = (cell.bg & ~Attributes.RGB_MASK) | $bg | Attributes.CM_RGB; - } - } - if ($hasFg) { - // Non-RGB attributes from model + force disable inverse + override + force RGB color mode - $fg = (cell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | $fg | Attributes.CM_RGB; - } - - // Handle case where inverse was specified by only one of bg override or fg override was set, - // resolving the other inverse color and setting the inverse flag if needed. - if (workColors.fg & FgFlags.INVERSE) { - if ($hasBg && !$hasFg) { - // Resolve bg color type (default color has a different meaning in fg vs bg) - if ((workColors.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - $fg = (workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; - } else { - $fg = (workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK); - } - $hasFg = true; - } - if (!$hasBg && $hasFg) { - // Resolve bg color type (default color has a different meaning in fg vs bg) - if ((workColors.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - $bg = (workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; - } else { - $bg = (workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK); - } - $hasBg = true; - } - } - - // Use the override if it exists - workColors.bg = $hasBg ? $bg : workColors.bg; - workColors.fg = $hasFg ? $fg : workColors.fg; - } - /** * Clips a row to ensure no pixels will be drawn outside the cells in the row. * @param y The row to clip. diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index d7512908..c7511946 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -3,34 +3,28 @@ * @license MIT */ -import { GlyphRenderer } from './GlyphRenderer'; -import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; -import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; -import { acquireTextureAtlas, removeTerminalFromCache } from 'browser/renderer/shared/CharAtlasCache'; -import { RectangleRenderer } from './RectangleRenderer'; -import { IWebGL2RenderingContext } from './Types'; -import { RenderModel, COMBINED_CHAR_BIT_MASK, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; -import { Disposable } from 'common/Lifecycle'; -import { Attributes, BgFlags, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; -import { Terminal, IEvent } from 'xterm'; -import { IRenderLayer } from './renderLayer/Types'; -import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ITextureAtlas } from 'browser/renderer/shared/Types'; -import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver'; -import { ITerminal, IColorSet } from 'browser/Types'; -import { EventEmitter, initEvent } from 'common/EventEmitter'; -import { CellData } from 'common/buffer/CellData'; import { addDisposableDomListener } from 'browser/Lifecycle'; +import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; +import { acquireTextureAtlas, removeTerminalFromCache } from 'browser/renderer/shared/CharAtlasCache'; +import { observeDevicePixelDimensions } from 'browser/renderer/shared/DevicePixelObserver'; +import { IRenderDimensions, IRenderer, IRequestRedrawEvent, ITextureAtlas } from 'browser/renderer/shared/Types'; import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/Services'; -import { CharData, IBufferLine, ICellData } from 'common/Types'; +import { IColorSet, ITerminal } from 'browser/Types'; import { AttributeData } from 'common/buffer/AttributeData'; +import { CellData } from 'common/buffer/CellData'; +import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants'; +import { initEvent } from 'common/EventEmitter'; +import { Disposable } from 'common/Lifecycle'; import { ICoreService, IDecorationService } from 'common/services/Services'; - -// Work variables to avoid garbage collection -let $fg = 0; -let $bg = 0; -let $hasFg = false; -let $hasBg = false; -let $isSelected = false; +import { CharData, IBufferLine, ICellData } from 'common/Types'; +import { Terminal } from 'xterm'; +import { GlyphRenderer } from './GlyphRenderer'; +import { RectangleRenderer } from './RectangleRenderer'; +import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; +import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; +import { IRenderLayer } from './renderLayer/Types'; +import { COMBINED_CHAR_BIT_MASK, RenderModel, RENDER_MODEL_BG_OFFSET, RENDER_MODEL_EXT_OFFSET, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_INDICIES_PER_CELL } from './RenderModel'; +import { IWebGL2RenderingContext } from './Types'; export class WebglRenderer extends Disposable implements IRenderer { private _renderLayers: IRenderLayer[]; @@ -39,7 +33,7 @@ export class WebglRenderer extends Disposable implements IRenderer { private _model: RenderModel = new RenderModel(); private _workCell: CellData = new CellData(); - private _workColors: { fg: number, bg: number, ext: number } = { fg: 0, bg: 0, ext: 0 }; + private _cellColorResolver: CellColorResolver; private _canvas: HTMLCanvasElement; private _gl: IWebGL2RenderingContext; @@ -67,6 +61,8 @@ export class WebglRenderer extends Disposable implements IRenderer { ) { super(); + this._cellColorResolver = new CellColorResolver(this._terminal, this._colors, this._model.selection, this._decorationService, this._coreBrowserService); + this._core = (this._terminal as any)._core; this._renderLayers = [ @@ -155,6 +151,7 @@ export class WebglRenderer extends Disposable implements IRenderer { l.reset(this._terminal); } + this._cellColorResolver.setColors(colors); this._rectangleRenderer.setColors(); this._refreshCharAtlas(); @@ -372,11 +369,11 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.lineLengths[y] = 0; joinedRanges = this._characterJoinerService.getJoinedCharacters(row); for (x = 0; x < terminal.cols; x++) { - lastBg = this._workColors.bg; + lastBg = this._cellColorResolver.result.bg; line.loadCell(x, cell); if (x === 0) { - lastBg = this._workColors.bg; + lastBg = this._cellColorResolver.result.bg; } // If true, indicates that the current character(s) to draw were joined. @@ -407,7 +404,7 @@ export class WebglRenderer extends Disposable implements IRenderer { i = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; // Load colors/resolve overrides into work colors - this._loadColorsForCell(x, row); + this._cellColorResolver.resolve(cell, x, row); if (code !== NULL_CELL_CODE) { this._model.lineLengths[y] = x + 1; @@ -415,9 +412,9 @@ export class WebglRenderer extends Disposable implements IRenderer { // Nothing has changed, no updates needed if (this._model.cells[i] === code && - this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workColors.bg && - this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workColors.fg && - this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._workColors.ext) { + this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._cellColorResolver.result.bg && + this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._cellColorResolver.result.fg && + this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._cellColorResolver.result.ext) { continue; } @@ -428,11 +425,11 @@ export class WebglRenderer extends Disposable implements IRenderer { // Cache the results in the model this._model.cells[i] = code; - this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._workColors.bg; - this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._workColors.fg; - this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._workColors.ext; + this._model.cells[i + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg; + this._model.cells[i + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; + this._model.cells[i + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext; - this._glyphRenderer.updateCell(x, y, code, this._workColors.bg, this._workColors.fg, this._workColors.ext, chars, lastBg); + this._glyphRenderer.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, lastBg); if (isJoined) { // Restore work cell @@ -443,9 +440,9 @@ export class WebglRenderer extends Disposable implements IRenderer { j = ((y * terminal.cols) + x) * RENDER_MODEL_INDICIES_PER_CELL; this._glyphRenderer.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0); this._model.cells[j] = NULL_CELL_CODE; - this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._workColors.bg; - this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._workColors.fg; - this._model.cells[j + RENDER_MODEL_EXT_OFFSET] = this._workColors.ext; + this._model.cells[j + RENDER_MODEL_BG_OFFSET] = this._cellColorResolver.result.bg; + this._model.cells[j + RENDER_MODEL_FG_OFFSET] = this._cellColorResolver.result.fg; + this._model.cells[j + RENDER_MODEL_EXT_OFFSET] = this._cellColorResolver.result.ext; } } } @@ -453,103 +450,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._rectangleRenderer.updateBackgrounds(this._model); } - /** - * Loads colors for the cell into the work colors object. This resolves overrides/inverse if - * necessary which is why the work cell object is not used. - */ - private _loadColorsForCell(x: number, y: number): void { - this._workColors.bg = this._workCell.bg; - this._workColors.fg = this._workCell.fg; - this._workColors.ext = this._workCell.bg & BgFlags.HAS_EXTENDED ? this._workCell.extended.ext : 0; - // Get any foreground/background overrides, this happens on the model to avoid spreading - // override logic throughout the different sub-renderers - - // Reset overrides work variables - $bg = 0; - $fg = 0; - $hasBg = false; - $hasFg = false; - $isSelected = false; - - // Apply decorations on the bottom layer - this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => { - if (d.backgroundColorRGB) { - $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; - $hasBg = true; - } - if (d.foregroundColorRGB) { - $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; - $hasFg = true; - } - }); - - // Apply the selection color if needed - $isSelected = this._model.selection.isCellSelected(this._terminal, x, y); - if ($isSelected) { - $bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; - $hasBg = true; - if (this._colors.selectionForeground) { - $fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; - $hasFg = true; - } - } - - // Apply decorations on the top layer - this._decorationService.forEachDecorationAtCell(x, y, 'top', d => { - if (d.backgroundColorRGB) { - $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; - $hasBg = true; - } - if (d.foregroundColorRGB) { - $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; - $hasFg = true; - } - }); - - // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag - // ahead of time in order to use the correct cache key - if ($hasBg) { - if ($isSelected) { - // Non-RGB attributes from model + force non-dim + override + force RGB color mode - $bg = (this._workCell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | $bg | Attributes.CM_RGB; - } else { - // Non-RGB attributes from model + override + force RGB color mode - $bg = (this._workCell.bg & ~Attributes.RGB_MASK) | $bg | Attributes.CM_RGB; - } - } - if ($hasFg) { - // Non-RGB attributes from model + force disable inverse + override + force RGB color mode - $fg = (this._workCell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | $fg | Attributes.CM_RGB; - } - - // Handle case where inverse was specified by only one of bg override or fg override was set, - // resolving the other inverse color and setting the inverse flag if needed. - if (this._workColors.fg & FgFlags.INVERSE) { - if ($hasBg && !$hasFg) { - // Resolve bg color type (default color has a different meaning in fg vs bg) - if ((this._workColors.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - $fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; - } else { - $fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this._workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK); - } - $hasFg = true; - } - if (!$hasBg && $hasFg) { - // Resolve bg color type (default color has a different meaning in fg vs bg) - if ((this._workColors.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - $bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; - } else { - $bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this._workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK); - } - $hasBg = true; - } - } - - // Use the override if it exists - this._workColors.bg = $hasBg ? $bg : this._workColors.bg; - this._workColors.fg = $hasFg ? $fg : this._workColors.fg; - } - /** * Recalculates the character and canvas dimensions. */ diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts new file mode 100644 index 00000000..b29475b9 --- /dev/null +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -0,0 +1,136 @@ +import { ISelectionRenderModel } from 'browser/renderer/shared/Types'; +import { ICoreBrowserService } from 'browser/services/Services'; +import { IColorSet } from 'browser/Types'; +import { Attributes, BgFlags, FgFlags } from 'common/buffer/Constants'; +import { IDecorationService } from 'common/services/Services'; +import { ICellData } from 'common/Types'; +import { Terminal } from 'xterm'; + +// Work variables to avoid garbage collection +let $fg = 0; +let $bg = 0; +let $hasFg = false; +let $hasBg = false; +let $isSelected = false; + +export class CellColorResolver { + /** + * The shared result of the {@link resolve} call. This is only safe to use immediately after as + * any other calls will share object. + */ + public readonly result: { fg: number, bg: number, ext: number } = { + fg: 0, + bg: 0, + ext: 0 + }; + + constructor( + private readonly _terminal: Terminal, + private _colors: IColorSet, + private readonly _selectionRenderModel: ISelectionRenderModel, + private readonly _decorationService: IDecorationService, + private readonly _coreBrowserService: ICoreBrowserService + ) { + } + + public setColors(colors: IColorSet): void { + this._colors = colors; + } + + /** + * Resolves colors for the cell, putting the result into the shared {@link result}. This resolves + * overrides, inverse and selection for the cell which can then be used to feed into the renderer. + */ + public resolve(cell: ICellData, x: number, y: number): void { + this.result.bg = cell.bg; + this.result.fg = cell.fg; + this.result.ext = cell.bg & BgFlags.HAS_EXTENDED ? cell.extended.ext : 0; + // Get any foreground/background overrides, this happens on the model to avoid spreading + // override logic throughout the different sub-renderers + + // Reset overrides work variables + $bg = 0; + $fg = 0; + $hasBg = false; + $hasFg = false; + $isSelected = false; + + // Apply decorations on the bottom layer + this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => { + if (d.backgroundColorRGB) { + $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasBg = true; + } + if (d.foregroundColorRGB) { + $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasFg = true; + } + }); + + // Apply the selection color if needed + $isSelected = this._selectionRenderModel.isCellSelected(this._terminal, x, y); + if ($isSelected) { + $bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; + $hasBg = true; + if (this._colors.selectionForeground) { + $fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF; + $hasFg = true; + } + } + + // Apply decorations on the top layer + this._decorationService.forEachDecorationAtCell(x, y, 'top', d => { + if (d.backgroundColorRGB) { + $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasBg = true; + } + if (d.foregroundColorRGB) { + $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $hasFg = true; + } + }); + + // Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag + // ahead of time in order to use the correct cache key + if ($hasBg) { + if ($isSelected) { + // Non-RGB attributes from model + force non-dim + override + force RGB color mode + $bg = (cell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | $bg | Attributes.CM_RGB; + } else { + // Non-RGB attributes from model + override + force RGB color mode + $bg = (cell.bg & ~Attributes.RGB_MASK) | $bg | Attributes.CM_RGB; + } + } + if ($hasFg) { + // Non-RGB attributes from model + force disable inverse + override + force RGB color mode + $fg = (cell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | $fg | Attributes.CM_RGB; + } + + // Handle case where inverse was specified by only one of bg override or fg override was set, + // resolving the other inverse color and setting the inverse flag if needed. + if (this.result.fg & FgFlags.INVERSE) { + if ($hasBg && !$hasFg) { + // Resolve bg color type (default color has a different meaning in fg vs bg) + if ((this.result.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { + $fg = (this.result.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + } else { + $fg = (this.result.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this.result.bg & (Attributes.RGB_MASK | Attributes.CM_MASK); + } + $hasFg = true; + } + if (!$hasBg && $hasFg) { + // Resolve bg color type (default color has a different meaning in fg vs bg) + if ((this.result.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { + $bg = (this.result.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + } else { + $bg = (this.result.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this.result.fg & (Attributes.RGB_MASK | Attributes.CM_MASK); + } + $hasBg = true; + } + } + + // Use the override if it exists + this.result.bg = $hasBg ? $bg : this.result.bg; + this.result.fg = $hasFg ? $fg : this.result.fg; + } +} From 622acaaad75a776f317bacc8d623169699a3327d Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Oct 2022 14:45:19 -0700 Subject: [PATCH 21/23] Remove old selection start/end values --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 16 +--------------- .../src/SelectionRenderLayer.ts | 8 ++++---- .../renderer/shared/SelectionRenderModel.ts | 6 ++++++ src/browser/renderer/shared/Types.d.ts | 2 ++ 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index ffea9f0e..89975241 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -13,20 +13,13 @@ import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRen import { ICoreBrowserService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; -import { Attributes, BgFlags, FgFlags, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; +import { WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { IBufferService, IDecorationService, IOptionsService } from 'common/services/Services'; import { ICellData } from 'common/Types'; import { Terminal } from 'xterm'; import { IRenderLayer } from './Types'; import { CellColorResolver } from 'browser/renderer/shared/CellColorResolver'; -// Work variables to avoid garbage collection -let $fg = 0; -let $bg = 0; -let $hasFg = false; -let $hasBg = false; -let $isSelected = false; - export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; protected _ctx!: CanvasRenderingContext2D; @@ -37,9 +30,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { private _scaledCharLeft: number = 0; private _scaledCharTop: number = 0; - protected _selectionStart: [number, number] | undefined; - protected _selectionEnd: [number, number] | undefined; - protected _columnSelectMode: boolean = false; protected _selectionModel: ISelectionRenderModel = createSelectionRenderModel(); private _cellColorResolver: CellColorResolver; @@ -89,10 +79,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { public onGridChanged(startRow: number, endRow: number): void {} public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { - // TODO: Remove these other variables in favor of the selection model - this._selectionStart = start; - this._selectionEnd = end; - this._columnSelectMode = columnSelectMode; this._selectionModel.update(this._terminal, start, end, columnSelectMode); } diff --git a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts index 6a803342..759b0a12 100644 --- a/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/SelectionRenderLayer.ts @@ -47,8 +47,8 @@ export class SelectionRenderLayer extends BaseRenderLayer { super.resize(dim); // On resize use the base render layer's cached selection values since resize clears _state // inside reset. - if (this._selectionStart && this._selectionEnd) { - this._redrawSelection(this._selectionStart, this._selectionEnd, this._columnSelectMode); + if (this._selectionModel.selectionStart && this._selectionModel.selectionEnd) { + this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode); } } @@ -61,12 +61,12 @@ export class SelectionRenderLayer extends BaseRenderLayer { public onBlur(): void { this.reset(); - this._redrawSelection(this._selectionStart, this._selectionEnd, this._columnSelectMode); + this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode); } public onFocus(): void { this.reset(); - this._redrawSelection(this._selectionStart, this._selectionEnd, this._columnSelectMode); + this._redrawSelection(this._selectionModel.selectionStart, this._selectionModel.selectionEnd, this._selectionModel.columnSelectMode); } public onSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void { diff --git a/src/browser/renderer/shared/SelectionRenderModel.ts b/src/browser/renderer/shared/SelectionRenderModel.ts index 8a6e8b85..db375778 100644 --- a/src/browser/renderer/shared/SelectionRenderModel.ts +++ b/src/browser/renderer/shared/SelectionRenderModel.ts @@ -15,6 +15,8 @@ class SelectionRenderModel implements ISelectionRenderModel { public viewportCappedEndRow!: number; public startCol!: number; public endCol!: number; + public selectionStart: [number, number] | undefined; + public selectionEnd: [number, number] | undefined; constructor() { this.clear(); @@ -29,9 +31,13 @@ class SelectionRenderModel implements ISelectionRenderModel { this.viewportCappedEndRow = 0; this.startCol = 0; this.endCol = 0; + this.selectionStart = undefined; + this.selectionEnd = undefined; } public update(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { + this.selectionStart = start; + this.selectionEnd = end; // Selection does not exist if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { this.clear(); diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 5e29411a..7410b175 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -149,6 +149,8 @@ export interface ISelectionRenderModel { readonly viewportCappedEndRow: number; readonly startCol: number; readonly endCol: number; + readonly selectionStart: [number, number] | undefined; + readonly selectionEnd: [number, number] | undefined; clear(): void; update(terminal: Terminal, start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode?: boolean): void; isCellSelected(terminal: Terminal, x: number, y: number): boolean; From 3b260a219302eaab0e590724253528234aab4cf8 Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Oct 2022 14:46:00 -0700 Subject: [PATCH 22/23] Fix resolved todo --- addons/xterm-addon-canvas/src/BaseRenderLayer.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index 89975241..ba385449 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -375,7 +375,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { glyph.size.y ); this._ctx.restore(); - // TODO: Move both renderers to use shared load color code } /** From e0324e32a8313a33b1333c6f61b9246e9ace1cda Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 4 Oct 2022 15:24:10 -0700 Subject: [PATCH 23/23] Bring back ImageBitmap optimization in canvas renderer It's more simple than it used to be, it will clear and debounce all glyphs when any change is made. --- .../xterm-addon-canvas/src/BaseRenderLayer.ts | 59 ++++++++++++++++++- src/browser/renderer/shared/Types.d.ts | 1 - 2 files changed, 58 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts index ba385449..2a9e09ba 100644 --- a/addons/xterm-addon-canvas/src/BaseRenderLayer.ts +++ b/addons/xterm-addon-canvas/src/BaseRenderLayer.ts @@ -32,6 +32,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { protected _selectionModel: ISelectionRenderModel = createSelectionRenderModel(); private _cellColorResolver: CellColorResolver; + private _bitmapGenerator?: BitmapGenerator; protected _charAtlas!: ITextureAtlas; @@ -115,6 +116,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { } this._charAtlas = acquireTextureAtlas(this._terminal, colorSet, this._scaledCellWidth, this._scaledCellHeight, this._scaledCharWidth, this._scaledCharHeight, this._coreBrowserService.dpr); this._charAtlas.warmUp(); + this._bitmapGenerator = new BitmapGenerator(this._charAtlas.cacheCanvas); } public resize(dim: IRenderDimensions): void { @@ -353,6 +355,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { * the character atlas to reduce draw time. */ protected _drawChars(cell: ICellData, x: number, y: number): void { + if (this._charAtlas.hasCanvasChanged) { + this._bitmapGenerator?.refresh(); + this._charAtlas.hasCanvasChanged = false; + } const chars = cell.getChars(); this._cellColorResolver.resolve(cell, x, y); let glyph: IRasterizedGlyph; @@ -363,8 +369,9 @@ export abstract class BaseRenderLayer implements IRenderLayer { } this._ctx.save(); this._clipRow(y); + // Draw the image, use the bitmap if it's available this._ctx.drawImage( - this._charAtlas!.cacheCanvas, + this._bitmapGenerator?.bitmap || this._charAtlas!.cacheCanvas, glyph.texturePosition.x, glyph.texturePosition.y, glyph.size.x, @@ -403,3 +410,53 @@ export abstract class BaseRenderLayer implements IRenderLayer { } } +/** + * The number of milliseconds to wait before generating the ImageBitmap, this is to debounce/batch + * the operation as window.createImageBitmap is asynchronous. + */ +const GLYPH_BITMAP_COMMIT_DELAY = 100; + +const enum BitmapGeneratorState { + IDLE = 0, + GENERATING = 1, + GENERATING_INVALID = 2 +} + +class BitmapGenerator { + private _state: BitmapGeneratorState = BitmapGeneratorState.IDLE; + private _commitTimeout: number | undefined = undefined; + private _bitmap: ImageBitmap | undefined = undefined; + public get bitmap(): ImageBitmap | undefined { return this._bitmap; } + + constructor(private readonly _canvas: HTMLCanvasElement) { + } + + public refresh(): void { + // Clear the bitmap immediately as it's stale + this._bitmap = undefined; + if (this._commitTimeout === undefined) { + this._commitTimeout = window.setTimeout(() => this._generate(), GLYPH_BITMAP_COMMIT_DELAY); + } + if (this._state === BitmapGeneratorState.GENERATING) { + this._state = BitmapGeneratorState.GENERATING_INVALID; + } + } + + private _generate(): void { + if (this._state === BitmapGeneratorState.IDLE) { + this._bitmap = undefined; + this._state = BitmapGeneratorState.GENERATING; + window.createImageBitmap(this._canvas).then(bitmap => { + if (this._state === BitmapGeneratorState.GENERATING_INVALID) { + this.refresh(); + } else { + this._bitmap = bitmap; + } + this._state = BitmapGeneratorState.IDLE; + }); + if (this._commitTimeout) { + this._commitTimeout = undefined; + } + } + } +} diff --git a/src/browser/renderer/shared/Types.d.ts b/src/browser/renderer/shared/Types.d.ts index 7410b175..7e2badcb 100644 --- a/src/browser/renderer/shared/Types.d.ts +++ b/src/browser/renderer/shared/Types.d.ts @@ -77,7 +77,6 @@ export interface IRenderer extends IDisposable { export interface ITextureAtlas extends IDisposable { readonly cacheCanvas: HTMLCanvasElement; - // TODO: Add endFrame and make this readonly hasCanvasChanged: boolean; /**