# \`onBeforeCompile\` fires only once, although I set \`needsUpdate = true\`

**URL:** <https://discourse.threejs.org/t/onbeforecompile-fires-only-once-although-i-set-needsupdate-true/38614>\
**Category:** Questions\
**Tags:** webgl-renderer, materials\
**Created:** [May 28, 2022, 8:45pm UTC](https://discourse.threejs.org/t/onbeforecompile-fires-only-once-although-i-set-needsupdate-true/38614 "2022-05-28T20:45:28Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![trusktr](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/trusktr/32/13726_2.png) [@trusktr](https://discourse.threejs.org/u/trusktr)\
**Post date:** [May 28, 2022, 8:45pm UTC](https://discourse.threejs.org/t/onbeforecompile-fires-only-once-although-i-set-needsupdate-true/38614/1 "2022-05-28T20:45:28Z")

</div>

I thought setting `material.needsUpdate = true` would cause a material’s `onBeforeCompile` to fire again, but it doesn’t.

From the docs:

> ### .needsUpdate : Boolean
> 
> Specifies that the material needs to be recompiled.

The term “recompile” there implies that `onBeforeCompile` should run again.

* * *

When `WebGLRenderer` [checks](https://github.com/mrdoob/three.js/blob/63958ace0b2db846381bd35c76724edfeed7a720/src/renderers/WebGLRenderer.js#L1366-L1391)

```js
if ( program !== undefined ) {

```

program is `undefined` only the first time (which makes sense), so it skips the branch that calls `material.onBeforeCompile` any time after that.

When new programs are made, it seems `program` is not `undefined` again, and `onBeforeCompile` never fires again.

(cc @marcofugaro, Changing the `.camera` in `three-projected-material` expects state changes to happen with `onBeforeCompile` but they don’t, [github issue](https://github.com/marcofugaro/three-projected-material/issues/45)).

---

<div class="post-metadata">

**Author:** ![trusktr](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/trusktr/32/13726_2.png) [@trusktr](https://discourse.threejs.org/u/trusktr)\
**Post date:** [May 28, 2022, 9:15pm UTC](https://discourse.threejs.org/t/onbeforecompile-fires-only-once-although-i-set-needsupdate-true/38614/2 "2022-05-28T21:15:02Z")

</div>

A problem that I’m seeing is that if a material subclass does the following, it won’t work as expected:

```javascript
class MyMaterial extends MeshPhongMaterial {
  #camera = new PerspectiveCamera
  get camera() {return this.#camera}
  set camera(c) {
    this.#camera = c
    this.needsUpdate = true // "re-compile"
  }

  onBeforeCompile(shader) {
    // only runs once, initially
    if (this.#camera instanceof OrthographicCamera)
      shader.defines.ORTHOGRAPHIC = ""
    else delete shader.defines.ORTHOGRAPHIC

    // ... patch shader for example ...
  }
}

```

and the problem is that, if we set `myMat.camera = new OrthographicCamera`, the `ORTHOGRAPHIC` define does not get updated.

### Workaround:

The above example can be written like the following instead:

```javascript
class MyMaterial extends MeshPhongMaterial {
  #camera = new PerspectiveCamera
  get camera() {return this.#camera}
  set camera(c) {
    this.#camera = c
    this.needsUpdate = true // "re-compile"
    this.onBeforeCompile = c instanceof PerspectiveCamera
      ? this.#onBeforeCompilePerspective
      ? this.#onBeforeCompileOrtho
  }

  #onBeforeCompilePerspective = (shader) => {
    delete shader.defines.ORTHOGRAPHIC
    patch(shader)
  }

  #onBeforeCompileOrtho = (shader) => {
    shader.defines.ORTHOGRAPHIC = ""
    patch(shader)
  }

  onBeforeCompile = this.#onBeforeCompilePerspective
}

function patch(shader) {...}

```

And now, because `WelGLProgram.getProgramCacheKey` will return a different key of the a different `onBeforeCompile` function is detected, it will make a new program upon toggle (and successive toggles use one program or the other).

---

<div class="post-metadata">

**Author:** ![Mugen87](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/mugen87/32/5645_2.png) [@Mugen87](https://discourse.threejs.org/u/Mugen87)\
**Post date:** [May 29, 2022, 8:07am UTC](https://discourse.threejs.org/t/onbeforecompile-fires-only-once-although-i-set-needsupdate-true/38614/3 "2022-05-29T08:07:37Z")

</div>

If you set `needsUpdate` to `true` and expect a recompilation of the shader, you have to ensure that the _program cache key_ actually changes. If not, `three.js` won’t recompile the shader since no structural changes happened.

When using `onBeforeCompile()` like in your above example, you have to make use of [customProgramCacheKey()](https://threejs.org/docs/index.html#api/en/materials/Material.customProgramCacheKey) which was introduced some while ago for a similar use case. This method returns `onBeforeCompile()` as a string by default, but you can implement it with any code you like. The documentation contains a code example to identify two different permutations that `onBeforeCompile()` can produce.

---

<div class="post-metadata">

**Author:** ![trusktr](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/trusktr/32/13726_2.png) [@trusktr](https://discourse.threejs.org/u/trusktr)\
**Post date:** [May 30, 2022, 6:14pm UTC](https://discourse.threejs.org/t/onbeforecompile-fires-only-once-although-i-set-needsupdate-true/38614/4 "2022-05-30T18:14:10Z")

</div>

> [@Mugen87](#):
>
> If you set `needsUpdate` to `true` and expect a recompilation of the shader, you have to ensure that the _program cache key_ actually changes. If not, `three.js` won’t recompile the shader since no structural changes happened.

Indeed I realized that after looking at the source code, namely that onBeforeCompile functions are used in the cache key hence I could swap them.

Maybe `needsUpdate` needs some addition regarding the “recompile” that it mentions. The docs made it seem like `needsUpdate` would surely trigger a recompile.

The doc for `customProgramCacheKey` says

> Unlike properties, the callback is not supported by .clone(), .copy() and .toJSON().

What does it mean? `material.clone()` will not include the `customProgramCacheKey` for the cloned material?

---

<div class="post-metadata">

**Author:** ![Mugen87](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.threejs.org/mugen87/32/5645_2.png) [@Mugen87](https://discourse.threejs.org/u/Mugen87)\
**Post date:** [May 30, 2022, 9:07pm UTC](https://discourse.threejs.org/t/onbeforecompile-fires-only-once-although-i-set-needsupdate-true/38614/5 "2022-05-30T21:07:46Z")

</div>

> [@trusktr](#):
>
> `material.clone()` will not include the `customProgramCacheKey` for the cloned material?

Yes, the same principles for `onBeforeCompile()` also apply to `customProgramCacheKey()`.
