Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 3 additions & 139 deletions addons/xterm-addon-canvas/src/CursorRenderLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { Terminal } from 'xterm';
import { toDisposable } from 'common/Lifecycle';
import { isFirefox } from 'common/Platform';
import { CursorBlinkStateManager } from 'browser/renderer/shared/CursorBlinkStateManager';

interface ICursorState {
x: number;
Expand Down Expand Up @@ -60,6 +61,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
'underline': this._renderUnderlineCursor.bind(this)
};
this.register(optionsService.onOptionChange(() => this._handleOptionsChanged()));
this._handleOptionsChanged();
this.register(toDisposable(() => {
this._cursorBlinkStateManager?.dispose();
this._cursorBlinkStateManager = undefined;
Expand Down Expand Up @@ -97,9 +99,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _handleOptionsChanged(): void {
if (this._optionsService.rawOptions.cursorBlink) {
if (!this._cursorBlinkStateManager) {
this._cursorBlinkStateManager = new CursorBlinkStateManager(this._coreBrowserService.isFocused, () => {
this._render(true);
}, this._coreBrowserService);
this._cursorBlinkStateManager = new CursorBlinkStateManager(() => this._render(true), this._coreBrowserService);
}
} else {
this._cursorBlinkStateManager?.dispose();
Expand Down Expand Up @@ -238,139 +238,3 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._ctx.restore();
}
}

class CursorBlinkStateManager {
public isCursorVisible: boolean;

private _animationFrame: number | undefined;
private _blinkStartTimeout: number | undefined;
private _blinkInterval: number | undefined;

/**
* 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 | undefined;

constructor(
isFocused: boolean,
private _renderCallback: () => void,
private _coreBrowserService: ICoreBrowserService
) {
this.isCursorVisible = true;
if (isFocused) {
this._restartInterval();
}
}

public get isPaused(): boolean { return !(this._blinkStartTimeout || this._blinkInterval); }

public dispose(): void {
if (this._blinkInterval) {
this._coreBrowserService.window.clearInterval(this._blinkInterval);
this._blinkInterval = undefined;
}
if (this._blinkStartTimeout) {
this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout);
this._blinkStartTimeout = undefined;
}
if (this._animationFrame) {
this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);
this._animationFrame = undefined;
}
}

public restartBlinkAnimation(): 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 = this._coreBrowserService.window.requestAnimationFrame(() => {
this._renderCallback();
this._animationFrame = undefined;
});
}
}

private _restartInterval(timeToStart: number = BLINK_INTERVAL): void {
// Clear any existing interval
if (this._blinkInterval) {
this._coreBrowserService.window.clearInterval(this._blinkInterval);
this._blinkInterval = undefined;
}

// 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 = this._coreBrowserService.window.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 = undefined;
if (time > 0) {
this._restartInterval(time);
return;
}
}

// Hide the cursor
this.isCursorVisible = false;
this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => {
this._renderCallback();
this._animationFrame = undefined;
});

// Setup the blink interval
this._blinkInterval = this._coreBrowserService.window.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 = undefined;
this._restartInterval(time);
return;
}

// Invert visibility and render
this.isCursorVisible = !this.isCursorVisible;
this._animationFrame = this._coreBrowserService.window.requestAnimationFrame(() => {
this._renderCallback();
this._animationFrame = undefined;
});
}, BLINK_INTERVAL);
}, timeToStart);
}

public pause(): void {
this.isCursorVisible = true;
if (this._blinkInterval) {
this._coreBrowserService.window.clearInterval(this._blinkInterval);
this._blinkInterval = undefined;
}
if (this._blinkStartTimeout) {
this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout);
this._blinkStartTimeout = undefined;
}
if (this._animationFrame) {
this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame);
this._animationFrame = undefined;
}
}

public resume(): void {
// Clear out any existing timers just in case
this.pause();

this._animationTimeRestarted = undefined;
this._restartInterval();
this.restartBlinkAnimation();
}
}
1 change: 1 addition & 0 deletions addons/xterm-addon-webgl/src/GlyphRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ export class GlyphRenderer extends Disposable {

public handleResize(): void {
const gl = this._gl;
gl.useProgram(this._program);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.uniform2f(this._resolutionLocation, gl.canvas.width, gl.canvas.height);
this.clear();
Expand Down
104 changes: 91 additions & 13 deletions addons/xterm-addon-webgl/src/RectangleRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,21 @@ void main() {
outColor = v_color;
}`;

interface IVertices {
attributes: 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;

class Vertices {
public attributes: Float32Array;
public count: number;

constructor() {
this.attributes = new Float32Array(INITIAL_BUFFER_RECTANGLE_CAPACITY);
this.count = 0;
}
}

// Work variables to avoid garbage collection
let $rgba = 0;
let $isDefault = false;
Expand All @@ -77,11 +82,10 @@ export class RectangleRenderer extends Disposable {
private _attributesBuffer: WebGLBuffer;
private _projectionLocation: WebGLUniformLocation;
private _bgFloat!: Float32Array;
private _cursorFloat!: Float32Array;

private _vertices: IVertices = {
count: 0,
attributes: new Float32Array(INITIAL_BUFFER_RECTANGLE_CAPACITY)
};
private _vertices: Vertices = new Vertices();
private _verticesCursor: Vertices = new Vertices();

constructor(
private _terminal: Terminal,
Expand Down Expand Up @@ -142,7 +146,15 @@ export class RectangleRenderer extends Disposable {
}));
}

public render(): void {
public renderBackgrounds(): void {
this._renderVertices(this._vertices);
}

public renderCursor(): void {
this._renderVertices(this._verticesCursor);
}

private _renderVertices(vertices: Vertices): void {
const gl = this._gl;

gl.useProgram(this._program);
Expand All @@ -153,8 +165,8 @@ export class RectangleRenderer extends Disposable {

// 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.TRIANGLE_STRIP, 4, gl.UNSIGNED_BYTE, 0, this._vertices.count);
gl.bufferData(gl.ARRAY_BUFFER, vertices.attributes, gl.DYNAMIC_DRAW);
gl.drawElementsInstanced(this._gl.TRIANGLE_STRIP, 4, gl.UNSIGNED_BYTE, 0, vertices.count);
}

public handleResize(): void {
Expand All @@ -167,6 +179,7 @@ export class RectangleRenderer extends Disposable {

private _updateCachedColors(colors: ReadonlyColorSet): void {
this._bgFloat = this._colorToFloat32Array(colors.background);
this._cursorFloat = this._colorToFloat32Array(colors.cursor);
}

private _updateViewportRectangle(): void {
Expand Down Expand Up @@ -231,7 +244,72 @@ export class RectangleRenderer extends Disposable {
vertices.count = rectangleCount;
}

private _updateRectangle(vertices: IVertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void {
public updateCursor(model: IRenderModel): void {
const vertices = this._verticesCursor;
const cursor = model.cursor;
if (!cursor || cursor.style === 'block') {
vertices.count = 0;
return;
}

let offset: number;
let rectangleCount = 0;

if (cursor.style === 'bar' || cursor.style === 'blur') {
// Left edge
offset = rectangleCount++ * INDICES_PER_RECTANGLE;
this._addRectangleFloat(
vertices.attributes,
offset,
cursor.x * this._dimensions.device.cell.width,
cursor.y * this._dimensions.device.cell.height,
cursor.style === 'bar' ? cursor.dpr * cursor.cursorWidth : cursor.dpr,
this._dimensions.device.cell.height,
this._cursorFloat
);
}
if (cursor.style === 'underline' || cursor.style === 'blur') {
// Bottom edge
offset = rectangleCount++ * INDICES_PER_RECTANGLE;
this._addRectangleFloat(
vertices.attributes,
offset,
cursor.x * this._dimensions.device.cell.width,
(cursor.y + 1) * this._dimensions.device.cell.height - cursor.dpr,
cursor.width * this._dimensions.device.cell.width,
cursor.dpr,
this._cursorFloat
);
}
if (cursor.style === 'blur') {
// Top edge
offset = rectangleCount++ * INDICES_PER_RECTANGLE;
this._addRectangleFloat(
vertices.attributes,
offset,
cursor.x * this._dimensions.device.cell.width,
cursor.y * this._dimensions.device.cell.height,
cursor.width * this._dimensions.device.cell.width,
cursor.dpr,
this._cursorFloat
);
// Right edge
offset = rectangleCount++ * INDICES_PER_RECTANGLE;
this._addRectangleFloat(
vertices.attributes,
offset,
(cursor.x + cursor.width) * this._dimensions.device.cell.width - cursor.dpr,
cursor.y * this._dimensions.device.cell.height,
cursor.dpr,
this._dimensions.device.cell.height,
this._cursorFloat
);
}

vertices.count = rectangleCount;
}

private _updateRectangle(vertices: Vertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void {
$isDefault = false;
if (fg & FgFlags.INVERSE) {
switch (fg & Attributes.CM_MASK) {
Expand Down
3 changes: 2 additions & 1 deletion addons/xterm-addon-webgl/src/RenderModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* @license MIT
*/

import { IRenderModel } from './Types';
import { ICursorRenderModel, IRenderModel } from './Types';
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
import { createSelectionRenderModel } from 'browser/renderer/shared/SelectionRenderModel';

Expand All @@ -18,6 +18,7 @@ export class RenderModel implements IRenderModel {
public cells: Uint32Array;
public lineLengths: Uint32Array;
public selection: ISelectionRenderModel;
public cursor?: ICursorRenderModel;

constructor() {
this.cells = new Uint32Array(0);
Expand Down
10 changes: 10 additions & 0 deletions addons/xterm-addon-webgl/src/Types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ export interface IRenderModel {
cells: Uint32Array;
lineLengths: Uint32Array;
selection: ISelectionRenderModel;
cursor?: ICursorRenderModel;
}

export interface ICursorRenderModel {
x: number;
y: number;
width: number;
style: string;
cursorWidth: number;
dpr: number;
}

export interface IWebGL2RenderingContext extends WebGLRenderingContext {
Expand Down
Loading