@xterm/addon-webgl 0.20.0-beta.23 → 0.20.0-beta.231
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/addon-webgl.js +1 -1
- package/lib/addon-webgl.js.map +1 -1
- package/lib/addon-webgl.mjs +5 -31
- package/lib/addon-webgl.mjs.map +4 -4
- package/package.json +4 -4
- package/src/CellColorResolver.ts +5 -2
- package/src/CharAtlasCache.ts +3 -1
- package/src/CursorBlinkStateManager.ts +67 -8
- package/src/DevicePixelObserver.ts +1 -1
- package/src/GlyphRenderer.ts +28 -20
- package/src/RectangleRenderer.ts +8 -7
- package/src/RenderModel.ts +7 -5
- package/src/TextureAtlas.ts +89 -62
- package/src/Types.ts +3 -3
- package/src/WebglAddon.ts +8 -14
- package/src/WebglRenderer.ts +93 -22
- package/src/customGlyphs/CustomGlyphDefinitions.ts +75 -30
- package/src/customGlyphs/CustomGlyphRasterizer.ts +33 -13
- package/src/renderLayer/BaseRenderLayer.ts +1 -1
- package/typings/addon-webgl.d.ts +3 -0
package/src/TextureAtlas.ts
CHANGED
|
@@ -14,8 +14,8 @@ import { IdleTaskQueue } from 'common/TaskQueue';
|
|
|
14
14
|
import { IColor } from 'common/Types';
|
|
15
15
|
import { AttributeData } from 'common/buffer/AttributeData';
|
|
16
16
|
import { Attributes, DEFAULT_COLOR, DEFAULT_EXT, UnderlineStyle } from 'common/buffer/Constants';
|
|
17
|
-
import { IUnicodeService } from 'common/services/Services';
|
|
18
|
-
import { Emitter } from '
|
|
17
|
+
import { ILogService, IUnicodeService } from 'common/services/Services';
|
|
18
|
+
import { Emitter } from 'common/Event';
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* A shared object which is used to draw nothing for a particular cell.
|
|
@@ -88,7 +88,8 @@ export class TextureAtlas implements ITextureAtlas {
|
|
|
88
88
|
constructor(
|
|
89
89
|
private readonly _document: Document,
|
|
90
90
|
private readonly _config: ICharAtlasConfig,
|
|
91
|
-
private readonly _unicodeService: IUnicodeService
|
|
91
|
+
private readonly _unicodeService: IUnicodeService,
|
|
92
|
+
private readonly _logService: ILogService
|
|
92
93
|
) {
|
|
93
94
|
this._createNewPage();
|
|
94
95
|
this._tmpCanvas = createCanvas(
|
|
@@ -119,7 +120,7 @@ export class TextureAtlas implements ITextureAtlas {
|
|
|
119
120
|
|
|
120
121
|
private _doWarmUp(): void {
|
|
121
122
|
// Pre-fill with ASCII 33-126, this is not urgent and done in idle callbacks
|
|
122
|
-
const queue = new IdleTaskQueue();
|
|
123
|
+
const queue = new IdleTaskQueue(this._logService);
|
|
123
124
|
for (let i = 33; i < 126; i++) {
|
|
124
125
|
queue.enqueue(() => {
|
|
125
126
|
if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT)) {
|
|
@@ -132,7 +133,9 @@ export class TextureAtlas implements ITextureAtlas {
|
|
|
132
133
|
|
|
133
134
|
private _requestClearModel = false;
|
|
134
135
|
public beginFrame(): boolean {
|
|
135
|
-
|
|
136
|
+
const result = this._requestClearModel;
|
|
137
|
+
this._requestClearModel = false;
|
|
138
|
+
return result;
|
|
136
139
|
}
|
|
137
140
|
|
|
138
141
|
public clearTexture(): void {
|
|
@@ -176,12 +179,23 @@ export class TextureAtlas implements ITextureAtlas {
|
|
|
176
179
|
|
|
177
180
|
// Gather details of the merge
|
|
178
181
|
const mergingPages = pagesBySize.slice(sameSizeI, sameSizeI + 4);
|
|
182
|
+
|
|
183
|
+
// Only proceed with merge if we have exactly 4 same-sized pages. If not, we cannot
|
|
184
|
+
// effectively reduce page count and merging would cause issues.
|
|
185
|
+
if (mergingPages.length < 4 || mergingPages.some(p => p.canvas.width !== mergingPages[0].canvas.width)) {
|
|
186
|
+
const newPage = new AtlasPage(this._document, this._textureSize);
|
|
187
|
+
this._pages.push(newPage);
|
|
188
|
+
this._activePages.push(newPage);
|
|
189
|
+
this._onAddTextureAtlasCanvas.fire(newPage.canvas);
|
|
190
|
+
return newPage;
|
|
191
|
+
}
|
|
192
|
+
|
|
179
193
|
const sortedMergingPagesIndexes = mergingPages.map(e => e.glyphs[0].texturePage).sort((a, b) => a > b ? 1 : -1);
|
|
180
194
|
const mergedPageIndex = this.pages.length - mergingPages.length;
|
|
181
195
|
|
|
182
196
|
// Merge into the new page
|
|
183
197
|
const mergedPage = this._mergePages(mergingPages, mergedPageIndex);
|
|
184
|
-
mergedPage.version
|
|
198
|
+
mergedPage.version = ++AtlasPage.nextVersion;
|
|
185
199
|
|
|
186
200
|
// Delete the pages, shifting glyph texture pages as needed
|
|
187
201
|
for (let i = sortedMergingPagesIndexes.length - 1; i >= 0; i--) {
|
|
@@ -239,7 +253,7 @@ export class TextureAtlas implements ITextureAtlas {
|
|
|
239
253
|
for (const g of adjustingPage.glyphs) {
|
|
240
254
|
g.texturePage--;
|
|
241
255
|
}
|
|
242
|
-
adjustingPage.version
|
|
256
|
+
adjustingPage.version = ++AtlasPage.nextVersion;
|
|
243
257
|
}
|
|
244
258
|
}
|
|
245
259
|
|
|
@@ -399,7 +413,7 @@ export class TextureAtlas implements ITextureAtlas {
|
|
|
399
413
|
const cache = this._getContrastCache(dim);
|
|
400
414
|
const adjustedColor = cache.getColor(bg, fg);
|
|
401
415
|
if (adjustedColor !== undefined) {
|
|
402
|
-
return adjustedColor
|
|
416
|
+
return adjustedColor ?? undefined;
|
|
403
417
|
}
|
|
404
418
|
|
|
405
419
|
const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse);
|
|
@@ -514,7 +528,8 @@ export class TextureAtlas implements ITextureAtlas {
|
|
|
514
528
|
// Draw custom characters if applicable
|
|
515
529
|
let customGlyph = false;
|
|
516
530
|
if (this._config.customGlyphs !== false) {
|
|
517
|
-
|
|
531
|
+
const variantOffset = this._workAttributeData.getUnderlineVariantOffset();
|
|
532
|
+
customGlyph = tryDrawCustomGlyph(this._tmpCtx, chars, padding, padding, this._config.deviceCellWidth, this._config.deviceCellHeight, this._config.deviceCharWidth, this._config.deviceCharHeight, this._config.fontSize, this._config.devicePixelRatio, backgroundColor.css, variantOffset);
|
|
518
533
|
}
|
|
519
534
|
|
|
520
535
|
// Whether to clear pixels based on a threshold difference between the glyph color and the
|
|
@@ -551,61 +566,67 @@ export class TextureAtlas implements ITextureAtlas {
|
|
|
551
566
|
}
|
|
552
567
|
this._tmpCtx.strokeStyle = this._getColorFromAnsiIndex(fg).css;
|
|
553
568
|
}
|
|
569
|
+
this._tmpCtx.fillStyle = this._tmpCtx.strokeStyle;
|
|
554
570
|
|
|
555
571
|
// Underline style/stroke
|
|
556
572
|
this._tmpCtx.beginPath();
|
|
557
573
|
const xLeft = padding;
|
|
558
|
-
const
|
|
559
|
-
const
|
|
560
|
-
const yBot = yTop + lineWidth * 2;
|
|
574
|
+
const yTopDefault = Math.ceil(padding + this._config.deviceCharHeight) - yOffset - (restrictToCellHeight ? lineWidth * 2 : 0);
|
|
575
|
+
const yBotDefault = yTopDefault + lineWidth * 2;
|
|
561
576
|
let nextOffset = this._workAttributeData.getUnderlineVariantOffset();
|
|
577
|
+
let yTop = 0;
|
|
578
|
+
let yBot = 0;
|
|
562
579
|
|
|
563
580
|
for (let i = 0; i < chWidth; i++) {
|
|
581
|
+
let wasFilled = false;
|
|
564
582
|
this._tmpCtx.save();
|
|
583
|
+
yTop = yTopDefault;
|
|
584
|
+
yBot = yBotDefault;
|
|
565
585
|
const xChLeft = xLeft + i * this._config.deviceCellWidth;
|
|
566
586
|
const xChRight = xLeft + (i + 1) * this._config.deviceCellWidth;
|
|
567
|
-
const xChMid = xChLeft + this._config.deviceCellWidth / 2;
|
|
568
587
|
switch (this._workAttributeData.extended.underlineStyle) {
|
|
569
588
|
case UnderlineStyle.DOUBLE:
|
|
570
|
-
this._tmpCtx.moveTo(xChLeft,
|
|
571
|
-
this._tmpCtx.lineTo(xChRight,
|
|
572
|
-
this._tmpCtx.moveTo(xChLeft,
|
|
573
|
-
this._tmpCtx.lineTo(xChRight,
|
|
589
|
+
this._tmpCtx.moveTo(xChLeft, yTopDefault);
|
|
590
|
+
this._tmpCtx.lineTo(xChRight, yTopDefault);
|
|
591
|
+
this._tmpCtx.moveTo(xChLeft, yBotDefault);
|
|
592
|
+
this._tmpCtx.lineTo(xChRight, yBotDefault);
|
|
574
593
|
break;
|
|
575
594
|
case UnderlineStyle.CURLY:
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
const yCurlyTop = lineWidth <= 1 ? yTop : Math.ceil(padding + this._config.deviceCharHeight + lineWidth / 2) - yOffset;
|
|
580
|
-
// Clip the left and right edges of the underline such that it can be drawn just outside
|
|
581
|
-
// the edge of the cell to ensure a continuous stroke when there are multiple underlined
|
|
582
|
-
// glyphs adjacent to one another.
|
|
595
|
+
yTop = this._config.deviceCharHeight + 1;
|
|
596
|
+
yBot = yTop + 3 * this._config.devicePixelRatio;
|
|
597
|
+
|
|
583
598
|
const clipRegion = new Path2D();
|
|
584
599
|
clipRegion.rect(xChLeft, yTop, this._config.deviceCellWidth, yBot - yTop);
|
|
585
600
|
this._tmpCtx.clip(clipRegion);
|
|
586
|
-
|
|
587
|
-
//
|
|
588
|
-
|
|
589
|
-
this.
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
601
|
+
|
|
602
|
+
// Draw a zigzag pattern, this is derived from the SVG used in monaco for the same
|
|
603
|
+
// style. The viewbox is 6x3 so scale it using that.
|
|
604
|
+
const cellW = this._config.deviceCellWidth;
|
|
605
|
+
const curlyH = (yBot - yTop);
|
|
606
|
+
const scaleX = cellW / 6;
|
|
607
|
+
const scaleY = curlyH / 3;
|
|
608
|
+
|
|
609
|
+
const polygons: number[][] = [
|
|
610
|
+
[0, 2, 1, 3, 2.4, 3, 0, 0.6],
|
|
611
|
+
[5.5, 0, 2.5, 3, 1.1, 3, 4.1, 0],
|
|
612
|
+
[4, 0, 6, 2, 6, 0.6, 5.4, 0],
|
|
613
|
+
];
|
|
614
|
+
|
|
615
|
+
for (const polygon of polygons) {
|
|
616
|
+
this._tmpCtx.beginPath();
|
|
617
|
+
for (let i = 0; i < polygon.length; i += 2) {
|
|
618
|
+
const x = xChLeft + polygon[i] * scaleX;
|
|
619
|
+
const y = yBot - polygon[i + 1] * scaleY;
|
|
620
|
+
if (i === 0) {
|
|
621
|
+
this._tmpCtx.moveTo(x, y);
|
|
622
|
+
} else {
|
|
623
|
+
this._tmpCtx.lineTo(x, y);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
this._tmpCtx.closePath();
|
|
627
|
+
this._tmpCtx.fill();
|
|
628
|
+
}
|
|
629
|
+
wasFilled = true;
|
|
609
630
|
break;
|
|
610
631
|
case UnderlineStyle.DOTTED:
|
|
611
632
|
const offsetWidth = nextOffset === 0 ? 0 :
|
|
@@ -614,14 +635,14 @@ export class TextureAtlas implements ITextureAtlas {
|
|
|
614
635
|
const isLineStart = nextOffset >= lineWidth ? false : true;
|
|
615
636
|
if (isLineStart === false || offsetWidth === 0) {
|
|
616
637
|
this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
|
|
617
|
-
this._tmpCtx.moveTo(xChLeft + offsetWidth,
|
|
618
|
-
this._tmpCtx.lineTo(xChRight,
|
|
638
|
+
this._tmpCtx.moveTo(xChLeft + offsetWidth, yTopDefault);
|
|
639
|
+
this._tmpCtx.lineTo(xChRight, yTopDefault);
|
|
619
640
|
} else {
|
|
620
641
|
this._tmpCtx.setLineDash([Math.round(lineWidth), Math.round(lineWidth)]);
|
|
621
|
-
this._tmpCtx.moveTo(xChLeft,
|
|
622
|
-
this._tmpCtx.lineTo(xChLeft + offsetWidth,
|
|
623
|
-
this._tmpCtx.moveTo(xChLeft + offsetWidth + lineWidth,
|
|
624
|
-
this._tmpCtx.lineTo(xChRight,
|
|
642
|
+
this._tmpCtx.moveTo(xChLeft, yTopDefault);
|
|
643
|
+
this._tmpCtx.lineTo(xChLeft + offsetWidth, yTopDefault);
|
|
644
|
+
this._tmpCtx.moveTo(xChLeft + offsetWidth + lineWidth, yTopDefault);
|
|
645
|
+
this._tmpCtx.lineTo(xChRight, yTopDefault);
|
|
625
646
|
}
|
|
626
647
|
nextOffset = computeNextVariantOffset(xChRight - xChLeft, lineWidth, nextOffset);
|
|
627
648
|
break;
|
|
@@ -634,16 +655,18 @@ export class TextureAtlas implements ITextureAtlas {
|
|
|
634
655
|
const gap = Math.floor(gapRatio * xChWidth);
|
|
635
656
|
const end = xChWidth - line - gap;
|
|
636
657
|
this._tmpCtx.setLineDash([line, gap, end]);
|
|
637
|
-
this._tmpCtx.moveTo(xChLeft,
|
|
638
|
-
this._tmpCtx.lineTo(xChRight,
|
|
658
|
+
this._tmpCtx.moveTo(xChLeft, yTopDefault);
|
|
659
|
+
this._tmpCtx.lineTo(xChRight, yTopDefault);
|
|
639
660
|
break;
|
|
640
661
|
case UnderlineStyle.SINGLE:
|
|
641
662
|
default:
|
|
642
|
-
this._tmpCtx.moveTo(xChLeft,
|
|
643
|
-
this._tmpCtx.lineTo(xChRight,
|
|
663
|
+
this._tmpCtx.moveTo(xChLeft, yTopDefault);
|
|
664
|
+
this._tmpCtx.lineTo(xChRight, yTopDefault);
|
|
644
665
|
break;
|
|
645
666
|
}
|
|
646
|
-
|
|
667
|
+
if (!wasFilled) {
|
|
668
|
+
this._tmpCtx.stroke();
|
|
669
|
+
}
|
|
647
670
|
this._tmpCtx.restore();
|
|
648
671
|
}
|
|
649
672
|
this._tmpCtx.restore();
|
|
@@ -916,7 +939,7 @@ export class TextureAtlas implements ITextureAtlas {
|
|
|
916
939
|
rasterizedGlyph.size.y
|
|
917
940
|
);
|
|
918
941
|
activePage.addGlyph(rasterizedGlyph);
|
|
919
|
-
activePage.version
|
|
942
|
+
activePage.version = ++AtlasPage.nextVersion;
|
|
920
943
|
|
|
921
944
|
return rasterizedGlyph;
|
|
922
945
|
}
|
|
@@ -1026,9 +1049,13 @@ class AtlasPage {
|
|
|
1026
1049
|
}
|
|
1027
1050
|
|
|
1028
1051
|
/**
|
|
1029
|
-
*
|
|
1052
|
+
* Monotonically increasing across all atlas pages globally. Used to detect when the texture
|
|
1053
|
+
* unit at a given index needs to be re-uploaded — both for content changes within the same
|
|
1054
|
+
* page and for a page object swap at the same index (which happens after a page merge,
|
|
1055
|
+
* where a per-page counter could coincide with the previously-bound page's value).
|
|
1030
1056
|
*/
|
|
1031
|
-
public
|
|
1057
|
+
public static nextVersion: number = 0;
|
|
1058
|
+
public version = ++AtlasPage.nextVersion;
|
|
1032
1059
|
|
|
1033
1060
|
// Texture atlas current positioning data. The texture packing strategy used is to fill from
|
|
1034
1061
|
// left-to-right and top-to-bottom. When the glyph being written is less than half of the current
|
|
@@ -1071,7 +1098,7 @@ class AtlasPage {
|
|
|
1071
1098
|
this.currentRow.y = 0;
|
|
1072
1099
|
this.currentRow.height = 0;
|
|
1073
1100
|
this.fixedRows.length = 0;
|
|
1074
|
-
this.version
|
|
1101
|
+
this.version = ++AtlasPage.nextVersion;
|
|
1075
1102
|
}
|
|
1076
1103
|
}
|
|
1077
1104
|
|
package/src/Types.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { FontWeight } from '@xterm/xterm';
|
|
|
7
7
|
import { IColorSet } from 'browser/Types';
|
|
8
8
|
import { ISelectionRenderModel } from 'browser/renderer/shared/Types';
|
|
9
9
|
import { CursorInactiveStyle, CursorStyle, type IDisposable } from 'common/Types';
|
|
10
|
-
import type {
|
|
10
|
+
import type { IEvent } from 'common/Event';
|
|
11
11
|
|
|
12
12
|
export interface IRenderModel {
|
|
13
13
|
cells: Uint32Array;
|
|
@@ -58,8 +58,8 @@ export interface ICharAtlasConfig {
|
|
|
58
58
|
export interface ITextureAtlas extends IDisposable {
|
|
59
59
|
readonly pages: { canvas: HTMLCanvasElement, version: number }[];
|
|
60
60
|
|
|
61
|
-
onAddTextureAtlasCanvas:
|
|
62
|
-
onRemoveTextureAtlasCanvas:
|
|
61
|
+
onAddTextureAtlasCanvas: IEvent<HTMLCanvasElement>;
|
|
62
|
+
onRemoveTextureAtlasCanvas: IEvent<HTMLCanvasElement>;
|
|
63
63
|
|
|
64
64
|
/**
|
|
65
65
|
* Warm up the texture atlas, adding common glyphs to avoid slowing early frame.
|
package/src/WebglAddon.ts
CHANGED
|
@@ -7,15 +7,14 @@ import type { ITerminalAddon, Terminal } from '@xterm/xterm';
|
|
|
7
7
|
import type { IWebglAddonOptions, WebglAddon as IWebglApi } from '@xterm/addon-webgl';
|
|
8
8
|
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
|
|
9
9
|
import { ITerminal } from 'browser/Types';
|
|
10
|
-
import { Disposable, toDisposable } from '
|
|
10
|
+
import { Disposable, toDisposable } from 'common/Lifecycle';
|
|
11
11
|
import { getSafariVersion, isSafari } from 'common/Platform';
|
|
12
|
-
import { ICoreService, IDecorationService,
|
|
12
|
+
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
|
13
13
|
import { IWebGL2RenderingContext } from './Types';
|
|
14
14
|
import { WebglRenderer } from './WebglRenderer';
|
|
15
|
-
import {
|
|
16
|
-
import { Emitter, Event } from 'vs/base/common/event';
|
|
15
|
+
import { Emitter, EventUtils } from 'common/Event';
|
|
17
16
|
|
|
18
|
-
export class WebglAddon extends Disposable implements ITerminalAddon
|
|
17
|
+
export class WebglAddon extends Disposable implements ITerminalAddon, IWebglApi {
|
|
19
18
|
private _terminal?: Terminal;
|
|
20
19
|
private _renderer?: WebglRenderer;
|
|
21
20
|
|
|
@@ -66,13 +65,8 @@ export class WebglAddon extends Disposable implements ITerminalAddon , IWebglApi
|
|
|
66
65
|
const charSizeService: ICharSizeService = unsafeCore._charSizeService;
|
|
67
66
|
const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService;
|
|
68
67
|
const decorationService: IDecorationService = unsafeCore._decorationService;
|
|
69
|
-
const logService: ILogService = unsafeCore._logService;
|
|
70
68
|
const themeService: IThemeService = unsafeCore._themeService;
|
|
71
69
|
|
|
72
|
-
// Set trace logger just in case it hasn't been yet which could happen when the addon is
|
|
73
|
-
// bundled separately to the core module
|
|
74
|
-
setTraceLogger(logService);
|
|
75
|
-
|
|
76
70
|
this._renderer = this._register(new WebglRenderer(
|
|
77
71
|
terminal,
|
|
78
72
|
characterJoinerService,
|
|
@@ -85,10 +79,10 @@ export class WebglAddon extends Disposable implements ITerminalAddon , IWebglApi
|
|
|
85
79
|
this._customGlyphs,
|
|
86
80
|
this._preserveDrawingBuffer
|
|
87
81
|
));
|
|
88
|
-
this._register(
|
|
89
|
-
this._register(
|
|
90
|
-
this._register(
|
|
91
|
-
this._register(
|
|
82
|
+
this._register(EventUtils.forward(this._renderer.onContextLoss, this._onContextLoss));
|
|
83
|
+
this._register(EventUtils.forward(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas));
|
|
84
|
+
this._register(EventUtils.forward(this._renderer.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas));
|
|
85
|
+
this._register(EventUtils.forward(this._renderer.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas));
|
|
92
86
|
renderService.setRenderer(this._renderer);
|
|
93
87
|
|
|
94
88
|
this._register(toDisposable(() => {
|
package/src/WebglRenderer.ts
CHANGED
|
@@ -13,23 +13,29 @@ import { ICharSizeService, ICharacterJoinerService, ICoreBrowserService, IThemeS
|
|
|
13
13
|
import { CharData, IBufferLine, ICellData } from 'common/Types';
|
|
14
14
|
import { AttributeData } from 'common/buffer/AttributeData';
|
|
15
15
|
import { CellData } from 'common/buffer/CellData';
|
|
16
|
-
import { Attributes, Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
|
|
16
|
+
import { Attributes, Content, FgFlags, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
|
|
17
|
+
import { TextBlinkStateManager } from 'browser/renderer/shared/TextBlinkStateManager';
|
|
17
18
|
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
|
|
18
19
|
import { Terminal } from '@xterm/xterm';
|
|
19
20
|
import { GlyphRenderer } from './GlyphRenderer';
|
|
20
21
|
import { RectangleRenderer } from './RectangleRenderer';
|
|
21
|
-
import { COMBINED_CHAR_BIT_MASK,
|
|
22
|
+
import { COMBINED_CHAR_BIT_MASK, RenderModel, RenderModelConstants } from './RenderModel';
|
|
22
23
|
import { IWebGL2RenderingContext, type ITextureAtlas } from './Types';
|
|
23
24
|
import { LinkRenderLayer } from './renderLayer/LinkRenderLayer';
|
|
24
25
|
import { IRenderLayer } from './renderLayer/Types';
|
|
25
|
-
import { Emitter,
|
|
26
|
-
import { addDisposableListener } from '
|
|
27
|
-
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from '
|
|
26
|
+
import { Emitter, EventUtils } from 'common/Event';
|
|
27
|
+
import { addDisposableListener } from 'browser/Dom';
|
|
28
|
+
import { combinedDisposable, Disposable, MutableDisposable, toDisposable } from 'common/Lifecycle';
|
|
28
29
|
import { createRenderDimensions } from 'browser/renderer/shared/RendererUtils';
|
|
29
30
|
|
|
31
|
+
const enum Constants {
|
|
32
|
+
MERGE_RETRY_LIMIT = 32
|
|
33
|
+
}
|
|
34
|
+
|
|
30
35
|
export class WebglRenderer extends Disposable implements IRenderer {
|
|
31
36
|
private _renderLayers: IRenderLayer[];
|
|
32
|
-
private _cursorBlinkStateManager: MutableDisposable<CursorBlinkStateManager> = new MutableDisposable();
|
|
37
|
+
private _cursorBlinkStateManager: MutableDisposable<CursorBlinkStateManager> = this._register(new MutableDisposable());
|
|
38
|
+
private _textBlinkStateManager: TextBlinkStateManager;
|
|
33
39
|
private _charAtlasDisposable = this._register(new MutableDisposable());
|
|
34
40
|
private _charAtlas: ITextureAtlas | undefined;
|
|
35
41
|
private _devicePixelRatio: number;
|
|
@@ -37,8 +43,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
37
43
|
private _observerDisposable = this._register(new MutableDisposable());
|
|
38
44
|
|
|
39
45
|
private _model: RenderModel = new RenderModel();
|
|
46
|
+
private _rowHasBlinkingCells: boolean[] = [];
|
|
47
|
+
private _rowHasBlinkingCellsCount: number = 0;
|
|
40
48
|
private _workCell: ICellData = new CellData();
|
|
41
|
-
private _workCell2: ICellData = new CellData();
|
|
42
49
|
private _cellColorResolver: CellColorResolver;
|
|
43
50
|
|
|
44
51
|
private _canvas: HTMLCanvasElement;
|
|
@@ -105,6 +112,12 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
105
112
|
this._updateDimensions();
|
|
106
113
|
this._updateCursorBlink();
|
|
107
114
|
this._register(_optionsService.onOptionChange(() => this._handleOptionsChanged()));
|
|
115
|
+
this._textBlinkStateManager = this._register(new TextBlinkStateManager(
|
|
116
|
+
() => this._requestRedrawViewport(),
|
|
117
|
+
this._coreBrowserService,
|
|
118
|
+
this._optionsService
|
|
119
|
+
));
|
|
120
|
+
this._resetBlinkingRowState();
|
|
108
121
|
|
|
109
122
|
this._deviceMaxTextureSize = this._gl.getParameter(this._gl.MAX_TEXTURE_SIZE);
|
|
110
123
|
|
|
@@ -136,6 +149,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
136
149
|
this._observerDisposable.value = observeDevicePixelDimensions(this._canvas, w, (w, h) => this._setCanvasDevicePixelDimensions(w, h));
|
|
137
150
|
}));
|
|
138
151
|
|
|
152
|
+
this._register(addDisposableListener(this._coreBrowserService.mainDocument, 'mousedown', () => this._cursorBlinkStateManager.value?.restartBlinkAnimation()));
|
|
153
|
+
|
|
139
154
|
this._core.screenElement!.appendChild(this._canvas);
|
|
140
155
|
|
|
141
156
|
[this._rectangleRenderer.value, this._glyphRenderer.value] = this._initializeWebGLState();
|
|
@@ -143,6 +158,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
143
158
|
this._isAttached = this._core.screenElement!.isConnected;
|
|
144
159
|
|
|
145
160
|
this._register(toDisposable(() => {
|
|
161
|
+
clearTimeout(this._contextRestorationTimeout);
|
|
162
|
+
this._contextRestorationTimeout = undefined;
|
|
146
163
|
for (const l of this._renderLayers) {
|
|
147
164
|
l.dispose();
|
|
148
165
|
}
|
|
@@ -176,6 +193,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
176
193
|
this._updateDimensions();
|
|
177
194
|
|
|
178
195
|
this._model.resize(this._terminal.cols, this._terminal.rows);
|
|
196
|
+
this._resetBlinkingRowState();
|
|
179
197
|
|
|
180
198
|
// Resize all render layers
|
|
181
199
|
for (const l of this._renderLayers) {
|
|
@@ -202,6 +220,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
202
220
|
// Force a full refresh. Resizing `_glyphRenderer` should clear it already,
|
|
203
221
|
// so there is no need to clear it again here.
|
|
204
222
|
this._clearModel(false);
|
|
223
|
+
|
|
224
|
+
// Render synchronously to avoid flicker when the canvas is cleared
|
|
225
|
+
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1, sync: true });
|
|
205
226
|
}
|
|
206
227
|
|
|
207
228
|
public handleCharSizeChanged(): void {
|
|
@@ -226,6 +247,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
226
247
|
this._requestRedrawViewport();
|
|
227
248
|
}
|
|
228
249
|
|
|
250
|
+
public handleViewportVisibilityChange(isVisible: boolean): void {
|
|
251
|
+
this._textBlinkStateManager.setViewportVisible(isVisible);
|
|
252
|
+
}
|
|
253
|
+
|
|
229
254
|
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
|
230
255
|
for (const l of this._renderLayers) {
|
|
231
256
|
l.handleSelectionChanged(this._terminal, start, end, columnSelectMode);
|
|
@@ -285,8 +310,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
285
310
|
if (this._charAtlas !== atlas) {
|
|
286
311
|
this._onChangeTextureAtlas.fire(atlas.pages[0].canvas);
|
|
287
312
|
this._charAtlasDisposable.value = combinedDisposable(
|
|
288
|
-
|
|
289
|
-
|
|
313
|
+
EventUtils.forward(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas),
|
|
314
|
+
EventUtils.forward(atlas.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas)
|
|
290
315
|
);
|
|
291
316
|
}
|
|
292
317
|
this._charAtlas = atlas;
|
|
@@ -318,6 +343,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
318
343
|
l.reset(this._terminal);
|
|
319
344
|
}
|
|
320
345
|
|
|
346
|
+
this._resetBlinkingRowState();
|
|
347
|
+
this._textBlinkStateManager.setNeedsBlinkInViewport(false);
|
|
348
|
+
|
|
321
349
|
this._cursorBlinkStateManager.value?.restartBlinkAnimation();
|
|
322
350
|
this._updateCursorBlink();
|
|
323
351
|
}
|
|
@@ -353,6 +381,19 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
353
381
|
this._updateModel(start, end);
|
|
354
382
|
}
|
|
355
383
|
|
|
384
|
+
// A mid-update atlas page merge invalidates vertex data and may not bump the host
|
|
385
|
+
// page's version, so re-run the update and force a full texture rebind.
|
|
386
|
+
let merged = false;
|
|
387
|
+
let mergeRetries = 0;
|
|
388
|
+
while (this._charAtlas && this._glyphRenderer.value.beginFrame() && mergeRetries++ < Constants.MERGE_RETRY_LIMIT) {
|
|
389
|
+
merged = true;
|
|
390
|
+
this._clearModel(true);
|
|
391
|
+
this._updateModel(0, this._terminal.rows - 1);
|
|
392
|
+
}
|
|
393
|
+
if (merged) {
|
|
394
|
+
this._glyphRenderer.value.invalidateAtlasTextures();
|
|
395
|
+
}
|
|
396
|
+
|
|
356
397
|
// Render
|
|
357
398
|
this._rectangleRenderer.value.renderBackgrounds();
|
|
358
399
|
this._glyphRenderer.value.render(this._model);
|
|
@@ -415,6 +456,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
415
456
|
for (y = start; y <= end; y++) {
|
|
416
457
|
row = y + terminal.buffer.ydisp;
|
|
417
458
|
line = terminal.buffer.lines.get(row)!;
|
|
459
|
+
let rowHasBlinkingCells = false;
|
|
418
460
|
this._model.lineLengths[y] = 0;
|
|
419
461
|
isCursorRow = cursorY === row;
|
|
420
462
|
skipJoinedCheckUntilX = 0;
|
|
@@ -470,10 +512,14 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
470
512
|
|
|
471
513
|
chars = cell.getChars();
|
|
472
514
|
code = cell.getCode();
|
|
473
|
-
i = ((y * terminal.cols) + x) *
|
|
515
|
+
i = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL;
|
|
516
|
+
|
|
517
|
+
if (!rowHasBlinkingCells && cell.isBlink()) {
|
|
518
|
+
rowHasBlinkingCells = true;
|
|
519
|
+
}
|
|
474
520
|
|
|
475
521
|
// Load colors/resolve overrides into work colors
|
|
476
|
-
this._cellColorResolver.resolve(cell, x, row, this.dimensions.device.cell.width);
|
|
522
|
+
this._cellColorResolver.resolve(cell, x, row, this.dimensions.device.cell.width, this.dimensions.device.cell.height);
|
|
477
523
|
|
|
478
524
|
// Override colors for cursor cell
|
|
479
525
|
if (isCursorVisible && row === cursorY) {
|
|
@@ -501,15 +547,19 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
501
547
|
}
|
|
502
548
|
}
|
|
503
549
|
|
|
550
|
+
if (this._textBlinkStateManager.isEnabled && !this._textBlinkStateManager.isBlinkOn && cell.isBlink()) {
|
|
551
|
+
this._cellColorResolver.result.fg |= FgFlags.INVISIBLE;
|
|
552
|
+
}
|
|
553
|
+
|
|
504
554
|
if (code !== NULL_CELL_CODE) {
|
|
505
555
|
this._model.lineLengths[y] = x + 1;
|
|
506
556
|
}
|
|
507
557
|
|
|
508
558
|
// Nothing has changed, no updates needed
|
|
509
559
|
if (this._model.cells[i] === code &&
|
|
510
|
-
this._model.cells[i +
|
|
511
|
-
this._model.cells[i +
|
|
512
|
-
this._model.cells[i +
|
|
560
|
+
this._model.cells[i + RenderModelConstants.BG_OFFSET] === this._cellColorResolver.result.bg &&
|
|
561
|
+
this._model.cells[i + RenderModelConstants.FG_OFFSET] === this._cellColorResolver.result.fg &&
|
|
562
|
+
this._model.cells[i + RenderModelConstants.EXT_OFFSET] === this._cellColorResolver.result.ext) {
|
|
513
563
|
continue;
|
|
514
564
|
}
|
|
515
565
|
|
|
@@ -522,9 +572,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
522
572
|
|
|
523
573
|
// Cache the results in the model
|
|
524
574
|
this._model.cells[i] = code;
|
|
525
|
-
this._model.cells[i +
|
|
526
|
-
this._model.cells[i +
|
|
527
|
-
this._model.cells[i +
|
|
575
|
+
this._model.cells[i + RenderModelConstants.BG_OFFSET] = this._cellColorResolver.result.bg;
|
|
576
|
+
this._model.cells[i + RenderModelConstants.FG_OFFSET] = this._cellColorResolver.result.fg;
|
|
577
|
+
this._model.cells[i + RenderModelConstants.EXT_OFFSET] = this._cellColorResolver.result.ext;
|
|
528
578
|
|
|
529
579
|
width = cell.getWidth();
|
|
530
580
|
this._glyphRenderer.value!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, width, lastBg);
|
|
@@ -535,23 +585,43 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
535
585
|
|
|
536
586
|
// Null out non-first cells
|
|
537
587
|
for (x++; x <= lastCharX; x++) {
|
|
538
|
-
j = ((y * terminal.cols) + x) *
|
|
588
|
+
j = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL;
|
|
539
589
|
this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0, 0);
|
|
540
590
|
this._model.cells[j] = NULL_CELL_CODE;
|
|
541
591
|
// Don't re-resolve the cell color since multi-colored ligature backgrounds are not
|
|
542
592
|
// supported
|
|
543
|
-
this._model.cells[j +
|
|
544
|
-
this._model.cells[j +
|
|
545
|
-
this._model.cells[j +
|
|
593
|
+
this._model.cells[j + RenderModelConstants.BG_OFFSET] = this._cellColorResolver.result.bg;
|
|
594
|
+
this._model.cells[j + RenderModelConstants.FG_OFFSET] = this._cellColorResolver.result.fg;
|
|
595
|
+
this._model.cells[j + RenderModelConstants.EXT_OFFSET] = this._cellColorResolver.result.ext;
|
|
546
596
|
}
|
|
547
597
|
x--; // Go back to the previous update cell for next iteration
|
|
548
598
|
}
|
|
549
599
|
}
|
|
600
|
+
this._setRowBlinkState(y, rowHasBlinkingCells);
|
|
550
601
|
}
|
|
551
602
|
if (modelUpdated) {
|
|
552
603
|
this._rectangleRenderer.value!.updateBackgrounds(this._model);
|
|
553
604
|
}
|
|
554
605
|
this._rectangleRenderer.value!.updateCursor(this._model);
|
|
606
|
+
this._updateTextBlinkState();
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
private _resetBlinkingRowState(): void {
|
|
610
|
+
this._rowHasBlinkingCells = new Array(this._terminal.rows).fill(false);
|
|
611
|
+
this._rowHasBlinkingCellsCount = 0;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
private _setRowBlinkState(row: number, hasBlinkingCells: boolean): void {
|
|
615
|
+
const previous = this._rowHasBlinkingCells[row];
|
|
616
|
+
if (previous === hasBlinkingCells) {
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
this._rowHasBlinkingCells[row] = hasBlinkingCells;
|
|
620
|
+
this._rowHasBlinkingCellsCount += hasBlinkingCells ? 1 : -1;
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
private _updateTextBlinkState(): void {
|
|
624
|
+
this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount > 0);
|
|
555
625
|
}
|
|
556
626
|
|
|
557
627
|
/**
|
|
@@ -617,7 +687,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
617
687
|
// the change as it's an exact multiple of the cell sizes.
|
|
618
688
|
this._canvas.width = width;
|
|
619
689
|
this._canvas.height = height;
|
|
620
|
-
|
|
690
|
+
// Render synchronously to avoid flicker when the canvas is cleared
|
|
691
|
+
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1, sync: true });
|
|
621
692
|
}
|
|
622
693
|
|
|
623
694
|
private _requestRedrawViewport(): void {
|