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
73 changes: 71 additions & 2 deletions JetStreamDriver.js
Original file line number Diff line number Diff line change
Expand Up @@ -740,9 +740,16 @@ class Benchmark {
return tags.some((tag) => this.tags.has(tag.toLowerCase()));
}

get benchmarkArguments() {
return {
...this.plan.arguments,
iterationCount: this.iterations,
};
}

get runnerCode() {
return `{
const benchmark = new Benchmark(${this.iterations});
const benchmark = new Benchmark(${JSON.stringify(this.benchmarkArguments)});
const results = [];
const benchmarkName = "${this.name}";

Expand Down Expand Up @@ -1320,7 +1327,7 @@ class AsyncBenchmark extends DefaultBenchmark {
get runnerCode() {
return `
async function doRun() {
const benchmark = new Benchmark(${this.iterations});
const benchmark = new Benchmark(${JSON.stringify(this.benchmarkArguments)});
await benchmark.init?.();
const results = [];
const benchmarkName = "${this.name}";
Expand Down Expand Up @@ -2271,6 +2278,68 @@ let BENCHMARKS = [
allowUtf16: true,
tags: ["Default", "Wasm"],
}),
new AsyncBenchmark({
name: "babylonjs-startup-es5",
files: [
"./startup-helper/StartupBenchmark.js",
"./babylonjs/benchmark/startup.js",
],
preload: {
BUNDLE: "./babylonjs/dist/bundle.es5.min.js",
},
arguments: {
expectedCacheCommentCount: 21238,
},
tags: ["startup", "class", "es5", "babylonjs"],
iterations: 10,
}),
new AsyncBenchmark({
name: "babylonjs-startup-es6",
files: [
"./startup-helper/StartupBenchmark.js",
"./babylonjs/benchmark/startup.js",
],
preload: {
BUNDLE: "./babylonjs/dist/bundle.es6.min.js",
},
arguments: {
expectedCacheCommentCount: 21230,
},
tags: ["Default", "startup", "class", "es6", "babylonjs"],
iterations: 10,
}),
new AsyncBenchmark({
name: "babylonjs-scene-es5",
files: [
// Use non-minified sources for easier profiling:
// "./babylonjs/dist/bundle.es5.js",
"./babylonjs/dist/bundle.es5.min.js",
"./babylonjs/benchmark/scene.js",
],
preload: {
PARTICLES_BLOB: "./babylonjs/data/particles.json",
PIRATE_FORT_BLOB: "./babylonjs/data/pirateFort.glb",
CANNON_BLOB: "./babylonjs/data/cannon.glb",
},
tags: ["scene", "es5"],
iterations: 5,
}),
new AsyncBenchmark({
name: "babylonjs-scene-es6",
files: [
// Use non-minified sources for easier profiling:
// "./babylonjs/dist/bundle.es6.js",
"./babylonjs/dist/bundle.es6.min.js",
"./babylonjs/benchmark/scene.js",
],
preload: {
PARTICLES_BLOB: "./babylonjs/data/particles.json",
PIRATE_FORT_BLOB: "./babylonjs/data/pirateFort.glb",
CANNON_BLOB: "./babylonjs/data/cannon.glb",
},
tags: ["Default", "scene", "es6"],
iterations: 5,
}),
// WorkerTests
new AsyncBenchmark({
name: "bomb-workers",
Expand Down
34 changes: 34 additions & 0 deletions babylonjs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Babylon.js Benchmarks for JetStream

This project contains two benchmarks for testing the performance of the Babylon.js 3D engine.

## Build Instructions

```bash
# install required node packages.
npm ci
# build the workload, output is ./dist
npm run build
```

## Workloads

There are two distinct workloads in this benchmark suite:

### 1. Startup Workload

This benchmark measures the time it takes for the Babylon.js engine to initialize. It evaluates a large, bundled source file and measures the time to parse the code and execute a simple test. This workload is primarily focused on parse and startup time.

To run this benchmark in node for testing:
```bash
npm run test:startup
```

### 2. Scene Workload

This benchmark measures the rendering performance of a complex 3D scene. It loads 3D models (`.glb` files), animations, and particle systems, and then renders the scene for a number of frames.

To run this benchmark in node for testing:
```bash
npm run test:scene
```
25 changes: 25 additions & 0 deletions babylonjs/benchmark/scene-node.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { runComplexScene } from "../src/babylon-js-benchmark.mjs";
import { promises as fs } from "fs";
import path from "path";
import { fileURLToPath } from "url";

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const fortPath = path.resolve(__dirname, "../data/pirateFort.glb");
const cannonPath = path.resolve(__dirname, "../data/cannon.glb");
const particlePath = path.resolve(__dirname, "../data/particles.json");

async function main() {
try {
const fortBuffer = await fs.readFile(fortPath);
const cannonBuffer = await fs.readFile(cannonPath);
const particleData = JSON.parse(await fs.readFile(particlePath, "utf-8"))
const {classNames, cameraRotationLength} = await runComplexScene(fortBuffer, cannonBuffer, particleData, 1000);
} catch(e) {
console.error(e);
console.error(e.stack);
}
}

main();
50 changes: 50 additions & 0 deletions babylonjs/benchmark/scene.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// console.log = () => {};

// JetStream benchmark.
class Benchmark {
iterationCount = 0;
lastResult = {};
preloaded = {
fortData: null,
cannonData: null,
particlesJson: null,
};

constructor(iterationCount) {
this.iterationCount = iterationCount;
}

async init() {
const [fort, cannon, particles] = await Promise.all([
JetStream.getBinary(JetStream.preload.PIRATE_FORT_BLOB),
JetStream.getBinary(JetStream.preload.CANNON_BLOB),
JetStream.getString(JetStream.preload.PARTICLES_BLOB),
]);
this.preloaded.fortData = fort;
this.preloaded.cannonData = cannon;
this.preloaded.particlesJson = JSON.parse(particles);
}

async runIteration() {
const {classNames, cameraRotationLength} = await BabylonJSBenchmark.runComplexScene(
this.preloaded.fortData,
this.preloaded.cannonData,
this.preloaded.particlesJson,
100
);
this.lastResult = {
classNames,
cameraRotationLength
};
}

validate() {
this.expect("this.lastResult.classNames.length", this.lastResult.classNames.length, 2135);
this.expect("this.lastResult.cameraRotationLength", this.lastResult.cameraRotationLength, 0);
}

expect(name, value, expected) {
if (value != expected)
throw new Error(`Expected ${name} to be ${expected}, but got ${value}`);
}
}
3 changes: 3 additions & 0 deletions babylonjs/benchmark/startup-node.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { runTest } from "../src/babylon-js-benchmark.mjs";

console.log(runTest());
83 changes: 83 additions & 0 deletions babylonjs/benchmark/startup.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2025 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. 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.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``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 APPLE INC. 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.
*/

// console.log = () => {};

class Benchmark extends StartupBenchmark {
lastResult = {};
iteration = 0;

constructor({iterationCount, expectedCacheCommentCount}) {
super({
iterationCount,
codeReuseCount: 2,
expectedCacheCommentCount,
});
}

runIteration() {
const sourceCode = this.iterationSourceCodes[this.iteration];
if (!sourceCode)
throw new Error(`Could not find source for iteration ${this.iteration}`);
// Module in sourceCode it assigned to the ClassStartupTest variable.
let BabylonJSBenchmark;

// let initStart = performance.now();
eval(sourceCode);
// const runStart = performance.now();

const { classNames, cameraRotationLength } = BabylonJSBenchmark.runTest(30);
this.lastResult = {
classNames,
cameraRotationLength,
};
// const end = performance.now();
// const loadTime = runStart - initStart;
// const runTime = end - runStart;
// For local debugging:
// print(`Iteration ${this.iteration}:`);
// print(` Load time: ${loadTime.toFixed(2)}ms`);
// print(` Render time: ${runTime.toFixed(2)}ms`);
this.iteration++;
}

validate() {
this.expect(
"this.lastResult.classNames.length",
this.lastResult.classNames.length,
2135
);
this.expect(
"this.lastResult.cameraRotationLength",
Math.round(this.lastResult.cameraRotationLength * 1000),
464
);
}

expect(name, value, expected) {
if (value != expected)
throw new Error(`Expected ${name} to be ${expected}, but got ${value}`);
}
}
30 changes: 30 additions & 0 deletions babylonjs/build/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@

import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

const SNIPPET_URL = 'https://snippet.babylonjs.com/LCBQ5Y/6';
const RAW_SNIPPET_PATH = path.resolve(__dirname, '../data/snippets.LCBQ5Y.raw.json');
const PARTICLES_PATH = path.resolve(__dirname, '../data/particles.json');

async function main() {
const response = await fetch(SNIPPET_URL);
if (!response.ok) {
throw new Error(`Failed to download file: ${response.status} ${response.statusText}`);
}
const snippetJson = await response.json();

console.log(`Saving raw snippet: ${RAW_SNIPPET_PATH}`);
fs.writeFileSync(RAW_SNIPPET_PATH, JSON.stringify(snippetJson));

const jsonPayload = JSON.parse(snippetJson.jsonPayload);
const particleSystem = JSON.parse(jsonPayload.particleSystem);

console.log(`Saving particles: ${PARTICLES_PATH}:`);
fs.writeFileSync(PARTICLES_PATH, JSON.stringify(particleSystem, null, 2));
}

main();
Loading
Loading