Cores/Nds/NdsVideo.cs
using Sandbox.Rendering;

namespace Emulotl.Nds;

public sealed class NdsVideo : IVideoOutput
{
	public int Width => NdsConstants.ScreenWidth;
	public int Height => NdsConstants.ScreenHeight;
	public int GpuScale { get; private set; } = 1;
	public Texture OutputTexture { get; private set; }
	public CommandList RenderCommandList => null;
	public bool GpuReady { get; private set; }
	public bool RawPreview { get; set; }

	public void InitGpu( int scale )
	{
		GpuScale = Math.Max( 1, scale );
		OutputTexture = Texture.Create( Width, Height )
			.WithFormat( ImageFormat.RGBA8888 )
			.WithDynamicUsage()
			.Finish();
		OutputTexture.Update( (ReadOnlySpan<byte>)new byte[Width * Height * 4] );
		GpuReady = true;
	}

	public void DisposeGpu()
	{
		OutputTexture?.Dispose();
		OutputTexture = null;
		GpuReady = false;
	}

	private byte[] _frameBuffer;
	private bool _frameDirty;
	private readonly object _frameLock = new();

	public void SubmitFrame( uint[] framebuffer )
	{
		lock ( _frameLock )
		{
			int need = framebuffer.Length * 4;
			if ( _frameBuffer == null || _frameBuffer.Length != need )
				_frameBuffer = new byte[need];

			for ( int i = 0; i < framebuffer.Length; i++ )
			{
				uint p = framebuffer[i];
				int o = i << 2;
				_frameBuffer[o] = (byte)p;
				_frameBuffer[o + 1] = (byte)(p >> 8);
				_frameBuffer[o + 2] = (byte)(p >> 16);
				_frameBuffer[o + 3] = (byte)(p >> 24);
			}

			_frameDirty = true;
		}
	}

	public bool UploadAndBuildCommandList()
	{
		if ( OutputTexture == null )
			return false;

		lock ( _frameLock )
		{
			if ( !_frameDirty || _frameBuffer == null )
				return false;

			OutputTexture.Update( (ReadOnlySpan<byte>)_frameBuffer );
			_frameDirty = false;
		}

		return true;
	}

	public void PresentExternalFrame( ReadOnlySpan<byte> pixels )
	{
		int expected = Width * Height * 4;
		if ( OutputTexture != null && pixels.Length >= expected )
			OutputTexture.Update( pixels.Slice( 0, expected ) );
	}

	public byte[] CaptureScreenshot() => new byte[Width * Height * 4];

	public void CaptureScreenshotAsync( Action<byte[]> callback ) => callback?.Invoke( CaptureScreenshot() );

	public void ApplyDisplayOptions( in DisplayOptions options ) { }
}