From 261d97d2d303bce04a2ac34711104f7fd037299a Mon Sep 17 00:00:00 2001 From: Mr Robot Date: Fri, 2 Oct 2026 18:51:48 +0200 Subject: [PATCH 1/6] Honor PBR sunlight exposure inputs --- .../pbrlighting/PBRLightingUtils.glsllib | 10 +- .../com/jme3/material/PbrSunExposureTest.java | 113 ++++++++++++++++++ 2 files changed, 118 insertions(+), 5 deletions(-) create mode 100644 jme3-core/src/test/java/com/jme3/material/PbrSunExposureTest.java diff --git a/jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib index ddd42c08cd..12a642951b 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib @@ -48,6 +48,7 @@ #ifdef ENABLE_PBRLightingUtils_readPBRSurface varying vec4 Color; + vec2 newTexCoord; uniform float m_Roughness; uniform float m_Metallic; @@ -138,10 +139,10 @@ #if defined(EXPOSUREMAP) uniform sampler2D m_SunLightExposureMap; #endif - #if defined(USE_VERTEX_COLORS_AS_SUN_EXPOSURE) + #if defined(USE_VERTEX_COLORS_AS_SUN_EXPOSURE) || defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY) varying vec4 vertColors; #endif - #ifdef STATIC_SUN_EXPOSURE + #if defined(STATIC_SUN_EXPOSURE) || defined(STATIC_SUN_INTENSITY) uniform float m_StaticSunIntensity; #endif @@ -151,10 +152,10 @@ #ifdef EXPOSUREMAP surface.exposure *= texture2D(m_SunLightExposureMap, newTexCoord).r; #endif - #ifdef STATIC_SUN_EXPOSURE + #if defined(STATIC_SUN_EXPOSURE) || defined(STATIC_SUN_INTENSITY) surface.exposure *= m_StaticSunIntensity; //single float value to indicate percentage of sunlight hitting the model (only suitable for small models or models with equal sunlight exposure accross the entire model #endif - #ifdef USE_VERTEX_COLORS_AS_SUN_EXPOSURE + #if defined(USE_VERTEX_COLORS_AS_SUN_EXPOSURE) || defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY) surface.exposure *= vertColors.r; // use red channel of vertexColors for non-uniform sunlighting accross a single model #endif } @@ -321,7 +322,6 @@ } #ifdef ENABLE_PBRLightingUtils_readPBRSurface - vec2 newTexCoord; void PBRLightingUtils_readPBRSurface(inout PBRSurface surface){ diff --git a/jme3-core/src/test/java/com/jme3/material/PbrSunExposureTest.java b/jme3-core/src/test/java/com/jme3/material/PbrSunExposureTest.java new file mode 100644 index 0000000000..4ab4d33ac5 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/material/PbrSunExposureTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.material; + +import com.jme3.asset.AssetManager; +import com.jme3.system.TestUtil; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.regex.Pattern; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** Checks the material-to-shader contracts for PBR sunlight exposure inputs. */ +public class PbrSunExposureTest { + + private final AssetManager assets = TestUtil.createAssetManager(); + private final Material material = new Material(assets, "Common/MatDefs/Light/PBRLighting.j3md"); + private final String fragment = (String) assets.loadAsset("Common/MatDefs/Light/PBRLighting.frag"); + + @Test + public void staticIntensityUsesTheMaterialDefine() { + for (TechniqueDef technique : material.getMaterialDef().getTechniqueDefs("Default")) { + String define = technique.getShaderParamDefine("StaticSunIntensity"); + assertGuarded(fragment, define, "uniform float m_StaticSunIntensity;"); + assertGuarded(fragment, define, "surface.exposure *= m_StaticSunIntensity;"); + } + } + + @Test + public void vertexIntensityUsesTheSameDefineInBothStages() { + String vertex = (String) assets.loadAsset("Common/MatDefs/Light/PBRLighting.vert"); + for (TechniqueDef technique : material.getMaterialDef().getTechniqueDefs("Default")) { + String define = technique.getShaderParamDefine("UseVertexColorsAsSunIntensity"); + assertGuarded(vertex, define, "varying vec4 vertColors;"); + assertGuarded(vertex, define, "vertColors = inColor;"); + assertGuarded(fragment, define, "varying vec4 vertColors;"); + assertGuarded(fragment, define, "surface.exposure *= vertColors.r;"); + } + } + + @Test + public void adjustedTextureCoordinatesAreDeclaredBeforeExposureSampling() { + int declaration = fragment.indexOf("vec2 newTexCoord;"); + int sampling = fragment.indexOf("texture2D(m_SunLightExposureMap, newTexCoord)"); + assertTrue(declaration >= 0, "The adjusted texture coordinates must be declared"); + assertTrue(sampling > declaration, "The exposure helper must see the global declaration"); + } + + @Test + public void existingExposureDefinesRemainSupported() { + assertGuarded(fragment, "STATIC_SUN_EXPOSURE", "uniform float m_StaticSunIntensity;"); + assertGuarded(fragment, "STATIC_SUN_EXPOSURE", "surface.exposure *= m_StaticSunIntensity;"); + assertGuarded(fragment, "USE_VERTEX_COLORS_AS_SUN_EXPOSURE", "varying vec4 vertColors;"); + assertGuarded(fragment, "USE_VERTEX_COLORS_AS_SUN_EXPOSURE", "surface.exposure *= vertColors.r;"); + } + + private static void assertGuarded(String source, String define, String statement) { + Pattern name = Pattern.compile("\\b" + Pattern.quote(define) + "\\b"); + Deque conditions = new ArrayDeque<>(); + int found = 0; + for (String line : source.split("\\n")) { + String trimmed = line.trim(); + if (trimmed.startsWith("#if")) { + conditions.push(trimmed); + } else if (trimmed.startsWith("#elif")) { + conditions.pop(); + conditions.push(trimmed); + } else if (trimmed.startsWith("#endif")) { + conditions.pop(); + } + if (trimmed.contains(statement)) { + found++; + boolean guarded = false; + for (String condition : conditions) { + guarded |= name.matcher(condition).find(); + } + assertTrue(guarded, statement + " must be guarded by " + define); + } + } + assertEquals(1, found, "Expected one shader statement: " + statement); + } +} From ea7a0423aa4e366b3fc6b12acabeb8b2cf6565be Mon Sep 17 00:00:00 2001 From: Mr Robot Date: Fri, 2 Oct 2026 20:48:04 +0200 Subject: [PATCH 2/6] Support standalone sunlight exposure and render it in integration tests --- .../pbrlighting/PBRLightingUtils.glsllib | 15 +- .../light/pbr/TestPbrSunExposure.java | 181 ++++++++++++++++++ .../TestMatDefs/PbrSunExposureModule.frag | 11 ++ .../TestMatDefs/PbrSunExposureModule.j3md | 17 ++ 4 files changed, 217 insertions(+), 7 deletions(-) create mode 100644 jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPbrSunExposure.java create mode 100644 jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/TestMatDefs/PbrSunExposureModule.frag create mode 100644 jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/TestMatDefs/PbrSunExposureModule.j3md diff --git a/jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib index 12a642951b..8122f21455 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib @@ -1,5 +1,7 @@ #ifndef __PBR_LIGHT_UTILS_MODULE__ #define __PBR_LIGHT_UTILS_MODULE__ + + vec2 newTexCoord; #import "Common/ShaderLib/Math.glsllib" #import "Common/ShaderLib/PBR.glsllib" @@ -33,7 +35,7 @@ varying vec4 wTangent; #endif - #if defined(ENABLE_PBRLightingUtils_readPBRSurface)||defined(ENABLE_PBRLightingUtils_getTexCoord) + #if defined(ENABLE_PBRLightingUtils_readPBRSurface)||defined(ENABLE_PBRLightingUtils_getTexCoord)||defined(EXPOSUREMAP) varying vec2 texCoord; #ifdef SEPARATE_TEXCOORD varying vec2 texCoord2; @@ -48,7 +50,6 @@ #ifdef ENABLE_PBRLightingUtils_readPBRSurface varying vec4 Color; - vec2 newTexCoord; uniform float m_Roughness; uniform float m_Metallic; @@ -149,7 +150,10 @@ void PBRLightingUtils_readSunLightExposureParams(inout PBRSurface surface){ surface.exposure = 1.0; //default value - #ifdef EXPOSUREMAP + #ifdef EXPOSUREMAP + #ifndef ENABLE_PBRLightingUtils_readPBRSurface + newTexCoord = texCoord; + #endif surface.exposure *= texture2D(m_SunLightExposureMap, newTexCoord).r; #endif #if defined(STATIC_SUN_EXPOSURE) || defined(STATIC_SUN_INTENSITY) @@ -346,6 +350,7 @@ #endif + newTexCoord = texCoord; #if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) if (surface.hasTangents) { vec3 vViewDir = surface.viewDir * surface.tbnMat; @@ -366,11 +371,7 @@ newTexCoord = classicParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight); #endif #endif - } else { - newTexCoord = texCoord; } - #else - newTexCoord = texCoord; #endif #ifdef BASECOLORMAP diff --git a/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPbrSunExposure.java b/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPbrSunExposure.java new file mode 100644 index 0000000000..e7b289258e --- /dev/null +++ b/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPbrSunExposure.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2009-2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.light.pbr; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.scene.Geometry; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.shape.Quad; +import com.jme3.system.AppSettings; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import com.jme3.texture.image.ColorSpace; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import org.jmonkeyengine.screenshottests.testframework.TestContainingApp; +import org.jmonkeyengine.screenshottests.testframework.desktop.DesktopRunner; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; + +/** Compiles and renders exposure variants on both desktop and ANGLE CI jobs. */ +@Tag("integration") +public class TestPbrSunExposure { + + @Test + public void sunlightInputsRenderExpectedExposure() throws Exception { + CompletableFuture result = new CompletableFuture<>(); + CountDownLatch finished = new CountDownLatch(1); + final TestContainingApp app = new TestContainingApp() { + @Override + public void simpleInitApp() { + try { + verifyExposure(renderManager, assetManager); + result.complete(null); + } catch (Throwable failure) { + result.completeExceptionally(failure); + } finally { + finished.countDown(); + } + } + + @Override + public void handleError(String message, Throwable failure) { + result.completeExceptionally(failure == null ? new AssertionError(message) : failure); + stop(); + finished.countDown(); + } + }; + AppSettings settings = new AppSettings(true); + settings.setResolution(16, 16); + settings.setAudioRenderer(null); + settings.setUseInput(false); + settings.setRenderer(System.getProperty("jme.screenshot.renderer", AppSettings.LWJGL_OPENGL45)); + app.setSettings(settings); + app.setShowSettings(false); + new DesktopRunner().runApplicationUntilScenarioCompletes(app, finished); + result.get(1, TimeUnit.SECONDS); + } + + static void verifyExposure(RenderManager manager, AssetManager assets) { + Renderer renderer = manager.getRenderer(); + renderer.setMainFrameBufferSrgb(false); + renderer.setLinearizeSrgbImages(false); + Camera camera = new Camera(16, 16); + camera.setParallelProjection(true); + camera.setFrustum(0.1f, 10, -1, 1, 1, -1); + camera.setLocation(new Vector3f(0, 0, 2)); + camera.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + manager.setCamera(camera, false); + FrameBuffer target = new FrameBuffer(16, 16, 1); + target.setColorBuffer(Image.Format.RGBA8); + target.setDepthBuffer(Image.Format.Depth24); + try { + renderer.setFrameBuffer(target); + for (String variant : new String[]{"static", "vertex", "map", "parallax", "no-tangents", + "module-map", "module-control"}) { + boolean module = variant.startsWith("module"); + Material material = new Material(assets, module + ? "TestMatDefs/PbrSunExposureModule.j3md" : "Common/MatDefs/Light/PBRLighting.j3md"); + if (!module) { + material.setInt("DebugValuesMode", 6); + material.setBoolean("UseSpecularAA", false); + } + material.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off); + Geometry geometry = new Geometry("exposure", new Quad(2, 2)); + geometry.move(-1, -1, 0); + geometry.setMaterial(material); + if (variant.equals("static")) { + material.setFloat("StaticSunIntensity", 0.25f); + } else if (variant.equals("vertex")) { + material.setBoolean("UseVertexColorsAsSunIntensity", true); + geometry.getMesh().setBuffer(VertexBuffer.Type.Color, 4, new float[]{ + 0.25f, 0, 0, 1, 0.25f, 0, 0, 1, 0.25f, 0, 0, 1, 0.25f, 0, 0, 1}); + } else if (!variant.equals("module-control")) { + material.setTexture("SunLightExposureMap", exposureMap()); + } + if (variant.equals("parallax") || variant.equals("no-tangents")) { + material.setTexture("ParallaxMap", exposureMap()); + material.setFloat("ParallaxHeight", 0); + if (variant.equals("parallax")) { + geometry.getMesh().setBuffer(VertexBuffer.Type.Tangent, 4, new float[]{ + 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1}); + } + } + renderer.setBackgroundColor(ColorRGBA.Black); + renderer.clearBuffers(true, true, true); + geometry.updateGeometricState(); + manager.renderGeometry(geometry); + ByteBuffer pixels = BufferUtils.createByteBuffer(16 * 16 * 4); + renderer.readFrameBufferWithFormat(target, pixels, Image.Format.RGBA8); + int left = variant.equals("module-control") ? 255 : 64; + int right = variant.equals("static") || variant.equals("vertex") ? 64 + : variant.equals("module-control") ? 255 : 192; + assertPixel(pixels, 4, left, variant); + assertPixel(pixels, 12, right, variant); + } + } finally { + renderer.setFrameBuffer(null); + renderer.deleteFrameBuffer(target); + } + } + + private static Texture2D exposureMap() { + ByteBuffer data = BufferUtils.createByteBuffer(8); + data.put(new byte[]{64, 64, 64, (byte) 255, (byte) 192, (byte) 192, (byte) 192, (byte) 255}).flip(); + Texture2D texture = new Texture2D(new Image(Image.Format.RGBA8, 2, 1, data, ColorSpace.Linear)); + texture.setMinFilter(Texture.MinFilter.NearestNoMipMaps); + texture.setMagFilter(Texture.MagFilter.Nearest); + return texture; + } + + private static void assertPixel(ByteBuffer pixels, int x, int expected, String variant) { + int offset = (8 * 16 + x) * 4; + for (int channel = 0; channel < 3; channel++) { + assertEquals(expected, pixels.get(offset + channel) & 255, 1, variant + " channel " + channel); + } + assertEquals(255, pixels.get(offset + 3) & 255, variant + " alpha"); + } +} diff --git a/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/TestMatDefs/PbrSunExposureModule.frag b/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/TestMatDefs/PbrSunExposureModule.frag new file mode 100644 index 0000000000..ba1e9db313 --- /dev/null +++ b/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/TestMatDefs/PbrSunExposureModule.frag @@ -0,0 +1,11 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#define ENABLE_PBRLightingUtils_getWorldPosition 1 +#define ENABLE_PBRLightingUtils_getWorldNormal 1 +#define ENABLE_PBRLightingUtils_computeDirectLight 1 +#import "Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib" + +void main() { + PBRSurface surface; + PBRLightingUtils_readSunLightExposureParams(surface); + gl_FragColor = vec4(vec3(surface.exposure), 1.0); +} diff --git a/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/TestMatDefs/PbrSunExposureModule.j3md b/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/TestMatDefs/PbrSunExposureModule.j3md new file mode 100644 index 0000000000..5366767848 --- /dev/null +++ b/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/TestMatDefs/PbrSunExposureModule.j3md @@ -0,0 +1,17 @@ +MaterialDef PbrSunExposureModule { + MaterialParameters { + Texture2D SunLightExposureMap + } + Technique { + VertexShader GLSL310 GLSL300 GLSL150: Common/MatDefs/Light/PBRLighting.vert + FragmentShader GLSL310 GLSL300 GLSL150: TestMatDefs/PbrSunExposureModule.frag + WorldParameters { + WorldViewProjectionMatrix + WorldMatrix + NormalMatrix + } + Defines { + EXPOSUREMAP: SunLightExposureMap + } + } +} From 9bb563ee3f183ae791b8ccc1da6a818cba47e0ba Mon Sep 17 00:00:00 2001 From: Mr Robot Date: Sat, 3 Oct 2026 00:06:26 +0200 Subject: [PATCH 3/6] Remove shader source-text tests superseded by pixel coverage --- .../com/jme3/material/PbrSunExposureTest.java | 113 ------------------ 1 file changed, 113 deletions(-) delete mode 100644 jme3-core/src/test/java/com/jme3/material/PbrSunExposureTest.java diff --git a/jme3-core/src/test/java/com/jme3/material/PbrSunExposureTest.java b/jme3-core/src/test/java/com/jme3/material/PbrSunExposureTest.java deleted file mode 100644 index 4ab4d33ac5..0000000000 --- a/jme3-core/src/test/java/com/jme3/material/PbrSunExposureTest.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (c) 2026 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.material; - -import com.jme3.asset.AssetManager; -import com.jme3.system.TestUtil; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.regex.Pattern; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** Checks the material-to-shader contracts for PBR sunlight exposure inputs. */ -public class PbrSunExposureTest { - - private final AssetManager assets = TestUtil.createAssetManager(); - private final Material material = new Material(assets, "Common/MatDefs/Light/PBRLighting.j3md"); - private final String fragment = (String) assets.loadAsset("Common/MatDefs/Light/PBRLighting.frag"); - - @Test - public void staticIntensityUsesTheMaterialDefine() { - for (TechniqueDef technique : material.getMaterialDef().getTechniqueDefs("Default")) { - String define = technique.getShaderParamDefine("StaticSunIntensity"); - assertGuarded(fragment, define, "uniform float m_StaticSunIntensity;"); - assertGuarded(fragment, define, "surface.exposure *= m_StaticSunIntensity;"); - } - } - - @Test - public void vertexIntensityUsesTheSameDefineInBothStages() { - String vertex = (String) assets.loadAsset("Common/MatDefs/Light/PBRLighting.vert"); - for (TechniqueDef technique : material.getMaterialDef().getTechniqueDefs("Default")) { - String define = technique.getShaderParamDefine("UseVertexColorsAsSunIntensity"); - assertGuarded(vertex, define, "varying vec4 vertColors;"); - assertGuarded(vertex, define, "vertColors = inColor;"); - assertGuarded(fragment, define, "varying vec4 vertColors;"); - assertGuarded(fragment, define, "surface.exposure *= vertColors.r;"); - } - } - - @Test - public void adjustedTextureCoordinatesAreDeclaredBeforeExposureSampling() { - int declaration = fragment.indexOf("vec2 newTexCoord;"); - int sampling = fragment.indexOf("texture2D(m_SunLightExposureMap, newTexCoord)"); - assertTrue(declaration >= 0, "The adjusted texture coordinates must be declared"); - assertTrue(sampling > declaration, "The exposure helper must see the global declaration"); - } - - @Test - public void existingExposureDefinesRemainSupported() { - assertGuarded(fragment, "STATIC_SUN_EXPOSURE", "uniform float m_StaticSunIntensity;"); - assertGuarded(fragment, "STATIC_SUN_EXPOSURE", "surface.exposure *= m_StaticSunIntensity;"); - assertGuarded(fragment, "USE_VERTEX_COLORS_AS_SUN_EXPOSURE", "varying vec4 vertColors;"); - assertGuarded(fragment, "USE_VERTEX_COLORS_AS_SUN_EXPOSURE", "surface.exposure *= vertColors.r;"); - } - - private static void assertGuarded(String source, String define, String statement) { - Pattern name = Pattern.compile("\\b" + Pattern.quote(define) + "\\b"); - Deque conditions = new ArrayDeque<>(); - int found = 0; - for (String line : source.split("\\n")) { - String trimmed = line.trim(); - if (trimmed.startsWith("#if")) { - conditions.push(trimmed); - } else if (trimmed.startsWith("#elif")) { - conditions.pop(); - conditions.push(trimmed); - } else if (trimmed.startsWith("#endif")) { - conditions.pop(); - } - if (trimmed.contains(statement)) { - found++; - boolean guarded = false; - for (String condition : conditions) { - guarded |= name.matcher(condition).find(); - } - assertTrue(guarded, statement + " must be guarded by " + define); - } - } - assertEquals(1, found, "Expected one shader statement: " + statement); - } -} From 575f9885f66f55fb0c5451ebb66d454fdaf17968 Mon Sep 17 00:00:00 2001 From: Mr Robot Date: Sun, 4 Oct 2026 11:58:39 +0200 Subject: [PATCH 4/6] Add cross-platform PBR sunlight screenshot coverage --- .../android/light/pbr/TestPBRSunlight.java | 54 ++++ .../light/pbr/TestPBRSunlight.java | 51 ++++ .../light/pbr/ScenarioPBRSunlight.java | 250 ++++++++++++++++++ 3 files changed, 355 insertions(+) create mode 100644 jme3-screenshot-tests/jme3-screenshot-tests-android/src/androidTest/java/org/jmonkeyengine/screenshottests/android/light/pbr/TestPBRSunlight.java create mode 100644 jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPBRSunlight.java create mode 100644 jme3-screenshot-tests/jme3-screenshot-tests-shared/src/main/java/org/jmonkeyengine/screenshottests/scenarios/light/pbr/ScenarioPBRSunlight.java diff --git a/jme3-screenshot-tests/jme3-screenshot-tests-android/src/androidTest/java/org/jmonkeyengine/screenshottests/android/light/pbr/TestPBRSunlight.java b/jme3-screenshot-tests/jme3-screenshot-tests-android/src/androidTest/java/org/jmonkeyengine/screenshottests/android/light/pbr/TestPBRSunlight.java new file mode 100644 index 0000000000..16ea8777b4 --- /dev/null +++ b/jme3-screenshot-tests/jme3-screenshot-tests-android/src/androidTest/java/org/jmonkeyengine/screenshottests/android/light/pbr/TestPBRSunlight.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.android.light.pbr; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import org.jmonkeyengine.screenshottests.android.android.ScreenshotTestAndroidBase; +import org.jmonkeyengine.screenshottests.scenarios.light.pbr.ScenarioPBRSunlight; +import org.jmonkeyengine.screenshottests.testframework.AndroidRunner; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Runs the same sunlight exposure charts on Android GLES. */ +@RunWith(AndroidJUnit4.class) +public class TestPBRSunlight extends ScreenshotTestAndroidBase { + + @Test + public void testSunlightInputs() { + ScenarioPBRSunlight.testSunlightInputs().run(new AndroidRunner()); + } + + @Test + public void testSunlightParallax() { + ScenarioPBRSunlight.testSunlightParallax().run(new AndroidRunner()); + } +} diff --git a/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPBRSunlight.java b/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPBRSunlight.java new file mode 100644 index 0000000000..6a068b11af --- /dev/null +++ b/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPBRSunlight.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.light.pbr; + +import org.jmonkeyengine.screenshottests.scenarios.light.pbr.ScenarioPBRSunlight; +import org.jmonkeyengine.screenshottests.testframework.desktop.DesktopRunner; +import org.jmonkeyengine.screenshottests.testframework.desktop.ScreenshotTestDesktopBase; +import org.junit.jupiter.api.Test; + +/** Sunlight exposure reference images for desktop OpenGL and ANGLE. */ +public class TestPBRSunlight extends ScreenshotTestDesktopBase { + + @Test + public void testSunlightInputs() { + ScenarioPBRSunlight.testSunlightInputs().run(new DesktopRunner()); + } + + @Test + public void testSunlightParallax() { + ScenarioPBRSunlight.testSunlightParallax().run(new DesktopRunner()); + } +} diff --git a/jme3-screenshot-tests/jme3-screenshot-tests-shared/src/main/java/org/jmonkeyengine/screenshottests/scenarios/light/pbr/ScenarioPBRSunlight.java b/jme3-screenshot-tests/jme3-screenshot-tests-shared/src/main/java/org/jmonkeyengine/screenshottests/scenarios/light/pbr/ScenarioPBRSunlight.java new file mode 100644 index 0000000000..1700993aed --- /dev/null +++ b/jme3-screenshot-tests/jme3-screenshot-tests-shared/src/main/java/org/jmonkeyengine/screenshottests/scenarios/light/pbr/ScenarioPBRSunlight.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.scenarios.light.pbr; + +import static org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase.screenshotTest; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import com.jme3.texture.image.ColorSpace; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTest; + +/** + * Deterministic sunlight-exposure charts using the production PBR material. + * Each chart has exposure debug output (mode 6) above actual sunlit PBR output. + * No external assets, probes, shadows, post-processing or asynchronous baking + * are needed, so the same scene can run on desktop OpenGL, ANGLE and Android. + */ +public final class ScenarioPBRSunlight { + + private enum Input { + DEFAULT, ZERO, STATIC, VERTEX, MAP, COMBINED + } + + private ScenarioPBRSunlight() { + } + + /** + * Columns, left to right: no exposure parameters, static zero, static 0.5, + * vertex red, map red, and all three inputs multiplied together. + * + *

