implements PLN-0068

This commit is contained in:
bQUARKz 2026-05-23 19:19:28 +01:00
parent 1256d33a72
commit 668a061964
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
12 changed files with 121 additions and 121 deletions

View File

@ -658,7 +658,7 @@ impl AssetManager {
let offset = (p * 16 + c) * 2; let offset = (p * 16 + c) * 2;
let color_raw = let color_raw =
u16::from_le_bytes([palette_data[offset], palette_data[offset + 1]]); u16::from_le_bytes([palette_data[offset], palette_data[offset + 1]]);
*slot = Color(color_raw); *slot = Color::from_rgb565_lossy(color_raw);
} }
} }
@ -690,7 +690,7 @@ impl AssetManager {
let offset = (p * 16 + c) * 2; let offset = (p * 16 + c) * 2;
let color_raw = let color_raw =
u16::from_le_bytes([palette_data[offset], palette_data[offset + 1]]); u16::from_le_bytes([palette_data[offset], palette_data[offset + 1]]);
*slot = Color(color_raw); *slot = Color::from_rgb565_lossy(color_raw);
} }
} }
@ -1313,7 +1313,7 @@ mod tests {
AssetManager::decode_glyph_bank_from_buffer(&entry, &data).expect("glyph decode"); AssetManager::decode_glyph_bank_from_buffer(&entry, &data).expect("glyph decode");
assert_eq!(bank.pixel_indices, vec![1, 0, 2, 3]); assert_eq!(bank.pixel_indices, vec![1, 0, 2, 3]);
assert_eq!(bank.palettes[0][0], Color(0x1234)); assert_eq!(bank.palettes[0][0], Color::from_rgb565_lossy(0x1234));
} }
#[test] #[test]

View File

