playcanvas / engine

JavaScript game engine built on WebGL, WebGPU, WebXR and glTF
https://playcanvas.com
MIT License
9.6k stars 1.34k forks source link

Failed to transpile webgl vertex shader [Shader Id 3 LitShader-ShadowPass_1_0-proc] to WebGPU: [GLSL compilation failed] while rendering undefined #6673

Open erikdubbelboer opened 3 months ago

erikdubbelboer commented 3 months ago

This error is affecting only a couple of the users of https://poki.com/en/preview/94176748-9ef8-42c9-a44e-a95b70ec5680/da472bf8-04fa-4660-a885-2c2b6b783dda But it seems like an interesting error so I thought I'd create an issue just to be sure.

Affects both mobile and desktop Chrome 125.

I'm afraid I have no stack trace or further information for this error.

mvaligursky commented 3 months ago

It seems the glslang here fails to transpile the shader. It usually logs some warning messages to console just before it fails and we log this error. Maybe we could try and see if we can obtain those messages and print them out as part of this error.

mvaligursky commented 3 months ago

So when the compilation fails, we get now this (with PR https://github.com/playcanvas/engine/pull/6681)

Screenshot 2024-06-10 at 11 17 49

We have additional callstack. But ideally we'd capture the (yellow) text the glslang wasm logs and print it ourselves as part of this error. I've tried to override console.log/warn/err, but also glslang.print/printErr but no luck to capture it yet.

@Maksims @kungfooman - any idea how this can be captured?

We call initialize glslang here: https://github.com/playcanvas/engine/blob/f88018583563299d35b300e1e6f2818a0fa54eb1/src/platform/graphics/webgpu/webgpu-graphics-device.js#L186

and use it here: https://github.com/playcanvas/engine/blob/f88018583563299d35b300e1e6f2818a0fa54eb1/src/platform/graphics/webgpu/webgpu-shader.js#L152

repro using engine examples:

kungfooman commented 3 months ago

It's a bound function:

            var x = c.print || console.log.bind(console),
                y = c.printErr || console.warn.bind(console);

And c.printErr isn't accessible through its init system, so it would require some updates to glslang.js

mvaligursky commented 3 months ago

Happy to do some small changes to glslang.js - what would you suggest?

kungfooman commented 3 months ago

Happy to do some small changes to glslang.js - what would you suggest?

I just realized we can also temporarily overwrite console.warn until glslang.js loaded:

Both files belong next to glslang.js for testing:

glslangCaptureSingleError.js

const {warn} = console;
const lastErrors = [];
console.warn = msg => {
    lastErrors.push(msg);
    warn('Captured', msg);
}
const glslang_ = await import('./glslang.js');
const glslang = await glslang_.default();
// Restore warn method once glslang created a bound warn function out of it.
console.warn = warn;
function compileGLSL(...args) {
  lastErrors.length = 0;
  try {
    glslang.compileGLSL(...args);
  } catch (e) {
    const tmp = lastErrors.join('\n');
    // Rethrow with captured errors.
    throw new Error(tmp + e);
  }
}
/** @todo Same error capturing for compileGLSLZeroCopy */
const {compileGLSLZeroCopy} = glslang;
export {compileGLSL, compileGLSLZeroCopy};
console.warn("NORMAL WARN");

test.html

<script type="module">
  // Now the API even looks readable:
  import {compileGLSL} from './glslangCaptureSingleError.js';
  try {
    const ret = compileGLSL('#version 150\nout herp\nderp\nerrp;', 'fragment');
    console.log("ret", ret);
  } catch (e) {
    console.error("Full error:", e);
  }
</script>

Output:

image

Of course the extraneous console.log's need to be removed, I just left them for making the captured messages mentally reconstructable in the image.

(I still can't run WebGPU examples on Linux, so I just made up this mini html for testing)

The only objection I can think of is the top level await, but that seems better supported than WebGPU:

mvaligursky commented 3 months ago

Hey that's pretty cool, thanks for looking into this. Considering the import of glslang is async, we disable the console warnings till this is imported .. we might miss printing some other useful warning for the user during that time? I guess we could print them out after the await, but those could be out of order with other messages, or even not print at all if some exception is thrown during that time?

kungfooman commented 3 months ago

Hey that's pretty cool, thanks for looking into this. Considering the import of glslang is async, we disable the console warnings till this is imported .. we might miss printing some other useful warning for the user during that time?

Right, overwriting console.warn is definitely wonky, seems like refactoring glslang.js itself would make most sense, probably should open an issue there?

https://github.com/search?q=repo%3AKhronosGroup%2Fglslang+compileGLSLZeroCopy&type=code

(Otherwise the next version updates will have to jump through the same hoops again or it will be forgotten etc.)

mvaligursky commented 3 months ago

Yeah I look at this, I think the repo is here, but it's not very active https://github.com/kainino0x/glslang.js?tab=readme-ov-file

mvaligursky commented 3 months ago

I'll probably go ahead with your initial suggestion. Override console.warn by a wrapper, that still writes things out, but capture those internally to an array while calling compileGLSL - that seems like it should have no side-effects.