public void DumpBgTiles() { var image = new DirectBitmap(256, 256); int vramBaseOffset0 = 0x0; int vramBaseOffset1 = 0x8000; Color[] palette = LcdController.Palettes.Palette0; // You have to supply the code to get the tiles palette Func <int, int> get4BitPaletteNumber = (int tileNumber) => { for (int i = 0; i < 4; i++) { int pal = TileHelpers.FindBgPaletteForTile(tileNumber, LcdController.Bg[i].TileMap); if (pal != 0) { return(pal * 16); } } return(0); }; DebugDrawTiles(image, Memory.VRam, vramBaseOffset0, palette, false, get4BitPaletteNumber); image.Bitmap.Save(string.Format("../../../../dump/{0}Tiles{1}_{2}.png", "BGV0", "4bpp", Rom.RomName)); DebugDrawTiles(image, Memory.VRam, vramBaseOffset1, palette, false, get4BitPaletteNumber); image.Bitmap.Save(string.Format("../../../../dump/{0}Tiles{1}_{2}.png", "BGV1", "4bpp", Rom.RomName)); DebugDrawTiles(image, Memory.VRam, vramBaseOffset0, palette, true, get4BitPaletteNumber); image.Bitmap.Save(string.Format("../../../../dump/{0}Tiles{1}_{2}.png", "BGV0", "8bpp", Rom.RomName)); DebugDrawTiles(image, Memory.VRam, vramBaseOffset1, palette, true, get4BitPaletteNumber); image.Bitmap.Save(string.Format("../../../../dump/{0}Tiles{1}_{2}.png", "BGV1", "8bpp", Rom.RomName)); }
public int PixelValue(int screenX, int screenY) { int paletteOffset = 0; int scrollX = ScrollX; if (scrollX >= bgWidthInPixel) { scrollX -= bgWidthInPixel; } int scrollY = ScrollY; if (scrollY >= bgHeightInPixel) { scrollY -= bgHeightInPixel; } int wrappedBgY = scrollY + screenY; if (wrappedBgY >= bgHeightInPixel) { wrappedBgY -= bgHeightInPixel; } // Which line within the current tile are we rendering? int tileRow = wrappedBgY % 8; // If we reach the edge of the Bg, wrap around int wrappedBgX = scrollX + screenX; if (wrappedBgX >= bgWidthInPixel) { wrappedBgX -= bgWidthInPixel; } // Which column within the current tile are we rendering? int tileColumn = wrappedBgX % 8; var tileMetaData = TileMap.TileMapItemFromBgXY(wrappedBgX, wrappedBgY); // If we are in 4 bpp mode the tilemap contains which 16 colour palette to use. 16 entries per palette if (eightBitColour == false) { paletteOffset = tileMetaData.Palette * 16; } int tileVramOffset = (int)(tileDataVramOffset + ((tileMetaData.TileNumber) * tileSize)); int paletteIndex = TileHelpers.GetTilePixel(tileColumn, tileRow, eightBitColour, gba.Memory.VRam, tileVramOffset, tileMetaData.FlipHorizontal, tileMetaData.FlipVertical); if (paletteIndex == 0) { return(0); } return(paletteOffset + paletteIndex); }
// What is the pixel value within the sprite. passing 0,0 returns the pixel value of the top left of this sprite public int PixelValue(int spriteX, int spriteY) { Size spriteDimensions = Attributes.Dimensions; if (spriteX < 0 || spriteX >= spriteDimensions.Width || spriteY < 0 || spriteY >= spriteDimensions.Height) { return(0); } int currentSpriteColumnInTiles = spriteX / 8; int currentSpriteRowInTiles = spriteY / 8; int currentColumnWithinTile = spriteX % 8; int currentRowWithinTile = spriteY % 8; if (hFlip) { currentSpriteColumnInTiles = (spriteWidthInTiles - 1) - currentSpriteColumnInTiles; } if (vFlip) { currentSpriteRowInTiles = (spriteHeightInTiles - 1) - currentSpriteRowInTiles; } // This offset will be set to point to the start of the 8x8 that spriteX,spriteY is within int vramTileOffset; // We count tile sizes in 4bpp when measuring tile rows / tile grid position // Addressing mode (1d / 2d) if (TileMapping2D) { // 2D addressing, vram is thought of as a 32x32 matrix of tiles. A sprites tiles are arranged as you would view them on a screen int full32TileRowSizeInBytes = LcdController.Tile_Size_4bit * 32; vramTileOffset = vramBaseOffset + offsetToFirstTile + (currentSpriteRowInTiles * full32TileRowSizeInBytes) + (currentSpriteColumnInTiles * tileSize); } else { // 1D addressing, all the sprites tiles are contiguous in vram vramTileOffset = vramBaseOffset + offsetToFirstTile + (currentSpriteRowInTiles * spriteRowSizeInBytes) + (currentSpriteColumnInTiles * tileSize); } // Lookup the actual pixel value (which is a palette index) in the tile data int paletteIndex = TileHelpers.GetTilePixel(currentColumnWithinTile, currentRowWithinTile, eightBitColour, gba.Memory.VRam, vramTileOffset, hFlip, vFlip); // Pal 0 == Transparent if (paletteIndex == 0) { return(0); } return(paletteOffset + paletteIndex); }
// Code to dump both BG and Obj tiles. Quite a complex list of parameters in order to make it work for both public void DebugDrawTiles(DirectBitmap image, byte[] vram, int vramBaseOffset, Color[] palette, bool eightBitColour, Func <int, int> getTile4BitPaletteNumber) { int tileCountX = 32; int tileCountY = 32; int tileX = 0; int tileY = 0; int tileSize = eightBitColour ? LcdController.Tile_Size_8bit: LcdController.Tile_Size_4bit; int totalTiles = eightBitColour ? 512 : 1024; for (int tileNumber = 0; tileNumber < totalTiles; tileNumber++) { int tileVramOffset = vramBaseOffset + (tileNumber * tileSize); int paletteOffset = 0; if (eightBitColour == false && getTile4BitPaletteNumber != null) { paletteOffset = getTile4BitPaletteNumber(tileNumber); } // Add one tiles pixels for (int y = 0; y < 8; y++) { for (int x = 0; x < 8; x++) { int paletteIndex = TileHelpers.GetTilePixel(x, y, eightBitColour, vram, tileVramOffset, false, false); // 0 == transparent / pal 0 int colIndex = (paletteIndex == 0 ? 0 : paletteOffset + paletteIndex); image.SetPixel(x + (tileX * 8), y + (tileY * 8), palette[colIndex]); } } // Coordinates on the output image tileX++; if (tileX == tileCountX) { tileX = 0; tileY++; } } bool drawGrid = true; if (drawGrid) { GfxHelpers.DrawGrid(image.Bitmap, Color.FromArgb(64, 0, 0, 0), 0, 0, tileCountX, tileCountY, 8, 8); } }
// Used for debug rendering BG's. Renders the source BG, does not scroll etc public void DebugRenderScanline(int scanline, int scanlineWidth, DirectBitmap drawBuffer) { Color[] palette = gba.LcdController.Palettes.Palette0; int paletteOffset = 0; bool eightBitColour = (CntRegister.PaletteMode == BgPaletteMode.PaletteMode256x1); // Which line within the current tile are we rendering? int tileRow = scanline % 8; for (int x = 0; x < scanlineWidth; x++) { // Which column within the current tile are we rendering? int tileColumn = x % 8; var tileMetaData = TileMap.TileMapItemFromBgXY(x, scanline); // If we are in 4 bpp mode the tilemap contains which 16 colour palette to use. 16 entries per palette if (eightBitColour == false) { paletteOffset = tileMetaData.Palette * 16; } int tileSize = (eightBitColour ? LcdController.Tile_Size_8bit : LcdController.Tile_Size_4bit); // 4 bytes represent one row of pixel data for a single tile int tileVramOffset = (int)(tileDataVramOffset + ((tileMetaData.TileNumber) * tileSize)); // Sometimes Bg's can be set up with invalid data which won't be drawn if (tileVramOffset >= gba.Memory.VRam.Length) { continue; } int paletteIndex = TileHelpers.GetTilePixel(tileColumn, tileRow, eightBitColour, gba.Memory.VRam, tileVramOffset, tileMetaData.FlipHorizontal, tileMetaData.FlipVertical); // Pal 0 == Transparent if (paletteIndex == 0) { continue; } drawBuffer.SetPixel(x, scanline, palette[paletteOffset + paletteIndex]); } }
// Used for debug rendering BG's. Renders the source BG, does not scroll etc public void DebugRenderScanlineAffine(int scanline, int scanlineWidth, DirectBitmap drawBuffer) { Color[] palette = gba.LcdController.Palettes.Palette0; bool eightBitColour = CntRegister.PaletteMode == BgPaletteMode.PaletteMode256x1; // Which line within the current tile are we rendering? int tileRow = scanline % 8; for (int x = 0; x < scanlineWidth; x++) { // Coords (measured in tiles) of the tile we want to render int bgRow = scanline / 8; int bgColumn = x / 8; // Which row / column within the tile we are rendering? int tileColumn = x % 8; // Affine BG's have one byte screen data (the tile index). Also all tiles are 8bpp // Affine BG's are also all square (they have their own size table which is different to regular tiled bg's) int tileInfoOffset = (bgRow * bgWidthInTiles) + bgColumn; int tileNumber = gba.Memory.VRam[(CntRegister.ScreenBlockBaseAddress * 2048) + tileInfoOffset]; int tileVramOffset = (int)(tileDataVramOffset + (tileNumber * tileSize)); // Sometimes Bg's can be set up with invalid data which won't be drawn if (tileVramOffset >= gba.Memory.VRam.Length) { continue; } int paletteIndex = TileHelpers.GetTilePixel(tileColumn, tileRow, true, gba.Memory.VRam, tileVramOffset, false, false); // Pal 0 == Transparent if (paletteIndex == 0) { continue; } drawBuffer.SetPixel(x, scanline, palette[paletteIndex]); } }
public void DumpObjTiles() { var image = new DirectBitmap(256, 256); // OBJ Tiles are stored in a separate area in VRAM: 06010000-06017FFF (32 KBytes) in BG Mode 0-2, or 06014000-06017FFF (16 KBytes) in BG Mode 3-5. // We dump the whole memory area in both 4 and 8 bit modes. Some will look wrong depending on Bg mode, colour depth etc int vramBaseOffset = 0x00010000; Color[] palette = LcdController.Palettes.Palette1; // You have to supply the code to get the tiles palette Func <int, int> get4BitPaletteNumber = (int tileNumber) => { Obj obj = TileHelpers.FindFirstSpriteThatUsesTile(tileNumber, LcdController.ObjController.Obj); return(obj == null ? 0 : obj.Attributes.PaletteNumber * 16); }; DebugDrawTiles(image, Memory.VRam, vramBaseOffset, palette, true, get4BitPaletteNumber); image.Bitmap.Save(string.Format("../../../../dump/{0}Tiles{1}_{2}.png", "Obj", "8bpp", Rom.RomName)); DebugDrawTiles(image, Memory.VRam, vramBaseOffset, palette, false, get4BitPaletteNumber); image.Bitmap.Save(string.Format("../../../../dump/{0}Tiles{1}_{2}.png", "Obj", "4bpp", Rom.RomName)); }
public int PixelValueAffine(int screenX, int screenY) { // Scrolling values set the origin so that BG 0,0 == Screen 0,0 // Affine scroll are 24.8 fixed point numbers but as long as you shift away the fraction part at the end, you can just do integer math on them and they work int scrollX = AffineScrollXCached; // >> 8; int scrollY = AffineScrollYCached; // >> 8; // The game will have set up the matrix to be the inverse texture mapping matrix. I.E it maps from screen space to texture space. Just what we need! int textureSpaceX, textureSpaceY; textureSpaceX = ((scrollX + (AffineMatrix.Pa * screenX)) >> 8); textureSpaceY = ((scrollY + (AffineMatrix.Pc * screenX)) >> 8); //AffineMatrix.Multiply(screenX, screenY, out textureSpaceX, out textureSpaceY); // Apply displacement vector (affine scroll) // textureSpaceX += scrollX; // textureSpaceY += scrollY; // BG Wrap? if (CntRegister.DisplayAreaOverflow) { /* * while (textureSpaceX >= bgWidthInPixel) textureSpaceX -= bgWidthInPixel; * while (textureSpaceY >= bgHeightInPixel) textureSpaceY -= bgHeightInPixel; * while (textureSpaceX < 0) textureSpaceX += bgWidthInPixel; * while (textureSpaceY < 0) textureSpaceY += bgHeightInPixel; */ textureSpaceX &= (bgWidthInPixel - 1); textureSpaceY &= (bgHeightInPixel - 1); } else { if (textureSpaceX < 0 || textureSpaceX >= bgWidthInPixel) { return(0); } if (textureSpaceY < 0 || textureSpaceY >= bgHeightInPixel) { return(0); } } // Coords (measured in tiles) of the tile we want to render //int bgRow = textureSpaceY / 8; //int bgColumn = textureSpaceX / 8; // Which row / column within the tile we are rendering? int tileRow = textureSpaceY % 8; int tileColumn = textureSpaceX % 8; // Affine BG's have one byte screen data (the tile index). Also all tiles are 8bpp // Affine BG's are also all square (they have their own size table which is different to regular tiled bg's) //int tileInfoOffset = (bgRow * bgWidthInTiles) + bgColumn; uint tileInfoOffset = ((CntRegister.ScreenBlockBaseAddress * 2048u) | (uint)((textureSpaceY >> 3) * ((uint)bgWidthInPixel >> 3)) | (uint)(textureSpaceX >> 3)); int tileNumber = gba.Memory.VRam[tileInfoOffset]; int tileVramOffset = (int)(tileDataVramOffset + (tileNumber * tileSize)); // Sometimes Bg's can be set up with invalid data which won't be drawn if (tileVramOffset >= gba.Memory.VRam.Length) { return(0); } int paletteIndex = TileHelpers.GetTilePixel(tileColumn, tileRow, true, gba.Memory.VRam, tileVramOffset, false, false); return(paletteIndex); }
private void RenderScanlineTextMode() { ObjPrioritySort(); int scanline = CurrentScanline; bool windowing = (DisplayControlRegister.DisplayWin0 || DisplayControlRegister.DisplayWin1 || DisplayControlRegister.DisplayWin1 || DisplayControlRegister.DisplayObjWin); // We render front to back. Once a pixel is drawn we stop going through the layers. // TODO: In order to do blending we may need to go through all the layers for each pixel #if ParallelizeScanline var paritioner = Partitioner.Create(0, LcdController.Screen_X_Resolution, 80); var result = Parallel.ForEach(paritioner, (range) => { for (int x = range.Item1; x < range.Item2; x++) #else for (int x = 0; x < Screen_X_Resolution; x++) #endif { int paletteIndex; bool pixelDrawn = false; // Windowing can disable obj's and bg's bool objVisibleOverride = false; int windowRegion = 0; int bgVisibleOverride = 0; // bitmask for bg visible (from window) if (windowing) { windowRegion = TileHelpers.PixelWindowRegion(x, CurrentScanline, gba); // 0 is outside of all windows if (windowRegion == 0) { objVisibleOverride = Windows[(int) Window.WindowName.WindowOut].DisplayObjs; bgVisibleOverride = Windows[(int)Window.WindowName.WindowOut].DisplayBg0 | Windows[(int)Window.WindowName.WindowOut].DisplayBg1 | Windows[(int)Window.WindowName.WindowOut].DisplayBg2 | Windows[(int)Window.WindowName.WindowOut].DisplayBg3; } // Window 0 takes priority over window 1. We don't need to check if the point is within both areas as it is done in the function call above else if((windowRegion & (int)TileHelpers.WindowRegion.Window0) != 0) { objVisibleOverride = Windows[(int)Window.WindowName.Window0].DisplayObjs; bgVisibleOverride = Windows[(int)Window.WindowName.Window0].DisplayBg0 | Windows[(int)Window.WindowName.Window0].DisplayBg1 | Windows[(int)Window.WindowName.Window0].DisplayBg2 | Windows[(int)Window.WindowName.Window0].DisplayBg3; } else if ((windowRegion & (int)TileHelpers.WindowRegion.Window1) != 0) { objVisibleOverride = Windows[(int)Window.WindowName.Window1].DisplayObjs; bgVisibleOverride = Windows[(int)Window.WindowName.Window1].DisplayBg0 | Windows[(int)Window.WindowName.Window1].DisplayBg1 | Windows[(int)Window.WindowName.Window1].DisplayBg2 | Windows[(int)Window.WindowName.Window1].DisplayBg3; } } else { objVisibleOverride = true; bgVisibleOverride = 0; } // Start at the top priority, if something draws to the pixel, we can early out and stop processing this pixel for (int priority = 0; priority < 4; priority++) { // Sprite rendering if (DisplayControlRegister.DisplayObj && (!windowing || (windowing && objVisibleOverride))) { pixelDrawn = RenderSpritePixel(x, scanline, priority, windowing, windowRegion, ref bgVisibleOverride); if (pixelDrawn) { break; } } // No Sprite occupied this pixel, move on to backgrounds // Bg Rendering // Find the background with this priority for (int bgSelect = 0; bgSelect < 4; bgSelect++) { if (Bg[bgSelect].CntRegister.Priority != priority || DisplayControlRegister.BgVisible(Bg[bgSelect].BgNumber) == false || (windowing && ((bgVisibleOverride & (1 << bgSelect)) == 0))) { continue; } if (Bg[bgSelect].AffineMode) { paletteIndex = Bg[bgSelect].PixelValueAffine(x, scanline); } else { paletteIndex = Bg[bgSelect].PixelValue(x, scanline); } // Pal 0 == Transparent if (paletteIndex == 0) { continue; } drawBuffer.SetPixel(x, scanline, Palettes.Palette0[paletteIndex]); pixelDrawn = true; // Once a pixel has been drawn, no need to check other BG's break; } if(pixelDrawn) { break; } } // If nothing is drawn then default to backdrop colour if (pixelDrawn == false) { drawBuffer.SetPixel(x, scanline, Palettes.Palette0[0]); } } #if ParallelizeScanline }); // Parallel.For //while (result.IsCompleted == false) ; #endif }