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.
This commit is contained in:
Robert Allan James
2026-08-07 11:38:33 -04:00
parent f3acfb9b47
commit ab96ac0970
11 changed files with 54516 additions and 15 deletions
+33 -2
View File
@@ -3652,15 +3652,46 @@ document and committing that amendment as its own item.*
> scrolling, cursor/VT100 semantics, the Hermes message protocol, and Console as a fleet
> VM under Hera's birth protocol — later 4.3.x items, scoped once this slice is reviewed.
- [ ] **4.3.1 — Framebuffer sanity: draw a test pattern.** Confirm `framebuffer.c` is wired
- [x] **4.3.1 — Framebuffer sanity: draw a test pattern.** Confirm `framebuffer.c` is wired
to the real UEFI GOP `BootInfo` and draw a simple orientation-revealing test pattern, using
the existing raw pixel primitives only — no coordinate/Z machinery yet. *Refs:* §27.1.
- [ ] **4.3.2 — QEMU screenshot capability.** Add a monitor/QMP socket to the `qemu` targets
> **Done, 2026-08-07.** `fb_draw_orientation_test()` added to `framebuffer.c`/`.h` — fills
> the four raster corners RED/GREEN/BLUE/YELLOW via `fb_fill_rect` only. Wired into
> `kernel_main.c` calling `fb_init()` directly; `console_fb_init()`/`vt100_init()` removed
> from the boot path per Captain Bob's direction (vt100.c/console.c are obsolete, superseded
> by the fabric redesign, not to be exercised even incidentally).
>
> **Bug found and fixed, not scope creep — the diagnostic did its job.** First screendump
> (via 4.3.2) showed a clean R↔B channel swap (G correct, R and B corners exchanged) —
> spatial placement was correct, so this ruled out flip/rotation but caught a real color
> bug: `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 (pre-existing bug, not
> introduced this item). Fixed by swapping the two `pack_pixel` return bodies to match
> `framebuffer.h`'s already-correct doc comments; `kernel_main.c`'s GOP-format `switch`
> needed no change. Re-verified via a second screendump: all four corners render correctly
> (`fb/qemu-screenshot-20260807-113612.png`).
>
> Not addressed, not in scope: the pre-existing UEFI loader boot-log text remains visible
> behind the corner blocks, since this diagnostic paints four small rectangles and does not
> clear the framebuffer — expected, not a bug.
- [x] **4.3.2 — QEMU screenshot capability.** Add a monitor/QMP socket to the `qemu` targets
(mirroring the existing serial-socket pattern) so `screendump` can be issued and the 4.3.1
test pattern actually inspected. None exists today — all three targets currently run with
`-display none` and no monitor attached. *Refs:* §27.2.
> **Done, 2026-08-07 — mechanism already existed, didn't need building.**
> `scripts/qemu_screenshot.sh` was already a complete, working amd64 screendump path
> (monitor UNIX socket + `socat` + HMP `screendump`), just not wired into any
> `Makefile.starkernel` target and not previously exercised this session — 34 prior
> screenshots already sat in `logs/` from earlier use. Changed: output PNG now goes to a
> new top-level `fb/` directory (tracked in git, per Captain Bob — not `logs/`, not a
> gitignored temp dir); added a `python3`+PIL fallback for PPM→PNG conversion since
> `imagemagick` isn't installed on this machine. Left as a standalone script, not wired into
> a Makefile target, per direction — run directly for now. aarch64/riscv64 not covered by
> this script; not needed for 4.3.1's amd64-only diagnostic.
- [ ] **4.3.3 — Cartesian coordinate machinery.** Origin bottom-left `(0, 0)`, Y-up, plus a
new Z axis (depth-into-screen, not height) and a fixed orthographic projection as a
placeholder — not the final projection, no perspective/camera work yet. Angle open: true
+1 -1
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-08-07T05:36:57Z -->
<!-- Generated by mkcapsule --manifest 2026-08-07T14:56:31Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

+11
View File
@@ -84,6 +84,17 @@ void fb_fill_rect(uint32_t x, uint32_t y, uint32_t w, uint32_t h,
void fb_draw_glyph(uint32_t px, uint32_t py, uint8_t ch,
uint32_t fg, uint32_t bg);
/* -----------------------------------------------------------------------
* Boot diagnostic
* --------------------------------------------------------------------- */
/**
* One-time boot diagnostic (FABRIC.md item 4.3.1): fills each raster corner
* with a distinct solid color so a screendump reveals orientation. Not part
* of the Console drawing fabric -- diagnostic-only.
*/
void fb_draw_orientation_test(void);
/* -----------------------------------------------------------------------
* Scrolling
* --------------------------------------------------------------------- */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+13 -6
View File
@@ -9,8 +9,9 @@
# Makefile target which already built the ISO during the serial acceptance run).
#
# Outputs:
# <BUILD_DIR>/screenshot.ppm — raw PPM from QEMU screendump
# <LOG_DIR>/qemu-screenshot-YYYYMMDD-HHMMSS.{log,png} — serial log + PNG
# <BUILD_DIR>/screenshot.ppm — raw PPM from QEMU screendump
# <LOG_DIR>/qemu-screenshot-YYYYMMDD-HHMMSS.log — serial log
# fb/qemu-screenshot-YYYYMMDD-HHMMSS.png — PNG (tracked in git, not build/)
#
# Requires: qemu-system-x86_64, socat, xorriso, mtools, ovmf, imagemagick
@@ -61,14 +62,15 @@ fi
# --------------------------------------------------------------------------
# Setup paths
# --------------------------------------------------------------------------
mkdir -p "${LOG_DIR}" "${BUILD_DIR}"
FB_DIR="fb"
mkdir -p "${LOG_DIR}" "${BUILD_DIR}" "${FB_DIR}"
TS=$(date +%Y%m%d-%H%M%S)
SERIAL_LOG="${LOG_DIR}/qemu-screenshot-${TS}.log"
VARS_COPY="${BUILD_DIR}/OVMF_VARS_screenshot.fd"
MONITOR_SOCK="${BUILD_DIR}/qemu-monitor-${TS}.sock"
PPM_OUT="${BUILD_DIR}/screenshot.ppm"
PNG_LOG="${LOG_DIR}/qemu-screenshot-${TS}.png" # committed artifact
PNG_LOG="${FB_DIR}/qemu-screenshot-${TS}.png" # committed artifact
QEMU_PID_FILE="${BUILD_DIR}/qemu-screenshot.pid"
cp "${OVMF_VARS_RO}" "${VARS_COPY}"
@@ -144,9 +146,14 @@ if [ -f "${PPM_OUT}" ]; then
convert "${PPM_OUT}" "${PNG_LOG}"
rm -f "${PPM_OUT}"
echo " PNG : ${PNG_LOG}"
elif command -v python3 >/dev/null 2>&1 && python3 -c "import PIL" >/dev/null 2>&1; then
python3 -c "from PIL import Image; Image.open('${PPM_OUT}').save('${PNG_LOG}')"
rm -f "${PPM_OUT}"
echo " PNG : ${PNG_LOG} (via python3/PIL)"
else
echo " WARN: imagemagick 'convert' not found — PPM only (install imagemagick)"
PNG_LOG="${PPM_OUT}"
echo " WARN: no PPM->PNG converter found (imagemagick 'convert' or python3+PIL) — PPM only"
PNG_LOG="${FB_DIR}/$(basename "${PNG_LOG}" .png).ppm"
mv "${PPM_OUT}" "${PNG_LOG}"
fi
echo ""
echo "=== Screendump complete ==="
+50 -4
View File
@@ -66,17 +66,27 @@ static FbState g_fb;
* Internal helpers
* --------------------------------------------------------------------- */
/* Convert our 0x00RRGGBB to the hardware BGRX / RGBX 32-bit word */
/*
* 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) {
return (r << 16) | (g << 8) | b; /* RGBX: R@[23:16] */
/* 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): B@[23:16] G@[15:8] R@[7:0] */
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;
}
/* -----------------------------------------------------------------------
@@ -197,6 +207,42 @@ void fb_draw_glyph(uint32_t px, uint32_t py, uint8_t ch,
}
}
/* -----------------------------------------------------------------------
* 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
* --------------------------------------------------------------------- */
+9 -2
View File
@@ -796,7 +796,13 @@ static void kernel_main_deep(BootInfo *boot_info) {
if (mama->halted) goto idle;
#endif
/* Activate framebuffer VT100 now that POST is done — REPL-only */
/*
* FABRIC.md item 4.3.1 -- raw framebuffer boot diagnostic.
* console.c / vt100.c are superseded by the Console drawing-fabric
* redesign (FABRIC.md §27) and are deliberately not invoked here.
* fb_init() wires the raw GOP framebuffer directly; the orientation
* test pattern is drawn on a blank framebuffer, nothing else touches it.
*/
if (boot_info->framebuffer.base != NULL && boot_info->framebuffer.size > 0) {
FbPixelFormat fb_fmt;
switch (boot_info->framebuffer.pixel_format) {
@@ -804,7 +810,8 @@ static void kernel_main_deep(BootInfo *boot_info) {
case (UINT32)PixelBlueGreenRedReserved8BitPerColor: fb_fmt = FB_PIXEL_BGRX32; break;
default: fb_fmt = FB_PIXEL_BGRX32; break;
}
console_fb_init(&boot_info->framebuffer, fb_fmt);
fb_init(&boot_info->framebuffer, fb_fmt);
fb_draw_orientation_test();
}
/* Clear reboot-tries counter: we reached the REPL cleanly */