std1.0.0-beta-7 SafeC docs

Source gui/gui_fb.sc

View in the repository

// SafeC Standard Library — GUI backend: bare metal (a linear framebuffer,
// as set up by a bootloader — e.g. Multiboot2's framebuffer tag, a VESA
// VBE mode, or a board's fixed display-controller memory map). See gui.h
// for the portable API this implements.
//
// Unlike the OS-native backends (Cocoa/Win32/X11), there is no windowing
// system here — 'GuiWindow::create()' doesn't open anything, it just
// validates that gui_fb_configure() was called first; the portable layer
// allocates the same CPU-side RGBA8888 draw buffer every backend uses, and
// GuiWindow::present() copies (and pixel-format-converts) that buffer into
// the real framebuffer memory at the window's position (set_position(),
// default 0,0 — several windows may tile one screen). There is also no
// OS input stack — a bare-metal target's own keyboard/mouse/touch ISR is responsible for calling
// gui_fb_inject_event() with whatever it decodes; GuiWindow::poll_event() just
// drains the small queue that fills. Wire up your own PS/2/USB-HID/etc.
// driver to call gui_fb_inject_event() — this file has no opinion on
// which one you use.
//
// Call gui_fb_configure() exactly once, before GuiWindow::create(), with
// the real framebuffer's base address/pitch/pixel format — typically
// values your bootloader handed you (e.g. a Multiboot2 framebuffer tag's
// framebuffer_addr/framebuffer_pitch/framebuffer_bpp fields).
// safeguard: implements <std/gui/gui.h>   (the bare-metal backend; gui_fb.h is its setup)
#pragma once
#include <std/stddef.h>
#include <std/gui/gui.h>
#include <std/gui/gui_backend.h>
#include <std/gui/gui_fb.h>
#include <std/mem.h>

