UNPKG

wasmboy

Version:

Gameboy / Gameboy Color Emulator written for Web Assembly using AssemblyScript. Shell/Debugger in Preact

404 lines (348 loc) 16.8 kB
// Functions to debug graphical output import { BACKGROUND_MAP_LOCATION, TILE_DATA_LOCATION, OAM_TILES_LOCATION } from '../constants'; import { Graphics, Lcd, getTileDataAddress, drawPixelsFromLineOfTile, getMonochromeColorFromPalette, getColorizedGbHexColorFromPalette, getRedFromHexColor, getGreenFromHexColor, getBlueFromHexColor, getRgbColorFromPalette, getColorComponentFromRgb, loadFromVramBank } from '../graphics/index'; import { Cpu } from '../cpu/index'; import { eightBitLoadFromGBMemory, Memory } from '../memory/index'; import { checkBitOnByte } from '../helpers/index'; // Some Simple internal getters export function getLY(): i32 { return Graphics.scanlineRegister; } export function getScrollX(): i32 { return Graphics.scrollX; } export function getScrollY(): i32 { return Graphics.scrollY; } export function getWindowX(): i32 { return Graphics.windowX; } export function getWindowY(): i32 { return Graphics.windowY; } // TODO: Render by tile, rather than by pixel export function drawBackgroundMapToWasmMemory(showColor: i32): void { // http://www.codeslinger.co.uk/pages/projects/gameboy/graphics.html // Bit 7 - LCD Display Enable (0=Off, 1=On) // Bit 6 - Window Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF) // Bit 5 - Window Display Enable (0=Off, 1=On) // Bit 4 - BG & Window Tile Data Select (0=8800-97FF, 1=8000-8FFF) // Bit 3 - BG Tile Map Display Select (0=9800-9BFF, 1=9C00-9FFF) // Bit 2 - OBJ (Sprite) Size (0=8x8, 1=8x16) // Bit 1 - OBJ (Sprite) Display Enable (0=Off, 1=On) // Bit 0 - BG Display (for CGB see below) (0=Off, 1=On) // Get our seleted tile data memory location let tileDataMemoryLocation = Graphics.memoryLocationTileDataSelectZeroStart; if (Lcd.bgWindowTileDataSelect) { tileDataMemoryLocation = Graphics.memoryLocationTileDataSelectOneStart; } let tileMapMemoryLocation = Graphics.memoryLocationTileMapSelectZeroStart; if (Lcd.bgTileMapDisplaySelect) { tileMapMemoryLocation = Graphics.memoryLocationTileMapSelectOneStart; } for (let y: i32 = 0; y < 256; y++) { for (let x: i32 = 0; x < 256; x++) { // Get our current Y let pixelYPositionInMap: i32 = y; // Get our Current X position of our pixel on the on the 160x144 camera // this is done by getting the current scroll X position, // and adding it do what X Value the scanline is drawing on the camera. let pixelXPositionInMap: i32 = x; // Divide our pixel position by 8 to get our tile. // Since, there are 256x256 pixels, and 32x32 tiles. // 256 / 8 = 32. // Also, bitshifting by 3, do do a division by 8 // Need to use u16s, as they will be used to compute an address, which will cause weird errors and overflows let tileXPositionInMap: i32 = pixelXPositionInMap >> 3; let tileYPositionInMap: i32 = pixelYPositionInMap >> 3; // Get our tile address on the tileMap // NOTE: (tileMap represents where each tile is displayed on the screen) // NOTE: (tile map represents the entire map, now just what is within the "camera") // For instance, if we have y pixel 144. 144 / 8 = 18. 18 * 32 = line address in map memory. // And we have x pixel 160. 160 / 8 = 20. // * 32, because remember, this is NOT only for the camera, the actual map is 32x32. Therefore, the next tile line of the map, is 32 byte offset. // Think like indexing a 2d array, as a 1d array and it make sense :) let tileMapAddress: i32 = tileMapMemoryLocation + tileYPositionInMap * 32 + tileXPositionInMap; // Get the tile Id on the Tile Map let tileIdFromTileMap: i32 = loadFromVramBank(tileMapAddress, 0); // Now get our tileDataAddress for the corresponding tileID we found in the map // Read the comments in _getTileDataAddress() to see what's going on. // tl;dr if we had the tile map of "a b c d", and wanted tileId 2. // This funcitons returns the start of memory locaiton for the tile 'c'. let tileDataAddress: i32 = getTileDataAddress(tileDataMemoryLocation, tileIdFromTileMap); // Now we can process the the individual bytes that represent the pixel on a tile // Get the y pixel of the 8 by 8 tile. // Simply modulo the scanline. // For instance, let's say we are printing the first line of pixels on our camera, // And the first line of pixels on our tile. // yPixel = 1. 1 % 8 = 1. // And for the last line // yPixel = 144. 144 % 8 = 0. // 0 Represents last line of pixels in a tile, 1 represents first. 1 2 3 4 5 6 7 0. // Because remember, we are counting lines on the display NOT including zero let pixelYInTile: i32 = pixelYPositionInMap % 8; // Same logic as pixelYInTile. // However, We need to reverse our byte, // As pixel 0 is on byte 7, and pixel 1 is on byte 6, etc... // Therefore, is pixelX was 2, then really is need to be 5 // So 2 - 7 = -5, * 1 = 5 // Or to simplify, 7 - 2 = 5 haha! let pixelXInTile: i32 = pixelXPositionInMap % 8; pixelXInTile = 7 - pixelXInTile; // Get the GB Map Attributes // Bit 0-2 Background Palette number (BGP0-7) // Bit 3 Tile VRAM Bank number (0=Bank 0, 1=Bank 1) // Bit 4 Not used // Bit 5 Horizontal Flip (0=Normal, 1=Mirror horizontally) // Bit 6 Vertical Flip (0=Normal, 1=Mirror vertically) // Bit 7 BG-to-OAM Priority (0=Use OAM priority bit, 1=BG Priority) let bgMapAttributes: i32 = 0; if (Cpu.GBCEnabled && showColor > 0) { bgMapAttributes = loadFromVramBank(tileMapAddress, 1); } if (checkBitOnByte(6, bgMapAttributes)) { // We are mirroring the tile, therefore, we need to opposite byte // So if our pizel was 0 our of 8, it wild become 7 :) // TODO: This may be wrong :p pixelYInTile = 7 - pixelYInTile; } // Remember to represent a single line of 8 pixels on a tile, we need two bytes. // Therefore, we need to times our modulo by 2, to get the correct line of pixels on the tile. // But we need to load the time from a specific Vram bank let vramBankId: i32 = 0; if (checkBitOnByte(3, bgMapAttributes)) { vramBankId = 1; } // Remember to represent a single line of 8 pixels on a tile, we need two bytes. // Therefore, we need to times our modulo by 2, to get the correct line of pixels on the tile. // Again, think like you had to map a 2d array as a 1d. let byteOneForLineOfTilePixels: i32 = loadFromVramBank(tileDataAddress + pixelYInTile * 2, vramBankId); let byteTwoForLineOfTilePixels: i32 = loadFromVramBank(tileDataAddress + pixelYInTile * 2 + 1, vramBankId); // Now we can get the color for that pixel // Colors are represented by getting X position of ByteTwo, and X positon of Byte One // To Get the color Id. // For example, the result of the color id is 0000 00[xPixelByteTwo][xPixelByteOne] // See: How to draw a tile/sprite from memory: http://www.codeslinger.co.uk/pages/projects/gameboy/graphics.html let paletteColorId: i32 = 0; if (checkBitOnByte(pixelXInTile, byteTwoForLineOfTilePixels)) { // Byte one represents the second bit in our color id, so bit shift paletteColorId += 1; paletteColorId = paletteColorId << 1; } if (checkBitOnByte(pixelXInTile, byteOneForLineOfTilePixels)) { paletteColorId += 1; } // FINALLY, RENDER THAT PIXEL! let pixelStart: i32 = (y * 256 + x) * 3; if (Cpu.GBCEnabled && showColor > 0) { // Finally lets add some, C O L O R // Want the botom 3 bits let bgPalette: i32 = bgMapAttributes & 0x07; // Call the helper function to grab the correct color from the palette let rgbColorPalette: i32 = getRgbColorFromPalette(bgPalette, paletteColorId, false); // Split off into red green and blue let red: i32 = getColorComponentFromRgb(0, rgbColorPalette); let green: i32 = getColorComponentFromRgb(1, rgbColorPalette); let blue: i32 = getColorComponentFromRgb(2, rgbColorPalette); let offset: i32 = BACKGROUND_MAP_LOCATION + pixelStart; store<u8>(offset, <u8>red); store<u8>(offset + 1, <u8>green); store<u8>(offset + 2, <u8>blue); } else { // Only rendering camera for now, so coordinates are for the camera. // Get the rgb value for the color Id, will be repeated into R, G, B (if not colorized) let hexColor: i32 = getColorizedGbHexColorFromPalette(paletteColorId, Graphics.memoryLocationBackgroundPalette); let offset: i32 = BACKGROUND_MAP_LOCATION + pixelStart; // Red store<u8>(offset + 0, <u8>getRedFromHexColor(hexColor)); // Green store<u8>(offset + 1, <u8>getGreenFromHexColor(hexColor)); // Blue store<u8>(offset + 2, <u8>getBlueFromHexColor(hexColor)); } } } } export function drawTileDataToWasmMemory(): void { for (let tileDataMapGridY: i32 = 0; tileDataMapGridY < 0x17; tileDataMapGridY++) { for (let tileDataMapGridX: i32 = 0; tileDataMapGridX < 0x1f; tileDataMapGridX++) { // Get Our VramBankID let vramBankId: i32 = 0; if (tileDataMapGridX > 0x0f) { vramBankId = 1; } // Get our tile ID let tileId: i32 = tileDataMapGridY; if (tileDataMapGridY > 0x0f) { tileId -= 0x0f; } tileId = tileId << 4; if (tileDataMapGridX > 0x0f) { tileId = tileId + (tileDataMapGridX - 0x0f); } else { tileId = tileId + tileDataMapGridX; } // Finally get our tile Data location let tileDataMemoryLocation: i32 = Graphics.memoryLocationTileDataSelectOneStart; if (tileDataMapGridY > 0x0f) { tileDataMemoryLocation = Graphics.memoryLocationTileDataSelectZeroStart; } // Let's see if we have C O L O R // Set the map and sprite attributes to -1 // Meaning, we will draw monochrome let paletteLocation: i32 = Graphics.memoryLocationBackgroundPalette; let bgMapAttributes: i32 = -1; let spriteAttributes: i32 = -1; // Let's see if the tile is being used by a sprite for (let spriteRow: i32 = 0; spriteRow < 8; spriteRow++) { for (let spriteColumn: i32 = 0; spriteColumn < 5; spriteColumn++) { let spriteIndex = spriteColumn * 8 + spriteRow; // Sprites occupy 4 bytes in the sprite attribute table let spriteTableIndex: i32 = spriteIndex * 4; let spriteTileId: i32 = eightBitLoadFromGBMemory(Graphics.memoryLocationSpriteAttributesTable + spriteTableIndex + 2); if (tileId === spriteTileId) { let currentSpriteAttributes: i32 = eightBitLoadFromGBMemory( Graphics.memoryLocationSpriteAttributesTable + spriteTableIndex + 3 ); let spriteVramBankId: i32 = 0; if (Cpu.GBCEnabled && checkBitOnByte(3, currentSpriteAttributes)) { spriteVramBankId = 1; } if (spriteVramBankId === vramBankId) { spriteAttributes = currentSpriteAttributes; spriteRow = 8; spriteColumn = 5; // Set our paletteLocation paletteLocation = Graphics.memoryLocationSpritePaletteOne; if (checkBitOnByte(4, spriteAttributes)) { paletteLocation = Graphics.memoryLocationSpritePaletteTwo; } } } } } // If we didn't find a sprite, // Let's see if the tile is on the bg tile map // If so, use that bg map for attributes if (Cpu.GBCEnabled && spriteAttributes < 0) { let tileMapMemoryLocation = Graphics.memoryLocationTileMapSelectZeroStart; if (Lcd.bgTileMapDisplaySelect) { tileMapMemoryLocation = Graphics.memoryLocationTileMapSelectOneStart; } // Loop through the tileMap, and find if we have our current ID let foundTileMapAddress: i32 = -1; for (let x: i32 = 0; x < 32; x++) { for (let y: i32 = 0; y < 32; y++) { let tileMapAddress: i32 = tileMapMemoryLocation + y * 32 + x; let tileIdFromTileMap: i32 = loadFromVramBank(tileMapAddress, 0); // Check if we found our tileId if (tileId === tileIdFromTileMap) { foundTileMapAddress = tileMapAddress; x = 32; y = 32; } } } if (foundTileMapAddress >= 0) { bgMapAttributes = loadFromVramBank(foundTileMapAddress, 1); } } // Draw each Y line of the tile for (let tileLineY: i32 = 0; tileLineY < 8; tileLineY++) { drawPixelsFromLineOfTile( tileId, // tileId tileDataMemoryLocation, // Graphics.memoryLocationTileDataSelect vramBankId, // Vram Bank 0, // Tile Line X Start 7, // Tile Line X End tileLineY, // Tile Line Y tileDataMapGridX * 8, // Output line X tileDataMapGridY * 8 + tileLineY, // Output line Y 0x1f * 8, // Output Width TILE_DATA_LOCATION, // Wasm Memory Start false, // shouldRepresentMonochromeColorByColorId paletteLocation, // paletteLocation bgMapAttributes, // bgMapAttributes spriteAttributes // spriteAttributes ); } } } } export function drawOamToWasmMemory(): void { // Draw all 40 sprites // Going to be like BGB and do 8 x 5 sprites for (let spriteRow: i32 = 0; spriteRow < 8; spriteRow++) { for (let spriteColumn: i32 = 0; spriteColumn < 5; spriteColumn++) { let spriteIndex = spriteColumn * 8 + spriteRow; // Sprites occupy 4 bytes in the sprite attribute table let spriteTableIndex: i32 = spriteIndex * 4; // Y positon is offset by 16, X position is offset by 8 let spriteYPosition: i32 = eightBitLoadFromGBMemory(Graphics.memoryLocationSpriteAttributesTable + spriteTableIndex); let spriteXPosition: i32 = eightBitLoadFromGBMemory(Graphics.memoryLocationSpriteAttributesTable + spriteTableIndex + 1); let spriteTileId: i32 = eightBitLoadFromGBMemory(Graphics.memoryLocationSpriteAttributesTable + spriteTableIndex + 2); let tilesToDraw: i32 = 1; if (Lcd.tallSpriteSize) { // @binji says in 8x16 mode, even tileId always drawn first // This will fix shantae sprites which always uses odd numbered indexes // TODO: Do the actual Pandocs thing: // "In 8x16 mode, the lower bit of the tile number is ignored. Ie. the upper 8x8 tile is "NN AND FEh", and the lower 8x8 tile is "NN OR 01h"." // So just knock off the last bit? :) if (spriteTileId % 2 === 1) { spriteTileId -= 1; } tilesToDraw += 1; } // Get our sprite attributes since we know we shall be drawing the tile let spriteAttributes: i32 = eightBitLoadFromGBMemory(Graphics.memoryLocationSpriteAttributesTable + spriteTableIndex + 3); // Check if we should flip the sprite on the x or y axis let flipSpriteY: boolean = checkBitOnByte(6, spriteAttributes); let flipSpriteX: boolean = checkBitOnByte(5, spriteAttributes); // Find which VRAM Bank to load from let vramBankId: i32 = 0; if (Cpu.GBCEnabled && checkBitOnByte(3, spriteAttributes)) { vramBankId = 1; } // Find which monochrome palette we should use let paletteLocation: i32 = Graphics.memoryLocationSpritePaletteOne; if (checkBitOnByte(4, spriteAttributes)) { paletteLocation = Graphics.memoryLocationSpritePaletteTwo; } // Start Drawing our tiles for (let i: i32 = 0; i < tilesToDraw; i++) { // Draw each Y line of the tile for (let tileLineY: i32 = 0; tileLineY < 8; tileLineY++) { drawPixelsFromLineOfTile( spriteTileId + i, // tileId Graphics.memoryLocationTileDataSelectOneStart, // Graphics.memoryLocationTileDataSelect vramBankId, // VRAM Bank 0, // Tile Line X Start 7, // Tile Line X End tileLineY, // Tile Line Y spriteRow * 8, // Output line X spriteColumn * 16 + tileLineY + i * 8, // Output line Y 8 * 8, // Output Width OAM_TILES_LOCATION, // Wasm Memory Start false, // shouldRepresentMonochromeColorByColorId paletteLocation, // paletteLocation -1, // bgMapAttributes spriteAttributes // spriteAttributes ); } } } } }