@xterm/addon-webgl 0.20.0-beta.23 → 0.20.0-beta.230
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 +91 -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();
|
|
@@ -176,6 +191,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
176
191
|
this._updateDimensions();
|
|
177
192
|
|
|
178
193
|
this._model.resize(this._terminal.cols, this._terminal.rows);
|
|
194
|
+
this._resetBlinkingRowState();
|
|
179
195
|
|
|
180
196
|
// Resize all render layers
|
|
181
197
|
for (const l of this._renderLayers) {
|
|
@@ -202,6 +218,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
202
218
|
// Force a full refresh. Resizing `_glyphRenderer` should clear it already,
|
|
203
219
|
// so there is no need to clear it again here.
|
|
204
220
|
this._clearModel(false);
|
|
221
|
+
|
|
222
|
+
// Render synchronously to avoid flicker when the canvas is cleared
|
|
223
|
+
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1, sync: true });
|
|
205
224
|
}
|
|
206
225
|
|
|
207
226
|
public handleCharSizeChanged(): void {
|
|
@@ -226,6 +245,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
226
245
|
this._requestRedrawViewport();
|
|
227
246
|
}
|
|
228
247
|
|
|
248
|
+
public handleViewportVisibilityChange(isVisible: boolean): void {
|
|
249
|
+
this._textBlinkStateManager.setViewportVisible(isVisible);
|
|
250
|
+
}
|
|
251
|
+
|
|
229
252
|
public handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void {
|
|
230
253
|
for (const l of this._renderLayers) {
|
|
231
254
|
l.handleSelectionChanged(this._terminal, start, end, columnSelectMode);
|
|
@@ -285,8 +308,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
285
308
|
if (this._charAtlas !== atlas) {
|
|
286
309
|
this._onChangeTextureAtlas.fire(atlas.pages[0].canvas);
|
|
287
310
|
this._charAtlasDisposable.value = combinedDisposable(
|
|
288
|
-
|
|
289
|
-
|
|
311
|
+
EventUtils.forward(atlas.onAddTextureAtlasCanvas, this._onAddTextureAtlasCanvas),
|
|
312
|
+
EventUtils.forward(atlas.onRemoveTextureAtlasCanvas, this._onRemoveTextureAtlasCanvas)
|
|
290
313
|
);
|
|
291
314
|
}
|
|
292
315
|
this._charAtlas = atlas;
|
|
@@ -318,6 +341,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
318
341
|
l.reset(this._terminal);
|
|
319
342
|
}
|
|
320
343
|
|
|
344
|
+
this._resetBlinkingRowState();
|
|
345
|
+
this._textBlinkStateManager.setNeedsBlinkInViewport(false);
|
|
346
|
+
|
|
321
347
|
this._cursorBlinkStateManager.value?.restartBlinkAnimation();
|
|
322
348
|
this._updateCursorBlink();
|
|
323
349
|
}
|
|
@@ -353,6 +379,19 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
353
379
|
this._updateModel(start, end);
|
|
354
380
|
}
|
|
355
381
|
|
|
382
|
+
// A mid-update atlas page merge invalidates vertex data and may not bump the host
|
|
383
|
+
// page's version, so re-run the update and force a full texture rebind.
|
|
384
|
+
let merged = false;
|
|
385
|
+
let mergeRetries = 0;
|
|
386
|
+
while (this._charAtlas && this._glyphRenderer.value.beginFrame() && mergeRetries++ < Constants.MERGE_RETRY_LIMIT) {
|
|
387
|
+
merged = true;
|
|
388
|
+
this._clearModel(true);
|
|
389
|
+
this._updateModel(0, this._terminal.rows - 1);
|
|
390
|
+
}
|
|
391
|
+
if (merged) {
|
|
392
|
+
this._glyphRenderer.value.invalidateAtlasTextures();
|
|
393
|
+
}
|
|
394
|
+
|
|
356
395
|
// Render
|
|
357
396
|
this._rectangleRenderer.value.renderBackgrounds();
|
|
358
397
|
this._glyphRenderer.value.render(this._model);
|
|
@@ -415,6 +454,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
415
454
|
for (y = start; y <= end; y++) {
|
|
416
455
|
row = y + terminal.buffer.ydisp;
|
|
417
456
|
line = terminal.buffer.lines.get(row)!;
|
|
457
|
+
let rowHasBlinkingCells = false;
|
|
418
458
|
this._model.lineLengths[y] = 0;
|
|
419
459
|
isCursorRow = cursorY === row;
|
|
420
460
|
skipJoinedCheckUntilX = 0;
|
|
@@ -470,10 +510,14 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
470
510
|
|
|
471
511
|
chars = cell.getChars();
|
|
472
512
|
code = cell.getCode();
|
|
473
|
-
i = ((y * terminal.cols) + x) *
|
|
513
|
+
i = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL;
|
|
514
|
+
|
|
515
|
+
if (!rowHasBlinkingCells && cell.isBlink()) {
|
|
516
|
+
rowHasBlinkingCells = true;
|
|
517
|
+
}
|
|
474
518
|
|
|
475
519
|
// Load colors/resolve overrides into work colors
|
|
476
|
-
this._cellColorResolver.resolve(cell, x, row, this.dimensions.device.cell.width);
|
|
520
|
+
this._cellColorResolver.resolve(cell, x, row, this.dimensions.device.cell.width, this.dimensions.device.cell.height);
|
|
477
521
|
|
|
478
522
|
// Override colors for cursor cell
|
|
479
523
|
if (isCursorVisible && row === cursorY) {
|
|
@@ -501,15 +545,19 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
501
545
|
}
|
|
502
546
|
}
|
|
503
547
|
|
|
548
|
+
if (this._textBlinkStateManager.isEnabled && !this._textBlinkStateManager.isBlinkOn && cell.isBlink()) {
|
|
549
|
+
this._cellColorResolver.result.fg |= FgFlags.INVISIBLE;
|
|
550
|
+
}
|
|
551
|
+
|
|
504
552
|
if (code !== NULL_CELL_CODE) {
|
|
505
553
|
this._model.lineLengths[y] = x + 1;
|
|
506
554
|
}
|
|
507
555
|
|
|
508
556
|
// Nothing has changed, no updates needed
|
|
509
557
|
if (this._model.cells[i] === code &&
|
|
510
|
-
this._model.cells[i +
|
|
511
|
-
this._model.cells[i +
|
|
512
|
-
this._model.cells[i +
|
|
558
|
+
this._model.cells[i + RenderModelConstants.BG_OFFSET] === this._cellColorResolver.result.bg &&
|
|
559
|
+
this._model.cells[i + RenderModelConstants.FG_OFFSET] === this._cellColorResolver.result.fg &&
|
|
560
|
+
this._model.cells[i + RenderModelConstants.EXT_OFFSET] === this._cellColorResolver.result.ext) {
|
|
513
561
|
continue;
|
|
514
562
|
}
|
|
515
563
|
|
|
@@ -522,9 +570,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
522
570
|
|
|
523
571
|
// Cache the results in the model
|
|
524
572
|
this._model.cells[i] = code;
|
|
525
|
-
this._model.cells[i +
|
|
526
|
-
this._model.cells[i +
|
|
527
|
-
this._model.cells[i +
|
|
573
|
+
this._model.cells[i + RenderModelConstants.BG_OFFSET] = this._cellColorResolver.result.bg;
|
|
574
|
+
this._model.cells[i + RenderModelConstants.FG_OFFSET] = this._cellColorResolver.result.fg;
|
|
575
|
+
this._model.cells[i + RenderModelConstants.EXT_OFFSET] = this._cellColorResolver.result.ext;
|
|
528
576
|
|
|
529
577
|
width = cell.getWidth();
|
|
530
578
|
this._glyphRenderer.value!.updateCell(x, y, code, this._cellColorResolver.result.bg, this._cellColorResolver.result.fg, this._cellColorResolver.result.ext, chars, width, lastBg);
|
|
@@ -535,23 +583,43 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
535
583
|
|
|
536
584
|
// Null out non-first cells
|
|
537
585
|
for (x++; x <= lastCharX; x++) {
|
|
538
|
-
j = ((y * terminal.cols) + x) *
|
|
586
|
+
j = ((y * terminal.cols) + x) * RenderModelConstants.INDICIES_PER_CELL;
|
|
539
587
|
this._glyphRenderer.value!.updateCell(x, y, NULL_CELL_CODE, 0, 0, 0, NULL_CELL_CHAR, 0, 0);
|
|
540
588
|
this._model.cells[j] = NULL_CELL_CODE;
|
|
541
589
|
// Don't re-resolve the cell color since multi-colored ligature backgrounds are not
|
|
542
590
|
// supported
|
|
543
|
-
this._model.cells[j +
|
|
544
|
-
this._model.cells[j +
|
|
545
|
-
this._model.cells[j +
|
|
591
|
+
this._model.cells[j + RenderModelConstants.BG_OFFSET] = this._cellColorResolver.result.bg;
|
|
592
|
+
this._model.cells[j + RenderModelConstants.FG_OFFSET] = this._cellColorResolver.result.fg;
|
|
593
|
+
this._model.cells[j + RenderModelConstants.EXT_OFFSET] = this._cellColorResolver.result.ext;
|
|
546
594
|
}
|
|
547
595
|
x--; // Go back to the previous update cell for next iteration
|
|
548
596
|
}
|
|
549
597
|
}
|
|
598
|
+
this._setRowBlinkState(y, rowHasBlinkingCells);
|
|
550
599
|
}
|
|
551
600
|
if (modelUpdated) {
|
|
552
601
|
this._rectangleRenderer.value!.updateBackgrounds(this._model);
|
|
553
602
|
}
|
|
554
603
|
this._rectangleRenderer.value!.updateCursor(this._model);
|
|
604
|
+
this._updateTextBlinkState();
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
private _resetBlinkingRowState(): void {
|
|
608
|
+
this._rowHasBlinkingCells = new Array(this._terminal.rows).fill(false);
|
|
609
|
+
this._rowHasBlinkingCellsCount = 0;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
private _setRowBlinkState(row: number, hasBlinkingCells: boolean): void {
|
|
613
|
+
const previous = this._rowHasBlinkingCells[row];
|
|
614
|
+
if (previous === hasBlinkingCells) {
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
this._rowHasBlinkingCells[row] = hasBlinkingCells;
|
|
618
|
+
this._rowHasBlinkingCellsCount += hasBlinkingCells ? 1 : -1;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
private _updateTextBlinkState(): void {
|
|
622
|
+
this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount > 0);
|
|
555
623
|
}
|
|
556
624
|
|
|
557
625
|
/**
|
|
@@ -617,7 +685,8 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
|
|
617
685
|
// the change as it's an exact multiple of the cell sizes.
|
|
618
686
|
this._canvas.width = width;
|
|
619
687
|
this._canvas.height = height;
|
|
620
|
-
|
|
688
|
+
// Render synchronously to avoid flicker when the canvas is cleared
|
|
689
|
+
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1, sync: true });
|
|
621
690
|
}
|
|
622
691
|
|
|
623
692
|
private _requestRedrawViewport(): void {
|