OTOM CFD Benchmark Guide

The Bench tab runs real 2D and 3D fluid simulations on your GPU and measures how fast your hardware can actually drive them, without the frame-rate cap of your monitor. This page explains what is measured, how, and how to get results you can compare between machines.

1. Quick start

  1. Open the Bench tab in the left sidebar.
  2. Choose a Duration per test. 5 s is a quick check, 20 s is a good default for comparable results, and 60 s or more shows thermal throttling on laptops.
  3. Pick a Test set: the Standard suite (fixed tests, best for comparing machines) or any demo scene run at Light / Standard / Heavy / Extreme.
  4. Tick the tests you want. The estimate under the list shows the total run time.
  5. Press Start and keep the browser tab visible until it finishes.
  6. Read the results table and score, then save them with HTML Report, CSV, JSON or Copy.

Your own scene is saved before the run and restored afterwards. The benchmark only changes the grid size, solver engine, 2D/3D view and play state; your obstacles, colours and every other setting stay as they were.

2. Why "uncapped" FPS

A browser draws a new frame when the monitor refreshes. The usual FPS counter therefore counts screen refreshes, and it cannot go above your monitor's refresh rate (60, 120, 144 Hz…). A scene your GPU could run at 2,000 frames per second and one that barely makes 60 both read "60".

The benchmark instead measures how long one frame's work really takes on the GPU and CPU. The uncapped frame rate is simply one second divided by that time: the rate your hardware could sustain if nothing were holding it back to the monitor's pace.

Uncapped FPS = 1000 / frame time (ms) frame time = max(GPU time from frame start to frame end, CPU time spent preparing the frame)

3. How a frame is timed

The simulation runs on the GPU through WebGPU. Each frame, the app asks the GPU to record three high-resolution timestamps at precise points in its work queue:

Simulation (solver substeps) Render t0 t1 t2 GPU Sim = t1 − t0 GPU Render = t2 − t1 GPU frame time = t2 − t0

Because the simulation is sent to the GPU in several pieces while the CPU prepares the next ones, t2 − t0 measures the real critical path. If the GPU is the bottleneck it is pure GPU execution time; if the CPU is slow to prepare work, the GPU waits between pieces and that waiting is included too. The CPU time the app spends preparing the frame is measured separately, and the larger of the two is used as the frame time.

Timing source. This needs the WebGPU timestamp-query feature, available in current desktop Chrome and Edge. The benchmark panel and the Performance folder show which method is in use. Where it is missing (for example some Firefox or Safari versions), the app falls back to measuring wall-clock time until the GPU reports the frame finished. That fallback includes a little reporting delay, so it reads lower than the truth on very light scenes, and is labelled "approximate".

Chrome deliberately rounds GPU timestamps to 0.1 ms for privacy. A single frame of 0.3 ms can therefore read as 0.2 or 0.4 ms, but over the hundreds or thousands of frames in a test the rounding averages out.

4. The test set

Each test runs one solver at one grid size. Tests are grouped from light to heavy so a range of hardware, from integrated laptop graphics to desktop GPUs, lands somewhere meaningful.

TestGridCellsDefaultWhat it stresses
2D Light640×3600.23 MonPer-frame overhead: dispatch count, driver and browser cost. Small grids are dominated by fixed costs, not memory bandwidth.
2D Standard1280×7200.92 MonA typical working resolution.
2D Heavy2560×14403.7 MonMemory bandwidth and compute throughput of the incompressible solver.
2D Compressible1280×7200.92 MonThe explicit compressible (Euler/HLLC) solver, which uses 32-bit state and more arithmetic per cell. Runs the scene's Solver Steps/Frame (default 4) each frame.
3D Light64³0.26 Mon3D solver plus volume ray-marching at a light grid.
3D Standard128³2.1 MonA typical 3D working resolution; 3D texture bandwidth.
3D Heavy256³16.8 MoffLarge-memory, high-bandwidth GPUs. Needs several GB of free video memory; can fail or stall on smaller or integrated GPUs.
3D Compressible64³0.26 MoffThe 3D compressible solver, which runs 8 substeps per frame. Its pipelines compile on first use, which adds a one-time wait.

The tests use your current scene's obstacles and physical settings; only the grid size and engine are swapped. For results that are strictly comparable between machines, run the benchmark on the same scene: the default scene after Reset All Settings is the reference.

4b. Demo scenes as test sets

Besides the Standard suite, the Test set menu lists every demo scene bundled with the app (the same ones as the Demo tab). Choosing one benchmarks that real scene: its obstacles, painted shapes, meshes, physics options and view settings, at up to four sizes.

Tier2D demos3D demosDefault
Light½ × grid½ × resolutionon
Standardas authoredas authoredon
Heavy2 × grid2 × resolutionon
Extreme3 × grid4 × resolutionoff

Use the Standard suite to compare hardware, and demo sets to answer "how fast will this kind of simulation run on this machine?".

5. What happens during a run

  1. Snapshot. The current view (2D/3D), grid sizes, engines and play state are saved.
  2. Setup. For each test the solver is switched to the test's grid and engine, and its GPU memory is re-allocated. If the solver's GPU programs are not compiled yet, the run waits for them (up to 2 minutes).
  3. Warm-up (3 s, not recorded). The first frames after a re-allocation include driver warm-up and a freshly started flow. They are discarded so they do not drag the averages down.
  4. Measurement. For the chosen duration, every frame's timing is recorded. Only frames that actually advanced the solver are counted.
  5. Restore. After the last test, or when you press Cancel, your scene is put back.

The simulation keeps rendering to the screen throughout, exactly as it does in normal use, so the numbers describe the real application and not an artificial loop.

6. Metrics explained

Max FPS
The best sustained uncapped frame rate: the fastest half-second window of the test. A single frame is too noisy to be a fair "maximum", so short bursts are averaged.
Avg FPS
The uncapped frame rate over the whole measurement: 1000 divided by the mean frame time. This is the headline number for a test.
1% low
The frame rate of the slowest 1% of frames (computed from the 99th-percentile frame time). A big gap between Avg and 1% low means stutter: background work, driver hiccups or thermal throttling.
MLUPS
Million Lattice (grid-cell) Updates Per Second, the standard way to compare CFD solver speed across hardware. It counts how many grid cells the solver advances per second, independent of grid size:
MLUPS = cells × substeps per frame × Avg FPS / 1,000,000
A small grid with high FPS and a large grid with low FPS can reach similar MLUPS; larger grids usually give higher MLUPS because fixed per-frame costs are spread over more cells.
Peak MLUPS
MLUPS in the best half-second window (in the report and exports).
GPU Sim / GPU Render (ms)
Average GPU time spent on the solver and on drawing, per frame (hover a result row, or see the report). Render time depends on your canvas size, not the grid.
CPU (ms)
Average time the browser's JavaScript spent preparing each frame. If this is close to the frame time, the test is CPU-bound rather than GPU-bound.
Display FPS
How many frames were actually shown on screen per second: at most your monitor's refresh rate. Reported for reference only.

7. The throughput score

The score is the geometric mean of Avg MLUPS over the tests that completed:

Score = (MLUPS₁ × MLUPS₂ × … × MLUPSₙ)^(1/n)

A geometric mean keeps one very fast test from dominating: doubling the speed of any single test raises the score by the same factor, whichever test it is. Because the score depends on which tests were included, compare scores only between runs with the same test selection. The report and exports list the tests used.

8. Live readouts in Performance

The same timer runs all the time, so the Performance folder in both the 2D and 3D menus shows live, uncapped numbers for whatever scene you are running:

ReadoutMeaning
Uncapped FPS (real)What this scene would run at without the monitor cap, averaged over 0.5 s.
Display FPS (vsync)Frames actually shown per second (capped by the monitor).
Display Frame (ms)Time between shown frames.
GPU Sim (ms) / GPU Render (ms)GPU time per frame for the solver and for drawing.
CPU Frame (ms)JavaScript time per frame.
MLUPS (M cells/s)Solver throughput; 0 while paused.
Timing SourceGPU timestamps, or the approximate fallback.

These are useful for tuning a scene: raise the grid or iteration count and watch Uncapped FPS and GPU Sim respond, even while the display stays at 60.

9. Getting reliable results

10. Comparing machines

For a fair comparison, keep the following the same on every machine: the scene (use Reset All Settings), the test selection, the duration, the browser, and the window size. Then compare Avg FPS and MLUPS test by test, and the score overall.

The browser may hide the exact GPU model for privacy and report only the vendor and architecture (for example "nvidia · ampere"). Note the exact model yourself when you collect results.

11. Reports and exports

ButtonOutputBest for
HTML ReportA self-contained web page: score, system, all metrics per test, FPS-over-time charts and the method notes. Opens in any browser, prints to PDF.Sharing results, archiving a machine's test
CSVOne row per test with every metric, including the test set.Spreadsheets, collecting many machines
JSONEverything, including system information and the FPS-over-time series.Scripts and databases
CopyA short text summary on the clipboard.Pasting into chat or email

12. Limitations

13. FAQ

Why is Display FPS stuck at 60 when Uncapped FPS says 250?
Your monitor refreshes 60 times a second, so only 60 frames can be shown. The GPU finishes each frame's work in about 4 ms and then waits; 250 is what it could do without waiting.
Why does 2D Compressible get a higher FPS than 2D Incompressible at the same grid?
FPS measures the cost of one frame, not how fast the flow evolves. The incompressible solver must solve a pressure equation every step: about 40 Jacobi iterations, each a full pass over the grid, so roughly 50 GPU passes per step. The compressible solver is explicit: two Runge-Kutta stages and no pressure solve, only about 2–3 passes per step. It runs 4 steps per frame by default (Solver Steps/Frame in the Compressible folder), which the MLUPS figure accounts for. Each compressible step is therefore much cheaper, but explicit solvers must take much smaller time steps (limited by the speed of sound), so they need many more steps to simulate the same physical time. Higher FPS here does not mean the compressible case is the lighter simulation overall.
A test says "no frames measured" or "setup failed".
The GPU could not allocate or run that configuration, most often 3D Heavy on a GPU with too little memory. Untick it and run again.
The browser froze or the GPU reset during 3D Heavy.
The GPU took too long on a frame and the operating system reset it. Reload the page and leave 3D Heavy off on that machine.
Does the benchmark change my scene?
Only temporarily. It restores the view, grid sizes, engines and play state when it finishes or is cancelled. The flow itself restarts, as it does whenever the grid changes.
Why is my score different from yesterday?
Check the power source, other running programs, window size and browser version. Thermal state matters too: a GPU that is already hot runs slower.