Cores/Nds/Compute/ComputeRenderer2D.Snapshot.cs
using System.Threading;

namespace Emulotl.Nds;

public sealed partial class ComputeRenderer2D
{
	private sScanline[][] _snapScan;
	private uint[][] _snapVramBG;
	private uint[][] _snapVramOBJ;
	private uint[][] _snapPalBG;
	private uint[][] _snapPalOBJ;
	private sBGConfig[][] _snapLayer;
	private sOAM[][] _snapOAM;
	private Vec4i[][] _snapRot;
	private int[] _snapNumSprites;
	private sCompositorConfig[] _snapComp;
	private int[] _snapWindowEnable;
	private int[] _snapMasterBright;
	private int[] _snapUnitEnabled;
	private int[] _snapForcedBlank;
	private bool[] _snapScreenSwap;
	private int[] _snapBrightMode;
	private int[] _snapBrightFactor;
	private int[] _snapScreenOff;
	private int[] _snapDispMode;
	private int[] _snapVramCap;
	private uint[][] _snapVramDisplay;

	private bool[] _snapCapOn;
	private int[] _snapCapBank, _snapCapOffset, _snapCapWidth, _snapCapHeight;
	private int[] _snapCapMode, _snapCapEVA, _snapCapEVB, _snapCapSrcA3D, _snapCapSrcBEn, _snapCapSize;
	private int[] _snapCapFeedback, _snapCapSrcBLayer, _snapCapSrcBYOff;
	private uint[][] _snapCapSrcB;

	private int _writeSlot, _readySlot = -1, _readSlot = -1;
	private int _frameReady;
	private bool _hasFrame;

	private void CreateSnapshots()
	{
		_snapScan = new sScanline[FrameSlots][];
		_snapVramBG = new uint[FrameSlots][];
		_snapVramOBJ = new uint[FrameSlots][];
		_snapPalBG = new uint[FrameSlots][];
		_snapPalOBJ = new uint[FrameSlots][];
		_snapLayer = new sBGConfig[FrameSlots][];
		_snapOAM = new sOAM[FrameSlots][];
		_snapRot = new Vec4i[FrameSlots][];
		_snapNumSprites = new int[FrameSlots];
		_snapComp = new sCompositorConfig[FrameSlots];
		_snapWindowEnable = new int[FrameSlots];
		_snapMasterBright = new int[FrameSlots];
		_snapUnitEnabled = new int[FrameSlots];
		_snapForcedBlank = new int[FrameSlots];
		_snapScreenSwap = new bool[FrameSlots];
		_snapBrightMode = new int[FrameSlots];
		_snapBrightFactor = new int[FrameSlots];
		_snapScreenOff = new int[FrameSlots];
		_snapDispMode = new int[FrameSlots];
		_snapVramCap = new int[FrameSlots];
		_snapVramDisplay = new uint[FrameSlots][];

		_snapCapOn = new bool[FrameSlots];
		_snapCapBank = new int[FrameSlots];
		_snapCapOffset = new int[FrameSlots];
		_snapCapWidth = new int[FrameSlots];
		_snapCapHeight = new int[FrameSlots];
		_snapCapMode = new int[FrameSlots];
		_snapCapEVA = new int[FrameSlots];
		_snapCapEVB = new int[FrameSlots];
		_snapCapSrcA3D = new int[FrameSlots];
		_snapCapSrcBEn = new int[FrameSlots];
		_snapCapSize = new int[FrameSlots];
		_snapCapFeedback = new int[FrameSlots];
		_snapCapSrcBLayer = new int[FrameSlots];
		_snapCapSrcBYOff = new int[FrameSlots];
		_snapCapSrcB = new uint[FrameSlots][];

		for ( int i = 0; i < FrameSlots; i++ )
		{
			_snapCapSrcB[i] = new uint[ScreenW * ScreenH];
			_snapScan[i] = new sScanline[ScreenH];
			_snapVramBG[i] = new uint[U( _bgFlatBytes )];
			_snapVramOBJ[i] = new uint[U( _objFlatBytes )];
			_snapPalBG[i] = new uint[U( PalBGBytes )];
			_snapPalOBJ[i] = new uint[U( PalOBJBytes )];
			_snapLayer[i] = new sBGConfig[4];
			_snapOAM[i] = new sOAM[128];
			_snapRot[i] = new Vec4i[32];
			_snapVramDisplay[i] = new uint[0x20000 / 4];
		}

		_writeSlot = 0;
		_readySlot = 1;
		_readSlot = 2;
		_frameReady = 0;
		_hasFrame = false;
	}