@ -381,7 +381,7 @@ impl Gfx {
} }
pub fn clear(&mut self, color: Color) { pub fn clear(&mut self, color: Color) {
self.back.fill(color.0); self.back.fill(color.to_rgb565_lossy());
} }
/// Rectangle with blend mode. /// Rectangle with blend mode.
@ -406,7 +406,7 @@ impl Gfx {
let x1 = (x + w).clamp(0, fw); let x1 = (x + w).clamp(0, fw);
let y1 = (y + h).clamp(0, fh); let y1 = (y + h).clamp(0, fh);
let src = color.0; let src = color.to_rgb565_lossy();
for yy in y0..y1 { for yy in y0..y1 {
let row = (yy as usize) * self.w; let row = (yy as usize) * self.w;
@ -429,7 +429,7 @@ impl Gfx {
return; return;
} }
if x >= 0 && x < self.w as i32 && y >= 0 && y < self.h as i32 { 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.0; self.back[y as usize * self.w + x as usize] = color.to_rgb565_lossy();
} }
} }
@ -582,7 +582,7 @@ impl Gfx {
return; return;
} }
for x in start..=end { for x in start..=end {
self.back[y as usize * self.w + x as usize] = color.0; self.back[y as usize * self.w + x as usize] = color.to_rgb565_lossy();
} }
} }
@ -600,7 +600,7 @@ impl Gfx {
return; return;
} }
for y in start..=end { for y in start..=end {
self.back[y as usize * self.w + x as usize] = color.0; self.back[y as usize * self.w + x as usize] = color.to_rgb565_lossy();
} }
} }
@ -657,7 +657,7 @@ impl Gfx {
update: &ResolverUpdate, update: &ResolverUpdate,
resolved_glyph_slots: &[usize; 4], resolved_glyph_slots: &[usize; 4],
) { ) {
self.back.fill(Color::BLACK.raw()); self.back.fill(Color::BLACK.to_rgb565_lossy());
self.populate_layer_buckets(); self.populate_layer_buckets();
for (layer_index, glyph_slot) in for (layer_index, glyph_slot) in
@ -779,7 +779,7 @@ impl Gfx {
} }
let color = tile.bank.resolve_color(tile.entry.palette_id, px_index); 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.raw(); target.back[world_y as usize * target.screen_w + world_x as usize] = color.to_rgb565_lossy();
} }
} }
} }
@ -834,7 +834,7 @@ impl Gfx {
// 3. Resolve color via palette (from the tile inside the sprite) // 3. Resolve color via palette (from the tile inside the sprite)
let color = bank.resolve_color(sprite.glyph.palette_id, px_index); let color = bank.resolve_color(sprite.glyph.palette_id, px_index);
back[world_y as usize * screen_w + world_x as usize] = color.raw(); back[world_y as usize * screen_w + world_x as usize] = color.to_rgb565_lossy();
} }
} }
} }
@ -848,17 +848,17 @@ impl Gfx {
let weight = level as u16; let weight = level as u16;
let inv_weight = 31 - weight; let inv_weight = 31 - weight;
let (fr, fg, fb) = Color::unpack_to_native(fade_color.0); let (fr, fg, fb) = unpack_rgb565_native(fade_color.to_rgb565_lossy());
for px in back.iter_mut() { for px in back.iter_mut() {
let (sr, sg, sb) = Color::unpack_to_native(*px); let (sr, sg, sb) = unpack_rgb565_native(*px);
// Formula: (src * weight + fade * inv_weight) / 31 // Formula: (src * weight + fade * inv_weight) / 31
let r = ((sr as u16 * weight + fr as u16 * inv_weight) / 31) as u8; 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 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 b = ((sb as u16 * weight + fb as u16 * inv_weight) / 31) as u8;
*px = Color::pack_from_native(r, g, b); *px = pack_rgb565_native(r, g, b);
} }
} }
@ -900,7 +900,7 @@ impl Gfx {
} }
let glyph = glyph_for_char(c); let glyph = glyph_for_char(c);
let raw = color.0; let raw = color.to_rgb565_lossy();
let row_start = (-y).clamp(0, glyph_h) as usize; let row_start = (-y).clamp(0, glyph_h) as usize;
let row_end = (screen_h - y).clamp(0, glyph_h) as usize; let row_end = (screen_h - y).clamp(0, glyph_h) as usize;
@ -932,49 +932,60 @@ fn blend_rgb565(dst: u16, src: u16, mode: BlendMode) -> u16 {
BlendMode::None => src, BlendMode::None => src,
BlendMode::Half => { BlendMode::Half => {
let (dr, dg, db) = Color::unpack_to_native(dst); let (dr, dg, db) = unpack_rgb565_native(dst);
let (sr, sg, sb) = Color::unpack_to_native(src); let (sr, sg, sb) = unpack_rgb565_native(src);
let r = ((dr as u16 + sr as u16) >> 1) as u8; let r = ((dr as u16 + sr as u16) >> 1) as u8;
let g = ((dg as u16 + sg 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; let b = ((db as u16 + sb as u16) >> 1) as u8;
Color::pack_from_native(r, g, b) pack_rgb565_native(r, g, b)
} }
BlendMode::HalfPlus => { BlendMode::HalfPlus => {
let (dr, dg, db) = Color::unpack_to_native(dst); let (dr, dg, db) = unpack_rgb565_native(dst);
let (sr, sg, sb) = Color::unpack_to_native(src); let (sr, sg, sb) = unpack_rgb565_native(src);
let r = (dr as u16 + ((sr as u16) >> 1)).min(31) as u8; 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 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 b = (db as u16 + ((sb as u16) >> 1)).min(31) as u8;
Color::pack_from_native(r, g, b) pack_rgb565_native(r, g, b)
} }
BlendMode::HalfMinus => { BlendMode::HalfMinus => {
let (dr, dg, db) = Color::unpack_to_native(dst); let (dr, dg, db) = unpack_rgb565_native(dst);
let (sr, sg, sb) = Color::unpack_to_native(src); let (sr, sg, sb) = unpack_rgb565_native(src);
let r = (dr as i16 - ((sr as i16) >> 1)).max(0) as u8; 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 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 b = (db as i16 - ((sb as i16) >> 1)).max(0) as u8;
Color::pack_from_native(r, g, b) pack_rgb565_native(r, g, b)
} }
BlendMode::Full => { BlendMode::Full => {
let (dr, dg, db) = Color::unpack_to_native(dst); let (dr, dg, db) = unpack_rgb565_native(dst);
let (sr, sg, sb) = Color::unpack_to_native(src); let (sr, sg, sb) = unpack_rgb565_native(src);
let r = (dr as u16 + sr as u16).min(31) as u8; 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 g = (dg as u16 + sg as u16).min(63) as u8;
let b = (db as u16 + sb as u16).min(31) as u8; let b = (db as u16 + sb as u16).min(31) as u8;
Color::pack_from_native(r, g, b) pack_rgb565_native(r, g, b)
} }
} }
} }
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 pack_rgb565_native(r: u8, g: u8, b: u8) -> u16 {
((r as u16 & 0x1F) << 11) | ((g as u16 & 0x3F) << 5) | (b as u16 & 0x1F)
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@ -1133,7 +1144,7 @@ mod tests {
gfx.hud_fade_level = 31; gfx.hud_fade_level = 31;
gfx.render_scene_from_cache(&cache, &update, &[0, 0, 0, 0]); gfx.render_scene_from_cache(&cache, &update, &[0, 0, 0, 0]);
assert_eq!(gfx.back[0], Color::RED.raw()); assert_eq!(gfx.back[0], Color::RED.to_rgb565_lossy());
} }
#[test] #[test]
@ -1184,7 +1195,7 @@ mod tests {
gfx.render_scene_from_cache(&cache, &update, &[0, 0, 0, 0]); gfx.render_scene_from_cache(&cache, &update, &[0, 0, 0, 0]);
assert_eq!(gfx.back[0], Color::BLUE.raw()); assert_eq!(gfx.back[0], Color::BLUE.to_rgb565_lossy());
} }
#[test] #[test]

