Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/test/browser/gpu/atlas/textureAtlas.test.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 { strictEqual, throws } from 'assert';
7
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
8
import type { IGlyphRasterizer, IRasterizedGlyph } from '../../../../browser/gpu/raster/raster.js';
9
import { ensureNonNullable } from '../../../../browser/gpu/gpuUtils.js';
10
import type { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
11
import { TextureAtlas } from '../../../../browser/gpu/atlas/textureAtlas.js';
12
import { createCodeEditorServices } from '../../testCodeEditor.js';
13
import { assertIsValidGlyph } from './testUtil.js';
14
import { TextureAtlasSlabAllocator } from '../../../../browser/gpu/atlas/textureAtlasSlabAllocator.js';
15
import { DecorationStyleCache } from '../../../../browser/gpu/css/decorationStyleCache.js';
16
17
const blackInt = 0x000000FF;
18
const nullCharMetadata = 0x0;
19
20
let lastUniqueGlyph: string | undefined;
21
function getUniqueGlyphId(): [chars: string, tokenMetadata: number, charMetadata: number, x: number] {
22
if (!lastUniqueGlyph) {
23
lastUniqueGlyph = 'a';
24
} else {
25
lastUniqueGlyph = String.fromCharCode(lastUniqueGlyph.charCodeAt(0) + 1);
26
}
27
return [lastUniqueGlyph, blackInt, nullCharMetadata, 0];
28
}
29
30
class TestGlyphRasterizer implements IGlyphRasterizer {
31
readonly id = 0;
32
readonly cacheKey = '';
33
nextGlyphColor: [number, number, number, number] = [0, 0, 0, 0];
34
nextGlyphDimensions: [number, number] = [0, 0];
35
rasterizeGlyph(chars: string, tokenMetadata: number, charMetadata: number, colorMap: string[]): Readonly<IRasterizedGlyph> {
36
const w = this.nextGlyphDimensions[0];
37
const h = this.nextGlyphDimensions[1];
38
if (w === 0 || h === 0) {
39
throw new Error('TestGlyphRasterizer.nextGlyphDimensions must be set to a non-zero value before calling rasterizeGlyph');
40
}
41
const imageData = new ImageData(w, h);
42
let i = 0;
43
for (let y = 0; y < h; y++) {
44
for (let x = 0; x < w; x++) {
45
const [r, g, b, a] = this.nextGlyphColor;
46
i = (y * w + x) * 4;
47
imageData.data[i + 0] = r;
48
imageData.data[i + 1] = g;
49
imageData.data[i + 2] = b;
50
imageData.data[i + 3] = a;
51
}
52
}
53
const canvas = new OffscreenCanvas(w, h);
54
const ctx = ensureNonNullable(canvas.getContext('2d'));
55
ctx.putImageData(imageData, 0, 0);
56
return {
57
source: canvas,
58
boundingBox: { top: 0, left: 0, bottom: h - 1, right: w - 1 },
59
originOffset: { x: 0, y: 0 },
60
fontBoundingBoxAscent: 0,
61
fontBoundingBoxDescent: 0,
62
};
63
}
64
getTextMetrics(text: string): TextMetrics {
65
return null!;
66
}
67
}
68
69
suite('TextureAtlas', () => {
70
const store = ensureNoDisposablesAreLeakedInTestSuite();
71
72
suiteSetup(() => {
73
lastUniqueGlyph = undefined;
74
});
75
76
let instantiationService: IInstantiationService;
77
78
let atlas: TextureAtlas;
79
let glyphRasterizer: TestGlyphRasterizer;
80
81
setup(() => {
82
instantiationService = createCodeEditorServices(store);
83
atlas = store.add(instantiationService.createInstance(TextureAtlas, 2, undefined, new DecorationStyleCache()));
84
glyphRasterizer = new TestGlyphRasterizer();
85
glyphRasterizer.nextGlyphDimensions = [1, 1];
86
glyphRasterizer.nextGlyphColor = [0, 0, 0, 0xFF];
87
});
88
89
test('get single glyph', () => {
90
assertIsValidGlyph(atlas.getGlyph(glyphRasterizer, ...getUniqueGlyphId()), atlas);
91
});
92
93
test('get multiple glyphs', () => {
94
atlas = store.add(instantiationService.createInstance(TextureAtlas, 32, undefined, new DecorationStyleCache()));
95
for (let i = 0; i < 10; i++) {
96
assertIsValidGlyph(atlas.getGlyph(glyphRasterizer, ...getUniqueGlyphId()), atlas);
97
}
98
});
99
100
test('adding glyph to full page creates new page', () => {
101
let pageCount: number | undefined;
102
for (let i = 0; i < 4; i++) {
103
assertIsValidGlyph(atlas.getGlyph(glyphRasterizer, ...getUniqueGlyphId()), atlas);
104
if (pageCount === undefined) {
105
pageCount = atlas.pages.length;
106
} else {
107
strictEqual(atlas.pages.length, pageCount, 'the number of pages should not change when the page is being filled');
108
}
109
}
110
assertIsValidGlyph(atlas.getGlyph(glyphRasterizer, ...getUniqueGlyphId()), atlas);
111
strictEqual(atlas.pages.length, pageCount! + 1, 'the 5th glyph should overflow to a new page');
112
});
113
114
test('adding a glyph larger than the atlas', () => {
115
glyphRasterizer.nextGlyphDimensions = [3, 2];
116
throws(() => atlas.getGlyph(glyphRasterizer, ...getUniqueGlyphId()), 'should throw when the glyph is too large, this should not happen in practice');
117
});
118
119
test('adding a glyph larger than the standard slab size', () => {
120
glyphRasterizer.nextGlyphDimensions = [2, 2];
121
atlas = store.add(instantiationService.createInstance(TextureAtlas, 32, {
122
allocatorType: (canvas, textureIndex) => new TextureAtlasSlabAllocator(canvas, textureIndex, { slabW: 1, slabH: 1 })
123
}, new DecorationStyleCache()));
124
assertIsValidGlyph(atlas.getGlyph(glyphRasterizer, ...getUniqueGlyphId()), atlas);
125
});
126
127
test('adding a non-first glyph larger than the standard slab size, causing an overflow to a new page', () => {
128
atlas = store.add(instantiationService.createInstance(TextureAtlas, 2, {
129
allocatorType: (canvas, textureIndex) => new TextureAtlasSlabAllocator(canvas, textureIndex, { slabW: 1, slabH: 1 })
130
}, new DecorationStyleCache()));
131
assertIsValidGlyph(atlas.getGlyph(glyphRasterizer, ...getUniqueGlyphId()), atlas);
132
strictEqual(atlas.pages.length, 1);
133
glyphRasterizer.nextGlyphDimensions = [2, 2];
134
assertIsValidGlyph(atlas.getGlyph(glyphRasterizer, ...getUniqueGlyphId()), atlas);
135
strictEqual(atlas.pages.length, 2, 'the 2nd glyph should overflow to a new page with a larger slab size');
136
});
137
});
138
139