implements PLN-0021

This commit is contained in:
bQUARKz 2026-04-17 13:32:11 +01:00
parent 5ef43045bc
commit a1bd60671b
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
8 changed files with 104 additions and 18 deletions

View File

@ -251,7 +251,7 @@ impl FrameComposer {
return; return;
} }
gfx.render_all(); gfx.render_no_scene_frame();
} }
fn build_scene_runtime( fn build_scene_runtime(

View File

@ -209,8 +209,8 @@ impl GfxBridge for Gfx {
fn present(&mut self) { fn present(&mut self) {
self.present() self.present()
} }
fn render_all(&mut self) { fn render_no_scene_frame(&mut self) {
self.render_all() self.render_no_scene_frame()
} }
fn render_scene_from_cache(&mut self, cache: &SceneViewportCache, update: &ResolverUpdate) { fn render_scene_from_cache(&mut self, cache: &SceneViewportCache, update: &ResolverUpdate) {
self.render_scene_from_cache(cache, update) 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 /// This method composes the final frame by rasterizing layers and sprites in the
/// correct priority order into the back buffer. /// correct priority order into the back buffer.
/// Follows the hardware model where layers and sprites are composed every frame. /// 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(); self.populate_layer_buckets();
for bucket in &self.layer_buckets { for bucket in &self.layer_buckets {
Self::draw_bucket_on_buffer( Self::draw_bucket_on_buffer(

View File

@ -9,6 +9,7 @@ use crate::memory_banks::{
use crate::pad::Pad; use crate::pad::Pad;
use crate::touch::Touch; use crate::touch::Touch;
use prometeu_hal::cartridge::AssetsPayloadSource; use prometeu_hal::cartridge::AssetsPayloadSource;
use prometeu_hal::sprite::Sprite;
use prometeu_hal::{AssetBridge, AudioBridge, GfxBridge, HardwareBridge, PadBridge, TouchBridge}; use prometeu_hal::{AssetBridge, AudioBridge, GfxBridge, HardwareBridge, PadBridge, TouchBridge};
use std::sync::Arc; use std::sync::Arc;
@ -46,6 +47,18 @@ impl Default for Hardware {
} }
impl HardwareBridge 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 { fn gfx(&self) -> &dyn GfxBridge {
&self.gfx &self.gfx
} }

View File

@ -48,7 +48,7 @@ pub trait GfxBridge {
fn draw_horizontal_line(&mut self, x0: i32, x1: i32, y: i32, color: Color); 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 draw_vertical_line(&mut self, x: i32, y0: i32, y1: i32, color: Color);
fn present(&mut self); 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 render_scene_from_cache(&mut self, cache: &SceneViewportCache, update: &ResolverUpdate);
fn load_frame_sprites(&mut self, sprites: &[Sprite]); fn load_frame_sprites(&mut self, sprites: &[Sprite]);
fn draw_text(&mut self, x: i32, y: i32, text: &str, color: Color); fn draw_text(&mut self, x: i32, y: i32, text: &str, color: Color);

View File

@ -2,9 +2,14 @@ use crate::asset_bridge::AssetBridge;
use crate::audio_bridge::AudioBridge; use crate::audio_bridge::AudioBridge;
use crate::gfx_bridge::GfxBridge; use crate::gfx_bridge::GfxBridge;
use crate::pad_bridge::PadBridge; use crate::pad_bridge::PadBridge;
use crate::sprite::Sprite;
use crate::touch_bridge::TouchBridge; use crate::touch_bridge::TouchBridge;
pub trait HardwareBridge { 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(&self) -> &dyn GfxBridge;
fn gfx_mut(&mut self) -> &mut dyn GfxBridge; fn gfx_mut(&mut self) -> &mut dyn GfxBridge;

View File

@ -162,17 +162,19 @@ impl NativeInterface for VirtualMachineRuntime {
return Ok(()); return Ok(());
} }
*hw.gfx_mut().sprite_mut(index) = Sprite { if active {
hw.emit_sprite(Sprite {
glyph: Glyph { glyph_id, palette_id }, glyph: Glyph { glyph_id, palette_id },
x, x,
y, y,
layer: 0, layer: 0,
bank_id, bank_id,
active, active: false,
flip_x, flip_x,
flip_y, flip_y,
priority, priority,
}; });
}
ret.push_int(GfxOpStatus::Ok as i64); ret.push_int(GfxOpStatus::Ok as i64);
Ok(()) Ok(())
} }

View File

@ -5,6 +5,7 @@ use prometeu_bytecode::Value;
use prometeu_bytecode::assembler::assemble; use prometeu_bytecode::assembler::assemble;
use prometeu_bytecode::model::{BytecodeModule, ConstantPoolEntry, FunctionMeta, SyscallDecl}; use prometeu_bytecode::model::{BytecodeModule, ConstantPoolEntry, FunctionMeta, SyscallDecl};
use prometeu_drivers::hardware::Hardware; use prometeu_drivers::hardware::Hardware;
use prometeu_drivers::{GlyphBankPoolInstaller, MemoryBanks, SceneBankPoolInstaller};
use prometeu_hal::AudioOpStatus; use prometeu_hal::AudioOpStatus;
use prometeu_hal::GfxOpStatus; use prometeu_hal::GfxOpStatus;
use prometeu_hal::InputSignals; use prometeu_hal::InputSignals;
@ -12,10 +13,17 @@ use prometeu_hal::asset::{
AssetCodec, AssetEntry, AssetLoadError, AssetOpStatus, BankType, LoadStatus, AssetCodec, AssetEntry, AssetLoadError, AssetOpStatus, BankType, LoadStatus,
}; };
use prometeu_hal::cartridge::{AssetsPayloadSource, Cartridge}; 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::syscalls::caps;
use prometeu_hal::tile::Tile;
use prometeu_hal::tilemap::TileMap;
use prometeu_vm::VmInitError; use prometeu_vm::VmInitError;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
#[derive(Default)] #[derive(Default)]
@ -129,6 +137,40 @@ fn test_glyph_asset_data() -> Vec<u8> {
data 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] #[test]
fn initialize_vm_applies_cartridge_capabilities_before_loader_resolution() { fn initialize_vm_applies_cartridge_capabilities_before_loader_resolution() {
let mut runtime = VirtualMachineRuntime::new(None); 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 { .. }))); 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] #[test]
fn initialize_vm_success_clears_previous_crash_report() { fn initialize_vm_success_clears_previous_crash_report() {
let mut runtime = VirtualMachineRuntime::new(None); let mut runtime = VirtualMachineRuntime::new(None);

View File

@ -145,7 +145,7 @@ impl VirtualMachineRuntime {
if run.reason == LogicalFrameEndingReason::FrameSync if run.reason == LogicalFrameEndingReason::FrameSync
|| run.reason == LogicalFrameEndingReason::EndOfRom || run.reason == LogicalFrameEndingReason::EndOfRom
{ {
hw.gfx_mut().render_all(); hw.render_frame();
// 1. Snapshot full telemetry at logical frame end // 1. Snapshot full telemetry at logical frame end
let (glyph_bank, sound_bank) = Self::bank_telemetry_summary(hw); let (glyph_bank, sound_bank) = Self::bank_telemetry_summary(hw);
@ -250,6 +250,7 @@ impl VirtualMachineRuntime {
_signals: &InputSignals, _signals: &InputSignals,
hw: &mut dyn HardwareBridge, hw: &mut dyn HardwareBridge,
) { ) {
hw.begin_frame();
hw.audio_mut().clear_commands(); hw.audio_mut().clear_commands();
self.logs_written_this_frame.clear(); self.logs_written_this_frame.clear();
} }