From aef71fca80286ed85e1285dbe4c57f864f8b87e5 Mon Sep 17 00:00:00 2001 From: atomchild411 <143453386+atomchild411@users.noreply.github.com> Date: Fri, 16 Oct 2026 16:01:07 -0800 Subject: [PATCH 1/3] iris-hostgl, mgras: colour-index windows' frames composited as indices A colour-index window (IRIS GL's usual kind: flight's main window, every overlay and pop-up window) reached the screen only through the guest: it read each frame back, turned it into indices on the emulated CPU and put it to the X server. SGI's IRIS GL (IGLOO) running on host OpenGL showed flight's full-screen 8-bit window at a frame every few seconds. OP_SWAP's seventh slot gains bit 2, a colour-index window's frame, and bit 3, an overlay window's. Such a frame goes to the display now, through the new Display::present_index_at (default: not taken, so the guest puts it up as before). On IMPACT, disp::composite_index writes each pixel's index -- red byte, then green's low nibble, as the host draws index contexts -- where the board keeps that window's indices: the low 8 or 12 main planes by the window ID's display mode, the high 12 when the ID shows buffer B, or the overlay planes, for the colour map to show. Co-Authored-By: Claude Opus 5.5 --- iris-hostcall/src/lib.rs | 13 +++++ iris-hostgl/src/draw.rs | 21 +++++--- iris-hostgl/src/service.rs | 9 +++- src/dev/mgras/disp.rs | 99 ++++++++++++++++++++++++++++++++++++++ src/dev/mgras/mod.rs | 21 ++++++++ 5 files changed, 155 insertions(+), 8 deletions(-) diff --git a/iris-hostcall/src/lib.rs b/iris-hostcall/src/lib.rs index 4affec59..0c7affda 100644 --- a/iris-hostcall/src/lib.rs +++ b/iris-hostcall/src/lib.rs @@ -277,6 +277,19 @@ pub trait Display: Send + Sync { let _ = (x, y); self.present(window, bgra, stride, width, height) } + + /// A colour-index window's frame, for a window at `x`, `y` as above: each + /// pixel's index is its red byte (the low 8 bits) and the low nibble of + /// its green (the next 4), as the host draws for an index context. + /// `overlay`: the window is in the overlay planes. A display that keeps + /// colour-index pixels and shows them through a colour map (an emulated + /// board) puts the indices into the window and returns true; the default + /// takes none, and the program puts the frame up itself. + #[allow(clippy::too_many_arguments)] + fn present_index_at(&self, window: u32, x: i32, y: i32, bgra: &[u8], stride: usize, width: usize, height: usize, overlay: bool) -> bool { + let _ = (window, x, y, bgra, stride, width, height, overlay); + false + } } static DISPLAY: parking_lot::RwLock>> = parking_lot::RwLock::new(None); diff --git a/iris-hostgl/src/draw.rs b/iris-hostgl/src/draw.rs index 9ed94bcc..87055064 100644 --- a/iris-hostgl/src/draw.rs +++ b/iris-hostgl/src/draw.rs @@ -383,7 +383,15 @@ pub(crate) fn present_to( impl Sink { /// True if the frame has been dealt with and the program needs no pixels. - fn deliver(self, window: u32, at: Option<(i32, i32)>, bgra: &[u8], stride: usize, w: usize, h: usize) -> bool { + fn deliver(self, window: u32, at: Option<(i32, i32)>, bgra: &[u8], stride: usize, w: usize, h: usize, index: Option) -> bool { + // A colour-index frame goes to the display now, as indices (see + // iris_hostcall::Display::present_index_at), or back to the program. + if let Some(overlay) = index { + return match (iris_hostcall::display(), at) { + (Some(d), Some((x, y))) => d.present_index_at(window, x, y, bgra, stride, w, h, overlay), + _ => false, + }; + } match self { Sink::Now => { let ok = match iris_hostcall::display() { @@ -403,7 +411,7 @@ impl Sink { // let the next swap of this drawable be the fast one. A drawable // that cannot be presented into stays here for ever and behaves // exactly as it did before any of this. - Sink::Queued => Sink::Now.deliver(window, at, bgra, stride, w, h), + Sink::Queued => Sink::Now.deliver(window, at, bgra, stride, w, h, None), } } } @@ -412,7 +420,8 @@ impl Sink { /// rather than swap. `keep_front`: the front buffer is kept, so a swap copies /// the back into it. #[allow(clippy::too_many_arguments)] -pub fn swap(d: &Draw, window: u32, at: Option<(i32, i32)>, w: i32, h: i32, sink: Sink, abgr: bool, front: bool, keep_front: bool) -> Option> { +#[allow(clippy::too_many_arguments)] +pub fn swap(d: &Draw, window: u32, at: Option<(i32, i32)>, w: i32, h: i32, sink: Sink, abgr: bool, front: bool, keep_front: bool, index: Option) -> Option> { let (w, h) = (w.max(0), h.max(0)); let (rw, rh) = (w.min(d.w), h.min(d.h)); let mut frame = vec![0u8; w as usize * h as usize * 4]; @@ -480,7 +489,7 @@ pub fn swap(d: &Draw, window: u32, at: Option<(i32, i32)>, w: i32, h: i32, sink: // returns now. Mapping it here stopped the whole emulated machine for // that wait on every frame (powerflip: over half the CPU thread's // time in IOSurfaceLock). - if let (Some(sf), Sink::Queued) = (&flip.surface, sink) { + if let (Some(sf), Sink::Queued, None) = (&flip.surface, sink, index) { if crate::present::presents(window) { glFlush(); flip.busy.store(true, Ordering::Release); @@ -523,7 +532,7 @@ pub fn swap(d: &Draw, window: u32, at: Option<(i32, i32)>, w: i32, h: i32, sink: glFlush(); let mut done = false; let mapped = sf.with_pixels(&mut |px, stride| { - presented = sink.deliver(window, at, px, stride, rw, rh); + presented = sink.deliver(window, at, px, stride, rw, rh, index); if !presented && px.len() >= stride * rh && stride >= rw * 4 { hand_back(px, stride); } @@ -538,7 +547,7 @@ pub fn swap(d: &Draw, window: u32, at: Option<(i32, i32)>, w: i32, h: i32, sink: glReadBuffer(GL_COLOR_ATTACHMENT0); let mut bgra = vec![0u8; rw * rh * 4]; glReadPixels(0, 0, rw as i32, rh as i32, GL_BGRA, GL_UNSIGNED_BYTE, bgra.as_mut_ptr() as *mut c_void); - presented = sink.deliver(window, at, &bgra, rw * 4, rw, rh); + presented = sink.deliver(window, at, &bgra, rw * 4, rw, rh, index); if !presented { hand_back(&bgra, rw * 4); } diff --git a/iris-hostgl/src/service.rs b/iris-hostgl/src/service.rs index 576de1b0..5f19d4c9 100644 --- a/iris-hostgl/src/service.rs +++ b/iris-hostgl/src/service.rs @@ -629,6 +629,9 @@ impl Service for GlService { let [buf, w, h, abgr, x, y, place] = slots!(7); let at = (place & 1 != 0).then_some((x as i32, y as i32)); let front = place & 2 != 0; + // Bit 2: a colour-index window's frame, its indices for a + // display that keeps them (bit 3: in the overlay planes). + let index = (place & 4 != 0).then_some(place & 8 != 0); let (buf, w, h) = (buf, (w as i32).clamp(0, MAX_DIM), (h as i32).clamp(0, MAX_DIM)); let c = &self.clients[&client]; let (window, interval, last_swap, keep_front) = (c.draw, c.swap_interval, c.last_swap, c.fronts); @@ -638,7 +641,9 @@ impl Service for GlService { // thread (the first swap of each drawable is still inline: // see present::presents). Pacing is the `wait` below, slept // in the guest. - let sink = if c.swap_async || at.is_some() { draw::Sink::Queued } else { draw::Sink::Now }; + // Colour-index frames go now: the display takes them or hands + // them back to the program, which waits for the answer. + let sink = if index.is_none() && (c.swap_async || at.is_some()) { draw::Sink::Queued } else { draw::Sink::Now }; let Some(group) = c.current.and_then(|id| self.ctxs.get(&id)).map(|c| c.group) else { return Reply::Err(EINVAL); }; @@ -655,7 +660,7 @@ impl Service for GlService { } self.clients.get_mut(&client).unwrap().last_swap = Some(now + wait); let (dw, dh, samples) = (d.w, d.h, d.samples); - let frame = draw::swap(d, window, at, w, h, sink, abgr != 0, front, keep_front); + let frame = draw::swap(d, window, at, w, h, sink, abgr != 0, front, keep_front, index); // The window is a different size from the drawable, so the // next frame is drawn at the new one. After the read, not // before: the pixels presented are in the old buffer. Not on diff --git a/src/dev/mgras/disp.rs b/src/dev/mgras/disp.rs index 516dd8d5..26137947 100644 --- a/src/dev/mgras/disp.rs +++ b/src/dev/mgras/disp.rs @@ -83,6 +83,105 @@ pub fn composite(rss: &mut Rss, dcb: &Dcb, x: i32, y: i32, bgra: &[u8], stride: true } +/// The window ID a colour-index frame's window has, in the main planes (a +/// colour-index display mode) or the overlay planes (one that is on): the +/// commonest such ID over a grid of samples inside the window, as +/// `window_did`. None if no sampled pixel has one. +fn index_window_did(dcb: &Dcb, x: i32, y: i32, w: usize, h: usize, overlay: bool) -> Option { + let mut counts = [0u32; 32]; + let mut runs = Vec::new(); + let (screen_w, screen_h) = frame::display_size(dcb); + for sy in 0..16 { + let py = y + (h as i32 * (2 * sy + 1)) / 32; + if !(0..screen_h as i32).contains(&py) { + continue; + } + if overlay { + dcb.vc3.overlay_did_runs(py as usize, &mut runs); + } else { + dcb.vc3.main_did_runs(py as usize, &mut runs); + } + for sx in 0..16 { + let px = x + (w as i32 * (2 * sx + 1)) / 32; + if !(0..screen_w as i32).contains(&px) { + continue; + } + let did = runs.iter().rev().find(|r| r.0 as i32 <= px).map_or(0, |r| r.1) as u32; + let index = if overlay { dcb.xmap.overlay_mode(did) != 0 } else { dcb.xmap.main_mode(did) & 0x1F < 4 }; + if index { + counts[did as usize & 31] += 1; + } + } + } + let (did, n) = counts.iter().enumerate().max_by_key(|&(_, n)| *n)?; + (*n > 0).then_some(did as u8) +} + +/// Paint a colour-index window's host GL frame into the framebuffer, as +/// `composite` does an RGB one, but as indices: each pixel's index is its +/// red byte and the low nibble of its green (`bgra`: B, G, R, A), and goes +/// where the board keeps that window's indices -- the low 8 or 12 main +/// planes (by the window ID's display mode), the high 12 when the ID shows +/// buffer B (`frame::Frame::snapshot`), or the overlay planes for an overlay +/// window -- for the colour map to show, as GL's drawing would on the board. +/// Before this, the program read every frame back, turned it into indices +/// and put it to the X server itself: SGI's IRIS GL on host OpenGL ran +/// flight's full-screen colour-index window at a frame every few seconds. +/// False when no window ID fits, or there are no overlay planes. +#[allow(clippy::too_many_arguments)] +pub fn composite_index(rss: &mut Rss, dcb: &Dcb, x: i32, y: i32, bgra: &[u8], stride: usize, w: usize, h: usize, overlay: bool) -> bool { + let (main, over) = frame::scanout_buffers(rss, dcb); + let buf = match (overlay, over) { + (false, _) => main, + (true, Some(o)) => o, + (true, None) => return false, + }; + let Some(target) = index_window_did(dcb, x, y, w, h, overlay) else { return false }; + let b_half = !overlay && dcb.xmap.buf_select() >> target & 1 != 0; + let (mask, shift): (u64, u32) = if overlay { + (0xFF, 0) + } else if b_half { + (0xFFF << 12, 12) + } else if dcb.xmap.main_mode(target as u32) & 0x1F == 1 { + (0xFF, 0) + } else { + (0xFFF, 0) + }; + let mut runs = Vec::new(); + let (screen_w, screen_h) = frame::display_size(dcb); + for row in 0..h { + let sy = y + row as i32; + if !(0..screen_h as i32).contains(&sy) { + continue; + } + if overlay { + dcb.vc3.overlay_did_runs(sy as usize, &mut runs); + } else { + dcb.vc3.main_did_runs(sy as usize, &mut runs); + } + if runs.is_empty() || runs[0].0 != 0 { + runs.insert(0, (0, 0)); + } + let fb_y = (screen_h - 1 - sy as usize) as u32; + for (k, &(x0, did)) in runs.iter().enumerate() { + if did != target { + continue; + } + let x1 = runs.get(k + 1).map_or(screen_w as i32, |r| r.0 as i32); + let lo = (x0 as i32).max(x).max(0); + let hi = x1.min(x + w as i32).min(screen_w as i32); + for sx in lo..hi { + let i = row * stride + (sx - x) as usize * 4; + let Some(p) = bgra.get(i..i + 4) else { break }; + let index = p[2] as u64 | (p[1] as u64 & 0xF) << 8; + let old = rss.mem.get(&buf, sx as u32, fb_y); + rss.mem.put(&buf, sx as u32, fb_y, (old & !mask) | (index << shift & mask)); + } + } + } + true +} + /// Write a scanned-out frame (`0xFF_BB_GG_RR`, stride `OUT_STRIDE`) as a PNG. pub fn save_png(path: &str, frame: &[u32], width: usize, height: usize) -> Result<(), String> { let file = std::fs::File::create(path).map_err(|e| e.to_string())?; diff --git a/src/dev/mgras/mod.rs b/src/dev/mgras/mod.rs index dd2cdd0f..77589bce 100644 --- a/src/dev/mgras/mod.rs +++ b/src/dev/mgras/mod.rs @@ -783,6 +783,23 @@ impl Mgras { ok } + /// A colour-index window's host GL frame, as indices (`disp::composite_index`). + #[allow(clippy::too_many_arguments)] + pub fn composite_index(&self, x: i32, y: i32, bgra: &[u8], stride: usize, w: usize, h: usize, overlay: bool) -> bool { + let sub = self.submit.lock(); + self.wait_idle(); + // SAFETY: idle with `submit` held. + let rss = unsafe { &mut *self.rss.get() }; + let ok = disp::composite_index(rss, &self.front().dcb, x, y, bgra, stride, w, h, overlay); + if ok { + if let Some(rec) = sub.as_ref() { + rec.lock().put(record::Rec::Composite); + } + self.dirty.store(true, Ordering::Release); + } + ok + } + /// Framebuffer pixel (its 24 colour planes) at display (`x`, `y`), y = 0 /// the top row, once the board has run everything queued (tests). @@ -1492,4 +1509,8 @@ impl iris_hostcall::Display for ImpactScreen { fn present_at(&self, _window: u32, x: i32, y: i32, bgra: &[u8], stride: usize, width: usize, height: usize) -> bool { self.0.composite(x, y, bgra, stride, width, height) } + + fn present_index_at(&self, _window: u32, x: i32, y: i32, bgra: &[u8], stride: usize, width: usize, height: usize, overlay: bool) -> bool { + self.0.composite_index(x, y, bgra, stride, width, height, overlay) + } } From 184bfa86b6f4a5851fb4941095c3ba36b945b837 Mon Sep 17 00:00:00 2001 From: atomchild411 <143453386+atomchild411@users.noreply.github.com> Date: Fri, 16 Oct 2026 19:20:35 -0800 Subject: [PATCH 2/3] mgras: composite a colour-index frame only where its window's ID covers it index_window_did took the commonest colour-index (or overlay) window ID in the window's rectangle. Just after a window is mapped its ID may not be in the VC3's tables yet, and the commonest one around is then another window's: the frame went into that, the program was told it was shown, and gltest's overlay check read back 0s about one run in three. The ID must now cover three quarters of the samples on the screen, or the frame goes back to the program to put up itself. Co-Authored-By: Claude Opus 5.5 --- src/dev/mgras/disp.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/dev/mgras/disp.rs b/src/dev/mgras/disp.rs index 26137947..cb222c3d 100644 --- a/src/dev/mgras/disp.rs +++ b/src/dev/mgras/disp.rs @@ -86,8 +86,13 @@ pub fn composite(rss: &mut Rss, dcb: &Dcb, x: i32, y: i32, bgra: &[u8], stride: /// The window ID a colour-index frame's window has, in the main planes (a /// colour-index display mode) or the overlay planes (one that is on): the /// commonest such ID over a grid of samples inside the window, as -/// `window_did`. None if no sampled pixel has one. +/// `window_did` -- and only when it covers three quarters of the samples on +/// the screen. A window just mapped may not have its ID in the tables yet, +/// and the commonest ID around it is then someone else's: the frame went +/// there, the program was told it was shown, and gltest's overlay read back +/// 0s now and then. None, and the program puts the frame up itself. fn index_window_did(dcb: &Dcb, x: i32, y: i32, w: usize, h: usize, overlay: bool) -> Option { + let mut total = 0u32; let mut counts = [0u32; 32]; let mut runs = Vec::new(); let (screen_w, screen_h) = frame::display_size(dcb); @@ -108,13 +113,14 @@ fn index_window_did(dcb: &Dcb, x: i32, y: i32, w: usize, h: usize, overlay: bool } let did = runs.iter().rev().find(|r| r.0 as i32 <= px).map_or(0, |r| r.1) as u32; let index = if overlay { dcb.xmap.overlay_mode(did) != 0 } else { dcb.xmap.main_mode(did) & 0x1F < 4 }; + total += 1; if index { counts[did as usize & 31] += 1; } } } let (did, n) = counts.iter().enumerate().max_by_key(|&(_, n)| *n)?; - (*n > 0).then_some(did as u8) + (*n > 0 && *n * 4 >= total * 3).then_some(did as u8) } /// Paint a colour-index window's host GL frame into the framebuffer, as From 248ac5031fdf95b40240889b83b654ef78fad989 Mon Sep 17 00:00:00 2001 From: atomchild411 <143453386+atomchild411@users.noreply.github.com> Date: Sat, 17 Oct 2026 12:24:15 -0800 Subject: [PATCH 3/3] iris-hostgl: OP_INDEX_FRAMES, so a library asks before it sends indices A host from before colour-index frames (bits 2 and 3 of OP_SWAP's seventh slot) composites such a frame's encoded RGBA as colours and answers that it is on the screen: on upstream IRIS the guest library's overlay and colour-index windows read back 0 (gltest's overlay checks, irisgltest's overlay window). OP_INDEX_FRAMES (0x1_0013) answers 1 here; a host without it answers EINVAL, and the library has the frame handed back and puts it up itself, as before. Test: index_frames_are_asked_for. Co-Authored-By: Claude Opus 5.5 --- iris-hostgl/src/service.rs | 7 +++++++ iris-hostgl/src/tests.rs | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/iris-hostgl/src/service.rs b/iris-hostgl/src/service.rs index 5f19d4c9..f087ee5f 100644 --- a/iris-hostgl/src/service.rs +++ b/iris-hostgl/src/service.rs @@ -88,6 +88,12 @@ pub const OP_PRESENT_STATS: u64 = 0x1_000F; /// a host without it answers EINVAL, and the library keeps the one buffer. /// For this client from now on, its contexts made before as well. pub const OP_FRONT_BUFFERS: u64 = 0x1_0011; +/// Answered 1: OP_SWAP takes a colour-index window's frame as indices (bits +/// 2 and 3 of the seventh slot) for a display that keeps them. A host +/// without it answers EINVAL, and would composite such a frame's encoded +/// RGBA as colours -- so the library asks first, and without it has the +/// frame handed back and puts it up itself. +pub const OP_INDEX_FRAMES: u64 = 0x1_0013; /// The protocol: HELLO's second result, which the library checks. pub const PROTOCOL: u64 = 2; @@ -682,6 +688,7 @@ impl Service for GlService { Some(frame) => self.finish(mem, args, vec![(buf, frame)], Reply::Ok(0, wait)), } } + OP_INDEX_FRAMES => Reply::Ok(1, 0), OP_FRONT_BUFFERS => { if let Some(c) = self.clients.get_mut(&client) { c.fronts = true; diff --git a/iris-hostgl/src/tests.rs b/iris-hostgl/src/tests.rs index 6175b76b..2ad7bf86 100644 --- a/iris-hostgl/src/tests.rs +++ b/iris-hostgl/src/tests.rs @@ -1752,3 +1752,11 @@ fn color_matrix_limits_are_answered_and_raise_no_error() { assert!(v(0) >= 2, "a colour matrix stack of at least 2, as IMPACT's: {}", v(0)); assert_eq!(v(4), 1, "one deep"); } + +/// A library asks before it sends a colour-index frame as indices: a host +/// that cannot take them answers EINVAL (see OP_INDEX_FRAMES). +#[test] +fn index_frames_are_asked_for() { + let mut g = Guest::new(64); + assert_eq!(g.call(OP_INDEX_FRAMES, &[0]), Reply::Ok(1, 0)); +}