From c97feab05cc1cd532b4e67171c0a8823e5984b7f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 16 Nov 2018 14:22:43 -0800 Subject: [PATCH 01/69] Initial webgl renderer implementation --- demo/client.ts | 8 +- src/Terminal.ts | 3 + src/Types.ts | 1 + src/renderer/atlas/CharAtlasCache.ts | 11 +- src/renderer/atlas/CharAtlasUtils.ts | 4 +- src/renderer/webgl/GlyphRenderer.ts | 343 +++++++++++++++++++ src/renderer/webgl/RectangleRenderer.ts | 320 ++++++++++++++++++ src/renderer/webgl/RenderModel.ts | 59 ++++ src/renderer/webgl/Types.ts | 75 +++++ src/renderer/webgl/WebglCharAtlas.ts | 386 +++++++++++++++++++++ src/renderer/webgl/WebglRenderer.ts | 427 ++++++++++++++++++++++++ src/renderer/webgl/WebglUtils.ts | 51 +++ src/shared/atlas/Types.ts | 2 +- typings/xterm.d.ts | 6 +- 14 files changed, 1683 insertions(+), 13 deletions(-) create mode 100644 src/renderer/webgl/GlyphRenderer.ts create mode 100644 src/renderer/webgl/RectangleRenderer.ts create mode 100644 src/renderer/webgl/RenderModel.ts create mode 100644 src/renderer/webgl/Types.ts create mode 100644 src/renderer/webgl/WebglCharAtlas.ts create mode 100644 src/renderer/webgl/WebglRenderer.ts create mode 100644 src/renderer/webgl/WebglUtils.ts diff --git a/demo/client.ts b/demo/client.ts index d5196d37..23b3a502 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -203,11 +203,11 @@ function initOptions(term: TerminalType): void { bellSound: null, bellStyle: ['none', 'sound'], cursorStyle: ['block', 'underline', 'bar'], - experimentalCharAtlas: ['none', 'static', 'dynamic'], + experimentalCharAtlas: ['none', 'static', 'dynamic', 'webgl'], fontFamily: null, fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], - rendererType: ['dom', 'canvas'], + rendererType: ['dom', 'canvas', 'webgl'], experimentalBufferLineImpl: ['JsArray', 'TypedArray'] }; const options = Object.keys((term)._core.options); @@ -235,7 +235,7 @@ function initOptions(term: TerminalType): void { }); html += '
'; numberOptions.forEach(o => { - html += `
`; + html += `
`; }); html += '
'; Object.keys(stringOptions).forEach(o => { @@ -265,7 +265,7 @@ function initOptions(term: TerminalType): void { if (o === 'cols' || o === 'rows') { updateTerminalSize(); } else { - term.setOption(o, parseInt(input.value, 10)); + term.setOption(o, o === 'lineHeight' ? parseFloat(input.value) : parseInt(input.value, 10)); } }); }); diff --git a/src/Terminal.ts b/src/Terminal.ts index c9bc98ff..84fd192b 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -52,6 +52,7 @@ import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './common/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; import { KeyboardResultType, ICharset } from './core/Types'; +import { WebglRenderer } from './renderer/webgl/WebglRenderer'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -462,6 +463,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II this.renderer.onResize(this.cols, this.rows); this.refresh(0, this.rows - 1); } + break; case 'rendererType': if (this.renderer) { this.unregister(this.renderer); @@ -761,6 +763,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II switch (this.options.rendererType) { case 'canvas': this.renderer = new Renderer(this, this.options.theme); break; case 'dom': this.renderer = new DomRenderer(this, this.options.theme); break; + case 'webgl': this.renderer = new WebglRenderer(this, this.options.theme); break; default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } this.register(this.renderer); diff --git a/src/Types.ts b/src/Types.ts index e8578426..7f2f8816 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -311,6 +311,7 @@ export interface IBufferSet extends IEventEmitter { export interface ISelectionManager { selectionText: string; + hasSelection: boolean; selectionStart: [number, number]; selectionEnd: [number, number]; diff --git a/src/renderer/atlas/CharAtlasCache.ts b/src/renderer/atlas/CharAtlasCache.ts index eee93d6c..61b2ed13 100644 --- a/src/renderer/atlas/CharAtlasCache.ts +++ b/src/renderer/atlas/CharAtlasCache.ts @@ -11,11 +11,13 @@ import BaseCharAtlas from './BaseCharAtlas'; import DynamicCharAtlas from './DynamicCharAtlas'; import NoneCharAtlas from './NoneCharAtlas'; import StaticCharAtlas from './StaticCharAtlas'; +import WebglCharAtlas from '../webgl/WebglCharAtlas'; const charAtlasImplementations = { 'none': NoneCharAtlas, 'static': StaticCharAtlas, - 'dynamic': DynamicCharAtlas + 'dynamic': DynamicCharAtlas, + 'webgl': WebglCharAtlas }; interface ICharAtlasCacheEntry { @@ -38,9 +40,10 @@ export function acquireCharAtlas( terminal: ITerminal, colors: IColorSet, scaledCharWidth: number, - scaledCharHeight: number + scaledCharHeight: number, + devicePixelRatio?: number ): BaseCharAtlas { - const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors); + const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors, devicePixelRatio); // TODO: Currently if a terminal changes configs it will not free the entry reference (until it's disposed) @@ -54,6 +57,7 @@ export function acquireCharAtlas( } // The configs differ, release the terminal from the entry if (entry.ownedBy.length === 1) { + entry.atlas.dispose(); charAtlasCache.splice(i, 1); } else { entry.ownedBy.splice(ownedByIndex, 1); @@ -94,6 +98,7 @@ export function removeTerminalFromCache(terminal: ITerminal): void { if (index !== -1) { if (charAtlasCache[i].ownedBy.length === 1) { // Remove the cache entry if it's the only terminal + charAtlasCache[i].atlas.dispose(); charAtlasCache.splice(i, 1); } else { // Remove the reference from the cache entry diff --git a/src/renderer/atlas/CharAtlasUtils.ts b/src/renderer/atlas/CharAtlasUtils.ts index 59ac07df..47cdf45a 100644 --- a/src/renderer/atlas/CharAtlasUtils.ts +++ b/src/renderer/atlas/CharAtlasUtils.ts @@ -7,7 +7,7 @@ import { ITerminal } from '../../Types'; import { IColorSet } from '../Types'; import { ICharAtlasConfig } from '../../shared/atlas/Types'; -export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { +export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet, devicePixelRatio: number = window.devicePixelRatio): ICharAtlasConfig { // null out some fields that don't matter const clonedColors = { foreground: colors.foreground, @@ -21,7 +21,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number }; return { type: terminal.options.experimentalCharAtlas, - devicePixelRatio: window.devicePixelRatio, + devicePixelRatio, scaledCharWidth, scaledCharHeight, fontFamily: terminal.options.fontFamily, diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts new file mode 100644 index 00000000..0600f758 --- /dev/null +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -0,0 +1,343 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; +import { IRenderDimensions } from '../Types'; +import { ITerminal, IBufferLine } from '../../Types'; +import { NULL_CELL_CODE, CHAR_DATA_CHAR_INDEX } from '../../Buffer'; +import WebglCharAtlas from './WebglCharAtlas'; +import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; +import { INDICIES_PER_CELL } from './WebglRenderer'; +import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; + +interface IVertices { + attributes: Float32Array; + selectionAttributes: Float32Array; + cellPosition: Float32Array; + count: number; +} + +const enum VertexAttribLocations { + UNIT_QUAD = 0, + CELL_POSITION = 1, + OFFSET = 2, + SIZE = 3, + TEXCOORD = 4, + TEXSIZE = 5 +} + +const vertexShaderSource = `#version 300 es +layout (location = ${VertexAttribLocations.UNIT_QUAD}) in vec2 a_unitquad; +layout (location = ${VertexAttribLocations.CELL_POSITION}) in vec2 a_cellpos; +layout (location = ${VertexAttribLocations.OFFSET}) in vec2 a_offset; +layout (location = ${VertexAttribLocations.SIZE}) in vec2 a_size; +layout (location = ${VertexAttribLocations.TEXCOORD}) in vec2 a_texcoord; +layout (location = ${VertexAttribLocations.TEXSIZE}) in vec2 a_texsize; + +uniform mat4 u_projection; +uniform vec2 u_resolution; + +out vec2 v_texcoord; + +void main() { + vec2 zeroToOne = (a_offset / u_resolution) + a_cellpos + (a_unitquad * a_size); + gl_Position = u_projection * vec4(zeroToOne, 0.0, 1.0); + v_texcoord = a_texcoord + a_unitquad * a_texsize; +}`; + +const fragmentShaderSource = `#version 300 es +precision mediump float; + +in vec2 v_texcoord; + +uniform sampler2D u_texture; + +out vec4 outColor; + +void main() { + outColor = texture(u_texture, v_texcoord); +}`; + +const INDICES_PER_CELL = 8; +const BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT; + +export class GlyphRenderer { + private _atlas: WebglCharAtlas; + + private _program: WebGLProgram; + private _vertexArrayObject: IWebGLVertexArrayObject; + private _projectionLocation: WebGLUniformLocation; + private _resolutionLocation: WebGLUniformLocation; + private _textureLocation: WebGLUniformLocation; + private _atlasTexture: WebGLTexture; + private _attributesBuffer: WebGLBuffer; + private _cellPositionBuffer: WebGLBuffer; + + private _lineLengths: Int16Array = new Int16Array(0); + private _vertices: IVertices = { + count: 0, + attributes: new Float32Array(0), + selectionAttributes: new Float32Array(0), + cellPosition: new Float32Array(0) + }; + + constructor( + private _terminal: ITerminal, + private _gl: IWebGL2RenderingContext, + private _dimensions: IRenderDimensions + ) { + const gl = this._gl; + + this._program = createProgram(gl, vertexShaderSource, fragmentShaderSource); + + // Uniform locations + this._projectionLocation = gl.getUniformLocation(this._program, 'u_projection'); + this._resolutionLocation = gl.getUniformLocation(this._program, 'u_resolution'); + this._textureLocation = gl.getUniformLocation(this._program, 'u_texture'); + + // Create and set the vertex array object + this._vertexArrayObject = gl.createVertexArray(); + gl.bindVertexArray(this._vertexArrayObject); + + // Setup a_unitquad, this defines the 4 vertices of a rectangle + const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]); + const unitQuadVerticesBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, unitQuadVerticesBuffer); + gl.bufferData(gl.ARRAY_BUFFER, unitQuadVertices, gl.STATIC_DRAW); + gl.enableVertexAttribArray(VertexAttribLocations.UNIT_QUAD); + gl.vertexAttribPointer(VertexAttribLocations.UNIT_QUAD, 2, this._gl.FLOAT, false, 0, 0); + + // Setup the unit quad element array buffer, this points to indices in + // unitQuadVertuces to allow is to draw 2 triangles from the vertices + const unitQuadElementIndices = new Uint8Array([0, 1, 3, 0, 2, 3]); + const elementIndicesBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementIndicesBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, unitQuadElementIndices, gl.STATIC_DRAW); + + // Setup a_cellpos, this is separate as it rarely changed + this._cellPositionBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, this._cellPositionBuffer); + gl.enableVertexAttribArray(VertexAttribLocations.CELL_POSITION); + gl.vertexAttribPointer(VertexAttribLocations.CELL_POSITION, 2, gl.FLOAT, false, 0, 0); + gl.vertexAttribDivisor(VertexAttribLocations.CELL_POSITION, 1); + + // Setup attributes + this._attributesBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); + gl.enableVertexAttribArray(VertexAttribLocations.OFFSET); + gl.vertexAttribPointer(VertexAttribLocations.OFFSET, 2, gl.FLOAT, false, BYTES_PER_CELL, 0); + gl.vertexAttribDivisor(VertexAttribLocations.OFFSET, 1); + gl.enableVertexAttribArray(VertexAttribLocations.SIZE); + gl.vertexAttribPointer(VertexAttribLocations.SIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 2 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribDivisor(VertexAttribLocations.SIZE, 1); + gl.enableVertexAttribArray(VertexAttribLocations.TEXCOORD); + gl.vertexAttribPointer(VertexAttribLocations.TEXCOORD, 2, gl.FLOAT, false, BYTES_PER_CELL, 4 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribDivisor(VertexAttribLocations.TEXCOORD, 1); + gl.enableVertexAttribArray(VertexAttribLocations.TEXSIZE); + gl.vertexAttribPointer(VertexAttribLocations.TEXSIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 6 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribDivisor(VertexAttribLocations.TEXSIZE, 1); + + // Setup empty texture atlas + this._atlasTexture = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 255, 255])); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); + + // Allow drawing of transparent texture + gl.enable(gl.BLEND); + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + + // Set viewport + this.onResize(); + } + + public beginFrame(): boolean { + return this._atlas.beginFrame(); + } + + public updateCell(x: number, y: number, code: number, attr: number, bg: number, fg: number, chars: string): void { + this._updateCell(this._vertices.attributes, x, y, code, attr, bg, fg, chars); + } + + private _updateCell(array: Float32Array, x: number, y: number, code: number, attr: number, bg: number, fg: number, chars?: string): void { + const terminal = this._terminal; + + const i = ((y * terminal.cols) + x) * INDICES_PER_CELL; + + // Exit early if this is a null/space character + if (code === NULL_CELL_CODE) { + array.fill(0, i, i + INDICES_PER_CELL - 1); + return; + } + + let rasterizedGlyph: IRasterizedGlyph; + if (chars && chars.length > 1) { + rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg, this._terminal.options.enableBold); + } else { + rasterizedGlyph = this._atlas.getRasterizedGlyph(code, attr, bg, fg, this._terminal.options.enableBold); + } + + // Fill empty if no glyph was found + if (!rasterizedGlyph) { + array.fill(0, i, i + INDICES_PER_CELL - 1); + return; + } + + // a_origin + array[i ] = -rasterizedGlyph.offset.x + this._dimensions.scaledCharLeft; + array[i + 1] = -rasterizedGlyph.offset.y + this._dimensions.scaledCharTop; + // a_size + array[i + 2] = rasterizedGlyph.size.x / this._dimensions.scaledCanvasWidth; + array[i + 3] = rasterizedGlyph.size.y / this._dimensions.scaledCanvasHeight; + // a_texcoord + array[i + 4] = rasterizedGlyph.texturePositionClipSpace.x; + array[i + 5] = rasterizedGlyph.texturePositionClipSpace.y; + // a_texsize + array[i + 6] = rasterizedGlyph.sizeClipSpace.x; + array[i + 7] = rasterizedGlyph.sizeClipSpace.y; + } + + public updateLineEnd(x: number, y: number): void { + // Clears all cells to the right of the line end + const i = (y * this._terminal.cols + x + 1) * INDICES_PER_CELL; + this._vertices.attributes.fill(0, i, i + (this._terminal.cols - this._lineLengths[y]) * INDICES_PER_CELL - 1); + } + + public updateSelection(model: IRenderModel, columnSelectMode: boolean): void { + const terminal = this._terminal; + + this._vertices.selectionAttributes = this._vertices.attributes.slice(0); + + // TODO: Make fg and bg configurable, currently since the buffer doesn't + // support truecolor the char atlas cannot store it. + const fg = 0; + const bg = 7; + + if (columnSelectMode) { + const startCol = model.selection.startCol; + const width = model.selection.endCol - startCol; + const height = model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow + 1; + for (let y = model.selection.viewportCappedStartRow; y < model.selection.viewportCappedStartRow + height; y++) { + this._updateSelectionRange(startCol, startCol + width, y, model, bg, fg); + } + } else { + // Draw first row + const startCol = model.selection.viewportStartRow === model.selection.viewportCappedStartRow ? model.selection.startCol : 0; + const startRowEndCol = model.selection.viewportCappedStartRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols; + this._updateSelectionRange(startCol, startRowEndCol, model.selection.viewportCappedStartRow, model, bg, fg); + + // Draw middle rows + const middleRowsCount = Math.max(model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow - 1, 0); + for (let y = (model.selection.viewportCappedStartRow + 1); y <= model.selection.viewportCappedStartRow + middleRowsCount; y++) { + this._updateSelectionRange(0, startRowEndCol, y, model, bg, fg); + } + + // Draw final row + if (model.selection.viewportCappedStartRow !== model.selection.viewportCappedEndRow) { + // Only draw viewportEndRow if it's not the same as viewportStartRow + const endCol = model.selection.viewportEndRow === model.selection.viewportCappedEndRow ? model.selection.endCol : terminal.cols; + this._updateSelectionRange(0, endCol, model.selection.viewportCappedEndRow, model, bg, fg); + } + } + } + + private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number, fg: number): void { + const terminal = this._terminal; + const row = y + terminal.buffer.ydisp; + let line: IBufferLine; + for (let x = startCol; x < endCol; x++) { + const offset = (y * this._terminal.cols + x) * INDICIES_PER_CELL; + // Because the cache uses attr as a lookup key it needs to contain the selection colors as well + let attr = model.cells[offset + 1]; + attr = attr & ~0x3ffff | bg << 9 | fg; + const code = model.cells[offset]; + if (code & COMBINED_CHAR_BIT_MASK) { + if (!line) { + line = terminal.buffer.lines.get(row); + } + const charData = line.get(x); + const chars = charData[CHAR_DATA_CHAR_INDEX]; + this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg, chars); + } else { + this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg); + } + } + } + + public onResize(): void { + const terminal = this._terminal; + const gl = this._gl; + + gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); + + // Update vertices + const newCount = terminal.cols * terminal.rows * INDICES_PER_CELL; + if (this._vertices.count !== newCount) { + this._vertices.count = newCount; + this._vertices.attributes = new Float32Array(newCount); + this._lineLengths = new Int16Array(terminal.rows); + + this._vertices.cellPosition = new Float32Array(terminal.cols * terminal.rows * 2); + + let i = 0; + for (let y = 0; y < terminal.rows; y++) { + for (let x = 0; x < terminal.cols; x++) { + this._vertices.cellPosition[i++] = x / terminal.cols; + this._vertices.cellPosition[i++] = y / terminal.rows; + } + } + } + } + + public onThemeChanged(): void { + } + + public render(isSelectionVisible: boolean): void { + if (!this._atlas) { + return; + } + + const gl = this._gl; + + gl.useProgram(this._program); + gl.bindVertexArray(this._vertexArrayObject); + + gl.bindBuffer(gl.ARRAY_BUFFER, this._cellPositionBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this._vertices.cellPosition, gl.STATIC_DRAW); + gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); + gl.bufferData(gl.ARRAY_BUFFER, isSelectionVisible ? this._vertices.selectionAttributes : this._vertices.attributes, gl.DYNAMIC_DRAW); + + // Bind the texture atlas if it's changed + if (this._atlas.hasCanvasChanged) { + this._atlas.hasCanvasChanged = false; + gl.uniform1i(this._textureLocation, 0); + gl.activeTexture(gl.TEXTURE0 + 0); + gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, this._atlas.cacheCanvas); + gl.generateMipmap(gl.TEXTURE_2D); + } + + // Set uniforms + gl.uniformMatrix4fv(this._projectionLocation, false, PROJECTION_MATRIX); + gl.uniform2f(this._resolutionLocation, gl.canvas.width, gl.canvas.height); + + // Draw the viewport + gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, this._vertices.count / INDICES_PER_CELL); + } + + public setAtlas(atlas: WebglCharAtlas): void { + const gl = this._gl; + this._atlas = atlas; + + gl.bindTexture(gl.TEXTURE_2D, this._atlasTexture); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, atlas.cacheCanvas); + gl.generateMipmap(gl.TEXTURE_2D); + } + + public setDimensions(dimensions: IRenderDimensions): void { + this._dimensions = dimensions; + } +} diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts new file mode 100644 index 00000000..eca89b3a --- /dev/null +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -0,0 +1,320 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ITerminal } from '../../Types'; +import { IColorManager, IRenderDimensions } from '../Types'; +import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; +import { IColor } from '../../shared/Types'; +import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; +import { RENDER_INVERTED_DEFAULT_COLOR } from './RenderModel'; + +const enum VertexAttribLocations { + POSITION = 0, + SIZE = 1, + COLOR = 2, + UNIT_QUAD = 3 +} + +const vertexShaderSource = `#version 300 es +layout (location = ${VertexAttribLocations.POSITION}) in vec2 a_position; +layout (location = ${VertexAttribLocations.SIZE}) in vec2 a_size; +layout (location = ${VertexAttribLocations.COLOR}) in vec3 a_color; +layout (location = ${VertexAttribLocations.UNIT_QUAD}) in vec2 a_unitquad; + +uniform mat4 u_projection; +uniform vec2 u_resolution; + +out vec3 v_color; + +void main() { + vec2 zeroToOne = (a_position + (a_unitquad * a_size)) / u_resolution; + gl_Position = u_projection * vec4(zeroToOne, 0.0, 1.0); + v_color = a_color; +}`; + +const fragmentShaderSource = `#version 300 es +precision mediump float; + +in vec3 v_color; + +out vec4 outColor; + +void main() { + outColor = vec4(v_color, 1); +}`; + +interface IVertices { + attributes: Float32Array; + selection: Float32Array; + count: number; +} + +const INDICES_PER_RECTANGLE = 8; +const BYTES_PER_RECTANGLE = INDICES_PER_RECTANGLE * Float32Array.BYTES_PER_ELEMENT; + +const INITIAL_BUFFER_RECTANGLE_CAPACITY = 20 * INDICES_PER_RECTANGLE; + +export class RectangleRenderer { + + private _program: WebGLProgram; + private _vertexArrayObject: IWebGLVertexArrayObject; + private _resolutionLocation: WebGLUniformLocation; + private _attributesBuffer: WebGLBuffer; + private _projectionLocation: WebGLUniformLocation; + private _bgFloat: Float32Array; + private _selectionFloat: Float32Array; + + private _vertices: IVertices = { + count: 0, + attributes: new Float32Array(INITIAL_BUFFER_RECTANGLE_CAPACITY), + selection: new Float32Array(3 * INDICES_PER_RECTANGLE) + }; + + constructor( + private _terminal: ITerminal, + private _colorManager: IColorManager, + private _gl: IWebGL2RenderingContext, + private _dimensions: IRenderDimensions + ) { + const gl = this._gl; + + this._program = createProgram(gl, vertexShaderSource, fragmentShaderSource); + + // Uniform locations + this._resolutionLocation = gl.getUniformLocation(this._program, 'u_resolution'); + this._projectionLocation = gl.getUniformLocation(this._program, 'u_projection'); + + // Create and set the vertex array object + this._vertexArrayObject = gl.createVertexArray(); + gl.bindVertexArray(this._vertexArrayObject); + + // Setup a_unitquad, this defines the 4 vertices of a rectangle + const unitQuadVertices = new Float32Array([0, 0, 1, 0, 0, 1, 1, 1]); + const unitQuadVerticesBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, unitQuadVerticesBuffer); + gl.bufferData(gl.ARRAY_BUFFER, unitQuadVertices, gl.STATIC_DRAW); + gl.enableVertexAttribArray(VertexAttribLocations.UNIT_QUAD); + gl.vertexAttribPointer(VertexAttribLocations.UNIT_QUAD, 2, this._gl.FLOAT, false, 0, 0); + + // Setup the unit quad element array buffer, this points to indices in + // unitQuadVertuces to allow is to draw 2 triangles from the vertices + const unitQuadElementIndices = new Uint8Array([0, 1, 3, 0, 2, 3]); + const elementIndicesBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementIndicesBuffer); + gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, unitQuadElementIndices, gl.STATIC_DRAW); + + // Setup attributes + this._attributesBuffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); + gl.enableVertexAttribArray(VertexAttribLocations.POSITION); + gl.vertexAttribPointer(VertexAttribLocations.POSITION, 2, gl.FLOAT, false, BYTES_PER_RECTANGLE, 0); + gl.vertexAttribDivisor(VertexAttribLocations.POSITION, 1); + gl.enableVertexAttribArray(VertexAttribLocations.SIZE); + gl.vertexAttribPointer(VertexAttribLocations.SIZE, 2, gl.FLOAT, false, BYTES_PER_RECTANGLE, 2 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribDivisor(VertexAttribLocations.SIZE, 1); + gl.enableVertexAttribArray(VertexAttribLocations.COLOR); + gl.vertexAttribPointer(VertexAttribLocations.COLOR, 4, gl.FLOAT, false, BYTES_PER_RECTANGLE, 4 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribDivisor(VertexAttribLocations.COLOR, 1); + + this._updateCachedColors(); + } + + public render(): void { + const gl = this._gl; + + gl.useProgram(this._program); + + gl.bindVertexArray(this._vertexArrayObject); + + gl.uniformMatrix4fv(this._projectionLocation, false, PROJECTION_MATRIX); + gl.uniform2f(this._resolutionLocation, gl.canvas.width, gl.canvas.height); + + // Bind attributes buffer and draw + gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this._vertices.attributes, gl.DYNAMIC_DRAW); + gl.drawElementsInstanced(this._gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, this._vertices.count); + + // Bind selection buffer and draw + gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); + gl.bufferData(gl.ARRAY_BUFFER, this._vertices.selection, gl.DYNAMIC_DRAW); + gl.drawElementsInstanced(this._gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, 3); + } + + public onResize(): void { + this._updateViewportRectangle(); + } + + public onThemeChanged(): void { + this._updateCachedColors(); + this._updateViewportRectangle(); + } + + private _updateCachedColors(): void { + this._bgFloat = this._colorToFloat32Array(this._colorManager.colors.background); + this._selectionFloat = this._colorToFloat32Array(this._colorManager.colors.selection); + } + + private _updateViewportRectangle(): void { + // Set first rectangle that clears the screen + this._addRectangleFloat( + this._vertices.attributes, + 0, + 0, + 0, + this._terminal.cols * this._dimensions.scaledCellWidth, + this._terminal.rows * this._dimensions.scaledCellHeight, + this._bgFloat + ); + } + + public updateSelection(model: ISelectionRenderModel, columnSelectMode: boolean): void { + const terminal = this._terminal; + + if (!model.hasSelection) { + this._vertices.selection.fill(0, 0); + return; + } + + if (columnSelectMode) { + const startCol = model.startCol; + const width = model.endCol - startCol; + const height = model.viewportCappedEndRow - model.viewportCappedStartRow + 1; + this._addRectangleFloat( + this._vertices.selection, + 0, + startCol * this._dimensions.scaledCellWidth, + model.viewportCappedStartRow * this._dimensions.scaledCellHeight, + width * this._dimensions.scaledCellWidth, + height * this._dimensions.scaledCellHeight, + this._selectionFloat + ); + this._vertices.selection.fill(0, INDICES_PER_RECTANGLE); + } else { + // Draw first row + const startCol = model.viewportStartRow === model.viewportCappedStartRow ? model.startCol : 0; + const startRowEndCol = model.viewportCappedStartRow === model.viewportCappedEndRow ? model.endCol : terminal.cols; + this._addRectangleFloat( + this._vertices.selection, + 0, + startCol * this._dimensions.scaledCellWidth, + model.viewportCappedStartRow * this._dimensions.scaledCellHeight, + (startRowEndCol - startCol) * this._dimensions.scaledCellWidth, + this._dimensions.scaledCellHeight, + this._selectionFloat + ); + + // Draw middle rows + const middleRowsCount = Math.max(model.viewportCappedEndRow - model.viewportCappedStartRow - 1, 0); + this._addRectangleFloat( + this._vertices.selection, + INDICES_PER_RECTANGLE, + 0, + (model.viewportCappedStartRow + 1) * this._dimensions.scaledCellHeight, + terminal.cols * this._dimensions.scaledCellWidth, + middleRowsCount * this._dimensions.scaledCellHeight, + this._selectionFloat + ); + + // Draw final row + if (model.viewportCappedStartRow !== model.viewportCappedEndRow) { + // Only draw viewportEndRow if it's not the same as viewportStartRow + const endCol = model.viewportEndRow === model.viewportCappedEndRow ? model.endCol : terminal.cols; + this._addRectangleFloat( + this._vertices.selection, + INDICES_PER_RECTANGLE * 2, + 0, + model.viewportCappedEndRow * this._dimensions.scaledCellHeight, + endCol * this._dimensions.scaledCellWidth, + this._dimensions.scaledCellHeight, + this._selectionFloat + ); + } else { + this._vertices.selection.fill(0, INDICES_PER_RECTANGLE * 2); + } + } + } + + public updateBackgrounds(model: IRenderModel): void { + const terminal = this._terminal; + const vertices = this._vertices; + + let rectangleCount = 1; + + const DEFAULT_BACKGROUND_COLOR = 256; + for (let y = 0; y < terminal.rows; y++) { + let currentStartX = -1; + let currentBg = DEFAULT_BACKGROUND_COLOR; + for (let x = 0; x < terminal.cols; x++) { + const modelIndex = ((y * terminal.cols) + x) * 4; + const bg = model.cells[modelIndex + 2]; + if (bg !== currentBg) { + // A rectangle needs to be drawn if going from non-default to another color + if (currentBg !== DEFAULT_BACKGROUND_COLOR) { + const offset = rectangleCount++ * INDICES_PER_RECTANGLE; + this._updateRectangle(vertices, offset, currentBg, currentStartX, x, y); + } + currentStartX = x; + currentBg = bg; + } + } + // Finish rectangle if it's still going + if (currentBg !== DEFAULT_BACKGROUND_COLOR) { + const offset = rectangleCount++ * INDICES_PER_RECTANGLE; + this._updateRectangle(vertices, offset, currentBg, currentStartX, terminal.cols, y); + } + } + vertices.count = rectangleCount; + } + + private _updateRectangle(vertices: IVertices, offset: number, bg: number, startX: number, endX: number, y: number): void { + let color: IColor | null = null; + if (bg === RENDER_INVERTED_DEFAULT_COLOR) { + color = this._colorManager.colors.foreground; + } else if (bg < 256) { + color = this._colorManager.colors.ansi[bg]; + } + if (vertices.attributes.length < offset + 4) { + vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE); + } + const x1 = startX * this._dimensions.scaledCellWidth; + const y1 = y * this._dimensions.scaledCellHeight; + const r = ((color.rgba >> 24) & 0xFF) / 255; + const g = ((color.rgba >> 16) & 0xFF) / 255; + const b = ((color.rgba >> 8 ) & 0xFF) / 255; + + this._addRectangle(vertices.attributes, offset, x1, y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, r, g, b, 1); + } + + private _addRectangle(array: Float32Array, offset: number, x1: number, y1: number, width: number, height: number, r: number, g: number, b: number, a: number): void { + array[offset ] = x1; + array[offset + 1] = y1; + array[offset + 2] = width; + array[offset + 3] = height; + array[offset + 4] = r; + array[offset + 5] = g; + array[offset + 6] = b; + array[offset + 7] = a; + } + + private _addRectangleFloat(array: Float32Array, offset: number, x1: number, y1: number, width: number, height: number, color: Float32Array): void { + array[offset ] = x1; + array[offset + 1] = y1; + array[offset + 2] = width; + array[offset + 3] = height; + array[offset + 4] = color[0]; + array[offset + 5] = color[1]; + array[offset + 6] = color[2]; + array[offset + 7] = color[3]; + } + + private _colorToFloat32Array(color: IColor): Float32Array { + return new Float32Array([ + ((color.rgba >> 24) & 0xFF) / 255, + ((color.rgba >> 16) & 0xFF) / 255, + ((color.rgba >> 8 ) & 0xFF) / 255, + ((color.rgba ) & 0xFF) / 255 + ]); + } +} diff --git a/src/renderer/webgl/RenderModel.ts b/src/renderer/webgl/RenderModel.ts new file mode 100644 index 00000000..a14616c2 --- /dev/null +++ b/src/renderer/webgl/RenderModel.ts @@ -0,0 +1,59 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IRenderModel, ISelectionRenderModel } from './Types'; + +export const RENDER_MODEL_INDICIES_PER_CELL = 4; + +// HACK: Cannot use INVERTED_DEFAULT_COLOR (-1) here because _model.cells is a +// Uint32Array. This should be changed when true color is introduced to whatever +// the mechanism is for the buffer. +export const RENDER_INVERTED_DEFAULT_COLOR = 258; + +export const COMBINED_CHAR_BIT_MASK = 0x80000000; + +export class RenderModel implements IRenderModel { + public cells: Uint32Array; + public lineLengths: Uint32Array; + public selection: ISelectionRenderModel; + + constructor() { + this.cells = new Uint32Array(0); + this.lineLengths = new Uint32Array(0); + this.selection = { + hasSelection: false, + viewportStartRow: 0, + viewportEndRow: 0, + viewportCappedStartRow: 0, + viewportCappedEndRow: 0, + startCol: 0, + endCol: 0 + }; + } + + public resize(cols: number, rows: number): void { + const indexCount = cols * rows * RENDER_MODEL_INDICIES_PER_CELL; + if (indexCount !== this.cells.length) { + this.cells = new Uint32Array(indexCount); + this.lineLengths = new Uint32Array(rows); + } + } + + public clear(): void { + this.cells.fill(0, 0); + this.lineLengths.fill(0, 0); + this.clearSelection(); + } + + 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/src/renderer/webgl/Types.ts b/src/renderer/webgl/Types.ts new file mode 100644 index 00000000..6ebc8d64 --- /dev/null +++ b/src/renderer/webgl/Types.ts @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export interface IRasterizedGlyphSet { + [flags: 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; +} + +export interface IBoundingBox { + top: number; + left: number; + right: number; + bottom: number; +} + +export interface IRenderModel { + cells: Uint32Array; + lineLengths: Uint32Array; + selection: ISelectionRenderModel; +} + +export interface ISelectionRenderModel { + hasSelection: 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; + bindVertexArray(vao: IWebGLVertexArrayObject): void; + drawElementsInstanced(mode: number, count: number, type: number, offset: number, instanceCount: number): void; +} + +export interface IWebGLVertexArrayObject { +} diff --git a/src/renderer/webgl/WebglCharAtlas.ts b/src/renderer/webgl/WebglCharAtlas.ts new file mode 100644 index 00000000..86a60d07 --- /dev/null +++ b/src/renderer/webgl/WebglCharAtlas.ts @@ -0,0 +1,386 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { DIM_OPACITY, IGlyphIdentifier } from '../atlas/Types'; +import { ICharAtlasConfig } from '../../shared/atlas/Types'; +import { IColor } from '../../shared/Types'; +import BaseCharAtlas from '../atlas/BaseCharAtlas'; +import { DEFAULT_ANSI_COLORS } from '../ColorManager'; +import { clearColor } from '../../shared/atlas/CharAtlasGenerator'; +import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from './Types'; +import { DEFAULT_ATTR } from '../../Buffer'; +import { FLAGS } from '../Types'; +import { RENDER_INVERTED_DEFAULT_COLOR } from './RenderModel'; + +// 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; + +/** + * The amount of the texture to be filled before throwing it away and starting + * again. Since the throw away and individual glyph draws don't cost too much, + * 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. + */ +const NULL_RASTERIZED_GLYPH: IRasterizedGlyph = { + offset: { x: 0, y: 0 }, + texturePosition: { x: 0, y: 0 }, + texturePositionClipSpace: { x: 0, y: 0 }, + size: { x: 0, y: 0 }, + sizeClipSpace: { x: 0, y: 0 } +}; + +const TMP_CANVAS_GLYPH_PADDING = 2; + +export default class WebglCharAtlas extends BaseCharAtlas { + private _cacheMap: { [code: number]: IRasterizedGlyphSet } = {}; + private _cacheMapCombined: { [chars: string]: IRasterizedGlyphSet } = {}; + + // The texture that the atlas is drawn to + public cacheCanvas: HTMLCanvasElement; + private _cacheCtx: CanvasRenderingContext2D; + + private _tmpCanvas: HTMLCanvasElement; + // A temporary context that glyphs are drawn to before being transfered to the atlas. + private _tmpCtx: CanvasRenderingContext2D; + + // Since glyphs are expected to be around the same height, the packing + // strategy used it to fill a row with glyphs while keeping track of the + // tallest glyph in the row. Once the row is full a new row is started at + // (0,lastRow+lastRowTallestGlyph). + private _currentRowY: number = 0; + private _currentRowX: number = 0; + private _currentRowHeight: number = 0; + + public hasCanvasChanged = false; + + private _workBoundingBox: IBoundingBox = { top: 0, left: 0, bottom: 0, right: 0 }; + + constructor(document: Document, private _config: ICharAtlasConfig) { + super(); + + this.cacheCanvas = document.createElement('canvas'); + this.cacheCanvas.width = TEXTURE_WIDTH; + this.cacheCanvas.height = TEXTURE_HEIGHT; + // The canvas needs alpha because we use clearColor to convert the background color to alpha. + // It might also contain some characters with transparent backgrounds if allowTransparency is + // set. + this._cacheCtx = this.cacheCanvas.getContext('2d', {alpha: true}); + + this._tmpCanvas = document.createElement('canvas'); + this._tmpCanvas.width = this._config.scaledCharWidth * 2 + TMP_CANVAS_GLYPH_PADDING * 2; + this._tmpCanvas.height = this._config.scaledCharHeight + TMP_CANVAS_GLYPH_PADDING * 2; + this._tmpCtx = this._tmpCanvas.getContext('2d', {alpha: this._config.allowTransparency}); + + // This is useful for debugging + document.body.appendChild(this.cacheCanvas); + } + + public dispose(): void { + if (this.cacheCanvas.parentElement) { + this.cacheCanvas.parentElement.removeChild(this.cacheCanvas); + } + } + + protected _doWarmUp(): void { + // Pre-fill with ASCII 33-126 + for (let i = 33; i < 126; i++) { + const rasterizedGlyph = this._drawToCache(i, DEFAULT_ATTR, 256, 257, true); + this._cacheMap[i] = { + [DEFAULT_ATTR]: rasterizedGlyph + }; + } + } + + public beginFrame(): boolean { + if (this._currentRowY > TEXTURE_CAPACITY) { + this._cacheCtx.clearRect(0, 0, TEXTURE_WIDTH, TEXTURE_HEIGHT); + this._cacheMap = {}; + this._currentRowHeight = 0; + this._currentRowX = 0; + this._currentRowY = 0; + this._doWarmUp(); + return true; + } + return false; + } + + public getRasterizedGlyphCombinedChar(chars: string, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph { + let rasterizedGlyphSet = this._cacheMapCombined[chars]; + if (!rasterizedGlyphSet) { + rasterizedGlyphSet = {}; + this._cacheMapCombined[chars] = rasterizedGlyphSet; + } + let rasterizedGlyph = rasterizedGlyphSet[attr]; + if (!rasterizedGlyph) { + rasterizedGlyph = this._drawToCache(chars, attr, bg, fg, enableBold); + rasterizedGlyphSet[attr] = rasterizedGlyph; + } + return rasterizedGlyph; + } + + /** + * Gets the glyphs texture coords, drawing the texture if it's not already + */ + public getRasterizedGlyph(code: number, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph { + // Space is always an empty cell, special case this as it's so common + if (code === 32) { + return; + } + + let rasterizedGlyphSet = this._cacheMap[code]; + if (!rasterizedGlyphSet) { + rasterizedGlyphSet = {}; + this._cacheMap[code] = rasterizedGlyphSet; + } + let rasterizedGlyph = rasterizedGlyphSet[attr]; + if (!rasterizedGlyph) { + rasterizedGlyph = this._drawToCache(code, attr, bg, fg, enableBold); + rasterizedGlyphSet[attr] = rasterizedGlyph; + } + return rasterizedGlyph; + } + + public draw( + ctx: CanvasRenderingContext2D, + glyph: IGlyphIdentifier, + x: number, + y: number + ): boolean { + throw new Error('WebglCharAtlas is only compatible with the webgl renderer'); + } + + private _getColorFromAnsiIndex(idx: number): IColor { + if (idx < this._config.colors.ansi.length) { + return this._config.colors.ansi[idx]; + } + return DEFAULT_ANSI_COLORS[idx]; + } + + private _getBackgroundColor(bg: number): IColor { + if (this._config.allowTransparency) { + // 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; + } else if (bg === RENDER_INVERTED_DEFAULT_COLOR) { + return this._config.colors.foreground; + } else if (bg < 256) { + return this._getColorFromAnsiIndex(bg); + } + return this._config.colors.background; + } + + private _getForegroundColor(fg: number): IColor { + if (fg === RENDER_INVERTED_DEFAULT_COLOR) { + return this._config.colors.background; + } else if (fg < 256) { + // 256 color support + return this._getColorFromAnsiIndex(fg); + } + return this._config.colors.foreground; + } + + private _drawToCache(code: number, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph; + private _drawToCache(chars: string, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph; + private _drawToCache(codeOrChars: number | string, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph { + const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars; + + this.hasCanvasChanged = true; + + const flags = attr >> 18; + + const bold = !!(flags & FLAGS.BOLD) && enableBold; + const dim = !!(flags & FLAGS.DIM); + const italic = !!(flags & FLAGS.ITALIC); + + this._tmpCtx.save(); + + // draw the background + const backgroundColor = this._getBackgroundColor(bg); + // Use a 'copy' composite operation to clear any existing glyph out of _tmpCtxWithAlpha, regardless of + // transparency in backgroundColor + this._tmpCtx.globalCompositeOperation = 'copy'; + this._tmpCtx.fillStyle = backgroundColor.css; + this._tmpCtx.fillRect(0, 0, this._tmpCanvas.width, this._tmpCanvas.height); + this._tmpCtx.globalCompositeOperation = 'source-over'; + + // draw the foreground/glyph + const fontWeight = bold ? this._config.fontWeightBold : this._config.fontWeight; + const fontStyle = italic ? 'italic' : ''; + this._tmpCtx.font = + `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; + this._tmpCtx.textBaseline = 'top'; + + this._tmpCtx.fillStyle = this._getForegroundColor(fg).css; + + // Apply alpha to dim the character + if (dim) { + this._tmpCtx.globalAlpha = DIM_OPACITY; + } + + // Draw the character + this._tmpCtx.fillText(chars, TMP_CANVAS_GLYPH_PADDING, TMP_CANVAS_GLYPH_PADDING); + this._tmpCtx.restore(); + + // clear the background from the character to avoid issues with drawing over the previous + // character if it extends past it's bounds + const imageData = this._tmpCtx.getImageData( + 0, 0, this._tmpCanvas.width, this._tmpCanvas.height + ); + + // TODO: Support transparency + // let isEmpty = false; + // if (!this._config.allowTransparency) { + // isEmpty = clearColor(imageData, backgroundColor); + // } + + // Clear out the background color and determine if the glyph is empty. + const isEmpty = clearColor(imageData, backgroundColor); + + // Handle empty glyphs + if (isEmpty) { + return NULL_RASTERIZED_GLYPH; + } + + const rasterizedGlyph = this._findGlyphBoundingBox(imageData, this._workBoundingBox); + const clippedImageData = this._clipImageData(imageData, this._workBoundingBox); + + // Check if there is enough room in the current row and go to next if needed + if (this._currentRowX + this._config.scaledCharWidth > TEXTURE_WIDTH) { + this._currentRowX = 0; + this._currentRowY += this._currentRowHeight; + this._currentRowHeight = 0; + } + + // Record texture position + rasterizedGlyph.texturePosition.x = this._currentRowX; + rasterizedGlyph.texturePosition.y = this._currentRowY; + rasterizedGlyph.texturePositionClipSpace.x = this._currentRowX / TEXTURE_WIDTH; + rasterizedGlyph.texturePositionClipSpace.y = this._currentRowY / TEXTURE_HEIGHT; + + // Update atlas current row + this._currentRowHeight = Math.max(this._currentRowHeight, rasterizedGlyph.size.y); + this._currentRowX += rasterizedGlyph.size.x; + + // putImageData doesn't do any blending, so it will overwrite any existing cache entry for us + this._cacheCtx.putImageData(clippedImageData, rasterizedGlyph.texturePosition.x, rasterizedGlyph.texturePosition.y); + + return rasterizedGlyph; + } + + /** + * Given an ImageData object, find the bounding box of the non-transparent + * portion of the texture and return an IRasterizedGlyph with these + * dimensions. + * @param imageData The image data to read. + * @param boundingBox An IBoundingBox to put the clipped bounding box values. + */ + private _findGlyphBoundingBox(imageData: ImageData, boundingBox: IBoundingBox): IRasterizedGlyph { + boundingBox.top = 0; + let found = false; + for (let y = 0; y < this._tmpCanvas.height; y++) { + for (let x = 0; x < this._tmpCanvas.width; x++) { + const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3; + if (imageData.data[alphaOffset] !== 0) { + boundingBox.top = y; + found = true; + break; + } + } + if (found) { + break; + } + } + boundingBox.left = 0; + found = false; + for (let x = 0; x < this._tmpCanvas.width; x++) { + for (let y = 0; y < this._tmpCanvas.height; y++) { + const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3; + if (imageData.data[alphaOffset] !== 0) { + boundingBox.left = x; + found = true; + break; + } + } + if (found) { + break; + } + } + boundingBox.right = this._tmpCanvas.width; + found = false; + for (let x = this._tmpCanvas.width - 1; x >= 0; x--) { + for (let y = 0; y < this._tmpCanvas.height; y++) { + const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3; + if (imageData.data[alphaOffset] !== 0) { + boundingBox.right = x; + found = true; + break; + } + } + if (found) { + break; + } + } + boundingBox.bottom = this._tmpCanvas.height; + found = false; + for (let y = this._tmpCanvas.height - 1; y >= 0; y--) { + for (let x = 0; x < this._tmpCanvas.width; x++) { + const alphaOffset = y * this._tmpCanvas.width * 4 + x * 4 + 3; + if (imageData.data[alphaOffset] !== 0) { + boundingBox.bottom = y; + found = true; + break; + } + } + if (found) { + break; + } + } + return { + texturePosition: { x: 0, y: 0 }, + texturePositionClipSpace: { x: 0, y: 0 }, + size: { + x: boundingBox.right - boundingBox.left + 1, + y: boundingBox.bottom - boundingBox.top + 1 + }, + sizeClipSpace: { + x: (boundingBox.right - boundingBox.left + 1) / TEXTURE_WIDTH, + y: (boundingBox.bottom - boundingBox.top + 1) / TEXTURE_HEIGHT + }, + offset: { + x: -boundingBox.left + TMP_CANVAS_GLYPH_PADDING, + y: -boundingBox.top + TMP_CANVAS_GLYPH_PADDING + } + }; + } + + private _clipImageData(imageData: ImageData, boundingBox: IBoundingBox): ImageData { + const width = boundingBox.right - boundingBox.left + 1; + const height = boundingBox.bottom - boundingBox.top + 1; + const clippedData = new Uint8ClampedArray(width * height * 4); + for (let y = boundingBox.top; y <= boundingBox.bottom; y++) { + for (let x = boundingBox.left; x <= boundingBox.right; x++) { + const oldOffset = y * this._tmpCanvas.width * 4 + x * 4; + const newOffset = (y - boundingBox.top) * width * 4 + (x - boundingBox.left) * 4; + clippedData[newOffset] = imageData.data[oldOffset]; + clippedData[newOffset + 1] = imageData.data[oldOffset + 1]; + clippedData[newOffset + 2] = imageData.data[oldOffset + 2]; + clippedData[newOffset + 3] = imageData.data[oldOffset + 3]; + } + } + return new ImageData(clippedData, width, height); + } +} diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts new file mode 100644 index 00000000..25030319 --- /dev/null +++ b/src/renderer/webgl/WebglRenderer.ts @@ -0,0 +1,427 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { EventEmitter } from '../../common/EventEmitter'; +import { IRenderer, IRenderDimensions, IColorSet, IRenderLayer, FLAGS } from '../Types'; +import { ITheme } from 'xterm'; +import { CharacterJoinerHandler, ITerminal } from '../../Types'; +import { ColorManager } from '../ColorManager'; +import { RenderDebouncer } from '../../ui/RenderDebouncer'; +import { GlyphRenderer } from './GlyphRenderer'; +import { LinkRenderLayer } from '../LinkRenderLayer'; +import { CursorRenderLayer } from '../CursorRenderLayer'; +import { acquireCharAtlas } from '../atlas/CharAtlasCache'; +import WebglCharAtlas from './WebglCharAtlas'; +import { ScreenDprMonitor } from '../../ui/ScreenDprMonitor'; +import { RectangleRenderer } from './RectangleRenderer'; +import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../../Buffer'; +import { IWebGL2RenderingContext } from './Types'; +import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; +import { RenderModel, RENDER_INVERTED_DEFAULT_COLOR, COMBINED_CHAR_BIT_MASK } from './RenderModel'; + +export const INDICIES_PER_CELL = 4; + +export class WebglRenderer extends EventEmitter implements IRenderer { + private _renderDebouncer: RenderDebouncer; + private _renderLayers: IRenderLayer[]; + private _charAtlas: WebglCharAtlas; + private _screenDprMonitor: ScreenDprMonitor; + private _devicePixelRatio: number; + + private _model: RenderModel = new RenderModel(); + + private _canvas: HTMLCanvasElement; + private _gl: IWebGL2RenderingContext; + private _rectangleRenderer: RectangleRenderer; + private _glyphRenderer: GlyphRenderer; + + private _isPaused: boolean = false; + private _needsFullRefresh: boolean = false; + + public dimensions: IRenderDimensions; + public colorManager: ColorManager; + + constructor( + private _terminal: ITerminal, + theme: ITheme + ) { + super(); + const allowTransparency = this._terminal.options.allowTransparency; + this.colorManager = new ColorManager(document, allowTransparency); + + this._renderLayers = [ + new LinkRenderLayer(this._terminal.screenElement, 2, this.colorManager.colors, this._terminal), + new CursorRenderLayer(this._terminal.screenElement, 3, this.colorManager.colors) + ]; + this.dimensions = { + scaledCharWidth: null, + scaledCharHeight: null, + scaledCellWidth: null, + scaledCellHeight: null, + scaledCharLeft: null, + scaledCharTop: null, + scaledCanvasWidth: null, + scaledCanvasHeight: null, + canvasWidth: null, + canvasHeight: null, + actualCellWidth: null, + actualCellHeight: null + }; + this._devicePixelRatio = window.devicePixelRatio; + this._updateDimensions(); + + this._screenDprMonitor = new ScreenDprMonitor(); + this._screenDprMonitor.setListener(() => this.onWindowResize(window.devicePixelRatio)); + this.register(this._screenDprMonitor); + + this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); + + this._canvas = document.createElement('canvas'); + this._gl = this._canvas.getContext('webgl2') as IWebGL2RenderingContext; + if (!this._gl) { + throw new Error('WebGL2 not supported'); + } + this._terminal.screenElement.appendChild(this._canvas); + + this._rectangleRenderer = new RectangleRenderer(this._terminal, this.colorManager, this._gl, this.dimensions); + this._glyphRenderer = new GlyphRenderer(this._terminal, this._gl, this.dimensions); + + // Detect whether IntersectionObserver is detected and enable renderer pause + // and resume based on terminal visibility if so + if ('IntersectionObserver' in window) { + const observer = new IntersectionObserver(e => this.onIntersectionChange(e[0]), { threshold: 0 }); + observer.observe(this._terminal.element); + this.register({ dispose: () => observer.disconnect() }); + } + } + + public dispose(): void { + this._renderLayers.forEach(l => l.dispose()); + this._terminal.screenElement.removeChild(this._canvas); + } + + public onIntersectionChange(entry: IntersectionObserverEntry): void { + this._isPaused = entry.intersectionRatio === 0; + if (!this._isPaused && this._needsFullRefresh) { + this._terminal.refresh(0, this._terminal.rows - 1); + } + } + + private _refreshViewport(): void { + // Force a refresh + this._model.clear(); + if (this._isPaused) { + this._needsFullRefresh = true; + } else { + this._terminal.refresh(0, this._terminal.rows - 1); + } + } + + public setTheme(theme: ITheme | undefined): IColorSet { + if (theme) { + this.colorManager.setTheme(theme); + } + + // Clear layers and force a full render + this._renderLayers.forEach(l => { + l.onThemeChanged(this._terminal, this.colorManager.colors); + l.reset(this._terminal); + }); + + this._rectangleRenderer.onThemeChanged(); + this._glyphRenderer.onThemeChanged(); + + this._refreshCharAtlas(); + this._refreshViewport(); + + return this.colorManager.colors; + } + + public onWindowResize(devicePixelRatio: number): void { + // If the device pixel ratio changed, the char atlas needs to be regenerated + // and the terminal needs to refreshed + if (this._devicePixelRatio !== devicePixelRatio) { + this._devicePixelRatio = devicePixelRatio; + this.onResize(this._terminal.cols, this._terminal.rows, devicePixelRatio); + } + } + + public onResize(cols: number, rows: number, devicePixelRatio: number = window.devicePixelRatio): void { + // Update character and canvas dimensions + this._updateDimensions(devicePixelRatio); + + this._model.resize(this._terminal.cols, this._terminal.rows); + this._rectangleRenderer.onResize(); + + // Resize all render layers + this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions)); + + // Resize the canvas + this._canvas.width = this.dimensions.scaledCanvasWidth; + this._canvas.height = this.dimensions.scaledCanvasHeight; + this._canvas.style.width = `${this.dimensions.canvasWidth}px`; + this._canvas.style.height = `${this.dimensions.canvasHeight}px`; + + // Resize the screen + this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`; + this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`; + this._glyphRenderer.setDimensions(this.dimensions); + this._glyphRenderer.onResize(); + + this._refreshCharAtlas(devicePixelRatio); + this._refreshViewport(); + + this.emit('resize', { + width: this.dimensions.canvasWidth, + height: this.dimensions.canvasHeight + }); + } + + public onCharSizeChanged(): void { + this.onResize(this._terminal.cols, this._terminal.rows); + } + + public onBlur(): void { + this._renderLayers.forEach(l => l.onBlur(this._terminal)); + } + + public onFocus(): void { + this._renderLayers.forEach(l => l.onFocus(this._terminal)); + } + + public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void { + this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end, columnSelectMode)); + + this._updateSelectionModel(start, end); + + this._rectangleRenderer.updateSelection(this._model.selection, columnSelectMode); + this._glyphRenderer.updateSelection(this._model, columnSelectMode); + this.refreshRows(0, this._terminal.rows - 1); + } + + public onCursorMove(): void { + this._renderLayers.forEach(l => l.onCursorMove(this._terminal)); + } + + public onOptionsChanged(): void { + this._renderLayers.forEach(l => l.onOptionsChanged(this._terminal)); + this._updateDimensions(); + this._refreshCharAtlas(); + } + + /** + * Refreshes the char atlas, aquiring a new one if necessary. + * @param terminal The terminal. + * @param colorSet The color set to use for the char atlas. + */ + private _refreshCharAtlas(devicePixelRatio: number = window.devicePixelRatio): void { + if (this.dimensions.scaledCharWidth <= 0 && this.dimensions.scaledCharHeight <= 0) { + return; + } + + const atlas = acquireCharAtlas(this._terminal, this.colorManager.colors, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight, devicePixelRatio); + if (!('getRasterizedGlyph' in atlas)) { + throw new Error('The webgl renderer only works with the webgl char atlas'); + } + this._charAtlas = atlas as WebglCharAtlas; + this._charAtlas.warmUp(); + this._glyphRenderer.setAtlas(this._charAtlas); + } + + public clear(): void { + this._renderLayers.forEach(l => l.reset(this._terminal)); + } + + public refreshRows(start: number, end: number): void { + if (this._isPaused) { + this._needsFullRefresh = true; + return; + } + this._renderDebouncer.refresh(start, end); + } + + public registerCharacterJoiner(handler: CharacterJoinerHandler): number { + return -1; + } + + public deregisterCharacterJoiner(joinerId: number): boolean { + return false; + } + + private _renderRows(start: number, end: number): void { + // Update render layers + this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); + + // Tell renderer the frame is beginning + if (this._glyphRenderer.beginFrame()) { + this._model.clear(); + } + + // Update model to reflect what's drawn + this._updateModel(start, end); + + // Render + this._rectangleRenderer.render(); + this._glyphRenderer.render(this._model.selection.hasSelection); + + // Emit event + this._terminal.emit('refresh', { start, end }); + } + + private _updateModel(start: number, end: number): void { + const terminal = this._terminal; + + for (let y = start; y <= end; y++) { + const row = y + terminal.buffer.ydisp; + const line = terminal.buffer.lines.get(row); + this._model.lineLengths[y] = 0; + for (let x = 0; x < terminal.cols; x++) { + const charData = line.get(x); + const chars = charData[CHAR_DATA_CHAR_INDEX]; + let code = charData[CHAR_DATA_CODE_INDEX]; + const attr = charData[CHAR_DATA_ATTR_INDEX]; + const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL; + + // Nothing has changed, no updates needed + if (this._model.cells[i] === code && this._model.cells[i + 1] === attr) { + continue; + } + + // Resolve bg and fg and cache in the model + const flags = attr >> 18; + let bg = attr & 0x1ff; + let fg = (attr >> 9) & 0x1ff; + + // If inverse flag is on, the foreground should become the background. + if (flags & FLAGS.INVERSE) { + const temp = bg; + bg = fg; + fg = temp; + if (fg === 256) { + fg = RENDER_INVERTED_DEFAULT_COLOR; + } + if (bg === 257) { + bg = RENDER_INVERTED_DEFAULT_COLOR; + } + } + const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && !!(flags & FLAGS.BOLD) && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; + fg += drawInBrightColor ? 8 : 0; + + // Flag combined chars with a bit mask so they're easily identifiable + if (chars.length > 1) { + code = code | COMBINED_CHAR_BIT_MASK; + } + + this._model.cells[i ] = code; + this._model.cells[i + 1] = attr; + this._model.cells[i + 2] = bg; + this._model.cells[i + 3] = fg; + + this._glyphRenderer.updateCell(x, y, code, attr, bg, fg, chars); + } + } + this._rectangleRenderer.updateBackgrounds(this._model); + } + + private _updateSelectionModel(start: [number, number], end: [number, number]): 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.ydisp; + const viewportEndRow = end[1] - terminal.buffer.ydisp; + 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.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. + */ + private _updateDimensions(devicePixelRatio: number = window.devicePixelRatio): void { + // Perform a new measure if the CharMeasure dimensions are not yet available + if (!this._terminal.charMeasure.width || !this._terminal.charMeasure.height) { + return; + } + + // Calculate the scaled character width. Width is floored as it must be + // drawn to an integer grid in order for the CharAtlas "stamps" to not be + // blurry. When text is drawn to the grid not using the CharAtlas, it is + // clipped to ensure there is no overlap with the next cell. + + // NOTE: ceil fixes sometime, floor does others :s + + this.dimensions.scaledCharWidth = Math.floor(this._terminal.charMeasure.width * devicePixelRatio); + + // Calculate the scaled character height. Height is ceiled in case + // devicePixelRatio is a floating point number in order to ensure there is + // enough space to draw the character to the cell. + this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * devicePixelRatio); + + // Calculate the scaled cell height, if lineHeight is not 1 then the value + // will be floored because since lineHeight can never be lower then 1, there + // is a guarentee that the scaled line height will always be larger than + // scaled char height. + this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight); + + // Calculate the y coordinate within a cell that text should draw from in + // order to draw in the center of a cell. + this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); + + // Calculate the scaled cell width, taking the letterSpacing into account. + this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing); + + // Calculate the x coordinate with a cell that text should draw from in + // order to draw in the center of a cell. + this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing / 2); + + // Recalculate the canvas dimensions; scaled* define the actual number of + // pixel in the canvas + this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight; + this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth; + + // The the size of the canvas on the page. It's very important that this + // rounds to nearest integer and not ceils as browsers often set + // window.devicePixelRatio as something like 1.100000023841858, when it's + // actually 1.1. Ceiling causes blurriness as the backing canvas image is 1 + // pixel too large for the canvas element size. + this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / devicePixelRatio); + this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / devicePixelRatio); + + // this.dimensions.scaledCanvasHeight = this.dimensions.canvasHeight * devicePixelRatio; + // this.dimensions.scaledCanvasWidth = this.dimensions.canvasWidth * devicePixelRatio; + + // Get the _actual_ dimensions of an individual cell. This needs to be + // derived from the canvasWidth/Height calculated above which takes into + // account window.devicePixelRatio. CharMeasure.width/height by itself is + // insufficient when the page is not at 100% zoom level as CharMeasure is + // measured in CSS pixels, but the actual char size on the canvas can + // differ. + // this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows; + // this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols; + + // This fixes 110% and 125%, not 150% or 175% though + this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / devicePixelRatio; + this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / devicePixelRatio; + } +} diff --git a/src/renderer/webgl/WebglUtils.ts b/src/renderer/webgl/WebglUtils.ts new file mode 100644 index 00000000..8f166a23 --- /dev/null +++ b/src/renderer/webgl/WebglUtils.ts @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +/** + * A matrix that when multiplies will translate 0-1 coordinates (left to right, + * top to bottom) to clip space. + */ +export const PROJECTION_MATRIX = new Float32Array([ + 2, 0, 0, 0, + 0, -2, 0, 0, + 0, 0, 1, 0, + -1, 1, 0, 1 +]); + +export function createProgram(gl: WebGLRenderingContext, vertexSource: string, fragmentSource: string): WebGLProgram | undefined { + const program = gl.createProgram(); + gl.attachShader(program, createShader(gl, gl.VERTEX_SHADER, vertexSource)); + gl.attachShader(program, createShader(gl, gl.FRAGMENT_SHADER, fragmentSource)); + gl.linkProgram(program); + const success = gl.getProgramParameter(program, gl.LINK_STATUS); + if (success) { + return program; + } + + console.log(gl.getProgramInfoLog(program)); + gl.deleteProgram(program); +} + +export function createShader(gl: WebGLRenderingContext, type: number, source: string): WebGLShader | undefined { + const shader = gl.createShader(type); + gl.shaderSource(shader, source); + gl.compileShader(shader); + const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS); + if (success) { + return shader; + } + + console.log(gl.getShaderInfoLog(shader)); + gl.deleteShader(shader); +} + +export function expandFloat32Array(source: Float32Array, max: number): Float32Array { + const newLength = Math.min(source.length * 2, max); + const newArray = new Float32Array(newLength); + for (let i = 0; i < source.length; i++) { + newArray[i] = source[i]; + } + return newArray; +} diff --git a/src/shared/atlas/Types.ts b/src/shared/atlas/Types.ts index 25eaa716..7a69f78b 100644 --- a/src/shared/atlas/Types.ts +++ b/src/shared/atlas/Types.ts @@ -9,7 +9,7 @@ import { IColorSet } from '../Types'; export const CHAR_ATLAS_CELL_SPACING = 1; export interface ICharAtlasConfig { - type: 'none' | 'static' | 'dynamic'; + type: 'none' | 'static' | 'dynamic' | 'webgl'; devicePixelRatio: number; fontSize: number; fontFamily: string; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index c6b6b1e5..881ea292 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -18,7 +18,7 @@ declare module 'xterm' { /** * A string representing a renderer type. */ - export type RendererType = 'dom' | 'canvas'; + export type RendererType = 'dom' | 'canvas' | 'webgl'; /** * An object containing start up options for the terminal. @@ -99,7 +99,7 @@ declare module 'xterm' { * Currently defaults to 'static'. This option may be removed in the future. If it is, passed * parameters will be ignored. */ - experimentalCharAtlas?: 'none' | 'static' | 'dynamic'; + experimentalCharAtlas?: 'none' | 'static' | 'dynamic' | 'webgl'; /** * (EXPERIMENTAL) Defines which implementation to use for buffer lines. @@ -213,7 +213,7 @@ declare module 'xterm' { cursor?: string, /** The accent color of the cursor (used as the foreground color for a block cursor) */ cursorAccent?: string, - /** The selection color (can be transparent) */ + /** The selection background color (can be transparent) */ selection?: string, /** ANSI black (eg. `\x1b[30m`) */ black?: string, From e6d339a28954eec0290fe7a9c454b326d97fd130 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 19 Nov 2018 16:54:53 -0800 Subject: [PATCH 02/69] Fix wide char caching in webgl renderer --- src/renderer/webgl/GlyphRenderer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 0600f758..b6e4066d 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -162,13 +162,13 @@ export class GlyphRenderer { this._updateCell(this._vertices.attributes, x, y, code, attr, bg, fg, chars); } - private _updateCell(array: Float32Array, x: number, y: number, code: number, attr: number, bg: number, fg: number, chars?: string): void { + private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, attr: number, bg: number, fg: number, chars?: string): void { const terminal = this._terminal; const i = ((y * terminal.cols) + x) * INDICES_PER_CELL; // Exit early if this is a null/space character - if (code === NULL_CELL_CODE) { + if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) { array.fill(0, i, i + INDICES_PER_CELL - 1); return; } From 42eda149c71f8192076750bdb9be8a38b45266a6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 22 Nov 2018 10:57:45 -0800 Subject: [PATCH 03/69] Set theme in ctor --- src/renderer/webgl/WebglRenderer.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 25030319..61fb9d34 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -50,6 +50,9 @@ export class WebglRenderer extends EventEmitter implements IRenderer { super(); const allowTransparency = this._terminal.options.allowTransparency; this.colorManager = new ColorManager(document, allowTransparency); + if (theme) { + this.colorManager.setTheme(theme); + } this._renderLayers = [ new LinkRenderLayer(this._terminal.screenElement, 2, this.colorManager.colors, this._terminal), From 128b1b0a483319a1aa9c6dac1349436247503dbc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 23 Nov 2018 07:14:56 -0800 Subject: [PATCH 04/69] Use polyfill for TypedArray.fill --- src/renderer/webgl/GlyphRenderer.ts | 7 ++++--- src/renderer/webgl/RectangleRenderer.ts | 7 ++++--- src/renderer/webgl/RenderModel.ts | 5 +++-- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index b6e4066d..b431aec7 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -11,6 +11,7 @@ import WebglCharAtlas from './WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; +import { fill } from '../../core/TypedArrayUtils'; interface IVertices { attributes: Float32Array; @@ -169,7 +170,7 @@ export class GlyphRenderer { // Exit early if this is a null/space character if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) { - array.fill(0, i, i + INDICES_PER_CELL - 1); + fill(array, 0, i, i + INDICES_PER_CELL - 1); return; } @@ -182,7 +183,7 @@ export class GlyphRenderer { // Fill empty if no glyph was found if (!rasterizedGlyph) { - array.fill(0, i, i + INDICES_PER_CELL - 1); + fill(array, 0, i, i + INDICES_PER_CELL - 1); return; } @@ -203,7 +204,7 @@ export class GlyphRenderer { public updateLineEnd(x: number, y: number): void { // Clears all cells to the right of the line end const i = (y * this._terminal.cols + x + 1) * INDICES_PER_CELL; - this._vertices.attributes.fill(0, i, i + (this._terminal.cols - this._lineLengths[y]) * INDICES_PER_CELL - 1); + fill(this._vertices.attributes, 0, i, i + (this._terminal.cols - this._lineLengths[y]) * INDICES_PER_CELL - 1); } public updateSelection(model: IRenderModel, columnSelectMode: boolean): void { diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index eca89b3a..2642ec73 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -9,6 +9,7 @@ import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUti import { IColor } from '../../shared/Types'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; import { RENDER_INVERTED_DEFAULT_COLOR } from './RenderModel'; +import { fill } from '../../core/TypedArrayUtils'; const enum VertexAttribLocations { POSITION = 0, @@ -173,7 +174,7 @@ export class RectangleRenderer { const terminal = this._terminal; if (!model.hasSelection) { - this._vertices.selection.fill(0, 0); + fill(this._vertices.selection, 0, 0); return; } @@ -190,7 +191,7 @@ export class RectangleRenderer { height * this._dimensions.scaledCellHeight, this._selectionFloat ); - this._vertices.selection.fill(0, INDICES_PER_RECTANGLE); + fill(this._vertices.selection, 0, INDICES_PER_RECTANGLE); } else { // Draw first row const startCol = model.viewportStartRow === model.viewportCappedStartRow ? model.startCol : 0; @@ -231,7 +232,7 @@ export class RectangleRenderer { this._selectionFloat ); } else { - this._vertices.selection.fill(0, INDICES_PER_RECTANGLE * 2); + fill(this._vertices.selection, 0, INDICES_PER_RECTANGLE * 2); } } } diff --git a/src/renderer/webgl/RenderModel.ts b/src/renderer/webgl/RenderModel.ts index a14616c2..a3b2b2c9 100644 --- a/src/renderer/webgl/RenderModel.ts +++ b/src/renderer/webgl/RenderModel.ts @@ -4,6 +4,7 @@ */ import { IRenderModel, ISelectionRenderModel } from './Types'; +import { fill } from '../../core/TypedArrayUtils'; export const RENDER_MODEL_INDICIES_PER_CELL = 4; @@ -42,8 +43,8 @@ export class RenderModel implements IRenderModel { } public clear(): void { - this.cells.fill(0, 0); - this.lineLengths.fill(0, 0); + fill(this.cells, 0, 0); + fill(this.lineLengths, 0, 0); this.clearSelection(); } From 53dc2af2b772552fbc7bc069c6698265e1d9c852 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 23 Nov 2018 08:00:27 -0800 Subject: [PATCH 05/69] Use polyfill for TypedArray.slice --- src/core/TypedArrayUtils.test.ts | 122 ++++++++++++++++++++++++++-- src/core/TypedArrayUtils.ts | 26 ++++++ src/renderer/webgl/GlyphRenderer.ts | 4 +- 3 files changed, 142 insertions(+), 10 deletions(-) diff --git a/src/core/TypedArrayUtils.test.ts b/src/core/TypedArrayUtils.test.ts index ef86d314..d720d957 100644 --- a/src/core/TypedArrayUtils.test.ts +++ b/src/core/TypedArrayUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ import { assert } from 'chai'; -import { fill } from './TypedArrayUtils'; +import { fill, sliceFallback } from './TypedArrayUtils'; type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array @@ -29,7 +29,7 @@ function loopFill(array: TypedArray, value: number, start: number = 0, end?: num return array; } -describe('polyfill conformance tests', function(): void { +describe('polyfill conformance tests', () => { function deepEquals(a: TypedArray, b: TypedArray): void { assert.equal(a.length, b.length); @@ -38,8 +38,8 @@ describe('polyfill conformance tests', function(): void { } } - describe('TypedArray.fill', function(): void { - it('should work with all typed array types', function(): void { + describe('TypedArray.fill', () => { + it('should work with all typed array types', () => { const u81 = new Uint8Array(5); const u82 = new Uint8Array(5); deepEquals(fill(u81, 2), u82.fill(2)); @@ -79,7 +79,7 @@ describe('polyfill conformance tests', function(): void { deepEquals(fill(u8Clamped1, 2), u8Clamped2.fill(2)); deepEquals(fill(u8Clamped1, 257), u8Clamped2.fill(257)); }); - it('should work with all typed array types - explicit looping', function(): void { + it('should work with all typed array types - explicit looping', () => { const u81 = new Uint8Array(5); const u82 = new Uint8Array(5); deepEquals(loopFill(u81, 2), u82.fill(2)); @@ -119,7 +119,7 @@ describe('polyfill conformance tests', function(): void { deepEquals(loopFill(u8Clamped1, 2), u8Clamped2.fill(2)); deepEquals(loopFill(u8Clamped1, 257), u8Clamped2.fill(257)); }); - it('start offset', function(): void { + it('start offset', () => { for (let i = -2; i < 10; ++i) { const u81 = new Uint8Array(5); const u82 = new Uint8Array(5); @@ -130,7 +130,7 @@ describe('polyfill conformance tests', function(): void { deepEquals(loopFill(u82, -1, i), u83.fill(-1, i)); } }); - it('end offset', function(): void { + it('end offset', () => { for (let i = -2; i < 10; ++i) { const u81 = new Uint8Array(5); const u82 = new Uint8Array(5); @@ -141,7 +141,7 @@ describe('polyfill conformance tests', function(): void { deepEquals(loopFill(u82, -1, 0, i), u83.fill(-1, 0, i)); } }); - it('start/end offset', function(): void { + it('start/end offset', () => { for (let i = -2; i < 10; ++i) { for (let j = -2; j < 10; ++j) { const u81 = new Uint8Array(5); @@ -155,4 +155,110 @@ describe('polyfill conformance tests', function(): void { } }); }); + + describe('TypedArray.slice', () => { + describe('should work with all typed array types', () => { + it('Uint8Array', () => { + const a = new Uint8Array(5); + console.log(a); + console.log(a.slice(65535)); + console.log(sliceFallback(a, 65535)); + deepEquals(sliceFallback(a, 2), a.slice(2)); + deepEquals(sliceFallback(a, 65535), a.slice(65535)); + deepEquals(sliceFallback(a, -1), a.slice(-1)); + }); + it('Uint16Array', () => { + const u161 = new Uint16Array(5); + const u162 = new Uint16Array(5); + deepEquals(sliceFallback(u161, 2), u162.slice(2)); + deepEquals(sliceFallback(u161, 65535), u162.slice(65535)); + deepEquals(sliceFallback(u161, -1), u162.slice(-1)); + }); + it('Uint32Array', () => { + const u321 = new Uint32Array(5); + const u322 = new Uint32Array(5); + deepEquals(sliceFallback(u321, 2), u322.slice(2)); + deepEquals(sliceFallback(u321, 65537), u322.slice(65537)); + deepEquals(sliceFallback(u321, -1), u322.slice(-1)); + }); + it('Int8Array', () => { + const i81 = new Int8Array(5); + const i82 = new Int8Array(5); + deepEquals(sliceFallback(i81, 2), i82.slice(2)); + deepEquals(sliceFallback(i81, 65537), i82.slice(65537)); + deepEquals(sliceFallback(i81, -1), i82.slice(-1)); + }); + it('Int16Array', () => { + const i161 = new Int16Array(5); + const i162 = new Int16Array(5); + deepEquals(sliceFallback(i161, 2), i162.slice(2)); + deepEquals(sliceFallback(i161, 65535), i162.slice(65535)); + deepEquals(sliceFallback(i161, -1), i162.slice(-1)); + }); + it('Int32Array', () => { + const i321 = new Int32Array(5); + const i322 = new Int32Array(5); + deepEquals(sliceFallback(i321, 2), i322.slice(2)); + deepEquals(sliceFallback(i321, 65537), i322.slice(65537)); + deepEquals(sliceFallback(i321, -1), i322.slice(-1)); + }); + it('Float32Array', () => { + const f321 = new Float32Array(5); + const f322 = new Float32Array(5); + deepEquals(sliceFallback(f321, 2), f322.slice(2)); + deepEquals(sliceFallback(f321, 65537), f322.slice(65537)); + deepEquals(sliceFallback(f321, -1), f322.slice(-1)); + }); + it('Float64Array', () => { + const f641 = new Float64Array(5); + const f642 = new Float64Array(5); + deepEquals(sliceFallback(f641, 2), f642.slice(2)); + deepEquals(sliceFallback(f641, 65537), f642.slice(65537)); + deepEquals(sliceFallback(f641, -1), f642.slice(-1)); + }); + it('Uint8ClampedArray', () => { + const u8Clamped1 = new Uint8ClampedArray(5); + const u8Clamped2 = new Uint8ClampedArray(5); + deepEquals(sliceFallback(u8Clamped1, 2), u8Clamped2.slice(2)); + deepEquals(sliceFallback(u8Clamped1, 65537), u8Clamped2.slice(65537)); + deepEquals(sliceFallback(u8Clamped1, -1), u8Clamped2.slice(-1)); + }); + }); + it('start', () => { + const arr = new Uint32Array([1, 2, 3, 4, 5]); + deepEquals(sliceFallback(arr, -1), arr.slice(-1)); + deepEquals(sliceFallback(arr, 0), arr.slice(0)); + deepEquals(sliceFallback(arr, 1), arr.slice(1)); + deepEquals(sliceFallback(arr, 2), arr.slice(2)); + deepEquals(sliceFallback(arr, 3), arr.slice(3)); + deepEquals(sliceFallback(arr, 4), arr.slice(4)); + deepEquals(sliceFallback(arr, 5), arr.slice(5)); + }); + it('end', () => { + const arr = new Uint32Array([1, 2, 3, 4, 5]); + deepEquals(sliceFallback(arr, -1, -2), arr.slice(-1, -2)); + deepEquals(sliceFallback(arr, 0, -2), arr.slice(0, -2)); + deepEquals(sliceFallback(arr, 1, -2), arr.slice(1, -2)); + deepEquals(sliceFallback(arr, 2, -2), arr.slice(2, -2)); + deepEquals(sliceFallback(arr, 3, -2), arr.slice(3, -2)); + deepEquals(sliceFallback(arr, 4, -2), arr.slice(4, -2)); + deepEquals(sliceFallback(arr, 5, -2), arr.slice(5, -2)); + + deepEquals(sliceFallback(arr, -1, 3), arr.slice(-1, 3)); + deepEquals(sliceFallback(arr, 0, 3), arr.slice(0, 3)); + deepEquals(sliceFallback(arr, 1, 3), arr.slice(1, 3)); + deepEquals(sliceFallback(arr, 2, 3), arr.slice(2, 3)); + deepEquals(sliceFallback(arr, 3, 3), arr.slice(3, 3)); + deepEquals(sliceFallback(arr, 4, 3), arr.slice(4, 3)); + deepEquals(sliceFallback(arr, 5, 3), arr.slice(5, 3)); + + deepEquals(sliceFallback(arr, -1, 8), arr.slice(-1, 8)); + deepEquals(sliceFallback(arr, 0, 8), arr.slice(0, 8)); + deepEquals(sliceFallback(arr, 1, 8), arr.slice(1, 8)); + deepEquals(sliceFallback(arr, 2, 8), arr.slice(2, 8)); + deepEquals(sliceFallback(arr, 3, 8), arr.slice(3, 8)); + deepEquals(sliceFallback(arr, 4, 8), arr.slice(4, 8)); + deepEquals(sliceFallback(arr, 5, 8), arr.slice(5, 8)); + }); + }); }); diff --git a/src/core/TypedArrayUtils.ts b/src/core/TypedArrayUtils.ts index 56e9d7b0..262ae25b 100644 --- a/src/core/TypedArrayUtils.ts +++ b/src/core/TypedArrayUtils.ts @@ -38,3 +38,29 @@ export function fill(array: TypedArray, value: number, start: number = 0, end?: } return array; } + +export function slice(array: T, start?: number, end?: number): T { + // all modern engines that support .slice + if (array.slice) { + return array.slice(start, end) as T; + } + return sliceFallback(array, start, end); +} + +export function sliceFallback(array: T, start: number = 0, end: number = array.length): T { + if (start < 0) { + start = (array.length + start) % array.length; + } + if (end >= array.length) { + end = array.length; + } else { + end = (array.length + end) % array.length; + } + start = Math.min(start, end); + + const result: T = new (array).__proto__.constructor(end - start); + for (let i = 0; i < end - start; ++i) { + result[i] = array[i + start]; + } + return result; +} diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index b431aec7..bb6ebc88 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -11,7 +11,7 @@ import WebglCharAtlas from './WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; -import { fill } from '../../core/TypedArrayUtils'; +import { fill, slice } from '../../core/TypedArrayUtils'; interface IVertices { attributes: Float32Array; @@ -210,7 +210,7 @@ export class GlyphRenderer { public updateSelection(model: IRenderModel, columnSelectMode: boolean): void { const terminal = this._terminal; - this._vertices.selectionAttributes = this._vertices.attributes.slice(0); + this._vertices.selectionAttributes = slice(this._vertices.attributes, 0); // TODO: Make fg and bg configurable, currently since the buffer doesn't // support truecolor the char atlas cannot store it. From 186b2be4b91b4e6d7c222829d5fc40b2f464b26b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 24 Nov 2018 05:48:05 -0800 Subject: [PATCH 06/69] Fix imports --- src/renderer/webgl/GlyphRenderer.ts | 2 +- src/renderer/webgl/RectangleRenderer.ts | 2 +- src/renderer/webgl/RenderModel.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index bb6ebc88..63170f39 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -11,7 +11,7 @@ import WebglCharAtlas from './WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; -import { fill, slice } from '../../core/TypedArrayUtils'; +import { fill, slice } from '../../common/TypedArrayUtils'; interface IVertices { attributes: Float32Array; diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index 2642ec73..51b8bdce 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -9,7 +9,7 @@ import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUti import { IColor } from '../../shared/Types'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; import { RENDER_INVERTED_DEFAULT_COLOR } from './RenderModel'; -import { fill } from '../../core/TypedArrayUtils'; +import { fill } from '../../common/TypedArrayUtils'; const enum VertexAttribLocations { POSITION = 0, diff --git a/src/renderer/webgl/RenderModel.ts b/src/renderer/webgl/RenderModel.ts index a3b2b2c9..c55a6f73 100644 --- a/src/renderer/webgl/RenderModel.ts +++ b/src/renderer/webgl/RenderModel.ts @@ -4,7 +4,7 @@ */ import { IRenderModel, ISelectionRenderModel } from './Types'; -import { fill } from '../../core/TypedArrayUtils'; +import { fill } from '../../common/TypedArrayUtils'; export const RENDER_MODEL_INDICIES_PER_CELL = 4; From 60e3ddbfd03ea61e2837e51f6fbedd8c04e58507 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 24 Nov 2018 05:52:23 -0800 Subject: [PATCH 07/69] Update color codes after merging the move to constants --- src/renderer/webgl/RectangleRenderer.ts | 14 +++++++------- src/renderer/webgl/RenderModel.ts | 5 ----- src/renderer/webgl/WebglCharAtlas.ts | 15 +++++++-------- src/renderer/webgl/WebglRenderer.ts | 12 ++++++------ 4 files changed, 20 insertions(+), 26 deletions(-) diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index 51b8bdce..cd368621 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -8,8 +8,9 @@ import { IColorManager, IRenderDimensions } from '../Types'; import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IColor } from '../../shared/Types'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; -import { RENDER_INVERTED_DEFAULT_COLOR } from './RenderModel'; import { fill } from '../../common/TypedArrayUtils'; +import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from '../atlas/Types'; +import { is256Color } from '../atlas/CharAtlasUtils'; const enum VertexAttribLocations { POSITION = 0, @@ -243,16 +244,15 @@ export class RectangleRenderer { let rectangleCount = 1; - const DEFAULT_BACKGROUND_COLOR = 256; for (let y = 0; y < terminal.rows; y++) { let currentStartX = -1; - let currentBg = DEFAULT_BACKGROUND_COLOR; + let currentBg = DEFAULT_COLOR; for (let x = 0; x < terminal.cols; x++) { const modelIndex = ((y * terminal.cols) + x) * 4; const bg = model.cells[modelIndex + 2]; if (bg !== currentBg) { // A rectangle needs to be drawn if going from non-default to another color - if (currentBg !== DEFAULT_BACKGROUND_COLOR) { + if (currentBg !== DEFAULT_COLOR) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; this._updateRectangle(vertices, offset, currentBg, currentStartX, x, y); } @@ -261,7 +261,7 @@ export class RectangleRenderer { } } // Finish rectangle if it's still going - if (currentBg !== DEFAULT_BACKGROUND_COLOR) { + if (currentBg !== DEFAULT_COLOR) { const offset = rectangleCount++ * INDICES_PER_RECTANGLE; this._updateRectangle(vertices, offset, currentBg, currentStartX, terminal.cols, y); } @@ -271,9 +271,9 @@ export class RectangleRenderer { private _updateRectangle(vertices: IVertices, offset: number, bg: number, startX: number, endX: number, y: number): void { let color: IColor | null = null; - if (bg === RENDER_INVERTED_DEFAULT_COLOR) { + if (bg === INVERTED_DEFAULT_COLOR) { color = this._colorManager.colors.foreground; - } else if (bg < 256) { + } else if (is256Color(bg)) { color = this._colorManager.colors.ansi[bg]; } if (vertices.attributes.length < offset + 4) { diff --git a/src/renderer/webgl/RenderModel.ts b/src/renderer/webgl/RenderModel.ts index c55a6f73..54fc91ca 100644 --- a/src/renderer/webgl/RenderModel.ts +++ b/src/renderer/webgl/RenderModel.ts @@ -8,11 +8,6 @@ import { fill } from '../../common/TypedArrayUtils'; export const RENDER_MODEL_INDICIES_PER_CELL = 4; -// HACK: Cannot use INVERTED_DEFAULT_COLOR (-1) here because _model.cells is a -// Uint32Array. This should be changed when true color is introduced to whatever -// the mechanism is for the buffer. -export const RENDER_INVERTED_DEFAULT_COLOR = 258; - export const COMBINED_CHAR_BIT_MASK = 0x80000000; export class RenderModel implements IRenderModel { diff --git a/src/renderer/webgl/WebglCharAtlas.ts b/src/renderer/webgl/WebglCharAtlas.ts index 86a60d07..03e9b95b 100644 --- a/src/renderer/webgl/WebglCharAtlas.ts +++ b/src/renderer/webgl/WebglCharAtlas.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { DIM_OPACITY, IGlyphIdentifier } from '../atlas/Types'; +import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from '../atlas/Types'; import { ICharAtlasConfig } from '../../shared/atlas/Types'; import { IColor } from '../../shared/Types'; import BaseCharAtlas from '../atlas/BaseCharAtlas'; @@ -12,7 +12,7 @@ import { clearColor } from '../../shared/atlas/CharAtlasGenerator'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from './Types'; import { DEFAULT_ATTR } from '../../Buffer'; import { FLAGS } from '../Types'; -import { RENDER_INVERTED_DEFAULT_COLOR } from './RenderModel'; +import { is256Color } from '../atlas/CharAtlasUtils'; // 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. @@ -97,7 +97,7 @@ export default class WebglCharAtlas extends BaseCharAtlas { protected _doWarmUp(): void { // Pre-fill with ASCII 33-126 for (let i = 33; i < 126; i++) { - const rasterizedGlyph = this._drawToCache(i, DEFAULT_ATTR, 256, 257, true); + const rasterizedGlyph = this._drawToCache(i, DEFAULT_ATTR, DEFAULT_COLOR, DEFAULT_COLOR, true); this._cacheMap[i] = { [DEFAULT_ATTR]: rasterizedGlyph }; @@ -175,19 +175,18 @@ export default class WebglCharAtlas extends BaseCharAtlas { // 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; - } else if (bg === RENDER_INVERTED_DEFAULT_COLOR) { + } else if (bg === INVERTED_DEFAULT_COLOR) { return this._config.colors.foreground; - } else if (bg < 256) { + } else if (is256Color(bg)) { return this._getColorFromAnsiIndex(bg); } return this._config.colors.background; } private _getForegroundColor(fg: number): IColor { - if (fg === RENDER_INVERTED_DEFAULT_COLOR) { + if (fg === INVERTED_DEFAULT_COLOR) { return this._config.colors.background; - } else if (fg < 256) { - // 256 color support + } else if (is256Color(fg)) { return this._getColorFromAnsiIndex(fg); } return this._config.colors.foreground; diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 61fb9d34..dfefad48 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -18,8 +18,8 @@ import { ScreenDprMonitor } from '../../ui/ScreenDprMonitor'; import { RectangleRenderer } from './RectangleRenderer'; import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../../Buffer'; import { IWebGL2RenderingContext } from './Types'; -import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; -import { RenderModel, RENDER_INVERTED_DEFAULT_COLOR, COMBINED_CHAR_BIT_MASK } from './RenderModel'; +import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from '../atlas/Types'; +import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; export const INDICIES_PER_CELL = 4; @@ -302,11 +302,11 @@ export class WebglRenderer extends EventEmitter implements IRenderer { const temp = bg; bg = fg; fg = temp; - if (fg === 256) { - fg = RENDER_INVERTED_DEFAULT_COLOR; + if (fg === DEFAULT_COLOR) { + fg = INVERTED_DEFAULT_COLOR; } - if (bg === 257) { - bg = RENDER_INVERTED_DEFAULT_COLOR; + if (bg === DEFAULT_COLOR) { + bg = INVERTED_DEFAULT_COLOR; } } const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && !!(flags & FLAGS.BOLD) && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; From b8b111f4a6b5350fd2509affd576f7a711ba2ebf Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 25 Nov 2018 09:23:08 -0800 Subject: [PATCH 08/69] Only pass a subset of the attributes buffer to GPU --- src/renderer/webgl/GlyphRenderer.ts | 73 ++++++++++++++++------------- src/renderer/webgl/WebglRenderer.ts | 8 +++- 2 files changed, 47 insertions(+), 34 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 63170f39..3cd5679e 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -15,8 +15,14 @@ import { fill, slice } from '../../common/TypedArrayUtils'; interface IVertices { attributes: Float32Array; + /** + * These buffers are the ones used to bind to WebGL, the reason there are + * multiple is to allow double buffering to work as you cannot modify the + * buffer while it's being used by the GPU. Having multiple lets us start + * working on the next frame. + */ + attributesBuffers: Float32Array[]; selectionAttributes: Float32Array; - cellPosition: Float32Array; count: number; } @@ -61,8 +67,9 @@ void main() { outColor = texture(u_texture, v_texcoord); }`; -const INDICES_PER_CELL = 8; +const INDICES_PER_CELL = 10; const BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT; +const CELL_POSITION_INDICES = 2; export class GlyphRenderer { private _atlas: WebglCharAtlas; @@ -74,14 +81,16 @@ export class GlyphRenderer { private _textureLocation: WebGLUniformLocation; private _atlasTexture: WebGLTexture; private _attributesBuffer: WebGLBuffer; - private _cellPositionBuffer: WebGLBuffer; + private _activeBuffer: number = 0; - private _lineLengths: Int16Array = new Int16Array(0); private _vertices: IVertices = { count: 0, attributes: new Float32Array(0), - selectionAttributes: new Float32Array(0), - cellPosition: new Float32Array(0) + attributesBuffers: [ + new Float32Array(0), + new Float32Array(0) + ], + selectionAttributes: new Float32Array(0) }; constructor( @@ -117,13 +126,6 @@ export class GlyphRenderer { gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, elementIndicesBuffer); gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, unitQuadElementIndices, gl.STATIC_DRAW); - // Setup a_cellpos, this is separate as it rarely changed - this._cellPositionBuffer = gl.createBuffer(); - gl.bindBuffer(gl.ARRAY_BUFFER, this._cellPositionBuffer); - gl.enableVertexAttribArray(VertexAttribLocations.CELL_POSITION); - gl.vertexAttribPointer(VertexAttribLocations.CELL_POSITION, 2, gl.FLOAT, false, 0, 0); - gl.vertexAttribDivisor(VertexAttribLocations.CELL_POSITION, 1); - // Setup attributes this._attributesBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); @@ -139,6 +141,9 @@ export class GlyphRenderer { gl.enableVertexAttribArray(VertexAttribLocations.TEXSIZE); gl.vertexAttribPointer(VertexAttribLocations.TEXSIZE, 2, gl.FLOAT, false, BYTES_PER_CELL, 6 * Float32Array.BYTES_PER_ELEMENT); gl.vertexAttribDivisor(VertexAttribLocations.TEXSIZE, 1); + gl.enableVertexAttribArray(VertexAttribLocations.CELL_POSITION); + gl.vertexAttribPointer(VertexAttribLocations.CELL_POSITION, 2, gl.FLOAT, false, BYTES_PER_CELL, 8 * Float32Array.BYTES_PER_ELEMENT); + gl.vertexAttribDivisor(VertexAttribLocations.CELL_POSITION, 1); // Setup empty texture atlas this._atlasTexture = gl.createTexture(); @@ -166,11 +171,11 @@ export class GlyphRenderer { private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, attr: number, bg: number, fg: number, chars?: string): void { const terminal = this._terminal; - const i = ((y * terminal.cols) + x) * INDICES_PER_CELL; + const i = (y * terminal.cols + x) * INDICES_PER_CELL; // Exit early if this is a null/space character if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) { - fill(array, 0, i, i + INDICES_PER_CELL - 1); + fill(array, 0, i, i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES); return; } @@ -183,7 +188,7 @@ export class GlyphRenderer { // Fill empty if no glyph was found if (!rasterizedGlyph) { - fill(array, 0, i, i + INDICES_PER_CELL - 1); + fill(array, 0, i, i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES); return; } @@ -199,12 +204,7 @@ export class GlyphRenderer { // a_texsize array[i + 6] = rasterizedGlyph.sizeClipSpace.x; array[i + 7] = rasterizedGlyph.sizeClipSpace.y; - } - - public updateLineEnd(x: number, y: number): void { - // Clears all cells to the right of the line end - const i = (y * this._terminal.cols + x + 1) * INDICES_PER_CELL; - fill(this._vertices.attributes, 0, i, i + (this._terminal.cols - this._lineLengths[y]) * INDICES_PER_CELL - 1); + // a_cellpos only changes on resize } public updateSelection(model: IRenderModel, columnSelectMode: boolean): void { @@ -279,15 +279,16 @@ export class GlyphRenderer { if (this._vertices.count !== newCount) { this._vertices.count = newCount; this._vertices.attributes = new Float32Array(newCount); - this._lineLengths = new Int16Array(terminal.rows); - - this._vertices.cellPosition = new Float32Array(terminal.cols * terminal.rows * 2); + for (let i = 0; i < this._vertices.attributesBuffers.length; i++) { + this._vertices.attributesBuffers[i] = new Float32Array(newCount); + } let i = 0; for (let y = 0; y < terminal.rows; y++) { for (let x = 0; x < terminal.cols; x++) { - this._vertices.cellPosition[i++] = x / terminal.cols; - this._vertices.cellPosition[i++] = y / terminal.rows; + i += 8; + this._vertices.attributes[i++] = x / terminal.cols; + this._vertices.attributes[i++] = y / terminal.rows; } } } @@ -296,7 +297,7 @@ export class GlyphRenderer { public onThemeChanged(): void { } - public render(isSelectionVisible: boolean): void { + public render(renderModel: IRenderModel, isSelectionVisible: boolean): void { if (!this._atlas) { return; } @@ -306,10 +307,18 @@ export class GlyphRenderer { gl.useProgram(this._program); gl.bindVertexArray(this._vertexArrayObject); - gl.bindBuffer(gl.ARRAY_BUFFER, this._cellPositionBuffer); - gl.bufferData(gl.ARRAY_BUFFER, this._vertices.cellPosition, gl.STATIC_DRAW); + this._activeBuffer = (this._activeBuffer + 1) % 2; + this._vertices.attributesBuffers[this._activeBuffer]; + let bufferLength = 0; + for (let y = 0; y < renderModel.lineLengths.length; y++) { + const si = y * this._terminal.cols * INDICES_PER_CELL; + const sub = (isSelectionVisible ? this._vertices.selectionAttributes : this._vertices.attributes).subarray(si, si + renderModel.lineLengths[y] * INDICES_PER_CELL); + this._vertices.attributesBuffers[this._activeBuffer].set(sub, bufferLength); + bufferLength += sub.length; + } gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); - gl.bufferData(gl.ARRAY_BUFFER, isSelectionVisible ? this._vertices.selectionAttributes : this._vertices.attributes, gl.DYNAMIC_DRAW); + const buffer = this._vertices.attributesBuffers[this._activeBuffer].subarray(0, bufferLength); + gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STREAM_DRAW); // Bind the texture atlas if it's changed if (this._atlas.hasCanvasChanged) { @@ -326,7 +335,7 @@ export class GlyphRenderer { gl.uniform2f(this._resolutionLocation, gl.canvas.width, gl.canvas.height); // Draw the viewport - gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, this._vertices.count / INDICES_PER_CELL); + gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, buffer.length / INDICES_PER_CELL); } public setAtlas(atlas: WebglCharAtlas): void { diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index dfefad48..f8d73a32 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -16,7 +16,7 @@ import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import WebglCharAtlas from './WebglCharAtlas'; import { ScreenDprMonitor } from '../../ui/ScreenDprMonitor'; import { RectangleRenderer } from './RectangleRenderer'; -import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX } from '../../Buffer'; +import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, NULL_CELL_CODE } from '../../Buffer'; import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from '../atlas/Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; @@ -267,7 +267,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { // Render this._rectangleRenderer.render(); - this._glyphRenderer.render(this._model.selection.hasSelection); + this._glyphRenderer.render(this._model, this._model.selection.hasSelection); // Emit event this._terminal.emit('refresh', { start, end }); @@ -287,6 +287,10 @@ export class WebglRenderer extends EventEmitter implements IRenderer { const attr = charData[CHAR_DATA_ATTR_INDEX]; const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL; + if (code !== NULL_CELL_CODE) { + this._model.lineLengths[y] = x + 1; + } + // Nothing has changed, no updates needed if (this._model.cells[i] === code && this._model.cells[i + 1] === attr) { continue; From 6bc9b9b14d86c8d7bf91e2530d39c847e74b3ff3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 29 Nov 2018 10:26:04 -0800 Subject: [PATCH 09/69] Improve constructor use in sliceFallback --- src/common/TypedArrayUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/common/TypedArrayUtils.ts b/src/common/TypedArrayUtils.ts index 7c647861..e712e0a7 100644 --- a/src/common/TypedArrayUtils.ts +++ b/src/common/TypedArrayUtils.ts @@ -59,7 +59,7 @@ export function sliceFallback(array: T, start: number = 0, } start = Math.min(start, end); - const result: T = new (array).__proto__.constructor(end - start); + const result: T = new (array.constructor as any)(end - start); for (let i = 0; i < end - start; ++i) { result[i] = array[i + start]; } From 3d8b304af4dcd5c2447e10ee0a0c2ee94d22973f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 9 Dec 2018 08:31:49 -0800 Subject: [PATCH 10/69] Reduce precision of floats in fragment shader ~0.43ms/frame -> ~0.37ms/frame --- src/renderer/webgl/GlyphRenderer.ts | 2 +- src/renderer/webgl/RectangleRenderer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 3cd5679e..51adc999 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -55,7 +55,7 @@ void main() { }`; const fragmentShaderSource = `#version 300 es -precision mediump float; +precision lowp float; in vec2 v_texcoord; diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index 37732703..addc13c5 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -36,7 +36,7 @@ void main() { }`; const fragmentShaderSource = `#version 300 es -precision mediump float; +precision lowp float; in vec3 v_color; From bb9ccf40b62a6730dcf9b5faba77e170f0d44304 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 9 Dec 2018 09:28:04 -0800 Subject: [PATCH 11/69] Reduce diff with master --- src/renderer/atlas/CharAtlasCache.ts | 5 ++--- src/renderer/atlas/CharAtlasUtils.ts | 4 ++-- src/renderer/webgl/WebglRenderer.ts | 6 +++--- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/renderer/atlas/CharAtlasCache.ts b/src/renderer/atlas/CharAtlasCache.ts index 5a9c1a6e..db7db118 100644 --- a/src/renderer/atlas/CharAtlasCache.ts +++ b/src/renderer/atlas/CharAtlasCache.ts @@ -40,10 +40,9 @@ export function acquireCharAtlas( terminal: ITerminal, colors: IColorSet, scaledCharWidth: number, - scaledCharHeight: number, - devicePixelRatio?: number + scaledCharHeight: number ): BaseCharAtlas { - const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors, devicePixelRatio); + const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors); // TODO: Currently if a terminal changes configs it will not free the entry reference (until it's disposed) diff --git a/src/renderer/atlas/CharAtlasUtils.ts b/src/renderer/atlas/CharAtlasUtils.ts index 52c8a9bd..5b1add39 100644 --- a/src/renderer/atlas/CharAtlasUtils.ts +++ b/src/renderer/atlas/CharAtlasUtils.ts @@ -7,7 +7,7 @@ import { ITerminal } from '../../Types'; import { IColorSet } from '../Types'; import { DEFAULT_COLOR, ICharAtlasConfig } from './Types'; -export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet, devicePixelRatio: number = window.devicePixelRatio): ICharAtlasConfig { +export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { // null out some fields that don't matter const clonedColors = { foreground: colors.foreground, @@ -21,7 +21,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number }; return { type: terminal.options.experimentalCharAtlas, - devicePixelRatio, + devicePixelRatio: window.devicePixelRatio, scaledCharWidth, scaledCharHeight, fontFamily: terminal.options.fontFamily, diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index f8d73a32..92e9828a 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -173,7 +173,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this._glyphRenderer.setDimensions(this.dimensions); this._glyphRenderer.onResize(); - this._refreshCharAtlas(devicePixelRatio); + this._refreshCharAtlas(); this._refreshViewport(); this.emit('resize', { @@ -219,12 +219,12 @@ export class WebglRenderer extends EventEmitter implements IRenderer { * @param terminal The terminal. * @param colorSet The color set to use for the char atlas. */ - private _refreshCharAtlas(devicePixelRatio: number = window.devicePixelRatio): void { + private _refreshCharAtlas(): void { if (this.dimensions.scaledCharWidth <= 0 && this.dimensions.scaledCharHeight <= 0) { return; } - const atlas = acquireCharAtlas(this._terminal, this.colorManager.colors, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight, devicePixelRatio); + const atlas = acquireCharAtlas(this._terminal, this.colorManager.colors, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight); if (!('getRasterizedGlyph' in atlas)) { throw new Error('The webgl renderer only works with the webgl char atlas'); } From 5f9144dfd71b2aaedf43ed6a1ffd38167863b9a4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 27 Dec 2018 16:56:20 -0800 Subject: [PATCH 12/69] Fix null/whitespace early exit --- src/renderer/webgl/GlyphRenderer.ts | 6 +++--- src/renderer/webgl/WebglCharAtlas.ts | 5 ----- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 51adc999..3f8f6edf 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -6,7 +6,7 @@ import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderDimensions } from '../Types'; import { ITerminal, IBufferLine } from '../../Types'; -import { NULL_CELL_CODE, CHAR_DATA_CHAR_INDEX } from '../../Buffer'; +import { NULL_CELL_CODE, CHAR_DATA_CHAR_INDEX, WHITESPACE_CELL_CODE } from '../../Buffer'; import WebglCharAtlas from './WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; @@ -174,7 +174,7 @@ export class GlyphRenderer { const i = (y * terminal.cols + x) * INDICES_PER_CELL; // Exit early if this is a null/space character - if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) { + if (code === NULL_CELL_CODE || code === WHITESPACE_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) { fill(array, 0, i, i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES); return; } @@ -232,7 +232,7 @@ export class GlyphRenderer { // Draw middle rows const middleRowsCount = Math.max(model.selection.viewportCappedEndRow - model.selection.viewportCappedStartRow - 1, 0); - for (let y = (model.selection.viewportCappedStartRow + 1); y <= model.selection.viewportCappedStartRow + middleRowsCount; y++) { + for (let y = model.selection.viewportCappedStartRow + 1; y <= model.selection.viewportCappedStartRow + middleRowsCount; y++) { this._updateSelectionRange(0, startRowEndCol, y, model, bg, fg); } diff --git a/src/renderer/webgl/WebglCharAtlas.ts b/src/renderer/webgl/WebglCharAtlas.ts index d181acbf..6eae9579 100644 --- a/src/renderer/webgl/WebglCharAtlas.ts +++ b/src/renderer/webgl/WebglCharAtlas.ts @@ -133,11 +133,6 @@ export default class WebglCharAtlas extends BaseCharAtlas { * Gets the glyphs texture coords, drawing the texture if it's not already */ public getRasterizedGlyph(code: number, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph { - // Space is always an empty cell, special case this as it's so common - if (code === 32) { - return; - } - let rasterizedGlyphSet = this._cacheMap[code]; if (!rasterizedGlyphSet) { rasterizedGlyphSet = {}; From 9cf3d14d8a5705eab65c8c3512c0191cc3205205 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 27 Dec 2018 17:01:13 -0800 Subject: [PATCH 13/69] Clean up loop that sets cell coordinates --- src/renderer/webgl/GlyphRenderer.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 3f8f6edf..7b280f19 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -286,9 +286,9 @@ export class GlyphRenderer { let i = 0; for (let y = 0; y < terminal.rows; y++) { for (let x = 0; x < terminal.cols; x++) { - i += 8; - this._vertices.attributes[i++] = x / terminal.cols; - this._vertices.attributes[i++] = y / terminal.rows; + this._vertices.attributes[i + 8] = x / terminal.cols; + this._vertices.attributes[i + 9] = y / terminal.rows; + i += INDICES_PER_CELL; } } } From 01b37b2fef2acfc9a1ef6f6e700d1823c4fbf993 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 27 Dec 2018 17:10:18 -0800 Subject: [PATCH 14/69] Document how GlyphRenderer.render works --- src/renderer/webgl/GlyphRenderer.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 7b280f19..41cab75e 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -307,18 +307,28 @@ export class GlyphRenderer { gl.useProgram(this._program); gl.bindVertexArray(this._vertexArrayObject); + // Alternate buffers each frame as the active buffer gets locked while it's in use by the GPU this._activeBuffer = (this._activeBuffer + 1) % 2; - this._vertices.attributesBuffers[this._activeBuffer]; + const activeBuffer = this._vertices.attributesBuffers[this._activeBuffer]; + + // Copy data for each cell of each line up to its line length (the last non-whitespace cell) + // from the attributes buffer into activeBuffer, which is the one that gets bound to the GPU. + // The reasons for this are as follows: + // - So the active buffer can be alternated so we don't get blocked on rendering finishing + // - To copy either the normal attributes buffer or the selection attributes buffer when there + // is a selection + // - So we don't send vertices for all the line-ending whitespace to the GPU let bufferLength = 0; for (let y = 0; y < renderModel.lineLengths.length; y++) { const si = y * this._terminal.cols * INDICES_PER_CELL; const sub = (isSelectionVisible ? this._vertices.selectionAttributes : this._vertices.attributes).subarray(si, si + renderModel.lineLengths[y] * INDICES_PER_CELL); - this._vertices.attributesBuffers[this._activeBuffer].set(sub, bufferLength); + activeBuffer.set(sub, bufferLength); bufferLength += sub.length; } + + // Bind the attributes buffer gl.bindBuffer(gl.ARRAY_BUFFER, this._attributesBuffer); - const buffer = this._vertices.attributesBuffers[this._activeBuffer].subarray(0, bufferLength); - gl.bufferData(gl.ARRAY_BUFFER, buffer, gl.STREAM_DRAW); + gl.bufferData(gl.ARRAY_BUFFER, activeBuffer.subarray(0, bufferLength), gl.STREAM_DRAW); // Bind the texture atlas if it's changed if (this._atlas.hasCanvasChanged) { From b0e51b9ec9bdbeaeed3ab27e372c6482bc173fe9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 2 Jan 2019 15:09:38 -0800 Subject: [PATCH 15/69] Fix build error --- src/renderer/webgl/GlyphRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 41cab75e..bd231eb7 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -345,7 +345,7 @@ export class GlyphRenderer { gl.uniform2f(this._resolutionLocation, gl.canvas.width, gl.canvas.height); // Draw the viewport - gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, buffer.length / INDICES_PER_CELL); + gl.drawElementsInstanced(gl.TRIANGLES, 6, gl.UNSIGNED_BYTE, 0, bufferLength / INDICES_PER_CELL); } public setAtlas(atlas: WebglCharAtlas): void { From 98706aa7d2100e40829583ed61de402cdb41242f Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Tue, 8 Jan 2019 14:52:55 -0500 Subject: [PATCH 16/69] Use bg luminance to determine background color --- src/renderer/ColorManager.ts | 18 +++++++++++++++++- src/renderer/Types.ts | 1 + src/renderer/webgl/GlyphRenderer.ts | 8 +++++--- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 8a463670..05727530 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -103,6 +103,14 @@ export class ColorManager implements IColorManager { }; } + // Coefficients taken from: https://www.w3.org/TR/AERT/#color-contrast + public getLuminance(color: IColor) : number { + const r = color.rgba >> 24 & 0xff; + const g = color.rgba >> 16 & 0xff; + const b = color.rgba >> 8 & 0xff; + return (0.299 * r + 0.587 * g + 0.114 * b) / 255; + } + /** * Sets the terminal's theme. * @param theme The theme to use. If a partial theme is provided then default @@ -113,7 +121,15 @@ export class ColorManager implements IColorManager { this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND); this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true); this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true); - this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true); + + // HACK: while webgl renderer adds support for selection colors + // this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true); + if (this.getLuminance(this.colors.background) > 0.5) { + this.colors.selection = this._parseColor('#000', DEFAULT_SELECTION, true); + } else { + this.colors.selection = this._parseColor('#fff', DEFAULT_SELECTION, true); + } + this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]); this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]); this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]); diff --git a/src/renderer/Types.ts b/src/renderer/Types.ts index f2271f95..c02211f1 100644 --- a/src/renderer/Types.ts +++ b/src/renderer/Types.ts @@ -45,6 +45,7 @@ export interface IRenderer extends IEventEmitter, IDisposable { export interface IColorManager { colors: IColorSet; + getLuminance(color: IColor): number; } export interface IRenderDimensions { diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index bd231eb7..a149dd86 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -4,7 +4,7 @@ */ import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; -import { IRenderDimensions } from '../Types'; +import { IColorManager, IRenderDimensions } from '../Types'; import { ITerminal, IBufferLine } from '../../Types'; import { NULL_CELL_CODE, CHAR_DATA_CHAR_INDEX, WHITESPACE_CELL_CODE } from '../../Buffer'; import WebglCharAtlas from './WebglCharAtlas'; @@ -95,6 +95,7 @@ export class GlyphRenderer { constructor( private _terminal: ITerminal, + private _colorManager: IColorManager, private _gl: IWebGL2RenderingContext, private _dimensions: IRenderDimensions ) { @@ -214,8 +215,9 @@ export class GlyphRenderer { // TODO: Make fg and bg configurable, currently since the buffer doesn't // support truecolor the char atlas cannot store it. - const fg = 0; - const bg = 7; + const lumi = this._colorManager.getLuminance(this._colorManager.colors.background) + const fg = lumi > 0.5 ? 7 : 0; + const bg = lumi > 0.5 ? 0 : 7; if (columnSelectMode) { const startCol = model.selection.startCol; From 1d7a39bcfdde1bb2b6bcecf4a718a9e650baf7e7 Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Tue, 15 Jan 2019 16:36:38 -0500 Subject: [PATCH 17/69] Disable antialiasing and depth buffer for WebGL2 contexts --- src/renderer/webgl/WebglRenderer.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 92e9828a..133f18f9 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -82,7 +82,8 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); this._canvas = document.createElement('canvas'); - this._gl = this._canvas.getContext('webgl2') as IWebGL2RenderingContext; + const contextAttributes = { antialias: false, depth: false }; + this._gl = this._canvas.getContext('webgl2', contextAttributes) as IWebGL2RenderingContext; if (!this._gl) { throw new Error('WebGL2 not supported'); } From 4181321377e56ffeb200f6bc5ad12338fb8bdaf5 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 16 Jan 2019 11:15:38 -0800 Subject: [PATCH 18/69] Add super.dispose to WebglRenderer.dispose --- src/renderer/webgl/WebglRenderer.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 133f18f9..1df95f09 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -104,6 +104,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { public dispose(): void { this._renderLayers.forEach(l => l.dispose()); this._terminal.screenElement.removeChild(this._canvas); + super.dispose(); } public onIntersectionChange(entry: IntersectionObserverEntry): void { From e387b20b56d59c0d9f101afbb993621ab118fff2 Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Fri, 18 Jan 2019 15:25:58 -0500 Subject: [PATCH 19/69] Adding missing parameter to GlyphRenderer --- src/renderer/webgl/WebglRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 92e9828a..715d79eb 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -89,7 +89,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this._terminal.screenElement.appendChild(this._canvas); this._rectangleRenderer = new RectangleRenderer(this._terminal, this.colorManager, this._gl, this.dimensions); - this._glyphRenderer = new GlyphRenderer(this._terminal, this._gl, this.dimensions); + this._glyphRenderer = new GlyphRenderer(this._terminal, this.colorManager, this._gl, this.dimensions); // Detect whether IntersectionObserver is detected and enable renderer pause // and resume based on terminal visibility if so From 3077ae531145ce71ae8e0911a1a3a5557c3a9a56 Mon Sep 17 00:00:00 2001 From: Juan Campa Date: Mon, 21 Jan 2019 18:56:54 -0500 Subject: [PATCH 20/69] Only apply the selection hack for the WebGL renderer --- src/renderer/ColorManager.ts | 19 ++++++++++--------- src/renderer/webgl/WebglRenderer.ts | 1 + 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 05727530..329a6439 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -121,15 +121,7 @@ export class ColorManager implements IColorManager { this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND); this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true); this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true); - - // HACK: while webgl renderer adds support for selection colors - // this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true); - if (this.getLuminance(this.colors.background) > 0.5) { - this.colors.selection = this._parseColor('#000', DEFAULT_SELECTION, true); - } else { - this.colors.selection = this._parseColor('#fff', DEFAULT_SELECTION, true); - } - + this.colors.selection = this._parseColor(theme.selection, DEFAULT_SELECTION, true); this.colors.ansi[0] = this._parseColor(theme.black, DEFAULT_ANSI_COLORS[0]); this.colors.ansi[1] = this._parseColor(theme.red, DEFAULT_ANSI_COLORS[1]); this.colors.ansi[2] = this._parseColor(theme.green, DEFAULT_ANSI_COLORS[2]); @@ -148,6 +140,15 @@ export class ColorManager implements IColorManager { this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]); } + public applyWebglSelectionColorHack() { + // HACK: while webgl renderer adds support for selection colors + if (this.getLuminance(this.colors.background) > 0.5) { + this.colors.selection = this._parseColor('#000', DEFAULT_SELECTION, true); + } else { + this.colors.selection = this._parseColor('#fff', DEFAULT_SELECTION, true); + } + } + private _parseColor( css: string, fallback: IColor, diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index c627fa6f..ec9e140d 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -52,6 +52,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this.colorManager = new ColorManager(document, allowTransparency); if (theme) { this.colorManager.setTheme(theme); + this.colorManager.applyWebglSelectionColorHack(); } this._renderLayers = [ From 1927bc899a54ed33c20ed65bb8ac320f8c5cd33d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 25 Jan 2019 20:51:19 -0800 Subject: [PATCH 21/69] Fix lint --- src/renderer/ColorManager.ts | 2 +- src/renderer/webgl/GlyphRenderer.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index 05727530..b1ad94c0 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -104,7 +104,7 @@ export class ColorManager implements IColorManager { } // Coefficients taken from: https://www.w3.org/TR/AERT/#color-contrast - public getLuminance(color: IColor) : number { + public getLuminance(color: IColor): number { const r = color.rgba >> 24 & 0xff; const g = color.rgba >> 16 & 0xff; const b = color.rgba >> 8 & 0xff; diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index a149dd86..9ef24a71 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -215,7 +215,7 @@ export class GlyphRenderer { // TODO: Make fg and bg configurable, currently since the buffer doesn't // support truecolor the char atlas cannot store it. - const lumi = this._colorManager.getLuminance(this._colorManager.colors.background) + const lumi = this._colorManager.getLuminance(this._colorManager.colors.background); const fg = lumi > 0.5 ? 7 : 0; const bg = lumi > 0.5 ? 0 : 7; From 1ce75cd4e9f92a3361438a93718d99c58747102a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 8 Mar 2019 12:16:27 -0800 Subject: [PATCH 22/69] Encapsulate hacks inside WebglRenderer --- src/renderer/ColorManager.ts | 9 --------- src/renderer/webgl/WebglRenderer.ts | 12 +++++++++++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/renderer/ColorManager.ts b/src/renderer/ColorManager.ts index ae23fd7a..3459e48f 100644 --- a/src/renderer/ColorManager.ts +++ b/src/renderer/ColorManager.ts @@ -140,15 +140,6 @@ export class ColorManager implements IColorManager { this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]); } - public applyWebglSelectionColorHack() { - // HACK: while webgl renderer adds support for selection colors - if (this.getLuminance(this.colors.background) > 0.5) { - this.colors.selection = this._parseColor('#000', DEFAULT_SELECTION, true); - } else { - this.colors.selection = this._parseColor('#fff', DEFAULT_SELECTION, true); - } - } - private _parseColor( css: string, fallback: IColor, diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index ec9e140d..f4eeba37 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -52,7 +52,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this.colorManager = new ColorManager(document, allowTransparency); if (theme) { this.colorManager.setTheme(theme); - this.colorManager.applyWebglSelectionColorHack(); + this._applyBgLuminanceBasedSelection(); } this._renderLayers = [ @@ -108,6 +108,15 @@ export class WebglRenderer extends EventEmitter implements IRenderer { super.dispose(); } + private _applyBgLuminanceBasedSelection(): void { + // HACK: While webgl renderer adds support for selection colors + if (this.colorManager.getLuminance(this.colorManager.colors.background) > 0.5) { + this.colorManager.colors.selection = { css: '#000', rgba: 255 }; + } else { + this.colorManager.colors.selection = { css: '#fff', rgba: 4294967295 }; + } + } + public onIntersectionChange(entry: IntersectionObserverEntry): void { this._isPaused = entry.intersectionRatio === 0; if (!this._isPaused && this._needsFullRefresh) { @@ -128,6 +137,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { public setTheme(theme: ITheme | undefined): IColorSet { if (theme) { this.colorManager.setTheme(theme); + this._applyBgLuminanceBasedSelection(); } // Clear layers and force a full render From 5ebb4e82d1cdd5f90d605260aeff5ec3dd7a52d3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 8 Mar 2019 12:18:27 -0800 Subject: [PATCH 23/69] Clarify comment --- src/renderer/webgl/WebglRenderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index f4eeba37..b727a0a6 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -109,7 +109,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { } private _applyBgLuminanceBasedSelection(): void { - // HACK: While webgl renderer adds support for selection colors + // HACK: This is needed until webgl renderer adds support for selection colors if (this.colorManager.getLuminance(this.colorManager.colors.background) > 0.5) { this.colorManager.colors.selection = { css: '#000', rgba: 255 }; } else { From 72719a6d69f26b927c4208245d0e02270f1276ac Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 14:22:47 -0700 Subject: [PATCH 24/69] Fix WebGL rendering after UTF-32 by fixing get chardata attr --- src/BufferLine.ts | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/BufferLine.ts b/src/BufferLine.ts index 2bd5a113..248ae663 100644 --- a/src/BufferLine.ts +++ b/src/BufferLine.ts @@ -5,6 +5,7 @@ import { CharData, IBufferLine, ICellData, IColorRGB, IAttributeData } from './Types'; import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, WHITESPACE_CELL_CHAR, CHAR_DATA_ATTR_INDEX } from './Buffer'; import { stringFromCodePoint } from './core/input/TextDecoder'; +import { FLAGS } from './renderer/Types'; /** @@ -321,8 +322,26 @@ export class BufferLine implements IBufferLine { public get(index: number): CharData { const content = this._data[index * CELL_SIZE + Cell.CONTENT]; const cp = content & Content.CODEPOINT_MASK; + + // TODO: Need to move WebGL over to the new system and remove this block + const cell = new CellData(); + this.loadCell(index, cell); + const oldBg = cell.getBgColor() === -1 ? 256 : cell.getBgColor(); + const oldFg = cell.getFgColor() === -1 ? 256 : cell.getFgColor(); + const oldAttr = + (cell.isBold() ? FLAGS.BOLD : 0) | + (cell.isUnderline() ? FLAGS.UNDERLINE : 0) | + (cell.isBlink() ? FLAGS.BLINK : 0) | + (cell.isInverse() ? FLAGS.INVERSE : 0) | + (cell.isDim() ? FLAGS.DIM : 0) | + (cell.isItalic() ? FLAGS.ITALIC : 0); + const attrCompat = + oldBg | + (oldFg << 9) | + (oldAttr << 18); + return [ - this._data[index * CELL_SIZE + Cell.FG], + attrCompat, (content & Content.IS_COMBINED_MASK) ? this._combined[index] : (cp) ? stringFromCodePoint(cp) : '', From 30567ec3cf07fd9343a30209c99584072eadd162 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 3 May 2019 21:23:50 -0700 Subject: [PATCH 25/69] Fix event emitter in webgl renderer --- src/renderer/webgl/WebglRenderer.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index b727a0a6..a144b947 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -3,7 +3,6 @@ * @license MIT */ -import { EventEmitter } from '../../common/EventEmitter'; import { IRenderer, IRenderDimensions, IColorSet, IRenderLayer, FLAGS } from '../Types'; import { ITheme } from 'xterm'; import { CharacterJoinerHandler, ITerminal } from '../../Types'; @@ -20,10 +19,12 @@ import { CHAR_DATA_ATTR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_CHAR_INDEX, NULL_ import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR, DEFAULT_COLOR } from '../atlas/Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; +import { EventEmitter2, IEvent } from '../../common/EventEmitter2'; +import { Disposable } from '../../common/Lifecycle'; export const INDICIES_PER_CELL = 4; -export class WebglRenderer extends EventEmitter implements IRenderer { +export class WebglRenderer extends Disposable implements IRenderer { private _renderDebouncer: RenderDebouncer; private _renderLayers: IRenderLayer[]; private _charAtlas: WebglCharAtlas; @@ -43,11 +44,17 @@ export class WebglRenderer extends EventEmitter implements IRenderer { public dimensions: IRenderDimensions; public colorManager: ColorManager; + private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); + public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } + private _onRender = new EventEmitter2<{ start: number, end: number }>(); + public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } + constructor( private _terminal: ITerminal, theme: ITheme ) { super(); + const allowTransparency = this._terminal.options.allowTransparency; this.colorManager = new ColorManager(document, allowTransparency); if (theme) { @@ -80,7 +87,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this._screenDprMonitor.setListener(() => this.onWindowResize(window.devicePixelRatio)); this.register(this._screenDprMonitor); - this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); + this._renderDebouncer = new RenderDebouncer(this._renderRows.bind(this)); this._canvas = document.createElement('canvas'); const contextAttributes = { antialias: false, depth: false }; @@ -189,7 +196,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this._refreshCharAtlas(); this._refreshViewport(); - this.emit('resize', { + this._onCanvasResize.fire({ width: this.dimensions.canvasWidth, height: this.dimensions.canvasHeight }); @@ -255,7 +262,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this._needsFullRefresh = true; return; } - this._renderDebouncer.refresh(start, end); + this._renderDebouncer.refresh(start, end, this._terminal.rows); } public registerCharacterJoiner(handler: CharacterJoinerHandler): number { @@ -283,7 +290,7 @@ export class WebglRenderer extends EventEmitter implements IRenderer { this._glyphRenderer.render(this._model, this._model.selection.hasSelection); // Emit event - this._terminal.emit('refresh', { start, end }); + this._onRender.fire({ start, end }); } private _updateModel(start: number, end: number): void { From cf867602d184cf29aa184d6e6a581524c66c041c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 18:24:54 -0700 Subject: [PATCH 26/69] Use IColorSet instead of IColorManager --- src/renderer/webgl/ColorUtils.ts | 14 ++++++++ src/renderer/webgl/GlyphRenderer.ts | 10 +++--- src/renderer/webgl/RectangleRenderer.ts | 15 ++++---- src/renderer/webgl/WebglCharAtlas.ts | 5 +-- src/renderer/webgl/WebglRenderer.ts | 47 ++++++++++--------------- src/ui/ColorManager.ts | 8 ----- src/ui/Types.ts | 1 - 7 files changed, 49 insertions(+), 51 deletions(-) create mode 100644 src/renderer/webgl/ColorUtils.ts diff --git a/src/renderer/webgl/ColorUtils.ts b/src/renderer/webgl/ColorUtils.ts new file mode 100644 index 00000000..9efdeb9c --- /dev/null +++ b/src/renderer/webgl/ColorUtils.ts @@ -0,0 +1,14 @@ +/** + * @license MIT + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + */ + +import { IColor } from '../../ui/Types'; + +export function getLuminance(color: IColor): number { + // Coefficients taken from: https://www.w3.org/TR/AERT/#color-contrast + const r = color.rgba >> 24 & 0xff; + const g = color.rgba >> 16 & 0xff; + const b = color.rgba >> 8 & 0xff; + return (0.299 * r + 0.587 * g + 0.114 * b) / 255; +} diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 6a6a873d..9bd4573d 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -4,7 +4,7 @@ */ import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; -import { IColorManager, IRenderDimensions } from '../Types'; +import { IRenderDimensions } from '../Types'; import { ITerminal } from '../../Types'; import WebglCharAtlas from './WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; @@ -13,6 +13,8 @@ import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { fill, slice } from '../../common/TypedArrayUtils'; import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, CHAR_DATA_CHAR_INDEX } from '../../core/buffer/BufferLine'; import { IBufferLine } from '../../core/Types'; +import { IColorSet } from '../../ui/Types'; +import { getLuminance } from './ColorUtils'; interface IVertices { attributes: Float32Array; @@ -96,7 +98,7 @@ export class GlyphRenderer { constructor( private _terminal: ITerminal, - private _colorManager: IColorManager, + private _colors: IColorSet, private _gl: IWebGL2RenderingContext, private _dimensions: IRenderDimensions ) { @@ -216,7 +218,7 @@ export class GlyphRenderer { // TODO: Make fg and bg configurable, currently since the buffer doesn't // support truecolor the char atlas cannot store it. - const lumi = this._colorManager.getLuminance(this._colorManager.colors.background); + const lumi = getLuminance(this._colors.background); const fg = lumi > 0.5 ? 7 : 0; const bg = lumi > 0.5 ? 0 : 7; @@ -297,7 +299,7 @@ export class GlyphRenderer { } } - public onThemeChanged(): void { + public onThemeChange(): void { } public render(renderModel: IRenderModel, isSelectionVisible: boolean): void { diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index 7399cc83..e047d7ac 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -4,13 +4,14 @@ */ import { ITerminal } from '../../Types'; -import { IColorManager, IRenderDimensions, IColor } from '../Types'; +import { IRenderDimensions } from '../Types'; import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; import { fill } from '../../common/TypedArrayUtils'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; import { is256Color } from '../atlas/CharAtlasUtils'; import { DEFAULT_COLOR } from '../../common/Types'; +import { IColorSet, IColor } from '../../ui/Types'; const enum VertexAttribLocations { POSITION = 0, @@ -76,7 +77,7 @@ export class RectangleRenderer { constructor( private _terminal: ITerminal, - private _colorManager: IColorManager, + private _colors: IColorSet, private _gl: IWebGL2RenderingContext, private _dimensions: IRenderDimensions ) { @@ -148,14 +149,14 @@ export class RectangleRenderer { this._updateViewportRectangle(); } - public onThemeChanged(): void { + public onThemeChange(): void { this._updateCachedColors(); this._updateViewportRectangle(); } private _updateCachedColors(): void { - this._bgFloat = this._colorToFloat32Array(this._colorManager.colors.background); - this._selectionFloat = this._colorToFloat32Array(this._colorManager.colors.selection); + this._bgFloat = this._colorToFloat32Array(this._colors.background); + this._selectionFloat = this._colorToFloat32Array(this._colors.selection); } private _updateViewportRectangle(): void { @@ -272,9 +273,9 @@ export class RectangleRenderer { private _updateRectangle(vertices: IVertices, offset: number, bg: number, startX: number, endX: number, y: number): void { let color: IColor | null = null; if (bg === INVERTED_DEFAULT_COLOR) { - color = this._colorManager.colors.foreground; + color = this._colors.foreground; } else if (is256Color(bg)) { - color = this._colorManager.colors.ansi[bg]; + color = this._colors.ansi[bg]; } if (vertices.attributes.length < offset + 4) { vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE); diff --git a/src/renderer/webgl/WebglCharAtlas.ts b/src/renderer/webgl/WebglCharAtlas.ts index 6963e714..a6bd4013 100644 --- a/src/renderer/webgl/WebglCharAtlas.ts +++ b/src/renderer/webgl/WebglCharAtlas.ts @@ -5,13 +5,14 @@ import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from '../atlas/Types'; import BaseCharAtlas from '../atlas/BaseCharAtlas'; -import { DEFAULT_ANSI_COLORS } from '../ColorManager'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from './Types'; -import { FLAGS, IColor } from '../Types'; +import { FLAGS } from '../Types'; import { is256Color } from '../atlas/CharAtlasUtils'; import { clearColor } from '../atlas/CharAtlasGenerator'; import { DEFAULT_ATTR } from '../../core/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../common/Types'; +import { IColor } from '../../ui/Types'; +import { DEFAULT_ANSI_COLORS } from '../../ui/ColorManager'; // 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. diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 15fe77d9..f7ad0e58 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -3,10 +3,8 @@ * @license MIT */ -import { IRenderer, IRenderDimensions, IColorSet, IRenderLayer, FLAGS } from '../Types'; -import { ITheme } from 'xterm'; +import { IRenderer, IRenderDimensions, IRenderLayer, FLAGS } from '../Types'; import { CharacterJoinerHandler, ITerminal } from '../../Types'; -import { ColorManager } from '../ColorManager'; import { RenderDebouncer } from '../../ui/RenderDebouncer'; import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from '../LinkRenderLayer'; @@ -22,6 +20,8 @@ import { EventEmitter2, IEvent } from '../../common/EventEmitter2'; import { Disposable } from '../../common/Lifecycle'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from '../../core/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../common/Types'; +import { IColorSet } from '../../ui/Types'; +import { getLuminance } from './ColorUtils'; export const INDICIES_PER_CELL = 4; @@ -43,7 +43,6 @@ export class WebglRenderer extends Disposable implements IRenderer { private _needsFullRefresh: boolean = false; public dimensions: IRenderDimensions; - public colorManager: ColorManager; private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } @@ -52,20 +51,15 @@ export class WebglRenderer extends Disposable implements IRenderer { constructor( private _terminal: ITerminal, - theme: ITheme + private _colors: IColorSet ) { super(); - const allowTransparency = this._terminal.options.allowTransparency; - this.colorManager = new ColorManager(document, allowTransparency); - if (theme) { - this.colorManager.setTheme(theme); - this._applyBgLuminanceBasedSelection(); - } + this._applyBgLuminanceBasedSelection(); this._renderLayers = [ - new LinkRenderLayer(this._terminal.screenElement, 2, this.colorManager.colors, this._terminal), - new CursorRenderLayer(this._terminal.screenElement, 3, this.colorManager.colors) + new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._terminal), + new CursorRenderLayer(this._terminal.screenElement, 3, this._colors) ]; this.dimensions = { scaledCharWidth: null, @@ -98,8 +92,8 @@ export class WebglRenderer extends Disposable implements IRenderer { } this._terminal.screenElement.appendChild(this._canvas); - this._rectangleRenderer = new RectangleRenderer(this._terminal, this.colorManager, this._gl, this.dimensions); - this._glyphRenderer = new GlyphRenderer(this._terminal, this.colorManager, this._gl, this.dimensions); + this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions); + this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions); // Detect whether IntersectionObserver is detected and enable renderer pause // and resume based on terminal visibility if so @@ -118,10 +112,10 @@ export class WebglRenderer extends Disposable implements IRenderer { private _applyBgLuminanceBasedSelection(): void { // HACK: This is needed until webgl renderer adds support for selection colors - if (this.colorManager.getLuminance(this.colorManager.colors.background) > 0.5) { - this.colorManager.colors.selection = { css: '#000', rgba: 255 }; + if (getLuminance(this._colors.background) > 0.5) { + this._colors.selection = { css: '#000', rgba: 255 }; } else { - this.colorManager.colors.selection = { css: '#fff', rgba: 4294967295 }; + this._colors.selection = { css: '#fff', rgba: 4294967295 }; } } @@ -142,25 +136,20 @@ export class WebglRenderer extends Disposable implements IRenderer { } } - public setTheme(theme: ITheme | undefined): IColorSet { - if (theme) { - this.colorManager.setTheme(theme); - this._applyBgLuminanceBasedSelection(); - } + public onThemeChange(colors: IColorSet): void { + this._applyBgLuminanceBasedSelection(); // Clear layers and force a full render this._renderLayers.forEach(l => { - l.onThemeChanged(this._terminal, this.colorManager.colors); + l.onThemeChange(this._terminal, this._colors); l.reset(this._terminal); }); - this._rectangleRenderer.onThemeChanged(); - this._glyphRenderer.onThemeChanged(); + this._rectangleRenderer.onThemeChange(); + this._glyphRenderer.onThemeChange(); this._refreshCharAtlas(); this._refreshViewport(); - - return this.colorManager.colors; } public onWindowResize(devicePixelRatio: number): void { @@ -245,7 +234,7 @@ export class WebglRenderer extends Disposable implements IRenderer { return; } - const atlas = acquireCharAtlas(this._terminal, this.colorManager.colors, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight); + const atlas = acquireCharAtlas(this._terminal, this._colors, this.dimensions.scaledCharWidth, this.dimensions.scaledCharHeight); if (!('getRasterizedGlyph' in atlas)) { throw new Error('The webgl renderer only works with the webgl char atlas'); } diff --git a/src/ui/ColorManager.ts b/src/ui/ColorManager.ts index c751c442..6d68c58d 100644 --- a/src/ui/ColorManager.ts +++ b/src/ui/ColorManager.ts @@ -103,14 +103,6 @@ export class ColorManager implements IColorManager { }; } - // Coefficients taken from: https://www.w3.org/TR/AERT/#color-contrast - public getLuminance(color: IColor): number { - const r = color.rgba >> 24 & 0xff; - const g = color.rgba >> 16 & 0xff; - const b = color.rgba >> 8 & 0xff; - return (0.299 * r + 0.587 * g + 0.114 * b) / 255; - } - /** * Sets the terminal's theme. * @param theme The theme to use. If a partial theme is provided then default diff --git a/src/ui/Types.ts b/src/ui/Types.ts index cc360684..ef725ba6 100644 --- a/src/ui/Types.ts +++ b/src/ui/Types.ts @@ -5,7 +5,6 @@ export interface IColorManager { colors: IColorSet; - getLuminance(color: IColor): number; } export interface IColor { From f42e53f15a4bc3efbb66a857814112fae39fe3d4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 21:56:42 -0700 Subject: [PATCH 27/69] Update for recent changes --- demo/client.ts | 4 ++ src/renderer/webgl/GlyphRenderer.ts | 2 +- src/renderer/webgl/RectangleRenderer.ts | 2 +- src/renderer/webgl/WebglRenderer.ts | 85 +++++-------------------- 4 files changed, 21 insertions(+), 72 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index bcaabdd1..3369a82b 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -283,6 +283,10 @@ function initOptions(term: TerminalType): void { const input = document.getElementById(`opt-${o}`); addDomListener(input, 'change', () => { console.log('change', o, input.value); + if (o === 'rendererType' && input.value === 'webgl') { + term.setOption('experimentalCharAtlas', 'webgl'); + setTimeout(() => (document.getElementById(`opt-experimentalCharAtlas`)).value = 'webgl', 0); + } term.setOption(o, input.value); }); }); diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 9bd4573d..698a2f19 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -299,7 +299,7 @@ export class GlyphRenderer { } } - public onThemeChange(): void { + public setColors(): void { } public render(renderModel: IRenderModel, isSelectionVisible: boolean): void { diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index e047d7ac..7aeb5880 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -149,7 +149,7 @@ export class RectangleRenderer { this._updateViewportRectangle(); } - public onThemeChange(): void { + public setColors(): void { this._updateCachedColors(); this._updateViewportRectangle(); } diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index e3425173..b0d32e28 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -5,18 +5,15 @@ import { IRenderer, IRenderDimensions, IRenderLayer, FLAGS } from '../Types'; import { CharacterJoinerHandler, ITerminal } from '../../Types'; -import { RenderDebouncer } from '../../ui/RenderDebouncer'; import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from '../LinkRenderLayer'; import { CursorRenderLayer } from '../CursorRenderLayer'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import WebglCharAtlas from './WebglCharAtlas'; -import { ScreenDprMonitor } from '../../ui/ScreenDprMonitor'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; -import { EventEmitter2, IEvent } from '../../common/EventEmitter2'; import { Disposable } from '../../common/Lifecycle'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from '../../core/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../common/Types'; @@ -26,10 +23,8 @@ import { getLuminance } from './ColorUtils'; export const INDICIES_PER_CELL = 4; export class WebglRenderer extends Disposable implements IRenderer { - private _renderDebouncer: RenderDebouncer; private _renderLayers: IRenderLayer[]; private _charAtlas: WebglCharAtlas; - private _screenDprMonitor: ScreenDprMonitor; private _devicePixelRatio: number; private _model: RenderModel = new RenderModel(); @@ -39,16 +34,8 @@ export class WebglRenderer extends Disposable implements IRenderer { private _rectangleRenderer: RectangleRenderer; private _glyphRenderer: GlyphRenderer; - private _isPaused: boolean = false; - private _needsFullRefresh: boolean = false; - public dimensions: IRenderDimensions; - private _onCanvasResize = new EventEmitter2<{ width: number, height: number }>(); - public get onCanvasResize(): IEvent<{ width: number, height: number }> { return this._onCanvasResize.event; } - private _onRender = new EventEmitter2<{ start: number, end: number }>(); - public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; } - constructor( private _terminal: ITerminal, private _colors: IColorSet @@ -78,12 +65,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._devicePixelRatio = window.devicePixelRatio; this._updateDimensions(); - this._screenDprMonitor = new ScreenDprMonitor(); - this._screenDprMonitor.setListener(() => this.onWindowResize(window.devicePixelRatio)); - this.register(this._screenDprMonitor); - - this._renderDebouncer = new RenderDebouncer(this._renderRows.bind(this)); - this._canvas = document.createElement('canvas'); const contextAttributes = { antialias: false, depth: false }; this._gl = this._canvas.getContext('webgl2', contextAttributes) as IWebGL2RenderingContext; @@ -95,13 +76,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions); this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions); - // Detect whether IntersectionObserver is detected and enable renderer pause - // and resume based on terminal visibility if so - if ('IntersectionObserver' in window) { - const observer = new IntersectionObserver(e => this.onIntersectionChange(e[0]), { threshold: 0 }); - observer.observe(this._terminal.element); - this.register({ dispose: () => observer.disconnect() }); - } + // Update dimensions and acquire char atlas + this.onCharSizeChanged(); } public dispose(): void { @@ -119,24 +95,9 @@ export class WebglRenderer extends Disposable implements IRenderer { } } - public onIntersectionChange(entry: IntersectionObserverEntry): void { - this._isPaused = entry.intersectionRatio === 0; - if (!this._isPaused && this._needsFullRefresh) { - this._terminal.refresh(0, this._terminal.rows - 1); - } - } - - private _refreshViewport(): void { - // Force a refresh - this._model.clear(); - if (this._isPaused) { - this._needsFullRefresh = true; - } else { - this._terminal.refresh(0, this._terminal.rows - 1); - } - } - public setColors(colors: IColorSet): void { + this._colors = colors; + this._applyBgLuminanceBasedSelection(); // Clear layers and force a full render @@ -145,23 +106,22 @@ export class WebglRenderer extends Disposable implements IRenderer { l.reset(this._terminal); }); - this._rectangleRenderer.onThemeChange(); - this._glyphRenderer.onThemeChange(); + this._rectangleRenderer.setColors(); + this._glyphRenderer.setColors(); this._refreshCharAtlas(); - this._refreshViewport(); } - public onWindowResize(devicePixelRatio: number): void { + public onDevicePixelRatioChange(): void { // If the device pixel ratio changed, the char atlas needs to be regenerated // and the terminal needs to refreshed - if (this._devicePixelRatio !== devicePixelRatio) { - this._devicePixelRatio = devicePixelRatio; - this.onResize(this._terminal.cols, this._terminal.rows, devicePixelRatio); + if (this._devicePixelRatio !== window.devicePixelRatio) { + this._devicePixelRatio = window.devicePixelRatio; + this.onResize(this._terminal.cols, this._terminal.rows); } } - public onResize(cols: number, rows: number, devicePixelRatio: number = window.devicePixelRatio): void { + public onResize(cols: number, rows: number): void { // Update character and canvas dimensions this._updateDimensions(devicePixelRatio); @@ -184,12 +144,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._glyphRenderer.onResize(); this._refreshCharAtlas(); - this._refreshViewport(); - - this._onCanvasResize.fire({ - width: this.dimensions.canvasWidth, - height: this.dimensions.canvasHeight - }); } public onCharSizeChanged(): void { @@ -211,7 +165,9 @@ export class WebglRenderer extends Disposable implements IRenderer { this._rectangleRenderer.updateSelection(this._model.selection, columnSelectMode); this._glyphRenderer.updateSelection(this._model, columnSelectMode); - this.refreshRows(0, this._terminal.rows - 1); + + // TODO: #2102 Should this move to RenderCoordinator? + this._terminal.refresh(0, this._terminal.rows - 1); } public onCursorMove(): void { @@ -247,14 +203,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._renderLayers.forEach(l => l.reset(this._terminal)); } - public refreshRows(start: number, end: number): void { - if (this._isPaused) { - this._needsFullRefresh = true; - return; - } - this._renderDebouncer.refresh(start, end, this._terminal.rows); - } - public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return -1; } @@ -263,7 +211,7 @@ export class WebglRenderer extends Disposable implements IRenderer { return false; } - private _renderRows(start: number, end: number): void { + public renderRows(start: number, end: number): void { // Update render layers this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); @@ -278,9 +226,6 @@ export class WebglRenderer extends Disposable implements IRenderer { // Render this._rectangleRenderer.render(); this._glyphRenderer.render(this._model, this._model.selection.hasSelection); - - // Emit event - this._onRender.fire({ start, end }); } private _updateModel(start: number, end: number): void { From 6922156d07a438a70e49d43b729e93ef0e7a5896 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 23:30:10 -0700 Subject: [PATCH 28/69] Move atlas parts into webgl/ --- src/Terminal.ts | 12 +- src/TestUtils.test.ts | 3 + src/Types.ts | 1 + src/public/Terminal.ts | 7 + src/renderer/atlas/CharAtlasCache.ts | 4 +- src/renderer/webgl/GlyphRenderer.ts | 2 +- src/renderer/webgl/RectangleRenderer.ts | 4 +- src/renderer/webgl/WebglRenderer.ts | 6 +- src/renderer/webgl/WebglRendererAddon.ts | 26 ++++ src/renderer/webgl/atlas/BaseCharAtlas.ts | 56 ++++++++ src/renderer/webgl/atlas/CharAtlasCache.ts | 94 ++++++++++++ .../webgl/atlas/CharAtlasGenerator.ts | 129 +++++++++++++++++ src/renderer/webgl/atlas/CharAtlasUtils.ts | 58 ++++++++ src/renderer/webgl/atlas/LRUMap.test.ts | 65 +++++++++ src/renderer/webgl/atlas/LRUMap.ts | 136 ++++++++++++++++++ src/renderer/webgl/atlas/Types.ts | 35 +++++ .../webgl/{ => atlas}/WebglCharAtlas.ts | 20 +-- typings/xterm.d.ts | 9 +- 18 files changed, 642 insertions(+), 25 deletions(-) create mode 100644 src/renderer/webgl/WebglRendererAddon.ts create mode 100644 src/renderer/webgl/atlas/BaseCharAtlas.ts create mode 100644 src/renderer/webgl/atlas/CharAtlasCache.ts create mode 100644 src/renderer/webgl/atlas/CharAtlasGenerator.ts create mode 100644 src/renderer/webgl/atlas/CharAtlasUtils.ts create mode 100644 src/renderer/webgl/atlas/LRUMap.test.ts create mode 100644 src/renderer/webgl/atlas/LRUMap.ts create mode 100644 src/renderer/webgl/atlas/Types.ts rename src/renderer/webgl/{ => atlas}/WebglCharAtlas.ts (96%) diff --git a/src/Terminal.ts b/src/Terminal.ts index 7083c0f0..8517fabc 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -47,7 +47,6 @@ import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent } from './common/Types'; import { evaluateKeyboardEvent } from './core/input/Keyboard'; -import { WebglRenderer } from './renderer/webgl/WebglRenderer'; import { KeyboardResultType, ICharset, IBufferLine, IAttributeData } from './core/Types'; import { clone } from './common/Clone'; import { EventEmitter2, IEvent } from './common/EventEmitter2'; @@ -818,11 +817,16 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II } + public setRenderer(renderer: IRenderer): void { + this._renderCoordinator.setRenderer(renderer); + // this._renderCoordinator.onOptionsChanged(); + this.refresh(0, this.rows - 1); + } + private _createRenderer(): IRenderer { switch (this.options.rendererType) { - case 'canvas': return new Renderer(this, this._colorManager.colors); break; - case 'dom': return new DomRenderer(this, this._colorManager.colors); break; - case 'webgl': return new WebglRenderer(this, this._colorManager.colors); break; + case 'canvas': return new Renderer(this, this._colorManager.colors); + case 'dom': return new DomRenderer(this, this._colorManager.colors); default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } } diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index f83e7d22..e95d4741 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -22,6 +22,9 @@ export class TestTerminal extends Terminal { } export class MockTerminal implements ITerminal { + setRenderer(renderer: any): void { + throw new Error('Method not implemented.'); + } onCursorMove: IEvent; onLineFeed: IEvent; onSelectionChange: IEvent; diff --git a/src/Types.ts b/src/Types.ts index 91bee283..8db46901 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -270,6 +270,7 @@ export interface IPublicTerminal extends IDisposable, IEventEmitter { setOption(key: string, value: any): void; refresh(start: number, end: number): void; reset(): void; + setRenderer(renderer: any): void; } export interface IBufferAccessor { diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index f1874a3f..ce469b97 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -10,6 +10,7 @@ import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../Strings'; import { IEvent } from '../common/EventEmitter2'; import { AddonManager } from './AddonManager'; +import { WebglRendererAddon } from '../renderer/webgl/WebglRendererAddon'; export class Terminal implements ITerminalApi { private _core: ITerminal; @@ -183,6 +184,12 @@ export class Terminal implements ITerminalApi { public loadAddon(addon: ITerminalAddon): void { return this._addonManager.loadAddon(this, addon); } + public setRenderer(renderer: any): void { + this._core.setRenderer(renderer); + } + public loadWebgl(): void { + this.loadAddon(new WebglRendererAddon()); + } public static get strings(): ILocalizableStrings { return Strings; } diff --git a/src/renderer/atlas/CharAtlasCache.ts b/src/renderer/atlas/CharAtlasCache.ts index c5dcdc6b..30826d5b 100644 --- a/src/renderer/atlas/CharAtlasCache.ts +++ b/src/renderer/atlas/CharAtlasCache.ts @@ -9,15 +9,13 @@ import BaseCharAtlas from './BaseCharAtlas'; import DynamicCharAtlas from './DynamicCharAtlas'; import NoneCharAtlas from './NoneCharAtlas'; import StaticCharAtlas from './StaticCharAtlas'; -import WebglCharAtlas from '../webgl/WebglCharAtlas'; import { ICharAtlasConfig } from './Types'; import { IColorSet } from '../../ui/Types'; const charAtlasImplementations = { 'none': NoneCharAtlas, 'static': StaticCharAtlas, - 'dynamic': DynamicCharAtlas, - 'webgl': WebglCharAtlas + 'dynamic': DynamicCharAtlas }; interface ICharAtlasCacheEntry { diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 698a2f19..95b1e325 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -6,7 +6,7 @@ import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderDimensions } from '../Types'; import { ITerminal } from '../../Types'; -import WebglCharAtlas from './WebglCharAtlas'; +import WebglCharAtlas from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index 7aeb5880..62731bc5 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -8,8 +8,8 @@ import { IRenderDimensions } from '../Types'; import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; import { fill } from '../../common/TypedArrayUtils'; -import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; -import { is256Color } from '../atlas/CharAtlasUtils'; +import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; +import { is256Color } from './atlas/CharAtlasUtils'; import { DEFAULT_COLOR } from '../../common/Types'; import { IColorSet, IColor } from '../../ui/Types'; diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index b0d32e28..d93080d7 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -8,11 +8,11 @@ import { CharacterJoinerHandler, ITerminal } from '../../Types'; import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from '../LinkRenderLayer'; import { CursorRenderLayer } from '../CursorRenderLayer'; -import { acquireCharAtlas } from '../atlas/CharAtlasCache'; -import WebglCharAtlas from './WebglCharAtlas'; +import { acquireCharAtlas } from './atlas/CharAtlasCache'; +import WebglCharAtlas from './atlas/WebglCharAtlas'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; -import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; +import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from '../../common/Lifecycle'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from '../../core/buffer/BufferLine'; diff --git a/src/renderer/webgl/WebglRendererAddon.ts b/src/renderer/webgl/WebglRendererAddon.ts new file mode 100644 index 00000000..764caa0f --- /dev/null +++ b/src/renderer/webgl/WebglRendererAddon.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { Terminal, ITerminalAddon } from 'xterm'; +import { WebglRenderer } from './WebglRenderer'; + +export class WebglRendererAddon implements ITerminalAddon { + private _terminal: Terminal | undefined; + + constructor() {} + + public activate(terminal: Terminal): void { + if (!terminal.element) { + throw new Error('Cannot activate WebglRendererAddon before Terminal.open'); + } + this._terminal = terminal; + const core = (terminal as any)._core; + this._terminal.setRenderer(new WebglRenderer(core, core._colorManager.colors)); + } + + public dispose(): void { + throw new Error('WebglRendererAddon.dispose Not yet implemented'); + } +} diff --git a/src/renderer/webgl/atlas/BaseCharAtlas.ts b/src/renderer/webgl/atlas/BaseCharAtlas.ts new file mode 100644 index 00000000..ee69b381 --- /dev/null +++ b/src/renderer/webgl/atlas/BaseCharAtlas.ts @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IGlyphIdentifier } from './Types'; +import { IDisposable } from 'xterm'; + +export default abstract class BaseCharAtlas implements IDisposable { + private _didWarmUp: boolean = false; + + 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. + */ + protected _doWarmUp(): 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/src/renderer/webgl/atlas/CharAtlasCache.ts b/src/renderer/webgl/atlas/CharAtlasCache.ts new file mode 100644 index 00000000..9d16893b --- /dev/null +++ b/src/renderer/webgl/atlas/CharAtlasCache.ts @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ITerminal } from '../../../Types'; +import { generateConfig, configEquals } from './CharAtlasUtils'; +import BaseCharAtlas from './BaseCharAtlas'; +import WebglCharAtlas from './WebglCharAtlas'; +import { ICharAtlasConfig } from './Types'; +import { IColorSet } from '../../../ui/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: ITerminal[]; +} + +const charAtlasCache: ICharAtlasCacheEntry[] = []; + +/** + * Acquires a char atlas, either generating a new one or returning an existing + * one that is in use by another terminal. + * @param terminal The terminal. + * @param colors The colors to use. + */ +export function acquireCharAtlas( + terminal: ITerminal, + colors: IColorSet, + scaledCharWidth: number, + scaledCharHeight: number +): BaseCharAtlas { + const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors); + + // Check to see if the terminal already owns this config + for (let i = 0; i < charAtlasCache.length; i++) { + const entry = charAtlasCache[i]; + const ownedByIndex = entry.ownedBy.indexOf(terminal); + if (ownedByIndex >= 0) { + if (configEquals(entry.config, newConfig)) { + return entry.atlas; + } + // The configs differ, release the terminal from the entry + if (entry.ownedBy.length === 1) { + entry.atlas.dispose(); + charAtlasCache.splice(i, 1); + } else { + entry.ownedBy.splice(ownedByIndex, 1); + } + break; + } + } + + // Try match a char atlas from the cache + for (let i = 0; i < charAtlasCache.length; i++) { + const entry = charAtlasCache[i]; + if (configEquals(entry.config, newConfig)) { + // Add the terminal to the cache entry and return + entry.ownedBy.push(terminal); + return entry.atlas; + } + } + + const newEntry: ICharAtlasCacheEntry = { + atlas: new WebglCharAtlas(document, newConfig), + config: newConfig, + ownedBy: [terminal] + }; + charAtlasCache.push(newEntry); + return newEntry.atlas; +} + +/** + * Removes a terminal reference from the cache, allowing its memory to be freed. + * @param terminal The terminal to remove. + */ +export function removeTerminalFromCache(terminal: ITerminal): void { + for (let i = 0; i < charAtlasCache.length; i++) { + 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 terminal + charAtlasCache[i].atlas.dispose(); + charAtlasCache.splice(i, 1); + } else { + // Remove the reference from the cache entry + charAtlasCache[i].ownedBy.splice(index, 1); + } + break; + } + } +} diff --git a/src/renderer/webgl/atlas/CharAtlasGenerator.ts b/src/renderer/webgl/atlas/CharAtlasGenerator.ts new file mode 100644 index 00000000..e844f37a --- /dev/null +++ b/src/renderer/webgl/atlas/CharAtlasGenerator.ts @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { FontWeight } from 'xterm'; +import { isFirefox, isSafari } from '../../../common/Platform'; +import { ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types'; +import { IColor } from '../../../ui/Types'; + +/** + * Generates a char atlas. + * @param context The window or worker context. + * @param canvasFactory A function to generate a canvas with a width or height. + * @param config The config for the new char atlas. + */ +export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement, config: ICharAtlasConfig): HTMLCanvasElement | Promise { + const cellWidth = config.scaledCharWidth + CHAR_ATLAS_CELL_SPACING; + const cellHeight = config.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; + const canvas = canvasFactory( + /*255 ascii chars*/255 * cellWidth, + (/*default+default bold*/2 + /*0-15*/16 + /*0-15 bold*/16) * cellHeight + ); + const ctx = canvas.getContext('2d', {alpha: config.allowTransparency}); + + ctx.fillStyle = config.colors.background.css; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + ctx.save(); + ctx.fillStyle = config.colors.foreground.css; + ctx.font = getFont(config.fontWeight, config); + ctx.textBaseline = 'middle'; + + // Default color + for (let i = 0; i < 256; i++) { + ctx.save(); + ctx.beginPath(); + ctx.rect(i * cellWidth, 0, cellWidth, cellHeight); + ctx.clip(); + ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight / 2); + ctx.restore(); + } + // Default color bold + ctx.save(); + ctx.font = getFont(config.fontWeightBold, config); + for (let i = 0; i < 256; i++) { + ctx.save(); + ctx.beginPath(); + ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight); + ctx.clip(); + ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight * 1.5); + ctx.restore(); + } + ctx.restore(); + + // Colors 0-15 + ctx.font = getFont(config.fontWeight, config); + for (let colorIndex = 0; colorIndex < 16; colorIndex++) { + const y = (colorIndex + 2) * cellHeight; + // Draw ascii characters + for (let i = 0; i < 256; i++) { + ctx.save(); + ctx.beginPath(); + ctx.rect(i * cellWidth, y, cellWidth, cellHeight); + ctx.clip(); + ctx.fillStyle = config.colors.ansi[colorIndex].css; + ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2); + ctx.restore(); + } + } + + // Colors 0-15 bold + ctx.font = getFont(config.fontWeightBold, config); + for (let colorIndex = 0; colorIndex < 16; colorIndex++) { + const y = (colorIndex + 2 + 16) * cellHeight; + // Draw ascii characters + for (let i = 0; i < 256; i++) { + ctx.save(); + ctx.beginPath(); + ctx.rect(i * cellWidth, y, cellWidth, cellHeight); + ctx.clip(); + ctx.fillStyle = config.colors.ansi[colorIndex].css; + ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2); + ctx.restore(); + } + } + ctx.restore(); + + // Support is patchy for createImageBitmap at the moment, pass a canvas back + // if support is lacking as drawImage works there too. Firefox is also + // included here as ImageBitmap appears both buggy and has horrible + // performance (tested on v55). + if (!('createImageBitmap' in context) || isFirefox || isSafari) { + // Don't attempt to clear background colors if createImageBitmap is not supported + return canvas; + } + + const charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + + // Remove the background color from the image so characters may overlap + clearColor(charAtlasImageData, config.colors.background); + + return context.createImageBitmap(charAtlasImageData); +} + +/** + * Makes a partiicular rgb color in an ImageData completely transparent. + * @returns True if the result is "empty", meaning all pixels are fully transparent. + */ +export function clearColor(imageData: ImageData, color: IColor): boolean { + let isEmpty = true; + const r = color.rgba >>> 24; + const g = color.rgba >>> 16 & 0xFF; + const b = color.rgba >>> 8 & 0xFF; + for (let offset = 0; offset < imageData.data.length; offset += 4) { + if (imageData.data[offset] === r && + imageData.data[offset + 1] === g && + imageData.data[offset + 2] === b) { + imageData.data[offset + 3] = 0; + } else { + isEmpty = false; + } + } + return isEmpty; +} + +function getFont(fontWeight: FontWeight, config: ICharAtlasConfig): string { + return `${fontWeight} ${config.fontSize * config.devicePixelRatio}px ${config.fontFamily}`; +} diff --git a/src/renderer/webgl/atlas/CharAtlasUtils.ts b/src/renderer/webgl/atlas/CharAtlasUtils.ts new file mode 100644 index 00000000..b11ca085 --- /dev/null +++ b/src/renderer/webgl/atlas/CharAtlasUtils.ts @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ITerminal } from '../../../Types'; +import { ICharAtlasConfig } from './Types'; +import { DEFAULT_COLOR } from '../../../common/Types'; +import { IColorSet } from '../../../ui/Types'; + +export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { + // null out some fields that don't matter + const clonedColors = { + foreground: colors.foreground, + background: colors.background, + cursor: null, + cursorAccent: null, + selection: null, + // For the static char atlas, we only use the first 16 colors, but we need all 256 for the + // dynamic character atlas. + ansi: colors.ansi.slice(0, 16) + }; + return { + type: terminal.options.experimentalCharAtlas, + devicePixelRatio: window.devicePixelRatio, + scaledCharWidth, + scaledCharHeight, + fontFamily: terminal.options.fontFamily, + fontSize: terminal.options.fontSize, + fontWeight: terminal.options.fontWeight, + fontWeightBold: terminal.options.fontWeightBold, + allowTransparency: terminal.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.type === b.type && + 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/src/renderer/webgl/atlas/LRUMap.test.ts b/src/renderer/webgl/atlas/LRUMap.test.ts new file mode 100644 index 00000000..197d1159 --- /dev/null +++ b/src/renderer/webgl/atlas/LRUMap.test.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import LRUMap from './LRUMap'; + +describe('LRUMap', () => { + it('can be used to store and retrieve values', () => { + const map = new LRUMap(10); + map.set(1, 'valuea'); + map.set(2, 'valueb'); + map.set(3, 'valuec'); + assert.strictEqual(map.get(1), 'valuea'); + assert.strictEqual(map.get(2), 'valueb'); + assert.strictEqual(map.get(3), 'valuec'); + }); + + it('maintains a size from insertions', () => { + const map = new LRUMap(10); + assert.strictEqual(map.size, 0); + map.set(1, 'value'); + assert.strictEqual(map.size, 1); + map.set(2, 'value'); + assert.strictEqual(map.size, 2); + }); + + it('deletes the oldest entry when the capacity is exceeded', () => { + const map = new LRUMap(4); + map.set(1, 'value'); + map.set(2, 'value'); + map.set(3, 'value'); + map.set(4, 'value'); + map.set(5, 'value'); + assert.isNull(map.get(1)); + assert.isNotNull(map.get(2)); + assert.isNotNull(map.get(3)); + assert.isNotNull(map.get(4)); + assert.isNotNull(map.get(5)); + assert.strictEqual(map.size, 4); + }); + + it('prevents a recently accessed entry from getting deleted', () => { + const map = new LRUMap(2); + map.set(1, 'value'); + map.set(2, 'value'); + map.get(1); + // a would normally get deleted here, except that we called get() + map.set(3, 'value'); + assert.isNotNull(map.get(1)); + // b got deleted instead of a + assert.isNull(map.get(2)); + assert.isNotNull(map.get(3)); + }); + + it('supports mutation', () => { + const map = new LRUMap(10); + map.set(1, 'oldvalue'); + map.set(1, 'newvalue'); + // mutation doesn't change the size + assert.strictEqual(map.size, 1); + assert.strictEqual(map.get(1), 'newvalue'); + }); +}); diff --git a/src/renderer/webgl/atlas/LRUMap.ts b/src/renderer/webgl/atlas/LRUMap.ts new file mode 100644 index 00000000..d7e01ec6 --- /dev/null +++ b/src/renderer/webgl/atlas/LRUMap.ts @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +interface ILinkedListNode { + prev: ILinkedListNode; + next: ILinkedListNode; + key: number; + value: T; +} + +export default class LRUMap { + private _map: { [key: number]: ILinkedListNode } = {}; + private _head: ILinkedListNode = null; + private _tail: ILinkedListNode = null; + private _nodePool: ILinkedListNode[] = []; + public size: number = 0; + + constructor(public capacity: number) { } + + private _unlinkNode(node: ILinkedListNode): void { + const prev = node.prev; + const next = node.next; + if (node === this._head) { + this._head = next; + } + if (node === this._tail) { + this._tail = prev; + } + if (prev !== null) { + prev.next = next; + } + if (next !== null) { + next.prev = prev; + } + } + + private _appendNode(node: ILinkedListNode): void { + const tail = this._tail; + if (tail !== null) { + tail.next = node; + } + node.prev = tail; + node.next = null; + this._tail = node; + if (this._head === null) { + this._head = node; + } + } + + /** + * Preallocate a bunch of linked-list nodes. Allocating these nodes ahead of time means that + * they're more likely to live next to each other in memory, which seems to improve performance. + * + * Each empty object only consumes about 60 bytes of memory, so this is pretty cheap, even for + * large maps. + */ + public prealloc(count: number): void { + const nodePool = this._nodePool; + for (let i = 0; i < count; i++) { + nodePool.push({ + prev: null, + next: null, + key: null, + value: null + }); + } + } + + public get(key: number): T | null { + // This is unsafe: We're assuming our keyspace doesn't overlap with Object.prototype. However, + // it's faster than calling hasOwnProperty, and in our case, it would never overlap. + const node = this._map[key]; + if (node !== undefined) { + this._unlinkNode(node); + this._appendNode(node); + return node.value; + } + return null; + } + + /** + * Gets a value from a key without marking it as the most recently used item. + */ + public peekValue(key: number): T | null { + const node = this._map[key]; + if (node !== undefined) { + return node.value; + } + return null; + } + + public peek(): T | null { + const head = this._head; + return head === null ? null : head.value; + } + + public set(key: number, value: T): void { + // This is unsafe: See note above. + let node = this._map[key]; + if (node !== undefined) { + // already exists, we just need to mutate it and move it to the end of the list + node = this._map[key]; + this._unlinkNode(node); + node.value = value; + } else if (this.size >= this.capacity) { + // we're out of space: recycle the head node, move it to the tail + node = this._head; + this._unlinkNode(node); + delete this._map[node.key]; + node.key = key; + node.value = value; + this._map[key] = node; + } else { + // make a new element + const nodePool = this._nodePool; + if (nodePool.length > 0) { + // use a preallocated node if we can + node = nodePool.pop(); + node.key = key; + node.value = value; + } else { + node = { + prev: null, + next: null, + key, + value + }; + } + this._map[key] = node; + this.size++; + } + this._appendNode(node); + } +} diff --git a/src/renderer/webgl/atlas/Types.ts b/src/renderer/webgl/atlas/Types.ts new file mode 100644 index 00000000..1bdaf3b9 --- /dev/null +++ b/src/renderer/webgl/atlas/Types.ts @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { FontWeight } from 'xterm'; +import { IColorSet } from '../../../ui/Types'; + +export const INVERTED_DEFAULT_COLOR = 257; +export const DIM_OPACITY = 0.5; + +export const CHAR_ATLAS_CELL_SPACING = 1; + +export interface IGlyphIdentifier { + chars: string; + code: number; + bg: number; + fg: number; + bold: boolean; + dim: boolean; + italic: boolean; +} + +export interface ICharAtlasConfig { + type: 'none' | 'static' | 'dynamic' | 'webgl'; + devicePixelRatio: number; + fontSize: number; + fontFamily: string; + fontWeight: FontWeight; + fontWeightBold: FontWeight; + scaledCharWidth: number; + scaledCharHeight: number; + allowTransparency: boolean; + colors: IColorSet; +} diff --git a/src/renderer/webgl/WebglCharAtlas.ts b/src/renderer/webgl/atlas/WebglCharAtlas.ts similarity index 96% rename from src/renderer/webgl/WebglCharAtlas.ts rename to src/renderer/webgl/atlas/WebglCharAtlas.ts index a6bd4013..5da955bc 100644 --- a/src/renderer/webgl/WebglCharAtlas.ts +++ b/src/renderer/webgl/atlas/WebglCharAtlas.ts @@ -3,16 +3,16 @@ * @license MIT */ -import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from '../atlas/Types'; -import BaseCharAtlas from '../atlas/BaseCharAtlas'; -import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from './Types'; -import { FLAGS } from '../Types'; -import { is256Color } from '../atlas/CharAtlasUtils'; -import { clearColor } from '../atlas/CharAtlasGenerator'; -import { DEFAULT_ATTR } from '../../core/buffer/BufferLine'; -import { DEFAULT_COLOR } from '../../common/Types'; -import { IColor } from '../../ui/Types'; -import { DEFAULT_ANSI_COLORS } from '../../ui/ColorManager'; +import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from './Types'; +import BaseCharAtlas from './BaseCharAtlas'; +import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; +import { FLAGS } from '../../Types'; +import { is256Color } from './CharAtlasUtils'; +import { clearColor } from './CharAtlasGenerator'; +import { DEFAULT_ATTR } from '../../../core/buffer/BufferLine'; +import { DEFAULT_COLOR } from '../../../common/Types'; +import { IColor } from '../../../ui/Types'; +import { DEFAULT_ANSI_COLORS } from '../../../ui/ColorManager'; // 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. diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index a0fb74ce..de4051ed 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -18,7 +18,7 @@ declare module 'xterm' { /** * A string representing a renderer type. */ - export type RendererType = 'dom' | 'canvas' | 'webgl'; + export type RendererType = 'dom' | 'canvas'; /** * An object containing start up options for the terminal. @@ -99,7 +99,7 @@ declare module 'xterm' { * Currently defaults to 'static'. This option may be removed in the future. If it is, passed * parameters will be ignored. */ - experimentalCharAtlas?: 'none' | 'static' | 'dynamic' | 'webgl'; + experimentalCharAtlas?: 'none' | 'static' | 'dynamic'; /** * The font size used to render text. @@ -890,6 +890,11 @@ declare module 'xterm' { * @param addon The addon to load. */ loadAddon(addon: ITerminalAddon): void; + + /** + * (EXPERIMENTAL) + */ + setRenderer(renderer: any): void; } /** From 29d3b8281b475a204a499b83ac2741c6cce1caf6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 18 May 2019 23:38:49 -0700 Subject: [PATCH 29/69] Copy over typed array utils to webgl --- src/common/TypedArrayUtils.test.ts | 105 +--------- src/common/TypedArrayUtils.ts | 26 --- src/renderer/webgl/ColorUtils.ts | 2 +- src/renderer/webgl/GlyphRenderer.ts | 4 +- src/renderer/webgl/RectangleRenderer.ts | 4 +- src/renderer/webgl/RenderModel.ts | 2 +- src/renderer/webgl/TypedArray.test.ts | 191 ++++++++++++++++++ src/renderer/webgl/TypedArray.ts | 67 ++++++ src/renderer/webgl/WebglRenderer.ts | 2 +- src/renderer/webgl/atlas/CharAtlasCache.ts | 2 +- .../webgl/atlas/CharAtlasGenerator.ts | 3 +- src/renderer/webgl/atlas/CharAtlasUtils.ts | 4 +- src/renderer/webgl/atlas/Types.ts | 3 +- src/renderer/webgl/atlas/WebglCharAtlas.ts | 2 +- typings/xterm.d.ts | 20 ++ 15 files changed, 292 insertions(+), 145 deletions(-) create mode 100644 src/renderer/webgl/TypedArray.test.ts create mode 100644 src/renderer/webgl/TypedArray.ts diff --git a/src/common/TypedArrayUtils.test.ts b/src/common/TypedArrayUtils.test.ts index 8e838d2c..99b0fd82 100644 --- a/src/common/TypedArrayUtils.test.ts +++ b/src/common/TypedArrayUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ import { assert } from 'chai'; -import { fillFallback, sliceFallback, concat } from './TypedArrayUtils'; +import { fillFallback, concat } from './TypedArrayUtils'; type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array @@ -85,109 +85,6 @@ describe('polyfill conformance tests', function(): void { } }); }); - - describe('TypedArray.slice', () => { - describe('should work with all typed array types', () => { - it('Uint8Array', () => { - const a = new Uint8Array(5); - deepEquals(sliceFallback(a, 2), a.slice(2)); - deepEquals(sliceFallback(a, 65535), a.slice(65535)); - deepEquals(sliceFallback(a, -1), a.slice(-1)); - }); - it('Uint16Array', () => { - const u161 = new Uint16Array(5); - const u162 = new Uint16Array(5); - deepEquals(sliceFallback(u161, 2), u162.slice(2)); - deepEquals(sliceFallback(u161, 65535), u162.slice(65535)); - deepEquals(sliceFallback(u161, -1), u162.slice(-1)); - }); - it('Uint32Array', () => { - const u321 = new Uint32Array(5); - const u322 = new Uint32Array(5); - deepEquals(sliceFallback(u321, 2), u322.slice(2)); - deepEquals(sliceFallback(u321, 65537), u322.slice(65537)); - deepEquals(sliceFallback(u321, -1), u322.slice(-1)); - }); - it('Int8Array', () => { - const i81 = new Int8Array(5); - const i82 = new Int8Array(5); - deepEquals(sliceFallback(i81, 2), i82.slice(2)); - deepEquals(sliceFallback(i81, 65537), i82.slice(65537)); - deepEquals(sliceFallback(i81, -1), i82.slice(-1)); - }); - it('Int16Array', () => { - const i161 = new Int16Array(5); - const i162 = new Int16Array(5); - deepEquals(sliceFallback(i161, 2), i162.slice(2)); - deepEquals(sliceFallback(i161, 65535), i162.slice(65535)); - deepEquals(sliceFallback(i161, -1), i162.slice(-1)); - }); - it('Int32Array', () => { - const i321 = new Int32Array(5); - const i322 = new Int32Array(5); - deepEquals(sliceFallback(i321, 2), i322.slice(2)); - deepEquals(sliceFallback(i321, 65537), i322.slice(65537)); - deepEquals(sliceFallback(i321, -1), i322.slice(-1)); - }); - it('Float32Array', () => { - const f321 = new Float32Array(5); - const f322 = new Float32Array(5); - deepEquals(sliceFallback(f321, 2), f322.slice(2)); - deepEquals(sliceFallback(f321, 65537), f322.slice(65537)); - deepEquals(sliceFallback(f321, -1), f322.slice(-1)); - }); - it('Float64Array', () => { - const f641 = new Float64Array(5); - const f642 = new Float64Array(5); - deepEquals(sliceFallback(f641, 2), f642.slice(2)); - deepEquals(sliceFallback(f641, 65537), f642.slice(65537)); - deepEquals(sliceFallback(f641, -1), f642.slice(-1)); - }); - it('Uint8ClampedArray', () => { - const u8Clamped1 = new Uint8ClampedArray(5); - const u8Clamped2 = new Uint8ClampedArray(5); - deepEquals(sliceFallback(u8Clamped1, 2), u8Clamped2.slice(2)); - deepEquals(sliceFallback(u8Clamped1, 65537), u8Clamped2.slice(65537)); - deepEquals(sliceFallback(u8Clamped1, -1), u8Clamped2.slice(-1)); - }); - }); - it('start', () => { - const arr = new Uint32Array([1, 2, 3, 4, 5]); - deepEquals(sliceFallback(arr, -1), arr.slice(-1)); - deepEquals(sliceFallback(arr, 0), arr.slice(0)); - deepEquals(sliceFallback(arr, 1), arr.slice(1)); - deepEquals(sliceFallback(arr, 2), arr.slice(2)); - deepEquals(sliceFallback(arr, 3), arr.slice(3)); - deepEquals(sliceFallback(arr, 4), arr.slice(4)); - deepEquals(sliceFallback(arr, 5), arr.slice(5)); - }); - it('end', () => { - const arr = new Uint32Array([1, 2, 3, 4, 5]); - deepEquals(sliceFallback(arr, -1, -2), arr.slice(-1, -2)); - deepEquals(sliceFallback(arr, 0, -2), arr.slice(0, -2)); - deepEquals(sliceFallback(arr, 1, -2), arr.slice(1, -2)); - deepEquals(sliceFallback(arr, 2, -2), arr.slice(2, -2)); - deepEquals(sliceFallback(arr, 3, -2), arr.slice(3, -2)); - deepEquals(sliceFallback(arr, 4, -2), arr.slice(4, -2)); - deepEquals(sliceFallback(arr, 5, -2), arr.slice(5, -2)); - - deepEquals(sliceFallback(arr, -1, 3), arr.slice(-1, 3)); - deepEquals(sliceFallback(arr, 0, 3), arr.slice(0, 3)); - deepEquals(sliceFallback(arr, 1, 3), arr.slice(1, 3)); - deepEquals(sliceFallback(arr, 2, 3), arr.slice(2, 3)); - deepEquals(sliceFallback(arr, 3, 3), arr.slice(3, 3)); - deepEquals(sliceFallback(arr, 4, 3), arr.slice(4, 3)); - deepEquals(sliceFallback(arr, 5, 3), arr.slice(5, 3)); - - deepEquals(sliceFallback(arr, -1, 8), arr.slice(-1, 8)); - deepEquals(sliceFallback(arr, 0, 8), arr.slice(0, 8)); - deepEquals(sliceFallback(arr, 1, 8), arr.slice(1, 8)); - deepEquals(sliceFallback(arr, 2, 8), arr.slice(2, 8)); - deepEquals(sliceFallback(arr, 3, 8), arr.slice(3, 8)); - deepEquals(sliceFallback(arr, 4, 8), arr.slice(4, 8)); - deepEquals(sliceFallback(arr, 5, 8), arr.slice(5, 8)); - }); - }); }); describe('typed array convenience functions', () => { diff --git a/src/common/TypedArrayUtils.ts b/src/common/TypedArrayUtils.ts index f2651aa0..54699835 100644 --- a/src/common/TypedArrayUtils.ts +++ b/src/common/TypedArrayUtils.ts @@ -40,32 +40,6 @@ export function fillFallback(array: T, value: number, star return array; } -export function slice(array: T, start?: number, end?: number): T { - // all modern engines that support .slice - if (array.slice) { - return array.slice(start, end) as T; - } - return sliceFallback(array, start, end); -} - -export function sliceFallback(array: T, start: number = 0, end: number = array.length): T { - if (start < 0) { - start = (array.length + start) % array.length; - } - if (end >= array.length) { - end = array.length; - } else { - end = (array.length + end) % array.length; - } - start = Math.min(start, end); - - const result: T = new (array.constructor as any)(end - start); - for (let i = 0; i < end - start; ++i) { - result[i] = array[i + start]; - } - return result; -} - /** * Concat two typed arrays `a` and `b`. * Returns a new typed array. diff --git a/src/renderer/webgl/ColorUtils.ts b/src/renderer/webgl/ColorUtils.ts index 9efdeb9c..56127656 100644 --- a/src/renderer/webgl/ColorUtils.ts +++ b/src/renderer/webgl/ColorUtils.ts @@ -3,7 +3,7 @@ * Copyright (c) 2018 The xterm.js authors. All rights reserved. */ -import { IColor } from '../../ui/Types'; +import { IColor } from 'xterm'; export function getLuminance(color: IColor): number { // Coefficients taken from: https://www.w3.org/TR/AERT/#color-contrast diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 95b1e325..9d61b065 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -10,11 +10,11 @@ import WebglCharAtlas from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; -import { fill, slice } from '../../common/TypedArrayUtils'; +import { fill, slice } from './TypedArray'; import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, CHAR_DATA_CHAR_INDEX } from '../../core/buffer/BufferLine'; import { IBufferLine } from '../../core/Types'; -import { IColorSet } from '../../ui/Types'; import { getLuminance } from './ColorUtils'; +import { IColorSet } from 'xterm'; interface IVertices { attributes: Float32Array; diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index 62731bc5..a133abd4 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -7,11 +7,11 @@ import { ITerminal } from '../../Types'; import { IRenderDimensions } from '../Types'; import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; -import { fill } from '../../common/TypedArrayUtils'; +import { fill } from './TypedArray'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { is256Color } from './atlas/CharAtlasUtils'; import { DEFAULT_COLOR } from '../../common/Types'; -import { IColorSet, IColor } from '../../ui/Types'; +import { IColorSet, IColor } from 'xterm'; const enum VertexAttribLocations { POSITION = 0, diff --git a/src/renderer/webgl/RenderModel.ts b/src/renderer/webgl/RenderModel.ts index 54fc91ca..71d256d3 100644 --- a/src/renderer/webgl/RenderModel.ts +++ b/src/renderer/webgl/RenderModel.ts @@ -4,7 +4,7 @@ */ import { IRenderModel, ISelectionRenderModel } from './Types'; -import { fill } from '../../common/TypedArrayUtils'; +import { fill } from './TypedArray'; export const RENDER_MODEL_INDICIES_PER_CELL = 4; diff --git a/src/renderer/webgl/TypedArray.test.ts b/src/renderer/webgl/TypedArray.test.ts new file mode 100644 index 00000000..25676248 --- /dev/null +++ b/src/renderer/webgl/TypedArray.test.ts @@ -0,0 +1,191 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ +import { assert } from 'chai'; +import { fillFallback, sliceFallback } from './TypedArray'; + +type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray + | Int8Array | Int16Array | Int32Array + | Float32Array | Float64Array; + +function deepEquals(a: TypedArray, b: TypedArray): void { + assert.equal(a.length, b.length); + for (let i = 0; i < a.length; ++i) { + assert.equal(a[i], b[i]); + } +} + +describe('polyfill conformance tests', function(): void { + describe('TypedArray.fill', function(): void { + it('should work with all typed array types', function(): void { + const u81 = new Uint8Array(5); + const u82 = new Uint8Array(5); + deepEquals(fillFallback(u81, 2), u82.fill(2)); + deepEquals(fillFallback(u81, -1), u82.fill(-1)); + const u161 = new Uint16Array(5); + const u162 = new Uint16Array(5); + deepEquals(fillFallback(u161, 2), u162.fill(2)); + deepEquals(fillFallback(u161, 65535), u162.fill(65535)); + deepEquals(fillFallback(u161, -1), u162.fill(-1)); + const u321 = new Uint32Array(5); + const u322 = new Uint32Array(5); + deepEquals(fillFallback(u321, 2), u322.fill(2)); + deepEquals(fillFallback(u321, 65537), u322.fill(65537)); + deepEquals(fillFallback(u321, -1), u322.fill(-1)); + const i81 = new Int8Array(5); + const i82 = new Int8Array(5); + deepEquals(fillFallback(i81, 2), i82.fill(2)); + deepEquals(fillFallback(i81, -1), i82.fill(-1)); + const i161 = new Int16Array(5); + const i162 = new Int16Array(5); + deepEquals(fillFallback(i161, 2), i162.fill(2)); + deepEquals(fillFallback(i161, 65535), i162.fill(65535)); + deepEquals(fillFallback(i161, -1), i162.fill(-1)); + const i321 = new Int32Array(5); + const i322 = new Int32Array(5); + deepEquals(fillFallback(i321, 2), i322.fill(2)); + deepEquals(fillFallback(i321, 65537), i322.fill(65537)); + deepEquals(fillFallback(i321, -1), i322.fill(-1)); + const f321 = new Float32Array(5); + const f322 = new Float32Array(5); + deepEquals(fillFallback(f321, 1.2345), f322.fill(1.2345)); + const f641 = new Float64Array(5); + const f642 = new Float64Array(5); + deepEquals(fillFallback(f641, 1.2345), f642.fill(1.2345)); + const u8Clamped1 = new Uint8ClampedArray(5); + const u8Clamped2 = new Uint8ClampedArray(5); + deepEquals(fillFallback(u8Clamped1, 2), u8Clamped2.fill(2)); + deepEquals(fillFallback(u8Clamped1, 257), u8Clamped2.fill(257)); + }); + it('start offset', function(): void { + for (let i = -2; i < 10; ++i) { + const u81 = new Uint8Array(5); + const u83 = new Uint8Array(5); + deepEquals(fillFallback(u81, 2, i), u83.fill(2, i)); + deepEquals(fillFallback(u81, -1, i), u83.fill(-1, i)); + } + }); + it('end offset', function(): void { + for (let i = -2; i < 10; ++i) { + const u81 = new Uint8Array(5); + const u83 = new Uint8Array(5); + deepEquals(fillFallback(u81, 2, 0, i), u83.fill(2, 0, i)); + deepEquals(fillFallback(u81, -1, 0, i), u83.fill(-1, 0, i)); + } + }); + it('start/end offset', function(): void { + for (let i = -2; i < 10; ++i) { + for (let j = -2; j < 10; ++j) { + const u81 = new Uint8Array(5); + const u83 = new Uint8Array(5); + deepEquals(fillFallback(u81, 2, i, j), u83.fill(2, i, j)); + deepEquals(fillFallback(u81, -1, i, j), u83.fill(-1, i, j)); + } + } + }); + }); + + describe('TypedArray.slice', () => { + describe('should work with all typed array types', () => { + it('Uint8Array', () => { + const a = new Uint8Array(5); + deepEquals(sliceFallback(a, 2), a.slice(2)); + deepEquals(sliceFallback(a, 65535), a.slice(65535)); + deepEquals(sliceFallback(a, -1), a.slice(-1)); + }); + it('Uint16Array', () => { + const u161 = new Uint16Array(5); + const u162 = new Uint16Array(5); + deepEquals(sliceFallback(u161, 2), u162.slice(2)); + deepEquals(sliceFallback(u161, 65535), u162.slice(65535)); + deepEquals(sliceFallback(u161, -1), u162.slice(-1)); + }); + it('Uint32Array', () => { + const u321 = new Uint32Array(5); + const u322 = new Uint32Array(5); + deepEquals(sliceFallback(u321, 2), u322.slice(2)); + deepEquals(sliceFallback(u321, 65537), u322.slice(65537)); + deepEquals(sliceFallback(u321, -1), u322.slice(-1)); + }); + it('Int8Array', () => { + const i81 = new Int8Array(5); + const i82 = new Int8Array(5); + deepEquals(sliceFallback(i81, 2), i82.slice(2)); + deepEquals(sliceFallback(i81, 65537), i82.slice(65537)); + deepEquals(sliceFallback(i81, -1), i82.slice(-1)); + }); + it('Int16Array', () => { + const i161 = new Int16Array(5); + const i162 = new Int16Array(5); + deepEquals(sliceFallback(i161, 2), i162.slice(2)); + deepEquals(sliceFallback(i161, 65535), i162.slice(65535)); + deepEquals(sliceFallback(i161, -1), i162.slice(-1)); + }); + it('Int32Array', () => { + const i321 = new Int32Array(5); + const i322 = new Int32Array(5); + deepEquals(sliceFallback(i321, 2), i322.slice(2)); + deepEquals(sliceFallback(i321, 65537), i322.slice(65537)); + deepEquals(sliceFallback(i321, -1), i322.slice(-1)); + }); + it('Float32Array', () => { + const f321 = new Float32Array(5); + const f322 = new Float32Array(5); + deepEquals(sliceFallback(f321, 2), f322.slice(2)); + deepEquals(sliceFallback(f321, 65537), f322.slice(65537)); + deepEquals(sliceFallback(f321, -1), f322.slice(-1)); + }); + it('Float64Array', () => { + const f641 = new Float64Array(5); + const f642 = new Float64Array(5); + deepEquals(sliceFallback(f641, 2), f642.slice(2)); + deepEquals(sliceFallback(f641, 65537), f642.slice(65537)); + deepEquals(sliceFallback(f641, -1), f642.slice(-1)); + }); + it('Uint8ClampedArray', () => { + const u8Clamped1 = new Uint8ClampedArray(5); + const u8Clamped2 = new Uint8ClampedArray(5); + deepEquals(sliceFallback(u8Clamped1, 2), u8Clamped2.slice(2)); + deepEquals(sliceFallback(u8Clamped1, 65537), u8Clamped2.slice(65537)); + deepEquals(sliceFallback(u8Clamped1, -1), u8Clamped2.slice(-1)); + }); + }); + it('start', () => { + const arr = new Uint32Array([1, 2, 3, 4, 5]); + deepEquals(sliceFallback(arr, -1), arr.slice(-1)); + deepEquals(sliceFallback(arr, 0), arr.slice(0)); + deepEquals(sliceFallback(arr, 1), arr.slice(1)); + deepEquals(sliceFallback(arr, 2), arr.slice(2)); + deepEquals(sliceFallback(arr, 3), arr.slice(3)); + deepEquals(sliceFallback(arr, 4), arr.slice(4)); + deepEquals(sliceFallback(arr, 5), arr.slice(5)); + }); + it('end', () => { + const arr = new Uint32Array([1, 2, 3, 4, 5]); + deepEquals(sliceFallback(arr, -1, -2), arr.slice(-1, -2)); + deepEquals(sliceFallback(arr, 0, -2), arr.slice(0, -2)); + deepEquals(sliceFallback(arr, 1, -2), arr.slice(1, -2)); + deepEquals(sliceFallback(arr, 2, -2), arr.slice(2, -2)); + deepEquals(sliceFallback(arr, 3, -2), arr.slice(3, -2)); + deepEquals(sliceFallback(arr, 4, -2), arr.slice(4, -2)); + deepEquals(sliceFallback(arr, 5, -2), arr.slice(5, -2)); + + deepEquals(sliceFallback(arr, -1, 3), arr.slice(-1, 3)); + deepEquals(sliceFallback(arr, 0, 3), arr.slice(0, 3)); + deepEquals(sliceFallback(arr, 1, 3), arr.slice(1, 3)); + deepEquals(sliceFallback(arr, 2, 3), arr.slice(2, 3)); + deepEquals(sliceFallback(arr, 3, 3), arr.slice(3, 3)); + deepEquals(sliceFallback(arr, 4, 3), arr.slice(4, 3)); + deepEquals(sliceFallback(arr, 5, 3), arr.slice(5, 3)); + + deepEquals(sliceFallback(arr, -1, 8), arr.slice(-1, 8)); + deepEquals(sliceFallback(arr, 0, 8), arr.slice(0, 8)); + deepEquals(sliceFallback(arr, 1, 8), arr.slice(1, 8)); + deepEquals(sliceFallback(arr, 2, 8), arr.slice(2, 8)); + deepEquals(sliceFallback(arr, 3, 8), arr.slice(3, 8)); + deepEquals(sliceFallback(arr, 4, 8), arr.slice(4, 8)); + deepEquals(sliceFallback(arr, 5, 8), arr.slice(5, 8)); + }); + }); +}); diff --git a/src/renderer/webgl/TypedArray.ts b/src/renderer/webgl/TypedArray.ts new file mode 100644 index 00000000..33d59ea1 --- /dev/null +++ b/src/renderer/webgl/TypedArray.ts @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray + | Int8Array | Int16Array | Int32Array + | Float32Array | Float64Array; + + +/** + * polyfill for TypedArray.fill + * This is needed to support .fill in all safari versions and IE 11. + */ +export function fill(array: T, value: number, start?: number, end?: number): T { + // all modern engines that support .fill + if (array.fill) { + return array.fill(value, start, end) as T; + } + return fillFallback(array, value, start, end); +} + +export function fillFallback(array: T, value: number, start: number = 0, end: number = array.length): T { + // safari and IE 11 + // since IE 11 does not support Array.prototype.fill either + // we cannot use the suggested polyfill from MDN + // instead we simply fall back to looping + if (start >= array.length) { + return array; + } + start = (array.length + start) % array.length; + if (end >= array.length) { + end = array.length; + } else { + end = (array.length + end) % array.length; + } + for (let i = start; i < end; ++i) { + array[i] = value; + } + return array; +} + +export function slice(array: T, start?: number, end?: number): T { + // all modern engines that support .slice + if (array.slice) { + return array.slice(start, end) as T; + } + return sliceFallback(array, start, end); +} + +export function sliceFallback(array: T, start: number = 0, end: number = array.length): T { + if (start < 0) { + start = (array.length + start) % array.length; + } + if (end >= array.length) { + end = array.length; + } else { + end = (array.length + end) % array.length; + } + start = Math.min(start, end); + + const result: T = new (array.constructor as any)(end - start); + for (let i = 0; i < end - start; ++i) { + result[i] = array[i + start]; + } + return result; +} diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index d93080d7..27404a8a 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -17,7 +17,7 @@ import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from '../../common/Lifecycle'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from '../../core/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../common/Types'; -import { IColorSet } from '../../ui/Types'; +import { IColorSet } from 'xterm'; import { getLuminance } from './ColorUtils'; export const INDICIES_PER_CELL = 4; diff --git a/src/renderer/webgl/atlas/CharAtlasCache.ts b/src/renderer/webgl/atlas/CharAtlasCache.ts index 9d16893b..05bdadc2 100644 --- a/src/renderer/webgl/atlas/CharAtlasCache.ts +++ b/src/renderer/webgl/atlas/CharAtlasCache.ts @@ -8,7 +8,7 @@ import { generateConfig, configEquals } from './CharAtlasUtils'; import BaseCharAtlas from './BaseCharAtlas'; import WebglCharAtlas from './WebglCharAtlas'; import { ICharAtlasConfig } from './Types'; -import { IColorSet } from '../../../ui/Types'; +import { IColorSet } from 'xterm'; interface ICharAtlasCacheEntry { atlas: BaseCharAtlas; diff --git a/src/renderer/webgl/atlas/CharAtlasGenerator.ts b/src/renderer/webgl/atlas/CharAtlasGenerator.ts index e844f37a..0d343a43 100644 --- a/src/renderer/webgl/atlas/CharAtlasGenerator.ts +++ b/src/renderer/webgl/atlas/CharAtlasGenerator.ts @@ -3,10 +3,9 @@ * @license MIT */ -import { FontWeight } from 'xterm'; +import { FontWeight, IColor } from 'xterm'; import { isFirefox, isSafari } from '../../../common/Platform'; import { ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types'; -import { IColor } from '../../../ui/Types'; /** * Generates a char atlas. diff --git a/src/renderer/webgl/atlas/CharAtlasUtils.ts b/src/renderer/webgl/atlas/CharAtlasUtils.ts index b11ca085..855e32c8 100644 --- a/src/renderer/webgl/atlas/CharAtlasUtils.ts +++ b/src/renderer/webgl/atlas/CharAtlasUtils.ts @@ -6,11 +6,11 @@ import { ITerminal } from '../../../Types'; import { ICharAtlasConfig } from './Types'; import { DEFAULT_COLOR } from '../../../common/Types'; -import { IColorSet } from '../../../ui/Types'; +import { IColorSet } from 'xterm'; export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { // null out some fields that don't matter - const clonedColors = { + const clonedColors: IColorSet = { foreground: colors.foreground, background: colors.background, cursor: null, diff --git a/src/renderer/webgl/atlas/Types.ts b/src/renderer/webgl/atlas/Types.ts index 1bdaf3b9..ee6dc665 100644 --- a/src/renderer/webgl/atlas/Types.ts +++ b/src/renderer/webgl/atlas/Types.ts @@ -3,8 +3,7 @@ * @license MIT */ -import { FontWeight } from 'xterm'; -import { IColorSet } from '../../../ui/Types'; +import { FontWeight, IColorSet } from 'xterm'; export const INVERTED_DEFAULT_COLOR = 257; export const DIM_OPACITY = 0.5; diff --git a/src/renderer/webgl/atlas/WebglCharAtlas.ts b/src/renderer/webgl/atlas/WebglCharAtlas.ts index 5da955bc..a9cd8e7d 100644 --- a/src/renderer/webgl/atlas/WebglCharAtlas.ts +++ b/src/renderer/webgl/atlas/WebglCharAtlas.ts @@ -11,8 +11,8 @@ import { is256Color } from './CharAtlasUtils'; import { clearColor } from './CharAtlasGenerator'; import { DEFAULT_ATTR } from '../../../core/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../../common/Types'; -import { IColor } from '../../../ui/Types'; import { DEFAULT_ANSI_COLORS } from '../../../ui/ColorManager'; +import { IColor } from 'xterm'; // 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. diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index de4051ed..7d79d959 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -897,6 +897,26 @@ declare module 'xterm' { setRenderer(renderer: any): void; } + /** + * (EXPERIMENTAL) + */ + export interface IColor { + css: string; + rgba: number; + } + + /** + * (EXPERIMENTAL) + */ + export interface IColorSet { + foreground: IColor; + background: IColor; + cursor: IColor; + cursorAccent: IColor; + selection: IColor; + ansi: IColor[]; + } + /** * An addon that can provide additional functionality to the terminal. */ From 134c2ff85921685276dd4ac2b0cc026f386b176b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 19 May 2019 00:21:07 -0700 Subject: [PATCH 30/69] Move parts of webgl renderer to public API --- src/public/Terminal.ts | 1 + src/renderer/LinkRenderLayer.ts | 1 + src/renderer/webgl/GlyphRenderer.ts | 21 ++++---- src/renderer/webgl/RectangleRenderer.ts | 5 +- src/renderer/webgl/WebglRenderer.ts | 58 ++++++++++++---------- src/renderer/webgl/WebglRendererAddon.ts | 3 +- src/renderer/webgl/atlas/CharAtlasCache.ts | 9 ++-- src/renderer/webgl/atlas/CharAtlasUtils.ts | 19 +++---- src/renderer/webgl/atlas/Types.ts | 1 - typings/xterm.d.ts | 5 ++ 10 files changed, 62 insertions(+), 61 deletions(-) diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index ce469b97..3af5162c 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -32,6 +32,7 @@ export class Terminal implements ITerminalApi { public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; } public get element(): HTMLElement { return this._core.element; } + public get screenElement(): HTMLElement { return this._core.screenElement; } public get textarea(): HTMLTextAreaElement { return this._core.textarea; } public get rows(): number { return this._core.rows; } public get cols(): number { return this._core.cols; } diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index c92b10dc..6bba167c 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -15,6 +15,7 @@ export class LinkRenderLayer extends BaseRenderLayer { constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) { super(container, 'link', zIndex, true, colors); + // TODO: Need to expose link-related renderer API terminal.linkifier.onLinkHover(e => this._onLinkHover(e)); terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e)); } diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 9d61b065..de9c0bf7 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -5,16 +5,14 @@ import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderDimensions } from '../Types'; -import { ITerminal } from '../../Types'; import WebglCharAtlas from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { fill, slice } from './TypedArray'; -import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, CHAR_DATA_CHAR_INDEX } from '../../core/buffer/BufferLine'; -import { IBufferLine } from '../../core/Types'; +import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from '../../core/buffer/BufferLine'; import { getLuminance } from './ColorUtils'; -import { IColorSet } from 'xterm'; +import { IColorSet, Terminal, IBufferLine } from 'xterm'; interface IVertices { attributes: Float32Array; @@ -97,7 +95,7 @@ export class GlyphRenderer { }; constructor( - private _terminal: ITerminal, + private _terminal: Terminal, private _colors: IColorSet, private _gl: IWebGL2RenderingContext, private _dimensions: IRenderDimensions @@ -185,9 +183,9 @@ export class GlyphRenderer { let rasterizedGlyph: IRasterizedGlyph; if (chars && chars.length > 1) { - rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg, this._terminal.options.enableBold); + rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg, this._terminal.getOption('enableBold')); } else { - rasterizedGlyph = this._atlas.getRasterizedGlyph(code, attr, bg, fg, this._terminal.options.enableBold); + rasterizedGlyph = this._atlas.getRasterizedGlyph(code, attr, bg, fg, this._terminal.getOption('enableBold')); } // Fill empty if no glyph was found @@ -252,8 +250,8 @@ export class GlyphRenderer { private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number, fg: number): void { const terminal = this._terminal; - const row = y + terminal.buffer.ydisp; - let line: IBufferLine; + const row = y + terminal.buffer.viewportY; + let line: IBufferLine | undefined; for (let x = startCol; x < endCol; x++) { const offset = (y * this._terminal.cols + x) * INDICIES_PER_CELL; // Because the cache uses attr as a lookup key it needs to contain the selection colors as well @@ -262,10 +260,9 @@ export class GlyphRenderer { const code = model.cells[offset]; if (code & COMBINED_CHAR_BIT_MASK) { if (!line) { - line = terminal.buffer.lines.get(row); + line = terminal.buffer.getLine(row); } - const charData = line.get(x); - const chars = charData[CHAR_DATA_CHAR_INDEX]; + const chars = line.getCell(x).char; this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg, chars); } else { this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], attr, bg, fg); diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index a133abd4..ca4c062c 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -3,7 +3,6 @@ * @license MIT */ -import { ITerminal } from '../../Types'; import { IRenderDimensions } from '../Types'; import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; @@ -11,7 +10,7 @@ import { fill } from './TypedArray'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { is256Color } from './atlas/CharAtlasUtils'; import { DEFAULT_COLOR } from '../../common/Types'; -import { IColorSet, IColor } from 'xterm'; +import { IColorSet, IColor, Terminal } from 'xterm'; const enum VertexAttribLocations { POSITION = 0, @@ -76,7 +75,7 @@ export class RectangleRenderer { }; constructor( - private _terminal: ITerminal, + private _terminal: Terminal, private _colors: IColorSet, private _gl: IWebGL2RenderingContext, private _dimensions: IRenderDimensions diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 27404a8a..2fadebbd 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -17,7 +17,7 @@ import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from '../../common/Lifecycle'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from '../../core/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../common/Types'; -import { IColorSet } from 'xterm'; +import { IColorSet, Terminal } from 'xterm'; import { getLuminance } from './ColorUtils'; export const INDICIES_PER_CELL = 4; @@ -36,16 +36,20 @@ export class WebglRenderer extends Disposable implements IRenderer { public dimensions: IRenderDimensions; + private _core: ITerminal; + constructor( - private _terminal: ITerminal, + private _terminal: Terminal, private _colors: IColorSet ) { super(); + this._core = (this._terminal as any)._core; + this._applyBgLuminanceBasedSelection(); this._renderLayers = [ - new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._terminal), + new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._core), new CursorRenderLayer(this._terminal.screenElement, 3, this._colors) ]; this.dimensions = { @@ -102,8 +106,8 @@ export class WebglRenderer extends Disposable implements IRenderer { // Clear layers and force a full render this._renderLayers.forEach(l => { - l.setColors(this._terminal, this._colors); - l.reset(this._terminal); + l.setColors(this._core, this._colors); + l.reset(this._core); }); this._rectangleRenderer.setColors(); @@ -117,7 +121,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // and the terminal needs to refreshed if (this._devicePixelRatio !== window.devicePixelRatio) { this._devicePixelRatio = window.devicePixelRatio; - this.onResize(this._terminal.cols, this._terminal.rows); + this.onResize(this._core.cols, this._core.rows); } } @@ -125,11 +129,11 @@ export class WebglRenderer extends Disposable implements IRenderer { // Update character and canvas dimensions this._updateDimensions(devicePixelRatio); - this._model.resize(this._terminal.cols, this._terminal.rows); + this._model.resize(this._core.cols, this._core.rows); this._rectangleRenderer.onResize(); // Resize all render layers - this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions)); + this._renderLayers.forEach(l => l.resize(this._core, this.dimensions)); // Resize the canvas this._canvas.width = this.dimensions.scaledCanvasWidth; @@ -151,15 +155,15 @@ export class WebglRenderer extends Disposable implements IRenderer { } public onBlur(): void { - this._renderLayers.forEach(l => l.onBlur(this._terminal)); + this._renderLayers.forEach(l => l.onBlur(this._core)); } public onFocus(): void { - this._renderLayers.forEach(l => l.onFocus(this._terminal)); + this._renderLayers.forEach(l => l.onFocus(this._core)); } public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void { - this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end, columnSelectMode)); + this._renderLayers.forEach(l => l.onSelectionChanged(this._core, start, end, columnSelectMode)); this._updateSelectionModel(start, end); @@ -167,15 +171,15 @@ export class WebglRenderer extends Disposable implements IRenderer { this._glyphRenderer.updateSelection(this._model, columnSelectMode); // TODO: #2102 Should this move to RenderCoordinator? - this._terminal.refresh(0, this._terminal.rows - 1); + this._core.refresh(0, this._core.rows - 1); } public onCursorMove(): void { - this._renderLayers.forEach(l => l.onCursorMove(this._terminal)); + this._renderLayers.forEach(l => l.onCursorMove(this._core)); } public onOptionsChanged(): void { - this._renderLayers.forEach(l => l.onOptionsChanged(this._terminal)); + this._renderLayers.forEach(l => l.onOptionsChanged(this._core)); this._updateDimensions(); this._refreshCharAtlas(); } @@ -200,7 +204,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } public clear(): void { - this._renderLayers.forEach(l => l.reset(this._terminal)); + this._renderLayers.forEach(l => l.reset(this._core)); } public registerCharacterJoiner(handler: CharacterJoinerHandler): number { @@ -213,7 +217,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public renderRows(start: number, end: number): void { // Update render layers - this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); + this._renderLayers.forEach(l => l.onGridChanged(this._core, start, end)); // Tell renderer the frame is beginning if (this._glyphRenderer.beginFrame()) { @@ -229,7 +233,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } private _updateModel(start: number, end: number): void { - const terminal = this._terminal; + const terminal = this._core; for (let y = start; y <= end; y++) { const row = y + terminal.buffer.ydisp; @@ -288,7 +292,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } private _updateSelectionModel(start: [number, number], end: [number, number]): void { - const terminal = this._terminal; + const terminal = this._core; // Selection does not exist if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { @@ -322,7 +326,7 @@ export class WebglRenderer extends Disposable implements IRenderer { */ private _updateDimensions(devicePixelRatio: number = window.devicePixelRatio): void { // Perform a new measure if the CharMeasure dimensions are not yet available - if (!this._terminal.charMeasure.width || !this._terminal.charMeasure.height) { + if (!this._core.charMeasure.width || !this._core.charMeasure.height) { return; } @@ -333,34 +337,34 @@ export class WebglRenderer extends Disposable implements IRenderer { // NOTE: ceil fixes sometime, floor does others :s - this.dimensions.scaledCharWidth = Math.floor(this._terminal.charMeasure.width * devicePixelRatio); + this.dimensions.scaledCharWidth = Math.floor(this._core.charMeasure.width * devicePixelRatio); // Calculate the scaled character height. Height is ceiled in case // devicePixelRatio is a floating point number in order to ensure there is // enough space to draw the character to the cell. - this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * devicePixelRatio); + this.dimensions.scaledCharHeight = Math.ceil(this._core.charMeasure.height * devicePixelRatio); // Calculate the scaled cell height, if lineHeight is not 1 then the value // will be floored because since lineHeight can never be lower then 1, there // is a guarentee that the scaled line height will always be larger than // scaled char height. - this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight); + this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._core.options.lineHeight); // Calculate the y coordinate within a cell that text should draw from in // order to draw in the center of a cell. - this.dimensions.scaledCharTop = this._terminal.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); + this.dimensions.scaledCharTop = this._core.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); // Calculate the scaled cell width, taking the letterSpacing into account. - this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing); + this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._core.options.letterSpacing); // Calculate the x coordinate with a cell that text should draw from in // order to draw in the center of a cell. - this.dimensions.scaledCharLeft = Math.floor(this._terminal.options.letterSpacing / 2); + this.dimensions.scaledCharLeft = Math.floor(this._core.options.letterSpacing / 2); // Recalculate the canvas dimensions; scaled* define the actual number of // pixel in the canvas - this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight; - this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth; + this.dimensions.scaledCanvasHeight = this._core.rows * this.dimensions.scaledCellHeight; + this.dimensions.scaledCanvasWidth = this._core.cols * this.dimensions.scaledCellWidth; // The the size of the canvas on the page. It's very important that this // rounds to nearest integer and not ceils as browsers often set diff --git a/src/renderer/webgl/WebglRendererAddon.ts b/src/renderer/webgl/WebglRendererAddon.ts index 764caa0f..6c835ba8 100644 --- a/src/renderer/webgl/WebglRendererAddon.ts +++ b/src/renderer/webgl/WebglRendererAddon.ts @@ -16,8 +16,7 @@ export class WebglRendererAddon implements ITerminalAddon { throw new Error('Cannot activate WebglRendererAddon before Terminal.open'); } this._terminal = terminal; - const core = (terminal as any)._core; - this._terminal.setRenderer(new WebglRenderer(core, core._colorManager.colors)); + this._terminal.setRenderer(new WebglRenderer(terminal, (terminal as any)._core._colorManager.colors)); } public dispose(): void { diff --git a/src/renderer/webgl/atlas/CharAtlasCache.ts b/src/renderer/webgl/atlas/CharAtlasCache.ts index 05bdadc2..8d8459fd 100644 --- a/src/renderer/webgl/atlas/CharAtlasCache.ts +++ b/src/renderer/webgl/atlas/CharAtlasCache.ts @@ -3,19 +3,18 @@ * @license MIT */ -import { ITerminal } from '../../../Types'; import { generateConfig, configEquals } from './CharAtlasUtils'; import BaseCharAtlas from './BaseCharAtlas'; import WebglCharAtlas from './WebglCharAtlas'; import { ICharAtlasConfig } from './Types'; -import { IColorSet } from 'xterm'; +import { IColorSet, Terminal } from 'xterm'; 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: ITerminal[]; + ownedBy: Terminal[]; } const charAtlasCache: ICharAtlasCacheEntry[] = []; @@ -27,7 +26,7 @@ const charAtlasCache: ICharAtlasCacheEntry[] = []; * @param colors The colors to use. */ export function acquireCharAtlas( - terminal: ITerminal, + terminal: Terminal, colors: IColorSet, scaledCharWidth: number, scaledCharHeight: number @@ -76,7 +75,7 @@ export function acquireCharAtlas( * Removes a terminal reference from the cache, allowing its memory to be freed. * @param terminal The terminal to remove. */ -export function removeTerminalFromCache(terminal: ITerminal): void { +export function removeTerminalFromCache(terminal: Terminal): void { for (let i = 0; i < charAtlasCache.length; i++) { const index = charAtlasCache[i].ownedBy.indexOf(terminal); if (index !== -1) { diff --git a/src/renderer/webgl/atlas/CharAtlasUtils.ts b/src/renderer/webgl/atlas/CharAtlasUtils.ts index 855e32c8..2bda420f 100644 --- a/src/renderer/webgl/atlas/CharAtlasUtils.ts +++ b/src/renderer/webgl/atlas/CharAtlasUtils.ts @@ -3,12 +3,11 @@ * @license MIT */ -import { ITerminal } from '../../../Types'; import { ICharAtlasConfig } from './Types'; import { DEFAULT_COLOR } from '../../../common/Types'; -import { IColorSet } from 'xterm'; +import { IColorSet, Terminal, FontWeight } from 'xterm'; -export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { +export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: IColorSet): ICharAtlasConfig { // null out some fields that don't matter const clonedColors: IColorSet = { foreground: colors.foreground, @@ -21,15 +20,14 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number ansi: colors.ansi.slice(0, 16) }; return { - type: terminal.options.experimentalCharAtlas, devicePixelRatio: window.devicePixelRatio, scaledCharWidth, scaledCharHeight, - fontFamily: terminal.options.fontFamily, - fontSize: terminal.options.fontSize, - fontWeight: terminal.options.fontWeight, - fontWeightBold: terminal.options.fontWeightBold, - allowTransparency: terminal.options.allowTransparency, + fontFamily: terminal.getOption('fontFamily'), + fontSize: terminal.getOption('fontSize'), + fontWeight: terminal.getOption('fontWeight') as FontWeight, + fontWeightBold: terminal.getOption('fontWeightBold') as FontWeight, + allowTransparency: terminal.getOption('allowTransparency'), colors: clonedColors }; } @@ -40,8 +38,7 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean return false; } } - return a.type === b.type && - a.devicePixelRatio === b.devicePixelRatio && + return a.devicePixelRatio === b.devicePixelRatio && a.fontFamily === b.fontFamily && a.fontSize === b.fontSize && a.fontWeight === b.fontWeight && diff --git a/src/renderer/webgl/atlas/Types.ts b/src/renderer/webgl/atlas/Types.ts index ee6dc665..1df4f82d 100644 --- a/src/renderer/webgl/atlas/Types.ts +++ b/src/renderer/webgl/atlas/Types.ts @@ -21,7 +21,6 @@ export interface IGlyphIdentifier { } export interface ICharAtlasConfig { - type: 'none' | 'static' | 'dynamic' | 'webgl'; devicePixelRatio: number; fontSize: number; fontFamily: string; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 7d79d959..8712c3d8 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -895,6 +895,11 @@ declare module 'xterm' { * (EXPERIMENTAL) */ setRenderer(renderer: any): void; + screenElement: HTMLElement; + } + + export namespace Renderer { + const DEFAULT_COLOR: number; } /** From 061854b5356f972095579cfb7b35dfb73b178acc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 19 May 2019 00:55:02 -0700 Subject: [PATCH 31/69] Remove more dependencies on rest of project --- src/renderer/webgl/GlyphRenderer.ts | 3 +- src/renderer/webgl/Lifecycle.test.ts | 45 ++ src/renderer/webgl/Lifecycle.ts | 47 +++ src/renderer/webgl/Platform.ts | 10 + src/renderer/webgl/RectangleRenderer.ts | 3 +- src/renderer/webgl/WebglRenderer.ts | 31 +- .../webgl/atlas/CharAtlasGenerator.ts | 2 +- .../webgl/renderLayer/BaseRenderLayer.ts | 384 ++++++++++++++++++ .../webgl/renderLayer/CursorRenderLayer.ts | 358 ++++++++++++++++ .../webgl/renderLayer/LinkRenderLayer.ts | 71 ++++ src/renderer/webgl/renderLayer/Types.ts | 65 +++ typings/xterm.d.ts | 41 +- 12 files changed, 1039 insertions(+), 21 deletions(-) create mode 100644 src/renderer/webgl/Lifecycle.test.ts create mode 100644 src/renderer/webgl/Lifecycle.ts create mode 100644 src/renderer/webgl/Platform.ts create mode 100644 src/renderer/webgl/renderLayer/BaseRenderLayer.ts create mode 100644 src/renderer/webgl/renderLayer/CursorRenderLayer.ts create mode 100644 src/renderer/webgl/renderLayer/LinkRenderLayer.ts create mode 100644 src/renderer/webgl/renderLayer/Types.ts diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index de9c0bf7..545a666e 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -4,7 +4,6 @@ */ import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; -import { IRenderDimensions } from '../Types'; import WebglCharAtlas from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; @@ -12,7 +11,7 @@ import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { fill, slice } from './TypedArray'; import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from '../../core/buffer/BufferLine'; import { getLuminance } from './ColorUtils'; -import { IColorSet, Terminal, IBufferLine } from 'xterm'; +import { IColorSet, Terminal, IBufferLine, IRenderDimensions } from 'xterm'; interface IVertices { attributes: Float32Array; diff --git a/src/renderer/webgl/Lifecycle.test.ts b/src/renderer/webgl/Lifecycle.test.ts new file mode 100644 index 00000000..4b696fa5 --- /dev/null +++ b/src/renderer/webgl/Lifecycle.test.ts @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { assert } from 'chai'; +import { Disposable } from './Lifecycle'; + +class TestDisposable extends Disposable { + public get isDisposed(): boolean { + return this._isDisposed; + } +} + +describe('Disposable', () => { + describe('register', () => { + it('should register disposables', () => { + const d = new TestDisposable(); + const d2 = { + dispose: () => { throw new Error(); } + }; + d.register(d2); + assert.throws(() => d.dispose()); + }); + }); + describe('unregister', () => { + it('should unregister disposables', () => { + const d = new TestDisposable(); + const d2 = { + dispose: () => { throw new Error(); } + }; + d.register(d2); + d.unregister(d2); + assert.doesNotThrow(() => d.dispose()); + }); + }); + describe('dispose', () => { + it('should set is disposed flag', () => { + const d = new TestDisposable(); + assert.isFalse(d.isDisposed); + d.dispose(); + assert.isTrue(d.isDisposed); + }); + }); +}); diff --git a/src/renderer/webgl/Lifecycle.ts b/src/renderer/webgl/Lifecycle.ts new file mode 100644 index 00000000..209a3e2a --- /dev/null +++ b/src/renderer/webgl/Lifecycle.ts @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IDisposable } from 'xterm'; + +/** + * A base class that can be extended to provide convenience methods for managing the lifecycle of an + * object and its components. + */ +export abstract class Disposable implements IDisposable { + protected _disposables: IDisposable[] = []; + protected _isDisposed: boolean = false; + + constructor() { + } + + /** + * Disposes the object, triggering the `dispose` method on all registered IDisposables. + */ + public dispose(): void { + this._isDisposed = true; + this._disposables.forEach(d => d.dispose()); + this._disposables.length = 0; + } + + /** + * Registers a disposable object. + * @param d The disposable to register. + */ + public register(d: T): void { + this._disposables.push(d); + } + + /** + * Unregisters a disposable object if it has been registered, if not do + * nothing. + * @param d The disposable to unregister. + */ + public unregister(d: T): void { + const index = this._disposables.indexOf(d); + if (index !== -1) { + this._disposables.splice(index, 1); + } + } +} diff --git a/src/renderer/webgl/Platform.ts b/src/renderer/webgl/Platform.ts new file mode 100644 index 00000000..55a10b7f --- /dev/null +++ b/src/renderer/webgl/Platform.ts @@ -0,0 +1,10 @@ +/** + * Copyright (c) 2016 The xterm.js authors. All rights reserved. + * @license MIT + */ + +const isNode = (typeof navigator === 'undefined') ? true : false; +const userAgent = (isNode) ? 'node' : navigator.userAgent; + +export const isFirefox = !!~userAgent.indexOf('Firefox'); +export const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent); diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index ca4c062c..3fc8413c 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -3,14 +3,13 @@ * @license MIT */ -import { IRenderDimensions } from '../Types'; import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; import { fill } from './TypedArray'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { is256Color } from './atlas/CharAtlasUtils'; import { DEFAULT_COLOR } from '../../common/Types'; -import { IColorSet, IColor, Terminal } from 'xterm'; +import { IColorSet, IColor, Terminal, IRenderDimensions } from 'xterm'; const enum VertexAttribLocations { POSITION = 0, diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 2fadebbd..68f74c7b 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -3,22 +3,23 @@ * @license MIT */ -import { IRenderer, IRenderDimensions, IRenderLayer, FLAGS } from '../Types'; +import { FLAGS } from '../Types'; import { CharacterJoinerHandler, ITerminal } from '../../Types'; import { GlyphRenderer } from './GlyphRenderer'; -import { LinkRenderLayer } from '../LinkRenderLayer'; -import { CursorRenderLayer } from '../CursorRenderLayer'; +import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; +import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; import WebglCharAtlas from './atlas/WebglCharAtlas'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; -import { Disposable } from '../../common/Lifecycle'; +import { Disposable } from './Lifecycle'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from '../../core/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../common/Types'; -import { IColorSet, Terminal } from 'xterm'; +import { IColorSet, Terminal, IRenderDimensions, IRenderer } from 'xterm'; import { getLuminance } from './ColorUtils'; +import { IRenderLayer } from './renderLayer/Types'; export const INDICIES_PER_CELL = 4; @@ -106,8 +107,8 @@ export class WebglRenderer extends Disposable implements IRenderer { // Clear layers and force a full render this._renderLayers.forEach(l => { - l.setColors(this._core, this._colors); - l.reset(this._core); + l.setColors(this._terminal, this._colors); + l.reset(this._terminal); }); this._rectangleRenderer.setColors(); @@ -133,7 +134,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._rectangleRenderer.onResize(); // Resize all render layers - this._renderLayers.forEach(l => l.resize(this._core, this.dimensions)); + this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions)); // Resize the canvas this._canvas.width = this.dimensions.scaledCanvasWidth; @@ -155,15 +156,15 @@ export class WebglRenderer extends Disposable implements IRenderer { } public onBlur(): void { - this._renderLayers.forEach(l => l.onBlur(this._core)); + this._renderLayers.forEach(l => l.onBlur(this._terminal)); } public onFocus(): void { - this._renderLayers.forEach(l => l.onFocus(this._core)); + this._renderLayers.forEach(l => l.onFocus(this._terminal)); } public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void { - this._renderLayers.forEach(l => l.onSelectionChanged(this._core, start, end, columnSelectMode)); + this._renderLayers.forEach(l => l.onSelectionChanged(this._terminal, start, end, columnSelectMode)); this._updateSelectionModel(start, end); @@ -175,11 +176,11 @@ export class WebglRenderer extends Disposable implements IRenderer { } public onCursorMove(): void { - this._renderLayers.forEach(l => l.onCursorMove(this._core)); + this._renderLayers.forEach(l => l.onCursorMove(this._terminal)); } public onOptionsChanged(): void { - this._renderLayers.forEach(l => l.onOptionsChanged(this._core)); + this._renderLayers.forEach(l => l.onOptionsChanged(this._terminal)); this._updateDimensions(); this._refreshCharAtlas(); } @@ -204,7 +205,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } public clear(): void { - this._renderLayers.forEach(l => l.reset(this._core)); + this._renderLayers.forEach(l => l.reset(this._terminal)); } public registerCharacterJoiner(handler: CharacterJoinerHandler): number { @@ -217,7 +218,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public renderRows(start: number, end: number): void { // Update render layers - this._renderLayers.forEach(l => l.onGridChanged(this._core, start, end)); + this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); // Tell renderer the frame is beginning if (this._glyphRenderer.beginFrame()) { diff --git a/src/renderer/webgl/atlas/CharAtlasGenerator.ts b/src/renderer/webgl/atlas/CharAtlasGenerator.ts index 0d343a43..c01ea630 100644 --- a/src/renderer/webgl/atlas/CharAtlasGenerator.ts +++ b/src/renderer/webgl/atlas/CharAtlasGenerator.ts @@ -4,8 +4,8 @@ */ import { FontWeight, IColor } from 'xterm'; -import { isFirefox, isSafari } from '../../../common/Platform'; import { ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types'; +import { isFirefox, isSafari } from '../Platform'; /** * Generates a char atlas. diff --git a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts new file mode 100644 index 00000000..36c5bd53 --- /dev/null +++ b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts @@ -0,0 +1,384 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IRenderLayer } from './Types'; +import { ICellData } from '../../../core/Types'; +import { DEFAULT_COLOR } from '../../../common/Types'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from '../atlas/Types'; +import BaseCharAtlas from '../atlas/BaseCharAtlas'; +import { acquireCharAtlas } from '../atlas/CharAtlasCache'; +import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../../../core/buffer/BufferLine'; +import { IColorSet, IRenderDimensions, Terminal } from 'xterm'; + +export abstract class BaseRenderLayer implements IRenderLayer { + private _canvas: HTMLCanvasElement; + protected _ctx: CanvasRenderingContext2D; + private _scaledCharWidth: number = 0; + private _scaledCharHeight: number = 0; + private _scaledCellWidth: number = 0; + private _scaledCellHeight: number = 0; + private _scaledCharLeft: number = 0; + private _scaledCharTop: number = 0; + + protected _charAtlas: BaseCharAtlas; + + /** + * 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 + }; + + constructor( + private _container: HTMLElement, + id: string, + zIndex: number, + private _alpha: boolean, + protected _colors: IColorSet + ) { + this._canvas = document.createElement('canvas'); + this._canvas.classList.add(`xterm-${id}-layer`); + this._canvas.style.zIndex = zIndex.toString(); + this._initCanvas(); + this._container.appendChild(this._canvas); + } + + public dispose(): void { + this._container.removeChild(this._canvas); + if (this._charAtlas) { + this._charAtlas.dispose(); + } + } + + private _initCanvas(): void { + this._ctx = this._canvas.getContext('2d', {alpha: this._alpha}); + // Draw the background if this is an opaque layer + if (!this._alpha) { + this.clearAll(); + } + } + + public onOptionsChanged(terminal: Terminal): void {} + public onBlur(terminal: Terminal): void {} + public onFocus(terminal: Terminal): void {} + public onCursorMove(terminal: Terminal): void {} + public onGridChanged(terminal: Terminal, startRow: number, endRow: number): void {} + public onSelectionChanged(terminal: Terminal, start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {} + + public setColors(terminal: Terminal, colorSet: IColorSet): void { + this._refreshCharAtlas(terminal, colorSet); + } + + protected setTransparency(terminal: Terminal, alpha: boolean): void { + // Do nothing when alpha doesn't change + if (alpha === this._alpha) { + return; + } + + // Create new canvas and replace old one + const oldCanvas = this._canvas; + this._alpha = alpha; + // Cloning preserves properties + this._canvas = this._canvas.cloneNode(); + this._initCanvas(); + this._container.replaceChild(this._canvas, oldCanvas); + + // Regenerate char atlas and force a full redraw + this._refreshCharAtlas(terminal, this._colors); + this.onGridChanged(terminal, 0, terminal.rows - 1); + } + + /** + * Refreshes the char atlas, aquiring a new one if necessary. + * @param terminal The terminal. + * @param colorSet The color set to use for the char atlas. + */ + private _refreshCharAtlas(terminal: Terminal, colorSet: IColorSet): void { + if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { + return; + } + this._charAtlas = acquireCharAtlas(terminal, colorSet, this._scaledCharWidth, this._scaledCharHeight); + this._charAtlas.warmUp(); + } + + public resize(terminal: Terminal, dim: IRenderDimensions): void { + this._scaledCellWidth = dim.scaledCellWidth; + this._scaledCellHeight = dim.scaledCellHeight; + this._scaledCharWidth = dim.scaledCharWidth; + this._scaledCharHeight = dim.scaledCharHeight; + this._scaledCharLeft = dim.scaledCharLeft; + this._scaledCharTop = dim.scaledCharTop; + this._canvas.width = dim.scaledCanvasWidth; + this._canvas.height = dim.scaledCanvasHeight; + this._canvas.style.width = `${dim.canvasWidth}px`; + this._canvas.style.height = `${dim.canvasHeight}px`; + + // Draw the background if this is an opaque layer + if (!this._alpha) { + this.clearAll(); + } + + this._refreshCharAtlas(terminal, this._colors); + } + + public abstract reset(terminal: Terminal): void; + + /** + * Fills 1+ cells completely. This uses the existing fillStyle on the context. + * @param x The column to start at. + * @param y The row to start at + * @param width The number of columns to fill. + * @param height The number of rows to fill. + */ + protected fillCells(x: number, y: number, width: number, height: number): void { + this._ctx.fillRect( + x * this._scaledCellWidth, + y * this._scaledCellHeight, + width * this._scaledCellWidth, + height * this._scaledCellHeight); + } + + /** + * Fills a 1px line (2px on HDPI) at the bottom of the cell. This uses the + * existing fillStyle on the context. + * @param x The column to fill. + * @param y The row to fill. + */ + protected fillBottomLineAtCells(x: number, y: number, width: number = 1): void { + this._ctx.fillRect( + x * this._scaledCellWidth, + (y + 1) * this._scaledCellHeight - window.devicePixelRatio - 1 /* Ensure it's drawn within the cell */, + width * this._scaledCellWidth, + window.devicePixelRatio); + } + + /** + * Fills a 1px line (2px on HDPI) at the left of the cell. This uses the + * existing fillStyle on the context. + * @param x The column to fill. + * @param y The row to fill. + */ + protected fillLeftLineAtCell(x: number, y: number): void { + this._ctx.fillRect( + x * this._scaledCellWidth, + y * this._scaledCellHeight, + window.devicePixelRatio, + this._scaledCellHeight); + } + + /** + * Strokes a 1px rectangle (2px on HDPI) around a cell. This uses the existing + * strokeStyle on the context. + * @param x The column to fill. + * @param y The row to fill. + */ + protected strokeRectAtCell(x: number, y: number, width: number, height: number): void { + this._ctx.lineWidth = window.devicePixelRatio; + this._ctx.strokeRect( + x * this._scaledCellWidth + window.devicePixelRatio / 2, + y * this._scaledCellHeight + (window.devicePixelRatio / 2), + width * this._scaledCellWidth - window.devicePixelRatio, + (height * this._scaledCellHeight) - window.devicePixelRatio); + } + + /** + * Clears the entire canvas. + */ + protected clearAll(): void { + if (this._alpha) { + this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); + } else { + this._ctx.fillStyle = this._colors.background.css; + this._ctx.fillRect(0, 0, this._canvas.width, this._canvas.height); + } + } + + /** + * Clears 1+ cells completely. + * @param x The column to start at. + * @param y The row to start at. + * @param width The number of columns to clear. + * @param height The number of rows to clear. + */ + protected clearCells(x: number, y: number, width: number, height: number): void { + if (this._alpha) { + this._ctx.clearRect( + x * this._scaledCellWidth, + y * this._scaledCellHeight, + width * this._scaledCellWidth, + height * this._scaledCellHeight); + } else { + this._ctx.fillStyle = this._colors.background.css; + this._ctx.fillRect( + x * this._scaledCellWidth, + y * this._scaledCellHeight, + width * this._scaledCellWidth, + height * this._scaledCellHeight); + } + } + + /** + * Draws a truecolor character at the cell. The character will be clipped to + * ensure that it fits with the cell, including the cell to the right if it's + * a wide character. This uses the existing fillStyle on the context. + * @param terminal The terminal. + * @param cell The cell data for the character to draw. + * @param x The column to draw at. + * @param y The row to draw at. + * @param color The color of the character. + */ + protected fillCharTrueColor(terminal: Terminal, cell: CellData, x: number, y: number): void { + this._ctx.font = this._getFont(terminal, false, false); + this._ctx.textBaseline = 'middle'; + this._clipRow(terminal, y); + this._ctx.fillText( + cell.getChars(), + x * this._scaledCellWidth + this._scaledCharLeft, + y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2); + } + + /** + * Draws one or more characters at a cell. If possible this will draw using + * the character atlas to reduce draw time. + * @param terminal The terminal. + * @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(terminal: Terminal, cell: ICellData, x: number, y: number): void { + + // 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 (cell.isFgRGB() || cell.isBgRGB()) { + this._drawUncachedChars(terminal, cell, x, y); + 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 = terminal.getOption('drawBoldTextInBrightColors') && cell.isBold() && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; + + 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() && terminal.getOption('enableBold'); + this._currentGlyphIdentifier.dim = !!cell.isDim(); + this._currentGlyphIdentifier.italic = !!cell.isItalic(); + const atlasDidDraw = this._charAtlas && this._charAtlas.draw( + this._ctx, + this._currentGlyphIdentifier, + x * this._scaledCellWidth + this._scaledCharLeft, + y * this._scaledCellHeight + this._scaledCharTop + ); + + if (!atlasDidDraw) { + this._drawUncachedChars(terminal, cell, x, y); + } + } + + /** + * 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 terminal The terminal. + * @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(terminal: Terminal, cell: ICellData, x: number, y: number): void { + this._ctx.save(); + this._ctx.font = this._getFont(terminal, cell.isBold() && terminal.getOption('enableBold'), !!cell.isItalic()); + this._ctx.textBaseline = 'middle'; + + 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 { + this._ctx.fillStyle = this._colors.ansi[cell.getBgColor()].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 (terminal.getOption('drawBoldTextInBrightColors') && cell.isBold() && fg < 8) { + fg += 8; + } + this._ctx.fillStyle = this._colors.ansi[fg].css; + } + } + + this._clipRow(terminal, y); + + // Apply alpha to dim the character + if (cell.isDim()) { + this._ctx.globalAlpha = DIM_OPACITY; + } + // Draw the character + this._ctx.fillText( + cell.getChars(), + x * this._scaledCellWidth + this._scaledCharLeft, + y * this._scaledCellHeight + this._scaledCharTop + this._scaledCharHeight / 2); + this._ctx.restore(); + } + + /** + * Clips a row to ensure no pixels will be drawn outside the cells in the row. + * @param terminal The terminal. + * @param y The row to clip. + */ + private _clipRow(terminal: Terminal, y: number): void { + this._ctx.beginPath(); + this._ctx.rect( + 0, + y * this._scaledCellHeight, + terminal.cols * this._scaledCellWidth, + this._scaledCellHeight); + this._ctx.clip(); + } + + /** + * Gets the current font. + * @param terminal The terminal. + * @param isBold If we should use the bold fontWeight. + */ + protected _getFont(terminal: Terminal, isBold: boolean, isItalic: boolean): string { + const fontWeight = isBold ? terminal.getOption('fontWeightBold') : terminal.getOption('fontWeight'); + const fontStyle = isItalic ? 'italic' : ''; + + return `${fontStyle} ${fontWeight} ${terminal.getOption('fontSize') * window.devicePixelRatio}px ${terminal.getOption('fontFamily')}`; + } +} + diff --git a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts b/src/renderer/webgl/renderLayer/CursorRenderLayer.ts new file mode 100644 index 00000000..e51bdb7c --- /dev/null +++ b/src/renderer/webgl/renderLayer/CursorRenderLayer.ts @@ -0,0 +1,358 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IRenderDimensions, IColorSet, Terminal } from 'xterm'; +import { BaseRenderLayer } from './BaseRenderLayer'; +import { ICellData } from '../../../core/Types'; +import { CellData } from '../../../core/buffer/BufferLine'; + +interface ICursorState { + x: number; + y: number; + isFocused: boolean; + style: string; + width: number; +} + +/** + * The time between cursor blinks. + */ +const BLINK_INTERVAL = 600; + +export class CursorRenderLayer extends BaseRenderLayer { + private _state: ICursorState; + private _cursorRenderers: {[key: string]: (terminal: Terminal, x: number, y: number, cell: ICellData) => void}; + private _cursorBlinkStateManager: CursorBlinkStateManager; + private _cell: ICellData = new CellData(); + + constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { + super(container, 'cursor', zIndex, true, colors); + this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null + }; + this._cursorRenderers = { + 'bar': this._renderBarCursor.bind(this), + 'block': this._renderBlockCursor.bind(this), + 'underline': this._renderUnderlineCursor.bind(this) + }; + // TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open? + } + + public resize(terminal: Terminal, dim: IRenderDimensions): void { + super.resize(terminal, dim); + // Resizing the canvas discards the contents of the canvas so clear state + this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null + }; + } + + public reset(terminal: Terminal): void { + this._clearCursor(); + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.dispose(); + this._cursorBlinkStateManager = null; + this.onOptionsChanged(terminal); + } + } + + public onBlur(terminal: Terminal): void { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.pause(); + } + terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY); + } + + public onFocus(terminal: Terminal): void { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.resume(terminal); + } else { + terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY); + } + } + + public onOptionsChanged(terminal: Terminal): void { + if (terminal.getOption('cursorBlink')) { + if (!this._cursorBlinkStateManager) { + this._cursorBlinkStateManager = new CursorBlinkStateManager(terminal, () => { + this._render(terminal, true); + }); + } + } else { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.dispose(); + this._cursorBlinkStateManager = null; + } + // Request a refresh from the terminal as management of rendering is being + // moved back to the terminal + terminal.refresh(terminal.buffer.cursorY, terminal.buffer.cursorY); + } + } + + public onCursorMove(terminal: Terminal): void { + if (this._cursorBlinkStateManager) { + this._cursorBlinkStateManager.restartBlinkAnimation(terminal); + } + } + + public onGridChanged(terminal: Terminal, startRow: number, endRow: number): void { + if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) { + this._render(terminal, false); + } else { + this._cursorBlinkStateManager.restartBlinkAnimation(terminal); + } + } + + private _render(terminal: Terminal, triggeredByAnimationFrame: boolean): void { + // Don't draw the cursor if it's hidden + // TODO: Need to expose API for this + if (!(terminal as any)._core.cursorState || (terminal as any)._core.cursorHidden) { + this._clearCursor(); + return; + } + + const cursorY = terminal.buffer.baseY + terminal.buffer.cursorY; + const viewportRelativeCursorY = cursorY - terminal.buffer.viewportY; + + // Don't draw the cursor if it's off-screen + if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= terminal.rows) { + this._clearCursor(); + return; + } + + // TODO: Need fast buffere API for loading cell + (terminal as any)._core.buffer.getLine(cursorY).loadCell(terminal.buffer.cursorX, this._cell); + if (this._cell.content === undefined) { + return; + } + + if (!isTerminalFocused(terminal)) { + this._clearCursor(); + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor.css; + this._renderBlurCursor(terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell); + this._ctx.restore(); + this._state.x = terminal.buffer.cursorX; + this._state.y = viewportRelativeCursorY; + this._state.isFocused = false; + this._state.style = terminal.getOption('cursorStyle'); + this._state.width = this._cell.getWidth(); + return; + } + + // Don't draw the cursor if it's blinking + if (this._cursorBlinkStateManager && !this._cursorBlinkStateManager.isCursorVisible) { + this._clearCursor(); + return; + } + + if (this._state) { + // The cursor is already in the correct spot, don't redraw + if (this._state.x === terminal.buffer.cursorX && + this._state.y === viewportRelativeCursorY && + this._state.isFocused === isTerminalFocused(terminal) && + this._state.style === terminal.getOption('cursorStyle') && + this._state.width === this._cell.getWidth()) { + return; + } + this._clearCursor(); + } + + this._ctx.save(); + this._cursorRenderers[terminal.getOption('cursorStyle') || 'block'](terminal, terminal.buffer.cursorX, viewportRelativeCursorY, this._cell); + this._ctx.restore(); + + this._state.x = terminal.buffer.cursorX; + this._state.y = viewportRelativeCursorY; + this._state.isFocused = false; + this._state.style = terminal.getOption('cursorStyle'); + this._state.width = this._cell.getWidth(); + } + + private _clearCursor(): void { + if (this._state) { + this.clearCells(this._state.x, this._state.y, this._state.width, 1); + this._state = { + x: null, + y: null, + isFocused: null, + style: null, + width: null + }; + } + } + + private _renderBarCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void { + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor.css; + this.fillLeftLineAtCell(x, y); + this._ctx.restore(); + } + + private _renderBlockCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void { + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor.css; + this.fillCells(x, y, cell.getWidth(), 1); + this._ctx.fillStyle = this._colors.cursorAccent.css; + this.fillCharTrueColor(terminal, cell, x, y); + this._ctx.restore(); + } + + private _renderUnderlineCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void { + this._ctx.save(); + this._ctx.fillStyle = this._colors.cursor.css; + this.fillBottomLineAtCells(x, y); + this._ctx.restore(); + } + + private _renderBlurCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void { + this._ctx.save(); + this._ctx.strokeStyle = this._colors.cursor.css; + this.strokeRectAtCell(x, y, cell.getWidth(), 1); + this._ctx.restore(); + } +} + +class CursorBlinkStateManager { + public isCursorVisible: boolean; + + private _animationFrame: number; + private _blinkStartTimeout: number; + private _blinkInterval: number; + + /** + * The time at which the animation frame was restarted, this is used on the + * next render to restart the timers so they don't need to restart the timers + * multiple times over a short period. + */ + private _animationTimeRestarted: number; + + constructor( + terminal: Terminal, + private _renderCallback: () => void + ) { + this.isCursorVisible = true; + if (isTerminalFocused(terminal)) { + this._restartInterval(); + } + } + + public get isPaused(): boolean { return !(this._blinkStartTimeout || this._blinkInterval); } + + public dispose(): void { + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + this._blinkInterval = null; + } + if (this._blinkStartTimeout) { + window.clearTimeout(this._blinkStartTimeout); + this._blinkStartTimeout = null; + } + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = null; + } + } + + public restartBlinkAnimation(terminal: Terminal): void { + if (this.isPaused) { + return; + } + // Save a timestamp so that the restart can be done on the next interval + this._animationTimeRestarted = Date.now(); + // Force a cursor render to ensure it's visible and in the correct position + this.isCursorVisible = true; + if (!this._animationFrame) { + this._animationFrame = window.requestAnimationFrame(() => { + this._renderCallback(); + this._animationFrame = null; + }); + } + } + + private _restartInterval(timeToStart: number = BLINK_INTERVAL): void { + // Clear any existing interval + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + } + + // Setup the initial timeout which will hide the cursor, this is done before + // the regular interval is setup in order to support restarting the blink + // animation in a lightweight way (without thrashing clearInterval and + // setInterval). + this._blinkStartTimeout = setTimeout(() => { + // Check if another animation restart was requested while this was being + // started + if (this._animationTimeRestarted) { + const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted); + this._animationTimeRestarted = null; + if (time > 0) { + this._restartInterval(time); + return; + } + } + + // Hide the cursor + this.isCursorVisible = false; + this._animationFrame = window.requestAnimationFrame(() => { + this._renderCallback(); + this._animationFrame = null; + }); + + // Setup the blink interval + this._blinkInterval = setInterval(() => { + // Adjust the animation time if it was restarted + if (this._animationTimeRestarted) { + // calc time diff + // Make restart interval do a setTimeout initially? + const time = BLINK_INTERVAL - (Date.now() - this._animationTimeRestarted); + this._animationTimeRestarted = null; + this._restartInterval(time); + return; + } + + // Invert visibility and render + this.isCursorVisible = !this.isCursorVisible; + this._animationFrame = window.requestAnimationFrame(() => { + this._renderCallback(); + this._animationFrame = null; + }); + }, BLINK_INTERVAL); + }, timeToStart); + } + + public pause(): void { + this.isCursorVisible = true; + if (this._blinkInterval) { + window.clearInterval(this._blinkInterval); + this._blinkInterval = null; + } + if (this._blinkStartTimeout) { + window.clearTimeout(this._blinkStartTimeout); + this._blinkStartTimeout = null; + } + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = null; + } + } + + public resume(terminal: Terminal): void { + this._animationTimeRestarted = null; + this._restartInterval(); + this.restartBlinkAnimation(terminal); + } +} + +function isTerminalFocused(terminal: Terminal): boolean { + return document.activeElement === terminal.textarea && document.hasFocus(); +} diff --git a/src/renderer/webgl/renderLayer/LinkRenderLayer.ts b/src/renderer/webgl/renderLayer/LinkRenderLayer.ts new file mode 100644 index 00000000..eb47f7e5 --- /dev/null +++ b/src/renderer/webgl/renderLayer/LinkRenderLayer.ts @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ILinkifierEvent, ILinkifierAccessor } from '../../../Types'; +import { IRenderDimensions, IColorSet, Terminal } from 'xterm'; +import { BaseRenderLayer } from './BaseRenderLayer'; +import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; +import { is256Color } from '../atlas/CharAtlasUtils'; + +export class LinkRenderLayer extends BaseRenderLayer { + private _state: ILinkifierEvent = null; + + constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) { + super(container, 'link', zIndex, true, colors); + // TODO: Need to expose link-related renderer API + terminal.linkifier.onLinkHover(e => this._onLinkHover(e)); + terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e)); + } + + public resize(terminal: Terminal, dim: IRenderDimensions): void { + super.resize(terminal, dim); + // Resizing the canvas discards the contents of the canvas so clear state + this._state = null; + } + + public reset(terminal: Terminal): void { + this._clearCurrentLink(); + } + + private _clearCurrentLink(): void { + if (this._state) { + this.clearCells(this._state.x1, this._state.y1, this._state.cols - this._state.x1, 1); + const middleRowCount = this._state.y2 - this._state.y1 - 1; + if (middleRowCount > 0) { + this.clearCells(0, this._state.y1 + 1, this._state.cols, middleRowCount); + } + this.clearCells(0, this._state.y2, this._state.x2, 1); + this._state = null; + } + } + + private _onLinkHover(e: ILinkifierEvent): void { + if (e.fg === INVERTED_DEFAULT_COLOR) { + this._ctx.fillStyle = this._colors.background.css; + } else if (is256Color(e.fg)) { + // 256 color support + this._ctx.fillStyle = this._colors.ansi[e.fg].css; + } else { + this._ctx.fillStyle = this._colors.foreground.css; + } + + if (e.y1 === e.y2) { + // Single line link + this.fillBottomLineAtCells(e.x1, e.y1, e.x2 - e.x1); + } else { + // Multi-line link + this.fillBottomLineAtCells(e.x1, e.y1, e.cols - e.x1); + for (let y = e.y1 + 1; y < e.y2; y++) { + this.fillBottomLineAtCells(0, y, e.cols); + } + this.fillBottomLineAtCells(0, e.y2, e.x2); + } + this._state = e; + } + + private _onLinkLeave(e: ILinkifierEvent): void { + this._clearCurrentLink(); + } +} diff --git a/src/renderer/webgl/renderLayer/Types.ts b/src/renderer/webgl/renderLayer/Types.ts new file mode 100644 index 00000000..61e69f38 --- /dev/null +++ b/src/renderer/webgl/renderLayer/Types.ts @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { IDisposable, IRenderDimensions, IColorSet, Terminal } from 'xterm'; +import { ICharacterJoiner } from '../../Types'; + +export interface IRenderLayer extends IDisposable { + /** + * Called when the terminal loses focus. + */ + onBlur(terminal: Terminal): void; + + /** + * * Called when the terminal gets focus. + */ + onFocus(terminal: Terminal): void; + + /** + * Called when the cursor is moved. + */ + onCursorMove(terminal: Terminal): void; + + /** + * Called when options change. + */ + onOptionsChanged(terminal: Terminal): void; + + /** + * Called when the theme changes. + */ + setColors(terminal: Terminal, colorSet: IColorSet): void; + + /** + * Called when the data in the grid has changed (or needs to be rendered + * again). + */ + onGridChanged(terminal: Terminal, startRow: number, endRow: number): void; + + /** + * Calls when the selection changes. + */ + onSelectionChanged(terminal: Terminal, start: [number, number], end: [number, number], columnSelectMode: boolean): void; + + /** + * Registers a handler to join characters to render as a group + */ + registerCharacterJoiner?(joiner: ICharacterJoiner): void; + + /** + * Deregisters the specified character joiner handler + */ + deregisterCharacterJoiner?(joinerId: number): void; + + /** + * Resize the render layer. + */ + resize(terminal: Terminal, dim: IRenderDimensions): void; + + /** + * Clear the state of the render layer. + */ + reset(terminal: Terminal): void; +} diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 8712c3d8..a48e8a21 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -894,12 +894,51 @@ declare module 'xterm' { /** * (EXPERIMENTAL) */ - setRenderer(renderer: any): void; + setRenderer(renderer: IRenderer): void; screenElement: HTMLElement; } export namespace Renderer { const DEFAULT_COLOR: number; + const NULL_CELL_CODE: number; + const WHITESPACE_CELL_CODE: number; + const DEFAULT_ATTR: number; + const DEFAULT_ANSI_COLORS: string[]; + const FLAGS: any; + } + + export interface IRenderer extends IDisposable { + readonly dimensions: IRenderDimensions; + + dispose(): void; + setColors(colors: IColorSet): void; + onDevicePixelRatioChange(): void; + onResize(cols: number, rows: number): void; + onCharSizeChanged(): void; + onBlur(): void; + onFocus(): void; + onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void; + onCursorMove(): void; + onOptionsChanged(): void; + clear(): void; + renderRows(start: number, end: number): void; + registerCharacterJoiner(handler: (text: string) => [number, number][]): number; + deregisterCharacterJoiner(joinerId: number): boolean; + } + + 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; } /** From 20dfaae500654cd9fef72ecfcfc846a9d5876b9b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 19 May 2019 00:59:21 -0700 Subject: [PATCH 32/69] Fix runtime error --- src/renderer/webgl/renderLayer/CursorRenderLayer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts b/src/renderer/webgl/renderLayer/CursorRenderLayer.ts index e51bdb7c..161606bd 100644 --- a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/CursorRenderLayer.ts @@ -129,8 +129,8 @@ export class CursorRenderLayer extends BaseRenderLayer { return; } - // TODO: Need fast buffere API for loading cell - (terminal as any)._core.buffer.getLine(cursorY).loadCell(terminal.buffer.cursorX, this._cell); + // TODO: Need fast buffer API for loading cell + (terminal as any)._core.buffer.lines.get(cursorY).loadCell(terminal.buffer.cursorX, this._cell); if (this._cell.content === undefined) { return; } From 510985fb4e6c855d4ea709c1d8ef42333aaa47e7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 19 May 2019 01:49:42 -0700 Subject: [PATCH 33/69] Add a basic test --- src/public/Terminal.api.ts | 9 +++++++++ src/renderer/dom/DomRenderer.ts | 1 + src/renderer/webgl/WebglRenderer.ts | 4 ++-- src/renderer/webgl/renderLayer/Types.ts | 3 +-- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index d8325229..80ba489d 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -475,6 +475,15 @@ describe('API Integration Tests', () => { }); }); }); + + describe('WebGL Renderer', () => { + it('load doesn\'t throw', async function(): Promise { + this.timeout(10000); + await openTerminal({ rendererType: 'dom' }); + await page.waitForSelector('.xterm style'); + await page.evaluate(`window.term.loadWebgl();`); + }); + }); }); async function openTerminal(options: ITerminalOptions = {}): Promise { diff --git a/src/renderer/dom/DomRenderer.ts b/src/renderer/dom/DomRenderer.ts index 2797639d..16f02549 100644 --- a/src/renderer/dom/DomRenderer.ts +++ b/src/renderer/dom/DomRenderer.ts @@ -69,6 +69,7 @@ export class DomRenderer extends Disposable implements IRenderer { actualCellHeight: null }; this._updateDimensions(); + this._injectCss(); this._rowFactory = new DomRendererRowFactory(_terminal.options, document); diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 68f74c7b..b9690433 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -4,7 +4,7 @@ */ import { FLAGS } from '../Types'; -import { CharacterJoinerHandler, ITerminal } from '../../Types'; +import { ITerminal } from '../../Types'; import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; @@ -208,7 +208,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._renderLayers.forEach(l => l.reset(this._terminal)); } - public registerCharacterJoiner(handler: CharacterJoinerHandler): number { + public registerCharacterJoiner(handler: (text: string) => [number, number][]): number { return -1; } diff --git a/src/renderer/webgl/renderLayer/Types.ts b/src/renderer/webgl/renderLayer/Types.ts index 61e69f38..50a76a2c 100644 --- a/src/renderer/webgl/renderLayer/Types.ts +++ b/src/renderer/webgl/renderLayer/Types.ts @@ -4,7 +4,6 @@ */ import { IDisposable, IRenderDimensions, IColorSet, Terminal } from 'xterm'; -import { ICharacterJoiner } from '../../Types'; export interface IRenderLayer extends IDisposable { /** @@ -46,7 +45,7 @@ export interface IRenderLayer extends IDisposable { /** * Registers a handler to join characters to render as a group */ - registerCharacterJoiner?(joiner: ICharacterJoiner): void; + registerCharacterJoiner?(handler: (text: string) => [number, number][]): void; /** * Deregisters the specified character joiner handler From 250683364f87621296850fd4a82fc472ae99583d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 19 May 2019 02:23:07 -0700 Subject: [PATCH 34/69] Use more API in WebglRenderer --- src/renderer/webgl/WebglRenderer.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index b9690433..7cf68464 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -122,7 +122,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // and the terminal needs to refreshed if (this._devicePixelRatio !== window.devicePixelRatio) { this._devicePixelRatio = window.devicePixelRatio; - this.onResize(this._core.cols, this._core.rows); + this.onResize(this._terminal.cols, this._terminal.rows); } } @@ -130,7 +130,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Update character and canvas dimensions this._updateDimensions(devicePixelRatio); - this._model.resize(this._core.cols, this._core.rows); + this._model.resize(this._terminal.cols, this._terminal.rows); this._rectangleRenderer.onResize(); // Resize all render layers @@ -172,7 +172,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._glyphRenderer.updateSelection(this._model, columnSelectMode); // TODO: #2102 Should this move to RenderCoordinator? - this._core.refresh(0, this._core.rows - 1); + this._core.refresh(0, this._terminal.rows - 1); } public onCursorMove(): void { @@ -293,7 +293,7 @@ export class WebglRenderer extends Disposable implements IRenderer { } private _updateSelectionModel(start: [number, number], end: [number, number]): void { - const terminal = this._core; + const terminal = this._terminal; // Selection does not exist if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { @@ -302,8 +302,8 @@ export class WebglRenderer extends Disposable implements IRenderer { } // Translate from buffer position to viewport position - const viewportStartRow = start[1] - terminal.buffer.ydisp; - const viewportEndRow = end[1] - terminal.buffer.ydisp; + const viewportStartRow = start[1] - terminal.buffer.viewportY; + const viewportEndRow = end[1] - terminal.buffer.viewportY; const viewportCappedStartRow = Math.max(viewportStartRow, 0); const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); @@ -349,23 +349,23 @@ export class WebglRenderer extends Disposable implements IRenderer { // will be floored because since lineHeight can never be lower then 1, there // is a guarentee that the scaled line height will always be larger than // scaled char height. - this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._core.options.lineHeight); + this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.getOption('lineHeight')); // Calculate the y coordinate within a cell that text should draw from in // order to draw in the center of a cell. - this.dimensions.scaledCharTop = this._core.options.lineHeight === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); + this.dimensions.scaledCharTop = this._terminal.getOption('lineHeight') === 1 ? 0 : Math.round((this.dimensions.scaledCellHeight - this.dimensions.scaledCharHeight) / 2); // Calculate the scaled cell width, taking the letterSpacing into account. - this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._core.options.letterSpacing); + this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.getOption('letterSpacing')); // Calculate the x coordinate with a cell that text should draw from in // order to draw in the center of a cell. - this.dimensions.scaledCharLeft = Math.floor(this._core.options.letterSpacing / 2); + this.dimensions.scaledCharLeft = Math.floor(this._terminal.getOption('letterSpacing') / 2); // Recalculate the canvas dimensions; scaled* define the actual number of // pixel in the canvas - this.dimensions.scaledCanvasHeight = this._core.rows * this.dimensions.scaledCellHeight; - this.dimensions.scaledCanvasWidth = this._core.cols * this.dimensions.scaledCellWidth; + this.dimensions.scaledCanvasHeight = this._terminal.rows * this.dimensions.scaledCellHeight; + this.dimensions.scaledCanvasWidth = this._terminal.cols * this.dimensions.scaledCellWidth; // The the size of the canvas on the page. It's very important that this // rounds to nearest integer and not ceils as browsers often set From 4beea9b7048db193cc1d496e515c9b16578b162b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 19 May 2019 15:44:13 -0700 Subject: [PATCH 35/69] Add tests for webgl bg colors --- src/public/Terminal.api.ts | 9 -- src/public/Terminal.ts | 4 +- src/renderer/webgl/WebglRenderer.api.ts | 136 +++++++++++++++++++++++ src/renderer/webgl/WebglRenderer.ts | 10 +- src/renderer/webgl/WebglRendererAddon.ts | 6 +- 5 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 src/renderer/webgl/WebglRenderer.api.ts diff --git a/src/public/Terminal.api.ts b/src/public/Terminal.api.ts index 80ba489d..d8325229 100644 --- a/src/public/Terminal.api.ts +++ b/src/public/Terminal.api.ts @@ -475,15 +475,6 @@ describe('API Integration Tests', () => { }); }); }); - - describe('WebGL Renderer', () => { - it('load doesn\'t throw', async function(): Promise { - this.timeout(10000); - await openTerminal({ rendererType: 'dom' }); - await page.waitForSelector('.xterm style'); - await page.evaluate(`window.term.loadWebgl();`); - }); - }); }); async function openTerminal(options: ITerminalOptions = {}): Promise { diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 3af5162c..ace272d8 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -188,8 +188,8 @@ export class Terminal implements ITerminalApi { public setRenderer(renderer: any): void { this._core.setRenderer(renderer); } - public loadWebgl(): void { - this.loadAddon(new WebglRendererAddon()); + public loadWebgl(preserveDrawingBuffer?: boolean): void { + this.loadAddon(new WebglRendererAddon(preserveDrawingBuffer)); } public static get strings(): ILocalizableStrings { return Strings; diff --git a/src/renderer/webgl/WebglRenderer.api.ts b/src/renderer/webgl/WebglRenderer.api.ts new file mode 100644 index 00000000..0eb111e5 --- /dev/null +++ b/src/renderer/webgl/WebglRenderer.api.ts @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import * as puppeteer from 'puppeteer'; +import { assert } from 'chai'; +import { ITerminalOptions } from '../../Types'; + +const APP = 'http://127.0.0.1:3000/test'; + +let browser: puppeteer.Browser; +let page: puppeteer.Page; +const width = 800; +const height = 600; + +describe('WebGL Renderer Integration Tests', () => { + before(async function(): Promise { + this.timeout(10000); + browser = await puppeteer.launch({ + headless: process.argv.indexOf('--headless') !== -1, + slowMo: 80, + args: [`--window-size=${width},${height}`] + }); + page = (await browser.pages())[0]; + await page.setViewport({ width, height }); + }); + + after(() => { + browser.close(); + }); + + beforeEach(async () => { + await page.goto(APP); + }); + + describe('WebGL Renderer', () => { + it.only('background colors normal', async function(): Promise { + this.timeout(10000); + await openTerminal({ + rendererType: 'dom', + theme: { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + } + }); + // await writeSync(`\\x1b[41m${' '.repeat(5 * 5)}`); + await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); + await page.evaluate(`window.term.loadWebgl(true);`); + assert.deepEqual(await getCellBgColor(1, 1), [1, 2, 3, 255]); + assert.deepEqual(await getCellBgColor(2, 1), [4, 5, 6, 255]); + assert.deepEqual(await getCellBgColor(3, 1), [7, 8, 9, 255]); + assert.deepEqual(await getCellBgColor(4, 1), [10, 11, 12, 255]); + assert.deepEqual(await getCellBgColor(5, 1), [13, 14, 15, 255]); + assert.deepEqual(await getCellBgColor(6, 1), [16, 17, 18, 255]); + assert.deepEqual(await getCellBgColor(7, 1), [19, 20, 21, 255]); + assert.deepEqual(await getCellBgColor(8, 1), [22, 23, 24, 255]); + }); + + it.only('background colors bright', async function(): Promise { + this.timeout(10000); + await openTerminal({ + rendererType: 'dom', + theme: { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + } + }); + // await writeSync(`\\x1b[41m${' '.repeat(5 * 5)}`); + await writeSync(`\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `); + await page.evaluate(`window.term.loadWebgl(true);`); + assert.deepEqual(await getCellBgColor(1, 1), [1, 2, 3, 255]); + assert.deepEqual(await getCellBgColor(2, 1), [4, 5, 6, 255]); + assert.deepEqual(await getCellBgColor(3, 1), [7, 8, 9, 255]); + assert.deepEqual(await getCellBgColor(4, 1), [10, 11, 12, 255]); + assert.deepEqual(await getCellBgColor(5, 1), [13, 14, 15, 255]); + assert.deepEqual(await getCellBgColor(6, 1), [16, 17, 18, 255]); + assert.deepEqual(await getCellBgColor(7, 1), [19, 20, 21, 255]); + assert.deepEqual(await getCellBgColor(8, 1), [22, 23, 24, 255]); + }); + }); +}); + +async function openTerminal(options: ITerminalOptions = {}): Promise { + await page.evaluate(`window.term = new Terminal(${JSON.stringify(options)})`); + await page.evaluate(`window.term.open(document.querySelector('#terminal-container'))`); + if (options.rendererType === 'dom') { + await page.waitForSelector('.xterm-rows'); + } else { + await page.waitForSelector('.xterm-text-layer'); + } +} + +async function writeSync(data: string): Promise { + await page.evaluate(`window.term.write('${data}');`); + while (true) { + if (await page.evaluate(`window.term._core.writeBuffer.length === 0`)) { + break; + } + } +} + +// async function getPixelAt(x: number, y: number): Promise { +// await page.evaluate(` +// window.gl = window.term._core._renderCoordinator._renderer._gl; +// window.result = new Uint8Array(4); +// window.gl.readPixels(${x}, window.gl.drawingBufferHeight - 1 - ${y}, 1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result); +// `); +// return await page.evaluate(`Array.from(window.result)`); +// } + +async function getCellBgColor(col: number, row: number): Promise { + await page.evaluate(` + window.gl = window.term._core._renderCoordinator._renderer._gl; + window.result = new Uint8Array(4); + window.d = window.term._core._renderCoordinator.dimensions; + window.gl.readPixels( + Math.floor(${col - 1} * window.d.scaledCellWidth), + Math.floor(window.gl.drawingBufferHeight - 1 - ${row - 1} * window.d.scaledCellHeight), + 1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result + ); + `); + return await page.evaluate(`Array.from(window.result)`); +} diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 7cf68464..1ef53cfa 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -41,7 +41,8 @@ export class WebglRenderer extends Disposable implements IRenderer { constructor( private _terminal: Terminal, - private _colors: IColorSet + private _colors: IColorSet, + preserveDrawingBuffer?: boolean ) { super(); @@ -71,7 +72,12 @@ export class WebglRenderer extends Disposable implements IRenderer { this._updateDimensions(); this._canvas = document.createElement('canvas'); - const contextAttributes = { antialias: false, depth: false }; + + const contextAttributes = { + antialias: false, + depth: false, + preserveDrawingBuffer + }; this._gl = this._canvas.getContext('webgl2', contextAttributes) as IWebGL2RenderingContext; if (!this._gl) { throw new Error('WebGL2 not supported'); diff --git a/src/renderer/webgl/WebglRendererAddon.ts b/src/renderer/webgl/WebglRendererAddon.ts index 6c835ba8..6ce27b0a 100644 --- a/src/renderer/webgl/WebglRendererAddon.ts +++ b/src/renderer/webgl/WebglRendererAddon.ts @@ -9,14 +9,16 @@ import { WebglRenderer } from './WebglRenderer'; export class WebglRendererAddon implements ITerminalAddon { private _terminal: Terminal | undefined; - constructor() {} + constructor( + private _preserveDrawingBuffer?: boolean + ) {} public activate(terminal: Terminal): void { if (!terminal.element) { throw new Error('Cannot activate WebglRendererAddon before Terminal.open'); } this._terminal = terminal; - this._terminal.setRenderer(new WebglRenderer(terminal, (terminal as any)._core._colorManager.colors)); + this._terminal.setRenderer(new WebglRenderer(terminal, (terminal as any)._core._colorManager.colors, this._preserveDrawingBuffer)); } public dispose(): void { From b79d73d7c0e39f6d148e09bcfee6624b9cf3cbd4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 19 May 2019 15:54:49 -0700 Subject: [PATCH 36/69] Add foreground tests --- src/renderer/webgl/WebglRenderer.api.ts | 100 ++++++++++++++++++------ 1 file changed, 76 insertions(+), 24 deletions(-) diff --git a/src/renderer/webgl/WebglRenderer.api.ts b/src/renderer/webgl/WebglRenderer.api.ts index 0eb111e5..d2dc06f0 100644 --- a/src/renderer/webgl/WebglRenderer.api.ts +++ b/src/renderer/webgl/WebglRenderer.api.ts @@ -35,7 +35,7 @@ describe('WebGL Renderer Integration Tests', () => { }); describe('WebGL Renderer', () => { - it.only('background colors normal', async function(): Promise { + it('foreground colors normal', async function(): Promise { this.timeout(10000); await openTerminal({ rendererType: 'dom', @@ -50,20 +50,73 @@ describe('WebGL Renderer Integration Tests', () => { white: '#161718' } }); - // await writeSync(`\\x1b[41m${' '.repeat(5 * 5)}`); - await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); + await writeSync(`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█`); await page.evaluate(`window.term.loadWebgl(true);`); - assert.deepEqual(await getCellBgColor(1, 1), [1, 2, 3, 255]); - assert.deepEqual(await getCellBgColor(2, 1), [4, 5, 6, 255]); - assert.deepEqual(await getCellBgColor(3, 1), [7, 8, 9, 255]); - assert.deepEqual(await getCellBgColor(4, 1), [10, 11, 12, 255]); - assert.deepEqual(await getCellBgColor(5, 1), [13, 14, 15, 255]); - assert.deepEqual(await getCellBgColor(6, 1), [16, 17, 18, 255]); - assert.deepEqual(await getCellBgColor(7, 1), [19, 20, 21, 255]); - assert.deepEqual(await getCellBgColor(8, 1), [22, 23, 24, 255]); + assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); + assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); + assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); + assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]); + assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]); + assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]); + assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]); + assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); }); - it.only('background colors bright', async function(): Promise { + it('foreground colors bright', async function(): Promise { + this.timeout(10000); + await openTerminal({ + rendererType: 'dom', + theme: { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + } + }); + await writeSync(`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`); + await page.evaluate(`window.term.loadWebgl(true);`); + assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); + assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); + assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); + assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]); + assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]); + assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]); + assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]); + assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); + }); + + it('background colors normal', async function(): Promise { + this.timeout(10000); + await openTerminal({ + rendererType: 'dom', + theme: { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + } + }); + await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); + await page.evaluate(`window.term.loadWebgl(true);`); + assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); + assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); + assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); + assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]); + assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]); + assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]); + assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]); + assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); + }); + + it('background colors bright', async function(): Promise { this.timeout(10000); await openTerminal({ rendererType: 'dom', @@ -78,17 +131,16 @@ describe('WebGL Renderer Integration Tests', () => { brightWhite: '#161718' } }); - // await writeSync(`\\x1b[41m${' '.repeat(5 * 5)}`); await writeSync(`\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `); await page.evaluate(`window.term.loadWebgl(true);`); - assert.deepEqual(await getCellBgColor(1, 1), [1, 2, 3, 255]); - assert.deepEqual(await getCellBgColor(2, 1), [4, 5, 6, 255]); - assert.deepEqual(await getCellBgColor(3, 1), [7, 8, 9, 255]); - assert.deepEqual(await getCellBgColor(4, 1), [10, 11, 12, 255]); - assert.deepEqual(await getCellBgColor(5, 1), [13, 14, 15, 255]); - assert.deepEqual(await getCellBgColor(6, 1), [16, 17, 18, 255]); - assert.deepEqual(await getCellBgColor(7, 1), [19, 20, 21, 255]); - assert.deepEqual(await getCellBgColor(8, 1), [22, 23, 24, 255]); + assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); + assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); + assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); + assert.deepEqual(await getCellColor(4, 1), [10, 11, 12, 255]); + assert.deepEqual(await getCellColor(5, 1), [13, 14, 15, 255]); + assert.deepEqual(await getCellColor(6, 1), [16, 17, 18, 255]); + assert.deepEqual(await getCellColor(7, 1), [19, 20, 21, 255]); + assert.deepEqual(await getCellColor(8, 1), [22, 23, 24, 255]); }); }); }); @@ -121,14 +173,14 @@ async function writeSync(data: string): Promise { // return await page.evaluate(`Array.from(window.result)`); // } -async function getCellBgColor(col: number, row: number): Promise { +async function getCellColor(col: number, row: number): Promise { await page.evaluate(` window.gl = window.term._core._renderCoordinator._renderer._gl; window.result = new Uint8Array(4); window.d = window.term._core._renderCoordinator.dimensions; window.gl.readPixels( - Math.floor(${col - 1} * window.d.scaledCellWidth), - Math.floor(window.gl.drawingBufferHeight - 1 - ${row - 1} * window.d.scaledCellHeight), + Math.floor((${col - 0.5}) * window.d.scaledCellWidth), + Math.floor(window.gl.drawingBufferHeight - 1 - (${row - 0.5}) * window.d.scaledCellHeight), 1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result ); `); From 493e8ad758ecb1f119a8bef3a5f8ad64c6273f1b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 19 May 2019 18:06:25 -0700 Subject: [PATCH 37/69] Remove webgl dependence on DEFAULT_ANSI_COLORS --- src/renderer/webgl/atlas/CharAtlasUtils.ts | 2 +- src/renderer/webgl/atlas/WebglCharAtlas.ts | 7 +++---- src/ui/ColorManager.ts | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/renderer/webgl/atlas/CharAtlasUtils.ts b/src/renderer/webgl/atlas/CharAtlasUtils.ts index 2bda420f..9bbd614a 100644 --- a/src/renderer/webgl/atlas/CharAtlasUtils.ts +++ b/src/renderer/webgl/atlas/CharAtlasUtils.ts @@ -17,7 +17,7 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number selection: null, // For the static char atlas, we only use the first 16 colors, but we need all 256 for the // dynamic character atlas. - ansi: colors.ansi.slice(0, 16) + ansi: colors.ansi.slice() }; return { devicePixelRatio: window.devicePixelRatio, diff --git a/src/renderer/webgl/atlas/WebglCharAtlas.ts b/src/renderer/webgl/atlas/WebglCharAtlas.ts index a9cd8e7d..85496aa1 100644 --- a/src/renderer/webgl/atlas/WebglCharAtlas.ts +++ b/src/renderer/webgl/atlas/WebglCharAtlas.ts @@ -11,7 +11,6 @@ import { is256Color } from './CharAtlasUtils'; import { clearColor } from './CharAtlasGenerator'; import { DEFAULT_ATTR } from '../../../core/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../../common/Types'; -import { DEFAULT_ANSI_COLORS } from '../../../ui/ColorManager'; import { IColor } from 'xterm'; // In practice we're probably never going to exhaust a texture this large. For debugging purposes, @@ -158,10 +157,10 @@ export default class WebglCharAtlas extends BaseCharAtlas { } private _getColorFromAnsiIndex(idx: number): IColor { - if (idx < this._config.colors.ansi.length) { - return this._config.colors.ansi[idx]; + if (idx >= this._config.colors.ansi.length) { + throw new Error('No color found for idx ' + idx); } - return DEFAULT_ANSI_COLORS[idx]; + return this._config.colors.ansi[idx]; } private _getBackgroundColor(bg: number): IColor { diff --git a/src/ui/ColorManager.ts b/src/ui/ColorManager.ts index 923b2823..276b6e14 100644 --- a/src/ui/ColorManager.ts +++ b/src/ui/ColorManager.ts @@ -16,7 +16,7 @@ const DEFAULT_SELECTION = { // An IIFE to generate DEFAULT_ANSI_COLORS. Do not mutate DEFAULT_ANSI_COLORS, instead make a copy // and mutate that. -export const DEFAULT_ANSI_COLORS = (() => { +export const DEFAULT_ANSI_COLORS: IColor[] = (() => { const colors = [ // dark: fromHex('#2e3436'), From 5b86f9291e9303d7981a4a475eaff7ca2a31dd2c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 31 May 2019 18:10:46 -0700 Subject: [PATCH 38/69] Fix conflicts in webgl renderer --- demo/client.ts | 6 +----- src/renderer/webgl/GlyphRenderer.ts | 4 ++-- src/renderer/webgl/atlas/WebglCharAtlas.ts | 18 +++++++++--------- .../webgl/renderLayer/BaseRenderLayer.ts | 4 ++-- 4 files changed, 14 insertions(+), 18 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index ede79334..10d27c86 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -218,7 +218,7 @@ function initOptions(term: TerminalType): void { fontFamily: null, fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], - rendererType: ['dom', 'canvas', 'webgl'] + rendererType: ['dom', 'canvas'] }; const options = Object.keys((term)._core.options); const booleanOptions = []; @@ -286,10 +286,6 @@ function initOptions(term: TerminalType): void { const input = document.getElementById(`opt-${o}`); addDomListener(input, 'change', () => { console.log('change', o, input.value); - if (o === 'rendererType' && input.value === 'webgl') { - term.setOption('experimentalCharAtlas', 'webgl'); - setTimeout(() => (document.getElementById(`opt-experimentalCharAtlas`)).value = 'webgl', 0); - } term.setOption(o, input.value); }); }); diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 545a666e..4a7c44e0 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -182,9 +182,9 @@ export class GlyphRenderer { let rasterizedGlyph: IRasterizedGlyph; if (chars && chars.length > 1) { - rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg, this._terminal.getOption('enableBold')); + rasterizedGlyph = this._atlas.getRasterizedGlyphCombinedChar(chars, attr, bg, fg); } else { - rasterizedGlyph = this._atlas.getRasterizedGlyph(code, attr, bg, fg, this._terminal.getOption('enableBold')); + rasterizedGlyph = this._atlas.getRasterizedGlyph(code, attr, bg, fg); } // Fill empty if no glyph was found diff --git a/src/renderer/webgl/atlas/WebglCharAtlas.ts b/src/renderer/webgl/atlas/WebglCharAtlas.ts index 85496aa1..6832d4dc 100644 --- a/src/renderer/webgl/atlas/WebglCharAtlas.ts +++ b/src/renderer/webgl/atlas/WebglCharAtlas.ts @@ -96,7 +96,7 @@ export default class WebglCharAtlas extends BaseCharAtlas { protected _doWarmUp(): void { // Pre-fill with ASCII 33-126 for (let i = 33; i < 126; i++) { - const rasterizedGlyph = this._drawToCache(i, DEFAULT_ATTR, DEFAULT_COLOR, DEFAULT_COLOR, true); + const rasterizedGlyph = this._drawToCache(i, DEFAULT_ATTR, DEFAULT_COLOR, DEFAULT_COLOR); this._cacheMap[i] = { [DEFAULT_ATTR]: rasterizedGlyph }; @@ -116,7 +116,7 @@ export default class WebglCharAtlas extends BaseCharAtlas { return false; } - public getRasterizedGlyphCombinedChar(chars: string, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph { + public getRasterizedGlyphCombinedChar(chars: string, attr: number, bg: number, fg: number): IRasterizedGlyph { let rasterizedGlyphSet = this._cacheMapCombined[chars]; if (!rasterizedGlyphSet) { rasterizedGlyphSet = {}; @@ -124,7 +124,7 @@ export default class WebglCharAtlas extends BaseCharAtlas { } let rasterizedGlyph = rasterizedGlyphSet[attr]; if (!rasterizedGlyph) { - rasterizedGlyph = this._drawToCache(chars, attr, bg, fg, enableBold); + rasterizedGlyph = this._drawToCache(chars, attr, bg, fg); rasterizedGlyphSet[attr] = rasterizedGlyph; } return rasterizedGlyph; @@ -133,7 +133,7 @@ export default class WebglCharAtlas extends BaseCharAtlas { /** * Gets the glyphs texture coords, drawing the texture if it's not already */ - public getRasterizedGlyph(code: number, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph { + public getRasterizedGlyph(code: number, attr: number, bg: number, fg: number): IRasterizedGlyph { let rasterizedGlyphSet = this._cacheMap[code]; if (!rasterizedGlyphSet) { rasterizedGlyphSet = {}; @@ -141,7 +141,7 @@ export default class WebglCharAtlas extends BaseCharAtlas { } let rasterizedGlyph = rasterizedGlyphSet[attr]; if (!rasterizedGlyph) { - rasterizedGlyph = this._drawToCache(code, attr, bg, fg, enableBold); + rasterizedGlyph = this._drawToCache(code, attr, bg, fg); rasterizedGlyphSet[attr] = rasterizedGlyph; } return rasterizedGlyph; @@ -186,16 +186,16 @@ export default class WebglCharAtlas extends BaseCharAtlas { return this._config.colors.foreground; } - private _drawToCache(code: number, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph; - private _drawToCache(chars: string, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph; - private _drawToCache(codeOrChars: number | string, attr: number, bg: number, fg: number, enableBold: boolean): IRasterizedGlyph { + private _drawToCache(code: number, attr: number, bg: number, fg: number): IRasterizedGlyph; + private _drawToCache(chars: string, attr: number, bg: number, fg: number): IRasterizedGlyph; + private _drawToCache(codeOrChars: number | string, attr: number, bg: number, fg: number): IRasterizedGlyph { const chars = typeof codeOrChars === 'number' ? String.fromCharCode(codeOrChars) : codeOrChars; this.hasCanvasChanged = true; const flags = attr >> 18; - const bold = !!(flags & FLAGS.BOLD) && enableBold; + const bold = !!(flags & FLAGS.BOLD); const dim = !!(flags & FLAGS.DIM); const italic = !!(flags & FLAGS.ITALIC); diff --git a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts index 36c5bd53..6d20c614 100644 --- a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts @@ -287,7 +287,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._currentGlyphIdentifier.code = cell.getCode() || WHITESPACE_CELL_CODE; this._currentGlyphIdentifier.bg = bg; this._currentGlyphIdentifier.fg = fg; - this._currentGlyphIdentifier.bold = cell.isBold() && terminal.getOption('enableBold'); + this._currentGlyphIdentifier.bold = !!cell.isBold(); this._currentGlyphIdentifier.dim = !!cell.isDim(); this._currentGlyphIdentifier.italic = !!cell.isItalic(); const atlasDidDraw = this._charAtlas && this._charAtlas.draw( @@ -315,7 +315,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ private _drawUncachedChars(terminal: Terminal, cell: ICellData, x: number, y: number): void { this._ctx.save(); - this._ctx.font = this._getFont(terminal, cell.isBold() && terminal.getOption('enableBold'), !!cell.isItalic()); + this._ctx.font = this._getFont(terminal, !!cell.isBold(), !!cell.isItalic()); this._ctx.textBaseline = 'middle'; if (cell.isInverse()) { From ee81f50f3a0320a8fb57da6fa13c8d0bbd2a5063 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 12 Jun 2019 18:25:05 -0700 Subject: [PATCH 39/69] Fix conflicts --- src/renderer/webgl/GlyphRenderer.ts | 2 +- src/renderer/webgl/WebglRenderer.ts | 10 ++++++---- src/renderer/webgl/atlas/WebglCharAtlas.ts | 2 +- src/renderer/webgl/renderLayer/BaseRenderLayer.ts | 4 ++-- src/renderer/webgl/renderLayer/CursorRenderLayer.ts | 4 ++-- 5 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 4a7c44e0..56beb364 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -9,7 +9,7 @@ import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRaster import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { fill, slice } from './TypedArray'; -import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from '../../core/buffer/BufferLine'; +import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from '../../common/buffer/BufferLine'; import { getLuminance } from './ColorUtils'; import { IColorSet, Terminal, IBufferLine, IRenderDimensions } from 'xterm'; diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 1ef53cfa..c6819f24 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -15,7 +15,7 @@ import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from './Lifecycle'; -import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from '../../core/buffer/BufferLine'; +import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from 'common/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../common/Types'; import { IColorSet, Terminal, IRenderDimensions, IRenderer } from 'xterm'; import { getLuminance } from './ColorUtils'; @@ -332,8 +332,10 @@ export class WebglRenderer extends Disposable implements IRenderer { * Recalculates the character and canvas dimensions. */ private _updateDimensions(devicePixelRatio: number = window.devicePixelRatio): void { + // TODO: Acquire CharSizeService properly + // Perform a new measure if the CharMeasure dimensions are not yet available - if (!this._core.charMeasure.width || !this._core.charMeasure.height) { + if (!(this._core)._charSizeService.width || !(this._core)._charSizeService.height) { return; } @@ -344,12 +346,12 @@ export class WebglRenderer extends Disposable implements IRenderer { // NOTE: ceil fixes sometime, floor does others :s - this.dimensions.scaledCharWidth = Math.floor(this._core.charMeasure.width * devicePixelRatio); + this.dimensions.scaledCharWidth = Math.floor((this._core)._charSizeService.width * devicePixelRatio); // Calculate the scaled character height. Height is ceiled in case // devicePixelRatio is a floating point number in order to ensure there is // enough space to draw the character to the cell. - this.dimensions.scaledCharHeight = Math.ceil(this._core.charMeasure.height * devicePixelRatio); + this.dimensions.scaledCharHeight = Math.ceil((this._core)._charSizeService.height * devicePixelRatio); // Calculate the scaled cell height, if lineHeight is not 1 then the value // will be floored because since lineHeight can never be lower then 1, there diff --git a/src/renderer/webgl/atlas/WebglCharAtlas.ts b/src/renderer/webgl/atlas/WebglCharAtlas.ts index 6832d4dc..9c6f9e9b 100644 --- a/src/renderer/webgl/atlas/WebglCharAtlas.ts +++ b/src/renderer/webgl/atlas/WebglCharAtlas.ts @@ -9,7 +9,7 @@ import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { FLAGS } from '../../Types'; import { is256Color } from './CharAtlasUtils'; import { clearColor } from './CharAtlasGenerator'; -import { DEFAULT_ATTR } from '../../../core/buffer/BufferLine'; +import { DEFAULT_ATTR } from 'common/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../../common/Types'; import { IColor } from 'xterm'; diff --git a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts index 6d20c614..fa6cd95f 100644 --- a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts @@ -4,12 +4,12 @@ */ import { IRenderLayer } from './Types'; -import { ICellData } from '../../../core/Types'; +import { ICellData } from '../../../common/Types'; import { DEFAULT_COLOR } from '../../../common/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from '../atlas/Types'; import BaseCharAtlas from '../atlas/BaseCharAtlas'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; -import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../../../core/buffer/BufferLine'; +import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../../../common/buffer/BufferLine'; import { IColorSet, IRenderDimensions, Terminal } from 'xterm'; export abstract class BaseRenderLayer implements IRenderLayer { diff --git a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts b/src/renderer/webgl/renderLayer/CursorRenderLayer.ts index 161606bd..425cbef3 100644 --- a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/CursorRenderLayer.ts @@ -5,8 +5,8 @@ import { IRenderDimensions, IColorSet, Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { ICellData } from '../../../core/Types'; -import { CellData } from '../../../core/buffer/BufferLine'; +import { ICellData } from '../../../common/Types'; +import { CellData } from '../../../common/buffer/BufferLine'; interface ICursorState { x: number; From a5e466869c1eb5f151f7628e519349e9ad9e2064 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 13 Jun 2019 09:51:22 -0700 Subject: [PATCH 40/69] Fix lint --- src/renderer/webgl/renderLayer/BaseRenderLayer.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts index fa6cd95f..2abe3633 100644 --- a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts @@ -4,8 +4,7 @@ */ import { IRenderLayer } from './Types'; -import { ICellData } from '../../../common/Types'; -import { DEFAULT_COLOR } from '../../../common/Types'; +import { ICellData, DEFAULT_COLOR } from '../../../common/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from '../atlas/Types'; import BaseCharAtlas from '../atlas/BaseCharAtlas'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; From a19ea1ec5242382ec693cf0a0c8b82f365dec86b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 15:46:07 -0700 Subject: [PATCH 41/69] Remove forked LifeCycle and TypedArray files --- src/common/TypedArrayUtils.test.ts | 105 ++++++++++++- src/common/TypedArrayUtils.ts | 26 ++++ src/renderer/webgl/GlyphRenderer.ts | 2 +- src/renderer/webgl/Lifecycle.test.ts | 45 ------ src/renderer/webgl/Lifecycle.ts | 47 ------ src/renderer/webgl/RectangleRenderer.ts | 2 +- src/renderer/webgl/RenderModel.ts | 2 +- src/renderer/webgl/TypedArray.test.ts | 191 ------------------------ src/renderer/webgl/TypedArray.ts | 67 --------- src/renderer/webgl/WebglRenderer.ts | 2 +- 10 files changed, 134 insertions(+), 355 deletions(-) delete mode 100644 src/renderer/webgl/Lifecycle.test.ts delete mode 100644 src/renderer/webgl/Lifecycle.ts delete mode 100644 src/renderer/webgl/TypedArray.test.ts delete mode 100644 src/renderer/webgl/TypedArray.ts diff --git a/src/common/TypedArrayUtils.test.ts b/src/common/TypedArrayUtils.test.ts index 429a3a33..ed3541db 100644 --- a/src/common/TypedArrayUtils.test.ts +++ b/src/common/TypedArrayUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ import { assert } from 'chai'; -import { fillFallback, concat } from 'common/TypedArrayUtils'; +import { fillFallback, concat, sliceFallback } from 'common/TypedArrayUtils'; type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array @@ -85,6 +85,109 @@ describe('polyfill conformance tests', function(): void { } }); }); + + describe('TypedArray.slice', () => { + describe('should work with all typed array types', () => { + it('Uint8Array', () => { + const a = new Uint8Array(5); + deepEquals(sliceFallback(a, 2), a.slice(2)); + deepEquals(sliceFallback(a, 65535), a.slice(65535)); + deepEquals(sliceFallback(a, -1), a.slice(-1)); + }); + it('Uint16Array', () => { + const u161 = new Uint16Array(5); + const u162 = new Uint16Array(5); + deepEquals(sliceFallback(u161, 2), u162.slice(2)); + deepEquals(sliceFallback(u161, 65535), u162.slice(65535)); + deepEquals(sliceFallback(u161, -1), u162.slice(-1)); + }); + it('Uint32Array', () => { + const u321 = new Uint32Array(5); + const u322 = new Uint32Array(5); + deepEquals(sliceFallback(u321, 2), u322.slice(2)); + deepEquals(sliceFallback(u321, 65537), u322.slice(65537)); + deepEquals(sliceFallback(u321, -1), u322.slice(-1)); + }); + it('Int8Array', () => { + const i81 = new Int8Array(5); + const i82 = new Int8Array(5); + deepEquals(sliceFallback(i81, 2), i82.slice(2)); + deepEquals(sliceFallback(i81, 65537), i82.slice(65537)); + deepEquals(sliceFallback(i81, -1), i82.slice(-1)); + }); + it('Int16Array', () => { + const i161 = new Int16Array(5); + const i162 = new Int16Array(5); + deepEquals(sliceFallback(i161, 2), i162.slice(2)); + deepEquals(sliceFallback(i161, 65535), i162.slice(65535)); + deepEquals(sliceFallback(i161, -1), i162.slice(-1)); + }); + it('Int32Array', () => { + const i321 = new Int32Array(5); + const i322 = new Int32Array(5); + deepEquals(sliceFallback(i321, 2), i322.slice(2)); + deepEquals(sliceFallback(i321, 65537), i322.slice(65537)); + deepEquals(sliceFallback(i321, -1), i322.slice(-1)); + }); + it('Float32Array', () => { + const f321 = new Float32Array(5); + const f322 = new Float32Array(5); + deepEquals(sliceFallback(f321, 2), f322.slice(2)); + deepEquals(sliceFallback(f321, 65537), f322.slice(65537)); + deepEquals(sliceFallback(f321, -1), f322.slice(-1)); + }); + it('Float64Array', () => { + const f641 = new Float64Array(5); + const f642 = new Float64Array(5); + deepEquals(sliceFallback(f641, 2), f642.slice(2)); + deepEquals(sliceFallback(f641, 65537), f642.slice(65537)); + deepEquals(sliceFallback(f641, -1), f642.slice(-1)); + }); + it('Uint8ClampedArray', () => { + const u8Clamped1 = new Uint8ClampedArray(5); + const u8Clamped2 = new Uint8ClampedArray(5); + deepEquals(sliceFallback(u8Clamped1, 2), u8Clamped2.slice(2)); + deepEquals(sliceFallback(u8Clamped1, 65537), u8Clamped2.slice(65537)); + deepEquals(sliceFallback(u8Clamped1, -1), u8Clamped2.slice(-1)); + }); + }); + it('start', () => { + const arr = new Uint32Array([1, 2, 3, 4, 5]); + deepEquals(sliceFallback(arr, -1), arr.slice(-1)); + deepEquals(sliceFallback(arr, 0), arr.slice(0)); + deepEquals(sliceFallback(arr, 1), arr.slice(1)); + deepEquals(sliceFallback(arr, 2), arr.slice(2)); + deepEquals(sliceFallback(arr, 3), arr.slice(3)); + deepEquals(sliceFallback(arr, 4), arr.slice(4)); + deepEquals(sliceFallback(arr, 5), arr.slice(5)); + }); + it('end', () => { + const arr = new Uint32Array([1, 2, 3, 4, 5]); + deepEquals(sliceFallback(arr, -1, -2), arr.slice(-1, -2)); + deepEquals(sliceFallback(arr, 0, -2), arr.slice(0, -2)); + deepEquals(sliceFallback(arr, 1, -2), arr.slice(1, -2)); + deepEquals(sliceFallback(arr, 2, -2), arr.slice(2, -2)); + deepEquals(sliceFallback(arr, 3, -2), arr.slice(3, -2)); + deepEquals(sliceFallback(arr, 4, -2), arr.slice(4, -2)); + deepEquals(sliceFallback(arr, 5, -2), arr.slice(5, -2)); + + deepEquals(sliceFallback(arr, -1, 3), arr.slice(-1, 3)); + deepEquals(sliceFallback(arr, 0, 3), arr.slice(0, 3)); + deepEquals(sliceFallback(arr, 1, 3), arr.slice(1, 3)); + deepEquals(sliceFallback(arr, 2, 3), arr.slice(2, 3)); + deepEquals(sliceFallback(arr, 3, 3), arr.slice(3, 3)); + deepEquals(sliceFallback(arr, 4, 3), arr.slice(4, 3)); + deepEquals(sliceFallback(arr, 5, 3), arr.slice(5, 3)); + + deepEquals(sliceFallback(arr, -1, 8), arr.slice(-1, 8)); + deepEquals(sliceFallback(arr, 0, 8), arr.slice(0, 8)); + deepEquals(sliceFallback(arr, 1, 8), arr.slice(1, 8)); + deepEquals(sliceFallback(arr, 2, 8), arr.slice(2, 8)); + deepEquals(sliceFallback(arr, 3, 8), arr.slice(3, 8)); + deepEquals(sliceFallback(arr, 4, 8), arr.slice(4, 8)); + deepEquals(sliceFallback(arr, 5, 8), arr.slice(5, 8)); + }); + }); }); describe('typed array convenience functions', () => { diff --git a/src/common/TypedArrayUtils.ts b/src/common/TypedArrayUtils.ts index 54699835..f2651aa0 100644 --- a/src/common/TypedArrayUtils.ts +++ b/src/common/TypedArrayUtils.ts @@ -40,6 +40,32 @@ export function fillFallback(array: T, value: number, star return array; } +export function slice(array: T, start?: number, end?: number): T { + // all modern engines that support .slice + if (array.slice) { + return array.slice(start, end) as T; + } + return sliceFallback(array, start, end); +} + +export function sliceFallback(array: T, start: number = 0, end: number = array.length): T { + if (start < 0) { + start = (array.length + start) % array.length; + } + if (end >= array.length) { + end = array.length; + } else { + end = (array.length + end) % array.length; + } + start = Math.min(start, end); + + const result: T = new (array.constructor as any)(end - start); + for (let i = 0; i < end - start; ++i) { + result[i] = array[i + start]; + } + return result; +} + /** * Concat two typed arrays `a` and `b`. * Returns a new typed array. diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index 56beb364..fe40c375 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -8,7 +8,7 @@ import WebglCharAtlas from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; -import { fill, slice } from './TypedArray'; +import { fill, slice } from 'common/TypedArrayUtils'; import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from '../../common/buffer/BufferLine'; import { getLuminance } from './ColorUtils'; import { IColorSet, Terminal, IBufferLine, IRenderDimensions } from 'xterm'; diff --git a/src/renderer/webgl/Lifecycle.test.ts b/src/renderer/webgl/Lifecycle.test.ts deleted file mode 100644 index 4b696fa5..00000000 --- a/src/renderer/webgl/Lifecycle.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import { Disposable } from './Lifecycle'; - -class TestDisposable extends Disposable { - public get isDisposed(): boolean { - return this._isDisposed; - } -} - -describe('Disposable', () => { - describe('register', () => { - it('should register disposables', () => { - const d = new TestDisposable(); - const d2 = { - dispose: () => { throw new Error(); } - }; - d.register(d2); - assert.throws(() => d.dispose()); - }); - }); - describe('unregister', () => { - it('should unregister disposables', () => { - const d = new TestDisposable(); - const d2 = { - dispose: () => { throw new Error(); } - }; - d.register(d2); - d.unregister(d2); - assert.doesNotThrow(() => d.dispose()); - }); - }); - describe('dispose', () => { - it('should set is disposed flag', () => { - const d = new TestDisposable(); - assert.isFalse(d.isDisposed); - d.dispose(); - assert.isTrue(d.isDisposed); - }); - }); -}); diff --git a/src/renderer/webgl/Lifecycle.ts b/src/renderer/webgl/Lifecycle.ts deleted file mode 100644 index 209a3e2a..00000000 --- a/src/renderer/webgl/Lifecycle.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { IDisposable } from 'xterm'; - -/** - * A base class that can be extended to provide convenience methods for managing the lifecycle of an - * object and its components. - */ -export abstract class Disposable implements IDisposable { - protected _disposables: IDisposable[] = []; - protected _isDisposed: boolean = false; - - constructor() { - } - - /** - * Disposes the object, triggering the `dispose` method on all registered IDisposables. - */ - public dispose(): void { - this._isDisposed = true; - this._disposables.forEach(d => d.dispose()); - this._disposables.length = 0; - } - - /** - * Registers a disposable object. - * @param d The disposable to register. - */ - public register(d: T): void { - this._disposables.push(d); - } - - /** - * Unregisters a disposable object if it has been registered, if not do - * nothing. - * @param d The disposable to unregister. - */ - public unregister(d: T): void { - const index = this._disposables.indexOf(d); - if (index !== -1) { - this._disposables.splice(index, 1); - } - } -} diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index 3fc8413c..0a1c31d9 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -5,7 +5,7 @@ import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; -import { fill } from './TypedArray'; +import { fill } from 'common/TypedArrayUtils'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { is256Color } from './atlas/CharAtlasUtils'; import { DEFAULT_COLOR } from '../../common/Types'; diff --git a/src/renderer/webgl/RenderModel.ts b/src/renderer/webgl/RenderModel.ts index 71d256d3..a9ee24e9 100644 --- a/src/renderer/webgl/RenderModel.ts +++ b/src/renderer/webgl/RenderModel.ts @@ -4,7 +4,7 @@ */ import { IRenderModel, ISelectionRenderModel } from './Types'; -import { fill } from './TypedArray'; +import { fill } from 'common/TypedArrayUtils'; export const RENDER_MODEL_INDICIES_PER_CELL = 4; diff --git a/src/renderer/webgl/TypedArray.test.ts b/src/renderer/webgl/TypedArray.test.ts deleted file mode 100644 index 25676248..00000000 --- a/src/renderer/webgl/TypedArray.test.ts +++ /dev/null @@ -1,191 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - */ -import { assert } from 'chai'; -import { fillFallback, sliceFallback } from './TypedArray'; - -type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray - | Int8Array | Int16Array | Int32Array - | Float32Array | Float64Array; - -function deepEquals(a: TypedArray, b: TypedArray): void { - assert.equal(a.length, b.length); - for (let i = 0; i < a.length; ++i) { - assert.equal(a[i], b[i]); - } -} - -describe('polyfill conformance tests', function(): void { - describe('TypedArray.fill', function(): void { - it('should work with all typed array types', function(): void { - const u81 = new Uint8Array(5); - const u82 = new Uint8Array(5); - deepEquals(fillFallback(u81, 2), u82.fill(2)); - deepEquals(fillFallback(u81, -1), u82.fill(-1)); - const u161 = new Uint16Array(5); - const u162 = new Uint16Array(5); - deepEquals(fillFallback(u161, 2), u162.fill(2)); - deepEquals(fillFallback(u161, 65535), u162.fill(65535)); - deepEquals(fillFallback(u161, -1), u162.fill(-1)); - const u321 = new Uint32Array(5); - const u322 = new Uint32Array(5); - deepEquals(fillFallback(u321, 2), u322.fill(2)); - deepEquals(fillFallback(u321, 65537), u322.fill(65537)); - deepEquals(fillFallback(u321, -1), u322.fill(-1)); - const i81 = new Int8Array(5); - const i82 = new Int8Array(5); - deepEquals(fillFallback(i81, 2), i82.fill(2)); - deepEquals(fillFallback(i81, -1), i82.fill(-1)); - const i161 = new Int16Array(5); - const i162 = new Int16Array(5); - deepEquals(fillFallback(i161, 2), i162.fill(2)); - deepEquals(fillFallback(i161, 65535), i162.fill(65535)); - deepEquals(fillFallback(i161, -1), i162.fill(-1)); - const i321 = new Int32Array(5); - const i322 = new Int32Array(5); - deepEquals(fillFallback(i321, 2), i322.fill(2)); - deepEquals(fillFallback(i321, 65537), i322.fill(65537)); - deepEquals(fillFallback(i321, -1), i322.fill(-1)); - const f321 = new Float32Array(5); - const f322 = new Float32Array(5); - deepEquals(fillFallback(f321, 1.2345), f322.fill(1.2345)); - const f641 = new Float64Array(5); - const f642 = new Float64Array(5); - deepEquals(fillFallback(f641, 1.2345), f642.fill(1.2345)); - const u8Clamped1 = new Uint8ClampedArray(5); - const u8Clamped2 = new Uint8ClampedArray(5); - deepEquals(fillFallback(u8Clamped1, 2), u8Clamped2.fill(2)); - deepEquals(fillFallback(u8Clamped1, 257), u8Clamped2.fill(257)); - }); - it('start offset', function(): void { - for (let i = -2; i < 10; ++i) { - const u81 = new Uint8Array(5); - const u83 = new Uint8Array(5); - deepEquals(fillFallback(u81, 2, i), u83.fill(2, i)); - deepEquals(fillFallback(u81, -1, i), u83.fill(-1, i)); - } - }); - it('end offset', function(): void { - for (let i = -2; i < 10; ++i) { - const u81 = new Uint8Array(5); - const u83 = new Uint8Array(5); - deepEquals(fillFallback(u81, 2, 0, i), u83.fill(2, 0, i)); - deepEquals(fillFallback(u81, -1, 0, i), u83.fill(-1, 0, i)); - } - }); - it('start/end offset', function(): void { - for (let i = -2; i < 10; ++i) { - for (let j = -2; j < 10; ++j) { - const u81 = new Uint8Array(5); - const u83 = new Uint8Array(5); - deepEquals(fillFallback(u81, 2, i, j), u83.fill(2, i, j)); - deepEquals(fillFallback(u81, -1, i, j), u83.fill(-1, i, j)); - } - } - }); - }); - - describe('TypedArray.slice', () => { - describe('should work with all typed array types', () => { - it('Uint8Array', () => { - const a = new Uint8Array(5); - deepEquals(sliceFallback(a, 2), a.slice(2)); - deepEquals(sliceFallback(a, 65535), a.slice(65535)); - deepEquals(sliceFallback(a, -1), a.slice(-1)); - }); - it('Uint16Array', () => { - const u161 = new Uint16Array(5); - const u162 = new Uint16Array(5); - deepEquals(sliceFallback(u161, 2), u162.slice(2)); - deepEquals(sliceFallback(u161, 65535), u162.slice(65535)); - deepEquals(sliceFallback(u161, -1), u162.slice(-1)); - }); - it('Uint32Array', () => { - const u321 = new Uint32Array(5); - const u322 = new Uint32Array(5); - deepEquals(sliceFallback(u321, 2), u322.slice(2)); - deepEquals(sliceFallback(u321, 65537), u322.slice(65537)); - deepEquals(sliceFallback(u321, -1), u322.slice(-1)); - }); - it('Int8Array', () => { - const i81 = new Int8Array(5); - const i82 = new Int8Array(5); - deepEquals(sliceFallback(i81, 2), i82.slice(2)); - deepEquals(sliceFallback(i81, 65537), i82.slice(65537)); - deepEquals(sliceFallback(i81, -1), i82.slice(-1)); - }); - it('Int16Array', () => { - const i161 = new Int16Array(5); - const i162 = new Int16Array(5); - deepEquals(sliceFallback(i161, 2), i162.slice(2)); - deepEquals(sliceFallback(i161, 65535), i162.slice(65535)); - deepEquals(sliceFallback(i161, -1), i162.slice(-1)); - }); - it('Int32Array', () => { - const i321 = new Int32Array(5); - const i322 = new Int32Array(5); - deepEquals(sliceFallback(i321, 2), i322.slice(2)); - deepEquals(sliceFallback(i321, 65537), i322.slice(65537)); - deepEquals(sliceFallback(i321, -1), i322.slice(-1)); - }); - it('Float32Array', () => { - const f321 = new Float32Array(5); - const f322 = new Float32Array(5); - deepEquals(sliceFallback(f321, 2), f322.slice(2)); - deepEquals(sliceFallback(f321, 65537), f322.slice(65537)); - deepEquals(sliceFallback(f321, -1), f322.slice(-1)); - }); - it('Float64Array', () => { - const f641 = new Float64Array(5); - const f642 = new Float64Array(5); - deepEquals(sliceFallback(f641, 2), f642.slice(2)); - deepEquals(sliceFallback(f641, 65537), f642.slice(65537)); - deepEquals(sliceFallback(f641, -1), f642.slice(-1)); - }); - it('Uint8ClampedArray', () => { - const u8Clamped1 = new Uint8ClampedArray(5); - const u8Clamped2 = new Uint8ClampedArray(5); - deepEquals(sliceFallback(u8Clamped1, 2), u8Clamped2.slice(2)); - deepEquals(sliceFallback(u8Clamped1, 65537), u8Clamped2.slice(65537)); - deepEquals(sliceFallback(u8Clamped1, -1), u8Clamped2.slice(-1)); - }); - }); - it('start', () => { - const arr = new Uint32Array([1, 2, 3, 4, 5]); - deepEquals(sliceFallback(arr, -1), arr.slice(-1)); - deepEquals(sliceFallback(arr, 0), arr.slice(0)); - deepEquals(sliceFallback(arr, 1), arr.slice(1)); - deepEquals(sliceFallback(arr, 2), arr.slice(2)); - deepEquals(sliceFallback(arr, 3), arr.slice(3)); - deepEquals(sliceFallback(arr, 4), arr.slice(4)); - deepEquals(sliceFallback(arr, 5), arr.slice(5)); - }); - it('end', () => { - const arr = new Uint32Array([1, 2, 3, 4, 5]); - deepEquals(sliceFallback(arr, -1, -2), arr.slice(-1, -2)); - deepEquals(sliceFallback(arr, 0, -2), arr.slice(0, -2)); - deepEquals(sliceFallback(arr, 1, -2), arr.slice(1, -2)); - deepEquals(sliceFallback(arr, 2, -2), arr.slice(2, -2)); - deepEquals(sliceFallback(arr, 3, -2), arr.slice(3, -2)); - deepEquals(sliceFallback(arr, 4, -2), arr.slice(4, -2)); - deepEquals(sliceFallback(arr, 5, -2), arr.slice(5, -2)); - - deepEquals(sliceFallback(arr, -1, 3), arr.slice(-1, 3)); - deepEquals(sliceFallback(arr, 0, 3), arr.slice(0, 3)); - deepEquals(sliceFallback(arr, 1, 3), arr.slice(1, 3)); - deepEquals(sliceFallback(arr, 2, 3), arr.slice(2, 3)); - deepEquals(sliceFallback(arr, 3, 3), arr.slice(3, 3)); - deepEquals(sliceFallback(arr, 4, 3), arr.slice(4, 3)); - deepEquals(sliceFallback(arr, 5, 3), arr.slice(5, 3)); - - deepEquals(sliceFallback(arr, -1, 8), arr.slice(-1, 8)); - deepEquals(sliceFallback(arr, 0, 8), arr.slice(0, 8)); - deepEquals(sliceFallback(arr, 1, 8), arr.slice(1, 8)); - deepEquals(sliceFallback(arr, 2, 8), arr.slice(2, 8)); - deepEquals(sliceFallback(arr, 3, 8), arr.slice(3, 8)); - deepEquals(sliceFallback(arr, 4, 8), arr.slice(4, 8)); - deepEquals(sliceFallback(arr, 5, 8), arr.slice(5, 8)); - }); - }); -}); diff --git a/src/renderer/webgl/TypedArray.ts b/src/renderer/webgl/TypedArray.ts deleted file mode 100644 index 33d59ea1..00000000 --- a/src/renderer/webgl/TypedArray.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - */ - -export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray - | Int8Array | Int16Array | Int32Array - | Float32Array | Float64Array; - - -/** - * polyfill for TypedArray.fill - * This is needed to support .fill in all safari versions and IE 11. - */ -export function fill(array: T, value: number, start?: number, end?: number): T { - // all modern engines that support .fill - if (array.fill) { - return array.fill(value, start, end) as T; - } - return fillFallback(array, value, start, end); -} - -export function fillFallback(array: T, value: number, start: number = 0, end: number = array.length): T { - // safari and IE 11 - // since IE 11 does not support Array.prototype.fill either - // we cannot use the suggested polyfill from MDN - // instead we simply fall back to looping - if (start >= array.length) { - return array; - } - start = (array.length + start) % array.length; - if (end >= array.length) { - end = array.length; - } else { - end = (array.length + end) % array.length; - } - for (let i = start; i < end; ++i) { - array[i] = value; - } - return array; -} - -export function slice(array: T, start?: number, end?: number): T { - // all modern engines that support .slice - if (array.slice) { - return array.slice(start, end) as T; - } - return sliceFallback(array, start, end); -} - -export function sliceFallback(array: T, start: number = 0, end: number = array.length): T { - if (start < 0) { - start = (array.length + start) % array.length; - } - if (end >= array.length) { - end = array.length; - } else { - end = (array.length + end) % array.length; - } - start = Math.min(start, end); - - const result: T = new (array.constructor as any)(end - start); - for (let i = 0; i < end - start; ++i) { - result[i] = array[i + start]; - } - return result; -} diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index c6819f24..69663338 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -14,7 +14,7 @@ import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; -import { Disposable } from './Lifecycle'; +import { Disposable } from 'common/Lifecycle'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from 'common/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../common/Types'; import { IColorSet, Terminal, IRenderDimensions, IRenderer } from 'xterm'; From d4169f353881ad98027754c85fdfdd5f10111191 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 19:31:42 -0700 Subject: [PATCH 42/69] Fix tests --- src/renderer/webgl/WebglRenderer.api.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/renderer/webgl/WebglRenderer.api.ts b/src/renderer/webgl/WebglRenderer.api.ts index d2dc06f0..62a4c7cf 100644 --- a/src/renderer/webgl/WebglRenderer.api.ts +++ b/src/renderer/webgl/WebglRenderer.api.ts @@ -166,7 +166,7 @@ async function writeSync(data: string): Promise { // async function getPixelAt(x: number, y: number): Promise { // await page.evaluate(` -// window.gl = window.term._core._renderCoordinator._renderer._gl; +// window.gl = window.term._core._renderService._renderer._gl; // window.result = new Uint8Array(4); // window.gl.readPixels(${x}, window.gl.drawingBufferHeight - 1 - ${y}, 1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result); // `); @@ -175,9 +175,9 @@ async function writeSync(data: string): Promise { async function getCellColor(col: number, row: number): Promise { await page.evaluate(` - window.gl = window.term._core._renderCoordinator._renderer._gl; + window.gl = window.term._core._renderService._renderer._gl; window.result = new Uint8Array(4); - window.d = window.term._core._renderCoordinator.dimensions; + window.d = window.term._core._renderService.dimensions; window.gl.readPixels( Math.floor((${col - 0.5}) * window.d.scaledCellWidth), Math.floor(window.gl.drawingBufferHeight - 1 - (${row - 0.5}) * window.d.scaledCellHeight), From 21d07d1f0ed6812eb6dea4017ef21cb581486b9b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 19:34:31 -0700 Subject: [PATCH 43/69] Remove unused portion of CharAtlasGenerator --- .../webgl/atlas/CharAtlasGenerator.ts | 128 ------------------ src/renderer/webgl/atlas/WebglCharAtlas.ts | 22 ++- 2 files changed, 21 insertions(+), 129 deletions(-) delete mode 100644 src/renderer/webgl/atlas/CharAtlasGenerator.ts diff --git a/src/renderer/webgl/atlas/CharAtlasGenerator.ts b/src/renderer/webgl/atlas/CharAtlasGenerator.ts deleted file mode 100644 index c01ea630..00000000 --- a/src/renderer/webgl/atlas/CharAtlasGenerator.ts +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Copyright (c) 2018 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { FontWeight, IColor } from 'xterm'; -import { ICharAtlasConfig, CHAR_ATLAS_CELL_SPACING } from './Types'; -import { isFirefox, isSafari } from '../Platform'; - -/** - * Generates a char atlas. - * @param context The window or worker context. - * @param canvasFactory A function to generate a canvas with a width or height. - * @param config The config for the new char atlas. - */ -export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (width: number, height: number) => HTMLCanvasElement, config: ICharAtlasConfig): HTMLCanvasElement | Promise { - const cellWidth = config.scaledCharWidth + CHAR_ATLAS_CELL_SPACING; - const cellHeight = config.scaledCharHeight + CHAR_ATLAS_CELL_SPACING; - const canvas = canvasFactory( - /*255 ascii chars*/255 * cellWidth, - (/*default+default bold*/2 + /*0-15*/16 + /*0-15 bold*/16) * cellHeight - ); - const ctx = canvas.getContext('2d', {alpha: config.allowTransparency}); - - ctx.fillStyle = config.colors.background.css; - ctx.fillRect(0, 0, canvas.width, canvas.height); - - ctx.save(); - ctx.fillStyle = config.colors.foreground.css; - ctx.font = getFont(config.fontWeight, config); - ctx.textBaseline = 'middle'; - - // Default color - for (let i = 0; i < 256; i++) { - ctx.save(); - ctx.beginPath(); - ctx.rect(i * cellWidth, 0, cellWidth, cellHeight); - ctx.clip(); - ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight / 2); - ctx.restore(); - } - // Default color bold - ctx.save(); - ctx.font = getFont(config.fontWeightBold, config); - for (let i = 0; i < 256; i++) { - ctx.save(); - ctx.beginPath(); - ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight); - ctx.clip(); - ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight * 1.5); - ctx.restore(); - } - ctx.restore(); - - // Colors 0-15 - ctx.font = getFont(config.fontWeight, config); - for (let colorIndex = 0; colorIndex < 16; colorIndex++) { - const y = (colorIndex + 2) * cellHeight; - // Draw ascii characters - for (let i = 0; i < 256; i++) { - ctx.save(); - ctx.beginPath(); - ctx.rect(i * cellWidth, y, cellWidth, cellHeight); - ctx.clip(); - ctx.fillStyle = config.colors.ansi[colorIndex].css; - ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2); - ctx.restore(); - } - } - - // Colors 0-15 bold - ctx.font = getFont(config.fontWeightBold, config); - for (let colorIndex = 0; colorIndex < 16; colorIndex++) { - const y = (colorIndex + 2 + 16) * cellHeight; - // Draw ascii characters - for (let i = 0; i < 256; i++) { - ctx.save(); - ctx.beginPath(); - ctx.rect(i * cellWidth, y, cellWidth, cellHeight); - ctx.clip(); - ctx.fillStyle = config.colors.ansi[colorIndex].css; - ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2); - ctx.restore(); - } - } - ctx.restore(); - - // Support is patchy for createImageBitmap at the moment, pass a canvas back - // if support is lacking as drawImage works there too. Firefox is also - // included here as ImageBitmap appears both buggy and has horrible - // performance (tested on v55). - if (!('createImageBitmap' in context) || isFirefox || isSafari) { - // Don't attempt to clear background colors if createImageBitmap is not supported - return canvas; - } - - const charAtlasImageData = ctx.getImageData(0, 0, canvas.width, canvas.height); - - // Remove the background color from the image so characters may overlap - clearColor(charAtlasImageData, config.colors.background); - - return context.createImageBitmap(charAtlasImageData); -} - -/** - * Makes a partiicular rgb color in an ImageData completely transparent. - * @returns True if the result is "empty", meaning all pixels are fully transparent. - */ -export function clearColor(imageData: ImageData, color: IColor): boolean { - let isEmpty = true; - const r = color.rgba >>> 24; - const g = color.rgba >>> 16 & 0xFF; - const b = color.rgba >>> 8 & 0xFF; - for (let offset = 0; offset < imageData.data.length; offset += 4) { - if (imageData.data[offset] === r && - imageData.data[offset + 1] === g && - imageData.data[offset + 2] === b) { - imageData.data[offset + 3] = 0; - } else { - isEmpty = false; - } - } - return isEmpty; -} - -function getFont(fontWeight: FontWeight, config: ICharAtlasConfig): string { - return `${fontWeight} ${config.fontSize * config.devicePixelRatio}px ${config.fontFamily}`; -} diff --git a/src/renderer/webgl/atlas/WebglCharAtlas.ts b/src/renderer/webgl/atlas/WebglCharAtlas.ts index 9c6f9e9b..6c952e0f 100644 --- a/src/renderer/webgl/atlas/WebglCharAtlas.ts +++ b/src/renderer/webgl/atlas/WebglCharAtlas.ts @@ -8,7 +8,6 @@ import BaseCharAtlas from './BaseCharAtlas'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { FLAGS } from '../../Types'; import { is256Color } from './CharAtlasUtils'; -import { clearColor } from './CharAtlasGenerator'; import { DEFAULT_ATTR } from 'common/buffer/BufferLine'; import { DEFAULT_COLOR } from '../../../common/Types'; import { IColor } from 'xterm'; @@ -377,3 +376,24 @@ export default class WebglCharAtlas extends BaseCharAtlas { return new ImageData(clippedData, width, height); } } + +/** + * Makes a partiicular rgb color in an ImageData completely transparent. + * @returns True if the result is "empty", meaning all pixels are fully transparent. + */ +function clearColor(imageData: ImageData, color: IColor): boolean { + let isEmpty = true; + const r = color.rgba >>> 24; + const g = color.rgba >>> 16 & 0xFF; + const b = color.rgba >>> 8 & 0xFF; + for (let offset = 0; offset < imageData.data.length; offset += 4) { + if (imageData.data[offset] === r && + imageData.data[offset + 1] === g && + imageData.data[offset + 2] === b) { + imageData.data[offset + 3] = 0; + } else { + isEmpty = false; + } + } + return isEmpty; +} From e959cce8999fb9a1662d0c7c8e0ff2b1621bb181 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 19:35:05 -0700 Subject: [PATCH 44/69] Remove webgl LRUMap --- src/renderer/webgl/atlas/LRUMap.test.ts | 65 ----------- src/renderer/webgl/atlas/LRUMap.ts | 136 ------------------------ 2 files changed, 201 deletions(-) delete mode 100644 src/renderer/webgl/atlas/LRUMap.test.ts delete mode 100644 src/renderer/webgl/atlas/LRUMap.ts diff --git a/src/renderer/webgl/atlas/LRUMap.test.ts b/src/renderer/webgl/atlas/LRUMap.test.ts deleted file mode 100644 index 197d1159..00000000 --- a/src/renderer/webgl/atlas/LRUMap.test.ts +++ /dev/null @@ -1,65 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { assert } from 'chai'; -import LRUMap from './LRUMap'; - -describe('LRUMap', () => { - it('can be used to store and retrieve values', () => { - const map = new LRUMap(10); - map.set(1, 'valuea'); - map.set(2, 'valueb'); - map.set(3, 'valuec'); - assert.strictEqual(map.get(1), 'valuea'); - assert.strictEqual(map.get(2), 'valueb'); - assert.strictEqual(map.get(3), 'valuec'); - }); - - it('maintains a size from insertions', () => { - const map = new LRUMap(10); - assert.strictEqual(map.size, 0); - map.set(1, 'value'); - assert.strictEqual(map.size, 1); - map.set(2, 'value'); - assert.strictEqual(map.size, 2); - }); - - it('deletes the oldest entry when the capacity is exceeded', () => { - const map = new LRUMap(4); - map.set(1, 'value'); - map.set(2, 'value'); - map.set(3, 'value'); - map.set(4, 'value'); - map.set(5, 'value'); - assert.isNull(map.get(1)); - assert.isNotNull(map.get(2)); - assert.isNotNull(map.get(3)); - assert.isNotNull(map.get(4)); - assert.isNotNull(map.get(5)); - assert.strictEqual(map.size, 4); - }); - - it('prevents a recently accessed entry from getting deleted', () => { - const map = new LRUMap(2); - map.set(1, 'value'); - map.set(2, 'value'); - map.get(1); - // a would normally get deleted here, except that we called get() - map.set(3, 'value'); - assert.isNotNull(map.get(1)); - // b got deleted instead of a - assert.isNull(map.get(2)); - assert.isNotNull(map.get(3)); - }); - - it('supports mutation', () => { - const map = new LRUMap(10); - map.set(1, 'oldvalue'); - map.set(1, 'newvalue'); - // mutation doesn't change the size - assert.strictEqual(map.size, 1); - assert.strictEqual(map.get(1), 'newvalue'); - }); -}); diff --git a/src/renderer/webgl/atlas/LRUMap.ts b/src/renderer/webgl/atlas/LRUMap.ts deleted file mode 100644 index d7e01ec6..00000000 --- a/src/renderer/webgl/atlas/LRUMap.ts +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -interface ILinkedListNode { - prev: ILinkedListNode; - next: ILinkedListNode; - key: number; - value: T; -} - -export default class LRUMap { - private _map: { [key: number]: ILinkedListNode } = {}; - private _head: ILinkedListNode = null; - private _tail: ILinkedListNode = null; - private _nodePool: ILinkedListNode[] = []; - public size: number = 0; - - constructor(public capacity: number) { } - - private _unlinkNode(node: ILinkedListNode): void { - const prev = node.prev; - const next = node.next; - if (node === this._head) { - this._head = next; - } - if (node === this._tail) { - this._tail = prev; - } - if (prev !== null) { - prev.next = next; - } - if (next !== null) { - next.prev = prev; - } - } - - private _appendNode(node: ILinkedListNode): void { - const tail = this._tail; - if (tail !== null) { - tail.next = node; - } - node.prev = tail; - node.next = null; - this._tail = node; - if (this._head === null) { - this._head = node; - } - } - - /** - * Preallocate a bunch of linked-list nodes. Allocating these nodes ahead of time means that - * they're more likely to live next to each other in memory, which seems to improve performance. - * - * Each empty object only consumes about 60 bytes of memory, so this is pretty cheap, even for - * large maps. - */ - public prealloc(count: number): void { - const nodePool = this._nodePool; - for (let i = 0; i < count; i++) { - nodePool.push({ - prev: null, - next: null, - key: null, - value: null - }); - } - } - - public get(key: number): T | null { - // This is unsafe: We're assuming our keyspace doesn't overlap with Object.prototype. However, - // it's faster than calling hasOwnProperty, and in our case, it would never overlap. - const node = this._map[key]; - if (node !== undefined) { - this._unlinkNode(node); - this._appendNode(node); - return node.value; - } - return null; - } - - /** - * Gets a value from a key without marking it as the most recently used item. - */ - public peekValue(key: number): T | null { - const node = this._map[key]; - if (node !== undefined) { - return node.value; - } - return null; - } - - public peek(): T | null { - const head = this._head; - return head === null ? null : head.value; - } - - public set(key: number, value: T): void { - // This is unsafe: See note above. - let node = this._map[key]; - if (node !== undefined) { - // already exists, we just need to mutate it and move it to the end of the list - node = this._map[key]; - this._unlinkNode(node); - node.value = value; - } else if (this.size >= this.capacity) { - // we're out of space: recycle the head node, move it to the tail - node = this._head; - this._unlinkNode(node); - delete this._map[node.key]; - node.key = key; - node.value = value; - this._map[key] = node; - } else { - // make a new element - const nodePool = this._nodePool; - if (nodePool.length > 0) { - // use a preallocated node if we can - node = nodePool.pop(); - node.key = key; - node.value = value; - } else { - node = { - prev: null, - next: null, - key, - value - }; - } - this._map[key] = node; - this.size++; - } - this._appendNode(node); - } -} From 4b4b58e66251c3d282ef42e97baf2702da06f9ca Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 20:37:54 -0700 Subject: [PATCH 45/69] Fix screen dpr change not refreshing canvas renderer --- src/browser/services/RenderService.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 5035ed3d..00f3a769 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -43,7 +43,7 @@ export class RenderService extends Disposable implements IRenderService { this.register(this._renderDebouncer); this._screenDprMonitor = new ScreenDprMonitor(); - this._screenDprMonitor.setListener(() => this._renderer.onDevicePixelRatioChange()); + this._screenDprMonitor.setListener(() => this.onDevicePixelRatioChange()); this.register(this._screenDprMonitor); this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged())); @@ -51,7 +51,7 @@ export class RenderService extends Disposable implements IRenderService { // dprchange should handle this case, we need this as well for browsers that don't support the // matchMedia query. - this.register(addDisposableDomListener(window, 'resize', () => this._renderer.onDevicePixelRatioChange())); + this.register(addDisposableDomListener(window, 'resize', () => this.onDevicePixelRatioChange())); // Detect whether IntersectionObserver is detected and enable renderer pause // and resume based on terminal visibility if so @@ -122,6 +122,7 @@ export class RenderService extends Disposable implements IRenderService { public onDevicePixelRatioChange(): void { this._renderer.onDevicePixelRatioChange(); + this.refreshRows(0, this._rowCount); } public onResize(cols: number, rows: number): void { From 2584de86302efd465dc8fff40c00e561f0f6e8bb Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 20:55:52 -0700 Subject: [PATCH 46/69] Clear webgl model after theme or dpr change --- src/renderer/webgl/WebglRenderer.ts | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 69663338..eee31849 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -121,6 +121,9 @@ export class WebglRenderer extends Disposable implements IRenderer { this._glyphRenderer.setColors(); this._refreshCharAtlas(); + + // Force a full refresh + this._model.clear(); } public onDevicePixelRatioChange(): void { @@ -134,7 +137,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public onResize(cols: number, rows: number): void { // Update character and canvas dimensions - this._updateDimensions(devicePixelRatio); + this._updateDimensions(); this._model.resize(this._terminal.cols, this._terminal.rows); this._rectangleRenderer.onResize(); @@ -155,6 +158,9 @@ export class WebglRenderer extends Disposable implements IRenderer { this._glyphRenderer.onResize(); this._refreshCharAtlas(); + + // Force a full refresh + this._model.clear(); } public onCharSizeChanged(): void { @@ -331,7 +337,7 @@ export class WebglRenderer extends Disposable implements IRenderer { /** * Recalculates the character and canvas dimensions. */ - private _updateDimensions(devicePixelRatio: number = window.devicePixelRatio): void { + private _updateDimensions(): void { // TODO: Acquire CharSizeService properly // Perform a new measure if the CharMeasure dimensions are not yet available @@ -346,12 +352,12 @@ export class WebglRenderer extends Disposable implements IRenderer { // NOTE: ceil fixes sometime, floor does others :s - this.dimensions.scaledCharWidth = Math.floor((this._core)._charSizeService.width * devicePixelRatio); + this.dimensions.scaledCharWidth = Math.floor((this._core)._charSizeService.width * this._devicePixelRatio); // Calculate the scaled character height. Height is ceiled in case // devicePixelRatio is a floating point number in order to ensure there is // enough space to draw the character to the cell. - this.dimensions.scaledCharHeight = Math.ceil((this._core)._charSizeService.height * devicePixelRatio); + this.dimensions.scaledCharHeight = Math.ceil((this._core)._charSizeService.height * this._devicePixelRatio); // Calculate the scaled cell height, if lineHeight is not 1 then the value // will be floored because since lineHeight can never be lower then 1, there @@ -380,8 +386,8 @@ export class WebglRenderer extends Disposable implements IRenderer { // window.devicePixelRatio as something like 1.100000023841858, when it's // actually 1.1. Ceiling causes blurriness as the backing canvas image is 1 // pixel too large for the canvas element size. - this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / devicePixelRatio); - this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / devicePixelRatio); + this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / this._devicePixelRatio); + this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / this._devicePixelRatio); // this.dimensions.scaledCanvasHeight = this.dimensions.canvasHeight * devicePixelRatio; // this.dimensions.scaledCanvasWidth = this.dimensions.canvasWidth * devicePixelRatio; @@ -396,7 +402,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols; // This fixes 110% and 125%, not 150% or 175% though - this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / devicePixelRatio; - this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / devicePixelRatio; + this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / this._devicePixelRatio; + this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio; } } From 6c232a0e117c2331f280f3babb98c67f12e1a078 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 22:03:55 -0700 Subject: [PATCH 47/69] Remove renderer API --- src/renderer/webgl/ColorUtils.ts | 2 +- src/renderer/webgl/GlyphRenderer.ts | 4 +- src/renderer/webgl/RectangleRenderer.ts | 4 +- src/renderer/webgl/WebglRenderer.ts | 16 +++-- src/renderer/webgl/WebglRendererAddon.ts | 2 +- src/renderer/webgl/atlas/CharAtlasCache.ts | 3 +- src/renderer/webgl/atlas/CharAtlasUtils.ts | 3 +- src/renderer/webgl/atlas/Types.ts | 3 +- src/renderer/webgl/atlas/WebglCharAtlas.ts | 2 +- .../webgl/renderLayer/BaseRenderLayer.ts | 4 +- .../webgl/renderLayer/CursorRenderLayer.ts | 4 +- .../webgl/renderLayer/LinkRenderLayer.ts | 4 +- src/renderer/webgl/renderLayer/Types.ts | 4 +- typings/xterm.d.ts | 69 ------------------- 14 files changed, 36 insertions(+), 88 deletions(-) diff --git a/src/renderer/webgl/ColorUtils.ts b/src/renderer/webgl/ColorUtils.ts index 56127656..80372e65 100644 --- a/src/renderer/webgl/ColorUtils.ts +++ b/src/renderer/webgl/ColorUtils.ts @@ -3,7 +3,7 @@ * Copyright (c) 2018 The xterm.js authors. All rights reserved. */ -import { IColor } from 'xterm'; +import { IColor } from 'browser/Types'; export function getLuminance(color: IColor): number { // Coefficients taken from: https://www.w3.org/TR/AERT/#color-contrast diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index fe40c375..e76b2f7b 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -11,7 +11,9 @@ import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { fill, slice } from 'common/TypedArrayUtils'; import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from '../../common/buffer/BufferLine'; import { getLuminance } from './ColorUtils'; -import { IColorSet, Terminal, IBufferLine, IRenderDimensions } from 'xterm'; +import { Terminal, IBufferLine } from 'xterm'; +import { IColorSet } from 'browser/Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; interface IVertices { attributes: Float32Array; diff --git a/src/renderer/webgl/RectangleRenderer.ts b/src/renderer/webgl/RectangleRenderer.ts index 93748a04..54fddc7d 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/src/renderer/webgl/RectangleRenderer.ts @@ -9,7 +9,9 @@ import { fill } from 'common/TypedArrayUtils'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { is256Color } from './atlas/CharAtlasUtils'; import { DEFAULT_COLOR } from '../../common/buffer/Constants'; -import { IColorSet, IColor, Terminal, IRenderDimensions } from 'xterm'; +import { Terminal } from 'xterm'; +import { IColorSet, IColor } from 'browser/Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; const enum VertexAttribLocations { POSITION = 0, diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index 444dde71..d5896b9c 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -16,9 +16,11 @@ import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from 'common/buffer/BufferLine'; import { DEFAULT_COLOR, FLAGS } from '../../common/buffer/Constants'; -import { IColorSet, Terminal, IRenderDimensions, IRenderer } from 'xterm'; +import { Terminal } from 'xterm'; import { getLuminance } from './ColorUtils'; import { IRenderLayer } from './renderLayer/Types'; +import { IRenderDimensions, IRenderer } from 'browser/renderer/Types'; +import { IColorSet } from 'browser/Types'; export const INDICIES_PER_CELL = 4; @@ -50,8 +52,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._applyBgLuminanceBasedSelection(); this._renderLayers = [ - new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._core), - new CursorRenderLayer(this._terminal.screenElement, 3, this._colors) + new LinkRenderLayer((this._terminal).screenElement, 2, this._colors, this._core), + new CursorRenderLayer((this._terminal).screenElement, 3, this._colors) ]; this.dimensions = { scaledCharWidth: null, @@ -81,7 +83,7 @@ export class WebglRenderer extends Disposable implements IRenderer { if (!this._gl) { throw new Error('WebGL2 not supported'); } - this._terminal.screenElement.appendChild(this._canvas); + (this._terminal).screenElement.appendChild(this._canvas); this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions); this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions); @@ -92,7 +94,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public dispose(): void { this._renderLayers.forEach(l => l.dispose()); - this._terminal.screenElement.removeChild(this._canvas); + (this._terminal).screenElement.removeChild(this._canvas); super.dispose(); } @@ -151,8 +153,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._canvas.style.height = `${this.dimensions.canvasHeight}px`; // Resize the screen - this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`; - this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`; + (this._terminal).screenElement.style.width = `${this.dimensions.canvasWidth}px`; + (this._terminal).screenElement.style.height = `${this.dimensions.canvasHeight}px`; this._glyphRenderer.setDimensions(this.dimensions); this._glyphRenderer.onResize(); diff --git a/src/renderer/webgl/WebglRendererAddon.ts b/src/renderer/webgl/WebglRendererAddon.ts index 6ce27b0a..9ebe954f 100644 --- a/src/renderer/webgl/WebglRendererAddon.ts +++ b/src/renderer/webgl/WebglRendererAddon.ts @@ -18,7 +18,7 @@ export class WebglRendererAddon implements ITerminalAddon { throw new Error('Cannot activate WebglRendererAddon before Terminal.open'); } this._terminal = terminal; - this._terminal.setRenderer(new WebglRenderer(terminal, (terminal as any)._core._colorManager.colors, this._preserveDrawingBuffer)); + (this._terminal)._renderService.setRenderer(new WebglRenderer(terminal, (terminal as any)._core._colorManager.colors, this._preserveDrawingBuffer)); } public dispose(): void { diff --git a/src/renderer/webgl/atlas/CharAtlasCache.ts b/src/renderer/webgl/atlas/CharAtlasCache.ts index 8d8459fd..bd5b8201 100644 --- a/src/renderer/webgl/atlas/CharAtlasCache.ts +++ b/src/renderer/webgl/atlas/CharAtlasCache.ts @@ -7,7 +7,8 @@ import { generateConfig, configEquals } from './CharAtlasUtils'; import BaseCharAtlas from './BaseCharAtlas'; import WebglCharAtlas from './WebglCharAtlas'; import { ICharAtlasConfig } from './Types'; -import { IColorSet, Terminal } from 'xterm'; +import { Terminal } from 'xterm'; +import { IColorSet } from 'browser/Types'; interface ICharAtlasCacheEntry { atlas: BaseCharAtlas; diff --git a/src/renderer/webgl/atlas/CharAtlasUtils.ts b/src/renderer/webgl/atlas/CharAtlasUtils.ts index 5d1f160f..0554746d 100644 --- a/src/renderer/webgl/atlas/CharAtlasUtils.ts +++ b/src/renderer/webgl/atlas/CharAtlasUtils.ts @@ -5,7 +5,8 @@ import { ICharAtlasConfig } from './Types'; import { DEFAULT_COLOR } from '../../../common/buffer/Constants'; -import { IColorSet, Terminal, FontWeight } from 'xterm'; +import { Terminal, FontWeight } from 'xterm'; +import { IColorSet } from 'browser/Types'; export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: Terminal, colors: IColorSet): ICharAtlasConfig { // null out some fields that don't matter diff --git a/src/renderer/webgl/atlas/Types.ts b/src/renderer/webgl/atlas/Types.ts index 1df4f82d..2cb1db40 100644 --- a/src/renderer/webgl/atlas/Types.ts +++ b/src/renderer/webgl/atlas/Types.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { FontWeight, IColorSet } from 'xterm'; +import { FontWeight } from 'xterm'; +import { IColorSet } from 'browser/Types'; export const INVERTED_DEFAULT_COLOR = 257; export const DIM_OPACITY = 0.5; diff --git a/src/renderer/webgl/atlas/WebglCharAtlas.ts b/src/renderer/webgl/atlas/WebglCharAtlas.ts index bf1f805e..af5d2122 100644 --- a/src/renderer/webgl/atlas/WebglCharAtlas.ts +++ b/src/renderer/webgl/atlas/WebglCharAtlas.ts @@ -9,7 +9,7 @@ import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { FLAGS, DEFAULT_COLOR } from '../../../common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; import { DEFAULT_ATTR } from 'common/buffer/BufferLine'; -import { IColor } from 'xterm'; +import { IColor } from 'browser/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. diff --git a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts index b7730a94..1b880c12 100644 --- a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts @@ -10,7 +10,9 @@ import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from '../atlas/ import BaseCharAtlas from '../atlas/BaseCharAtlas'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../../../common/buffer/BufferLine'; -import { IColorSet, IRenderDimensions, Terminal } from 'xterm'; +import { Terminal } from 'xterm'; +import { IColorSet } from 'browser/Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; diff --git a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts b/src/renderer/webgl/renderLayer/CursorRenderLayer.ts index 425cbef3..da7d212c 100644 --- a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/CursorRenderLayer.ts @@ -3,10 +3,12 @@ * @license MIT */ -import { IRenderDimensions, IColorSet, Terminal } from 'xterm'; +import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; import { ICellData } from '../../../common/Types'; import { CellData } from '../../../common/buffer/BufferLine'; +import { IColorSet } from 'browser/Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; interface ICursorState { x: number; diff --git a/src/renderer/webgl/renderLayer/LinkRenderLayer.ts b/src/renderer/webgl/renderLayer/LinkRenderLayer.ts index eb47f7e5..50a3e111 100644 --- a/src/renderer/webgl/renderLayer/LinkRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/LinkRenderLayer.ts @@ -4,10 +4,12 @@ */ import { ILinkifierEvent, ILinkifierAccessor } from '../../../Types'; -import { IRenderDimensions, IColorSet, Terminal } from 'xterm'; +import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; import { is256Color } from '../atlas/CharAtlasUtils'; +import { IColorSet } from 'browser/Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; export class LinkRenderLayer extends BaseRenderLayer { private _state: ILinkifierEvent = null; diff --git a/src/renderer/webgl/renderLayer/Types.ts b/src/renderer/webgl/renderLayer/Types.ts index 50a76a2c..148ea469 100644 --- a/src/renderer/webgl/renderLayer/Types.ts +++ b/src/renderer/webgl/renderLayer/Types.ts @@ -3,7 +3,9 @@ * @license MIT */ -import { IDisposable, IRenderDimensions, IColorSet, Terminal } from 'xterm'; +import { IDisposable, Terminal } from 'xterm'; +import { IColorSet } from 'browser/Types'; +import { IRenderDimensions } from 'browser/renderer/Types'; export interface IRenderLayer extends IDisposable { /** diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 564f4935..f435367d 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -793,75 +793,6 @@ declare module 'xterm' { * @param addon The addon to load. */ loadAddon(addon: ITerminalAddon): void; - - /** - * (EXPERIMENTAL) - */ - setRenderer(renderer: IRenderer): void; - screenElement: HTMLElement; - } - - export namespace Renderer { - const DEFAULT_COLOR: number; - const NULL_CELL_CODE: number; - const WHITESPACE_CELL_CODE: number; - const DEFAULT_ATTR: number; - const DEFAULT_ANSI_COLORS: string[]; - const FLAGS: any; - } - - export interface IRenderer extends IDisposable { - readonly dimensions: IRenderDimensions; - - dispose(): void; - setColors(colors: IColorSet): void; - onDevicePixelRatioChange(): void; - onResize(cols: number, rows: number): void; - onCharSizeChanged(): void; - onBlur(): void; - onFocus(): void; - onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void; - onCursorMove(): void; - onOptionsChanged(): void; - clear(): void; - renderRows(start: number, end: number): void; - registerCharacterJoiner(handler: (text: string) => [number, number][]): number; - deregisterCharacterJoiner(joinerId: number): boolean; - } - - 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; - } - - /** - * (EXPERIMENTAL) - */ - export interface IColor { - css: string; - rgba: number; - } - - /** - * (EXPERIMENTAL) - */ - export interface IColorSet { - foreground: IColor; - background: IColor; - cursor: IColor; - cursorAccent: IColor; - selection: IColor; - ansi: IColor[]; } /** From 3a7aa77950af84058859d2577c9e94ecf1505821 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 22:18:53 -0700 Subject: [PATCH 48/69] Remove remaining parts of renderer API, fix webgl tests --- src/Terminal.ts | 6 ------ src/TestUtils.test.ts | 3 --- src/Types.d.ts | 1 - src/browser/services/RenderService.ts | 5 +++-- src/public/Terminal.ts | 3 --- src/renderer/webgl/WebglRendererAddon.ts | 2 +- 6 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index f2b2a8c8..eaf2a41d 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -686,12 +686,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp } - public setRenderer(renderer: IRenderer): void { - this._renderService.setRenderer(renderer); - // this._renderCoordinator.onOptionsChanged(); - this.refresh(0, this.rows - 1); - } - private _createRenderer(): IRenderer { switch (this.options.rendererType) { case 'canvas': return new Renderer(this, this._colorManager.colors, this._charSizeService); break; diff --git a/src/TestUtils.test.ts b/src/TestUtils.test.ts index 1446c41a..6b92bf0b 100644 --- a/src/TestUtils.test.ts +++ b/src/TestUtils.test.ts @@ -24,9 +24,6 @@ export class TestTerminal extends Terminal { } export class MockTerminal implements ITerminal { - setRenderer(renderer: any): void { - throw new Error('Method not implemented.'); - } onBlur: IEvent; onFocus: IEvent; onA11yChar: IEvent; diff --git a/src/Types.d.ts b/src/Types.d.ts index 8aadfa51..9a08a7a1 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -271,7 +271,6 @@ export interface IPublicTerminal extends IDisposable { writeUtf8(data: Uint8Array): void; refresh(start: number, end: number): void; reset(): void; - setRenderer(renderer: any): void; } export interface IBufferAccessor { diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index 00f3a769..b41f1ebc 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -105,13 +105,14 @@ export class RenderService extends Disposable implements IRenderService { // TODO: RenderCoordinator should be the only one to dispose the renderer this._renderer.dispose(); this._renderer = renderer; + this.refreshRows(0, this._rowCount - 1); } private _fullRefresh(): void { if (this._isPaused) { this._needsFullRefresh = true; } else { - this.refreshRows(0, this._rowCount); + this.refreshRows(0, this._rowCount - 1); } } @@ -122,7 +123,7 @@ export class RenderService extends Disposable implements IRenderService { public onDevicePixelRatioChange(): void { this._renderer.onDevicePixelRatioChange(); - this.refreshRows(0, this._rowCount); + this.refreshRows(0, this._rowCount - 1); } public onResize(cols: number, rows: number): void { diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index a1830576..2026d70e 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -162,9 +162,6 @@ export class Terminal implements ITerminalApi { public loadAddon(addon: ITerminalAddon): void { return this._addonManager.loadAddon(this, addon); } - public setRenderer(renderer: any): void { - this._core.setRenderer(renderer); - } public loadWebgl(preserveDrawingBuffer?: boolean): void { this.loadAddon(new WebglRendererAddon(preserveDrawingBuffer)); } diff --git a/src/renderer/webgl/WebglRendererAddon.ts b/src/renderer/webgl/WebglRendererAddon.ts index 9ebe954f..e29a7f07 100644 --- a/src/renderer/webgl/WebglRendererAddon.ts +++ b/src/renderer/webgl/WebglRendererAddon.ts @@ -18,7 +18,7 @@ export class WebglRendererAddon implements ITerminalAddon { throw new Error('Cannot activate WebglRendererAddon before Terminal.open'); } this._terminal = terminal; - (this._terminal)._renderService.setRenderer(new WebglRenderer(terminal, (terminal as any)._core._colorManager.colors, this._preserveDrawingBuffer)); + (this._terminal)._core._renderService.setRenderer(new WebglRenderer(terminal, (terminal as any)._core._colorManager.colors, this._preserveDrawingBuffer)); } public dispose(): void { From ddb9fbd1d6bc6183eb2b16fcc7487b8f5c5f8722 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 22:25:40 -0700 Subject: [PATCH 49/69] Improve type safety in webgl --- src/public/Terminal.ts | 1 - src/renderer/webgl/WebglRenderer.ts | 14 +++++++------- src/renderer/webgl/WebglRendererAddon.ts | 9 +++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 2026d70e..23869218 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -33,7 +33,6 @@ export class Terminal implements ITerminalApi { public get onResize(): IEvent<{ cols: number, rows: number }> { return this._core.onResize; } public get element(): HTMLElement { return this._core.element; } - public get screenElement(): HTMLElement { return this._core.screenElement; } public get textarea(): HTMLTextAreaElement { return this._core.textarea; } public get rows(): number { return this._core.rows; } public get cols(): number { return this._core.cols; } diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index d5896b9c..a01098df 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -47,13 +47,13 @@ export class WebglRenderer extends Disposable implements IRenderer { ) { super(); - this._core = (this._terminal as any)._core; + this._core = (this._terminal)._core; this._applyBgLuminanceBasedSelection(); this._renderLayers = [ - new LinkRenderLayer((this._terminal).screenElement, 2, this._colors, this._core), - new CursorRenderLayer((this._terminal).screenElement, 3, this._colors) + new LinkRenderLayer(this._core.screenElement, 2, this._colors, this._core), + new CursorRenderLayer(this._core.screenElement, 3, this._colors) ]; this.dimensions = { scaledCharWidth: null, @@ -83,7 +83,7 @@ export class WebglRenderer extends Disposable implements IRenderer { if (!this._gl) { throw new Error('WebGL2 not supported'); } - (this._terminal).screenElement.appendChild(this._canvas); + this._core.screenElement.appendChild(this._canvas); this._rectangleRenderer = new RectangleRenderer(this._terminal, this._colors, this._gl, this.dimensions); this._glyphRenderer = new GlyphRenderer(this._terminal, this._colors, this._gl, this.dimensions); @@ -94,7 +94,7 @@ export class WebglRenderer extends Disposable implements IRenderer { public dispose(): void { this._renderLayers.forEach(l => l.dispose()); - (this._terminal).screenElement.removeChild(this._canvas); + this._core.screenElement.removeChild(this._canvas); super.dispose(); } @@ -153,8 +153,8 @@ export class WebglRenderer extends Disposable implements IRenderer { this._canvas.style.height = `${this.dimensions.canvasHeight}px`; // Resize the screen - (this._terminal).screenElement.style.width = `${this.dimensions.canvasWidth}px`; - (this._terminal).screenElement.style.height = `${this.dimensions.canvasHeight}px`; + this._core.screenElement.style.width = `${this.dimensions.canvasWidth}px`; + this._core.screenElement.style.height = `${this.dimensions.canvasHeight}px`; this._glyphRenderer.setDimensions(this.dimensions); this._glyphRenderer.onResize(); diff --git a/src/renderer/webgl/WebglRendererAddon.ts b/src/renderer/webgl/WebglRendererAddon.ts index e29a7f07..4b67e380 100644 --- a/src/renderer/webgl/WebglRendererAddon.ts +++ b/src/renderer/webgl/WebglRendererAddon.ts @@ -5,10 +5,10 @@ import { Terminal, ITerminalAddon } from 'xterm'; import { WebglRenderer } from './WebglRenderer'; +import { IRenderService } from 'browser/services/Services'; +import { IColorSet } from 'browser/Types'; export class WebglRendererAddon implements ITerminalAddon { - private _terminal: Terminal | undefined; - constructor( private _preserveDrawingBuffer?: boolean ) {} @@ -17,8 +17,9 @@ export class WebglRendererAddon implements ITerminalAddon { if (!terminal.element) { throw new Error('Cannot activate WebglRendererAddon before Terminal.open'); } - this._terminal = terminal; - (this._terminal)._core._renderService.setRenderer(new WebglRenderer(terminal, (terminal as any)._core._colorManager.colors, this._preserveDrawingBuffer)); + const renderService: IRenderService = (terminal)._core._renderService; + const colors: IColorSet = (terminal)._core._colorManager.colors; + renderService.setRenderer(new WebglRenderer(terminal, colors, this._preserveDrawingBuffer)); } public dispose(): void { From b7cdbb99ca4daebfb8c75b71e2f7336eb87f733e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 22:27:07 -0700 Subject: [PATCH 50/69] Remove default exports in webgl char atlas files --- src/renderer/webgl/GlyphRenderer.ts | 2 +- src/renderer/webgl/WebglRenderer.ts | 2 +- src/renderer/webgl/atlas/BaseCharAtlas.ts | 2 +- src/renderer/webgl/atlas/CharAtlasCache.ts | 4 ++-- src/renderer/webgl/atlas/WebglCharAtlas.ts | 4 ++-- src/renderer/webgl/renderLayer/BaseRenderLayer.ts | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/renderer/webgl/GlyphRenderer.ts b/src/renderer/webgl/GlyphRenderer.ts index e76b2f7b..f95b7530 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/src/renderer/webgl/GlyphRenderer.ts @@ -4,7 +4,7 @@ */ import { createProgram, PROJECTION_MATRIX } from './WebglUtils'; -import WebglCharAtlas from './atlas/WebglCharAtlas'; +import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; diff --git a/src/renderer/webgl/WebglRenderer.ts b/src/renderer/webgl/WebglRenderer.ts index a01098df..c4d2c27e 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/src/renderer/webgl/WebglRenderer.ts @@ -8,7 +8,7 @@ import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; import { acquireCharAtlas } from './atlas/CharAtlasCache'; -import WebglCharAtlas from './atlas/WebglCharAtlas'; +import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; diff --git a/src/renderer/webgl/atlas/BaseCharAtlas.ts b/src/renderer/webgl/atlas/BaseCharAtlas.ts index ee69b381..470736f1 100644 --- a/src/renderer/webgl/atlas/BaseCharAtlas.ts +++ b/src/renderer/webgl/atlas/BaseCharAtlas.ts @@ -6,7 +6,7 @@ import { IGlyphIdentifier } from './Types'; import { IDisposable } from 'xterm'; -export default abstract class BaseCharAtlas implements IDisposable { +export abstract class BaseCharAtlas implements IDisposable { private _didWarmUp: boolean = false; public dispose(): void { } diff --git a/src/renderer/webgl/atlas/CharAtlasCache.ts b/src/renderer/webgl/atlas/CharAtlasCache.ts index bd5b8201..647b96b4 100644 --- a/src/renderer/webgl/atlas/CharAtlasCache.ts +++ b/src/renderer/webgl/atlas/CharAtlasCache.ts @@ -4,8 +4,8 @@ */ import { generateConfig, configEquals } from './CharAtlasUtils'; -import BaseCharAtlas from './BaseCharAtlas'; -import WebglCharAtlas from './WebglCharAtlas'; +import { BaseCharAtlas } from './BaseCharAtlas'; +import { WebglCharAtlas } from './WebglCharAtlas'; import { ICharAtlasConfig } from './Types'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; diff --git a/src/renderer/webgl/atlas/WebglCharAtlas.ts b/src/renderer/webgl/atlas/WebglCharAtlas.ts index af5d2122..f7624285 100644 --- a/src/renderer/webgl/atlas/WebglCharAtlas.ts +++ b/src/renderer/webgl/atlas/WebglCharAtlas.ts @@ -4,7 +4,7 @@ */ import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from './Types'; -import BaseCharAtlas from './BaseCharAtlas'; +import { BaseCharAtlas } from './BaseCharAtlas'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { FLAGS, DEFAULT_COLOR } from '../../../common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; @@ -41,7 +41,7 @@ const NULL_RASTERIZED_GLYPH: IRasterizedGlyph = { const TMP_CANVAS_GLYPH_PADDING = 2; -export default class WebglCharAtlas extends BaseCharAtlas { +export class WebglCharAtlas extends BaseCharAtlas { private _cacheMap: { [code: number]: IRasterizedGlyphSet } = {}; private _cacheMapCombined: { [chars: string]: IRasterizedGlyphSet } = {}; diff --git a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts index 1b880c12..b88d7330 100644 --- a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts +++ b/src/renderer/webgl/renderLayer/BaseRenderLayer.ts @@ -7,7 +7,7 @@ import { IRenderLayer } from './Types'; import { ICellData } from '../../../common/Types'; import { DEFAULT_COLOR } from '../../../common/buffer/Constants'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from '../atlas/Types'; -import BaseCharAtlas from '../atlas/BaseCharAtlas'; +import { BaseCharAtlas } from '../atlas/BaseCharAtlas'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import { CellData, AttributeData, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../../../common/buffer/BufferLine'; import { Terminal } from 'xterm'; From d7daa53b5094c7e02b6ac6a609df66d3a720ad4f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Jun 2019 23:55:17 -0700 Subject: [PATCH 51/69] Move webgl renderer into addon dir --- addons/xterm-addon-webgl/.gitignore | 2 + addons/xterm-addon-webgl/.npmignore | 4 ++ addons/xterm-addon-webgl/LICENSE | 19 ++++++ addons/xterm-addon-webgl/package.json | 20 ++++++ .../xterm-addon-webgl/src}/ColorUtils.ts | 0 .../xterm-addon-webgl/src}/GlyphRenderer.ts | 2 +- .../xterm-addon-webgl/src}/Platform.ts | 0 .../src}/RectangleRenderer.ts | 2 +- .../xterm-addon-webgl/src}/RenderModel.ts | 0 .../xterm-addon-webgl/src}/Types.ts | 0 .../src}/WebglRenderer.api.ts | 2 +- .../xterm-addon-webgl/src}/WebglRenderer.ts | 2 +- .../src}/WebglRendererAddon.ts | 0 .../xterm-addon-webgl/src}/WebglUtils.ts | 0 .../src}/atlas/BaseCharAtlas.ts | 0 .../src}/atlas/CharAtlasCache.ts | 0 .../src}/atlas/CharAtlasUtils.ts | 2 +- .../xterm-addon-webgl/src}/atlas/Types.ts | 0 .../src}/atlas/WebglCharAtlas.ts | 2 +- .../src}/renderLayer/BaseRenderLayer.ts | 4 +- .../src}/renderLayer/CursorRenderLayer.ts | 4 +- .../src}/renderLayer/LinkRenderLayer.ts | 2 +- .../src}/renderLayer/Types.ts | 0 addons/xterm-addon-webgl/src/tsconfig.json | 27 ++++++++ .../typings/xterm-addon-webgl.d.ts | 68 +++++++++++++++++++ addons/xterm-addon-webgl/webpack.config.js | 31 +++++++++ demo/client.ts | 3 + src/public/Terminal.ts | 4 -- tsconfig.all.json | 1 + 29 files changed, 186 insertions(+), 15 deletions(-) create mode 100644 addons/xterm-addon-webgl/.gitignore create mode 100644 addons/xterm-addon-webgl/.npmignore create mode 100644 addons/xterm-addon-webgl/LICENSE create mode 100644 addons/xterm-addon-webgl/package.json rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/ColorUtils.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/GlyphRenderer.ts (99%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/Platform.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/RectangleRenderer.ts (99%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/RenderModel.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/Types.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/WebglRenderer.api.ts (99%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/WebglRenderer.ts (99%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/WebglRendererAddon.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/WebglUtils.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/atlas/BaseCharAtlas.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/atlas/CharAtlasCache.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/atlas/CharAtlasUtils.ts (96%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/atlas/Types.ts (100%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/atlas/WebglCharAtlas.ts (99%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/renderLayer/BaseRenderLayer.ts (99%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/renderLayer/CursorRenderLayer.ts (99%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/renderLayer/LinkRenderLayer.ts (96%) rename {src/renderer/webgl => addons/xterm-addon-webgl/src}/renderLayer/Types.ts (100%) create mode 100644 addons/xterm-addon-webgl/src/tsconfig.json create mode 100644 addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts create mode 100644 addons/xterm-addon-webgl/webpack.config.js diff --git a/addons/xterm-addon-webgl/.gitignore b/addons/xterm-addon-webgl/.gitignore new file mode 100644 index 00000000..a9f4ed54 --- /dev/null +++ b/addons/xterm-addon-webgl/.gitignore @@ -0,0 +1,2 @@ +lib +node_modules \ No newline at end of file diff --git a/addons/xterm-addon-webgl/.npmignore b/addons/xterm-addon-webgl/.npmignore new file mode 100644 index 00000000..e8fc8237 --- /dev/null +++ b/addons/xterm-addon-webgl/.npmignore @@ -0,0 +1,4 @@ +**/*.api.js +**/*.api.ts +tsconfig.json +.yarnrc diff --git a/addons/xterm-addon-webgl/LICENSE b/addons/xterm-addon-webgl/LICENSE new file mode 100644 index 00000000..b9dc26fe --- /dev/null +++ b/addons/xterm-addon-webgl/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2018, The xterm.js authors (https://github.com/xtermjs/xterm.js) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/addons/xterm-addon-webgl/package.json b/addons/xterm-addon-webgl/package.json new file mode 100644 index 00000000..34c678dd --- /dev/null +++ b/addons/xterm-addon-webgl/package.json @@ -0,0 +1,20 @@ +{ + "name": "xterm-addon-webgl", + "version": "0.1.0-beta6", + "author": { + "name": "The xterm.js authors", + "url": "https://xtermjs.org/" + }, + "main": "lib/xterm-addon-webgl.js", + "types": "typings/xterm-addon-webgl.d.ts", + "license": "MIT", + "scripts": { + "build": "../../node_modules/.bin/tsc -p src", + "prepackage": "npm run build", + "package": "../../node_modules/.bin/webpack", + "prepublishOnly": "npm run package" + }, + "peerDependencies": { + "xterm": "^3.14.0" + } +} diff --git a/src/renderer/webgl/ColorUtils.ts b/addons/xterm-addon-webgl/src/ColorUtils.ts similarity index 100% rename from src/renderer/webgl/ColorUtils.ts rename to addons/xterm-addon-webgl/src/ColorUtils.ts diff --git a/src/renderer/webgl/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts similarity index 99% rename from src/renderer/webgl/GlyphRenderer.ts rename to addons/xterm-addon-webgl/src/GlyphRenderer.ts index 768060e4..50357b28 100644 --- a/src/renderer/webgl/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -9,7 +9,7 @@ import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRaster import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { fill, slice } from 'common/TypedArrayUtils'; -import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from '../../common/buffer/Constants'; +import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { getLuminance } from './ColorUtils'; import { Terminal, IBufferLine } from 'xterm'; import { IColorSet } from 'browser/Types'; diff --git a/src/renderer/webgl/Platform.ts b/addons/xterm-addon-webgl/src/Platform.ts similarity index 100% rename from src/renderer/webgl/Platform.ts rename to addons/xterm-addon-webgl/src/Platform.ts diff --git a/src/renderer/webgl/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts similarity index 99% rename from src/renderer/webgl/RectangleRenderer.ts rename to addons/xterm-addon-webgl/src/RectangleRenderer.ts index 54fddc7d..401f4568 100644 --- a/src/renderer/webgl/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -8,7 +8,7 @@ import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelect import { fill } from 'common/TypedArrayUtils'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { is256Color } from './atlas/CharAtlasUtils'; -import { DEFAULT_COLOR } from '../../common/buffer/Constants'; +import { DEFAULT_COLOR } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColorSet, IColor } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; diff --git a/src/renderer/webgl/RenderModel.ts b/addons/xterm-addon-webgl/src/RenderModel.ts similarity index 100% rename from src/renderer/webgl/RenderModel.ts rename to addons/xterm-addon-webgl/src/RenderModel.ts diff --git a/src/renderer/webgl/Types.ts b/addons/xterm-addon-webgl/src/Types.ts similarity index 100% rename from src/renderer/webgl/Types.ts rename to addons/xterm-addon-webgl/src/Types.ts diff --git a/src/renderer/webgl/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts similarity index 99% rename from src/renderer/webgl/WebglRenderer.api.ts rename to addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 62a4c7cf..14c27719 100644 --- a/src/renderer/webgl/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -5,7 +5,7 @@ import * as puppeteer from 'puppeteer'; import { assert } from 'chai'; -import { ITerminalOptions } from '../../Types'; +import { ITerminalOptions } from '../../../src/Types'; const APP = 'http://127.0.0.1:3000/test'; diff --git a/src/renderer/webgl/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts similarity index 99% rename from src/renderer/webgl/WebglRenderer.ts rename to addons/xterm-addon-webgl/src/WebglRenderer.ts index 8a310abc..a743bfd9 100644 --- a/src/renderer/webgl/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ITerminal } from '../../Types'; +import { ITerminal } from '../../../src/Types'; import { GlyphRenderer } from './GlyphRenderer'; import { LinkRenderLayer } from './renderLayer/LinkRenderLayer'; import { CursorRenderLayer } from './renderLayer/CursorRenderLayer'; diff --git a/src/renderer/webgl/WebglRendererAddon.ts b/addons/xterm-addon-webgl/src/WebglRendererAddon.ts similarity index 100% rename from src/renderer/webgl/WebglRendererAddon.ts rename to addons/xterm-addon-webgl/src/WebglRendererAddon.ts diff --git a/src/renderer/webgl/WebglUtils.ts b/addons/xterm-addon-webgl/src/WebglUtils.ts similarity index 100% rename from src/renderer/webgl/WebglUtils.ts rename to addons/xterm-addon-webgl/src/WebglUtils.ts diff --git a/src/renderer/webgl/atlas/BaseCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/BaseCharAtlas.ts similarity index 100% rename from src/renderer/webgl/atlas/BaseCharAtlas.ts rename to addons/xterm-addon-webgl/src/atlas/BaseCharAtlas.ts diff --git a/src/renderer/webgl/atlas/CharAtlasCache.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts similarity index 100% rename from src/renderer/webgl/atlas/CharAtlasCache.ts rename to addons/xterm-addon-webgl/src/atlas/CharAtlasCache.ts diff --git a/src/renderer/webgl/atlas/CharAtlasUtils.ts b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts similarity index 96% rename from src/renderer/webgl/atlas/CharAtlasUtils.ts rename to addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts index 0554746d..1c43dd70 100644 --- a/src/renderer/webgl/atlas/CharAtlasUtils.ts +++ b/addons/xterm-addon-webgl/src/atlas/CharAtlasUtils.ts @@ -4,7 +4,7 @@ */ import { ICharAtlasConfig } from './Types'; -import { DEFAULT_COLOR } from '../../../common/buffer/Constants'; +import { DEFAULT_COLOR } from 'common/buffer/Constants'; import { Terminal, FontWeight } from 'xterm'; import { IColorSet } from 'browser/Types'; diff --git a/src/renderer/webgl/atlas/Types.ts b/addons/xterm-addon-webgl/src/atlas/Types.ts similarity index 100% rename from src/renderer/webgl/atlas/Types.ts rename to addons/xterm-addon-webgl/src/atlas/Types.ts diff --git a/src/renderer/webgl/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts similarity index 99% rename from src/renderer/webgl/atlas/WebglCharAtlas.ts rename to addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 48ec0bf4..9a833fdf 100644 --- a/src/renderer/webgl/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -6,7 +6,7 @@ import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from './Types'; import { BaseCharAtlas } from './BaseCharAtlas'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; -import { FLAGS, DEFAULT_COLOR, DEFAULT_ATTR } from '../../../common/buffer/Constants'; +import { FLAGS, DEFAULT_COLOR, DEFAULT_ATTR } from 'common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; import { IColor } from 'browser/Types'; diff --git a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts similarity index 99% rename from src/renderer/webgl/renderLayer/BaseRenderLayer.ts rename to addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 0604da8f..263a0d21 100644 --- a/src/renderer/webgl/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -4,8 +4,8 @@ */ import { IRenderLayer } from './Types'; -import { ICellData } from '../../../common/Types'; -import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from '../../../common/buffer/Constants'; +import { ICellData } from 'common/Types'; +import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from '../atlas/Types'; import { BaseCharAtlas } from '../atlas/BaseCharAtlas'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; diff --git a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts similarity index 99% rename from src/renderer/webgl/renderLayer/CursorRenderLayer.ts rename to addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts index 3f24c6f0..b0bf581f 100644 --- a/src/renderer/webgl/renderLayer/CursorRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/CursorRenderLayer.ts @@ -5,8 +5,8 @@ import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { ICellData } from '../../../common/Types'; -import { CellData } from '../../../common/buffer/CellData'; +import { ICellData } from 'common/Types'; +import { CellData } from 'common/buffer/CellData'; import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; diff --git a/src/renderer/webgl/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts similarity index 96% rename from src/renderer/webgl/renderLayer/LinkRenderLayer.ts rename to addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index 50a3e111..1ae22cf6 100644 --- a/src/renderer/webgl/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ILinkifierEvent, ILinkifierAccessor } from '../../../Types'; +import { ILinkifierEvent, ILinkifierAccessor } from '../../../../src/Types'; import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; diff --git a/src/renderer/webgl/renderLayer/Types.ts b/addons/xterm-addon-webgl/src/renderLayer/Types.ts similarity index 100% rename from src/renderer/webgl/renderLayer/Types.ts rename to addons/xterm-addon-webgl/src/renderLayer/Types.ts diff --git a/addons/xterm-addon-webgl/src/tsconfig.json b/addons/xterm-addon-webgl/src/tsconfig.json new file mode 100644 index 00000000..34149159 --- /dev/null +++ b/addons/xterm-addon-webgl/src/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "module": "commonjs", + "target": "es5", + "lib": [ + "dom", + "es6", + ], + "rootDir": ".", + "outDir": "../out", + "sourceMap": true, + "removeComments": true, + "baseUrl": ".", + "paths": { + "common/*": [ "../../../src/common/*" ], + "browser/*": [ "../../../src/browser/*" ] + } + }, + "include": [ + "./**/*", + "../../../typings/xterm.d.ts" + ], + "references": [ + { "path": "../../../src/common" }, + { "path": "../../../src/browser" } + ] +} diff --git a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts new file mode 100644 index 00000000..94bc5297 --- /dev/null +++ b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { Terminal, ILinkMatcherOptions, IDisposable, ITerminalAddon } from 'xterm'; + +declare module 'xterm-addon-search' { + /** + * Options for a search. + */ + export interface ISearchOptions { + /** + * Whether the search term is a regex. + */ + regex?: boolean; + + /** + * Whether to search for a whole word, the result is only valid if it's + * suppounded in "non-word" characters such as `_`, `(`, `)` or space. + */ + wholeWord?: boolean; + + /** + * Whether the search is case sensitive. + */ + caseSensitive?: boolean; + + /** + * Whether to do an indcremental search, this will expand the selection if it + * still matches the term the user typed. Note that this only affects + * `findNext`, not `findPrevious`. + */ + incremental?: boolean; + } + + /** + * An xterm.js addon that provides search functionality. + */ + export class SearchAddon implements ITerminalAddon { + /** + * Activates the addon + * @param terminal The terminal the addon is being loaded in. + */ + public activate(terminal: Terminal): void; + + /** + * Disposes the addon. + */ + public dispose(): void; + + /** + * Search forwards for the next result that matches the search term and + * options. + * @param term The search term. + * @param searchOptions The options for the search. + */ + public findNext(term: string, searchOptions?: ISearchOptions): boolean; + + /** + * Search backwards for the previous result that matches the search term and + * options. + * @param term The search term. + * @param searchOptions The options for the search. + */ + public findPrevious(term: string, searchOptions?: ISearchOptions): boolean; + } +} diff --git a/addons/xterm-addon-webgl/webpack.config.js b/addons/xterm-addon-webgl/webpack.config.js new file mode 100644 index 00000000..825f6734 --- /dev/null +++ b/addons/xterm-addon-webgl/webpack.config.js @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +const path = require('path'); + +const addonName = 'WebglAddon'; +const mainFile = 'xterm-addon-weblg.js'; + +module.exports = { + entry: `./out/${addonName}.js`, + devtool: 'source-map', + module: { + rules: [ + { + test: /\.js$/, + use: ["source-map-loader"], + enforce: "pre", + exclude: /node_modules/ + } + ] + }, + output: { + filename: mainFile, + path: path.resolve('./lib'), + library: addonName, + libraryTarget: 'umd' + }, + mode: 'production' +}; diff --git a/demo/client.ts b/demo/client.ts index e0d55d57..03d1bb73 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -13,6 +13,7 @@ import { AttachAddon } from '../addons/xterm-addon-attach/out/AttachAddon'; import { FitAddon } from '../addons/xterm-addon-fit/out/FitAddon'; import { SearchAddon, ISearchOptions } from '../addons/xterm-addon-search/out/SearchAddon'; import { WebLinksAddon } from '../addons/xterm-addon-web-links/out/WebLinksAddon'; +import { WebglRendererAddon } from '../addons/xterm-addon-webgl/out/WebglRendererAddon'; // Use webpacked version (yarn package) // import { Terminal } from '../lib/xterm'; @@ -43,6 +44,8 @@ let socketURL; let socket; let pid; +(window).webgl = () => term.loadAddon(new WebglRendererAddon()); + const terminalContainer = document.getElementById('terminal-container'); const actionElements = { findNext: document.querySelector('#find-next'), diff --git a/src/public/Terminal.ts b/src/public/Terminal.ts index 23869218..0dae4def 100644 --- a/src/public/Terminal.ts +++ b/src/public/Terminal.ts @@ -11,7 +11,6 @@ import { Terminal as TerminalCore } from '../Terminal'; import * as Strings from '../browser/LocalizableStrings'; import { IEvent } from 'common/EventEmitter'; import { AddonManager } from './AddonManager'; -import { WebglRendererAddon } from '../renderer/webgl/WebglRendererAddon'; export class Terminal implements ITerminalApi { private _core: ITerminal; @@ -161,9 +160,6 @@ export class Terminal implements ITerminalApi { public loadAddon(addon: ITerminalAddon): void { return this._addonManager.loadAddon(this, addon); } - public loadWebgl(preserveDrawingBuffer?: boolean): void { - this.loadAddon(new WebglRendererAddon(preserveDrawingBuffer)); - } public static get strings(): ILocalizableStrings { return Strings; } diff --git a/tsconfig.all.json b/tsconfig.all.json index d2670811..bc633fe6 100644 --- a/tsconfig.all.json +++ b/tsconfig.all.json @@ -8,5 +8,6 @@ { "path": "./addons/xterm-addon-fit/src" }, { "path": "./addons/xterm-addon-search/src" }, { "path": "./addons/xterm-addon-web-links/src" } + { "path": "./addons/xterm-addon-webgl/src" } ] } From 50d6711fc8bb527f65fff453f6ed154ab6f025b3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 00:01:00 -0700 Subject: [PATCH 52/69] Fix webgl packaging --- .../src/{WebglRendererAddon.ts => WebglAddon.ts} | 2 +- addons/xterm-addon-webgl/webpack.config.js | 10 +++++++++- demo/client.ts | 5 +++-- 3 files changed, 13 insertions(+), 4 deletions(-) rename addons/xterm-addon-webgl/src/{WebglRendererAddon.ts => WebglAddon.ts} (93%) diff --git a/addons/xterm-addon-webgl/src/WebglRendererAddon.ts b/addons/xterm-addon-webgl/src/WebglAddon.ts similarity index 93% rename from addons/xterm-addon-webgl/src/WebglRendererAddon.ts rename to addons/xterm-addon-webgl/src/WebglAddon.ts index 4b67e380..0074f9b2 100644 --- a/addons/xterm-addon-webgl/src/WebglRendererAddon.ts +++ b/addons/xterm-addon-webgl/src/WebglAddon.ts @@ -8,7 +8,7 @@ import { WebglRenderer } from './WebglRenderer'; import { IRenderService } from 'browser/services/Services'; import { IColorSet } from 'browser/Types'; -export class WebglRendererAddon implements ITerminalAddon { +export class WebglAddon implements ITerminalAddon { constructor( private _preserveDrawingBuffer?: boolean ) {} diff --git a/addons/xterm-addon-webgl/webpack.config.js b/addons/xterm-addon-webgl/webpack.config.js index 825f6734..578b1102 100644 --- a/addons/xterm-addon-webgl/webpack.config.js +++ b/addons/xterm-addon-webgl/webpack.config.js @@ -6,7 +6,7 @@ const path = require('path'); const addonName = 'WebglAddon'; -const mainFile = 'xterm-addon-weblg.js'; +const mainFile = 'xterm-addon-webgl.js'; module.exports = { entry: `./out/${addonName}.js`, @@ -21,6 +21,14 @@ module.exports = { } ] }, + resolve: { + modules: ['./node_modules'], + extensions: [ '.js' ], + alias: { + common: path.resolve('../../out/common'), + browser: path.resolve('../../out/browser') + } + }, output: { filename: mainFile, path: path.resolve('./lib'), diff --git a/demo/client.ts b/demo/client.ts index 03d1bb73..83d9b841 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -13,7 +13,7 @@ import { AttachAddon } from '../addons/xterm-addon-attach/out/AttachAddon'; import { FitAddon } from '../addons/xterm-addon-fit/out/FitAddon'; import { SearchAddon, ISearchOptions } from '../addons/xterm-addon-search/out/SearchAddon'; import { WebLinksAddon } from '../addons/xterm-addon-web-links/out/WebLinksAddon'; -import { WebglRendererAddon } from '../addons/xterm-addon-webgl/out/WebglRendererAddon'; +import { WebglAddon } from '../addons/xterm-addon-webgl/out/WebglAddon'; // Use webpacked version (yarn package) // import { Terminal } from '../lib/xterm'; @@ -21,6 +21,7 @@ import { WebglRendererAddon } from '../addons/xterm-addon-webgl/out/WebglRendere // import { FitAddon } from 'xterm-addon-fit'; // import { SearchAddon, ISearchOptions } from 'xterm-addon-search'; // import { WebLinksAddon } from 'xterm-addon-web-links'; +// import { WebglAddon } from 'xterm-addon-webgl'; // Pulling in the module's types relies on the above, it's looks a // little weird here as we're importing "this" module @@ -44,7 +45,7 @@ let socketURL; let socket; let pid; -(window).webgl = () => term.loadAddon(new WebglRendererAddon()); +(window).webgl = () => term.loadAddon(new WebglAddon()); const terminalContainer = document.getElementById('terminal-container'); const actionElements = { From eae0ab9e026f5ef9d40c1184bc163473d02d8cc9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 00:04:02 -0700 Subject: [PATCH 53/69] Add a webgl button to the demo --- demo/client.ts | 3 +-- demo/index.html | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/client.ts b/demo/client.ts index 83d9b841..ca0fb1da 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -45,8 +45,6 @@ let socketURL; let socket; let pid; -(window).webgl = () => term.loadAddon(new WebglAddon()); - const terminalContainer = document.getElementById('terminal-container'); const actionElements = { findNext: document.querySelector('#find-next'), @@ -91,6 +89,7 @@ if (document.location.pathname === '/test') { } else { createTerminal(); document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler); + document.getElementById('webgl').addEventListener('click', () => term.loadAddon(new WebglAddon())); } function createTerminal(): void { diff --git a/demo/index.html b/demo/index.html index 7a939da5..28ce91ce 100644 --- a/demo/index.html +++ b/demo/index.html @@ -36,6 +36,7 @@

Attention: The demo is a barebones implementation and is designed for the development and evaluation of xterm.js only. Exposing the demo to the public as is would introduce security risks for the host.

+ From d272422c0bc53daf2cb5ba4c625d2110a439adb1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 00:06:04 -0700 Subject: [PATCH 54/69] Fix webgl addon version --- addons/xterm-addon-webgl/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/xterm-addon-webgl/package.json b/addons/xterm-addon-webgl/package.json index 34c678dd..7ba43b1d 100644 --- a/addons/xterm-addon-webgl/package.json +++ b/addons/xterm-addon-webgl/package.json @@ -1,6 +1,6 @@ { "name": "xterm-addon-webgl", - "version": "0.1.0-beta6", + "version": "0.1.0-beta1", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" From 765297e67cf599cd3a4d1d4ff56dd23e8fa6bbbf Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 00:11:34 -0700 Subject: [PATCH 55/69] Add addon typings --- .../typings/xterm-addon-webgl.d.ts | 52 ++----------------- 1 file changed, 5 insertions(+), 47 deletions(-) diff --git a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts index 94bc5297..5199a260 100644 --- a/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts +++ b/addons/xterm-addon-webgl/typings/xterm-addon-webgl.d.ts @@ -3,41 +3,15 @@ * @license MIT */ -import { Terminal, ILinkMatcherOptions, IDisposable, ITerminalAddon } from 'xterm'; - -declare module 'xterm-addon-search' { - /** - * Options for a search. - */ - export interface ISearchOptions { - /** - * Whether the search term is a regex. - */ - regex?: boolean; - - /** - * Whether to search for a whole word, the result is only valid if it's - * suppounded in "non-word" characters such as `_`, `(`, `)` or space. - */ - wholeWord?: boolean; - - /** - * Whether the search is case sensitive. - */ - caseSensitive?: boolean; - - /** - * Whether to do an indcremental search, this will expand the selection if it - * still matches the term the user typed. Note that this only affects - * `findNext`, not `findPrevious`. - */ - incremental?: boolean; - } +import { Terminal, IDisposable, ITerminalAddon } from 'xterm'; +declare module 'xterm-addon-webgl' { /** * An xterm.js addon that provides search functionality. */ - export class SearchAddon implements ITerminalAddon { + export class WebglAddon implements ITerminalAddon { + constructor(preserveDrawingBuffer?: boolean); + /** * Activates the addon * @param terminal The terminal the addon is being loaded in. @@ -48,21 +22,5 @@ declare module 'xterm-addon-search' { * Disposes the addon. */ public dispose(): void; - - /** - * Search forwards for the next result that matches the search term and - * options. - * @param term The search term. - * @param searchOptions The options for the search. - */ - public findNext(term: string, searchOptions?: ISearchOptions): boolean; - - /** - * Search backwards for the previous result that matches the search term and - * options. - * @param term The search term. - * @param searchOptions The options for the search. - */ - public findPrevious(term: string, searchOptions?: ISearchOptions): boolean; } } From dd9b7553292c0bf535f54a3f48f85ecc2308952b Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 00:17:48 -0700 Subject: [PATCH 56/69] Fix webgl api tests --- addons/xterm-addon-webgl/src/WebglRenderer.api.ts | 8 ++++---- demo/client.ts | 2 ++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 14c27719..864a66ff 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -51,7 +51,7 @@ describe('WebGL Renderer Integration Tests', () => { } }); await writeSync(`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█`); - await page.evaluate(`window.term.loadWebgl(true);`); + await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); @@ -78,7 +78,7 @@ describe('WebGL Renderer Integration Tests', () => { } }); await writeSync(`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`); - await page.evaluate(`window.term.loadWebgl(true);`); + await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); @@ -105,7 +105,7 @@ describe('WebGL Renderer Integration Tests', () => { } }); await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); - await page.evaluate(`window.term.loadWebgl(true);`); + await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); @@ -132,7 +132,7 @@ describe('WebGL Renderer Integration Tests', () => { } }); await writeSync(`\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `); - await page.evaluate(`window.term.loadWebgl(true);`); + await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); diff --git a/demo/client.ts b/demo/client.ts index ca0fb1da..005d171d 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -34,6 +34,7 @@ export interface IWindowWithTerminal extends Window { FitAddon?: typeof FitAddon; SearchAddon?: typeof SearchAddon; WebLinksAddon?: typeof WebLinksAddon; + WebglAddon?: typeof WebglAddon; } declare let window: IWindowWithTerminal; @@ -86,6 +87,7 @@ if (document.location.pathname === '/test') { window.FitAddon = FitAddon; window.SearchAddon = SearchAddon; window.WebLinksAddon = WebLinksAddon; + window.WebglAddon = WebglAddon; } else { createTerminal(); document.getElementById('dispose').addEventListener('click', disposeRecreateButtonHandler); From 15647a030164b8fe7021bf5e20ea86c41661fc8e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 00:27:12 -0700 Subject: [PATCH 57/69] Speed up webgl tests --- .../src/WebglRenderer.api.ts | 124 ++++++++---------- 1 file changed, 52 insertions(+), 72 deletions(-) diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts index 864a66ff..b26790c4 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.api.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.api.ts @@ -6,6 +6,7 @@ import * as puppeteer from 'puppeteer'; import { assert } from 'chai'; import { ITerminalOptions } from '../../../src/Types'; +import { ITheme } from 'xterm'; const APP = 'http://127.0.0.1:3000/test'; @@ -14,9 +15,10 @@ let page: puppeteer.Page; const width = 800; const height = 600; -describe('WebGL Renderer Integration Tests', () => { +describe('WebGL Renderer Integration Tests', function(): void { + this.timeout(20000); + before(async function(): Promise { - this.timeout(10000); browser = await puppeteer.launch({ headless: process.argv.indexOf('--headless') !== -1, slowMo: 80, @@ -24,6 +26,9 @@ describe('WebGL Renderer Integration Tests', () => { }); page = (await browser.pages())[0]; await page.setViewport({ width, height }); + await page.goto(APP); + await openTerminal(); + await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); }); after(() => { @@ -31,27 +36,23 @@ describe('WebGL Renderer Integration Tests', () => { }); beforeEach(async () => { - await page.goto(APP); + await page.evaluate(`window.term.reset()`); }); describe('WebGL Renderer', () => { it('foreground colors normal', async function(): Promise { - this.timeout(10000); - await openTerminal({ - rendererType: 'dom', - theme: { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - } - }); + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); await writeSync(`\\x1b[30m█\\x1b[31m█\\x1b[32m█\\x1b[33m█\\x1b[34m█\\x1b[35m█\\x1b[36m█\\x1b[37m█`); - await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); @@ -63,22 +64,18 @@ describe('WebGL Renderer Integration Tests', () => { }); it('foreground colors bright', async function(): Promise { - this.timeout(10000); - await openTerminal({ - rendererType: 'dom', - theme: { - brightBlack: '#010203', - brightRed: '#040506', - brightGreen: '#070809', - brightYellow: '#0a0b0c', - brightBlue: '#0d0e0f', - brightMagenta: '#101112', - brightCyan: '#131415', - brightWhite: '#161718' - } - }); + const theme: ITheme = { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); await writeSync(`\\x1b[90m█\\x1b[91m█\\x1b[92m█\\x1b[93m█\\x1b[94m█\\x1b[95m█\\x1b[96m█\\x1b[97m█`); - await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); @@ -90,22 +87,18 @@ describe('WebGL Renderer Integration Tests', () => { }); it('background colors normal', async function(): Promise { - this.timeout(10000); - await openTerminal({ - rendererType: 'dom', - theme: { - black: '#010203', - red: '#040506', - green: '#070809', - yellow: '#0a0b0c', - blue: '#0d0e0f', - magenta: '#101112', - cyan: '#131415', - white: '#161718' - } - }); + const theme: ITheme = { + black: '#010203', + red: '#040506', + green: '#070809', + yellow: '#0a0b0c', + blue: '#0d0e0f', + magenta: '#101112', + cyan: '#131415', + white: '#161718' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); await writeSync(`\\x1b[40m \\x1b[41m \\x1b[42m \\x1b[43m \\x1b[44m \\x1b[45m \\x1b[46m \\x1b[47m `); - await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); @@ -117,22 +110,18 @@ describe('WebGL Renderer Integration Tests', () => { }); it('background colors bright', async function(): Promise { - this.timeout(10000); - await openTerminal({ - rendererType: 'dom', - theme: { - brightBlack: '#010203', - brightRed: '#040506', - brightGreen: '#070809', - brightYellow: '#0a0b0c', - brightBlue: '#0d0e0f', - brightMagenta: '#101112', - brightCyan: '#131415', - brightWhite: '#161718' - } - }); + const theme: ITheme = { + brightBlack: '#010203', + brightRed: '#040506', + brightGreen: '#070809', + brightYellow: '#0a0b0c', + brightBlue: '#0d0e0f', + brightMagenta: '#101112', + brightCyan: '#131415', + brightWhite: '#161718' + }; + await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`); await writeSync(`\\x1b[100m \\x1b[101m \\x1b[102m \\x1b[103m \\x1b[104m \\x1b[105m \\x1b[106m \\x1b[107m `); - await page.evaluate(`window.term.loadAddon(new WebglAddon(true));`); assert.deepEqual(await getCellColor(1, 1), [1, 2, 3, 255]); assert.deepEqual(await getCellColor(2, 1), [4, 5, 6, 255]); assert.deepEqual(await getCellColor(3, 1), [7, 8, 9, 255]); @@ -164,15 +153,6 @@ async function writeSync(data: string): Promise { } } -// async function getPixelAt(x: number, y: number): Promise { -// await page.evaluate(` -// window.gl = window.term._core._renderService._renderer._gl; -// window.result = new Uint8Array(4); -// window.gl.readPixels(${x}, window.gl.drawingBufferHeight - 1 - ${y}, 1, 1, window.gl.RGBA, window.gl.UNSIGNED_BYTE, window.result); -// `); -// return await page.evaluate(`Array.from(window.result)`); -// } - async function getCellColor(col: number, row: number): Promise { await page.evaluate(` window.gl = window.term._core._renderService._renderer._gl; From 07b6b69be786c49dec168f5fed27fe3ff6ac03af Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 00:30:45 -0700 Subject: [PATCH 58/69] Remove old comment --- addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts | 1 - src/renderer/LinkRenderLayer.ts | 1 - 2 files changed, 2 deletions(-) diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index 1ae22cf6..c942870f 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -16,7 +16,6 @@ export class LinkRenderLayer extends BaseRenderLayer { constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) { super(container, 'link', zIndex, true, colors); - // TODO: Need to expose link-related renderer API terminal.linkifier.onLinkHover(e => this._onLinkHover(e)); terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e)); } diff --git a/src/renderer/LinkRenderLayer.ts b/src/renderer/LinkRenderLayer.ts index e56a10af..99eff4a2 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/renderer/LinkRenderLayer.ts @@ -15,7 +15,6 @@ export class LinkRenderLayer extends BaseRenderLayer { constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) { super(container, 'link', zIndex, true, colors); - // TODO: Need to expose link-related renderer API terminal.linkifier.onLinkHover(e => this._onLinkHover(e)); terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e)); } From 04ba3510e4d1c0bff3484499137d0e185eee016f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 14:37:44 -0700 Subject: [PATCH 59/69] Adopt automated released in webgl addon --- addons/xterm-addon-webgl/.npmignore | 1 + addons/xterm-addon-webgl/package.json | 2 +- bin/publish.js | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/addons/xterm-addon-webgl/.npmignore b/addons/xterm-addon-webgl/.npmignore index e8fc8237..1c794445 100644 --- a/addons/xterm-addon-webgl/.npmignore +++ b/addons/xterm-addon-webgl/.npmignore @@ -2,3 +2,4 @@ **/*.api.ts tsconfig.json .yarnrc +webpack.config.js diff --git a/addons/xterm-addon-webgl/package.json b/addons/xterm-addon-webgl/package.json index 7ba43b1d..e4f34147 100644 --- a/addons/xterm-addon-webgl/package.json +++ b/addons/xterm-addon-webgl/package.json @@ -1,6 +1,6 @@ { "name": "xterm-addon-webgl", - "version": "0.1.0-beta1", + "version": "0.1.0", "author": { "name": "The xterm.js authors", "url": "https://xtermjs.org/" diff --git a/bin/publish.js b/bin/publish.js index e03fa6ab..0610d116 100644 --- a/bin/publish.js +++ b/bin/publish.js @@ -27,7 +27,8 @@ const addonPackageDirs = [ path.resolve(__dirname, '../addons/xterm-addon-attach'), path.resolve(__dirname, '../addons/xterm-addon-fit'), path.resolve(__dirname, '../addons/xterm-addon-search'), - path.resolve(__dirname, '../addons/xterm-addon-web-links') + path.resolve(__dirname, '../addons/xterm-addon-web-links'), + path.resolve(__dirname, '../addons/xterm-addon-webgl') ]; addonPackageDirs.forEach(p => { const addon = path.basename(p); From 6e04058783e02a7d358d682429ddd7919d7a7b69 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 14:47:44 -0700 Subject: [PATCH 60/69] Reduce diff --- demo/index.html | 2 +- src/Types.d.ts | 1 - src/browser/ColorManager.ts | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/demo/index.html b/demo/index.html index 28ce91ce..99ae413d 100644 --- a/demo/index.html +++ b/demo/index.html @@ -36,7 +36,7 @@

Attention: The demo is a barebones implementation and is designed for the development and evaluation of xterm.js only. Exposing the demo to the public as is would introduce security risks for the host.

- + diff --git a/src/Types.d.ts b/src/Types.d.ts index 9a08a7a1..6f587931 100644 --- a/src/Types.d.ts +++ b/src/Types.d.ts @@ -299,7 +299,6 @@ export interface ITerminalOptions extends IPublicTerminalOptions { export interface ISelectionManager { selectionText: string; - hasSelection: boolean; selectionStart: [number, number]; selectionEnd: [number, number]; diff --git a/src/browser/ColorManager.ts b/src/browser/ColorManager.ts index 4ac8fefe..70d21a7a 100644 --- a/src/browser/ColorManager.ts +++ b/src/browser/ColorManager.ts @@ -17,7 +17,7 @@ const DEFAULT_SELECTION = { // An IIFE to generate DEFAULT_ANSI_COLORS. Do not mutate DEFAULT_ANSI_COLORS, instead make a copy // and mutate that. -export const DEFAULT_ANSI_COLORS: IColor[] = (() => { +export const DEFAULT_ANSI_COLORS = (() => { const colors = [ // dark: fromHex('#2e3436'), From 1ebae41a8b1912b9346523616566a16387adff75 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 16:35:38 -0700 Subject: [PATCH 61/69] Pull attribute compat stuff into addon --- .../xterm-addon-webgl/src/CharDataCompat.ts | 28 +++++++++++++++++++ addons/xterm-addon-webgl/src/Constants.ts | 15 ++++++++++ addons/xterm-addon-webgl/src/WebglRenderer.ts | 6 ++-- .../src/atlas/WebglCharAtlas.ts | 3 +- src/common/buffer/BufferLine.ts | 21 ++------------ src/common/buffer/Constants.ts | 11 -------- 6 files changed, 51 insertions(+), 33 deletions(-) create mode 100644 addons/xterm-addon-webgl/src/CharDataCompat.ts create mode 100644 addons/xterm-addon-webgl/src/Constants.ts diff --git a/addons/xterm-addon-webgl/src/CharDataCompat.ts b/addons/xterm-addon-webgl/src/CharDataCompat.ts new file mode 100644 index 00000000..13613c88 --- /dev/null +++ b/addons/xterm-addon-webgl/src/CharDataCompat.ts @@ -0,0 +1,28 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { CellData } from 'common/buffer/CellData'; +import { FLAGS } from './Constants'; +import { IBufferLine } from 'common/Types'; + +export function getCompatAttr(bufferLine: IBufferLine, index: number): number { + // TODO: Need to move WebGL over to the new system and remove this block + const cell = new CellData(); + bufferLine.loadCell(index, cell); + const oldBg = cell.getBgColor() === -1 ? 256 : cell.getBgColor(); + const oldFg = cell.getFgColor() === -1 ? 256 : cell.getFgColor(); + const oldAttr = + (cell.isBold() ? FLAGS.BOLD : 0) | + (cell.isUnderline() ? FLAGS.UNDERLINE : 0) | + (cell.isBlink() ? FLAGS.BLINK : 0) | + (cell.isInverse() ? FLAGS.INVERSE : 0) | + (cell.isDim() ? FLAGS.DIM : 0) | + (cell.isItalic() ? FLAGS.ITALIC : 0); + const attrCompat = + oldBg | + (oldFg << 9) | + (oldAttr << 18); + return attrCompat; +} diff --git a/addons/xterm-addon-webgl/src/Constants.ts b/addons/xterm-addon-webgl/src/Constants.ts new file mode 100644 index 00000000..ab18b97a --- /dev/null +++ b/addons/xterm-addon-webgl/src/Constants.ts @@ -0,0 +1,15 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +// TODO: Should be removed after chardata workaround is fixed +export const enum FLAGS { + BOLD = 1, + UNDERLINE = 2, + BLINK = 4, + INVERSE = 8, + INVISIBLE = 16, + DIM = 32, + ITALIC = 64 +} diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index a743bfd9..0d8cdf45 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -14,12 +14,14 @@ import { IWebGL2RenderingContext } from './Types'; import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; -import { DEFAULT_COLOR, FLAGS, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { getLuminance } from './ColorUtils'; import { IRenderLayer } from './renderLayer/Types'; import { IRenderDimensions, IRenderer } from 'browser/renderer/Types'; import { IColorSet } from 'browser/Types'; +import { FLAGS } from './Constants'; +import { getCompatAttr } from './CharDataCompat'; export const INDICIES_PER_CELL = 4; @@ -256,7 +258,7 @@ export class WebglRenderer extends Disposable implements IRenderer { const charData = line.get(x); const chars = charData[CHAR_DATA_CHAR_INDEX]; let code = charData[CHAR_DATA_CODE_INDEX]; - const attr = charData[CHAR_DATA_ATTR_INDEX]; + const attr = getCompatAttr(line, x); // charData[CHAR_DATA_ATTR_INDEX]; const i = ((y * terminal.cols) + x) * INDICIES_PER_CELL; if (code !== NULL_CELL_CODE) { diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 9a833fdf..fa18734b 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -6,9 +6,10 @@ import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from './Types'; import { BaseCharAtlas } from './BaseCharAtlas'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; -import { FLAGS, DEFAULT_COLOR, DEFAULT_ATTR } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, DEFAULT_ATTR } from 'common/buffer/Constants'; import { is256Color } from './CharAtlasUtils'; import { IColor } from 'browser/Types'; +import { FLAGS } from '../Constants'; // 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. diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index f0240445..487ea128 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -5,7 +5,7 @@ import { CharData, IBufferLine, ICellData } from 'common/Types'; import { stringFromCodePoint } from 'common/input/TextDecoder'; -import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, FLAGS, Content } from 'common/buffer/Constants'; +import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants'; import { CellData } from 'common/buffer/CellData'; import { AttributeData } from 'common/buffer/AttributeData'; @@ -74,25 +74,8 @@ export class BufferLine implements IBufferLine { const content = this._data[index * CELL_SIZE + Cell.CONTENT]; const cp = content & Content.CODEPOINT_MASK; - // TODO: Need to move WebGL over to the new system and remove this block - const cell = new CellData(); - this.loadCell(index, cell); - const oldBg = cell.getBgColor() === -1 ? 256 : cell.getBgColor(); - const oldFg = cell.getFgColor() === -1 ? 256 : cell.getFgColor(); - const oldAttr = - (cell.isBold() ? FLAGS.BOLD : 0) | - (cell.isUnderline() ? FLAGS.UNDERLINE : 0) | - (cell.isBlink() ? FLAGS.BLINK : 0) | - (cell.isInverse() ? FLAGS.INVERSE : 0) | - (cell.isDim() ? FLAGS.DIM : 0) | - (cell.isItalic() ? FLAGS.ITALIC : 0); - const attrCompat = - oldBg | - (oldFg << 9) | - (oldAttr << 18); - return [ - attrCompat, + this._data[index * CELL_SIZE + Cell.FG], (content & Content.IS_COMBINED_MASK) ? this._combined[index] : (cp) ? stringFromCodePoint(cp) : '', diff --git a/src/common/buffer/Constants.ts b/src/common/buffer/Constants.ts index fb726a1c..276a5c54 100644 --- a/src/common/buffer/Constants.ts +++ b/src/common/buffer/Constants.ts @@ -126,14 +126,3 @@ export const enum BgFlags { ITALIC = 0x4000000, DIM = 0x8000000 } - -// TODO: Should be removed after chardata workaround is fixed -export const enum FLAGS { - BOLD = 1, - UNDERLINE = 2, - BLINK = 4, - INVERSE = 8, - INVISIBLE = 16, - DIM = 32, - ITALIC = 64 -} From 7148358acf1fe4345393b6d288ed931c17e6699c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 16:40:45 -0700 Subject: [PATCH 62/69] Prevent crash on true color, add TODOs for it --- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 4 ++++ addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 401f4568..6dfee505 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -275,7 +275,11 @@ export class RectangleRenderer { if (bg === INVERTED_DEFAULT_COLOR) { color = this._colors.foreground; } else if (is256Color(bg)) { + // TODO: Need to do a separate set for 16 color palette? color = this._colors.ansi[bg]; + } else { + // TODO: Add support for true color + color = this._colors.foreground; } if (vertices.attributes.length < offset + 4) { vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE); diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index fa18734b..33464269 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -170,8 +170,10 @@ export class WebglCharAtlas extends BaseCharAtlas { } else if (bg === INVERTED_DEFAULT_COLOR) { return this._config.colors.foreground; } else if (is256Color(bg)) { + // TODO: Need to do a separate set for 16 color palette? return this._getColorFromAnsiIndex(bg); } + // TODO: Support true color return this._config.colors.background; } @@ -179,8 +181,10 @@ export class WebglCharAtlas extends BaseCharAtlas { if (fg === INVERTED_DEFAULT_COLOR) { return this._config.colors.background; } else if (is256Color(fg)) { + // TODO: Need to do a separate set for 16 color palette? return this._getColorFromAnsiIndex(fg); } + // TODO: Support true color return this._config.colors.foreground; } From 8b4901e8f30659a793c97a91a04b7fafd731aec8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 15 Jun 2019 16:43:01 -0700 Subject: [PATCH 63/69] Reduce diff --- src/common/buffer/BufferLine.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/common/buffer/BufferLine.ts b/src/common/buffer/BufferLine.ts index 487ea128..8e742be3 100644 --- a/src/common/buffer/BufferLine.ts +++ b/src/common/buffer/BufferLine.ts @@ -73,7 +73,6 @@ export class BufferLine implements IBufferLine { public get(index: number): CharData { const content = this._data[index * CELL_SIZE + Cell.CONTENT]; const cp = content & Content.CODEPOINT_MASK; - return [ this._data[index * CELL_SIZE + Cell.FG], (content & Content.IS_COMBINED_MASK) From 5100c7a30dfe1e06129ba631311fdb95b0b91d5a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 16 Jun 2019 00:17:14 -0700 Subject: [PATCH 64/69] Use Constants from core --- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 2 +- addons/xterm-addon-webgl/src/{Types.ts => Types.d.ts} | 0 addons/xterm-addon-webgl/src/WebglRenderer.ts | 2 +- addons/xterm-addon-webgl/src/atlas/{Types.ts => Types.d.ts} | 5 ----- addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 3 ++- addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts | 3 ++- addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts | 2 +- 7 files changed, 7 insertions(+), 10 deletions(-) rename addons/xterm-addon-webgl/src/{Types.ts => Types.d.ts} (100%) rename addons/xterm-addon-webgl/src/atlas/{Types.ts => Types.d.ts} (83%) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 6dfee505..24b4994b 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -6,7 +6,7 @@ import { createProgram, expandFloat32Array, PROJECTION_MATRIX } from './WebglUtils'; import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; import { fill } from 'common/TypedArrayUtils'; -import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { is256Color } from './atlas/CharAtlasUtils'; import { DEFAULT_COLOR } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; diff --git a/addons/xterm-addon-webgl/src/Types.ts b/addons/xterm-addon-webgl/src/Types.d.ts similarity index 100% rename from addons/xterm-addon-webgl/src/Types.ts rename to addons/xterm-addon-webgl/src/Types.d.ts diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index 0d8cdf45..defed962 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -11,7 +11,7 @@ import { acquireCharAtlas } from './atlas/CharAtlasCache'; import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { RectangleRenderer } from './RectangleRenderer'; import { IWebGL2RenderingContext } from './Types'; -import { INVERTED_DEFAULT_COLOR } from './atlas/Types'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { RenderModel, COMBINED_CHAR_BIT_MASK } from './RenderModel'; import { Disposable } from 'common/Lifecycle'; import { DEFAULT_COLOR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_CODE_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CODE } from 'common/buffer/Constants'; diff --git a/addons/xterm-addon-webgl/src/atlas/Types.ts b/addons/xterm-addon-webgl/src/atlas/Types.d.ts similarity index 83% rename from addons/xterm-addon-webgl/src/atlas/Types.ts rename to addons/xterm-addon-webgl/src/atlas/Types.d.ts index 2cb1db40..1de843e0 100644 --- a/addons/xterm-addon-webgl/src/atlas/Types.ts +++ b/addons/xterm-addon-webgl/src/atlas/Types.d.ts @@ -6,11 +6,6 @@ import { FontWeight } from 'xterm'; import { IColorSet } from 'browser/Types'; -export const INVERTED_DEFAULT_COLOR = 257; -export const DIM_OPACITY = 0.5; - -export const CHAR_ATLAS_CELL_SPACING = 1; - export interface IGlyphIdentifier { chars: string; code: number; diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 33464269..c3da6955 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -3,7 +3,8 @@ * @license MIT */ -import { DIM_OPACITY, IGlyphIdentifier, INVERTED_DEFAULT_COLOR, ICharAtlasConfig } from './Types'; +import { IGlyphIdentifier, ICharAtlasConfig } from './Types'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { BaseCharAtlas } from './BaseCharAtlas'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { DEFAULT_COLOR, DEFAULT_ATTR } from 'common/buffer/Constants'; diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 263a0d21..999d94f4 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -6,7 +6,8 @@ import { IRenderLayer } from './Types'; import { ICellData } from 'common/Types'; import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from '../atlas/Types'; +import { IGlyphIdentifier } from '../atlas/Types'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { BaseCharAtlas } from '../atlas/BaseCharAtlas'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import { Terminal } from 'xterm'; diff --git a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts index c942870f..d37f2640 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/LinkRenderLayer.ts @@ -6,7 +6,7 @@ import { ILinkifierEvent, ILinkifierAccessor } from '../../../../src/Types'; import { Terminal } from 'xterm'; import { BaseRenderLayer } from './BaseRenderLayer'; -import { INVERTED_DEFAULT_COLOR } from '../atlas/Types'; +import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { is256Color } from '../atlas/CharAtlasUtils'; import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; From 1b7862ce8a26dc56d10c02dce575aeec9a65c42a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 17 Jun 2019 07:35:52 -0700 Subject: [PATCH 65/69] Move slice typed array util into webgl addon --- addons/xterm-addon-webgl/src/GlyphRenderer.ts | 3 +- .../xterm-addon-webgl/src/TypedArray.test.ts | 122 ++++++++++++++++++ addons/xterm-addon-webgl/src/TypedArray.ts | 34 +++++ src/common/TypedArrayUtils.test.ts | 105 +-------------- src/common/TypedArrayUtils.ts | 26 ---- 5 files changed, 159 insertions(+), 131 deletions(-) create mode 100644 addons/xterm-addon-webgl/src/TypedArray.test.ts create mode 100644 addons/xterm-addon-webgl/src/TypedArray.ts diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index 50357b28..dc375348 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -8,7 +8,8 @@ import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; import { INDICIES_PER_CELL } from './WebglRenderer'; import { COMBINED_CHAR_BIT_MASK } from './RenderModel'; -import { fill, slice } from 'common/TypedArrayUtils'; +import { fill } from 'common/TypedArrayUtils'; +import { slice } from './TypedArray'; import { NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { getLuminance } from './ColorUtils'; import { Terminal, IBufferLine } from 'xterm'; diff --git a/addons/xterm-addon-webgl/src/TypedArray.test.ts b/addons/xterm-addon-webgl/src/TypedArray.test.ts new file mode 100644 index 00000000..f18c6774 --- /dev/null +++ b/addons/xterm-addon-webgl/src/TypedArray.test.ts @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ +import { assert } from 'chai'; +import { sliceFallback } from './TypedArray'; + +type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray + | Int8Array | Int16Array | Int32Array + | Float32Array | Float64Array; + +function deepEquals(a: TypedArray, b: TypedArray): void { + assert.equal(a.length, b.length); + for (let i = 0; i < a.length; ++i) { + assert.equal(a[i], b[i]); + } +} + +describe('polyfill conformance tests', function(): void { + describe('TypedArray.slice', () => { + describe('should work with all typed array types', () => { + it('Uint8Array', () => { + const a = new Uint8Array(5); + deepEquals(sliceFallback(a, 2), a.slice(2)); + deepEquals(sliceFallback(a, 65535), a.slice(65535)); + deepEquals(sliceFallback(a, -1), a.slice(-1)); + }); + it('Uint16Array', () => { + const u161 = new Uint16Array(5); + const u162 = new Uint16Array(5); + deepEquals(sliceFallback(u161, 2), u162.slice(2)); + deepEquals(sliceFallback(u161, 65535), u162.slice(65535)); + deepEquals(sliceFallback(u161, -1), u162.slice(-1)); + }); + it('Uint32Array', () => { + const u321 = new Uint32Array(5); + const u322 = new Uint32Array(5); + deepEquals(sliceFallback(u321, 2), u322.slice(2)); + deepEquals(sliceFallback(u321, 65537), u322.slice(65537)); + deepEquals(sliceFallback(u321, -1), u322.slice(-1)); + }); + it('Int8Array', () => { + const i81 = new Int8Array(5); + const i82 = new Int8Array(5); + deepEquals(sliceFallback(i81, 2), i82.slice(2)); + deepEquals(sliceFallback(i81, 65537), i82.slice(65537)); + deepEquals(sliceFallback(i81, -1), i82.slice(-1)); + }); + it('Int16Array', () => { + const i161 = new Int16Array(5); + const i162 = new Int16Array(5); + deepEquals(sliceFallback(i161, 2), i162.slice(2)); + deepEquals(sliceFallback(i161, 65535), i162.slice(65535)); + deepEquals(sliceFallback(i161, -1), i162.slice(-1)); + }); + it('Int32Array', () => { + const i321 = new Int32Array(5); + const i322 = new Int32Array(5); + deepEquals(sliceFallback(i321, 2), i322.slice(2)); + deepEquals(sliceFallback(i321, 65537), i322.slice(65537)); + deepEquals(sliceFallback(i321, -1), i322.slice(-1)); + }); + it('Float32Array', () => { + const f321 = new Float32Array(5); + const f322 = new Float32Array(5); + deepEquals(sliceFallback(f321, 2), f322.slice(2)); + deepEquals(sliceFallback(f321, 65537), f322.slice(65537)); + deepEquals(sliceFallback(f321, -1), f322.slice(-1)); + }); + it('Float64Array', () => { + const f641 = new Float64Array(5); + const f642 = new Float64Array(5); + deepEquals(sliceFallback(f641, 2), f642.slice(2)); + deepEquals(sliceFallback(f641, 65537), f642.slice(65537)); + deepEquals(sliceFallback(f641, -1), f642.slice(-1)); + }); + it('Uint8ClampedArray', () => { + const u8Clamped1 = new Uint8ClampedArray(5); + const u8Clamped2 = new Uint8ClampedArray(5); + deepEquals(sliceFallback(u8Clamped1, 2), u8Clamped2.slice(2)); + deepEquals(sliceFallback(u8Clamped1, 65537), u8Clamped2.slice(65537)); + deepEquals(sliceFallback(u8Clamped1, -1), u8Clamped2.slice(-1)); + }); + }); + it('start', () => { + const arr = new Uint32Array([1, 2, 3, 4, 5]); + deepEquals(sliceFallback(arr, -1), arr.slice(-1)); + deepEquals(sliceFallback(arr, 0), arr.slice(0)); + deepEquals(sliceFallback(arr, 1), arr.slice(1)); + deepEquals(sliceFallback(arr, 2), arr.slice(2)); + deepEquals(sliceFallback(arr, 3), arr.slice(3)); + deepEquals(sliceFallback(arr, 4), arr.slice(4)); + deepEquals(sliceFallback(arr, 5), arr.slice(5)); + }); + it('end', () => { + const arr = new Uint32Array([1, 2, 3, 4, 5]); + deepEquals(sliceFallback(arr, -1, -2), arr.slice(-1, -2)); + deepEquals(sliceFallback(arr, 0, -2), arr.slice(0, -2)); + deepEquals(sliceFallback(arr, 1, -2), arr.slice(1, -2)); + deepEquals(sliceFallback(arr, 2, -2), arr.slice(2, -2)); + deepEquals(sliceFallback(arr, 3, -2), arr.slice(3, -2)); + deepEquals(sliceFallback(arr, 4, -2), arr.slice(4, -2)); + deepEquals(sliceFallback(arr, 5, -2), arr.slice(5, -2)); + + deepEquals(sliceFallback(arr, -1, 3), arr.slice(-1, 3)); + deepEquals(sliceFallback(arr, 0, 3), arr.slice(0, 3)); + deepEquals(sliceFallback(arr, 1, 3), arr.slice(1, 3)); + deepEquals(sliceFallback(arr, 2, 3), arr.slice(2, 3)); + deepEquals(sliceFallback(arr, 3, 3), arr.slice(3, 3)); + deepEquals(sliceFallback(arr, 4, 3), arr.slice(4, 3)); + deepEquals(sliceFallback(arr, 5, 3), arr.slice(5, 3)); + + deepEquals(sliceFallback(arr, -1, 8), arr.slice(-1, 8)); + deepEquals(sliceFallback(arr, 0, 8), arr.slice(0, 8)); + deepEquals(sliceFallback(arr, 1, 8), arr.slice(1, 8)); + deepEquals(sliceFallback(arr, 2, 8), arr.slice(2, 8)); + deepEquals(sliceFallback(arr, 3, 8), arr.slice(3, 8)); + deepEquals(sliceFallback(arr, 4, 8), arr.slice(4, 8)); + deepEquals(sliceFallback(arr, 5, 8), arr.slice(5, 8)); + }); + }); +}); diff --git a/addons/xterm-addon-webgl/src/TypedArray.ts b/addons/xterm-addon-webgl/src/TypedArray.ts new file mode 100644 index 00000000..ee93be35 --- /dev/null +++ b/addons/xterm-addon-webgl/src/TypedArray.ts @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray + | Int8Array | Int16Array | Int32Array + | Float32Array | Float64Array; + +export function slice(array: T, start?: number, end?: number): T { + // all modern engines that support .slice + if (array.slice) { + return array.slice(start, end) as T; + } + return sliceFallback(array, start, end); +} + +export function sliceFallback(array: T, start: number = 0, end: number = array.length): T { + if (start < 0) { + start = (array.length + start) % array.length; + } + if (end >= array.length) { + end = array.length; + } else { + end = (array.length + end) % array.length; + } + start = Math.min(start, end); + + const result: T = new (array.constructor as any)(end - start); + for (let i = 0; i < end - start; ++i) { + result[i] = array[i + start]; + } + return result; +} diff --git a/src/common/TypedArrayUtils.test.ts b/src/common/TypedArrayUtils.test.ts index ed3541db..429a3a33 100644 --- a/src/common/TypedArrayUtils.test.ts +++ b/src/common/TypedArrayUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ import { assert } from 'chai'; -import { fillFallback, concat, sliceFallback } from 'common/TypedArrayUtils'; +import { fillFallback, concat } from 'common/TypedArrayUtils'; type TypedArray = Uint8Array | Uint16Array | Uint32Array | Uint8ClampedArray | Int8Array | Int16Array | Int32Array @@ -85,109 +85,6 @@ describe('polyfill conformance tests', function(): void { } }); }); - - describe('TypedArray.slice', () => { - describe('should work with all typed array types', () => { - it('Uint8Array', () => { - const a = new Uint8Array(5); - deepEquals(sliceFallback(a, 2), a.slice(2)); - deepEquals(sliceFallback(a, 65535), a.slice(65535)); - deepEquals(sliceFallback(a, -1), a.slice(-1)); - }); - it('Uint16Array', () => { - const u161 = new Uint16Array(5); - const u162 = new Uint16Array(5); - deepEquals(sliceFallback(u161, 2), u162.slice(2)); - deepEquals(sliceFallback(u161, 65535), u162.slice(65535)); - deepEquals(sliceFallback(u161, -1), u162.slice(-1)); - }); - it('Uint32Array', () => { - const u321 = new Uint32Array(5); - const u322 = new Uint32Array(5); - deepEquals(sliceFallback(u321, 2), u322.slice(2)); - deepEquals(sliceFallback(u321, 65537), u322.slice(65537)); - deepEquals(sliceFallback(u321, -1), u322.slice(-1)); - }); - it('Int8Array', () => { - const i81 = new Int8Array(5); - const i82 = new Int8Array(5); - deepEquals(sliceFallback(i81, 2), i82.slice(2)); - deepEquals(sliceFallback(i81, 65537), i82.slice(65537)); - deepEquals(sliceFallback(i81, -1), i82.slice(-1)); - }); - it('Int16Array', () => { - const i161 = new Int16Array(5); - const i162 = new Int16Array(5); - deepEquals(sliceFallback(i161, 2), i162.slice(2)); - deepEquals(sliceFallback(i161, 65535), i162.slice(65535)); - deepEquals(sliceFallback(i161, -1), i162.slice(-1)); - }); - it('Int32Array', () => { - const i321 = new Int32Array(5); - const i322 = new Int32Array(5); - deepEquals(sliceFallback(i321, 2), i322.slice(2)); - deepEquals(sliceFallback(i321, 65537), i322.slice(65537)); - deepEquals(sliceFallback(i321, -1), i322.slice(-1)); - }); - it('Float32Array', () => { - const f321 = new Float32Array(5); - const f322 = new Float32Array(5); - deepEquals(sliceFallback(f321, 2), f322.slice(2)); - deepEquals(sliceFallback(f321, 65537), f322.slice(65537)); - deepEquals(sliceFallback(f321, -1), f322.slice(-1)); - }); - it('Float64Array', () => { - const f641 = new Float64Array(5); - const f642 = new Float64Array(5); - deepEquals(sliceFallback(f641, 2), f642.slice(2)); - deepEquals(sliceFallback(f641, 65537), f642.slice(65537)); - deepEquals(sliceFallback(f641, -1), f642.slice(-1)); - }); - it('Uint8ClampedArray', () => { - const u8Clamped1 = new Uint8ClampedArray(5); - const u8Clamped2 = new Uint8ClampedArray(5); - deepEquals(sliceFallback(u8Clamped1, 2), u8Clamped2.slice(2)); - deepEquals(sliceFallback(u8Clamped1, 65537), u8Clamped2.slice(65537)); - deepEquals(sliceFallback(u8Clamped1, -1), u8Clamped2.slice(-1)); - }); - }); - it('start', () => { - const arr = new Uint32Array([1, 2, 3, 4, 5]); - deepEquals(sliceFallback(arr, -1), arr.slice(-1)); - deepEquals(sliceFallback(arr, 0), arr.slice(0)); - deepEquals(sliceFallback(arr, 1), arr.slice(1)); - deepEquals(sliceFallback(arr, 2), arr.slice(2)); - deepEquals(sliceFallback(arr, 3), arr.slice(3)); - deepEquals(sliceFallback(arr, 4), arr.slice(4)); - deepEquals(sliceFallback(arr, 5), arr.slice(5)); - }); - it('end', () => { - const arr = new Uint32Array([1, 2, 3, 4, 5]); - deepEquals(sliceFallback(arr, -1, -2), arr.slice(-1, -2)); - deepEquals(sliceFallback(arr, 0, -2), arr.slice(0, -2)); - deepEquals(sliceFallback(arr, 1, -2), arr.slice(1, -2)); - deepEquals(sliceFallback(arr, 2, -2), arr.slice(2, -2)); - deepEquals(sliceFallback(arr, 3, -2), arr.slice(3, -2)); - deepEquals(sliceFallback(arr, 4, -2), arr.slice(4, -2)); - deepEquals(sliceFallback(arr, 5, -2), arr.slice(5, -2)); - - deepEquals(sliceFallback(arr, -1, 3), arr.slice(-1, 3)); - deepEquals(sliceFallback(arr, 0, 3), arr.slice(0, 3)); - deepEquals(sliceFallback(arr, 1, 3), arr.slice(1, 3)); - deepEquals(sliceFallback(arr, 2, 3), arr.slice(2, 3)); - deepEquals(sliceFallback(arr, 3, 3), arr.slice(3, 3)); - deepEquals(sliceFallback(arr, 4, 3), arr.slice(4, 3)); - deepEquals(sliceFallback(arr, 5, 3), arr.slice(5, 3)); - - deepEquals(sliceFallback(arr, -1, 8), arr.slice(-1, 8)); - deepEquals(sliceFallback(arr, 0, 8), arr.slice(0, 8)); - deepEquals(sliceFallback(arr, 1, 8), arr.slice(1, 8)); - deepEquals(sliceFallback(arr, 2, 8), arr.slice(2, 8)); - deepEquals(sliceFallback(arr, 3, 8), arr.slice(3, 8)); - deepEquals(sliceFallback(arr, 4, 8), arr.slice(4, 8)); - deepEquals(sliceFallback(arr, 5, 8), arr.slice(5, 8)); - }); - }); }); describe('typed array convenience functions', () => { diff --git a/src/common/TypedArrayUtils.ts b/src/common/TypedArrayUtils.ts index f2651aa0..54699835 100644 --- a/src/common/TypedArrayUtils.ts +++ b/src/common/TypedArrayUtils.ts @@ -40,32 +40,6 @@ export function fillFallback(array: T, value: number, star return array; } -export function slice(array: T, start?: number, end?: number): T { - // all modern engines that support .slice - if (array.slice) { - return array.slice(start, end) as T; - } - return sliceFallback(array, start, end); -} - -export function sliceFallback(array: T, start: number = 0, end: number = array.length): T { - if (start < 0) { - start = (array.length + start) % array.length; - } - if (end >= array.length) { - end = array.length; - } else { - end = (array.length + end) % array.length; - } - start = Math.min(start, end); - - const result: T = new (array.constructor as any)(end - start); - for (let i = 0; i < end - start; ++i) { - result[i] = array[i + start]; - } - return result; -} - /** * Concat two typed arrays `a` and `b`. * Returns a new typed array. From 5321f0373fc8f1380ec1853c6ae24680fa395712 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 17 Jun 2019 13:42:44 -0700 Subject: [PATCH 66/69] Fix quote and comma --- demo/index.html | 2 +- tsconfig.all.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/demo/index.html b/demo/index.html index 99ae413d..ef8f3891 100644 --- a/demo/index.html +++ b/demo/index.html @@ -36,7 +36,7 @@

Attention: The demo is a barebones implementation and is designed for the development and evaluation of xterm.js only. Exposing the demo to the public as is would introduce security risks for the host.

- + diff --git a/tsconfig.all.json b/tsconfig.all.json index bc633fe6..06efafcb 100644 --- a/tsconfig.all.json +++ b/tsconfig.all.json @@ -7,7 +7,7 @@ { "path": "./addons/xterm-addon-attach/src" }, { "path": "./addons/xterm-addon-fit/src" }, { "path": "./addons/xterm-addon-search/src" }, - { "path": "./addons/xterm-addon-web-links/src" } + { "path": "./addons/xterm-addon-web-links/src" }, { "path": "./addons/xterm-addon-webgl/src" } ] } From e10adaf3d2ee7250a252aedb74f73d6e6d7d36a3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 23 Jun 2019 13:10:37 -0700 Subject: [PATCH 67/69] Have yarn clean also delete addon out/ --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d291f147..ca803391 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,7 @@ "prepare": "npm run build", "prepublishOnly": "npm run package", "watch": "tsc -b -w ./tsconfig.all.json --preserveWatchOutput", - "clean": "rm -rf lib out addons/*/lib" + "clean": "rm -rf lib out addons/*/lib addons/*/out" }, "devDependencies": { "@types/chai": "^3.4.34", From fdae27e7eae3225be7143fd7216ce0ddafe7d8fd Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 23 Jun 2019 13:11:25 -0700 Subject: [PATCH 68/69] Remove unused Platform.ts in webgl --- addons/xterm-addon-webgl/src/Platform.ts | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 addons/xterm-addon-webgl/src/Platform.ts diff --git a/addons/xterm-addon-webgl/src/Platform.ts b/addons/xterm-addon-webgl/src/Platform.ts deleted file mode 100644 index 55a10b7f..00000000 --- a/addons/xterm-addon-webgl/src/Platform.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Copyright (c) 2016 The xterm.js authors. All rights reserved. - * @license MIT - */ - -const isNode = (typeof navigator === 'undefined') ? true : false; -const userAgent = (isNode) ? 'node' : navigator.userAgent; - -export const isFirefox = !!~userAgent.indexOf('Firefox'); -export const isSafari = /^((?!chrome|android).)*safari/i.test(userAgent); From 533f3bd0dcade8e339b6c02843e1450041960904 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 23 Jun 2019 13:12:49 -0700 Subject: [PATCH 69/69] Remove unneeded TODOs --- addons/xterm-addon-webgl/src/RectangleRenderer.ts | 1 - addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts | 2 -- 2 files changed, 3 deletions(-) diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index 24b4994b..d4ce3b4c 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -275,7 +275,6 @@ export class RectangleRenderer { if (bg === INVERTED_DEFAULT_COLOR) { color = this._colors.foreground; } else if (is256Color(bg)) { - // TODO: Need to do a separate set for 16 color palette? color = this._colors.ansi[bg]; } else { // TODO: Add support for true color diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index c3da6955..6a941d55 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -171,7 +171,6 @@ export class WebglCharAtlas extends BaseCharAtlas { } else if (bg === INVERTED_DEFAULT_COLOR) { return this._config.colors.foreground; } else if (is256Color(bg)) { - // TODO: Need to do a separate set for 16 color palette? return this._getColorFromAnsiIndex(bg); } // TODO: Support true color @@ -182,7 +181,6 @@ export class WebglCharAtlas extends BaseCharAtlas { if (fg === INVERTED_DEFAULT_COLOR) { return this._config.colors.background; } else if (is256Color(fg)) { - // TODO: Need to do a separate set for 16 color palette? return this._getColorFromAnsiIndex(fg); } // TODO: Support true color