three.js icon indicating copy to clipboard operation
three.js copied to clipboard

Defer blocking queries against WebGLPrograms after linking

Open toji opened this issue 4 years ago • 11 comments

Program linking can happen asynchronously, but is forced to block and wait for completion the first time information about that program is queried after linking. This includes calls to getAttribLocation/getUniformLocation/getUniform/getProgramParameter/getProgramLogInfo, etc.

This change defers most of those calls until the first time until the first time that they're actually needed by the page, which can allow the browser time to complete linking asynchronously and thus reduce blocking, especially when used in conjunction with renderer.compile().

It's worth noting that many materials are initialized the first time they are encountered by the render loop and then immediately used for rendering, which negates most of the benefit that this could otherwise provide, but it lays the groundwork for more efficient patterns in the future and can significantly help classes like PMREMGenerator that are already using renderer.compile() internally.

The ideal endpoint is for Three to start taking advantage of KHR_parallel_shader_compile as proposed by #16321 and #16662. This is just a lower-impact stepping stone along the way.

toji avatar Jun 26 '20 23:06 toji

Review ping?

toji avatar Jul 26 '20 18:07 toji

Thanks for the feedback! Addressed all comments.

toji avatar Jul 27 '20 16:07 toji

I've tested your branch locally and it seems the webgl_lightningstrike example is failing. I just get a black screen with no console logs.

Mugen87 avatar Jul 27 '20 20:07 Mugen87

I'll look into it, thanks!

toji avatar Jul 27 '20 20:07 toji

Fixed. Explanation above.

toji avatar Jul 27 '20 21:07 toji

Sorry for the delay on this one.

Program linking can happen asynchronously, but is forced to block and wait for completion the first time information about that program is queried after linking.

I think that is working as intended. Otherwise programs may not be ready in the render call, no?

For example, this code renders a cube:

import * as THREE from 'https://cdn.skypack.dev/three'

const camera = new THREE.PerspectiveCamera();
camera.position.z = 1;

const geometry = new THREE.BoxGeometry( 0.2, 0.2, 0.2 );
const material = new THREE.MeshNormalMaterial();

const mesh = new THREE.Mesh( geometry, material );

const renderer = new THREE.WebGLRenderer();
renderer.setSize( 300, 300 );
renderer.render( mesh, camera );
document.body.appendChild( renderer.domElement );

https://jsfiddle.net/L7nkdc56/

If the linking was async, wouldn't it be possible that we would end up with a black render?

mrdoob avatar Oct 28 '21 13:10 mrdoob

This change does not force asynchronous linking. What it does do (or at least will do when I rebase it over a year and a half of Three.js churn) is moves various calls that are forced synchronously to wait on linking to finish (getUniforms(), etc) to the first time that they are used rather than always happening immediately after link.

Given how Three is architected, this functionally means that most programs still end up blocking because they wait to compile the programs until an object is added to the Scene Graph, so first use happens ~immediately. In some cases, however, such as PMREMGenerator that links a program on construction but may not use it until much later, it offers an opportunity for most/all of the program linking to happen asynchronously without impacting the scene's rendering.

As stated in the first comment, this was intended only as a lower-impact stepping stone to enabling more robust async shader compiles.

toji avatar Oct 29 '21 16:10 toji

Rebased and confirmed it still works as intended. Code's even a bit simpler before, due to something that's changed about how morph targets are handled that made at least once large chunk of code disappear. 👍

toji avatar Oct 29 '21 17:10 toji

This has great potential in combination with our new support of the fetch API. Imagine: load a GLB, but pull the tiny JSON part first; use that to construct the scene and start the shaders compiling while waiting for the bin and texture data to come down. Once all is ready, start the render loop with all the work already done.

elalish avatar Oct 29 '21 18:10 elalish

@mrdoob: I'm going to be looking at https://github.com/mrdoob/three.js/pull/19752 again after our discussion on Dec 9th, but this PR is a pre-requisite for that work. It does not change any existing behavior, makes a few isolated scenarios less janky, and breaks up a dependency that prevents further work on making shader compiling async.

Critically, this change does NOT alter the behavior that objects in Three are visible as soon as they are attached to the scene. (Even if that causes jank.)

toji avatar Dec 13 '21 22:12 toji

Verified the PR again and it looks all good!

Mugen87 avatar Dec 16 '21 08:12 Mugen87

📦 Bundle size

Full ESM build, minified and gzipped.

Filesize dev Filesize PR Diff
651.4 kB (161.4 kB) 651.5 kB (161.4 kB) +107 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Filesize dev Filesize PR Diff
444.1 kB (107.4 kB) 444.2 kB (107.4 kB) +107 B

github-actions[bot] avatar Oct 12 '23 18:10 github-actions[bot]

Rebased to resolve merge conflicts after discussion kicked up on #19752 again.

toji avatar Oct 12 '23 18:10 toji

Since I think Ricardo feels positive about this change and supporting KHR_parallel_shader_compile is a valuable addition to the engine, I'll merge so we can move on with https://github.com/mrdoob/three.js/pull/19752.

Mugen87 avatar Oct 13 '23 10:10 Mugen87

Thank you!

toji avatar Oct 13 '23 16:10 toji