GLSLのコンパイルエラーをブラウザのコンソールに表示する

# 誰向け？

GLSLのコンパイルエラーのエラーメッセージがブラウザのコンソールで見られず困っている人向け。

この記事ではp5.jsを使った例を上げますが、p5.jsを使っていない人にも考え方は役に立つはず。

# 解決したい問題
そもそもp5.jsはGLSLのコンパイルエラーのエラーメッセージをコンソールに表示する機能を備えています。

![スクリーンショット 2024-03-12 23.03.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85139/55c3a351-a6b4-dc1d-96f6-bf4a95243ebd.png)

この画像内の

```
🌸 p5.js says: Snap! Error linking shader program: Precisions of uniform 'uRed' differ between VERTEX and FRAGMENT shaders.
```

という奴がそうです。

ところが私の環境だと何故かこれがコンソールに出なくなってしまいました。

`Node.js`か`React`か`Next.js`か`TypeScript`あたりが怪しいのですが、調査しても原因特定できず。心当たりのある方は教えて下さい…。

ということで、下記の方法では、p5.jsの機能に頼らず、シェーダーのコンパイルエラーのエラーメッセージを取得します。


# 方法
`Vertex Shader`と`Fragment Shader`と、それらを集約した`WebGL Program`の3箇所にエラーが格納されています。これを読み出せばいいです。

:::note info
`Vertex Shader`と`Fragment Shader`のエラー
    → `getShaderInfoLog`で取得
`WebGL Program`のエラー
    → `getProgramInfoLog`で取得
:::

