diff --git a/src/webgpu/p5.RendererWebGPU.js b/src/webgpu/p5.RendererWebGPU.js index 42946ebf34..0eb6635223 100644 --- a/src/webgpu/p5.RendererWebGPU.js +++ b/src/webgpu/p5.RendererWebGPU.js @@ -93,6 +93,10 @@ function rendererWebGPU(p5, fn) { this.finalCamera = new Camera(this); this.finalCamera._computeCameraDefaultSettings(); this.finalCamera._setDefaultCamera(); + + this.depthFormat = 'depth24plus-stencil8'; + this.depthTexture = null; + this.depthTextureView = null; } async setupContext() { @@ -133,7 +137,6 @@ function rendererWebGPU(p5, fn) { }); // TODO disablable stencil - this.depthFormat = 'depth24plus-stencil8'; this.mainFramebuffer = this.createFramebuffer({ _useCanvasFormat: true }); this._updateSize(); this._update(); @@ -190,6 +193,7 @@ function rendererWebGPU(p5, fn) { } _updateSize() { + if (!this.device || !this.depthFormat) return; if (this.depthTexture && this.depthTexture.destroy) { this.flushDraw(); const textureToDestroy = this.depthTexture; @@ -284,6 +288,7 @@ function rendererWebGPU(p5, fn) { } clear(...args) { + if (!this.device || !this.drawingContext) return; const _r = args[0] || 0; const _g = args[1] || 0; const _b = args[2] || 0; @@ -350,6 +355,7 @@ function rendererWebGPU(p5, fn) { * occlude anything subsequently drawn. */ clearDepth(depth = 1) { + if (!this.device || !this.depthTextureView) return; this._finishActiveRenderPass(); const commandEncoder = this.device.createCommandEncoder(); diff --git a/test/unit/webgpu/p5.RendererWebGPU.js b/test/unit/webgpu/p5.RendererWebGPU.js index b3c9001fc4..94c426908e 100644 --- a/test/unit/webgpu/p5.RendererWebGPU.js +++ b/test/unit/webgpu/p5.RendererWebGPU.js @@ -16,7 +16,7 @@ suite('WebGPU p5.RendererWebGPU', function() { }); beforeEach(async function() { - await myp5.createCanvas(50, 50, 'webgpu'); + await myp5.createCanvas(50, 50, myp5.WEBGPU); }); afterEach(function() { @@ -126,4 +126,41 @@ suite('WebGPU p5.RendererWebGPU', function() { } }); }); + + suite('Stability', function() { + test('pixelDensity() after setAttributes() should not crash', async function() { + // This test simulates the issue where a synchronous call (pixelDensity) + // happens before an asynchronous initialization (setAttributes -> _resetContext) + // is complete. + await new Promise((resolve, reject) => { + try { + myp5 = new p5(p => { + p.setup = async function() { + try { + await p.createCanvas(100, 100, p.WEBGPU); + + // This triggers an asynchronous _resetContext + p.setAttributes({ antialias: true }); + + // This triggers a synchronous resize() -> _updateSize() + // before the new renderer's device is ready. + expect(() => { + p.pixelDensity(1); + }).not.toThrow(); + + resolve(); + } catch (err) { + reject(err); + } + }; + }); + } catch (err) { + reject(err); + } + }); + + // Verify stability after the async reset + expect(myp5._renderer).to.exist; + }); + }); });