use crate::asset::GlyphAssetSlotIndex; use crate::frame_composer::FrameComposer; use crate::gfx::Gfx; use crate::memory_banks::{GlyphBankPoolAccess, MemoryBanks, SceneBankPoolAccess}; use prometeu_hal::app_mode::AppMode; use prometeu_hal::{ OwnedRgba8888Frame, RenderSubmission, RenderSubmissionPacket, RenderWorkerBackend, RenderWorkerError, }; use std::sync::{Arc, Mutex}; pub struct LocalFramebufferRenderBackend { gfx: Mutex, frame_composer: Mutex, } impl LocalFramebufferRenderBackend { pub fn new(width: usize, height: usize, glyph_banks: Arc) -> Self { Self::new_with_parts( width, height, glyph_banks, Arc::new(MemoryBanks::new()), GlyphAssetSlotIndex::new(), ) } pub fn new_with_memory_banks( width: usize, height: usize, memory_banks: Arc, glyph_slot_index: GlyphAssetSlotIndex, ) -> Self { Self::new_with_parts( width, height, Arc::clone(&memory_banks) as Arc, memory_banks, glyph_slot_index, ) } fn new_with_parts( width: usize, height: usize, glyph_banks: Arc, scene_banks: Arc, glyph_slot_index: GlyphAssetSlotIndex, ) -> Self { Self { gfx: Mutex::new(Gfx::new(width, height, glyph_banks)), frame_composer: Mutex::new(FrameComposer::new( width, height, scene_banks, glyph_slot_index, )), } } pub fn render_submission( &self, submission: &RenderSubmission, ) -> Result { let mut gfx = self.gfx.lock().map_err(|_| RenderWorkerError::InternalFailure)?; match &submission.packet { RenderSubmissionPacket::Game2D(packet) if submission.app_mode == AppMode::Game => { if packet.composer.bound_scene.is_none() { gfx.render_game2d_frame_packet(packet); } else { let mut frame_composer = self .frame_composer .lock() .map_err(|_| RenderWorkerError::InternalFailure)?; frame_composer.render_game2d_frame_packet(packet, &mut *gfx); gfx.apply_gfx2d_commands(&packet.gfx2d); } } RenderSubmissionPacket::ShellUi(packet) if submission.app_mode == AppMode::Shell => { gfx.render_shell_ui_frame_packet(packet); } _ => return Err(RenderWorkerError::RenderFailed), } gfx.present(); let (width, height) = gfx.size(); OwnedRgba8888Frame::packed( submission.frame_id, submission.ownership, width, height, gfx.front_buffer().to_vec(), ) .map_err(|_| RenderWorkerError::InternalFailure) } } impl RenderWorkerBackend for LocalFramebufferRenderBackend { fn render( &self, submission: &RenderSubmission, ) -> Result { self.render_submission(submission) } } #[cfg(test)] mod tests { use super::*; use crate::memory_banks::MemoryBanks; use prometeu_hal::app_mode::AppMode; use prometeu_hal::color::Color; use prometeu_hal::primitives::Rect; use prometeu_hal::{ FrameId, Game2DFramePacket, Gfx2dCommand, GfxUiCommand, RenderOwnership, ShellUiFramePacket, }; fn backend() -> LocalFramebufferRenderBackend { let banks = Arc::new(MemoryBanks::new()); LocalFramebufferRenderBackend::new(4, 4, banks) } #[test] fn local_backend_renders_game2d_submission_to_owned_frame() { let backend = backend(); let mut packet = Game2DFramePacket::default(); packet.gfx2d = vec![Gfx2dCommand::FillRect { rect: Rect { x: 1, y: 1, w: 2, h: 2 }, color: Color::RED, }]; let ownership = RenderOwnership::new(3, AppMode::Game, 9); let submission = RenderSubmission::game2d(FrameId::new(5), packet).with_ownership(ownership); let frame = backend.render(&submission).expect("game frame should render"); assert_eq!(frame.frame_id, FrameId::new(5)); assert_eq!(frame.ownership, ownership); assert_eq!(frame.width, 4); assert_eq!(frame.height, 4); assert_eq!(frame.pixels[1 + 4], Color::RED.raw()); assert_eq!(frame.pixels[2 + 2 * 4], Color::RED.raw()); } #[test] fn local_backend_renders_shell_ui_submission_to_owned_frame() { let backend = backend(); let packet = ShellUiFramePacket::new(vec![GfxUiCommand::FillRect { rect: Rect { x: 0, y: 0, w: 1, h: 1 }, color: Color::GREEN, }]); let ownership = RenderOwnership::new(4, AppMode::Shell, 10); let submission = RenderSubmission::shell_ui(FrameId::new(6), packet).with_ownership(ownership); let frame = backend.render(&submission).expect("shell frame should render"); assert_eq!(frame.frame_id, FrameId::new(6)); assert_eq!(frame.ownership, ownership); assert_eq!(frame.pixels[0], Color::GREEN.raw()); } }