implements PLN-0077 composer buffer packet
This commit is contained in:
parent
7c35edcb84
commit
5e3cb87b57
@ -1,6 +1,5 @@
|
||||
use crate::asset::GlyphAssetSlotIndex;
|
||||
use crate::memory_banks::SceneBankPoolAccess;
|
||||
use prometeu_hal::GfxBridge;
|
||||
use prometeu_hal::glyph::Glyph;
|
||||
use prometeu_hal::scene_bank::SceneBank;
|
||||
use prometeu_hal::scene_viewport_cache::SceneViewportCache;
|
||||
@ -8,6 +7,10 @@ use prometeu_hal::scene_viewport_resolver::{
|
||||
CacheRefreshRequest, ResolverUpdate, SceneViewportResolver,
|
||||
};
|
||||
use prometeu_hal::sprite::Sprite;
|
||||
use prometeu_hal::{
|
||||
BoundScenePacket, Camera2D, ComposerFramePacket, Game2DFramePacket, GameSpritePacket,
|
||||
GfxBridge, HudPacket,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
const EMPTY_SPRITE: Sprite = Sprite {
|
||||
@ -132,6 +135,28 @@ pub struct FrameComposer {
|
||||
sprite_controller: SpriteController,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct ComposerBuffer {
|
||||
pub bound_scene: Option<BoundScenePacket>,
|
||||
pub camera: Camera2D,
|
||||
pub sprites: Vec<GameSpritePacket>,
|
||||
pub hud: HudPacket,
|
||||
}
|
||||
|
||||
impl ComposerBuffer {
|
||||
pub fn close(self) -> Game2DFramePacket {
|
||||
Game2DFramePacket::new(
|
||||
ComposerFramePacket {
|
||||
bound_scene: self.bound_scene,
|
||||
camera: self.camera,
|
||||
sprites: self.sprites,
|
||||
hud: self.hud,
|
||||
},
|
||||
Vec::new(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FrameComposer {
|
||||
pub fn new(
|
||||
viewport_width_px: usize,
|
||||
@ -246,6 +271,35 @@ impl FrameComposer {
|
||||
self.sprite_controller.ordered_sprites()
|
||||
}
|
||||
|
||||
pub fn composer_buffer_snapshot(&self) -> ComposerBuffer {
|
||||
ComposerBuffer {
|
||||
bound_scene: self
|
||||
.active_scene_id
|
||||
.map(|scene_bank_id| BoundScenePacket { bank_id: scene_bank_id as i32 }),
|
||||
camera: Camera2D { x: self.camera_x_px, y: self.camera_y_px },
|
||||
sprites: self
|
||||
.ordered_sprites()
|
||||
.into_iter()
|
||||
.map(|sprite| GameSpritePacket {
|
||||
glyph_id: sprite.glyph.glyph_id as i32,
|
||||
palette_id: sprite.glyph.palette_id as i32,
|
||||
x: sprite.x,
|
||||
y: sprite.y,
|
||||
layer: sprite.layer as i32,
|
||||
bank_id: sprite.bank_id as i32,
|
||||
flip_x: sprite.flip_x,
|
||||
flip_y: sprite.flip_y,
|
||||
priority: sprite.priority as i32,
|
||||
})
|
||||
.collect(),
|
||||
hud: HudPacket::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close_game2d_packet(&self) -> Game2DFramePacket {
|
||||
self.composer_buffer_snapshot().close()
|
||||
}
|
||||
|
||||
pub fn render_frame(&mut self, gfx: &mut dyn GfxBridge) {
|
||||
let ordered_sprites = self.ordered_sprites();
|
||||
gfx.load_frame_sprites(&ordered_sprites);
|
||||
@ -759,6 +813,73 @@ mod tests {
|
||||
assert_eq!(gfx.front_buffer()[0], Color::WHITE.raw());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composer_buffer_snapshot_closes_into_game2d_packet_without_fade_state() {
|
||||
let mut frame_composer = FrameComposer::new(
|
||||
Hardware::W,
|
||||
Hardware::H,
|
||||
Arc::new(MemoryBanks::default()),
|
||||
make_glyph_slot_index(&[]),
|
||||
);
|
||||
|
||||
frame_composer.set_camera(10, 20);
|
||||
frame_composer.begin_frame();
|
||||
assert!(frame_composer.emit_sprite(Sprite {
|
||||
glyph: Glyph { glyph_id: 7, palette_id: 3 },
|
||||
x: 4,
|
||||
y: 5,
|
||||
layer: 2,
|
||||
bank_id: 1,
|
||||
active: false,
|
||||
flip_x: true,
|
||||
flip_y: false,
|
||||
priority: 9,
|
||||
}));
|
||||
|
||||
let packet = frame_composer.close_game2d_packet();
|
||||
|
||||
assert_eq!(packet.composer.bound_scene, None);
|
||||
assert_eq!(packet.composer.camera, Camera2D { x: 10, y: 20 });
|
||||
assert_eq!(packet.composer.sprites.len(), 1);
|
||||
assert_eq!(packet.composer.sprites[0].glyph_id, 7);
|
||||
assert_eq!(packet.composer.sprites[0].palette_id, 3);
|
||||
assert_eq!(packet.composer.sprites[0].layer, 2);
|
||||
assert!(packet.composer.sprites[0].flip_x);
|
||||
assert!(packet.composer.hud.commands.is_empty());
|
||||
assert!(packet.gfx2d.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn composer_buffer_preserves_integer_layer_ordering() {
|
||||
let mut frame_composer = FrameComposer::new(
|
||||
Hardware::W,
|
||||
Hardware::H,
|
||||
Arc::new(MemoryBanks::default()),
|
||||
make_glyph_slot_index(&[]),
|
||||
);
|
||||
frame_composer.begin_frame();
|
||||
|
||||
for (glyph_id, layer, priority) in [(1, 3, 1), (2, 1, 2), (3, 1, 1), (4, 0, 9)] {
|
||||
assert!(frame_composer.emit_sprite(Sprite {
|
||||
glyph: Glyph { glyph_id, palette_id: 0 },
|
||||
x: 0,
|
||||
y: 0,
|
||||
layer,
|
||||
bank_id: 0,
|
||||
active: false,
|
||||
flip_x: false,
|
||||
flip_y: false,
|
||||
priority,
|
||||
}));
|
||||
}
|
||||
|
||||
let packet = frame_composer.close_game2d_packet();
|
||||
let glyph_order: Vec<i32> =
|
||||
packet.composer.sprites.iter().map(|sprite| sprite.glyph_id).collect();
|
||||
|
||||
assert_eq!(glyph_order, vec![4, 3, 2, 1]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn render_frame_with_scene_applies_refreshes_before_composition() {
|
||||
let banks = Arc::new(MemoryBanks::new());
|
||||
|
||||
@ -8,6 +8,7 @@ use crate::memory_banks::{
|
||||
};
|
||||
use crate::pad::Pad;
|
||||
use crate::touch::Touch;
|
||||
use prometeu_hal::Game2DFramePacket;
|
||||
use prometeu_hal::cartridge::AssetsPayloadSource;
|
||||
use prometeu_hal::sprite::Sprite;
|
||||
use prometeu_hal::{AssetBridge, AudioBridge, GfxBridge, HardwareBridge, PadBridge, TouchBridge};
|
||||
@ -67,6 +68,10 @@ impl HardwareBridge for Hardware {
|
||||
self.frame_composer.emit_sprite(sprite)
|
||||
}
|
||||
|
||||
fn close_game2d_packet(&self) -> Game2DFramePacket {
|
||||
self.frame_composer.close_game2d_packet()
|
||||
}
|
||||
|
||||
fn render_frame(&mut self) {
|
||||
self.frame_composer.render_frame(&mut self.gfx);
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ mod touch;
|
||||
|
||||
pub use crate::asset::AssetManager;
|
||||
pub use crate::audio::{Audio, AudioCommand, Channel, MAX_CHANNELS, OUTPUT_SAMPLE_RATE};
|
||||
pub use crate::frame_composer::{FrameComposer, SceneStatus, SpriteController};
|
||||
pub use crate::frame_composer::{ComposerBuffer, FrameComposer, SceneStatus, SpriteController};
|
||||
pub use crate::gfx::Gfx;
|
||||
pub use crate::memory_banks::{
|
||||
GlyphBankPoolAccess, GlyphBankPoolInstaller, MemoryBanks, SceneBankPoolAccess,
|
||||
|
||||
@ -2,6 +2,7 @@ use crate::asset_bridge::AssetBridge;
|
||||
use crate::audio_bridge::AudioBridge;
|
||||
use crate::gfx_bridge::GfxBridge;
|
||||
use crate::pad_bridge::PadBridge;
|
||||
use crate::render_submission::Game2DFramePacket;
|
||||
use crate::sprite::Sprite;
|
||||
use crate::touch_bridge::TouchBridge;
|
||||
|
||||
@ -11,6 +12,7 @@ pub trait HardwareBridge {
|
||||
fn unbind_scene(&mut self);
|
||||
fn set_camera(&mut self, x: i32, y: i32);
|
||||
fn emit_sprite(&mut self, sprite: Sprite) -> bool;
|
||||
fn close_game2d_packet(&self) -> Game2DFramePacket;
|
||||
fn render_frame(&mut self);
|
||||
fn has_glyph_bank(&self, bank_id: usize) -> bool;
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ use crate::fs::{FsState, VirtualFS};
|
||||
use crate::services::memcard::MemcardService;
|
||||
use prometeu_hal::asset::{BankTelemetry, BankType};
|
||||
use prometeu_hal::log::{LogLevel, LogService, LogSource};
|
||||
use prometeu_hal::{HardwareBridge, HostContext, InputSignals};
|
||||
use prometeu_hal::{HardwareBridge, HostContext, InputSignals, RenderSubmissionPacket};
|
||||
use prometeu_vm::LogicalFrameEndingReason;
|
||||
use std::collections::HashMap;
|
||||
use std::panic::{AssertUnwindSafe, catch_unwind};
|
||||
@ -221,7 +221,15 @@ impl VirtualMachineRuntime {
|
||||
|| run.reason == LogicalFrameEndingReason::EndOfRom
|
||||
{
|
||||
self.render_manager.set_active_app_mode(self.current_cartridge_app_mode);
|
||||
self.render_manager.close_compat_frame();
|
||||
if self.current_cartridge_app_mode == AppMode::Game {
|
||||
self.render_manager
|
||||
.close_frame_with_packet(RenderSubmissionPacket::Game2D(
|
||||
hw.close_game2d_packet(),
|
||||
))
|
||||
.expect("game packet must match Game app mode");
|
||||
} else {
|
||||
self.render_manager.close_compat_frame();
|
||||
}
|
||||
|
||||
if let Err(payload) = catch_unwind(AssertUnwindSafe(|| hw.render_frame())) {
|
||||
let message = Self::host_panic_payload_to_string(payload);
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
{"type":"meta","next_id":{"DSC":39,"AGD":39,"DEC":31,"PLN":87,"LSN":47,"CLSN":1}}
|
||||
{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0038","status":"in_progress","ticket":"render-frame-packet-boundary","title":"Logical Render Pipelines and Command Packets","created_at":"2026-05-25","updated_at":"2026-05-25","tags":["gfx","renderer","runtime","frame-composer","architecture","ui","pipeline"],"agendas":[{"id":"AGD-0038","file":"AGD-0038-renderframepacket-boundary-for-classic-2d-renderer.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25"}],"decisions":[{"id":"DEC-0030","file":"DEC-0030-logical-render-pipeline-command-boundary.md","status":"accepted","created_at":"2026-05-25","updated_at":"2026-05-25","ref_agenda":"AGD-0038"}],"plans":[{"id":"PLN-0073","file":"PLN-0073-render-contract-specs.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0074","file":"PLN-0074-hal-render-submission-types.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0075","file":"PLN-0075-rendermanager-core.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0076","file":"PLN-0076-capabilities-and-abi-domain-split.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0077","file":"PLN-0077-composer-buffer-and-game2d-packet.md","status":"done","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0078","file":"PLN-0078-classic2d-game-renderer-consumer.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0079","file":"PLN-0079-gfx2d-primitive-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0080","file":"PLN-0080-shell-ui-packet-and-gfxui-domain.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0081","file":"PLN-0081-shell-hub-migration.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0082","file":"PLN-0082-frame-publication-and-present-boundary.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0083","file":"PLN-0083-fade-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0084","file":"PLN-0084-host-debug-overlay-removal.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0085","file":"PLN-0085-pbs-stdlib-and-syscall-declarations.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]},{"id":"PLN-0086","file":"PLN-0086-end-to-end-render-boundary-validation.md","status":"open","created_at":"2026-05-25","updated_at":"2026-05-25","ref_decisions":["DEC-0030"]}],"lessons":[]}
|
||||
{"type":"discussion","id":"DSC-0035","status":"done","ticket":"task-owned-shell-windows","title":"Agenda - Task-Owned Shell Windows","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","task","window-manager","shell","lifecycle"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0044","file":"discussion/lessons/DSC-0035-task-owned-shell-windows/LSN-0044-task-window-liveness-belongs-to-the-task.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]}
|
||||
{"type":"discussion","id":"DSC-0034","status":"done","ticket":"system-os-domain-facades","title":"Agenda - SystemOS Domain Facades","created_at":"2026-05-15","updated_at":"2026-05-15","tags":["runtime","os","services","api-surface","lifecycle","fs"],"agendas":[],"decisions":[],"plans":[],"lessons":[{"id":"LSN-0043","file":"discussion/lessons/DSC-0034-system-os-domain-facades/LSN-0043-systemos-domain-facades.md","status":"done","created_at":"2026-05-15","updated_at":"2026-05-15"}]}
|
||||
{"type":"discussion","id":"DSC-0023","status":"done","ticket":"perf-full-migration-to-atomic-telemetry","title":"Agenda - [PERF] Full Migration to Atomic Telemetry","created_at":"2026-04-10","updated_at":"2026-04-10","tags":["perf","runtime","telemetry"],"agendas":[{"id":"AGD-0021","file":"AGD-0021-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"decisions":[{"id":"DEC-0008","file":"DEC-0008-full-migration-to-atomic-telemetry.md","status":"accepted","created_at":"2026-04-10","updated_at":"2026-04-10"}],"plans":[{"id":"PLN-0007","file":"PLN-0007-full-migration-to-atomic-telemetry.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}],"lessons":[{"id":"LSN-0028","file":"discussion/lessons/DSC-0023-perf-full-migration-to-atomic-telemetry/LSN-0028-converging-to-single-atomic-telemetry-source.md","status":"done","created_at":"2026-04-10","updated_at":"2026-04-10"}]}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
id: PLN-0077
|
||||
ticket: render-frame-packet-boundary
|
||||
title: Composer Buffer and Game2D Packet
|
||||
status: open
|
||||
status: done
|
||||
created: 2026-05-25
|
||||
ref_decisions: [DEC-0030]
|
||||
tags: [gfx, renderer, runtime, frame-composer, architecture, ui, pipeline]
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user