mirror of
https://github.com/subhobhai943/SUB-OS.git
synced 2026-10-25 16:52:29 +00:00
perf(gui): scene-dirty compositor + cursor overlay + 60 FPS pacer + animations
Rework the compositor so it stops repainting the whole screen every frame, tripling the frame rate (17 -> 54 FPS at HD) and adding smooth animation. - Scene-dirty skipping (gui/gui_desktop.c): the full scene is recomposited only when something other than the bare pointer changed -- input, an open animation, or a ~10 Hz heartbeat for live content (clock, graphs). Idle and pointer-only frames skip the recomposite entirely. - Cursor save-under (gui/gui_cursor.c): the pointer is an overlay that stashes and restores the pixels beneath it, so moving the mouse never forces a full repaint -- pointer motion runs at the frame cap. - 60 FPS frame pacer: each iteration is timed against the monotonic clock and padded to a 16.666 ms budget with the new ktime_delay_us() (HPET-precise busy-wait), giving consistent pacing instead of a free-running spin. - Window open animation (gui/gui_wm.c): new windows grow from 60% to full size with a cubic ease-out over ~160 ms; gui_wm_update() drives it and reports while any window is still animating so the compositor keeps redrawing. - Default display mode set to 1280x720 (HD) -- the sweet spot where the software compositor sustains a near-60 FPS cursor with headroom for effects. Kernel: kernel/time/ktime.c gains ktime_delay_us(), a precise HPET-based delay (bounded PIT fallback) used by the pacer. Verified live (screenshotted): boots to 1280x720, logs in, tray reads ~54 FPS (green) while the pointer moves, no cursor ghosting, windows render cleanly. Clean build on x86_64/aarch64/armv8i; 0 panics / 0 OOM. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01Fh5z6V7nGPbHvqVG5p4CXX
This commit is contained in:
1 parent
4ecf119377
commit
2c2e2d536e
8 files changed
+200
-13
No files matched your search
@@ -81,3 +81,51 @@ void gui_cursor_draw(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Save-under overlay: lets the compositor move the cursor without repainting
|
||||
// the whole scene. The block under the cursor (body + drop shadow) is stashed
|
||||
// before drawing and restored on the next frame.
|
||||
// ---------------------------------------------------------------------------
|
||||
#define CUR_SAVE_W (CURSOR_W + 3)
|
||||
#define CUR_SAVE_H (CURSOR_H + 3)
|
||||
|
||||
static uint32_t g_save[CUR_SAVE_H * CUR_SAVE_W];
|
||||
static int g_save_x = -1, g_save_y = -1, g_save_w = 0, g_save_h = 0;
|
||||
|
||||
// Put back whatever was under the cursor last frame (erasing it).
|
||||
void gui_cursor_restore_under(void) {
|
||||
if (g_save_x < 0) return;
|
||||
uint32_t* bb = gui_gfx_get_backbuffer();
|
||||
if (!bb) { g_save_x = -1; return; }
|
||||
int W = gui_gfx_get_width();
|
||||
for (int j = 0; j < g_save_h; j++) {
|
||||
uint32_t* dst = &bb[(g_save_y + j) * W + g_save_x];
|
||||
const uint32_t* src = &g_save[j * g_save_w];
|
||||
for (int i = 0; i < g_save_w; i++) dst[i] = src[i];
|
||||
}
|
||||
g_save_x = -1;
|
||||
}
|
||||
|
||||
// Stash the block under the current cursor position, then draw the cursor.
|
||||
void gui_cursor_composite(void) {
|
||||
uint32_t* bb = gui_gfx_get_backbuffer();
|
||||
if (!bb) return;
|
||||
int W = gui_gfx_get_width();
|
||||
int H = gui_gfx_get_height();
|
||||
|
||||
int x = g_cur_x, y = g_cur_y;
|
||||
int w = CUR_SAVE_W, h = CUR_SAVE_H;
|
||||
if (x + w > W) w = W - x;
|
||||
if (y + h > H) h = H - y;
|
||||
if (w <= 0 || h <= 0) { g_save_x = -1; gui_cursor_draw(); return; }
|
||||
|
||||
for (int j = 0; j < h; j++) {
|
||||
const uint32_t* srow = &bb[(y + j) * W + x];
|
||||
uint32_t* d = &g_save[j * w];
|
||||
for (int i = 0; i < w; i++) d[i] = srow[i];
|
||||
}
|
||||
g_save_x = x; g_save_y = y; g_save_w = w; g_save_h = h;
|
||||
|
||||
gui_cursor_draw();
|
||||
}
|
||||
+53
-12
@@ -86,6 +86,14 @@ static uint64_t g_frames = 0;
|
||||
static uint32_t g_fps = 0; // frames per second, sampled from ktime
|
||||
static uint64_t g_fps_last_ms = 0;
|
||||
static uint64_t g_fps_last_frames = 0;
|
||||
static uint64_t g_last_scene_ms = 0; // last full scene recomposite (for heartbeat)
|
||||
static int g_prev_mx = -1, g_prev_my = -1;
|
||||
|
||||
// Target frame budget: 60 FPS == 16666 us. The compositor only repaints the
|
||||
// full scene when it is dirty; between times it just moves the cursor overlay,
|
||||
// so idle and pointer-only frames comfortably hit the cap.
|
||||
#define FRAME_BUDGET_US 16666
|
||||
#define SCENE_HEARTBEAT_MS 100 // ~10 Hz refresh for live content (clock, graphs)
|
||||
|
||||
/*
|
||||
* Cached wallpaper.
|
||||
@@ -730,6 +738,8 @@ int gui_desktop_run(void) {
|
||||
printk(KERN_INFO "GUI: Desktop session started (ESC returns to the kernel TTY)\n");
|
||||
|
||||
while (g_desktop_running) {
|
||||
uint64_t frame_start_us = ktime_us();
|
||||
|
||||
// --- 1. Sample input -----------------------------------------------
|
||||
const mouse_state_t* ms = mouse_get_state();
|
||||
int mx = ms ? ms->x : gui_gfx_get_width() / 2;
|
||||
@@ -785,28 +795,59 @@ int gui_desktop_run(void) {
|
||||
}
|
||||
}
|
||||
|
||||
// --- 4. Composite the scene, back to front --------------------------
|
||||
// --- 4. Decide what needs repainting --------------------------------
|
||||
// Advancing open animations mutates window geometry, so this both drives
|
||||
// the effect and reports whether it is still running.
|
||||
bool wm_animating = gui_wm_update();
|
||||
|
||||
uint64_t now_ms = ktime_ms();
|
||||
bool heartbeat = (now_ms - g_last_scene_ms) >= SCENE_HEARTBEAT_MS;
|
||||
bool interacting = left || right || key ||
|
||||
g_start_menu_open || g_context_menu_open ||
|
||||
gui_dialog_is_open();
|
||||
|
||||
// A full recomposite is needed when anything but the bare cursor moved;
|
||||
// otherwise we keep the retained scene and just slide the cursor overlay.
|
||||
bool scene_dirty = wm_animating || interacting || heartbeat || (g_frames == 0);
|
||||
|
||||
if ((g_frames & 0x1F) == 0) sample_cpu_history();
|
||||
|
||||
gui_desktop_render_background();
|
||||
gui_desktop_render_icons();
|
||||
gui_wm_render();
|
||||
gui_desktop_render_taskbar();
|
||||
gui_desktop_render_start_menu();
|
||||
gui_desktop_render_context_menu();
|
||||
gui_dialog_render();
|
||||
gui_cursor_draw();
|
||||
if (scene_dirty) {
|
||||
gui_desktop_render_background();
|
||||
gui_desktop_render_icons();
|
||||
gui_wm_render();
|
||||
gui_desktop_render_taskbar();
|
||||
gui_desktop_render_start_menu();
|
||||
gui_desktop_render_context_menu();
|
||||
gui_dialog_render();
|
||||
g_last_scene_ms = now_ms;
|
||||
} else {
|
||||
// Retained scene: erase the previous cursor from it before redrawing.
|
||||
gui_cursor_restore_under();
|
||||
}
|
||||
|
||||
// The cursor is an overlay drawn on top of the (retained or fresh) scene,
|
||||
// so pointer motion never forces a full recomposite.
|
||||
gui_cursor_composite();
|
||||
|
||||
gui_gfx_present();
|
||||
g_frames++;
|
||||
g_pushed_pixels += gui_gfx_last_present_pixels();
|
||||
g_prev_mx = mx;
|
||||
g_prev_my = my;
|
||||
|
||||
// --- 5. Pace to the 60 FPS budget -----------------------------------
|
||||
uint64_t elapsed_us = ktime_us() - frame_start_us;
|
||||
if (elapsed_us < FRAME_BUDGET_US) {
|
||||
ktime_delay_us(FRAME_BUDGET_US - elapsed_us);
|
||||
}
|
||||
|
||||
// Sample the frame rate roughly twice a second from the monotonic clock.
|
||||
uint64_t now_ms = ktime_ms();
|
||||
uint64_t dt = now_ms - g_fps_last_ms;
|
||||
uint64_t fps_now_ms = ktime_ms();
|
||||
uint64_t dt = fps_now_ms - g_fps_last_ms;
|
||||
if (dt >= 500) {
|
||||
g_fps = (uint32_t)(((g_frames - g_fps_last_frames) * 1000ULL) / dt);
|
||||
g_fps_last_ms = now_ms;
|
||||
g_fps_last_ms = fps_now_ms;
|
||||
g_fps_last_frames = g_frames;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,9 @@
|
||||
#include <drivers/keyboard.h>
|
||||
#include <lib/string.h>
|
||||
#include <mm/kmalloc.h>
|
||||
#include <kernel/ktime.h>
|
||||
|
||||
#define WM_OPEN_ANIM_MS 160
|
||||
|
||||
#define BTN_SIZE 14
|
||||
#define BTN_GAP 4
|
||||
@@ -85,9 +88,63 @@ gui_window_t* gui_wm_create_window(const char* title, int x, int y, int w, int h
|
||||
|
||||
g_window_order[g_num_windows++] = win->id;
|
||||
gui_wm_focus_window(win->id);
|
||||
|
||||
// Kick off the grow-open animation: remember the final geometry and centre,
|
||||
// then start small. gui_wm_update() eases it back to full over ~160 ms.
|
||||
win->target_w = win->width;
|
||||
win->target_h = win->height;
|
||||
win->center_x = win->x + win->width / 2;
|
||||
win->center_y = win->y + win->height / 2;
|
||||
win->anim_open_ns = ktime_ns();
|
||||
win->width = win->target_w * 6 / 10;
|
||||
win->height = win->target_h * 6 / 10;
|
||||
win->x = win->center_x - win->width / 2;
|
||||
win->y = win->center_y - win->height / 2;
|
||||
if (win->x < 0) win->x = 0;
|
||||
if (win->y < 0) win->y = 0;
|
||||
return win;
|
||||
}
|
||||
|
||||
// Advance open animations. Returns true while any window is still animating.
|
||||
bool gui_wm_update(void) {
|
||||
bool animating = false;
|
||||
uint64_t now = ktime_ns();
|
||||
|
||||
for (int i = 0; i < g_num_windows; i++) {
|
||||
gui_window_t* win = gui_wm_get_window(g_window_order[i]);
|
||||
if (!win || win->anim_open_ns == 0) continue;
|
||||
|
||||
uint64_t elapsed_ms = (now - win->anim_open_ns) / 1000000ULL;
|
||||
if (elapsed_ms >= WM_OPEN_ANIM_MS) {
|
||||
// Snap to the final geometry and stop animating.
|
||||
win->width = win->target_w;
|
||||
win->height = win->target_h;
|
||||
win->x = win->center_x - win->target_w / 2;
|
||||
win->y = win->center_y - win->target_h / 2;
|
||||
win->anim_open_ns = 0;
|
||||
} else {
|
||||
animating = true;
|
||||
int p = (int)(elapsed_ms * 1000 / WM_OPEN_ANIM_MS); // 0..1000
|
||||
int inv = 1000 - p;
|
||||
int ease = 1000 - (inv * inv / 1000) * inv / 1000; // cubic ease-out
|
||||
int scale = 600 + 400 * ease / 1000; // 600..1000 permille
|
||||
win->width = win->target_w * scale / 1000;
|
||||
win->height = win->target_h * scale / 1000;
|
||||
win->x = win->center_x - win->width / 2;
|
||||
win->y = win->center_y - win->height / 2;
|
||||
}
|
||||
|
||||
// Keep the animating window on screen.
|
||||
if (win->x < 0) win->x = 0;
|
||||
if (win->y < 0) win->y = 0;
|
||||
if (win->x + win->width > g_work_w) win->x = g_work_w - win->width;
|
||||
if (win->y + win->height > g_work_h) win->y = g_work_h - win->height;
|
||||
if (win->x < 0) win->x = 0;
|
||||
if (win->y < 0) win->y = 0;
|
||||
}
|
||||
return animating;
|
||||
}
|
||||
|
||||
static int order_index_of(int win_id) {
|
||||
for (int i = 0; i < g_num_windows; i++) {
|
||||
if (g_window_order[i] == win_id) return i;
|
||||
|
||||
Reference in new issue
Block a user