View File

@ -1,75 +1,87 @@
/// Represents a 16-bit color in the RGB565 format. /// Represents a canonical RGBA8888 color in RGBA channel order.
///
/// The RGB565 format is a common high-color representation for embedded systems:
/// - **Red**: 5 bits (0..31)
/// - **Green**: 6 bits (0..63)
/// - **Blue**: 5 bits (0..31)
///
/// Prometeu does not have a hardware alpha channel. Transparency is achieved
/// by using a specific color key (Magenta / 0xF81F) which the GFX engine
/// skips during rendering.
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct Color(pub u16); pub struct Color(pub u32);
impl Color { impl Color {
pub const BLACK: Self = Self::rgb(0, 0, 0); // 0x0000 pub const BLACK: Self = Self::rgb(0, 0, 0);
pub const WHITE: Self = Self::rgb(255, 255, 255); // 0xFFFF pub const WHITE: Self = Self::rgb(255, 255, 255);
pub const RED: Self = Self::rgb(255, 0, 0); // 0xF800 pub const RED: Self = Self::rgb(255, 0, 0);
pub const GREEN: Self = Self::rgb(0, 255, 0); // 0x07E0 pub const GREEN: Self = Self::rgb(0, 255, 0);
pub const BLUE: Self = Self::rgb(0, 0, 255); // 0x001F pub const BLUE: Self = Self::rgb(0, 0, 255);
pub const YELLOW: Self = Self::rgb(255, 255, 0); // 0xFFE0 pub const YELLOW: Self = Self::rgb(255, 255, 0);
pub const ORANGE: Self = Self::rgb(255, 128, 0); pub const ORANGE: Self = Self::rgb(255, 128, 0);
pub const INDIGO: Self = Self::rgb(75, 0, 130); pub const INDIGO: Self = Self::rgb(75, 0, 130);
pub const GRAY: Self = Self::rgb(128, 128, 128); pub const GRAY: Self = Self::rgb(128, 128, 128);
pub const CYAN: Self = Self::rgb(0, 255, 255); // 0x07FF pub const CYAN: Self = Self::rgb(0, 255, 255);
pub const MAGENTA: Self = Self::rgb(255, 0, 255); // 0xF81F pub const MAGENTA: Self = Self::rgb(255, 0, 255);
pub const COLOR_KEY: Self = Self::MAGENTA; pub const TRANSPARENT: Self = Self::rgba(0, 0, 0, 0);
pub const TRANSPARENT: Self = Self::MAGENTA; #[deprecated(note = "Use RGBA alpha transparency; COLOR_KEY is temporary migration residue.")]
pub const COLOR_KEY: Self = Self::TRANSPARENT;
/// Extracts channels in the native RGB565 range: /// Extracts RGBA8888 channels from canonical RGBA raw order.
/// R: 0..31, G: 0..63, B: 0..31
#[inline(always)] #[inline(always)]
pub const fn unpack_to_native(px: u16) -> (u8, u8, u8) { pub const fn unpack_to_native(px: u32) -> (u8, u8, u8, u8) {
let r = ((px >> 11) & 0x1F) as u8; let r = ((px >> 24) & 0xFF) as u8;
let g = ((px >> 5) & 0x3F) as u8; let g = ((px >> 16) & 0xFF) as u8;
let b = (px & 0x1F) as u8; let b = ((px >> 8) & 0xFF) as u8;
(r, g, b) let a = (px & 0xFF) as u8;
(r, g, b, a)
} }
/// Packs channels from the native RGB565 range into a pixel: /// Packs RGBA8888 channels into canonical RGBA raw order.
/// R: 0..31, G: 0..63, B: 0..31
#[inline(always)] #[inline(always)]
pub const fn pack_from_native(r: u8, g: u8, b: u8) -> u16 { pub const fn pack_from_native(r: u8, g: u8, b: u8, a: u8) -> u32 {
((r as u16 & 0x1F) << 11) | ((g as u16 & 0x3F) << 5) | (b as u16 & 0x1F) ((r as u32) << 24) | ((g as u32) << 16) | ((b as u32) << 8) | a as u32
} }
/// Creates an RGB565 color from 8-bit components (0..255). /// Creates an opaque RGBA8888 color from 8-bit RGB components.
pub const fn rgb(r: u8, g: u8, b: u8) -> Self { pub const fn rgb(r: u8, g: u8, b: u8) -> Self {
let r5 = (r as u16 >> 3) & 0x1F; Self::rgba(r, g, b, 255)
let g6 = (g as u16 >> 2) & 0x3F; }
let b5 = (b as u16 >> 3) & 0x1F;
Self((r5 << 11) | (g6 << 5) | b5) pub const fn rgba(r: u8, g: u8, b: u8, a: u8) -> Self {
Self(Self::pack_from_native(r, g, b, a))
} }
pub const fn gray_scale(c: u8) -> Self { pub const fn gray_scale(c: u8) -> Self {
Self::rgb(c, c, c) Self::rgb(c, c, c)
} }
pub const fn from_raw(raw: u16) -> Self { pub const fn from_raw(raw: u32) -> Self {
Self(raw) Self(raw)
} }
pub const fn raw(self) -> u16 { pub const fn raw(self) -> u32 {
self.0 self.0
} }
pub const fn hex(self) -> i32 { pub const fn hex(self) -> i32 {
let (r5, g6, b5) = Self::unpack_to_native(self.0); let (r, g, b, _) = Self::unpack_to_native(self.0);
let r8 = ((r5 as u32) << 3) | ((r5 as u32) >> 2); let hex = (r as u32) << 16 | (g as u32) << 8 | b as u32;
let g8 = ((g6 as u32) << 2) | ((g6 as u32) >> 4);
let b8 = ((b5 as u32) << 3) | ((b5 as u32) >> 2);
let hex = r8 << 16 | g8 << 8 | b8;
hex as i32 hex as i32
} }
pub const fn alpha(self) -> u8 {
(self.0 & 0xFF) as u8
}
#[deprecated(note = "Temporary bridge until the renderer buffer migrates to RGBA8888.")]
pub const fn to_rgb565_lossy(self) -> u16 {
let (r, g, b, _) = Self::unpack_to_native(self.0);
let r5 = (r as u16 >> 3) & 0x1F;
let g6 = (g as u16 >> 2) & 0x3F;
let b5 = (b as u16 >> 3) & 0x1F;
(r5 << 11) | (g6 << 5) | b5
}
#[deprecated(note = "Temporary bridge until asset palettes migrate to RGBA8888.")]
pub const fn from_rgb565_lossy(raw: u16) -> Self {
let r5 = ((raw >> 11) & 0x1F) as u32;
let g6 = ((raw >> 5) & 0x3F) as u32;
let b5 = (raw & 0x1F) as u32;
let r = ((r5 << 3) | (r5 >> 2)) as u8;
let g = ((g6 << 2) | (g6 >> 4)) as u8;
let b = ((b5 << 3) | (b5 >> 2)) as u8;
Self::rgba(r, g, b, 255)
}
} }

