From a1bd60671bb5d20370bcb6a093d9b9d4b9ad8513 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 17 Apr 2026 13:32:11 +0100 Subject: [PATCH] implements PLN-0021 --- .../prometeu-drivers/src/frame_composer.rs | 2 +- crates/console/prometeu-drivers/src/gfx.rs | 6 +- .../console/prometeu-drivers/src/hardware.rs | 13 ++++ crates/console/prometeu-hal/src/gfx_bridge.rs | 2 +- .../prometeu-hal/src/hardware_bridge.rs | 5 ++ .../src/virtual_machine_runtime/dispatch.rs | 24 ++++--- .../src/virtual_machine_runtime/tests.rs | 67 ++++++++++++++++++- .../src/virtual_machine_runtime/tick.rs | 3 +- 8 files changed, 104 insertions(+), 18 deletions(-) diff --git a/crates/console/prometeu-drivers/src/frame_composer.rs b/crates/console/prometeu-drivers/src/frame_composer.rs index 378423c4..9a174e18 100644 --- a/crates/console/prometeu-drivers/src/frame_composer.rs +++ b/crates/console/prometeu-drivers/src/frame_composer.rs @@ -251,7 +251,7 @@ impl FrameComposer { return; } - gfx.render_all(); + gfx.render_no_scene_frame(); } fn build_scene_runtime( diff --git a/crates/console/prometeu-drivers/src/gfx.rs b/crates/console/prometeu-drivers/src/gfx.rs index e78b3e4c..4a1c9dbb 100644 --- a/crates/console/prometeu-drivers/src/gfx.rs +++ b/crates/console/prometeu-drivers/src/gfx.rs @@ -209,8 +209,8 @@ impl GfxBridge for Gfx { fn present(&mut self) { self.present() } - fn render_all(&mut self) { - self.render_all() + fn render_no_scene_frame(&mut self) { + self.render_no_scene_frame() } fn render_scene_from_cache(&mut self, cache: &SceneViewportCache, update: &ResolverUpdate) { self.render_scene_from_cache(cache, update) @@ -552,7 +552,7 @@ impl Gfx { /// This method composes the final frame by rasterizing layers and sprites in the /// correct priority order into the back buffer. /// Follows the hardware model where layers and sprites are composed every frame. - pub fn render_all(&mut self) { + pub fn render_no_scene_frame(&mut self) { self.populate_layer_buckets(); for bucket in &self.layer_buckets { Self::draw_bucket_on_buffer( diff --git a/crates/console/prometeu-drivers/src/hardware.rs b/crates/console/prometeu-drivers/src/hardware.rs index e1ef2d3f..c9de3447 100644 --- a/crates/console/prometeu-drivers/src/hardware.rs +++ b/crates/console/prometeu-drivers/src/hardware.rs @@ -9,6 +9,7 @@ use crate::memory_banks::{ use crate::pad::Pad; use crate::touch::Touch; use prometeu_hal::cartridge::AssetsPayloadSource; +use prometeu_hal::sprite::Sprite; use prometeu_hal::{AssetBridge, AudioBridge, GfxBridge, HardwareBridge, PadBridge, TouchBridge}; use std::sync::Arc; @@ -46,6 +47,18 @@ impl Default for Hardware { } impl HardwareBridge for Hardware { + fn begin_frame(&mut self) { + self.frame_composer.begin_frame(); + } + + fn emit_sprite(&mut self, sprite: Sprite) { + let _ = self.frame_composer.emit_sprite(sprite); + } + + fn render_frame(&mut self) { + self.frame_composer.render_frame(&mut self.gfx); + } + fn gfx(&self) -> &dyn GfxBridge { &self.gfx } diff --git a/crates/console/prometeu-hal/src/gfx_bridge.rs b/crates/console/prometeu-hal/src/gfx_bridge.rs index b000d20d..44829579 100644 --- a/crates/console/prometeu-hal/src/gfx_bridge.rs +++ b/crates/console/prometeu-hal/src/gfx_bridge.rs @@ -48,7 +48,7 @@ pub trait GfxBridge { fn draw_horizontal_line(&mut self, x0: i32, x1: i32, y: i32, color: Color); fn draw_vertical_line(&mut self, x: i32, y0: i32, y1: i32, color: Color); fn present(&mut self); - fn render_all(&mut self); + fn render_no_scene_frame(&mut self); fn render_scene_from_cache(&mut self, cache: &SceneViewportCache, update: &ResolverUpdate); fn load_frame_sprites(&mut self, sprites: &[Sprite]); fn draw_text(&mut self, x: i32, y: i32, text: &str, color: Color); diff --git a/crates/console/prometeu-hal/src/hardware_bridge.rs b/crates/console/prometeu-hal/src/hardware_bridge.rs index 1e8d9728..2bcb3376 100644 --- a/crates/console/prometeu-hal/src/hardware_bridge.rs +++ b/crates/console/prometeu-hal/src/hardware_bridge.rs @@ -2,9 +2,14 @@ use crate::asset_bridge::AssetBridge; use crate::audio_bridge::AudioBridge; use crate::gfx_bridge::GfxBridge; use crate::pad_bridge::PadBridge; +use crate::sprite::Sprite; use crate::touch_bridge::TouchBridge; pub trait HardwareBridge { + fn begin_frame(&mut self); + fn emit_sprite(&mut self, sprite: Sprite); + fn render_frame(&mut self); + fn gfx(&self) -> &dyn GfxBridge; fn gfx_mut(&mut self) -> &mut dyn GfxBridge; diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs index 86184215..e3ec62ef 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/dispatch.rs @@ -162,17 +162,19 @@ impl NativeInterface for VirtualMachineRuntime { return Ok(()); } - *hw.gfx_mut().sprite_mut(index) = Sprite { - glyph: Glyph { glyph_id, palette_id }, - x, - y, - layer: 0, - bank_id, - active, - flip_x, - flip_y, - priority, - }; + if active { + hw.emit_sprite(Sprite { + glyph: Glyph { glyph_id, palette_id }, + x, + y, + layer: 0, + bank_id, + active: false, + flip_x, + flip_y, + priority, + }); + } ret.push_int(GfxOpStatus::Ok as i64); Ok(()) } diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs index 4c0c3b38..53df08e8 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/tests.rs @@ -5,6 +5,7 @@ use prometeu_bytecode::Value; use prometeu_bytecode::assembler::assemble; use prometeu_bytecode::model::{BytecodeModule, ConstantPoolEntry, FunctionMeta, SyscallDecl}; use prometeu_drivers::hardware::Hardware; +use prometeu_drivers::{GlyphBankPoolInstaller, MemoryBanks, SceneBankPoolInstaller}; use prometeu_hal::AudioOpStatus; use prometeu_hal::GfxOpStatus; use prometeu_hal::InputSignals; @@ -12,10 +13,17 @@ use prometeu_hal::asset::{ AssetCodec, AssetEntry, AssetLoadError, AssetOpStatus, BankType, LoadStatus, }; use prometeu_hal::cartridge::{AssetsPayloadSource, Cartridge}; -use prometeu_hal::glyph_bank::GLYPH_BANK_PALETTE_COUNT_V1; +use prometeu_hal::color::Color; +use prometeu_hal::glyph::Glyph; +use prometeu_hal::glyph_bank::{GLYPH_BANK_PALETTE_COUNT_V1, GlyphBank, TileSize}; +use prometeu_hal::scene_bank::SceneBank; +use prometeu_hal::scene_layer::{ParallaxFactor, SceneLayer}; use prometeu_hal::syscalls::caps; +use prometeu_hal::tile::Tile; +use prometeu_hal::tilemap::TileMap; use prometeu_vm::VmInitError; use std::collections::HashMap; +use std::sync::Arc; use std::sync::atomic::Ordering; #[derive(Default)] @@ -129,6 +137,40 @@ fn test_glyph_asset_data() -> Vec { data } +fn runtime_test_glyph_bank(tile_size: TileSize, palette_id: u8, color: Color) -> GlyphBank { + let size = tile_size as usize; + let mut bank = GlyphBank::new(tile_size, size, size); + bank.palettes[palette_id as usize][1] = color; + for pixel in &mut bank.pixel_indices { + *pixel = 1; + } + bank +} + +fn runtime_test_scene(glyph_bank_id: u8, palette_id: u8, tile_size: TileSize) -> SceneBank { + let layer = SceneLayer { + active: true, + glyph_bank_id, + tile_size, + parallax_factor: ParallaxFactor { x: 1.0, y: 1.0 }, + tilemap: TileMap { + width: 2, + height: 2, + tiles: vec![ + Tile { + active: true, + glyph: Glyph { glyph_id: 0, palette_id }, + flip_x: false, + flip_y: false, + }; + 4 + ], + }, + }; + + SceneBank { layers: std::array::from_fn(|_| layer.clone()) } +} + #[test] fn initialize_vm_applies_cartridge_capabilities_before_loader_resolution() { let mut runtime = VirtualMachineRuntime::new(None); @@ -233,6 +275,29 @@ fn tick_returns_panic_report_distinct_from_trap() { assert!(matches!(runtime.last_crash_report, Some(CrashReport::VmPanic { .. }))); } +#[test] +fn tick_renders_bound_eight_pixel_scene_through_frame_composer_path() { + let mut runtime = VirtualMachineRuntime::new(None); + let mut vm = VirtualMachine::default(); + let signals = InputSignals::default(); + let program = + serialized_single_function_module(assemble("FRAME_SYNC\nHALT").expect("assemble"), vec![]); + let cartridge = cartridge_with_program(program, caps::NONE); + + let banks = Arc::new(MemoryBanks::new()); + banks.install_glyph_bank(0, Arc::new(runtime_test_glyph_bank(TileSize::Size8, 2, Color::BLUE))); + banks.install_scene_bank(0, Arc::new(runtime_test_scene(0, 2, TileSize::Size8))); + let mut hardware = Hardware::new_with_memory_banks(Arc::clone(&banks)); + assert!(hardware.frame_composer.bind_scene(0)); + + runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize"); + let report = runtime.tick(&mut vm, &signals, &mut hardware); + + assert!(report.is_none(), "frame render path must not crash"); + hardware.gfx.present(); + assert_eq!(hardware.gfx.front_buffer()[0], Color::BLUE.raw()); +} + #[test] fn initialize_vm_success_clears_previous_crash_report() { let mut runtime = VirtualMachineRuntime::new(None); diff --git a/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs b/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs index ba58892b..4a281f66 100644 --- a/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs +++ b/crates/console/prometeu-system/src/virtual_machine_runtime/tick.rs @@ -145,7 +145,7 @@ impl VirtualMachineRuntime { if run.reason == LogicalFrameEndingReason::FrameSync || run.reason == LogicalFrameEndingReason::EndOfRom { - hw.gfx_mut().render_all(); + hw.render_frame(); // 1. Snapshot full telemetry at logical frame end let (glyph_bank, sound_bank) = Self::bank_telemetry_summary(hw); @@ -250,6 +250,7 @@ impl VirtualMachineRuntime { _signals: &InputSignals, hw: &mut dyn HardwareBridge, ) { + hw.begin_frame(); hw.audio_mut().clear_commands(); self.logs_written_this_frame.clear(); }