Cores/Nds/GPU.Capture.cs
using System.Numerics;

namespace Emulotl.Nds;

public sealed partial class GPU
{
	private const ushort CBFlag_IsCapture = 1 << 15;
	private const ushort CBFlag_Complete = 1 << 14;
	private const ushort CBFlag_Synced = 1 << 13;

	public ushort[] VRAMCaptureBlockFlags = new ushort[16];

	public int[] VRAMCBF_ABG = new int[0x20];
	public int[] VRAMCBF_AOBJ = new int[0x10];
	public int[] VRAMCBF_BBG = new int[0x8];
	public int[] VRAMCBF_BOBJ = new int[0x8];

	private void ResetCapture()
	{
		Array.Clear( VRAMCaptureBlockFlags );
		Array.Fill( VRAMCBF_ABG, -1 );
		Array.Fill( VRAMCBF_AOBJ, -1 );
		Array.Fill( VRAMCBF_BBG, -1 );
		Array.Fill( VRAMCBF_BOBJ, -1 );
	}

	private int GetUniqueBankCBF( uint mask, uint offset )
	{
		if ( mask == 0 || (mask & (mask - 1)) != 0 ) return -1;
		if ( (mask & 0x1F0) != 0 ) return -1;
		int num = BitOperations.TrailingZeroCount( mask );
		offset = (offset >> 1) & 0x3;
		return (num << 2) | (int)offset;
	}

	private void SetCBF( int[] cbf, uint[] map, int i )
	{
		cbf[i] = GetUniqueBankCBF( map[i], (uint)i );
	}

	private void SetRangeCBF( int[] cbf, uint[] map, int based, int n )
	{
		for ( int i = 0; i < n; i++ )
			cbf[based + i] = GetUniqueBankCBF( map[based + i], (uint)(based + i) );
	}

	private void VRAMCBFlagsSet( uint bank, uint block, ushort val )
	{
		int cb = (int)(bank << 2);
		uint len = (uint)((val >> 4) & 0x3);
		uint b = block;
		for ( uint i = 0; i < len; i++ )
		{
			VRAMCaptureBlockFlags[cb + (int)b] = val;
			b = (b + 1) & 0x3;
		}
	}

	private void VRAMCBFlagsClear( uint bank, uint block )
	{
		int cb = (int)(bank << 2);
		ushort flags = VRAMCaptureBlockFlags[cb + (int)block];
		uint start = (uint)(flags & 0x3);
		uint len = (uint)((flags >> 4) & 0x3);
		uint b = start;
		for ( uint i = 0; i < len; i++ )
		{
			VRAMCaptureBlockFlags[cb + (int)b] = 0;
			b = (b + 1) & 0x3;
		}
	}

	private void VRAMCBFlagsOr( uint bank, uint block, ushort val )
	{
		int cb = (int)(bank << 2);
		ushort flags = VRAMCaptureBlockFlags[cb + (int)block];
		uint start = (uint)(flags & 0x3);
		uint len = (uint)((flags >> 4) & 0x3);
		uint b = start;
		for ( uint i = 0; i < len; i++ )
		{
			VRAMCaptureBlockFlags[cb + (int)b] |= val;
			b = (b + 1) & 0x3;
		}
	}

	public void CheckCaptureStart()
	{
		uint dstbank = (CaptureCnt >> 16) & 0x3;
		if ( (VRAMMap_LCDC & (1u << (int)dstbank)) == 0 )
			return;

		uint dstoff = (CaptureCnt >> 18) & 0x3;
		uint size = (CaptureCnt >> 20) & 0x3;
		uint len = (size == 0) ? 1 : size;

		int cb = (int)(dstbank << 2);
		uint b = dstoff;
		for ( uint i = 0; i < len; i++ )
		{
			ushort oldflags = VRAMCaptureBlockFlags[cb + (int)b];
			b = (b + 1) & 0x3;

			if ( (oldflags & CBFlag_IsCapture) == 0 )
				continue;

			uint oldstart = (uint)(oldflags & 0x3);
			uint oldsize = (uint)((oldflags >> 6) & 0x3);
			if ( oldstart == dstoff && oldsize == size )
				continue;

			NDS.Render2DA.SyncVRAMCapture( dstbank, oldstart, oldsize, (oldflags & CBFlag_Complete) != 0 );
			VRAMCBFlagsClear( dstbank, oldstart );
		}

		ushort newval = (ushort)(CBFlag_IsCapture | dstoff | (dstbank << 2) | (len << 4) | (size << 6));
		VRAMCBFlagsSet( dstbank, dstoff, newval );
		NDS.Render2DA.AllocCapture( dstbank, dstoff, size );
	}

