Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/gpu/renderStrategy/fullFileRenderStrategy.ts
5240 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { getActiveWindow } from '../../../../base/browser/dom.js';
7
import { Color } from '../../../../base/common/color.js';
8
import { BugIndicatingError } from '../../../../base/common/errors.js';
9
import { CursorColumns } from '../../../common/core/cursorColumns.js';
10
import type { IViewLineTokens } from '../../../common/tokens/lineTokens.js';
11
import { ViewEventType, type ViewConfigurationChangedEvent, type ViewDecorationsChangedEvent, type ViewLineMappingChangedEvent, type ViewLinesChangedEvent, type ViewLinesDeletedEvent, type ViewLinesInsertedEvent, type ViewScrollChangedEvent, type ViewThemeChangedEvent, type ViewTokensChangedEvent, type ViewZonesChangedEvent } from '../../../common/viewEvents.js';
12
import type { ViewportData } from '../../../common/viewLayout/viewLinesViewportData.js';
13
import type { ViewLineRenderingData } from '../../../common/viewModel.js';
14
import type { ViewContext } from '../../../common/viewModel/viewContext.js';
15
import type { ViewLineOptions } from '../../viewParts/viewLines/viewLineOptions.js';
16
import type { ITextureAtlasPageGlyph } from '../atlas/atlas.js';
17
import { createContentSegmenter, type IContentSegmenter } from '../contentSegmenter.js';
18
import { fullFileRenderStrategyWgsl } from './fullFileRenderStrategy.wgsl.js';
19
import { BindingId } from '../gpu.js';
20
import { GPULifecycle } from '../gpuDisposable.js';
21
import { quadVertices } from '../gpuUtils.js';
22
import { GlyphRasterizer } from '../raster/glyphRasterizer.js';
23
import { ViewGpuContext } from '../viewGpuContext.js';
24
import { BaseRenderStrategy } from './baseRenderStrategy.js';
25
import { InlineDecoration } from '../../../common/viewModel/inlineDecorations.js';
26
27
const enum Constants {
28
IndicesPerCell = 6,
29
}
30
31
const enum CellBufferInfo {
32
FloatsPerEntry = 6,
33
BytesPerEntry = CellBufferInfo.FloatsPerEntry * 4,
34
Offset_X = 0,
35
Offset_Y = 1,
36
Offset_Unused1 = 2,
37
Offset_Unused2 = 3,
38
GlyphIndex = 4,
39
TextureIndex = 5,
40
}
41
42
type QueuedBufferEvent = (
43
ViewConfigurationChangedEvent |
44
ViewLineMappingChangedEvent |
45
ViewLinesDeletedEvent |
46
ViewZonesChangedEvent
47
);
48
49
/**
50
* A render strategy that tracks a large buffer, uploading only dirty lines as they change and
51
* leveraging heavy caching. This is the most performant strategy but has limitations around long
52
* lines and too many lines.
53
*/
54
export class FullFileRenderStrategy extends BaseRenderStrategy {
55
56
/**
57
* The hard cap for line count that can be rendered by the GPU renderer.
58
*/
59
static readonly maxSupportedLines = 3000;
60
61
/**
62
* The hard cap for line columns that can be rendered by the GPU renderer.
63
*/
64
static readonly maxSupportedColumns = 200;
65
66
readonly type = 'fullfile';
67
readonly wgsl: string = fullFileRenderStrategyWgsl;
68
69
private _cellBindBuffer!: GPUBuffer;
70
71
/**
72
* The cell value buffers, these hold the cells and their glyphs. It's double buffers such that
73
* the thread doesn't block when one is being uploaded to the GPU.
74
*/
75
private _cellValueBuffers!: [ArrayBuffer, ArrayBuffer];
76
private _activeDoubleBufferIndex: 0 | 1 = 0;
77
78
private readonly _upToDateLines: [Set<number>, Set<number>] = [new Set(), new Set()];
79
private _visibleObjectCount: number = 0;
80
private _finalRenderedLine: number = 0;
81
82
private _scrollOffsetBindBuffer: GPUBuffer;
83
private _scrollOffsetValueBuffer: Float32Array;
84
private _scrollInitialized: boolean = false;
85
86
private readonly _queuedBufferUpdates: [QueuedBufferEvent[], QueuedBufferEvent[]] = [[], []];
87
88
get bindGroupEntries(): GPUBindGroupEntry[] {
89
return [
90
{ binding: BindingId.Cells, resource: { buffer: this._cellBindBuffer } },
91
{ binding: BindingId.ScrollOffset, resource: { buffer: this._scrollOffsetBindBuffer } }
92
];
93
}
94
95
constructor(
96
context: ViewContext,
97
viewGpuContext: ViewGpuContext,
98
device: GPUDevice,
99
glyphRasterizer: { value: GlyphRasterizer },
100
) {
101
super(context, viewGpuContext, device, glyphRasterizer);
102
103
const bufferSize = FullFileRenderStrategy.maxSupportedLines * FullFileRenderStrategy.maxSupportedColumns * Constants.IndicesPerCell * Float32Array.BYTES_PER_ELEMENT;
104
this._cellBindBuffer = this._register(GPULifecycle.createBuffer(this._device, {
105
label: 'Monaco full file cell buffer',
106
size: bufferSize,
107
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
108
})).object;
109
this._cellValueBuffers = [
110
new ArrayBuffer(bufferSize),
111
new ArrayBuffer(bufferSize),
112
];
113
114
const scrollOffsetBufferSize = 2;
115
this._scrollOffsetBindBuffer = this._register(GPULifecycle.createBuffer(this._device, {
116
label: 'Monaco scroll offset buffer',
117
size: scrollOffsetBufferSize * Float32Array.BYTES_PER_ELEMENT,
118
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
119
})).object;
120
this._scrollOffsetValueBuffer = new Float32Array(scrollOffsetBufferSize);
121
}
122
123
// #region Event handlers
124
125
// The primary job of these handlers is to:
126
// 1. Invalidate the up to date line cache, which will cause the line to be re-rendered when
127
// it's _within the viewport_.
128
// 2. Pass relevant events on to the render function so it can force certain line ranges to be
129
// re-rendered even if they're not in the viewport. For example when a view zone is added,
130
// there are lines that used to be visible but are no longer, so those ranges must be
131
// cleared and uploaded to the GPU.
132
133
public override onConfigurationChanged(e: ViewConfigurationChangedEvent): boolean {
134
this._invalidateAllLines();
135
this._queueBufferUpdate(e);
136
return true;
137
}
138
139
public override onDecorationsChanged(e: ViewDecorationsChangedEvent): boolean {
140
this._invalidateAllLines();
141
return true;
142
}
143
144
public override onTokensChanged(e: ViewTokensChangedEvent): boolean {
145
// TODO: This currently fires for the entire viewport whenever scrolling stops
146
// https://github.com/microsoft/vscode/issues/233942
147
for (const range of e.ranges) {
148
this._invalidateLineRange(range.fromLineNumber, range.toLineNumber);
149
}
150
return true;
151
}
152
153
public override onLinesDeleted(e: ViewLinesDeletedEvent): boolean {
154
// TODO: This currently invalidates everything after the deleted line, it could shift the
155
// line data up to retain some up to date lines
156
// TODO: This does not invalidate lines that are no longer in the file
157
this._invalidateLinesFrom(e.fromLineNumber);
158
this._queueBufferUpdate(e);
159
return true;
160
}
161
162
public override onLinesInserted(e: ViewLinesInsertedEvent): boolean {
163
// TODO: This currently invalidates everything after the deleted line, it could shift the
164
// line data up to retain some up to date lines
165
this._invalidateLinesFrom(e.fromLineNumber);
166
return true;
167
}
168
169
public override onLinesChanged(e: ViewLinesChangedEvent): boolean {
170
this._invalidateLineRange(e.fromLineNumber, e.fromLineNumber + e.count);
171
return true;
172
}
173
174
public override onScrollChanged(e?: ViewScrollChangedEvent): boolean {
175
if (this._store.isDisposed) {
176
return false;
177
}
178
const dpr = getActiveWindow().devicePixelRatio;
179
this._scrollOffsetValueBuffer[0] = (e?.scrollLeft ?? this._context.viewLayout.getCurrentScrollLeft()) * dpr;
180
this._scrollOffsetValueBuffer[1] = (e?.scrollTop ?? this._context.viewLayout.getCurrentScrollTop()) * dpr;
181
this._device.queue.writeBuffer(this._scrollOffsetBindBuffer, 0, this._scrollOffsetValueBuffer as Float32Array<ArrayBuffer>);
182
return true;
183
}
184
185
public override onThemeChanged(e: ViewThemeChangedEvent): boolean {
186
this._invalidateAllLines();
187
return true;
188
}
189
190
public override onLineMappingChanged(e: ViewLineMappingChangedEvent): boolean {
191
this._invalidateAllLines();
192
this._queueBufferUpdate(e);
193
return true;
194
}
195
196
public override onZonesChanged(e: ViewZonesChangedEvent): boolean {
197
this._invalidateAllLines();
198
this._queueBufferUpdate(e);
199
200
return true;
201
}
202
203
// #endregion
204
205
private _invalidateAllLines(): void {
206
this._upToDateLines[0].clear();
207
this._upToDateLines[1].clear();
208
}
209
210
private _invalidateLinesFrom(lineNumber: number): void {
211
for (const i of [0, 1]) {
212
const upToDateLines = this._upToDateLines[i];
213
for (const upToDateLine of upToDateLines) {
214
if (upToDateLine >= lineNumber) {
215
upToDateLines.delete(upToDateLine);
216
}
217
}
218
}
219
}
220
221
private _invalidateLineRange(fromLineNumber: number, toLineNumber: number): void {
222
for (let i = fromLineNumber; i <= toLineNumber; i++) {
223
this._upToDateLines[0].delete(i);
224
this._upToDateLines[1].delete(i);
225
}
226
}
227
228
reset() {
229
this._invalidateAllLines();
230
for (const bufferIndex of [0, 1]) {
231
// Zero out buffer and upload to GPU to prevent stale rows from rendering
232
const buffer = new Float32Array(this._cellValueBuffers[bufferIndex]);
233
buffer.fill(0, 0, buffer.length);
234
this._device.queue.writeBuffer(this._cellBindBuffer, 0, buffer.buffer, 0, buffer.byteLength);
235
}
236
this._finalRenderedLine = 0;
237
}
238
239
update(viewportData: ViewportData, viewLineOptions: ViewLineOptions): number {
240
// IMPORTANT: This is a hot function. Variables are pre-allocated and shared within the
241
// loop. This is done so we don't need to trust the JIT compiler to do this optimization to
242
// avoid potential additional blocking time in garbage collector which is a common cause of
243
// dropped frames.
244
245
let chars = '';
246
let segment: string | undefined;
247
let charWidth = 0;
248
let y = 0;
249
let x = 0;
250
let absoluteOffsetX = 0;
251
let absoluteOffsetY = 0;
252
let tabXOffset = 0;
253
let glyph: Readonly<ITextureAtlasPageGlyph>;
254
let cellIndex = 0;
255
256
let tokenStartIndex = 0;
257
let tokenEndIndex = 0;
258
let tokenMetadata = 0;
259
260
let decorationStyleSetBold: boolean | undefined;
261
let decorationStyleSetColor: number | undefined;
262
let decorationStyleSetOpacity: number | undefined;
263
let decorationStyleSetStrikethrough: boolean | undefined;
264
let decorationStyleSetStrikethroughThickness: number | undefined;
265
let decorationStyleSetStrikethroughColor: number | undefined;
266
267
let lineData: ViewLineRenderingData;
268
let decoration: InlineDecoration;
269
let fillStartIndex = 0;
270
let fillEndIndex = 0;
271
272
let tokens: IViewLineTokens;
273
274
const dpr = getActiveWindow().devicePixelRatio;
275
let contentSegmenter: IContentSegmenter;
276
277
if (!this._scrollInitialized) {
278
this.onScrollChanged();
279
this._scrollInitialized = true;
280
}
281
282
// Update cell data
283
const cellBuffer = new Float32Array(this._cellValueBuffers[this._activeDoubleBufferIndex]);
284
const lineIndexCount = FullFileRenderStrategy.maxSupportedColumns * Constants.IndicesPerCell;
285
286
const upToDateLines = this._upToDateLines[this._activeDoubleBufferIndex];
287
let dirtyLineStart = 3000;
288
let dirtyLineEnd = 0;
289
290
// Handle any queued buffer updates
291
const queuedBufferUpdates = this._queuedBufferUpdates[this._activeDoubleBufferIndex];
292
while (queuedBufferUpdates.length) {
293
const e = queuedBufferUpdates.shift()!;
294
switch (e.type) {
295
// TODO: Refine these cases so we're not throwing away everything
296
case ViewEventType.ViewConfigurationChanged:
297
case ViewEventType.ViewLineMappingChanged:
298
case ViewEventType.ViewZonesChanged: {
299
cellBuffer.fill(0);
300
301
dirtyLineStart = 1;
302
dirtyLineEnd = Math.max(dirtyLineEnd, this._finalRenderedLine);
303
this._finalRenderedLine = 0;
304
break;
305
}
306
case ViewEventType.ViewLinesDeleted: {
307
// Shift content below deleted line up
308
const deletedLineContentStartIndex = (e.fromLineNumber - 1) * FullFileRenderStrategy.maxSupportedColumns * Constants.IndicesPerCell;
309
const deletedLineContentEndIndex = (e.toLineNumber) * FullFileRenderStrategy.maxSupportedColumns * Constants.IndicesPerCell;
310
const nullContentStartIndex = (this._finalRenderedLine - (e.toLineNumber - e.fromLineNumber + 1)) * FullFileRenderStrategy.maxSupportedColumns * Constants.IndicesPerCell;
311
cellBuffer.set(cellBuffer.subarray(deletedLineContentEndIndex), deletedLineContentStartIndex);
312
313
// Zero out content on lines that are no longer valid
314
cellBuffer.fill(0, nullContentStartIndex);
315
316
// Update dirty lines and final rendered line
317
dirtyLineStart = Math.min(dirtyLineStart, e.fromLineNumber);
318
dirtyLineEnd = Math.max(dirtyLineEnd, this._finalRenderedLine);
319
this._finalRenderedLine -= e.toLineNumber - e.fromLineNumber + 1;
320
break;
321
}
322
}
323
}
324
325
for (y = viewportData.startLineNumber; y <= viewportData.endLineNumber; y++) {
326
327
// Only attempt to render lines that the GPU renderer can handle
328
if (!this._viewGpuContext.canRender(viewLineOptions, viewportData, y)) {
329
fillStartIndex = ((y - 1) * FullFileRenderStrategy.maxSupportedColumns) * Constants.IndicesPerCell;
330
fillEndIndex = (y * FullFileRenderStrategy.maxSupportedColumns) * Constants.IndicesPerCell;
331
cellBuffer.fill(0, fillStartIndex, fillEndIndex);
332
333
dirtyLineStart = Math.min(dirtyLineStart, y);
334
dirtyLineEnd = Math.max(dirtyLineEnd, y);
335
336
continue;
337
}
338
339
// Skip updating the line if it's already up to date
340
if (upToDateLines.has(y)) {
341
continue;
342
}
343
344
dirtyLineStart = Math.min(dirtyLineStart, y);
345
dirtyLineEnd = Math.max(dirtyLineEnd, y);
346
347
lineData = viewportData.getViewLineRenderingData(y);
348
tabXOffset = 0;
349
350
contentSegmenter = createContentSegmenter(lineData, viewLineOptions);
351
charWidth = viewLineOptions.spaceWidth * dpr;
352
absoluteOffsetX = (lineData.minColumn - 1) * charWidth;
353
354
tokens = lineData.tokens;
355
tokenStartIndex = lineData.minColumn - 1;
356
tokenEndIndex = 0;
357
for (let tokenIndex = 0, tokensLen = tokens.getCount(); tokenIndex < tokensLen; tokenIndex++) {
358
tokenEndIndex = tokens.getEndOffset(tokenIndex);
359
if (tokenEndIndex <= tokenStartIndex) {
360
// The faux indent part of the line should have no token type
361
continue;
362
}
363
364
tokenMetadata = tokens.getMetadata(tokenIndex);
365
366
for (x = tokenStartIndex; x < tokenEndIndex; x++) {
367
// Only render lines that do not exceed maximum columns
368
if (x > FullFileRenderStrategy.maxSupportedColumns) {
369
break;
370
}
371
segment = contentSegmenter.getSegmentAtIndex(x);
372
if (segment === undefined) {
373
continue;
374
}
375
chars = segment;
376
377
if (!(lineData.isBasicASCII && viewLineOptions.useMonospaceOptimizations)) {
378
charWidth = this.glyphRasterizer.getTextMetrics(chars).width;
379
}
380
381
decorationStyleSetColor = undefined;
382
decorationStyleSetBold = undefined;
383
decorationStyleSetOpacity = undefined;
384
decorationStyleSetStrikethrough = undefined;
385
decorationStyleSetStrikethroughThickness = undefined;
386
decorationStyleSetStrikethroughColor = undefined;
387
388
// Apply supported inline decoration styles to the cell metadata
389
for (decoration of lineData.inlineDecorations) {
390
// This is Range.strictContainsPosition except it works at the cell level,
391
// it's also inlined to avoid overhead.
392
if (
393
(y < decoration.range.startLineNumber || y > decoration.range.endLineNumber) ||
394
(y === decoration.range.startLineNumber && x < decoration.range.startColumn - 1) ||
395
(y === decoration.range.endLineNumber && x >= decoration.range.endColumn - 1)
396
) {
397
continue;
398
}
399
400
const rules = ViewGpuContext.decorationCssRuleExtractor.getStyleRules(this._viewGpuContext.canvas.domNode, decoration.inlineClassName);
401
for (const rule of rules) {
402
for (const r of rule.style) {
403
const value = rule.styleMap.get(r)?.toString() ?? '';
404
switch (r) {
405
case 'color': {
406
// TODO: This parsing and error handling should move into canRender so fallback
407
// to DOM works
408
const parsedColor = Color.Format.CSS.parse(value);
409
if (!parsedColor) {
410
throw new BugIndicatingError('Invalid color format ' + value);
411
}
412
decorationStyleSetColor = parsedColor.toNumber32Bit();
413
break;
414
}
415
case 'font-weight': {
416
const parsedValue = parseCssFontWeight(value);
417
if (parsedValue >= 400) {
418
decorationStyleSetBold = true;
419
// TODO: Set bold (https://github.com/microsoft/vscode/issues/237584)
420
} else {
421
decorationStyleSetBold = false;
422
// TODO: Set normal (https://github.com/microsoft/vscode/issues/237584)
423
}
424
break;
425
}
426
case 'opacity': {
427
const parsedValue = parseCssOpacity(value);
428
decorationStyleSetOpacity = parsedValue;
429
break;
430
}
431
case 'text-decoration':
432
case 'text-decoration-line': {
433
if (value === 'line-through') {
434
decorationStyleSetStrikethrough = true;
435
}
436
break;
437
}
438
case 'text-decoration-thickness': {
439
const match = value.match(/^(\d+(?:\.\d+)?)px$/);
440
if (match) {
441
decorationStyleSetStrikethroughThickness = parseFloat(match[1]);
442
}
443
break;
444
}
445
case 'text-decoration-color': {
446
let colorValue = value;
447
const varMatch = value.match(/^var\((--[^,]+),\s*(?:initial|inherit)\)$/);
448
if (varMatch) {
449
colorValue = ViewGpuContext.decorationCssRuleExtractor.resolveCssVariable(this._viewGpuContext.canvas.domNode, varMatch[1]);
450
}
451
const parsedColor = Color.Format.CSS.parse(colorValue);
452
if (parsedColor) {
453
decorationStyleSetStrikethroughColor = parsedColor.toNumber32Bit();
454
}
455
break;
456
}
457
case 'text-decoration-style': {
458
// These are validated in canRender and use default behavior
459
break;
460
}
461
default: throw new BugIndicatingError('Unexpected inline decoration style');
462
}
463
}
464
}
465
}
466
467
if (chars === ' ' || chars === '\t') {
468
// Zero out glyph to ensure it doesn't get rendered
469
cellIndex = ((y - 1) * FullFileRenderStrategy.maxSupportedColumns + x) * Constants.IndicesPerCell;
470
cellBuffer.fill(0, cellIndex, cellIndex + CellBufferInfo.FloatsPerEntry);
471
// Adjust xOffset for tab stops
472
if (chars === '\t') {
473
// Find the pixel offset between the current position and the next tab stop
474
const offsetBefore = x + tabXOffset;
475
tabXOffset = CursorColumns.nextRenderTabStop(x + tabXOffset, lineData.tabSize);
476
absoluteOffsetX += charWidth * (tabXOffset - offsetBefore);
477
// Convert back to offset excluding x and the current character
478
tabXOffset -= x + 1;
479
} else {
480
absoluteOffsetX += charWidth;
481
}
482
continue;
483
}
484
485
const decorationStyleSetId = ViewGpuContext.decorationStyleCache.getOrCreateEntry(decorationStyleSetColor, decorationStyleSetBold, decorationStyleSetOpacity, decorationStyleSetStrikethrough, decorationStyleSetStrikethroughThickness, decorationStyleSetStrikethroughColor);
486
glyph = this._viewGpuContext.atlas.getGlyph(this.glyphRasterizer, chars, tokenMetadata, decorationStyleSetId, absoluteOffsetX);
487
488
absoluteOffsetY = Math.round(
489
// Top of layout box (includes line height)
490
viewportData.relativeVerticalOffset[y - viewportData.startLineNumber] * dpr +
491
492
// Delta from top of layout box (includes line height) to top of the inline box (no line height)
493
Math.floor((viewportData.lineHeight * dpr - (glyph.fontBoundingBoxAscent + glyph.fontBoundingBoxDescent)) / 2) +
494
495
// Delta from top of inline box (no line height) to top of glyph origin. If the glyph was drawn
496
// with a top baseline for example, this ends up drawing the glyph correctly using the alphabetical
497
// baseline.
498
glyph.fontBoundingBoxAscent
499
);
500
501
cellIndex = ((y - 1) * FullFileRenderStrategy.maxSupportedColumns + x) * Constants.IndicesPerCell;
502
cellBuffer[cellIndex + CellBufferInfo.Offset_X] = Math.floor(absoluteOffsetX);
503
cellBuffer[cellIndex + CellBufferInfo.Offset_Y] = absoluteOffsetY;
504
cellBuffer[cellIndex + CellBufferInfo.GlyphIndex] = glyph.glyphIndex;
505
cellBuffer[cellIndex + CellBufferInfo.TextureIndex] = glyph.pageIndex;
506
507
// Adjust the x pixel offset for the next character
508
absoluteOffsetX += charWidth;
509
}
510
511
tokenStartIndex = tokenEndIndex;
512
}
513
514
// Clear to end of line
515
fillStartIndex = ((y - 1) * FullFileRenderStrategy.maxSupportedColumns + tokenEndIndex) * Constants.IndicesPerCell;
516
fillEndIndex = (y * FullFileRenderStrategy.maxSupportedColumns) * Constants.IndicesPerCell;
517
cellBuffer.fill(0, fillStartIndex, fillEndIndex);
518
519
upToDateLines.add(y);
520
}
521
522
const visibleObjectCount = (viewportData.endLineNumber - viewportData.startLineNumber + 1) * lineIndexCount;
523
524
// Only write when there is changed data
525
dirtyLineStart = Math.min(dirtyLineStart, FullFileRenderStrategy.maxSupportedLines);
526
dirtyLineEnd = Math.min(dirtyLineEnd, FullFileRenderStrategy.maxSupportedLines);
527
if (dirtyLineStart <= dirtyLineEnd) {
528
// Write buffer and swap it out to unblock writes
529
this._device.queue.writeBuffer(
530
this._cellBindBuffer,
531
(dirtyLineStart - 1) * lineIndexCount * Float32Array.BYTES_PER_ELEMENT,
532
cellBuffer.buffer,
533
(dirtyLineStart - 1) * lineIndexCount * Float32Array.BYTES_PER_ELEMENT,
534
(dirtyLineEnd - dirtyLineStart + 1) * lineIndexCount * Float32Array.BYTES_PER_ELEMENT
535
);
536
}
537
538
this._finalRenderedLine = Math.max(this._finalRenderedLine, dirtyLineEnd);
539
540
this._activeDoubleBufferIndex = this._activeDoubleBufferIndex ? 0 : 1;
541
542
this._visibleObjectCount = visibleObjectCount;
543
544
return visibleObjectCount;
545
}
546
547
draw(pass: GPURenderPassEncoder, viewportData: ViewportData): void {
548
if (this._visibleObjectCount <= 0) {
549
throw new BugIndicatingError('Attempt to draw 0 objects');
550
}
551
pass.draw(
552
quadVertices.length / 2,
553
this._visibleObjectCount,
554
undefined,
555
(viewportData.startLineNumber - 1) * FullFileRenderStrategy.maxSupportedColumns
556
);
557
}
558
559
/**
560
* Queue updates that need to happen on the active buffer, not just the cache. This will be
561
* deferred to when the actual cell buffer is changed since the active buffer could be locked by
562
* the GPU which would block the main thread.
563
*/
564
private _queueBufferUpdate(e: QueuedBufferEvent) {
565
this._queuedBufferUpdates[0].push(e);
566
this._queuedBufferUpdates[1].push(e);
567
}
568
}
569
570
function parseCssFontWeight(value: string) {
571
switch (value) {
572
case 'lighter':
573
case 'normal': return 400;
574
case 'bolder':
575
case 'bold': return 700;
576
}
577
return parseInt(value);
578
}
579
580
function parseCssOpacity(value: string): number {
581
if (value.endsWith('%')) {
582
return parseFloat(value.substring(0, value.length - 1)) / 100;
583
}
584
if (value.match(/^\d+(?:\.\d*)/)) {
585
return parseFloat(value);
586
}
587
return 1;
588
}
589
590