Skip to content

WebGPURenderer: WebGL buffer for UBO update isn't reused. #31679

@mdevecka

Description

@mdevecka

Description

For some reason WebGL fallback renderer massively allocates webgl buffers each frame (1000+/sec) and never frees them when using bloom postprocessing.
Eventually this leads to WebGL context crash.
There does not seem to be any indication of leakage inside BloomNode.js as everything looks to be cached.
Measurement is done by trapping the createBuffer / deleteBuffer method usage on WebGL2RenderingContext.prototype.

Reproduction steps

See code.
Also reproducible by changing the renderer to fallback in:
https://threejs.org/examples/?q=bloom#webgpu_postprocessing_bloom

Code

import { vec4, vec3, pass } from 'three/tsl'
import { BoxGeometry, Mesh, MeshBasicMaterial, PerspectiveCamera, Scene, WebGPURenderer, PostProcessing, Color } from 'three/webgpu'
import { bloom } from 'three/addons/tsl/display/BloomNode.js';

const usage = new Map();

window["$usage"] = usage;

function incUsage(name) {
  usage.set(name, (usage.get(name) || 0) + 1);
}

function decUsage(name) {
  usage.set(name, (usage.get(name) || 0) - 1);
}

function overrideFunc(func, action) {
  const orig = func[0][func[1]];
  func[0][func[1]] = function(...args) {
    action();
    return orig.apply(this, args);
  }
}

function trapUsage(name, createFunc, deleteFunc) {
  overrideFunc(createFunc, () => incUsage(name));
  overrideFunc(deleteFunc, () => decUsage(name));
}

const obj = WebGL2RenderingContext.prototype;
trapUsage('buffer', [obj, 'createBuffer'], [obj, 'deleteBuffer']);

const renderer = new WebGPURenderer({ forceWebGL: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const scene = new Scene();
const camera = new PerspectiveCamera(50, window.innerWidth / window.innerHeight);
camera.position.set(3, 3, 3);
camera.lookAt(0, 0, 0);

const geometry = new BoxGeometry();
const material = new MeshBasicMaterial({ color: new Color(0, 1, 1, 1) });

const mesh = new Mesh(geometry, material)
scene.add(mesh)

const postProcessing = new PostProcessing(renderer);
const scenePass = pass(scene, camera);
const scenePassColor = scenePass.getTextureNode('output');
const bloomPass = bloom(scenePassColor);
bloomPass.threshold.value = 0;
bloomPass.strength.value = 2;
bloomPass.radius.value = 0;
postProcessing.outputNode = scenePassColor.add(bloomPass);
//postProcessing.outputNode = scenePassColor;

renderer.setAnimationLoop(() => {
  postProcessing.render(scene, camera)
});

Live example

N/A

Screenshots

No response

Version

r179

Device

No response

Browser

Chrome

OS

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions