Skip to content
Merged
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
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,8 @@ jobs:
# own behavioral tests and exact guide receipts; this job needs no GPU.
- name: Lint the WASM target and generate the browser application
run: just web-verify
- name: Check the optional WASM measurement adapter
run: just benchmark-web-check
- name: Save verified WASM dependencies and build output
if: success() && github.event_name == 'push' && github.ref == 'refs/heads/main' && steps.dependencies.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
Expand Down
3 changes: 2 additions & 1 deletion .oxfmtrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"/docs/preview/vendor/**",
"/vendor/**",
"/examples/**",
"/fixtures/gltf/*/**"
"/fixtures/gltf/*/**",
"/fixtures/benchmarks/*/source/**"
]
}
1 change: 1 addition & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ repository's pre-push hook. The optional CI container supplies Node. Run
`just fmt` before checking your changes with `just verify`.

- [Development](docs/development.md): setup, commands, and document/import formats.
- [Testing](TESTING.md): verification entry points, performance tooling, external suites, and fixture sources.
- [Architecture](docs/architecture/architecture.md): responsibilities and state boundaries.
- [Proposals](rfcs/README.md): concrete future ideas, rationale, and unresolved decisions; short work items stay in [TODO.md](TODO.md).
- [Documentation pipeline](docs/architecture/documentation-pipeline.md): executable guides, input replay, callouts, and animation authoring.
Expand Down
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ edition = "2024"
rust-version = "1.95"
publish = false

[features]
# Contributor-only instrumentation; normal builds contain no clocks or recorder.
viewport-measure = []

[lib]
crate-type = ["rlib", "cdylib"]
# This private application library owns the existing single test inventory.
Expand Down
129 changes: 129 additions & 0 deletions TESTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Testing N3

This is the contributor index for verification, benchmark tooling, reference
suites, and test assets. Setup and environment details live in
[development](docs/development.md); change and review rules live in
[AGENTS.md](AGENTS.md). The [justfile](justfile) defines the executable commands.

## Choose the evidence you need

| Task | Entry point | What it establishes |
| ---------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------- |
| Verify an implementation change | `just verify` | Native formatting, Clippy, tooling regressions, and the complete Rust suite including exact guide replay |
| Run Rust behavior tests | `just test` | The native Rust suite, including guide checks; accepts Cargo test arguments for focused runs |
| Check generated user documentation | `just docs check` | Read-only replay and exact artifact verification on the native renderer |
| Check the browser build | `just web-verify` | Node wrapper regressions, WASM Clippy, and static-site generation; does not launch a browser |
| Check development tooling | `just tools-test` | Python tooling tests and Node wrapper tests |
| Check real Metal capture | `just test-metal` | Focused capture regressions on a Mac with an available GPU |
| Reproduce CI environments | `just ci`, `just ci-macos` | Explicit Ubuntu Docker checks, or macOS bundle/signing and native integration checks, respectively |
| Measure the viewport | `just measure-viewport --help` | Discover the opt-in native/browser build-run-measure workflow and its workload options |
| Repeat and compare measurements | `just benchmark-viewport --help` | Run a controlled suite, aggregate raw reports, and compare a baseline with a candidate |
| Verify benchmark correctness | `just benchmark-test` | Recorder, evidence admission, statistics, lifecycle, browser policy, and renderer parity contracts |

