Skip to content

Commit 9d9b9ea

Browse files
authored
Merge pull request #245 from ryanbreen/feat/gpu-window-manager
GPU-composited tiling window manager with VirGL textured quads
2 parents 27184e2 + 52539ba commit 9d9b9ea

8 files changed

Lines changed: 460 additions & 219 deletions

File tree

kernel/src/drivers/virtio/gpu_pci.rs

Lines changed: 198 additions & 179 deletions
Large diffs are not rendered by default.

kernel/src/drivers/virtio/virgl.rs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -520,25 +520,34 @@ impl CommandBuffer {
520520
/// Create a sampler view (binds a texture resource for shader sampling).
521521
///
522522
/// VirGL protocol: CREATE_OBJECT(SAMPLER_VIEW) with 6 payload DWORDs:
523-
/// `[handle, res_handle, format, first_level, last_level, swizzle_packed]`
523+
/// `[handle, res_handle, fmt_target, val0(layers), val1(levels), swizzle_packed]`
524524
///
525+
/// `fmt_target`: bits [0:23] = pipe format, bits [24:31] = pipe texture target.
526+
/// `val0`: `first_layer | (last_layer << 16)` (for TEXTURE_2D, both 0).
527+
/// `val1`: `first_level | (last_level << 8)` (for single-mip, both 0).
525528
/// `swizzle_packed` encodes channel mapping: `r | (g<<3) | (b<<6) | (a<<9)`
526529
/// using constants from `swizzle::*`. Use `swizzle::IDENTITY` for default.
527530
pub fn create_sampler_view(
528531
&mut self,
529532
handle: u32,
530533
res_handle: u32,
531534
format: u32,
535+
target: u32,
536+
first_layer: u32,
537+
last_layer: u32,
532538
first_level: u32,
533539
last_level: u32,
534540
swizzle_packed: u32,
535541
) {
542+
let fmt_target = (format & 0x00FF_FFFF) | ((target & 0xFF) << 24);
543+
let val0 = (first_layer & 0xFFFF) | ((last_layer & 0xFFFF) << 16);
544+
let val1 = (first_level & 0xFF) | ((last_level & 0xFF) << 8);
536545
self.push(Self::cmd0(ccmd::CREATE_OBJECT, obj::SAMPLER_VIEW, 6));
537546
self.push(handle);
538547
self.push(res_handle);
539-
self.push(format);
540-
self.push(first_level);
541-
self.push(last_level);
548+
self.push(fmt_target);
549+
self.push(val0);
550+
self.push(val1);
542551
self.push(swizzle_packed);
543552
}
544553

kernel/src/syscall/graphics.rs

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -455,24 +455,31 @@ fn handle_virgl_op(cmd: &FbDrawCmd) -> SyscallResult {
455455
let pixels = unsafe {
456456
core::slice::from_raw_parts(buf_ptr as *const u32, pixel_count as usize)
457457
};
458-
match crate::drivers::virtio::gpu_pci::virgl_composite_frame(pixels, width, height) {
458+
// Try GPU-textured compositing first, fall back to direct blit
459+
match crate::drivers::virtio::gpu_pci::virgl_composite_frame_textured(pixels, width, height) {
459460
Ok(()) => SyscallResult::Ok(0),
460-
Err(e) => {
461-
crate::serial_println!("[virgl-syscall] composite_frame FAILED: {}", e);
462-
SyscallResult::Err(super::ErrorCode::InvalidArgument as u64)
461+
Err(_) => {
462+
match crate::drivers::virtio::gpu_pci::virgl_composite_frame(pixels, width, height) {
463+
Ok(()) => SyscallResult::Ok(0),
464+
Err(e) => {
465+
crate::serial_println!("[virgl-syscall] composite_frame FAILED: {}", e);
466+
SyscallResult::Err(super::ErrorCode::InvalidArgument as u64)
467+
}
468+
}
463469
}
464470
}
465471
}
466472
11 => {
467473
// CreateWindowBuffer: allocate shared pixel buffer
468-
// p1=width, p2=height
469-
// Returns: buffer_id in OK value, userspace mmap addr in p1/p2 of output
474+
// p1=width, p2=height, p3/p4 = output pointer (lo/hi) for mmap addr
475+
// Returns: buffer_id in OK value, writes 64-bit mmap addr to *out_ptr
470476
let width = cmd.p1 as u32;
471477
let height = cmd.p2 as u32;
478+
let out_ptr = (cmd.p3 as u32 as u64) | ((cmd.p4 as u32 as u64) << 32);
472479
if width == 0 || height == 0 || width > 4096 || height > 4096 {
473480
return SyscallResult::Err(super::ErrorCode::InvalidArgument as u64);
474481
}
475-
handle_create_window_buffer(width, height)
482+
handle_create_window_buffer(width, height, out_ptr)
476483
}
477484
12 => {
478485
// RegisterWindow: register a buffer as a visible window
@@ -554,7 +561,10 @@ fn handle_virgl_op(cmd: &FbDrawCmd) -> SyscallResult {
554561
None => SyscallResult::Err(super::ErrorCode::InvalidArgument as u64),
555562
}
556563
}
557-
_ => SyscallResult::Err(super::ErrorCode::InvalidArgument as u64),
564+
_ => {
565+
crate::serial_println!("[virgl-op] UNKNOWN op={}", cmd.op);
566+
SyscallResult::Err(super::ErrorCode::InvalidArgument as u64)
567+
}
558568
}
559569
}
560570

@@ -565,7 +575,7 @@ fn handle_virgl_op(cmd: &FbDrawCmd) -> SyscallResult {
565575
/// the process's mmap_hint, and the caller should use the window buffer API to
566576
/// get the mapped pointer).
567577
#[cfg(target_arch = "aarch64")]
568-
fn handle_create_window_buffer(width: u32, height: u32) -> SyscallResult {
578+
fn handle_create_window_buffer(width: u32, height: u32, out_addr_ptr: u64) -> SyscallResult {
569579
use crate::memory::vma::{MmapFlags, Protection, Vma};
570580
use crate::syscall::memory_common::{
571581
get_current_thread_id, prot_to_page_flags, flush_tlb, round_down_to_page, PAGE_SIZE,
@@ -580,22 +590,32 @@ fn handle_create_window_buffer(width: u32, height: u32) -> SyscallResult {
580590

581591
let size = (width as usize) * (height as usize) * 4;
582592
let num_pages = (size + PAGE_SIZE as usize - 1) / PAGE_SIZE as usize;
593+
crate::serial_println!("[window] create_window_buffer: {}x{} ({} bytes, {} pages)", width, height, size, num_pages);
583594

584595
// Get current process
585596
let current_thread_id = match get_current_thread_id() {
586597
Some(id) => id,
587-
None => return SyscallResult::Err(super::ErrorCode::NoSuchProcess as u64),
598+
None => {
599+
crate::serial_println!("[window] ERROR: get_current_thread_id returned None");
600+
return SyscallResult::Err(super::ErrorCode::NoSuchProcess as u64);
601+
}
588602
};
589603

590604
let mut manager_guard = crate::process::manager();
591605
let manager = match *manager_guard {
592606
Some(ref mut m) => m,
593-
None => return SyscallResult::Err(super::ErrorCode::NoSuchProcess as u64),
607+
None => {
608+
crate::serial_println!("[window] ERROR: process manager not available");
609+
return SyscallResult::Err(super::ErrorCode::NoSuchProcess as u64);
610+
}
594611
};
595612

596613
let (pid, process) = match manager.find_process_by_thread_mut(current_thread_id) {
597614
Some(p) => p,
598-
None => return SyscallResult::Err(super::ErrorCode::NoSuchProcess as u64),
615+
None => {
616+
crate::serial_println!("[window] ERROR: thread {:?} not in process table", current_thread_id);
617+
return SyscallResult::Err(super::ErrorCode::NoSuchProcess as u64);
618+
}
599619
};
600620

601621
// Allocate virtual address range from mmap hint
@@ -667,11 +687,15 @@ fn handle_create_window_buffer(width: u32, height: u32) -> SyscallResult {
667687
buffer_id, pid.as_u64(), width, height, new_addr, first_phys
668688
);
669689

670-
// Return buffer_id in upper 32 bits, mmap address in lower 32 bits
671-
// (userspace can reconstruct: buffer_id = result >> 32, addr = result & 0xFFFFFFFF)
672-
// Actually, return buffer_id and the address is the mmap_hint before allocation
673-
// For simplicity, pack both: high 32 = buffer_id, low 32 = mmap addr page-aligned
674-
SyscallResult::Ok(((buffer_id as u64) << 32) | (new_addr & 0xFFFF_FFFF))
690+
// Write full 64-bit mmap address to userspace output pointer
691+
if out_addr_ptr != 0 && out_addr_ptr < USER_SPACE_MAX {
692+
unsafe {
693+
core::ptr::write(out_addr_ptr as *mut u64, new_addr);
694+
}
695+
}
696+
697+
// Return just the buffer_id
698+
SyscallResult::Ok(buffer_id as u64)
675699
}
676700

677701
/// sys_fbdraw - Draw to the left pane of the framebuffer

libs/libbreenix/src/graphics.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -451,23 +451,21 @@ impl Default for WindowInfo {
451451
/// Returns a WindowBuffer with a mapped pixel pointer and buffer ID.
452452
/// The buffer can be registered with the compositor via `register_window()`.
453453
pub fn create_window(width: u32, height: u32) -> Result<WindowBuffer, Error> {
454+
let mut mmap_addr: u64 = 0;
455+
let out_ptr = &mut mmap_addr as *mut u64 as u64;
454456
let cmd = FbDrawCmd {
455457
op: draw_op::CREATE_WINDOW_BUFFER,
456458
p1: width as i32,
457459
p2: height as i32,
458-
p3: 0,
459-
p4: 0,
460+
p3: out_ptr as i32,
461+
p4: (out_ptr >> 32) as i32,
460462
color: 0,
461463
};
462464
let ret = unsafe { raw::syscall1(nr::FBDRAW, &cmd as *const FbDrawCmd as u64) as i64 };
463465
if ret < 0 {
464466
return Err(Error::Os(Errno::from_raw(-ret)));
465467
}
466-
let result = ret as u64;
467-
let buffer_id = (result >> 32) as u32;
468-
let mmap_addr = (result & 0xFFFF_FFFF) as u64;
469-
// On 64-bit, userspace mmap addresses may have upper bits from sign extension
470-
// but the kernel returns the low 32 bits; reconstruct full address
468+
let buffer_id = ret as u32;
471469
Ok(WindowBuffer {
472470
id: buffer_id,
473471
pixels: mmap_addr as *mut u32,

run.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,10 @@ if [ "$PARALLELS" = true ]; then
331331

332332
# VirtIO GPU with 3D acceleration (required for VirGL)
333333
prlctl set "$PARALLELS_VM" --3d-accelerate highest 2>/dev/null || true
334-
prlctl set "$PARALLELS_VM" --videosize 128 2>/dev/null || true
334+
prlctl set "$PARALLELS_VM" --videosize 256 2>/dev/null || true
335+
# high-resolution OFF so the VM window maps 1:1 pixels-to-points on Retina Macs.
336+
# With high-resolution ON, a 1280x960 guest appears as a tiny 640x480 point window.
337+
prlctl set "$PARALLELS_VM" --high-resolution off 2>/dev/null || true
335338

336339
# Remove any default devices Parallels created (cdrom, hdd, serial) to avoid conflicts
337340
for dev in hdd0 hdd1 hdd2 cdrom0 cdrom1 serial0 serial1; do

scripts/parallels/quick-test.sh

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,23 @@ EXT2_HDD_DIR="$PARALLELS_DIR/breenix-ext2.hdd"
1717
SERIAL_LOG="/tmp/breenix-parallels-serial.log"
1818
SCREENSHOT="/tmp/breenix-screenshot.png"
1919

20-
echo "=== Building ==="
20+
echo "=== Building kernel ==="
2121
touch kernel/src/drivers/virtio/gpu_pci.rs
2222
scripts/parallels/build-efi.sh --kernel 2>&1 | grep -E "warning|error|Build Complete|bytes\)" | head -10
2323

24+
echo "=== Building userspace + ext2 ==="
25+
./userspace/programs/build.sh --arch aarch64 2>&1 | tail -1
26+
./scripts/create_ext2_disk.sh --arch aarch64 >/dev/null 2>&1
27+
# Recreate ext2 Parallels HDD from fresh ext2 image
28+
EXT2_IMG="target/ext2-aarch64.img"
29+
if [ -f "$EXT2_IMG" ]; then
30+
rm -rf "$EXT2_HDD_DIR"
31+
prl_disk_tool create --hdd "$EXT2_HDD_DIR" --size 64M >/dev/null 2>&1
32+
EXT2_HDS=$(find "$EXT2_HDD_DIR" -name "*.hds" | head -1)
33+
cp "$EXT2_IMG" "$EXT2_HDS"
34+
echo "ext2 HDD updated"
35+
fi
36+
2437
LOADER_EFI="target/aarch64-unknown-uefi/release/parallels-loader.efi"
2538
KERNEL_ELF="target/aarch64-breenix/release/kernel-aarch64"
2639

@@ -54,7 +67,8 @@ prlctl set "$VM_NAME" --memsize 2048 >/dev/null 2>&1
5467
prlctl set "$VM_NAME" --cpus 4 >/dev/null 2>&1
5568
prlctl set "$VM_NAME" --efi-boot on >/dev/null 2>&1 || true
5669
prlctl set "$VM_NAME" --3d-accelerate highest >/dev/null 2>&1 || true
57-
prlctl set "$VM_NAME" --videosize 128 >/dev/null 2>&1 || true
70+
prlctl set "$VM_NAME" --videosize 256 >/dev/null 2>&1 || true
71+
prlctl set "$VM_NAME" --high-resolution off >/dev/null 2>&1 || true
5872
for dev in hdd0 hdd1 hdd2 cdrom0 cdrom1 serial0 serial1; do
5973
prlctl set "$VM_NAME" --device-del "$dev" >/dev/null 2>&1 || true
6074
done

userspace/programs/src/bounce.rs

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use std::process;
1111
use libbreenix::graphics;
1212
use libbreenix::graphics::{FlushRect, VirglRect};
1313
use libbreenix::time;
14+
use libbreenix::types;
1415

1516
use libgfx::color::Color;
1617
use libgfx::font;
@@ -123,10 +124,29 @@ impl FpsCounter {
123124

124125
const NUM_RECTS: usize = 6;
125126

127+
/// Window buffer dimensions — a nice floating window size for the compositor.
128+
const WIN_W: u32 = 400;
129+
const WIN_H: u32 = 300;
130+
126131
fn main() {
127132
let boot_id = clock_monotonic_ns();
128133
println!("Bounce demo starting (for Gus!) [boot_id={:016x}]", boot_id);
129134

135+
// Try window-buffer mode first: BWM composites us as a floating window
136+
match graphics::create_window(WIN_W, WIN_H) {
137+
Ok(win) => {
138+
let _ = graphics::register_window(win.id, b"Bounce");
139+
println!("[bounce] Window mode: id={} {}x{} @ {:p} [boot_id={:016x}]",
140+
win.id, WIN_W, WIN_H, win.pixels, boot_id);
141+
let mut rects = make_rects();
142+
run_window_loop(&win, &mut rects);
143+
return;
144+
}
145+
Err(e) => {
146+
println!("[bounce] create_window failed: {} — falling back to VirGL direct", e);
147+
}
148+
}
149+
130150
let info = match graphics::fbinfo() {
131151
Ok(info) => info,
132152
Err(_e) => { println!("Error: Could not get framebuffer info"); process::exit(1); }
@@ -135,15 +155,7 @@ fn main() {
135155
let height = info.height as i32;
136156
let width = info.left_pane_width() as i32;
137157

138-
let mut rects = [
139-
AnimRect::new( 50, 40, 900, 700, 120, 80, Color::rgb(255, 50, 50)), // Red
140-
AnimRect::new(200, 150, -800, 600, 90, 110, Color::rgb( 50, 255, 50)), // Green
141-
AnimRect::new(100, 300, 750, -850, 140, 60, Color::rgb( 50, 50, 255)), // Blue
142-
AnimRect::new(300, 100, -700, -750, 70, 130, Color::rgb(255, 255, 50)), // Yellow
143-
AnimRect::new(150, 400, 850, 500, 100, 100, Color::rgb(255, 50, 255)), // Magenta
144-
AnimRect::new(350, 250, -650, 800, 110, 70, Color::rgb( 50, 255, 255)), // Cyan
145-
];
146-
158+
let mut rects = make_rects();
147159
let bg_packed = graphics::rgb(15, 15, 30);
148160

149161
// Try VirGL GPU rendering first
@@ -160,6 +172,55 @@ fn main() {
160172
}
161173
}
162174

175+
fn make_rects() -> [AnimRect; NUM_RECTS] {
176+
[
177+
AnimRect::new( 50, 40, 900, 700, 120, 80, Color::rgb(255, 50, 50)), // Red
178+
AnimRect::new(200, 150, -800, 600, 90, 110, Color::rgb( 50, 255, 50)), // Green
179+
AnimRect::new(100, 300, 750, -850, 140, 60, Color::rgb( 50, 50, 255)), // Blue
180+
AnimRect::new(300, 100, -700, -750, 70, 130, Color::rgb(255, 255, 50)), // Yellow
181+
AnimRect::new(150, 400, 850, 500, 100, 100, Color::rgb(255, 50, 255)), // Magenta
182+
AnimRect::new(350, 250, -650, 800, 110, 70, Color::rgb( 50, 255, 255)), // Cyan
183+
]
184+
}
185+
186+
/// Render bouncing rects into a window buffer. BWM composites this as a floating window.
187+
fn run_window_loop(win: &graphics::WindowBuffer, rects: &mut [AnimRect; NUM_RECTS]) {
188+
let width = win.width as i32;
189+
let height = win.height as i32;
190+
let bpp = 4usize;
191+
let stride = width as usize * bpp;
192+
let bg = Color::rgb(15, 15, 30);
193+
194+
let mut fb = unsafe {
195+
FrameBuf::from_raw(
196+
win.pixels as *mut u8,
197+
width as usize,
198+
height as usize,
199+
stride,
200+
bpp,
201+
true, // BGRA
202+
)
203+
};
204+
205+
let mut fps = FpsCounter::new();
206+
const SUBSTEPS: i32 = 8;
207+
208+
loop {
209+
for _ in 0..SUBSTEPS {
210+
for rect in rects.iter_mut() { rect.step(SUBSTEPS); }
211+
for rect in rects.iter_mut() { rect.bounce_walls(width, height); }
212+
}
213+
214+
fb.clear(bg);
215+
for rect in rects.iter() { rect.draw(&mut fb); }
216+
fps.tick();
217+
fps.draw(&mut fb);
218+
219+
// Throttle to ~60 FPS — BWM reads our buffer each frame anyway
220+
let _ = time::nanosleep(&libbreenix::types::Timespec { tv_sec: 0, tv_nsec: 16_000_000 });
221+
}
222+
}
223+
163224
fn build_virgl_rects(rects: &[AnimRect; NUM_RECTS]) -> [VirglRect; NUM_RECTS] {
164225
let mut vr = [VirglRect::default(); NUM_RECTS];
165226
for (i, rect) in rects.iter().enumerate() {

0 commit comments

Comments
 (0)