Skip to content

Close the O= finding: an object is read once, so it is placed once - #9

Merged
mishushakov merged 1 commit into
mainfrom
shm-object-read-once
Jul 26, 2026
Merged

mishushakov merged 1 commit into
mainfrom
shm-object-read-once

Conversation

@mishushakov

Copy link
Copy Markdown
Owner

One object per frame, every tile placed out of it at an offset, was tried a second time -- against kitty this time, since the first attempt had only Ghostty's silence to go on. kitty draws it exactly as Ghostty does, which is to say it does not, and that is what identified the cause: the spec has the terminal "read the data from the memory object and then unlink and close it". One read is all an object gets. A frame that places ninety-one tiles out of one object makes ninety-one commands of it, the first consumes the name, and every later shm_open finds nothing -- so the frame's first tile draws and the screen is otherwise black. Two implementations of a rule, not one implementation's gap.

Which this codebase already knew. src/term/shm.rs sweeps by presence because an object that still exists is one the terminal has not read; the sentence that makes the sweep work is the one that makes one object per frame impossible. It was in the tree the whole time, two files from the change.

So no code moves here: the emission is an object per tile, byte for byte what it was. What changes is what the tree says about why, in the three places that were guessing -- place_shm, the pty assertion, and the finding in RENDERING.md -- and the finding is closed rather than left open. It now says what is not worth doing (probing for it, gating on a terminal's name, trying a third terminal) and what would work: one transmission per frame, packing the tiles into a single atlas, a=t once, then a=p per tile with a source rectangle out of the stored image. That is one read, and legal. Its cost is written down too, because it is not small -- image ids become per-frame rather than per-tile, which drags in the a=d,d=i release, TileGrid's id scheme, the cursor and overlay z-order, and deletes for an image store that grows a frame at a time.

The numbers are re-measured on this machine rather than carried over, and the benchmark keeps the one-object row so the 0.2 ms still on the table stays a number rather than a memory:

shm: object per tile             1.6 ms/frame   ->  633 fps ceiling
shm: per tile, packed in place   0.9 ms/frame   -> 1124 fps ceiling
shm: one object per frame        0.7 ms/frame   -> 1528 fps ceiling

Net of the two attempts: the larger half is in and unaffected -- packing into the mapping the terminal reads rather than into a buffer to be copied in, 1.6 ms/frame to 0.9. The offsets were the smaller half, and they were never available.

One thing this does not have: the error reply that would confirm the mechanism instead of predicting it. q=2 suppresses those, on both terminals. The finding says so, and says the q=0 reproduction is two lines of shell for whoever wants it certain.

One object per frame, every tile placed out of it at an offset, was tried a second time --
against kitty this time, since the first attempt had only Ghostty's silence to go on. kitty
draws it exactly as Ghostty does, which is to say it does not, and that is what identified
the cause: the spec has the terminal "read the data from the memory object and then unlink
and close it". One read is all an object gets. A frame that places ninety-one tiles out of
one object makes ninety-one commands of it, the first consumes the name, and every later
`shm_open` finds nothing -- so the frame's first tile draws and the screen is otherwise
black. Two implementations of a rule, not one implementation's gap.

Which this codebase already knew. `src/term/shm.rs` sweeps by presence *because* an object
that still exists is one the terminal has not read; the sentence that makes the sweep work
is the one that makes one object per frame impossible. It was in the tree the whole time,
two files from the change.

So no code moves here: the emission is an object per tile, byte for byte what it was. What
changes is what the tree says about why, in the three places that were guessing --
`place_shm`, the pty assertion, and the finding in RENDERING.md -- and the finding is closed
rather than left open. It now says what is not worth doing (probing for it, gating on a
terminal's name, trying a third terminal) and what would work: one *transmission* per frame,
packing the tiles into a single atlas, `a=t` once, then `a=p` per tile with a source
rectangle out of the stored image. That is one read, and legal. Its cost is written down
too, because it is not small -- image ids become per-frame rather than per-tile, which drags
in the `a=d,d=i` release, `TileGrid`'s id scheme, the cursor and overlay z-order, and
deletes for an image store that grows a frame at a time.

The numbers are re-measured on this machine rather than carried over, and the benchmark
keeps the one-object row so the 0.2 ms still on the table stays a number rather than a
memory:

    shm: object per tile             1.6 ms/frame   ->  633 fps ceiling
    shm: per tile, packed in place   0.9 ms/frame   -> 1124 fps ceiling
    shm: one object per frame        0.7 ms/frame   -> 1528 fps ceiling

Net of the two attempts: the larger half is in and unaffected -- packing into the mapping
the terminal reads rather than into a buffer to be copied in, 1.6 ms/frame to 0.9. The
offsets were the smaller half, and they were never available.

One thing this does not have: the error reply that would confirm the mechanism instead of
predicting it. `q=2` suppresses those, on both terminals. The finding says so, and says the
`q=0` reproduction is two lines of shell for whoever wants it certain.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@mishushakov
mishushakov merged commit 3cbb577 into main Jul 26, 2026
3 checks passed
@mishushakov
mishushakov deleted the shm-object-read-once branch July 26, 2026 22:26
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.

1 participant