feat(init,display): loader streams ELF segments (any size, kills half-built proc); window pixels RGBA8 premul (tiny-skia native), canvas mapped plain memory + pixels_mut/touch
This commit is contained in:
@@ -13,8 +13,8 @@ pub(crate) const STACK_PAGES: usize = 4;
|
|||||||
/// The most there is room for under the image (page 0 stays unmapped).
|
/// The most there is room for under the image (page 0 stays unmapped).
|
||||||
const MAX_STACK_PAGES: usize = USER_CODE_BASE / PAGE_SIZE - 1;
|
const MAX_STACK_PAGES: usize = USER_CODE_BASE / PAGE_SIZE - 1;
|
||||||
const CREATE_CAP: CapSlot = CapSlot::const_new(0);
|
const CREATE_CAP: CapSlot = CapSlot::const_new(0);
|
||||||
/// Where a program file is read to before it is loaded (32 pages: the
|
/// Where a program file is read through on its way into a process, a
|
||||||
/// largest program init will load; the gatekeeper is past 16).
|
/// chunk at a time, so a program may be any size.
|
||||||
const SCRATCH_VA: VirtualAddress = VirtualAddress::from_raw(0x0120_0000);
|
const SCRATCH_VA: VirtualAddress = VirtualAddress::from_raw(0x0120_0000);
|
||||||
const SCRATCH_PAGES: u16 = 32;
|
const SCRATCH_PAGES: u16 = 32;
|
||||||
|
|
||||||
@@ -23,14 +23,54 @@ pub(crate) struct LoadedProcess {
|
|||||||
pub(crate) entry_point: EntryPoint,
|
pub(crate) entry_point: EntryPoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reads all of `filename` into `scratch`; its size, or `None` if it is
|
/// A program file read forwards through a buffer (storage has no seek): a
|
||||||
/// missing, unreadable or larger than `scratch`.
|
/// segment's bytes are asked for in increasing file order.
|
||||||
fn read_file(storage: &mut Storage, scratch: &mut [u8], filename: &[u8]) -> Option<usize> {
|
struct Stream<'a> {
|
||||||
let handle = storage.open(filename).ok()?;
|
storage: &'a mut Storage,
|
||||||
let size = storage.file_size(handle).ok().filter(|s| *s <= scratch.len());
|
handle: libkos::fs::FileHandle,
|
||||||
let read = size.and_then(|s| storage.read(handle, &mut scratch[..s]).ok());
|
buf: &'a mut [u8],
|
||||||
let _ = storage.close(handle);
|
/// The file offset of `buf[0]`, and how much of `buf` holds file.
|
||||||
read.filter(|r| Some(*r) == size)
|
at: usize,
|
||||||
|
len: usize,
|
||||||
|
size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Stream<'_> {
|
||||||
|
/// Reads the next chunk after what `buf` holds; false at the end or
|
||||||
|
/// on an error.
|
||||||
|
fn advance(&mut self) -> bool {
|
||||||
|
let next = self.at + self.len;
|
||||||
|
let want = self.buf.len().min(self.size - next.min(self.size));
|
||||||
|
match self.storage.read(self.handle, &mut self.buf[..want]) {
|
||||||
|
Ok(n) if n > 0 => {
|
||||||
|
(self.at, self.len) = (next, n);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hands the file's bytes `from..from + len` to `sink`, piece by piece,
|
||||||
|
/// with each piece's place in that range.
|
||||||
|
fn copy(&mut self, from: usize, len: usize, mut sink: impl FnMut(usize, &[u8])) -> bool {
|
||||||
|
if from < self.at || from.checked_add(len).is_none_or(|end| end > self.size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let mut done = 0;
|
||||||
|
while done < len {
|
||||||
|
let want = from + done;
|
||||||
|
while want >= self.at + self.len {
|
||||||
|
if !self.advance() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let start = want - self.at;
|
||||||
|
let n = (self.len - start).min(len - done);
|
||||||
|
sink(done, &self.buf[start..start + n]);
|
||||||
|
done += n;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn load_binary(
|
pub(crate) fn load_binary(
|
||||||
@@ -40,53 +80,57 @@ pub(crate) fn load_binary(
|
|||||||
limits: Limits,
|
limits: Limits,
|
||||||
stack_pages: usize,
|
stack_pages: usize,
|
||||||
) -> Option<LoadedProcess> {
|
) -> Option<LoadedProcess> {
|
||||||
let stack_pages = stack_pages.min(MAX_STACK_PAGES);
|
let handle = storage.open(filename).ok()?;
|
||||||
let file_size = read_file(storage, scratch, filename)?;
|
let loaded = storage
|
||||||
let elf_data = &scratch[..file_size];
|
.file_size(handle)
|
||||||
let elf_info = match elf::parse(elf_data) {
|
.ok()
|
||||||
Ok(info) => info,
|
.and_then(|size| load_from(&mut Stream { storage, handle, buf: scratch, at: 0, len: 0, size }, limits, stack_pages));
|
||||||
Err(_) => return None,
|
let _ = storage.close(handle);
|
||||||
};
|
loaded
|
||||||
|
|
||||||
let process_cap = syscall::create_process(CREATE_CAP, limits)?;
|
|
||||||
|
|
||||||
for segment in elf_info.segments() {
|
|
||||||
let page_start = segment.vaddr & !0xFFF;
|
|
||||||
let offset_in_page = segment.vaddr & 0xFFF;
|
|
||||||
let total_size = offset_in_page + segment.mem_size;
|
|
||||||
let seg_pages = total_size.div_ceil(PAGE_SIZE);
|
|
||||||
|
|
||||||
let page_count = PageCount::new(u16::try_from(seg_pages).ok()?)?;
|
|
||||||
let local = libkos::window::request(page_count)?;
|
|
||||||
local.fill_u32(ByteOffset::ZERO, seg_pages * PAGE_SIZE / 4, 0);
|
|
||||||
|
|
||||||
let copy_len = segment.file_size.min(segment.mem_size);
|
|
||||||
let src = segment
|
|
||||||
.file_offset
|
|
||||||
.checked_add(copy_len)
|
|
||||||
.and_then(|end| elf_data.get(segment.file_offset..end));
|
|
||||||
if let Some(src) = src.filter(|_| copy_len > 0) {
|
|
||||||
local.copy_from(ByteOffset::new(offset_in_page), src);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_from(file: &mut Stream, limits: Limits, stack_pages: usize) -> Option<LoadedProcess> {
|
||||||
|
let stack_pages = stack_pages.min(MAX_STACK_PAGES);
|
||||||
|
// The headers are in the first chunk.
|
||||||
|
if !file.advance() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let elf_info = elf::parse(&file.buf[..file.len]).ok()?;
|
||||||
|
let process_cap = syscall::create_process(CREATE_CAP, limits)?;
|
||||||
|
let filled = elf_info.segments().all(|segment| load_segment(file, process_cap, segment).is_some())
|
||||||
|
&& (0..stack_pages).all(|i| {
|
||||||
|
let dst = VirtualAddress::from_raw(USER_CODE_BASE - (stack_pages - i) * PAGE_SIZE);
|
||||||
|
PageCount::new(1).and_then(syscall::request_pages).is_some_and(|va| {
|
||||||
|
syscall::map_page(process_cap, va, dst, false);
|
||||||
|
true
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if !filled {
|
||||||
|
let _ = syscall::kill(process_cap);
|
||||||
|
syscall::drop_cap(process_cap);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(LoadedProcess { process_cap, entry_point: EntryPoint::from_raw(elf_info.entry_point) })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fresh pages with the segment's bytes, zero past them, moved into the
|
||||||
|
/// process at its address.
|
||||||
|
fn load_segment(file: &mut Stream, process_cap: CapSlot, segment: &elf::Segment) -> Option<()> {
|
||||||
|
let page_start = segment.vaddr & !0xFFF;
|
||||||
|
let offset_in_page = segment.vaddr & 0xFFF;
|
||||||
|
let seg_pages = (offset_in_page + segment.mem_size).div_ceil(PAGE_SIZE);
|
||||||
|
let local = libkos::window::request(PageCount::new(u16::try_from(seg_pages).ok()?)?)?;
|
||||||
|
local.fill_u32(ByteOffset::ZERO, seg_pages * PAGE_SIZE / 4, 0);
|
||||||
|
let copy_len = segment.file_size.min(segment.mem_size);
|
||||||
|
if copy_len > 0 && !file.copy(segment.file_offset, copy_len, |at, bytes| local.copy_from(ByteOffset::new(offset_in_page + at), bytes)) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
for p in 0..seg_pages {
|
for p in 0..seg_pages {
|
||||||
let src_page = VirtualAddress::from_raw(local.address().raw() + p * PAGE_SIZE);
|
let src_page = VirtualAddress::from_raw(local.address().raw() + p * PAGE_SIZE);
|
||||||
let dst_page = VirtualAddress::from_raw(page_start + p * PAGE_SIZE);
|
let dst_page = VirtualAddress::from_raw(page_start + p * PAGE_SIZE);
|
||||||
syscall::map_page(process_cap, src_page, dst_page, segment.flags.execute);
|
syscall::map_page(process_cap, src_page, dst_page, segment.flags.execute);
|
||||||
}
|
}
|
||||||
}
|
Some(())
|
||||||
|
|
||||||
for i in 0..stack_pages {
|
|
||||||
let page_count = PageCount::new(1)?;
|
|
||||||
let stack_va = syscall::request_pages(page_count)?;
|
|
||||||
let dst = VirtualAddress::from_raw(USER_CODE_BASE - (stack_pages - i) * PAGE_SIZE);
|
|
||||||
syscall::map_page(process_cap, stack_va, dst, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(LoadedProcess {
|
|
||||||
process_cap,
|
|
||||||
entry_point: EntryPoint::from_raw(elf_info.entry_point),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A region of init's own to read program files into.
|
/// A region of init's own to read program files into.
|
||||||
|
|||||||
@@ -5,12 +5,10 @@
|
|||||||
//! front.
|
//! front.
|
||||||
|
|
||||||
use crate::syscall;
|
use crate::syscall;
|
||||||
use crate::window::Window;
|
use types::address::VirtualAddress;
|
||||||
use types::address::{ByteOffset, VirtualAddress};
|
|
||||||
use types::bounded::CapSlot;
|
use types::bounded::CapSlot;
|
||||||
use types::display::window::{self as w, PIXEL_BYTES};
|
use types::display::window::{self as w, PIXEL_BYTES};
|
||||||
use types::display::{Color, Dimension, ScreenCoord};
|
use types::display::{Color, Dimension, ScreenCoord};
|
||||||
use types::mapping::PagePerms;
|
|
||||||
|
|
||||||
/// Where an app's canvas is mapped.
|
/// Where an app's canvas is mapped.
|
||||||
const CANVAS_VA: VirtualAddress = VirtualAddress::from_raw(0x0100_0000);
|
const CANVAS_VA: VirtualAddress = VirtualAddress::from_raw(0x0100_0000);
|
||||||
@@ -67,11 +65,13 @@ struct Damage {
|
|||||||
y1: u16,
|
y1: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A window's pixels, mapped here. Draw with the `Framebuffer` methods (and
|
/// A window's pixels, mapped here as plain memory. Draw with the
|
||||||
/// `raster`); `flush` presents everything drawn since the last one.
|
/// `Framebuffer` methods (and `raster`), or on the bytes themselves
|
||||||
|
/// (`pixels_mut`, then `touch`); `flush` presents everything changed since
|
||||||
|
/// the last one.
|
||||||
pub struct Canvas {
|
pub struct Canvas {
|
||||||
window: CapSlot,
|
window: CapSlot,
|
||||||
pixels: Window,
|
pixels: &'static mut [u8],
|
||||||
width: Dimension,
|
width: Dimension,
|
||||||
height: Dimension,
|
height: Dimension,
|
||||||
damage: Option<Damage>,
|
damage: Option<Damage>,
|
||||||
@@ -84,7 +84,7 @@ impl Canvas {
|
|||||||
syscall::Received::Message { words, cap: Some(buffer), .. } if words[0] == w::OK => (words, buffer),
|
syscall::Received::Message { words, cap: Some(buffer), .. } if words[0] == w::OK => (words, buffer),
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
let pixels = crate::window::map(buffer, CANVAS_VA, PagePerms::ReadWrite).ok()?;
|
let pixels = crate::window::map_buffer(buffer, CANVAS_VA).ok()?;
|
||||||
let (width, height) = (Dimension::new(words[1] as u16), Dimension::new(words[2] as u16));
|
let (width, height) = (Dimension::new(words[1] as u16), Dimension::new(words[2] as u16));
|
||||||
// The display says how big; the mapping says how much is there.
|
// The display says how big; the mapping says how much is there.
|
||||||
(width.raw() as usize * height.raw() as usize * PIXEL_BYTES <= pixels.len())
|
(width.raw() as usize * height.raw() as usize * PIXEL_BYTES <= pixels.len())
|
||||||
@@ -95,12 +95,19 @@ impl Canvas {
|
|||||||
self.window
|
self.window
|
||||||
}
|
}
|
||||||
|
|
||||||
fn offset(&self, x: u16, y: u16) -> ByteOffset {
|
/// The pixels, `width * height` of them, each the bytes R, G, B, A.
|
||||||
ByteOffset::new((y as usize * self.width.raw() as usize + x as usize) * PIXEL_BYTES)
|
pub fn pixels_mut(&mut self) -> &mut [u8] {
|
||||||
|
let n = self.width.raw() as usize * self.height.raw() as usize * PIXEL_BYTES;
|
||||||
|
&mut self.pixels[..n]
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marks `x..x+w`, `y..y+h` (already clipped) as changed.
|
fn offset(&self, x: u16, y: u16) -> usize {
|
||||||
fn touch(&mut self, x: u16, y: u16, w: u16, h: u16) {
|
(y as usize * self.width.raw() as usize + x as usize) * PIXEL_BYTES
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Marks a rectangle as changed, for the next `flush`; clipped.
|
||||||
|
pub fn touch(&mut self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension) {
|
||||||
|
let Some((x, y, w, h)) = self.clip(x, y, w, h) else { return };
|
||||||
let (x1, y1) = (x + w, y + h);
|
let (x1, y1) = (x + w, y + h);
|
||||||
self.damage = Some(match self.damage {
|
self.damage = Some(match self.damage {
|
||||||
None => Damage { x0: x, y0: y, x1, y1 },
|
None => Damage { x0: x, y0: y, x1, y1 },
|
||||||
@@ -112,7 +119,15 @@ impl Canvas {
|
|||||||
fn clip(&self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension) -> Option<(u16, u16, u16, u16)> {
|
fn clip(&self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension) -> Option<(u16, u16, u16, u16)> {
|
||||||
let (cw, ch) = (self.width.raw(), self.height.raw());
|
let (cw, ch) = (self.width.raw(), self.height.raw());
|
||||||
let (x, y) = (x.raw(), y.raw());
|
let (x, y) = (x.raw(), y.raw());
|
||||||
(x < cw && y < ch).then(|| (x, y, w.raw().min(cw - x), h.raw().min(ch - y)))
|
(x < cw && y < ch && w.raw() > 0 && h.raw() > 0).then(|| (x, y, w.raw().min(cw - x), h.raw().min(ch - y)))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Fills `len` pixels from byte `at` with `color`.
|
||||||
|
fn fill(&mut self, at: usize, len: usize, color: Color) {
|
||||||
|
let px = color.to_rgba();
|
||||||
|
for chunk in self.pixels[at..at + len * PIXEL_BYTES].chunks_exact_mut(PIXEL_BYTES) {
|
||||||
|
chunk.copy_from_slice(&px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,28 +141,24 @@ impl port_framebuffer::Framebuffer for Canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_pixel(&mut self, x: ScreenCoord, y: ScreenCoord, color: Color) {
|
fn write_pixel(&mut self, x: ScreenCoord, y: ScreenCoord, color: Color) {
|
||||||
if let Some((x, y, _, _)) = self.clip(x, y, Dimension::new(1), Dimension::new(1)) {
|
self.fill_rect(x, y, Dimension::new(1), Dimension::new(1), color);
|
||||||
self.pixels.write(self.offset(x, y), color.raw());
|
|
||||||
self.touch(x, y, 1, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_row(&mut self, x: ScreenCoord, y: ScreenCoord, row: &[Color]) {
|
fn write_row(&mut self, x: ScreenCoord, y: ScreenCoord, row: &[Color]) {
|
||||||
let Some((x, y, w, _)) = self.clip(x, y, Dimension::new(row.len().min(u16::MAX as usize) as u16), Dimension::new(1)) else { return };
|
let len = Dimension::new(row.len().min(u16::MAX as usize) as u16);
|
||||||
let mut run = [0u32; 64];
|
let Some((cx, cy, w, _)) = self.clip(x, y, len, Dimension::new(1)) else { return };
|
||||||
for (i, chunk) in row[..w as usize].chunks(run.len()).enumerate() {
|
let at = self.offset(cx, cy);
|
||||||
for (slot, c) in run.iter_mut().zip(chunk) {
|
for (chunk, c) in self.pixels[at..at + w as usize * PIXEL_BYTES].chunks_exact_mut(PIXEL_BYTES).zip(row) {
|
||||||
*slot = c.raw();
|
chunk.copy_from_slice(&c.to_rgba());
|
||||||
}
|
}
|
||||||
self.pixels.write_u32s(self.offset(x + (i * 64) as u16, y), &run[..chunk.len()]);
|
self.touch(x, y, len, Dimension::new(1));
|
||||||
}
|
|
||||||
self.touch(x, y, w, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fill_rect(&mut self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension, color: Color) {
|
fn fill_rect(&mut self, x: ScreenCoord, y: ScreenCoord, w: Dimension, h: Dimension, color: Color) {
|
||||||
let Some((x, y, w, h)) = self.clip(x, y, w, h) else { return };
|
let Some((cx, cy, cw, ch)) = self.clip(x, y, w, h) else { return };
|
||||||
for row in y..y + h {
|
for row in cy..cy + ch {
|
||||||
self.pixels.fill_u32(self.offset(x, row), w as usize, color.raw());
|
let at = self.offset(cx, row);
|
||||||
|
self.fill(at, cw as usize, color);
|
||||||
}
|
}
|
||||||
self.touch(x, y, w, h);
|
self.touch(x, y, w, h);
|
||||||
}
|
}
|
||||||
@@ -165,14 +176,14 @@ impl port_framebuffer::Framebuffer for Canvas {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
let Some((_, top, _, height)) = self.clip(ScreenCoord::new(0), top, self.width, height) else { return };
|
|
||||||
let by = by.raw().min(height);
|
|
||||||
let row = self.width.raw() as usize * PIXEL_BYTES;
|
|
||||||
let kept = (height - by) as usize * row;
|
|
||||||
self.pixels.copy_within(self.offset(0, top + by), self.offset(0, top), kept);
|
|
||||||
let width = self.width;
|
let width = self.width;
|
||||||
|
let Some((_, top, _, height)) = self.clip(ScreenCoord::new(0), top, width, height) else { return };
|
||||||
|
let by = by.raw().min(height);
|
||||||
|
let kept = (height - by) as usize * width.raw() as usize * PIXEL_BYTES;
|
||||||
|
let (from, to) = (self.offset(0, top + by), self.offset(0, top));
|
||||||
|
self.pixels.copy_within(from..from + kept, to);
|
||||||
self.fill_rect(ScreenCoord::new(0), ScreenCoord::new(top + height - by), width, Dimension::new(by), fill);
|
self.fill_rect(ScreenCoord::new(0), ScreenCoord::new(top + height - by), width, Dimension::new(by), fill);
|
||||||
self.touch(0, top, width.raw(), height);
|
self.touch(ScreenCoord::new(0), ScreenCoord::new(top), width, Dimension::new(height));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ impl Host for KernelHost {
|
|||||||
if at + PIXEL_BYTES > window.len() {
|
if at + PIXEL_BYTES > window.len() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
*px = Color::from_raw(window.read::<u32>(ByteOffset::new(at)));
|
*px = Color::from_rgba(window.read::<u32>(ByteOffset::new(at)).to_le_bytes());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,17 @@ impl Color {
|
|||||||
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
|
pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
|
||||||
Self(((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32))
|
Self(((a as u32) << 24) | ((r as u32) << 16) | ((g as u32) << 8) | (b as u32))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// As a window's pixel is stored: the bytes R, G, B, A (see
|
||||||
|
/// `window::BUFFER`).
|
||||||
|
pub const fn to_rgba(self) -> [u8; 4] {
|
||||||
|
let [b, g, r, a] = self.0.to_le_bytes();
|
||||||
|
[r, g, b, a]
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn from_rgba([r, g, b, a]: [u8; 4]) -> Self {
|
||||||
|
Self(u32::from_le_bytes([b, g, r, a]))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
@@ -61,9 +72,10 @@ pub mod window {
|
|||||||
/// (`pack_name`). Reply `[OK, width, height, 0]` carrying the window
|
/// (`pack_name`). Reply `[OK, width, height, 0]` carrying the window
|
||||||
/// capability, which the launcher passes on; the new window is in front.
|
/// capability, which the launcher passes on; the new window is in front.
|
||||||
pub const OPEN: usize = 1;
|
pub const OPEN: usize = 1;
|
||||||
/// Through a window: reply `[OK, width, height, 0]` carrying its pixel buffer, a
|
/// Through a window: reply `[OK, width, height, 0]` carrying its pixel
|
||||||
/// region the holder maps read-write: `width * height` pixels, rows
|
/// buffer, a region the holder maps read-write: `width * height`
|
||||||
/// one after another, each pixel a `Color` as a little-endian u32.
|
/// pixels, rows one after another, each pixel the bytes R, G, B, A
|
||||||
|
/// (alpha premultiplied; the display shows it as opaque).
|
||||||
pub const BUFFER: usize = 2;
|
pub const BUFFER: usize = 2;
|
||||||
/// Through a window: the rectangle (`pack_rect`) that changed.
|
/// Through a window: the rectangle (`pack_rect`) that changed.
|
||||||
pub const PRESENT: usize = 3;
|
pub const PRESENT: usize = 3;
|
||||||
|
|||||||
Reference in New Issue
Block a user