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