perf(display): present copies window rows as-is (scanout R8G8B8X8 = window byte order, Host hands out row bytes, Framebuffer::write_rgba_row); Window copy_from/copy_to one fenced memcpy; full present ~65ms -> ~3ms
This commit is contained in:
@@ -37,6 +37,11 @@ pub struct VirtioFramebuffer {
|
|||||||
dirty: Option<(usize, usize, usize, usize)>,
|
dirty: Option<(usize, usize, usize, usize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `c` as the screen stores it: the bytes R, G, B, then one ignored.
|
||||||
|
fn word(c: Color) -> u32 {
|
||||||
|
u32::from_le_bytes(c.to_rgba())
|
||||||
|
}
|
||||||
|
|
||||||
impl VirtioFramebuffer {
|
impl VirtioFramebuffer {
|
||||||
/// Takes over the virtio GPU behind `mmio` and makes a framebuffer its
|
/// Takes over the virtio GPU behind `mmio` and makes a framebuffer its
|
||||||
/// scanout. `None` if it is not a GPU or any step fails.
|
/// scanout. `None` if it is not a GPU or any step fails.
|
||||||
@@ -113,6 +118,17 @@ impl Framebuffer for VirtioFramebuffer {
|
|||||||
self.height
|
self.height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_rgba_row(&mut self, x: ScreenCoord, y: ScreenCoord, bytes: &[u8]) {
|
||||||
|
let (w, h) = (self.width.raw() as usize, self.height.raw() as usize);
|
||||||
|
let (x, y) = (x.raw() as usize, y.raw() as usize);
|
||||||
|
if x >= w || y >= h {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let n = (bytes.len() / FB_BPP).min(w - x);
|
||||||
|
self.touch(x, y, x + n, y + 1);
|
||||||
|
self.pixels.copy_from(self.pixel(x, y), &bytes[..n * FB_BPP]);
|
||||||
|
}
|
||||||
|
|
||||||
fn write_pixel(&mut self, x: ScreenCoord, y: ScreenCoord, color: Color) {
|
fn write_pixel(&mut self, x: ScreenCoord, y: ScreenCoord, color: Color) {
|
||||||
self.write_row(x, y, &[color]);
|
self.write_row(x, y, &[color]);
|
||||||
}
|
}
|
||||||
@@ -129,7 +145,7 @@ impl Framebuffer for VirtioFramebuffer {
|
|||||||
let mut at = self.pixel(x, y);
|
let mut at = self.pixel(x, y);
|
||||||
for chunk in pixels[..n].chunks(RUN) {
|
for chunk in pixels[..n].chunks(RUN) {
|
||||||
for (slot, c) in run.iter_mut().zip(chunk) {
|
for (slot, c) in run.iter_mut().zip(chunk) {
|
||||||
*slot = c.raw();
|
*slot = word(*c);
|
||||||
}
|
}
|
||||||
self.pixels.write_u32s(at, &run[..chunk.len()]);
|
self.pixels.write_u32s(at, &run[..chunk.len()]);
|
||||||
at = at + chunk.len() * FB_BPP;
|
at = at + chunk.len() * FB_BPP;
|
||||||
@@ -144,14 +160,14 @@ impl Framebuffer for VirtioFramebuffer {
|
|||||||
let bottom = (y + h.raw() as usize).min(fh);
|
let bottom = (y + h.raw() as usize).min(fh);
|
||||||
self.touch(x, y, x + w, bottom);
|
self.touch(x, y, x + w, bottom);
|
||||||
for row in y..bottom {
|
for row in y..bottom {
|
||||||
self.pixels.fill_u32(self.pixel(x, row), w, color.raw());
|
self.pixels.fill_u32(self.pixel(x, row), w, word(color));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear(&mut self, color: Color) {
|
fn clear(&mut self, color: Color) {
|
||||||
let (w, h) = (self.width.raw() as usize, self.height.raw() as usize);
|
let (w, h) = (self.width.raw() as usize, self.height.raw() as usize);
|
||||||
self.touch(0, 0, w, h);
|
self.touch(0, 0, w, h);
|
||||||
self.pixels.fill_u32(ByteOffset::ZERO, w * h, color.raw());
|
self.pixels.fill_u32(ByteOffset::ZERO, w * h, word(color));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scroll_up(&mut self, top: ScreenCoord, height: Dimension, by: Dimension, fill: Color) {
|
fn scroll_up(&mut self, top: ScreenCoord, height: Dimension, by: Dimension, fill: Color) {
|
||||||
@@ -163,7 +179,7 @@ impl Framebuffer for VirtioFramebuffer {
|
|||||||
self.touch(0, top, self.width.raw() as usize, bottom);
|
self.touch(0, top, self.width.raw() as usize, bottom);
|
||||||
self.pixels.copy_within(self.pixel(0, top + by), self.pixel(0, top), moved * self.stride);
|
self.pixels.copy_within(self.pixel(0, top + by), self.pixel(0, top), moved * self.stride);
|
||||||
let width = self.width.raw() as usize;
|
let width = self.width.raw() as usize;
|
||||||
self.pixels.fill_u32(self.pixel(0, bottom - by), by * width, fill.raw());
|
self.pixels.fill_u32(self.pixel(0, bottom - by), by * width, word(fill));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sends the host only what changed since the last flush.
|
/// Sends the host only what changed since the last flush.
|
||||||
|
|||||||
@@ -28,9 +28,10 @@ const DISPLAY_H: ByteOffset = ByteOffset::new(24 + 12);
|
|||||||
|
|
||||||
pub fn create_2d(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, width: Dimension, height: Dimension) -> bool {
|
pub fn create_2d(mmio: &impl MmioAccess, queue: &VirtioQueue, dma: &GpuDmaAddrs, resource_id: u32, width: Dimension, height: Dimension) -> bool {
|
||||||
write_cmd_header(&dma.cmd, 0x0101);
|
write_cmd_header(&dma.cmd, 0x0101);
|
||||||
// Resource id, format (1: B8G8R8A8), width, height.
|
// Resource id, format (134: R8G8B8X8, a window's own byte order, so
|
||||||
|
// its rows are copied as they are), width, height.
|
||||||
dma.cmd.write(BODY, resource_id);
|
dma.cmd.write(BODY, resource_id);
|
||||||
dma.cmd.write(BODY + 4, 1u32);
|
dma.cmd.write(BODY + 4, 134u32);
|
||||||
dma.cmd.write(BODY + 8, width.raw() as u32);
|
dma.cmd.write(BODY + 8, width.raw() as u32);
|
||||||
dma.cmd.write(BODY + 12, height.raw() as u32);
|
dma.cmd.write(BODY + 12, height.raw() as u32);
|
||||||
submit_cmd(mmio, queue, dma, 40)
|
submit_cmd(mmio, queue, dma, 40)
|
||||||
|
|||||||
@@ -104,18 +104,24 @@ impl Window {
|
|||||||
Window { base, len }
|
Window { base, len }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `src` into the window at `offset`, as one copy: for memory (pages,
|
||||||
|
/// DMA buffers), not device registers. Fenced, so a device told
|
||||||
|
/// afterwards sees all of it.
|
||||||
pub fn copy_from(&self, offset: ByteOffset, src: &[u8]) {
|
pub fn copy_from(&self, offset: ByteOffset, src: &[u8]) {
|
||||||
self.at(offset, src.len(), 1);
|
let addr = self.at(offset, src.len(), 1);
|
||||||
for (i, b) in src.iter().enumerate() {
|
// SAFETY: in range of memory mapped for this process; `src` is
|
||||||
self.write(offset + i, *b);
|
// ordinary memory, so the two cannot overlap.
|
||||||
}
|
unsafe { core::ptr::copy_nonoverlapping(src.as_ptr(), addr as *mut u8, src.len()) }
|
||||||
|
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The window's bytes at `offset` into `dst`, as one copy (see
|
||||||
|
/// `copy_from`); what a device wrote before is seen.
|
||||||
pub fn copy_to(&self, offset: ByteOffset, dst: &mut [u8]) {
|
pub fn copy_to(&self, offset: ByteOffset, dst: &mut [u8]) {
|
||||||
self.at(offset, dst.len(), 1);
|
let addr = self.at(offset, dst.len(), 1);
|
||||||
for (i, b) in dst.iter_mut().enumerate() {
|
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
|
||||||
*b = self.read(offset + i);
|
// SAFETY: as above.
|
||||||
}
|
unsafe { core::ptr::copy_nonoverlapping(addr as *const u8, dst.as_mut_ptr(), dst.len()) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `count` u32s from `offset` set to `value`: one check, then a run of
|
/// `count` u32s from `offset` set to `value`: one check, then a run of
|
||||||
|
|||||||
@@ -21,6 +21,21 @@ pub trait Framebuffer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A run of a window's pixels from (x, y) rightwards, each the bytes
|
||||||
|
/// R, G, B, A (see `types::display::window::BUFFER`).
|
||||||
|
fn write_rgba_row(&mut self, x: ScreenCoord, y: ScreenCoord, bytes: &[u8]) {
|
||||||
|
let mut run = [Color::from_raw(0); 64];
|
||||||
|
let mut at = x;
|
||||||
|
for chunk in bytes.chunks(4 * run.len()) {
|
||||||
|
let n = chunk.len() / 4;
|
||||||
|
for (c, px) in run.iter_mut().zip(chunk.as_chunks::<4>().0) {
|
||||||
|
*c = Color::from_rgba(*px);
|
||||||
|
}
|
||||||
|
self.write_row(at, y, &run[..n]);
|
||||||
|
at = ScreenCoord::new(at.raw().saturating_add(n as u16));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Moves the band of rows from `top`, `height` tall, up by `by` rows,
|
/// Moves the band of rows from `top`, `height` tall, up by `by` rows,
|
||||||
/// and fills the `by` rows left at its bottom with `fill`.
|
/// and fills the `by` rows left at its bottom with `fill`.
|
||||||
fn scroll_up(&mut self, top: ScreenCoord, height: Dimension, by: Dimension, fill: Color);
|
fn scroll_up(&mut self, top: ScreenCoord, height: Dimension, by: Dimension, fill: Color);
|
||||||
|
|||||||
@@ -3,13 +3,10 @@
|
|||||||
//! the display's memory has a fixed ceiling whatever apps do.
|
//! the display's memory has a fixed ceiling whatever apps do.
|
||||||
|
|
||||||
use libkos::syscall;
|
use libkos::syscall;
|
||||||
use libkos::window::Window;
|
use types::address::VirtualAddress;
|
||||||
use types::address::{ByteOffset, VirtualAddress};
|
|
||||||
use types::badge::Badge;
|
use types::badge::Badge;
|
||||||
use types::bounded::{CapSlot, PageCount};
|
use types::bounded::{CapSlot, PageCount};
|
||||||
use types::display::Color;
|
|
||||||
use types::display::window::PIXEL_BYTES;
|
use types::display::window::PIXEL_BYTES;
|
||||||
use types::mapping::PagePerms;
|
|
||||||
use types::permission::{Permission, PermissionSet};
|
use types::permission::{Permission, PermissionSet};
|
||||||
|
|
||||||
use crate::server::Host;
|
use crate::server::Host;
|
||||||
@@ -26,12 +23,13 @@ pub struct KernelHost {
|
|||||||
/// from.
|
/// from.
|
||||||
ep: CapSlot,
|
ep: CapSlot,
|
||||||
input: Option<CapSlot>,
|
input: Option<CapSlot>,
|
||||||
buffers: [Option<(CapSlot, Window)>; MAX_WINDOWS],
|
/// Each slot's region and its pixels, mapped here as plain memory.
|
||||||
|
buffers: [Option<(CapSlot, &'static mut [u8])>; MAX_WINDOWS],
|
||||||
}
|
}
|
||||||
|
|
||||||
impl KernelHost {
|
impl KernelHost {
|
||||||
pub fn new(ep: CapSlot, input: Option<CapSlot>) -> Self {
|
pub fn new(ep: CapSlot, input: Option<CapSlot>) -> Self {
|
||||||
Self { ep, input, buffers: [None; MAX_WINDOWS] }
|
Self { ep, input, buffers: [const { None }; MAX_WINDOWS] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,8 +41,8 @@ impl Host for KernelHost {
|
|||||||
let pages = PageCount::new(u16::try_from(bytes.div_ceil(PAGE)).ok()?)?;
|
let pages = PageCount::new(u16::try_from(bytes.div_ceil(PAGE)).ok()?)?;
|
||||||
let region = syscall::create_region(pages).ok()?;
|
let region = syscall::create_region(pages).ok()?;
|
||||||
let va = VirtualAddress::from_raw(BUFFERS_VA + index * BUFFER_SPAN);
|
let va = VirtualAddress::from_raw(BUFFERS_VA + index * BUFFER_SPAN);
|
||||||
match libkos::window::map(region, va, PagePerms::ReadWrite) {
|
match libkos::window::map_buffer(region, va) {
|
||||||
Ok(window) => *slot = Some((region, window)),
|
Ok(pixels) => *slot = Some((region, pixels)),
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
syscall::drop_cap(region);
|
syscall::drop_cap(region);
|
||||||
return None;
|
return None;
|
||||||
@@ -55,20 +53,16 @@ impl Host for KernelHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn blank(&mut self, index: usize) {
|
fn blank(&mut self, index: usize) {
|
||||||
if let Some((_, window)) = self.buffers.get(index).and_then(Option::as_ref) {
|
if let Some((_, pixels)) = self.buffers.get_mut(index).and_then(Option::as_mut) {
|
||||||
window.fill_u32(ByteOffset::ZERO, window.len() / PIXEL_BYTES, 0);
|
pixels.fill(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read(&self, index: usize, from: usize, out: &mut [Color]) {
|
fn pixels(&self, index: usize, from: usize, count: usize) -> &[u8] {
|
||||||
let Some((_, window)) = self.buffers.get(index).and_then(Option::as_ref) else { return };
|
let Some((_, pixels)) = self.buffers.get(index).and_then(Option::as_ref) else { return &[] };
|
||||||
for (i, px) in out.iter_mut().enumerate() {
|
let start = (from * PIXEL_BYTES).min(pixels.len());
|
||||||
let at = (from + i) * PIXEL_BYTES;
|
let end = from.saturating_add(count).saturating_mul(PIXEL_BYTES).min(pixels.len());
|
||||||
if at + PIXEL_BYTES > window.len() {
|
&pixels[start..end]
|
||||||
return;
|
|
||||||
}
|
|
||||||
*px = Color::from_rgba(window.read::<u32>(ByteOffset::new(at)).to_le_bytes());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reclaim(&mut self, index: usize) {
|
fn reclaim(&mut self, index: usize) {
|
||||||
|
|||||||
@@ -20,8 +20,6 @@ use crate::windows::{MAX_WINDOWS, Windows};
|
|||||||
|
|
||||||
/// The OS bar: one row of text.
|
/// The OS bar: one row of text.
|
||||||
pub const BAR_HEIGHT: u16 = font::HEIGHT;
|
pub const BAR_HEIGHT: u16 = font::HEIGHT;
|
||||||
/// Pixels copied per row at a time.
|
|
||||||
const RUN: usize = 256;
|
|
||||||
|
|
||||||
/// What the display needs from the kernel.
|
/// What the display needs from the kernel.
|
||||||
pub trait Host {
|
pub trait Host {
|
||||||
@@ -31,8 +29,10 @@ pub trait Host {
|
|||||||
/// Blanks slot `index`'s buffer, so a new window shows nothing of the
|
/// Blanks slot `index`'s buffer, so a new window shows nothing of the
|
||||||
/// last one's.
|
/// last one's.
|
||||||
fn blank(&mut self, index: usize);
|
fn blank(&mut self, index: usize);
|
||||||
/// Pixels of slot `index`'s buffer from pixel `from` on, into `out`.
|
/// Up to `count` pixels of slot `index`'s buffer from pixel `from` on,
|
||||||
fn read(&self, index: usize, from: usize, out: &mut [Color]);
|
/// as its bytes (R, G, B, A each); fewer past its end. The app may be
|
||||||
|
/// drawing into it meanwhile: a torn frame, no worse.
|
||||||
|
fn pixels(&self, index: usize, from: usize, count: usize) -> &[u8];
|
||||||
/// Takes back every copy of slot `index`'s buffer handed out.
|
/// Takes back every copy of slot `index`'s buffer handed out.
|
||||||
fn reclaim(&mut self, index: usize);
|
fn reclaim(&mut self, index: usize);
|
||||||
/// A copy of the display's endpoint carrying `badge`.
|
/// A copy of the display's endpoint carrying `badge`.
|
||||||
@@ -141,15 +141,9 @@ impl<F: Framebuffer, H: Host> DisplayServer<F, H> {
|
|||||||
let (ww, wh) = (self.window_size().0.raw(), self.window_size().1.raw());
|
let (ww, wh) = (self.window_size().0.raw(), self.window_size().1.raw());
|
||||||
let (x0, y0) = (x.raw().min(ww), y.raw().min(wh));
|
let (x0, y0) = (x.raw().min(ww), y.raw().min(wh));
|
||||||
let (x1, y1) = (x0.saturating_add(width.raw()).min(ww), y0.saturating_add(height.raw()).min(wh));
|
let (x1, y1) = (x0.saturating_add(width.raw()).min(ww), y0.saturating_add(height.raw()).min(wh));
|
||||||
let mut run = [Color::from_raw(0); RUN];
|
|
||||||
for row in y0..y1 {
|
for row in y0..y1 {
|
||||||
let mut col = x0;
|
let bytes = self.host.pixels(index, row as usize * ww as usize + x0 as usize, usize::from(x1 - x0));
|
||||||
while col < x1 {
|
self.fb.write_rgba_row(ScreenCoord::new(x0), ScreenCoord::new(row + BAR_HEIGHT), bytes);
|
||||||
let n = usize::from(x1 - col).min(RUN);
|
|
||||||
self.host.read(index, row as usize * ww as usize + col as usize, &mut run[..n]);
|
|
||||||
self.fb.write_row(ScreenCoord::new(col), ScreenCoord::new(row + BAR_HEIGHT), &run[..n]);
|
|
||||||
col += n as u16;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.fb.flush();
|
self.fb.flush();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ const H: usize = 16 + 8;
|
|||||||
/// Buffers in memory; a capability is its slot number plus 40.
|
/// Buffers in memory; a capability is its slot number plus 40.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct FakeHost {
|
struct FakeHost {
|
||||||
buffers: Vec<Option<Vec<Color>>>,
|
/// Each slot's pixels, the bytes R, G, B, A.
|
||||||
|
buffers: Vec<Option<Vec<u8>>>,
|
||||||
minted: Vec<usize>,
|
minted: Vec<usize>,
|
||||||
revoked: Vec<CapSlot>,
|
revoked: Vec<CapSlot>,
|
||||||
reclaimed: Vec<usize>,
|
reclaimed: Vec<usize>,
|
||||||
@@ -28,17 +29,16 @@ impl Host for FakeHost {
|
|||||||
if self.buffers.len() <= index {
|
if self.buffers.len() <= index {
|
||||||
self.buffers.resize(index + 1, None);
|
self.buffers.resize(index + 1, None);
|
||||||
}
|
}
|
||||||
self.buffers[index].get_or_insert_with(|| vec![Color::from_raw(0); pixels]);
|
self.buffers[index].get_or_insert_with(|| vec![0; pixels * 4]);
|
||||||
CapSlot::new(40 + index as u8)
|
CapSlot::new(40 + index as u8)
|
||||||
}
|
}
|
||||||
fn blank(&mut self, index: usize) {
|
fn blank(&mut self, index: usize) {
|
||||||
if let Some(b) = self.buffers[index].as_mut() {
|
if let Some(b) = self.buffers[index].as_mut() {
|
||||||
b.fill(Color::from_raw(0));
|
b.fill(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn read(&self, index: usize, from: usize, out: &mut [Color]) {
|
fn pixels(&self, index: usize, from: usize, count: usize) -> &[u8] {
|
||||||
let b = self.buffers[index].as_ref().unwrap();
|
&self.buffers[index].as_ref().unwrap()[from * 4..(from + count) * 4]
|
||||||
out.copy_from_slice(&b[from..from + out.len()]);
|
|
||||||
}
|
}
|
||||||
fn reclaim(&mut self, index: usize) {
|
fn reclaim(&mut self, index: usize) {
|
||||||
self.reclaimed.push(index);
|
self.reclaimed.push(index);
|
||||||
@@ -58,6 +58,13 @@ impl Host for FakeHost {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A window buffer all one colour, as an app would draw it.
|
||||||
|
fn fill(buf: &mut [u8], c: Color) {
|
||||||
|
for px in buf.chunks_exact_mut(4) {
|
||||||
|
px.copy_from_slice(&c.to_rgba());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Srv = DisplayServer<GridFramebuffer<W, H>, FakeHost>;
|
type Srv = DisplayServer<GridFramebuffer<W, H>, FakeHost>;
|
||||||
|
|
||||||
fn server() -> Srv {
|
fn server() -> Srv {
|
||||||
@@ -76,7 +83,7 @@ fn open(s: &mut Srv, opener: usize, name: &[u8]) -> usize {
|
|||||||
/// Paints window `slot`'s buffer one colour and presents all of it.
|
/// Paints window `slot`'s buffer one colour and presents all of it.
|
||||||
fn paint(s: &mut Srv, badge: usize, slot: usize, color: Color) -> [usize; 4] {
|
fn paint(s: &mut Srv, badge: usize, slot: usize, color: Color) -> [usize; 4] {
|
||||||
// The app writes its buffer directly; here, through the fake.
|
// The app writes its buffer directly; here, through the fake.
|
||||||
s.host_mut_for_test().buffers[slot].as_mut().unwrap().fill(color);
|
fill(s.host_mut_for_test().buffers[slot].as_mut().unwrap(), color);
|
||||||
let rect = w::pack_rect(
|
let rect = w::pack_rect(
|
||||||
types::display::ScreenCoord::new(0),
|
types::display::ScreenCoord::new(0),
|
||||||
types::display::ScreenCoord::new(0),
|
types::display::ScreenCoord::new(0),
|
||||||
@@ -156,9 +163,9 @@ fn closing_takes_the_window_and_its_buffer_back() {
|
|||||||
assert_eq!(shown(&s), Some(BLUE), "the terminal is in front again");
|
assert_eq!(shown(&s), Some(BLUE), "the terminal is in front again");
|
||||||
assert_eq!(s.handle_call(snake, [w::POLL, 0, 0, 0], None).0[0], MALFORMED, "closed");
|
assert_eq!(s.handle_call(snake, [w::POLL, 0, 0, 0], None).0[0], MALFORMED, "closed");
|
||||||
// Its buffer is reused, blank, for the next window.
|
// Its buffer is reused, blank, for the next window.
|
||||||
s.host_mut_for_test().buffers[1].as_mut().unwrap().fill(RED);
|
fill(s.host_mut_for_test().buffers[1].as_mut().unwrap(), RED);
|
||||||
open(&mut s, 2, b"Snake");
|
open(&mut s, 2, b"Snake");
|
||||||
assert!(s.host().buffers[1].as_ref().unwrap().iter().all(|c| *c == Color::from_raw(0)));
|
assert!(s.host().buffers[1].as_ref().unwrap().iter().all(|b| *b == 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -176,7 +183,7 @@ fn a_launcher_gone_takes_its_windows_with_it() {
|
|||||||
fn a_present_outside_the_window_is_clipped() {
|
fn a_present_outside_the_window_is_clipped() {
|
||||||
let mut s = server();
|
let mut s = server();
|
||||||
let t = open(&mut s, 1, b"Terminal");
|
let t = open(&mut s, 1, b"Terminal");
|
||||||
s.host_mut_for_test().buffers[0].as_mut().unwrap().fill(RED);
|
fill(s.host_mut_for_test().buffers[0].as_mut().unwrap(), RED);
|
||||||
let rect = w::pack_rect(
|
let rect = w::pack_rect(
|
||||||
types::display::ScreenCoord::new(30),
|
types::display::ScreenCoord::new(30),
|
||||||
types::display::ScreenCoord::new(5),
|
types::display::ScreenCoord::new(5),
|
||||||
|
|||||||
Reference in New Issue
Block a user