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
26 changes: 26 additions & 0 deletions .github/release-notes/v2.1.8.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# RustyNES v2.1.8 — "Fathom" (performance — "Tempo")

The **performance** step of the v2.1.5 → v2.2.0 "deepen the existing project" run — profile-first optimization of the hot render + display paths, every change proven byte-identical (or shipped default-off behind a runtime knob). The shipped default build is **byte-identical**: **AccuracyCoin 141/141 (100.00%)**, nestest 0-diff, `visual_regression` / `pal_apu_tests` unchanged. Two PRs (#284, #286).

## Performance

### Specialized visible-scanline fast PPU dot path (A1) — #284

Profiling (`perf` on a mixed corpus) put `Ppu::tick` at **46.5% of frame self-time** — the real hot spot. A1 adds a straight-line fast path for the common visible-scanline case (visible scanline, dots 1..256, rendering stably enabled, no sub-dot disturbance), running the identical helper sequence with the statically-dead event branches pruned; the instant anything sub-scanline happens (a `$2000/$2001/$2005/$2006/$2007` write, a `$2007` read during render, a sprite-0-hit dot, rendering toggling, an armed OAMADDR corruption from the v2.1.7 `Rp2c02G` model, etc.) it **drops to the exact 2-cycle-ALE loop**.

- **Measured: ~12% faster** on the rendering-heavy `nes_run_frame_nestest` (4.54 → 3.98 ms/frame); neutral on the rendering-disabled path.
- **Byte-identity is the contract, and it's proven:** a new `fast_dotloop_diff` differential test runs the corpus (nestest, flowing_palette, oam_stress, AccuracyCoin, MMC1/MMC3 Holy Mapperel, the scanline demo — and a dedicated `Rp2c02G` OAMADDR-corruption dimension) through **both** the fast and exact paths and asserts **bit-identical** framebuffer + palette-index buffer + audio + CPU cycles + full snapshot, every frame.
- **Shipped default-OFF** (opt-in `Nes::set_fast_dotloop`) — as the roadmap's highest-risk item, it's held off the shipped default this cut so the build stays byte-identical, with promotion-to-default recommended after review + a clean-host Criterion confirmation.
- Honest architectural finding: a *whole-scanline batch* is precluded by the v2.0.0 lockstep every-cycle-bus-access scheduler (the PPU is never invited to run a scanline uninterrupted), so this is a per-dot specialization, not a dot-batch.

### Vectorized software blitter + wasm size/startup (A2/A4) — #286

- **A2 — SIMD software blitter** (`crates/rustynes-frontend/src/gfx_blit.rs`): scalar / `blit_u32` / `wide::u32x8` (desktop) / `core::arch::wasm32` `v128` (`+simd128`) / scalar-fallback paths for the palette-index → RGBA conversion, all asserted **SIMD == scalar byte-identical** (`simd_equals_scalar_byte_identical`, sweeping all 512 LUT entries for both the composite-2C02 and RGB-2C05 LUTs). Honest measured result: the conversion is a **memory-bound LUT gather** (~19 GiB/s, the box's single-thread DRAM ceiling), so no SIMD path clears the >3% adoption bar — the dispatcher **keeps scalar-`u32` as the default** and the SIMD variants are validated + available but not forced. (The NTSC ladders are GPU WGSL shaders, so there's no hot CPU display transform there to vectorize.)
- **A4 — wasm size/startup**: `wasm-opt -O4` (SIMD + bulk-memory preserved) shrinks the wasm-bindgen output 12.7 → 11.1 MiB raw; streaming instantiation documented. Real `trunk build --release`: **3.99 MiB gzip total — 1.01 MiB headroom under the 5 MiB budget.**

## Notes

- Version: workspace `2.1.7 → 2.1.8` (Cargo.lock refreshed). No dependency or mobile changes (the mobile apps re-release at v2.3.0).
- **No default-build behavior change.** The fast dot path is default-off (and differential-tested bit-identical when on); the SIMD blitter is byte-identical to scalar and stays non-default. AccuracyCoin stays **141/141 (100.00%)**, nestest 0-diff, `visual_regression` byte-identical, save-state and the 60-ROM commercial oracle unchanged.
- Docs updated in-change: `docs/performance.md` (the profiled fast-path design + measured before/after + the A2/A4 tables), `docs/ppu-2c02.md`, `docs/frontend.md`. New `fast_dotloop_diff` + `gfx_blit` tests.
- Full host gate green: fmt · clippy (native all-targets + feature combos + all wasm32 targets incl. `+simd128`) · rustdoc (`-D warnings`) · `no_std` cross-compile (`thumbv7em`) · markdownlint (pinned v0.39.0) · the CPU/PPU + `--features test-roms` suites (incl. `fast_dotloop_diff` 3/3, `gfx_blit` 3/3) · a real `trunk build --release` under the gzip budget · version consistency.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ cycle-accurate core later replaced.

## [Unreleased]

## [2.1.8] - 2026-07-12 - "Fathom" (performance — default-off specialized fast PPU dot-loop (differential-tested byte-identical, +12% rendering-heavy) + SIMD software blitter + wasm size/startup pass; "Tempo")

### Performance

- **Specialized visible-scanline fast dot path (v2.1.8 "Performance" A1;
Expand Down
36 changes: 18 additions & 18 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ members = [
]

[workspace.package]
version = "2.1.7"
version = "2.1.8"
edition = "2024"
rust-version = "1.96"
license = "MIT OR Apache-2.0"
Expand Down
25 changes: 13 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</p>

<p align="center">
<a href="https://github.com/doublegate/RustyNES/actions"><img src="https://github.com/doublegate/RustyNES/workflows/CI/badge.svg" alt="Build Status"></a> <a href="#license"><img src="https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg" alt="License: MIT OR Apache-2.0"></a> <a href="https://github.com/doublegate/RustyNES/releases"><img src="https://img.shields.io/badge/version-v2.1.7-blue.svg" alt="Version"></a> <a href="rust-toolchain.toml"><img src="https://img.shields.io/badge/rust-1.96-orange.svg" alt="Rust: 1.96"></a><br>
<a href="https://github.com/doublegate/RustyNES/actions"><img src="https://github.com/doublegate/RustyNES/workflows/CI/badge.svg" alt="Build Status"></a> <a href="#license"><img src="https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg" alt="License: MIT OR Apache-2.0"></a> <a href="https://github.com/doublegate/RustyNES/releases"><img src="https://img.shields.io/badge/version-v2.1.8-blue.svg" alt="Version"></a> <a href="rust-toolchain.toml"><img src="https://img.shields.io/badge/rust-1.96-orange.svg" alt="Rust: 1.96"></a><br>
<a href="#compatibility-and-accuracy"><img src="https://img.shields.io/badge/AccuracyCoin-100%25%20(141%2F141)-brightgreen.svg" alt="AccuracyCoin"></a> <a href="#compatibility-and-accuracy"><img src="https://img.shields.io/badge/nestest-0--diff-brightgreen.svg" alt="nestest"></a> <a href="https://doublegate.github.io/RustyNES/"><img src="https://img.shields.io/badge/play-in%20browser-success.svg" alt="Try in browser"></a><br>
<a href="#platform-support"><img src="https://img.shields.io/badge/platform-Windows%20%7C%20Linux%20%7C%20macOS%20%7C%20Web%20%7C%20Android%20%7C%20iOS-lightgrey.svg" alt="Platform"></a>
</p>
Expand Down Expand Up @@ -775,16 +775,17 @@ and the Material-for-MkDocs documentation handbook at

## Current Release

RustyNES's current release is **v2.1.7 "Fathom" ("Stepping")**, the hardware-revisions
step of the v2.1.5 → v2.2.0 "deepen the project" run — it adds an opt-in, default-off
**selectable PPU die-revision** model (2C02 rev-E+ OAMADDR/`$2003` corruption à la *Huge
Insect*, power-up palette variants) and a **power-on RAM model** (all-zero default, opt-in
seeded/patterned — for games that read uninitialized RAM like *Final Fantasy*), plus an
opt-in **2A03 die-revision** (`Rp2A03G`/`Rp2A03H`) config that gates the "unexpected read"
DMA behavior. The last is the honest **DMA frontier** work: direct instrumentation proved
the die-revision DMA gate is a **no-op on every committed oracle ROM** (and no reference
emulator branches DMA on die stepping), so the residual is documented in **ADR 0033**
rather than faked. Every toggle defaults off, so the shipped build stays **byte-identical**:
RustyNES's current release is **v2.1.8 "Fathom" ("Tempo")**, the performance step of the
v2.1.5 → v2.2.0 "deepen the project" run — it adds a **specialized visible-scanline fast
PPU dot path** (a straight-line renderer for clean dots that drops to the exact 2-cycle-ALE
loop the instant any sub-scanline disturbance occurs; **~12% faster** on rendering-heavy
frames), a set of **SIMD-validated software palette→RGBA blitter paths** (scalar / `wide` /
wasm `+simd128`, all proven byte-identical — but the conversion is a **memory-bound LUT
gather**, so no SIMD path clears the >3% bar and **scalar stays the default**; the vector
paths are validated and available, not a shipped speedup), and a **wasm size/startup pass**
(`wasm-opt -O4` → 3.99 MiB gzip, comfortably under budget). The fast dot path is
**default-off** and **differential-tested bit-identical** to the exact path across the
corpus (framebuffer + audio + cycles + snapshot) — so the shipped build stays byte-identical:
**AccuracyCoin stays 141/141 (100.00%)**, nestest 0-diff, `visual_regression` /
`pal_apu_tests` unchanged.

Expand Down Expand Up @@ -912,7 +913,7 @@ If you use RustyNES in academic research, please cite:
author = {RustyNES Contributors},
title = {RustyNES: A Cycle-Accurate NES Emulator in Rust},
year = {2026},
version = {2.1.7},
version = {2.1.8},
url = {https://github.com/doublegate/RustyNES},
note = {Cycle-accurate NES emulator on a master-clock-precise scheduler;
AccuracyCoin 100\% (141/141), nestest 0-diff; 172 mapper families,
Expand Down
Loading