From 27cd9ecb974877b7890540758cf630847f69ebfa Mon Sep 17 00:00:00 2001 From: sunag Date: Sun, 24 Aug 2025 16:00:27 -0300 Subject: [PATCH 01/13] Introduce `CanvasRenderTarget` --- src/Three.WebGPU.Nodes.js | 1 + src/Three.WebGPU.js | 1 + src/core/CanvasRenderTarget.js | 46 +++++++++++ src/renderers/common/Backend.js | 2 +- src/renderers/common/Renderer.js | 13 +++- src/renderers/webgpu/WebGPUBackend.js | 78 ++++++++++++++++++- .../webgpu/utils/WebGPUTextureUtils.js | 17 +++- 7 files changed, 151 insertions(+), 7 deletions(-) create mode 100644 src/core/CanvasRenderTarget.js diff --git a/src/Three.WebGPU.Nodes.js b/src/Three.WebGPU.Nodes.js index 4be505a3031bb6..e3f749bce81029 100644 --- a/src/Three.WebGPU.Nodes.js +++ b/src/Three.WebGPU.Nodes.js @@ -19,6 +19,7 @@ export { default as NodeLoader } from './loaders/nodes/NodeLoader.js'; export { default as NodeObjectLoader } from './loaders/nodes/NodeObjectLoader.js'; export { default as NodeMaterialLoader } from './loaders/nodes/NodeMaterialLoader.js'; export { ClippingGroup } from './objects/ClippingGroup.js'; +export { CanvasRenderTarget } from './core/CanvasRenderTarget.js'; export * from './nodes/Nodes.js'; import * as TSL from './nodes/TSL.js'; export { TSL }; diff --git a/src/Three.WebGPU.js b/src/Three.WebGPU.js index 332b02a68c92a8..0cf400663142ff 100644 --- a/src/Three.WebGPU.js +++ b/src/Three.WebGPU.js @@ -21,6 +21,7 @@ export { default as NodeLoader } from './loaders/nodes/NodeLoader.js'; export { default as NodeObjectLoader } from './loaders/nodes/NodeObjectLoader.js'; export { default as NodeMaterialLoader } from './loaders/nodes/NodeMaterialLoader.js'; export { ClippingGroup } from './objects/ClippingGroup.js'; +export { CanvasRenderTarget } from './core/CanvasRenderTarget.js'; export * from './nodes/Nodes.js'; import * as TSL from './nodes/TSL.js'; export { TSL }; diff --git a/src/core/CanvasRenderTarget.js b/src/core/CanvasRenderTarget.js new file mode 100644 index 00000000000000..535ba478e7e5e0 --- /dev/null +++ b/src/core/CanvasRenderTarget.js @@ -0,0 +1,46 @@ +import { RenderTarget } from './RenderTarget.js'; +import { REVISION } from '../constants.js'; + +/** + * CanvasRenderTarget is a specialized RenderTarget for rendering to a Canvas element. + * + * @augments RenderTarget + */ +class CanvasRenderTarget extends RenderTarget { + + /** + * @param {HTMLCanvasElement|OffscreenCanvas} canvas - The canvas to use as the render target. + * @param {Object} [options={}] - Optional parameters for the render target. + */ + constructor( canvas, options = {} ) { + + super( canvas.width, canvas.height, options ); + + // OffscreenCanvas does not have setAttribute, see #22811 + if ( 'setAttribute' in canvas ) canvas.setAttribute( 'data-engine', `three.js r${ REVISION } webgpu` ); + + this.canvas = canvas; + + this.isCanvasRenderTarget = true; + + } + + /** + * Sets the size of the canvas. + * + * @param {number} width - The new width of the canvas. + * @param {number} height - The new height of the canvas. + * @returns {CanvasRenderTarget} The updated render target. + */ + setSize( width, height ) { + + this.canvas.width = width; + this.canvas.height = height; + + return super.setSize( width, height ); + + } + +} + +export { CanvasRenderTarget }; diff --git a/src/renderers/common/Backend.js b/src/renderers/common/Backend.js index 8df332b390bf71..0d7bcbc91db02f 100644 --- a/src/renderers/common/Backend.js +++ b/src/renderers/common/Backend.js @@ -587,7 +587,7 @@ class Backend { domElement = ( this.parameters.canvas !== undefined ) ? this.parameters.canvas : createCanvasElement(); // OffscreenCanvas does not have setAttribute, see #22811 - if ( 'setAttribute' in domElement ) domElement.setAttribute( 'data-engine', `three.js r${REVISION} webgpu` ); + if ( 'setAttribute' in domElement ) domElement.setAttribute( 'data-engine', `three.js r${ REVISION } webgpu` ); this.domElement = domElement; diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js index 4507848188eea6..4ded428495bbdc 100644 --- a/src/renderers/common/Renderer.js +++ b/src/renderers/common/Renderer.js @@ -1244,6 +1244,7 @@ class Renderer { frameBufferTarget.depthBuffer = depth; frameBufferTarget.stencilBuffer = stencil; + if ( outputRenderTarget !== null ) { frameBufferTarget.setSize( outputRenderTarget.width, outputRenderTarget.height, outputRenderTarget.depth ); @@ -1289,6 +1290,7 @@ class Renderer { const previousRenderId = nodeFrame.renderId; const previousRenderContext = this._currentRenderContext; const previousRenderObjectFunction = this._currentRenderObjectFunction; + const previousRenderTarget = this._renderTarget; // @@ -1313,6 +1315,12 @@ class Renderer { renderTarget = outputRenderTarget; + if ( outputRenderTarget && outputRenderTarget.isCanvasRenderTarget ) { + + outputRenderTarget.samples = this.samples; + + } + } // @@ -1519,6 +1527,8 @@ class Renderer { this._renderOutput( renderTarget ); + this.setRenderTarget( previousRenderTarget, activeCubeFace, activeMipmapLevel ); + } // @@ -1570,7 +1580,6 @@ class Renderer { this.autoClear = currentAutoClear; this.xr.enabled = currentXR; - } /** @@ -2175,7 +2184,7 @@ class Renderer { */ get isOutputTarget() { - return this._renderTarget === this._outputRenderTarget || this._renderTarget === null; + return this._outputRenderTarget !== null || this._renderTarget === null; } diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index 0d5e64c42f9f38..8463a1b0bff964 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -154,6 +154,13 @@ class WebGPUBackend extends Backend { */ this.occludedResolveCache = new Map(); + /** + * A map that manages the WebGPU contexts for CanvasRenderTarget. + * + * @type {WeakMap} + */ + this.canvasRTContexts = new WeakMap(); + } /** @@ -465,7 +472,13 @@ class WebGPUBackend extends Backend { if ( isRenderCameraDepthArray !== true ) { - const textureView = textureData.texture.createView( viewDescriptor ); + let textureView; + + if ( renderTarget.isCanvasRenderTarget !== true ) { + + textureView = textureData.texture.createView( viewDescriptor ); + + } let view, resolveTarget; @@ -546,6 +559,26 @@ class WebGPUBackend extends Backend { } + if ( renderTarget.isCanvasRenderTarget ) { + + const colorAttachment = descriptor.colorAttachments[ 0 ]; + const context = this.getContextFromCanvasRenderTarget( renderTarget ); + const view = context.getCurrentTexture().createView(); + + const textureData = this.get( renderTarget.textures[ 0 ] ); + + if ( textureData.msaaTexture ) { + + colorAttachment.resolveTarget = view; + + } else { + + colorAttachment.view = view; + + } + + } + if ( descriptorBase.depthStencilView ) { descriptor.depthStencilAttachment = { @@ -769,6 +802,49 @@ class WebGPUBackend extends Backend { } + /** + * Gets the WebGPU context from a CanvasRenderTarget. + * + * @param {CanvasRenderTarget} canvasRT - The canvas render target. + * @returns {GPUCanvasContext} The WebGPU context. + */ + getContextFromCanvasRenderTarget( canvasRT ) { + + let contextData = this.canvasRTContexts.get( canvasRT ); + + if ( contextData === undefined ) { + + const context = canvasRT.canvas.getContext( 'webgpu' ); + + const alphaMode = this.parameters.alpha ? 'premultiplied' : 'opaque'; + const colorSpace = this.renderer.outputColorSpace; + + const toneMappingMode = ColorManagement.getToneMappingMode( colorSpace ); + + context.configure( { + device: this.device, + format: this.utils.getPreferredCanvasFormat(), + usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC, + alphaMode: alphaMode, + toneMapping: { + mode: toneMappingMode + } + } ); + + contextData = { + context, + alphaMode, + colorSpace + }; + + this.canvasRTContexts.set( canvasRT, contextData ); + + } + + return contextData.context; + + } + /** * This method creates layer descriptors for each camera in an array camera * to prepare for rendering to a depth array texture. diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js index 5f1426ef372989..b050a9a6c38002 100644 --- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js +++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js @@ -202,25 +202,36 @@ class WebGPUTextureUtils { const { width, height, depth, levels } = options; + let format; + if ( texture.isFramebufferTexture ) { if ( options.renderTarget ) { - options.format = this.backend.utils.getCurrentColorFormat( options.renderTarget ); + format = this.backend.utils.getCurrentColorFormat( options.renderTarget ); } else { - options.format = this.backend.utils.getPreferredCanvasFormat(); + format = this.backend.utils.getPreferredCanvasFormat(); } } + if ( texture.renderTarget && texture.renderTarget.isCanvasRenderTarget ) { + + format = this.backend.utils.getPreferredCanvasFormat(); + + } + const dimension = this._getDimension( texture ); - const format = texture.internalFormat || options.format || getFormat( texture, backend.device ); + + format = texture.internalFormat || format || getFormat( texture, backend.device ); textureData.format = format; + // + const { samples, primarySamples, isMSAA } = backend.utils.getTextureSampleData( texture ); let usage = GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC; From b508986fbcea756a685fe6fd35e2146480a666d3 Mon Sep 17 00:00:00 2001 From: sunag Date: Sun, 24 Aug 2025 16:13:54 -0300 Subject: [PATCH 02/13] add `webgpu_multiple_canvas` example --- examples/files.json | 1 + .../screenshots/webgpu_multiple_canvas.jpg | Bin 0 -> 28143 bytes examples/webgpu_multiple_canvas.html | 198 ++++++++++++++++++ 3 files changed, 199 insertions(+) create mode 100644 examples/screenshots/webgpu_multiple_canvas.jpg create mode 100644 examples/webgpu_multiple_canvas.html diff --git a/examples/files.json b/examples/files.json index 7d474ed60fc365..daecf68924b4ae 100644 --- a/examples/files.json +++ b/examples/files.json @@ -381,6 +381,7 @@ "webgpu_morphtargets", "webgpu_morphtargets_face", "webgpu_mrt", + "webgpu_multiple_canvas", "webgpu_multiple_elements", "webgpu_mrt_mask", "webgpu_multiple_rendertargets", diff --git a/examples/screenshots/webgpu_multiple_canvas.jpg b/examples/screenshots/webgpu_multiple_canvas.jpg new file mode 100644 index 0000000000000000000000000000000000000000..35f5ade17c3de283475754529a0fec8d8b779451 GIT binary patch literal 28143 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

$LYF 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 literal 0 HcmV?d00001 diff --git a/examples/webgpu_multiple_canvas.html b/examples/webgpu_multiple_canvas.html new file mode 100644 index 00000000000000..a4f26af10d1677 --- /dev/null +++ b/examples/webgpu_multiple_canvas.html @@ -0,0 +1,198 @@ + + + + three.js webgpu - multiple canvas + + + + + + + +

+
three.js - multiple canvas
+
+ + + + + + + From 6320c0136832472e2b204d064ca6b1e5bef82787 Mon Sep 17 00:00:00 2001 From: sunag Date: Sun, 24 Aug 2025 16:33:09 -0300 Subject: [PATCH 03/13] Update Renderer.js --- src/renderers/common/Renderer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js index 4ded428495bbdc..bfc960cdc3c7b0 100644 --- a/src/renderers/common/Renderer.js +++ b/src/renderers/common/Renderer.js @@ -2184,7 +2184,7 @@ class Renderer { */ get isOutputTarget() { - return this._outputRenderTarget !== null || this._renderTarget === null; + return this._renderTarget === this._outputRenderTarget || this._renderTarget === null; } From e98469154fb60f12371fddd3952d850bfdd8eea9 Mon Sep 17 00:00:00 2001 From: sunag Date: Sun, 24 Aug 2025 16:59:40 -0300 Subject: [PATCH 04/13] Update puppeteer.js --- test/e2e/puppeteer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/test/e2e/puppeteer.js b/test/e2e/puppeteer.js index 4757a7cb830432..291425d32ab21c 100644 --- a/test/e2e/puppeteer.js +++ b/test/e2e/puppeteer.js @@ -133,6 +133,7 @@ const exceptionList = [ 'webgpu_compute_texture_pingpong', 'webgpu_compute_water', 'webgpu_materials', + 'webgpu_multiple_canvas', 'webgpu_video_panorama', 'webgpu_postprocessing_bloom_emissive', 'webgpu_lights_tiled', From d1dd08e4365a8dc9657e02cbbf5b6273d3f4b9bc Mon Sep 17 00:00:00 2001 From: sunag Date: Mon, 25 Aug 2025 09:29:28 -0300 Subject: [PATCH 05/13] use internalFormat for now --- src/renderers/webgpu/utils/WebGPUTextureUtils.js | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js index fe19e1f56f8316..246056db8ca18c 100644 --- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js +++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js @@ -202,17 +202,15 @@ class WebGPUTextureUtils { const { width, height, depth, levels } = options; - let format; - if ( texture.isFramebufferTexture ) { if ( options.renderTarget ) { - format = this.backend.utils.getCurrentColorFormat( options.renderTarget ); + options.format = this.backend.utils.getCurrentColorFormat( options.renderTarget ); } else { - format = this.backend.utils.getPreferredCanvasFormat(); + options.format = this.backend.utils.getPreferredCanvasFormat(); } @@ -220,13 +218,12 @@ class WebGPUTextureUtils { if ( texture.renderTarget && texture.renderTarget.isCanvasRenderTarget ) { - format = this.backend.utils.getPreferredCanvasFormat(); + texture.internalFormat = this.backend.utils.getPreferredCanvasFormat(); } const dimension = this._getDimension( texture ); - - format = texture.internalFormat || format || getFormat( texture, backend.device ); + const format = texture.internalFormat || options.format || getFormat( texture, backend.device ); textureData.format = format; From 255f9dc5bbf721bc46923092812842ed6d5261ab Mon Sep 17 00:00:00 2001 From: sunag Date: Mon, 25 Aug 2025 10:57:32 -0300 Subject: [PATCH 06/13] respect pixel ratio --- examples/webgpu_multiple_canvas.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/webgpu_multiple_canvas.html b/examples/webgpu_multiple_canvas.html index a4f26af10d1677..62c1d42a7bbae0 100644 --- a/examples/webgpu_multiple_canvas.html +++ b/examples/webgpu_multiple_canvas.html @@ -115,6 +115,8 @@ element.className = 'list-item'; const sceneCanvas = document.createElement( 'canvas' ); + sceneCanvas.width = Math.floor( 200 * window.devicePixelRatio ); + sceneCanvas.height = Math.floor( 200 * window.devicePixelRatio ); element.appendChild( sceneCanvas ); const descriptionElement = document.createElement( 'div' ); @@ -172,7 +174,7 @@ scenes.forEach( function ( scene ) { // so something moves - scene.children[ 0 ].rotation.y = Date.now() * 0.001; + //scene.children[ 0 ].rotation.y = Date.now() * 0.001; // get the canvas render target const canvasRenderTarget = scene.userData.canvasRenderTarget; From 6e834275c9ae4840506e52bff8c2c4a1d9d1fc2b Mon Sep 17 00:00:00 2001 From: sunag Date: Mon, 25 Aug 2025 11:35:43 -0300 Subject: [PATCH 07/13] cleanup --- src/renderers/webgpu/WebGPUBackend.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index 8463a1b0bff964..2fd5e15ebd7fc8 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -350,14 +350,15 @@ class WebGPUBackend extends Backend { } const colorAttachment = descriptor.colorAttachments[ 0 ]; + const contextView = this.context.getCurrentTexture().createView(); if ( this.renderer.samples > 0 ) { - colorAttachment.resolveTarget = this.context.getCurrentTexture().createView(); + colorAttachment.resolveTarget = contextView; } else { - colorAttachment.view = this.context.getCurrentTexture().createView(); + colorAttachment.view = contextView; } From f00f9006adbbc62e3b80ce2991683ca32d749a1d Mon Sep 17 00:00:00 2001 From: sunag Date: Mon, 25 Aug 2025 15:30:31 -0300 Subject: [PATCH 08/13] improve antialiasing performance - wip --- src/materials/nodes/Line2NodeMaterial.js | 4 +- src/materials/nodes/NodeMaterial.js | 2 +- src/nodes/display/PassNode.js | 2 +- src/nodes/shapes/Shapes.js | 2 +- src/renderers/common/Renderer.js | 40 +++++++++- src/renderers/common/Textures.js | 1 + src/renderers/common/XRManager.js | 2 +- src/renderers/webgl-fallback/WebGLBackend.js | 2 +- .../webgl-fallback/utils/WebGLState.js | 2 +- src/renderers/webgpu/WebGPUBackend.js | 15 +--- .../webgpu/utils/WebGPUTextureUtils.js | 80 ++++++++++++++----- src/renderers/webgpu/utils/WebGPUUtils.js | 4 +- 12 files changed, 112 insertions(+), 44 deletions(-) diff --git a/src/materials/nodes/Line2NodeMaterial.js b/src/materials/nodes/Line2NodeMaterial.js index e36f68b6fd2e1e..a8dc24fca504b2 100644 --- a/src/materials/nodes/Line2NodeMaterial.js +++ b/src/materials/nodes/Line2NodeMaterial.js @@ -378,7 +378,7 @@ class Line2NodeMaterial extends NodeMaterial { if ( ! useDash ) { - if ( useAlphaToCoverage && renderer.samples > 1 ) { + if ( useAlphaToCoverage && renderer.currentSamples > 0 ) { const dnorm = norm.fwidth(); alpha.assign( smoothstep( dnorm.negate().add( 0.5 ), dnorm.add( 0.5 ), norm ).oneMinus() ); @@ -395,7 +395,7 @@ class Line2NodeMaterial extends NodeMaterial { // round endcaps - if ( useAlphaToCoverage && renderer.samples > 1 ) { + if ( useAlphaToCoverage && renderer.currentSamples > 0 ) { const a = vUv.x; const b = vUv.y.greaterThan( 0.0 ).select( vUv.y.sub( 1.0 ), vUv.y.add( 1.0 ) ); diff --git a/src/materials/nodes/NodeMaterial.js b/src/materials/nodes/NodeMaterial.js index c76ccb101b8b3e..eca22b5e84f60f 100644 --- a/src/materials/nodes/NodeMaterial.js +++ b/src/materials/nodes/NodeMaterial.js @@ -589,7 +589,7 @@ class NodeMaterial extends Material { if ( unionPlanes.length > 0 || intersectionPlanes.length > 0 ) { - const samples = builder.renderer.samples; + const samples = builder.renderer.currentSamples; if ( this.alphaToCoverage && samples > 1 ) { diff --git a/src/nodes/display/PassNode.js b/src/nodes/display/PassNode.js index 8e4766dd0e577f..dde38c3b695a18 100644 --- a/src/nodes/display/PassNode.js +++ b/src/nodes/display/PassNode.js @@ -663,7 +663,7 @@ class PassNode extends TempNode { setup( { renderer } ) { - this.renderTarget.samples = this.options.samples === undefined ? renderer.samples : this.options.samples; + this.renderTarget.samples = this.options.samples === undefined ? renderer.currentSamples : this.options.samples; this.renderTarget.texture.type = renderer.getColorBufferType(); diff --git a/src/nodes/shapes/Shapes.js b/src/nodes/shapes/Shapes.js index bf9c5b8828f00c..42fc1e02e24d37 100644 --- a/src/nodes/shapes/Shapes.js +++ b/src/nodes/shapes/Shapes.js @@ -16,7 +16,7 @@ export const shapeCircle = Fn( ( [ coord = uv() ], { renderer, material } ) => { let alpha; - if ( material.alphaToCoverage && renderer.samples > 1 ) { + if ( material.alphaToCoverage && renderer.sacurrentSamplesmples > 0 ) { const dlen = float( len2.fwidth() ).toVar(); diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js index 4507848188eea6..01f471428ee5b7 100644 --- a/src/renderers/common/Renderer.js +++ b/src/renderers/common/Renderer.js @@ -117,7 +117,7 @@ class Renderer { * @type {number} * @default 0 */ - this.samples = samples || ( antialias === true ) ? 4 : 0; + this._samples = samples || ( antialias === true ) ? 4 : 0; /** * Whether the renderer should automatically clear the current rendering target @@ -2144,6 +2144,44 @@ class Renderer { } + /** + * Returns `true` if a framebuffer target is needed to perform tone mapping or color space conversion. + * If this is the case, the renderer allocates an internal render target for that purpose. + * + */ + get needsFrameBufferTarget() { + + const useToneMapping = this.currentToneMapping !== NoToneMapping; + const useColorSpace = this.currentColorSpace !== ColorManagement.workingColorSpace; + + return useToneMapping || useColorSpace; + + } + + get samples() { + + return this._samples; + + } + + get currentSamples() { + + let samples = this._samples; + + if ( this._renderTarget !== null ) { + + samples = this._renderTarget.samples; + + } else if ( this.needsFrameBufferTarget ) { + + samples = 0; + + } + + return samples; + + } + /** * The current tone mapping of the renderer. When not producing screen output, * the tone mapping is always `NoToneMapping`. diff --git a/src/renderers/common/Textures.js b/src/renderers/common/Textures.js index 5a51b48220da09..36388d35d15155 100644 --- a/src/renderers/common/Textures.js +++ b/src/renderers/common/Textures.js @@ -84,6 +84,7 @@ class Textures extends DataMap { depthTexture.image.width = mipWidth; depthTexture.image.height = mipHeight; depthTexture.image.depth = size.depth; + depthTexture.renderTarget = renderTarget; depthTexture.isArrayTexture = renderTarget.multiview === true && size.depth > 1; depthTextureMips[ activeMipmapLevel ] = depthTexture; diff --git a/src/renderers/common/XRManager.js b/src/renderers/common/XRManager.js index c4d52dc76b78fb..12f5180a6e30b2 100644 --- a/src/renderers/common/XRManager.js +++ b/src/renderers/common/XRManager.js @@ -1040,7 +1040,7 @@ class XRManager extends EventDispatcher { // fallback to XRWebGLLayer const layerInit = { - antialias: renderer.samples > 0, + antialias: renderer.currentSamples > 0, alpha: true, depth: renderer.depth, stencil: renderer.stencil, diff --git a/src/renderers/webgl-fallback/WebGLBackend.js b/src/renderers/webgl-fallback/WebGLBackend.js index b1072f0264e38f..20eeacd93382f3 100644 --- a/src/renderers/webgl-fallback/WebGLBackend.js +++ b/src/renderers/webgl-fallback/WebGLBackend.js @@ -219,7 +219,7 @@ class WebGLBackend extends Backend { const parameters = this.parameters; const contextAttributes = { - antialias: renderer.samples > 0, + antialias: renderer.currentSamples > 0, alpha: true, // always true for performance reasons depth: renderer.depth, stencil: renderer.stencil diff --git a/src/renderers/webgl-fallback/utils/WebGLState.js b/src/renderers/webgl-fallback/utils/WebGLState.js index 1d781e13df2b74..7df6def7cc1774 100644 --- a/src/renderers/webgl-fallback/utils/WebGLState.js +++ b/src/renderers/webgl-fallback/utils/WebGLState.js @@ -754,7 +754,7 @@ class WebGLState { this.setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits ); - material.alphaToCoverage === true && this.backend.renderer.samples > 1 + material.alphaToCoverage === true && this.backend.renderer.currentSamples > 1 ? this.enable( gl.SAMPLE_ALPHA_TO_COVERAGE ) : this.disable( gl.SAMPLE_ALPHA_TO_COVERAGE ); diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js index 0d5e64c42f9f38..a22408ac4cf393 100644 --- a/src/renderers/webgpu/WebGPUBackend.js +++ b/src/renderers/webgpu/WebGPUBackend.js @@ -92,14 +92,6 @@ class WebGPUBackend extends Backend { */ this.context = null; - /** - * A reference to the color attachment of the default framebuffer. - * - * @type {?GPUTexture} - * @default null - */ - this.colorBuffer = null; - /** * A reference to the default render pass descriptor. * @@ -328,9 +320,9 @@ class WebGPUBackend extends Backend { const colorAttachment = descriptor.colorAttachments[ 0 ]; - if ( this.renderer.samples > 0 ) { + if ( this.renderer.currentSamples > 0 ) { - colorAttachment.view = this.colorBuffer.createView(); + colorAttachment.view = this.textureUtils.getColorBuffer().createView(); } else { @@ -344,7 +336,7 @@ class WebGPUBackend extends Backend { const colorAttachment = descriptor.colorAttachments[ 0 ]; - if ( this.renderer.samples > 0 ) { + if ( this.renderer.currentSamples > 0 ) { colorAttachment.resolveTarget = this.context.getCurrentTexture().createView(); @@ -2192,7 +2184,6 @@ class WebGPUBackend extends Backend { */ updateSize() { - this.colorBuffer = this.textureUtils.getColorBuffer(); this.defaultRenderPassdescriptor = null; } diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js index a8a6ad8a23badc..bf5962c085fd40 100644 --- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js +++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js @@ -86,21 +86,22 @@ class WebGPUTextureUtils { */ this.defaultVideoFrame = null; - /** - * Represents the color attachment of the default framebuffer. - * - * @type {?GPUTexture} - * @default null - */ - this.colorBuffer = null; - - /** - * Represents the depth attachment of the default framebuffer. - * - * @type {DepthTexture} - */ - this.depthTexture = new DepthTexture(); - this.depthTexture.name = 'depthBuffer'; + this.frameBufferData = { + color: { + buffer: null, // TODO: Move to Texture + width: 0, + height: 0, + samples: 0 + }, + depth: { + texture: new DepthTexture(), + width: 0, + height: 0, + samples: 0, + depth: false, + stencil: false + } + }; } @@ -359,24 +360,44 @@ class WebGPUTextureUtils { */ getColorBuffer() { - if ( this.colorBuffer ) this.colorBuffer.destroy(); - const backend = this.backend; const { width, height } = backend.getDrawingBufferSize(); + const samples = backend.renderer.currentSamples; + + const frameBufferColor = this.frameBufferData.color; + + if ( frameBufferColor.width === width && frameBufferColor.height === height && frameBufferColor.samples === samples ) { + + return frameBufferColor.buffer; - this.colorBuffer = backend.device.createTexture( { + } + + // recreate + + let colorBuffer = frameBufferColor.buffer; + + if ( colorBuffer ) colorBuffer.destroy(); + + colorBuffer = backend.device.createTexture( { label: 'colorBuffer', size: { width: width, height: height, depthOrArrayLayers: 1 }, - sampleCount: backend.utils.getSampleCount( backend.renderer.samples ), + sampleCount: backend.utils.getSampleCount( backend.renderer.currentSamples ), format: backend.utils.getPreferredCanvasFormat(), usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC } ); - return this.colorBuffer; + // + + frameBufferColor.buffer = colorBuffer; + frameBufferColor.width = width; + frameBufferColor.height = height; + frameBufferColor.samples = samples; + + return colorBuffer; } @@ -392,8 +413,23 @@ class WebGPUTextureUtils { const backend = this.backend; const { width, height } = backend.getDrawingBufferSize(); + const samples = backend.renderer.currentSamples; + + const frameBufferDepth = this.frameBufferData.depth; + const depthTexture = frameBufferDepth.texture; + + if ( depthTexture.width === width && + depthTexture.height === height && + depthTexture.samples === samples && + depthTexture.depth === depth && + depthTexture.stencil === stencil ) { + + return backend.get( depthTexture ).texture; + + } + + // - const depthTexture = this.depthTexture; const depthTextureGPU = backend.get( depthTexture ).texture; let format, type; @@ -422,6 +458,8 @@ class WebGPUTextureUtils { } + // recreate + depthTexture.name = 'depthBuffer'; depthTexture.format = format; depthTexture.type = type; diff --git a/src/renderers/webgpu/utils/WebGPUUtils.js b/src/renderers/webgpu/utils/WebGPUUtils.js index 7496a02b990487..8bd69432c353f4 100644 --- a/src/renderers/webgpu/utils/WebGPUUtils.js +++ b/src/renderers/webgpu/utils/WebGPUUtils.js @@ -83,7 +83,7 @@ class WebGPUUtils { const renderer = this.backend.renderer; const renderTarget = renderer.getRenderTarget(); - samples = renderTarget ? renderTarget.samples : renderer.samples; + samples = renderTarget ? renderTarget.samples : renderer.currentSamples; } else if ( texture.renderTarget ) { @@ -186,7 +186,7 @@ class WebGPUUtils { } - return this.getSampleCount( this.backend.renderer.samples ); + return this.getSampleCount( this.backend.renderer.currentSamples ); } From 6ab90e3222a082b2e29b61156839c4ed41f671cd Mon Sep 17 00:00:00 2001 From: sunag Date: Mon, 25 Aug 2025 15:41:14 -0300 Subject: [PATCH 09/13] cleanup --- src/nodes/shapes/Shapes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nodes/shapes/Shapes.js b/src/nodes/shapes/Shapes.js index 42fc1e02e24d37..777187a8f8e016 100644 --- a/src/nodes/shapes/Shapes.js +++ b/src/nodes/shapes/Shapes.js @@ -16,7 +16,7 @@ export const shapeCircle = Fn( ( [ coord = uv() ], { renderer, material } ) => { let alpha; - if ( material.alphaToCoverage && renderer.sacurrentSamplesmples > 0 ) { + if ( material.alphaToCoverage && renderer.currentSamples > 0 ) { const dlen = float( len2.fwidth() ).toVar(); From 976b9a20d2b854005d1c2c96773a9be28e693e2c Mon Sep 17 00:00:00 2001 From: sunag Date: Mon, 25 Aug 2025 15:44:04 -0300 Subject: [PATCH 10/13] cleanup --- src/renderers/common/Renderer.js | 2 +- src/renderers/webgpu/utils/WebGPUTextureUtils.js | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js index 01f471428ee5b7..f3502b935f9fbf 100644 --- a/src/renderers/common/Renderer.js +++ b/src/renderers/common/Renderer.js @@ -2166,7 +2166,7 @@ class Renderer { get currentSamples() { - let samples = this._samples; + let samples = this._samples; if ( this._renderTarget !== null ) { diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js index bf5962c085fd40..0261af15b7fe79 100644 --- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js +++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js @@ -418,11 +418,11 @@ class WebGPUTextureUtils { const frameBufferDepth = this.frameBufferData.depth; const depthTexture = frameBufferDepth.texture; - if ( depthTexture.width === width && - depthTexture.height === height && - depthTexture.samples === samples && - depthTexture.depth === depth && - depthTexture.stencil === stencil ) { + if ( depthTexture.width === width && + depthTexture.height === height && + depthTexture.samples === samples && + depthTexture.depth === depth && + depthTexture.stencil === stencil ) { return backend.get( depthTexture ).texture; From d96bac8f6ffed046ff306d5e33c6d2d192b85540 Mon Sep 17 00:00:00 2001 From: sunag Date: Mon, 25 Aug 2025 15:45:35 -0300 Subject: [PATCH 11/13] cleanup --- src/renderers/webgpu/utils/WebGPUTextureUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js index 0261af15b7fe79..0429ed9a16cf0b 100644 --- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js +++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js @@ -427,7 +427,7 @@ class WebGPUTextureUtils { return backend.get( depthTexture ).texture; } - + // const depthTextureGPU = backend.get( depthTexture ).texture; From 7eb1acb123d67713e5a5b16aa0c2ec15b2415bb9 Mon Sep 17 00:00:00 2001 From: sunag Date: Mon, 25 Aug 2025 16:11:32 -0300 Subject: [PATCH 12/13] Update PassNode.js --- src/nodes/display/PassNode.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/nodes/display/PassNode.js b/src/nodes/display/PassNode.js index dde38c3b695a18..8e4766dd0e577f 100644 --- a/src/nodes/display/PassNode.js +++ b/src/nodes/display/PassNode.js @@ -663,7 +663,7 @@ class PassNode extends TempNode { setup( { renderer } ) { - this.renderTarget.samples = this.options.samples === undefined ? renderer.currentSamples : this.options.samples; + this.renderTarget.samples = this.options.samples === undefined ? renderer.samples : this.options.samples; this.renderTarget.texture.type = renderer.getColorBufferType(); From 34404469bf6621f4261bc58cd8a0515acc4d3aa5 Mon Sep 17 00:00:00 2001 From: sunag Date: Sun, 31 Aug 2025 00:10:43 -0300 Subject: [PATCH 13/13] fix canvas texture check --- src/renderers/webgpu/utils/WebGPUTextureUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js index a33c6e0f6e22c9..14f600a198ebde 100644 --- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js +++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js @@ -217,7 +217,7 @@ class WebGPUTextureUtils { } - if ( texture.renderTarget && texture.renderTarget.isCanvasRenderTarget ) { + if ( texture.renderTarget && texture.renderTarget.isCanvasRenderTarget === true && texture.isDepthTexture !== true ) { texture.internalFormat = this.backend.utils.getPreferredCanvasFormat();