灵符lingfu

ling-lang

↓ 0 下载

Ling - The Omniglot Systems Language

关键词 systems, polyglot, ai, crypto, game
lingfu install ling-lang
许可证Apache-2.0 OR MIT
作者Ling Lin, Sanny Lin
README代码 / 文件版本依赖关系图依赖此包的项目安全构建设置

代码 / 文件 (995)

crates/ling-kernel/src/drivers/framebuffer.rs
//! Linear framebuffer support across x86_64 (Multiboot2) and aarch64 (BCM2835 mailbox).

use core::ptr;

#[cfg(target_arch = "x86_64")]
extern "C" {
    static mb2_info_ptr: u32;
}

#[derive(Clone, Copy)]
pub struct FbInfo {
    pub addr: u64,
    pub pitch: u32,
    pub width: u32,
    pub height: u32,
    pub bpp: u8,
}

static mut FB: Option<FbInfo> = None;

// Overscan safe-area margin. The logical origin is inset by (OX, OY) from the
// real top-left, and width()/height() report the reduced drawable area, so UI
// laid out against those bounds lands fully inside a display that crops the
// signal edges (common on HDMI panels/TVs — on the Pi 5 the visible area can
// start a couple % in, so content at a true (0,0) gets clipped off the top-left
// corner). aarch64/Pi sets this after framebuffer init; it stays 0 on x86_64
// (VMs present the whole surface, no overscan), so set_pixel/fill_rect/width/
// height are unchanged there.
static mut OX: u32 = 0;
static mut OY: u32 = 0;

const BACKBUFFER_MAX: usize = 1920 * 1080 * 4;
static mut BACKBUFFER: [u8; BACKBUFFER_MAX] = [0u8; BACKBUFFER_MAX];

fn back_buf() -> &'static mut [u8; BACKBUFFER_MAX] {
    unsafe { &mut *&raw mut BACKBUFFER }
}

/// Physical address + magic of the VBE handoff block the disk-boot
/// stage2 leaves behind (bootloader/stage2.asm's LFB_INFO_ADDR/_MAGIC --
/// keep in sync). Layout: magic, physbase, pitch, width, height, bpp,
/// each a u32.
#[cfg(target_arch = "x86_64")]
const LFB_INFO_ADDR: u64 = 0x6000;
#[cfg(target_arch = "x86_64")]
const LFB_INFO_MAGIC: u32 = 0x4942_464C; // "LFBI"

/// True when this kernel was loaded by the disk-boot path (stage1/stage2,
/// no GRUB): there is no Multiboot2 info structure at all. The desktop
/// uses this to decide between the Live auto-login flow and the installed
/// system's login greeter.
#[cfg(target_arch = "x86_64")]
pub fn booted_from_disk() -> bool {
    unsafe { ptr::read_volatile(&raw const mb2_info_ptr) == 0 }
}