`Vertex Shader` と `Fragment Shader` については、[WebGL2RenderingContext](https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext) と [WebGLShader](https://developer.mozilla.org/en-US/docs/Web/API/WebGLShader) のインスタンスを取得し、Context の [getShaderInfoLog](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getShaderInfoLog) メソッドに Shader のインスタンスを渡せばエラーメッセージが取得できるので、それを `console.log` などすればよいです。

p5.js の場合は、 [p5.Shader](https://p5js.org/reference/#/p5.Shader) の中にある vertexShader と fragmentShader のインスタンスを取り出して、Context の getShaderInfoLog に渡します。

`WebGL Program` については、[getProgramInfoLog](https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/getProgramInfoLog) メソッドを使って、ほぼ同様にします。

## 各シェーダー・プログラムのインスタンスのありか
https://github.com/processing/p5.js/blob/main/src/webgl/p5.Shader.js
こちらのソースコードを見ると、`p5.Shader`は
`_vertShader`
`_fragShader`
`_glProgram`
というプロパティを持っていることがわかります。これらがそうです。

# コード例

まずはHTML。`p5.js`と`script.js`を読み込んでいるだけで、あとはテンプレ、ボイラープレートです。
```html:index.html
<!DOCTYPE html>
<html lang="ja">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.8.0/p5.js"></script>
    </head>

    <body>
        <script src="script.js"></script>
    </body>

</html>
```

次の `JavaScript` がこの記事のメインです！
`p5.Shader` の中にある `Vertex Shader` と `Fragment Shader` と `WebGL Program` をそれぞれ取り出して、メッセージが記録されていればそれをコンソールに `throw` しています。
```javascript:script.js
window.onload = function () {

    const sketch = (p5) => {
        let myShader;  // p5.Shaderを格納
        let myCanvas; // p5.Rendererを格納(canvasを指し示す)
        let gl; // WebGL2RenderingContextを格納

        p5.preload = () => {
            // preload内でシェーダを読み込む
            myShader = p5.loadShader('./shader.vert', './shader.frag');
        }

        p5.setup = () => {
            myCanvas = p5.createCanvas(400, 400, p5.WEBGL); // 作ったCanvasを代入
            gl = myCanvas.elt.getContext('webgl2'); // GL2のContextを取得して代入
            p5.noStroke();
        }

        p5.draw = () => {
            p5.background(0);
            const red = [1.0, 0.0, 0.0];
            const green = [0.0, 1.0, 0.0];
            myShader.setUniform("uColors", [red, green].flat());
            p5.shader(myShader);

            // vertex shaderのコンパイルエラーを取得して表示
            const messageVert = gl.getShaderInfoLog(myShader._vertShader);
            if (messageVert !== null && messageVert.length > 0) {
                console.log("error of vertex shader");
                throw messageVert;
            }

            // fragment shaderのコンパイルエラーを取得して表示
            const messageFrag = gl.getShaderInfoLog(myShader._fragShader);
            if (messageFrag !== null && messageFrag.length > 0) {
                console.log("error of fragment shader");
                throw messageFrag;
            }

            // WebGLProgramのエラーを取得して表示
            const messageProgram = gl.getProgramInfoLog(myShader._glProgram);
            if (messageProgram !== null && messageProgram.length > 0) {
                console.log("error of gl program");
                throw messageProgram;
            }

            p5.beginShape(p5.TRIANGLES); // 三角形を描画
            p5.vertex(0.0, 0.5); // gl_VertexID == 0
            p5.vertex(-0.5, -0.5); // gl_VertexID == 1
            p5.vertex(0.5, -0.5); // gl_VertexID == 2
            p5.endShape();
        }
    }

    new p5(sketch);
};
```

2つのシェーダーのGLSLコードは、この記事の内容的にはどうでもいいですが、後ほどこれを少し破壊して、どんなエラーが出るか確認してみます。
```glsl:shader.vert
#version 300 es

precision mediump float;

uniform vec3 uColors[2];
in vec3 aPosition;
out vec4 vertColor;

void main() {
    gl_Position = vec4(aPosition, 1.0);
    // 1色目と2色目はuniformで受け取り、3色目はハードコードしています。
    vertColor =
        gl_VertexID == 0 ? vec4(uColors[0],1.0):
        gl_VertexID == 1 ? vec4(uColors[1],1.0):
        vec4(0.0, 0.0, 1.0, 1.0);
}
```

```glsl:shader.frag
#version 300 es

precision mediump float;

in vec4 vertColor;
out vec4 fragColor;

void main() {
    // vertex shaderから受け取った値を色として出力するだけ
    fragColor = vertColor;
}
```

## 出力結果
![ダウンロード.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85139/af433f82-d8c1-ce91-4e2c-213a36597f03.png)


## エラー例1 vertex shaderのエラー

```diff_glsl:shader.vert
- out vec4 vertColor;
+ out vec3 vertColor;
```

vertex shader で型エラーを引き起こしてみます。

![スクリーンショット 2024-03-14 15.03.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85139/4482a092-66ad-b519-d1f4-869ec3d5e20c.png)

```
script.js:30 Uncaught ERROR: 0:12: '=' : dimension mismatch
ERROR: 0:12: 'assign' : cannot convert from 'mediump 4-component vector of float' to 'out mediump 3-component vector of float'
```

いいですね。fragment shader ではなく vertex shader が怒られている、ということもちゃんとわかります。

## エラー例2 fragment shaderのエラー

```diff_glsl:shader.frag
- out vec4 fragColor;
```

こうすると…

![スクリーンショット 2024-03-14 15.08.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85139/6dd041fb-28dc-5244-9df1-6d9f4f0bb9c3.png)

```
script.js:37 Uncaught ERROR: 0:9: 'fragColor' : undeclared identifier
ERROR: 0:9: 'assign' : l-value required (can't modify a const)
ERROR: 0:9: '=' : dimension mismatch
ERROR: 0:9: 'assign' : cannot convert from 'in highp 4-component vector of float' to 'const highp float'
```

ちゃんと `undeclared identifier` と怒られましたね。`fragment shader` が怒られていることもちゃんとわかります。

## エラー例3 vertex shaderとfragment shaderの連携エラー

昔の呼び方で `varying` と呼ばれていた奴ですね。型を不一致にさせてみます。

```diff_glsl:shader.frag
- in vec4 vertColor;
- out vec4 fragColor;
+ in vec3 vertColor;
+ out vec3 fragColor;
```

![スクリーンショット 2024-03-15 15.17.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/85139/affddc20-0d45-f165-5bbc-83b461398616.png)

```
Uncaught Types of varying 'vertColor' differ between VERTEX and FRAGMENT shaders.
FRAGMENT varying vertColor does not match any VERTEX varying
```

今回は `WebGL Program` でエラーが発生したことがわかります。

# まとめ

とにかくこれらを取得して、console.log

:::note info
`Vertex Shader`と`Fragment Shader`のエラー
    → `getShaderInfoLog`で取得
`WebGL Program`のエラー
    → `getProgramInfoLog`で取得
:::