// 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]); } }
// Used for debug rendering BG's. Renders the source BG, does not scroll etc public void RenderScanlineAffine(int scanline, int scanlineWidth, DirectBitmap drawBuffer) { Color[] palette = gba.LcdController.Palettes.Palette0; int paletteOffset = 0; bool eightBitColour = true; // 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)); 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]); } }
bool RenderSpritePixel(int screenX, int screenY, int priority, bool windowing, int windowRegion, ref int bgVisibleOverride) { int paletteIndex; // If a sprite has the same priority as a bg, the sprite is drawn on top, therefore we check sprites first foreach (var obj in priorityObjList[priority]) { if (obj.Attributes.RotationAndScaling) { // Clip against the bounding box which can be DoubleSize. This is the only time doublesize is actually checed if (obj.BoundingBoxScreenSpace.ContainsPoint(screenX, screenY) == false) { continue; } int sourceWidth = obj.Attributes.Dimensions.Width; int sourceHeight = obj.Attributes.Dimensions.Height; // 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! OamAffineMatrix rotScaleMatrix = obj.Attributes.AffineMatrix(); // NB: Order of operations counts here! // Transform with the origin set to the centre of the sprite (that's what the - width/height /2 below is for) int originX = screenX - obj.Attributes.XPosition - (sourceWidth / 2); int originY = screenY - obj.Attributes.YPosition - (sourceHeight / 2); // Not well documented anywhere but when double size is enabled we render offset by half the original source width / height if(obj.Attributes.DoubleSize) { originX -= sourceWidth / 2; originY -= sourceHeight / 2; } int transformedX, transformedY; rotScaleMatrix.Multiply(originX , originY, out transformedX, out transformedY); // Transform back from centre of sprite transformedX += (sourceWidth / 2); transformedY += (sourceHeight / 2); paletteIndex = obj.PixelValue(transformedX, transformedY); } else { if (obj.BoundingBoxScreenSpace.ContainsPoint(screenX, screenY) == false) { continue; } //paletteIndex = obj.PixelScreenValue(x, scanline); paletteIndex = obj.PixelValue(screenX - obj.Attributes.XPositionAdjusted(), screenY - obj.Attributes.YPositionAdjusted()); } // Pal 0 == Transparent if (paletteIndex == 0) { continue; } // TODO: I *think* this will render the Obj window correctly but i cannot test it yet // This pixel belongs to a sprite in the Obj Window and Win 0 & 1 are not enclosing this pixel if (windowing && DisplayControlRegister.DisplayObjWin && obj.Attributes.Mode == ObjAttributes.ObjMode.ObjWindow && ((windowRegion & (int)TileHelpers.WindowRegion.WindowIn) == 0)) { bgVisibleOverride = Windows[(int)Window.WindowName.WindowObj].DisplayBg0 | Windows[(int)Window.WindowName.WindowObj].DisplayBg1 | Windows[(int)Window.WindowName.WindowObj].DisplayBg2 | Windows[(int)Window.WindowName.WindowObj].DisplayBg3; return false; } drawBuffer.SetPixel(screenX, screenY, Palettes.Palette1[paletteIndex]); return true; } return false; }
// DENNIS: the way I did threaded rendering was by caching the LCD IO registers, and updating them for the PPU whenever it started rendering the next scanline private void RenderScanlineTextMode() { /* if(frameNumber % 30 != 0) { return; } */ /* if (DisplayControlRegister.BgVisible(0)) Bg[0].CacheScanline(); if (DisplayControlRegister.BgVisible(1)) Bg[1].CacheScanline(); if (DisplayControlRegister.BgVisible(2)) Bg[2].CacheScanline(); if (DisplayControlRegister.BgVisible(3)) Bg[3].CacheScanline(); */ ObjController.ObjPrioritySort(); int scanline = CurrentScanline; bool windowing = (DisplayControlRegister.DisplayWin0 || DisplayControlRegister.DisplayWin1 || DisplayControlRegister.DisplayWin1 || DisplayControlRegister.DisplayObjWin); //Bg[0].WaitForScanline(); //Bg[1].WaitForScanline(); //Bg[2].WaitForScanline(); //Bg[3].WaitForScanline(); // 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 // Highest priority (0) is drawn at the front. Lower priorities can be obscured for (int priority = 0; priority < 4; priority++) { // Sprite rendering // If a sprite has the same priority as a bg, the sprite is drawn on top, therefore we check sprites first if (DisplayControlRegister.DisplayObj && (!windowing || (windowing && objVisibleOverride))) { pixelDrawn = ObjController.RenderSpritePixel(drawBuffer, x, scanline, priority, windowing, windowRegion, ref bgVisibleOverride); if (pixelDrawn) { break; } } // No Sprite occupied this pixel, move on to backgrounds // Bg Rendering // Find the backgrounds with this priority // In case of same priority, BG0 has highest (drawn at front) 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[2].AffineMode && bgSelect == 1) { continue; } */ if (Bg[bgSelect].AffineMode) { paletteIndex = Bg[bgSelect].PixelValueAffine(x, scanline); } else { paletteIndex = Bg[bgSelect].PixelValue(x, scanline); } // TODO: If we need to blend then get the pixels we need to blend here /* lock (Bg[bgSelect].ScanlineData) { paletteIndex = Bg[bgSelect].ScanlineData[x]; } */ // 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 }