	public void DrawScanline( uint line )
	{
		if ( !GpuReady || line >= ScreenH )
			return;

		UpdateScanlineConfig( line );
	}

	public void VBlank()
	{
		if ( !GpuReady )
			return;

		int s = _writeSlot;

		UpdateLayerConfig();
		UpdateOAM();
		UpdateCompositorConfig();

		_windowEnable = ((_gpu2d.DispCnt & (1 << 15)) != 0) ? 1 : 0;
		_masterBright = _num == 0 ? _gpu.MasterBrightnessA : _gpu.MasterBrightnessB;
		_unitEnabled = _gpu2d.Enabled ? 1 : 0;
		_forcedBlank = _gpu2d.ForcedBlank;

		Array.Copy( _scanlineConfig, _snapScan[s], ScreenH );
		Array.Copy( _layerConfig, _snapLayer[s], 4 );
		Array.Copy( _spriteOAM, _snapOAM[s], 128 );
		Array.Copy( _rotscale, _snapRot[s], 32 );
		_snapNumSprites[s] = _numSprites;
		_snapComp[s] = _compConfig;
		_snapWindowEnable[s] = _windowEnable;
		_snapMasterBright[s] = _masterBright;
		_snapUnitEnabled[s] = _unitEnabled;
		_snapForcedBlank[s] = _forcedBlank;
		_snapScreenSwap[s] = _gpu.ScreenSwap;

		int dispMode = (int)((_gpu2d.DispCnt >> 16) & 0x3);
		int screenOff;
		if ( _unitEnabled == 0 )
			screenOff = (_num == 0) ? 2 : 1;
		else if ( _forcedBlank != 0 )
			screenOff = 1;
		else if ( dispMode == 0 )
			screenOff = 1;
		else
			screenOff = 0;

		_snapBrightMode[s] = (_masterBright >> 14) & 0x3;
		_snapBrightFactor[s] = Math.Min( _masterBright & 0x1F, 16 );
		_snapScreenOff[s] = screenOff;

		_snapDispMode[s] = dispMode;
		_snapVramCap[s] = -1;
		if ( dispMode == 2 )
		{
			int blk = (int)((_gpu2d.DispCnt >> 18) & 0x3);
			if ( _num == 0 && (_gpu.VRAMMap_LCDC & (1u << blk)) != 0 )
				_snapVramCap[s] = _gpu.GetCaptureBlock_LCDC( (uint)blk << 17 );
			byte[] bank = blk == 0 ? _gpu.VRAM_A : blk == 1 ? _gpu.VRAM_B : blk == 2 ? _gpu.VRAM_C : _gpu.VRAM_D;
			Buffer.BlockCopy( bank, 0, _snapVramDisplay[s], 0, 0x20000 );
		}

		byte[] bgSrc = _num == 0 ? _gpu.VRAMFlat_ABG : _gpu.VRAMFlat_BBG;
		byte[] objSrc = _num == 0 ? _gpu.VRAMFlat_AOBJ : _gpu.VRAMFlat_BOBJ;
		Buffer.BlockCopy( bgSrc, 0, _snapVramBG[s], 0, _bgFlatBytes );
		Buffer.BlockCopy( objSrc, 0, _snapVramOBJ[s], 0, _objFlatBytes );

		BuildPalettes( s );

		if ( _num == 0 )
			DecodeCapture( s );

		_writeSlot = Interlocked.Exchange( ref _readySlot, _writeSlot );
		Interlocked.Exchange( ref _frameReady, 1 );
	}

