This commit is contained in:
CasualPokePlayer 2023-09-25 05:25:39 -07:00
parent 3d5c2120f2
commit 07126b1e48
6 changed files with 143 additions and 297 deletions

View File

@ -593,18 +593,6 @@ namespace BizHawk.Client.Common
videoProvider.BufferWidth, videoProvider.BufferHeight, out virtualWidth, out virtualHeight);
}
// TODO: it is bad that this is happening outside the filter chain
// the filter chain has the ability to add padding...
// for now, we have to have some hacks. this could be improved by refactoring the filter setup hacks to be in one place only though
// could the PADDING be done as filters too? that would be nice.
var fCoreScreenControl = CreateCoreScreenControl();
if (fCoreScreenControl != null)
{
var sz = fCoreScreenControl.PresizeInput("default", new(bufferWidth, bufferHeight));
virtualWidth = bufferWidth = sz.Width;
virtualHeight = bufferHeight = sz.Height;
}
var padding = CalculateCompleteContentPaddingSum(true, false);
virtualWidth += padding.Horizontal;
virtualHeight += padding.Vertical;
@ -768,24 +756,10 @@ namespace BizHawk.Client.Common
var bufferWidth = videoProvider.BufferWidth;
var bufferHeight = videoProvider.BufferHeight;
var presenterTextureWidth = bufferWidth;
var presenterTextureHeight = bufferHeight;
var vw = videoProvider.VirtualWidth;
var vh = videoProvider.VirtualHeight;
// TODO: it is bad that this is happening outside the filter chain
// the filter chain has the ability to add padding...
// for now, we have to have some hacks. this could be improved by refactoring the filter setup hacks to be in one place only though
// could the PADDING be done as filters too? that would be nice.
var fCoreScreenControl = CreateCoreScreenControl();
if(fCoreScreenControl != null)
{
var sz = fCoreScreenControl.PresizeInput("default", new(bufferWidth, bufferHeight));
presenterTextureWidth = vw = sz.Width;
presenterTextureHeight = vh = sz.Height;
}
if (GlobalConfig.DispFixAspectRatio)
{
switch (GlobalConfig.DispManagerAR)
@ -862,7 +836,7 @@ namespace BizHawk.Client.Common
if (fPresent != null)
{
fPresent.VirtualTextureSize = new(vw, vh);
fPresent.TextureSize = new(presenterTextureWidth, presenterTextureHeight);
fPresent.TextureSize = new(bufferWidth, bufferHeight);
fPresent.BackgroundColor = videoProvider.BackgroundColor;
fPresent.Config_FixAspectRatio = GlobalConfig.DispFixAspectRatio;
fPresent.Config_FixScaleInteger = GlobalConfig.DispFixScaleInteger;

View File

@ -182,268 +182,25 @@ namespace BizHawk.Client.Common.Filters
{
private readonly NDS _nds;
// TODO: actually use this
#if false
private bool Nop = false;
#endif
// matrices used for transforming screens
private Matrix4x4 matTop, matBot;
private Matrix4x4 matTopInvert, matBotInvert;
// final output area size
private Size outputSize;
private static float Round(float f)
=> (float)Math.Round(f);
public ScreenControlNDS(NDS nds)
{
_nds = nds;
}
public override void Initialize()
{
//we're going to be blitting the source as pieces to a new render target, so we need our input to be a texture
DeclareInput(SurfaceDisposition.Texture);
}
private void CrunchNumbers()
{
MatrixStack top = new(), bot = new();
// set up transforms for each screen based on screen control values
// this will be TRICKY depending on how many features we have, but once it's done, everything should be easy
var settings = _nds.GetSettings();
switch (settings.ScreenLayout)
{
//gap only applies to vertical, I guess
case NDS.ScreenLayoutKind.Vertical:
bot.Translate(0, 192);
bot.Translate(0, settings.ScreenGap);
break;
case NDS.ScreenLayoutKind.Horizontal:
bot.Translate(256, 0);
break;
case NDS.ScreenLayoutKind.Top:
case NDS.ScreenLayoutKind.Bottom:
// do nothing here, we'll discard the other screen
break;
default:
throw new InvalidOperationException();
}
// this doesn't make any sense, it's likely to be too much for a monitor to gracefully handle too
if (settings.ScreenLayout != NDS.ScreenLayoutKind.Horizontal)
{
var rot = settings.ScreenRotation switch
{
NDS.ScreenRotationKind.Rotate90 => 90,
NDS.ScreenRotationKind.Rotate180 => 180,
NDS.ScreenRotationKind.Rotate270 => 270,
_ => 0
};
top.RotateZ(rot);
bot.RotateZ(rot);
}
// TODO: refactor some of the below into a class that doesn't require having top and bottom replica code
matTop = top.Top;
matBot = bot.Top;
// apply transforms from standard input screen positions to output screen positions
var top_TL = Vector2.Transform(new(0, 0), matTop);
var top_TR = Vector2.Transform(new(256, 0), matTop);
var top_BL = Vector2.Transform(new(0, 192), matTop);
var top_BR = Vector2.Transform(new(256, 192), matTop);
var bot_TL = Vector2.Transform(new(0, 0), matBot);
var bot_TR = Vector2.Transform(new(256, 0), matBot);
var bot_BL = Vector2.Transform(new(0, 192), matBot);
var bot_BR = Vector2.Transform(new(256, 192), matBot);
// in case of math errors in the transforms, we'll round this stuff.. although...
// we're gonna use matrix transforms for drawing later, so it isn't extremely helpful
// TODO - need more consideration of numerical precision here, because the typical case should be rock solid
top_TL.X = Round(top_TL.X); top_TL.Y = Round(top_TL.Y);
top_TR.X = Round(top_TR.X); top_TR.Y = Round(top_TR.Y);
top_BL.X = Round(top_BL.X); top_BL.Y = Round(top_BL.Y);
top_BR.X = Round(top_BR.X); top_BR.Y = Round(top_BR.Y);
bot_TL.X = Round(bot_TL.X); bot_TL.Y = Round(bot_TL.Y);
bot_TR.X = Round(bot_TR.X); bot_TR.Y = Round(bot_TR.Y);
bot_BL.X = Round(bot_BL.X); bot_BL.Y = Round(bot_BL.Y);
bot_BR.X = Round(bot_BR.X); bot_BR.Y = Round(bot_BR.Y);
#if false
// precalculate some useful metrics
top_width = (int)(top_TR.X - top_TL.X);
top_height = (int)(top_BR.Y - top_TR.Y);
bot_width = (int)(bot_TR.X - bot_TL.X);
bot_height = (int)(bot_BR.Y - bot_TR.Y);
#endif
// the size can now be determined in a kind of fluffily magical way by transforming edges and checking the bounds
float fxmin = 100000, fymin = 100000, fxmax = -100000, fymax = -100000;
if (settings.ScreenLayout != NDS.ScreenLayoutKind.Bottom)
{
fxmin = Math.Min(Math.Min(Math.Min(Math.Min(top_TL.X, top_TR.X), top_BL.X), top_BR.X), fxmin);
fymin = Math.Min(Math.Min(Math.Min(Math.Min(top_TL.Y, top_TR.Y), top_BL.Y), top_BR.Y), fymin);
fxmax = Math.Max(Math.Max(Math.Max(Math.Max(top_TL.X, top_TR.X), top_BL.X), top_BR.X), fxmax);
fymax = Math.Max(Math.Max(Math.Max(Math.Max(top_TL.Y, top_TR.Y), top_BL.Y), top_BR.Y), fymax);
}
if (settings.ScreenLayout != NDS.ScreenLayoutKind.Top)
{
fxmin = Math.Min(Math.Min(Math.Min(Math.Min(bot_TL.X, bot_TR.X), bot_BL.X), bot_BR.X), fxmin);
fymin = Math.Min(Math.Min(Math.Min(Math.Min(bot_TL.Y, bot_TR.Y), bot_BL.Y), bot_BR.Y), fymin);
fxmax = Math.Max(Math.Max(Math.Max(Math.Max(bot_TL.X, bot_TR.X), bot_BL.X), bot_BR.X), fxmax);
fymax = Math.Max(Math.Max(Math.Max(Math.Max(bot_TL.Y, bot_TR.Y), bot_BL.Y), bot_BR.Y), fymax);
}
// relocate whatever we got back into the viewable area
top.Translate(-fxmin, -fymin);
bot.Translate(-fxmin, -fymin);
matTop = top.Top;
matBot = bot.Top;
// do some more rounding
unsafe
{
fixed (Matrix4x4* matTopP = &matTop, matBotP = &matBot)
{
float* matTopF = (float*)matTopP, matBotF = (float*)matBotP;
for (var i = 0; i < 4 * 4; i++)
{
if (Math.Abs(matTopF[i]) < 0.0000001f)
{
matTopF[i] = 0;
}
if (Math.Abs(matBotF[i]) < 0.0000001f)
{
matBotF[i] = 0;
}
}
}
}
Matrix4x4.Invert(matTop, out matTopInvert);
Matrix4x4.Invert(matBot, out matBotInvert);
outputSize = new((int)(fxmax - fxmin), (int)(fymax - fymin));
}
public override Size PresizeInput(string channel, Size size)
{
CrunchNumbers();
return outputSize;
}
public override Size PresizeOutput(string channel, Size size)
{
CrunchNumbers();
return base.PresizeOutput(channel, outputSize);
}
public override void SetInputFormat(string channel, SurfaceState state)
{
CrunchNumbers();
var ss = new SurfaceState(new(outputSize), SurfaceDisposition.RenderTarget);
DeclareOutput(ss, channel);
}
public override Vector2 UntransformPoint(string channel, Vector2 point)
{
var settings = _nds.GetSettings();
var invert = settings.ScreenInvert
&& settings.ScreenLayout != NDS.ScreenLayoutKind.Top
&& settings.ScreenLayout != NDS.ScreenLayoutKind.Bottom;
point = Vector2.Transform(point, invert ? matTopInvert : matBotInvert);
// hack to accomodate input tracking system's float-point sense (based on the core's VideoBuffer height)
// actually, this is needed for a reason similar to the "TouchScreenStart" that I removed.
// So, something like that needs readding if we're to get rid of this hack.
// (should redo it as a mouse coordinate offset or something.. but the key is to pipe it to the point where this is needed.. that is where MainForm does DisplayManager.UntransformPoint()
point.Y *= 2;
// in case we're in this layout, we get confused, so fix it
if (settings.ScreenLayout == NDS.ScreenLayoutKind.Top) point = new(0.0f, 0.0f);
// TODO: we probably need more subtle logic here.
// some capability to return -1,-1 perhaps in case the cursor is nowhere.
// not sure about that
return point;
var ret = _nds.GetTouchCoords((int)point.X, (int)point.Y);
var vp = _nds.AsVideoProvider();
ret.X *= vp.BufferWidth / 255.0f;
ret.Y *= vp.BufferHeight / 191.0f;
return ret;
}
public override Vector2 TransformPoint(string channel, Vector2 point)
{
return Vector2.Transform(point, matTop);
}
public override void Run()
{
#if false
if (Nop)
{
return;
}
#endif
// TODO: this could be more efficient (draw only in gap)
FilterProgram.GL.ClearColor(Color.Black);
FilterProgram.GuiRenderer.Begin(outputSize);
FilterProgram.GuiRenderer.DisableBlending();
// TODO: may depend on input, or other factors, not sure yet
// watch out though... if we filter linear, then screens will bleed into each other.
// so we will have to break them into render targets first.
InputTexture.SetFilterNearest();
//draw screens
var renderTop = false;
var renderBottom = false;
var settings = _nds.GetSettings();
switch (settings.ScreenLayout)
{
case NDS.ScreenLayoutKind.Bottom:
renderBottom = true;
break;
case NDS.ScreenLayoutKind.Top:
renderTop = true;
break;
case NDS.ScreenLayoutKind.Vertical:
case NDS.ScreenLayoutKind.Horizontal:
renderTop = renderBottom = true;
break;
default:
throw new InvalidOperationException();
}
var invert = settings.ScreenInvert && renderTop && renderBottom;
if (renderTop)
{
FilterProgram.GuiRenderer.Modelview.Push();
FilterProgram.GuiRenderer.Modelview.PreMultiplyMatrix(invert ? matBot : matTop);
FilterProgram.GuiRenderer.DrawSubrect(InputTexture, 0, 0, 256, 192, 0.0f, 0.0f, 1.0f, 0.5f);
FilterProgram.GuiRenderer.Modelview.Pop();
}
if (renderBottom)
{
FilterProgram.GuiRenderer.Modelview.Push();
FilterProgram.GuiRenderer.Modelview.PreMultiplyMatrix(invert ? matTop : matBot);
FilterProgram.GuiRenderer.DrawSubrect(InputTexture, 0, 0, 256, 192, 0.0f, 0.5f, 1.0f, 1.0f);
FilterProgram.GuiRenderer.Modelview.Pop();
}
FilterProgram.GuiRenderer.End();
// TODO
Console.WriteLine($"TODO ScreenControlNDS TransformPoint {point.X} / {point.Y}");
return base.TransformPoint(channel, point);
}
}

View File

@ -157,15 +157,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
}
}
[UnmanagedFunctionPointer(CC)]
public delegate IntPtr RequestGLContextCallback();
[UnmanagedFunctionPointer(CC)]
public delegate void ReleaseGLContextCallback(IntPtr context);
[UnmanagedFunctionPointer(CC)]
public delegate void ActivateGLContextCallback(IntPtr context);
[UnmanagedFunctionPointer(CC)]
public delegate IntPtr GetGLProcAddressCallback(string proc);
@ -263,5 +254,45 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
[BizImport(CC)]
public abstract void ReadFrameBuffer(int[] buffer);
public enum ScreenLayout : int
{
Natural,
Vertical,
Horizontal,
// TODO? do we want this?
// Hybrid,
}
public enum ScreenRotation : int
{
Deg0,
Deg90,
Deg180,
Deg270,
}
public enum ScreenSizing : int
{
Even = 0,
TopOnly = 4,
BotOnly = 5,
}
[StructLayout(LayoutKind.Sequential)]
public struct ScreenSettings
{
public ScreenLayout ScreenLayout;
public ScreenRotation ScreenRotation;
public ScreenSizing ScreenSizing;
public int ScreenGap;
public bool ScreenSwap;
}
[BizImport(CC)]
public abstract void SetScreenSettings(ref ScreenSettings screenSettings, out int width, out int height, out int vwidth, out int vheight);
[BizImport(CC)]
public abstract void GetTouchCoords(ref int x, ref int y);
}
}

