diff --git a/addons/xterm-addon-webgl/src/GlyphRenderer.ts b/addons/xterm-addon-webgl/src/GlyphRenderer.ts index f3fd53a6..e9055a17 100644 --- a/addons/xterm-addon-webgl/src/GlyphRenderer.ts +++ b/addons/xterm-addon-webgl/src/GlyphRenderer.ts @@ -6,15 +6,11 @@ import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; import { WebglCharAtlas } from './atlas/WebglCharAtlas'; import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types'; -import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_BG_OFFSET } from './RenderModel'; import { fill } from 'common/TypedArrayUtils'; -import { slice } from './TypedArray'; -import { NULL_CELL_CODE, Attributes, FgFlags } from 'common/buffer/Constants'; +import { NULL_CELL_CODE } from 'common/buffer/Constants'; import { Terminal, IBufferLine } from 'xterm'; -import { IColor } from 'common/Types'; import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; -import { AttributeData } from 'common/buffer/AttributeData'; interface IVertices { attributes: Float32Array; @@ -25,7 +21,6 @@ interface IVertices { * working on the next frame. */ attributesBuffers: Float32Array[]; - selectionAttributes: Float32Array; count: number; } @@ -92,8 +87,7 @@ export class GlyphRenderer { attributesBuffers: [ new Float32Array(0), new Float32Array(0) - ], - selectionAttributes: new Float32Array(0) + ] }; constructor( @@ -217,91 +211,6 @@ export class GlyphRenderer { // a_cellpos only changes on resize } - public updateSelection(model: IRenderModel): void { - const terminal = this._terminal; - - this._vertices.selectionAttributes = slice(this._vertices.attributes, 0); - - const bg = (this._colors.selectionOpaque.rgba >>> 8) | Attributes.CM_RGB; - - if (model.selection.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); - } - } 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); - - // 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); - } - - // 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); - } - } - } - - private _updateSelectionRange(startCol: number, endCol: number, y: number, model: IRenderModel, bg: number): void { - const terminal = this._terminal; - const row = y + terminal.buffer.active.viewportY; - let line: IBufferLine | undefined; - for (let x = startCol; x < endCol; x++) { - const offset = (y * this._terminal.cols + x) * RENDER_MODEL_INDICIES_PER_CELL; - const code = model.cells[offset]; - let fg = model.cells[offset + RENDER_MODEL_FG_OFFSET]; - if (fg & FgFlags.INVERSE) { - const workCell = new AttributeData(); - workCell.fg = fg; - workCell.bg = model.cells[offset + RENDER_MODEL_BG_OFFSET]; - // Get attributes from fg (excluding inverse) and resolve inverse by pullibng rgb colors - // from bg. This is needed since the inverse fg color should be based on the original bg - // color, not on the selection color - fg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK | FgFlags.INVERSE); - switch (workCell.getBgColorMode()) { - case Attributes.CM_P16: - case Attributes.CM_P256: - const c = this._getColorFromAnsiIndex(workCell.getBgColor()).rgba; - fg |= (c >> 8) & Attributes.RED_MASK | (c >> 8) & Attributes.GREEN_MASK | (c >> 8) & Attributes.BLUE_MASK; - case Attributes.CM_RGB: - const arr = AttributeData.toColorRGB(workCell.getBgColor()); - fg |= arr[0] << Attributes.RED_SHIFT | arr[1] << Attributes.GREEN_SHIFT | arr[2] << Attributes.BLUE_SHIFT; - case Attributes.CM_DEFAULT: - default: - const c2 = this._colors.background.rgba; - fg |= (c2 >> 8) & Attributes.RED_MASK | (c2 >> 8) & Attributes.GREEN_MASK | (c2 >> 8) & Attributes.BLUE_MASK; - } - fg |= Attributes.CM_RGB; - } - if (code & COMBINED_CHAR_BIT_MASK) { - if (!line) { - line = terminal.buffer.active.getLine(row); - } - const chars = line!.getCell(x)!.getChars(); - this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg, chars); - } else { - this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg); - } - } - } - - private _getColorFromAnsiIndex(idx: number): IColor { - if (idx >= this._colors.ansi.length) { - throw new Error('No color found for idx ' + idx); - } - return this._colors.ansi[idx]; - } - public clear(force?: boolean): void { const terminal = this._terminal; const newCount = terminal.cols * terminal.rows * INDICES_PER_CELL; @@ -336,7 +245,7 @@ export class GlyphRenderer { public setColors(): void { } - public render(renderModel: IRenderModel, isSelectionVisible: boolean): void { + public render(renderModel: IRenderModel): void { if (!this._atlas) { return; } @@ -360,7 +269,7 @@ export class GlyphRenderer { 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); + const sub = this._vertices.attributes.subarray(si, si + renderModel.lineLengths[y] * INDICES_PER_CELL); activeBuffer.set(sub, bufferLength); bufferLength += sub.length; } diff --git a/addons/xterm-addon-webgl/src/RectangleRenderer.ts b/addons/xterm-addon-webgl/src/RectangleRenderer.ts index ab0b34e9..420e58d4 100644 --- a/addons/xterm-addon-webgl/src/RectangleRenderer.ts +++ b/addons/xterm-addon-webgl/src/RectangleRenderer.ts @@ -4,8 +4,7 @@ */ import { createProgram, expandFloat32Array, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils'; -import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types'; -import { fill } from 'common/TypedArrayUtils'; +import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext } from './Types'; import { Attributes, FgFlags } from 'common/buffer/Constants'; import { Terminal } from 'xterm'; import { IColor } from 'common/Types'; @@ -50,7 +49,6 @@ void main() { interface IVertices { attributes: Float32Array; - selection: Float32Array; count: number; } @@ -67,12 +65,10 @@ export class RectangleRenderer { 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) + attributes: new Float32Array(INITIAL_BUFFER_RECTANGLE_CAPACITY) }; constructor( @@ -138,11 +134,6 @@ export class RectangleRenderer { 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 { @@ -156,7 +147,6 @@ export class RectangleRenderer { private _updateCachedColors(): void { this._bgFloat = this._colorToFloat32Array(this._colors.background); - this._selectionFloat = this._colorToFloat32Array(this._colors.selectionOpaque); } private _updateViewportRectangle(): void { @@ -172,73 +162,6 @@ export class RectangleRenderer { ); } - public updateSelection(model: ISelectionRenderModel): void { - const terminal = this._terminal; - - if (!model.hasSelection) { - fill(this._vertices.selection, 0, 0); - return; - } - - if (model.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 - ); - fill(this._vertices.selection, 0, INDICES_PER_RECTANGLE); - } else { - // Draw first row - const startCol = model.viewportStartRow === model.viewportCappedStartRow ? model.startCol : 0; - const startRowEndCol = model.viewportCappedStartRow === model.viewportEndRow ? 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 { - fill(this._vertices.selection, 0, INDICES_PER_RECTANGLE * 2); - } - } - } - public updateBackgrounds(model: IRenderModel): void { const terminal = this._terminal; const vertices = this._vertices; diff --git a/addons/xterm-addon-webgl/src/WebglRenderer.ts b/addons/xterm-addon-webgl/src/WebglRenderer.ts index e80464da..1b45ae3a 100644 --- a/addons/xterm-addon-webgl/src/WebglRenderer.ts +++ b/addons/xterm-addon-webgl/src/WebglRenderer.ts @@ -167,10 +167,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._core.screenElement!.style.height = `${this.dimensions.canvasHeight}px`; this._rectangleRenderer.onResize(); - if (this._model.selection.hasSelection) { - // Update selection as dimensions have changed - this._rectangleRenderer.updateSelection(this._model.selection); - } this._glyphRenderer.setDimensions(this.dimensions); this._glyphRenderer.onResize(); @@ -201,10 +197,8 @@ export class WebglRenderer extends Disposable implements IRenderer { for (const l of this._renderLayers) { l.onSelectionChanged(this._terminal, start, end, columnSelectMode); } - this._updateSelectionModel(start, end, columnSelectMode); - - this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); + this._requestRedrawViewport(); } public onCursorMove(): void { @@ -246,7 +240,7 @@ export class WebglRenderer extends Disposable implements IRenderer { this._charAtlas?.clearTexture(); this._model.clear(); this._updateModel(0, this._terminal.rows - 1); - this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); + this._requestRedrawViewport(); } public clear(): void { @@ -292,7 +286,7 @@ export class WebglRenderer extends Disposable implements IRenderer { // Render this._rectangleRenderer.render(); - this._glyphRenderer.render(this._model, this._model.selection.hasSelection); + this._glyphRenderer.render(this._model); } private _updateModel(start: number, end: number): void { @@ -376,10 +370,6 @@ export class WebglRenderer extends Disposable implements IRenderer { } } this._rectangleRenderer.updateBackgrounds(this._model); - if (this._model.selection.hasSelection) { - // Model could be updated but the selection is unchanged - this._glyphRenderer.updateSelection(this._model); - } } /** @@ -390,16 +380,22 @@ export class WebglRenderer extends Disposable implements IRenderer { this._workColors.bg = this._workCell.bg; this._workColors.fg = this._workCell.fg; - // Get any decoration foreground/background overrides, this happens on the model to avoid - // spreading decoration override logic throughout the different sub-renderers let bgOverride: number | undefined; let fgOverride: number | undefined; + + // Apply the selection color if needed + if (this._isCellSelected(x, y)) { + bgOverride = this._colors.selectionOpaque.rgba >> 8 & 0xFFFFFF; + } + + // Get any decoration foreground/background overrides, this happens on the model to avoid + // spreading decoration override logic throughout the different sub-renderers for (const d of this._decorationService.getDecorationsAtCell(x, y)) { if (d.backgroundColorRGB) { - bgOverride = (d.backgroundColorRGB.rgba >> 8) >>> 0 & 0xFFFFFF; + bgOverride = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; } if (d.foregroundColorRGB) { - fgOverride = (d.foregroundColorRGB.rgba >> 8) >>> 0 & 0xFFFFFF; + fgOverride = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; } } @@ -440,13 +436,27 @@ export class WebglRenderer extends Disposable implements IRenderer { this._workColors.fg = fgOverride ?? this._workColors.fg; } + private _isCellSelected(x: number, y: number): boolean { + if (!this._model.selection.hasSelection) { + return false; + } + y -= this._terminal.buffer.active.viewportY; + if (this._model.selection.columnSelectMode) { + return x >= this._model.selection.startCol && y >= this._model.selection.viewportCappedStartRow && + x < this._model.selection.endCol && y < this._model.selection.viewportCappedEndRow; + } + return (y > this._model.selection.viewportStartRow && y < this._model.selection.viewportEndRow) || + (this._model.selection.viewportStartRow === this._model.selection.viewportEndRow && y === this._model.selection.viewportStartRow && x >= this._model.selection.startCol && x < this._model.selection.endCol) || + (this._model.selection.viewportStartRow < this._model.selection.viewportEndRow && y === this._model.selection.viewportEndRow && x < this._model.selection.endCol) || + (this._model.selection.viewportStartRow < this._model.selection.viewportEndRow && y === this._model.selection.viewportStartRow && x >= this._model.selection.startCol); + } + private _updateSelectionModel(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean = false): void { const terminal = this._terminal; // Selection does not exist if (!start || !end || (start[0] === end[0] && start[1] === end[1])) { this._model.clearSelection(); - this._rectangleRenderer.updateSelection(this._model.selection); return; } @@ -459,7 +469,6 @@ export class WebglRenderer extends Disposable implements IRenderer { // No need to draw the selection if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { this._model.clearSelection(); - this._rectangleRenderer.updateSelection(this._model.selection); return; } @@ -471,8 +480,6 @@ export class WebglRenderer extends Disposable implements IRenderer { this._model.selection.viewportCappedEndRow = viewportCappedEndRow; this._model.selection.startCol = start[0]; this._model.selection.endCol = end[0]; - - this._rectangleRenderer.updateSelection(this._model.selection); } /** @@ -546,6 +553,10 @@ export class WebglRenderer extends Disposable implements IRenderer { this.dimensions.actualCellHeight = this.dimensions.scaledCellHeight / this._devicePixelRatio; this.dimensions.actualCellWidth = this.dimensions.scaledCellWidth / this._devicePixelRatio; } + + private _requestRedrawViewport(): void { + this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 }); + } } // TODO: Share impl with core