View File

@ -32,9 +32,10 @@ pub struct GlyphBank {
/// Decoded pixel data stored as one palette index per pixel. /// Decoded pixel data stored as one palette index per pixel.
/// Serialized payloads are packed; runtime memory is expanded. /// Serialized payloads are packed; runtime memory is expanded.
/// Index 0 is always reserved for transparency. /// Palette indices are ordinary indices; transparency is resolved through
/// the RGBA alpha channel of the palette entry.
pub pixel_indices: Vec<u8>, pub pixel_indices: Vec<u8>,
/// Runtime-facing v1 palette table: 64 palettes of 16 RGB565 colors each. /// Runtime-facing v1 palette table: 64 palettes of 16 RGBA8888 colors each.
pub palettes: [[Color; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1], pub palettes: [[Color; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1],
} }
@ -45,7 +46,7 @@ impl GlyphBank {
tile_size, tile_size,
width, width,
height, height,
pixel_indices: vec![0; width * height], // Index 0 = Transparent pixel_indices: vec![0; width * height],
palettes: [[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1], palettes: [[Color::BLACK; GLYPH_BANK_COLORS_PER_PALETTE]; GLYPH_BANK_PALETTE_COUNT_V1],
} }
} }
@ -65,22 +66,16 @@ impl GlyphBank {
if pixel_x < self.width && pixel_y < self.height { if pixel_x < self.width && pixel_y < self.height {
self.pixel_indices[pixel_y * self.width + pixel_x] self.pixel_indices[pixel_y * self.width + pixel_x]
} else { } else {
0 // Default to transparent if out of bounds 0
} }
} }
/// Maps a 4-bit index to a real RGB565 Color using the specified palette. /// Maps a 4-bit index to a real RGBA8888 Color using the specified palette.
pub fn resolve_color(&self, palette_id: u8, pixel_index: u8) -> Color { pub fn resolve_color(&self, palette_id: u8, pixel_index: u8) -> Color {
// Hardware Rule: Index 0 is always transparent.
// We use Magenta as the 'transparent' signal color during composition.
if pixel_index == 0 {
return Color::COLOR_KEY;
}
self.palettes self.palettes
.get(palette_id as usize) .get(palette_id as usize)
.and_then(|palette| palette.get(pixel_index as usize)) .and_then(|palette| palette.get(pixel_index as usize))
.copied() .copied()
.unwrap_or(Color::COLOR_KEY) .unwrap_or(Color::TRANSPARENT)
} }
} }

