diff --git a/src/webgpu/p5.RendererWebGPU.js b/src/webgpu/p5.RendererWebGPU.js index d4d26e1df2..e526c5e777 100644 --- a/src/webgpu/p5.RendererWebGPU.js +++ b/src/webgpu/p5.RendererWebGPU.js @@ -69,6 +69,9 @@ function rendererWebGPU(p5, fn) { this.length = length; this._renderer = renderer; this._schema = schema; + + // Flag to track whether the buffer has been removed from GPU memory + this._isRemoved = false; // Struct buffers are always packed as floats this._arrayType = schema !== null ? Float32Array : arrayType; // The element type this buffer has already been checked against, so @@ -145,6 +148,10 @@ function rendererWebGPU(p5, fn) { update(data) { const device = this._renderer.device; + if(this._isRemoved) { + throw new Error('Cannot update() in a removed storage buffer'); + } + if (this._schema !== null) { // Buffer was created with a struct array if ( @@ -280,6 +287,10 @@ function rendererWebGPU(p5, fn) { const device = this._renderer.device; this._renderer.flushDraw(); + if(this._isRemoved) { + throw new Error('Cannot read() from a removed storage buffer'); + } + const stagingBuffer = device.createBuffer({ size: this.size, usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ @@ -381,6 +392,10 @@ function rendererWebGPU(p5, fn) { set(index, value) { const device = this._renderer.device; + if(this._isRemoved) { + throw new Error('Cannot set() on a removed storage buffer'); + } + if (this._schema !== null) { // buffer was created with an array of structs if ( @@ -435,6 +450,28 @@ function rendererWebGPU(p5, fn) { ); } } + + /** + * Removes the storage buffer from GPU memory. + * + * @method remove + * @for p5.StorageBuffer + * @beta + * @webgpu + * @webgpuOnly + */ + + remove() { + const renderer = this._renderer; + if (!this._isRemoved && this.buffer && this.buffer.destroy) { + // Handle pending draws before destroying the buffer to avoid GPU errors + renderer.flushDraw(); + const bufferToDestroy = this.buffer; + renderer._postSubmitCallbacks.push(() => bufferToDestroy.destroy()); + this.buffer = null; + this._isRemoved = true; + } + } } /** diff --git a/test/unit/webgpu/p5.RendererWebGPU.js b/test/unit/webgpu/p5.RendererWebGPU.js index 57aa98c397..7970fbceea 100644 --- a/test/unit/webgpu/p5.RendererWebGPU.js +++ b/test/unit/webgpu/p5.RendererWebGPU.js @@ -368,6 +368,55 @@ suite('WebGPU p5.RendererWebGPU', function () { }); }); + suite('StorageBuffer.remove()', function () { + test('removes a StorageBuffer from the WebGPU\'s memory', function () { + const buf = myp5.createStorage(new Float32Array([1, 2, 3, 4])); + + // Remove the buffer and check that it is no longer accessible + buf.remove(); + expect(buf.buffer).to.be.null; + }); + + test('throws when setting a value on a removed StorageBuffer', function () { + const buf = myp5.createStorage(new Float32Array([1, 2, 3, 4])); + buf.remove(); + expect(() => buf.set(0, 5.0)).to.throw(); + }); + + test('throws when updating a removed StorageBuffer', function () { + const buf = myp5.createStorage(new Float32Array([1, 2, 3, 4])); + buf.remove(); + expect(() => buf.update(new Float32Array([5, 6, 7, 8]))).to.throw(); + }); + + test('throws when reading a removed StorageBuffer', async function () { + const buf = myp5.createStorage(new Float32Array([1, 2, 3, 4])); + buf.remove(); + await expect(buf.read()).rejects.toThrow(); + }); + + test('calling remove() multiple times does not throw', function () { + const buf = myp5.createStorage(new Float32Array([1, 2, 3, 4])); + expect(() => { + buf.remove(); + buf.remove(); + }).not.toThrow(); + }); + + test('creating a new StorageBuffer after removing one in the same variable works properly', async function () { + let buf = myp5.createStorage(new Float32Array([1, 2, 3, 4])); + buf.remove(); + + buf = myp5.createStorage(new Float32Array([5, 6, 7, 8])); + const result = await buf.read(); + + expect(result[0]).to.be.closeTo(5, 0.001); + expect(result[1]).to.be.closeTo(6, 0.001); + expect(result[2]).to.be.closeTo(7, 0.001); + expect(result[3]).to.be.closeTo(8, 0.001); + }); + }); + suite('StorageList', function () { test('reads back float values pushed by a compute shader', async function () { const src = myp5.createStorage(new Float32Array([10, 20, 30]));