diff --git a/examples/files.json b/examples/files.json
index ca5dc08cf753b7..1f571ae8bd4688 100644
--- a/examples/files.json
+++ b/examples/files.json
@@ -384,6 +384,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 00000000000000..483b3073a95b7c
Binary files /dev/null and b/examples/screenshots/webgpu_multiple_canvas.jpg differ
diff --git a/examples/webgpu_multiple_canvas.html b/examples/webgpu_multiple_canvas.html
new file mode 100644
index 00000000000000..eb34fa165e00e3
--- /dev/null
+++ b/examples/webgpu_multiple_canvas.html
@@ -0,0 +1,199 @@
+
+
+
+ three.js webgpu - multiple canvas
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Three.WebGPU.Nodes.js b/src/Three.WebGPU.Nodes.js
index 6a9d0983296cd2..28e9bd37b53bba 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 { default as InspectorBase } from './renderers/common/InspectorBase.js';
+export { default as CanvasTarget } from './renderers/common/CanvasTarget.js';
export { ClippingGroup } from './objects/ClippingGroup.js';
export * from './nodes/Nodes.js';
import * as TSL from './nodes/TSL.js';
diff --git a/src/Three.WebGPU.js b/src/Three.WebGPU.js
index 59b436436206f9..25a6f220ffa129 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 { default as InspectorBase } from './renderers/common/InspectorBase.js';
+export { default as CanvasTarget } from './renderers/common/CanvasTarget.js';
export { ClippingGroup } from './objects/ClippingGroup.js';
export * from './nodes/Nodes.js';
import * as TSL from './nodes/TSL.js';
diff --git a/src/renderers/common/CanvasTarget.js b/src/renderers/common/CanvasTarget.js
new file mode 100644
index 00000000000000..6e6c1281b723b0
--- /dev/null
+++ b/src/renderers/common/CanvasTarget.js
@@ -0,0 +1,377 @@
+import { EventDispatcher } from '../../core/EventDispatcher.js';
+import { Vector4 } from '../../math/Vector4.js';
+import { FramebufferTexture } from '../../textures/FramebufferTexture.js';
+import { DepthTexture } from '../../textures/DepthTexture.js';
+
+/**
+ * CanvasTarget is a class that represents the final output destination of the renderer.
+ *
+ * @augments EventDispatcher
+ */
+class CanvasTarget extends EventDispatcher {
+
+ /**
+ * CanvasTarget options.
+ *
+ * @typedef {Object} CanvasTarget~Options
+ * @property {boolean} [antialias=false] - Whether MSAA as the default anti-aliasing should be enabled or not.
+ * @property {number} [samples=0] - When `antialias` is `true`, `4` samples are used by default. This parameter can set to any other integer value than 0
+ * to overwrite the default.
+ */
+
+ /**
+ * Constructs a new CanvasTarget.
+ *
+ * @param {HTMLCanvasElement|OffscreenCanvas} domElement - The canvas element to render to.
+ * @param {Object} [parameters={}] - The parameters.
+ */
+ constructor( domElement, parameters = {} ) {
+
+ super();
+
+ const {
+ antialias = false,
+ samples = 0
+ } = parameters;
+
+ /**
+ * A reference to the canvas element the renderer is drawing to.
+ * This value of this property will automatically be created by
+ * the renderer.
+ *
+ * @type {HTMLCanvasElement|OffscreenCanvas}
+ */
+ this.domElement = domElement;
+
+ /**
+ * The renderer's pixel ratio.
+ *
+ * @private
+ * @type {number}
+ * @default 1
+ */
+ this._pixelRatio = 1;
+
+ /**
+ * The width of the renderer's default framebuffer in logical pixel unit.
+ *
+ * @private
+ * @type {number}
+ */
+ this._width = this.domElement.width;
+
+ /**
+ * The height of the renderer's default framebuffer in logical pixel unit.
+ *
+ * @private
+ * @type {number}
+ */
+ this._height = this.domElement.height;
+
+ /**
+ * The viewport of the renderer in logical pixel unit.
+ *
+ * @private
+ * @type {Vector4}
+ */
+ this._viewport = new Vector4( 0, 0, this._width, this._height );
+
+ /**
+ * The scissor rectangle of the renderer in logical pixel unit.
+ *
+ * @private
+ * @type {Vector4}
+ */
+ this._scissor = new Vector4( 0, 0, this._width, this._height );
+
+ /**
+ * Whether the scissor test should be enabled or not.
+ *
+ * @private
+ * @type {boolean}
+ */
+ this._scissorTest = false;
+
+ /**
+ * The number of MSAA samples.
+ *
+ * @private
+ * @type {number}
+ * @default 0
+ */
+ this._samples = samples || ( antialias === true ) ? 4 : 0;
+
+ /**
+ * The color texture of the default framebuffer.
+ *
+ * @type {FramebufferTexture}
+ */
+ this.colorTexture = new FramebufferTexture();
+
+ /**
+ * The depth texture of the default framebuffer.
+ *
+ * @type {DepthTexture}
+ */
+ this.depthTexture = new DepthTexture();
+
+ }
+
+ /**
+ * The number of samples used for multi-sample anti-aliasing (MSAA).
+ *
+ * @type {number}
+ * @default 0
+ */
+ get samples() {
+
+ return this._samples;
+
+ }
+
+ /**
+ * Returns the pixel ratio.
+ *
+ * @return {number} The pixel ratio.
+ */
+ getPixelRatio() {
+
+ return this._pixelRatio;
+
+ }
+
+ /**
+ * Returns the drawing buffer size in physical pixels. This method honors the pixel ratio.
+ *
+ * @param {Vector2} target - The method writes the result in this target object.
+ * @return {Vector2} The drawing buffer size.
+ */
+ getDrawingBufferSize( target ) {
+
+ return target.set( this._width * this._pixelRatio, this._height * this._pixelRatio ).floor();
+
+ }
+
+ /**
+ * Returns the renderer's size in logical pixels. This method does not honor the pixel ratio.
+ *
+ * @param {Vector2} target - The method writes the result in this target object.
+ * @return {Vector2} The renderer's size in logical pixels.
+ */
+ getSize( target ) {
+
+ return target.set( this._width, this._height );
+
+ }
+
+ /**
+ * Sets the given pixel ratio and resizes the canvas if necessary.
+ *
+ * @param {number} [value=1] - The pixel ratio.
+ */
+ setPixelRatio( value = 1 ) {
+
+ if ( this._pixelRatio === value ) return;
+
+ this._pixelRatio = value;
+
+ this.setSize( this._width, this._height, false );
+
+ }
+
+ /**
+ * This method allows to define the drawing buffer size by specifying
+ * width, height and pixel ratio all at once. The size of the drawing
+ * buffer is computed with this formula:
+ * ```js
+ * size.x = width * pixelRatio;
+ * size.y = height * pixelRatio;
+ * ```
+ *
+ * @param {number} width - The width in logical pixels.
+ * @param {number} height - The height in logical pixels.
+ * @param {number} pixelRatio - The pixel ratio.
+ */
+ setDrawingBufferSize( width, height, pixelRatio ) {
+
+ // Renderer can't be resized while presenting in XR.
+ if ( this.xr && this.xr.isPresenting ) return;
+
+ this._width = width;
+ this._height = height;
+
+ this._pixelRatio = pixelRatio;
+
+ this.domElement.width = Math.floor( width * pixelRatio );
+ this.domElement.height = Math.floor( height * pixelRatio );
+
+ this.setViewport( 0, 0, width, height );
+
+ this._dispatchResize();
+
+ }
+
+ /**
+ * Sets the size of the renderer.
+ *
+ * @param {number} width - The width in logical pixels.
+ * @param {number} height - The height in logical pixels.
+ * @param {boolean} [updateStyle=true] - Whether to update the `style` attribute of the canvas or not.
+ */
+ setSize( width, height, updateStyle = true ) {
+
+ // Renderer can't be resized while presenting in XR.
+ if ( this.xr && this.xr.isPresenting ) return;
+
+ this._width = width;
+ this._height = height;
+
+ this.domElement.width = Math.floor( width * this._pixelRatio );
+ this.domElement.height = Math.floor( height * this._pixelRatio );
+
+ if ( updateStyle === true ) {
+
+ this.domElement.style.width = width + 'px';
+ this.domElement.style.height = height + 'px';
+
+ }
+
+ this.setViewport( 0, 0, width, height );
+
+ this._dispatchResize();
+
+ }
+
+ /**
+ * Returns the scissor rectangle.
+ *
+ * @param {Vector4} target - The method writes the result in this target object.
+ * @return {Vector4} The scissor rectangle.
+ */
+ getScissor( target ) {
+
+ const scissor = this._scissor;
+
+ target.x = scissor.x;
+ target.y = scissor.y;
+ target.width = scissor.width;
+ target.height = scissor.height;
+
+ return target;
+
+ }
+
+ /**
+ * Defines the scissor rectangle.
+ *
+ * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the box in logical pixel unit.
+ * Instead of passing four arguments, the method also works with a single four-dimensional vector.
+ * @param {number} y - The vertical coordinate for the lower left corner of the box in logical pixel unit.
+ * @param {number} width - The width of the scissor box in logical pixel unit.
+ * @param {number} height - The height of the scissor box in logical pixel unit.
+ */
+ setScissor( x, y, width, height ) {
+
+ const scissor = this._scissor;
+
+ if ( x.isVector4 ) {
+
+ scissor.copy( x );
+
+ } else {
+
+ scissor.set( x, y, width, height );
+
+ }
+
+ }
+
+ /**
+ * Returns the scissor test value.
+ *
+ * @return {boolean} Whether the scissor test should be enabled or not.
+ */
+ getScissorTest() {
+
+ return this._scissorTest;
+
+ }
+
+ /**
+ * Defines the scissor test.
+ *
+ * @param {boolean} boolean - Whether the scissor test should be enabled or not.
+ */
+ setScissorTest( boolean ) {
+
+ this._scissorTest = boolean;
+
+ }
+
+ /**
+ * Returns the viewport definition.
+ *
+ * @param {Vector4} target - The method writes the result in this target object.
+ * @return {Vector4} The viewport definition.
+ */
+ getViewport( target ) {
+
+ return target.copy( this._viewport );
+
+ }
+
+ /**
+ * Defines the viewport.
+ *
+ * @param {number | Vector4} x - The horizontal coordinate for the lower left corner of the viewport origin in logical pixel unit.
+ * @param {number} y - The vertical coordinate for the lower left corner of the viewport origin in logical pixel unit.
+ * @param {number} width - The width of the viewport in logical pixel unit.
+ * @param {number} height - The height of the viewport in logical pixel unit.
+ * @param {number} minDepth - The minimum depth value of the viewport. WebGPU only.
+ * @param {number} maxDepth - The maximum depth value of the viewport. WebGPU only.
+ */
+ setViewport( x, y, width, height, minDepth = 0, maxDepth = 1 ) {
+
+ const viewport = this._viewport;
+
+ if ( x.isVector4 ) {
+
+ viewport.copy( x );
+
+ } else {
+
+ viewport.set( x, y, width, height );
+
+ }
+
+ viewport.minDepth = minDepth;
+ viewport.maxDepth = maxDepth;
+
+ }
+
+ /**
+ * Dispatches the resize event.
+ *
+ * @private
+ */
+ _dispatchResize() {
+
+ this.dispatchEvent( { type: 'resize' } );
+
+ }
+
+ /**
+ * Frees the GPU-related resources allocated by this instance. Call this
+ * method whenever this instance is no longer used in your app.
+ *
+ * @fires RenderTarget#dispose
+ */
+ dispose() {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ }
+
+}
+
+export default CanvasTarget;
diff --git a/src/renderers/common/Renderer.js b/src/renderers/common/Renderer.js
index b003d26508bb73..8b56b5e7db46f9 100644
--- a/src/renderers/common/Renderer.js
+++ b/src/renderers/common/Renderer.js
@@ -18,6 +18,7 @@ import NodeLibrary from './nodes/NodeLibrary.js';
import Lighting from './Lighting.js';
import XRManager from './XRManager.js';
import InspectorBase from './InspectorBase.js';
+import CanvasTarget from './CanvasTarget.js';
import NodeMaterial from '../../materials/nodes/NodeMaterial.js';
@@ -99,15 +100,6 @@ class Renderer {
multiview = false
} = parameters;
- /**
- * A reference to the canvas element the renderer is drawing to.
- * This value of this property will automatically be created by
- * the renderer.
- *
- * @type {HTMLCanvasElement|OffscreenCanvas}
- */
- this.domElement = backend.getDomElement();
-
/**
* A reference to the current backend.
*
@@ -115,15 +107,6 @@ class Renderer {
*/
this.backend = backend;
- /**
- * The number of MSAA samples.
- *
- * @private
- * @type {number}
- * @default 0
- */
- this._samples = samples || ( antialias === true ) ? 4 : 0;
-
/**
* Whether the renderer should automatically clear the current rendering target
* before execute a `render()` call. The target can be the canvas (default framebuffer)
@@ -271,65 +254,40 @@ class Renderer {
// internals
- this._inspector = new InspectorBase();
- this._inspector.setRenderer( this );
-
- /**
- * This callback function can be used to provide a fallback backend, if the primary backend can't be targeted.
- *
- * @private
- * @type {?Function}
- */
- this._getFallback = getFallback;
-
- /**
- * The renderer's pixel ratio.
- *
- * @private
- * @type {number}
- * @default 1
- */
- this._pixelRatio = 1;
-
- /**
- * The width of the renderer's default framebuffer in logical pixel unit.
- *
- * @private
- * @type {number}
- */
- this._width = this.domElement.width;
-
/**
- * The height of the renderer's default framebuffer in logical pixel unit.
+ * OnCanvasTargetResize callback function.
*
* @private
- * @type {number}
+ * @type {Function}
*/
- this._height = this.domElement.height;
+ this._onCanvasTargetResize = this._onCanvasTargetResize.bind( this );
/**
- * The viewport of the renderer in logical pixel unit.
+ * The canvas target for rendering.
*
* @private
- * @type {Vector4}
+ * @type {CanvasTarget}
*/
- this._viewport = new Vector4( 0, 0, this._width, this._height );
+ this._canvasTarget = new CanvasTarget( backend.getDomElement(), { antialias, samples } );
+ this._canvasTarget.addEventListener( 'resize', this._onCanvasTargetResize );
+ this._canvasTarget.isDefaultCanvasTarget = true;
/**
- * The scissor rectangle of the renderer in logical pixel unit.
+ * The inspector provides information about the internal renderer state.
*
* @private
- * @type {Vector4}
+ * @type {InspectorBase}
*/
- this._scissor = new Vector4( 0, 0, this._width, this._height );
+ this._inspector = new InspectorBase();
+ this._inspector.setRenderer( this );
/**
- * Whether the scissor test should be enabled or not.
+ * This callback function can be used to provide a fallback backend, if the primary backend can't be targeted.
*
* @private
- * @type {boolean}
+ * @type {?Function}
*/
- this._scissorTest = false;
+ this._getFallback = getFallback;
/**
* A reference to a renderer module for managing shader attributes.
@@ -834,6 +792,19 @@ class Renderer {
}
+ /**
+ * A reference to the canvas element the renderer is drawing to.
+ * This value of this property will automatically be created by
+ * the renderer.
+ *
+ * @type {HTMLCanvasElement|OffscreenCanvas}
+ */
+ get domElement() {
+
+ return this._canvasTarget.domElement;
+
+ }
+
/**
* The coordinate system of the renderer. The value of this property
* depends on the selected backend. Either `THREE.WebGLCoordinateSystem` or
@@ -1315,11 +1286,13 @@ class Renderer {
}
- frameBufferTarget.viewport.copy( this._viewport );
- frameBufferTarget.scissor.copy( this._scissor );
- frameBufferTarget.viewport.multiplyScalar( this._pixelRatio );
- frameBufferTarget.scissor.multiplyScalar( this._pixelRatio );
- frameBufferTarget.scissorTest = this._scissorTest;
+ const canvasTarget = this._canvasTarget;
+
+ frameBufferTarget.viewport.copy( canvasTarget._viewport );
+ frameBufferTarget.scissor.copy( canvasTarget._scissor );
+ frameBufferTarget.viewport.multiplyScalar( canvasTarget._pixelRatio );
+ frameBufferTarget.scissor.multiplyScalar( canvasTarget._pixelRatio );
+ frameBufferTarget.scissorTest = canvasTarget._scissorTest;
frameBufferTarget.multiview = outputRenderTarget !== null ? outputRenderTarget.multiview : false;
frameBufferTarget.resolveDepthBuffer = outputRenderTarget !== null ? outputRenderTarget.resolveDepthBuffer : true;
frameBufferTarget._autoAllocateDepthBuffer = outputRenderTarget !== null ? outputRenderTarget._autoAllocateDepthBuffer : false;
@@ -1437,9 +1410,11 @@ class Renderer {
//
- let viewport = this._viewport;
- let scissor = this._scissor;
- let pixelRatio = this._pixelRatio;
+ const canvasTarget = this._canvasTarget;
+
+ let viewport = canvasTarget._viewport;
+ let scissor = canvasTarget._scissor;
+ let pixelRatio = canvasTarget._pixelRatio;
if ( renderTarget !== null ) {
@@ -1464,7 +1439,7 @@ class Renderer {
renderContext.viewport = renderContext.viewportValue.equals( _screen ) === false;
renderContext.scissorValue.copy( scissor ).multiplyScalar( pixelRatio ).floor();
- renderContext.scissor = this._scissorTest && renderContext.scissorValue.equals( _screen ) === false;
+ renderContext.scissor = canvasTarget._scissorTest && renderContext.scissorValue.equals( _screen ) === false;
renderContext.scissorValue.width >>= activeMipmapLevel;
renderContext.scissorValue.height >>= activeMipmapLevel;
@@ -1608,8 +1583,10 @@ class Renderer {
_setXRLayerSize( width, height ) {
- this._width = width;
- this._height = height;
+ // TODO: Find a better solution to resize the canvas when in XR.
+
+ this._canvasTarget._width = width;
+ this._canvasTarget._height = height;
this.setViewport( 0, 0, width, height );
@@ -1741,7 +1718,7 @@ class Renderer {
*/
getPixelRatio() {
- return this._pixelRatio;
+ return this._canvasTarget.getPixelRatio();
}
@@ -1753,7 +1730,7 @@ class Renderer {
*/
getDrawingBufferSize( target ) {
- return target.set( this._width * this._pixelRatio, this._height * this._pixelRatio ).floor();
+ return this._canvasTarget.getDrawingBufferSize( target );
}
@@ -1765,7 +1742,7 @@ class Renderer {
*/
getSize( target ) {
- return target.set( this._width, this._height );
+ return this._canvasTarget.getSize( target );
}
@@ -1776,11 +1753,7 @@ class Renderer {
*/
setPixelRatio( value = 1 ) {
- if ( this._pixelRatio === value ) return;
-
- this._pixelRatio = value;
-
- this.setSize( this._width, this._height, false );
+ this._canvasTarget.setPixelRatio( value );
}
@@ -1802,17 +1775,7 @@ class Renderer {
// Renderer can't be resized while presenting in XR.
if ( this.xr && this.xr.isPresenting ) return;
- this._width = width;
- this._height = height;
-
- this._pixelRatio = pixelRatio;
-
- this.domElement.width = Math.floor( width * pixelRatio );
- this.domElement.height = Math.floor( height * pixelRatio );
-
- this.setViewport( 0, 0, width, height );
-
- if ( this._initialized ) this.backend.updateSize();
+ this._canvasTarget.setDrawingBufferSize( width, height, pixelRatio );
}
@@ -1828,22 +1791,7 @@ class Renderer {
// Renderer can't be resized while presenting in XR.
if ( this.xr && this.xr.isPresenting ) return;
- this._width = width;
- this._height = height;
-
- this.domElement.width = Math.floor( width * this._pixelRatio );
- this.domElement.height = Math.floor( height * this._pixelRatio );
-
- if ( updateStyle === true ) {
-
- this.domElement.style.width = width + 'px';
- this.domElement.style.height = height + 'px';
-
- }
-
- this.setViewport( 0, 0, width, height );
-
- if ( this._initialized ) this.backend.updateSize();
+ this._canvasTarget.setSize( width, height, updateStyle );
}
@@ -1879,14 +1827,7 @@ class Renderer {
*/
getScissor( target ) {
- const scissor = this._scissor;
-
- target.x = scissor.x;
- target.y = scissor.y;
- target.width = scissor.width;
- target.height = scissor.height;
-
- return target;
+ return this._canvasTarget.getScissor( target );
}
@@ -1901,17 +1842,7 @@ class Renderer {
*/
setScissor( x, y, width, height ) {
- const scissor = this._scissor;
-
- if ( x.isVector4 ) {
-
- scissor.copy( x );
-
- } else {
-
- scissor.set( x, y, width, height );
-
- }
+ this._canvasTarget.setScissor( x, y, width, height );
}
@@ -1922,7 +1853,7 @@ class Renderer {
*/
getScissorTest() {
- return this._scissorTest;
+ return this._canvasTarget.getScissorTest();
}
@@ -1933,7 +1864,9 @@ class Renderer {
*/
setScissorTest( boolean ) {
- this._scissorTest = boolean;
+ this._canvasTarget.setScissorTest( boolean );
+
+ // TODO: Move it to CanvasTarget event listener.
this.backend.setScissorTest( boolean );
@@ -1947,7 +1880,7 @@ class Renderer {
*/
getViewport( target ) {
- return target.copy( this._viewport );
+ return this._canvasTarget.getViewport( target );
}
@@ -1963,20 +1896,7 @@ class Renderer {
*/
setViewport( x, y, width, height, minDepth = 0, maxDepth = 1 ) {
- const viewport = this._viewport;
-
- if ( x.isVector4 ) {
-
- viewport.copy( x );
-
- } else {
-
- viewport.set( x, y, width, height );
-
- }
-
- viewport.minDepth = minDepth;
- viewport.maxDepth = maxDepth;
+ this._canvasTarget.setViewport( x, y, width, height, minDepth, maxDepth );
}
@@ -2252,7 +2172,7 @@ class Renderer {
*/
get samples() {
- return this._samples;
+ return this._canvasTarget.samples;
}
@@ -2267,7 +2187,7 @@ class Renderer {
*/
get currentSamples() {
- let samples = this._samples;
+ let samples = this.samples;
if ( this._renderTarget !== null ) {
@@ -2404,6 +2324,32 @@ class Renderer {
}
+ /**
+ * Sets the canvas target. The canvas target manages the HTML canvas
+ * or the offscreen canvas the renderer draws into.
+ *
+ * @param {CanvasTarget} canvasTarget - The canvas target.
+ */
+ setCanvasTarget( canvasTarget ) {
+
+ this._canvasTarget.removeEventListener( 'resize', this._onCanvasTargetResize );
+
+ this._canvasTarget = canvasTarget;
+ this._canvasTarget.addEventListener( 'resize', this._onCanvasTargetResize );
+
+ }
+
+ /**
+ * Returns the current canvas target.
+ *
+ * @return {CanvasTarget} The current canvas target.
+ */
+ getCanvasTarget() {
+
+ return this._canvasTarget;
+
+ }
+
/**
* Resets the renderer to the initial state before WebXR started.
*
@@ -3288,6 +3234,17 @@ class Renderer {
}
+ /**
+ * Callback when the canvas has been resized.
+ *
+ * @private
+ */
+ _onCanvasTargetResize() {
+
+ if ( this._initialized ) this.backend.updateSize();
+
+ }
+
/**
* Alias for `compileAsync()`.
*
diff --git a/src/renderers/webgpu/WebGPUBackend.js b/src/renderers/webgpu/WebGPUBackend.js
index 086238f42a1b4f..8dd6b0723dfdf6 100644
--- a/src/renderers/webgpu/WebGPUBackend.js
+++ b/src/renderers/webgpu/WebGPUBackend.js
@@ -13,7 +13,7 @@ import WebGPUBindingUtils from './utils/WebGPUBindingUtils.js';
import WebGPUPipelineUtils from './utils/WebGPUPipelineUtils.js';
import WebGPUTextureUtils from './utils/WebGPUTextureUtils.js';
-import { WebGPUCoordinateSystem, TimestampQuery } from '../../constants.js';
+import { WebGPUCoordinateSystem, TimestampQuery, REVISION } from '../../constants.js';
import WebGPUTimestampQueryPool from './utils/WebGPUTimestampQueryPool.js';
import { warnOnce, error } from '../../utils.js';
import { ColorManagement } from '../../math/ColorManagement.js';
@@ -84,14 +84,6 @@ class WebGPUBackend extends Backend {
*/
this.device = null;
- /**
- * A reference to the context.
- *
- * @type {?GPUCanvasContext}
- * @default null
- */
- this.context = null;
-
/**
* A reference to the default render pass descriptor.
*
@@ -224,28 +216,63 @@ class WebGPUBackend extends Backend {
} );
- const context = ( parameters.context !== undefined ) ? parameters.context : renderer.domElement.getContext( 'webgpu' );
-
this.device = device;
- this.context = context;
- const alphaMode = parameters.alpha ? 'premultiplied' : 'opaque';
+ this.trackTimestamp = this.trackTimestamp && this.hasFeature( GPUFeatureName.TimestampQuery );
+
+ this.updateSize();
+
+ }
+
+ /**
+ * A reference to the context.
+ *
+ * @type {?GPUCanvasContext}
+ * @default null
+ */
+ get context() {
+
+ const canvasTarget = this.renderer.getCanvasTarget();
+ const canvasData = this.get( canvasTarget );
+
+ let context = canvasData.context;
+
+ if ( context === undefined ) {
- const toneMappingMode = ColorManagement.getToneMappingMode( this.renderer.outputColorSpace );
+ const parameters = this.parameters;
+
+ if ( canvasTarget.isDefaultCanvasTarget === true && parameters.context !== undefined ) {
+
+ context = parameters.context;
+
+ } else {
+
+ context = canvasTarget.domElement.getContext( 'webgpu' );
- this.context.configure( {
- device: this.device,
- format: this.utils.getPreferredCanvasFormat(),
- usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
- alphaMode: alphaMode,
- toneMapping: {
- mode: toneMappingMode
}
- } );
- this.trackTimestamp = this.trackTimestamp && this.hasFeature( GPUFeatureName.TimestampQuery );
+ // OffscreenCanvas does not have setAttribute, see #22811
+ if ( 'setAttribute' in canvasTarget.domElement ) canvasTarget.domElement.setAttribute( 'data-engine', `three.js r${ REVISION } webgpu` );
- this.updateSize();
+ const alphaMode = parameters.alpha ? 'premultiplied' : 'opaque';
+
+ const toneMappingMode = ColorManagement.getToneMappingMode( this.renderer.outputColorSpace );
+
+ context.configure( {
+ device: this.device,
+ format: this.utils.getPreferredCanvasFormat(),
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
+ alphaMode: alphaMode,
+ toneMapping: {
+ mode: toneMappingMode
+ }
+ } );
+
+ canvasData.context = context;
+
+ }
+
+ return context;
}
@@ -298,11 +325,13 @@ class WebGPUBackend extends Backend {
*/
_getDefaultRenderPassDescriptor() {
- let descriptor = this.defaultRenderPassdescriptor;
+ const renderer = this.renderer;
+ const canvasTarget = renderer.getCanvasTarget();
+ const canvasData = this.get( canvasTarget );
- if ( descriptor === null ) {
+ let descriptor = canvasData.descriptor;
- const renderer = this.renderer;
+ if ( descriptor === undefined ) {
descriptor = {
colorAttachments: [ {
@@ -310,7 +339,7 @@ class WebGPUBackend extends Backend {
} ],
};
- if ( this.renderer.depth === true || this.renderer.stencil === true ) {
+ if ( renderer.depth === true || renderer.stencil === true ) {
descriptor.depthStencilAttachment = {
view: this.textureUtils.getDepthBuffer( renderer.depth, renderer.stencil ).createView()
@@ -320,7 +349,7 @@ class WebGPUBackend extends Backend {
const colorAttachment = descriptor.colorAttachments[ 0 ];
- if ( this.renderer.currentSamples > 0 ) {
+ if ( renderer.currentSamples > 0 ) {
colorAttachment.view = this.textureUtils.getColorBuffer().createView();
@@ -330,13 +359,13 @@ class WebGPUBackend extends Backend {
}
- this.defaultRenderPassdescriptor = descriptor;
+ canvasData.descriptor = descriptor;
}
const colorAttachment = descriptor.colorAttachments[ 0 ];
- if ( this.renderer.currentSamples > 0 ) {
+ if ( renderer.currentSamples > 0 ) {
colorAttachment.resolveTarget = this.context.getCurrentTexture().createView();
@@ -2185,7 +2214,7 @@ class WebGPUBackend extends Backend {
*/
updateSize() {
- this.defaultRenderPassdescriptor = null;
+ this.delete( this.renderer.getCanvasTarget() );
}
diff --git a/src/renderers/webgpu/utils/WebGPUTextureUtils.js b/src/renderers/webgpu/utils/WebGPUTextureUtils.js
index 002c7f70b900b0..5c0aa5e82c688f 100644
--- a/src/renderers/webgpu/utils/WebGPUTextureUtils.js
+++ b/src/renderers/webgpu/utils/WebGPUTextureUtils.js
@@ -17,7 +17,6 @@ import {
UnsignedInt101111Type, RGBA_BPTC_Format, RGB_ETC1_Format, RGB_S3TC_DXT1_Format, RED_RGTC1_Format, SIGNED_RED_RGTC1_Format, RED_GREEN_RGTC2_Format, SIGNED_RED_GREEN_RGTC2_Format
} from '../../../constants.js';
import { CubeTexture } from '../../../textures/CubeTexture.js';
-import { DepthTexture } from '../../../textures/DepthTexture.js';
import { Texture } from '../../../textures/Texture.js';
import { warn, error } from '../../../utils.js';
@@ -87,23 +86,6 @@ class WebGPUTextureUtils {
*/
this.defaultVideoFrame = null;
- this.frameBufferData = {
- color: {
- buffer: null, // TODO: Move to FramebufferTexture
- width: 0,
- height: 0,
- samples: 0
- },
- depth: {
- texture: new DepthTexture(),
- width: 0,
- height: 0,
- samples: 0,
- depth: false,
- stencil: false
- }
- };
-
/**
* A cache of shared texture samplers.
*
@@ -398,20 +380,22 @@ class WebGPUTextureUtils {
getColorBuffer() {
const backend = this.backend;
+ const canvasTarget = backend.renderer.getCanvasTarget();
const { width, height } = backend.getDrawingBufferSize();
const samples = backend.renderer.currentSamples;
- const frameBufferColor = this.frameBufferData.color;
+ const colorTexture = canvasTarget.colorTexture;
+ const colorTextureData = backend.get( colorTexture );
- if ( frameBufferColor.width === width && frameBufferColor.height === height && frameBufferColor.samples === samples ) {
+ if ( colorTexture.width === width && colorTexture.height === height && colorTexture.samples === samples ) {
- return frameBufferColor.buffer;
+ return colorTextureData.texture;
}
// recreate
- let colorBuffer = frameBufferColor.buffer;
+ let colorBuffer = colorTextureData.texture;
if ( colorBuffer ) colorBuffer.destroy();
@@ -429,10 +413,11 @@ class WebGPUTextureUtils {
//
- frameBufferColor.buffer = colorBuffer;
- frameBufferColor.width = width;
- frameBufferColor.height = height;
- frameBufferColor.samples = samples;
+ colorTexture.source.width = width;
+ colorTexture.source.height = height;
+ colorTexture.samples = samples;
+
+ colorTextureData.texture = colorBuffer;
return colorBuffer;
@@ -449,11 +434,11 @@ class WebGPUTextureUtils {
getDepthBuffer( depth = true, stencil = false ) {
const backend = this.backend;
+ const canvasTarget = backend.renderer.getCanvasTarget();
const { width, height } = backend.getDrawingBufferSize();
const samples = backend.renderer.currentSamples;
- const frameBufferDepth = this.frameBufferData.depth;
- const depthTexture = frameBufferDepth.texture;
+ const depthTexture = canvasTarget.depthTexture;
if ( depthTexture.width === width &&
depthTexture.height === height &&
diff --git a/test/e2e/puppeteer.js b/test/e2e/puppeteer.js
index ffda3042b9b369..0c29e29481f6b6 100644
--- a/test/e2e/puppeteer.js
+++ b/test/e2e/puppeteer.js
@@ -132,6 +132,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',