View File

@ -9,7 +9,6 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
private readonly IVideoProvider _vp;
private readonly LibMelonDS _core;
private readonly Action _activateGLContextCallback;
private readonly int[] _vbuf = new int[256 * 16 * 384 * 16];
internal bool VideoDirty;
@ -25,18 +24,19 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
public int[] GetVideoBuffer()
{
var vb = _vp.GetVideoBuffer();
if (VideoDirty)
{
_activateGLContextCallback();
_core.ReadFrameBuffer(_vbuf);
_core.ReadFrameBuffer(vb);
VideoDirty = false;
}
return _vbuf;
return vb;
}
public int VirtualWidth => 256;
public int VirtualHeight => 384;
public int VirtualWidth { get; internal set; }
public int VirtualHeight { get; internal set; }
public int BufferWidth => _vp.BufferWidth;
public int BufferHeight => _vp.BufferHeight;
public int VsyncNumerator => _vp.VsyncNumerator;

View File

@ -101,25 +101,33 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
public enum ScreenLayoutKind
{
Natural,
Vertical,
Horizontal,
[Display(Name = "Top Only")]
Top,
[Display(Name = "Bottom Only")]
Bottom,
}
public enum ScreenRotationKind
{
[Display(Name = "0°")]
Rotate0,
[Display(Name = "90°")]
Rotate90,
[Display(Name = "180°")]
Rotate180,
[Display(Name = "270°")]
Rotate270,
}
public class NDSSettings
{
[DisplayName("Screen Layout")]
[Description("Adjusts the layout of the screens")]
[DefaultValue(ScreenLayoutKind.Vertical)]
[Description("Adjusts the layout of the screens. Natural will change between Vertical and Horizontal depending on Screen Rotation")]
[DefaultValue(ScreenLayoutKind.Natural)]
[TypeConverter(typeof(DescribableEnumConverter))]
public ScreenLayoutKind ScreenLayout { get; set; }
[DisplayName("Invert Screens")]
@ -130,6 +138,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
[DisplayName("Rotation")]
[Description("Adjusts the orientation of the screens")]
[DefaultValue(ScreenRotationKind.Rotate0)]
[TypeConverter(typeof(DescribableEnumConverter))]
public ScreenRotationKind ScreenRotation { get; set; }
[JsonIgnore]
@ -147,13 +156,16 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
public enum AudioBitDepthType : int
{
Auto,
[Display(Name = "10")]
Ten,
[Display(Name = "16")]
Sixteen,
}
[DisplayName("Audio Bit Depth")]
[Description("Auto will set the audio bit depth most accurate to the console (10 for DS, 16 for DSi).")]
[DefaultValue(AudioBitDepthType.Auto)]
[TypeConverter(typeof(DescribableEnumConverter))]
public AudioBitDepthType AudioBitDepth { get; set; }
[DisplayName("Alt Lag")]
@ -445,9 +457,56 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
public NDSSyncSettings GetSyncSettings()
=> _syncSettings.Clone();
private void RefreshScreenSettings(NDSSettings settings)
{
var screenSettings = new LibMelonDS.ScreenSettings
{
ScreenLayout = settings.ScreenLayout switch
{
ScreenLayoutKind.Natural => LibMelonDS.ScreenLayout.Natural,
ScreenLayoutKind.Vertical => LibMelonDS.ScreenLayout.Vertical,
ScreenLayoutKind.Horizontal => LibMelonDS.ScreenLayout.Horizontal,
_ => LibMelonDS.ScreenLayout.Natural,
},
ScreenRotation = settings.ScreenRotation switch
{
ScreenRotationKind.Rotate0 => LibMelonDS.ScreenRotation.Deg0,
ScreenRotationKind.Rotate90 => LibMelonDS.ScreenRotation.Deg90,
ScreenRotationKind.Rotate180 => LibMelonDS.ScreenRotation.Deg180,
ScreenRotationKind.Rotate270 => LibMelonDS.ScreenRotation.Deg270,
_ => LibMelonDS.ScreenRotation.Deg0,
},
ScreenSizing = settings.ScreenLayout switch
{
ScreenLayoutKind.Top => LibMelonDS.ScreenSizing.TopOnly,
ScreenLayoutKind.Bottom => LibMelonDS.ScreenSizing.BotOnly,
_ => LibMelonDS.ScreenSizing.Even,
},
ScreenGap = Math.Max(0, Math.Min(settings.ScreenGap, 128)),
ScreenSwap = settings.ScreenInvert
};
_openGLProvider.ActivateGLContext(_glContext); // SetScreenSettings will re-present the frame, so needs OpenGL context active
_core.SetScreenSettings(ref screenSettings, out var w , out var h, out var vw, out var vh);
BufferWidth = w;
BufferHeight = h;
_glTextureProvider.VirtualWidth = vw;
_glTextureProvider.VirtualHeight = vh;
_glTextureProvider.VideoDirty = true;
}
public PutSettingsDirtyBits PutSettings(NDSSettings o)
{
var ret = NDSSettings.NeedsScreenResize(_settings, o);
// ScreenInvert changing won't need a screen resize
// but it will change the underlying image
if (_glContext != null && (ret || _settings.ScreenInvert != o.ScreenInvert))
{
RefreshScreenSettings(o);
}
_settings = o;
return ret ? PutSettingsDirtyBits.ScreenLayoutChanged : PutSettingsDirtyBits.None;
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Threading;
@ -64,12 +65,22 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
private readonly MelonDSGLTextureProvider _glTextureProvider;
private readonly IOpenGLProvider _openGLProvider;
private readonly object _glContext;
private readonly LibMelonDS.GetGLProcAddressCallback _getGLProcAddressCallback;
private object _glContext;
private IntPtr GetGLProcAddressCallback(string proc)
=> _openGLProvider.GetGLProcAddress(proc);
public Vector2 GetTouchCoords(int x, int y)
{
if (_glContext != null)
{
_core.GetTouchCoords(ref x, ref y);
}
return new(x, y);
}
[CoreConstructor(VSystemID.Raw.NDS)]
public NDS(CoreLoadParameters<NDSSettings, NDSSyncSettings> lp)
: base(lp.Comm, new()
@ -77,7 +88,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
DefaultWidth = 256,
DefaultHeight = 384,
MaxWidth = 256 * 16,
MaxHeight = 384 * 16,
MaxHeight = (384 + 128) * 16,
MaxSamples = 1024,
DefaultFpsNumerator = 33513982,
DefaultFpsDenominator = 560190,
@ -145,6 +156,18 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
}
}
if (_activeSyncSettings.ThreeDeeRenderer == NDSSyncSettings.ThreeDeeRendererType.Software)
{
if (!_openGLProvider.SupportsGLVersion(3, 1))
{
lp.Comm.Notify("OpenGL 3.1 is not supported on this machine, screen control options will not work.", null);
}
else
{
_glContext = _openGLProvider.RequestGLContext(3, 1, true, false);
}
}
_core = PreInit<LibMelonDS>(new()
{
Filename = "melonDS.wbx",
@ -242,7 +265,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
_configCallbackInterface.AllCallbacksInArray(_adapter),
_fileCallbackInterface.AllCallbacksInArray(_adapter),
_logCallback,
_getGLProcAddressCallback);
_glContext != null ? _getGLProcAddressCallback : null);
if (error != IntPtr.Zero)
{
using (_exe.EnterExit())
@ -284,6 +307,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
{
_glTextureProvider = new(this, _core, () => _openGLProvider.ActivateGLContext(_glContext));
_serviceProvider.Register<IVideoProvider>(_glTextureProvider);
RefreshScreenSettings(_settings);
}
}
@ -444,6 +468,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.NDS
if (_glContext != null)
{
_openGLProvider.ReleaseGLContext(_glContext);
_glContext = null;
}
base.Dispose();