	public void CheckCaptureEnd()
	{
		uint dstbank = (CaptureCnt >> 16) & 0x3;
		uint dstoff = (CaptureCnt >> 18) & 0x3;
		uint size = (CaptureCnt >> 20) & 0x3;

		ushort flags = VRAMCaptureBlockFlags[(int)((dstbank << 2) | dstoff)];
		if ( (flags & CBFlag_IsCapture) == 0 )
			return;

		uint oldstart = (uint)(flags & 0x3);
		uint oldsize = (uint)((flags >> 6) & 0x3);
		if ( dstoff != oldstart || size != oldsize )
			return;

		VRAMCBFlagsOr( dstbank, dstoff, CBFlag_Complete );
	}

	public void SyncVRAMCaptureBlock( uint block, bool write )
	{
		ushort flags = VRAMCaptureBlockFlags[(int)block];
		if ( (flags & CBFlag_IsCapture) == 0 ) return;

		uint bank = block >> 2;
		uint start = (uint)(flags & 0x3);
		uint len = (uint)((flags >> 6) & 0x3);

		if ( (flags & CBFlag_Synced) != 0 )
		{
			if ( write )
				VRAMCBFlagsClear( bank, start );
			return;
		}

		NDS.Render2DA.SyncVRAMCapture( bank, start, len, (flags & CBFlag_Complete) != 0 );

		if ( write )
			VRAMCBFlagsClear( bank, start );
		else
			VRAMCBFlagsOr( bank, start, CBFlag_Synced );
	}

	public void SyncAllVRAMCaptures()
	{
		for ( uint b = 0; b < 16; b++ )
		{
			ushort flags = VRAMCaptureBlockFlags[(int)b];
			if ( (flags & CBFlag_IsCapture) == 0 )
				continue;
			if ( (flags & CBFlag_Synced) != 0 )
				continue;

			uint bank = b >> 2;
			uint start = (uint)(flags & 0x3);
			uint len = (uint)((flags >> 6) & 0x3);

			NDS.Render2DA.SyncVRAMCapture( bank, start, len, (flags & CBFlag_Complete) != 0 );
			VRAMCBFlagsClear( bank, start );
		}
	}

	public int GetCaptureBlock_LCDC( uint offset )
	{
		ushort flags = VRAMCaptureBlockFlags[(int)(offset >> 15)];
		if ( (flags & CBFlag_IsCapture) != 0 )
			return (int)(((offset >> 15) & 0xC) | (uint)(flags & 0x3));
		return -1;
	}

	private void GetCaptureInfo( int[] info, int[] cbf, int len )
	{
		for ( int b = 0; b < len; b++ )
		{
			int idx = cbf[b];
			if ( idx < 0 )
			{
				info[b] = -1;
				continue;
			}

			ushort flags = VRAMCaptureBlockFlags[idx];
			if ( (flags & CBFlag_IsCapture) != 0 )
				info[b] = flags & 0xF;
			else
				info[b] = -1;
		}
	}

	public void GetCaptureInfo_ABG( int[] info ) => GetCaptureInfo( info, VRAMCBF_ABG, 32 );
	public void GetCaptureInfo_AOBJ( int[] info ) => GetCaptureInfo( info, VRAMCBF_AOBJ, 16 );
	public void GetCaptureInfo_BBG( int[] info ) => GetCaptureInfo( info, VRAMCBF_BBG, 8 );
	public void GetCaptureInfo_BOBJ( int[] info ) => GetCaptureInfo( info, VRAMCBF_BOBJ, 8 );

	public void GetCaptureInfo_Texture( int[] info )
	{
		for ( int b = 0; b < 16; b++ )
		{
			int bank = b >> 2;
			int subblock = b & 0x3;
			uint mask = VRAMMap_Texture[bank];
			ushort cbf = 0;

			if ( mask == (1 << 0) )
				cbf = VRAMCaptureBlockFlags[(0 << 2) | subblock];
			else if ( mask == (1 << 1) )
				cbf = VRAMCaptureBlockFlags[(1 << 2) | subblock];
			else if ( mask == (1 << 2) )
				cbf = VRAMCaptureBlockFlags[(2 << 2) | subblock];
			else if ( mask == (1 << 3) )
				cbf = VRAMCaptureBlockFlags[(3 << 2) | subblock];

			if ( (cbf & CBFlag_IsCapture) != 0 )
				info[b] = cbf & 0xF;
			else
				info[b] = -1;
		}
	}
}