	private void DecodeCapture( int s )
	{
		if ( !_gpu.CaptureEnable )
		{
			_snapCapOn[s] = false;
			return;
		}

		uint cap = _gpu.CaptureCnt;
		uint sz = (cap >> 20) & 0x3;
		int width, height;
		if ( sz == 0 ) { width = 128; height = 128; }
		else { width = 256; height = 64 * (int)sz; }

		_snapCapOn[s] = true;
		_snapCapWidth[s] = width;
		_snapCapHeight[s] = height;
		_snapCapSize[s] = (int)sz;
		_snapCapBank[s] = (int)((cap >> 16) & 0x3);
		_snapCapOffset[s] = (int)((cap >> 18) & 0x3);
		_snapCapMode[s] = (int)((cap >> 29) & 0x3);

		int eva = (int)(cap & 0x1F);
		int evb = (int)((cap >> 8) & 0x1F);
		if ( eva > 16 ) eva = 16;
		if ( evb > 16 ) evb = 16;
		_snapCapEVA[s] = eva;
		_snapCapEVB[s] = evb;
		_snapCapSrcA3D[s] = (int)((cap >> 24) & 0x1);

		bool srcBen = ((cap >> 25) & 0x1) == 0;
		_snapCapSrcBEn[s] = srcBen ? 1 : 0;
		_snapCapFeedback[s] = 0;
		_snapCapSrcBLayer[s] = 0;
		_snapCapSrcBYOff[s] = 0;
		if ( srcBen )
		{
			uint srcvram = (_gpu2d.DispCnt >> 18) & 0x3;
			int capblk = _gpu.GetCaptureBlock_LCDC( srcvram << 17 );
			if ( capblk >= 0 )
			{
				_snapCapFeedback[s] = 1;
				_snapCapSrcBLayer[s] = capblk >> 2;
				_snapCapSrcBYOff[s] = ((_gpu2d.DispCnt >> 16) & 0x3) != 2 ? (int)((cap >> 26) & 0x3) * 64 : 0;
			}
			else
				BuildCaptureSrcB( s, cap, width, height );
		}
	}

	private void BuildCaptureSrcB( int s, uint cap, int width, int height )
	{
		uint dispcnt = _gpu2d.DispCnt;
		uint srcvram = (dispcnt >> 18) & 0x3;
		byte[] bank = srcvram == 0 ? _gpu.VRAM_A : srcvram == 1 ? _gpu.VRAM_B : srcvram == 2 ? _gpu.VRAM_C : _gpu.VRAM_D;
		if ( bank == null )
		{
			_snapCapSrcBEn[s] = 0;
			return;
		}

		uint mask = 0x1FFFF;
		uint[] dst = _snapCapSrcB[s];
		for ( int y = 0; y < height; y++ )
		{
			uint offset = (uint)(y * 256);
			if ( ((dispcnt >> 16) & 0x3) != 2 )
				offset += ((cap >> 26) & 0x3) << 14;
			uint word = offset & 0xFFFF;
			int row = y * width;
			for ( int x = 0; x < width; x++ )
			{
				uint a = ((word + (uint)x) * 2) & mask;
				dst[row + x] = (uint)(bank[a] | (bank[a + 1] << 8));
			}
		}
	}

	private void BuildPalettes( int s )
	{
		Buffer.BlockCopy( _gpu.Palette, _num * 0x400, _snapPalBG[s], 0, 0x200 );

		byte[] bgExt = _num == 0 ? _gpu.VRAMFlat_ABGExtPal : _gpu.VRAMFlat_BBGExtPal;
		for ( int slot = 0; slot < 4; slot++ )
		{
			for ( int p = 0; p < 16; p++ )
			{
				int row = 1 + (slot * 16) + p;
				int srcOff = (slot * 0x2000) + (p * 0x200);
				Buffer.BlockCopy( bgExt, srcOff, _snapPalBG[s], row * 0x200, 0x200 );
			}
		}

		Buffer.BlockCopy( _gpu.Palette, (_num * 0x400) + 0x200, _snapPalOBJ[s], 0, 0x200 );

		byte[] objExt = _num == 0 ? _gpu.VRAMFlat_AOBJExtPal : _gpu.VRAMFlat_BOBJExtPal;
		for ( int p = 0; p < 16; p++ )
		{
			int row = 1 + p;
			int srcOff = p * 0x200;
			Buffer.BlockCopy( objExt, srcOff, _snapPalOBJ[s], row * 0x200, 0x200 );
		}
	}

	private bool AcquireReadSlot( out int slot )
	{
		if ( Interlocked.Exchange( ref _frameReady, 0 ) == 1 )
		{
			_readSlot = Interlocked.Exchange( ref _readySlot, _readSlot );
			_hasFrame = true;
		}

		slot = _readSlot;
		return _hasFrame && slot >= 0;
	}
}