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 + + + + + + + +
+
three.js - 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',