Path: blob/main/src/vs/editor/browser/gpu/atlas/textureAtlasPage.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { Disposable, toDisposable } from '../../../../base/common/lifecycle.js';6import { NKeyMap } from '../../../../base/common/map.js';7import { ILogService, LogLevel } from '../../../../platform/log/common/log.js';8import { IThemeService } from '../../../../platform/theme/common/themeService.js';9import type { IBoundingBox, IGlyphRasterizer } from '../raster/raster.js';10import type { IReadableTextureAtlasPage, ITextureAtlasAllocator, ITextureAtlasPageGlyph, GlyphMap } from './atlas.js';11import { TextureAtlasShelfAllocator } from './textureAtlasShelfAllocator.js';12import { TextureAtlasSlabAllocator } from './textureAtlasSlabAllocator.js';1314export type AllocatorType = 'shelf' | 'slab' | ((canvas: OffscreenCanvas, textureIndex: number) => ITextureAtlasAllocator);1516export class TextureAtlasPage extends Disposable implements IReadableTextureAtlasPage {1718private _version: number = 0;19get version(): number { return this._version; }2021/**22* The maximum number of glyphs that can be drawn to the page. This is currently a hard static23* cap that must not be reached as it will cause the GPU buffer to overflow.24*/25static readonly maximumGlyphCount = 5_000;2627private _usedArea: IBoundingBox = { left: 0, top: 0, right: 0, bottom: 0 };28public get usedArea(): Readonly<IBoundingBox> { return this._usedArea; }2930private readonly _canvas: OffscreenCanvas;31get source(): OffscreenCanvas { return this._canvas; }3233private readonly _glyphMap: GlyphMap<ITextureAtlasPageGlyph> = new NKeyMap();34private readonly _glyphInOrderSet: Set<ITextureAtlasPageGlyph> = new Set();35get glyphs(): IterableIterator<ITextureAtlasPageGlyph> {36return this._glyphInOrderSet.values();37}3839private readonly _allocator: ITextureAtlasAllocator;40private _colorMap!: string[];4142constructor(43textureIndex: number,44pageSize: number,45allocatorType: AllocatorType,46@ILogService private readonly _logService: ILogService,47@IThemeService themeService: IThemeService,48) {49super();5051this._canvas = new OffscreenCanvas(pageSize, pageSize);52this._colorMap = themeService.getColorTheme().tokenColorMap;5354switch (allocatorType) {55case 'shelf': this._allocator = new TextureAtlasShelfAllocator(this._canvas, textureIndex); break;56case 'slab': this._allocator = new TextureAtlasSlabAllocator(this._canvas, textureIndex); break;57default: this._allocator = allocatorType(this._canvas, textureIndex); break;58}5960// Reduce impact of a memory leak if this object is not released61this._register(toDisposable(() => {62this._canvas.width = 1;63this._canvas.height = 1;64}));65}6667public getGlyph(rasterizer: IGlyphRasterizer, chars: string, tokenMetadata: number, decorationStyleSetId: number): Readonly<ITextureAtlasPageGlyph> | undefined {68// IMPORTANT: There are intentionally no intermediate variables here to aid in runtime69// optimization as it's a very hot function70return this._glyphMap.get(chars, tokenMetadata, decorationStyleSetId, rasterizer.cacheKey) ?? this._createGlyph(rasterizer, chars, tokenMetadata, decorationStyleSetId);71}7273private _createGlyph(rasterizer: IGlyphRasterizer, chars: string, tokenMetadata: number, decorationStyleSetId: number): Readonly<ITextureAtlasPageGlyph> | undefined {74// Ensure the glyph can fit on the page75if (this._glyphInOrderSet.size >= TextureAtlasPage.maximumGlyphCount) {76return undefined;77}7879// Rasterize and allocate the glyph80const rasterizedGlyph = rasterizer.rasterizeGlyph(chars, tokenMetadata, decorationStyleSetId, this._colorMap);81const glyph = this._allocator.allocate(rasterizedGlyph);8283// Ensure the glyph was allocated84if (glyph === undefined) {85// TODO: undefined here can mean the glyph was too large for a slab on the page, this86// can lead to big problems if we don't handle it properly https://github.com/microsoft/vscode/issues/23298487return undefined;88}8990// Save the glyph91this._glyphMap.set(glyph, chars, tokenMetadata, decorationStyleSetId, rasterizer.cacheKey);92this._glyphInOrderSet.add(glyph);9394// Update page version and it's tracked used area95this._version++;96this._usedArea.right = Math.max(this._usedArea.right, glyph.x + glyph.w - 1);97this._usedArea.bottom = Math.max(this._usedArea.bottom, glyph.y + glyph.h - 1);9899if (this._logService.getLevel() === LogLevel.Trace) {100this._logService.trace('New glyph', {101chars,102tokenMetadata,103decorationStyleSetId,104rasterizedGlyph,105glyph106});107}108109return glyph;110}111112getUsagePreview(): Promise<Blob> {113return this._allocator.getUsagePreview();114}115116getStats(): string {117return this._allocator.getStats();118}119}120121122