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
13 changes: 13 additions & 0 deletions iris-hostcall/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Option<std::sync::Arc<dyn Display>>> = parking_lot::RwLock::new(None);
Expand Down
21 changes: 15 additions & 6 deletions iris-hostgl/src/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>) -> 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() {
Expand All @@ -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),
}
}
}
Expand All @@ -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<Vec<u8>> {
#[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<bool>) -> Option<Vec<u8>> {
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];
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Expand All @@ -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);
}
Expand Down
16 changes: 14 additions & 2 deletions iris-hostgl/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -629,6 +635,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);
Expand All @@ -638,7 +647,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);
};
Expand All @@ -655,7 +666,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
Expand All @@ -677,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;
Expand Down
8 changes: 8 additions & 0 deletions iris-hostgl/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
105 changes: 105 additions & 0 deletions src/dev/mgras/disp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,111 @@ 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` -- 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<u8> {
let mut total = 0u32;
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 };
total += 1;
if index {
counts[did as usize & 31] += 1;
}
}
}
let (did, n) = counts.iter().enumerate().max_by_key(|&(_, n)| *n)?;
(*n > 0 && *n * 4 >= total * 3).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())?;
Expand Down
21 changes: 21 additions & 0 deletions src/dev/mgras/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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)
}
}
Loading