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
48 changes: 48 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,54 @@ hashes are given where a change is easiest to understand by reading the commit.
produced. (`src/ci.rs`, `src/gfx_display.rs`)
### SGI Octane (IP30)

- **IMPACT on the Octane passes the GL pixel and state suites.** On IRIX
6.5.22 pixtest went from 52 to 366 passing cases (the two left fail on the
Indigo2 too) and glstate from 393 to 394. The Octane's microcode keeps the
pixel pipeline's state blocks 0x8C words higher than the Indigo2's, so
glDrawPixels drew RGB images as RGBA; the colour matrix reads back where
the Indigo2's does. Both boards: RGBA 12,12,12 has no alpha planes, and
glReadPixels now returns alpha 1 from it instead of colour bits.
- **No more "hq idle wait timeout" killing the X server.** The kernel waits
for an idle board by counting polls of a status register, and a context
switch behind long drawing ran out of polls. A status poll of a busy board
now gives it up to 100 µs first, so the wait is counted in work done.
- **Double-buffered GL on the Octane's IMPACT no longer shows half-drawn
frames** (the octahedra / `bongo` saver flickered and tore). The Octane
kernel flips buffers at the retrace after the swap is reported, without
waiting for the board to go idle; the swap was reported when the command
processor reached it, while the raster engine was still drawing the
frame. It is now reported once the frame is drawn. The Indigo2 kernel
drains the board before each swap, so it never showed this. The display
also takes its frame just before the retrace now, as scanout does, so a
flip shows from the next frame.
- **The Octane's board is `[graphics] board`, as on every other machine**,
and `--graphics` works on it. `[octane] graphics` is retired: a config
that still sets it is refused with a message naming the new key. Unset,
the Octane gets the SI; Newport, XZ and Extreme are refused there, and
`board = "none"` (Octane only) leaves the port empty.
- **Perfetto traces on the Octane and the O2** (`--features perfetto`):
the `perfetto` monitor command was registered only on the Indy and
Indigo2. IMPACT traces now show frame capture and present, STATUS reads,
flags, window-ID modes and VC3 tables, the draw bank, the interrupt lines
and, on the Octane, the HEART packets and acknowledgements
(docs/perfetto.md).
- **The Octane takes the textured IMPACT boards.** `[graphics] board =
"highimpact"` is the SI with texture option and `"maximpact"` the MXI
(two GEs, two REs), with the board version bytes the IP30 kernel decodes;
hinv and gfxinfo name them as on the real machine. glstate, pixtest and
blendtest pass on both as on the Indigo2's High and Maximum IMPACT;
iris-gui offers them too.
- **OpenGL selection works on IMPACT** (both machines): glRenderMode
(GL_SELECT) keeps the name stack in the GE and records hits, so picking
with glSelectBuffer returns the objects under the cursor instead of
nothing. glstate now passes all 398 cases on IRIX 6.5.22, and
`test/gltest/seltest` checks hit records against GL's rules.
- **GR2 takes its display frame before the retrace too**, for the same
reason as IMPACT.
- **`mgras status` and the log name the microcode the kernel loaded**: a
fingerprint of the HQ microcode and of each GE11 program, kept as they are
written, and the IRIX release when it is a known one.

- **User programs run from the caches, two to four times faster.** The
Octane's IRIX maps user pages with cache attribute 4 (cacheable coherent
exclusive), which the TLB read as uncached, so every user fetch, load and
Expand Down
7 changes: 3 additions & 4 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ the first `[section]`, because TOML tables continue until the next header.

| Key | Default | Values and behavior |
|---|---|---|
| `graphics.board` | `newport` | newport, xz, extreme, solidimpact, highimpact, maximpact. |
| `graphics.board` | `newport`; `solidimpact` on the Octane | newport, xz, extreme, solidimpact, highimpact, maximpact, none. Extreme is Indigo2 only. The Octane takes only `solidimpact` (SI), `highimpact` (SI with texture option), `maximpact` (MXI) or `none` (no board: the machine runs on its serial console, with no window). |
| `graphics.heads` | `1` | 1 or 2; dual-head requires Newport. |
| `graphics.resolution` | `guest` | guest, 1024x768, 1280x960, 1280x1024; presets require Newport. |

