Skip to content
Closed
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
4 changes: 4 additions & 0 deletions Apps/Playground/Scripts/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,7 @@
"title": "Gaussian Splatting Loading",
"playgroundId": "#CID4NN#204",
"renderCount": 15,
"renderReadinessPump": true,
"referenceImage": "gsplat-loading.png"
},
{
Expand All @@ -808,6 +809,7 @@
{
"title": "needDepthPrePass",
"playgroundId": "#7A66KI#1",
"renderReadinessPump": true,
"referenceImage": "needDepthPrePass.png"
},
{
Expand Down Expand Up @@ -1348,11 +1350,13 @@
{
"title": "GLTF Serializer Skinning and Animation",
"playgroundId": "#DMZBX1#1",
"renderReadinessPump": true,
"referenceImage": "gltfSerializerSkinningAndAnimation.png"
},
{
"title": "GLTF Serializer Skinning and Animation (Right Handed)",
"playgroundId": "#DMZBX1#2",
"renderReadinessPump": true,
"referenceImage": "gltfSerializerSkinningAndAnimation.png"
},
{
Expand Down
124 changes: 123 additions & 1 deletion Apps/Playground/Scripts/validation_native.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,81 @@
return getExclusionReason(t);
}

function shouldUseRenderReadinessPump(test) {
if (test && typeof test.renderReadinessPump === "boolean") {
return test.renderReadinessPump;
}
return globalThis.__nativeValidationRenderReadinessPump === true;
}

function formatReadinessError(error) {
if (error && error.stack) {
return error.stack;
}
if (error && error.message) {
return error.message;
}
return String(error);
}

function startSceneReadinessRenderPump(scene) {
let stopped = false;
let frameCount = 0;
let errorCount = 0;
let firstError = null;
let lastError = null;

const pump = function () {
let nextPumpDelay = 100;
if (stopped) {
return;
}
if (!scene || scene.isDisposed === true || (typeof scene.isDisposed === "function" && scene.isDisposed())) {
stopped = true;
return;
}

try {
if (scene.activeCamera && typeof scene.render === "function") {
nextPumpDelay = 16;
// Readiness renders are only for material/effect compilation.
// Preserve the screenshot test's animation frame count.
scene.render(true, true);
Comment thread
matthargett marked this conversation as resolved.
frameCount++;
}
} catch (e) {
Comment thread
matthargett marked this conversation as resolved.
errorCount++;
if (errorCount === 1) {
firstError = e;
console.warn("Readiness render pump encountered a transient error; continuing to poll scene readiness:\n" +
formatReadinessError(e));
}
lastError = e;
}

setTimeout(pump, nextPumpDelay);
};
Comment thread
matthargett marked this conversation as resolved.

setTimeout(pump, 0);
return {
stop: function () {
stopped = true;
},
getFrameCount: function () {
return frameCount;
},
getErrorCount: function () {
return errorCount;
},
getFirstError: function () {
return firstError;
},
getLastError: function () {
return lastError;
}
};
}

function logRunSummary() {
console.log("Run complete. ran=" + ranCount +
" passed=" + passedCount +
Expand Down Expand Up @@ -263,11 +338,33 @@
let stopped = false;
let pendingScreenshot = null;
let evaluated = false;
let readinessPump = null;

const stopReadinessPump = function (logFrameCount) {
if (readinessPump === null) {
return null;
}

const pump = readinessPump;
readinessPump = null;
const summary = {
frameCount: pump.getFrameCount(),
errorCount: pump.getErrorCount(),
firstError: pump.getFirstError(),
lastError: pump.getLastError()
};
pump.stop();
if (logFrameCount && summary.frameCount > 0) {
console.log("Readiness render pump rendered " + summary.frameCount + " frame(s) before validation frame counting for " + (test.title || "(unnamed)") + ".");
}
return summary;
};

const runEvaluation = function (screenshot) {
if (evaluated) {
return;
}
stopReadinessPump(false);
evaluated = true;
evaluateScreenshot(test, screenshot, renderImage, done, compareFunction);
};
Expand All @@ -283,12 +380,34 @@
// never-ready scene into a fast test failure instead of a silent hang.
currentScene.onReadyTimeoutDuration = 10 * 60 * 1000;
currentScene.onReadyTimeoutObservable.addOnce(function () {
let readinessErrorDetails = "";
const readinessSummary = stopReadinessPump(false);
if (readinessSummary !== null && readinessSummary.errorCount > 0) {
readinessErrorDetails = "\nReadiness render pump observed " + readinessSummary.errorCount + " exception(s).\nFirst error:\n" +
formatReadinessError(readinessSummary.firstError);
if (readinessSummary.lastError !== readinessSummary.firstError) {
readinessErrorDetails += "\nLast error:\n" + formatReadinessError(readinessSummary.lastError);
}
}
evaluated = true;
stopped = true;
console.error("Scene '" + (test.title || "?") + "' did not become ready within " +
(currentScene.onReadyTimeoutDuration / 1000) + "s.");
(currentScene.onReadyTimeoutDuration / 1000) + "s." + readinessErrorDetails);
failTest(done);
});
Comment thread
matthargett marked this conversation as resolved.

if (shouldUseRenderReadinessPump(test)) {
readinessPump = startSceneReadinessRenderPump(currentScene);
}

currentScene.executeWhenReady(function () {
if (evaluated) {
return;
}
const readinessSummary = stopReadinessPump(true);
if (readinessSummary !== null && readinessSummary.errorCount > 0) {
console.warn("Readiness render pump recovered from " + readinessSummary.errorCount + " transient exception(s) before " + (test.title || "(unnamed)") + " became ready.");
}
if (currentScene.activeCamera && currentScene.activeCamera.useAutoRotationBehavior) {
currentScene.activeCamera.useAutoRotationBehavior = false;
}
Expand Down Expand Up @@ -328,6 +447,9 @@
}
}
catch (e) {
stopReadinessPump(false);
evaluated = true;
stopped = true;
console.error(e);
failTest(done);
}
Expand Down
12 changes: 12 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,18 @@ elseif(IOS)
set(DEPLOYMENT_TARGET "13" CACHE STRING "")
endif()

if(CMAKE_HOST_APPLE
AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR
AND NOT CMAKE_TOOLCHAIN_FILE
AND NOT IOS
AND NOT VISIONOS
AND NOT CMAKE_OSX_SYSROOT)
# Use one coherent Xcode SDK for headers and frameworks. Leaving this empty
# can mix Command Line Tools headers with frameworks from the selected
# Xcode, which turns SDK-only diagnostics into warnings-as-errors.
set(CMAKE_OSX_SYSROOT macosx CACHE STRING "macOS SDK" FORCE)
endif()

project(BabylonNative)

set_property(GLOBAL PROPERTY USE_FOLDERS ON)
Expand Down
10 changes: 10 additions & 0 deletions Dependencies/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,16 @@ endif()
# --------------------------------------------------
FetchContent_MakeAvailable_With_Message(JsRuntimeHost)

if(APPLE AND NAPI_JAVASCRIPT_ENGINE STREQUAL "JavaScriptCore" AND TARGET napi)
# find_library adds the SDK framework directory with -F, which does not
# classify framework headers as system headers. JsRuntimeHost uses
# -pedantic -Werror, so make that classification explicit for napi and all
# consumers while preserving warnings-as-errors for project sources.
get_filename_component(_JAVASCRIPTCORE_FRAMEWORKS_DIR "${JAVASCRIPTCORE_LIBRARY}" DIRECTORY)
target_compile_options(napi PUBLIC "SHELL:-iframework ${_JAVASCRIPTCORE_FRAMEWORKS_DIR}")
unset(_JAVASCRIPTCORE_FRAMEWORKS_DIR)
endif()

# --------------------------------------------------
# metal-cpp
# --------------------------------------------------
Expand Down