diff --git a/Cargo.lock b/Cargo.lock index 3f59d6f..00565db 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -599,6 +599,7 @@ dependencies = [ "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "bit 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "bitfield 0.13.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", diff --git a/Cargo.toml b/Cargo.toml index 9c1afd0..11aed8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ sdl2 = "0.32.2" minifb = "0.11.2" time = "0.1.42" bitfield = "0.13.1" +bitflags = "1.1.0" [profile.dev] opt-level = 1 diff --git a/src/backend/minifb_backend.rs b/src/backend/minifb_backend.rs index 2d1991e..0391535 100644 --- a/src/backend/minifb_backend.rs +++ b/src/backend/minifb_backend.rs @@ -7,7 +7,7 @@ extern crate minifb; use minifb::{Key, Window, WindowOptions}; use super::EmulatorBackend; -use crate::core::gpu::Gpu; +use crate::core::gpu::{DISPLAY_HEIGHT, DISPLAY_WIDTH}; use crate::core::keypad; pub struct MinifbBackend { @@ -20,8 +20,8 @@ impl MinifbBackend { pub fn new() -> MinifbBackend { let window = Window::new( "rustboyadvance-ng", - Gpu::DISPLAY_WIDTH, - Gpu::DISPLAY_HEIGHT, + DISPLAY_WIDTH, + DISPLAY_HEIGHT, WindowOptions { borderless: true, scale: minifb::Scale::X4, @@ -39,7 +39,7 @@ impl MinifbBackend { } impl EmulatorBackend for MinifbBackend { - fn render(&mut self, buffer: Vec) { + fn render(&mut self, buffer: &[u32]) { self.frames_rendered += 1; if self.first_frame_start.elapsed() >= time::Duration::from_secs(1) { let title = format!("rustboyadvance-ng ({} fps)", self.frames_rendered); @@ -47,7 +47,7 @@ impl EmulatorBackend for MinifbBackend { self.first_frame_start = time::Instant::now(); self.frames_rendered = 0; } - self.window.update_with_buffer(&buffer).unwrap(); + self.window.update_with_buffer(buffer).unwrap(); } fn get_key_state(&mut self) -> u16 { diff --git a/src/backend/mod.rs b/src/backend/mod.rs index c45c85e..8b8e0be 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -7,7 +7,7 @@ mod sdl2_backend; pub use sdl2_backend::Sdl2Backend; pub trait EmulatorBackend { - fn render(&mut self, buffer: Vec); + fn render(&mut self, buffer: &[u32]); fn get_key_state(&mut self) -> u16; } @@ -24,5 +24,5 @@ impl EmulatorBackend for DummyBackend { fn get_key_state(&mut self) -> u16 { keypad::KEYINPUT_ALL_RELEASED } - fn render(&mut self, _buffer: Vec) {} + fn render(&mut self, _buffer: &[u32]) {} } diff --git a/src/backend/sdl2_backend.rs b/src/backend/sdl2_backend.rs index 234bc6e..bf0c0d9 100644 --- a/src/backend/sdl2_backend.rs +++ b/src/backend/sdl2_backend.rs @@ -1,4 +1,3 @@ -use std::convert::TryFrom; use std::time; use crate::bit::BitIndex; @@ -6,13 +5,13 @@ use crate::bit::BitIndex; extern crate sdl2; use sdl2::event::Event; use sdl2::keyboard::Keycode; -use sdl2::pixels::{Color, PixelFormat, PixelFormatEnum}; +use sdl2::pixels::{Color, PixelFormatEnum}; use sdl2::rect::{Point, Rect}; -use sdl2::render::{Texture, TextureCreator, WindowCanvas}; -use sdl2::video::{Window, WindowContext}; +use sdl2::render::{TextureCreator, WindowCanvas}; +use sdl2::video::WindowContext; use super::EmulatorBackend; -use crate::core::gpu::Gpu; +use crate::core::gpu::{DISPLAY_HEIGHT, DISPLAY_WIDTH}; use crate::core::keypad; pub struct Sdl2Backend { @@ -24,8 +23,8 @@ pub struct Sdl2Backend { keyinput: u16, } -const SCREEN_WIDTH: u32 = Gpu::DISPLAY_WIDTH as u32; -const SCREEN_HEIGHT: u32 = Gpu::DISPLAY_HEIGHT as u32; +const SCREEN_WIDTH: u32 = DISPLAY_WIDTH as u32; +const SCREEN_HEIGHT: u32 = DISPLAY_HEIGHT as u32; impl Sdl2Backend { pub fn new() -> Sdl2Backend { @@ -57,7 +56,7 @@ impl Sdl2Backend { } impl EmulatorBackend for Sdl2Backend { - fn render(&mut self, buffer: Vec) { + fn render(&mut self, buffer: &[u32]) { let mut texture = self .tc .create_texture_target(PixelFormatEnum::RGB24, SCREEN_WIDTH, SCREEN_HEIGHT) diff --git a/src/bin/main.rs b/src/bin/main.rs index 3e95ea4..c1a341c 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -65,10 +65,9 @@ fn run_emulator(matches: &ArgMatches) -> GBAResult<()> { loop { let start_time = time::Instant::now(); gba.frame(); - let time_passed = time::Instant::now() - start_time; + let time_passed = start_time.elapsed(); if time_passed <= frame_time { - let duration = frame_time - time_passed; - ::std::thread::sleep(duration); + ::std::thread::sleep(frame_time - time_passed); } } } diff --git a/src/core/gba.rs b/src/core/gba.rs index 8fd4359..de0766d 100644 --- a/src/core/gba.rs +++ b/src/core/gba.rs @@ -66,7 +66,7 @@ impl GameBoyAdvance { let cycles = self.emulate_cpu(); self.emulate_peripherals(cycles); } - self.backend.render(self.io.borrow().gpu.render()); + self.backend.render(self.io.borrow().gpu.get_framebuffer()); while self.io.borrow().gpu.state == GpuState::VBlank { let cycles = self.emulate_cpu(); self.emulate_peripherals(cycles); @@ -106,7 +106,7 @@ impl GameBoyAdvance { self.emulate_peripherals(cycles); if self.io.borrow().gpu.state == GpuState::VBlank { - self.backend.render(self.io.borrow().gpu.render()); + self.backend.render(self.io.borrow().gpu.get_framebuffer()); } Ok(executed_insn) diff --git a/src/core/gpu/blend.rs b/src/core/gpu/blend.rs new file mode 100644 index 0000000..7668800 --- /dev/null +++ b/src/core/gpu/blend.rs @@ -0,0 +1,117 @@ +use std::cmp; +use std::ops::Sub; + +use super::regs::*; +use super::*; + +#[derive(Debug, Primitive, Clone, Copy)] +pub enum BldMode { + BldNone = 0b00, + BldAlpha = 0b01, + BldWhite = 0b10, + BldBlack = 0b11, +} + +impl From for BldMode { + fn from(v: u16) -> BldMode { + BldMode::from_u16(v).unwrap() + } +} + +impl Rgb15 { + fn blend_with(self, other: Rgb15, my_weight: u16, other_weight: u16) -> Rgb15 { + let r = cmp::min(31, (self.r() * my_weight + other.r() * other_weight) >> 4); + let g = cmp::min(31, (self.g() * my_weight + other.g() * other_weight) >> 4); + let b = cmp::min(31, (self.b() * my_weight + other.b() * other_weight) >> 4); + Rgb15::from_rgb(r, g, b) + } +} + +impl Gpu { + fn get_topmost_color( + &self, + sb: &SysBus, + screen_x: usize, + layers: &BlendFlags, + ) -> Option { + // TODO - only BGs are supported, don't forget OBJs + + let mut color: Option = None; + + // priorities are 0-4 when 0 is the highest + 'outer: for priority in 0..4 { + for bg in 0..4 { + let c = self.bg[bg].line[screen_x]; + if self.dispcnt.disp_bg(bg) + && !c.is_transparent() + && (layers.is_empty() || layers.contains(BG_LAYER_FLAG[bg])) + && self.bg[bg].bgcnt.priority() == priority + { + color = Some(c); + break 'outer; + } + } + } + if color.is_none() && layers.contains(BlendFlags::BACKDROP) { + color = Some(Rgb15(sb.palette_ram.read_16(0))) + } + color + } + + pub fn blend_line(&mut self, sb: &mut SysBus) -> Scanline { + let mut bld_line = Scanline::default(); + let bldmode = self.bldcnt.mode(); + match bldmode { + BldMode::BldAlpha => { + let top_layers = self.bldcnt.top(); + let bottom_layers = self.bldcnt.bottom(); + + for x in 0..DISPLAY_WIDTH { + if let Some(top_color) = self.get_topmost_color(sb, x, &top_layers) { + if let Some(bot_color) = self.get_topmost_color(sb, x, &bottom_layers) { + let eva = self.bldalpha.eva(); + let evb = self.bldalpha.evb(); + bld_line[x] = top_color.blend_with(bot_color, eva, evb); + } else { + bld_line[x] = top_color; + } + } else { + bld_line[x] = self.get_topmost_color(sb, x, &BlendFlags::all()).unwrap(); + } + } + } + BldMode::BldWhite => { + let top_layers = self.bldcnt.top(); + let evy = self.bldy; + + for x in 0..DISPLAY_WIDTH { + bld_line[x] = + if let Some(top_color) = self.get_topmost_color(sb, x, &top_layers) { + top_color.blend_with(Rgb15::WHITE, 16 - evy, evy) + } else { + self.get_topmost_color(sb, x, &BlendFlags::all()).unwrap() + }; + } + } + BldMode::BldBlack => { + let top_layers = self.bldcnt.top(); + let evy = self.bldy; + + for x in 0..DISPLAY_WIDTH { + bld_line[x] = + if let Some(top_color) = self.get_topmost_color(sb, x, &top_layers) { + top_color.blend_with(Rgb15::BLACK, 16 - evy, evy) + } else { + self.get_topmost_color(sb, x, &BlendFlags::all()).unwrap() + }; + } + } + BldMode::BldNone => { + for x in 0..DISPLAY_WIDTH { + bld_line[x] = self.get_topmost_color(sb, x, &BlendFlags::all()).unwrap(); + } + } + } + bld_line + } +} diff --git a/src/core/gpu/mod.rs b/src/core/gpu/mod.rs index f9878a1..29b31bb 100644 --- a/src/core/gpu/mod.rs +++ b/src/core/gpu/mod.rs @@ -1,16 +1,68 @@ use std::fmt; use super::arm7tdmi::{Addr, Bus}; -pub use super::palette::{PixelFormat, Rgb15}; use super::*; use crate::bitfield::Bit; use crate::num::FromPrimitive; +mod blend; +mod mosaic; + mod regs; pub use regs::*; pub const VRAM_ADDR: Addr = 0x0600_0000; +pub const DISPLAY_WIDTH: usize = 240; +pub const DISPLAY_HEIGHT: usize = 160; + +const CYCLES_PIXEL: usize = 4; +const CYCLES_HDRAW: usize = 960; +const CYCLES_HBLANK: usize = 272; +const CYCLES_SCANLINE: usize = 1232; +const CYCLES_VDRAW: usize = 197120; +const CYCLES_VBLANK: usize = 83776; + +const TILE_SIZE: u32 = 0x20; + +// TODO - remove the one in palette.rs +bitfield! { + #[derive(Copy, Clone, Default)] + pub struct Rgb15(u16); + impl Debug; + pub r, set_r: 4, 0; + pub g, set_g: 9, 5; + pub b, set_b: 14, 10; + pub is_transparent, _ : 15; +} + +impl Rgb15 { + pub const BLACK: Rgb15 = Rgb15(0); + pub const WHITE: Rgb15 = Rgb15(0x7fff); + pub const TRANSPARENT: Rgb15 = Rgb15(0x8000); + + pub fn to_rgb24(&self) -> u32 { + ((self.r() as u32) << 19) | ((self.g() as u32) << 11) | ((self.b() as u32) << 3) + } + + pub fn from_rgb(r: u16, g: u16, b: u16) -> Rgb15 { + let mut c = Rgb15(0); + c.set_r(r); + c.set_g(g); + c.set_b(b); + c + } + + pub fn get_rgb(&self) -> (u16, u16, u16) { + (self.r(), self.g(), self.b()) + } +} + +#[derive(Debug, Primitive, Copy, Clone)] +pub enum PixelFormat { + BPP4 = 0, + BPP8 = 1, +} #[derive(Debug, Primitive, Clone, Copy)] pub enum BGMode { @@ -41,21 +93,20 @@ impl Default for GpuState { } use GpuState::*; -pub struct FrameBuffer([Rgb15; 512 * 512]); +pub struct FrameBuffer([u32; DISPLAY_WIDTH * DISPLAY_HEIGHT]); impl fmt::Debug for FrameBuffer { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "FrameBuffer: ")?; for i in 0..6 { - let (r, g, b) = self.0[i].get_rgb24(); - write!(f, "#{:x}{:x}{:x}, ", r, g, b)?; + write!(f, "#{:06x}, ", self[i])?; } write!(f, "...") } } impl std::ops::Index for FrameBuffer { - type Output = Rgb15; + type Output = u32; fn index(&self, index: usize) -> &Self::Output { &self.0[index] } @@ -67,67 +118,108 @@ impl std::ops::IndexMut for FrameBuffer { } } +#[derive(Copy, Clone)] +pub struct Scanline([Rgb15; DISPLAY_WIDTH]); + +impl Default for Scanline { + fn default() -> Scanline { + Scanline([Rgb15(0); DISPLAY_WIDTH]) + } +} + +impl fmt::Debug for Scanline { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "Scanline: ")?; + for i in 0..6 { + write!(f, "#{:06x}, ", self[i].0)?; + } + write!(f, "...") + } +} + +impl std::ops::Index for Scanline { + type Output = Rgb15; + fn index(&self, index: usize) -> &Self::Output { + &self.0[index] + } +} + +impl std::ops::IndexMut for Scanline { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.0[index] + } +} + +#[derive(Debug, Default, Copy, Clone)] +pub struct Bg { + pub bgcnt: BgControl, + pub bgvofs: u16, + pub bghofs: u16, + line: Scanline, + + // for mosaic + mosaic_first_row: Scanline, +} + +#[derive(Debug, Default, Copy, Clone)] +pub struct BgAffine { + pub pa: i16, // dx + pub pb: i16, // dmx + pub pc: i16, // dy + pub pd: i16, // dmy + pub x: i32, + pub y: i32, +} + #[derive(Debug)] pub struct Gpu { // registers pub dispcnt: DisplayControl, pub dispstat: DisplayStatus, - pub bgcnt: [BgControl; 4], - pub bgvofs: [u16; 4], - pub bghofs: [u16; 4], + + pub bg: [Bg; 4], + pub bg_aff: [BgAffine; 2], + pub win0h: u16, pub win1h: u16, pub win0v: u16, pub win1v: u16, pub winin: u16, pub winout: u16, - pub mosaic: u16, - pub bldcnt: u16, - pub bldalpha: u16, + pub mosaic: RegMosaic, + pub bldcnt: BlendControl, + pub bldalpha: BlendAlpha, pub bldy: u16, cycles: usize, - pub pixeldata: FrameBuffer, + pub frame_buffer: FrameBuffer, pub state: GpuState, pub current_scanline: usize, // VCOUNT } impl Gpu { - pub const DISPLAY_WIDTH: usize = 240; - pub const DISPLAY_HEIGHT: usize = 160; - - pub const CYCLES_PIXEL: usize = 4; - pub const CYCLES_HDRAW: usize = 960; - pub const CYCLES_HBLANK: usize = 272; - pub const CYCLES_SCANLINE: usize = 1232; - pub const CYCLES_VDRAW: usize = 197120; - pub const CYCLES_VBLANK: usize = 83776; - - pub const TILE_SIZE: u32 = 0x20; - pub fn new() -> Gpu { Gpu { dispcnt: DisplayControl(0x80), dispstat: DisplayStatus(0), - bgcnt: [BgControl(0), BgControl(0), BgControl(0), BgControl(0)], - bgvofs: [0; 4], - bghofs: [0; 4], + bg: [Bg::default(); 4], + bg_aff: [BgAffine::default(); 2], win0h: 0, win1h: 0, win0v: 0, win1v: 0, winin: 0, winout: 0, - mosaic: 0, - bldcnt: 0, - bldalpha: 0, + mosaic: RegMosaic(0), + bldcnt: BlendControl(0), + bldalpha: BlendAlpha(0), bldy: 0, state: HDraw, current_scanline: 0, cycles: 0, - pixeldata: FrameBuffer([Rgb15::from(0); 512 * 512]), + frame_buffer: FrameBuffer([0; DISPLAY_WIDTH * DISPLAY_HEIGHT]), } } @@ -143,30 +235,35 @@ impl Gpu { let ofs = addr - VRAM_ADDR; match format { PixelFormat::BPP4 => { - let byte = sb.vram.read_8(ofs + index2d!(x / 2, y, 4)); + let byte = sb.vram.read_8(ofs + index2d!(Addr, x / 2, y, 4)); if x & 1 != 0 { (byte >> 4) as usize } else { (byte & 0xf) as usize } } - PixelFormat::BPP8 => sb.vram.read_8(ofs + index2d!(x, y, 8)) as usize, + PixelFormat::BPP8 => sb.vram.read_8(ofs + index2d!(Addr, x, y, 8)) as usize, } } pub fn get_palette_color(&self, sb: &SysBus, index: u32, palette_index: u32) -> Rgb15 { - sb.palette_ram - .read_16(2 * index + 0x20 * palette_index) - .into() + if index == 0 || (palette_index != 0 && index % 16 == 0) { + return Rgb15::TRANSPARENT; + } + Rgb15(sb.palette_ram.read_16(2 * index + 0x20 * palette_index)) } - fn scanline_mode0(&mut self, bg: usize, sb: &mut SysBus) { - let (h_ofs, v_ofs) = (self.bghofs[bg] as u32, self.bgvofs[bg] as u32); - let tileset_base = self.bgcnt[bg].char_block() - VRAM_ADDR; - let tilemap_base = self.bgcnt[bg].screen_block() - VRAM_ADDR; - let (tile_size, pixel_format) = self.bgcnt[bg].tile_format(); + fn render_pixel(&mut self, x: i32, y: i32, p: Rgb15) { + self.frame_buffer.0[index2d!(usize, x, y, DISPLAY_WIDTH)] = p.to_rgb24(); + } - let (bg_width, bg_height) = self.bgcnt[bg].size_regular(); + fn scanline_reg_bg(&mut self, bg: usize, sb: &mut SysBus) { + let (h_ofs, v_ofs) = (self.bg[bg].bghofs as u32, self.bg[bg].bgvofs as u32); + let tileset_base = self.bg[bg].bgcnt.char_block(); + let tilemap_base = self.bg[bg].bgcnt.screen_block(); + let (tile_size, pixel_format) = self.bg[bg].bgcnt.tile_format(); + + let (bg_width, bg_height) = self.bg[bg].bgcnt.size_regular(); let screen_y = self.current_scanline as u32; let mut screen_x = 0; @@ -186,65 +283,70 @@ impl Gpu { (256, 256) => 0, (512, 256) => bg_x / 256, (256, 512) => bg_y / 256, - (512, 512) => index2d!(bg_x / 256, bg_y / 256, 2), + (512, 512) => index2d!(u32, bg_x / 256, bg_y / 256, 2), _ => unreachable!(), } as u32; - let se_row = (bg_x / 8) % 32; + let mut se_row = (bg_x / 8) % 32; let se_column = (bg_y / 8) % 32; // this will be non-zero if the h-scroll lands in a middle of a tile let mut start_tile_x = bg_x % 8; + let tile_py = (bg_y % 8) as u32; - for t in 0..32 { - let map_addr = tilemap_base + loop { + let mut map_addr = tilemap_base + SCREEN_BLOCK_SIZE * screen_block - + 2 * (index2d!((se_row + t) % 32, se_column, 32) as u32); - let entry = TileMapEntry(sb.vram.read_16(map_addr - VRAM_ADDR)); - let tile_addr = tileset_base + entry.tile_index() * tile_size; + + 2 * index2d!(u32, se_row, se_column, 32); + for _ in se_row..32 { + let entry = TileMapEntry(sb.vram.read_16(map_addr - VRAM_ADDR)); + let tile_addr = tileset_base + entry.tile_index() * tile_size; - for tile_px in start_tile_x..=7 { - let tile_py = (bg_y % 8) as u32; - let index = self.read_pixel_index( - sb, - tile_addr, - if entry.x_flip() { 7 - tile_px } else { tile_px }, - if entry.y_flip() { 7 - tile_py } else { tile_py }, - pixel_format, - ); - let palette_bank = match pixel_format { - PixelFormat::BPP4 => entry.palette_bank() as u32, - PixelFormat::BPP8 => 0u32, - }; - let color = self.get_palette_color(sb, index as u32, palette_bank); - if color.get_rgb24() != (0, 0, 0) { - self.pixeldata[index2d!(screen_x as usize, screen_y as usize, 512)] = color; - } - screen_x += 1; - if (Gpu::DISPLAY_WIDTH as u32) == screen_x { - return; + for tile_px in start_tile_x..=7 { + let index = self.read_pixel_index( + sb, + tile_addr, + if entry.x_flip() { 7 - tile_px } else { tile_px }, + if entry.y_flip() { 7 - tile_py } else { tile_py }, + pixel_format, + ); + let palette_bank = match pixel_format { + PixelFormat::BPP4 => entry.palette_bank() as u32, + PixelFormat::BPP8 => 0u32, + }; + let color = self.get_palette_color(sb, index as u32, palette_bank); + self.bg[bg].line[screen_x as usize] = color; + screen_x += 1; + if (DISPLAY_WIDTH as u32) == screen_x { + return; + } } + start_tile_x = 0; + map_addr += 2; } - start_tile_x = 0; - if se_row + t == 31 { - if bg_width == 512 { - screen_block = screen_block ^ 1; - } + se_row = 0; + if bg_width == 512 { + screen_block = screen_block ^ 1; } } } - fn scanline_mode3(&mut self, _bg: u32, sb: &mut SysBus) { + fn scanline_aff_bg(&mut self, bg: usize, sb: &mut SysBus) { + // TODO + } + + fn scanline_mode3(&mut self, bg: usize, sb: &mut SysBus) { let y = self.current_scanline; - for x in 0..Self::DISPLAY_WIDTH { - let pixel_index = index2d!(x, y, Self::DISPLAY_WIDTH); - let pixel_ofs = 2 * (pixel_index as u32); - self.pixeldata[index2d!(x, y, 512)] = sb.vram.read_16(pixel_ofs).into(); + for x in 0..DISPLAY_WIDTH { + let pixel_index = index2d!(u32, x, y, DISPLAY_WIDTH); + let pixel_ofs = 2 * pixel_index; + let color = Rgb15(sb.vram.read_16(pixel_ofs)); + self.bg[bg].line[x] = color; } } - fn scanline_mode4(&mut self, _bg: u32, sb: &mut SysBus) { + fn scanline_mode4(&mut self, bg: usize, sb: &mut SysBus) { let page_ofs: u32 = match self.dispcnt.display_frame() { 0 => 0x0600_0000 - VRAM_ADDR, 1 => 0x0600_a000 - VRAM_ADDR, @@ -253,26 +355,43 @@ impl Gpu { let y = self.current_scanline; - for x in 0..Self::DISPLAY_WIDTH { - let bitmap_index = index2d!(x, y, Self::DISPLAY_WIDTH); + for x in 0..DISPLAY_WIDTH { + let bitmap_index = index2d!(x, y, DISPLAY_WIDTH); let bitmap_ofs = page_ofs + (bitmap_index as u32); let index = sb.vram.read_8(bitmap_ofs as Addr) as u32; - self.pixeldata[index2d!(x, y, 512)] = self.get_palette_color(sb, index, 0); + let color = self.get_palette_color(sb, index, 0); + self.bg[bg].line[x] = color; } } - pub fn scanline(&mut self, sb: &mut SysBus) { + pub fn render_scanline(&mut self, sb: &mut SysBus) { + // TODO - also render objs match self.dispcnt.mode() { BGMode::BGMode0 => { - for bg in (0..3).rev() { + for bg in 0..3 { if self.dispcnt.disp_bg(bg) { - self.scanline_mode0(bg, sb); + self.scanline_reg_bg(bg, sb); } } } + BGMode::BGMode1 => { + if self.dispcnt.disp_bg(2) { + self.scanline_aff_bg(2, sb); + } + if self.dispcnt.disp_bg(1) { + self.scanline_reg_bg(1, sb); + } + if self.dispcnt.disp_bg(0) { + self.scanline_reg_bg(0, sb); + } + } BGMode::BGMode2 => { - self.scanline_mode0(3, sb); - self.scanline_mode0(2, sb); + if self.dispcnt.disp_bg(3) { + self.scanline_aff_bg(3, sb); + } + if self.dispcnt.disp_bg(2) { + self.scanline_aff_bg(2, sb); + } } BGMode::BGMode3 => { self.scanline_mode3(2, sb); @@ -282,18 +401,16 @@ impl Gpu { } _ => panic!("{:?} not supported", self.dispcnt.mode()), } + self.mosaic_sfx(); + let post_blend_line = self.blend_line(sb); + for x in 0..DISPLAY_WIDTH { + self.frame_buffer.0[x + self.current_scanline * DISPLAY_WIDTH] = + post_blend_line[x].to_rgb24(); + } } - pub fn render(&self) -> Vec { - let mut buffer = vec![0u32; Gpu::DISPLAY_WIDTH * Gpu::DISPLAY_WIDTH]; - for y in 0..Gpu::DISPLAY_HEIGHT { - for x in 0..Gpu::DISPLAY_WIDTH { - let (r, g, b) = self.pixeldata[index2d!(x as usize, y as usize, 512)].get_rgb24(); - buffer[index2d!(x, y, Gpu::DISPLAY_WIDTH)] = - ((r as u32) << 16) | ((g as u32) << 8) | (b as u32); - } - } - buffer + pub fn get_framebuffer(&self) -> &[u32] { + &self.frame_buffer.0 } } @@ -311,12 +428,12 @@ impl SyncedIoDevice for Gpu { match self.state { HDraw => { - if self.cycles > Gpu::CYCLES_HDRAW { + if self.cycles > CYCLES_HDRAW { self.current_scanline += 1; - self.cycles -= Gpu::CYCLES_HDRAW; + self.cycles -= CYCLES_HDRAW; - if self.current_scanline < Gpu::DISPLAY_HEIGHT { - self.scanline(sb); + if self.current_scanline < DISPLAY_HEIGHT { + self.render_scanline(sb); // HBlank self.dispstat.set_hblank(true); if self.dispstat.hblank_irq_enable() { @@ -324,7 +441,6 @@ impl SyncedIoDevice for Gpu { }; self.state = HBlank; } else { - self.scanline(sb); self.dispstat.set_vblank(true); if self.dispstat.vblank_irq_enable() { irqs.set_LCD_VBlank(true); @@ -334,21 +450,21 @@ impl SyncedIoDevice for Gpu { } } HBlank => { - if self.cycles > Gpu::CYCLES_HBLANK { - self.cycles -= Gpu::CYCLES_HBLANK; + if self.cycles > CYCLES_HBLANK { + self.cycles -= CYCLES_HBLANK; self.state = HDraw; self.dispstat.set_hblank(false); self.dispstat.set_vblank(false); } } VBlank => { - if self.cycles > Gpu::CYCLES_VBLANK { - self.cycles -= Gpu::CYCLES_VBLANK; + if self.cycles > CYCLES_VBLANK { + self.cycles -= CYCLES_VBLANK; self.state = HDraw; self.dispstat.set_hblank(false); self.dispstat.set_vblank(false); self.current_scanline = 0; - self.scanline(sb); + self.render_scanline(sb); } } } diff --git a/src/core/gpu/mosaic.rs b/src/core/gpu/mosaic.rs new file mode 100644 index 0000000..ebb04fb --- /dev/null +++ b/src/core/gpu/mosaic.rs @@ -0,0 +1,35 @@ +use super::*; +use regs::RegMosaic; + +impl RegMosaic { + fn is_enabled_for_bg(&self) -> bool { + (self.bg_hsize() != 0) || (self.bg_vsize() != 0) + } +} + +impl Gpu { + fn mosaic_bg(&mut self) { + let hsize = (self.mosaic.bg_hsize() + 1) as usize; + let vsize = (self.mosaic.bg_vsize() + 1) as usize; + + for bg in 0..4 { + if self.dispcnt.disp_bg(bg) && self.bg[bg].bgcnt.mosaic() { + let y = self.current_scanline as usize; + if y % vsize == 0 { + self.bg[bg].mosaic_first_row = self.bg[bg].line.clone(); + } + for x in 0..DISPLAY_WIDTH { + let color = self.bg[bg].mosaic_first_row[(x / hsize) * hsize]; + self.bg[bg].line[x] = color; + } + } + } + } + + pub fn mosaic_sfx(&mut self) { + if self.mosaic.is_enabled_for_bg() { + self.mosaic_bg(); + } + // TODO obj mosaic + } +} diff --git a/src/core/gpu/regs.rs b/src/core/gpu/regs.rs index 78601ff..bba1f80 100644 --- a/src/core/gpu/regs.rs +++ b/src/core/gpu/regs.rs @@ -1,23 +1,7 @@ +use super::blend::BldMode; use super::*; -bitfield! { - pub struct DisplayControl(u16); - impl Debug; - u16; - pub into BGMode, mode, set_mode: 2, 0; - pub display_frame, set_display_frame: 4, 4; - pub hblank_interval_free, _: 5; - pub obj_character_vram_mapping, _: 6; - pub forst_vblank, _: 7; - pub disp_bg0, _ : 8; - pub disp_bg1, _ : 9; - pub disp_bg2, _ : 10; - pub disp_bg3, _ : 11; - pub disp_obj, _ : 12; - pub disp_window0, _ : 13; - pub disp_window1, _ : 14; - pub disp_obj_window, _ : 15; -} +pub const SCREEN_BLOCK_SIZE: u32 = 0x800; impl DisplayControl { pub fn disp_bg(&self, bg: usize) -> bool { @@ -25,35 +9,6 @@ impl DisplayControl { } } -bitfield! { - pub struct DisplayStatus(u16); - impl Debug; - u16; - pub get_vblank, set_vblank: 0; - pub get_hblank, set_hblank: 1; - pub get_vcount, set_vcount: 2; - pub vblank_irq_enable, _ : 3; - pub hblank_irq_enable, _ : 4; - pub vcount_irq_enable, _ : 5; - pub vcount_setting, _ : 15, 8; -} - -bitfield! { - #[derive(Copy, Clone)] - pub struct BgControl(u16); - impl Debug; - u16; - pub bg_priority, _: 1, 0; - pub character_base_block, _: 3, 2; - pub moasic, _ : 6; - pub palette256, _ : 7; - pub screen_base_block, _: 12, 8; - pub affine_wraparound, _: 13; - pub bg_size, _ : 15, 14; -} - -pub const SCREEN_BLOCK_SIZE: u32 = 0x800; - impl BgControl { pub fn char_block(&self) -> u32 { VRAM_ADDR + (self.character_base_block() as u32) * 0x4000 @@ -73,11 +28,121 @@ impl BgControl { } } + pub fn size_affine(&self) -> (u32, u32) { + match self.bg_size() { + 0b00 => (128, 128), + 0b01 => (256, 256), + 0b10 => (512, 512), + 0b11 => (1024, 1024), + _ => unreachable!(), + } + } + pub fn tile_format(&self) -> (u32, PixelFormat) { if self.palette256() { - (2 * Gpu::TILE_SIZE, PixelFormat::BPP8) + (2 * TILE_SIZE, PixelFormat::BPP8) } else { - (Gpu::TILE_SIZE, PixelFormat::BPP4) + (TILE_SIZE, PixelFormat::BPP4) } } } + +// struct definitions below because the bitfield! macro messes up syntax highlighting in vscode. +bitfield! { + pub struct DisplayControl(u16); + impl Debug; + u16; + pub into BGMode, mode, set_mode: 2, 0; + pub display_frame, set_display_frame: 4, 4; + pub hblank_interval_free, _: 5; + pub obj_character_vram_mapping, _: 6; + pub forst_vblank, _: 7; + pub disp_bg0, _ : 8; + pub disp_bg1, _ : 9; + pub disp_bg2, _ : 10; + pub disp_bg3, _ : 11; + pub disp_obj, _ : 12; + pub disp_window0, _ : 13; + pub disp_window1, _ : 14; + pub disp_obj_window, _ : 15; +} + +bitfield! { + pub struct DisplayStatus(u16); + impl Debug; + u16; + pub get_vblank, set_vblank: 0; + pub get_hblank, set_hblank: 1; + pub get_vcount, set_vcount: 2; + pub vblank_irq_enable, _ : 3; + pub hblank_irq_enable, _ : 4; + pub vcount_irq_enable, _ : 5; + pub vcount_setting, _ : 15, 8; +} + +bitfield! { + #[derive(Default, Copy, Clone)] + pub struct BgControl(u16); + impl Debug; + u16; + pub priority, _: 1, 0; + pub character_base_block, _: 3, 2; + pub mosaic, _ : 6; + pub palette256, _ : 7; + pub screen_base_block, _: 12, 8; + pub affine_wraparound, _: 13; + pub bg_size, _ : 15, 14; +} + +bitfield! { + #[derive(Default, Copy, Clone)] + pub struct RegMosaic(u16); + impl Debug; + u32; + pub bg_hsize, _: 3, 0; + pub bg_vsize, _: 7, 4; + pub obj_hsize, _ : 11, 8; + pub obj_vsize, _ : 15, 12; +} + +bitflags! { + pub struct BlendFlags: u32 { + const BG0 = 0b00000001; + const BG1 = 0b00000010; + const BG2 = 0b00000100; + const BG3 = 0b00001000; + const OBJ = 0b00010000; + const BACKDROP = 0b00100000; // BACKDROP + } +} + +impl From for BlendFlags { + fn from(v: u16) -> BlendFlags { + BlendFlags::from_bits(v as u32).unwrap() + } +} + +pub const BG_LAYER_FLAG: [BlendFlags; 4] = [ + BlendFlags::BG0, + BlendFlags::BG1, + BlendFlags::BG2, + BlendFlags::BG3, +]; + +bitfield! { + #[derive(Default, Copy, Clone)] + pub struct BlendControl(u16); + impl Debug; + pub into BlendFlags, top, _: 5, 0; + pub into BldMode, mode, set_mode: 7, 6; + pub into BlendFlags, bottom, _: 13, 8; +} + +bitfield! { + #[derive(Default, Copy, Clone)] + pub struct BlendAlpha(u16); + impl Debug; + u16; + pub eva, _: 5, 0; + pub evb, _: 12, 8; +} diff --git a/src/core/interrupt.rs b/src/core/interrupt.rs index e02f19b..d1e71c7 100644 --- a/src/core/interrupt.rs +++ b/src/core/interrupt.rs @@ -1,5 +1,3 @@ -use super::arm7tdmi::Core; - #[derive(Debug, Primitive, Copy, Clone, PartialEq)] #[allow(non_camel_case_types)] pub enum Interrupt { @@ -54,22 +52,35 @@ impl IrqBitmask { bitfield! { #[derive(Default, Copy, Clone, PartialEq)] - #[allow(non_snake_case)] pub struct IrqBitmask(u16); impl Debug; u16; + #[allow(non_snake_case)] pub LCD_VBlank, set_LCD_VBlank: 0; + #[allow(non_snake_case)] pub LCD_HBlank, set_LCD_HBlank: 1; + #[allow(non_snake_case)] pub LCD_VCounterMatch, set_LCD_VCounterMatch: 2; + #[allow(non_snake_case)] pub Timer0_Overflow, set_Timer0_Overflow: 3; + #[allow(non_snake_case)] pub Timer1_Overflow, set_Timer1_Overflow: 4; + #[allow(non_snake_case)] pub Timer2_Overflow, set_Timer2_Overflow: 5; + #[allow(non_snake_case)] pub Timer3_Overflow, set_Timer3_Overflow: 6; + #[allow(non_snake_case)] pub SerialCommunication, set_SerialCommunication: 7; + #[allow(non_snake_case)] pub DMA0, set_DMA0: 8; + #[allow(non_snake_case)] pub DMA1, set_DMA1: 9; + #[allow(non_snake_case)] pub DMA2, set_DMA2: 10; + #[allow(non_snake_case)] pub DMA3, set_DMA3: 11; + #[allow(non_snake_case)] pub Keypad, set_Keypad: 12; + #[allow(non_snake_case)] pub GamePak, set_GamePak: 13; } diff --git a/src/core/ioregs.rs b/src/core/ioregs.rs index 5d62737..193fbbc 100644 --- a/src/core/ioregs.rs +++ b/src/core/ioregs.rs @@ -31,14 +31,18 @@ pub mod consts { pub const REG_BG2PB: Addr = IO_BASE + 0x_0022; // 2 W BG2 Rotation/Scaling Parameter B (dmx) pub const REG_BG2PC: Addr = IO_BASE + 0x_0024; // 2 W BG2 Rotation/Scaling Parameter C (dy) pub const REG_BG2PD: Addr = IO_BASE + 0x_0026; // 2 W BG2 Rotation/Scaling Parameter D (dmy) - pub const REG_BG2X: Addr = IO_BASE + 0x_0028; // 4 W BG2 Reference Point X-Coordinate - pub const REG_BG2Y: Addr = IO_BASE + 0x_002C; // 4 W BG2 Reference Point Y-Coordinate + pub const REG_BG2X_L: Addr = IO_BASE + 0x_0028; // 4 W BG2 Reference Point X-Coordinate, lower 16 bit + pub const REG_BG2X_H: Addr = IO_BASE + 0x_002A; // 4 W BG2 Reference Point X-Coordinate, upper 16 bit + pub const REG_BG2Y_L: Addr = IO_BASE + 0x_002C; // 4 W BG2 Reference Point Y-Coordinate, lower 16 bit + pub const REG_BG2Y_H: Addr = IO_BASE + 0x_002E; // 4 W BG2 Reference Point Y-Coordinate, upper 16 bit pub const REG_BG3PA: Addr = IO_BASE + 0x_0030; // 2 W BG3 Rotation/Scaling Parameter A (dx) pub const REG_BG3PB: Addr = IO_BASE + 0x_0032; // 2 W BG3 Rotation/Scaling Parameter B (dmx) pub const REG_BG3PC: Addr = IO_BASE + 0x_0034; // 2 W BG3 Rotation/Scaling Parameter C (dy) pub const REG_BG3PD: Addr = IO_BASE + 0x_0036; // 2 W BG3 Rotation/Scaling Parameter D (dmy) - pub const REG_BG3X: Addr = IO_BASE + 0x_0038; // 4 W BG3 Reference Point X-Coordinate - pub const REG_BG3Y: Addr = IO_BASE + 0x_003C; // 4 W BG3 Reference Point Y-Coordinate + pub const REG_BG3X_L: Addr = IO_BASE + 0x_0038; // 4 W BG3 Reference Point X-Coordinate, lower 16 bit + pub const REG_BG3X_H: Addr = IO_BASE + 0x_003A; // 4 W BG3 Reference Point X-Coordinate, upper 16 bit + pub const REG_BG3Y_L: Addr = IO_BASE + 0x_003C; // 4 W BG3 Reference Point Y-Coordinate, lower 16 bit + pub const REG_BG3Y_H: Addr = IO_BASE + 0x_003E; // 4 W BG3 Reference Point Y-Coordinate, upper 16 bit pub const REG_WIN0H: Addr = IO_BASE + 0x_0040; // 2 W Window 0 Horizontal Dimensions pub const REG_WIN1H: Addr = IO_BASE + 0x_0042; // 2 W Window 1 Horizontal Dimensions pub const REG_WIN0V: Addr = IO_BASE + 0x_0044; // 2 W Window 0 Vertical Dimensions @@ -155,20 +159,18 @@ impl Bus for IoRegs { REG_DISPCNT => io.gpu.dispcnt.0, REG_DISPSTAT => io.gpu.dispstat.0, REG_VCOUNT => io.gpu.current_scanline as u16, - REG_BG0CNT => io.gpu.bgcnt[0].0, - REG_BG1CNT => io.gpu.bgcnt[1].0, - REG_BG2CNT => io.gpu.bgcnt[2].0, - REG_BG3CNT => io.gpu.bgcnt[3].0, + REG_BG0CNT => io.gpu.bg[0].bgcnt.0, + REG_BG1CNT => io.gpu.bg[1].bgcnt.0, + REG_BG2CNT => io.gpu.bg[2].bgcnt.0, + REG_BG3CNT => io.gpu.bg[3].bgcnt.0, REG_WIN0H => io.gpu.win0h, REG_WIN1H => io.gpu.win1h, REG_WIN0V => io.gpu.win0v, REG_WIN1V => io.gpu.win1v, REG_WININ => io.gpu.winin, REG_WINOUT => io.gpu.winout, - REG_MOSAIC => io.gpu.mosaic, - REG_BLDCNT => io.gpu.bldcnt, - REG_BLDALPHA => io.gpu.bldalpha, - REG_BLDY => io.gpu.bldy, + REG_BLDCNT => io.gpu.bldcnt.0, + REG_BLDALPHA => io.gpu.bldalpha.0, REG_IME => io.intc.interrupt_master_enable as u16, REG_IE => io.intc.interrupt_enable.0 as u16, @@ -206,28 +208,44 @@ impl Bus for IoRegs { match addr + IO_BASE { REG_DISPCNT => io.gpu.dispcnt.0 |= value, REG_DISPSTAT => io.gpu.dispstat.0 |= value, - REG_BG0CNT => io.gpu.bgcnt[0].0 |= value, - REG_BG1CNT => io.gpu.bgcnt[1].0 |= value, - REG_BG2CNT => io.gpu.bgcnt[2].0 |= value, - REG_BG3CNT => io.gpu.bgcnt[3].0 |= value, - REG_BG0HOFS => io.gpu.bghofs[0] = value, - REG_BG0VOFS => io.gpu.bgvofs[0] = value, - REG_BG1HOFS => io.gpu.bghofs[1] = value, - REG_BG1VOFS => io.gpu.bgvofs[1] = value, - REG_BG2HOFS => io.gpu.bghofs[2] = value, - REG_BG2VOFS => io.gpu.bgvofs[2] = value, - REG_BG3HOFS => io.gpu.bghofs[3] = value, - REG_BG3VOFS => io.gpu.bgvofs[3] = value, + REG_BG0CNT => io.gpu.bg[0].bgcnt.0 |= value, + REG_BG1CNT => io.gpu.bg[1].bgcnt.0 |= value, + REG_BG2CNT => io.gpu.bg[2].bgcnt.0 |= value, + REG_BG3CNT => io.gpu.bg[3].bgcnt.0 |= value, + REG_BG0HOFS => io.gpu.bg[0].bghofs = value & 0x1ff, + REG_BG0VOFS => io.gpu.bg[0].bgvofs = value & 0x1ff, + REG_BG1HOFS => io.gpu.bg[1].bghofs = value & 0x1ff, + REG_BG1VOFS => io.gpu.bg[1].bgvofs = value & 0x1ff, + REG_BG2HOFS => io.gpu.bg[2].bghofs = value & 0x1ff, + REG_BG2VOFS => io.gpu.bg[2].bgvofs = value & 0x1ff, + REG_BG3HOFS => io.gpu.bg[3].bghofs = value & 0x1ff, + REG_BG3VOFS => io.gpu.bg[3].bgvofs = value & 0x1ff, + REG_BG2X_L => io.gpu.bg_aff[0].x |= (value as u32) as i32, + REG_BG2X_H => io.gpu.bg_aff[0].x |= ((value as u32) << 16) as i32, + REG_BG2Y_L => io.gpu.bg_aff[0].y |= (value as u32) as i32, + REG_BG2Y_H => io.gpu.bg_aff[0].y |= ((value as u32) << 16) as i32, + REG_BG3X_L => io.gpu.bg_aff[1].x |= (value as u32) as i32, + REG_BG3X_H => io.gpu.bg_aff[1].x |= ((value as u32) << 16) as i32, + REG_BG3Y_L => io.gpu.bg_aff[1].y |= (value as u32) as i32, + REG_BG3Y_H => io.gpu.bg_aff[1].y |= ((value as u32) << 16) as i32, + REG_BG2PA => io.gpu.bg_aff[0].pa = value as i16, + REG_BG2PB => io.gpu.bg_aff[0].pb = value as i16, + REG_BG2PC => io.gpu.bg_aff[0].pc = value as i16, + REG_BG2PD => io.gpu.bg_aff[0].pd = value as i16, + REG_BG3PA => io.gpu.bg_aff[1].pa = value as i16, + REG_BG3PB => io.gpu.bg_aff[1].pb = value as i16, + REG_BG3PC => io.gpu.bg_aff[1].pc = value as i16, + REG_BG3PD => io.gpu.bg_aff[1].pd = value as i16, REG_WIN0H => io.gpu.win0h = value, REG_WIN1H => io.gpu.win1h = value, REG_WIN0V => io.gpu.win0v = value, REG_WIN1V => io.gpu.win1v = value, REG_WININ => io.gpu.winin = value, REG_WINOUT => io.gpu.winout = value, - REG_MOSAIC => io.gpu.mosaic = value, - REG_BLDCNT => io.gpu.bldcnt = value, - REG_BLDALPHA => io.gpu.bldalpha = value, - REG_BLDY => io.gpu.bldy = value, + REG_MOSAIC => io.gpu.mosaic.0 = value, + REG_BLDCNT => io.gpu.bldcnt.0 = value, + REG_BLDALPHA => io.gpu.bldalpha.0 = value, + REG_BLDY => io.gpu.bldy = value & 0b11111, REG_IME => io.intc.interrupt_master_enable = value != 0, REG_IE => io.intc.interrupt_enable.0 = value, diff --git a/src/core/timer.rs b/src/core/timer.rs index 7dfb6a2..7b07fa2 100644 --- a/src/core/timer.rs +++ b/src/core/timer.rs @@ -114,7 +114,6 @@ impl SyncedIoDevice for Timers { } } TimerAction::Increment => {} - _ => {} } } } diff --git a/src/debugger/tile_view.rs b/src/debugger/tile_view.rs index 2a88f5b..127ca9a 100644 --- a/src/debugger/tile_view.rs +++ b/src/debugger/tile_view.rs @@ -6,14 +6,7 @@ use sdl2::rect::{Point, Rect}; use sdl2::render::Canvas; use crate::core::gba::GameBoyAdvance; -use crate::core::palette::*; - -impl Into for Rgb15 { - fn into(self) -> Color { - let (r, g, b) = self.get_rgb24(); - Color::RGB(r, g, b) - } -} +use crate::core::gpu::PixelFormat; fn draw_tile( gba: &GameBoyAdvance, @@ -29,9 +22,11 @@ fn draw_tile( .gpu .read_pixel_index(&gba.sysbus, tile_addr, x, y, pixel_format); let color = io.gpu.get_palette_color(&gba.sysbus, index as u32, 0); - let (r, g, b) = color.get_rgb24(); - - canvas.set_draw_color(Color::RGB(r, g, b)); + canvas.set_draw_color(Color::RGB( + (color.r() as u8) << 3, + (color.g() as u8) << 3, + (color.b() as u8) << 3, + )); canvas.draw_point(p.offset(x as i32, y as i32)).unwrap(); } } @@ -52,7 +47,7 @@ pub fn create_tile_view(bg: u32, gba: &GameBoyAdvance) { let mut canvas = window.into_canvas().build().unwrap(); - let bgcnt = gba.io.borrow().gpu.bgcnt[bg as usize].clone(); + let bgcnt = gba.io.borrow().gpu.bg[bg as usize].bgcnt.clone(); let (tile_size, pixel_format) = bgcnt.tile_format(); let tileset_addr = bgcnt.char_block(); diff --git a/src/lib.rs b/src/lib.rs index 9ef69b0..8572f4a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,6 +6,8 @@ extern crate num_traits; extern crate bit; #[macro_use] extern crate bitfield; +#[macro_use] +extern crate bitflags; extern crate byteorder; diff --git a/src/util.rs b/src/util.rs index ea1c983..fbe2836 100644 --- a/src/util.rs +++ b/src/util.rs @@ -14,4 +14,7 @@ macro_rules! index2d { ($x:expr, $y:expr, $w:expr) => { $w * $y + $x }; + ($t:ty, $x:expr, $y:expr, $w:expr) => { + (($w as $t) * ($y as $t) + ($x as $t)) as $t + }; }