Skip to content

Commit

Permalink
Add compileAsync
Browse files Browse the repository at this point in the history
The compileAsync method adds a way to allow apps to wait on shader
compilation before adding objects to a scene.
  • Loading branch information
toji committed Oct 13, 2023
1 parent da47522 commit f1b8061
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 18 deletions.
8 changes: 7 additions & 1 deletion examples/webgl_loader_gltf.html
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,13 @@
// model

const loader = new GLTFLoader().setPath( 'models/gltf/DamagedHelmet/glTF/' );
loader.load( 'DamagedHelmet.gltf', function ( gltf ) {
loader.load( 'DamagedHelmet.gltf', async function ( gltf ) {

// Calling compileAsync returns a promise that resolves when gltf.scene can be added
// to scene without unnecessary stalling on shader compiation. This helps the page
// stay responsive during startup.

await renderer.compileAsync( gltf.scene, scene );

scene.add( gltf.scene );

Expand Down
9 changes: 8 additions & 1 deletion examples/webgl_loader_gltf_transmission.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,17 @@
new GLTFLoader()
.setPath( 'models/gltf/' )
.setDRACOLoader( new DRACOLoader().setDecoderPath( 'jsm/libs/draco/gltf/' ) )
.load( 'IridescentDishWithOlives.glb', function ( gltf ) {
.load( 'IridescentDishWithOlives.glb', async function ( gltf ) {

mixer = new THREE.AnimationMixer( gltf.scene );
mixer.clipAction( gltf.animations[ 0 ] ).play();

// Calling compileAsync returns a promise that resolves when gltf.scene can be added
// to scene without unnecessary stalling on shader compiation. This helps the page
// stay responsive during startup.

await renderer.compileAsync( gltf.scene, scene );

scene.add( gltf.scene );

} );
Expand Down
189 changes: 173 additions & 16 deletions src/renderers/WebGLRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -917,29 +917,29 @@ class WebGLRenderer {

// Compile

this.compile = function ( scene, camera ) {
function prepareMaterial( material, scene, object ) {

function prepare( material, scene, object ) {
if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) {

if ( material.transparent === true && material.side === DoubleSide && material.forceSinglePass === false ) {
material.side = BackSide;
material.needsUpdate = true;
getProgram( material, scene, object );

material.side = BackSide;
material.needsUpdate = true;
getProgram( material, scene, object );
material.side = FrontSide;
material.needsUpdate = true;
getProgram( material, scene, object );

material.side = FrontSide;
material.needsUpdate = true;
getProgram( material, scene, object );
material.side = DoubleSide;

material.side = DoubleSide;
} else {

} else {
getProgram( material, scene, object );

getProgram( material, scene, object );
}

}
}

}
this.compile = function ( scene, camera ) {

currentRenderState = renderStates.get( scene );
currentRenderState.init();
Expand Down Expand Up @@ -976,13 +976,116 @@ class WebGLRenderer {

const material2 = material[ i ];

prepare( material2, scene, object );
prepareMaterial( material2, scene, object );

}

} else {

prepareMaterial( material, scene, object );

}

}

} );

renderStateStack.pop();
currentRenderState = null;

};

// compileAsync

this.compileAsync = function ( scene, targetScene = null ) {

// If no explicit targetScene was given use the scene instead
if ( ! targetScene ) {

targetScene = scene;

}

currentRenderState = renderStates.get( targetScene );
currentRenderState.init();

renderStateStack.push( currentRenderState );

let foundScene = scene === targetScene;

// Gather lights from both the scene and the new object that will be added
// to the scene.
targetScene.traverseVisible( function ( object ) {

if ( object === scene ) {

foundScene = true;

}

if ( object.isLight ) {

currentRenderState.pushLight( object );

if ( object.castShadow ) {

currentRenderState.pushShadow( object );

}

}

} );

// If the scene wasn't already part of the targetScene, add any lights it
// contains as well.
if ( ! foundScene ) {

scene.traverseVisible( function ( object ) {

if ( object.isLight ) {

currentRenderState.pushLight( object );

if ( object.castShadow ) {

currentRenderState.pushShadow( object );

}

}

} );

}

currentRenderState.setupLights( _this._useLegacyLights );

const compiling = new Set();

// Only initialize materials in the new scene, not the targetScene.

scene.traverse( function ( object ) {

const material = object.material;

if ( material ) {

if ( Array.isArray( material ) ) {

for ( let i = 0; i < material.length; i ++ ) {

const material2 = material[ i ];

prepareMaterial( material2, targetScene, object );
compiling.add( material2 );

}

} else {

prepare( material, scene, object );
prepareMaterial( material, targetScene, object );
compiling.add( material );

}

Expand All @@ -993,6 +1096,60 @@ class WebGLRenderer {
renderStateStack.pop();
currentRenderState = null;

// Wait for all the materials in the new object to indicate that they're
// ready to be used before resolving the promise.

return new Promise( ( resolve ) => {

function checkMaterialsReady() {

compiling.forEach( function ( material ) {

const materialProperties = properties.get( material );
const program = materialProperties.currentProgram;

if ( program.isReady() ) {

// remove any programs that report they're ready to use from the list
compiling.delete( material );

}

} );

// once the list of compiling materials is empty, call the callback

if ( compiling.size === 0 ) {

resolve( scene );
return;

}

// if some materials are still not ready, wait a bit and check again

setTimeout( checkMaterialsReady, 10 );

}

if ( extensions.get( 'KHR_parallel_shader_compile' ) !== null ) {

// If we can check the compilation status of the materials without
// blocking then do so right away.

checkMaterialsReady();

} else {

// Otherwise start by waiting a bit to give the materials we just
// initialized a chance to finish.

setTimeout( checkMaterialsReady, 10 );

}

} );

};

// Animation Loop
Expand Down
21 changes: 21 additions & 0 deletions src/renderers/webgl/WebGLProgram.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { ShaderChunk } from '../shaders/ShaderChunk.js';
import { NoToneMapping, AddOperation, MixOperation, MultiplyOperation, CubeRefractionMapping, CubeUVReflectionMapping, CubeReflectionMapping, PCFSoftShadowMap, PCFShadowMap, VSMShadowMap, ACESFilmicToneMapping, CineonToneMapping, CustomToneMapping, ReinhardToneMapping, LinearToneMapping, GLSL3, LinearSRGBColorSpace, SRGBColorSpace, LinearDisplayP3ColorSpace, DisplayP3ColorSpace, P3Primaries, Rec709Primaries } from '../../constants.js';
import { ColorManagement } from '../../math/ColorManagement.js';

// From https://www.khronos.org/registry/webgl/extensions/KHR_parallel_shader_compile/
const COMPLETION_STATUS_KHR = 0x91B1;

let programIdCount = 0;

function handleSource( string, errorLine ) {
Expand Down Expand Up @@ -1014,6 +1017,24 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) {

};

// indicate when the program is ready to be used

// if the KHR_parallel_shader_compile extension isn't supported, flag the
// program as ready immediately. It may cause a stall when it's first used.
let programReady = ! parameters.rendererExtensionParallelShaderCompile;

this.isReady = function () {

if ( ! programReady ) {

programReady = gl.getProgramParameter( program, COMPLETION_STATUS_KHR );

}

return programReady;

};

// free resource

this.destroy = function () {
Expand Down
1 change: 1 addition & 0 deletions src/renderers/webgl/WebGLPrograms.js
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,7 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
rendererExtensionFragDepth: IS_WEBGL2 || extensions.has( 'EXT_frag_depth' ),
rendererExtensionDrawBuffers: IS_WEBGL2 || extensions.has( 'WEBGL_draw_buffers' ),
rendererExtensionShaderTextureLod: IS_WEBGL2 || extensions.has( 'EXT_shader_texture_lod' ),
rendererExtensionParallelShaderCompile: extensions.has( 'KHR_parallel_shader_compile' ),

customProgramCacheKey: material.customProgramCacheKey()

Expand Down

0 comments on commit f1b8061

Please sign in to comment.