Files
LithosAnanake/src/starkernel/hal/framebuffer.c
T
Robert Allan James ab96ac0970 starkernel: item 4.3.1 -- framebuffer orientation test, found and fixed a real color-swap bug
Adds fb_draw_orientation_test() (framebuffer.c/.h): fills the four raster
corners RED/GREEN/BLUE/YELLOW via fb_fill_rect. Wired into kernel_main.c
calling fb_init() directly -- console_fb_init()/vt100_init() removed from
the boot path, since vt100.c/console.c are superseded by the Console
drawing-fabric redesign (FABRIC.md ss27) and should not be exercised even
incidentally.

The diagnostic caught a real, pre-existing bug on its first run: framebuffer.c's
pack_pixel() had its FB_PIXEL_RGBX32/FB_PIXEL_BGRX32 branches swapped relative
to UEFI GOP's own byte-order naming convention, producing a clean R<->B channel
swap (G unaffected). Spatial placement was already correct -- no flip/rotation.
Fixed by swapping pack_pixel's two return bodies to match framebuffer.h's
already-correct doc comments; kernel_main.c's GOP-format switch needed no change.

Also item 4.3.2 -- QEMU screenshot capability. scripts/qemu_screenshot.sh
already existed (monitor socket + socat + HMP screendump), just unwired and
unused this session. Redirected its PNG output to a new top-level fb/
directory (tracked in git, not logs/, not a gitignored temp dir) and added a
python3+PIL fallback for PPM->PNG conversion since imagemagick isn't
installed here. Left as a standalone script for now, not wired into a
Makefile target.

FABRIC.md items 4.3.1 and 4.3.2 marked done with acceptance evidence.
2026-08-07 11:38:33 -04:00

