From ae379df251969cfc5def2fbb8305c9a2ec46a378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Magalh=C3=A3es?= <joamag@gmail.com> Date: Mon, 11 Jul 2022 00:01:15 +0100 Subject: [PATCH] feat: support for 8x16 sprites/objects --- CHANGELOG.md | 2 +- examples/web/index.html | 2 +- examples/web/index.ts | 21 +++++++++++++------ src/ppu.rs | 45 ++++++++++++++++++++++++++++++++++------- 4 files changed, 55 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d4756df..d2425423 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -* +* Support for 8x16 sprites ### Changed diff --git a/examples/web/index.html b/examples/web/index.html index 17dc8202..3000b3a8 100644 --- a/examples/web/index.html +++ b/examples/web/index.html @@ -69,7 +69,7 @@ <div id="separator-keyboard" class="separator" style="display: none;"></div> <div id="section-debug" class="section" style="display: none;"> <div id="debug" class="debug"> - <canvas id="canvas-tiles" class="canvas-tiles" width="128" height="128"></canvas> + <canvas id="canvas-tiles" class="canvas-tiles" width="128" height="192"></canvas> </div> </div> <div id="separator-debug" class="separator" style="display: none;"></div> diff --git a/examples/web/index.ts b/examples/web/index.ts index 8af69031..f2e4f018 100644 --- a/examples/web/index.ts +++ b/examples/web/index.ts @@ -541,9 +541,18 @@ const registerButtons = () => { ); const videoBuff = new DataView(canvasImage.data.buffer); - const drawSprite = ( + /** + * Draws the tile at the given index to the proper + * vertical offset in the given context and buffer. + * + * @param index The index of the sprite to be drawn. + * @param format The pixel format of the sprite. + */ + const drawTile = ( index: number, - format: PixelFormat = PixelFormat.RGB + context: CanvasRenderingContext2D, + buffer: DataView, + format: PixelFormat = PixelFormat.RGB, ) => { const pixels = state.gameBoy.get_tile_buffer(index); const line = Math.floor(index / 16); @@ -558,7 +567,7 @@ const registerButtons = () => { (pixels[index + 1] << 16) | (pixels[index + 2] << 8) | (format == PixelFormat.RGBA ? pixels[index + 3] : 0xff); - videoBuff.setUint32(offset, color); + buffer.setUint32(offset, color); counter++; if (counter == 8) { @@ -568,11 +577,11 @@ const registerButtons = () => { offset += PixelFormat.RGBA; } } - canvasTilesCtx.putImageData(canvasImage, 0, 0); + context.putImageData(canvasImage, 0, 0); }; - for (let index = 0; index < 256; index++) { - drawSprite(index); + for (let index = 0; index < 384; index++) { + drawTile(index, canvasTilesCtx, videoBuff); } const vram = state.gameBoy.vram_eager(); diff --git a/src/ppu.rs b/src/ppu.rs index 364ac835..14dcca66 100644 --- a/src/ppu.rs +++ b/src/ppu.rs @@ -16,6 +16,7 @@ pub const PALETTE_SIZE: usize = 4; pub const RGB_SIZE: usize = 3; pub const TILE_WIDTH: usize = 8; pub const TILE_HEIGHT: usize = 8; +pub const TILE_DOUBLE_HEIGHT: usize = 16; /// The number of tiles that can be store in Game Boy's /// VRAM memory according to specifications. @@ -410,7 +411,7 @@ impl Ppu { 0x0040 => { let value = if self.switch_bg { 0x01 } else { 0x00 } | if self.switch_obj { 0x02 } else { 0x00 } - | if self.obj_size { 0x02 } else { 0x00 } + | if self.obj_size { 0x04 } else { 0x00 } | if self.bg_map { 0x08 } else { 0x00 } | if self.bg_tile { 0x10 } else { 0x00 } | if self.switch_window { 0x20 } else { 0x00 } @@ -642,7 +643,7 @@ impl Ppu { self.render_map(self.bg_map, self.scx, self.scy, 0, 0); } if self.switch_window { - self.render_map(self.window_map, 0, 0, self.wx - 7, self.wy); + self.render_map(self.window_map, 0, 0, self.wx, self.wy); } if self.switch_obj { self.render_objects(); @@ -704,7 +705,7 @@ impl Ppu { // in case the current pixel to be drawn for the line // is visible within the window draws it an increments // the X coordinate of the tile - if index as u8 >= wx { + if index as i16 >= wx as i16 - 7 { // obtains the current pixel data from the tile and // re-maps it according to the current palette let pixel = self.tiles[tile_index].get(x, y); @@ -757,11 +758,17 @@ impl Ppu { // under iteration to be checked for drawing let obj = self.obj_data[index]; + let obj_height = if self.obj_size { + TILE_DOUBLE_HEIGHT + } else { + TILE_HEIGHT + }; + // verifies if the sprite is currently located at the // current line that is going to be drawn and skips it // in case it's not let is_contained = - (obj.y <= self.ly as i16) && ((obj.y + TILE_HEIGHT as i16) > self.ly as i16); + (obj.y <= self.ly as i16) && ((obj.y + obj_height as i16) > self.ly as i16); if !is_contained { continue; } @@ -779,13 +786,37 @@ impl Ppu { // point for the draw operation to be performed let mut frame_offset = (self.ly as usize * DISPLAY_WIDTH + obj.x as usize) * RGB_SIZE; - let tile_offset = self.ly as i16 - obj.y; + // the relative title offset should range from 0 to 7 in 8x8 + // objects and from 0 to 15 in 8x16 objects + let mut tile_offset = self.ly as i16 - obj.y; + + // saves some space for the reference to the tile that + // is going to be used in the current operation + let tile: &Tile; + + // in case we're facing a 8x16 object and we must + // differentiate between the handling of the top tile + // and the bottom tile through bitwise manipulation + // of the tile index + if self.obj_size { + if tile_offset < 8 { + tile = &self.tiles[obj.tile as usize & 0xfe]; + } else { + tile = &self.tiles[obj.tile as usize | 0x01]; + tile_offset = tile_offset - 8; + } + } + // otherwise we're facing a 8x8 sprite and we should grab + // the tile directly from the object's tile index + else { + tile = &self.tiles[obj.tile as usize]; + } let tile_row: &[u8]; if obj.yflip { - tile_row = self.tiles[obj.tile as usize].get_row((7 - tile_offset) as usize); + tile_row = tile.get_row((7 - tile_offset) as usize); } else { - tile_row = self.tiles[obj.tile as usize].get_row((tile_offset) as usize); + tile_row = tile.get_row((tile_offset) as usize); } for x in 0..TILE_WIDTH { -- GitLab