View File

@ -37,7 +37,6 @@ pub enum Syscall {
GfxDrawDisc = 0x1005, GfxDrawDisc = 0x1005,
GfxDrawSquare = 0x1006, GfxDrawSquare = 0x1006,
GfxDrawText = 0x1008, GfxDrawText = 0x1008,
GfxClear565 = 0x1010,
ComposerBindScene = 0x1101, ComposerBindScene = 0x1101,
ComposerUnbindScene = 0x1102, ComposerUnbindScene = 0x1102,
ComposerSetCamera = 0x1103, ComposerSetCamera = 0x1103,

View File

@ -29,8 +29,4 @@ pub(crate) const ENTRIES: &[SyscallRegistryEntry] = &[
.args(4) .args(4)
.caps(caps::GFX) .caps(caps::GFX)
.cost(20), .cost(20),
SyscallRegistryEntry::builder(Syscall::GfxClear565, "gfx", "clear_565")
.args(1)
.caps(caps::GFX)
.cost(20),
]; ];

View File

@ -21,7 +21,6 @@ impl Syscall {
0x1005 => Some(Self::GfxDrawDisc), 0x1005 => Some(Self::GfxDrawDisc),
0x1006 => Some(Self::GfxDrawSquare), 0x1006 => Some(Self::GfxDrawSquare),
0x1008 => Some(Self::GfxDrawText), 0x1008 => Some(Self::GfxDrawText),
0x1010 => Some(Self::GfxClear565),
0x1101 => Some(Self::ComposerBindScene), 0x1101 => Some(Self::ComposerBindScene),
0x1102 => Some(Self::ComposerUnbindScene), 0x1102 => Some(Self::ComposerUnbindScene),
0x1103 => Some(Self::ComposerSetCamera), 0x1103 => Some(Self::ComposerSetCamera),
@ -71,7 +70,6 @@ impl Syscall {
Self::GfxDrawDisc => "GfxDrawDisc", Self::GfxDrawDisc => "GfxDrawDisc",
Self::GfxDrawSquare => "GfxDrawSquare", Self::GfxDrawSquare => "GfxDrawSquare",
Self::GfxDrawText => "GfxDrawText", Self::GfxDrawText => "GfxDrawText",
Self::GfxClear565 => "GfxClear565",
Self::ComposerBindScene => "ComposerBindScene", Self::ComposerBindScene => "ComposerBindScene",
Self::ComposerUnbindScene => "ComposerUnbindScene", Self::ComposerUnbindScene => "ComposerUnbindScene",
Self::ComposerSetCamera => "ComposerSetCamera", Self::ComposerSetCamera => "ComposerSetCamera",

View File

@ -210,10 +210,6 @@ fn status_first_syscall_signatures_are_pinned() {
assert_eq!(draw_text.arg_slots, 4); assert_eq!(draw_text.arg_slots, 4);
assert_eq!(draw_text.ret_slots, 0); assert_eq!(draw_text.ret_slots, 0);
let clear_565 = meta_for(Syscall::GfxClear565);
assert_eq!(clear_565.arg_slots, 1);
assert_eq!(clear_565.ret_slots, 0);
let bind_scene = meta_for(Syscall::ComposerBindScene); let bind_scene = meta_for(Syscall::ComposerBindScene);
assert_eq!(bind_scene.arg_slots, 1); assert_eq!(bind_scene.arg_slots, 1);
assert_eq!(bind_scene.ret_slots, 1); assert_eq!(bind_scene.ret_slots, 1);

View File

@ -95,8 +95,10 @@ impl VmRuntimeHost<'_> {
Ok(()) Ok(())
} }
pub(crate) fn get_color(&self, value: i64) -> Color { pub(crate) fn get_color(&self, value: i64) -> Result<Color, VmFault> {
Color::from_raw(value as u16) let raw = u32::try_from(value)
.map_err(|_| VmFault::Trap(TRAP_OOB, "Color value out of bounds".into()))?;
Ok(Color::from_raw(raw))
} }
fn int_arg_to_usize_status(value: i64) -> Result<usize, ComposerOpStatus> { fn int_arg_to_usize_status(value: i64) -> Result<usize, ComposerOpStatus> {
@ -126,7 +128,6 @@ impl VmRuntimeHost<'_> {
| Syscall::GfxDrawDisc | Syscall::GfxDrawDisc
| Syscall::GfxDrawSquare | Syscall::GfxDrawSquare
| Syscall::GfxDrawText | Syscall::GfxDrawText
| Syscall::GfxClear565
| Syscall::ComposerBindScene | Syscall::ComposerBindScene
| Syscall::ComposerUnbindScene | Syscall::ComposerUnbindScene
| Syscall::ComposerSetCamera | Syscall::ComposerSetCamera
@ -183,7 +184,7 @@ impl NativeInterface for VmRuntimeHost<'_> {
Syscall::SystemHasCart => unreachable!(), Syscall::SystemHasCart => unreachable!(),
Syscall::SystemRunCart => unreachable!(), Syscall::SystemRunCart => unreachable!(),
Syscall::GfxClear => { Syscall::GfxClear => {
let color = self.get_color(expect_int(args, 0)?); let color = self.get_color(expect_int(args, 0)?)?;
hw.gfx_mut().clear(color); hw.gfx_mut().clear(color);
Ok(()) Ok(())
} }
@ -192,7 +193,7 @@ impl NativeInterface for VmRuntimeHost<'_> {
let y = expect_int(args, 1)? as i32; let y = expect_int(args, 1)? as i32;
let w = expect_int(args, 2)? as i32; let w = expect_int(args, 2)? as i32;
let h = expect_int(args, 3)? as i32; let h = expect_int(args, 3)? as i32;
let color = self.get_color(expect_int(args, 4)?); let color = self.get_color(expect_int(args, 4)?)?;
hw.gfx_mut().fill_rect(x, y, w, h, color); hw.gfx_mut().fill_rect(x, y, w, h, color);
Ok(()) Ok(())
} }
@ -201,7 +202,7 @@ impl NativeInterface for VmRuntimeHost<'_> {
let y1 = expect_int(args, 1)? as i32; let y1 = expect_int(args, 1)? as i32;
let x2 = expect_int(args, 2)? as i32; let x2 = expect_int(args, 2)? as i32;
let y2 = expect_int(args, 3)? as i32; let y2 = expect_int(args, 3)? as i32;
let color = self.get_color(expect_int(args, 4)?); let color = self.get_color(expect_int(args, 4)?)?;
hw.gfx_mut().draw_line(x1, y1, x2, y2, color); hw.gfx_mut().draw_line(x1, y1, x2, y2, color);
Ok(()) Ok(())
} }
@ -209,7 +210,7 @@ impl NativeInterface for VmRuntimeHost<'_> {
let x = expect_int(args, 0)? as i32; let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32; let y = expect_int(args, 1)? as i32;
let r = expect_int(args, 2)? as i32; let r = expect_int(args, 2)? as i32;
let color = self.get_color(expect_int(args, 3)?); let color = self.get_color(expect_int(args, 3)?)?;
hw.gfx_mut().draw_circle(x, y, r, color); hw.gfx_mut().draw_circle(x, y, r, color);
Ok(()) Ok(())
} }
@ -217,8 +218,8 @@ impl NativeInterface for VmRuntimeHost<'_> {
let x = expect_int(args, 0)? as i32; let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32; let y = expect_int(args, 1)? as i32;
let r = expect_int(args, 2)? as i32; let r = expect_int(args, 2)? as i32;
let border_color = self.get_color(expect_int(args, 3)?); let border_color = self.get_color(expect_int(args, 3)?)?;
let fill_color = self.get_color(expect_int(args, 4)?); let fill_color = self.get_color(expect_int(args, 4)?)?;
hw.gfx_mut().draw_disc(x, y, r, border_color, fill_color); hw.gfx_mut().draw_disc(x, y, r, border_color, fill_color);
Ok(()) Ok(())
} }
@ -227,8 +228,8 @@ impl NativeInterface for VmRuntimeHost<'_> {
let y = expect_int(args, 1)? as i32; let y = expect_int(args, 1)? as i32;
let w = expect_int(args, 2)? as i32; let w = expect_int(args, 2)? as i32;
let h = expect_int(args, 3)? as i32; let h = expect_int(args, 3)? as i32;
let border_color = self.get_color(expect_int(args, 4)?); let border_color = self.get_color(expect_int(args, 4)?)?;
let fill_color = self.get_color(expect_int(args, 5)?); let fill_color = self.get_color(expect_int(args, 5)?)?;
hw.gfx_mut().draw_square(x, y, w, h, border_color, fill_color); hw.gfx_mut().draw_square(x, y, w, h, border_color, fill_color);
Ok(()) Ok(())
} }
@ -236,18 +237,10 @@ impl NativeInterface for VmRuntimeHost<'_> {
let x = expect_int(args, 0)? as i32; let x = expect_int(args, 0)? as i32;
let y = expect_int(args, 1)? as i32; let y = expect_int(args, 1)? as i32;
let msg = expect_string(args, 2, "message")?; let msg = expect_string(args, 2, "message")?;
let color = self.get_color(expect_int(args, 3)?); let color = self.get_color(expect_int(args, 3)?)?;
hw.gfx_mut().draw_text(x, y, &msg, color); hw.gfx_mut().draw_text(x, y, &msg, color);
Ok(()) Ok(())
} }
Syscall::GfxClear565 => {
let color_val = expect_int(args, 0)? as u32;
if color_val > 0xFFFF {
return Err(VmFault::Trap(TRAP_OOB, "Color value out of bounds".into()));
}
hw.gfx_mut().clear(Color::from_raw(color_val as u16));
Ok(())
}
Syscall::ComposerBindScene => { Syscall::ComposerBindScene => {
let scene_bank_id = match Self::int_arg_to_usize_status(expect_int(args, 0)?) { let scene_bank_id = match Self::int_arg_to_usize_status(expect_int(args, 0)?) {
Ok(id) => id, Ok(id) => id,

View File

@ -2545,12 +2545,12 @@ mod tests {
#[test] #[test]
fn test_syscall_results_count_mismatch_panic() { fn test_syscall_results_count_mismatch_panic() {
// GfxClear565 (0x1010) expects 0 results // GfxClear (0x1001) expects 0 results
let rom = vec![ let rom = vec![
0x17, 0x00, // PushI32 0x17, 0x00, // PushI32
0x00, 0x00, 0x00, 0x00, // value 0 0x00, 0x00, 0x00, 0x00, // value 0
0x70, 0x00, // Syscall + Reserved 0x70, 0x00, // Syscall + Reserved
0x10, 0x10, 0x00, 0x00, // Syscall ID 0x1010 0x01, 0x10, 0x00, 0x00, // Syscall ID 0x1001
]; ];
struct BadNative; struct BadNative;
@ -2562,7 +2562,7 @@ mod tests {
ret: &mut HostReturn, ret: &mut HostReturn,
_ctx: &mut HostContext, _ctx: &mut HostContext,
) -> Result<(), VmFault> { ) -> Result<(), VmFault> {
// Wrong: GfxClear565 is void but we push something // Wrong: GfxClear is void but we push something
ret.push_int(42); ret.push_int(42);
Ok(()) Ok(())
} }

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

View File

@ -2,7 +2,7 @@
id: PLN-0068 id: PLN-0068
ticket: rgba8888-framebuffer-and-pixel-format-direction ticket: rgba8888-framebuffer-and-pixel-format-direction
title: RGBA8888 Color Type HAL and ABI Surface title: RGBA8888 Color Type HAL and ABI Surface
status: open status: done
created: 2026-05-23 created: 2026-05-23
ref_decisions: [DEC-0029] ref_decisions: [DEC-0029]
tags: [gfx, framebuffer, rgb565, rgba8888, renderer, assets, host, backend] tags: [gfx, framebuffer, rgb565, rgba8888, renderer, assets, host, backend]