Cores/Nds/Compute/ComputeRenderer2D.Update.cs
namespace Emulotl.Nds;

public sealed partial class ComputeRenderer2D
{
	private static readonly byte[] SpriteWidthTab =
	[
		8, 16, 8, 8,
		16, 32, 8, 8,
		32, 32, 16, 8,
		64, 64, 32, 8
	];
	private static readonly byte[] SpriteHeightTab =
	[
		8, 8, 16, 8,
		16, 8, 32, 8,
		32, 16, 32, 8,
		64, 32, 64, 8
	];

	private readonly sBGConfig[] _layerConfig = new sBGConfig[4];
	private readonly int[] _captureInfoBG = new int[32];
	private readonly int[] _captureInfoOBJ = new int[16];
	private readonly sOAM[] _spriteOAM = new sOAM[128];
	private readonly Vec4i[] _rotscale = new Vec4i[32];
	private int _numSprites;
	private readonly sScanline[] _scanlineConfig = new sScanline[ScreenH];
	private sCompositorConfig _compConfig;
	private int _windowEnable;
	private int _masterBright;
	private int _unitEnabled;
	private int _forcedBlank;
	private bool _win0Wrap;
	private bool _win1Wrap;

	private int OamU16( int u16index )
	{
		int baseByte = (_num * 0x400) + (u16index * 2);
		return _gpu.OAM[baseByte] | (_gpu.OAM[baseByte + 1] << 8);
	}

	private int OamS16( int u16index )
	{
		int v = OamU16( u16index );
		return (v << 16) >> 16;
	}

