raw capability for DisplayManager handling the NDS screen layouts. someone else will have to hook up the configuration in "CreateCoreScreenControl()". Gap, rotation, and layouts all supported. You will see that other configurations with varying view sizes won't be hard either.

Note that this was actually not very difficult, compared to dealing with the client sizing logic, which was teetering on the brink of total collapse. I may have messed something up while trying to support it here (mainly related to emu-space and client-space padding). There were many assumptions that the core's videoProvider would remain sensible, which this "core screen control" system subverts. The padding and sizing was added after I designed the pipeline specifically for this, so the padding and sizing is not handling it very well. Nonetheless, I think it works.
This commit is contained in:
zeromus 2020-03-29 21:49:39 -04:00
parent 9bc0363a31
commit e45943c26a
5 changed files with 336 additions and 34 deletions

View File

@ -43,6 +43,11 @@ namespace BizHawk.Client.Common
return Buttons;
}
public void AcceptNewFloat(Tuple<string, float> newValue)
{
Floats[newValue.Item1] = newValue.Item2;
}
public void AcceptNewFloats(IEnumerable<Tuple<string, float>> newValues)
{
foreach (var sv in newValues)

View File

@ -3,6 +3,7 @@
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
@ -248,6 +249,8 @@ namespace BizHawk.Client.EmuHawk
if (!includeUserFilters)
selectedChain = null;
Filters.BaseFilter fCoreScreenControl = CreateCoreScreenControl();
var fPresent = new Filters.FinalPresentation(chainOutSize);
var fInput = new Filters.SourceImage(chainInSize);
var fOSD = new Filters.OSD();
@ -276,6 +279,9 @@ namespace BizHawk.Client.EmuHawk
//add the first filter, encompassing output from the emulator core
chain.AddFilter(fInput, "input");
if (fCoreScreenControl != null)
chain.AddFilter(fCoreScreenControl, "CoreScreenControl");
// if a non-zero padding is required, add a filter to allow for that
// note, we have two sources of padding right now.. one can come from the VideoProvider and one from the user.
// we're combining these now and just using black, for sake of being lean, despite the discussion below:
@ -402,12 +408,12 @@ namespace BizHawk.Client.EmuHawk
v = _currentFilterProgram.UntransformPoint("default", v);
// Poop
if (Global.Emulator is MelonDS ds && ds.TouchScreenStart.HasValue)
{
Point touchLocation = ds.TouchScreenStart.Value;
v.Y = (int)((double)ds.BufferHeight / MelonDS.NativeHeight * (v.Y - touchLocation.Y));
v.X = (int)((double)ds.BufferWidth / MelonDS.NativeWidth * (v.X - touchLocation.X));
}
//if (Global.Emulator is MelonDS ds && ds.TouchScreenStart.HasValue)
//{
// Point touchLocation = ds.TouchScreenStart.Value;
// v.Y = (int)((double)ds.BufferHeight / MelonDS.NativeHeight * (v.Y - touchLocation.Y));
// v.X = (int)((double)ds.BufferWidth / MelonDS.NativeWidth * (v.X - touchLocation.X));
//}
return new Point((int)v.X, (int)v.Y);
}
@ -450,6 +456,18 @@ namespace BizHawk.Client.EmuHawk
UpdateSourceInternal(job);
}
Filters.BaseFilter CreateCoreScreenControl()
{
if (Global.Emulator is MelonDS nds)
{
//TODO: need to pipe layout settings into here now
var filter = new Filters.ScreenControlNDS(nds);
return filter;
}
return null;
}
public BitmapBuffer RenderVideoProvider(IVideoProvider videoProvider)
{
// TODO - we might need to gather more Global.Config.DispXXX properties here, so they can be overridden
@ -554,6 +572,18 @@ namespace BizHawk.Client.EmuHawk
FixRatio(Global.Config.DispCustomUserArx, Global.Config.DispCustomUserAry, 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 Size(bufferWidth, bufferHeight));
virtualWidth = bufferWidth = sz.Width;
virtualHeight = bufferHeight = sz.Height;
}
var padding = CalculateCompleteContentPadding(true, false);
virtualWidth += padding.Horizontal;
virtualHeight += padding.Vertical;
@ -562,6 +592,7 @@ namespace BizHawk.Client.EmuHawk
bufferWidth += padding.Horizontal;
bufferHeight += padding.Vertical;
// old stuff
var fvp = new FakeVideoProvider
{
@ -679,7 +710,7 @@ namespace BizHawk.Client.EmuHawk
return new Size(256, 192);
}
var size = filterProgram.Filters[filterProgram.Filters.Count - 1].FindOutput().SurfaceFormat.Size;
var size = filterProgram.Filters.Last().FindOutput().SurfaceFormat.Size;
return size;
}
@ -720,23 +751,41 @@ namespace BizHawk.Client.EmuHawk
//simulate = true;
int[] videoBuffer = videoProvider.GetVideoBuffer();
int bufferWidth = videoProvider.BufferWidth;
int bufferHeight = videoProvider.BufferHeight;
bool isGlTextureId = videoBuffer.Length == 1;
int vw = videoProvider.BufferWidth;
int vh = videoProvider.BufferHeight;
//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 Size(bufferWidth, bufferHeight));
vw = sz.Width;
vh = sz.Height;
}
if (Global.Config.DispFixAspectRatio)
{
if (Global.Config.DispManagerAR == EDispManagerAR.System)
{
vw = videoProvider.VirtualWidth;
vh = videoProvider.VirtualHeight;
//already set
}
if (Global.Config.DispManagerAR == EDispManagerAR.Custom)
{
//not clear what any of these other options mean for "screen controlled" systems
vw = Global.Config.DispCustomUserARWidth;
vh = Global.Config.DispCustomUserARHeight;
}
if (Global.Config.DispManagerAR == EDispManagerAR.CustomRatio)
{
//not clear what any of these other options mean for "screen controlled" systems
FixRatio(Global.Config.DispCustomUserArx, Global.Config.DispCustomUserAry, videoProvider.BufferWidth, videoProvider.BufferHeight, out vw, out vh);
}
}
@ -745,12 +794,6 @@ namespace BizHawk.Client.EmuHawk
vw += padding.Horizontal;
vh += padding.Vertical;
int[] videoBuffer = videoProvider.GetVideoBuffer();
int bufferWidth = videoProvider.BufferWidth;
int bufferHeight = videoProvider.BufferHeight;
bool isGlTextureId = videoBuffer.Length == 1;
BitmapBuffer bb = null;
Texture2d videoTexture = null;
if (!simulate)
@ -790,10 +833,17 @@ namespace BizHawk.Client.EmuHawk
Filters.SourceImage fInput = filterProgram["input"] as Filters.SourceImage;
fInput.Texture = videoTexture;
int presenterTextureWidth = bufferWidth;
int presenterTextureHeight = bufferHeight;
//ZERO 29-MAR-2020 - not sure about this
presenterTextureWidth = chainOutsize.Width;
presenterTextureHeight = chainOutsize.Height;
//setup the final presentation filter
Filters.FinalPresentation fPresent = filterProgram["presentation"] as Filters.FinalPresentation;
fPresent.VirtualTextureSize = new Size(vw, vh);
fPresent.TextureSize = new Size(bufferWidth, bufferHeight);
fPresent.TextureSize = new Size(presenterTextureWidth, presenterTextureHeight);
fPresent.BackgroundColor = videoProvider.BackgroundColor;
fPresent.GuiRenderer = Renderer;
fPresent.Flip = isGlTextureId;
@ -804,6 +854,14 @@ namespace BizHawk.Client.EmuHawk
fPresent.GL = GL;
//POOPY. why are we delivering the GL context this way? such bad
Filters.ScreenControlNDS fNDS = filterProgram["CoreScreenControl"] as Filters.ScreenControlNDS;
if (fNDS != null)
{
fNDS.GuiRenderer = Renderer;
fNDS.GL = GL;
}
filterProgram.Compile("default", chainInsize, chainOutsize, !job.Offscreen);
if (simulate)

