dev/render-all-scene-cache-and-camera-integration #16
@ -52,6 +52,8 @@ pub struct Gfx {
|
||||
pub glyph_banks: Arc<dyn GlyphBankPoolAccess>,
|
||||
/// Deferred overlay/debug capture kept separate from canonical game composition.
|
||||
overlay: DeferredGfxOverlay,
|
||||
/// Internal guard to replay deferred overlay commands without re-enqueueing them.
|
||||
is_draining_overlay: bool,
|
||||
/// Hardware sprite list (512 slots). Equivalent to OAM (Object Attribute Memory).
|
||||
pub sprites: [Sprite; 512],
|
||||
|
||||
@ -292,6 +294,7 @@ impl Gfx {
|
||||
back: vec![0; len],
|
||||
glyph_banks,
|
||||
overlay: DeferredGfxOverlay::default(),
|
||||
is_draining_overlay: false,
|
||||
sprites: [EMPTY_SPRITE; 512],
|
||||
sprite_count: 0,
|
||||
scene_fade_level: 31,
|
||||
@ -319,6 +322,34 @@ impl Gfx {
|
||||
&self.overlay
|
||||
}
|
||||
|
||||
pub fn drain_overlay_debug(&mut self) {
|
||||
let commands = self.overlay.take_commands();
|
||||
self.is_draining_overlay = true;
|
||||
|
||||
for command in commands {
|
||||
match command {
|
||||
OverlayCommand::FillRectBlend { x, y, w, h, color, mode } => {
|
||||
self.fill_rect_blend(x, y, w, h, color, mode)
|
||||
}
|
||||
OverlayCommand::DrawLine { x0, y0, x1, y1, color } => {
|
||||
self.draw_line(x0, y0, x1, y1, color)
|
||||
}
|
||||
OverlayCommand::DrawCircle { x, y, r, color } => self.draw_circle(x, y, r, color),
|
||||
OverlayCommand::DrawDisc { x, y, r, border_color, fill_color } => {
|
||||
self.draw_disc(x, y, r, border_color, fill_color)
|
||||
}
|
||||
OverlayCommand::DrawSquare { x, y, w, h, border_color, fill_color } => {
|
||||
self.draw_square(x, y, w, h, border_color, fill_color)
|
||||
}
|
||||
OverlayCommand::DrawText { x, y, text, color } => {
|
||||
self.draw_text(x, y, &text, color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.is_draining_overlay = false;
|
||||
}
|
||||
|
||||
/// The buffer that the host should display (RGB565).
|
||||
pub fn front_buffer(&self) -> &[u16] {
|
||||
&self.front
|
||||
@ -338,7 +369,10 @@ impl Gfx {
|
||||
color: Color,
|
||||
mode: BlendMode,
|
||||
) {
|
||||
self.overlay.push(OverlayCommand::FillRectBlend { x, y, w, h, color, mode });
|
||||
if !self.is_draining_overlay {
|
||||
self.overlay.push(OverlayCommand::FillRectBlend { x, y, w, h, color, mode });
|
||||
return;
|
||||
}
|
||||
if color == Color::COLOR_KEY {
|
||||
return;
|
||||
}
|
||||
@ -380,7 +414,10 @@ impl Gfx {
|
||||
|
||||
/// 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) {
|
||||
self.overlay.push(OverlayCommand::DrawLine { x0, y0, x1, y1, color });
|
||||
if !self.is_draining_overlay {
|
||||
self.overlay.push(OverlayCommand::DrawLine { x0, y0, x1, y1, color });
|
||||
return;
|
||||
}
|
||||
if color == Color::COLOR_KEY {
|
||||
return;
|
||||
}
|
||||
@ -413,7 +450,10 @@ impl Gfx {
|
||||
|
||||
/// Draws a circle outline using Midpoint Circle Algorithm.
|
||||
pub fn draw_circle(&mut self, xc: i32, yc: i32, r: i32, color: Color) {
|
||||
self.overlay.push(OverlayCommand::DrawCircle { x: xc, y: yc, r, color });
|
||||
if !self.is_draining_overlay {
|
||||
self.overlay.push(OverlayCommand::DrawCircle { x: xc, y: yc, r, color });
|
||||
return;
|
||||
}
|
||||
if color == Color::COLOR_KEY {
|
||||
return;
|
||||
}
|
||||
@ -482,7 +522,10 @@ impl Gfx {
|
||||
|
||||
/// 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.overlay.push(OverlayCommand::DrawDisc { x, y, r, border_color, fill_color });
|
||||
if !self.is_draining_overlay {
|
||||
self.overlay.push(OverlayCommand::DrawDisc { x, y, r, border_color, fill_color });
|
||||
return;
|
||||
}
|
||||
self.fill_circle(x, y, r, fill_color);
|
||||
self.draw_circle(x, y, r, border_color);
|
||||
}
|
||||
@ -512,7 +555,10 @@ impl Gfx {
|
||||
border_color: Color,
|
||||
fill_color: Color,
|
||||
) {
|
||||
self.overlay.push(OverlayCommand::DrawSquare { x, y, w, h, border_color, fill_color });
|
||||
if !self.is_draining_overlay {
|
||||
self.overlay.push(OverlayCommand::DrawSquare { x, y, w, h, border_color, fill_color });
|
||||
return;
|
||||
}
|
||||
self.fill_rect(x, y, w, h, fill_color);
|
||||
self.draw_rect(x, y, w, h, border_color);
|
||||
}
|
||||
@ -800,7 +846,10 @@ impl Gfx {
|
||||
}
|
||||
|
||||
pub fn draw_text(&mut self, x: i32, y: i32, text: &str, color: Color) {
|
||||
self.overlay.push(OverlayCommand::DrawText { x, y, text: text.to_string(), color });
|
||||
if !self.is_draining_overlay {
|
||||
self.overlay.push(OverlayCommand::DrawText { x, y, text: text.to_string(), color });
|
||||
return;
|
||||
}
|
||||
let mut cx = x;
|
||||
for c in text.chars() {
|
||||
self.draw_char(cx, y, c, color);
|
||||
@ -947,12 +996,10 @@ mod tests {
|
||||
let mut gfx = Gfx::new(10, 10, banks);
|
||||
gfx.begin_overlay_frame();
|
||||
gfx.draw_line(0, 0, 9, 9, Color::WHITE);
|
||||
gfx.drain_overlay_debug();
|
||||
assert_eq!(gfx.back[0], Color::WHITE.0);
|
||||
assert_eq!(gfx.back[9 * 10 + 9], Color::WHITE.0);
|
||||
assert_eq!(
|
||||
gfx.overlay().commands(),
|
||||
&[OverlayCommand::DrawLine { x0: 0, y0: 0, x1: 9, y1: 9, color: Color::WHITE }]
|
||||
);
|
||||
assert_eq!(gfx.overlay().command_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -980,11 +1027,12 @@ mod tests {
|
||||
let mut gfx = Gfx::new(10, 10, banks);
|
||||
gfx.begin_overlay_frame();
|
||||
gfx.draw_square(2, 2, 6, 6, Color::WHITE, Color::BLACK);
|
||||
gfx.drain_overlay_debug();
|
||||
// Border
|
||||
assert_eq!(gfx.back[2 * 10 + 2], Color::WHITE.0);
|
||||
// Fill
|
||||
assert_eq!(gfx.back[3 * 10 + 3], Color::BLACK.0);
|
||||
assert_eq!(gfx.overlay().command_count(), 2);
|
||||
assert_eq!(gfx.overlay().command_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@ -32,4 +32,8 @@ impl DeferredGfxOverlay {
|
||||
pub fn command_count(&self) -> usize {
|
||||
self.commands.len()
|
||||
}
|
||||
|
||||
pub fn take_commands(&mut self) -> Vec<OverlayCommand> {
|
||||
std::mem::take(&mut self.commands)
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,6 +70,7 @@ impl HardwareBridge for Hardware {
|
||||
|
||||
fn render_frame(&mut self) {
|
||||
self.frame_composer.render_frame(&mut self.gfx);
|
||||
self.gfx.drain_overlay_debug();
|
||||
}
|
||||
|
||||
fn has_glyph_bank(&self, bank_id: usize) -> bool {
|
||||
|
||||
@ -354,6 +354,95 @@ fn tick_renders_scene_through_public_composer_syscalls() {
|
||||
assert_eq!(hardware.gfx.front_buffer()[0], Color::BLUE.raw());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tick_draw_text_survives_scene_backed_frame_composition() {
|
||||
let mut runtime = VirtualMachineRuntime::new(None);
|
||||
let mut vm = VirtualMachine::default();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble(
|
||||
"PUSH_I32 0\nHOSTCALL 0\nPOP_N 1\n\
|
||||
PUSH_I32 0\nPUSH_I32 0\nHOSTCALL 1\n\
|
||||
PUSH_I32 0\nPUSH_I32 0\nPUSH_CONST 0\nPUSH_I32 65535\nHOSTCALL 2\n\
|
||||
FRAME_SYNC\nHALT",
|
||||
)
|
||||
.expect("assemble");
|
||||
let program = serialized_single_function_module_with_consts(
|
||||
code,
|
||||
vec![ConstantPoolEntry::String("I".into())],
|
||||
vec![
|
||||
SyscallDecl {
|
||||
module: "composer".into(),
|
||||
name: "bind_scene".into(),
|
||||
version: 1,
|
||||
arg_slots: 1,
|
||||
ret_slots: 1,
|
||||
},
|
||||
SyscallDecl {
|
||||
module: "composer".into(),
|
||||
name: "set_camera".into(),
|
||||
version: 1,
|
||||
arg_slots: 2,
|
||||
ret_slots: 0,
|
||||
},
|
||||
SyscallDecl {
|
||||
module: "gfx".into(),
|
||||
name: "draw_text".into(),
|
||||
version: 1,
|
||||
arg_slots: 4,
|
||||
ret_slots: 0,
|
||||
},
|
||||
],
|
||||
);
|
||||
let cartridge = cartridge_with_program(program, caps::GFX);
|
||||
|
||||
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));
|
||||
hardware.gfx.scene_fade_level = 31;
|
||||
hardware.gfx.hud_fade_level = 31;
|
||||
|
||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
||||
|
||||
assert!(report.is_none(), "scene-backed overlay text must not crash");
|
||||
hardware.gfx.present();
|
||||
assert_eq!(hardware.gfx.front_buffer()[0], Color::WHITE.raw());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tick_draw_text_survives_no_scene_frame_path() {
|
||||
let mut runtime = VirtualMachineRuntime::new(None);
|
||||
let mut vm = VirtualMachine::default();
|
||||
let signals = InputSignals::default();
|
||||
let code = assemble(
|
||||
"PUSH_I32 0\nPUSH_I32 0\nPUSH_CONST 0\nPUSH_I32 65535\nHOSTCALL 0\nFRAME_SYNC\nHALT",
|
||||
)
|
||||
.expect("assemble");
|
||||
let program = serialized_single_function_module_with_consts(
|
||||
code,
|
||||
vec![ConstantPoolEntry::String("I".into())],
|
||||
vec![SyscallDecl {
|
||||
module: "gfx".into(),
|
||||
name: "draw_text".into(),
|
||||
version: 1,
|
||||
arg_slots: 4,
|
||||
ret_slots: 0,
|
||||
}],
|
||||
);
|
||||
let cartridge = cartridge_with_program(program, caps::GFX);
|
||||
let mut hardware = Hardware::new();
|
||||
hardware.gfx.scene_fade_level = 31;
|
||||
hardware.gfx.hud_fade_level = 31;
|
||||
|
||||
runtime.initialize_vm(&mut vm, &cartridge).expect("runtime must initialize");
|
||||
let report = runtime.tick(&mut vm, &signals, &mut hardware);
|
||||
|
||||
assert!(report.is_none(), "no-scene overlay text must not crash");
|
||||
hardware.gfx.present();
|
||||
assert_eq!(hardware.gfx.front_buffer()[0], Color::WHITE.raw());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn initialize_vm_success_clears_previous_crash_report() {
|
||||
let mut runtime = VirtualMachineRuntime::new(None);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user