Implement background scrolling!
tonc's brin_demo.gba now works as intended :) Former-commit-id: 596c063c5968534f42e42f52203c85262b9c6fa2
This commit is contained in:
parent
f862209911
commit
eb2a1a02fe
6 changed files with 109 additions and 130 deletions
|
@ -41,7 +41,6 @@ fn run_emulator(matches: &ArgMatches) -> GBAResult<()> {
|
||||||
|
|
||||||
let mut core = Core::new();
|
let mut core = Core::new();
|
||||||
core.reset();
|
core.reset();
|
||||||
core.set_verbose(true);
|
|
||||||
if skip_bios {
|
if skip_bios {
|
||||||
core.gpr[13] = 0x0300_7f00;
|
core.gpr[13] = 0x0300_7f00;
|
||||||
core.gpr_banked_r13[0] = 0x0300_7f00; // USR/SYS
|
core.gpr_banked_r13[0] = 0x0300_7f00; // USR/SYS
|
||||||
|
@ -59,6 +58,7 @@ fn run_emulator(matches: &ArgMatches) -> GBAResult<()> {
|
||||||
let mut gba = GameBoyAdvance::new(core, bios_bin, gamepak, backend);
|
let mut gba = GameBoyAdvance::new(core, bios_bin, gamepak, backend);
|
||||||
|
|
||||||
if debug {
|
if debug {
|
||||||
|
gba.cpu.set_verbose(true);
|
||||||
let mut debugger = Debugger::new(gba);
|
let mut debugger = Debugger::new(gba);
|
||||||
println!("starting debugger...");
|
println!("starting debugger...");
|
||||||
debugger.repl()?;
|
debugger.repl()?;
|
||||||
|
|
|
@ -52,31 +52,15 @@ impl GameBoyAdvance {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn emulate_n_cycles(&mut self, mut n: usize) {
|
|
||||||
let mut cycles = 0;
|
|
||||||
loop {
|
|
||||||
let previous_cycles = self.cpu.cycles;
|
|
||||||
self.cpu.step_one(&mut self.sysbus).unwrap();
|
|
||||||
let new_cycles = self.cpu.cycles - previous_cycles;
|
|
||||||
|
|
||||||
self.gpu.step(new_cycles, &mut self.sysbus);
|
|
||||||
cycles += new_cycles;
|
|
||||||
|
|
||||||
if n <= cycles {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn frame(&mut self) {
|
pub fn frame(&mut self) {
|
||||||
self.update_key_state();
|
self.update_key_state();
|
||||||
while self.gpu.state == GpuState::VBlank {
|
|
||||||
self.emulate();
|
|
||||||
}
|
|
||||||
while self.gpu.state != GpuState::VBlank {
|
while self.gpu.state != GpuState::VBlank {
|
||||||
self.emulate();
|
self.emulate();
|
||||||
}
|
}
|
||||||
self.backend.render(self.gpu.render());
|
self.backend.render(self.gpu.render());
|
||||||
|
while self.gpu.state == GpuState::VBlank {
|
||||||
|
self.emulate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_key_state(&mut self) {
|
fn update_key_state(&mut self) {
|
||||||
|
@ -105,8 +89,9 @@ impl GameBoyAdvance {
|
||||||
let irq_bit_index = irq as usize;
|
let irq_bit_index = irq as usize;
|
||||||
let reg_ie = self.sysbus.ioregs.read_reg(REG_IE);
|
let reg_ie = self.sysbus.ioregs.read_reg(REG_IE);
|
||||||
if reg_ie.bit(irq_bit_index) {
|
if reg_ie.bit(irq_bit_index) {
|
||||||
self.sysbus.ioregs.write_reg(REG_IF, (1 << irq_bit_index) as u16);
|
self.sysbus
|
||||||
println!("entering {:?}", irq);
|
.ioregs
|
||||||
|
.write_reg(REG_IF, (1 << irq_bit_index) as u16);
|
||||||
self.cpu.exception(Exception::Irq);
|
self.cpu.exception(Exception::Irq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
168
src/core/gpu.rs
168
src/core/gpu.rs
|
@ -85,40 +85,43 @@ pub struct BgControl {
|
||||||
moasic: bool,
|
moasic: bool,
|
||||||
palette256: bool, // 0=16/16, 1=256/1)
|
palette256: bool, // 0=16/16, 1=256/1)
|
||||||
screen_base_block: u8,
|
screen_base_block: u8,
|
||||||
wraparound: bool,
|
affine_wraparound: bool,
|
||||||
screen_width: usize,
|
bg_size: u32,
|
||||||
screen_height: usize,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u16> for BgControl {
|
impl From<u16> for BgControl {
|
||||||
fn from(v: u16) -> Self {
|
fn from(v: u16) -> Self {
|
||||||
let (width, height) = match v.bit_range(14..16) {
|
|
||||||
0 => (256, 256),
|
|
||||||
1 => (512, 256),
|
|
||||||
2 => (256, 512),
|
|
||||||
3 => (512, 512),
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
BgControl {
|
BgControl {
|
||||||
bg_priority: v.bit_range(0..2) as u8,
|
bg_priority: v.bit_range(0..2) as u8,
|
||||||
character_base_block: v.bit_range(2..4) as u8,
|
character_base_block: v.bit_range(2..4) as u8,
|
||||||
moasic: v.bit(6),
|
moasic: v.bit(6),
|
||||||
palette256: v.bit(7),
|
palette256: v.bit(7),
|
||||||
screen_base_block: v.bit_range(8..13) as u8,
|
screen_base_block: v.bit_range(8..13) as u8,
|
||||||
wraparound: v.bit(13),
|
affine_wraparound: v.bit(13),
|
||||||
screen_width: width,
|
bg_size: v.bit_range(14..16) as u32,
|
||||||
screen_height: height,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const SCREEN_BLOCK_SIZE: u32 = 0x800;
|
||||||
|
|
||||||
impl BgControl {
|
impl BgControl {
|
||||||
pub fn char_block(&self) -> Addr {
|
pub fn char_block(&self) -> Addr {
|
||||||
VRAM_ADDR + (self.character_base_block as u32) * 0x4000
|
VRAM_ADDR + (self.character_base_block as u32) * 0x4000
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn screen_block(&self) -> Addr {
|
pub fn screen_block(&self) -> Addr {
|
||||||
VRAM_ADDR + (self.screen_base_block as u32) * 0x800
|
VRAM_ADDR + (self.screen_base_block as u32) * SCREEN_BLOCK_SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
fn size_regular(&self) -> (u32, u32) {
|
||||||
|
match self.bg_size {
|
||||||
|
0b00 => (256, 256),
|
||||||
|
0b01 => (512, 256),
|
||||||
|
0b10 => (256, 512),
|
||||||
|
0b11 => (512, 512),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tile_format(&self) -> (u32, PixelFormat) {
|
pub fn tile_format(&self) -> (u32, PixelFormat) {
|
||||||
|
@ -222,9 +225,9 @@ impl Gpu {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn bgofs(&self, bg: u32, sysbus: &SysBus) -> (u32, u32) {
|
fn bgofs(&self, bg: u32, sysbus: &SysBus) -> (u32, u32) {
|
||||||
let hofs = (sysbus.ioregs.read_reg(REG_BG0HOFS + 4 * bg) & 0x1ff) as u32;
|
let hofs = sysbus.ioregs.read_reg(REG_BG0HOFS + 4 * bg) & 0x1ff;
|
||||||
let vofs = (sysbus.ioregs.read_reg(REG_BG0VOFS + 4 * bg) & 0x1ff) as u32;
|
let vofs = sysbus.ioregs.read_reg(REG_BG0VOFS + 4 * bg) & 0x1ff;
|
||||||
(hofs, vofs)
|
(hofs as u32, vofs as u32)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// helper method that reads the palette index from a base address and x + y
|
/// helper method that reads the palette index from a base address and x + y
|
||||||
|
@ -232,29 +235,20 @@ impl Gpu {
|
||||||
&self,
|
&self,
|
||||||
sysbus: &SysBus,
|
sysbus: &SysBus,
|
||||||
addr: Addr,
|
addr: Addr,
|
||||||
mut x: u32,
|
x: u32,
|
||||||
mut y: u32,
|
y: u32,
|
||||||
width: u32,
|
|
||||||
format: PixelFormat,
|
format: PixelFormat,
|
||||||
x_flip: bool,
|
|
||||||
y_flip: bool,
|
|
||||||
) -> usize {
|
) -> usize {
|
||||||
if x_flip {
|
|
||||||
x = 7 - x;
|
|
||||||
}
|
|
||||||
if y_flip {
|
|
||||||
y = 7 - x;
|
|
||||||
}
|
|
||||||
match format {
|
match format {
|
||||||
PixelFormat::BPP4 => {
|
PixelFormat::BPP4 => {
|
||||||
let byte = sysbus.read_8(addr + width * y + x / 2);
|
let byte = sysbus.read_8(addr + index2d!(x / 2, y, 4));
|
||||||
if x & 1 != 0 {
|
if x & 1 != 0 {
|
||||||
(byte >> 4) as usize
|
(byte >> 4) as usize
|
||||||
} else {
|
} else {
|
||||||
(byte & 0xf) as usize
|
(byte & 0xf) as usize
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PixelFormat::BPP8 => sysbus.read_8(addr + width * y + x) as usize,
|
PixelFormat::BPP8 => sysbus.read_8(addr + index2d!(x, y, 8)) as usize,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,57 +260,75 @@ impl Gpu {
|
||||||
|
|
||||||
fn scanline_mode0(&mut self, bg: u32, sysbus: &mut SysBus) {
|
fn scanline_mode0(&mut self, bg: u32, sysbus: &mut SysBus) {
|
||||||
let bgcnt = self.bgcnt(bg, sysbus);
|
let bgcnt = self.bgcnt(bg, sysbus);
|
||||||
|
let (h_ofs, v_ofs) = self.bgofs(bg, sysbus);
|
||||||
let tileset_base = bgcnt.char_block();
|
let tileset_base = bgcnt.char_block();
|
||||||
let tilemap_base = bgcnt.screen_block();
|
let tilemap_base = bgcnt.screen_block();
|
||||||
let (tile_size, pixel_format) = bgcnt.tile_format();
|
let (tile_size, pixel_format) = bgcnt.tile_format();
|
||||||
|
|
||||||
let tiles_per_row = (bgcnt.screen_width / 8) as u32;
|
let (bg_width, bg_height) = bgcnt.size_regular();
|
||||||
|
|
||||||
let mut px = 0;
|
let screen_y = self.current_scanline as u32;
|
||||||
let py = self.current_scanline as u32;
|
let mut screen_x = 0;
|
||||||
|
|
||||||
for tile in 0..tiles_per_row {
|
// calculate the bg coords at the top-left corner, including wraparound
|
||||||
let map_index = tile + (py / 8) * tiles_per_row;
|
let bg_x = (screen_x + h_ofs) % bg_width;
|
||||||
let map_addr = tilemap_base + 2 * map_index;
|
let bg_y = (screen_y + v_ofs) % bg_height;
|
||||||
|
|
||||||
|
// calculate the initial screen entry index
|
||||||
|
// | (256,256) | (512,256) | (256,512) | (512,512) |
|
||||||
|
// |-----------|-----------|-------------|-----------|
|
||||||
|
// | | | [1] | [2][3] |
|
||||||
|
// | [0] | [0][1] | [0] | [0][1] |
|
||||||
|
// |___________|___________|_____________|___________|
|
||||||
|
//
|
||||||
|
let mut screen_block = match (bg_width, bg_height) {
|
||||||
|
(256, 256) => 0,
|
||||||
|
(512, 256) => bg_x / 256,
|
||||||
|
(256, 512) => bg_y / 256,
|
||||||
|
(512, 512) => index2d!(bg_x / 256, bg_y / 256, 2),
|
||||||
|
_ => unreachable!(),
|
||||||
|
} as u32;
|
||||||
|
|
||||||
|
let 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;
|
||||||
|
|
||||||
|
for t in 0..32 {
|
||||||
|
let map_addr = tilemap_base
|
||||||
|
+ SCREEN_BLOCK_SIZE * screen_block
|
||||||
|
+ 2 * (index2d!((se_row + t) % 32, se_column, 32) as u32);
|
||||||
let entry = TileMapEntry::from(sysbus.read_16(map_addr));
|
let entry = TileMapEntry::from(sysbus.read_16(map_addr));
|
||||||
let tile_addr = tileset_base + entry.tile_index * tile_size;
|
let tile_addr = tileset_base + entry.tile_index * tile_size;
|
||||||
let tile_y = py % 8;
|
|
||||||
for tile_x in 0..=7 {
|
for tile_px in start_tile_x..=7 {
|
||||||
let color = match pixel_format {
|
let tile_py = (bg_y % 8) as u32;
|
||||||
PixelFormat::BPP4 => {
|
let index = self.read_pixel_index(
|
||||||
let index = self.read_pixel_index(
|
sysbus,
|
||||||
sysbus,
|
tile_addr,
|
||||||
tile_addr,
|
if entry.x_flip { 7 - tile_px } else { tile_px },
|
||||||
tile_x,
|
if entry.y_flip { 7 - tile_py } else { tile_py },
|
||||||
tile_y as u32,
|
pixel_format,
|
||||||
4,
|
);
|
||||||
pixel_format,
|
let palette_bank = match pixel_format {
|
||||||
entry.x_flip,
|
PixelFormat::BPP4 => entry.palette_bank as u32,
|
||||||
entry.y_flip,
|
PixelFormat::BPP8 => 0u32,
|
||||||
);
|
|
||||||
self.get_palette_color(sysbus, index as u32, entry.palette_bank as u32)
|
|
||||||
}
|
|
||||||
PixelFormat::BPP8 => {
|
|
||||||
let index = self.read_pixel_index(
|
|
||||||
sysbus,
|
|
||||||
tile_addr,
|
|
||||||
tile_x,
|
|
||||||
tile_y as u32,
|
|
||||||
8,
|
|
||||||
pixel_format,
|
|
||||||
entry.x_flip,
|
|
||||||
entry.y_flip,
|
|
||||||
);
|
|
||||||
self.get_palette_color(sysbus, index as u32, 0)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
let color = self.get_palette_color(sysbus, index as u32, palette_bank);
|
||||||
if color.get_rgb24() != (0, 0, 0) {
|
if color.get_rgb24() != (0, 0, 0) {
|
||||||
self.pixeldata[((px + tile_x) as usize) + (py as usize) * 512] = color;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
px += 8;
|
start_tile_x = 0;
|
||||||
if px == bgcnt.screen_width as u32 {
|
if se_row + t == 31 {
|
||||||
return;
|
if bg_width == 512 {
|
||||||
|
screen_block = screen_block ^ 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -325,9 +337,9 @@ impl Gpu {
|
||||||
let y = self.current_scanline;
|
let y = self.current_scanline;
|
||||||
|
|
||||||
for x in 0..Self::DISPLAY_WIDTH {
|
for x in 0..Self::DISPLAY_WIDTH {
|
||||||
let pixel_index = x + y * Self::DISPLAY_WIDTH;
|
let pixel_index = index2d!(x, y, Self::DISPLAY_WIDTH);
|
||||||
let pixel_addr = 0x0600_0000 + 2 * (pixel_index as u32);
|
let pixel_addr = 0x0600_0000 + 2 * (pixel_index as u32);
|
||||||
self.pixeldata[x + y * 512] = sb.read_16(pixel_addr).into();
|
self.pixeldata[index2d!(x, y, 512)] = sb.read_16(pixel_addr).into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,10 +353,10 @@ impl Gpu {
|
||||||
let y = self.current_scanline;
|
let y = self.current_scanline;
|
||||||
|
|
||||||
for x in 0..Self::DISPLAY_WIDTH {
|
for x in 0..Self::DISPLAY_WIDTH {
|
||||||
let bitmap_index = x + y * Self::DISPLAY_WIDTH;
|
let bitmap_index = index2d!(x, y, Self::DISPLAY_WIDTH);
|
||||||
let bitmap_addr = page + (bitmap_index as u32);
|
let bitmap_addr = page + (bitmap_index as u32);
|
||||||
let index = sysbus.read_8(bitmap_addr as Addr) as u32;
|
let index = sysbus.read_8(bitmap_addr as Addr) as u32;
|
||||||
self.pixeldata[x + y * 512] = self.get_palette_color(sysbus, index, 0);
|
self.pixeldata[index2d!(x, y, 512)] = self.get_palette_color(sysbus, index, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -377,9 +389,8 @@ impl Gpu {
|
||||||
let mut buffer = vec![0u32; Gpu::DISPLAY_WIDTH * Gpu::DISPLAY_WIDTH];
|
let mut buffer = vec![0u32; Gpu::DISPLAY_WIDTH * Gpu::DISPLAY_WIDTH];
|
||||||
for y in 0..Gpu::DISPLAY_HEIGHT {
|
for y in 0..Gpu::DISPLAY_HEIGHT {
|
||||||
for x in 0..Gpu::DISPLAY_WIDTH {
|
for x in 0..Gpu::DISPLAY_WIDTH {
|
||||||
let index = (x as usize) + (y as usize) * (512 as usize);
|
let (r, g, b) = self.pixeldata[index2d!(x as usize, y as usize, 512)].get_rgb24();
|
||||||
let (r, g, b) = self.pixeldata[index].get_rgb24();
|
buffer[index2d!(x, y, Gpu::DISPLAY_WIDTH)] =
|
||||||
buffer[x + Gpu::DISPLAY_WIDTH * y] =
|
|
||||||
((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
|
((r as u32) << 16) | ((g as u32) << 8) | (b as u32);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -387,8 +398,6 @@ impl Gpu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// *TODO* Running the Gpu step by step causes a massive performance impact, so for now not treat it as an emulated IO device.
|
|
||||||
|
|
||||||
impl EmuIoDev for Gpu {
|
impl EmuIoDev for Gpu {
|
||||||
fn step(&mut self, cycles: usize, sysbus: &mut SysBus) -> (usize, Option<Interrupt>) {
|
fn step(&mut self, cycles: usize, sysbus: &mut SysBus) -> (usize, Option<Interrupt>) {
|
||||||
self.cycles += cycles;
|
self.cycles += cycles;
|
||||||
|
@ -400,7 +409,7 @@ impl EmuIoDev for Gpu {
|
||||||
|
|
||||||
dispstat.vcount_flag = dispstat.vcount_setting as usize == self.current_scanline;
|
dispstat.vcount_flag = dispstat.vcount_setting as usize == self.current_scanline;
|
||||||
if dispstat.vcount_irq_enable {
|
if dispstat.vcount_irq_enable {
|
||||||
panic!("VCOUNT IRQ NOT IMPL");
|
println!("VCOUNT IRQ NOT IMPL");
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.state {
|
match self.state {
|
||||||
|
@ -420,6 +429,7 @@ impl EmuIoDev for Gpu {
|
||||||
};
|
};
|
||||||
(HBlank, irq)
|
(HBlank, irq)
|
||||||
} else {
|
} else {
|
||||||
|
self.scanline(sysbus);
|
||||||
dispstat.vblank_flag = true;
|
dispstat.vblank_flag = true;
|
||||||
let irq = if dispstat.vblank_irq_enable {
|
let irq = if dispstat.vblank_irq_enable {
|
||||||
Some(Interrupt::LCD_VBlank)
|
Some(Interrupt::LCD_VBlank)
|
||||||
|
|
|
@ -28,34 +28,10 @@ fn draw_tile(
|
||||||
) {
|
) {
|
||||||
for y in 0..8 {
|
for y in 0..8 {
|
||||||
for x in 0..8 {
|
for x in 0..8 {
|
||||||
let color = match pixel_format {
|
let index = gba
|
||||||
PixelFormat::BPP4 => {
|
.gpu
|
||||||
let index = gba.gpu.read_pixel_index(
|
.read_pixel_index(&gba.sysbus, tile_addr, x, y, pixel_format);
|
||||||
&gba.sysbus,
|
let color = gba.gpu.get_palette_color(&gba.sysbus, index as u32, 0);
|
||||||
tile_addr,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
4,
|
|
||||||
pixel_format,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
gba.gpu.get_palette_color(&gba.sysbus, index as u32, 0xf)
|
|
||||||
}
|
|
||||||
PixelFormat::BPP8 => {
|
|
||||||
let index = gba.gpu.read_pixel_index(
|
|
||||||
&gba.sysbus,
|
|
||||||
tile_addr,
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
8,
|
|
||||||
pixel_format,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
);
|
|
||||||
gba.gpu.get_palette_color(&gba.sysbus, index as u32, 0)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let (r, g, b) = color.get_rgb24();
|
let (r, g, b) = color.get_rgb24();
|
||||||
|
|
||||||
canvas.set_draw_color(Color::RGB(r, g, b));
|
canvas.set_draw_color(Color::RGB(r, g, b));
|
||||||
|
|
|
@ -14,9 +14,10 @@ extern crate nom;
|
||||||
extern crate ansi_term;
|
extern crate ansi_term;
|
||||||
extern crate colored; // not needed in Rust 2018
|
extern crate colored; // not needed in Rust 2018
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
pub mod util;
|
||||||
pub mod backend;
|
pub mod backend;
|
||||||
pub mod core;
|
pub mod core;
|
||||||
pub mod debugger;
|
pub mod debugger;
|
||||||
pub mod disass;
|
pub mod disass;
|
||||||
pub mod minifb_backend;
|
pub mod minifb_backend;
|
||||||
pub mod util;
|
|
||||||
|
|
|
@ -8,3 +8,10 @@ pub fn read_bin_file(filename: &str) -> io::Result<Vec<u8>> {
|
||||||
file.read_to_end(&mut buf)?;
|
file.read_to_end(&mut buf)?;
|
||||||
Ok(buf)
|
Ok(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! index2d {
|
||||||
|
($x:expr, $y:expr, $w:expr) => {
|
||||||
|
$w * $y + $x
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
Reference in a new issue