From 95507a65523de8819d88630ae99aa4af79764aab Mon Sep 17 00:00:00 2001 From: Egor Kuklin Date: Fri, 21 Aug 2026 15:55:50 +0700 Subject: [PATCH 1/3] WebGLRenderer: Unbind PIXEL_PACK_BUFFER after async readback. (#34321) Co-authored-by: Cursor Agent Co-authored-by: Egor Kuklin --- src/renderers/WebGLRenderer.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/renderers/WebGLRenderer.js b/src/renderers/WebGLRenderer.js index a86234d1f0bd53..cf2b0cc6b10162 100644 --- a/src/renderers/WebGLRenderer.js +++ b/src/renderers/WebGLRenderer.js @@ -3256,6 +3256,7 @@ class WebGLRenderer { _gl.bufferData( _gl.PIXEL_PACK_BUFFER, buffer.byteLength, _gl.STREAM_READ ); _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), 0 ); + _gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, null ); // reset the frame buffer to the currently set buffer before waiting const currFramebuffer = _currentRenderTarget !== null ? properties.get( _currentRenderTarget ).__webglFramebuffer : null; @@ -3271,6 +3272,7 @@ class WebGLRenderer { // read the data and delete the buffer _gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, glBuffer ); _gl.getBufferSubData( _gl.PIXEL_PACK_BUFFER, 0, buffer ); + _gl.bindBuffer( _gl.PIXEL_PACK_BUFFER, null ); _gl.deleteBuffer( glBuffer ); _gl.deleteSync( sync ); From 04d9e4ee54ac151781afd2434add674c1eaa6090 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Fri, 21 Aug 2026 11:34:50 +0200 Subject: [PATCH 2/3] WebGLTextureUtils: Delete `PIXEL_PACK_BUFFER` after copy. (#34323) --- src/renderers/webgl-fallback/utils/WebGLTextureUtils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js b/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js index 4387a3f2486550..b9b4f131c69f0d 100644 --- a/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js +++ b/src/renderers/webgl-fallback/utils/WebGLTextureUtils.js @@ -1281,6 +1281,7 @@ class WebGLTextureUtils { backend.state.bindFramebuffer( gl.READ_FRAMEBUFFER, null ); + gl.deleteBuffer( buffer ); gl.deleteFramebuffer( fb ); return dstBuffer; From 00ea66ecb4b253c78e65b5f5a558af4696ba88a7 Mon Sep 17 00:00:00 2001 From: Michael Herzog Date: Fri, 21 Aug 2026 12:49:18 +0200 Subject: [PATCH 3/3] WebGPURenderer: Add clipping + stencil demo. (#34325) --- examples/files.json | 1 + .../screenshots/webgpu_clipping_stencil.jpg | Bin 0 -> 7496 bytes examples/webgpu_clipping_stencil.html | 321 ++++++++++++++++++ src/renderers/common/ClippingContext.js | 12 +- src/renderers/webgl-fallback/WebGLBackend.js | 1 + .../webgl-fallback/utils/WebGLState.js | 19 +- 6 files changed, 343 insertions(+), 11 deletions(-) create mode 100644 examples/screenshots/webgpu_clipping_stencil.jpg create mode 100644 examples/webgpu_clipping_stencil.html diff --git a/examples/files.json b/examples/files.json index e67c9ac04f3979..e704fd87b33b47 100644 --- a/examples/files.json +++ b/examples/files.json @@ -317,6 +317,7 @@ "webgpu_centroid_sampling", "webgpu_clearcoat", "webgpu_clipping", + "webgpu_clipping_stencil", "webgpu_compile_async", "webgpu_compute_audio", "webgpu_compute_birds", diff --git a/examples/screenshots/webgpu_clipping_stencil.jpg b/examples/screenshots/webgpu_clipping_stencil.jpg new file mode 100644 index 0000000000000000000000000000000000000000..dbe8c4d6bab321defaf7733a6ac122e5a8a5bdc4 GIT binary patch literal 7496 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 SgXM%;F8>(vzcnO?HuWEVz6}Kc literal 0 HcmV?d00001 diff --git a/examples/webgpu_clipping_stencil.html b/examples/webgpu_clipping_stencil.html new file mode 100644 index 00000000000000..a3ff121284a010 --- /dev/null +++ b/examples/webgpu_clipping_stencil.html @@ -0,0 +1,321 @@ + + + + three.js webgpu - clipping stencil + + + + + + + + + +
+ + +
+ three.jsClipping Stencil +
+ + Solid geometry with clip planes and stencil materials. +
+ + + + + + + diff --git a/src/renderers/common/ClippingContext.js b/src/renderers/common/ClippingContext.js index 1ed98b973eeb54..d441736f01bfa2 100644 --- a/src/renderers/common/ClippingContext.js +++ b/src/renderers/common/ClippingContext.js @@ -5,6 +5,8 @@ import { Vector4 } from '../../math/Vector4.js'; const _plane = /*@__PURE__*/ new Plane(); +let _clippingContextId = 0; + /** * Represents the state that is used to perform clipping via clipping planes. * There is a default clipping context for each render context. When the @@ -22,6 +24,14 @@ class ClippingContext { */ constructor( parentContext = null ) { + /** + * The id of the clipping context. + * + * @type {number} + * @readonly + */ + this.id = _clippingContextId ++; + /** * The clipping context's version. * @@ -222,7 +232,7 @@ class ClippingContext { if ( update ) { this.version ++; - this.cacheKey = `${ this.intersectionPlanes.length }:${ this.unionPlanes.length }`; + this.cacheKey = `${ this.id }:${ this.intersectionPlanes.length }:${ this.unionPlanes.length }`; } diff --git a/src/renderers/webgl-fallback/WebGLBackend.js b/src/renderers/webgl-fallback/WebGLBackend.js index ff4bb212b9407d..00649a36bbe28a 100644 --- a/src/renderers/webgl-fallback/WebGLBackend.js +++ b/src/renderers/webgl-fallback/WebGLBackend.js @@ -821,6 +821,7 @@ class WebGLBackend extends Backend { const clearStencil = renderer.getClearStencil(); if ( depth ) this.state.setDepthMask( true ); + if ( stencil ) this.state.setStencilMask( 0xffffffff ); if ( descriptor.textures === null ) { diff --git a/src/renderers/webgl-fallback/utils/WebGLState.js b/src/renderers/webgl-fallback/utils/WebGLState.js index 1fb2f4fa6ea106..897be18b97d081 100644 --- a/src/renderers/webgl-fallback/utils/WebGLState.js +++ b/src/renderers/webgl-fallback/utils/WebGLState.js @@ -959,28 +959,27 @@ class WebGLState { ? this.enable( gl.SAMPLE_ALPHA_TO_COVERAGE ) : this.disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); - if ( hardwareClippingPlanes > 0 ) { - if ( this.currentClippingPlanes !== hardwareClippingPlanes ) { + if ( this.currentClippingPlanes !== hardwareClippingPlanes ) { - const CLIP_DISTANCE0_WEBGL = 0x3000; + const CLIP_DISTANCE0_WEBGL = 0x3000; - for ( let i = 0; i < 8; i ++ ) { + for ( let i = 0; i < 8; i ++ ) { - if ( i < hardwareClippingPlanes ) { + if ( i < hardwareClippingPlanes ) { - this.enable( CLIP_DISTANCE0_WEBGL + i ); + this.enable( CLIP_DISTANCE0_WEBGL + i ); - } else { - - this.disable( CLIP_DISTANCE0_WEBGL + i ); + } else { - } + this.disable( CLIP_DISTANCE0_WEBGL + i ); } } + this.currentClippingPlanes = hardwareClippingPlanes; + } }