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
44 changes: 23 additions & 21 deletions iris-hostgl/src/draw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -365,7 +365,10 @@ pub enum Sink {
Queued,
}

/// Hand a frame to `d`: at a screen position when the program gave one.
/// Hand a frame to `d`: at a screen position when the program gave one. A
/// colour-index window's frame (`index`: Some(overlay)) goes as indices, and
/// only with a position (see iris_hostcall::Display::present_index_at).
#[allow(clippy::too_many_arguments)]
pub(crate) fn present_to(
d: &dyn iris_hostcall::Display,
window: u32,
Expand All @@ -374,35 +377,31 @@ pub(crate) fn present_to(
stride: usize,
w: usize,
h: usize,
index: Option<bool>,
) -> bool {
match at {
Some((x, y)) => d.present_at(window, x, y, bgra, stride, w, h),
None => d.present(window, bgra, stride, w, h),
match (index, at) {
(Some(overlay), Some((x, y))) => d.present_index_at(window, x, y, bgra, stride, w, h, overlay),
(Some(_), None) => false,
(None, Some((x, y))) => d.present_at(window, x, y, bgra, stride, w, h),
(None, None) => d.present(window, bgra, stride, w, h),
}
}

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, 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,
};
}
#[allow(clippy::too_many_arguments)]
fn deliver(self, window: u32, at: Option<(i32, i32)>, bgra: &[u8], stride: usize, w: usize, h: usize, index: Option<bool>, flush: bool) -> bool {
match self {
Sink::Now => {
let ok = match iris_hostcall::display() {
Some(d) => present_to(d.as_ref(), window, at, bgra, stride, w, h),
Some(d) => present_to(d.as_ref(), window, at, bgra, stride, w, h, index),
None => false,
};
crate::present::note(window, ok);
ok
}
Sink::Queued if crate::present::presents(window) => {
crate::present::queue(window, at, bgra, stride, w, h);
crate::present::queue(window, at, bgra, stride, w, h, index, flush);
true
}
// Not a drawable we have seen present. Whether presenting works is
Expand All @@ -411,7 +410,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, None),
Sink::Queued => Sink::Now.deliver(window, at, bgra, stride, w, h, index, flush),
}
}
}
Expand All @@ -420,8 +419,11 @@ 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)]
/// `flush`: the frame is shown because the program flushed, not swapped (a
/// front buffer, a colour-index window): it never waits for the presenter,
/// and replaces a frame still waiting (see present::queue).
#[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>> {
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>, flush: 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 @@ -489,11 +491,11 @@ 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, None) = (&flip.surface, sink, index) {
if let (Some(sf), Sink::Queued) = (&flip.surface, sink) {
if crate::present::presents(window) {
glFlush();
flip.busy.store(true, Ordering::Release);
crate::present::queue_surface(window, at, sf.clone(), flip.busy.clone(), rw, rh);
crate::present::queue_surface(window, at, sf.clone(), flip.busy.clone(), rw, rh, index, flush);
for (&n, &v) in names.iter().zip(pack.iter()) {
glPixelStorei(n, v);
}
Expand Down Expand Up @@ -532,7 +534,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, index);
presented = sink.deliver(window, at, px, stride, rw, rh, index, flush);
if !presented && px.len() >= stride * rh && stride >= rw * 4 {
hand_back(px, stride);
}
Expand All @@ -547,7 +549,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, index);
presented = sink.deliver(window, at, &bgra, rw * 4, rw, rh, index, flush);
if !presented {
hand_back(&bgra, rw * 4);
}
Expand Down
2 changes: 1 addition & 1 deletion iris-hostgl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ use exec::Exec;
#[cfg(any(target_os = "macos", target_os = "linux"))]
pub use service::{
GlService, OP_BATCH, OP_BATCH_ASYNC, OP_CREATE, OP_DESTROY, OP_DRAWABLE_GONE, OP_FINISH, OP_GETSTRING, OP_GLX_STRING, OP_GOODBYE, OP_HELLO,
OP_MAKECURRENT, OP_PBUFFER, OP_RELEASE, OP_SWAP, OP_SWAP_INTERVAL, OP_VIDEO_SYNC, PROTOCOL,
OP_MAKECURRENT, OP_PBUFFER, OP_PRESENT_LATER, OP_RELEASE, OP_SWAP, OP_SWAP_INTERVAL, OP_VIDEO_SYNC, PROTOCOL,
};

/// Answer host call [`iris_hostcall::GL`] with a new host GL service.
Expand Down
56 changes: 38 additions & 18 deletions iris-hostgl/src/present.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@ struct Frame {
src: Src,
w: usize,
h: usize,
/// A colour-index window's frame (Some(overlay)), shown as indices.
index: Option<bool>,
}

/// A frame's pixels: a copy, or the surface the GPU is rendering them into,
Expand All @@ -123,7 +125,10 @@ impl Src {

#[derive(Default)]
struct State {
queued: Option<Frame>,
/// Frames waiting, oldest first: at most one a window (a newer frame of
/// the same window replaces it), so one window's flushes never drop
/// another window's frame.
queued: Vec<Frame>,
/// A frame is being presented right now.
busy: bool,
/// Buffers of frames already presented or replaced.
Expand Down Expand Up @@ -180,25 +185,37 @@ pub fn forget(window: u32) {
///
/// `bgra` is `h` rows of `stride` bytes, `w` pixels each, as
/// `Display::present` wants them.
pub fn queue(window: u32, at: Option<(i32, i32)>, bgra: &[u8], stride: usize, w: usize, h: usize) {
let mut st = admit();
///
/// `flush`: the program flushed rather than swapped (front-buffer or
/// colour-index drawing). Such a frame never waits: it replaces the one
/// still waiting, so the screen gets the newest drawing as fast as this
/// thread can put it there, and the last one always. SGI's IRIS GL flushes
/// after every primitive it draws into the front buffer -- flight drew its
/// cockpit with 550 flushes a second -- and waiting for the presenter on
/// each made the drawing crawl.
#[allow(clippy::too_many_arguments)]
pub fn queue(window: u32, at: Option<(i32, i32)>, bgra: &[u8], stride: usize, w: usize, h: usize, index: Option<bool>, flush: bool) {
let mut st = admit(flush);
let mut buf = st.spare.pop().unwrap_or_default();
buf.clear();
buf.extend_from_slice(bgra);
put(st, Frame { window, at, src: Src::Bytes { bgra: buf, stride }, w, h });
put(st, Frame { window, at, src: Src::Bytes { bgra: buf, stride }, w, h, index });
}

/// Queue a frame still being rendered into `sf`: this thread maps it (waiting
/// for the GPU there, not in the swap) and presents `w` x `h` of it, then
/// clears `busy` so its drawable may draw into it again.
pub fn queue_surface(window: u32, at: Option<(i32, i32)>, sf: Arc<dyn Surface>, busy: Arc<AtomicBool>, w: usize, h: usize) {
let st = admit();
put(st, Frame { window, at, src: Src::Surface { sf, busy }, w, h });
/// clears `busy` so its drawable may draw into it again. `index`, `flush`:
/// as [`queue`].
#[allow(clippy::too_many_arguments)]
pub fn queue_surface(window: u32, at: Option<(i32, i32)>, sf: Arc<dyn Surface>, busy: Arc<AtomicBool>, w: usize, h: usize, index: Option<bool>, flush: bool) {
let st = admit(flush);
put(st, Frame { window, at, src: Src::Surface { sf, busy }, w, h, index });
}

/// The state, once there is room for another frame: starts the thread, and
/// waits while [`MAX_IN_FLIGHT`] frames are already in flight.
fn admit() -> MutexGuard<'static, State> {
/// waits while [`MAX_IN_FLIGHT`] frames are already in flight -- unless the
/// frame is a flush's, which replaces the waiting one instead.
fn admit(flush: bool) -> MutexGuard<'static, State> {
let p = presenter();
let mut st = lock(p);
if !st.started {
Expand All @@ -210,7 +227,7 @@ fn admit() -> MutexGuard<'static, State> {
// One being presented and one already waiting: wait for the first to
// finish rather than let the guest run further ahead.
let mut waited = false;
while st.queued.is_some() as usize + st.busy as usize >= MAX_IN_FLIGHT {
while !flush && st.queued.len() + st.busy as usize >= MAX_IN_FLIGHT {
waited = true;
st = wait(&p.idle, st);
}
Expand All @@ -220,18 +237,21 @@ fn admit() -> MutexGuard<'static, State> {
st
}

/// Put `f` in the queue, replacing whatever is waiting there: it is older.
/// Put `f` in the queue, replacing a frame of its window still waiting there:
/// that one is older.
fn put(mut st: MutexGuard<'static, State>, f: Frame) {
if let Some(old) = st.queued.take() {
if let Some(at) = st.queued.iter().position(|q| q.window == f.window) {
let old = std::mem::replace(&mut st.queued[at], f);
REPLACED.fetch_add(1, Ordering::Relaxed);
if let Some(buf) = old.src.release() {
if st.spare.len() < SPARE_BUFFERS {
st.spare.push(buf);
}
}
} else {
st.queued.push(f);
}
QUEUED.fetch_add(1, Ordering::Relaxed);
st.queued = Some(f);
drop(st);
presenter().work.notify_one();
}
Expand All @@ -240,7 +260,7 @@ fn put(mut st: MutexGuard<'static, State>, f: Frame) {
pub fn drain() {
let p = presenter();
let mut st = lock(p);
while st.queued.is_some() || st.busy {
while !st.queued.is_empty() || st.busy {
st = wait(&p.idle, st);
}
}
Expand All @@ -252,7 +272,7 @@ fn run() {
let frame = {
let mut st = lock(p);
loop {
match st.queued.take() {
match (!st.queued.is_empty()).then(|| st.queued.remove(0)) {
Some(f) => {
st.busy = true;
break f;
Expand All @@ -265,12 +285,12 @@ fn run() {
// queuer must not be held up by it.
let ok = match iris_hostcall::display() {
Some(d) => match &frame.src {
Src::Bytes { bgra, stride } => crate::draw::present_to(d.as_ref(), frame.window, frame.at, bgra, *stride, frame.w, frame.h),
Src::Bytes { bgra, stride } => crate::draw::present_to(d.as_ref(), frame.window, frame.at, bgra, *stride, frame.w, frame.h, frame.index),
Src::Surface { sf, .. } => {
let mut ok = false;
let mapped = sf.with_pixels(&mut |px, stride| {
if px.len() >= stride * frame.h && stride >= frame.w * 4 {
ok = crate::draw::present_to(d.as_ref(), frame.window, frame.at, px, stride, frame.w, frame.h);
ok = crate::draw::present_to(d.as_ref(), frame.window, frame.at, px, stride, frame.w, frame.h, frame.index);
}
});
mapped && ok
Expand Down
Loading
Loading