296 lines
10 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
StarForth — Steady-State Virtual Machine Runtime
Copyright (c) 20232025 Robert A. James
All rights reserved.
Licensed under the StarForth License, Version 1.0.
*/
/**
* framebuffer.c — UEFI GOP framebuffer pixel driver
*
* Supports BGRX32 and RGBX32 pixel formats.
* Pixel scale factor allows 1×/2×/4× rendering for hi-res displays.
*
* Hi-res mode:
* 1080p (1920×1080) @ scale=1 → 240 cols × 67 rows (8×16 cells)
* 1080p (1920×1080) @ scale=2 → 120 cols × 33 rows (16×32 cells)
* 4K (3840×2160) @ scale=2 → 240 cols × 67 rows (16×32 cells)
* 4K (3840×2160) @ scale=4 → 120 cols × 33 rows (32×64 cells)
*/
#include "framebuffer.h"
#include <stdint.h>
/* Font API from font_8x16.c */
extern const uint8_t *font_8x16_glyph(uint8_t ch);
/* ANSI 16-color palette (indices 015, packed 0x00RRGGBB) */
const uint32_t FB_ANSI_PALETTE[16] __attribute__((visibility("hidden"))) = {
/* 0 Black */ FB_RGB(0x00, 0x00, 0x00),
/* 1 Red */ FB_RGB(0xAA, 0x00, 0x00),
/* 2 Green */ FB_RGB(0x00, 0xAA, 0x00),
/* 3 Yellow */ FB_RGB(0xAA, 0x55, 0x00),
/* 4 Blue */ FB_RGB(0x00, 0x00, 0xAA),
/* 5 Magenta */ FB_RGB(0xAA, 0x00, 0xAA),
/* 6 Cyan */ FB_RGB(0x00, 0xAA, 0xAA),
/* 7 White */ FB_RGB(0xAA, 0xAA, 0xAA),
/* 8 Br.Black */ FB_RGB(0x55, 0x55, 0x55),
/* 9 Br.Red */ FB_RGB(0xFF, 0x55, 0x55),
/* 10 Br.Green */ FB_RGB(0x55, 0xFF, 0x55),
/* 11 Br.Yellow */ FB_RGB(0xFF, 0xFF, 0x55),
/* 12 Br.Blue */ FB_RGB(0x55, 0x55, 0xFF),
/* 13 Br.Magenta */ FB_RGB(0xFF, 0x55, 0xFF),
/* 14 Br.Cyan */ FB_RGB(0x55, 0xFF, 0xFF),
/* 15 Br.White */ FB_RGB(0xFF, 0xFF, 0xFF),
};
/* -----------------------------------------------------------------------
* Module state
* --------------------------------------------------------------------- */
typedef struct {
volatile uint32_t *base; /* GOP framebuffer base (32-bit pixels) */
uint32_t stride; /* pixels_per_scanline */
uint32_t width; /* horizontal resolution in pixels */
uint32_t height; /* vertical resolution in pixels */
FbPixelFormat fmt; /* pixel format (BGRX vs RGBX) */
uint32_t scale; /* pixel scale: 1, 2, or 4 (hi-res) */
int ready;
} FbState;
static FbState g_fb;
/* -----------------------------------------------------------------------
* Internal helpers
* --------------------------------------------------------------------- */
/*
* Convert our 0x00RRGGBB to the hardware 32-bit word.
*
* GOP format names describe increasing byte address in memory (little-endian
* uint32 store), e.g. PixelRedGreenBlueReserved8BitPerColor means byte0=R,
* byte1=G, byte2=B, byte3=X -- which as a little-endian uint32 puts R in
* bits[7:0], G in bits[15:8], B in bits[23:16]. The two branches below were
* previously swapped relative to that (found via FABRIC.md item 4.3.1's
* orientation test: corners rendered with R and B channels exchanged).
*/
static uint32_t pack_pixel(uint32_t rgb)
{
uint32_t r = FB_R(rgb);
uint32_t g = FB_G(rgb);
uint32_t b = FB_B(rgb);
if (g_fb.fmt == FB_PIXEL_RGBX32) {
/* RGBX byte order R,G,B: byte0=R@[7:0] byte1=G@[15:8] byte2=B@[23:16] */
return (b << 16) | (g << 8) | r;
}
/* BGRX (default), byte order B,G,R: byte0=B@[7:0] byte1=G@[15:8] byte2=R@[23:16] */
return (r << 16) | (g << 8) | b;
}
/* -----------------------------------------------------------------------
* Lifecycle
* --------------------------------------------------------------------- */
void fb_init(const FramebufferInfo *info, FbPixelFormat fmt)
{
if (!info || !info->base || info->width == 0 || info->height == 0) {
return;
}
g_fb.base = (volatile uint32_t *)info->base;
g_fb.stride = info->pixels_per_scanline;
g_fb.width = info->width;
g_fb.height = info->height;
g_fb.fmt = fmt;
g_fb.ready = 1;
/*
* Auto-select scale factor based on resolution.
* ≥ 2560 wide → 2× (gives readable 16px cells on QHD/4K)
* ≥ 3840 wide → 4× (gives readable 32px cells on 4K at density)
* Otherwise → 1× (1080p and below)
*/
if (info->width >= 3840) {
g_fb.scale = 4;
} else if (info->width >= 2560) {
g_fb.scale = 2;
} else {
g_fb.scale = 1;
}
}
int fb_is_available(void) { return g_fb.ready; }
uint32_t fb_width(void) { return g_fb.width; }
uint32_t fb_height(void) { return g_fb.height; }
/** Effective character cell dimensions in pixels (accounts for scale). */
uint32_t fb_cell_w(void) { return 8u * g_fb.scale; }
uint32_t fb_cell_h(void) { return 16u * g_fb.scale; }
/* -----------------------------------------------------------------------
* Pixel-level primitives
* --------------------------------------------------------------------- */
void fb_put_pixel(uint32_t x, uint32_t y, uint32_t rgb)
{
if (!g_fb.ready || x >= g_fb.width || y >= g_fb.height) return;
g_fb.base[y * g_fb.stride + x] = pack_pixel(rgb);
}
void fb_fill_rect(uint32_t x, uint32_t y, uint32_t w, uint32_t h,
uint32_t rgb)
{
uint32_t px, py;
uint32_t packed;
uint32_t x_end, y_end;
if (!g_fb.ready) return;
packed = pack_pixel(rgb);
x_end = x + w;
y_end = y + h;
if (x_end > g_fb.width) x_end = g_fb.width;
if (y_end > g_fb.height) y_end = g_fb.height;
for (py = y; py < y_end; py++) {
volatile uint32_t *row = g_fb.base + py * g_fb.stride + x;
for (px = 0; px < (x_end - x); px++) {
row[px] = packed;
}
}
}
/* -----------------------------------------------------------------------
* Glyph rendering
* --------------------------------------------------------------------- */
/**
* Draw one 8×16 glyph at pixel position (px, py).
* Scales by g_fb.scale so the same font works at any resolution.
*/
void fb_draw_glyph(uint32_t px, uint32_t py, uint8_t ch,
uint32_t fg, uint32_t bg)
{
const uint8_t *glyph;
uint32_t row, col, sr, sc;
uint32_t pfg, pbg;
if (!g_fb.ready) return;
glyph = font_8x16_glyph(ch);
pfg = pack_pixel(fg);
pbg = pack_pixel(bg);
for (row = 0; row < 16u; row++) {
uint8_t bits = glyph[row];
for (col = 0; col < 8u; col++) {
uint32_t on = (bits >> (7u - col)) & 1u;
uint32_t packed = on ? pfg : pbg;
uint32_t base_x = px + col * g_fb.scale;
uint32_t base_y = py + row * g_fb.scale;
/* pixel block for scale > 1 */
for (sr = 0; sr < g_fb.scale; sr++) {
uint32_t fy = base_y + sr;
if (fy >= g_fb.height) continue;
volatile uint32_t *line =
g_fb.base + fy * g_fb.stride + base_x;
for (sc = 0; sc < g_fb.scale; sc++) {
if (base_x + sc < g_fb.width) {
line[sc] = packed;
}
}
}
}
}
}
/* -----------------------------------------------------------------------
* Boot diagnostic: orientation test pattern
* --------------------------------------------------------------------- */
/**
* fb_draw_orientation_test — one-time boot diagnostic (FABRIC.md item 4.3.1).
*
* Fills each raster corner with a distinct solid color so a screendump
* immediately reveals whether the image is flipped or rotated. Not part of
* the Console drawing fabric itself -- diagnostic-only, analogous to item
* 4.2's self-test scaffolding.
*
* Corner colors (raster/top-left-origin coordinates, pre-Cartesian-flip):
* top-left RED
* top-right GREEN
* bottom-left BLUE
* bottom-right YELLOW
*/
void fb_draw_orientation_test(void)
{
uint32_t block_w, block_h;
if (!g_fb.ready) return;
block_w = g_fb.width / 8u;
block_h = g_fb.height / 8u;
if (block_w == 0) block_w = 1;
if (block_h == 0) block_h = 1;
fb_fill_rect(0, 0, block_w, block_h, FB_RGB(0xFF, 0x00, 0x00)); /* top-left: RED */
fb_fill_rect(g_fb.width - block_w, 0, block_w, block_h, FB_RGB(0x00, 0xFF, 0x00)); /* top-right: GREEN */
fb_fill_rect(0, g_fb.height - block_h, block_w, block_h, FB_RGB(0x00, 0x00, 0xFF)); /* bottom-left: BLUE */
fb_fill_rect(g_fb.width - block_w, g_fb.height - block_h, block_w, block_h,
FB_RGB(0xFF, 0xFF, 0x00)); /* bottom-right: YELLOW */
}
/* -----------------------------------------------------------------------
* Scrolling
* --------------------------------------------------------------------- */
/**
* Scroll the framebuffer up by `char_rows` character rows.
* Each character row is (16 × scale) pixels tall.
* The vacated rows at the bottom are filled with bg.
*
* Uses a word-wide copy loop — no libc memmove dependency.
*/
void fb_scroll_rows(uint32_t char_rows, uint32_t bg)
{
uint32_t pixel_rows_to_scroll;
uint32_t src_y, dst_y;
uint32_t remaining_rows;
uint32_t x;
uint32_t packed_bg;
if (!g_fb.ready || char_rows == 0) return;
pixel_rows_to_scroll = char_rows * 16u * g_fb.scale;
if (pixel_rows_to_scroll >= g_fb.height) {
fb_fill_rect(0, 0, g_fb.width, g_fb.height, bg);
return;
}
packed_bg = pack_pixel(bg);
/* Copy pixel rows upward */
for (dst_y = 0; dst_y + pixel_rows_to_scroll < g_fb.height; dst_y++) {
src_y = dst_y + pixel_rows_to_scroll;
volatile uint32_t *src = g_fb.base + src_y * g_fb.stride;
volatile uint32_t *dst = g_fb.base + dst_y * g_fb.stride;
for (x = 0; x < g_fb.width; x++) {
dst[x] = src[x];
}
}
/* Clear the newly exposed rows at the bottom */
remaining_rows = g_fb.height - (g_fb.height - pixel_rows_to_scroll);
for (dst_y = g_fb.height - pixel_rows_to_scroll;
dst_y < g_fb.height; dst_y++) {
volatile uint32_t *row = g_fb.base + dst_y * g_fb.stride;
for (x = 0; x < g_fb.width; x++) {
row[x] = packed_bg;
}
}
(void)remaining_rows; /* calculated inline above */
}