namespace std {

static struct GuiFbConfig gFbConfig;
static int gFbConfigured = 0;
static struct GuiBackend gFbBackend;
static int gFbBackendInit = 0;
static double gFbVirtualMs = 0.0;
static fn long long() gFbClock;
static int gFbClockSet = 0;

void gui_fb_configure(struct GuiFbConfig cfg) {
    gFbConfig = cfg;
    gFbConfigured = 1;
}

void gui_fb_set_clock(fn long long() nowNs) {
    gFbClock = nowNs;
    gFbClockSet = 1;
}

// The ring an input ISR fills (no allocation on that path); pump() moves
// it into the portable queue.
struct GuiFbPlatform {
    struct GuiEvent queue[GUI_FB_QUEUE_CAP];
    int queueHead;
    int queueTail;
};

// A target's own input driver/ISR calls this with whatever it decodes
// (e.g. a PS/2 scancode translated to a GuiEvent) — GuiWindow::poll_event() then
// hands it back out through the normal API, same as every other backend.
void gui_fb_inject_event(&GuiWindow win, struct GuiEvent ev) {
    ?&GuiWindowState ost = gui_window_state(win);
    match (ost) {
        case null: return;
        case some(st): {
            unsafe {
                if (st.native.is_null()) { return; }
                struct GuiFbPlatform* p = (struct GuiFbPlatform*)st.native;
                int next = (p->queueTail + 1) % GUI_FB_QUEUE_CAP;
                if (next == p->queueHead) { return; } // full — drop
                p->queue[p->queueTail] = ev;
                p->queueTail = next;
            }
        }
    }
}

static int __gui_fb_init() { return gFbConfigured; }

static int __gui_fb_create(&GuiWindowState st, const &GuiWindowOptions opts) {
    if (!gFbConfigured) { return 0; }
    unsafe {
        struct GuiFbPlatform* p = (struct GuiFbPlatform*)alloc_zeroed_raw_(sizeof(struct GuiFbPlatform));
        if (p == (struct GuiFbPlatform*)0) { return 0; }
        st.native = (?&Erased)p;
        st.key = (unsigned long long)(unsigned long)p;
    }
    st.scale = 1.0;
    st.pixelW = st.logicalW;
    st.pixelH = st.logicalH;
    if (st.x == GUI_POS_DEFAULT) { st.x = 0; }
    if (st.y == GUI_POS_DEFAULT) { st.y = 0; }
    st.focused = 1;
    return 1;
}

static void __gui_fb_destroy(&GuiWindowState st) {
    unsafe {
        if (!st.native.is_null()) { dealloc((?&heap Erased)st.native); }
    }
    st.native = null;
}

// The portable layer draws RGBA8888 into the window's buffer; this
// backend's job is turning that into whatever the real hardware
// framebuffer's pixel format is when presenting.
static void __gui_fb_present(&GuiWindowState st, const &unsigned char px, int bw, int bh,
                             int rx, int ry, int rw, int rh) {
    if (!gFbConfigured) { return; }
    unsafe {
        unsigned char* fb = (unsigned char*)gFbConfig.base;
        const unsigned char* pixels = (const unsigned char*)px;
        int y = ry;
        while (y < ry + rh) {
            int sy = st.y + y;
            if (sy >= 0 && sy < gFbConfig.height) {
                unsigned long srcRow = (unsigned long)y * (unsigned long)bw * 4UL;
                unsigned long dstRow = (unsigned long)sy * gFbConfig.pitch;
                int x = rx;
                while (x < rx + rw) {
                    int sx = st.x + x;
                    if (sx >= 0 && sx < gFbConfig.width) {
                        unsigned long si = srcRow + (unsigned long)x * 4UL;
                        unsigned char r = pixels[si + 0UL];
                        unsigned char g = pixels[si + 1UL];
                        unsigned char b = pixels[si + 2UL];
                        if (gFbConfig.bpp == 32) {
                            unsigned int packedColor = ((unsigned int)r << gFbConfig.redShift) |
                                                   ((unsigned int)g << gFbConfig.greenShift) |
                                                   ((unsigned int)b << gFbConfig.blueShift);
                            unsigned long di = dstRow + (unsigned long)sx * 4UL;
                            fb[di + 0UL] = (unsigned char)(packedColor & 0xFFU);
                            fb[di + 1UL] = (unsigned char)((packedColor >> 8) & 0xFFU);
                            fb[di + 2UL] = (unsigned char)((packedColor >> 16) & 0xFFU);
                            fb[di + 3UL] = (unsigned char)((packedColor >> 24) & 0xFFU);
                        } else if (gFbConfig.bpp == 16) {
                            // RGB565.
                            unsigned int r5 = ((unsigned int)r >> 3) & 0x1FU;
                            unsigned int g6 = ((unsigned int)g >> 2) & 0x3FU;
                            unsigned int b5 = ((unsigned int)b >> 3) & 0x1FU;
                            unsigned int packed16 = (r5 << 11) | (g6 << 5) | b5;
                            unsigned long di = dstRow + (unsigned long)sx * 2UL;
                            fb[di + 0UL] = (unsigned char)(packed16 & 0xFFU);
                            fb[di + 1UL] = (unsigned char)((packed16 >> 8) & 0xFFU);
                        }
                    }
                    x = x + 1;
                }
            }
            y = y + 1;
        }
    }
}

static void __gui_fb_set_title(&GuiWindowState st, const &char title) { }  // no window chrome on bare metal

static void __gui_fb_set_size(&GuiWindowState st, int w, int h) {
    st.logicalW = w; st.logicalH = h;
    st.pixelW = w; st.pixelH = h;
    struct GuiEvent ev = GuiEvent::new(GUI_EVENT_RESIZE);
    ev.width = w;
    ev.height = h;
    gui_backend_post(st, ev);
}

static void __gui_fb_set_position(&GuiWindowState st, int x, int y) {
    st.x = x;
    st.y = y;
    struct GuiEvent ev = GuiEvent::new(GUI_EVENT_MOVE);
    ev.x = (double)x;
    ev.y = (double)y;
    gui_backend_post(st, ev);
}

static void __gui_fb_noop(&GuiWindowState st) { }
static void __gui_fb_set_int(&GuiWindowState st, int v) { }

static int __gui_fb_pump(int timeoutMs) {
    int any = 0;
    unsigned long n = gui_backend_window_count();
    unsigned long i = 0UL;
    while (i < n) {
        ?&GuiWindowState ost = gui_backend_window_at(i);
        match (ost) {
            case some(st): {
                unsafe {
                    if (!st.native.is_null()) {
                        struct GuiFbPlatform* p = (struct GuiFbPlatform*)st.native;
                        while (p->queueHead != p->queueTail) {
                            struct GuiEvent ev = p->queue[p->queueHead];
                            p->queueHead = (p->queueHead + 1) % GUI_FB_QUEUE_CAP;
                            gui_backend_post(st, ev);
                            any = 1;
                        }
                    }
                }
            }
            case null: { }
        }
        i = i + 1UL;
    }
    // No clock of its own to sleep on: a wait just lets the virtual
    // clock move (see gui_fb_set_clock for a real one).
    if (!any && timeoutMs > 0 && !gFbClockSet) { gFbVirtualMs = gFbVirtualMs + (double)timeoutMs; }
    return any;
}

static long long __gui_fb_now_ns() {
    if (gFbClockSet) { return gFbClock(); }
    return (long long)(gFbVirtualMs * 1000000.0);
}

static int __gui_fb_clip_set(const &char utf8, unsigned long n) { return 0; }
static int __gui_fb_clip_get(&String out) { return 0; }
static double __gui_fb_scale() { return 1.0; }

static struct GuiNativeHandle __gui_fb_native(&GuiWindowState st) {
    struct GuiNativeHandle h;
    h.kind = GUI_NATIVE_FB;
    unsafe { h.display = (unsigned long long)(unsigned long)gFbConfig.base; }
    h.window = st.key;
    h.view = 0ULL;
    h.visual = 0ULL;
    return h;
}

&GuiBackend gui_platform_backend() {
    if (!gFbBackendInit) {
        gFbBackend.name = "fb";
        gFbBackend.primaryMod = GUI_MOD_CTRL;
        gFbBackend.init = __gui_fb_init;
        gFbBackend.window_create = __gui_fb_create;
        gFbBackend.window_destroy = __gui_fb_destroy;
        gFbBackend.window_present = __gui_fb_present;
        gFbBackend.window_set_title = __gui_fb_set_title;
        gFbBackend.window_set_size = __gui_fb_set_size;
        gFbBackend.window_set_position = __gui_fb_set_position;
        gFbBackend.window_update_limits = __gui_fb_noop;
        gFbBackend.window_set_visible = __gui_fb_set_int;
        gFbBackend.window_focus = __gui_fb_noop;
        gFbBackend.window_set_cursor = __gui_fb_set_int;
        gFbBackend.pump = __gui_fb_pump;
        gFbBackend.wake = (fn void())0;
        gFbBackend.now_ns = __gui_fb_now_ns;
        gFbBackend.clipboard_set = __gui_fb_clip_set;
        gFbBackend.clipboard_get = __gui_fb_clip_get;
        gFbBackend.display_scale = __gui_fb_scale;
        gFbBackend.native_handle = __gui_fb_native;
        gFbBackendInit = 1;
    }
    return &gFbBackend;
}

} // namespace std