// SUB-OS Boot Logo & Splash Screen // // Two presentations of the same artwork, because the framebuffer only exists // from step [5/14] onward: // // * boot_logo_print_text() -- ASCII rendition on the text console and the // serial line, shown from the very first moments of the boot. // * boot_logo_splash_*() -- the real bitmap on the framebuffer, with a // progress bar, once the video driver is up. // // The bitmap is 4-bit palette indexed (init/logo_data.c, generated by // scripts/mklogo.py), so 160x160 pixels cost 12.5 KB rather than 100 KB. #include #include #include #include #include #include #include #include #define SPLASH_BG_TOP 0xFF0B132B #define SPLASH_BG_BOTTOM 0xFF1C2541 #define SPLASH_ACCENT 0xFF38BDF8 #define SPLASH_ACCENT_LO 0xFF0284C7 #define SPLASH_TEXT 0xFFF8FAFC #define SPLASH_MUTED 0xFF94A3B8 #define SPLASH_TRACK 0xFF1E293B #define SPLASH_BORDER 0xFF475569 // Boot reaches the desktop within a few hundred milliseconds, so without a // floor the logo would flash past unseen. Hold it briefly and animate the bar // to completion during the hold, which also gives the splash a natural finish. #define SPLASH_MIN_MS 1800 #define SPLASH_FRAME_MS 20 static bool g_splash_active = false; static uint64_t g_splash_start_ticks = 0; static uint64_t g_splash_hold_ticks = 0; static int g_logo_x = 0, g_logo_y = 0, g_logo_scale = 1; static int g_bar_x = 0, g_bar_y = 0, g_bar_w = 0; static int g_status_y = 0; static int g_last_percent = -1; // --------------------------------------------------------------------------- // Text console // --------------------------------------------------------------------------- void boot_logo_print_text(void) { printk("\n"); for (int i = 0; boot_logo_ascii[i] != NULL; i++) { printk(ANSI_BRIGHT_CYAN " %s\n" ANSI_RESET, boot_logo_ascii[i]); } printk("\n"); printk(ANSI_BRIGHT_WHITE " S U B - O S\n" ANSI_RESET); printk(ANSI_BRIGHT_BLACK " Modular Monolithic Kernel %s\n" ANSI_RESET, SUBOS_VERSION_STRING); printk("\n"); } // --------------------------------------------------------------------------- // Bitmap blitting // --------------------------------------------------------------------------- // Unpack one 4-bit index. Two pixels share a byte, high nibble first. static inline uint8_t logo_index_at(int px, int py) { int linear = py * boot_logo_width + px; uint8_t byte = boot_logo_bitmap[linear >> 1]; return (linear & 1) ? (byte & 0x0F) : (byte >> 4); } void boot_logo_draw(int x, int y, int scale) { if (scale < 1) scale = 1; const fb_info_t* fb = fb_get_info(); if (!fb || !fb->active) return; for (int py = 0; py < boot_logo_height; py++) { for (int px = 0; px < boot_logo_width; px++) { uint8_t index = logo_index_at(px, py); // Leave the artwork's transparent surround alone so the logo sits // on the gradient rather than inside a flat rectangle. if (boot_logo_transparent_mask & (1u << index)) continue; uint32_t color = boot_logo_palette[index]; if (scale == 1) { fb_put_pixel((uint32_t)(x + px), (uint32_t)(y + py), color); } else { fb_draw_rect((uint32_t)(x + px * scale), (uint32_t)(y + py * scale), (uint32_t)scale, (uint32_t)scale, color); } } } } // --------------------------------------------------------------------------- // Splash screen // --------------------------------------------------------------------------- static void draw_gradient(int width, int height) { uint32_t top = SPLASH_BG_TOP, bottom = SPLASH_BG_BOTTOM; int tr = (top >> 16) & 0xFF, tg = (top >> 8) & 0xFF, tb = top & 0xFF; int br = (bottom >> 16) & 0xFF, bg = (bottom >> 8) & 0xFF, bb = bottom & 0xFF; for (int y = 0; y < height; y++) { int r = tr + ((br - tr) * y) / (height ? height : 1); int g = tg + ((bg - tg) * y) / (height ? height : 1); int b = tb + ((bb - tb) * y) / (height ? height : 1); fb_draw_rect(0, (uint32_t)y, (uint32_t)width, 1, 0xFF000000u | ((uint32_t)r << 16) | ((uint32_t)g << 8) | (uint32_t)b); } } static void draw_centered(int y, const char* text, uint32_t color, int screen_w) { int width = (int)strlen(text) * 8; fb_draw_string((uint32_t)((screen_w - width) / 2), (uint32_t)y, text, color, 0); } bool boot_logo_splash_begin(void) { const fb_info_t* fb = fb_get_info(); if (!fb || !fb->active || !fb->address || fb->width == 0 || fb->height == 0) { return false; } int sw = (int)fb->width; int sh = (int)fb->height; // Double the logo when the panel is tall enough to carry it; otherwise the // splash would crowd out the progress bar. g_logo_scale = (sh >= 560 && sw >= 640) ? 2 : 1; int logo_px = boot_logo_width * g_logo_scale; g_logo_x = (sw - logo_px) / 2; g_logo_y = (sh - logo_px) / 2 - 60; if (g_logo_y < 12) g_logo_y = 12; g_bar_w = (sw * 45) / 100; if (g_bar_w > 420) g_bar_w = 420; g_bar_x = (sw - g_bar_w) / 2; g_bar_y = g_logo_y + logo_px + 56; g_status_y = g_bar_y + 26; draw_gradient(sw, sh); boot_logo_draw(g_logo_x, g_logo_y, g_logo_scale); draw_centered(g_logo_y + logo_px + 18, "S U B - O S", SPLASH_TEXT, sw); draw_centered(g_logo_y + logo_px + 34, SUBOS_VERSION_STRING, SPLASH_MUTED, sw); // Progress track fb_draw_rect((uint32_t)g_bar_x, (uint32_t)g_bar_y, (uint32_t)g_bar_w, 12, SPLASH_TRACK); fb_draw_rect((uint32_t)g_bar_x, (uint32_t)g_bar_y, (uint32_t)g_bar_w, 1, SPLASH_BORDER); fb_draw_rect((uint32_t)g_bar_x, (uint32_t)(g_bar_y + 11), (uint32_t)g_bar_w, 1, SPLASH_BORDER); fb_draw_rect((uint32_t)g_bar_x, (uint32_t)g_bar_y, 1, 12, SPLASH_BORDER); fb_draw_rect((uint32_t)(g_bar_x + g_bar_w - 1), (uint32_t)g_bar_y, 1, 12, SPLASH_BORDER); draw_centered(sh - 20, "Modular Monolithic Kernel", 0xFF64748B, sw); g_splash_active = true; g_last_percent = -1; g_splash_start_ticks = pit_get_ticks(); // The PIT runs at 100 Hz, so one tick is 10 ms. uint32_t hold_ms = SPLASH_MIN_MS; const char* param = init_get_param("splashtime"); if (param) { int requested = atoi(param); if (requested >= 0 && requested <= 10000) hold_ms = (uint32_t)requested; } g_splash_hold_ticks = hold_ms / 10; boot_logo_splash_progress(0, "Starting kernel subsystems"); return true; } void boot_logo_splash_progress(int percent, const char* status) { if (!g_splash_active) return; const fb_info_t* fb = fb_get_info(); if (!fb || !fb->active) return; if (percent < 0) percent = 0; if (percent > 100) percent = 100; // The bar only ever grows, so repaint just the newly filled span. int filled = ((g_bar_w - 2) * percent) / 100; if (percent > g_last_percent) { fb_draw_rect((uint32_t)(g_bar_x + 1), (uint32_t)(g_bar_y + 1), (uint32_t)filled, 10, SPLASH_ACCENT); // A darker leading edge gives the fill some depth. if (filled > 2) { fb_draw_rect((uint32_t)(g_bar_x + 1), (uint32_t)(g_bar_y + 8), (uint32_t)filled, 3, SPLASH_ACCENT_LO); } g_last_percent = percent; } if (status) { int sw = (int)fb->width; // Clear the previous status before writing the new one. fb_draw_rect(0, (uint32_t)g_status_y, (uint32_t)sw, 10, SPLASH_BG_BOTTOM); char line[80]; snprintf(line, sizeof(line), "%s ... %d%%", status, percent); draw_centered(g_status_y, line, SPLASH_MUTED, sw); } } void boot_logo_splash_end(void) { if (!g_splash_active) return; // Sweep whatever is left of the bar to 100% while waiting out the hold, so // a fast boot still shows a complete progress animation rather than a jump. uint64_t deadline = g_splash_start_ticks + g_splash_hold_ticks; int start_percent = (g_last_percent < 0) ? 0 : g_last_percent; while (pit_get_ticks() < deadline) { uint64_t now = pit_get_ticks(); uint64_t elapsed = now - g_splash_start_ticks; int percent = start_percent; if (g_splash_hold_ticks > 0) { int span = 100 - start_percent; percent = start_percent + (int)((elapsed * (uint64_t)span) / g_splash_hold_ticks); } if (percent > 100) percent = 100; boot_logo_splash_progress(percent, "Starting the desktop environment"); pit_sleep(SPLASH_FRAME_MS); } boot_logo_splash_progress(100, "Ready"); g_splash_active = false; g_last_percent = -1; } bool boot_logo_splash_active(void) { return g_splash_active; }