The vertex red channel rises from 0.25 to 0.75, while the map's four + * equal-width bands contain 0, 64/255, 128/255 and 1. The combined column is + * {@code 0.5 * (0.25 + 0.5 * u) * mapRed(u)}. Other vertex/texture channels + * deliberately differ from red. Vertex colours must not tint the albedo. + * The first debug column must be white and the second black; their lit + * counterparts must be fully lit and black respectively.

+ * + * @return the screenshot test configuration + */ + public static ScreenshotTest testSunlightInputs() { + return screenshotTest(new SunlightChart(false)).setFramesToTakeScreenshotsOn(3); + } + + /** + * Columns, left to right: exposure map without parallax, with parallax and + * valid tangents, and with parallax but no tangent buffer. + * + *

All quads are tilted 45 degrees. A constant maximum-height map and a + * deliberately large nonzero height shift the middle column's exposure + * bands to the right. The outer debug columns must retain identical band + * widths. This detects both sampling the original UVs after parallax and + * attempting a parallax offset without a valid tangent basis.

+ * + * @return the screenshot test configuration + */ + public static ScreenshotTest testSunlightParallax() { + return screenshotTest(new SunlightChart(true)).setFramesToTakeScreenshotsOn(3); + } + + private static final class SunlightChart extends BaseAppState { + private final boolean parallax; + private final Node chart = new Node("sunlight exposure chart"); + private final int columns; + + private SunlightChart(boolean parallax) { + this.parallax = parallax; + columns = parallax ? 3 : Input.values().length; + } + + @Override + protected void initialize(Application app) { + Camera camera = app.getCamera(); + camera.setParallelProjection(true); + camera.setLocation(new Vector3f(0, 0, 30)); + camera.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + updateFrustum(camera); + + // Keep debug values linear, including the procedural exposure map. + app.getRenderer().setMainFrameBufferSrgb(false); + app.getRenderer().setLinearizeSrgbImages(false); + app.getViewPort().setBackgroundColor(new ColorRGBA(0.06f, 0.08f, 0.12f, 1)); + + DirectionalLight sunlight = new DirectionalLight(); + sunlight.setDirection(new Vector3f(0, 0, -1)); + sunlight.setColor(new ColorRGBA(1.5f, 1.5f, 1.5f, 1)); + chart.addLight(sunlight); + ((SimpleApplication) app).getRootNode().attachChild(chart); + + Texture2D exposureMap = redChannelMap(0, 64, 128, 255); + Texture2D heightMap = redChannelMap(255); + for (int row = 0; row < 2; row++) { + for (int column = 0; column < columns; column++) { + Geometry tile = createTile(app.getAssetManager(), row == 0); + tile.setLocalTranslation(2 * column - columns + 1, 1 - 2 * row, 0); + if (parallax) { + configureParallax(tile, column, exposureMap, heightMap); + } else { + configureInput(tile, Input.values()[column], exposureMap); + } + chart.attachChild(tile); + } + } + } + + @Override + public void update(float tpf) { + // Android surface resizes can otherwise replace the horizontal + // frustum. Preserve the complete chart at every capture resolution. + updateFrustum(getApplication().getCamera()); + } + + private void updateFrustum(Camera camera) { + camera.setFrustum(1, 100, -columns, columns, 2.4f, -2.4f); + } + + @Override + protected void cleanup(Application app) { + chart.removeFromParent(); + } + + @Override + protected void onEnable() { + } + + @Override + protected void onDisable() { + } + } + + private static Geometry createTile(AssetManager assets, boolean debug) { + Quad mesh = new Quad(1.6f, 1.5f); + // Centre the quad so that parallax columns have identical silhouettes. + mesh.setBuffer(VertexBuffer.Type.Position, 3, new float[]{ + -0.8f, -0.75f, 0, 0.8f, -0.75f, 0, + 0.8f, 0.75f, 0, -0.8f, 0.75f, 0 + }); + mesh.updateBound(); + // Every tile has these colours; only VERTEX and COMBINED enable them. + mesh.setBuffer(VertexBuffer.Type.Color, 4, new float[]{ + 0.25f, 1, 0, 1, 0.75f, 0, 1, 1, + 0.75f, 0, 1, 1, 0.25f, 1, 0, 1 + }); + + Material material = new Material(assets, "Common/MatDefs/Light/PBRLighting.j3md"); + material.setColor("BaseColor", new ColorRGBA(0.8f, 0.35f, 0.12f, 1)); + material.setFloat("Metallic", 0); + material.setFloat("Roughness", 0.8f); + material.setBoolean("UseSpecularAA", false); + if (debug) { + material.setInt("DebugValuesMode", 6); + } + Geometry tile = new Geometry(debug ? "exposure" : "sunlit PBR", mesh); + tile.setMaterial(material); + return tile; + } + + private static void configureInput(Geometry tile, Input input, Texture2D exposureMap) { + Material material = tile.getMaterial(); + if (input == Input.ZERO) { + material.setFloat("StaticSunIntensity", 0); + } else if (input == Input.STATIC || input == Input.COMBINED) { + material.setFloat("StaticSunIntensity", 0.5f); + } + if (input == Input.VERTEX || input == Input.COMBINED) { + material.setBoolean("UseVertexColorsAsSunIntensity", true); + } + if (input == Input.MAP || input == Input.COMBINED) { + material.setTexture("SunLightExposureMap", exposureMap); + } + } + + private static void configureParallax(Geometry tile, int column, + Texture2D exposureMap, Texture2D heightMap) { + tile.rotate(0, FastMath.QUARTER_PI, 0); + Material material = tile.getMaterial(); + material.setTexture("SunLightExposureMap", exposureMap); + if (column != 2) { + tile.getMesh().setBuffer(VertexBuffer.Type.Tangent, 4, new float[]{ + 1, 0, 0, 1, 1, 0, 0, 1, + 1, 0, 0, 1, 1, 0, 0, 1 + }); + } + if (column != 0) { + material.setTexture("ParallaxMap", heightMap); + material.setFloat("ParallaxHeight", 0.75f); + material.setBoolean("SteepParallax", false); + } + } + + private static Texture2D redChannelMap(int... redValues) { + ByteBuffer pixels = BufferUtils.createByteBuffer(redValues.length * 4); + for (int red : redValues) { + // Non-red channels are intentionally unsuitable as exposure data. + pixels.put((byte) red).put((byte) (255 - red)).put((byte) 32).put((byte) 255); + } + pixels.flip(); + Texture2D texture = new Texture2D(new Image(Image.Format.RGBA8, + redValues.length, 1, pixels, ColorSpace.Linear)); + texture.setMinFilter(Texture.MinFilter.NearestNoMipMaps); + texture.setMagFilter(Texture.MagFilter.Nearest); + texture.setWrap(Texture.WrapMode.EdgeClamp); + return texture; + } +} From 2293c3dc4b402a8a7775fc0a4a530600a3fb6a8c Mon Sep 17 00:00:00 2001 From: Mr Robot Date: Sun, 4 Oct 2026 18:05:12 +0200 Subject: [PATCH 5/6] Add reviewed CI baselines for PBR sunlight screenshots --- ...br.TestPBRSunlight.testSunlightInputs_f3.png | Bin 0 -> 6351 bytes ....TestPBRSunlight.testSunlightParallax_f3.png | Bin 0 -> 6085 bytes ...br.TestPBRSunlight.testSunlightInputs_f3.png | Bin 0 -> 10021 bytes ...tPBRSunlight.testSunlightInputs_f3_angle.png | Bin 0 -> 10021 bytes ....TestPBRSunlight.testSunlightParallax_f3.png | Bin 0 -> 9856 bytes ...BRSunlight.testSunlightParallax_f3_angle.png | Bin 0 -> 9856 bytes 6 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 jme3-screenshot-tests/jme3-screenshot-tests-android/src/main/resources/org.jmonkeyengine.screenshottests.android.light.pbr.TestPBRSunlight.testSunlightInputs_f3.png create mode 100644 jme3-screenshot-tests/jme3-screenshot-tests-android/src/main/resources/org.jmonkeyengine.screenshottests.android.light.pbr.TestPBRSunlight.testSunlightParallax_f3.png create mode 100644 jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSunlight.testSunlightInputs_f3.png create mode 100644 jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSunlight.testSunlightInputs_f3_angle.png create mode 100644 jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSunlight.testSunlightParallax_f3.png create mode 100644 jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSunlight.testSunlightParallax_f3_angle.png diff --git a/jme3-screenshot-tests/jme3-screenshot-tests-android/src/main/resources/org.jmonkeyengine.screenshottests.android.light.pbr.TestPBRSunlight.testSunlightInputs_f3.png b/jme3-screenshot-tests/jme3-screenshot-tests-android/src/main/resources/org.jmonkeyengine.screenshottests.android.light.pbr.TestPBRSunlight.testSunlightInputs_f3.png new file mode 100644 index 0000000000000000000000000000000000000000..db78372b9a2c59563b57fcab45698be71061cef3 GIT binary patch literal 6351 zcmeAS@N?(olHy`uVBq!ia0y~yU>!+WWln-nTt7iA^H@JB@e}7~Z%Zw+x-`7V}wRqb7|8mlx@38^TVIcW` zyYH=i{+n}teXE_7TKl_u{XRLfqswakTU%L0RkfTn%e?mZ@#CfLGmhl1s(kb2&79lc zfBntrTG_w%?X2CqcZbXE&-n}Foc_$u&wu{y?ic@RE&jbZKL6Zb-R)1g96(u+Lzn@S zV-G<_9-k&m0zQ z0Qz>cu{s(ERPVOjF4p<=^k00j4tM#xoA2fIY!9WZkF$%fV%c#m>wN;KZMf;{abVj} zW&fYw6+rIK2lMZ@B}%k?{MuJ2am+kAy`T56!93fNZ#QoBeY~=_F!%W_qwMqbcQ=0g zvr+ndS=KJ@wB0}NJeECMR@S(3^W4f^-0_!#*FVfnvo<{vduH3e`L?#V&)3(RrJvaXS=rYbbHkxxr`??CCa)atQXLP7w zbf{pM4;73Kjt#Fym)W{+e16m9KqH literal 0 HcmV?d00001 diff --git a/jme3-screenshot-tests/jme3-screenshot-tests-android/src/main/resources/org.jmonkeyengine.screenshottests.android.light.pbr.TestPBRSunlight.testSunlightParallax_f3.png b/jme3-screenshot-tests/jme3-screenshot-tests-android/src/main/resources/org.jmonkeyengine.screenshottests.android.light.pbr.TestPBRSunlight.testSunlightParallax_f3.png new file mode 100644 index 0000000000000000000000000000000000000000..b8e029c673868e07dfdbce72a6d69291bca546bd GIT binary patch literal 6085 zcmeAS@N?(olHy`uVBq!ia0y~yU(AgYD40Yr^T zjt0W8O$j$HzhTt=_3O_J+lK+yUzdJsabEjvZ*ouJ?z?IJ7FSNs-@jS?z`C&A!S^p_ zZBNa55Z7 zVsQZai-AL!fe}b4_%N^lsRjc^0g&jSQOVIj7)=T2*}*#Z`RU|u;CeAZK5n#eIogN@ z6_!-*N!Xs1+puW;`Tm>2d|!Y4e*T8Dz3T7QYv20}kKO)eQ+;URz5jF5{TpJ}mM!0> zynS>2``a(W0=M5<8}(MNXXCxx52KB@(VR*BL^*8xO30%FGB0wg=h-qcMD&af8H_gk zM+c9nUbPKiDN=Up-1Osb1VJ_S0lPh;-JQ`s-)P5|<2V0}G@ZR?_iwZT)RPv zB#K&B=TLDijx7WfM9F&9+Mc`!5}id6L9Xpp%Y@lp+oSFE<+ygO+aH?n_p+GhkIN;O zOYVD5p6B=d{yxuh9+iy$z|AfwWpky?XtOn)^ZJNG7!Z zX3IA9!$XBl-1Y_a+0c$fNqf}4CZ$~GLhRT6#hbC!wVq9)Q+mgVyxMvLw_xr`>VY9) zhbDZtN@SKk>d{s4>Bm@&#&@2$W>&bV8`Jq>S=6URjrJuu6M|6RDp8J-Y($z&{@<`W z713GZ^kw;<)bm}aR7M=AFc_nO%eXBx$P4VO7{O~ZMnf&X2)i1*-aaOg|wIFmaqnY}|*?~BTQUsyT9{%B9WTW!xxt~dEk zIC7JfUzJriE#_#4^(AA=4i)ldY6JRhbr?4#axV{j91J^{aPe9ImKrEBpu7TO0hS8F z1c*@}K!WHDng-A={T~{|IQ)s6-uv3YAv?^Dzy*#sC##zW)<`TD(k+=}n`(|t#Jlv? z49bbMiZ?&&T56TLjt^Ox7J@Y3hUr3Vg~)o%D)Ib`F+7ca^V~fG;%VfLcU8MHZsZ3B zN0q)djnWZ>oxK>x74;&VtNHtaTTtIgLM5IXp1s{*nOl?kIO=*P4*PJX9o3Ufsz^I} zog7ywXDFTlRopE?Py-n>zz1DL2rPH?i=gRaUNLekC3 zzEXxhK5dbVQyj!?)5v1hS4hiZ{N{TJ7I9>0zI{Ae>XzGMuC6?ew?-06bzfM@_dYVB z_7IyuTN%XPgO8fwGSq8`oDI-l>e+Ak7tRMidot?l8hUGoR={Z7#%Qoz{+|V zAcDHX1yvcC1TcxjHVX(CAbx>x`pUi~i0+`10DacWd-1>|fJyvkCPCC?L+WU3BRFpL e$|1AZk|m$E=NFRQ#qr~BEa;Q%#a$m)*8L3uWA5Al literal 0 HcmV?d00001 diff --git a/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSunlight.testSunlightInputs_f3_angle.png b/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSunlight.testSunlightInputs_f3_angle.png new file mode 100644 index 0000000000000000000000000000000000000000..9868eae6b2e75f8ae1625210febd4c1e1d11a219 GIT binary patch literal 10021 zcmeI2eM}Q)9LMV$4nsqxF*1pC$%)RPv zB#K&B=TLDijx7WfM9F&9+Mc`!5}id6L9Xpp%Y@lp+oSFE<+ygO+aH?n_p+GhkIN;O zOYVD5p6B=d{yxuh9+iy$z|AfwWpky?XtOn)^ZJNG7!Z zX3IA9!$XBl-1Y_a+0c$fNqf}4CZ$~GLhRT6#hbC!wVq9)Q+mgVyxMvLw_xr`>VY9) zhbDZtN@SKk>d{s4>Bm@&#&@2$W>&bV8`Jq>S=6URjrJuu6M|6RDp8J-Y($z&{@<`W z713GZ^kw;<)bm}aR7M=AFc_nO%eXBx$P4VO7{O~ZMnf&X2)i1*-aaOg|wIFmaqnY}|*?~BTQUsyT9{%B9WTW!xxt~dEk zIC7JfUzJriE#_#4^(AA=4i)ldY6JRhbr?4#axV{j91J^{aPe9ImKrEBpu7TO0hS8F z1c*@}K!WHDng-A={T~{|IQ)s6-uv3YAv?^Dzy*#sC##zW)<`TD(k+=}n`(|t#Jlv? z49bbMiZ?&&T56TLjt^Ox7J@Y3hUr3Vg~)o%D)Ib`F+7ca^V~fG;%VfLcU8MHZsZ3B zN0q)djnWZ>oxK>x74;&VtNHtaTTtIgLM5IXp1s{*nOl?kIO=*P4*PJX9o3Ufsz^I} zog7ywXDFTlRopE?Py-n>zz1DL2rPH?i=gRaUNLekC3 zzEXxhK5dbVQyj!?)5v1hS4hiZ{N{TJ7I9>0zI{Ae>XzGMuC6?ew?-06bzfM@_dYVB z_7IyuTN%XPgO8fwGSq8`oDI-l>e+Ak7tRMidot?l8hUGoR={Z7#%Qoz{+|V zAcDHX1yvcC1TcxjHVX(CAbx>x`pUi~i0+`10DacWd-1>|fJyvkCPCC?L+WU3BRFpL e$|1AZk|m$E=NFRQ#qr~BEa;Q%#a$m)*8L3uWA5Al literal 0 HcmV?d00001 diff --git a/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSunlight.testSunlightParallax_f3.png b/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSunlight.testSunlightParallax_f3.png new file mode 100644 index 0000000000000000000000000000000000000000..85b79db2c9ea70dc8f13179b6b65d05ac1026524 GIT binary patch literal 9856 zcmeAS@N?(olHy`uVBq!ia0y~yVEh8a6FAs_A`zP>9b;fnbN6&{45^5Fd)0B(21`-4 zhulpZd{PHDHfjGn*!%TdX`FiV`h`xH7BZbzxxD?spFiKu*ZSxJ? z$tsl&?6#hh*hiTd;Sf`Pp}wwe+pn*$ubb`v|L=F0wS4)BBh&H@^7-am`1Z80-6!YJ z5w)D0LtB0qwqLe-p>!dANmdSBK%vGP`#ZI(6?h${sR7+`OwDJve7VKiX1yez zZF51Edv8x@S2N4_{R!wQv&1A}Gqc27e|H?tlr0fV-t@KxXnFK5R%?@uj<=_ST*WCo zd&5S~%kPfM`n_JzEipOwImq%$x;GYf^4{8K19X+|j2kVzzB9_~-xWl$#~zuHGa4kL zK{A?3Ml;T6Su$F@j@CG%mGEf0WVER^+Ik&r(2w?UM!S8ZJ>kJOIPi8?^E?&Le>?c9 zCaLhhSfK9tsbP3Bcf4Eh|IeSCul@V~rq2HV=iBwQ|9-FEpMMxpFl{_6iWt$jF{u|i zKqH-Ogw%+6Q_u<@#8KOp0vmuiHp2%ocp_~b^AJ8r)2VwCHo#)2CX5&_I>r}{7=%gk zHiHeI^qf30>LmE6+Gr?^rlrvwH(D-@7L}tF3T*CVwAvkQoQ$@YMq8DmZST<@3Ut*zm8r9NhKbrZp*U4P`m!rz;yYi$()t}J8lboFyt=akR{ E0ACNyvj6}9 literal 0 HcmV?d00001 diff --git a/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSunlight.testSunlightParallax_f3_angle.png b/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSunlight.testSunlightParallax_f3_angle.png new file mode 100644 index 0000000000000000000000000000000000000000..85b79db2c9ea70dc8f13179b6b65d05ac1026524 GIT binary patch literal 9856 zcmeAS@N?(olHy`uVBq!ia0y~yVEh8a6FAs_A`zP>9b;fnbN6&{45^5Fd)0B(21`-4 zhulpZd{PHDHfjGn*!%TdX`FiV`h`xH7BZbzxxD?spFiKu*ZSxJ? z$tsl&?6#hh*hiTd;Sf`Pp}wwe+pn*$ubb`v|L=F0wS4)BBh&H@^7-am`1Z80-6!YJ z5w)D0LtB0qwqLe-p>!dANmdSBK%vGP`#ZI(6?h${sR7+`OwDJve7VKiX1yez zZF51Edv8x@S2N4_{R!wQv&1A}Gqc27e|H?tlr0fV-t@KxXnFK5R%?@uj<=_ST*WCo zd&5S~%kPfM`n_JzEipOwImq%$x;GYf^4{8K19X+|j2kVzzB9_~-xWl$#~zuHGa4kL zK{A?3Ml;T6Su$F@j@CG%mGEf0WVER^+Ik&r(2w?UM!S8ZJ>kJOIPi8?^E?&Le>?c9 zCaLhhSfK9tsbP3Bcf4Eh|IeSCul@V~rq2HV=iBwQ|9-FEpMMxpFl{_6iWt$jF{u|i zKqH-Ogw%+6Q_u<@#8KOp0vmuiHp2%ocp_~b^AJ8r)2VwCHo#)2CX5&_I>r}{7=%gk zHiHeI^qf30>LmE6+Gr?^rlrvwH(D-@7L}tF3T*CVwAvkQoQ$@YMq8DmZST<@3Ut*zm8r9NhKbrZp*U4P`m!rz;yYi$()t}J8lboFyt=akR{ E0ACNyvj6}9 literal 0 HcmV?d00001 From 5e47ab5e493fb342b0fade1b0676318fa00dee39 Mon Sep 17 00:00:00 2001 From: Mr Robot Date: Sun, 4 Oct 2026 18:09:24 +0200 Subject: [PATCH 6/6] Dispose PBR test framebuffer through its object manager --- .../screenshottests/light/pbr/TestPbrSunExposure.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPbrSunExposure.java b/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPbrSunExposure.java index e7b289258e..6705c71ca4 100644 --- a/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPbrSunExposure.java +++ b/jme3-screenshot-tests/jme3-screenshot-tests-desktop/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPbrSunExposure.java @@ -158,7 +158,7 @@ static void verifyExposure(RenderManager manager, AssetManager assets) { } } finally { renderer.setFrameBuffer(null); - renderer.deleteFrameBuffer(target); + target.dispose(); } }