rumpk/hal/framebuffer.zig

156 lines
3.9 KiB
Zig

// SPDX-License-Identifier: LCL-1.0
// Copyright (c) 2026 Markus Maiwald
// Stewardship: Self Sovereign Society Foundation
//
// This file is part of the Nexus Commonwealth.
// See legal/LICENSE_COMMONWEALTH.md for license terms.
//! Rumpk Layer 0: The Canvas (Static Framebuffer)
//!
//! Implements the primary graphics buffer (VRAM) living in BSS.
//! Provides primitives for pixel plotting, rect-filling, and screen fading.
//!
//! SAFETY: All drawing operations include clipping logic to prevent BSS overflow.
//! Resolution is fixed at 1920x1080 ARGB.
const std = @import("std");
// Resolution: 1920x1080 @ 32bpp (ARGB)
pub const WIDTH: usize = 1920;
pub const HEIGHT: usize = 1080;
pub const STRIDE: usize = WIDTH;
// The Physical Backing Store (~7.9MB in BSS)
// Zero-initialized at boot.
var fb_memory: [WIDTH * HEIGHT]u32 = [_]u32{0} ** (WIDTH * HEIGHT);
// Clip rectangle (for MU_COMMAND_CLIP)
var clip_x: i32 = 0;
var clip_y: i32 = 0;
var clip_w: i32 = WIDTH;
var clip_h: i32 = HEIGHT;
pub const Rect = struct {
x: i32,
y: i32,
w: i32,
h: i32,
};
pub fn get_buffer() [*]u32 {
return &fb_memory;
}
pub fn fb_get_buffer_phys() usize {
// Identity mapping for now
return @intFromPtr(&fb_memory);
}
pub fn get_size() usize {
return WIDTH * HEIGHT * @sizeOf(u32);
}
pub fn init() void {
// Clear to dark blue (Nexus brand)
clear(0xFF101030);
}
pub fn clear(color: u32) void {
@memset(&fb_memory, color);
}
pub fn set_clip(r: Rect) void {
clip_x = r.x;
clip_y = r.y;
clip_w = r.w;
clip_h = r.h;
}
pub fn put_pixel(x: i32, y: i32, color: u32) void {
if (x < 0 or y < 0) return;
if (x >= @as(i32, @intCast(WIDTH)) or y >= @as(i32, @intCast(HEIGHT))) return;
// Clip check
if (x < clip_x or x >= clip_x + clip_w) return;
if (y < clip_y or y >= clip_y + clip_h) return;
const ux: usize = @intCast(x);
const uy: usize = @intCast(y);
fb_memory[uy * WIDTH + ux] = color;
}
// Optimized Rect Fill (The Workhorse for microui)
pub fn fill_rect(x: i32, y: i32, w: i32, h: i32, color: u32) void {
// Clipping to screen
var rx = x;
var ry = y;
var rw = w;
var rh = h;
if (rx < 0) {
rw += rx;
rx = 0;
}
if (ry < 0) {
rh += ry;
ry = 0;
}
if (rx + rw > @as(i32, @intCast(WIDTH))) {
rw = @as(i32, @intCast(WIDTH)) - rx;
}
if (ry + rh > @as(i32, @intCast(HEIGHT))) {
rh = @as(i32, @intCast(HEIGHT)) - ry;
}
if (rw <= 0 or rh <= 0) return;
// Clip to clip rect
if (rx < clip_x) {
rw -= (clip_x - rx);
rx = clip_x;
}
if (ry < clip_y) {
rh -= (clip_y - ry);
ry = clip_y;
}
if (rx + rw > clip_x + clip_w) {
rw = clip_x + clip_w - rx;
}
if (ry + rh > clip_y + clip_h) {
rh = clip_y + clip_h - ry;
}
if (rw <= 0 or rh <= 0) return;
const start_row: usize = @intCast(ry);
const end_row: usize = start_row + @as(usize, @intCast(rh));
const start_col: usize = @intCast(rx);
const cols: usize = @intCast(rw);
var row = start_row;
while (row < end_row) : (row += 1) {
const offset = row * WIDTH + start_col;
@memset(fb_memory[offset .. offset + cols], color);
}
}
// THE FADE (Optimization: Dim pixels instead of clearing)
// This creates the "Ghost Trail" effect cheaply.
pub fn fade_screen(factor: u8) void {
for (&fb_memory) |*pixel| {
if (pixel.* == 0) continue;
// Extract Green Channel (Assuming 0xAARRGGBB)
// We only care about green for the matrix effect
var g = (pixel.* >> 8) & 0xFF;
if (g > factor) {
g -= factor;
} else {
g = 0;
}
// Reconstruct: Keep it mostly green but allow it to fade to black
// We zero out Red and Blue for the matrix look during fade.
pixel.* = (@as(u32, g) << 8) | 0xFF000000;
}
}