From fac9bd2255f02fe2dc506494f3195df371267405 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Fri, 12 Jun 2026 19:34:41 -0700 Subject: [PATCH 1/5] Re-enable 4 validation tests that now pass on Babylon Native A full `--include-excluded` sweep of validation_native.js on latest master shows these four previously-quarantined tests now pass on Win32 D3D11. Their exclusion reasons (Win32 crashes / "disabled until BabylonNative fixes") are resolved, so this re-enables them. config.json-only change. - NME Multi Build (was: "Test crashes or hangs on Babylon Native") - Custom render target (was: "crashes on Win32 D3D11 plain (STATUS_BREAKPOINT)") - UV2 Morphing (was: "fails on Win32 D3D11 sweep; disabled until BabylonNative fixes") - Vertex Pulling - Morph Targets and Bones (same) Each verified passing 3x in isolation and in the normal (non-excluded) run flow. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Apps/Playground/Scripts/config.json | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/Apps/Playground/Scripts/config.json b/Apps/Playground/Scripts/config.json index 3b7389558..134fcf061 100644 --- a/Apps/Playground/Scripts/config.json +++ b/Apps/Playground/Scripts/config.json @@ -843,8 +843,6 @@ { "title": "NME Multi Build", "playgroundId": "#D8AK3Z#113", - "excludeFromAutomaticTesting": true, - "reason": "Test crashes or hangs on Babylon Native", "referenceImage": "nme-multi-build.png" }, { @@ -1265,8 +1263,6 @@ { "title": "Custom render target", "playgroundId": "#TQCEBF#5", - "excludeFromAutomaticTesting": true, - "reason": "Test crashes on Win32 D3D11 plain (STATUS_BREAKPOINT)", "referenceImage": "customRTT.png" }, { @@ -2839,9 +2835,7 @@ { "title": "UV2 Morphing", "playgroundId": "#N5DLM7#1", - "referenceImage": "UV2-Morphing.png", - "excludeFromAutomaticTesting": true, - "reason": "Test fails locally on Win32 D3D11 sweep; disabled until BabylonNative fixes are made." + "referenceImage": "UV2-Morphing.png" }, { "title": "FrameGraph nrge shadows", @@ -4449,9 +4443,7 @@ { "title": "Vertex Pulling - Morph Targets and Bones", "playgroundId": "#CT4C3C#2", - "referenceImage": "vertexPullingMorphBones.png", - "excludeFromAutomaticTesting": true, - "reason": "Test fails locally on Win32 D3D11 sweep; disabled until BabylonNative fixes are made." + "referenceImage": "vertexPullingMorphBones.png" }, { "title": "Vertex Pulling - isUnIndexed with index buffer", From 56bd531c60bb98ab9fd773d10175fefde7a06647 Mon Sep 17 00:00:00 2001 From: Branimir Karadzic Date: Mon, 6 Jul 2026 08:14:49 -0700 Subject: [PATCH 2/5] Bump babylonjs to 9.15.0 to pick up merged native-engine fixes The re-enabled validation tests depend on native-engine fixes that landed in babylonjs 9.14.0+ (depthCullingState.depthTest, cube render targets, updateTextureData / engine.name). The Apps lockfile was pinned to 9.9.1, so 'npm ci' installed an engine without those fixes and the tests crashed on the old WebGL-only paths. Bump to 9.15.0 (protocol-compatible: PROTOCOL_VERSION 9). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Apps/package-lock.json | 70 +++++++++++++++++++++--------------------- Apps/package.json | 14 ++++----- 2 files changed, 42 insertions(+), 42 deletions(-) diff --git a/Apps/package-lock.json b/Apps/package-lock.json index 2156eb656..d8adf7009 100644 --- a/Apps/package-lock.json +++ b/Apps/package-lock.json @@ -11,13 +11,13 @@ "UnitTests/JavaScript" ], "dependencies": { - "babylonjs": "^9.3.4", - "babylonjs-addons": "^9.3.4", - "babylonjs-gltf2interface": "^9.3.4", - "babylonjs-gui": "^9.3.4", - "babylonjs-loaders": "^9.3.4", - "babylonjs-materials": "^9.3.4", - "babylonjs-serializers": "^9.3.4", + "babylonjs": "^9.15.0", + "babylonjs-addons": "^9.15.0", + "babylonjs-gltf2interface": "^9.15.0", + "babylonjs-gui": "^9.15.0", + "babylonjs-loaders": "^9.15.0", + "babylonjs-materials": "^9.15.0", + "babylonjs-serializers": "^9.15.0", "jsc-android": "^241213.1.0", "v8-android": "^7.8.2" } @@ -2596,63 +2596,63 @@ } }, "node_modules/babylonjs": { - "version": "9.9.1", - "resolved": "https://registry.npmjs.org/babylonjs/-/babylonjs-9.9.1.tgz", - "integrity": "sha512-zMH8r0l/il9PJhy2+uS+/EyeLBy/ElZg0lKLj2d+ZrUarOanjMISaACw0HUx7C1EgYoXUFSMQLYcpXguleh8GA==", + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/babylonjs/-/babylonjs-9.15.0.tgz", + "integrity": "sha512-IJQhrxsxxj4KCg4aSsB5chLidzAfbghr8rnzo6sRLFin6ipfeayCxg7MevjaMzqdVmc/BOMU6sj49nSNrBq+yQ==", "hasInstallScript": true, "license": "Apache-2.0" }, "node_modules/babylonjs-addons": { - "version": "9.9.1", - "resolved": "https://registry.npmjs.org/babylonjs-addons/-/babylonjs-addons-9.9.1.tgz", - "integrity": "sha512-Hi9QZqeanqG+4VAAmJmHJh7JKf1bc/Y5Ml+wuv+W+3f/dKltZpy/pii/QN4vhYRyrU0n+S8OJc+kVeaUUFjHEg==", + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/babylonjs-addons/-/babylonjs-addons-9.15.0.tgz", + "integrity": "sha512-7vxm0ffFXWHCZWDlYYleifb6mg1iSH7nNo+sA9706z7QIfpNnLS9+9cTYadWobvhURwaD/DHAKnm7Z8ckq9YAA==", "license": "Apache-2.0", "dependencies": { - "babylonjs": "9.9.1" + "babylonjs": "9.15.0" } }, "node_modules/babylonjs-gltf2interface": { - "version": "9.9.1", - "resolved": "https://registry.npmjs.org/babylonjs-gltf2interface/-/babylonjs-gltf2interface-9.9.1.tgz", - "integrity": "sha512-qHE7pASEWbRORU+NEPSSL9fyVh6gBNqC1ugV94YL9Dz0HnLcH8YNcxdi6Pf+oXgFHRrXWZiJATn37HynsF7/0g==", + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/babylonjs-gltf2interface/-/babylonjs-gltf2interface-9.15.0.tgz", + "integrity": "sha512-Yr/WvOvnsZFN2LoyNU/ZZbT+lXMdNA9OiZzE4RibI1tnYxQjpPKT6X1cxQ/ACJPVzKwudxzJxQ/f/Tdba4TLDg==", "license": "Apache-2.0" }, "node_modules/babylonjs-gui": { - "version": "9.9.1", - "resolved": "https://registry.npmjs.org/babylonjs-gui/-/babylonjs-gui-9.9.1.tgz", - "integrity": "sha512-w6HSvuHSqLYUOuxvko+gkjAQMvatf7gnOPufLRslm+P4tC+5KMEd0hyC+1vHCJuQmZXXkoc/GnozzFeaqllHZw==", + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/babylonjs-gui/-/babylonjs-gui-9.15.0.tgz", + "integrity": "sha512-klF2ywhA040JMQ3Z5XaGkN4S0GIOshtnvtUd8kbYqlLYEu5i6/y/zvB8V3O4geslzV0kWcBe2mIthlr7M7p4og==", "license": "Apache-2.0", "dependencies": { - "babylonjs": "9.9.1" + "babylonjs": "9.15.0" } }, "node_modules/babylonjs-loaders": { - "version": "9.9.1", - "resolved": "https://registry.npmjs.org/babylonjs-loaders/-/babylonjs-loaders-9.9.1.tgz", - "integrity": "sha512-bFSe4TQTi7aA7RsWmuZHQLX4mBMoza5D+AFRD2tVrizGyGCfQJALKnKze7Z3OetFyeo72838GXJAXmyiOWcxAA==", + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/babylonjs-loaders/-/babylonjs-loaders-9.15.0.tgz", + "integrity": "sha512-k5Kg9wmuy0n4ZAWu9woFk3C1yEwSvQHRv0Ct2GOQtuRvIHUIyRcW4KEKOsW0GOiApSjXh9nQcabdJTnUo8IBNw==", "license": "Apache-2.0", "dependencies": { - "babylonjs": "9.9.1", - "babylonjs-gltf2interface": "9.9.1" + "babylonjs": "9.15.0", + "babylonjs-gltf2interface": "9.15.0" } }, "node_modules/babylonjs-materials": { - "version": "9.9.1", - "resolved": "https://registry.npmjs.org/babylonjs-materials/-/babylonjs-materials-9.9.1.tgz", - "integrity": "sha512-2ai5hk59dzHRUN28/5/Nijknn+IungSuoBsrHrNZE2T/yIT+/T/IrU0ebm53nRYliv6NG0IaxSCfnh6euZUj7A==", + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/babylonjs-materials/-/babylonjs-materials-9.15.0.tgz", + "integrity": "sha512-E9C5EB0nZJrGvT7Mb38gypAnzi1q8vr/Gi+fTeqRQilIU8mdk+YzD5unfKOROEv8arRFCXGWrtBqkIN8k2PFxA==", "license": "Apache-2.0", "dependencies": { - "babylonjs": "9.9.1" + "babylonjs": "9.15.0" } }, "node_modules/babylonjs-serializers": { - "version": "9.9.1", - "resolved": "https://registry.npmjs.org/babylonjs-serializers/-/babylonjs-serializers-9.9.1.tgz", - "integrity": "sha512-nZJPplzHprwJhp4JVLgB3dEpJR2HvEElPIDvOoAFf1IcaKwcDja2tZTGXIbARdWlTX547pN0qN/vEd6dYLrnDA==", + "version": "9.15.0", + "resolved": "https://registry.npmjs.org/babylonjs-serializers/-/babylonjs-serializers-9.15.0.tgz", + "integrity": "sha512-iLYwPzZrUr2SnwHcsXjHJHYdJIVC+boW6z/olE0mE5GvqmfKXTtqFr9wRGrsq85BarhHQkfVlfQdFwhFq/JG4w==", "license": "Apache-2.0", "dependencies": { - "babylonjs": "9.9.1", - "babylonjs-gltf2interface": "9.9.1" + "babylonjs": "9.15.0", + "babylonjs-gltf2interface": "9.15.0" } }, "node_modules/balanced-match": { diff --git a/Apps/package.json b/Apps/package.json index f6a7e31e7..632285ede 100644 --- a/Apps/package.json +++ b/Apps/package.json @@ -9,13 +9,13 @@ "getNightly": "node scripts/getNightly.js" }, "dependencies": { - "babylonjs": "^9.3.4", - "babylonjs-addons": "^9.3.4", - "babylonjs-gltf2interface": "^9.3.4", - "babylonjs-gui": "^9.3.4", - "babylonjs-loaders": "^9.3.4", - "babylonjs-materials": "^9.3.4", - "babylonjs-serializers": "^9.3.4", + "babylonjs": "^9.15.0", + "babylonjs-addons": "^9.15.0", + "babylonjs-gltf2interface": "^9.15.0", + "babylonjs-gui": "^9.15.0", + "babylonjs-loaders": "^9.15.0", + "babylonjs-materials": "^9.15.0", + "babylonjs-serializers": "^9.15.0", "jsc-android": "^241213.1.0", "v8-android": "^7.8.2" } From 6da5f2be71f1c6e3eb566cc72cb90bbb2ec8afd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Tue, 21 Jul 2026 07:50:52 -0700 Subject: [PATCH 3/5] Fix GL sampler texture-unit collision (NME Multi Build GL_INVALID_OPERATION) AppendSamplers shares one `stages` map across a program's vertex and fragment shader passes. Babylon's generated GLSL frequently declares the same sampler in both stages, so the fragment pass re-ran `stages[name] = stages.size()` on names already present in the map. Overwriting an existing key doesn't grow a std::map, so stages.size() stayed constant and every such sampler collapsed onto the same texture unit. For the heavy PBR node material in the "NME Multi Build" test this put 11 unpopulated sampler2D uniforms and the in-use ReflectionCubeSampler (samplerCube) all on unit 12. GLES/ANGLE rejects conflicting sampler types on one unit, so the draw failed with glDrawElementsInstanced GL_INVALID_OPERATION (exit 3 crash on Linux/Debug, silently-dropped draw on ANGLE Release). D3D11/Metal don't validate this, which is why the bug was GL-only. Guard the assignment so each sampler gets its own distinct unit the first time its name is seen, mirroring Babylon.js WebGL's Effect._bindSamplerUniformToChannel. No divergent code path: GL now behaves identically to the browser. Validated on Windows (test index 139, NME Multi Build): - OpenGL/ANGLE: exit 3 crash -> pass, pixel diff 0.390% - D3D11: unchanged, pass, pixel diff 0.383% Full 48-test sweep: no regressions (identical pass/fail to baseline). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 31593778-5e27-44af-9729-a47eba541cb6 --- .../Source/ShaderCompilerCommon.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/Plugins/ShaderCompiler/Source/ShaderCompilerCommon.cpp b/Plugins/ShaderCompiler/Source/ShaderCompilerCommon.cpp index b47e6e3df..a805ec283 100644 --- a/Plugins/ShaderCompiler/Source/ShaderCompilerCommon.cpp +++ b/Plugins/ShaderCompiler/Source/ShaderCompilerCommon.cpp @@ -98,8 +98,19 @@ namespace Babylon::ShaderCompilerCommon #if OPENGL BX_UNUSED(compiler); - const auto stage{static_cast(stages.size())}; - stages[sampler.name] = stage; + // A program's vertex and fragment shaders share this stages map, and Babylon's + // generated GLSL frequently declares the same sampler in both stages. Assign a stage + // (texture unit) the first time a sampler name is seen and reuse it thereafter; without + // the guard the second pass would re-run stages[name] = stages.size() on already-present + // names, which doesn't grow the map, collapsing every such sampler onto the same unit. + // That produced multiple sampler2D uniforms and a samplerCube all pointing at one unit, + // which GLES/ANGLE rejects at draw time with GL_INVALID_OPERATION (D3D11/Metal don't + // validate this, so the bug was GL-only). Each sampler now gets its own distinct unit, + // mirroring WebGL's Effect._bindSamplerUniformToChannel. + if (stages.find(sampler.name) == stages.end()) + { + stages[sampler.name] = static_cast(stages.size()); + } #else stages[sampler.name] = static_cast(compiler.get_decoration(sampler.id, spv::DecorationBinding)); #endif From 0a7942a6f0eaae1cb837f934fea11acafaac6162 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Tue, 21 Jul 2026 08:56:12 -0700 Subject: [PATCH 4/5] Exclude Custom render target on OpenGL (cube skybox face-boundary seam) The Custom render target test (#TQCEBF#5) fails only on the OpenGL backend with a ~12% pixel diff localized to the cube skybox in the upper sky; it passes on D3D11/D3D12. RenderDoc root-cause: the skybox samplerCube faces are correctly per-face vertical-flipped on GL (required by originBottomLeft); disabling that flip worsens the diff to 43%. The residual seam is a bgfx-level GL cubemap face-boundary/seamless-filtering artifact, not a Babylon Native regression. Follows the established excludedGraphicsApis precedent for GL-specific divergences; tracked for OpenGL backend follow-up. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 31593778-5e27-44af-9729-a47eba541cb6 --- Apps/Playground/Scripts/config.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Apps/Playground/Scripts/config.json b/Apps/Playground/Scripts/config.json index 134fcf061..f7ba4e18e 100644 --- a/Apps/Playground/Scripts/config.json +++ b/Apps/Playground/Scripts/config.json @@ -1263,7 +1263,9 @@ { "title": "Custom render target", "playgroundId": "#TQCEBF#5", - "referenceImage": "customRTT.png" + "referenceImage": "customRTT.png", + "excludedGraphicsApis": ["OpenGL"], + "reason": "GL cube skybox face-boundary seam (~12% pixel diff in upper sky); passes on D3D11/D3D12. Per-face vertical flip is required (disabling it worsens diff to 43%); residual seam is a bgfx-level GL cubemap face-boundary/seamless-filtering issue. Tracked for OpenGL backend follow-up." }, { "title": "Draco Mesh Compression (decodeMeshAsync, numWorkers = 1)", From 716e62d6f48b9b6a421fbb370c00cf8d469f9b91 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Tue, 21 Jul 2026 12:30:43 -0700 Subject: [PATCH 5/5] Fix single-target texture morph rendering invisible on OpenGL Texture-based morph targets with a single target create a numLayers==1 raw 2D-array texture. bgfx only allocates a real array texture (GL_TEXTURE_2D_ARRAY / D3D11 Texture2DArray) when numLayers > 1; a single layer collapses to a plain GL_TEXTURE_2D. The cross-compiled GLSL always samples the resource as 'sampler2DArray morphTargets', so on OpenGL the single-layer 2D texture cannot bind to the array sampler and reads as zero. With influence 1 that makes positionUpdated collapse to the origin, so the morphed mesh disappears (e.g. the 'UV2 Morphing' validation test: ~35.6% pixel diff on OpenGL, correct on D3D11/D3D12). WebGL2 creates a real depth-1 TEXTURE_2D_ARRAY. Match that by allocating at least two layers in LoadRawTexture2DArray so bgfx builds a true array texture; only the requested 'depth' layers are ever uploaded or sampled (the shader indexes an explicit layer). Also removed a stray BGFX_CAPS_TEXTURE_2D_ARRAY constant that was mistakenly OR'd into the texture create flags (its bit lands in the sampler border-color-index field; a no-op without BORDER addressing). Validated on Win32 OpenGL/ANGLE: UV2 Morphing 35.58% -> 0.105% (matches D3D11 0.105%); other morph tests unaffected on both backends. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 31593778-5e27-44af-9729-a47eba541cb6 --- Plugins/NativeEngine/Source/NativeEngine.cpp | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/Plugins/NativeEngine/Source/NativeEngine.cpp b/Plugins/NativeEngine/Source/NativeEngine.cpp index 864031710..8fa756893 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.cpp +++ b/Plugins/NativeEngine/Source/NativeEngine.cpp @@ -1484,8 +1484,21 @@ namespace Babylon const auto height{static_cast(rawHeight)}; const auto depth{static_cast(rawDepth)}; - uint64_t flags{BGFX_TEXTURE_NONE | BGFX_SAMPLER_NONE | BGFX_CAPS_TEXTURE_2D_ARRAY}; - texture->Create2D(width, height, generateMips, depth, Cast(format), flags); + // bgfx only allocates a true array texture (GL_TEXTURE_2D_ARRAY / + // D3D11 Texture2DArray) when numLayers > 1; a single-layer request + // collapses to a plain 2D texture (GL_TEXTURE_2D). The cross-compiled + // shaders always sample this resource as a 2D-array sampler + // (e.g. `sampler2DArray morphTargets`), so on OpenGL the single-layer + // 2D texture cannot bind to the array sampler and reads as zero -- + // which, for texture-based morph targets with a single target, + // collapses all vertices to the origin and makes the mesh disappear. + // WebGL2 creates a real depth-1 TEXTURE_2D_ARRAY, so match that by + // allocating at least two layers; only the requested `depth` layers + // are ever uploaded or sampled (the shader indexes an explicit layer). + const uint16_t allocLayers{depth > 1 ? depth : static_cast(2)}; + + uint64_t flags{BGFX_TEXTURE_NONE | BGFX_SAMPLER_NONE}; + texture->Create2D(width, height, generateMips, allocLayers, Cast(format), flags); if (!data.IsNull()) {