	public void UpdateScanlineConfig( uint line )
	{
		ref sScanline cfg = ref _scanlineConfig[(int)line];

		uint dispcnt = _gpu2d.DispCnt;
		uint bgmode = dispcnt & 0x7;
		bool xmosaic = _gpu2d.BGMosaicSize[0] > 0;

		if ( (dispcnt & (1 << 3)) != 0 )
		{
			int xpos = _gpu3d.RenderXPos & 0x1FF;
			cfg.BGOffsetX0 = xpos - ((xpos & 0x100) << 1);
			cfg.BGOffsetY0 = (int)line;
			cfg.BGMosaicEnable0 = 0;
		}
		else
		{
			cfg.BGOffsetX0 = _gpu2d.BGXPos[0];
			if ( (_gpu2d.BGCnt[0] & (1 << 6)) != 0 )
			{
				cfg.BGOffsetY0 = (int)(_gpu2d.BGYPos[0] + _gpu2d.BGMosaicLine);
				cfg.BGMosaicEnable0 = xmosaic ? 1 : 0;
			}
			else
			{
				cfg.BGOffsetY0 = (int)(_gpu2d.BGYPos[0] + line);
				cfg.BGMosaicEnable0 = 0;
			}
		}

		cfg.BGOffsetX1 = _gpu2d.BGXPos[1];
		if ( (_gpu2d.BGCnt[1] & (1 << 6)) != 0 )
		{
			cfg.BGOffsetY1 = (int)(_gpu2d.BGYPos[1] + _gpu2d.BGMosaicLine);
			cfg.BGMosaicEnable1 = xmosaic ? 1 : 0;
		}
		else
		{
			cfg.BGOffsetY1 = (int)(_gpu2d.BGYPos[1] + line);
			cfg.BGMosaicEnable1 = 0;
		}

		if ( (bgmode == 2) || (bgmode >= 4 && bgmode <= 6) )
		{
			cfg.BGOffsetX2 = _gpu2d.BGXRefInternal[0];
			cfg.BGOffsetY2 = _gpu2d.BGYRefInternal[0];
			cfg.BGRotscale0_0 = _gpu2d.BGRotA[0];
			cfg.BGRotscale0_1 = _gpu2d.BGRotB[0];
			cfg.BGRotscale0_2 = _gpu2d.BGRotC[0];
			cfg.BGRotscale0_3 = _gpu2d.BGRotD[0];
		}
		else
		{
			cfg.BGOffsetX2 = _gpu2d.BGXPos[2];
			if ( (_gpu2d.BGCnt[2] & (1 << 6)) != 0 )
				cfg.BGOffsetY2 = (int)(_gpu2d.BGYPos[2] + _gpu2d.BGMosaicLine);
			else
				cfg.BGOffsetY2 = (int)(_gpu2d.BGYPos[2] + line);
		}
		cfg.BGMosaicEnable2 = ((_gpu2d.BGCnt[2] & (1 << 6)) != 0) ? (xmosaic ? 1 : 0) : 0;

		if ( bgmode >= 1 && bgmode <= 5 )
		{
			cfg.BGOffsetX3 = _gpu2d.BGXRefInternal[1];
			cfg.BGOffsetY3 = _gpu2d.BGYRefInternal[1];
			cfg.BGRotscale1_0 = _gpu2d.BGRotA[1];
			cfg.BGRotscale1_1 = _gpu2d.BGRotB[1];
			cfg.BGRotscale1_2 = _gpu2d.BGRotC[1];
			cfg.BGRotscale1_3 = _gpu2d.BGRotD[1];
		}
		else
		{
			cfg.BGOffsetX3 = _gpu2d.BGXPos[3];
			if ( (_gpu2d.BGCnt[3] & (1 << 6)) != 0 )
				cfg.BGOffsetY3 = (int)(_gpu2d.BGYPos[3] + _gpu2d.BGMosaicLine);
			else
				cfg.BGOffsetY3 = (int)(_gpu2d.BGYPos[3] + line);
		}
		cfg.BGMosaicEnable3 = ((_gpu2d.BGCnt[3] & (1 << 6)) != 0) ? (xmosaic ? 1 : 0) : 0;

		int palBase = _num * 0x400;
		cfg.BackColor = _gpu.Palette[palBase] | (_gpu.Palette[palBase + 1] << 8);

		cfg.MosaicSize0 = _gpu2d.BGMosaicSize[0];
		cfg.MosaicSize1 = _gpu2d.BGMosaicSize[1];
		cfg.MosaicSize2 = _gpu2d.OBJMosaicSize[0];
		cfg.MosaicSize3 = _gpu2d.OBJMosaicSize[1];

		uint winregs;
		if ( (dispcnt & 0xE000) != 0 )
			winregs = _gpu2d.WinCnt[2];
		else
			winregs = 0xFF;

		if ( (dispcnt & (1 << 15)) != 0 )
			winregs |= (uint)_gpu2d.WinCnt[3] << 8;
		else
			winregs |= 0xFF00;

		if ( (dispcnt & (1 << 14)) != 0 )
			winregs |= (uint)_gpu2d.WinCnt[1] << 16;
		else
			winregs |= 0xFF0000;

		if ( (dispcnt & (1 << 13)) != 0 )
			winregs |= (uint)_gpu2d.WinCnt[0] << 24;
		else
			winregs |= 0xFF000000;

		cfg.WinRegs = winregs;
		cfg.WinMask = 0;

		if ( ((dispcnt & (1 << 13)) != 0) && ((_gpu2d.Win0Active & 0x1) != 0) )
		{
			int x0 = _gpu2d.Win0Coords[0];
			int x1 = _gpu2d.Win0Coords[1];
			if ( x0 <= x1 )
			{
				cfg.WinPos0 = x0;
				cfg.WinPos1 = x1;
				if ( _win0Wrap ) cfg.WinMask |= 1 << 0;
				cfg.WinMask |= 1 << 1;
				_win0Wrap = false;
			}
			else
			{
				cfg.WinPos0 = x1;
				cfg.WinPos1 = x0;
				if ( _win0Wrap ) cfg.WinMask |= 1 << 0;
				cfg.WinMask |= 1 << 2;
				_win0Wrap = true;
			}
		}
		else
		{
			cfg.WinPos0 = 256;
			cfg.WinPos1 = 256;
		}

		if ( ((dispcnt & (1 << 14)) != 0) && ((_gpu2d.Win1Active & 0x1) != 0) )
		{
			int x0 = _gpu2d.Win1Coords[0];
			int x1 = _gpu2d.Win1Coords[1];
			if ( x0 <= x1 )
			{
				cfg.WinPos2 = x0;
				cfg.WinPos3 = x1;
				if ( _win1Wrap ) cfg.WinMask |= 1 << 3;
				cfg.WinMask |= 1 << 4;
				_win1Wrap = false;
			}
			else
			{
				cfg.WinPos2 = x1;
				cfg.WinPos3 = x0;
				if ( _win1Wrap ) cfg.WinMask |= 1 << 3;
				cfg.WinMask |= 1 << 5;
				_win1Wrap = true;
			}
		}
		else
		{
			cfg.WinPos2 = 256;
			cfg.WinPos3 = 256;
		}
	}

	public void UpdateLayerConfig()
	{
		uint dispcnt = _gpu2d.DispCnt;

		if ( _num == 0 )
			_gpu.GetCaptureInfo_ABG( _captureInfoBG );
		else
			_gpu.GetCaptureInfo_BBG( _captureInfoBG );
		int capturemask = _num == 0 ? 0x1F : 0x7;

		uint tilebase, mapbase;
		if ( _num == 0 )
		{
			tilebase = ((dispcnt >> 24) & 0x7) << 16;
			mapbase = ((dispcnt >> 27) & 0x7) << 16;
		}
		else
		{
			tilebase = 0;
			mapbase = 0;
		}

		int[] layertype = [1, 1, 0, 0];
		switch ( dispcnt & 0x7 )
		{
			case 0: layertype[2] = 1; layertype[3] = 1; break;
			case 1: layertype[2] = 1; layertype[3] = 2; break;
			case 2: layertype[2] = 2; layertype[3] = 2; break;
			case 3: layertype[2] = 1; layertype[3] = 3; break;
			case 4: layertype[2] = 2; layertype[3] = 3; break;
			case 5: layertype[2] = 3; layertype[3] = 3; break;
			case 6: layertype[0] = 0; layertype[1] = 0; layertype[2] = 4; layertype[3] = 0; break;
			case 7: layertype[2] = 0; layertype[3] = 0; break;
		}

		for ( int layer = 0; layer < 4; layer++ )
		{
			_layerConfig[layer] = default;
			int type = layertype[layer];
			if ( type == 0 )
				continue;

			ushort bgcnt = _gpu2d.BGCnt[layer];
			sBGConfig cfg = default;
			cfg.CapBlock = -1;

			cfg.TileOffset = (int)tilebase + (((bgcnt >> 2) & 0xF) << 14);
			cfg.MapOffset = (int)mapbase + (((bgcnt >> 8) & 0x1F) << 11);
			cfg.PalOffset = 0;

			if ( (layer == 0) && ((dispcnt & (1 << 3)) != 0) )
			{
				cfg.SizeX = 256; cfg.SizeY = 192;
				cfg.Type = 6;
				cfg.Clamp = 1;
			}
			else if ( type == 1 )
			{
				switch ( bgcnt >> 14 )
				{
					case 0: cfg.SizeX = 256; cfg.SizeY = 256; break;
					case 1: cfg.SizeX = 512; cfg.SizeY = 256; break;
					case 2: cfg.SizeX = 256; cfg.SizeY = 512; break;
					default: cfg.SizeX = 512; cfg.SizeY = 512; break;
				}

				if ( (bgcnt & (1 << 7)) != 0 )
				{
					cfg.Type = 1;
					if ( (dispcnt & (1 << 30)) != 0 )
					{
						int paloff = layer;
						if ( (layer < 2) && ((bgcnt & (1 << 13)) != 0) )
							paloff += 2;
						cfg.PalOffset = 1 + (16 * paloff);
					}
				}
				else
				{
					cfg.Type = 0;
				}
				cfg.Clamp = 0;
			}
			else if ( type == 2 )
			{
				switch ( bgcnt >> 14 )
				{
					case 0: cfg.SizeX = 128; cfg.SizeY = 128; break;
					case 1: cfg.SizeX = 256; cfg.SizeY = 256; break;
					case 2: cfg.SizeX = 512; cfg.SizeY = 512; break;
					default: cfg.SizeX = 1024; cfg.SizeY = 1024; break;
				}
				cfg.Type = 2;
				cfg.Clamp = ((bgcnt & (1 << 13)) != 0) ? 0 : 1;
			}
			else if ( type == 3 )
			{
				if ( (bgcnt & (1 << 7)) != 0 )
				{
					switch ( bgcnt >> 14 )
					{
						case 0: cfg.SizeX = 128; cfg.SizeY = 128; break;
						case 1: cfg.SizeX = 256; cfg.SizeY = 256; break;
						case 2: cfg.SizeX = 512; cfg.SizeY = 256; break;
						default: cfg.SizeX = 512; cfg.SizeY = 512; break;
					}

					cfg.TileOffset = 0;
					cfg.MapOffset = ((bgcnt >> 8) & 0x1F) << 14;

					if ( (bgcnt & (1 << 2)) != 0 )
					{
						cfg.Type = 5;
						if ( Cap128 != null && (cfg.SizeX == 128 || cfg.SizeX == 256) )
						{
							int mapsz = cfg.SizeX * cfg.SizeY * 2;
							uint startaddr = (uint)cfg.MapOffset >> 14;
							uint endaddr = ((uint)(cfg.MapOffset + mapsz) + 0x3FFF) >> 14;
							int capblock = -1;
							for ( uint b = startaddr; b < endaddr; b++ )
							{
								int blk = _captureInfoBG[(int)(b & (uint)capturemask)];
								if ( blk != -1 ) capblock = blk;
							}
							if ( capblock != -1 )
							{
								cfg.CapBlock = capblock;
								cfg.CapSize = cfg.SizeX == 128 ? 0 : 1;
								cfg.CapYOffset = cfg.SizeX == 128 ? ((cfg.MapOffset >> 8) & 0x7F) : ((cfg.MapOffset >> 9) & 0xFF);
							}
						}
					}
					else
						cfg.Type = 4;
				}
				else
				{
					switch ( bgcnt >> 14 )
					{
						case 0: cfg.SizeX = 128; cfg.SizeY = 128; break;
						case 1: cfg.SizeX = 256; cfg.SizeY = 256; break;
						case 2: cfg.SizeX = 512; cfg.SizeY = 512; break;
						default: cfg.SizeX = 1024; cfg.SizeY = 1024; break;
					}

					cfg.Type = 3;
					if ( (dispcnt & (1 << 30)) != 0 )
					{
						int paloff = layer;
						if ( (layer < 2) && ((bgcnt & (1 << 13)) != 0) )
							paloff += 2;
						cfg.PalOffset = 1 + (16 * paloff);
					}
				}
				cfg.Clamp = ((bgcnt & (1 << 13)) != 0) ? 0 : 1;
			}
			else
			{
				switch ( bgcnt >> 14 )
				{
					case 0: cfg.SizeX = 512; cfg.SizeY = 1024; break;
					case 1: cfg.SizeX = 1024; cfg.SizeY = 512; break;
					case 2: cfg.SizeX = 512; cfg.SizeY = 256; break;
					default: cfg.SizeX = 512; cfg.SizeY = 512; break;
				}
				cfg.Type = 4;
				cfg.TileOffset = 0;
				cfg.MapOffset = 0;
				cfg.Clamp = ((bgcnt & (1 << 13)) != 0) ? 0 : 1;
			}

			_layerConfig[layer] = cfg;
		}
	}

	public void UpdateOAM()
	{
		for ( int i = 0; i < 32; i++ )
		{
			_rotscale[i].X = OamS16( (i * 16) + 3 );
			_rotscale[i].Y = OamS16( (i * 16) + 7 );
			_rotscale[i].Z = OamS16( (i * 16) + 11 );
			_rotscale[i].W = OamS16( (i * 16) + 15 );
		}

		uint dispcnt = _gpu2d.DispCnt;
		int numsprites = 0;

		if ( _num == 0 )
			_gpu.GetCaptureInfo_AOBJ( _captureInfoOBJ );
		else
			_gpu.GetCaptureInfo_BOBJ( _captureInfoOBJ );
		int objcapturemask = _num == 0 ? 0xF : 0x7;

		for ( int sprnum = 0; sprnum < 128; sprnum++ )
		{
			int attr0 = OamU16( sprnum * 4 + 0 );
			int attr1 = OamU16( sprnum * 4 + 1 );

			int sprtype = (attr0 >> 8) & 0x3;
			if ( sprtype == 2 )
				continue;

			int xpos = (attr1 << 23) >> 23;
			int ypos = (attr0 << 24) >> 24;

			int sizeparam = (attr0 >> 14) | ((attr1 & 0xC000) >> 12);
			int width = SpriteWidthTab[sizeparam];
			int height = SpriteHeightTab[sizeparam];
			int boundwidth = width;
			int boundheight = height;
			if ( sprtype == 3 )
			{
				boundwidth <<= 1;
				boundheight <<= 1;
			}

			if ( xpos <= -boundwidth )
				continue;

			bool yc0 = ((ypos + boundheight) > 0) && (ypos < 192);
			bool yc1 = (((ypos & 0xFF) + boundheight) > 0) && ((ypos & 0xFF) < 192);
			if ( !(yc0 || yc1) )
				continue;

			int attr2 = OamU16( sprnum * 4 + 2 );
			int sprmode = (attr0 >> 10) & 0x3;
			if ( sprmode == 3 )
			{
				if ( (dispcnt & 0x60) == 0x60 )
					continue;
				if ( (attr2 >> 12) == 0 )
					continue;
			}

			if ( numsprites >= 128 )
				break;

			sOAM spr = default;
			spr.CapBlock = -1;
			spr.PositionX = xpos;
			spr.PositionY = ypos;
			spr.SizeX = width;
			spr.SizeY = height;
			spr.BoundSizeX = boundwidth;
			spr.BoundSizeY = boundheight;

			if ( (sprtype & 1) != 0 )
			{
				spr.FlipX = 0;
				spr.FlipY = 0;
				spr.Rotscale = (attr1 >> 9) & 0x1F;
			}
			else
			{
				spr.FlipX = ((attr1 & (1 << 12)) != 0) ? 1 : 0;
				spr.FlipY = ((attr1 & (1 << 13)) != 0) ? 1 : 0;
				spr.Rotscale = -1;
			}

			spr.OBJMode = sprmode;
			spr.Mosaic = (((attr0 & (1 << 12)) != 0) && (sprmode != 2)) ? 1 : 0;
			spr.BGPrio = (attr2 >> 10) & 0x3;

			int tilenum = attr2 & 0x3FF;

			if ( sprmode == 3 )
			{
				spr.Type = 2;
				if ( (dispcnt & (1 << 6)) != 0 )
				{
					spr.TileOffset = tilenum << (7 + (int)((dispcnt >> 22) & 0x1));
					spr.TileStride = width * 2;
				}
				else
				{
					bool is256 = (dispcnt & (1 << 5)) != 0;
					if ( is256 )
					{
						spr.TileOffset = ((tilenum & 0x01F) << 4) + ((tilenum & 0x3E0) << 7);
						spr.TileStride = 256 * 2;
					}
					else
					{
						spr.TileOffset = ((tilenum & 0x00F) << 4) + ((tilenum & 0x3F0) << 7);
						spr.TileStride = 128 * 2;
					}

					if ( Cap128 != null )
					{
						int startaddr = spr.TileOffset >> 14;
						int endaddr = (spr.TileOffset + (height * spr.TileStride) + 0x3FFF) >> 14;
						int capblock = -1;
						for ( int b = startaddr; b < endaddr; b++ )
						{
							int blk = _captureInfoOBJ[b & objcapturemask];
							if ( blk != -1 ) capblock = blk;
						}
						if ( capblock != -1 )
						{
							spr.CapBlock = capblock;
							if ( !is256 )
							{
								spr.Type = 3;
								spr.CapSize = 0;
								spr.TileOffset &= 0x7FFF;
							}
							else
							{
								spr.Type = 4;
								spr.CapSize = 1;
								spr.TileOffset &= 0x1FFFF;
							}
						}
					}
				}
				spr.PalOffset = 1 + (attr2 >> 12);
			}
			else
			{
				if ( (dispcnt & (1 << 4)) != 0 )
				{
					spr.TileOffset = tilenum << (5 + (int)((dispcnt >> 20) & 0x3));
					spr.TileStride = (width >> 3) * 32;
					if ( (attr0 & (1 << 13)) != 0 )
						spr.TileStride <<= 1;
				}
				else
				{
					spr.TileOffset = tilenum << 5;
					spr.TileStride = 32 * 32;
				}

				if ( (attr0 & (1 << 13)) != 0 )
				{
					spr.Type = 1;
					if ( (dispcnt & (1 << 31)) != 0 )
						spr.PalOffset = 1 + (attr2 >> 12);
					else
						spr.PalOffset = 0;
				}
				else
				{
					spr.Type = 0;
					spr.PalOffset = (attr2 >> 12) << 4;
				}
			}

			_spriteOAM[numsprites] = spr;
			numsprites++;
		}

		_numSprites = numsprites;
	}

