From 936c946e19e8bbfd9d8f2383c96dd7e52de8bf93 Mon Sep 17 00:00:00 2001 From: Matt Hargett Date: Tue, 16 Jun 2026 09:59:41 +0200 Subject: [PATCH] Preserve animation frames during readiness renders Render screenshot readiness-pump frames with animations ignored so material and effect readiness can advance without consuming the configured validation frame count. The GLTF serializer skinning tests are already enabled on current upstream master, so this keeps them compatible with readiness-driven validation. --- Apps/Playground/Scripts/validation_native.js | 91 ++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/Apps/Playground/Scripts/validation_native.js b/Apps/Playground/Scripts/validation_native.js index 0b75478a8..c0878f6de 100644 --- a/Apps/Playground/Scripts/validation_native.js +++ b/Apps/Playground/Scripts/validation_native.js @@ -72,6 +72,58 @@ return getExclusionReason(t); } + function shouldUseRenderReadinessPump(test) { + if (test && test.renderReadinessPump === false) { + return false; + } + if (globalThis.__nativeValidationRenderReadinessPump === false) { + return false; + } + return true; + } + + function startSceneReadinessRenderPump(scene, onFatalError) { + let stopped = false; + let frameCount = 0; + + const pump = function () { + 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") { + // Readiness renders are only for material/effect compilation. + // Preserve the screenshot test's animation frame count. + scene.render(true, true); + frameCount++; + } + } catch (e) { + stopped = true; + if (typeof onFatalError === "function") { + onFatalError(e); + } + return; + } + + setTimeout(pump, 16); + }; + + setTimeout(pump, 0); + return { + stop: function () { + stopped = true; + }, + getFrameCount: function () { + return frameCount; + } + }; + } + function logRunSummary() { console.log("Run complete. ran=" + ranCount + " passed=" + passedCount + @@ -235,11 +287,16 @@ let stopped = false; let pendingScreenshot = null; let evaluated = false; + let readinessPump = null; const runEvaluation = function (screenshot) { if (evaluated) { return; } + if (readinessPump !== null) { + readinessPump.stop(); + readinessPump = null; + } evaluated = true; evaluateScreenshot(test, screenshot, renderImage, done, compareFunction); }; @@ -255,12 +312,40 @@ // never-ready scene into a fast test failure instead of a silent hang. currentScene.onReadyTimeoutDuration = 10 * 60 * 1000; currentScene.onReadyTimeoutObservable.addOnce(function () { + if (readinessPump !== null) { + readinessPump.stop(); + readinessPump = null; + } + evaluated = true; console.error("Scene '" + (test.title || "?") + "' did not become ready within " + (currentScene.onReadyTimeoutDuration / 1000) + "s."); failTest(done); }); + if (shouldUseRenderReadinessPump(test)) { + readinessPump = startSceneReadinessRenderPump(currentScene, function (error) { + if (evaluated) { + return; + } + evaluated = true; + stopped = true; + console.error("Readiness render pump failed: " + (error && error.message ? error.message : String(error))); + failTest(done); + }); + } + currentScene.executeWhenReady(function () { + if (evaluated) { + return; + } + if (readinessPump !== null) { + const readinessFrameCount = readinessPump.getFrameCount(); + readinessPump.stop(); + readinessPump = null; + if (readinessFrameCount > 0) { + console.log("Readiness render pump rendered " + readinessFrameCount + " frame(s) before validation frame counting for " + (test.title || "(unnamed)") + "."); + } + } if (currentScene.activeCamera && currentScene.activeCamera.useAutoRotationBehavior) { currentScene.activeCamera.useAutoRotationBehavior = false; } @@ -300,6 +385,12 @@ } } catch (e) { + if (readinessPump !== null) { + readinessPump.stop(); + readinessPump = null; + } + evaluated = true; + stopped = true; console.error(e); failTest(done); }