implements PLN-0069

This commit is contained in:
bQUARKz 2026-05-23 19:21:02 +01:00
parent 668a061964
commit c4c2dfb017
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
4 changed files with 65 additions and 65 deletions

View File

@ -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<u16>,
front: Vec<u32>,
/// Back buffer: the working buffer where canonical game frames are composed
back: Vec<u16>,
back: Vec<u32>,
/// Shared access to graphical memory banks (tiles and palettes).
pub glyph_banks: Arc<dyn GlyphBankPoolAccess>,
@ -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]

View File

@ -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);

View File

@ -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":[]}

View File

@ -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]