Expand Down Expand Up @@ -326,13 +326,12 @@ DS1685's NVRAM, behind IOC3), `banks` (the MB in each HEART bank: 64, 128, 256
or 512, filled from bank 0 with no gap, 2048 in all), `serial_port_a` and
`serial_port_b`/`serial_log` (IOC3's two ports, the first the console),
`headless`, `[clock] fixed_mhz` (CP0 Count; 97.5 for the 195 MHz R10000),
`[network]`, `[[port_forward]]`, `nat_subnet` and `[scsi.N]` (`controller` 0
is the internal bus, 1 the external). See [the Octane guide](docs/octane-ip30.md).
`[network]`, `[[port_forward]]`, `nat_subnet`, `[scsi.N]` (`controller` 0
is the internal bus, 1 the external) and `[graphics] board`. See [the Octane guide](docs/octane-ip30.md).

| Key | Default | Values and behavior |
|---|---|---|
| `octane.flash` | unset | Boot flash file, keeping the PROM's records between runs. Created from `prom` when missing and then used as it is, so delete it after changing `prom`; a file of another size is refused. Unset, the flash is in memory and every start is from `prom`. |
| `octane.graphics` | `"solidimpact"` | `solidimpact` or `none`. With `none` the machine runs on its serial console, with no window. |

### Hardware constraints and precedence

Expand Down
2 changes: 1 addition & 1 deletion HELP.md
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ profile = "indy_ip24" # "indigo2_ip22" (--ip22), "indigo2_ip28", or "o2_ip
cpu = "r4400" # "r5000" or "r10000" (--cpu); use R10000 on IP28

[graphics]
board = "newport" # "newport", "xz", "extreme", "solidimpact" ("impact:solid"), "highimpact" ("impact:high"), "maximpact" ("impact:max")
board = "newport" # "newport", "xz", "extreme", "solidimpact" ("impact:solid"), "highimpact" ("impact:high"), "maximpact" ("impact:max"); the Octane: IMPACT (default solidimpact) or "none"
heads = 1 # 2 = dual-head Newport (second REX3 in GIO slot 1)
resolution = "guest" # or "1024x768", "1280x960", "1280x1024"

Expand Down
5 changes: 5 additions & 0 deletions clanker/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,8 @@ IMPACT (MGRAS/GR4): HQ3/GE HLE, RSS raster, pixel memory, the raster JIT. Hardwa
| [DESIGN.md](mgras/DESIGN.md) | design | MGRAS (IMPACT / GR4) design |
| [ci-copypixels-unloaded-mask.md](mgras/ci-copypixels-unloaded-mask.md) | rule | Colour-index glCopyPixels runs a pipeline whose state is never loaded |
| [dcb-device-numbers.md](mgras/dcb-device-numbers.md) | design | DCB device numbers: trust the kernel's dcbctrl writes, not header names |
| [display-snapshot-before-retrace.md](mgras/display-snapshot-before-retrace.md) | rule | Take the IMPACT display frame before the retrace pulse, not after |
| [swap-waits-for-raster.md](mgras/swap-waits-for-raster.md) | rule | Report SCHEDULE_SWAP only once the raster engines have drawn the frame |
| [engine-pipeline-performance.md](mgras/engine-pipeline-performance.md) | measure | The CPU -> HQ -> raster pipeline: what made it slow |
| [eram-state-ideas.md](mgras/eram-state-ideas.md) | open | GE state and ERAM: ideas for later (not built) |
| [eram-transfers-come-once-per-ge.md](mgras/eram-transfers-come-once-per-ge.md) | rule | ERAM transfers come once per GE11 (Maximum Impact) |
Expand All @@ -227,8 +229,10 @@ IMPACT (MGRAS/GR4): HQ3/GE HLE, RSS raster, pixel memory, the raster JIT. Hardwa
| [gr4-jit.md](mgras/gr4-jit.md) | design | The raster JIT (`gr4-jit`) and its Cranelift pitfalls |
| [guest-gl-benchmarks-miss-board-time.md](mgras/guest-gl-benchmarks-miss-board-time.md) | rule | Board-bound GL work slows the guest clock (and guest benchmarks lie) |
| [header-errata.md](mgras/header-errata.md) | design | ignore/gr4 headers: known errata (fold back into the headers) |
| [hq4-microcode-moves-eram-state.md](mgras/hq4-microcode-moves-eram-state.md) | rule | The Octane's IMPACT microcode keeps ERAM state 0x8C words higher |
| [mirai-pixel-zoom-and-scissor-query.md](mgras/mirai-pixel-zoom-and-scissor-query.md) | rule | Mirai: flipped glDrawPixels and the scissor-enable readback |
| [pixel-path-pixtest.md](mgras/pixel-path-pixtest.md) | howto | IMPACT pixel path: pixtest and what it settled |
| [texture-readback-follows-libglcore.md](mgras/texture-readback-follows-libglcore.md) | rule | glGetTexImage of ALPHA, LUMINANCE, INTENSITY and RGB textures is libGLcore's, not the board's |
| [window-origin-is-signed.md](mgras/window-origin-is-signed.md) | rule | The GL window origin's halves are signed |

## odyssey/
Expand Down Expand Up @@ -349,6 +353,7 @@ Running IRIX as a guest: install, network setup, shell quirks, X defaults.
| Note | Kind | Title |
|---|---|---|
| [cdrom-changer-eject-no-mediad-remount.md](irix/cdrom-changer-eject-no-mediad-remount.md) | open | CD changer `cdrom-eject` does not trigger a `mediad` remount |
| [delay-loop-calibrated-cold.md](irix/delay-loop-calibrated-cold.md) | open | IRIX calibrates its microsecond delay loop cold, so JIT runs make every delay short (Octane "RAD UST not incrementing") |
| [default-visual-24bit.md](irix/default-visual-24bit.md) | rule | The ip28 guest's X server defaults to 24-bit TrueColor |
| [irix-csh-scratch-raw-device-gotchas-when-you-cant-use-iris-ci.md](irix/irix-csh-scratch-raw-device-gotchas-when-you-cant-use-iris-ci.md) | howto | IRIX csh + scratch raw-device gotchas (when you can't use iris-ci) |
| [irix-install.md](irix/irix-install.md) | howto | Installing IRIX on iris (from scratch) |
Expand Down
55 changes: 55 additions & 0 deletions clanker/irix/delay-loop-calibrated-cold.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# IRIX calibrates its microsecond delay loop cold, so JIT runs make every delay short

Status: open (measured 2026-10-28 on IP30, IRIX 6.5.22; no fix chosen).

**Symptom.** Some Octane boots print
`ALERT: /hw/node/xtalk/15/pci/3: RAD UST not incrementing` before
"The system is coming up". In the guest's SYSLOG it showed up on 5 of about
62 boots. Two of four jitv2 test boots raised it. The alert is cosmetic: the driver prints
it and goes on, and audio works.

**What the driver checks.** It reads the RAD UST counter (BAR0 0x30), spins a
10 µs kernel delay, reads it again and alerts if it moved by fewer than 3
ticks. The kernel converts with 960 ns per tick (it divides by 0x3c0), so
`UST_NS_PER_TICK` in `src/dev/rad1.rs` must stay 960. The counter itself is
fine. The model enables it on 0x0e then 0x0a, and it counts.

**Cause.** The kernel's microsecond delay is a counted spin loop (11 taken
`bgtz` to self per outer pass). The kernel calibrates it once, early in boot,
against the HEART counter, which runs on host time. The figure kept is the
nanoseconds per outer pass, stored as a word at 0xffffffffffffc4b8 (read it
with `mem 0xffffffffffffc4b8 1 w`). At that point the loop runs cold:

| build | calibrated ns/pass | the same loop later, quiet guest |
|---|---|---|
| interpreter (default features) | 567 (0x237) | ~610 measured in user mode |
| jitv2 | 1148–1267 (0x47c–0x4f3) | ~180–300 (9 passes in 1.6–2.7 µs) |

Under jitv2 every kernel `us_delay` therefore lasts about one fifth of what
was asked. For the RAD check, "10 µs" took 1.6–2.7 µs of host time, which is
2 or 3 UST ticks: right at the threshold, hence one boot in a few.

On the interpreter the calibration matches the steady speed and the check
passes (29 ticks in a traced boot).

**Wider impact.** This is not specific to RAD. Every driver delay and every
delay-counted timeout in the kernel is about 5× short on jitv2. Most of our
devices answer at once, so it rarely matters. Suspect it when a jitv2-only
spurious timeout appears on a device that completes asynchronously (SCSI,
AIC-7880, ISP1040).

**Seen biting.** IMPACT's context switch waits for an idle board with up
to 6 x 15000 DMABUSY polls 10 us apart (about 0.9 s nominal, about 0.2 s
under jitv2). Behind long drawing it ran out, logged "mgras: hq idle wait
timeout" and "FlushHQ (rss_sh) failed for ctxsw", and the X server died.
Mitigated in the model: a STATUS or DMABUSY read of a busy board waits up
to 100 us for it (`Mgras::settle`), so polls count board work.

**Reproduce.** On an overlay boot, trace BAR0 0x30 reads in `Rad1::bar_read`
silently (store the first read, print both at the second). An `eprintln!` or
a gdb breakpoint on the first read stretches the delay and hides the bug.

**Fix options (none chosen).** Make the calibration see steady-state speed
(for instance, have jitv2 compile a page synchronously when it spins in a
tight self-loop at boot). Or accept the short delays and keep device models
tolerant of them. Do not slow the RAD UST down: the kernel hard-codes 960 ns.
26 changes: 26 additions & 0 deletions clanker/mgras/display-snapshot-before-retrace.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Take the IMPACT display frame before the retrace pulse, not after

Status: rule (2026-10-28).

**Symptom.** Double-buffered GL (octahedra, the `bongo` saver) tore on the
Octane, although a trace showed the guest correct: SCHEDULE_SWAP, the
kernel's XMAP `buf_select` flip about 7 ms later in its retrace handler, and
the next frame's drawing only after the flip.

**Cause.** The refresh thread raised the retrace, slept 500 us, then copied
the visible frame (a millisecond or more). The handler's flip landed inside
that copy, and the client began drawing into the buffer being copied.

**Rule.** Snapshot first, then pulse the retrace (`Mgras::refresh_loop`).
A flip made at retrace N shows from frame N+1, as scanout does. The STATUS
vertical-blank window (`begin_frame`) moves with the pulse.

GR2 (`Gr2::display_loop`) had the same order (pulse, hold the vertical
status 700 us, then compose) and now composes first too. REX3 was already
right: it copies the framebuffer and the XMAP/VC2 state into the screen
caches, then raises VBLANK; it only presents the live framebuffer when no
pixel changed since the last copy.

This was not the whole Octane tearing: the main cause was the swap being
reported before the raster engines finished the frame
(`swap-waits-for-raster.md`).
6 changes: 4 additions & 2 deletions clanker/mgras/gl-state-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ default has, reads it back with the matching getter, and checks the
answer. It is board-neutral (GL, GLX, Xlib only), so the same program
maps GR2 later; only the decoders are per board.

As of 2026-10-13 on MaxImpact (6.5.22): 397 pass, 1 fail (selection-mode
name stack, not modelled), 15 XFAIL (libGLcore's own departures, listed in
As of 2026-10-28 on IRIX 6.5.22 (IP28 High IMPACT, Octane SI with texture
and MXI): 398 pass, 0 fail; the selection name stack is modelled
(`Gl::sel`, test/gltest/seltest.c). Earlier (2026-10-13, MaxImpact): 397
pass, 1 fail (name stack), 15 XFAIL (libGLcore's own departures, listed in
the test), 20 skipped (extensions IMPACT rejects).

The map itself is in ignore/gr4/GE11.h "STATE READBACK". The model is
Expand Down
32 changes: 32 additions & 0 deletions clanker/mgras/hq4-microcode-moves-eram-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# The Octane's IMPACT microcode keeps ERAM state 0x8C words higher

Status: rule (IRIX 6.5.22, measured 2026-10-28). Check again on 6.5.30.

**Symptom.** On the Octane, pixtest failed 314 of 486 cases on IRIX 6.5.22:
glDrawPixels of RGB images came out shifted by one component (8-byte RGBA16
pixels read from 6-byte RGB16 rows), and glGetFloatv(GL_COLOR_MATRIX_SGI)
read zeros. The same binaries pass on an IP28 with 6.5.22.

**Cause.** libGLcore and the GE11 microcode put state where the microcode
wants it, and the HQ4 build moves some blocks:

| What | Indigo2 (HQ3) | Octane (HQ4) |
|---|---|---|
| GL state from the stipple enables up (viewport 0xB91, clear values, ...) | I2 address | +0x8C |
| pixel pipeline ERAM blocks (expand state 0xD05, transfer size 0xDA8, texture load 0xDB1-0xDBB) | I2 address | +0x8C |
| pipeline program (0x13B), module states in GE memory (0x187-0x1EF), zoom (0x191) | same | same |
| colour matrix readback (0x1B9-0x1C8) | same | same |

The GE11 program is identical on both 6.5.22 boards (fingerprint
`5467b0da1d106495`); only the HQ microcode differs (`mgras status`).

**Rule.** `Hq4Quirks` translates to the Indigo2 layout on the way in:
`ge_state` for readbacks, `pixel_state_address` for PIXEL_STATE /
INIT_PIXEL_STATE_ERAM and for the module state addresses in a program write
(`Hq3Engine::canonical_pixel_state`). The pixel HLE and `PixMem` only know
the Indigo2 layout. A new release's layout should be told apart by the
microcode fingerprint (`src/dev/mgras/microcode.rs`), not by the board.

**Also.** IRIX base 6.5 on the IP28 reads viewport, scissor, clear values and
raster state from addresses the 6.5.22 map does not answer (glstate: 31
failures). Not mapped yet.
31 changes: 31 additions & 0 deletions clanker/mgras/swap-waits-for-raster.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Report SCHEDULE_SWAP only once the raster engines have drawn the frame

Status: rule (2026-10-28).

**Symptom.** Octane (IP30) IMPACT, double-buffered GL fullscreen (`bongo
-geometry 1280x1024+0+0`, the octahedra saver): flicker and tearing, frames
shown half drawn. Shots taken with the CPU stopped (`stop`, `mgras shot`)
showed the displayed half partly cleared and redrawn. The IP28 was fine.

**Cause.** The HQ3 model raised the swap flag (FLAG_CP0, HQ4 pending bit
10) when SCHEDULE_SWAP reached it in `hq_fifo`. The frame's raster writes
could still be queued in `rss_fifo`. The Octane kernel's retrace handler
reads STATUS and flips XMAP buf_select once the swap is pending, even on
0x55 (HQ idle, board busy). The real pipe behind the HQ is shallow; ours
is not. The Indigo2 kernel flushes FIFOSTATUS to 0 (whole board idle,
`cfifo_level`) before each swap, so it never saw the race.

Not the cause, checked: bank tracking. VALIDATE_BANKS alternates [0,0] and
[1,1], context restores carry the same bank in image words 16-17, and every
flip shows the other half. The gr4 raster JIT was not the cause either:
`mgras jit off` showed the same partial frames.

**Fix.** `Hq3Sink::finish_raster` (drain `rss_fifo`) before `set_flags
(FLAG_CP0)` in the CP_SCHEDULE_SWAP handler. Perfetto: `swap waits raster`
on `IMPACT swap wait`.

**Visual note.** On the Octane the SI offers no double-buffered 24-bit
visual, so GLUT's double-buffered RGB gets the 12-bit pair visual
(DRAW_BUFFER [2, 1], XMAP format 7, both buffers in one word); on the IP28
it gets 24-bit [4, 1]. A bug in one of these paths shows on one machine
only.
20 changes: 20 additions & 0 deletions clanker/mgras/texture-readback-follows-libglcore.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# glGetTexImage of ALPHA, LUMINANCE, INTENSITY and RGB textures is libGLcore's, not the board's

Status: rule (IRIX 6.5.22, 2026-10-28).

**Symptom.** texsweep's `texread` lines report differing bytes for most
sized formats: ALPHA textures read back `ff000000` (alpha in red), RGB,
LUMINANCE and INTENSITY with alpha 0. The output is byte-for-byte the same
on an IP28 High IMPACT, an Octane SI with texture and an MXI.

**Cause.** libGLcore asks the TE for the texture's own components (XFRMODE
format 9 for one, 0xA two, 7 RGB, 8 RGBA; the DMA lengths match) and then
expands them on the host by component count alone: one component to red,
two to red and alpha, anything missing 0. GL 1.1 would give (0,0,0,A),
(L,0,0,1) and (R,G,B,1). Details in the local `ignore/gr4/GE11.h`.

**Rule.** Do not "fix" the model's texel readback (`Te1::read_line`) to
produce GL's answer: it returns what the board returns, and libGLcore's
expansion is what an application on real IRIX sees. Counts that are GL-
correct: RGBA formats 0, rgba2/rgba4 176, luminance-alpha 120 (plus 4-bit
rounding).
2 changes: 1 addition & 1 deletion clanker/skills/irix-gfx-loop/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ the compiled path. If the user has a build they use, build into your own

`test/gltest` holds board-neutral GL/X programs: `glprim` (one primitive per
run, `--help` lists every option), `gltest` (scenes and `--bench`),
`pixtest`, `blendtest`, `glstate`, plus helpers `xgetpix` (read root-window
`pixtest`, `blendtest`, `glstate`, `seltest` (selection hit records), plus helpers `xgetpix` (read root-window
pixels through XGetImage), `xclick`, `xresize`. `irix.sh` builds them with
MIPSpro.

Expand Down
Loading
Loading