pub fn init() {
    #[cfg(target_arch = "x86_64")]
    unsafe {
        let info_ptr = ptr::read_volatile(&raw const mb2_info_ptr);
        if info_ptr == 0 {
            // Disk boot: no Multiboot2 info exists. stage2 may have set a
            // VBE mode and left its tagged handoff block instead.
            if ptr::read_volatile(LFB_INFO_ADDR as *const u32) == LFB_INFO_MAGIC {
                let addr = ptr::read_volatile((LFB_INFO_ADDR + 4) as *const u32) as u64;
                let pitch = ptr::read_volatile((LFB_INFO_ADDR + 8) as *const u32);
                let width = ptr::read_volatile((LFB_INFO_ADDR + 12) as *const u32);
                let height = ptr::read_volatile((LFB_INFO_ADDR + 16) as *const u32);
                let bpp = ptr::read_volatile((LFB_INFO_ADDR + 20) as *const u32) as u8;
                if addr != 0 && width > 0 && height > 0 && (bpp == 24 || bpp == 32) {
                    ptr::write(&raw mut FB, Some(FbInfo { addr, pitch, width, height, bpp }));
                }
            }
            return;
        }
        let total_size = ptr::read_unaligned(info_ptr as *const u32);
        let mut offset: u32 = 8;
        while offset + 8 <= total_size {
            let tag_ptr = (info_ptr + offset) as *const u32;
            let tag_type = ptr::read_unaligned(tag_ptr);
            let tag_size = ptr::read_unaligned(tag_ptr.add(1));
            if tag_type == 0 {
                break;
            }
            if tag_type == 8 && tag_size >= 29 {
                let base = info_ptr + offset;
                let addr = ptr::read_unaligned((base + 8) as *const u64);
                let pitch = ptr::read_unaligned((base + 16) as *const u32);
                let width = ptr::read_unaligned((base + 20) as *const u32);
                let height = ptr::read_unaligned((base + 24) as *const u32);
                let bpp = ptr::read_volatile((base + 28) as *const u8);
                ptr::write(
                    &raw mut FB,
                    Some(FbInfo { addr, pitch, width, height, bpp }),
                );
                return;
            }
            offset += (tag_size + 7) & !7;
        }
    }

    #[cfg(target_arch = "aarch64")]
    {
        if let Some((addr, pitch, width, height, bpp)) =
            // 1280x720 (720p): a standard HDMI/TV mode. TVs often won't cleanly
            // scan out the 4:3 1024x768 computer mode, and the firmware may give
            // an odd surface for it. The firmware clamps to the display's real
            // mode anyway, and we read the actual geometry back.
            crate::arch::mailbox::allocate_framebuffer(1280, 720, 32)
        {
            unsafe {
                ptr::write(&raw mut FB, Some(FbInfo { addr, pitch, width, height, bpp }));
            }
            // Overscan safe area: pull all rendering in from the edges so a
            // monitor that crops the HDMI signal (reported ~10px off the top-
            // left on real Pi 5 hardware) doesn't clip the UI. Proportional
            // (~2%) so it scales with resolution; the config screen can nudge it.
            set_safe_margin(width / 48, height / 48);
        }
    }
}

// -- Live resolution switching (bochs/QEMU/VirtualBox DISPI) -----------------
// VBE's mode-set is a real-mode BIOS call we can't make in long mode -- but the
// bochs VBE "DISPI" register interface (used by QEMU's std VGA and VirtualBox's
// VBoxVGA) reprograms width/height/bpp at runtime with plain port I/O, and the
// linear framebuffer stays at the same aperture. So on those adapters we CAN
// change resolution live; elsewhere `set_mode` reports false and the caller
// falls back to persist-and-reboot.
#[cfg(target_arch = "x86_64")]
mod dispi {
    use crate::arch::io::{inw, outw};
    const INDEX: u16 = 0x01CE;
    const DATA: u16 = 0x01CF;
    const I_ID: u16 = 0;
    const I_XRES: u16 = 1;
    const I_YRES: u16 = 2;
    const I_BPP: u16 = 3;
    const I_ENABLE: u16 = 4;
    const I_VIRT_WIDTH: u16 = 6;
    const ENABLED: u16 = 0x01;
    const GETCAPS: u16 = 0x02;
    const LFB: u16 = 0x40;

    unsafe fn read(idx: u16) -> u16 {
        outw(INDEX, idx);
        inw(DATA)
    }
    unsafe fn write(idx: u16, val: u16) {
        outw(INDEX, idx);
        outw(DATA, val);
    }
    /// A bochs-DISPI-capable adapter reports an id of 0xB0C0..=0xB0CF.
    pub fn present() -> bool {
        let id = unsafe { read(I_ID) };
        (0xB0C0..=0xB0CF).contains(&id)
    }
    /// Query the adapter's maximum supported width/height. The DISPI spec:
    /// with the GETCAPS bit set in ENABLE, reading XRES/YRES returns the max
    /// the card can do (bounded by its VRAM), then GETCAPS is cleared. This is
    /// how we offer only the modes the actual card supports instead of a
    /// hardcoded guess.
    pub fn max_caps() -> Option<(u32, u32)> {
        if !present() {
            return None;
        }
        unsafe {
            let prev = read(I_ENABLE);
            write(I_ENABLE, GETCAPS);
            let mw = read(I_XRES) as u32;
            let mh = read(I_YRES) as u32;
            write(I_ENABLE, prev); // restore whatever mode was live
            if mw == 0 || mh == 0 {
                None
            } else {
                Some((mw, mh))
            }
        }
    }
    pub fn set(w: u16, h: u16) {
        unsafe {
            write(I_ENABLE, 0); // must disable before changing geometry
            write(I_XRES, w);
            write(I_YRES, h);
            write(I_BPP, 32);
            write(I_VIRT_WIDTH, w); // pitch = w * 4
            write(I_ENABLE, ENABLED | LFB);
        }
    }
}

