From c4c2dfb017fa5f15f27efe072005e264028b0049 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Sat, 23 May 2026 19:21:02 +0100 Subject: [PATCH] implements PLN-0069 --- crates/console/prometeu-drivers/src/gfx.rs | 124 +++++++++--------- crates/console/prometeu-hal/src/gfx_bridge.rs | 2 +- discussion/index.ndjson | 2 +- ...9-rgba8888-cpu-renderer-and-composition.md | 2 +- 4 files changed, 65 insertions(+), 65 deletions(-) diff --git a/crates/console/prometeu-drivers/src/gfx.rs b/crates/console/prometeu-drivers/src/gfx.rs index 14313a16..1c708e3e 100644 --- a/crates/console/prometeu-drivers/src/gfx.rs +++ b/crates/console/prometeu-drivers/src/gfx.rs @@ -40,9 +40,9 @@ pub struct Gfx { /// Height of the internal framebuffer in pixels. h: usize, /// Front buffer: the "VRAM" currently being displayed by the Host window. - front: Vec, + front: Vec, /// Back buffer: the working buffer where canonical game frames are composed - back: Vec, + back: Vec, /// Shared access to graphical memory banks (tiles and palettes). pub glyph_banks: Arc, @@ -67,7 +67,7 @@ pub struct Gfx { // const GLYPH_UNKNOWN: [u8; 5] = [0x7, 0x7, 0x7, 0x7, 0x7]; struct RenderTarget<'a> { - back: &'a mut [u16], + back: &'a mut [u32], screen_w: usize, screen_h: usize, } @@ -206,7 +206,7 @@ impl GfxBridge for Gfx { fn size(&self) -> (usize, usize) { self.size() } - fn front_buffer(&self) -> &[u16] { + fn front_buffer(&self) -> &[u32] { self.front_buffer() } fn clear(&mut self, color: Color) { @@ -375,13 +375,13 @@ impl Gfx { (self.w, self.h) } - /// The buffer that the host should display (RGB565). - pub fn front_buffer(&self) -> &[u16] { + /// The buffer that the host should display (RGBA8888 in RGBA raw order). + pub fn front_buffer(&self) -> &[u32] { &self.front } pub fn clear(&mut self, color: Color) { - self.back.fill(color.to_rgb565_lossy()); + self.back.fill(color.raw()); } /// Rectangle with blend mode. @@ -394,7 +394,7 @@ impl Gfx { color: Color, mode: BlendMode, ) { - if color == Color::COLOR_KEY { + if color.alpha() == 0 { return; } @@ -406,14 +406,14 @@ impl Gfx { let x1 = (x + w).clamp(0, fw); let y1 = (y + h).clamp(0, fh); - let src = color.to_rgb565_lossy(); + let src = color.raw(); for yy in y0..y1 { let row = (yy as usize) * self.w; for xx in x0..x1 { let idx = row + (xx as usize); let dst = self.back[idx]; - self.back[idx] = blend_rgb565(dst, src, mode); + self.back[idx] = blend_rgba8888(dst, src, mode); } } } @@ -425,17 +425,17 @@ impl Gfx { /// Draws a single pixel. pub fn draw_pixel(&mut self, x: i32, y: i32, color: Color) { - if color == Color::COLOR_KEY { + if color.alpha() == 0 { return; } if x >= 0 && x < self.w as i32 && y >= 0 && y < self.h as i32 { - self.back[y as usize * self.w + x as usize] = color.to_rgb565_lossy(); + self.back[y as usize * self.w + x as usize] = color.raw(); } } /// Draws a line between two points using Bresenham's algorithm. pub fn draw_line(&mut self, x0: i32, y0: i32, x1: i32, y1: i32, color: Color) { - if color == Color::COLOR_KEY { + if color.alpha() == 0 { return; } @@ -467,7 +467,7 @@ impl Gfx { /// Draws a circle outline using Midpoint Circle Algorithm. pub fn draw_circle(&mut self, xc: i32, yc: i32, r: i32, color: Color) { - if color == Color::COLOR_KEY { + if color.alpha() == 0 { return; } @@ -503,7 +503,7 @@ impl Gfx { /// Draws a filled circle. pub fn fill_circle(&mut self, xc: i32, yc: i32, r: i32, color: Color) { - if color == Color::COLOR_KEY { + if color.alpha() == 0 { return; } @@ -541,7 +541,7 @@ impl Gfx { /// Draws a rectangle outline. pub fn draw_rect(&mut self, x: i32, y: i32, w: i32, h: i32, color: Color) { - if color == Color::COLOR_KEY { + if color.alpha() == 0 { return; } @@ -569,7 +569,7 @@ impl Gfx { } fn draw_horizontal_line(&mut self, x0: i32, x1: i32, y: i32, color: Color) { - if color == Color::COLOR_KEY { + if color.alpha() == 0 { return; } @@ -582,12 +582,12 @@ impl Gfx { return; } for x in start..=end { - self.back[y as usize * self.w + x as usize] = color.to_rgb565_lossy(); + self.back[y as usize * self.w + x as usize] = color.raw(); } } fn draw_vertical_line(&mut self, x: i32, y0: i32, y1: i32, color: Color) { - if color == Color::COLOR_KEY { + if color.alpha() == 0 { return; } @@ -600,7 +600,7 @@ impl Gfx { return; } for y in start..=end { - self.back[y as usize * self.w + x as usize] = color.to_rgb565_lossy(); + self.back[y as usize * self.w + x as usize] = color.raw(); } } @@ -657,7 +657,7 @@ impl Gfx { update: &ResolverUpdate, resolved_glyph_slots: &[usize; 4], ) { - self.back.fill(Color::BLACK.to_rgb565_lossy()); + self.back.fill(Color::BLACK.raw()); self.populate_layer_buckets(); for (layer_index, glyph_slot) in @@ -703,7 +703,7 @@ impl Gfx { } fn draw_cache_layer_to_buffer( - back: &mut [u16], + back: &mut [u32], screen_w: usize, screen_h: usize, cache: &SceneViewportCache, @@ -779,13 +779,13 @@ impl Gfx { } let color = tile.bank.resolve_color(tile.entry.palette_id, px_index); - target.back[world_y as usize * target.screen_w + world_x as usize] = color.to_rgb565_lossy(); + target.back[world_y as usize * target.screen_w + world_x as usize] = color.raw(); } } } fn draw_bucket_on_buffer( - back: &mut [u16], + back: &mut [u32], screen_w: usize, screen_h: usize, bucket: &[usize], @@ -802,7 +802,7 @@ impl Gfx { } fn draw_sprite_pixel_by_pixel( - back: &mut [u16], + back: &mut [u32], screen_w: usize, screen_h: usize, sprite: &Sprite, @@ -834,31 +834,32 @@ impl Gfx { // 3. Resolve color via palette (from the tile inside the sprite) let color = bank.resolve_color(sprite.glyph.palette_id, px_index); - back[world_y as usize * screen_w + world_x as usize] = color.to_rgb565_lossy(); + back[world_y as usize * screen_w + world_x as usize] = color.raw(); } } } /// Applies the fade effect to the entire back buffer. /// level: 0 (full color) to 31 (visible) - fn apply_fade_to_buffer(back: &mut [u16], level: u8, fade_color: Color) { + fn apply_fade_to_buffer(back: &mut [u32], level: u8, fade_color: Color) { if level >= 31 { return; } // Fully visible, skip processing let weight = level as u16; let inv_weight = 31 - weight; - let (fr, fg, fb) = unpack_rgb565_native(fade_color.to_rgb565_lossy()); + let (fr, fg, fb, fa) = unpack_rgba8888(fade_color.raw()); for px in back.iter_mut() { - let (sr, sg, sb) = unpack_rgb565_native(*px); + let (sr, sg, sb, sa) = unpack_rgba8888(*px); // Formula: (src * weight + fade * inv_weight) / 31 let r = ((sr as u16 * weight + fr as u16 * inv_weight) / 31) as u8; let g = ((sg as u16 * weight + fg as u16 * inv_weight) / 31) as u8; let b = ((sb as u16 * weight + fb as u16 * inv_weight) / 31) as u8; + let a = ((sa as u16 * weight + fa as u16 * inv_weight) / 31) as u8; - *px = pack_rgb565_native(r, g, b); + *px = pack_rgba8888(r, g, b, a); } } @@ -885,7 +886,7 @@ impl Gfx { } fn draw_char(&mut self, x: i32, y: i32, c: char, color: Color) { - if color == Color::COLOR_KEY { + if color.alpha() == 0 { return; } @@ -900,7 +901,7 @@ impl Gfx { } let glyph = glyph_for_char(c); - let raw = color.to_rgb565_lossy(); + let raw = color.raw(); let row_start = (-y).clamp(0, glyph_h) as usize; let row_end = (screen_h - y).clamp(0, glyph_h) as usize; @@ -925,65 +926,64 @@ impl Gfx { } } -/// Blends in RGB565 per channel with saturation. -/// `dst` and `src` are RGB565 pixels (u16). -fn blend_rgb565(dst: u16, src: u16, mode: BlendMode) -> u16 { +fn blend_rgba8888(dst: u32, src: u32, mode: BlendMode) -> u32 { match mode { BlendMode::None => src, BlendMode::Half => { - let (dr, dg, db) = unpack_rgb565_native(dst); - let (sr, sg, sb) = unpack_rgb565_native(src); + let (dr, dg, db, da) = unpack_rgba8888(dst); + let (sr, sg, sb, sa) = unpack_rgba8888(src); let r = ((dr as u16 + sr as u16) >> 1) as u8; let g = ((dg as u16 + sg as u16) >> 1) as u8; let b = ((db as u16 + sb as u16) >> 1) as u8; - pack_rgb565_native(r, g, b) + let a = ((da as u16 + sa as u16) >> 1) as u8; + pack_rgba8888(r, g, b, a) } BlendMode::HalfPlus => { - let (dr, dg, db) = unpack_rgb565_native(dst); - let (sr, sg, sb) = unpack_rgb565_native(src); + let (dr, dg, db, da) = unpack_rgba8888(dst); + let (sr, sg, sb, sa) = unpack_rgba8888(src); - let r = (dr as u16 + ((sr as u16) >> 1)).min(31) as u8; - let g = (dg as u16 + ((sg as u16) >> 1)).min(63) as u8; - let b = (db as u16 + ((sb as u16) >> 1)).min(31) as u8; + let r = (dr as u16 + ((sr as u16) >> 1)).min(255) as u8; + let g = (dg as u16 + ((sg as u16) >> 1)).min(255) as u8; + let b = (db as u16 + ((sb as u16) >> 1)).min(255) as u8; + let a = (da as u16 + ((sa as u16) >> 1)).min(255) as u8; - pack_rgb565_native(r, g, b) + pack_rgba8888(r, g, b, a) } BlendMode::HalfMinus => { - let (dr, dg, db) = unpack_rgb565_native(dst); - let (sr, sg, sb) = unpack_rgb565_native(src); + let (dr, dg, db, da) = unpack_rgba8888(dst); + let (sr, sg, sb, sa) = unpack_rgba8888(src); let r = (dr as i16 - ((sr as i16) >> 1)).max(0) as u8; let g = (dg as i16 - ((sg as i16) >> 1)).max(0) as u8; let b = (db as i16 - ((sb as i16) >> 1)).max(0) as u8; + let a = (da as i16 - ((sa as i16) >> 1)).max(0) as u8; - pack_rgb565_native(r, g, b) + pack_rgba8888(r, g, b, a) } BlendMode::Full => { - let (dr, dg, db) = unpack_rgb565_native(dst); - let (sr, sg, sb) = unpack_rgb565_native(src); + let (dr, dg, db, da) = unpack_rgba8888(dst); + let (sr, sg, sb, sa) = unpack_rgba8888(src); - let r = (dr as u16 + sr as u16).min(31) as u8; - let g = (dg as u16 + sg as u16).min(63) as u8; - let b = (db as u16 + sb as u16).min(31) as u8; + let r = (dr as u16 + sr as u16).min(255) as u8; + let g = (dg as u16 + sg as u16).min(255) as u8; + let b = (db as u16 + sb as u16).min(255) as u8; + let a = (da as u16 + sa as u16).min(255) as u8; - pack_rgb565_native(r, g, b) + pack_rgba8888(r, g, b, a) } } } -fn unpack_rgb565_native(px: u16) -> (u8, u8, u8) { - let r = ((px >> 11) & 0x1F) as u8; - let g = ((px >> 5) & 0x3F) as u8; - let b = (px & 0x1F) as u8; - (r, g, b) +fn unpack_rgba8888(px: u32) -> (u8, u8, u8, u8) { + Color::unpack_to_native(px) } -fn pack_rgb565_native(r: u8, g: u8, b: u8) -> u16 { - ((r as u16 & 0x1F) << 11) | ((g as u16 & 0x3F) << 5) | (b as u16 & 0x1F) +fn pack_rgba8888(r: u8, g: u8, b: u8, a: u8) -> u32 { + Color::pack_from_native(r, g, b, a) } #[cfg(test)] @@ -1144,7 +1144,7 @@ mod tests { gfx.hud_fade_level = 31; gfx.render_scene_from_cache(&cache, &update, &[0, 0, 0, 0]); - assert_eq!(gfx.back[0], Color::RED.to_rgb565_lossy()); + assert_eq!(gfx.back[0], Color::RED.raw()); } #[test] @@ -1195,7 +1195,7 @@ mod tests { gfx.render_scene_from_cache(&cache, &update, &[0, 0, 0, 0]); - assert_eq!(gfx.back[0], Color::BLUE.to_rgb565_lossy()); + assert_eq!(gfx.back[0], Color::BLUE.raw()); } #[test] diff --git a/crates/console/prometeu-hal/src/gfx_bridge.rs b/crates/console/prometeu-hal/src/gfx_bridge.rs index 34dd1465..ad0b7424 100644 --- a/crates/console/prometeu-hal/src/gfx_bridge.rs +++ b/crates/console/prometeu-hal/src/gfx_bridge.rs @@ -24,7 +24,7 @@ pub enum GfxOpStatus { pub trait GfxBridge { fn size(&self) -> (usize, usize); - fn front_buffer(&self) -> &[u16]; + fn front_buffer(&self) -> &[u32]; fn clear(&mut self, color: Color); fn fill_rect_blend(&mut self, x: i32, y: i32, w: i32, h: i32, color: Color, mode: BlendMode); fn fill_rect(&mut self, x: i32, y: i32, w: i32, h: i32, color: Color); diff --git a/discussion/index.ndjson b/discussion/index.ndjson index 15765a48..411b9717 100644 --- a/discussion/index.ndjson +++ b/discussion/index.ndjson @@ -35,4 +35,4 @@ {"type":"discussion","id":"DSC-0032","status":"done","ticket":"system-os-lifecycle-process-task-contract","title":"Agenda - SystemOS Lifecycle, Process and Task Contract","created_at":"2026-05-14","updated_at":"2026-05-15","tags":["runtime","os","lifecycle","process","task","shell","firmware"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0041","file":"discussion/lessons/DSC-0032-system-os-lifecycle-process-task-contract/LSN-0041-systemos-lifecycle-authority.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0033","status":"done","ticket":"system-os-service-ownership-and-module-layout","title":"Agenda - SystemOS Service Ownership and Module Layout","created_at":"2026-05-14","updated_at":"2026-05-15","tags":["runtime","os","services","module-layout","vm","window-manager","logging"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0042","file":"discussion/lessons/DSC-0033-system-os-service-ownership-and-module-layout/LSN-0042-systemos-service-ownership-boundary.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]} {"type":"discussion","id":"DSC-0036","status":"done","ticket":"prometeu-hub-ui-direction","title":"Agenda - Prometeu Hub UI Direction","created_at":"2026-05-15","updated_at":"2026-05-22","tags":["hub","ui","shell","system-apps","lifecycle","design-system"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0045","file":"discussion/lessons/DSC-0036-prometeu-hub-ui-direction/LSN-0045-hub-ui-slices-should-prove-os-boundaries.md","status":"done","created_at":"2026-05-22","updated_at":"2026-05-22"}]} -{"type":"discussion","id":"DSC-0037","status":"in_progress","ticket":"rgba8888-framebuffer-and-pixel-format-direction","title":"Agenda - RGBA8888 Framebuffer and Pixel Format Direction","created_at":"2026-05-22","updated_at":"2026-05-23","tags":["gfx","framebuffer","rgb565","rgba8888","renderer","assets","host","backend"],"agendas":[{"id":"AGD-0037","file":"AGD-0037-rgba8888-framebuffer-and-pixel-format-direction.md","status":"accepted","created_at":"2026-05-22","updated_at":"2026-05-23"}],"decisions":[{"id":"DEC-0029","file":"DEC-0029-rgba8888-runtime-pixel-format-contract.md","status":"accepted","created_at":"2026-05-23","updated_at":"2026-05-23","ref_agenda":"AGD-0037"}],"plans":[{"id":"PLN-0067","file":"PLN-0067-rgba8888-published-contracts-and-specs.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0068","file":"PLN-0068-rgba8888-color-type-hal-and-abi-surface.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0069","file":"PLN-0069-rgba8888-cpu-renderer-and-composition.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0070","file":"PLN-0070-rgba8888-asset-palettes-tooling-and-fixtures.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0071","file":"PLN-0071-rgba8888-host-presentation-path.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0072","file":"PLN-0072-rgba8888-residue-removal-and-end-to-end-validation.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]}],"lessons":[]} +{"type":"discussion","id":"DSC-0037","status":"in_progress","ticket":"rgba8888-framebuffer-and-pixel-format-direction","title":"Agenda - RGBA8888 Framebuffer and Pixel Format Direction","created_at":"2026-05-22","updated_at":"2026-05-23","tags":["gfx","framebuffer","rgb565","rgba8888","renderer","assets","host","backend"],"agendas":[{"id":"AGD-0037","file":"AGD-0037-rgba8888-framebuffer-and-pixel-format-direction.md","status":"accepted","created_at":"2026-05-22","updated_at":"2026-05-23"}],"decisions":[{"id":"DEC-0029","file":"DEC-0029-rgba8888-runtime-pixel-format-contract.md","status":"accepted","created_at":"2026-05-23","updated_at":"2026-05-23","ref_agenda":"AGD-0037"}],"plans":[{"id":"PLN-0067","file":"PLN-0067-rgba8888-published-contracts-and-specs.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0068","file":"PLN-0068-rgba8888-color-type-hal-and-abi-surface.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0069","file":"PLN-0069-rgba8888-cpu-renderer-and-composition.md","status":"done","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0070","file":"PLN-0070-rgba8888-asset-palettes-tooling-and-fixtures.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0071","file":"PLN-0071-rgba8888-host-presentation-path.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]},{"id":"PLN-0072","file":"PLN-0072-rgba8888-residue-removal-and-end-to-end-validation.md","status":"open","created_at":"2026-05-23","updated_at":"2026-05-23","ref_decisions":["DEC-0029"]}],"lessons":[]} diff --git a/discussion/workflow/plans/PLN-0069-rgba8888-cpu-renderer-and-composition.md b/discussion/workflow/plans/PLN-0069-rgba8888-cpu-renderer-and-composition.md index 727994b6..85176692 100644 --- a/discussion/workflow/plans/PLN-0069-rgba8888-cpu-renderer-and-composition.md +++ b/discussion/workflow/plans/PLN-0069-rgba8888-cpu-renderer-and-composition.md @@ -2,7 +2,7 @@ id: PLN-0069 ticket: rgba8888-framebuffer-and-pixel-format-direction title: RGBA8888 CPU Renderer and Composition -status: open +status: done created: 2026-05-23 ref_decisions: [DEC-0029] tags: [gfx, framebuffer, rgb565, rgba8888, renderer, assets, host, backend]