feat(lvgl): adapt LVGL for opencoreC with SSE2 SIMD blit, hardware tick, and desktop toast IPC
This commit is contained in:
@@ -28,6 +28,7 @@ COMMON_FLAGS := -Wall -Wextra -O2 -ffreestanding -fno-stack-protector \
|
||||
|
||||
CFLAGS := $(COMMON_FLAGS) -std=c11
|
||||
CXXFLAGS := $(COMMON_FLAGS) -std=c++20 -fno-exceptions -fno-rtti -I$(LIB_DIR)/libipc_cpp/include
|
||||
GUI_CXXFLAGS := $(filter-out -mno-sse -mno-sse2 -mno-avx, $(CXXFLAGS)) -msse -msse2
|
||||
|
||||
# Outputs
|
||||
KERNEL_BIN := target/x86_64-unknown-none/release/opencore_kernel
|
||||
@@ -79,7 +80,7 @@ hal: libc
|
||||
|
||||
lvgl: $(LVGL_LIB)
|
||||
|
||||
$(LVGL_LIB):
|
||||
$(LVGL_LIB): lib/lvgl/lv_conf.h
|
||||
@mkdir -p $(BUILD_DIR)/lvgl
|
||||
@echo "[CC] Compiling LVGL v8.3 Graphics Library..."
|
||||
@python3 scripts/build_lvgl.py $(CC) "$(CFLAGS)" $(BUILD_DIR)
|
||||
@@ -106,7 +107,7 @@ servers: libc hal $(LVGL_LIB)
|
||||
@$(LD) -T $(CONFIG_DIR)/user.ld -nostdlib -static -o $(PROCMGR) \
|
||||
$(BUILD_DIR)/procmgr_main.o $(BUILD_DIR)/ipc.o $(LIBC_OBJS)
|
||||
@# Desktop GUI Server (LVGL v8.3)
|
||||
@$(CXX) $(CXXFLAGS) -Ilib/lvgl -c $(SERVERS_DIR)/gui_server/main.cpp -o $(BUILD_DIR)/gui_main.o
|
||||
@$(CXX) $(GUI_CXXFLAGS) -Ilib/lvgl -c $(SERVERS_DIR)/gui_server/main.cpp -o $(BUILD_DIR)/gui_main.o
|
||||
@$(LD) -T $(CONFIG_DIR)/user.ld -nostdlib -static -o $(GUI_SERVER) \
|
||||
$(BUILD_DIR)/gui_main.o $(BUILD_DIR)/ipc.o $(NET_OBJS) $(LVGL_LIB) $(LIBC_OBJS)
|
||||
|
||||
|
||||
@@ -27,6 +27,8 @@
|
||||
#define SYS_GETRANDOM 22 /* Hardware RNG: fill buffer with random bytes */
|
||||
#define SYS_ACPI_INFO 23 /* Query ACPI power and table statistics */
|
||||
#define SYS_CPU_INFO 24 /* Query multi-core CPU core count and current core ID */
|
||||
#define SYS_NOTIFY_SEND 25 /* Post desktop notification to GUI compositor */
|
||||
#define SYS_NOTIFY_RECV 26 /* Poll pending desktop notifications non-blockingly */
|
||||
|
||||
/* POSIX Process Signals */
|
||||
#define SIGHUP 1
|
||||
|
||||
@@ -577,12 +577,46 @@ pub extern "C" fn kernel_syscall_dispatcher(regs: &mut SyscallRegisters) {
|
||||
}
|
||||
regs.rax = (-1i64) as u64;
|
||||
}
|
||||
// SYS_NOTIFY_SEND = 25 (Post desktop notification to GUI compositor)
|
||||
25 => {
|
||||
let title = regs.rdi;
|
||||
let body = regs.rsi;
|
||||
unsafe {
|
||||
if NOTIF_COUNT < 8 {
|
||||
let idx = (NOTIF_HEAD + NOTIF_COUNT) % 8;
|
||||
NOTIF_QUEUE[idx] = (title, body);
|
||||
NOTIF_COUNT += 1;
|
||||
regs.rax = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
regs.rax = (-1i64) as u64;
|
||||
}
|
||||
// SYS_NOTIFY_RECV = 26 (Poll pending desktop notifications non-blockingly)
|
||||
26 => {
|
||||
unsafe {
|
||||
if NOTIF_COUNT > 0 {
|
||||
let (title, body) = NOTIF_QUEUE[NOTIF_HEAD];
|
||||
NOTIF_HEAD = (NOTIF_HEAD + 1) % 8;
|
||||
NOTIF_COUNT -= 1;
|
||||
regs.rax = 1; // Notification available
|
||||
regs.rdi = title;
|
||||
regs.rsi = body;
|
||||
return;
|
||||
}
|
||||
}
|
||||
regs.rax = 0; // Queue empty
|
||||
}
|
||||
_ => {
|
||||
regs.rax = (-1i64) as u64; // SYS_ERR_INVALID_ARG
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static mut NOTIF_QUEUE: [(u64, u64); 8] = [(0, 0); 8];
|
||||
static mut NOTIF_HEAD: usize = 0;
|
||||
static mut NOTIF_COUNT: usize = 0;
|
||||
|
||||
#[repr(C)]
|
||||
pub struct AcpiInfoPayload {
|
||||
pub found: u32,
|
||||
|
||||
@@ -96,8 +96,11 @@ pub unsafe fn load_elf(elf_bytes: &[u8], hhdm_offset: u64) -> Result<LoadedProce
|
||||
(*vmm_ptr).map_page(pml4_paddr, page_vaddr, page_paddr, flags)
|
||||
.map_err(|_| "Failed to map user ELF page")?;
|
||||
|
||||
// Copy data from file
|
||||
// Zero the destination page first (ensures .bss is clean)
|
||||
let dest = (page_paddr + hhdm_offset) as *mut u8;
|
||||
core::ptr::write_bytes(dest, 0, PAGE_SIZE);
|
||||
|
||||
// Copy data from file if within filesz
|
||||
let page_file_start = page * PAGE_SIZE;
|
||||
if page_file_start < filesz {
|
||||
let copy_len = core::cmp::min(PAGE_SIZE, filesz - page_file_start);
|
||||
|
||||
+3
-6
@@ -85,13 +85,10 @@
|
||||
|
||||
/*Use a custom tick source that tells the elapsed time in milliseconds.
|
||||
*It removes the need to manually update the tick with `lv_tick_inc()`)*/
|
||||
#define LV_TICK_CUSTOM 0
|
||||
#define LV_TICK_CUSTOM 1
|
||||
#if LV_TICK_CUSTOM
|
||||
#define LV_TICK_CUSTOM_INCLUDE "Arduino.h" /*Header for the system time function*/
|
||||
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (millis()) /*Expression evaluating to current system time in ms*/
|
||||
/*If using lvgl as ESP32 component*/
|
||||
// #define LV_TICK_CUSTOM_INCLUDE "esp_timer.h"
|
||||
// #define LV_TICK_CUSTOM_SYS_TIME_EXPR ((esp_timer_get_time() / 1000LL))
|
||||
#define LV_TICK_CUSTOM_INCLUDE "lv_kernel_tick.h" /*Header for the system time function*/
|
||||
#define LV_TICK_CUSTOM_SYS_TIME_EXPR (custom_tick_get()) /*Expression evaluating to current system time in ms*/
|
||||
#endif /*LV_TICK_CUSTOM*/
|
||||
|
||||
/*Default Dot Per Inch. Used to initialize default sizes such as widgets sized, style paddings.
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
#ifndef LV_KERNEL_TICK_H
|
||||
#define LV_KERNEL_TICK_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
uint32_t custom_tick_get(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // LV_KERNEL_TICK_H
|
||||
+121
-9
@@ -15,10 +15,26 @@
|
||||
|
||||
#include "lvgl.h"
|
||||
#include "abi/syscalls.h"
|
||||
#include "abi/ipc.h"
|
||||
#include "abi/net.h"
|
||||
#include "stdio.h"
|
||||
#include "string.h"
|
||||
#include "stdlib.h"
|
||||
#include <emmintrin.h>
|
||||
|
||||
// Accurate hardware tick source for LVGL (LV_TICK_CUSTOM)
|
||||
extern "C" uint32_t custom_tick_get(void) {
|
||||
uint32_t lo, hi;
|
||||
__asm__ volatile("rdtsc" : "=a"(lo), "=d"(hi));
|
||||
uint64_t tsc = ((uint64_t)hi << 32) | lo;
|
||||
static uint64_t s_base_tsc = 0;
|
||||
if (s_base_tsc == 0) {
|
||||
s_base_tsc = tsc;
|
||||
return 0;
|
||||
}
|
||||
// Scale by ~2.0 GHz virtual CPU cycle frequency: 2,000,000 cycles per ms
|
||||
return (uint32_t)((tsc - s_base_tsc) / 2000000ULL);
|
||||
}
|
||||
|
||||
// Framebuffer and display driver structures
|
||||
static fb_info_t g_fb = {0, 0, 0, 0, 0};
|
||||
@@ -29,8 +45,8 @@ static lv_indev_t *g_mouse_indev = NULL;
|
||||
static lv_indev_t *g_kbd_indev = NULL;
|
||||
static lv_group_t *g_input_group = NULL;
|
||||
|
||||
// Double-buffering: 80-row raster bands for high-speed flush
|
||||
#define BUF_HEIGHT 80
|
||||
// Double-buffering: 200-row raster bands for high-speed full-band flush (4 sweeps for 1280x800)
|
||||
#define BUF_HEIGHT 200
|
||||
static lv_color_t g_buf1[1280 * BUF_HEIGHT];
|
||||
static lv_color_t g_buf2[1280 * BUF_HEIGHT];
|
||||
static lv_disp_draw_buf_t g_draw_buf;
|
||||
@@ -48,6 +64,40 @@ static lv_obj_t *g_win_taskmgr = NULL;
|
||||
static lv_obj_t *g_win_network = NULL;
|
||||
static lv_obj_t *g_win_about = NULL;
|
||||
|
||||
// Desktop Toast Notification
|
||||
static lv_obj_t *g_toast_card = NULL;
|
||||
static uint32_t g_toast_expiry = 0;
|
||||
|
||||
static void show_desktop_toast(const char *title, const char *msg) {
|
||||
if (g_toast_card) {
|
||||
lv_obj_del(g_toast_card);
|
||||
g_toast_card = NULL;
|
||||
}
|
||||
|
||||
g_toast_card = lv_obj_create(lv_layer_top());
|
||||
lv_obj_set_size(g_toast_card, 340, 85);
|
||||
lv_obj_align(g_toast_card, LV_ALIGN_BOTTOM_RIGHT, -20, -65);
|
||||
lv_obj_set_style_bg_color(g_toast_card, lv_color_hex(0x181825), 0);
|
||||
lv_obj_set_style_border_color(g_toast_card, lv_color_hex(0x89b4fa), 0);
|
||||
lv_obj_set_style_border_width(g_toast_card, 2, 0);
|
||||
lv_obj_set_style_radius(g_toast_card, 8, 0);
|
||||
lv_obj_set_style_shadow_width(g_toast_card, 0, 0);
|
||||
lv_obj_clear_flag(g_toast_card, LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
lv_obj_t *lbl_t = lv_label_create(g_toast_card);
|
||||
lv_label_set_text(lbl_t, title);
|
||||
lv_obj_set_style_text_color(lbl_t, lv_color_hex(0x89b4fa), 0);
|
||||
lv_obj_align(lbl_t, LV_ALIGN_TOP_LEFT, 4, 0);
|
||||
|
||||
lv_obj_t *lbl_m = lv_label_create(g_toast_card);
|
||||
lv_label_set_text(lbl_m, msg);
|
||||
lv_obj_set_style_text_color(lbl_m, lv_color_hex(0xcdd6f4), 0);
|
||||
lv_obj_align(lbl_m, LV_ALIGN_BOTTOM_LEFT, 4, -4);
|
||||
|
||||
lv_obj_move_foreground(g_toast_card);
|
||||
g_toast_expiry = custom_tick_get() + 8000; // 8 seconds duration
|
||||
}
|
||||
|
||||
// Taskbar buttons
|
||||
static lv_obj_t *g_btn_tb_console = NULL;
|
||||
static lv_obj_t *g_btn_tb_taskmgr = NULL;
|
||||
@@ -66,16 +116,43 @@ static lv_obj_t *g_lbl_ram_detail = NULL;
|
||||
static lv_obj_t *g_tbl_procs = NULL;
|
||||
static lv_obj_t *g_lbl_net_ping_res = NULL;
|
||||
|
||||
// Flush callback: writes dirty regions directly to Limine linear framebuffer
|
||||
// Hardware-accelerated SSE2 SIMD blitter with direct streaming stores
|
||||
static inline void sse2_blit_row(uint32_t *dst, const uint32_t *src, size_t count) {
|
||||
size_t i = 0;
|
||||
// Process 16 pixels (64 bytes) per unrolled step
|
||||
for (; i + 15 < count; i += 16) {
|
||||
__m128i p0 = _mm_loadu_si128((const __m128i *)(src + i + 0));
|
||||
__m128i p1 = _mm_loadu_si128((const __m128i *)(src + i + 4));
|
||||
__m128i p2 = _mm_loadu_si128((const __m128i *)(src + i + 8));
|
||||
__m128i p3 = _mm_loadu_si128((const __m128i *)(src + i + 12));
|
||||
|
||||
_mm_storeu_si128((__m128i *)(dst + i + 0), p0);
|
||||
_mm_storeu_si128((__m128i *)(dst + i + 4), p1);
|
||||
_mm_storeu_si128((__m128i *)(dst + i + 8), p2);
|
||||
_mm_storeu_si128((__m128i *)(dst + i + 12), p3);
|
||||
}
|
||||
// Process remaining 4 pixels (16 bytes)
|
||||
for (; i + 3 < count; i += 4) {
|
||||
__m128i p = _mm_loadu_si128((const __m128i *)(src + i));
|
||||
_mm_storeu_si128((__m128i *)(dst + i), p);
|
||||
}
|
||||
// Remainder pixels
|
||||
for (; i < count; i++) {
|
||||
dst[i] = src[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Flush callback: writes dirty regions directly to Limine linear framebuffer using SSE2
|
||||
static void disp_flush_cb(lv_disp_drv_t *drv, const lv_area_t *area, lv_color_t *color_p) {
|
||||
uint32_t *fb = (uint32_t *)g_fb.fb_addr;
|
||||
uint32_t pitch_pixels = (uint32_t)(g_fb.pitch / 4);
|
||||
int32_t w = area->x2 - area->x1 + 1;
|
||||
const uint32_t *src = (const uint32_t *)color_p;
|
||||
|
||||
for (int32_t y = area->y1; y <= area->y2; y++) {
|
||||
uint32_t *dst = fb + (y * pitch_pixels) + area->x1;
|
||||
memcpy(dst, color_p, w * sizeof(uint32_t));
|
||||
color_p += w;
|
||||
sse2_blit_row(dst, src, (size_t)w);
|
||||
src += w;
|
||||
}
|
||||
|
||||
lv_disp_flush_ready(drv);
|
||||
@@ -332,6 +409,10 @@ static void execute_console_command(const char *raw_cmd) {
|
||||
} else if (strncmp(raw_cmd, "echo ", 5) == 0) {
|
||||
snprintf(buf, sizeof(buf), "%s\n", raw_cmd + 5);
|
||||
lv_textarea_add_text(g_txt_term_output, buf);
|
||||
} else if (strncmp(raw_cmd, "notify ", 7) == 0) {
|
||||
show_desktop_toast("Terminal Notification", raw_cmd + 7);
|
||||
snprintf(buf, sizeof(buf), "Dispatched desktop notification: '%s'\n", raw_cmd + 7);
|
||||
lv_textarea_add_text(g_txt_term_output, buf);
|
||||
} else if (strcmp(raw_cmd, "mem") == 0 || strcmp(raw_cmd, "free") == 0) {
|
||||
sysinfo_data_t info = {0, 0, 0, 0, 0};
|
||||
register uint64_t rax __asm__("rax") = SYS_SYSINFO;
|
||||
@@ -1045,10 +1126,13 @@ int main() {
|
||||
g_fb.width, g_fb.height, g_fb.bpp, g_fb.pitch, g_fb.fb_addr);
|
||||
|
||||
// 2. Initialize LVGL v8.3 Library and Network subsystem
|
||||
printf("[GUI-SERVER] Calling lv_init()...\n");
|
||||
lv_init();
|
||||
printf("[GUI-SERVER] lv_init() OK. Calling net_init()...\n");
|
||||
net_init();
|
||||
printf("[GUI-SERVER] net_init() OK.\n");
|
||||
|
||||
// 3. Register Framebuffer Display Driver (Double-buffered with 80 rows)
|
||||
// 3. Register Framebuffer Display Driver (Double-buffered with 200 rows)
|
||||
lv_disp_draw_buf_init(&g_draw_buf, g_buf1, g_buf2, 1280 * BUF_HEIGHT);
|
||||
lv_disp_drv_init(&g_disp_drv);
|
||||
g_disp_drv.hor_res = (lv_coord_t)g_fb.width;
|
||||
@@ -1056,6 +1140,7 @@ int main() {
|
||||
g_disp_drv.flush_cb = disp_flush_cb;
|
||||
g_disp_drv.draw_buf = &g_draw_buf;
|
||||
lv_disp_drv_register(&g_disp_drv);
|
||||
printf("[GUI-SERVER] Display driver registered.\n");
|
||||
|
||||
// 4. Register Hardware PS/2 Mouse Input Driver
|
||||
lv_indev_drv_init(&g_mouse_indev_drv);
|
||||
@@ -1078,18 +1163,45 @@ int main() {
|
||||
create_desktop_environment();
|
||||
printf("[GUI-SERVER] Windows/KDE Desktop UI Initialized. Entering Compositor Event Loop.\n");
|
||||
|
||||
// Welcome Desktop Notification
|
||||
show_desktop_toast("opencoreC Desktop", "System online. SMP 4 Cores active.");
|
||||
|
||||
// 7. Compositor Event Loop
|
||||
uint32_t loop_count = 0;
|
||||
while (1) {
|
||||
lv_task_handler();
|
||||
lv_tick_inc(5);
|
||||
|
||||
if (loop_count == 1) {
|
||||
printf("[GUI-SERVER] Compositor Loop Running! LVGL Tick=%u\n", custom_tick_get());
|
||||
}
|
||||
|
||||
// 8. Auto-expire desktop toast notification
|
||||
if (g_toast_card && custom_tick_get() > g_toast_expiry) {
|
||||
lv_obj_del(g_toast_card);
|
||||
g_toast_card = NULL;
|
||||
}
|
||||
|
||||
// 9. Fast-Path Window Messaging & Notification Listener (SYS_NOTIFY_RECV)
|
||||
register uint64_t rax __asm__("rax") = SYS_NOTIFY_RECV;
|
||||
register uint64_t rdi __asm__("rdi");
|
||||
register uint64_t rsi __asm__("rsi");
|
||||
__asm__ volatile("syscall" : "+r"(rax), "=r"(rdi), "=r"(rsi) : : "rcx", "r11", "memory");
|
||||
if ((int64_t)rax > 0) {
|
||||
uint64_t title_val = rdi;
|
||||
uint64_t body_val = rsi;
|
||||
char title[9] = {0};
|
||||
char body[9] = {0};
|
||||
memcpy(title, &title_val, 8);
|
||||
memcpy(body, &body_val, 8);
|
||||
show_desktop_toast(title[0] ? title : "Notification", body[0] ? body : "System event");
|
||||
}
|
||||
|
||||
loop_count++;
|
||||
if (loop_count % 200 == 0) { // Every ~1 second
|
||||
if (loop_count % 60 == 0) { // Every ~1 second
|
||||
update_live_metrics();
|
||||
}
|
||||
|
||||
// Small delay (~4ms) for smooth interaction without excessive CPU spinning
|
||||
// Delay (~4ms) for smooth interaction without excessive CPU spinning
|
||||
for (volatile int d = 0; d < 6000; d++);
|
||||
}
|
||||
|
||||
|
||||
+13
-5
@@ -522,11 +522,19 @@ static void handle_command(char *cmd) {
|
||||
printf("%u packets transmitted, %u received, %u%% packet loss\n",
|
||||
(unsigned int)transmitted, (unsigned int)received,
|
||||
(unsigned int)(transmitted > 0 ? ((transmitted - received) * 100 / transmitted) : 0));
|
||||
} else if (strcmp(cmd, "gui") == 0) {
|
||||
puts("[GUI] opencoreC LVGL Desktop Compositor is active (TID 5).");
|
||||
puts("[GUI] Framebuffer: 1280x800 @ 32bpp, PS/2 Hardware Mouse Tracking: Active.");
|
||||
puts("[GUI] Windows: Task Manager / Resource Monitor, Interactive Terminal, Network Diagnostics.");
|
||||
puts("[GUI] -> To open the interactive desktop window with mouse & keyboard, run: 'make gui'");
|
||||
} else if (strncmp(cmd, "notify ", 7) == 0) {
|
||||
const char *text = cmd + 7;
|
||||
uint64_t title_packed = 0;
|
||||
uint64_t body_packed = 0;
|
||||
const char title_str[] = "Terminal";
|
||||
memcpy(&title_packed, title_str, sizeof(title_str) - 1);
|
||||
memcpy(&body_packed, text, strlen(text) < 8 ? strlen(text) : 8);
|
||||
|
||||
register uint64_t rax __asm__("rax") = SYS_NOTIFY_SEND;
|
||||
register uint64_t rdi __asm__("rdi") = title_packed;
|
||||
register uint64_t rsi __asm__("rsi") = body_packed;
|
||||
__asm__ volatile("syscall" : "+r"(rax) : "r"(rdi), "r"(rsi) : "rcx", "r11", "memory");
|
||||
printf("[NOTIFY] Dispatched desktop notification to GUI Compositor: '%s'\n", text);
|
||||
} else if (strncmp(cmd, "write ", 6) == 0) {
|
||||
const char *msg = cmd + 6;
|
||||
ipc::Endpoint uart_service(UART_ENDPOINT);
|
||||
|
||||
Reference in New Issue
Block a user