Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expand object collection buffer when capacity hit #228855

Merged
merged 3 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion src/vs/editor/browser/gpu/objectCollectionBuffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ export interface IObjectCollectionBuffer<T extends ObjectCollectionBufferPropert
*/
readonly onDidChange: Event<void>;

/**
* Fires when the buffer is recreated.
*/
readonly onDidChangeBuffer: Event<void>;

/**
* Creates an entry in the collection. This will return a managed object that can be modified
* which will update the underlying buffer.
Expand Down Expand Up @@ -96,6 +101,8 @@ class ObjectCollectionBuffer<T extends ObjectCollectionBufferPropertySpec[]> ext

private readonly _onDidChange = this._register(new Emitter<void>());
readonly onDidChange = this._onDidChange.event;
private readonly _onDidChangeBuffer = this._register(new Emitter<void>());
readonly onDidChangeBuffer = this._onDidChangeBuffer.event;

constructor(
public propertySpecs: T,
Expand All @@ -118,7 +125,8 @@ class ObjectCollectionBuffer<T extends ObjectCollectionBufferPropertySpec[]> ext

createEntry(data: ObjectCollectionPropertyValues<T>): IObjectCollectionBufferEntry<T> {
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);
Expand All @@ -143,6 +151,14 @@ class ObjectCollectionBuffer<T extends ObjectCollectionBufferPropertySpec[]> 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<T extends ObjectCollectionBufferPropertySpec[]> extends Disposable implements IObjectCollectionBufferEntry<T> {
Expand Down
42 changes: 28 additions & 14 deletions src/vs/editor/browser/gpu/rectangleRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -34,7 +36,7 @@ export class RectangleRenderer extends ViewEventHandler {
private _pipeline!: GPURenderPipeline;

private _vertexBuffer!: GPUBuffer;
private _shapeBindBuffer!: GPUBuffer;
private readonly _shapeBindBuffer: MutableDisposable<IReference<GPUBuffer>> = this._register(new MutableDisposable());

private _scrollOffsetBindBuffer!: GPUBuffer;
private _scrollOffsetValueBuffer!: Float32Array;
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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<RectangleRendererEntrySpec> {
// TODO: Expand buffer if needed
return this._shapeCollection.createEntry({ x, y, width, height, red, green, blue, alpha });
}

Expand All @@ -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();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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', () => {
Expand Down
Loading