Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/gpu/viewGpuContext.ts
3294 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 * as nls from '../../../nls.js';
7
import { addDisposableListener, getActiveWindow } from '../../../base/browser/dom.js';
8
import { createFastDomNode, type FastDomNode } from '../../../base/browser/fastDomNode.js';
9
import { BugIndicatingError } from '../../../base/common/errors.js';
10
import { Disposable } from '../../../base/common/lifecycle.js';
11
import type { ViewportData } from '../../common/viewLayout/viewLinesViewportData.js';
12
import type { ViewLineOptions } from '../viewParts/viewLines/viewLineOptions.js';
13
import { observableValue, runOnChange, type IObservable } from '../../../base/common/observable.js';
14
import { IInstantiationService } from '../../../platform/instantiation/common/instantiation.js';
15
import { TextureAtlas } from './atlas/textureAtlas.js';
16
import { IConfigurationService } from '../../../platform/configuration/common/configuration.js';
17
import { INotificationService, IPromptChoice, Severity } from '../../../platform/notification/common/notification.js';
18
import { GPULifecycle } from './gpuDisposable.js';
19
import { ensureNonNullable, observeDevicePixelDimensions } from './gpuUtils.js';
20
import { RectangleRenderer } from './rectangleRenderer.js';
21
import type { ViewContext } from '../../common/viewModel/viewContext.js';
22
import { DecorationCssRuleExtractor } from './css/decorationCssRuleExtractor.js';
23
import { Event } from '../../../base/common/event.js';
24
import { EditorOption, type IEditorOptions } from '../../common/config/editorOptions.js';
25
import { DecorationStyleCache } from './css/decorationStyleCache.js';
26
import { InlineDecorationType } from '../../common/viewModel/inlineDecorations.js';
27
28
export class ViewGpuContext extends Disposable {
29
/**
30
* The hard cap for line columns rendered by the GPU renderer.
31
*/
32
readonly maxGpuCols = 2000;
33
34
readonly canvas: FastDomNode<HTMLCanvasElement>;
35
readonly ctx: GPUCanvasContext;
36
37
static device: Promise<GPUDevice>;
38
static deviceSync: GPUDevice | undefined;
39
40
readonly rectangleRenderer: RectangleRenderer;
41
42
private static readonly _decorationCssRuleExtractor = new DecorationCssRuleExtractor();
43
static get decorationCssRuleExtractor(): DecorationCssRuleExtractor {
44
return ViewGpuContext._decorationCssRuleExtractor;
45
}
46
47
private static readonly _decorationStyleCache = new DecorationStyleCache();
48
static get decorationStyleCache(): DecorationStyleCache {
49
return ViewGpuContext._decorationStyleCache;
50
}
51
52
private static _atlas: TextureAtlas | undefined;
53
54
/**
55
* The shared texture atlas to use across all views.
56
*
57
* @throws if called before the GPU device is resolved
58
*/
59
static get atlas(): TextureAtlas {
60
if (!ViewGpuContext._atlas) {
61
throw new BugIndicatingError('Cannot call ViewGpuContext.textureAtlas before device is resolved');
62
}
63
return ViewGpuContext._atlas;
64
}
65
/**
66
* The shared texture atlas to use across all views. This is a convenience alias for
67
* {@link ViewGpuContext.atlas}.
68
*
69
* @throws if called before the GPU device is resolved
70
*/
71
get atlas(): TextureAtlas {
72
return ViewGpuContext.atlas;
73
}
74
75
readonly canvasDevicePixelDimensions: IObservable<{ width: number; height: number }>;
76
readonly devicePixelRatio: IObservable<number>;
77
readonly contentLeft: IObservable<number>;
78
79
constructor(
80
context: ViewContext,
81
@IInstantiationService private readonly _instantiationService: IInstantiationService,
82
@INotificationService private readonly _notificationService: INotificationService,
83
@IConfigurationService private readonly configurationService: IConfigurationService,
84
) {
85
super();
86
87
this.canvas = createFastDomNode(document.createElement('canvas'));
88
this.canvas.setClassName('editorCanvas');
89
90
// Adjust the canvas size to avoid drawing under the scroll bar
91
this._register(Event.runAndSubscribe(configurationService.onDidChangeConfiguration, e => {
92
if (!e || e.affectsConfiguration('editor.scrollbar.verticalScrollbarSize')) {
93
const verticalScrollbarSize = configurationService.getValue<IEditorOptions>('editor').scrollbar?.verticalScrollbarSize ?? 14;
94
this.canvas.domNode.style.boxSizing = 'border-box';
95
this.canvas.domNode.style.paddingRight = `${verticalScrollbarSize}px`;
96
}
97
}));
98
99
this.ctx = ensureNonNullable(this.canvas.domNode.getContext('webgpu'));
100
101
// Request the GPU device, we only want to do this a single time per window as it's async
102
// and can delay the initial render.
103
if (!ViewGpuContext.device) {
104
ViewGpuContext.device = GPULifecycle.requestDevice((message) => {
105
const choices: IPromptChoice[] = [{
106
label: nls.localize('editor.dom.render', "Use DOM-based rendering"),
107
run: () => this.configurationService.updateValue('editor.experimentalGpuAcceleration', 'off'),
108
}];
109
this._notificationService.prompt(Severity.Warning, message, choices);
110
}).then(ref => {
111
ViewGpuContext.deviceSync = ref.object;
112
if (!ViewGpuContext._atlas) {
113
ViewGpuContext._atlas = this._instantiationService.createInstance(TextureAtlas, ref.object.limits.maxTextureDimension2D, undefined, ViewGpuContext.decorationStyleCache);
114
}
115
return ref.object;
116
});
117
}
118
119
const dprObs = observableValue(this, getActiveWindow().devicePixelRatio);
120
this._register(addDisposableListener(getActiveWindow(), 'resize', () => {
121
dprObs.set(getActiveWindow().devicePixelRatio, undefined);
122
}));
123
this.devicePixelRatio = dprObs;
124
this._register(runOnChange(this.devicePixelRatio, () => ViewGpuContext.atlas?.clear()));
125
126
const canvasDevicePixelDimensions = observableValue(this, { width: this.canvas.domNode.width, height: this.canvas.domNode.height });
127
this._register(observeDevicePixelDimensions(
128
this.canvas.domNode,
129
getActiveWindow(),
130
(width, height) => {
131
this.canvas.domNode.width = width;
132
this.canvas.domNode.height = height;
133
canvasDevicePixelDimensions.set({ width, height }, undefined);
134
}
135
));
136
this.canvasDevicePixelDimensions = canvasDevicePixelDimensions;
137
138
const contentLeft = observableValue(this, 0);
139
this._register(this.configurationService.onDidChangeConfiguration(e => {
140
contentLeft.set(context.configuration.options.get(EditorOption.layoutInfo).contentLeft, undefined);
141
}));
142
this.contentLeft = contentLeft;
143
144
this.rectangleRenderer = this._instantiationService.createInstance(RectangleRenderer, context, this.contentLeft, this.devicePixelRatio, this.canvas.domNode, this.ctx, ViewGpuContext.device);
145
}
146
147
/**
148
* This method determines which lines can be and are allowed to be rendered using the GPU
149
* renderer. Eventually this should trend all lines, except maybe exceptional cases like
150
* decorations that use class names.
151
*/
152
public canRender(options: ViewLineOptions, viewportData: ViewportData, lineNumber: number): boolean {
153
const data = viewportData.getViewLineRenderingData(lineNumber);
154
155
// Check if the line has simple attributes that aren't supported
156
if (
157
data.containsRTL ||
158
data.maxColumn > this.maxGpuCols
159
) {
160
return false;
161
}
162
163
// Check if all inline decorations are supported
164
if (data.inlineDecorations.length > 0) {
165
let supported = true;
166
for (const decoration of data.inlineDecorations) {
167
if (decoration.type !== InlineDecorationType.Regular) {
168
supported = false;
169
break;
170
}
171
const styleRules = ViewGpuContext._decorationCssRuleExtractor.getStyleRules(this.canvas.domNode, decoration.inlineClassName);
172
supported &&= styleRules.every(rule => {
173
// Pseudo classes aren't supported currently
174
if (rule.selectorText.includes(':')) {
175
return false;
176
}
177
for (const r of rule.style) {
178
if (!supportsCssRule(r, rule.style)) {
179
return false;
180
}
181
}
182
return true;
183
});
184
if (!supported) {
185
break;
186
}
187
}
188
return supported;
189
}
190
191
return true;
192
}
193
194
/**
195
* Like {@link canRender} but returns detailed information about why the line cannot be rendered.
196
*/
197
public canRenderDetailed(options: ViewLineOptions, viewportData: ViewportData, lineNumber: number): string[] {
198
const data = viewportData.getViewLineRenderingData(lineNumber);
199
const reasons: string[] = [];
200
if (data.containsRTL) {
201
reasons.push('containsRTL');
202
}
203
if (data.maxColumn > this.maxGpuCols) {
204
reasons.push('maxColumn > maxGpuCols');
205
}
206
if (data.inlineDecorations.length > 0) {
207
let supported = true;
208
const problemTypes: InlineDecorationType[] = [];
209
const problemSelectors: string[] = [];
210
const problemRules: string[] = [];
211
for (const decoration of data.inlineDecorations) {
212
if (decoration.type !== InlineDecorationType.Regular) {
213
problemTypes.push(decoration.type);
214
supported = false;
215
continue;
216
}
217
const styleRules = ViewGpuContext._decorationCssRuleExtractor.getStyleRules(this.canvas.domNode, decoration.inlineClassName);
218
supported &&= styleRules.every(rule => {
219
// Pseudo classes aren't supported currently
220
if (rule.selectorText.includes(':')) {
221
problemSelectors.push(rule.selectorText);
222
return false;
223
}
224
for (const r of rule.style) {
225
if (!supportsCssRule(r, rule.style)) {
226
problemRules.push(`${r}: ${rule.style[r as any]}`);
227
return false;
228
}
229
}
230
return true;
231
});
232
if (!supported) {
233
continue;
234
}
235
}
236
if (problemTypes.length > 0) {
237
reasons.push(`inlineDecorations with unsupported types (${problemTypes.map(e => `\`${e}\``).join(', ')})`);
238
}
239
if (problemRules.length > 0) {
240
reasons.push(`inlineDecorations with unsupported CSS rules (${problemRules.map(e => `\`${e}\``).join(', ')})`);
241
}
242
if (problemSelectors.length > 0) {
243
reasons.push(`inlineDecorations with unsupported CSS selectors (${problemSelectors.map(e => `\`${e}\``).join(', ')})`);
244
}
245
}
246
return reasons;
247
}
248
}
249
250
/**
251
* A list of supported decoration CSS rules that can be used in the GPU renderer.
252
*/
253
const gpuSupportedDecorationCssRules = [
254
'color',
255
'font-weight',
256
'opacity',
257
];
258
259
function supportsCssRule(rule: string, style: CSSStyleDeclaration) {
260
if (!gpuSupportedDecorationCssRules.includes(rule)) {
261
return false;
262
}
263
// Check for values that aren't supported
264
switch (rule) {
265
default: return true;
266
}
267
}
268
269