333 lines
12 KiB
TypeScript
333 lines
12 KiB
TypeScript
/**
|
|
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
|
* @license MIT
|
|
*/
|
|
|
|
import { createProgram, expandFloat32Array, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils';
|
|
import { IRenderModel, IWebGLVertexArrayObject, IWebGL2RenderingContext, ISelectionRenderModel } from './Types';
|
|
import { fill } from 'common/TypedArrayUtils';
|
|
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants';
|
|
import { is256Color } from './atlas/CharAtlasUtils';
|
|
import { DEFAULT_COLOR, Attributes, FgFlags } from 'common/buffer/Constants';
|
|
import { Terminal } from 'xterm';
|
|
import { IColorSet, IColor } from 'browser/Types';
|
|
import { IRenderDimensions } from 'browser/renderer/Types';
|
|
|
|
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 lowp 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: Terminal,
|
|
private _colors: IColorSet,
|
|
private _gl: IWebGL2RenderingContext,
|
|
private _dimensions: IRenderDimensions
|
|
) {
|
|
const gl = this._gl;
|
|
|
|
this._program = throwIfFalsy(createProgram(gl, vertexShaderSource, fragmentShaderSource));
|
|
|
|
// Uniform locations
|
|
this._resolutionLocation = throwIfFalsy(gl.getUniformLocation(this._program, 'u_resolution'));
|
|
this._projectionLocation = throwIfFalsy(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 = throwIfFalsy(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 setColors(): void {
|
|
this._updateCachedColors();
|
|
this._updateViewportRectangle();
|
|
}
|
|
|
|
private _updateCachedColors(): void {
|
|
this._bgFloat = this._colorToFloat32Array(this._colors.background);
|
|
this._selectionFloat = this._colorToFloat32Array(this._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) {
|
|
fill(this._vertices.selection, 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
|
|
);
|
|
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.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 {
|
|
fill(this._vertices.selection, 0, INDICES_PER_RECTANGLE * 2);
|
|
}
|
|
}
|
|
}
|
|
|
|
public updateBackgrounds(model: IRenderModel): void {
|
|
const terminal = this._terminal;
|
|
const vertices = this._vertices;
|
|
|
|
let rectangleCount = 1;
|
|
|
|
for (let y = 0; y < terminal.rows; y++) {
|
|
let currentStartX = -1;
|
|
let currentBg = DEFAULT_COLOR;
|
|
let currentFg = DEFAULT_COLOR;
|
|
for (let x = 0; x < terminal.cols; x++) {
|
|
const modelIndex = ((y * terminal.cols) + x) * 4;
|
|
const bg = model.cells[modelIndex + 2];
|
|
const fg = model.cells[modelIndex + 1];
|
|
if (bg !== currentBg) {
|
|
// A rectangle needs to be drawn if going from non-default to another color
|
|
// TODO: DEFAULT_COLOR probably isn't right anymore?
|
|
if (currentBg !== DEFAULT_COLOR) {
|
|
const offset = rectangleCount++ * INDICES_PER_RECTANGLE;
|
|
this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, x, y);
|
|
}
|
|
currentStartX = x;
|
|
currentBg = bg;
|
|
currentFg = fg;
|
|
}
|
|
}
|
|
// Finish rectangle if it's still going
|
|
if (currentBg !== DEFAULT_COLOR) {
|
|
const offset = rectangleCount++ * INDICES_PER_RECTANGLE;
|
|
this._updateRectangle(vertices, offset, currentFg, currentBg, currentStartX, terminal.cols, y);
|
|
}
|
|
}
|
|
vertices.count = rectangleCount;
|
|
}
|
|
|
|
private _updateRectangle(vertices: IVertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void {
|
|
let color: IColor | null = null;
|
|
if (fg & FgFlags.INVERSE) {
|
|
// Inverted color
|
|
color = this._colors.foreground;
|
|
} else if ((bg & Attributes.CM_MASK) === Attributes.CM_P16 || (bg & Attributes.CM_MASK) === Attributes.CM_P256) {
|
|
// 256 palette
|
|
color = this._colors.ansi[bg & Attributes.PCOLOR_MASK];
|
|
} else {
|
|
// TODO: Support true color
|
|
// Default color
|
|
color = this._colors.background;
|
|
}
|
|
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
|
|
]);
|
|
}
|
|
}
|