View File

@ -72,7 +72,10 @@ namespace BizHawk.Client.EmuHawk.Filters
protected Texture2d InputTexture;
private Texture2d _outputTexture;
// setup utilities
/// <summary>
/// Indicate a 'RenderTarget' disposition if you want to draw directly to the input
/// Indicate a 'Texture' disposition if you want to use it to draw to a newly allocated render target
/// </summary>
protected IOSurfaceInfo DeclareInput(SurfaceDisposition disposition = SurfaceDisposition.Unspecified, string channel = "default")
{
return DeclareIO(SurfaceDirection.Input, channel, disposition);

View File

@ -1,6 +1,7 @@
using System;
using System.Drawing;
using BizHawk.Client.EmuHawk.FilterManager;
using BizHawk.Emulation.Cores.Consoles.Nintendo.NDS;
using BizHawk.Bizware.BizwareGL;
using OpenTK;
@ -168,6 +169,237 @@ namespace BizHawk.Client.EmuHawk.Filters
}
}
/// <summary>
/// special screen control features for NDS
/// </summary>
public class ScreenControlNDS : BaseFilter
{
public IGL GL;
public IGuiRenderer GuiRenderer;
MelonDS nds;
//TODO: actually use this
bool nop = false;
//SCREEN CONTROL VALUES
//-------------------
public int Gap = 0;
public int ScreenRotationDegreesCW = 0;
public enum ScreenLayout
{
Vertical, Horizontal, Top, Bottom
}
ScreenLayout Layout = ScreenLayout.Vertical;
//-------------------
//matrices used for transforming screens
Matrix4 matTop, matBot;
Matrix4 matTopInvert, matBotInvert;
//final output area size
Size outputSize;
static float Round(float f) { return (float)Math.Round(f); }
//TODO: put somewhere in extension methods useful for fixing deficiencies in opentk matrix types
static Vector2 Transform(Matrix4 m, Vector2 v)
{
var r = new Vector4(v.X,v.Y,0,1) * m;
return new Vector2(r.X, r.Y);
}
public ScreenControlNDS(MelonDS nds)
{
//not sure if we actually need this nds instance yet
this.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);
}
void CrunchNumbers()
{
MatrixStack top = new MatrixStack(), bot = new MatrixStack();
//-----------------------------------
//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
//gap only applies to vertical, I guess
if (Layout == ScreenLayout.Vertical)
{
bot.Translate(0, 192);
bot.Translate(0, Gap);
}
else if (Layout == ScreenLayout.Horizontal)
{
bot.Translate(256, 0);
}
else if (Layout == ScreenLayout.Top)
{
//do nothing here, we'll discard bottom screen
}
top.RotateZ(ScreenRotationDegreesCW);
bot.RotateZ(ScreenRotationDegreesCW);
//-----------------------------------
//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;
matTopInvert = matTop.Inverted();
matBotInvert = matBot.Inverted();
//apply transforms from standard input screen positions to output screen positions
Vector2 top_TL = Transform(matTop, new Vector2(0, 0));
Vector2 top_TR = Transform(matTop, new Vector2(256, 0));
Vector2 top_BL = Transform(matTop, new Vector2(0, 192));
Vector2 top_BR = Transform(matTop, new Vector2(256, 192));
Vector2 bot_TL = Transform(matBot, new Vector2(0, 0));
Vector2 bot_TR = Transform(matBot, new Vector2(256, 0));
Vector2 bot_BL = Transform(matBot, new Vector2(0, 192));
Vector2 bot_BR = Transform(matBot, new Vector2(256, 192));
//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);
////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);
//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 (Layout != ScreenLayout.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 (Layout != ScreenLayout.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;
matTopInvert = matTop.Inverted();
matBotInvert = matBot.Inverted();
outputSize = new Size((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 SurfaceFormat(outputSize), SurfaceDisposition.RenderTarget);
DeclareOutput(ss, channel);
}
public override Vector2 UntransformPoint(string channel, Vector2 point)
{
point = Transform(matBotInvert, point);
//hack to accomodate input tracking system's float-point sense (based on the core's videobuffer height)
point.Y *= 2;
//in case we're in this layout, we get confused, so fix it
if (Layout == ScreenLayout.Top)
point = Vector2.Zero;
//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;
}
public override Vector2 TransformPoint(string channel, Vector2 point)
{
//NOT TESTED, probably needs adjustment
return Transform(matBot, point);
}
public override void Run()
{
if (nop)
return;
//TODO: this could be more efficient (draw only in gap)
GL.SetClearColor(Color.Black);
GL.Clear(OpenTK.Graphics.OpenGL.ClearBufferMask.ColorBufferBit);
FilterProgram.GuiRenderer.Begin(outputSize);
GuiRenderer.SetBlendState(GL.BlendNoneCopy);
//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
bool renderTop = false;
bool renderBottom = false;
if (Layout == ScreenLayout.Bottom) renderBottom = true;
if (Layout == ScreenLayout.Top) renderTop = true;
if (Layout == ScreenLayout.Vertical) renderTop = renderBottom = true;
if (Layout == ScreenLayout.Horizontal) renderTop = renderBottom = true;
if (renderTop)
{
GuiRenderer.Modelview.Push();
GuiRenderer.Modelview.PostMultiplyMatrix(matTop);
GuiRenderer.DrawSubrect(InputTexture, 0, 0, 256, 192, 0.0f, 0.0f, 1.0f, 0.5f);
GuiRenderer.Modelview.Pop();
}
if (renderBottom)
{
GuiRenderer.Modelview.Push();
GuiRenderer.Modelview.PostMultiplyMatrix(matBot);
GuiRenderer.DrawSubrect(InputTexture, 0, 0, 256, 192, 0.0f, 0.5f, 1.0f, 1.0f);
GuiRenderer.Modelview.Pop();
}
GuiRenderer.End();
}
}
public class FinalPresentation : BaseFilter
{
public enum eFilterOption

View File

@ -968,26 +968,30 @@ namespace BizHawk.Client.EmuHawk
}
} // foreach event
// also handle floats
conInput.AcceptNewFloats(Input.Instance.GetFloats().Select(o =>
//also handle floats
//we'll need to isolate the mouse coordinates so we can translate them
Tuple<string, float> mouseX = null, mouseY = null;
var floats = Input.Instance.GetFloats();
foreach (var f in Input.Instance.GetFloats())
{
// hackish
if (o.Item1 == "WMouse X")
{
var p = DisplayManager.UntransformPoint(new Point((int)o.Item2, 0));
float x = p.X / (float)_currentVideoProvider.BufferWidth;
return new Tuple<string, float>("WMouse X", (x * 20000) - 10000);
}
if (f.Item1 == "WMouse X")
mouseX = f;
else if (f.Item1 == "WMouse Y")
mouseY = f;
else conInput.AcceptNewFloat(f);
}
if (o.Item1 == "WMouse Y")
{
var p = DisplayManager.UntransformPoint(new Point(0, (int)o.Item2));
float y = p.Y / (float)_currentVideoProvider.BufferHeight;
return new Tuple<string, float>("WMouse Y", (y * 20000) - 10000);
}
//if we found mouse coordinates (and why wouldn't we?) then translate them now
//NOTE: these must go together, because in the case of screen rotation, X and Y are transformed together
if(mouseX != null && mouseY != null)
{
var p = DisplayManager.UntransformPoint(new Point((int)mouseX.Item2, (int)mouseY.Item2));
float x = p.X / (float)_currentVideoProvider.BufferWidth;
float y = p.Y / (float)_currentVideoProvider.BufferHeight;
conInput.AcceptNewFloat(new Tuple<string, float>("WMouse X", (x * 20000) - 10000));
conInput.AcceptNewFloat(new Tuple<string, float>("WMouse Y", (y * 20000) - 10000));
}
return o;
}));
}
public void RebootCore()