Skip to content

iris-hostgl: run command buffers on a GL thread of their own - #218

Merged
techomancer merged 2 commits into
techomancer:mainfrom
atomchild411:hostgl-thread
Oct 13, 2026
Merged

techomancer merged 2 commits into
techomancer:mainfrom
atomchild411:hostgl-thread

Conversation

@atomchild411

Copy link
Copy Markdown
Contributor

Host GL answered every call on the emulated CPU's thread, so while it replayed a command
buffer the whole machine stood still: no instruction ran and no interrupt was taken. Big
buffers take 2-5 ms. IRIX then took its 1 kHz clock late ~15 times a second, its audio
driver refilled HAL2's DMA ring late, and sound crackled under any busy GL program, on
every loud low note of a demo, in Quake, and so on. It is the host-GL side of
rules/mgras/guest-gl-benchmarks-miss-board-time.md: whatever keeps the guest from taking
its ticks costs it time and sound.

What changes

The GL service runs on a thread of its own (iris-hostgl/src/threaded.rs), which owns
every context. The CPU thread only hands it work, through a bounded queue:

  • OP_BATCH_ASYNC (new, 0x1_0010): the buffer is read on the CPU thread (a missing
    page is still a need-page answer), queued, and answered at once. Only buffers that read
    nothing outside themselves may be sent this way; one that tries is cut short and logged.
    A host without the thread treats it as OP_BATCH; an older host answers EINVAL, which
    is how the library finds out. The protocol number does not change.
  • Every other call goes through the same queue, so calls stay in order, and the CPU
    thread waits for its answer. It lends the GL thread its view of guest memory for the
    call, so a call costs two hand-offs however much it reads.
  • ENOTYET (new, 0x3001, iris-hostcall): a call from a client with buffers still
    queued is answered at once instead of waiting behind them; the library yields and asks
    again, so IRIX keeps running. Only a client that has sent OP_BATCH_ASYNC can have
    buffers queued, so older libraries never see it.

IRIS_HOSTGL_INLINE=1 keeps the service on the CPU thread, for comparison.

The guest half (sending self-contained buffers with OP_BATCH_ASYNC, retrying on
ENOTYET, IRIS_GL_ASYNC=0 to turn it off) is in iris-guest-tools,
gl/glshim_rt.c.

Measurements

Same features (hostgl,jitv2,tcache,lightning,gpujit), same disk, this tree with and
without the change. IP28, Max IMPACT, IRIX 6.5.22, macOS; the guest's libGL is the
host-GL library with async buffers.

without with
GL demo with synthesized sound: dropouts 19.7/s (31 ms lost/s) 3.7/s (4 ms lost/s)
Quake 3 demo four timedemo, 1024x768 30.2-30.5 fps 33.9 fps
glcheck (host GL) PASSED, 227-238 fps PASSED, 247 fps
gltest n32 / o32 see below 167/167, 167/167
irisgltest all ok all ok
boot to ssh, idle iris CPU 27 s, 208% 27 s, 208%
the demo's score with no graphics 0 dropouts 0 dropouts
SGI's libGL on the emulated board (glcheck) 40-45 fps 42 fps

The library on a host without the thread falls back to OP_BATCH: gltest 6 of 6 runs
clean. Libraries without OP_BATCH_ASYNC work unchanged. gltest's "the window kept its
pixels" fails now and then on either build (once in 12 runs without the change, with or
without the new library); it is not this change.

Tests: 4 new in iris-hostgl/src/tests.rs (buffers sent without waiting run in order
before a waited call; one that reaches for memory is cut short; on the CPU thread an async
buffer is a plain batch; a call behind queued buffers is told to come back). 31/31 on
macOS and on Linux (Debian 12 arm64, with a Linux backend that is not in this PR).
iris-hostgl is outside the workspace, so CI does not build it.

Notes

  • The thread wants a host core beside the CPU thread. On a 4-vCPU VM it cost frame rate
    (Quake 3 22.8 -> 14 fps); IRIS_HOSTGL_INLINE=1 is there for such hosts.
  • Emulated graphics boards do not go through any of this.
  • See rules/hostgl/host-calls-hold-the-cpu-thread.md.

🤖 Generated with Claude Code

A host call is answered on the thread that runs the emulated CPU, so while
host GL replayed a buffer the whole machine stood still: no instruction,
no interrupt. Big buffers took 2-5 ms; IRIX acknowledged its 1 kHz tick
late ~15 times a second, its audio driver refilled its few-millisecond DMA
ring late, and HAL2 played gaps -- 3% of a demo's sound, heard as crackle
on every loud low note.

The service now runs on a thread of its own (threaded.rs), which owns
every context. The CPU thread hands it work through a bounded queue:

- OP_BATCH_ASYNC (new): the buffer is read on the CPU thread (need-page as
  ever), queued and answered at once. The library sends only buffers that
  read nothing outside themselves this way; one that tries is cut short
  and logged. Without the thread it is OP_BATCH; a host without it answers
  EINVAL, which is how the library finds out -- the protocol number stays.
- Every other call runs on the GL thread after the buffers before it, and
  the CPU thread waits for its answer. It lends the GL thread its view of
  the program's memory for the call -- it does nothing else until the
  answer comes -- so a call costs two hand-offs however much it reads.
- But a call from a client with buffers still queued is answered ENOTYET
  (iris-hostcall: new, 0x3001) at once rather than held behind them: the
  library yields and asks again, so IRIX keeps running. Only a library that
  sends OP_BATCH_ASYNC can have buffers queued, so only one that knows
  ENOTYET is told it.

Each side spins ~30 us for its next message before it sleeps, since most
answers come sooner than a thread wakes. IRIS_HOSTGL_INLINE=1 keeps the
service on the CPU thread. The thread wants a host core of its own beside
the CPU thread.

Measured against this tree without the change, same binary features and
disk (IP28, Max IMPACT, macOS, the guest's libGL the async host-GL
library): a GL demo with synthesized sound went from 19.7 to 3.7 output
dropouts a second (31 -> 4 ms of sound lost a second; its score alone has
none either way) and Quake 3's timedemo from 30.2-30.5 to 33.9 fps. Not
host GL, unchanged: boot time, idle CPU, the score with no graphics, and
SGI's own libGL on the emulated board. gltest 167/167 n32 and o32,
glcheck passes with the reference checksum, irisgltest all ok. The new
library on a host without the thread falls back to OP_BATCH (6 of 6
gltest runs clean); libraries without OP_BATCH_ASYNC work unchanged.
gltest's "the window kept its pixels" fails now and then on either
build, with or without the new library.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
atomchild411 added a commit to atomchild411/iris-guest-tools that referenced this pull request Oct 13, 2026
release/make-tardist.sh packages an unpacked release as an inst(1M)
tardist, product iristools, with gendist on IRIX. It installs the release
in /usr/local/iris-tools/release and runs its install.sh as an exitop;
`versions remove iristools` runs install.sh -u as a removeop, so both ways
in go through the same installer.

README and the release's README.txt: the libraries send self-contained
buffers with OP_BATCH_ASYNC, which wants an IRIS whose host GL runs on its
own thread (techomancer/iris#218) and falls back to waiting on any other;
IRIS_GL_ASYNC=0 turns it off.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-Authored-By: Claude Opus 5.5 <[email protected]>
@techomancer
techomancer merged commit f0eeef9 into techomancer:main Oct 13, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants