GBA.Unity/Assets/emulator/PpuRenderer.cs

1707 lines
60 KiB
C#

using System;
using System.IO;
using System.Runtime.CompilerServices;
using static MyStruct;
using Avx2 = MyStruct;
using static OptimeGBA.Bits;
using static OptimeGBA.CoreUtil;
using static OptimeGBA.MemoryUtil;
namespace OptimeGBA
{
public sealed unsafe class PpuRenderer
{
public int Width;
public int Height;
public Nds Nds;
public PpuRenderer(Nds nds, int width, int height)
{
Width = width;
Height = height;
Nds = nds;
Backgrounds = new Background[4] {
new Background(Nds != null, 0),
new Background(Nds != null, 1),
new Background(Nds != null, 2),
new Background(Nds != null, 3),
};
Array.Fill(DebugEnableBg, true);
int ScreenBufferSize = Width * Height;
#if UNSAFE
ScreenFront = MemoryUtil.AllocateUnmanagedArray16(ScreenBufferSize);
ScreenBack = MemoryUtil.AllocateUnmanagedArray16(ScreenBufferSize);
// 16 wide to allow tiles to poke out on each side for efficiency
WinMasks = MemoryUtil.AllocateUnmanagedArray(Width + 16);
BgLo = MemoryUtil.AllocateUnmanagedArray32(width + 16);
BgHi = MemoryUtil.AllocateUnmanagedArray32(width + 16);
#else
ScreenFront = new ushort[ScreenBufferSize];
ScreenBack = new ushort[ScreenBufferSize];
WinMasks = new byte[width + 16];
BgLo = new uint[width + 16];
BgHi = new uint[width + 16];
#endif
ObjBuffer = new ObjPixel[Width];
ObjWindowBuffer = new byte[Width];
for (uint i = 0; i < ScreenBufferSize; i++)
{
ScreenFront[i] = 0x7FFF;
ScreenBack[i] = 0x7FFF;
}
if (nds == null)
{
DisplayMode = 1;
}
// Load 3D placeholder
// Why do I waste time on useless crap like this
Stream img = typeof(PpuRenderer).Assembly.GetManifestResourceStream("OptimeGBA-OpenTK.resources.3d-placeholder.raw");
if (img == null)
{
img = typeof(PpuRenderer).Assembly.GetManifestResourceStream("OptimeGBA-SDL.resources.3d-placeholder.raw");
}
PlaceholderFor3D = new ushort[img.Length / 2];
int val = 0;
int index = 0;
while (val != -1)
{
val = img.ReadByte();
byte r = (byte)val;
val = img.ReadByte();
byte g = (byte)val;
val = img.ReadByte();
byte b = (byte)val;
val = img.ReadByte();
byte a = (byte)val;
// Crush it to RGB555
r >>= 3;
g >>= 3;
b >>= 3;
PlaceholderFor3D[index++] = (ushort)((b << 10) | (g << 5) | r);
}
for (uint i = 0; i < Width; i++)
WinMasks[i + 8] = 0b111111;
}
// RGB555
public static ushort[] PlaceholderFor3D;
// Internal State
public const int BYTES_PER_PIXEL = 4;
public bool DebugForce3DLayer = false;
public bool RenderingDone = false;
// RGB, 24-bit
#if UNSAFE
public ushort* ScreenFront;
public ushort* ScreenBack;
public byte* WinMasks;
public uint* BgLo;
public uint* BgHi;
~PpuRenderer()
{
MemoryUtil.FreeUnmanagedArray(ScreenFront);
MemoryUtil.FreeUnmanagedArray(ScreenBack);
MemoryUtil.FreeUnmanagedArray(WinMasks);
MemoryUtil.FreeUnmanagedArray(BgLo);
MemoryUtil.FreeUnmanagedArray(BgHi);
}
#else
public ushort[] ScreenFront;
public ushort[] ScreenBack;
public byte[] WinMasks;
// Bytes 0-1: Color
// Byte 2: Flag
// Byte 3: Priority
public uint[] BgLo;
public uint[] BgHi;
#endif
public byte[] Palettes = new byte[1024];
public byte[] Oam = new byte[1024];
public ObjPixel[] ObjBuffer;
public byte[] ObjWindowBuffer;
public uint TotalFrames;
const uint CoarseBlockSize = 65536;
const uint CharBlockSize = 16384;
const uint MapBlockSize = 2048;
const uint MapBlockSizeAffineNds = 16384;
public bool[] DebugEnableBg = new bool[4];
public bool DebugEnableObj = true;
public bool DebugEnableRendering = true;
public static uint[] ColorLut = GenerateRgb555To888Lut(false);
public static uint[] ColorLutCorrected = GenerateRgb555To888Lut(true);
public static uint[] GenerateRgb555To888Lut(bool colorCorrection)
{
uint[] lut = new uint[32768];
for (uint i = 0; i < 32768; i++)
{
lut[i] = Rgb555To888(i, colorCorrection);
}
return lut;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Rgb555To888(uint data, bool colorCorrection)
{
byte r = (byte)((data >> 0) & 0b11111);
byte g = (byte)((data >> 5) & 0b11111);
byte b = (byte)((data >> 10) & 0b11111);
if (colorCorrection)
{
// byuu color correction, customized for my tastes
double ppuGamma = 4.0, outGamma = 3.0;
double lb = Math.Pow(b / 31.0, ppuGamma);
double lg = Math.Pow(g / 31.0, ppuGamma);
double lr = Math.Pow(r / 31.0, ppuGamma);
byte fr = (byte)(Math.Pow((0 * lb + 10 * lg + 245 * lr) / 255, 1 / outGamma) * 0xFF);
byte fg = (byte)(Math.Pow((20 * lb + 230 * lg + 5 * lr) / 255, 1 / outGamma) * 0xFF);
byte fb = (byte)(Math.Pow((230 * lb + 5 * lg + 20 * lr) / 255, 1 / outGamma) * 0xFF);
return (uint)((0xFF << 24) | (fb << 16) | (fg << 8) | (fr << 0));
}
else
{
byte fr = (byte)((255 / 31) * r);
byte fg = (byte)((255 / 31) * g);
byte fb = (byte)((255 / 31) * b);
return (uint)((0xFF << 24) | (fb << 16) | (fg << 8) | (fr << 0));
}
}
// BGCNT
public Background[] Backgrounds;
// DISPCNT
public uint BgMode;
public bool CgbMode;
public bool DisplayFrameSelect;
public bool HBlankIntervalFree;
public bool ObjCharOneDimensional;
public bool ForcedBlank;
public bool[] ScreenDisplayBg = new bool[4];
public bool ScreenDisplayObj;
public bool Window0DisplayFlag;
public bool Window1DisplayFlag;
public bool ObjWindowDisplayFlag;
public bool AnyWindowEnabled = false;
// DISPCNT - NDS Exclusive
public bool Bg0Is3D;
public bool BitmapObjShape;
public bool BitmapObjMapping;
public uint DisplayMode;
public uint LcdcVramBlock;
public uint TileObj1DBoundary;
public bool BitmapObj1DBoundary;
public uint CharBaseBlockCoarse;
public uint MapBaseBlockCoarse;
public bool BgExtendedPalettes;
public bool ObjExtendedPalettes;
// WIN0H
public byte Win0HRight;
public byte Win0HLeft;
// WIN1H
public byte Win1HRight;
public byte Win1HLeft;
// WIN0V
public byte Win0VBottom;
public byte Win0VTop;
// WIN1V
public byte Win1VBottom;
public byte Win1VTop;
// WININ
public byte Win0InEnable;
public byte Win1InEnable;
// WINOUT
public byte WinOutEnable;
public byte WinObjEnable;
// BLDCNT
public BlendEffect BlendEffect = 0;
public uint Target1Flags;
public uint Target2Flags;
// BLDALPHA
public uint BlendACoeff;
public uint BlendBCoeff;
// BLDY
public uint BlendBrightness;
// MOSAIC
public uint BgMosaicX;
public uint BgMosaicY;
public uint ObjMosaicX;
public uint ObjMosaicY;
public uint BgMosaicYCounter;
public uint ObjMosaicYCounter;
// Raw register values
public ushort WININValue;
public ushort WINOUTValue;
public ushort BLDCNTValue;
public uint BLDALPHAValue;
public void RenderScanlineGba(uint vcount, byte[] vramArr)
{
if (!ForcedBlank)
{
fixed (byte* vram = vramArr)
{
if (BgMode <= 2)
{
PrepareBackgroundAndWindow(vcount);
}
switch (BgMode)
{
case 0:
case 1:
case 2:
RenderBgModes(vcount, vram);
break;
case 3:
RenderMode3(vcount, vram);
break;
case 4:
RenderMode4(vcount, vram);
break;
}
if (BgMode <= 2)
{
Composite(vcount);
if (DebugEnableObj && ScreenDisplayObj && vcount != 159) RenderObjs(vcount + 1, vram);
}
}
}
else
{
RenderWhiteScanline(vcount);
}
}
public void RenderScanlineNds(uint vcount, byte[] bgVramArr, byte[] objVramArr)
{
if (!ForcedBlank)
{
fixed (byte* bgVram = bgVramArr, objVram = objVramArr)
{
switch (DisplayMode)
{
case 1: // Regular rendering
PrepareBackgroundAndWindow(vcount);
RenderBgModes(vcount, bgVram);
if (DebugForce3DLayer) {
uint srcBase = (uint)(vcount * Width);
for (uint i = 0; i < Width; i++)
{
BgLo[i + 8] = BgHi[i + 8];
BgHi[i + 8] = (uint)(Nds.Ppu3D.Screen[srcBase + i]);
}
}
Composite(vcount);
if (DebugEnableObj && ScreenDisplayObj && vcount != 191) RenderObjs(vcount + 1, objVram);
break;
case 2: // LCDC Mode
RenderMode3(vcount, bgVram);
break;
}
}
}
else
{
RenderWhiteScanline(vcount);
}
}
public void RunVblankOperations()
{
Backgrounds[2].CopyAffineParams();
Backgrounds[3].CopyAffineParams();
BgMosaicYCounter = BgMosaicY;
ObjMosaicYCounter = ObjMosaicY;
#if OPENTK_DEBUGGER
PrepareBackground();
#endif
}
public void IncrementMosaicCounters()
{
if (++BgMosaicYCounter > BgMosaicY)
{
BgMosaicYCounter = 0;
}
if (++ObjMosaicYCounter > ObjMosaicY)
{
ObjMosaicYCounter = 0;
}
}
public void SwapBuffers()
{
var temp = ScreenBack;
ScreenBack = ScreenFront;
ScreenFront = temp;
}
public int[] BgList = new int[4];
public Background[] BgRefList = new Background[4];
public uint BgCount = 0;
public bool BackgroundSettingsDirty = true;
public void PrepareBackgroundAndWindow(uint vcount)
{
if (BackgroundSettingsDirty)
{
PrepareBackground();
}
bool win0InsideY = (byte)(vcount - Win0VTop) < (byte)(Win0VBottom - Win0VTop) && Window0DisplayFlag;
bool win1InsideY = (byte)(vcount - Win1VTop) < (byte)(Win1VBottom - Win1VTop) && Window1DisplayFlag;
byte win0ThresholdX = (byte)(Win0HRight - Win0HLeft);
byte win1ThresholdX = (byte)(Win1HRight - Win1HLeft);
if (!win0InsideY) win0ThresholdX = 0;
if (!win1InsideY) win1ThresholdX = 0;
byte win0HPos = (byte)(-Win0HLeft);
byte win1HPos = (byte)(-Win1HLeft);
// Erase with priority 4, backdrop flag, and color 0;
uint eraseColor = (uint)((4 << 24) | ((byte)BlendFlag.Backdrop << 16) | LookupPalette(0));
if (AnyWindowEnabled)
{
for (uint i = 0; i < Width; i++)
{
byte val = WinOutEnable;
if (win0HPos < win0ThresholdX)
{
val = Win0InEnable;
}
else if (win1HPos < win1ThresholdX)
{
val = Win1InEnable;
}
else if (ObjWindowBuffer[i] != 0)
{
val = WinObjEnable;
}
win0HPos++;
win1HPos++;
WinMasks[i + 8] = val;
// Also prepare backgrounds arrays in this loop
BgHi[i + 8] = eraseColor;
}
}
else
{
for (uint i = 0; i < Width; i++)
{
WinMasks[i + 8] = 0b111111;
BgHi[i + 8] = eraseColor;
}
}
}
public void PrepareBackground()
{
BgCount = 0;
for (int bg = 0; bg < 4; bg++)
{
// -1 means disabled
// Look up backgrounds in reverse order to ensure backgrounds are in correct order
// (backgrounds carry a specific render order even if they are the same priority)
int invBg = 3 - bg;
BgList[bg] = -1;
BgList[BgCount] = invBg;
if (BgIsEnabled(invBg))
{
BgCount++;
}
}
// Insertion sort backgrounds according to priority
int key;
int j;
for (int i = 1; i < BgCount; i++)
{
key = (int)Backgrounds[BgList[i]].Priority;
j = i - 1;
while (j >= 0 && Backgrounds[BgList[j]].Priority < key)
{
Swap(ref BgList[j + 1], ref BgList[j]);
j--;
}
}
// Look up references for each background
for (int i = 0; i < BgCount; i++)
{
BgRefList[i] = Backgrounds[BgList[i]];
}
Backgrounds[0].Mode = BackgroundMode.Char;
Backgrounds[1].Mode = BackgroundMode.Char;
Backgrounds[2].Mode = BackgroundMode.Char;
Backgrounds[3].Mode = BackgroundMode.Char;
if (Nds == null)
{
switch (BgMode)
{
case 1:
Backgrounds[2].Mode = BackgroundMode.Affine;
break;
case 2:
Backgrounds[2].Mode = BackgroundMode.Affine;
Backgrounds[3].Mode = BackgroundMode.Affine;
break;
}
}
else
{
if (Bg0Is3D)
{
Backgrounds[0].Mode = BackgroundMode.Display3D;
}
switch (BgMode)
{
case 1:
Backgrounds[3].Mode = BackgroundMode.Affine;
break;
case 2:
Backgrounds[2].Mode = BackgroundMode.Affine;
Backgrounds[3].Mode = BackgroundMode.Affine;
break;
case 3:
Backgrounds[3].Mode = BackgroundMode.Extended;
break;
case 4:
Backgrounds[2].Mode = BackgroundMode.Affine;
Backgrounds[3].Mode = BackgroundMode.Extended;
break;
case 5:
Backgrounds[2].Mode = BackgroundMode.Extended;
Backgrounds[3].Mode = BackgroundMode.Extended;
break;
case 6:
Backgrounds[0].Mode = BackgroundMode.Display3D;
Backgrounds[2].Mode = BackgroundMode.Large;
break;
}
}
// Extended mode backgrounds have extra options
for (int i = 2; i < 4; i++)
{
var bg = Backgrounds[i];
if (bg.Mode == BackgroundMode.Extended)
{
if (bg.AffineBitmap)
{
if (bg.AffineBitmapFullColor)
{
bg.Mode = BackgroundMode.AffineFullColorBitmap;
}
else
{
bg.Mode = BackgroundMode.Affine256ColorBitmap;
}
}
else
{
// TODO: implement affine BGs with 16-bit BG map entries
}
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ushort LookupPalette(uint index)
{
return GetUshort(Palettes, index * 2);
}
public readonly static uint[] CharBlockHeightTable = {
0, 0, // Size 0 - 256x256
0, 0, // Size 1 - 512x256
0, 1, // Size 2 - 256x512
0, 2, // Size 3 - 512x512
};
public readonly static uint[] CharBlockWidthTable = {
0, 0, // Size 0 - 256x256
0, 1, // Size 1 - 512x256
0, 0, // Size 2 - 256x512
0, 1, // Size 3 - 512x512
};
public readonly static uint[] CharWidthTable = { 256, 512, 256, 512 };
public readonly static uint[] CharHeightTable = { 256, 256, 512, 512 };
public void RenderCharBackground(uint vcount, byte* vram, Background bg)
{
bool enableMosaicX = bg.EnableMosaic && BgMosaicX != 0;
fixed (byte* palettes = Palettes)
{
#if UNSAFE
if (enableMosaicX)
{
_RenderCharBackground(vcount, vram, palettes, WinMasks, BgHi, BgLo, bg, true);
}
else
{
_RenderCharBackground(vcount, vram, palettes, WinMasks, BgHi, BgLo, bg, false);
}
#else
fixed (byte* winMasks = WinMasks)
{
fixed (uint* hi = BgHi, lo = BgLo)
{
if (enableMosaicX)
{
_RenderCharBackground(vcount, vram, palettes, winMasks, hi, lo, bg, true);
}
else
{
_RenderCharBackground(vcount, vram, palettes, winMasks, hi, lo, bg, false);
}
}
}
#endif
}
}
//[MethodImpl(MethodImplOptions.AggressiveOptimization | MethodImplOptions.AggressiveInlining)]
//private void _RenderCharBackground(
// uint vcount, byte* vram,
// byte* palettes,
// byte* winMasks,
// uint* hi, uint* lo,
// Background bg, bool mosaicX
// )
//{
// uint charBase = bg.CharBaseBlock * CharBlockSize + CharBaseBlockCoarse * CoarseBlockSize;
// uint mapBase = bg.MapBaseBlock * MapBlockSize + MapBaseBlockCoarse * CoarseBlockSize;
// uint pixelY = bg.VerticalOffset + vcount;
// if (bg.EnableMosaic)
// {
// pixelY -= BgMosaicYCounter;
// }
// uint pixelYWrapped = pixelY & 255;
// uint screenSizeBase = bg.ScreenSize * 2;
// uint verticalOffsetBlocks = CharBlockHeightTable[screenSizeBase + ((pixelY & 511) >> 8)];
// uint mapVertOffset = MapBlockSize * verticalOffsetBlocks;
// uint tileY = pixelYWrapped >> 3;
// uint intraTileY = pixelYWrapped & 7;
// uint pixelX = bg.HorizontalOffset;
// uint intraTileX = bg.HorizontalOffset & 7;
// uint lineIndex = 8 - intraTileX;
// uint tilesToRender = (uint)(Width / 8);
// if (lineIndex < 8) tilesToRender++;
// uint mosaicXCounter = BgMosaicX;
// // Every byte of these vectors are filled
// Vector256<int> metaVec = Vector256.Create((bg.Priority << 8) | (1 << bg.Id));
// for (uint tile = 0; tile < tilesToRender; tile++)
// {
// uint pixelXWrapped = pixelX & 255;
// // 2 bytes per tile
// uint tileX = pixelXWrapped >> 3;
// uint horizontalOffsetBlocks = CharBlockWidthTable[screenSizeBase + ((pixelX & 511) >> 8)];
// uint mapHoriOffset = MapBlockSize * horizontalOffsetBlocks;
// uint mapEntryIndex = mapBase + mapVertOffset + mapHoriOffset + tileY * 64 + tileX * 2;
// uint mapEntry = GetUshort(vram, mapEntryIndex);
// uint tileNumber = mapEntry & 1023; // 10 bits
// bool xFlip = BitTest(mapEntry, 10);
// bool yFlip = BitTest(mapEntry, 11);
// uint effectiveIntraTileY = intraTileY;
// if (yFlip)
// {
// effectiveIntraTileY ^= 7;
// }
// Vector256<uint> clearMaskVec;
// Vector256<uint> indicesVec = Vector256<uint>.Zero;
// uint paletteRow = 0;
// if (bg.Use8BitColor)
// {
// clearMaskVec = Vector256.Create(0xFFU);
// uint vramTileAddr = charBase + tileNumber * 64 + effectiveIntraTileY * 8;
// ulong data = GetUlong(vram, vramTileAddr);
// if (data != 0)
// {
// indicesVec = Avx2.ConvertToVector256Int32((byte*)&data).AsUInt32();
// if (xFlip)
// {
// // First, reverse within 128-bit lanes
// indicesVec = Avx2.Shuffle(indicesVec, 0b00_01_10_11);
// // Then, swap upper and lower halves
// indicesVec = Avx2.Permute2x128(indicesVec, indicesVec, 1);
// }
// indicesVec = Avx2.And(indicesVec, clearMaskVec);
// }
// else
// {
// pixelX += 8;
// lineIndex += 8;
// continue;
// }
// }
// else
// {
// clearMaskVec = Vector256.Create(0xFU);
// paletteRow = (mapEntry >> 12) & 0xF;
// uint vramTileAddr = charBase + tileNumber * 32 + effectiveIntraTileY * 4;
// uint data = GetUint(vram, vramTileAddr);
// if (data != 0)
// {
// Vector256<uint> shifts;
// if (xFlip)
// {
// shifts = Vector256.Create(28U, 24U, 20U, 16U, 12U, 8U, 4U, 0U);
// }
// else
// {
// shifts = Vector256.Create(0U, 4U, 8U, 12U, 16U, 20U, 24U, 28U);
// }
// indicesVec = Vector256.Create(data);
// indicesVec = Avx2.ShiftRightLogicalVariable(indicesVec, shifts);
// indicesVec = Avx2.And(indicesVec, clearMaskVec);
// }
// else
// {
// pixelX += 8;
// lineIndex += 8;
// continue;
// }
// }
// Vector256<int> color = Avx2.GatherVector256((int*)((ushort*)palettes + paletteRow * 16), indicesVec.AsInt32(), sizeof(ushort));
// color = Avx2.And(color, Vector256.Create(0xFFFF));
// // Weave metadata (priority, ID) into color data
// color = Avx2.Or(color, Avx2.ShiftLeftLogical(metaVec, 16));
// Vector256<int> winMask = Avx2.ConvertToVector256Int32((byte*)(winMasks + lineIndex));
// winMask = Avx2.And(winMask, metaVec);
// winMask = Avx2.CompareEqual(winMask, Vector256<int>.Zero);
// // Get important color bits
// Vector256<int> clear = Avx2.And(indicesVec, clearMaskVec).AsInt32();
// // Are those bits clear?
// clear = Avx2.CompareEqual(clear, Vector256<int>.Zero);
// // Merge with window mask
// winMask = Avx2.Or(winMask, clear);
// winMask = Avx2.Xor(winMask, Vector256.Create(0xFFFFFFFF).AsInt32());
// // Push back covered pixels from hi to lo
// Avx2.MaskStore((int*)(lo + lineIndex), winMask, Avx2.LoadVector256((int*)(hi + lineIndex)));
// Avx2.MaskStore((int*)(hi + lineIndex), winMask, color);
// pixelX += 8;
// lineIndex += 8;
// }
//}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void _RenderCharBackground(
uint vcount, byte* vram,
byte* palettes,
byte* winMasks,
uint* hi, uint* lo,
Background bg, bool mosaicX
)
{
uint charBase = bg.CharBaseBlock * CharBlockSize + CharBaseBlockCoarse * CoarseBlockSize;
uint mapBase = bg.MapBaseBlock * MapBlockSize + MapBaseBlockCoarse * CoarseBlockSize;
uint pixelY = bg.VerticalOffset + vcount;
if (bg.EnableMosaic)
{
pixelY -= BgMosaicYCounter;
}
uint pixelYWrapped = pixelY & 255;
uint screenSizeBase = bg.ScreenSize * 2;
uint verticalOffsetBlocks = CharBlockHeightTable[screenSizeBase + ((pixelY & 511) >> 8)];
uint mapVertOffset = MapBlockSize * verticalOffsetBlocks;
uint tileY = pixelYWrapped >> 3;
uint intraTileY = pixelYWrapped & 7;
uint pixelX = bg.HorizontalOffset;
uint intraTileX = bg.HorizontalOffset & 7;
uint lineIndex = 8 - intraTileX;
uint tilesToRender = (uint)(Width / 8);
if (lineIndex < 8) tilesToRender++;
uint mosaicXCounter = BgMosaicX;
// Every byte of these vectors are filled
Vector256<int> metaVec = Vector256<int>.Create((bg.Priority << 8) | (1 << bg.Id));
for (uint tile = 0; tile < tilesToRender; tile++)
{
uint pixelXWrapped = pixelX & 255;
// 2 bytes per tile
uint tileX = pixelXWrapped >> 3;
uint horizontalOffsetBlocks = CharBlockWidthTable[screenSizeBase + ((pixelX & 511) >> 8)];
uint mapHoriOffset = MapBlockSize * horizontalOffsetBlocks;
uint mapEntryIndex = mapBase + mapVertOffset + mapHoriOffset + tileY * 64 + tileX * 2;
uint mapEntry = GetUshort(vram, mapEntryIndex);
uint tileNumber = mapEntry & 1023; // 10 bits
bool xFlip = BitTest(mapEntry, 10);
bool yFlip = BitTest(mapEntry, 11);
uint effectiveIntraTileY = intraTileY;
if (yFlip)
{
effectiveIntraTileY ^= 7;
}
Vector256<uint> clearMaskVec;
Vector256<uint> indicesVec = Vector256<uint>.Zero;
uint paletteRow = 0;
if (bg.Use8BitColor)
{
clearMaskVec = Vector256<uint>.Create(0xFFU);
uint vramTileAddr = charBase + tileNumber * 64 + effectiveIntraTileY * 8;
ulong data = GetUlong(vram, vramTileAddr);
if (data != 0)
{
//indicesVec = Avx2.ConvertToVector256Int32((byte*)&data).AsUInt32();
indicesVec = Avx2.ConvertToVector256Int32(data).AsUInt32();
if (xFlip)
{
// First, reverse within 128-bit lanes
indicesVec = Avx2.Shuffle(indicesVec, 0b00_01_10_11);
// Then, swap upper and lower halves
indicesVec = Avx2.Permute2x128(indicesVec, indicesVec, 1);
}
indicesVec = Avx2.And(indicesVec, clearMaskVec);
}
else
{
pixelX += 8;
lineIndex += 8;
continue;
}
}
else
{
clearMaskVec = Vector256<uint>.Create(0xFU);
paletteRow = (mapEntry >> 12) & 0xF;
uint vramTileAddr = charBase + tileNumber * 32 + effectiveIntraTileY * 4;
uint data = GetUint(vram, vramTileAddr);
if (data != 0)
{
Vector256<uint> shifts;
if (xFlip)
{
shifts = Vector256<uint>.Create(28U, 24U, 20U, 16U, 12U, 8U, 4U, 0U);
}
else
{
shifts = Vector256<uint>.Create(0U, 4U, 8U, 12U, 16U, 20U, 24U, 28U);
}
indicesVec = Vector256<uint>.Create(data);
indicesVec = Avx2.ShiftRightLogicalVariable(indicesVec, shifts);
indicesVec = Avx2.And(indicesVec, clearMaskVec);
}
else
{
pixelX += 8;
lineIndex += 8;
continue;
}
}
Vector256<int> color = Avx2.GatherVector256((int*)((ushort*)palettes + paletteRow * 16), indicesVec.AsInt32(), sizeof(ushort));
color = Avx2.And(color, Vector256<int>.Create(0xFFFF));
// Weave metadata (priority, ID) into color data
color = Avx2.Or(color, Avx2.ShiftLeftLogical(metaVec, 16));
Vector256<int> winMask = Avx2.ConvertToVector256Int32((byte*)(winMasks + lineIndex));
winMask = Avx2.And(winMask, metaVec);
winMask = Avx2.CompareEqual(winMask, Vector256<int>.Zero);
// Get important color bits
Vector256<int> clear = Avx2.And(indicesVec, clearMaskVec).AsInt32();
// Are those bits clear?
clear = Avx2.CompareEqual(clear, Vector256<int>.Zero);
// Merge with window mask
winMask = Avx2.Or(winMask, clear);
winMask = Avx2.Xor(winMask, Vector256<int>.Create(0xFFFFFFFF).AsInt32());
// Push back covered pixels from hi to lo
Avx2.MaskStore((int*)(lo + lineIndex), winMask, Avx2.LoadVector256((int*)(hi + lineIndex)));
Avx2.MaskStore((int*)(hi + lineIndex), winMask, color);
pixelX += 8;
lineIndex += 8;
}
}
public readonly static int[] AffineSizeShiftTable = { 7, 8, 9, 10 };
public readonly static uint[] AffineSizeTable = { 128, 256, 512, 1024 };
public readonly static uint[] AffineTileSizeTable = { 16, 32, 64, 128 };
public readonly static uint[] AffineSizeMask = { 127, 255, 511, 1023 };
public void RenderAffineBackground(uint vcount, byte* vram, Background bg)
{
uint charBase = bg.CharBaseBlock * CharBlockSize;
uint mapBase = bg.MapBaseBlock * MapBlockSize;
ushort meta = bg.GetMeta();
int posX = bg.AffinePosX;
int posY = bg.AffinePosY;
uint size = AffineSizeTable[bg.ScreenSize];
uint sizeMask = AffineSizeMask[bg.ScreenSize];
uint tileSize = AffineTileSizeTable[bg.ScreenSize];
for (uint p = 0; p < Width; p++)
{
uint pixelX = (uint)((posX >> 8) & 0x7FFFF);
uint pixelY = (uint)((posY >> 8) & 0x7FFFF);
posX += bg.AffineA;
posY += bg.AffineC;
if (!bg.OverflowWrap && (pixelX >= size || pixelY >= size))
{
continue;
}
pixelX &= sizeMask;
pixelY &= sizeMask;
uint tileX = pixelX >> 3;
uint intraTileX = pixelX & 7;
uint tileY = pixelY >> 3;
uint intraTileY = pixelY & 7;
// 1 byte per tile
uint mapEntryIndex = mapBase + (tileY * tileSize) + (tileX * 1);
uint tileNumber = vram[mapEntryIndex];
// Always 256color
// 256 color, 64 bytes per tile, 8 bytes per row
uint vramAddr = charBase + (tileNumber * 64) + (intraTileY * 8) + (intraTileX / 1);
byte vramValue = vram[vramAddr];
if (vramValue != 0)
{
PlaceBgPixel(p + 8, LookupPalette(vramValue), meta);
}
}
bg.AffinePosX += bg.AffineB;
bg.AffinePosY += bg.AffineD;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PlaceBgPixel(uint lineIndex, ushort color, ushort meta)
{
if ((WinMasks[lineIndex] & meta) != 0)
{
BgLo[lineIndex] = BgHi[lineIndex];
BgHi[lineIndex] = (uint)(color | ((uint)meta << 16));
}
}
public readonly static uint[] ObjSizeTable = {
// Square
8, 16, 32, 64,
8, 16, 32, 64,
// Rectangular 1
16, 32, 32, 64,
8, 8, 16, 32,
// Rectangular 2
8, 8, 16, 32,
16, 32, 32, 64,
// Invalid
0, 0, 0, 0,
0, 0, 0, 0,
};
public void RenderObjs(uint vcount, byte* vram)
{
// OAM address for the last sprite
uint oamBase = 0;
for (int s = 0; s < 128; s++, oamBase += 8)
{
uint attr0 = (uint)(Oam[oamBase + 1] << 8 | Oam[oamBase + 0]);
uint attr1 = (uint)(Oam[oamBase + 3] << 8 | Oam[oamBase + 2]);
uint attr2 = (uint)(Oam[oamBase + 5] << 8 | Oam[oamBase + 4]);
uint yPos = attr0 & 255;
bool affine = BitTest(attr0, 8);
ObjMode mode = (ObjMode)((attr0 >> 10) & 0b11);
bool mosaic = BitTest(attr0, 12);
bool use8BitColor = BitTest(attr0, 13);
ObjShape shape = (ObjShape)((attr0 >> 14) & 0b11);
uint xPos = attr1 & 511;
bool xFlip = BitTest(attr1, 12) && !affine;
bool yFlip = BitTest(attr1, 13) && !affine;
uint objSize = (attr1 >> 14) & 0b11;
uint tileNumber = attr2 & 1023;
uint palette = (attr2 >> 12) & 15;
uint xSize = ObjSizeTable[((int)shape * 8) + 0 + objSize];
uint ySize = ObjSizeTable[((int)shape * 8) + 4 + objSize];
int yEnd = ((int)yPos + (int)ySize) & 255;
uint screenLineBase = xPos;
bool disabled = BitTest(attr0, 9);
byte priority = (byte)((attr2 >> 10) & 0b11);
bool render = false;
if (!disabled && !affine)
{
if ((vcount >= yPos && vcount < yEnd) || (yEnd < yPos && vcount < yEnd))
{
render = true;
}
}
else if (affine)
{
if (disabled)
{
yEnd += (int)ySize;
}
if ((vcount >= yPos && vcount < yEnd) || (yEnd < yPos && vcount < yEnd))
{
render = true;
}
}
if ((byte)mode == 3 || (byte)shape == 3) render = false;
if (!render) continue;
// y relative to the object itself
int objPixelY = (int)(vcount - yPos) & 255;
if (yFlip)
{
objPixelY = (int)ySize - objPixelY - 1;
}
// Tile numbers are halved in 256-color mode
if (use8BitColor) tileNumber >>= 1;
if (!affine)
{
for (uint x = 0; x < xSize; x++)
{
if (screenLineBase < Width)
{
int objPixelX = (int)x;
if (xFlip)
{
objPixelX = (int)(xSize - objPixelX - 1);
}
RenderObjPixel(vram, objPixelX, objPixelY, tileNumber, xSize, use8BitColor, screenLineBase, palette, priority, mode);
}
screenLineBase = (screenLineBase + 1) % 512;
}
}
else
{
uint renderXSize = xSize;
bool doubleSize = BitTest(attr0, 9);
if (doubleSize)
{
renderXSize *= 2;
}
uint parameterId = (attr1 >> 9) & 0b11111;
uint pBase = parameterId * 32;
short pA = (short)GetUshort(Oam, pBase + 6);
short pB = (short)GetUshort(Oam, pBase + 14);
short pC = (short)GetUshort(Oam, pBase + 22);
short pD = (short)GetUshort(Oam, pBase + 30);
uint xofs;
uint yofs;
int xfofs;
int yfofs;
if (!doubleSize)
{
xofs = xSize / 2;
yofs = ySize / 2;
xfofs = 0;
yfofs = 0;
}
else
{
xofs = xSize;
yofs = ySize;
xfofs = -(int)xofs / 2;
yfofs = -(int)yofs / 2;
}
// Left edge
int origXEdge0 = (int)(0 - xofs);
int origY = (int)(objPixelY - yofs);
// Calculate starting parameters for matrix multiplications
int shiftedXOfs = (int)(xofs + xfofs << 8);
int shiftedYOfs = (int)(yofs + yfofs << 8);
int pBYOffset = pB * origY + shiftedXOfs;
int pDYOffset = pD * origY + shiftedYOfs;
int objPixelXEdge0 = (int)(pA * origXEdge0 + pBYOffset);
int objPixelYEdge0 = (int)(pC * origXEdge0 + pDYOffset);
for (int x = 0; x < renderXSize; x++)
{
if (screenLineBase < Width)
{
uint lerpedObjPixelX = (uint)(objPixelXEdge0 >> 8);
uint lerpedObjPixelY = (uint)(objPixelYEdge0 >> 8);
if (lerpedObjPixelX < xSize && lerpedObjPixelY < ySize)
{
RenderObjPixel(vram, (int)lerpedObjPixelX, (int)lerpedObjPixelY, tileNumber, xSize, use8BitColor, screenLineBase, palette, priority, mode);
}
}
objPixelXEdge0 += pA;
objPixelYEdge0 += pC;
screenLineBase = (screenLineBase + 1) % 512;
}
}
}
}
public readonly ushort[] NdsCharObjBoundary = new ushort[] { 32, 64, 128, 256 };
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RenderObjPixel(byte* vram, int objX, int objY, uint tile, uint width, bool use8BitColor, uint x, uint palette, byte priority, ObjMode mode)
{
uint intraTileX = (uint)(objX & 7);
uint intraTileY = (uint)(objY & 7);
uint tileX = (uint)(objX / 8);
uint tileY = (uint)(objY / 8);
uint charBase = Nds != null ? 0U : 0x10000U;
tile <<= (int)TileObj1DBoundary;
uint effectiveTileNumber = (uint)(tile + tileX);
if (ObjCharOneDimensional)
{
effectiveTileNumber += tileY * (width / 8);
}
else
{
if (use8BitColor)
{
effectiveTileNumber += 16 * tileY;
}
else
{
effectiveTileNumber += 32 * tileY;
}
}
if (use8BitColor)
{
// 256 color, 64 bytes per tile, 8 bytes per row
uint vramAddr = charBase + (effectiveTileNumber * 64) + (intraTileY * 8) + (intraTileX / 1);
uint vramValue = vram[vramAddr];
byte finalColor = (byte)vramValue;
if (finalColor != 0)
{
PlaceObjPixel(x, LookupPalette(finalColor), finalColor, priority, mode);
}
}
else
{
// 16 color, 32 bytes per tile, 4 bytes per row
uint vramAddr = charBase + (effectiveTileNumber * 32) + (intraTileY * 4) + (intraTileX / 2);
uint vramValue = vram[vramAddr];
// Lower 4 bits is left pixel, upper 4 bits is right pixel
uint color = (vramValue >> (int)((intraTileX & 1) * 4)) & 0xF;
byte finalColor = (byte)(palette * 16 + color);
if (color != 0)
{
PlaceObjPixel(x, LookupPalette(finalColor), finalColor, priority, mode);
}
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void PlaceObjPixel(uint x, ushort color, byte paletteIndex, byte priority, ObjMode mode)
{
switch (mode)
{
case ObjMode.Normal:
if (priority < ObjBuffer[x].Priority)
{
ObjBuffer[x] = new ObjPixel(color, paletteIndex, priority, mode);
}
break;
case ObjMode.Translucent:
if (priority < ObjBuffer[x].Priority)
{
ObjBuffer[x] = new ObjPixel(color, paletteIndex, priority, mode);
}
ObjBuffer[x].Priority = priority;
break;
default:
if (ObjWindowDisplayFlag)
{
ObjWindowBuffer[x] = 1;
}
break;
}
}
public void Composite(uint vcount)
{
uint screenBase = (uint)(vcount * Width);
for (int i = 0; i < Width; i++)
{
uint winMask = WinMasks[i + 8];
ObjPixel objPixel = ObjBuffer[i];
uint hi = BgHi[i + 8];
uint lo = BgLo[i + 8];
ushort hiColor = (ushort)hi;
ushort loColor = (ushort)lo;
BlendFlag hiPixelFlag = (BlendFlag)((byte)(hi >> 16));
BlendFlag loPixelFlag = (BlendFlag)((byte)(lo >> 16));
uint objPaletteIndex = objPixel.PaletteIndex + 256U;
uint effectiveTarget1Flags = Target1Flags;
BlendEffect effectiveBlendEffect = BlendEffect;
if (objPaletteIndex != 256 && (winMask & (uint)WindowFlag.Obj) != 0)
{
byte hiPrio = (byte)(hi >> 24);
byte loPrio = (byte)(lo >> 24);
if (objPixel.Priority <= hiPrio)
{
loColor = hiColor;
loPixelFlag = hiPixelFlag;
hiColor = LookupPalette(objPaletteIndex);
hiPixelFlag = BlendFlag.Obj;
}
else if (objPixel.Priority <= loPrio)
{
loColor = LookupPalette(objPaletteIndex);
loPixelFlag = BlendFlag.Obj;
}
if (objPixel.Mode == ObjMode.Translucent)
{
effectiveTarget1Flags |= (uint)BlendFlag.Obj;
effectiveBlendEffect = BlendEffect.Blend;
winMask |= (uint)WindowFlag.ColorMath;
}
}
if (
effectiveBlendEffect != BlendEffect.None &&
(effectiveTarget1Flags & (uint)hiPixelFlag) != 0 &&
(winMask & (uint)WindowFlag.ColorMath) != 0
)
{
byte r1 = (byte)((hiColor >> 0) & 0x1F);
byte g1 = (byte)((hiColor >> 5) & 0x1F);
byte b1 = (byte)((hiColor >> 10) & 0x1F);
byte fr = r1;
byte fg = g1;
byte fb = b1;
switch (BlendEffect)
{
case BlendEffect.Blend:
if ((Target2Flags & (uint)loPixelFlag) != 0)
{
byte r2 = (byte)((loColor >> 0) & 0x1F);
byte g2 = (byte)((loColor >> 5) & 0x1F);
byte b2 = (byte)((loColor >> 10) & 0x1F);
fr = (byte)((Math.Min(511U, r1 * BlendACoeff + r2 * BlendBCoeff) >> 4) & 0x1FU);
fg = (byte)((Math.Min(511U, g1 * BlendACoeff + g2 * BlendBCoeff) >> 4) & 0x1FU);
fb = (byte)((Math.Min(511U, b1 * BlendACoeff + b2 * BlendBCoeff) >> 4) & 0x1FU);
}
break;
case BlendEffect.Lighten:
fr = (byte)((r1 + (((31 - r1) * BlendBrightness) >> 4)) & 0x1FU);
fg = (byte)((g1 + (((31 - g1) * BlendBrightness) >> 4)) & 0x1FU);
fb = (byte)((b1 + (((31 - b1) * BlendBrightness) >> 4)) & 0x1FU);
break;
case BlendEffect.Darken:
fr = (byte)((r1 - ((r1 * BlendBrightness) >> 4)) & 0x1FU);
fg = (byte)((g1 - ((g1 * BlendBrightness) >> 4)) & 0x1FU);
fb = (byte)((b1 - ((b1 * BlendBrightness) >> 4)) & 0x1FU);
break;
}
ScreenBack[screenBase++] = (ushort)((fb << 10) | (fg << 5) | fr);
}
else
{
ScreenBack[screenBase++] = hiColor;
}
// It's the frontend's responsibility to convert rgb555 to rgb888
// Use this loop as an opportunity to clear the sprite buffer
ObjBuffer[i].Color = 0;
ObjBuffer[i].PaletteIndex = 0;
ObjBuffer[i].Priority = 4;
ObjWindowBuffer[i] = 0;
}
}
public bool BgIsEnabled(int id)
{
if (Nds == null)
{
switch (BgMode)
{
case 1:
if (id == 3) return false;
break;
case 2:
if (id == 0) return false;
if (id == 1) return false;
break;
}
}
else
{
if (BgMode == 6 && (id == 0 || id == 2)) return false;
}
return ScreenDisplayBg[id] && DebugEnableBg[id];
}
public void RenderBgModes(uint vcount, byte* vram)
{
for (uint i = 0; i < BgCount; i++)
{
var bg = BgRefList[i];
switch (bg.Mode)
{
case BackgroundMode.Char:
RenderCharBackground(vcount, vram, bg);
break;
case BackgroundMode.Affine:
RenderAffineBackground(vcount, vram, bg);
break;
case BackgroundMode.AffineFullColorBitmap:
RenderAffineBitmapBackground(vcount, vram, bg, true);
break;
case BackgroundMode.Affine256ColorBitmap:
RenderAffineBitmapBackground(vcount, vram, bg, false);
break;
case BackgroundMode.Display3D:
Render3DBackground(vcount, vram, bg);
break;
}
}
}
public void Render3DBackground(uint vcount, byte* vram, Background bg)
{
uint srcBase = (uint)(vcount * Width);
ushort meta = bg.GetMeta();
for (uint i = 0; i < Width; i++)
{
if (Nds.Ppu3D.Screen[srcBase + i] != 0)
PlaceBgPixel(i + 8, Nds.Ppu3D.Screen[srcBase + i], meta);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RenderAffineBitmapBackground(uint vcount, byte* vram, Background bg, bool fullColor)
{
// TODO: actually implement rendering affine here
// TODO: implement 256-color mode (no fullColor)
uint screenBase = (uint)(vcount * Width);
uint vramBase = (uint)(vcount * Width * 2) + bg.MapBaseBlock * MapBlockSizeAffineNds;
ushort meta = bg.GetMeta();
for (uint p = 0; p < Width; p++)
{
ushort data = GetUshort(vram, vramBase);
PlaceBgPixel(p + 8, data, meta);
screenBase++;
vramBase += 2;
}
}
public void RenderMode4(uint vcount, byte* vram)
{
uint screenBase = (uint)(vcount * Width);
uint vramBase = (uint)(0x0 + vcount * Width);
for (uint p = 0; p < Width; p++)
{
uint vramVal = vram[vramBase];
ScreenBack[screenBase] = LookupPalette(vramVal);
vramBase++;
screenBase++;
}
}
public void RenderMode3(uint vcount, byte* vram)
{
uint screenBase = (uint)(vcount * Width);
uint vramBase = (uint)(vcount * Width * 2) + LcdcVramBlock * 131072;
for (uint p = 0; p < Width; p++)
{
byte b0 = vram[vramBase + 0];
byte b1 = vram[vramBase + 1];
ushort data = (ushort)((b1 << 8) | b0);
ScreenBack[screenBase] = data;
screenBase++;
vramBase += 2;
}
}
public void RenderWhiteScanline(uint vcount)
{
// Render white
uint screenBase = (uint)(vcount * Width);
for (uint p = 0; p < Width; p++)
{
ScreenBack[screenBase] = 0x7FFF;
screenBase++;
}
}
public byte ReadHwio8(uint addr)
{
switch (addr)
{
case 0x08: // BG0CNT B0
case 0x09: // BG0CNT B1
case 0x0A: // BG1CNT B0
case 0x0B: // BG1CNT B1
case 0x0C: // BG2CNT B0
case 0x0D: // BG2CNT B1
case 0x0E: // BG3CNT B0
case 0x0F: // BG3CNT B1
return Backgrounds[(addr >> 1) & 3].ReadBGCNT(addr & 1);
case 0x48: // WININ B0
return (byte)((WININValue >> 0) & 0x3F);
case 0x49: // WININ B1
return (byte)((WININValue >> 8) & 0x3F);
case 0x4A: // WINOUT B0
return (byte)((WINOUTValue >> 0) & 0x3F);
case 0x4B: // WINOUT B1
return (byte)((WINOUTValue >> 8) & 0x3F);
case 0x50: // BLDCNT B0
return (byte)((BLDCNTValue >> 0) & 0xFF);
case 0x51: // BLDCNT B1
return (byte)((BLDCNTValue >> 8) & 0x3F);
case 0x52: // BLDALPHA B0
return (byte)(BLDALPHAValue >> 0);
case 0x53: // BLDALPHA B1
return (byte)(BLDALPHAValue >> 8);
}
return 0;
}
public void WriteHwio8(uint addr, byte val)
{
switch (addr)
{
case 0x08: // BG0CNT B0
case 0x09: // BG0CNT B1
case 0x0A: // BG1CNT B0
case 0x0B: // BG1CNT B1
case 0x0C: // BG2CNT B0
case 0x0D: // BG2CNT B1
case 0x0E: // BG3CNT B0
case 0x0F: // BG3CNT B1
Backgrounds[(addr >> 1) & 3].WriteBGCNT(addr & 1, val);
BackgroundSettingsDirty = true;
break;
case 0x10: // BG0HOFS B0
case 0x11: // BG0HOFS B1
case 0x12: // BG0VOFS B0
case 0x13: // BG0VOFS B1
Backgrounds[0].WriteBGOFS(addr & 3, val);
break;
case 0x14: // BG1HOFS B0
case 0x15: // BG1HOFS B1
case 0x16: // BG1VOFS B0
case 0x17: // BG1VOFS B1
Backgrounds[1].WriteBGOFS(addr & 3, val);
break;
case 0x18: // BG2HOFS B0
case 0x19: // BG2HOFS B1
case 0x1A: // BG2VOFS B0
case 0x1B: // BG2VOFS B1
Backgrounds[2].WriteBGOFS(addr & 3, val);
break;
case 0x1C: // BG3HOFS B0
case 0x1D: // BG3HOFS B1
case 0x1E: // BG3VOFS B0
case 0x1F: // BG3VOFS B1
Backgrounds[3].WriteBGOFS(addr & 3, val);
break;
case 0x20: // BG2PA B0
case 0x21: // BG2PA B1
case 0x22: // BG2PB B0
case 0x23: // BG2PB B1
case 0x24: // BG2PC B0
case 0x25: // BG2PC B1
case 0x26: // BG2PD B0
case 0x27: // BG2PD B1
Backgrounds[2].WriteBGPX(addr & 7, val);
break;
case 0x28: // BG2X B0
case 0x29: // BG2X B1
case 0x2A: // BG2X B2
case 0x2B: // BG2X B3
case 0x2C: // BG2Y B0
case 0x2D: // BG2Y B1
case 0x2E: // BG2Y B2
case 0x2F: // BG2Y B3
Backgrounds[2].WriteBGXY(addr & 7, val);
break;
case 0x30: // BG3PA B0
case 0x31: // BG3PA B1
case 0x32: // BG3PB B0
case 0x33: // BG3PB B1
case 0x34: // BG3PC B0
case 0x35: // BG3PC B1
case 0x36: // BG3PD B0
case 0x37: // BG3PD B1
Backgrounds[3].WriteBGPX(addr & 7, val);
break;
case 0x38: // BG3X B0
case 0x39: // BG3X B1
case 0x3A: // BG3X B2
case 0x3B: // BG3X B3
case 0x3C: // BG3Y B0
case 0x3D: // BG3Y B1
case 0x3E: // BG3Y B2
case 0x3F: // BG3Y B3
Backgrounds[3].WriteBGXY(addr & 7, val);
break;
case 0x40: // WIN0H B0
Win0HRight = val;
break;
case 0x41: // WIN0H B1
Win0HLeft = val;
break;
case 0x42: // WIN1H B0
Win1HRight = val;
break;
case 0x43: // WIN1H B1
Win1HLeft = val;
break;
case 0x44: // WIN0V B0
Win0VBottom = val;
break;
case 0x45: // WIN0V B1
Win0VTop = val;
break;
case 0x46: // WIN1V B0
Win1VBottom = val;
break;
case 0x47: // WIN1V B1
Win1VTop = val;
break;
case 0x48: // WININ B0
Win0InEnable = (byte)(val & 0b111111U);
WININValue &= 0x7F00;
WININValue |= (ushort)(val << 0);
break;
case 0x49: // WININ B1
Win1InEnable = (byte)(val & 0b111111U);
WININValue &= 0x007F;
WININValue |= (ushort)(val << 8);
break;
case 0x4A: // WINOUT B0
WinOutEnable = (byte)(val & 0b111111U);
WINOUTValue &= 0x7F00;
WINOUTValue |= (ushort)(val << 0);
break;
case 0x4B: // WINOUT B1
WinObjEnable = (byte)(val & 0b111111U);
WINOUTValue &= 0x007F;
WINOUTValue |= (ushort)(val << 8);
break;
case 0x4C: // MOSAIC B0
BgMosaicX = (byte)((val >> 0) & 0xF);
BgMosaicY = (byte)((val >> 4) & 0xF);
break;
case 0x4D: // MOSAIC B1
ObjMosaicX = (byte)((val >> 0) & 0xF);
ObjMosaicY = (byte)((val >> 4) & 0xF);
break;
case 0x50: // BLDCNT B0
Target1Flags = val & 0b111111U;
BlendEffect = (BlendEffect)((val >> 6) & 0b11U);
BLDCNTValue &= 0x7F00;
BLDCNTValue |= (ushort)(val << 0);
break;
case 0x51: // BLDCNT B1
Target2Flags = val & 0b111111U;
BLDCNTValue &= 0x00FF;
BLDCNTValue |= (ushort)(val << 8);
break;
case 0x52: // BLDALPHA B0
BlendACoeff = val & 0b11111U;
BLDALPHAValue &= 0x7F00;
BLDALPHAValue |= (ushort)(val << 0);
break;
case 0x53: // BLDALPHA B1
BlendBCoeff = val & 0b11111U;
BLDALPHAValue &= 0x00FF;
BLDALPHAValue |= (ushort)(val << 8);
break;
case 0x54: // BLDY
BlendBrightness = (byte)(val & 0b11111);
break;
}
}
}
}