/// Change the display resolution live (32bpp), reusing the existing linear
/// framebuffer aperture. Returns false if the adapter isn't DISPI-capable
/// (real hardware / a mode too big for the back buffer) -- the caller then
/// persists a next-boot preference instead. Keeps `FB.addr`; updates
/// width/height/pitch/bpp and clears the new surface.
#[cfg(target_arch = "x86_64")]
pub fn set_mode(w: u32, h: u32) -> bool {
    if w == 0 || h == 0 || (w as usize) * (h as usize) * 4 > BACKBUFFER_MAX {
        return false;
    }
    let Some(mut fb) = get() else { return false };
    if !dispi::present() {
        return false;
    }
    dispi::set(w as u16, h as u16);
    fb.width = w;
    fb.height = h;
    fb.bpp = 32;
    fb.pitch = w * 4;
    unsafe { ptr::write(&raw mut FB, Some(fb)) };
    back_clear(0x10_10_18);
    present();
    true
}

/// The Pi: ask the firmware for a new surface (physical + virtual size), take
/// whatever geometry it really gave (the Pi 5 quirks are handled in the
/// mailbox code) and re-derive the overscan margin. The window layer re-sizes
/// itself from `width()`/`height()` afterwards.
#[cfg(target_arch = "aarch64")]
pub fn set_mode(w: u32, h: u32) -> bool {
    if w == 0 || h == 0 || (w as usize) * (h as usize) * 4 > BACKBUFFER_MAX {
        return false;
    }
    let Some((addr, pitch, width, height, bpp)) = crate::arch::mailbox::allocate_framebuffer(w, h, 32) else {
        return false;
    };
    unsafe {
        ptr::write(&raw mut FB, Some(FbInfo { addr, pitch, width, height, bpp }));
    }
    set_safe_margin(width / 48, height / 48);
    clear_physical(0);
    // what the firmware really gave (its geometry / pitch / depth answers
    // are not always what was asked): on the serial log for the test runs
    let d = unsafe { crate::arch::mailbox::FB_DIAG };
    crate::console_write(b"display: ");
    crate::print_decimal(width as u64);
    crate::console_write(b"x");
    crate::print_decimal(height as u64);
    crate::console_write(b" pitch ");
    crate::print_decimal(pitch as u64);
    crate::console_write(b" bpp ");
    crate::print_decimal(bpp as u64);
    crate::console_write(b" (phys ");
    crate::print_decimal(d[0] as u64);
    crate::console_write(b"x");
    crate::print_decimal(d[1] as u64);
    crate::console_write(b" virt ");
    crate::print_decimal(d[2] as u64);
    crate::console_write(b"x");
    crate::print_decimal(d[3] as u64);
    crate::console_write(b" size ");
    crate::print_decimal(d[5] as u64);
    crate::console_write(b")
");
    true
}

#[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
pub fn set_mode(_w: u32, _h: u32) -> bool {
    false
}

/// The surface's full size (before the overscan margin), for the mode row.
pub fn mode_dims() -> (u32, u32) {
    get().map(|f| (f.width, f.height)).unwrap_or((0, 0))
}

pub fn get() -> Option<FbInfo> {
    unsafe { ptr::read(&raw const FB) }
}

pub fn available() -> bool {
    get().is_some()
}

pub fn width() -> u32 {
    get().map(|f| f.width).unwrap_or(0).saturating_sub(unsafe { OX } * 2)
}

pub fn height() -> u32 {
    get().map(|f| f.height).unwrap_or(0).saturating_sub(unsafe { OY } * 2)
}

/// Set the overscan safe-area margin (pixels inset on each edge). All direct
/// framebuffer drawing shifts in by this and width()/height() shrink by twice
/// it, so UI laid out against those bounds stays within a display that crops
/// its edges. The Pi config screen exposes this as a live alignment nudge.
pub fn set_safe_margin(ox: u32, oy: u32) {
    unsafe {
        OX = ox;
        OY = oy;
    }
}

/// Current overscan safe-area margin (pixels inset per edge).
pub fn safe_margin() -> (u32, u32) {
    unsafe { (OX, OY) }
}

pub fn set_pixel(x: u32, y: u32, color: u32) {
    unsafe {
        let Some(fb) = get() else { return };
        let x = x + OX;
        let y = y + OY;
        if x >= fb.width || y >= fb.height {
            return;
        }
        let bypp = fb.bpp as u32 / 8;
        let byte_offset = y as u64 * fb.pitch as u64 + x as u64 * bypp as u64;
        let p = (fb.addr + byte_offset) as *mut u8;
        match bypp {
            4 => ptr::write_volatile(p as *mut u32, color),
            3 => {
                ptr::write_volatile(p, (color & 0xFF) as u8);
                ptr::write_volatile(p.add(1), ((color >> 8) & 0xFF) as u8);
                ptr::write_volatile(p.add(2), ((color >> 16) & 0xFF) as u8);
            },
            2 => ptr::write_volatile(p as *mut u16, rgb565(color)),
            _ => {},
        }
    }
}

pub fn fill_rect(x: u32, y: u32, w: u32, h: u32, color: u32) {
    let Some(fb) = get() else { return };
    let (x, y) = unsafe { (x + OX, y + OY) };
    if x >= fb.width || y >= fb.height {
        return;
    }
    let w = w.min(fb.width - x);
    let h = h.min(fb.height - y);
    if w == 0 || h == 0 {
        return;
    }
    let bypp = fb.bpp as u32 / 8;
    if bypp == 4 {
        for row in y..y + h {
            let row_start = (fb.addr + row as u64 * fb.pitch as u64 + x as u64 * 4) as *mut u32;
            for col in 0..w as usize {
                unsafe { ptr::write_volatile(row_start.add(col), color) };
            }
        }
    } else if bypp == 3 {
        let b0 = (color & 0xFF) as u8;
        let b1 = ((color >> 8) & 0xFF) as u8;
        let b2 = ((color >> 16) & 0xFF) as u8;
        for row in y..y + h {
            let mut p = (fb.addr + row as u64 * fb.pitch as u64 + x as u64 * 3) as *mut u8;
            for _ in 0..w {
                unsafe {
                    ptr::write_volatile(p, b0);
                    ptr::write_volatile(p.add(1), b1);
                    ptr::write_volatile(p.add(2), b2);
                    p = p.add(3);
                }
            }
        }
    } else if bypp == 2 {
        let p16 = rgb565(color);
        for row in y..y + h {
            let row_start = (fb.addr + row as u64 * fb.pitch as u64 + x as u64 * 2) as *mut u16;
            for col in 0..w as usize {
                unsafe { ptr::write_volatile(row_start.add(col), p16) };
            }
        }
    }
}

/// Fill the WHOLE physical framebuffer, margin included, with one 0xRRGGBB
/// color — so nothing drawn earlier (the boot banner) lingers in the overscan
/// margin around a full-screen app.
pub fn clear_physical(color: u32) {
    let Some(fb) = get() else { return };
    for y in 0..fb.height as usize {
        let base = fb.addr as usize + y * fb.pitch as usize;
        unsafe {
            match fb.bpp {
                32 => {
                    let row = base as *mut u32;
                    for x in 0..fb.width as usize {
                        ptr::write_volatile(row.add(x), color);
                    }
                },
                16 => {
                    let row = base as *mut u16;
                    for x in 0..fb.width as usize {
                        ptr::write_volatile(row.add(x), rgb565(color));
                    }
                },
                _ => {
                    for x in 0..fb.width as usize * (fb.bpp as usize / 8) {
                        ptr::write_volatile((base + x) as *mut u8, 0);
                    }
                },
            }
        }
    }
}

/// Copy a whole `sw`×`sh` frame of 0xRRGGBB pixels to the live framebuffer at
/// the safe-area origin, converting to the panel's format (32/24bpp, or the
/// RGB565 the Pi 5 firmware scans out). One pass, row by row — the Ling app
/// runtime's `present()`; far cheaper than per-pixel `set_pixel`.
pub fn blit_rgb(src: &[u32], sw: usize, sh: usize) {
    let Some(fb) = get() else { return };
    let (ox, oy) = unsafe { (OX as usize, OY as usize) };
    let (fw, fh) = (fb.width as usize, fb.height as usize);
    if ox >= fw || oy >= fh || sw == 0 {
        return;
    }
    let w = sw.min(fw - ox);
    let h = sh.min(fh - oy).min(src.len() / sw);
    let pitch = fb.pitch as usize;
    for y in 0..h {
        let row = &src[y * sw..y * sw + w];
        let base = fb.addr as usize + (y + oy) * pitch;
        unsafe {
            match fb.bpp {
                32 => {
                    let dst = (base + ox * 4) as *mut u32;
                    ptr::copy_nonoverlapping(row.as_ptr(), dst, w);
                },
                16 => {
                    // Two pixels per 32-bit store: the Pi's screen is write-combined
                    // memory, so wide plain stores go several times faster than
                    // one volatile 16-bit write per pixel.
                    let dst = (base + ox * 2) as *mut u16;
                    let mut x = 0usize;
                    if (dst as usize) & 3 == 0 {
                        let d32 = dst as *mut u32;
                        while x + 1 < w {
                            let v = rgb565(row[x]) as u32 | ((rgb565(row[x + 1]) as u32) << 16);
                            ptr::write(d32.add(x / 2), v);
                            x += 2;
                        }
                    }
                    while x < w {
                        ptr::write(dst.add(x), rgb565(row[x]));
                        x += 1;
                    }
                },
                24 => {
                    let mut p = (base + ox * 3) as *mut u8;
                    for &c in row {
                        ptr::write_volatile(p, (c & 0xFF) as u8);
                        ptr::write_volatile(p.add(1), ((c >> 8) & 0xFF) as u8);
                        ptr::write_volatile(p.add(2), ((c >> 16) & 0xFF) as u8);
                        p = p.add(3);
                    }
                },
                _ => {},
            }
        }
    }
}

/// Pack a 0xRRGGBB color into 16-bit RGB565, for the 16bpp framebuffer the
/// Pi 5 firmware sometimes hands back regardless of the requested depth.
#[inline]
fn rgb565(color: u32) -> u16 {
    let r = (color >> 16) & 0xFF;
    let g = (color >> 8) & 0xFF;
    let b = color & 0xFF;
    ((((r >> 3) << 11) | ((g >> 2) << 5) | (b >> 3)) & 0xFFFF) as u16
}

pub fn clear(color: u32) {
    let (w, h) = (width(), height());
    fill_rect(0, 0, w, h, color);
}

/// Is the display a bochs-DISPI adapter we can resize live? (QEMU std VGA /
/// VirtualBox VBoxVGA.) Settings uses this to label the Display row.
pub fn dispi_capable() -> bool {
    #[cfg(target_arch = "x86_64")]
    {
        dispi::present()
    }
    #[cfg(not(target_arch = "x86_64"))]
    {
        false
    }
}

/// The adapter's maximum supported (width, height), queried from the card via
/// DISPI GETCAPS -- `None` if not a DISPI adapter. Settings uses this to list
/// only the modes the card can actually do.
pub fn dispi_max() -> Option<(u32, u32)> {
    #[cfg(target_arch = "x86_64")]
    {
        dispi::max_caps()
    }
    #[cfg(not(target_arch = "x86_64"))]
    {
        None
    }
}

/// How many 32bpp pixels the back buffer can hold -- the other ceiling on a
/// live mode (a mode must fit both the card's caps and this buffer).
pub fn max_pixels() -> usize {
    BACKBUFFER_MAX / 4
}

// -- Clip rectangle (scissor) ------------------------------------------------
// Every primitive that funnels through back_set_pixel / back_fill_rect /
// back_blend_pixel respects this. A window sets it to its body rect while
// drawing content, so text/widgets can't spill past the window edges (or over
// neighbouring windows). `None` = draw to the whole screen.
static mut CLIP: Option<(u32, u32, u32, u32)> = None;

pub fn set_clip(x: u32, y: u32, w: u32, h: u32) {
    unsafe { CLIP = Some((x, y, w, h)) };
}
pub fn clear_clip() {
    unsafe { CLIP = None };
}
#[inline]
fn clip_ok(x: u32, y: u32) -> bool {
    match unsafe { CLIP } {
        Some((cx, cy, cw, ch)) => x >= cx && y >= cy && x < cx + cw && y < cy + ch,
        None => true,
    }
}
/// Intersect a rect with the active clip; None if fully clipped out.
#[inline]
fn clip_rect(x: u32, y: u32, w: u32, h: u32) -> Option<(u32, u32, u32, u32)> {
    match unsafe { CLIP } {
        None => Some((x, y, w, h)),
        Some((cx, cy, cw, ch)) => {
            let x0 = x.max(cx);
            let y0 = y.max(cy);
            let x1 = (x + w).min(cx + cw);
            let y1 = (y + h).min(cy + ch);
            if x1 <= x0 || y1 <= y0 {
                None
            } else {
                Some((x0, y0, x1 - x0, y1 - y0))
            }
        }
    }
}

pub fn back_set_pixel(x: u32, y: u32, color: u32) {
    let Some(fb) = get() else { return };
    if x >= fb.width || y >= fb.height {
        return;
    }
    if !clip_ok(x, y) {
        return;
    }
    let bypp = fb.bpp as u32 / 8;
    let offset = y as usize * fb.pitch as usize + x as usize * bypp as usize;
    if offset + bypp as usize > BACKBUFFER_MAX {
        return;
    }
    let buf = back_buf();
    match bypp {
        4 => buf[offset..offset + 4].copy_from_slice(&color.to_le_bytes()),
        3 => {
            buf[offset] = (color & 0xFF) as u8;
            buf[offset + 1] = ((color >> 8) & 0xFF) as u8;
            buf[offset + 2] = ((color >> 16) & 0xFF) as u8;
        },
        _ => {},
    }
}

pub fn back_fill_rect(x: u32, y: u32, w: u32, h: u32, color: u32) {
    let Some(fb) = get() else { return };
    if x >= fb.width || y >= fb.height {
        return;
    }
    let w = w.min(fb.width - x);
    let h = h.min(fb.height - y);
    if w == 0 || h == 0 {
        return;
    }
    let (x, y, w, h) = match clip_rect(x, y, w, h) {
        Some(r) => r,
        None => return,
    };
    let bypp = fb.bpp as u32 / 8;
    let buf = back_buf();
    if bypp == 4 {
        let col_bytes = color.to_le_bytes();
        for row in y..y + h {
            let row_offset = row as usize * fb.pitch as usize + x as usize * 4;
            if row_offset + (w as usize) * 4 <= BACKBUFFER_MAX {
                for col in 0..w as usize {
                    let off = row_offset + col * 4;
                    buf[off..off + 4].copy_from_slice(&col_bytes);
                }
            }
        }
    } else if bypp == 3 {
        let b0 = (color & 0xFF) as u8;
        let b1 = ((color >> 8) & 0xFF) as u8;
        let b2 = ((color >> 16) & 0xFF) as u8;
        for row in y..y + h {
            let row_offset = row as usize * fb.pitch as usize + x as usize * 3;
            if row_offset + (w as usize) * 3 <= BACKBUFFER_MAX {
                for col in 0..w as usize {
                    let off = row_offset + col * 3;
                    buf[off] = b0;
                    buf[off + 1] = b1;
                    buf[off + 2] = b2;
                }
            }
        }
    }
}

pub fn back_clear(color: u32) {
    let (w, h) = (width(), height());
    back_fill_rect(0, 0, w, h, color);
}

/// Blit one full row of 0xRRGGBB pixels at line `y` -- the wallpaper
/// cache's fast path (per-pixel `back_set_pixel` over a whole frame is
/// visibly slow under TCG).
pub fn back_blit_row(y: u32, colors: &[u32]) {
    let Some(fb) = get() else { return };
    if y >= fb.height {
        return;
    }
    let w = (colors.len() as u32).min(fb.width) as usize;
    let bypp = fb.bpp as usize / 8;
    let row_off = y as usize * fb.pitch as usize;
    if row_off + w * bypp > BACKBUFFER_MAX || (bypp != 3 && bypp != 4) {
        return;
    }
    let buf = back_buf();
    if bypp == 4 {
        for (x, &c) in colors[..w].iter().enumerate() {
            buf[row_off + x * 4..row_off + x * 4 + 4].copy_from_slice(&c.to_le_bytes());
        }
    } else {
        for (x, &c) in colors[..w].iter().enumerate() {
            let o = row_off + x * 3;
            buf[o] = (c & 0xFF) as u8;
            buf[o + 1] = ((c >> 8) & 0xFF) as u8;
            buf[o + 2] = ((c >> 16) & 0xFF) as u8;
        }
    }
}

/// Integer square root (largest `r` with `r*r <= v`) -- the corner-arc code
/// below needs sqrt but this crate is no_std with no libm (same constraint
/// `wm_liquid::scale_w_pct` documents), and Newton over u32 converges in a
/// handful of iterations at these magnitudes (radii are tens of pixels).
fn isqrt(v: u32) -> u32 {
    if v < 2 {
        return v;
    }
    let mut x = v;
    let mut y = (x + 1) / 2;
    while y < x {
        x = y;
        y = (x + v / x) / 2;
    }
    x
}

/// Horizontal inset of a rounded rect's row `dy` (0-based from the top)
/// for corner radius `r` in a rect of height `h`: 0 for the straight middle
/// rows, quarter-circle-shaped for the top/bottom `r` rows. Shared by the
/// fill and blend variants so the two can never disagree on shape.
pub fn rounded_row_inset(dy: u32, h: u32, r: u32) -> u32 {
    let r = r.min(h / 2);
    if r == 0 {
        return 0;
    }
    let d = if dy < r {
        r - dy
    } else if dy >= h - r {
        dy - (h - 1 - r)
    } else {
        return 0;
    };
    if d > r {
        return r;
    }
    r - isqrt(r * r - d * d)
}

/// Filled rounded rectangle into the back buffer: plain row spans with
/// quarter-circle insets on the top/bottom `radius` rows. No antialiasing
/// -- honest hard-edged spans, same as every other primitive here.
pub fn back_fill_rounded_rect(x: u32, y: u32, w: u32, h: u32, radius: u32, color: u32) {
    if h == 0 || w == 0 {
        return;
    }
    for dy in 0..h {
        let inset = rounded_row_inset(dy, h, radius).min(w / 2);
        back_fill_rect(x + inset, y + dy, w - inset * 2, 1, color);
    }
}

/// Filled circle into the back buffer -- row spans via `isqrt`, the same
/// arc math as `rounded_row_inset` without the straight middle section.
pub fn back_fill_circle(cx: u32, cy: u32, radius: u32, color: u32) {
    if radius == 0 {
        return;
    }
    let r = radius as i64;
    for dy in -r..=r {
        let e = isqrt((r * r - dy * dy) as u32) as i64;
        let row_y = cy as i64 + dy;
        let row_x = cx as i64 - e;
        if row_y < 0 || row_x + 2 * e < 0 {
            continue;
        }
        back_fill_rect(row_x.max(0) as u32, row_y as u32, (2 * e + 1) as u32, 1, color);
    }
}

/// Alpha-blend one back-buffer pixel: `out = src*a + dst*(255-a)` per
/// channel, `alpha` in 0..=255. Reads the back buffer (not the live
/// framebuffer) so blending composes correctly with everything drawn
/// earlier this frame, before the single `present()` blit.
fn back_blend_pixel(x: u32, y: u32, color: u32, alpha: u32) {
    let Some(fb) = get() else { return };
    if x >= fb.width || y >= fb.height {
        return;
    }
    if !clip_ok(x, y) {
        return;
    }
    let bypp = fb.bpp as u32 / 8;
    let offset = y as usize * fb.pitch as usize + x as usize * bypp as usize;
    if offset + bypp as usize > BACKBUFFER_MAX || (bypp != 3 && bypp != 4) {
        return;
    }
    let buf = back_buf();
    let inv = 255 - alpha;
    for ch in 0..3usize {
        let src = (color >> (ch * 8)) & 0xFF;
        let dst = buf[offset + ch] as u32;
        buf[offset + ch] = ((src * alpha + dst * inv) / 255) as u8;
    }
}

/// Alpha-blended rectangle into the back buffer, `alpha` 0..=255 (255 =
/// opaque, use `back_fill_rect` instead there -- this is per-pixel
/// read-modify-write and costs accordingly). This is what makes real
/// drop shadows and glass effects possible; before it existed every
/// "shadow" would have been a fake solid grey.
pub fn back_blend_rect(x: u32, y: u32, w: u32, h: u32, color: u32, alpha: u32) {
    let alpha = alpha.min(255);
    if alpha == 0 {
        return;
    }
    for dy in 0..h {
        for dx in 0..w {
            back_blend_pixel(x + dx, y + dy, color, alpha);
        }
    }
}

/// Alpha-blended rounded rectangle -- the drop-shadow primitive (a dark
/// blended rounded rect offset a few pixels under a window reads as soft
/// shadow even without a real gaussian falloff).
pub fn back_blend_rounded_rect(x: u32, y: u32, w: u32, h: u32, radius: u32, color: u32, alpha: u32) {
    let alpha = alpha.min(255);
    if alpha == 0 || h == 0 {
        return;
    }
    for dy in 0..h {
        let inset = rounded_row_inset(dy, h, radius).min(w / 2);
        for dx in inset..w - inset {
            back_blend_pixel(x + dx, y + dy, color, alpha);
        }
    }
}

/// Target present rate ("Hz" in Settings). In a VM the guest can't drive the
/// host's real refresh, so this caps how often the desktop copies the back
/// buffer to the screen -- a genuine, honest frame-rate limit (and it saves
/// CPU). 0 = uncapped.
static mut PRESENT_HZ: u32 = 60;
static mut LAST_PRESENT_US: u64 = 0;

pub fn set_present_hz(hz: u32) {
    unsafe { PRESENT_HZ = hz };
}
pub fn present_hz() -> u32 {
    unsafe { PRESENT_HZ }
}

pub fn present() {
    #[cfg(target_arch = "x86_64")]
    unsafe {
        let hz = PRESENT_HZ;
        if hz > 0 {
            let interval = 1_000_000u64 / hz as u64;
            let next = LAST_PRESENT_US.wrapping_add(interval);
            // Yield to other cooperative tasks until this frame's slot; also
            // guard against a backwards/zero clock so we can't spin forever.
            let mut spins = 0u32;
            while crate::arch::timer::now_us() < next && spins < 100_000 {
                crate::proc::sched::yield_now();
                spins += 1;
            }
            LAST_PRESENT_US = crate::arch::timer::now_us();
        }
    }
    unsafe {
        let Some(fb) = get() else { return };
        let total = (fb.pitch as usize * fb.height as usize).min(BACKBUFFER_MAX);
        let dst = fb.addr as *mut u8;
        let buf = back_buf();
        ptr::copy_nonoverlapping(buf.as_ptr(), dst, total);
    }
}

← 返回索引