From 76f02584d0485f41d88c26ddfeeb879534b5a3ea Mon Sep 17 00:00:00 2001 From: Nilton Constantino Date: Tue, 20 Jan 2026 07:14:32 +0000 Subject: [PATCH] add primitive draw call on GFX --- crates/prometeu-core/src/hardware/gfx.rs | 185 +++++++++++++++++- .../src/prometeu_os/prometeu_os.rs | 46 +++++ docs/specs/topics/chapter-2.md | 4 + 3 files changed, 232 insertions(+), 3 deletions(-) diff --git a/crates/prometeu-core/src/hardware/gfx.rs b/crates/prometeu-core/src/hardware/gfx.rs index 3e7ab817..a2cf6831 100644 --- a/crates/prometeu-core/src/hardware/gfx.rs +++ b/crates/prometeu-core/src/hardware/gfx.rs @@ -158,6 +158,136 @@ impl Gfx { self.fill_rect_blend(x, y, w, h, color, BlendMode::None); } + /// Draws a single pixel. + pub fn draw_pixel(&mut self, x: i32, y: i32, color: Color) { + 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; + } + } + + /// 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) { + let dx = (x1 - x0).abs(); + let sx = if x0 < x1 { 1 } else { -1 }; + let dy = -(y1 - y0).abs(); + let sy = if y0 < y1 { 1 } else { -1 }; + let mut err = dx + dy; + + let mut x = x0; + let mut y = y0; + + loop { + self.draw_pixel(x, y, color); + if x == x1 && y == y1 { break; } + let e2 = 2 * err; + if e2 >= dy { + err += dy; + x += sx; + } + if e2 <= dx { + err += dx; + y += sy; + } + } + } + + /// Draws a circle outline using Midpoint Circle Algorithm. + pub fn draw_circle(&mut self, xc: i32, yc: i32, r: i32, color: Color) { + if r < 0 { return; } + let mut x = 0; + let mut y = r; + let mut d = 3 - 2 * r; + self.draw_circle_points(xc, yc, x, y, color); + while y >= x { + x += 1; + if d > 0 { + y -= 1; + d = d + 4 * (x - y) + 10; + } else { + d = d + 4 * x + 6; + } + self.draw_circle_points(xc, yc, x, y, color); + } + } + + fn draw_circle_points(&mut self, xc: i32, yc: i32, x: i32, y: i32, color: Color) { + self.draw_pixel(xc + x, yc + y, color); + self.draw_pixel(xc - x, yc + y, color); + self.draw_pixel(xc + x, yc - y, color); + self.draw_pixel(xc - x, yc - y, color); + self.draw_pixel(xc + y, yc + x, color); + self.draw_pixel(xc - y, yc + x, color); + self.draw_pixel(xc + y, yc - x, color); + self.draw_pixel(xc - y, yc - x, color); + } + + /// Draws a filled circle. + pub fn fill_circle(&mut self, xc: i32, yc: i32, r: i32, color: Color) { + if r < 0 { return; } + let mut x = 0; + let mut y = r; + let mut d = 3 - 2 * r; + self.draw_circle_lines(xc, yc, x, y, color); + while y >= x { + x += 1; + if d > 0 { + y -= 1; + d = d + 4 * (x - y) + 10; + } else { + d = d + 4 * x + 6; + } + self.draw_circle_lines(xc, yc, x, y, color); + } + } + + fn draw_circle_lines(&mut self, xc: i32, yc: i32, x: i32, y: i32, color: Color) { + self.draw_horizontal_line(xc - x, xc + x, yc + y, color); + self.draw_horizontal_line(xc - x, xc + x, yc - y, color); + self.draw_horizontal_line(xc - y, xc + y, yc + x, color); + self.draw_horizontal_line(xc - y, xc + y, yc - x, color); + } + + /// Draws a disc (filled circle with border). + pub fn draw_disc(&mut self, x: i32, y: i32, r: i32, border_color: Color, fill_color: Color) { + self.fill_circle(x, y, r, fill_color); + self.draw_circle(x, y, r, border_color); + } + + /// Draws a rectangle outline. + pub fn draw_rect(&mut self, x: i32, y: i32, w: i32, h: i32, color: Color) { + if w <= 0 || h <= 0 { return; } + self.draw_horizontal_line(x, x + w - 1, y, color); + self.draw_horizontal_line(x, x + w - 1, y + h - 1, color); + self.draw_vertical_line(x, y, y + h - 1, color); + self.draw_vertical_line(x + w - 1, y, y + h - 1, color); + } + + /// Draws a square (filled rectangle with border). + pub fn draw_square(&mut self, x: i32, y: i32, w: i32, h: i32, border_color: Color, fill_color: Color) { + self.fill_rect(x, y, w, h, fill_color); + self.draw_rect(x, y, w, h, border_color); + } + + fn draw_horizontal_line(&mut self, x0: i32, x1: i32, y: i32, color: Color) { + if y < 0 || y >= self.h as i32 { return; } + let start = x0.max(0); + let end = x1.min(self.w as i32 - 1); + if start > end { return; } + for x in start..=end { + self.back[y as usize * self.w + x as usize] = color.0; + } + } + + fn draw_vertical_line(&mut self, x: i32, y0: i32, y1: i32, color: Color) { + if x < 0 || x >= self.w as i32 { return; } + let start = y0.max(0); + let end = y1.min(self.h as i32 - 1); + if start > end { return; } + for y in start..=end { + self.back[y as usize * self.w + x as usize] = color.0; + } + } + /// Double buffer swap (O(1), no pixel copying). /// Typically called by the Host when it's time to display the finished frame. pub fn present(&mut self) { @@ -492,15 +622,64 @@ impl Gfx { if (row >> (2 - col_idx)) & 1 == 1 { let px = x + col_idx as i32; let py = y + row_idx as i32; - if px >= 0 && px < self.w as i32 && py >= 0 && py < self.h as i32 { - self.back[py as usize * self.w + px as usize] = color.0; - } + self.draw_pixel(px, py, color); } } } } } +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_draw_pixel() { + let mut gfx = Gfx::new(10, 10); + gfx.draw_pixel(5, 5, Color::WHITE); + assert_eq!(gfx.back[5 * 10 + 5], Color::WHITE.0); + + // Out of bounds should not panic + gfx.draw_pixel(-1, -1, Color::WHITE); + gfx.draw_pixel(10, 10, Color::WHITE); + } + + #[test] + fn test_draw_line() { + let mut gfx = Gfx::new(10, 10); + gfx.draw_line(0, 0, 9, 9, Color::WHITE); + assert_eq!(gfx.back[0], Color::WHITE.0); + assert_eq!(gfx.back[9 * 10 + 9], Color::WHITE.0); + } + + #[test] + fn test_draw_rect() { + let mut gfx = Gfx::new(10, 10); + gfx.draw_rect(0, 0, 10, 10, Color::WHITE); + assert_eq!(gfx.back[0], Color::WHITE.0); + assert_eq!(gfx.back[9], Color::WHITE.0); + assert_eq!(gfx.back[90], Color::WHITE.0); + assert_eq!(gfx.back[99], Color::WHITE.0); + } + + #[test] + fn test_fill_circle() { + let mut gfx = Gfx::new(10, 10); + gfx.fill_circle(5, 5, 2, Color::WHITE); + assert_eq!(gfx.back[5 * 10 + 5], Color::WHITE.0); + } + + #[test] + fn test_draw_square() { + let mut gfx = Gfx::new(10, 10); + gfx.draw_square(2, 2, 6, 6, Color::WHITE, Color::BLACK); + // Border + assert_eq!(gfx.back[2 * 10 + 2], Color::WHITE.0); + // Fill + assert_eq!(gfx.back[3 * 10 + 3], Color::BLACK.0); + } +} + /// Blends in RGB565 per channel with saturation. /// `dst` and `src` are RGB565 pixels (u16). fn blend_rgb565(dst: u16, src: u16, mode: BlendMode) -> u16 { diff --git a/crates/prometeu-core/src/prometeu_os/prometeu_os.rs b/crates/prometeu-core/src/prometeu_os/prometeu_os.rs index 8a0657af..f3f5287b 100644 --- a/crates/prometeu-core/src/prometeu_os/prometeu_os.rs +++ b/crates/prometeu-core/src/prometeu_os/prometeu_os.rs @@ -580,6 +580,52 @@ impl NativeInterface for PrometeuOS { hw.gfx_mut().fill_rect(x, y, w, h, color); Ok(200) } + // gfx.draw_line(x1, y1, x2, y2, color_index) + 0x1003 => { + let color_idx = vm.pop_integer()? as usize; + let y2 = vm.pop_integer()? as i32; + let x2 = vm.pop_integer()? as i32; + let y1 = vm.pop_integer()? as i32; + let x1 = vm.pop_integer()? as i32; + let color = self.get_color(color_idx, hw); + hw.gfx_mut().draw_line(x1, y1, x2, y2, color); + Ok(200) + } + // gfx.draw_circle(x, y, r, color_index) + 0x1004 => { + let color_idx = vm.pop_integer()? as usize; + let r = vm.pop_integer()? as i32; + let y = vm.pop_integer()? as i32; + let x = vm.pop_integer()? as i32; + let color = self.get_color(color_idx, hw); + hw.gfx_mut().draw_circle(x, y, r, color); + Ok(200) + } + // gfx.draw_disc(x, y, r, border_color_idx, fill_color_idx) + 0x1005 => { + let fill_color_idx = vm.pop_integer()? as usize; + let border_color_idx = vm.pop_integer()? as usize; + let r = vm.pop_integer()? as i32; + let y = vm.pop_integer()? as i32; + let x = vm.pop_integer()? as i32; + let fill_color = self.get_color(fill_color_idx, hw); + let border_color = self.get_color(border_color_idx, hw); + hw.gfx_mut().draw_disc(x, y, r, border_color, fill_color); + Ok(300) + } + // gfx.draw_square(x, y, w, h, border_color_idx, fill_color_idx) + 0x1006 => { + let fill_color_idx = vm.pop_integer()? as usize; + let border_color_idx = vm.pop_integer()? as usize; + let h = vm.pop_integer()? as i32; + let w = vm.pop_integer()? as i32; + let y = vm.pop_integer()? as i32; + let x = vm.pop_integer()? as i32; + let fill_color = self.get_color(fill_color_idx, hw); + let border_color = self.get_color(border_color_idx, hw); + hw.gfx_mut().draw_square(x, y, w, h, border_color, fill_color); + Ok(200) + } // --- Input Syscalls --- diff --git a/docs/specs/topics/chapter-2.md b/docs/specs/topics/chapter-2.md index 7e2fd295..f1c4d97a 100644 --- a/docs/specs/topics/chapter-2.md +++ b/docs/specs/topics/chapter-2.md @@ -213,6 +213,10 @@ Heap is: | `0x0002` | `system.run_cart` | - | - | | `0x1001` | `gfx.clear` | `color_idx` | - | | `0x1002` | `gfx.draw_rect` | `x, y, w, h, color_idx` | - | +| `0x1003` | `gfx.draw_line` | `x1, y1, x2, y2, color_idx` | - | +| `0x1004` | `gfx.draw_circle` | `xc, yc, r, color_idx` | - | +| `0x1005` | `gfx.draw_disc` | `xc, yc, r, b_col, f_col` | - | +| `0x1006` | `gfx.draw_square` | `x, y, w, h, b_col, f_col` | - | | `0x2001` | `input.get_pad` | `button_id` | `bool` | | `0x3001` | `audio.play` | `s_id, v_id, vol, pan, pitch`| - |