diff --git a/emu/src/cpu/arm/instructions.rs b/emu/src/cpu/arm/instructions.rs index 293ad82..ca84adc 100644 --- a/emu/src/cpu/arm/instructions.rs +++ b/emu/src/cpu/arm/instructions.rs @@ -100,7 +100,6 @@ pub enum ArmModeInstruction { rm_operand_register: u32, }, PSRTransfer { - condition: Condition, psr_kind: PsrKind, kind: PsrOpKind, }, @@ -659,7 +658,6 @@ impl From for ArmModeInstruction { { // PSR instruction return PSRTransfer { - condition, psr_kind: PsrKind::from(op_code.get_bit(22)), kind: PsrOpKind::from(op_code), }; diff --git a/emu/src/cpu/arm/mode.rs b/emu/src/cpu/arm/mode.rs index cc2f444..03d0356 100644 --- a/emu/src/cpu/arm/mode.rs +++ b/emu/src/cpu/arm/mode.rs @@ -47,11 +47,7 @@ impl std::fmt::Display for ArmModeOpcode { ArmModeInstruction::MultiplyLong { .. } => { "FMT: |_Cond__|0_0_0|__code__|S|_RdHi_|_RdLo_|_Rs__|1_0_0_1|_Rm__|" } - ArmModeInstruction::PSRTransfer { - condition: _, - psr_kind: _, - kind, - } => match kind { + ArmModeInstruction::PSRTransfer { psr_kind: _, kind } => match kind { crate::cpu::arm::alu_instruction::PsrOpKind::Mrs { .. } => { "FMT: |_Cond__|0_0_0_1_0|P|0_0_1_1_1_1|_Rd__|0_0_0_0_0_0_0_0_0_0_0_0_0|" } diff --git a/emu/src/cpu/arm/operations.rs b/emu/src/cpu/arm/operations.rs index 75a00fe..118128d 100644 --- a/emu/src/cpu/arm/operations.rs +++ b/emu/src/cpu/arm/operations.rs @@ -414,7 +414,7 @@ impl Arm7tdmi { ArithmeticOpResult { result, - carry: first_op < second_op, + carry: first_op >= second_op, overflow: different_sign && sign_op2 == sign_r, sign: result.get_bit(31), zero: result == 0, @@ -472,7 +472,7 @@ impl Arm7tdmi { } } - fn mov(&mut self, rd: usize, op2: u32, s: bool) { + pub fn mov(&mut self, rd: usize, op2: u32, s: bool) { self.registers.set_register_at(rd, op2); if s { @@ -1080,7 +1080,6 @@ mod tests { assert_eq!( op_code.instruction, ArmModeInstruction::PSRTransfer { - condition: Condition::EQ, psr_kind: PsrKind::Cpsr, kind: PsrOpKind::Msr { source_register: 12 @@ -1154,7 +1153,7 @@ mod tests { cpu.execute_arm(op_code); assert!(!cpu.cpsr.sign_flag()); assert!(cpu.cpsr.zero_flag()); - assert!(!cpu.cpsr.carry_flag()); + assert!(cpu.cpsr.carry_flag()); assert!(!cpu.cpsr.overflow_flag()); } @@ -1587,7 +1586,6 @@ mod tests { assert_eq!( op_code.instruction, ArmModeInstruction::PSRTransfer { - condition: Condition::EQ, psr_kind: PsrKind::Cpsr, kind: PsrOpKind::Mrs { destination_register: 12 @@ -1704,7 +1702,7 @@ mod tests { cpu.execute_arm(op_code); assert_eq!(cpu.registers.register_at(1), 5); - assert!(!cpu.cpsr.carry_flag()); + assert!(cpu.cpsr.carry_flag()); assert!(!cpu.cpsr.overflow_flag()); assert!(!cpu.cpsr.zero_flag()); assert!(!cpu.cpsr.sign_flag()); @@ -1716,7 +1714,7 @@ mod tests { cpu.execute_arm(op_code); assert_eq!(cpu.registers.register_at(1) as i32, -5); - assert!(cpu.cpsr.carry_flag()); + assert!(!cpu.cpsr.carry_flag()); assert!(!cpu.cpsr.overflow_flag()); assert!(cpu.cpsr.sign_flag()); assert!(!cpu.cpsr.zero_flag()); @@ -1747,7 +1745,7 @@ mod tests { cpu.execute_arm(op_code); assert_eq!(cpu.registers.register_at(1), (i32::MIN + 1) as u32); - assert!(cpu.cpsr.carry_flag()); + assert!(!cpu.cpsr.carry_flag()); assert!(cpu.cpsr.overflow_flag()); assert!(cpu.cpsr.sign_flag()); assert!(!cpu.cpsr.zero_flag()); @@ -1953,7 +1951,7 @@ mod tests { cpu.execute_arm(op_code); assert_eq!(cpu.registers.register_at(1), 5); - assert!(!cpu.cpsr.carry_flag()); + assert!(cpu.cpsr.carry_flag()); assert!(!cpu.cpsr.zero_flag()); assert!(!cpu.cpsr.overflow_flag()); assert!(!cpu.cpsr.sign_flag()); @@ -2118,7 +2116,7 @@ mod tests { cpu.execute_arm(op_code); assert_eq!(cpu.registers.register_at(1), i32::MAX as u32); - assert!(!cpu.cpsr.carry_flag()); + assert!(cpu.cpsr.carry_flag()); assert!(!cpu.cpsr.zero_flag()); assert!(cpu.cpsr.overflow_flag()); assert!(!cpu.cpsr.sign_flag()); @@ -2151,7 +2149,7 @@ mod tests { cpu.execute_arm(op_code); assert_eq!(cpu.registers.register_at(1), i32::MAX as u32); - assert!(!cpu.cpsr.carry_flag()); + assert!(cpu.cpsr.carry_flag()); assert!(!cpu.cpsr.zero_flag()); assert!(cpu.cpsr.overflow_flag()); assert!(!cpu.cpsr.sign_flag()); @@ -2181,7 +2179,6 @@ mod tests { assert_eq!( op_code.instruction, ArmModeInstruction::PSRTransfer { - condition: Condition::AL, psr_kind: PsrKind::Cpsr, kind: PsrOpKind::Mrs { destination_register: 0 @@ -2210,7 +2207,6 @@ mod tests { assert_eq!( op_code.instruction, ArmModeInstruction::PSRTransfer { - condition: Condition::AL, psr_kind: PsrKind::Spsr, kind: PsrOpKind::Mrs { destination_register: 0 @@ -2242,7 +2238,6 @@ mod tests { assert_eq!( op_code.instruction, ArmModeInstruction::PSRTransfer { - condition: Condition::AL, psr_kind: PsrKind::Cpsr, kind: PsrOpKind::Msr { source_register: 0 } } @@ -2264,7 +2259,6 @@ mod tests { assert_eq!( op_code.instruction, ArmModeInstruction::PSRTransfer { - condition: Condition::AL, psr_kind: PsrKind::Spsr, kind: PsrOpKind::Msr { source_register: 0 } } @@ -2286,7 +2280,6 @@ mod tests { assert_eq!( op_code.instruction, ArmModeInstruction::PSRTransfer { - condition: Condition::AL, psr_kind: PsrKind::Cpsr, kind: PsrOpKind::MsrFlg { operand: AluSecondOperandInfo::Register { @@ -2314,7 +2307,6 @@ mod tests { assert_eq!( op_code.instruction, ArmModeInstruction::PSRTransfer { - condition: Condition::AL, psr_kind: PsrKind::Spsr, kind: PsrOpKind::MsrFlg { operand: AluSecondOperandInfo::Register { diff --git a/emu/src/cpu/arm7tdmi.rs b/emu/src/cpu/arm7tdmi.rs index 2efcb0f..06bbb9a 100644 --- a/emu/src/cpu/arm7tdmi.rs +++ b/emu/src/cpu/arm7tdmi.rs @@ -201,11 +201,7 @@ impl Arm7tdmi { rn, destination, ), - ArmModeInstruction::PSRTransfer { - condition: _, - psr_kind, - kind, - } => self.psr_transfer(kind, psr_kind), + ArmModeInstruction::PSRTransfer { psr_kind, kind } => self.psr_transfer(kind, psr_kind), ArmModeInstruction::Multiply { variant, should_set_codes, @@ -304,7 +300,9 @@ impl Arm7tdmi { ArmModeInstruction::CoprocessorDataTransfer { .. } => todo!(), ArmModeInstruction::CoprocessorDataOperation => todo!(), ArmModeInstruction::CoprocessorRegisterTransfer => todo!(), - ArmModeInstruction::SoftwareInterrupt => todo!(), + ArmModeInstruction::SoftwareInterrupt => { + self.handle_exception(ExceptionType::SoftwareInterrupt) + } }; } @@ -558,7 +556,7 @@ impl Arm7tdmi { Mode::Supervisor => { self.register_bank.r13_svc = self.registers.register_at(13); self.register_bank.r14_svc = self.registers.register_at(14); - self.register_bank.spsr_svc = self.spsr; + self.register_bank.spsr_svc = self.cpsr; } Mode::Abort => { self.register_bank.r13_abt = self.registers.register_at(13); @@ -683,7 +681,7 @@ mod tests { use super::*; #[test] - fn check_branch() { + fn arm_branch() { // Covers a positive offset // 15(1111b) << 2 = 60 bytes @@ -718,7 +716,7 @@ mod tests { #[test] #[should_panic] - fn check_unknown_instruction() { + fn arm_unknown_instruction() { let op_code = 0b1110_1111_1111_1111_1111_1111_1111_1111; let mut cpu = Arm7tdmi::default(); @@ -729,7 +727,7 @@ mod tests { } #[test] - fn check_block_data_transfer() { + fn arm_block_data_transfer() { { // LDM with post-increment let op_code = 0b1110_100_0_1_0_1_1_1101_0000000010100010; @@ -889,7 +887,7 @@ mod tests { } #[test] - fn check_half_word_data_transfer() { + fn arm_half_word_data_transfer() { { // Register offset let op_code = 0b1110_0001_1000_0010_0000_0000_1011_0001; @@ -1064,7 +1062,7 @@ mod tests { } #[test] - fn check_pc_relative_load() { + fn thumb_pc_relative_load() { let mut cpu = Arm7tdmi::default(); let op_code = 0b0100_1001_0101_1000_u16; let op_code: ThumbModeOpcode = Arm7tdmi::decode(op_code); @@ -1077,7 +1075,7 @@ mod tests { } #[test] - fn check_load_store_register_offset() { + fn thumb_load_store_register_offset() { // Checks Store Word { let mut cpu = Arm7tdmi::default(); @@ -1140,7 +1138,7 @@ mod tests { } #[test] - fn check_load_store_immediate_offset() { + fn thumb_load_store_immediate_offset() { { // Store Word let op_code = 0b0110_0011_0111_1000; @@ -1193,7 +1191,7 @@ mod tests { } #[test] - fn check_add_subtract() { + fn thumb_add_subtract() { // Check sub { let mut cpu = Arm7tdmi::default(); @@ -1206,7 +1204,7 @@ mod tests { assert_eq!(cpu.registers.register_at(1), -1_i32 as u32); assert!(!cpu.cpsr.zero_flag()); - assert!(cpu.cpsr.carry_flag()); + assert!(!cpu.cpsr.carry_flag()); assert!(cpu.cpsr.sign_flag()); assert!(!cpu.cpsr.overflow_flag()); } @@ -1230,7 +1228,7 @@ mod tests { } #[test] - fn check_cond_branch() { + fn thumb_cond_branch() { let mut cpu = Arm7tdmi::default(); let op_code = 0b1101_1011_11111100; let op_code: ThumbModeOpcode = Arm7tdmi::decode(op_code); @@ -1253,7 +1251,7 @@ mod tests { } #[test] - fn check_uncond_branch() { + fn thumb_uncond_branch() { let mut cpu = Arm7tdmi::default(); let op_code = 0b1110_0001_0010_1111; let op_code: ThumbModeOpcode = Arm7tdmi::decode(op_code); @@ -1266,7 +1264,7 @@ mod tests { } #[test] - fn check_hi_reg_operation_branch_ex() { + fn thumb_hi_reg_operation_branch_ex() { { // BX Hs let mut cpu = Arm7tdmi::default(); @@ -1331,7 +1329,7 @@ mod tests { assert!(cpu.cpsr.zero_flag()); assert!(!cpu.cpsr.sign_flag()); assert!(!cpu.cpsr.overflow_flag()); - assert!(!cpu.cpsr.carry_flag()); + assert!(cpu.cpsr.carry_flag()); } { // Cmp Hd, Rs @@ -1346,7 +1344,7 @@ mod tests { assert!(!cpu.cpsr.zero_flag()); assert!(cpu.cpsr.sign_flag()); - assert!(cpu.cpsr.carry_flag()); + assert!(!cpu.cpsr.carry_flag()); assert!(!cpu.cpsr.overflow_flag()); } { @@ -1362,7 +1360,7 @@ mod tests { assert!(!cpu.cpsr.zero_flag()); assert!(!cpu.cpsr.sign_flag()); - assert!(!cpu.cpsr.carry_flag()); + assert!(cpu.cpsr.carry_flag()); assert!(!cpu.cpsr.overflow_flag()); } { @@ -1463,7 +1461,7 @@ mod tests { } #[test] - fn check_push_pop_register() { + fn thumb_push_pop_register() { { // Store + save LR let mut cpu = Arm7tdmi::default(); @@ -1516,7 +1514,7 @@ mod tests { } #[test] - fn check_add_offset_sp() { + fn thumb_add_offset_sp() { { // Positive offset let mut cpu = Arm7tdmi::default(); @@ -1542,7 +1540,7 @@ mod tests { } #[test] - fn check_sp_relative_load() { + fn thumb_sp_relative_load() { { // Load let mut cpu = Arm7tdmi::default(); @@ -1572,7 +1570,7 @@ mod tests { } #[test] - fn check_alu_op() { + fn thumb_alu_op() { { // mul let mut cpu = Arm7tdmi::default(); @@ -1643,10 +1641,24 @@ mod tests { assert!(cpu.cpsr.sign_flag()); assert!(!cpu.cpsr.zero_flag()); } + { + // lsl + let mut cpu = Arm7tdmi::default(); + let op_code = 0b0100_0000_1000_1000; + let op_code: ThumbModeOpcode = Arm7tdmi::decode(op_code); + + cpu.registers.set_register_at(0, 1); + cpu.registers.set_register_at(1, 1); + + cpu.execute_thumb(op_code); + + assert_eq!(cpu.registers.register_at(0), 2); + assert_eq!(cpu.registers.register_at(1), 1); + } } #[test] - fn check_long_branch_link() { + fn thumb_long_branch_link() { let mut cpu = Arm7tdmi::default(); let op_code = 0b1111_1000_0100_0000; let op_code: ThumbModeOpcode = Arm7tdmi::decode(op_code); @@ -1668,7 +1680,7 @@ mod tests { } #[test] - fn check_load_store_halfword() { + fn thumb_load_store_halfword() { { // Load let mut cpu = Arm7tdmi::default(); @@ -1698,7 +1710,7 @@ mod tests { } #[test] - fn check_load_store_sign_extend_byte_halfword() { + fn thumb_load_store_sign_extend_byte_halfword() { struct Test { opcode: u16, expected_decode: ThumbModeInstruction, @@ -1796,7 +1808,7 @@ mod tests { } #[test] - fn check_load_address() { + fn thumb_load_address() { struct Test { opcode: u16, expected_decode: ThumbModeInstruction, @@ -1850,7 +1862,7 @@ mod tests { } #[test] - fn check_multiple_load_store() { + fn thumb_multiple_load_store() { struct Test { opcode: u16, expected_decode: ThumbModeInstruction, diff --git a/emu/src/cpu/hardware/lcd.rs b/emu/src/cpu/hardware/lcd.rs index 1ef1dbb..05195d0 100644 --- a/emu/src/cpu/hardware/lcd.rs +++ b/emu/src/cpu/hardware/lcd.rs @@ -4,7 +4,15 @@ use object_attributes::RotationScaling; use crate::bitwise::Bits; +use self::object_attributes::ColorMode; +use self::object_attributes::ObjMode; +use self::object_attributes::ObjShape; +use self::object_attributes::ObjSize; +use self::object_attributes::TransformationKind; +use self::point::Point; + mod object_attributes; +mod point; /// GBA display width const LCD_WIDTH: usize = 240; @@ -12,10 +20,18 @@ const LCD_WIDTH: usize = 240; /// GBA display height const LCD_HEIGHT: usize = 160; +// Sprites are positioned inside a 512x256 size (x position is 9 bits and y position is 8 bits) +/// World height +const WORLD_HEIGHT: u16 = 256; + #[derive(Default, Clone, Copy)] pub struct Color(pub u16); impl Color { + pub const fn from_palette_color(value: u16) -> Self { + Self(value) + } + pub fn from_rgb(red: u8, green: u8, blue: u8) -> Self { let red: u16 = red.into(); let green: u16 = green.into(); @@ -37,6 +53,26 @@ impl Color { } } +enum ObjMappingKind { + TwoDimensional, + OneDimensional, +} + +impl From for ObjMappingKind { + fn from(value: bool) -> Self { + match value { + false => Self::TwoDimensional, + true => Self::OneDimensional, + } + } +} + +#[derive(Copy, Clone, Default)] +struct PixelInfo { + color: Color, + priority: u8, +} + pub struct Lcd { /// LCD Control pub dispcnt: u16, @@ -129,6 +165,7 @@ pub struct Lcd { should_draw: bool, obj_attributes_arr: [ObjAttributes; 128], rotation_scaling_params: [RotationScaling; 32], + sprite_pixels_scanline: [Option; LCD_WIDTH], } impl Default for Lcd { @@ -181,6 +218,7 @@ impl Default for Lcd { should_draw: false, obj_attributes_arr: [ObjAttributes::default(); 128], rotation_scaling_params: [RotationScaling::default(); 32], + sprite_pixels_scanline: [None; LCD_WIDTH], } } } @@ -192,6 +230,230 @@ pub struct LcdStepOutput { } impl Lcd { + fn read_color_from_obj_palette(&self, color_idx: usize) -> Color { + let low_nibble = self.obj_palette_ram[color_idx] as u16; + let high_nibble = self.obj_palette_ram[color_idx + 1] as u16; + + Color::from_palette_color((high_nibble << 8) | low_nibble) + } + + fn get_texture_space_point( + &self, + sprite_size: Point, + pixel_screen_sprite_origin: Point, + transformation_kind: TransformationKind, + obj_mode: ObjMode, + ) -> Point { + if let object_attributes::TransformationKind::RotationScaling { + rotation_scaling_parameter, + } = transformation_kind + { + // We have to use f64 for translating/rot/scale because we might have negative values when using the pixel + // in the carthesian plane having the origin as the center of the sprite. + // We could use i16 as well but then we would still need to use f64 to apply the transformation. + + // RotScale matrix + let rotscale_params = self.rotation_scaling_params[rotation_scaling_parameter as usize]; + let sprite_size = sprite_size.map(|el| el as f64); + + // This is the pixel coordinate in the screen space using the sprite center as origin of the reference system + // This is needed because the rotscale is applied taking the center of the sprite as the origin of the rotation + // If the sprite is in AffineDouble mode then it has double dimensions and the center is at +sprite_width/+sprite_height insted of + // just half ot that. + let pixel_screen_sprite_center = pixel_screen_sprite_origin.map(|el| el as f64) + - match obj_mode { + ObjMode::Affine => sprite_size / 2.0, + ObjMode::AffineDouble => sprite_size, + _ => unreachable!(), + }; + + // Applying transformation. + // The result will be a pixel in the texture space which still has the center of the sprite as the origin of the reference system + let pixel_texture_sprite_center = pixel_screen_sprite_center * rotscale_params; + + // Moving back the reference system to the origin of the sprite (top-left corner). + pixel_texture_sprite_center + sprite_size / 2.0 + } else { + // TODO: Implement flip + pixel_screen_sprite_origin.map(|el| el as f64) + } + } + + fn process_sprites_scanline(&mut self) { + self.sprite_pixels_scanline = [None; LCD_WIDTH]; + + let y = self.vcount; + + for obj in self.obj_attributes_arr.into_iter() { + if matches!( + obj.attribute0.obj_mode, + object_attributes::ObjMode::Disabled + ) || matches!( + obj.attribute0.gfx_mode, + object_attributes::GfxMode::ObjectWindow + ) { + continue; + } + + let (sprite_width, sprite_height) = + match (obj.attribute0.obj_shape, obj.attribute1.obj_size) { + (ObjShape::Square, ObjSize::Size0) => (8_u8, 8_u8), + (ObjShape::Horizontal, ObjSize::Size0) => (16, 8), + (ObjShape::Vertical, ObjSize::Size0) => (8, 16), + (ObjShape::Square, ObjSize::Size1) => (16, 16), + (ObjShape::Horizontal, ObjSize::Size1) => (32, 8), + (ObjShape::Vertical, ObjSize::Size1) => (8, 32), + (ObjShape::Square, ObjSize::Size2) => (32, 32), + (ObjShape::Horizontal, ObjSize::Size2) => (32, 16), + (ObjShape::Vertical, ObjSize::Size2) => (16, 32), + (ObjShape::Square, ObjSize::Size3) => (64, 64), + (ObjShape::Horizontal, ObjSize::Size3) => (64, 32), + (ObjShape::Vertical, ObjSize::Size3) => (32, 64), + }; + + // We can represent the size of the sprite using a point. + let sprite_size = Point::new(sprite_width as u16, sprite_height as u16); + + // Sprite size using tiles as dimensions + let sprite_size_tile = sprite_size / 8; + + let sprite_position = Point::new( + obj.attribute1.x_coordinate, + obj.attribute0.y_coordinate as u16, + ); + + let is_affine_double = matches!( + obj.attribute0.obj_mode, + object_attributes::ObjMode::AffineDouble + ); + + // Sprite size in screen space (takes into account double size sprites) + let sprite_screen_size = sprite_size * if is_affine_double { 2 } else { 1 }; + + for idx in 0..sprite_screen_size.x { + // This is the pixel coordinate in the screen space using the sprite origin (top-left corner) as origin of the reference system + let pixel_screen_sprite_origin = + Point::new(idx, (y + WORLD_HEIGHT - sprite_position.y) % WORLD_HEIGHT); + + // We check that the coordinates in the screen space are inside the sprite + // Taking care of the fact that if the sprite in AffineDouble it has double the dimensions + if pixel_screen_sprite_origin.x > sprite_screen_size.x + || pixel_screen_sprite_origin.y > sprite_screen_size.y + { + continue; + } + + // We apply the transformation. + // The result is a pixel in the texture space with the origin of the sprite (top-left corner) as the origin of the reference system + let pixel_texture_sprite_origin = self.get_texture_space_point( + sprite_size, + pixel_screen_sprite_origin, + obj.attribute1.transformation_kind, + obj.attribute0.obj_mode, + ); + + // We check that the pixel is inside the sprite + if pixel_texture_sprite_origin.x < 0.0 + || pixel_texture_sprite_origin.y < 0.0 + || pixel_texture_sprite_origin.x >= sprite_size.x as f64 + || pixel_texture_sprite_origin.y >= sprite_size.y as f64 + { + continue; + } + + let pixel_texture_sprite_origin = pixel_texture_sprite_origin.map(|el| el as u16); + + // Pixel in texture space using tiles as dimensions + let pixel_texture_tile = pixel_texture_sprite_origin / 8; + + // Offset of the pixel inside the tile + let y_tile_idx = pixel_texture_sprite_origin.y % 8; + let x_tile_idx = pixel_texture_sprite_origin.x % 8; + + let color_offset = match obj.attribute0.color_mode { + ColorMode::Palette8bpp => { + // We multiply *2 because in 8bpp tiles indeces are always even + let tile_number = obj.attribute2.tile_number + + match self.get_obj_character_vram_mapping() { + ObjMappingKind::OneDimensional => { + // In this case memory is seen as a single array. + // tile_number is the offset of the first tile in memory. + // then we access [y][x] by doing y*number_cols + x, as if we were to access an array as a matrix + pixel_texture_tile.y * sprite_size_tile.x * 2 + + pixel_texture_tile.x * 2 + } + ObjMappingKind::TwoDimensional => { + // A charblock is 32x32 tiles + pixel_texture_tile.y * 32 + pixel_texture_tile.x * 2 + } + }; + + // A tile is 8x8 mini-bitmap. + // A tile is 64bytes long in 8bpp. + let palette_offset = + tile_number as u32 * 32 + y_tile_idx as u32 * 8 + x_tile_idx as u32; + + // TODO: Move 0x10000 to a variable. It is the offset where OBJ VRAM starts in vram + self.video_ram[0x10000 + palette_offset as usize] + } + ColorMode::Palette4bpp => { + let tile_number = obj.attribute2.tile_number + + match self.get_obj_character_vram_mapping() { + ObjMappingKind::OneDimensional => { + // In this case memory is seen as a single array. + // tile_number is the offset of the first tile in memory. + // then we access [y][x] by doing y*number_cols + x, as if we were to access an array as a matrix + pixel_texture_tile.y * sprite_size_tile.x + pixel_texture_tile.x + } + ObjMappingKind::TwoDimensional => { + // A charblock is 32x32 tiles + obj.attribute2.tile_number + + pixel_texture_tile.y * 32 + + pixel_texture_tile.x + } + }; + + // A tile is 32bytes long in 4bpp. + let tile_data = tile_number * 32 + y_tile_idx * 4 + x_tile_idx / 2; + + let palette_offset_low = if tile_data % 2 == 0 { + tile_data.get_bits(0..=3) + } else { + tile_data.get_bits(4..=7) + }; + + let palette_offset = + (obj.attribute2.palette_number << 4) | (palette_offset_low as u8); + self.video_ram[0x10000 + palette_offset as usize] + } + }; + + let x_screen = sprite_position.x + idx; + + if x_screen >= self.sprite_pixels_scanline.len() as u16 { + continue; + } + + let get_pixel_info_closure = || PixelInfo { + color: self.read_color_from_obj_palette(color_offset as usize), + priority: obj.attribute2.priority, + }; + + self.sprite_pixels_scanline[x_screen as usize] = + Some(self.sprite_pixels_scanline[x_screen as usize].map_or_else( + get_pixel_info_closure, + |current_pixel_info| { + if current_pixel_info.priority >= obj.attribute2.priority { + get_pixel_info_closure() + } else { + current_pixel_info + } + }, + )); + } + } + } + pub fn step(&mut self) -> LcdStepOutput { // This will be much more complex obviously let mut output = LcdStepOutput::default(); @@ -208,6 +470,7 @@ impl Lcd { (self.obj_attributes_arr, self.rotation_scaling_params) = object_attributes::get_attributes(self.obj_attributes.as_slice()); + self.process_sprites_scanline(); } else if self.pixel_index == 240 { // We're entering Hblank @@ -235,7 +498,9 @@ impl Lcd { let pixel_y = self.vcount; let pixel_x = self.pixel_index; - self.buffer[pixel_y as usize][pixel_x as usize] = Color::from_rgb(31, 31, 31); + self.buffer[pixel_y as usize][pixel_x as usize] = self.sprite_pixels_scanline + [pixel_x as usize] + .map_or_else(|| Color::from_rgb(31, 31, 31), |info| info.color); } log(format!( @@ -284,6 +549,10 @@ impl Lcd { self.dispcnt.get_bits(0..=2).try_into().unwrap() } + fn get_obj_character_vram_mapping(&self) -> ObjMappingKind { + self.dispcnt.get_bit(6).into() + } + fn get_vcount_setting(&self) -> u8 { self.dispstat.get_byte(1) } diff --git a/emu/src/cpu/hardware/lcd/object_attributes.rs b/emu/src/cpu/hardware/lcd/object_attributes.rs index 219aad3..485c21d 100644 --- a/emu/src/cpu/hardware/lcd/object_attributes.rs +++ b/emu/src/cpu/hardware/lcd/object_attributes.rs @@ -5,7 +5,7 @@ use std::ops::{Index, IndexMut}; use crate::bitwise::Bits; #[derive(Default, Clone, Copy)] -enum ObjMode { +pub enum ObjMode { #[default] Normal, Affine, @@ -26,7 +26,7 @@ impl From for ObjMode { } #[derive(Default, Clone, Copy)] -enum GfxMode { +pub enum GfxMode { #[default] Normal, AlphaBlending, @@ -47,7 +47,7 @@ impl TryFrom for GfxMode { } #[derive(Default, Clone, Copy)] -enum ColorMode { +pub enum ColorMode { /// 16 colors #[default] Palette4bpp, @@ -65,7 +65,7 @@ impl From for ColorMode { } #[derive(Default, Clone, Copy)] -enum ObjShape { +pub enum ObjShape { #[default] Square, Horizontal, @@ -86,7 +86,7 @@ impl TryFrom for ObjShape { } #[derive(Default, Clone, Copy)] -enum ObjSize { +pub enum ObjSize { #[default] Size0, Size1, @@ -108,13 +108,13 @@ impl From for ObjSize { #[allow(dead_code)] #[derive(Default, Clone, Copy)] -struct ObjAttribute0 { - y_coordinate: u8, - obj_mode: ObjMode, - gfx_mode: GfxMode, +pub struct ObjAttribute0 { + pub y_coordinate: u8, + pub obj_mode: ObjMode, + pub gfx_mode: GfxMode, obj_mosaic: bool, - color_mode: ColorMode, - obj_shape: ObjShape, + pub color_mode: ColorMode, + pub obj_shape: ObjShape, } impl TryFrom for ObjAttribute0 { @@ -133,7 +133,7 @@ impl TryFrom for ObjAttribute0 { #[allow(dead_code)] #[derive(Clone, Copy)] -enum TransformationKind { +pub enum TransformationKind { RotationScaling { rotation_scaling_parameter: u8, }, @@ -154,10 +154,10 @@ impl Default for TransformationKind { #[allow(dead_code)] #[derive(Default, Clone, Copy)] -struct ObjAttribute1 { - x_coordinate: u16, - transformation_kind: TransformationKind, - obj_size: ObjSize, +pub struct ObjAttribute1 { + pub x_coordinate: u16, + pub transformation_kind: TransformationKind, + pub obj_size: ObjSize, } impl ObjAttribute1 { @@ -169,8 +169,8 @@ impl ObjAttribute1 { rotation_scaling_parameter: value.get_bits(9..=13) as u8, }, ObjMode::Normal | ObjMode::Disabled => TransformationKind::Flip { - horizontal_flip: value.get_bit(11), - vertical_flip: value.get_bit(12), + horizontal_flip: value.get_bit(12), + vertical_flip: value.get_bit(13), }, }, obj_size: value.get_bits(14..=15).into(), @@ -179,11 +179,22 @@ impl ObjAttribute1 { } #[allow(dead_code)] -#[derive(Default, Clone, Copy)] -struct ObjAttribute2 { - tile_number: u16, - priority: u8, - palette_number: u8, +#[derive(Clone, Copy)] +pub struct ObjAttribute2 { + pub tile_number: u16, + pub priority: u8, + pub palette_number: u8, +} + +impl Default for ObjAttribute2 { + fn default() -> Self { + Self { + tile_number: 0, + // Lowest priority + priority: 3, + palette_number: 0, + } + } } impl From for ObjAttribute2 { @@ -199,9 +210,9 @@ impl From for ObjAttribute2 { #[allow(dead_code)] #[derive(Default, Clone, Copy)] pub struct ObjAttributes { - attribute0: ObjAttribute0, - attribute1: ObjAttribute1, - attribute2: ObjAttribute2, + pub attribute0: ObjAttribute0, + pub attribute1: ObjAttribute1, + pub attribute2: ObjAttribute2, } impl TryFrom<[u16; 3]> for ObjAttributes { @@ -225,6 +236,30 @@ pub struct RotationScaling { pd: u16, } +impl RotationScaling { + /// Gives back the result of P*T where + /// P = [ pa pb ] + /// [ pc pd ] + /// and + /// T = [ x ] + /// [ y ] + /// pa, pb, pc, pd are first converted to floating number from the fixed point representation + pub fn apply(&self, x: f64, y: f64) -> (f64, f64) { + let a = Self::get_float_from_fixed_point(self.pa); + let b = Self::get_float_from_fixed_point(self.pb); + let c = Self::get_float_from_fixed_point(self.pc); + let d = Self::get_float_from_fixed_point(self.pd); + + (x.mul_add(a, y * b), x.mul_add(c, y * d)) + } + + fn get_float_from_fixed_point(value: u16) -> f64 { + // We interpret the value as signed and we divide by 2^8 since the rotation/scaling parameter + // is represented as an 8.8 fixed point value. + (value as i16) as f64 / 256.0 + } +} + impl Index for RotationScaling { type Output = u16; fn index(&self, index: usize) -> &Self::Output { diff --git a/emu/src/cpu/hardware/lcd/point.rs b/emu/src/cpu/hardware/lcd/point.rs new file mode 100644 index 0000000..d167bc2 --- /dev/null +++ b/emu/src/cpu/hardware/lcd/point.rs @@ -0,0 +1,99 @@ +use std::ops; + +use super::object_attributes::RotationScaling; + +/// A simple struct to represent a point in a carthesian plane. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +pub(super) struct Point { + pub(super) x: T, + pub(super) y: T, +} + +impl Point { + pub(super) const fn new(x: T, y: T) -> Self { + Self { x, y } + } + + pub(super) fn map(self, f: fn(T) -> U) -> Point { + Point:: { + x: f(self.x), + y: f(self.y), + } + } +} + +impl ops::Add for Point +where + T: ops::Add, +{ + type Output = Self; + fn add(self, rhs: Self) -> Self::Output { + Self { + x: self.x + rhs.x, + y: self.y + rhs.y, + } + } +} + +impl ops::Sub for Point +where + T: ops::Sub, +{ + type Output = Self; + fn sub(self, rhs: Self) -> Self::Output { + Self { + x: self.x - rhs.x, + y: self.y - rhs.y, + } + } +} + +impl ops::Mul for Point { + type Output = Self; + fn mul(self, rhs: RotationScaling) -> Self::Output { + let r = rhs.apply(self.x, self.y); + + Self { x: r.0, y: r.1 } + } +} + +impl ops::Mul for Point +where + T: ops::Mul + Copy, +{ + type Output = Self; + fn mul(self, rhs: T) -> Self::Output { + Self { + x: self.x * rhs, + y: self.y * rhs, + } + } +} + +impl ops::Div for Point +where + T: ops::Div + Copy, +{ + type Output = Self; + fn div(self, rhs: T) -> Self::Output { + Self { + x: self.x / rhs, + y: self.y / rhs, + } + } +} + +#[cfg(test)] +mod tests { + use super::Point; + + #[test] + fn test_point() { + let p = Point { x: 10_u16, y: 10 }; + + assert_eq!(p / 2, Point { x: 5_u16, y: 5 }); + assert_eq!(p * 2, Point { x: 20_u16, y: 20 }); + assert_eq!(p + Point { x: 1_u16, y: 1 }, Point { x: 11_u16, y: 11 }); + assert_eq!(p - Point { x: 1_u16, y: 1 }, Point { x: 9_u16, y: 9 }); + } +} diff --git a/emu/src/cpu/thumb/operations.rs b/emu/src/cpu/thumb/operations.rs index a649d5b..238ff30 100644 --- a/emu/src/cpu/thumb/operations.rs +++ b/emu/src/cpu/thumb/operations.rs @@ -107,8 +107,40 @@ impl Arm7tdmi { rs, true, ), - ThumbModeAluInstruction::Lsl => todo!(), - ThumbModeAluInstruction::Lsr => todo!(), + ThumbModeAluInstruction::Lsl => { + // If the shift amount in 0 then the second operand is just Rd + let second_operand = if rs == 0 { + self.registers.register_at(rd.try_into().unwrap()) + } else { + // Otherwise we reuse the ARM logic + self.shift_operand( + crate::cpu::arm::alu_instruction::ArmModeAluInstruction::Mov, + true, + ShiftKind::Lsl, + rs, + self.registers.register_at(rd.try_into().unwrap()), + ) + }; + + self.mov(rd.try_into().unwrap(), second_operand, true); + } + ThumbModeAluInstruction::Lsr => { + // If the shift amount in 0 then the second operand is just Rd + let second_operand = if rs == 0 { + self.registers.register_at(rd.try_into().unwrap()) + } else { + // Otherwise we reuse the ARM logic + self.shift_operand( + crate::cpu::arm::alu_instruction::ArmModeAluInstruction::Mov, + true, + ShiftKind::Lsr, + rs, + self.registers.register_at(rd.try_into().unwrap()), + ) + }; + + self.mov(rd.try_into().unwrap(), second_operand, true); + } ThumbModeAluInstruction::Asr => todo!(), ThumbModeAluInstruction::Adc => todo!(), ThumbModeAluInstruction::Sbc => todo!(), @@ -548,12 +580,12 @@ mod tests { let op_code = 0b0010_0000_0000_0000_u16; let op_code: ThumbModeOpcode = Arm7tdmi::decode(op_code); assert_eq!( - op_code.instruction, ThumbModeInstruction::MoveCompareAddSubtractImm { operation: Operation::Mov, destination_register: 0, offset: 0, - } + }, + op_code.instruction, ); cpu.execute_thumb(op_code); @@ -562,4 +594,18 @@ mod tests { assert!(!cpu.cpsr.sign_flag()); assert!(cpu.cpsr.zero_flag()); } + + #[test] + fn decode_lsl() { + let op_code = 0b0100_0000_1000_1000_u16; + let op_code: ThumbModeOpcode = Arm7tdmi::decode(op_code); + assert_eq!( + ThumbModeInstruction::AluOp { + alu_operation: ThumbModeAluInstruction::Lsl, + source_register: 1, + destination_register: 0, + }, + op_code.instruction, + ); + } } diff --git a/ui/src/app.rs b/ui/src/app.rs index d6e2d67..e605a76 100644 --- a/ui/src/app.rs +++ b/ui/src/app.rs @@ -72,6 +72,7 @@ impl ClementineApp { open.insert(tools[1].name().to_owned()); open.insert(tools[2].name().to_owned()); + open.insert(tools[3].name().to_owned()); #[cfg(feature = "disassembler")] open.insert(tools[5].name().to_owned());