Repository: playcanvas/engine
Pull request #7311 (2025-01-24 11:13:12)

Fix some issues with CameraFrame.enabled not working correctly
small fix


Patch:
diff --git a/examples/src/examples/graphics/ambient-occlusion.controls.mjs b/examples/src/examples/graphics/ambient-occlusion.controls.mjs
index d017196d2..a2cfed677 100644
--- a/examples/src/examples/graphics/ambient-occlusion.controls.mjs
+++ b/examples/src/examples/graphics/ambient-occlusion.controls.mjs
@@ -7,6 +7,15 @@ import * as pc from 'playcanvas';
 export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => {
     const { BindingTwoWay, BooleanInput, LabelGroup, Panel, SelectInput, SliderInput } = ReactPCUI;
     return fragment(
+        jsx(
+            LabelGroup,
+            { text: 'enabled' },
+            jsx(BooleanInput, {
+                type: 'toggle',
+                binding: new BindingTwoWay(),
+                link: { observer, path: 'data.enabled' }
+            })
+        ),
         jsx(
             Panel,
             { headerText: 'Ambient Occlusion' },
diff --git a/examples/src/examples/graphics/ambient-occlusion.example.mjs b/examples/src/examples/graphics/ambient-occlusion.example.mjs
index 7b9ac7437..9601daa65 100644
--- a/examples/src/examples/graphics/ambient-occlusion.example.mjs
+++ b/examples/src/examples/graphics/ambient-occlusion.example.mjs
@@ -146,7 +146,8 @@ assetListLoader.load(() => {
     cameraEntity.addComponent('camera', {
         clearColor: new pc.Color(0.4, 0.45, 0.5),
         nearClip: 1,
-        farClip: 600
+        farClip: 600,
+        toneMapping: pc.TONEMAP_NEUTRAL
     });
 
     // add orbit camera script
@@ -175,6 +176,9 @@ assetListLoader.load(() => {
 
     const applySettings = () => {
 
+        // enabled
+        cameraFrame.enabled = data.get('data.enabled');
+
         cameraFrame.ssao.type = data.get('data.ssao.type');
         cameraFrame.ssao.blurEnabled = data.get('data.ssao.blurEnabled');
         cameraFrame.ssao.intensity = data.get('data.ssao.intensity');
@@ -215,6 +219,7 @@ assetListLoader.load(() => {
 
     // initial settings
     data.set('data', {
+        enabled: true,
         ssao: {
             type: pc.SSAOTYPE_LIGHTING,
             blurEnabled: true,
diff --git a/examples/src/examples/graphics/post-processing.controls.mjs b/examples/src/examples/graphics/post-processing.controls.mjs
index 72ac1d7f8..90d9487ed 100644
--- a/examples/src/examples/graphics/post-processing.controls.mjs
+++ b/examples/src/examples/graphics/post-processing.controls.mjs
@@ -7,6 +7,15 @@ import * as pc from 'playcanvas';
 export const controls = ({ observer, ReactPCUI, React, jsx, fragment }) => {
     const { BindingTwoWay, BooleanInput, LabelGroup, Panel, SelectInput, SliderInput } = ReactPCUI;
     return fragment(
+        jsx(
+            LabelGroup,
+            { text: 'enabled' },
+            jsx(BooleanInput, {
+                type: 'toggle',
+                binding: new BindingTwoWay(),
+                link: { observer, path: 'data.enabled' }
+            })
+        ),
         jsx(
             Panel,
             { headerText: 'Scene Rendering' },
diff --git a/examples/src/examples/graphics/post-processing.example.mjs b/examples/src/examples/graphics/post-processing.example.mjs
index 0368d8565..86d505665 100644
--- a/examples/src/examples/graphics/post-processing.example.mjs
+++ b/examples/src/examples/graphics/post-processing.example.mjs
@@ -237,6 +237,9 @@ assetListLoader.load(() => {
             material.update();
         });
 
+        // enabled
+        cameraFrame.enabled = data.get('data.enabled');
+
         // Scene
         cameraFrame.rendering.renderTargetScale = data.get('data.scene.scale');
         cameraFrame.rendering.toneMapping = data.get('data.scene.tonemapping');
@@ -283,6 +286,7 @@ assetListLoader.load(() => {
 
     // set initial values
     data.set('data', {
+        enabled: true,
         scene: {
             scale: 1.8,
             background: 6,
diff --git a/src/core/preprocessor.js b/src/core/preprocessor.js
index 9c34bb979..de80adeae 100644
--- a/src/core/preprocessor.js
+++ b/src/core/preprocessor.js
@@ -102,6 +102,14 @@ class Preprocessor {
             }
         });
 
+        // extract defines with name starting with __INJECT_
+        const injectDefines = new Map();
+        defines.forEach((value, key) => {
+            if (key.startsWith('__INJECT_')) {
+                injectDefines.set(key, value);
+            }
+        });
+
         // strip comments again after the includes have been resolved
         source = this.stripComments(source);
 
@@ -111,6 +119,9 @@ class Preprocessor {
         // process array sizes
         source = this.processArraySize(source, intDefines);
 
+        // inject defines
+        source = this.injectDefines(source, injectDefines);
+
         return source;
     }
 
@@ -131,6 +142,28 @@ class Preprocessor {
         return source;
     }
 
+    static injectDefines(source, injectDefines) {
+
+        if (source !== null && injectDefines.size > 0) {
+
+            // replace all instances of the injected defines with the value itself
+            const lines = source.split('\n');
+            injectDefines.forEach((value, key) => {
+                const regex = new RegExp(`\\b${key}\\b`, 'g');
+                for (let i = 0; i < lines.length; i++) {
+
+                    // replace them on lines that do not contain a preprocessor directive (the define itself for example)
+                    if (!lines[i].includes('#')) {
+                        lines[i] = lines[i].replace(regex, value);
+                    }
+                }
+            });
+            source = lines.join('\n');
+        }
+
+        return source;
+    }
+
     static RemoveEmptyLines(source) {
 
         if (source !== null) {
diff --git a/src/extras/render-passes/camera-frame.js b/src/extras/render-passes/camera-frame.js
index beffa5b10..a705c3861 100644
--- a/src/extras/render-passes/camera-frame.js
+++ b/src/extras/render-passes/camera-frame.js
@@ -173,6 +173,9 @@ import { CameraFrameOptions, RenderPassCameraFrame } from './render-pass-camera-
  * @category Render Pass
  */
 class CameraFrame {
+    /** @private */
+    _enabled = true;
+
     /**
      * Rendering settings.
      *
@@ -303,7 +306,7 @@ class CameraFrame {
         Debug.assert(cameraComponent, 'CameraFrame: cameraComponent must be defined');
 
         this.updateOptions();
-        this.enabled = true;
+        this.enable();
     }
 
     /**
@@ -314,40 +317,44 @@ class CameraFrame {
     }
 
     enable() {
-        if (!this.renderPassCamera) {
-            const cameraComponent = this.cameraComponent;
-            this.renderPassCamera = new RenderPassCameraFrame(this.app, cameraComponent, this.options);
-            cameraComponent.renderPasses = [this.renderPassCamera];
-        }
+        Debug.assert(!this.renderPassCamera);
+
+        const cameraComponent = this.cameraComponent;
+        this.renderPassCamera = new RenderPassCameraFrame(this.app, cameraComponent, this.options);
+        cameraComponent.renderPasses = [this.renderPassCamera];
     }
 
     disable() {
-        if (this.renderPassCamera) {
-            const cameraComponent = this.cameraComponent;
-            cameraComponent.renderPasses?.forEach((renderPass) => {
-                renderPass.destroy();
-            });
-            cameraComponent.renderPasses = [];
-            cameraComponent.rendering = null;
-
-            cameraComponent.jitter = 0;
-
-            // no longer HDR rendering
-            cameraComponent.gammaCorrection = GAMMA_SRGB;
-        }
+        Debug.assert(this.renderPassCamera);
+
+        const cameraComponent = this.cameraComponent;
+        cameraComponent.renderPasses?.forEach((renderPass) => {
+            renderPass.destroy();
+        });
+        cameraComponent.renderPasses = [];
+        cameraComponent.rendering = null;
+
+        cameraComponent.jitter = 0;
+
+        // no longer HDR rendering
+        cameraComponent.gammaCorrection = GAMMA_SRGB;
+
+        this.renderPassCamera = null;
     }
 
     /**
-     * Sets the enabled state of the camera frame. This disabled the render passes, and releases
-     * any resources.
+     * Sets the enabled state of the camera frame. Passing false will release associated resources.
      *
      * @type {boolean}
      */
     set enabled(value) {
-        if (value) {
-            this.enable();
-        } else {
-            this.disable();
+        if (this._enabled !== value) {
+            if (value) {
+                this.enable();
+            } else {
+                this.disable();
+            }
+            this._enabled = value;
         }
     }
 
@@ -357,7 +364,7 @@ class CameraFrame {
      * @type {boolean}
      */
     get enabled() {
-        return this.renderPassCamera !== null;
+        return this._enabled;
     }
 
     updateOptions() {
@@ -382,7 +389,7 @@ class CameraFrame {
      */
     update() {
 
-        if (!this.enabled) return;
+        if (!this._enabled) return;
 
         const cameraComponent = this.cameraComponent;
         const { options, renderPassCamera, rendering, bloom, grading, vignette, fringing, taa, ssao } = this;
diff --git a/src/scene/shader-lib/chunks/skybox/frag/skybox.js b/src/scene/shader-lib/chunks/skybox/frag/skybox.js
index 638218b60..5921e9940 100644
--- a/src/scene/shader-lib/chunks/skybox/frag/skybox.js
+++ b/src/scene/shader-lib/chunks/skybox/frag/skybox.js
@@ -41,14 +41,14 @@ export default /* glsl */`
             #endif
 
             dir.x *= -1.0;
-            vec3 linear = SKYBOX_DECODE_FNC(textureCube(texture_cubeMap, dir));
+            vec3 linear = __INJECT_SKYBOX_DECODE_FNC(textureCube(texture_cubeMap, dir));
 
         #else // env-atlas
 
             vec3 dir = vViewDir * vec3(-1.0, 1.0, 1.0);
             vec2 uv = toSphericalUv(normalize(dir));
 
-            vec3 linear = SKYBOX_DECODE_FNC(texture2D(texture_envAtlas, mapRoughnessUv(uv, mipLevel)));
+            vec3 linear = __INJECT_SKYBOX_DECODE_FNC(texture2D(texture_envAtlas, mapRoughnessUv(uv, mipLevel)));
 
         #endif
 
diff --git a/src/scene/skybox/sky-mesh.js b/src/scene/skybox/sky-mesh.js
index d4dc6d128..c633cfa59 100644
--- a/src/scene/skybox/sky-mesh.js
+++ b/src/scene/skybox/sky-mesh.js
@@ -43,7 +43,7 @@ class SkyMesh {
         });
 
         // defines
-        material.setDefine('SKYBOX_DECODE_FNC', ChunkUtils.decodeFunc(texture.encoding));
+        material.setDefine('__INJECT_SKYBOX_DECODE_FNC', ChunkUtils.decodeFunc(texture.encoding));
         if (type !== SKYTYPE_INFINITE) material.setDefine('SKYMESH', '');
         if (texture.cubemap) material.setDefine('SKY_CUBEMAP', '');
 


Test patch:
diff --git a/test/core/preprocessor.test.mjs b/test/core/preprocessor.test.mjs
index 2d88e9067..843752fa7 100644
--- a/test/core/preprocessor.test.mjs
+++ b/test/core/preprocessor.test.mjs
@@ -16,6 +16,8 @@ describe('Preprocessor', function () {
 
     const srcData = `
         
+        #define __INJECT_COUNT 2
+        #define __INJECT_STRING hello
         #define FEATURE1
         #define FEATURE2
 
@@ -95,6 +97,9 @@ describe('Preprocessor', function () {
         #if NAME != hello
             CMP5
         #endif
+
+        TESTINJECTION __INJECT_COUNT
+        INJECTSTRING __INJECT_STRING(x)
     `;
 
     it('returns false for MORPH_A', function () {
@@ -201,4 +206,12 @@ describe('Preprocessor', function () {
         expect(Preprocessor.run(srcData, includes, { stripDefines: true }).includes('#')).to.equal(false);
     });
 
+    it('returns true for working integer injection', function () {
+        expect(Preprocessor.run(srcData, includes).includes('TESTINJECTION 2')).to.equal(true);
+    });
+
+    it('returns true for working string injection', function () {
+        expect(Preprocessor.run(srcData, includes).includes('INJECTSTRING hello(x)')).to.equal(true);
+    });
+
 });