Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Apps/Playground/Android/BabylonNative/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ target_link_libraries(BabylonNativeJNI
PRIVATE Blob
PRIVATE Canvas
PRIVATE Console
PRIVATE CubeTexture
PRIVATE GraphicsDevice
PRIVATE NativeCamera
PRIVATE NativeCapture
Expand Down
1 change: 1 addition & 0 deletions Apps/Playground/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ target_link_libraries(Playground
PRIVATE bx
PRIVATE Canvas
PRIVATE Console
PRIVATE CubeTexture
PRIVATE ExternalTexture
PRIVATE GraphicsDevice
PRIVATE NativeCamera
Expand Down
14 changes: 0 additions & 14 deletions Apps/Playground/Scripts/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -892,8 +892,6 @@
{
"title": "NMEGLTF",
"playgroundId": "#WGZLGJ#10320",
"excludeFromAutomaticTesting": true,
"reason": "Pixel comparison fails (more than 20% pixels differ)",
"referenceImage": "nmegltf.png"
},
{
Expand Down Expand Up @@ -1108,15 +1106,11 @@
{
"title": "Anisotropic",
"playgroundId": "#MAXCNU#1",
"excludeFromAutomaticTesting": true,
"reason": "Pixel comparison fails (more than 20% pixels differ)",
"referenceImage": "anisotropic.png"
},
{
"title": "Clear Coat",
"playgroundId": "#YACNQS#2",
"excludeFromAutomaticTesting": true,
"reason": "Pixel comparison fails (more than 20% pixels differ)",
"referenceImage": "clearCoat.png"
},
{
Expand Down Expand Up @@ -1661,22 +1655,16 @@
{
"title": "PBRMetallicRoughnessMaterial",
"playgroundId": "#2FDQT5#13",
"excludeFromAutomaticTesting": true,
"reason": "Pixel comparison fails (more than 20% pixels differ)",
"referenceImage": "PBRMetallicRoughnessMaterial.png"
},
{
"title": "PBRSpecularGlossinessMaterial",
"playgroundId": "#Z1VL3V#4",
"excludeFromAutomaticTesting": true,
"reason": "Pixel comparison fails (more than 20% pixels differ)",
"referenceImage": "PBRSpecularGlossinessMaterial.png"
},
{
"title": "PBR",
"playgroundId": "#LCA0Q4#27",
"excludeFromAutomaticTesting": true,
"reason": "Pixel comparison fails (more than 20% pixels differ)",
"referenceImage": "pbr.png"
},
{
Expand Down Expand Up @@ -1985,8 +1973,6 @@
"title": "Prepass SSAO + depth of field",
"playgroundId": "#8F5HYV#72",
"renderCount": 10,
"excludeFromAutomaticTesting": true,
"reason": "Pixel comparison fails (more than 20% pixels differ)",
"referenceImage": "prepass-ssao-dof.png"
},
{
Expand Down
8 changes: 8 additions & 0 deletions Apps/Playground/Shared/AppContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <Babylon/Polyfills/Blob.h>
#include <Babylon/Polyfills/Canvas.h>
#include <Babylon/Polyfills/Console.h>
#include <Babylon/Polyfills/CubeTexture.h>
#include <Babylon/Polyfills/Performance.h>
#include <Babylon/Polyfills/TextDecoder.h>
#include <Babylon/Polyfills/Window.h>
Expand Down Expand Up @@ -215,6 +216,13 @@ AppContext::AppContext(
// Commenting out recast.js for now because v8jsi is incompatible with asm.js.
// m_scriptLoader->LoadScript("app:///Scripts/recast.js");
m_scriptLoader->LoadScript("app:///Scripts/babylon.max.js");
// CubeTexture polyfill must run AFTER babylon.max.js is evaluated because
// it patches BABYLON.NativeEngine.prototype.createCubeTexture. The
// ScriptLoader's Dispatch is ordered against LoadScript on the same task
// chain, so this is guaranteed to run after babylon.max.js completes.
m_scriptLoader->Dispatch([](Napi::Env env) {
Babylon::Polyfills::CubeTexture::Initialize(env);
});
m_scriptLoader->LoadScript("app:///Scripts/babylonjs.loaders.js");
m_scriptLoader->LoadScript("app:///Scripts/babylonjs.materials.js");
m_scriptLoader->LoadScript("app:///Scripts/babylon.gui.js");
Expand Down
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,7 @@ option(BABYLON_NATIVE_PLUGIN_TESTUTILS "Include Babylon Native Plugin TestUtils.
# Polyfills
option(BABYLON_NATIVE_POLYFILL_WINDOW "Include Babylon Native Polyfill Window." ON)
option(BABYLON_NATIVE_POLYFILL_CANVAS "Include Babylon Native Polyfill Canvas." ON)
option(BABYLON_NATIVE_POLYFILL_CUBETEXTURE "Include Babylon Native Polyfill CubeTexture (.dds/.ktx/.ktx2 -> .env fallback for NativeEngine.createCubeTexture)." ON)

# Sanitizers
option(ENABLE_SANITIZERS "Enable AddressSanitizer and UBSan" OFF)
Expand Down
4 changes: 4 additions & 0 deletions Polyfills/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ endif()
if(BABYLON_NATIVE_POLYFILL_CANVAS)
add_subdirectory(Canvas)
endif()

if(BABYLON_NATIVE_POLYFILL_CUBETEXTURE)
add_subdirectory(CubeTexture)
endif()
16 changes: 16 additions & 0 deletions Polyfills/CubeTexture/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
set(SOURCES
"Include/Babylon/Polyfills/CubeTexture.h"
"Source/CubeTexture.cpp")

add_library(CubeTexture ${SOURCES})
warnings_as_errors(CubeTexture)

target_include_directories(CubeTexture
PUBLIC "Include")

target_link_libraries(CubeTexture
PUBLIC napi
PUBLIC Foundation)

set_property(TARGET CubeTexture PROPERTY FOLDER Polyfills)
source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES})
31 changes: 31 additions & 0 deletions Polyfills/CubeTexture/Include/Babylon/Polyfills/CubeTexture.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#pragma once

#include <napi/env.h>
#include <Babylon/Api.h>

namespace Babylon::Polyfills::CubeTexture
{
// Patches BABYLON.NativeEngine.prototype.createCubeTexture to transparently
// retry .dds / .ktx / .ktx2 single-URL cubemap loads as .env (a format BN
// does support). Babylon's CDN co-hosts both, so the swap is invisible to
// consumer JS. Falls back to the original (which throws "Cannot load
// cubemap because 6 files were not defined") on 404 - preserving existing
// error semantics.
//
// Call Initialize AFTER babylon.max.js has been evaluated; the patch
// requires BABYLON.NativeEngine.prototype to exist. When using the
// jsruntimehost ScriptLoader, the simplest pattern is:
//
// scriptLoader.LoadScript("app:///Scripts/babylon.max.js");
// scriptLoader.Dispatch([](Napi::Env env) {
// Babylon::Polyfills::CubeTexture::Initialize(env);
// });
//
// The Dispatch queues onto the same ordered task chain as LoadScript, so
// Initialize is guaranteed to run after babylon.max.js finishes evaluating.
//
// This is a tactical bridge until Plugins/NativeEngine wires a proper
// loader registry path for cubemap formats. Safe to call multiple times;
// the patch is idempotent.
void BABYLON_API Initialize(Napi::Env env);
}
134 changes: 134 additions & 0 deletions Polyfills/CubeTexture/Source/CubeTexture.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#include <Babylon/Polyfills/CubeTexture.h>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three layered concerns:

  1. Silent substitution. PG asks for .dds, we quietly load the .env co-located by Babylon's CDN. Tests "pass" but they're rendering a different file (different format, mip pyramid, precision). If BJS's .dds path regresses, we don't catch it.
  2. CDN-specific. The fallback only works for assets where someone happens to co-host .env. Babylon's playground CDN does; arbitrary BN consumers don't. Shipping this in Polyfills/ advertises a benefit non-Babylon consumers won't get.
  3. Wrong layer. This masks a real gap — BN's NativeEngine doesn't dispatch .dds/.ktx/.ktx2 cubemap loading even though bimg already supports the formats. The proper fix lives in Plugins/NativeEngine.

Let's discuss offline before merging.


#include <napi/env.h>

namespace Babylon::Polyfills::CubeTexture
{
namespace
{
// Embedded polyfill source. Kept verbatim from the original Playground
// cube_texture_polyfill.js (PR #1710 v0) so behaviour stays identical:
// - Patches BABYLON.NativeEngine.prototype.createCubeTexture.
// - Triggers only when the URL ends in .dds / .ktx / .ktx2 and no
// forcedExtension, 6-file array, or raw buffer was supplied.
// - Retries via NativeEngine._loadFile to fetch the .env counterpart;
// on 404 falls back to the original (which throws), preserving the
// existing failure mode.
// - Idempotent via the __cubeTexturePolyfillInstalled marker on the
// prototype.
constexpr const char* JS_SOURCE = R"javascript(
(function () {
"use strict";

if (typeof BABYLON === "undefined") {
return;
}
if (!BABYLON.NativeEngine || !BABYLON.NativeEngine.prototype) {
return;
}

var proto = BABYLON.NativeEngine.prototype;
if (proto.__cubeTexturePolyfillInstalled) {
return;
}
proto.__cubeTexturePolyfillInstalled = true;

var original = proto.createCubeTexture;
if (typeof original !== "function") {
return;
}

var FALLBACK_EXTS = [".dds", ".ktx", ".ktx2"];

function getExtension(url, forced) {
if (forced) {
return forced.toLowerCase();
}
var dot = url.lastIndexOf(".");
if (dot < 0) {
return "";
}
var ext = url.substring(dot).toLowerCase();
var q = ext.indexOf("?");
if (q >= 0) {
ext = ext.substring(0, q);
}
return ext;
}

function replaceExt(url, oldExt) {
return url.substring(0, url.length - oldExt.length) + ".env";
}

proto.createCubeTexture = function (rootUrl, scene, files, noMipmap, onLoad, onError, format, forcedExtension, createPolynomials, lodScale, lodOffset, fallback, loaderOptions, useSRGBBuffer, buffer) {
var ext = getExtension(rootUrl, forcedExtension);
var hasFiles = files && files.length === 6;
var canFallback = !buffer && !forcedExtension && !hasFiles && FALLBACK_EXTS.indexOf(ext) >= 0;

if (!canFallback) {
return original.apply(this, arguments);
}

var self = this;
var envUrl = replaceExt(rootUrl, ext);
var texture = fallback || new BABYLON.InternalTexture(self, 7 /* Cube */);
texture.isCube = true;
texture.url = rootUrl;

var settled = false;
var settle = function (action) {
if (settled) {
return;
}
settled = true;
try {
action();
} catch (e) {
if (onError) {
onError(e && e.message ? e.message : String(e), e);
}
}
};

var onEnvLoaded = function (data) {
settle(function () {
var buf = (data && data.byteLength !== undefined && !(data instanceof Uint8Array)) ? new Uint8Array(data, 0, data.byteLength) : data;
original.call(self, envUrl, scene, files, noMipmap, onLoad, onError, format, ".env", createPolynomials, lodScale || 0, lodOffset || 0, texture, loaderOptions, useSRGBBuffer || false, buf);
});
};

var onEnvFailed = function (/* request, exception */) {
settle(function () {
original.call(self, rootUrl, scene, files, noMipmap, onLoad, onError, format, forcedExtension, createPolynomials, lodScale, lodOffset, texture, loaderOptions, useSRGBBuffer, buffer);
});
};

try {
self._loadFile(envUrl, onEnvLoaded, undefined, undefined, true, onEnvFailed);
} catch (e) {
onEnvFailed(null, e);
}

return texture;
};

if (typeof console !== "undefined" && console.log) {
console.log("[CubeTexture polyfill] NativeEngine.createCubeTexture patched with .env fallback");
}
})();
)javascript";

constexpr const char* JS_SOURCE_URL = "babylon-native://polyfills/CubeTexture.js";
}

void Initialize(Napi::Env env)
{
// The free function Napi::Eval(env, source, url) is declared by every
// engine-specific <napi/env.h> across both N-API trees (Chakra, V8,
// JavaScriptCore in Core/Node-API/Include/Engine/<X>/, and JSI in
// Core/Node-API-JSI/Include/napi/). Using it uniformly avoids the
// Shared-only env.RunScript() which is missing from the JSI tree.
Napi::HandleScope scope{env};
Napi::Eval(env, JS_SOURCE, JS_SOURCE_URL);
}
}
Loading