	public void UpdateCompositorConfig()
	{
		_compConfig.BGPrio0 = -1;
		_compConfig.BGPrio1 = -1;
		_compConfig.BGPrio2 = -1;
		_compConfig.BGPrio3 = -1;

		byte le = _gpu2d.LayerEnable;
		if ( (le & (1 << 0)) != 0 ) _compConfig.BGPrio0 = _gpu2d.BGCnt[0] & 0x3;
		if ( (le & (1 << 1)) != 0 ) _compConfig.BGPrio1 = _gpu2d.BGCnt[1] & 0x3;
		if ( (le & (1 << 2)) != 0 ) _compConfig.BGPrio2 = _gpu2d.BGCnt[2] & 0x3;
		if ( (le & (1 << 3)) != 0 ) _compConfig.BGPrio3 = _gpu2d.BGCnt[3] & 0x3;

		_compConfig.EnableOBJ = ((le & (1 << 4)) != 0) ? 1 : 0;
		_compConfig.Enable3D = ((_gpu2d.DispCnt & (1 << 3)) != 0) ? 1 : 0;
		_compConfig.BlendCnt = _gpu2d.BlendCnt;
		_compConfig.BlendEffect = (_gpu2d.BlendCnt >> 6) & 0x3;
		_compConfig.BlendCoef0 = _gpu2d.EVA;
		_compConfig.BlendCoef1 = _gpu2d.EVB;
		_compConfig.BlendCoef2 = _gpu2d.EVY;
	}
}