From 26dbb880e1d7a880ad61d53a426aef3fdedf634e Mon Sep 17 00:00:00 2001 From: Daniel Imms <2193314+Tyriar@users.noreply.github.com> Date: Tue, 17 Sep 2024 07:47:13 -0700 Subject: [PATCH 1/2] Track dirty indices in objcollectionbuffer Fixes #228833 --- .../editor/browser/gpu/bufferDirtyTracker.ts | 72 +++++++++++++++++++ .../browser/gpu/objectCollectionBuffer.ts | 18 ++++- .../editor/browser/gpu/rectangleRenderer.ts | 7 +- 3 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 src/vs/editor/browser/gpu/bufferDirtyTracker.ts diff --git a/src/vs/editor/browser/gpu/bufferDirtyTracker.ts b/src/vs/editor/browser/gpu/bufferDirtyTracker.ts new file mode 100644 index 0000000000000..58c9f8c39c25e --- /dev/null +++ b/src/vs/editor/browser/gpu/bufferDirtyTracker.ts @@ -0,0 +1,72 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface IBufferDirtyTrackerReader { + /** + * The index of the first dirty index. + */ + readonly dataOffset: number | undefined; + /** + * The index of the last dirty index (inclusive). + */ + readonly dirtySize: number | undefined; + /** + * Whether the buffer is dirty. + */ + readonly isDirty: boolean; + /** + * Clear the dirty state. + */ + clear(): void; +} + +/** + * A simple tracker for dirty regions in a buffer. + */ +export class BufferDirtyTracker implements IBufferDirtyTrackerReader { + + private _startIndex: number | undefined; + private _endIndex: number | undefined; + + get dataOffset(): number | undefined { + return this._startIndex; + } + + get dirtySize(): number | undefined { + if (this._startIndex === undefined || this._endIndex === undefined) { + return undefined; + } + return this._endIndex - this._startIndex + 1; + } + + get isDirty(): boolean { return this._startIndex !== undefined; } + + /** + * Flag the index(es) as modified. Returns the index flagged. + * @param index An index to flag. + * @param length An optional length to flag. Defaults to 1. + */ + flag(index: number, length: number = 1): number { + this._flag(index); + if (length > 1) { + this._flag(index + length - 1); + } + return index; + } + + private _flag(index: number) { + if (this._startIndex === undefined || index < this._startIndex) { + this._startIndex = index; + } + if (this._endIndex === undefined || index > this._endIndex) { + this._endIndex = index; + } + } + + clear() { + this._startIndex = undefined; + this._endIndex = undefined; + } +} diff --git a/src/vs/editor/browser/gpu/objectCollectionBuffer.ts b/src/vs/editor/browser/gpu/objectCollectionBuffer.ts index afad65089954b..5f3119ecaa48c 100644 --- a/src/vs/editor/browser/gpu/objectCollectionBuffer.ts +++ b/src/vs/editor/browser/gpu/objectCollectionBuffer.ts @@ -6,6 +6,7 @@ import { Emitter, Event } from '../../../base/common/event.js'; import { Disposable, dispose, toDisposable, type IDisposable } from '../../../base/common/lifecycle.js'; import { LinkedList } from '../../../base/common/linkedList.js'; +import { BufferDirtyTracker, type IBufferDirtyTrackerReader } from './bufferDirtyTracker.js'; export interface ObjectCollectionBufferPropertySpec { name: string; @@ -37,6 +38,11 @@ export interface IObjectCollectionBuffer ext return this._entries.size; } + private _dirtyTracker = new BufferDirtyTracker(); + get dirtyTracker(): IBufferDirtyTrackerReader { return this._dirtyTracker; } + private readonly _propertySpecsMap: Map = new Map(); private readonly _entrySize: number; private readonly _entries: LinkedList> = new LinkedList(); @@ -112,7 +121,7 @@ class ObjectCollectionBuffer ext throw new Error(`Cannot create more entries ObjectCollectionBuffer entries (capacity=${this.capacity})`); } - const value = new ObjectCollectionBufferEntry(this.view, this._propertySpecsMap, this._entries.size, data); + const value = new ObjectCollectionBufferEntry(this.view, this._propertySpecsMap, this._dirtyTracker, this._entries.size, data); const removeFromEntries = this._entries.push(value); const listeners: IDisposable[] = []; listeners.push(Event.forward(value.onDidChange, this._onDidChange)); @@ -129,6 +138,7 @@ class ObjectCollectionBuffer ext entry.i--; } } + this._dirtyTracker.flag(deletedEntryIndex, (this._entries.size - deletedEntryIndex) * this._entrySize); dispose(listeners); })); return value; @@ -145,6 +155,7 @@ class ObjectCollectionBufferEntry, + private _dirtyTracker: BufferDirtyTracker, public i: number, data: ObjectCollectionPropertyValues, ) { @@ -152,6 +163,7 @@ class ObjectCollectionBufferEntry Date: Tue, 17 Sep 2024 07:53:34 -0700 Subject: [PATCH 2/2] Tests for BufferDirtyTracker --- .../view/gpu/bufferDirtyTracker.test.ts | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 src/vs/editor/test/browser/view/gpu/bufferDirtyTracker.test.ts diff --git a/src/vs/editor/test/browser/view/gpu/bufferDirtyTracker.test.ts b/src/vs/editor/test/browser/view/gpu/bufferDirtyTracker.test.ts new file mode 100644 index 0000000000000..0ddc7a5befeff --- /dev/null +++ b/src/vs/editor/test/browser/view/gpu/bufferDirtyTracker.test.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { strictEqual } from 'assert'; +import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js'; +import { BufferDirtyTracker } from '../../../../browser/gpu/bufferDirtyTracker.js'; + +suite('BufferDirtyTracker', () => { + ensureNoDisposablesAreLeakedInTestSuite(); + + let bdt: BufferDirtyTracker; + + function assertState(dataOffset: number | undefined, dirtySize: number | undefined) { + strictEqual(bdt.dataOffset, dataOffset); + strictEqual(bdt.dirtySize, dirtySize); + strictEqual(bdt.isDirty, dataOffset !== undefined); + } + + setup(() => { + bdt = new BufferDirtyTracker(); + }); + + test('flag(index)', () => { + strictEqual(bdt.flag(0), 0); + assertState(0, 1); + strictEqual(bdt.flag(31), 31); + assertState(0, 32); + bdt.clear(); + assertState(undefined, undefined); + strictEqual(bdt.flag(10), 10); + assertState(10, 1); + strictEqual(bdt.flag(15), 15); + assertState(10, 6); + }); + + test('flag(index, length)', () => { + bdt.flag(0, 32); + assertState(0, 32); + bdt.clear(); + assertState(undefined, undefined); + bdt.flag(10, 6); + assertState(10, 6); + }); +});