// =================================================================================================== // Affine objects // =================================================================================================== private ushort GetAffineOBJPixel(uint TileID, OBJSize OBJsz, byte px, byte py, bool ColorMode, byte PaletteBank) { /* * Though this is really similar to regular objects, we cannot just do it in a straight line, so we have to calculate the address * over and over. For regular sprites / backgrounds it is faster to just do it all in a row */ uint PixelAddress = 0x10000; // OBJ vram starts at 0x10000 within VRAM // base address is the same for 4bpp and 8bpp sprites // Tonc about Sprite tile memory offsets: Always per 4bpp tile size: start = base + id * 32 PixelAddress += (uint)(TileID * 0x20); // removed shifting for less arithmetic, like in regular objects PixelAddress += (uint)(this.OAM2DMap ? (OBJsz.Width * (py >> 3) * 4) : (32 * 0x20 * (py >> 3))); if (!ColorMode) // 4bpp { PixelAddress += (uint)(4 * (py & 0x07)); PixelAddress += (uint)(0x20 * (px >> 3)); byte PaletteNibble = this.gba.mem.VRAM[PixelAddress + ((px & 0x07) >> 1)]; if ((px & 1) == 1) { PaletteNibble >>= 4; } PaletteNibble &= 0x0f; if (PaletteNibble == 0) { return(Transparent); } return(this.GetPaletteEntry(0x200 + (uint)PaletteBank * 0x20 + (uint)(2 * PaletteNibble))); } else // 8bpp { PixelAddress += (uint)(8 * (py & 0x07)); PixelAddress += (uint)(0x40 * (px >> 3)); byte VRAMEntry = this.gba.mem.VRAM[PixelAddress + (px & 0x07)]; if (VRAMEntry == 0) { return(Transparent); } return(this.GetPaletteEntry(0x200 + 2 * (uint)VRAMEntry)); } }
private void RenderAffineOBJ(short OBJy, OBJSize OBJsz, bool ColorMode, bool Mosaic, ushort OBJ_ATTR1, ushort OBJ_ATTR2, bool DoubleRendering, bool EnableBlending, bool UseOBJWindowMask = false) { int StartX = OBJ_ATTR1 & 0x01ff; if ((OBJ_ATTR1 & 0x0100) > 0) { StartX = (int)(StartX | 0xffff_ff00); // sign extend } ushort TileID = (ushort)(OBJ_ATTR2 & 0x03ff); byte Priority = (byte)((OBJ_ATTR2 & 0x0c00) >> 10); byte PaletteBank = (byte)((OBJ_ATTR2 & 0xf000) >> 12); byte AffineIndex = (byte)((OBJ_ATTR1 & 0x3e00) >> 9); ushort RotScaleIndex = (ushort)(32 * AffineIndex + 6); // PA, PB, PC, PD: short[] RotateScaleParams = new short[4]; for (int di = 0; di < 4; di++, RotScaleIndex += 8) { RotateScaleParams[di] = (short)(this.gba.mem.OAM[RotScaleIndex] | (this.gba.mem.OAM[RotScaleIndex + 1] << 8)); } uint px, py; uint px0 = (uint)(OBJsz.Width >> 1); uint py0 = (uint)(OBJsz.Height >> 1); // distance with the midpoint of the sprite short dy, dx; if (DoubleRendering) { dy = (short)(scanline - OBJy - OBJsz.Height); dx = (short)(-OBJsz.Width); } else { dy = (short)(scanline - OBJy - (OBJsz.Height >> 1)); dx = (short)-(OBJsz.Width >> 1); } // What the object width is to be interpreted as for looping over x coordinates byte FictionalOBJWidth = (byte)(DoubleRendering ? 2 * OBJsz.Width : OBJsz.Width); for (int ix = 0; ix < FictionalOBJWidth; ix++, dx++) { if ((StartX + ix < 0)) { continue; } else if ((StartX + ix) >= width) { break; } if (this.OBJLayers[Priority][StartX + ix] != Transparent) { continue; } if (!OBJWindow[StartX + ix]) { continue; } // transform px = (uint)(((RotateScaleParams[0] * dx + RotateScaleParams[1] * dy) >> 8) + px0); py = (uint)(((RotateScaleParams[2] * dx + RotateScaleParams[3] * dy) >> 8) + py0); // use actual width of sprite, even for double rendering if (px >= OBJsz.Width || py >= OBJsz.Height) { continue; } if (UseOBJWindowMask) { this.OBJWindowMask [StartX + ix] = this.GetAffineOBJPixel(TileID, OBJsz, (byte)px, (byte)py, ColorMode, PaletteBank); } else { this.OBJLayers[Priority][StartX + ix] = this.GetAffineOBJPixel(TileID, OBJsz, (byte)px, (byte)py, ColorMode, PaletteBank); // update sprite blending mode override // comparison operators are always false comparing to null if (!(Priority >= OBJMaxPriority[StartX + ix])) { if (this.OBJLayers[Priority][StartX + ix] != Transparent) { this.OBJMaxPriority [StartX + ix] = Priority; this.OBJBlendingMask[StartX + ix] = EnableBlending; } } } } }
private void RenderRegularOBJ(short OBJy, OBJSize OBJsz, bool ColorMode, bool Mosaic, ushort OBJ_ATTR1, ushort OBJ_ATTR2, bool EnableBlending, bool UseOBJWindowMask = false) { int StartX = OBJ_ATTR1 & 0x01ff; if ((OBJ_ATTR1 & 0x0100) > 0) { StartX = (int)(StartX | 0xffff_ff00); // sign extend } int XSign = 1; bool VFlip = (OBJ_ATTR1 & 0x2000) > 0; bool HFlip = (OBJ_ATTR1 & 0x1000) > 0; ushort TileID = (ushort)(OBJ_ATTR2 & 0x03ff); byte Priority = (byte)((OBJ_ATTR2 & 0x0c00) >> 10); byte PaletteBank = (byte)((OBJ_ATTR2 & 0xf000) >> 12); byte dy = (byte)(scanline - OBJy); // between 0 and OBJsz.Height (8, 16, 32, 64) if (Mosaic) { dy -= (byte)(dy % this.IO.MOSAIC.OBJMosaicVStretch); } if (VFlip) { dy = (byte)(OBJsz.Height - dy - 1); } uint SliverBaseAddress; // base address for horizontal sprite sliver if (HFlip) { XSign = -1; // tiles are also in a different order when we flip horizontally StartX += OBJsz.Width - 1; } if (!ColorMode) // ========================= 4bpp ============================= { SliverBaseAddress = (uint)(TileID * 0x20); // removed shifting for less arithmetic, logically OBJsz.Width should be OBJsz.Width >> 3 for the width in tiles, and // 4 should be 0x20. This way we wrap around with the number of tiles, but since OBJsz.Width is a power of 2, // this is ever so slightly faster, at least I think. SliverBaseAddress += (uint)(this.OAM2DMap ? (OBJsz.Width * (dy >> 3) * 4) : (32 * 0x20 * (dy >> 3))); SliverBaseAddress += (uint)(4 * (dy & 0x07)); // offset within tile // prevent overflow, not sure what is supposed to happen if (SliverBaseAddress + ((OBJsz.Width >> 3) - 1) * 0x20 > 0x8000) { SliverBaseAddress = 0; } // base address for sprites is 0x10000 in OAM SliverBaseAddress = (SliverBaseAddress & 0x7fff) | 0x10000; for (int dTileX = 0; dTileX < (OBJsz.Width >> 3); dTileX++, StartX += 8 * XSign) { // foreground palette starts at 0x0500_0200 // we can use our same rendering method as for background, as we simply render a tile if (UseOBJWindowMask) { this.Render4bpp( ref this.OBJWindowMask, null, StartX, XSign, (uint)(SliverBaseAddress + (0x20 * dTileX)), (uint)(0x200 + PaletteBank * 0x20), Mosaic, this.IO.MOSAIC.OBJMosaicHStretch); } else { this.Render4bpp( ref this.OBJLayers[Priority], this.OBJWindow, StartX, XSign, (uint)(SliverBaseAddress + (0x20 * dTileX)), (uint)(0x200 + PaletteBank * 0x20), Mosaic, this.IO.MOSAIC.OBJMosaicHStretch); // update sprite blending mode override this.UpdateOBJMask(StartX, Priority, EnableBlending); } } } else // ========================= 8bpp ============================= { // Tonc about Sprite tile memory offsets: Always per 4bpp tile size: start = base + id * 32 SliverBaseAddress = (uint)(TileID * 0x20); // removed shifting for less arithmetic, like in 4bpp SliverBaseAddress += (uint)(this.OAM2DMap ? (OBJsz.Width * (dy >> 3) * 8) : (32 * 0x20 * (dy >> 3))); SliverBaseAddress += (uint)(8 * (dy & 0x07)); // offset within tile // prevent overflow, not sure what is supposed to happen if (SliverBaseAddress + (OBJsz.Width >> 3) * 0x20 > Transparent) { SliverBaseAddress = 0; } SliverBaseAddress = (SliverBaseAddress & 0x7fff) | 0x10000; for (int dTileX = 0; dTileX < (OBJsz.Width >> 3); dTileX++, StartX += 8 * XSign) { // we can use our same rendering method as for background, as we simply render a tile if (UseOBJWindowMask) { this.Render8bpp( ref this.OBJWindowMask, null, StartX, XSign, (uint)(SliverBaseAddress + (0x40 * dTileX)), Mosaic, this.IO.MOSAIC.OBJMosaicHStretch, PaletteOffset: 0x200 ); } else { this.Render8bpp( ref this.OBJLayers[Priority], this.OBJWindow, StartX, XSign, (uint)(SliverBaseAddress + (0x40 * dTileX)), Mosaic, this.IO.MOSAIC.OBJMosaicHStretch, PaletteOffset: 0x200 ); // update sprite blending mode override this.UpdateOBJMask(StartX, Priority, EnableBlending); } } } }