diff --git a/src/vs/editor/browser/gpu/objectCollectionBuffer.ts b/src/vs/editor/browser/gpu/objectCollectionBuffer.ts index 5f3119ecaa48c..a61fe8c09de78 100644 --- a/src/vs/editor/browser/gpu/objectCollectionBuffer.ts +++ b/src/vs/editor/browser/gpu/objectCollectionBuffer.ts @@ -48,6 +48,11 @@ export interface IObjectCollectionBuffer; + /** + * Fires when the buffer is recreated. + */ + readonly onDidChangeBuffer: Event; + /** * Creates an entry in the collection. This will return a managed object that can be modified * which will update the underlying buffer. @@ -96,6 +101,8 @@ class ObjectCollectionBuffer ext private readonly _onDidChange = this._register(new Emitter()); readonly onDidChange = this._onDidChange.event; + private readonly _onDidChangeBuffer = this._register(new Emitter()); + readonly onDidChangeBuffer = this._onDidChangeBuffer.event; constructor( public propertySpecs: T, @@ -118,7 +125,8 @@ class ObjectCollectionBuffer ext createEntry(data: ObjectCollectionPropertyValues): IObjectCollectionBufferEntry { if (this._entries.size === this.capacity) { - throw new Error(`Cannot create more entries ObjectCollectionBuffer entries (capacity=${this.capacity})`); + this._expandBuffer(); + this._onDidChangeBuffer.fire(); } const value = new ObjectCollectionBufferEntry(this.view, this._propertySpecsMap, this._dirtyTracker, this._entries.size, data); @@ -143,6 +151,14 @@ class ObjectCollectionBuffer ext })); return value; } + + private _expandBuffer() { + this.capacity *= 2; + const newView = new Float32Array(this.capacity * this._entrySize); + newView.set(this.view); + this.view = newView; + this.buffer = this.view.buffer; + } } class ObjectCollectionBufferEntry extends Disposable implements IObjectCollectionBufferEntry { diff --git a/src/vs/editor/browser/gpu/rectangleRenderer.ts b/src/vs/editor/browser/gpu/rectangleRenderer.ts index 421cd90252428..0859281f48b31 100644 --- a/src/vs/editor/browser/gpu/rectangleRenderer.ts +++ b/src/vs/editor/browser/gpu/rectangleRenderer.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { getActiveWindow } from '../../../base/browser/dom.js'; +import { Event } from '../../../base/common/event.js'; +import { IReference, MutableDisposable } from '../../../base/common/lifecycle.js'; import { EditorOption } from '../../common/config/editorOptions.js'; import { ViewEventHandler } from '../../common/viewEventHandler.js'; import type { ViewScrollChangedEvent } from '../../common/viewEvents.js'; @@ -34,7 +36,7 @@ export class RectangleRenderer extends ViewEventHandler { private _pipeline!: GPURenderPipeline; private _vertexBuffer!: GPUBuffer; - private _shapeBindBuffer!: GPUBuffer; + private readonly _shapeBindBuffer: MutableDisposable> = this._register(new MutableDisposable()); private _scrollOffsetBindBuffer!: GPUBuffer; private _scrollOffsetValueBuffer!: Float32Array; @@ -141,11 +143,20 @@ export class RectangleRenderer extends ViewEventHandler { // #region Storage buffers - this._shapeBindBuffer = this._register(GPULifecycle.createBuffer(this._device, { - label: 'Monaco rectangle renderer shape buffer', - size: this._shapeCollection.buffer.byteLength, - usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, - })).object; + const createShapeBindBuffer = () => { + return GPULifecycle.createBuffer(this._device, { + label: 'Monaco rectangle renderer shape buffer', + size: this._shapeCollection.buffer.byteLength, + usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST, + }); + }; + this._shapeBindBuffer.value = createShapeBindBuffer(); + this._register(Event.runAndSubscribe(this._shapeCollection.onDidChangeBuffer, () => { + this._shapeBindBuffer.value = createShapeBindBuffer(); + if (this._pipeline) { + this._updateBindGroup(this._pipeline, layoutInfoUniformBuffer); + } + })); // #endregion Storage buffers @@ -208,23 +219,26 @@ export class RectangleRenderer extends ViewEventHandler { // #region Bind group + this._updateBindGroup(this._pipeline, layoutInfoUniformBuffer); + + // endregion Bind group + + this._initialized = true; + } + + private _updateBindGroup(pipeline: GPURenderPipeline, layoutInfoUniformBuffer: GPUBuffer) { this._bindGroup = this._device.createBindGroup({ label: 'Monaco rectangle renderer bind group', - layout: this._pipeline.getBindGroupLayout(0), + layout: pipeline.getBindGroupLayout(0), entries: [ - { binding: RectangleRendererBindingId.Shapes, resource: { buffer: this._shapeBindBuffer } }, + { binding: RectangleRendererBindingId.Shapes, resource: { buffer: this._shapeBindBuffer.value!.object } }, { binding: RectangleRendererBindingId.LayoutInfoUniform, resource: { buffer: layoutInfoUniformBuffer } }, { binding: RectangleRendererBindingId.ScrollOffset, resource: { buffer: this._scrollOffsetBindBuffer } }, ], }); - - // endregion Bind group - - this._initialized = true; } register(x: number, y: number, width: number, height: number, red: number, green: number, blue: number, alpha: number): IObjectCollectionBufferEntry { - // TODO: Expand buffer if needed return this._shapeCollection.createEntry({ x, y, width, height, red, green, blue, alpha }); } @@ -240,7 +254,7 @@ export class RectangleRenderer extends ViewEventHandler { private _update() { const shapes = this._shapeCollection; if (shapes.dirtyTracker.isDirty) { - this._device.queue.writeBuffer(this._shapeBindBuffer, 0, shapes.buffer, shapes.dirtyTracker.dataOffset, shapes.dirtyTracker.dirtySize! * shapes.view.BYTES_PER_ELEMENT); + this._device.queue.writeBuffer(this._shapeBindBuffer.value!.object, 0, shapes.buffer, shapes.dirtyTracker.dataOffset, shapes.dirtyTracker.dirtySize! * shapes.view.BYTES_PER_ELEMENT); shapes.dirtyTracker.clear(); } diff --git a/src/vs/editor/test/browser/view/gpu/objectCollectionBuffer.test.ts b/src/vs/editor/test/browser/view/gpu/objectCollectionBuffer.test.ts index dc8c5b8de7bf0..ae8c0670d2b8b 100644 --- a/src/vs/editor/test/browser/view/gpu/objectCollectionBuffer.test.ts +++ b/src/vs/editor/test/browser/view/gpu/objectCollectionBuffer.test.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { deepStrictEqual, strictEqual, throws } from 'assert'; +import { deepStrictEqual, strictEqual } from 'assert'; import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; import { createObjectCollectionBuffer, type IObjectCollectionBuffer } from '../../../../browser/gpu/objectCollectionBuffer.js'; @@ -35,7 +35,11 @@ suite('ObjectCollectionBuffer', () => { { name: 'b' }, ] as const, 1)); store.add(buffer.createEntry({ a: 1, b: 2 })); - throws(() => buffer.createEntry({ a: 3, b: 4 })); + strictEqual(buffer.entryCount, 1); + strictEqual(buffer.buffer.byteLength, 8); + buffer.createEntry({ a: 3, b: 4 }); + strictEqual(buffer.entryCount, 2); + strictEqual(buffer.buffer.byteLength, 16); }); test('dispose entry', () => {