`just verify` remains the native development gate. Browser installation and
performance measurements are separate, optional workflows. See
[verification and hooks](docs/development.md#verification-and-git-hooks) for
the full environment and CI boundaries.

`just benchmark-web-check` compiles and lints the optional WASM measurement
adapter; the WASM CI lane includes this check alongside the ordinary browser
build. It does not install or launch a browser.

Compilation, replay, device interaction, and performance measurements answer
different questions. Report which evidence was collected. In particular, a
successful WASM build does not prove browser input behavior, and a short CPU
submission time does not measure GPU completion or display latency.

## Browser runtime and performance

The [browser development guide](docs/development.md#browser-build) owns runtime
interaction checks, embedding, and host limitations. Native and web share one
feature UI and user guide.

The [viewport measurement guide](docs/architecture/viewport-measurement.md) owns
setup, commands, report fields, workload definitions, fixture selection, and
comparison methodology. Its `editor`, `viewport`, and `renderer` modes measure the
complete app, editor viewport without egui, and scene renderer without editor
projection/feedback, respectively. They preserve the scene dimensions and record
execution counters to verify exclusions; **Hide UI** is not an isolation mode.
Its Playwright runner launches a fresh isolated Chromium
profile, supports headless and headed execution, and saves browser/GPU diagnostics
with raw samples. This currently automates the measurement loop, not every editor
interaction. The [Playwright browser documentation](https://playwright.dev/docs/browsers)
explains browser installation and execution modes; N3's tooling owns its version
pin and local cache paths.

Use the [repeated benchmark workflow](docs/architecture/viewport-measurement.md#repeated-benchmark-workflow)
for performance investigations. It runs fresh application/browser instances in
seeded shuffled rounds and writes raw reports, logs, and CLI/JSON summaries. The
suite defaults to a visible isolated browser because headless mode can change
frame pacing. It summarizes independent runs without treating every frame as an
independent repetition. Results are descriptive evidence, not a statistical
significance claim or a performance CI gate.

The implementation lives in [`tools/benchmark/`](tools/benchmark/README.md), with
its own tests and wire-contract fixtures. These correctness tests run in ordinary
verification and CI; timing collection stays opt-in.

Benchmark internal workloads: authored topology, scene rendering, object count,
or deformation. Keep format decoding correctness and loading performance distinct
from steady-state rendering. The
[authoring and interchange contract](docs/architecture/authoring-and-interchange.md)
explains why editable meshes and immutable scene assets need different coverage
even when an optimization uses shared code. Each platform retains its own
[capabilities and performance policy](docs/architecture/platform-boundaries.md).

## External reference suites and tools

These are resources to select from for a concrete investigation. Listing an
upstream suite here does not mean N3 runs or passes it in CI. Keep selected cases
and tool versions reproducible, and record their purpose alongside the result.

| Resource | Use in N3 |
| ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| [Khronos glTF Asset Generator](https://github.com/KhronosGroup/glTF-Asset-Generator) | Targeted importer cases covering accessors, buffers, materials, primitives, animation, and other glTF semantics |
| [Khronos glTF Validator](https://github.com/KhronosGroup/glTF-Validator) | Validate source assets independently of N3; valid input does not establish correct rendered output |
| [WebGPU Conformance Test Suite](https://github.com/gpuweb/cts) | Investigate behavior defined by the graphics API; the suite can run inside WPT or standalone, and does not provide an editor performance score |
| [wgpu testing guide](https://github.com/gfx-rs/wgpu/blob/trunk/docs/testing.md) | Find upstream validation, GPU, shader, and benchmark coverage when reducing a problem to wgpu or naga |

Use the [executable documentation pipeline](docs/architecture/documentation-pipeline.md)
for N3's feature behavior and rendered guide evidence. Upstream conformance tests
complement that application-specific contract.

## Benchmark methodology

These primary references explain the practices used by N3's benchmark tooling.
They are useful beyond any one renderer optimization; they are not dependencies
of the suite or evidence that N3 implements each framework's statistical model.

| Resource | Practice to apply |
| ----------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| [pyperf runner](https://pyperf.readthedocs.io/en/stable/runner.html) and [analysis](https://pyperf.readthedocs.io/en/stable/analyze.html) | Fresh worker processes, explicit warm-up, repeated measurements, raw samples and metadata, and visible noise rather than a single score |
| [Google Benchmark user guide](https://google.github.io/benchmark/user_guide.html) | Random interleaving to reduce ordering bias, repeated-run aggregates, and machine-readable results |
| [Criterion analysis](https://bheisler.github.io/criterion.rs/book/analysis.html) | Separate warm-up, measurement, and comparison; inspect outliers and distinguish small changes from noise |

N3's harness measures the real application's retained frame window. Whole-command
timing includes startup/import/shutdown, and a function microbenchmark has a
different execution model. Use those tools for a matching question rather than
substituting them for end-to-end frame measurements.

## Fixture sources and provenance

| Source | Role |
| ------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| [N3 geometry fixtures](fixtures/README.md) | Canonical small geometry and import cases, with expected topology and source attribution |
| [N3 glTF fixtures](fixtures/gltf/README.md) | The existing pinned Khronos subset, license records, checksums, and validator evidence |
| [Chess Set benchmark fixture](fixtures/benchmarks/chess-set/README.md) | Pinned CC0 Poly Haven scene, reproducible GLB, and source/derivative validation for native/browser measurements |
| [Khronos glTF Sample Assets](https://github.com/KhronosGroup/glTF-Sample-Assets) | Curated representative and feature-focused models; licensing is per asset, not uniformly CC0 |
| [Poly Haven](https://polyhaven.com/) and its [asset license](https://polyhaven.com/license) | CC0 models, textures, and environment maps when the existing corpus lacks a needed workload |

Reuse existing fixtures first. Keep canonical assets and attribution in
`fixtures/`; keep disposable experiments and review output in ignored local
storage. Preserve upstream files and record exact revisions, bytes, and checksums.
Derived fixtures need a reproducible recipe and their own provenance. The
[benchmark fixture policy](docs/architecture/viewport-measurement.md#workloads-and-fixture-selection)
defines the initial workload plan and suggested size budgets.

When adopting another suite or tool, add its authoritative link here and explain
which layer it tests. Put detailed instructions beside the owning workflow;
keep execution receipts and temporary results out of this index.
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ and document the implemented behavior in the executable user guide.
| Native accessibility | Wire egui's AccessKit adapter through the native event loop, add polite live announcements for toasts, and verify with VoiceOver. Keyboard focus and widget labels alone do not provide screen-reader announcements. |
| OS font support | Fonts as document materials; discovery/loading boundary suitable for a later web implementation. Separate from bundled interface fonts. |
| Web platform follow-up | Build on the [WASM browser baseline](docs/development.md#browser-build): browser/device coverage, touch/IME, external asset resource workflows, persistence/recovery, and multiple embedded workspaces. |
| Native/browser viewport profiling | [Issue #2](https://github.com/gridaco/n3/issues/2): reproduce large-mesh orbit stutter with comparable builds, real browser runs, and stage-level timings. Distinguish shared costs, web-host mistakes, and measured platform constraints before optimizing; follow the WASM baseline delivery. |
| Native/browser viewport optimization | [Issue #2](https://github.com/gridaco/n3/issues/2): use the [measurement harness](docs/architecture/viewport-measurement.md) to attribute large-mesh orbit costs and validate focused improvements. Physical-input latency, GPU/presentation timing, and additional workload families remain follow-ups. Preserve each host's performance policy; optimization is deferred. |
| Linux capture test concurrency | Diagnose allocator aborts and segmentation faults in parallel lavapipe tests, reproduced on the native baseline. The [opt-in CI runner](docs/development.md#verification-and-git-hooks) uses one test thread while retaining every test and exact capture; establish the cause before restoring parallel execution. |
| Exact macOS 27 guide frame | Replace the approximate static window chrome with measurements from a native macOS 27 N3 capture or Apple's design kit. Evaluate the translucent traffic-light material, highlights, and shadow only if exact mirroring is worth the rendering complexity. |

Expand Down
76 changes: 76 additions & 0 deletions docs/architecture/frame-meter.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Application frame meter

The shared **View → Developer → Show FPS Meter** action enables a transient,
off-by-default observation of host frame cadence. Native and browser use the
same arithmetic and overlay. The native host retains its presentation policy;
the browser retains its event-loop scheduling. This is a small live diagnostic,
separate from the repeatable [viewport measurement harness](viewport-measurement.md).

## Measurement contract

Each host records one monotonic timestamp after a successful application frame
has been submitted and its surface presentation requested. Failed acquisition,
skipped rendering, and egui layout retries do not count as completed frames. The
first timestamp establishes an anchor; subsequent timestamps define intervals.
For N intervals lasting T seconds, the sample is N/T FPS and 1000T/N ms per frame.
It is not an average of instantaneous reciprocal frame times.

A sample publishes on a successful frame once its observation span reaches at
least 500 ms. Actual elapsed time is the denominator, including scheduling gaps,
surface-acquisition waits, idle time, and stalls. There is no assumed refresh
rate, fabricated sample at startup, clamping of long intervals, or catch-up timer.
Toggle-off discards observations so re-enabling starts a fresh measurement.

These timestamps establish application submission cadence. They do not establish
GPU completion, display refresh, compositor presentation, dropped display frames,
or input-to-display latency. Host/browser throttling can reduce the observed
cadence; a low idle rate is not evidence of slow scene rendering. The visible
label is **App FPS · last sample**, with a waiting state before the first sample.

## Execution and UI boundaries

The meter never requests a redraw or installs a timer. An idle window retains
its last painted sample until ordinary work causes another frame. The host
records after painting, so a newly published sample becomes visible on a later
ordinary frame. Shared measurement state accepts elapsed monotonic timestamps;
it does not own native or browser clock APIs.

Collection uses bounded state and constant work per observed frame. It introduces
no GPU query, readback, fence, wait, per-frame allocation, or sorting.
Enabling any overlay has some CPU and draw cost; this contract promises a small,
bounded observation path, not mathematically zero overhead. Disabling the meter
avoids collection, per-frame clock reads, and its overlay. Display text is
formatted only when its sample changes.

The overlay deliberately uses a plain black rectangle and monospace white/gray
text in either theme. It is anchored to the bottom-right corner of the application
window, independent of the viewport rectangle, panels, and other overlays.
Its paint-only geometry has no hit target or focus owner.
Hiding the UI hides the overlay while leaving collection enabled.
The toggle is not a document edit or persistent preference and has no shortcut.
Use the ordinary semantic action and menu presenter, not a second UI-specific
toggle path.

## Verification boundaries

Pure recorder tests use explicit timestamps to verify the denominator, initial
anchor, publication interval, variable cadence, long gaps, and toggle resets.
Host wiring must count only successful surface presentation submissions and must
not introduce extra wakeups. UI tests cover paint-only input behavior and normal
menu dispatch.

For an overhead comparison, use repeated runs of the same build and scene with
the real toggle off/on, record that state, and keep frame dimensions and workload
identical. Include a stationary forced-rendering case as well as orbit: expensive
projection can mask a small overlay cost. Compare run-level cadence and CPU
distributions, retaining tails and background-load limitations. A browser clock
read, a rendered overlay, and an extra scheduling loop are different costs;
the meter adds only the first two. The measurement harness records actual meter
state so unlike workloads cannot silently become repeats of one case.

The [executable user guide](../guide/fps-meter.md) exercises the actual menu,
waiting overlay, Hide UI, and unchanged document, selection, settings, and undo
history. Its replay clock is deliberately not passed off as a host presentation
clock: the screenshot shows a waiting meter, never invented numeric performance.
Runtime native/browser observation is required to establish actual host cadence;
the guide is UI evidence, not a performance result.
Loading
Loading