Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/gpu/atlas/textureAtlasPage.ts
3296 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 { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';
7
import { NKeyMap } from '../../../../base/common/map.js';
8
import { ILogService, LogLevel } from '../../../../platform/log/common/log.js';
9
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
10
import type { IBoundingBox, IGlyphRasterizer } from '../raster/raster.js';
11
import type { IReadableTextureAtlasPage, ITextureAtlasAllocator, ITextureAtlasPageGlyph, GlyphMap } from './atlas.js';
12
import { TextureAtlasShelfAllocator } from './textureAtlasShelfAllocator.js';
13
import { TextureAtlasSlabAllocator } from './textureAtlasSlabAllocator.js';
14
15
export type AllocatorType = 'shelf' | 'slab' | ((canvas: OffscreenCanvas, textureIndex: number) => ITextureAtlasAllocator);
16
17
export class TextureAtlasPage extends Disposable implements IReadableTextureAtlasPage {
18
19
private _version: number = 0;
20
get version(): number { return this._version; }
21
22
/**
23
* The maximum number of glyphs that can be drawn to the page. This is currently a hard static
24
* cap that must not be reached as it will cause the GPU buffer to overflow.
25
*/
26
static readonly maximumGlyphCount = 5_000;
27
28
private _usedArea: IBoundingBox = { left: 0, top: 0, right: 0, bottom: 0 };
29
public get usedArea(): Readonly<IBoundingBox> { return this._usedArea; }
30
31
private readonly _canvas: OffscreenCanvas;
32
get source(): OffscreenCanvas { return this._canvas; }
33
34
private readonly _glyphMap: GlyphMap<ITextureAtlasPageGlyph> = new NKeyMap();
35
private readonly _glyphInOrderSet: Set<ITextureAtlasPageGlyph> = new Set();
36
get glyphs(): IterableIterator<ITextureAtlasPageGlyph> {
37
return this._glyphInOrderSet.values();
38
}
39
40
private readonly _allocator: ITextureAtlasAllocator;
41
private _colorMap!: string[];
42
43
constructor(
44
textureIndex: number,
45
pageSize: number,
46
allocatorType: AllocatorType,
47
@ILogService private readonly _logService: ILogService,
48
@IThemeService themeService: IThemeService,
49
) {
50
super();
51
52
this._canvas = new OffscreenCanvas(pageSize, pageSize);
53
this._colorMap = themeService.getColorTheme().tokenColorMap;
54
55
switch (allocatorType) {
56
case 'shelf': this._allocator = new TextureAtlasShelfAllocator(this._canvas, textureIndex); break;
57
case 'slab': this._allocator = new TextureAtlasSlabAllocator(this._canvas, textureIndex); break;
58
default: this._allocator = allocatorType(this._canvas, textureIndex); break;
59
}
60
61
// Reduce impact of a memory leak if this object is not released
62
this._register(toDisposable(() => {
63
this._canvas.width = 1;
64
this._canvas.height = 1;
65
}));
66
}
67
68
public getGlyph(rasterizer: IGlyphRasterizer, chars: string, tokenMetadata: number, decorationStyleSetId: number): Readonly<ITextureAtlasPageGlyph> | undefined {
69
// IMPORTANT: There are intentionally no intermediate variables here to aid in runtime
70
// optimization as it's a very hot function
71
return this._glyphMap.get(chars, tokenMetadata, decorationStyleSetId, rasterizer.cacheKey) ?? this._createGlyph(rasterizer, chars, tokenMetadata, decorationStyleSetId);
72
}
73
74
private _createGlyph(rasterizer: IGlyphRasterizer, chars: string, tokenMetadata: number, decorationStyleSetId: number): Readonly<ITextureAtlasPageGlyph> | undefined {
75
// Ensure the glyph can fit on the page
76
if (this._glyphInOrderSet.size >= TextureAtlasPage.maximumGlyphCount) {
77
return undefined;
78
}
79
80
// Rasterize and allocate the glyph
81
const rasterizedGlyph = rasterizer.rasterizeGlyph(chars, tokenMetadata, decorationStyleSetId, this._colorMap);
82
const glyph = this._allocator.allocate(rasterizedGlyph);
83
84
// Ensure the glyph was allocated
85
if (glyph === undefined) {
86
// TODO: undefined here can mean the glyph was too large for a slab on the page, this
87
// can lead to big problems if we don't handle it properly https://github.com/microsoft/vscode/issues/232984
88
return undefined;
89
}
90
91
// Save the glyph
92
this._glyphMap.set(glyph, chars, tokenMetadata, decorationStyleSetId, rasterizer.cacheKey);
93
this._glyphInOrderSet.add(glyph);
94
95
// Update page version and it's tracked used area
96
this._version++;
97
this._usedArea.right = Math.max(this._usedArea.right, glyph.x + glyph.w - 1);
98
this._usedArea.bottom = Math.max(this._usedArea.bottom, glyph.y + glyph.h - 1);
99
100
if (this._logService.getLevel() === LogLevel.Trace) {
101
this._logService.trace('New glyph', {
102
chars,
103
tokenMetadata,
104
decorationStyleSetId,
105
rasterizedGlyph,
106
glyph
107
});
108
}
109
110
return glyph;
111
}
112
113
getUsagePreview(): Promise<Blob> {
114
return this._allocator.getUsagePreview();
115
}
116
117
getStats(): string {
118
return this._allocator.getStats();
119
}
120
}
121
122