Path: blob/main/src/vs/editor/browser/gpu/rectangleRenderer.ts
3294 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 { getActiveWindow } from '../../../base/browser/dom.js';6import { Event } from '../../../base/common/event.js';7import { IReference, MutableDisposable } from '../../../base/common/lifecycle.js';8import type { IObservable } from '../../../base/common/observable.js';9import { EditorOption } from '../../common/config/editorOptions.js';10import { ViewEventHandler } from '../../common/viewEventHandler.js';11import type { ViewScrollChangedEvent } from '../../common/viewEvents.js';12import type { ViewportData } from '../../common/viewLayout/viewLinesViewportData.js';13import type { ViewContext } from '../../common/viewModel/viewContext.js';14import { GPULifecycle } from './gpuDisposable.js';15import { observeDevicePixelDimensions, quadVertices } from './gpuUtils.js';16import { createObjectCollectionBuffer, type IObjectCollectionBuffer, type IObjectCollectionBufferEntry } from './objectCollectionBuffer.js';17import { RectangleRendererBindingId, rectangleRendererWgsl } from './rectangleRenderer.wgsl.js';1819export type RectangleRendererEntrySpec = [20{ name: 'x' },21{ name: 'y' },22{ name: 'width' },23{ name: 'height' },24{ name: 'red' },25{ name: 'green' },26{ name: 'blue' },27{ name: 'alpha' },28];2930export class RectangleRenderer extends ViewEventHandler {3132private _device!: GPUDevice;33private _renderPassDescriptor!: GPURenderPassDescriptor;34private _renderPassColorAttachment!: GPURenderPassColorAttachment;35private _bindGroup!: GPUBindGroup;36private _pipeline!: GPURenderPipeline;3738private _vertexBuffer!: GPUBuffer;39private readonly _shapeBindBuffer: MutableDisposable<IReference<GPUBuffer>> = this._register(new MutableDisposable());4041private _scrollOffsetBindBuffer!: GPUBuffer;42private _scrollOffsetValueBuffer!: Float32Array;4344private _initialized: boolean = false;4546private readonly _shapeCollection: IObjectCollectionBuffer<RectangleRendererEntrySpec> = this._register(createObjectCollectionBuffer([47{ name: 'x' },48{ name: 'y' },49{ name: 'width' },50{ name: 'height' },51{ name: 'red' },52{ name: 'green' },53{ name: 'blue' },54{ name: 'alpha' },55], 32));5657constructor(58private readonly _context: ViewContext,59private readonly _contentLeft: IObservable<number>,60private readonly _devicePixelRatio: IObservable<number>,61private readonly _canvas: HTMLCanvasElement,62private readonly _ctx: GPUCanvasContext,63device: Promise<GPUDevice>,64) {65super();6667this._context.addEventHandler(this);6869this._initWebgpu(device);70}7172private async _initWebgpu(device: Promise<GPUDevice>) {7374// #region General7576this._device = await device;7778if (this._store.isDisposed) {79return;80}8182const presentationFormat = navigator.gpu.getPreferredCanvasFormat();83this._ctx.configure({84device: this._device,85format: presentationFormat,86alphaMode: 'premultiplied',87});8889this._renderPassColorAttachment = {90view: null!, // Will be filled at render time91loadOp: 'load',92storeOp: 'store',93};94this._renderPassDescriptor = {95label: 'Monaco rectangle renderer render pass',96colorAttachments: [this._renderPassColorAttachment],97};9899// #endregion General100101// #region Uniforms102103let layoutInfoUniformBuffer: GPUBuffer;104{105const enum Info {106FloatsPerEntry = 6,107BytesPerEntry = Info.FloatsPerEntry * 4,108Offset_CanvasWidth____ = 0,109Offset_CanvasHeight___ = 1,110Offset_ViewportOffsetX = 2,111Offset_ViewportOffsetY = 3,112Offset_ViewportWidth__ = 4,113Offset_ViewportHeight_ = 5,114}115const bufferValues = new Float32Array(Info.FloatsPerEntry);116const updateBufferValues = (canvasDevicePixelWidth: number = this._canvas.width, canvasDevicePixelHeight: number = this._canvas.height) => {117bufferValues[Info.Offset_CanvasWidth____] = canvasDevicePixelWidth;118bufferValues[Info.Offset_CanvasHeight___] = canvasDevicePixelHeight;119bufferValues[Info.Offset_ViewportOffsetX] = Math.ceil(this._context.configuration.options.get(EditorOption.layoutInfo).contentLeft * getActiveWindow().devicePixelRatio);120bufferValues[Info.Offset_ViewportOffsetY] = 0;121bufferValues[Info.Offset_ViewportWidth__] = bufferValues[Info.Offset_CanvasWidth____] - bufferValues[Info.Offset_ViewportOffsetX];122bufferValues[Info.Offset_ViewportHeight_] = bufferValues[Info.Offset_CanvasHeight___] - bufferValues[Info.Offset_ViewportOffsetY];123return bufferValues;124};125layoutInfoUniformBuffer = this._register(GPULifecycle.createBuffer(this._device, {126label: 'Monaco rectangle renderer uniform buffer',127size: Info.BytesPerEntry,128usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,129}, () => updateBufferValues())).object;130this._register(observeDevicePixelDimensions(this._canvas, getActiveWindow(), (w, h) => {131this._device.queue.writeBuffer(layoutInfoUniformBuffer, 0, updateBufferValues(w, h));132}));133}134135const scrollOffsetBufferSize = 2;136this._scrollOffsetBindBuffer = this._register(GPULifecycle.createBuffer(this._device, {137label: 'Monaco rectangle renderer scroll offset buffer',138size: scrollOffsetBufferSize * Float32Array.BYTES_PER_ELEMENT,139usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,140})).object;141this._scrollOffsetValueBuffer = new Float32Array(scrollOffsetBufferSize);142143// #endregion Uniforms144145// #region Storage buffers146147const createShapeBindBuffer = () => {148return GPULifecycle.createBuffer(this._device, {149label: 'Monaco rectangle renderer shape buffer',150size: this._shapeCollection.buffer.byteLength,151usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,152});153};154this._shapeBindBuffer.value = createShapeBindBuffer();155this._register(Event.runAndSubscribe(this._shapeCollection.onDidChangeBuffer, () => {156this._shapeBindBuffer.value = createShapeBindBuffer();157if (this._pipeline) {158this._updateBindGroup(this._pipeline, layoutInfoUniformBuffer);159}160}));161162// #endregion Storage buffers163164// #region Vertex buffer165166this._vertexBuffer = this._register(GPULifecycle.createBuffer(this._device, {167label: 'Monaco rectangle renderer vertex buffer',168size: quadVertices.byteLength,169usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,170}, quadVertices)).object;171172// #endregion Vertex buffer173174// #region Shader module175176const module = this._device.createShaderModule({177label: 'Monaco rectangle renderer shader module',178code: rectangleRendererWgsl,179});180181// #endregion Shader module182183// #region Pipeline184185this._pipeline = this._device.createRenderPipeline({186label: 'Monaco rectangle renderer render pipeline',187layout: 'auto',188vertex: {189module,190buffers: [191{192arrayStride: 2 * Float32Array.BYTES_PER_ELEMENT, // 2 floats, 4 bytes each193attributes: [194{ shaderLocation: 0, offset: 0, format: 'float32x2' }, // position195],196}197]198},199fragment: {200module,201targets: [202{203format: presentationFormat,204blend: {205color: {206srcFactor: 'src-alpha',207dstFactor: 'one-minus-src-alpha'208},209alpha: {210srcFactor: 'src-alpha',211dstFactor: 'one-minus-src-alpha'212},213},214}215],216},217});218219// #endregion Pipeline220221// #region Bind group222223this._updateBindGroup(this._pipeline, layoutInfoUniformBuffer);224225// endregion Bind group226227this._initialized = true;228}229230private _updateBindGroup(pipeline: GPURenderPipeline, layoutInfoUniformBuffer: GPUBuffer) {231this._bindGroup = this._device.createBindGroup({232label: 'Monaco rectangle renderer bind group',233layout: pipeline.getBindGroupLayout(0),234entries: [235{ binding: RectangleRendererBindingId.Shapes, resource: { buffer: this._shapeBindBuffer.value!.object } },236{ binding: RectangleRendererBindingId.LayoutInfoUniform, resource: { buffer: layoutInfoUniformBuffer } },237{ binding: RectangleRendererBindingId.ScrollOffset, resource: { buffer: this._scrollOffsetBindBuffer } },238],239});240}241242register(x: number, y: number, width: number, height: number, red: number, green: number, blue: number, alpha: number): IObjectCollectionBufferEntry<RectangleRendererEntrySpec> {243return this._shapeCollection.createEntry({ x, y, width, height, red, green, blue, alpha });244}245246// #region Event handlers247248public override onScrollChanged(e: ViewScrollChangedEvent): boolean {249if (this._device) {250const dpr = getActiveWindow().devicePixelRatio;251this._scrollOffsetValueBuffer[0] = this._context.viewLayout.getCurrentScrollLeft() * dpr;252this._scrollOffsetValueBuffer[1] = this._context.viewLayout.getCurrentScrollTop() * dpr;253this._device.queue.writeBuffer(this._scrollOffsetBindBuffer, 0, this._scrollOffsetValueBuffer as Float32Array<ArrayBuffer>);254}255return true;256}257258// #endregion259260private _update() {261if (!this._device) {262return;263}264const shapes = this._shapeCollection;265if (shapes.dirtyTracker.isDirty) {266this._device.queue.writeBuffer(this._shapeBindBuffer.value!.object, 0, shapes.buffer, shapes.dirtyTracker.dataOffset, shapes.dirtyTracker.dirtySize! * shapes.view.BYTES_PER_ELEMENT);267shapes.dirtyTracker.clear();268}269}270271draw(viewportData: ViewportData) {272if (!this._initialized) {273return;274}275276this._update();277278const encoder = this._device.createCommandEncoder({ label: 'Monaco rectangle renderer command encoder' });279280this._renderPassColorAttachment.view = this._ctx.getCurrentTexture().createView();281const pass = encoder.beginRenderPass(this._renderPassDescriptor);282pass.setPipeline(this._pipeline);283pass.setVertexBuffer(0, this._vertexBuffer);284pass.setBindGroup(0, this._bindGroup);285286// Only draw the content area287const contentLeft = Math.ceil(this._contentLeft.get() * this._devicePixelRatio.get());288pass.setScissorRect(contentLeft, 0, this._canvas.width - contentLeft, this._canvas.height);289290pass.draw(quadVertices.length / 2, this._shapeCollection.entryCount);291pass.end();292293const commandBuffer = encoder.finish();294this._device.queue.submit([commandBuffer]);295}296}297298299