Skip to content

Commit aa03874

Browse files
author
Nathan
committed
fix(desktop): T61 freeze - safe startup, backbuffer fallback, no HLT, no NT stores
- First 3 frames use minimal rendering (no rain, no audio, no effects) - Backbuffer allocation verified; falls back to direct FB if OOM - Proof-of-life pixels written directly to framebuffer at startup - NT stores (movnti) replaced with regular SSE2 everywhere - HLT removed from vsync/engine frame pacing (spin-loop only) - Stack increased to 512KB - Tier detection caps old dual-core CPUs to Standard
1 parent b6ee614 commit aa03874

7 files changed

Lines changed: 117 additions & 59 deletions

File tree

kernel/src/compositor.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ fn writeback_rows_parallel(start: usize, end: usize, data: *mut u8) {
6767
let dst = params.dst.add(dst_offset);
6868

6969
#[cfg(target_arch = "x86_64")]
70-
crate::graphics::simd::copy_row_sse2_nt(dst, src, params.width);
70+
crate::graphics::simd::copy_row_sse2(dst, src, params.width);
7171
#[cfg(not(target_arch = "x86_64"))]
7272
core::ptr::copy_nonoverlapping(src, dst, params.width);
7373
}

kernel/src/desktop.rs

Lines changed: 82 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2735,7 +2735,15 @@ impl Desktop {
27352735
// Initialize double buffering
27362736
crate::serial_println!("[Desktop] init_double_buffer...");
27372737
framebuffer::init_double_buffer();
2738-
framebuffer::set_double_buffer_mode(true);
2738+
// Verify backbuffer was actually allocated — if it failed (OOM),
2739+
// fall back to direct framebuffer mode to avoid invisible desktop
2740+
if framebuffer::get_backbuffer_ptr().is_some() {
2741+
framebuffer::set_double_buffer_mode(true);
2742+
crate::serial_println!("[Desktop] double buffer: OK");
2743+
} else {
2744+
framebuffer::set_double_buffer_mode(false);
2745+
crate::serial_println!("[Desktop] WARNING: backbuffer alloc failed, using direct FB mode");
2746+
}
27392747

27402748
// Initialize background cache for fast redraws
27412749
crate::serial_println!("[Desktop] init_background_cache...");
@@ -3014,22 +3022,28 @@ struct AppConfig {
30143022
// Estimate CPU speed via TSC (in MHz)
30153023
let tsc_mhz = crate::cpu::tsc_frequency() / 1_000_000;
30163024

3017-
// Compute a capability score:
3018-
// RAM contribution: 1 point per 128 MB
3019-
// CPU contribution: 1 point per 500 MHz
3020-
// Core contribution: 1 point per core
3025+
// Compute a capability score weighted toward CPU throughput.
3026+
// Full tier requires real rendering power — RAM alone isn't enough.
3027+
// RAM contribution: 1 point per 256 MB (capped at 8 — diminishing returns)
3028+
// CPU contribution: 1 point per 400 MHz (weighted more than RAM)
3029+
// Core contribution: 2 points per core (parallelism matters for rendering)
30213030
// Resolution penalty: -1 per million pixels above 1M
3022-
let ram_score = (phys_mb / 128) as i64;
3023-
let cpu_score = if tsc_mhz > 0 { (tsc_mhz / 500) as i64 } else { 2 }; // assume moderate if unknown
3024-
let core_score = cpus as i64;
3031+
let ram_score = ((phys_mb / 256) as i64).min(8);
3032+
let cpu_score = if tsc_mhz > 0 { (tsc_mhz / 400) as i64 } else { 2 };
3033+
let core_score = (cpus as i64) * 2;
30253034
let res_penalty = ((pixels as i64) - 1_000_000) / 1_000_000;
30263035
let score = ram_score + cpu_score + core_score - res_penalty;
30273036

3037+
// Hard cap: old dual-core CPUs (< 3 GHz) cannot sustain Full tier.
3038+
// The 4-layer matrix rain + visualizer + drone swarm need at least
3039+
// ~3 GHz × 4 cores or equivalent throughput to hit stable 30 FPS.
3040+
let cpu_limited = tsc_mhz > 0 && tsc_mhz < 3000 && cpus <= 2;
3041+
30283042
let tier = if phys_mb < 128 || heap_free_mb < 8 {
30293043
DesktopTier::CliOnly
3030-
} else if score <= 3 || phys_mb < 256 {
3044+
} else if score <= 4 || phys_mb < 256 {
30313045
DesktopTier::Minimal
3032-
} else if score <= 6 || phys_mb < 512 {
3046+
} else if score <= 8 || phys_mb < 512 || cpu_limited {
30333047
DesktopTier::Standard
30343048
} else {
30353049
DesktopTier::Full
@@ -3041,8 +3055,8 @@ struct AppConfig {
30413055
self.fps_high_count = 0;
30423056

30433057
crate::serial_println!(
3044-
"[Desktop] Tier={:?} (score={}, RAM={}MB, heap={}MB, CPUs={}, TSC={}MHz, {}x{})",
3045-
tier, score, phys_mb, heap_free_mb, cpus, tsc_mhz, self.width, self.height
3058+
"[Desktop] Tier={:?} (score={}, RAM={}MB, heap={}MB, CPUs={}, TSC={}MHz, {}x{}, cpu_limited={})",
3059+
tier, score, phys_mb, heap_free_mb, cpus, tsc_mhz, self.width, self.height, cpu_limited
30463060
);
30473061
}
30483062

@@ -7201,6 +7215,33 @@ struct AppConfig {
72017215
pub fn draw(&mut self) {
72027216
self.frame_count += 1;
72037217

7218+
// ── Safe startup: first 3 frames do MINIMAL rendering ──
7219+
// Eliminates all heavy code (matrix rain, audio analysis, game ticks,
7220+
// animations) from the initial frames. If this fixes the T61 freeze,
7221+
// the bug is in one of the skipped subsystems.
7222+
if self.frame_count <= 3 {
7223+
crate::serial_println!("[Desktop] safe frame {} / 3", self.frame_count);
7224+
// Get mouse state for cursor
7225+
let mouse = crate::mouse::get_state();
7226+
7227+
framebuffer::clear_backbuffer(0xFF010200);
7228+
framebuffer::begin_frame();
7229+
// Just draw icons + taskbar + cursor — no rain, no audio, no effects
7230+
self.draw_desktop_icons();
7231+
self.draw_taskbar();
7232+
self.draw_cursor();
7233+
// Update tracking state
7234+
self.last_cursor_x = mouse.x;
7235+
self.last_cursor_y = mouse.y;
7236+
self.last_window_count = self.windows.len();
7237+
self.last_start_menu_open = self.start_menu_open;
7238+
self.last_context_menu_visible = self.context_menu.visible;
7239+
framebuffer::end_frame();
7240+
framebuffer::swap_buffers();
7241+
crate::serial_println!("[Desktop] safe frame {} done", self.frame_count);
7242+
return;
7243+
}
7244+
72047245
// ── Auto-adjust tier based on sustained FPS ──
72057246
self.auto_adjust_tier();
72067247

@@ -14803,7 +14844,33 @@ pub fn run() {
1480314844

1480414845
crate::serial_println!("[GUI] Starting desktop environment...");
1480514846
crate::serial_println!("[GUI] Hotkeys: Alt+Tab, Win+Arrows, Alt+F4, Win=Start");
14806-
crate::serial_println!("[GUI] Target: ~60 FPS (16.6ms) with HLT-based frame limiting");
14847+
crate::serial_println!("[GUI] Target: ~60 FPS (16.6ms) with spin-loop frame limiting");
14848+
14849+
// ── Visual proof-of-life: write directly to framebuffer (bypass backbuffer) ──
14850+
// If this shows on screen but the desktop doesn't, the issue is in drawing/swap.
14851+
// If this doesn't show, the code never reaches run().
14852+
{
14853+
let fb = crate::framebuffer::get_framebuffer();
14854+
let w = crate::framebuffer::FB_WIDTH.load(core::sync::atomic::Ordering::Relaxed) as usize;
14855+
let pitch = crate::framebuffer::FB_PITCH.load(core::sync::atomic::Ordering::Relaxed) as usize;
14856+
if !fb.is_null() && w > 0 && pitch > 0 {
14857+
// Draw a bright green bar at top-left corner (16×4 pixels)
14858+
for y in 0..4usize {
14859+
for x in 0..16usize {
14860+
if x < w {
14861+
unsafe {
14862+
let dst = (fb as *mut u8).add(y * pitch).add(x * 4) as *mut u32;
14863+
dst.write_volatile(0xFF00FF00); // bright green
14864+
}
14865+
}
14866+
}
14867+
}
14868+
crate::serial_println!("[GUI] Proof-of-life pixels written to framebuffer");
14869+
}
14870+
}
14871+
14872+
// Frame counter for safe startup (skip heavy work on first few frames)
14873+
let mut loop_frame: u32 = 0;
1480714874

1480814875
loop {
1480914876
// Check exit flag
@@ -15303,7 +15370,7 @@ pub fn run() {
1530315370
}
1530415371

1530515372
// ═══════════════════════════════════════════════════════════════
15306-
// VSync frame pacing (adaptive HLT sleep + spin for precision)
15373+
// VSync frame pacing (spin-loop sleep with bail-out)
1530715374
// ═══════════════════════════════════════════════════════════════
1530815375
let render_time_us = engine::now_us().saturating_sub(frame_start);
1530915376
// Log FPS to serial every 120 frames for performance monitoring
@@ -15317,6 +15384,7 @@ pub fn run() {
1531715384
}
1531815385
}
1531915386
crate::gui::vsync::frame_end(frame_start);
15387+
loop_frame = loop_frame.saturating_add(1);
1532015388
}
1532115389

1532215390
// ═══════════════════════════════════════════════════════════════

kernel/src/framebuffer/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -738,7 +738,7 @@ fn swap_buffers_mmio_diff(addr: *mut u8, width: usize, height: usize, pitch: usi
738738
let src = b.as_ptr().add(src_offset);
739739
let dst = addr.add(dst_offset) as *mut u32;
740740
#[cfg(target_arch = "x86_64")]
741-
crate::graphics::simd::copy_row_sse2_nt(dst, src, width);
741+
crate::graphics::simd::copy_row_sse2(dst, src, width);
742742
#[cfg(not(target_arch = "x86_64"))]
743743
core::ptr::copy_nonoverlapping(src, dst, width);
744744
}
@@ -803,7 +803,7 @@ fn swap_buffers_mmio_diff(addr: *mut u8, width: usize, height: usize, pitch: usi
803803
let src = bb_row.as_ptr();
804804
let dst = addr.add(dst_offset) as *mut u32;
805805
#[cfg(target_arch = "x86_64")]
806-
crate::graphics::simd::copy_row_sse2_nt(dst, src, width);
806+
crate::graphics::simd::copy_row_sse2(dst, src, width);
807807
#[cfg(not(target_arch = "x86_64"))]
808808
core::ptr::copy_nonoverlapping(src, dst, width);
809809
}
@@ -825,7 +825,7 @@ fn swap_buffers_mmio(addr: *mut u8, width: usize, height: usize, pitch: usize) {
825825
let src = buf.as_ptr().add(src_offset);
826826
let dst = addr.add(dst_offset) as *mut u32;
827827
#[cfg(target_arch = "x86_64")]
828-
crate::graphics::simd::copy_row_sse2_nt(dst, src, width);
828+
crate::graphics::simd::copy_row_sse2(dst, src, width);
829829
#[cfg(not(target_arch = "x86_64"))]
830830
core::ptr::copy_nonoverlapping(src, dst, width);
831831
}

kernel/src/gdt.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ pub fn init() {
278278
fn alloc_kernel_stack() -> u64 {
279279
use alloc::vec::Vec;
280280

281-
const STACK_SIZE: usize = 256 * 1024; // 256 KB kernel stack
281+
const STACK_SIZE: usize = 512 * 1024; // 512 KB kernel stack
282282

283283
let stack: Vec<u8> = alloc::vec![0u8; STACK_SIZE];
284284
let stack_top = stack.as_ptr() as u64 + STACK_SIZE as u64;

kernel/src/gui/engine.rs

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -59,30 +59,17 @@ fn read_tsc() -> u64 {
5959
crate::arch::timestamp()
6060
}
6161

62-
/// Sleep until next frame (uses HLT to save CPU)
62+
/// Sleep until next frame (spin-loop, no HLT — safe on all hardware)
6363
pub fn wait_for_next_frame(frame_start_us: u64) {
6464
let elapsed = now_us().saturating_sub(frame_start_us);
6565

6666
if elapsed < TARGET_FRAME_US {
67-
let wait_us = TARGET_FRAME_US - elapsed;
68-
69-
// Use HLT for long waits (>1ms), spin for short waits
70-
if wait_us > 1000 {
71-
// HLT will wake on next interrupt (timer, keyboard, mouse)
72-
// This drops CPU from 100% to ~1%
73-
unsafe {
74-
// Enable interrupts and halt until interrupt
75-
#[cfg(target_arch = "x86_64")]
76-
core::arch::asm!("sti; hlt", options(nomem, nostack));
77-
#[cfg(not(target_arch = "x86_64"))]
78-
crate::arch::halt();
79-
}
80-
} else {
81-
// Short spin for precise timing
82-
let target = frame_start_us + TARGET_FRAME_US;
83-
while now_us() < target {
84-
core::hint::spin_loop();
85-
}
67+
let target = frame_start_us + TARGET_FRAME_US;
68+
let mut bail = 0u32;
69+
while now_us() < target {
70+
bail += 1;
71+
if bail >= 2_000_000 { break; } // safety bail-out
72+
core::hint::spin_loop();
8673
}
8774
}
8875

kernel/src/gui/vsync.rs

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -100,26 +100,27 @@ pub fn frame_end(frame_start_us: u64) {
100100
update_smooth_fps();
101101
}
102102

103-
/// Adaptive sleep: HLT loop for long waits, spin for short waits.
104-
/// More precise than a single HLT — loops until the target time.
103+
/// Frame sleep: spin-loop with bail-out to prevent infinite hang.
104+
/// Uses spin_loop_hint for CPU power savings on modern CPUs.
105+
/// Avoids HLT which can hang on real hardware if interrupt routing
106+
/// (PIC/APIC) is not perfectly configured.
105107
fn adaptive_sleep(target_us: u64) {
108+
// Cap sleep to 50ms max — never block longer than ~3 frames
109+
let capped = target_us.min(50_000);
106110
let start = super::engine::now_us();
107-
let end = start + target_us;
108-
109-
// Phase 1: HLT loop for waits > 2ms (each HLT wakes on timer IRQ ~1ms)
110-
while super::engine::now_us() + 2000 < end {
111-
unsafe {
112-
#[cfg(target_arch = "x86_64")]
113-
core::arch::asm!("sti; hlt", options(nomem, nostack));
114-
#[cfg(not(target_arch = "x86_64"))]
115-
{
116-
core::arch::asm!("wfe", options(nomem, nostack));
117-
}
118-
}
119-
}
120-
121-
// Phase 2: Spin-wait for the last ~2ms (precise timing)
122-
while super::engine::now_us() < end {
111+
let end = start + capped;
112+
113+
// Bail-out counter: even if now_us() is broken (returns 0),
114+
// we'll exit after ~2M iterations (~16ms at 3GHz spin rate)
115+
let mut bail = 0u32;
116+
const MAX_SPINS: u32 = 2_000_000;
117+
118+
loop {
119+
let now = super::engine::now_us();
120+
if now >= end { break; }
121+
// Safety: if now_us() isn't advancing, bail out
122+
bail += 1;
123+
if bail >= MAX_SPINS { break; }
123124
core::hint::spin_loop();
124125
}
125126
}

kernel/src/main.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,10 +300,12 @@ static KERNEL_ADDRESS_REQUEST: KernelAddressRequest = KernelAddressRequest::new(
300300
#[unsafe(link_section = ".requests")]
301301
static KERNEL_FILE_REQUEST: KernelFileRequest = KernelFileRequest::new();
302302

303-
/// Request larger stack (256 KB) from Limine to support deep desktop rendering + interrupts
303+
/// Request larger stack (512 KB) from Limine to support deep desktop rendering + interrupts
304+
/// 256 KB was insufficient for the desktop's nested draw_background() call chain
305+
/// (4-layer matrix rain × 256 columns × visualizer + drone_swarm per frame)
304306
#[used]
305307
#[unsafe(link_section = ".requests")]
306-
static STACK_SIZE_REQUEST: StackSizeRequest = StackSizeRequest::new().with_size(256 * 1024);
308+
static STACK_SIZE_REQUEST: StackSizeRequest = StackSizeRequest::new().with_size(512 * 1024);
307309

308310
/// Limine requests end marker
309311
#[used]

0 commit comments

Comments
 (0)