Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/editor/browser/gpu/rectangleRenderer.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 { getActiveWindow } from '../../../base/browser/dom.js';
7
import { Event } from '../../../base/common/event.js';
8
import { IReference, MutableDisposable } from '../../../base/common/lifecycle.js';
9
import type { IObservable } from '../../../base/common/observable.js';
10
import { EditorOption } from '../../common/config/editorOptions.js';
11
import { ViewEventHandler } from '../../common/viewEventHandler.js';
12
import type { ViewScrollChangedEvent } from '../../common/viewEvents.js';
13
import type { ViewportData } from '../../common/viewLayout/viewLinesViewportData.js';
14
import type { ViewContext } from '../../common/viewModel/viewContext.js';
15
import { GPULifecycle } from './gpuDisposable.js';
16
import { observeDevicePixelDimensions, quadVertices } from './gpuUtils.js';
17
import { createObjectCollectionBuffer, type IObjectCollectionBuffer, type IObjectCollectionBufferEntry } from './objectCollectionBuffer.js';
18
import { RectangleRendererBindingId, rectangleRendererWgsl } from './rectangleRenderer.wgsl.js';
19
20
export type RectangleRendererEntrySpec = [
21
{ name: 'x' },
22
{ name: 'y' },
23
{ name: 'width' },
24
{ name: 'height' },
25
{ name: 'red' },
26
{ name: 'green' },
27
{ name: 'blue' },
28
{ name: 'alpha' },
29
];
30
31
export class RectangleRenderer extends ViewEventHandler {
32
33
private _device!: GPUDevice;
34
private _renderPassDescriptor!: GPURenderPassDescriptor;
35
private _renderPassColorAttachment!: GPURenderPassColorAttachment;
36
private _bindGroup!: GPUBindGroup;
37
private _pipeline!: GPURenderPipeline;
38
39
private _vertexBuffer!: GPUBuffer;
40
private readonly _shapeBindBuffer: MutableDisposable<IReference<GPUBuffer>> = this._register(new MutableDisposable());
41
42
private _scrollOffsetBindBuffer!: GPUBuffer;
43
private _scrollOffsetValueBuffer!: Float32Array;
44
45
private _initialized: boolean = false;
46
47
private readonly _shapeCollection: IObjectCollectionBuffer<RectangleRendererEntrySpec> = this._register(createObjectCollectionBuffer([
48
{ name: 'x' },
49
{ name: 'y' },
50
{ name: 'width' },
51
{ name: 'height' },
52
{ name: 'red' },
53
{ name: 'green' },
54
{ name: 'blue' },
55
{ name: 'alpha' },
56
], 32));
57
58
constructor(
59
private readonly _context: ViewContext,
60
private readonly _contentLeft: IObservable<number>,
61
private readonly _devicePixelRatio: IObservable<number>,
62
private readonly _canvas: HTMLCanvasElement,
63
private readonly _ctx: GPUCanvasContext,
64
device: Promise<GPUDevice>,
65
) {
66
super();
67
68
this._context.addEventHandler(this);
69
70
this._initWebgpu(device);
71
}
72
73
private async _initWebgpu(device: Promise<GPUDevice>) {
74
75
// #region General
76
77
this._device = await device;
78
79
if (this._store.isDisposed) {
80
return;
81
}
82
83
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
84
this._ctx.configure({
85
device: this._device,
86
format: presentationFormat,
87
alphaMode: 'premultiplied',
88
});
89
90
this._renderPassColorAttachment = {
91
view: null!, // Will be filled at render time
92
loadOp: 'load',
93
storeOp: 'store',
94
};
95
this._renderPassDescriptor = {
96
label: 'Monaco rectangle renderer render pass',
97
colorAttachments: [this._renderPassColorAttachment],
98
};
99
100
// #endregion General
101
102
// #region Uniforms
103
104
let layoutInfoUniformBuffer: GPUBuffer;
105
{
106
const enum Info {
107
FloatsPerEntry = 6,
108
BytesPerEntry = Info.FloatsPerEntry * 4,
109
Offset_CanvasWidth____ = 0,
110
Offset_CanvasHeight___ = 1,
111
Offset_ViewportOffsetX = 2,
112
Offset_ViewportOffsetY = 3,
113
Offset_ViewportWidth__ = 4,
114
Offset_ViewportHeight_ = 5,
115
}
116
const bufferValues = new Float32Array(Info.FloatsPerEntry);
117
const updateBufferValues = (canvasDevicePixelWidth: number = this._canvas.width, canvasDevicePixelHeight: number = this._canvas.height) => {
118
bufferValues[Info.Offset_CanvasWidth____] = canvasDevicePixelWidth;
119
bufferValues[Info.Offset_CanvasHeight___] = canvasDevicePixelHeight;
120
bufferValues[Info.Offset_ViewportOffsetX] = Math.ceil(this._context.configuration.options.get(EditorOption.layoutInfo).contentLeft * getActiveWindow().devicePixelRatio);
121
bufferValues[Info.Offset_ViewportOffsetY] = 0;
122
bufferValues[Info.Offset_ViewportWidth__] = bufferValues[Info.Offset_CanvasWidth____] - bufferValues[Info.Offset_ViewportOffsetX];
123
bufferValues[Info.Offset_ViewportHeight_] = bufferValues[Info.Offset_CanvasHeight___] - bufferValues[Info.Offset_ViewportOffsetY];
124
return bufferValues;
125
};
126
layoutInfoUniformBuffer = this._register(GPULifecycle.createBuffer(this._device, {
127
label: 'Monaco rectangle renderer uniform buffer',
128
size: Info.BytesPerEntry,
129
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
130
}, () => updateBufferValues())).object;
131
this._register(observeDevicePixelDimensions(this._canvas, getActiveWindow(), (w, h) => {
132
this._device.queue.writeBuffer(layoutInfoUniformBuffer, 0, updateBufferValues(w, h));
133
}));
134
}
135
136
const scrollOffsetBufferSize = 2;
137
this._scrollOffsetBindBuffer = this._register(GPULifecycle.createBuffer(this._device, {
138
label: 'Monaco rectangle renderer scroll offset buffer',
139
size: scrollOffsetBufferSize * Float32Array.BYTES_PER_ELEMENT,
140
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
141
})).object;
142
this._scrollOffsetValueBuffer = new Float32Array(scrollOffsetBufferSize);
143
144
// #endregion Uniforms
145
146
// #region Storage buffers
147
148
const createShapeBindBuffer = () => {
149
return GPULifecycle.createBuffer(this._device, {
150
label: 'Monaco rectangle renderer shape buffer',
151
size: this._shapeCollection.buffer.byteLength,
152
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
153
});
154
};
155
this._shapeBindBuffer.value = createShapeBindBuffer();
156
this._register(Event.runAndSubscribe(this._shapeCollection.onDidChangeBuffer, () => {
157
this._shapeBindBuffer.value = createShapeBindBuffer();
158
if (this._pipeline) {
159
this._updateBindGroup(this._pipeline, layoutInfoUniformBuffer);
160
}
161
}));
162
163
// #endregion Storage buffers
164
165
// #region Vertex buffer
166
167
this._vertexBuffer = this._register(GPULifecycle.createBuffer(this._device, {
168
label: 'Monaco rectangle renderer vertex buffer',
169
size: quadVertices.byteLength,
170
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
171
}, quadVertices)).object;
172
173
// #endregion Vertex buffer
174
175
// #region Shader module
176
177
const module = this._device.createShaderModule({
178
label: 'Monaco rectangle renderer shader module',
179
code: rectangleRendererWgsl,
180
});
181
182
// #endregion Shader module
183
184
// #region Pipeline
185
186
this._pipeline = this._device.createRenderPipeline({
187
label: 'Monaco rectangle renderer render pipeline',
188
layout: 'auto',
189
vertex: {
190
module,
191
buffers: [
192
{
193
arrayStride: 2 * Float32Array.BYTES_PER_ELEMENT, // 2 floats, 4 bytes each
194
attributes: [
195
{ shaderLocation: 0, offset: 0, format: 'float32x2' }, // position
196
],
197
}
198
]
199
},
200
fragment: {
201
module,
202
targets: [
203
{
204
format: presentationFormat,
205
blend: {
206
color: {
207
srcFactor: 'src-alpha',
208
dstFactor: 'one-minus-src-alpha'
209
},
210
alpha: {
211
srcFactor: 'src-alpha',
212
dstFactor: 'one-minus-src-alpha'
213
},
214
},
215
}
216
],
217
},
218
});
219
220
// #endregion Pipeline
221
222
// #region Bind group
223
224
this._updateBindGroup(this._pipeline, layoutInfoUniformBuffer);
225
226
// endregion Bind group
227
228
this._initialized = true;
229
}
230
231
private _updateBindGroup(pipeline: GPURenderPipeline, layoutInfoUniformBuffer: GPUBuffer) {
232
this._bindGroup = this._device.createBindGroup({
233
label: 'Monaco rectangle renderer bind group',
234
layout: pipeline.getBindGroupLayout(0),
235
entries: [
236
{ binding: RectangleRendererBindingId.Shapes, resource: { buffer: this._shapeBindBuffer.value!.object } },
237
{ binding: RectangleRendererBindingId.LayoutInfoUniform, resource: { buffer: layoutInfoUniformBuffer } },
238
{ binding: RectangleRendererBindingId.ScrollOffset, resource: { buffer: this._scrollOffsetBindBuffer } },
239
],
240
});
241
}
242
243
register(x: number, y: number, width: number, height: number, red: number, green: number, blue: number, alpha: number): IObjectCollectionBufferEntry<RectangleRendererEntrySpec> {
244
return this._shapeCollection.createEntry({ x, y, width, height, red, green, blue, alpha });
245
}
246
247
// #region Event handlers
248
249
public override onScrollChanged(e: ViewScrollChangedEvent): boolean {
250
if (this._device) {
251
const dpr = getActiveWindow().devicePixelRatio;
252
this._scrollOffsetValueBuffer[0] = this._context.viewLayout.getCurrentScrollLeft() * dpr;
253
this._scrollOffsetValueBuffer[1] = this._context.viewLayout.getCurrentScrollTop() * dpr;
254
this._device.queue.writeBuffer(this._scrollOffsetBindBuffer, 0, this._scrollOffsetValueBuffer as Float32Array<ArrayBuffer>);
255
}
256
return true;
257
}
258
259
// #endregion
260
261
private _update() {
262
if (!this._device) {
263
return;
264
}
265
const shapes = this._shapeCollection;
266
if (shapes.dirtyTracker.isDirty) {
267
this._device.queue.writeBuffer(this._shapeBindBuffer.value!.object, 0, shapes.buffer, shapes.dirtyTracker.dataOffset, shapes.dirtyTracker.dirtySize! * shapes.view.BYTES_PER_ELEMENT);
268
shapes.dirtyTracker.clear();
269
}
270
}
271
272
draw(viewportData: ViewportData) {
273
if (!this._initialized) {
274
return;
275
}
276
277
this._update();
278
279
const encoder = this._device.createCommandEncoder({ label: 'Monaco rectangle renderer command encoder' });
280
281
this._renderPassColorAttachment.view = this._ctx.getCurrentTexture().createView();
282
const pass = encoder.beginRenderPass(this._renderPassDescriptor);
283
pass.setPipeline(this._pipeline);
284
pass.setVertexBuffer(0, this._vertexBuffer);
285
pass.setBindGroup(0, this._bindGroup);
286
287
// Only draw the content area
288
const contentLeft = Math.ceil(this._contentLeft.get() * this._devicePixelRatio.get());
289
pass.setScissorRect(contentLeft, 0, this._canvas.width - contentLeft, this._canvas.height);
290
291
pass.draw(quadVertices.length / 2, this._shapeCollection.entryCount);
292
pass.end();
293
294
const commandBuffer = encoder.finish();
295
this._device.queue.submit([commandBuffer]);
296
}
297
}
298
299