Skip to content

Framebuffer caching not working for ViewportDepthTextureNode and ViewporTextureNode when rendering to multiple canvases of different size #32689

@arodic

Description

@arodic

Description

This is related to issues previously discussed in #32642

ViewportTextureNode and ViewportDepthTextureNode have buffer caching mechanism that is supposed to reuse texture buffers per reference. However, when rendering to multiple targets of different size, the cached buffers are brute-force destroyed and re-created per each target and frame.

jsfiddle example shows that rendering two CanvasTargets of different size cause 6 texture destructions and creations per frame. This shows that caching mechanism is not actually working or is designed incorrectly.

  • ViewportDepthTextureNode: Uses a singleton _sharedDepthbuffer that is shared across all RenderTargets/CanvasTargets. This causes brute-force destruction and re-creation of depth buffer per reference on every rendered frame if references are not exactly the same size. This affects both CanvasTarget and RenderTarget

  • ViewporTextureNode Does not consider CanvasTarget as a reference. It simply does not do caching for CanvasTargets

  • Renderer: Uses a single _frameBufferTarget that resizes for each canvas (if not exactly the same size), triggering
    depthTexture.needsUpdate = true and causing texture recreation

cc @Mugen87

Reproduction steps

  1. Open js fiddle and examine js code
  2. Notice that each frame 6 buffer textures are destroyed and created.

Code

import * as THREE from 'three/webgpu';
			import { linearDepth, viewportLinearDepth, vec3 } from 'three/tsl';

			import WebGPU from 'three/addons/capabilities/WebGPU.js';

			if ( WebGPU.isAvailable() === false ) {

				document.body.appendChild( WebGPU.getErrorMessage() );
				throw new Error( 'No WebGPU support' );

			}

			let renderer;
			const views = [];

      let createTextureCount = 0;
      let createTexturePerFrameCount = 0;

			function createView( canvasId, rotationSpeed, width, height ) {

				const canvas = document.getElementById( canvasId );
				const canvasTarget = new THREE.CanvasTarget( canvas );
				canvasTarget.setPixelRatio( window.devicePixelRatio );
				canvasTarget.setSize( width, height );

				const scene = new THREE.Scene();

				const camera = new THREE.PerspectiveCamera( 50, 1, 0.1, 10 );
				camera.position.set( 0, 4, 0 );
				camera.lookAt( 0, 0, 0 );

				const depthDistance = viewportLinearDepth.distance( linearDepth() );

				const sphere = new THREE.Mesh(
					new THREE.SphereGeometry( 0.75, 32, 32 ),
					new THREE.MeshBasicNodeMaterial( {
						colorNode: vec3( depthDistance ),
						side: THREE.DoubleSide
					} )
				);
				sphere.renderOrder = 1;
				scene.add( sphere );

				const bgKnot = new THREE.Mesh(
					new THREE.TorusKnotGeometry( 1, 0.4, 50, 20 ),
					new THREE.MeshBasicNodeMaterial()
				);
				bgKnot.rotation.x = - Math.PI / 2;
				bgKnot.position.y = - 2.8;
				scene.add( bgKnot );

				return {
					canvasTarget,
					scene,
					camera,
					sphere,
					rotationSpeed,
					bgKnot,
				};

			}

			function init() {

				renderer = new THREE.WebGPURenderer( { antialias: true } );
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setAnimationLoop( animate );

        renderer.init().then( () => {

          const backend = renderer.backend;
          const originalCreateTexture = backend.createTexture.bind( backend );

          backend.createTexture = function( texture, options ) {
              createTextureCount++;
              document.getElementById('createTexture').textContent = createTextureCount;
              createTexturePerFrameCount++;
              return originalCreateTexture( texture, options );
          };

        } );

				views.push( createView( 'canvas1', 0.2, 360, 360 ) );
				views.push( createView( 'canvas2', 1.8, 240, 240 ) );

			}

			function animate() {

				const time = performance.now() * 0.001;

				for ( const view of views ) {
					view.bgKnot.rotation.x = time * view.rotationSpeed;
					view.bgKnot.rotation.y = time * view.rotationSpeed * 0.7;
					renderer.setCanvasTarget( view.canvasTarget );
					renderer.render( view.scene, view.camera );
				}

        document.getElementById('createTexturePerFrame').textContent = createTexturePerFrameCount;
        createTexturePerFrameCount = 0;

			}

			init();

Live example

Screenshots

Image

Version

dev

Device

No response

Browser

No response

OS

No response

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions