diff --git a/src/platform/graphics/graphics-device.js b/src/platform/graphics/graphics-device.js index bb93d4d9a3b..c76a896b9f3 100644 --- a/src/platform/graphics/graphics-device.js +++ b/src/platform/graphics/graphics-device.js @@ -489,6 +489,7 @@ class GraphicsDevice extends EventHandler { this.vertexBuffers = []; this.shader = null; this.shaderValid = undefined; + this.shaderAsyncCompile = false; this.renderTarget = null; } diff --git a/src/platform/graphics/null/null-graphics-device.js b/src/platform/graphics/null/null-graphics-device.js index 52b6d2908dc..96616cd76a3 100644 --- a/src/platform/graphics/null/null-graphics-device.js +++ b/src/platform/graphics/null/null-graphics-device.js @@ -106,7 +106,7 @@ class NullGraphicsDevice extends GraphicsDevice { draw(primitive, numInstances = 1, keepBuffers) { } - setShader(shader) { + setShader(shader, asyncCompile = false) { } setBlendState(blendState) { diff --git a/src/platform/graphics/webgl/webgl-graphics-device.js b/src/platform/graphics/webgl/webgl-graphics-device.js index 6a0dcfa75b7..d3a69e5f1c3 100644 --- a/src/platform/graphics/webgl/webgl-graphics-device.js +++ b/src/platform/graphics/webgl/webgl-graphics-device.js @@ -2122,7 +2122,7 @@ class WebglGraphicsDevice extends GraphicsDevice { draw(primitive, numInstances, keepBuffers) { const gl = this.gl; - this.activateShader(); + this.activateShader(this); if (!this.shaderValid) return; @@ -2749,9 +2749,19 @@ class WebglGraphicsDevice extends GraphicsDevice { * * @param {Shader} shader - The shader to assign to the device. */ - setShader(shader) { + + /** + * Sets the active shader to be used during subsequent draw calls. + * + * @param {Shader} shader - The shader to assign to the device. + * @param {boolean} asyncCompile - If true, rendering will be skipped until the shader is + * compiled, otherwise the rendering will wait for the shader compilation to finish. Defaults to + * false. + */ + setShader(shader, asyncCompile = false) { if (shader !== this.shader) { this.shader = shader; + this.shaderAsyncCompile = asyncCompile; this.shaderValid = undefined; // need to run activation / validation // #if _PROFILER @@ -2760,21 +2770,46 @@ class WebglGraphicsDevice extends GraphicsDevice { } } - activateShader() { + activateShader(device) { + const { shader } = this; + const { impl } = shader; if (this.shaderValid === undefined) { - const { shader } = this; + if (shader.failed) { this.shaderValid = false; - } else if (!shader.ready && !shader.impl.finalize(this, shader)) { - shader.failed = true; - this.shaderValid = false; - } else { - // Set the active shader - this.gl.useProgram(shader.impl.glProgram); - this.shaderValid = true; + } else if (!shader.ready) { + + // if the shader is async compiled and can be skipped if not ready + if (this.shaderAsyncCompile) { + + // if the shader is linked, finalize it + if (impl.isLinked(device)) { + if (!impl.finalize(this, shader)) { + shader.failed = true; + this.shaderValid = false; + } + } else { + // skip the async shader rendering + this.shaderValid = false; + } + + } else { + + // this cannot be skipped, wait for the shader to be ready + if (!impl.finalize(this, shader)) { + shader.failed = true; + this.shaderValid = false; + } + } } } + + if (this.shaderValid === undefined) { + // Set the active shader + this.gl.useProgram(impl.glProgram); + this.shaderValid = true; + } } /** diff --git a/src/platform/graphics/webgl/webgl-shader.js b/src/platform/graphics/webgl/webgl-shader.js index b47e35334fd..20f20ab4826 100644 --- a/src/platform/graphics/webgl/webgl-shader.js +++ b/src/platform/graphics/webgl/webgl-shader.js @@ -389,6 +389,23 @@ class WebglShader { return true; } + /** + * Check the linking status of a shader. + * + * @param {import('./webgl-graphics-device.js').WebglGraphicsDevice} device - The graphics device. + * @returns {boolean} True if the shader is already linked, false otherwise. Note that unless the + * device supports the KHR_parallel_shader_compile extension, this will always return true. + */ + isLinked(device) { + + const { extParallelShaderCompile } = device; + if (extParallelShaderCompile) { + return device.gl.getProgramParameter(this.glProgram, extParallelShaderCompile.COMPLETION_STATUS_KHR); + } + + return true; + } + /** * Truncate the WebGL shader compilation log to just include the error line plus the 5 lines * before and after it. diff --git a/src/platform/graphics/webgpu/webgpu-graphics-device.js b/src/platform/graphics/webgpu/webgpu-graphics-device.js index 24158e29bb0..4346064f3e7 100644 --- a/src/platform/graphics/webgpu/webgpu-graphics-device.js +++ b/src/platform/graphics/webgpu/webgpu-graphics-device.js @@ -479,7 +479,7 @@ class WebgpuGraphicsDevice extends GraphicsDevice { } } - setShader(shader) { + setShader(shader, asyncCompile = false) { if (shader !== this.shader) { this.shader = shader; diff --git a/src/scene/renderer/forward-renderer.js b/src/scene/renderer/forward-renderer.js index 54ab6b6ce60..29a140d05be 100644 --- a/src/scene/renderer/forward-renderer.js +++ b/src/scene/renderer/forward-renderer.js @@ -555,7 +555,8 @@ class ForwardRenderer extends Renderer { if (newMaterial) { - device.setShader(shaderInstance.shader); + const asyncCompile = false; + device.setShader(shaderInstance.shader, asyncCompile); // Uniforms I: material material.setParameters(device);