BizHawk/BizHawk.Client.EmuHawk/DisplayManager/FilterManager.cs

251 lines
7.2 KiB
C#

using System;
using System.Linq;
using System.Diagnostics;
using System.Collections.Generic;
using System.IO;
using System.Drawing;
using BizHawk.Common;
using BizHawk.Client.Common;
using BizHawk.Client.EmuHawk.Filters;
using BizHawk.Bizware.BizwareGL;
using BizHawk.Bizware.BizwareGL.Drivers.OpenTK;
using OpenTK;
using OpenTK.Graphics;
namespace BizHawk.Client.EmuHawk.FilterManager
{
public enum SurfaceDisposition
{
Unspecified, Texture, RenderTarget
}
public class SurfaceFormat
{
public SurfaceFormat(Size size) { this.Size = size; }
public Size Size { get; private set; }
}
public class SurfaceState
{
public SurfaceState() { }
public SurfaceState(SurfaceFormat surfaceFormat, SurfaceDisposition surfaceDisposition = SurfaceDisposition.Unspecified)
{
this.SurfaceFormat = surfaceFormat;
this.SurfaceDisposition = surfaceDisposition;
}
public SurfaceFormat SurfaceFormat;
public SurfaceDisposition SurfaceDisposition;
}
public interface IRenderTargetProvider
{
RenderTarget Get(Size size);
}
public class FilterProgram
{
public List<BaseFilter> Filters = new List<BaseFilter>();
Dictionary<string, BaseFilter> FilterNameIndex = new Dictionary<string, BaseFilter>();
public List<ProgramStep> Program = new List<ProgramStep>();
public BaseFilter this[string name]
{
get
{
BaseFilter ret;
FilterNameIndex.TryGetValue(name, out ret);
return ret;
}
}
public enum ProgramStepType
{
Run,
NewTarget,
FinalTarget
}
//services to filters:
public GuiRenderer GuiRenderer;
public IGL GL;
public IRenderTargetProvider RenderTargetProvider;
public RenderTarget GetRenderTarget(string channel = "default") { return CurrRenderTarget; }
public RenderTarget CurrRenderTarget;
public void AddFilter(BaseFilter filter, string name = "")
{
Filters.Add(filter);
FilterNameIndex[name] = filter;
}
/// <summary>
/// Receives a point in the coordinate space of the output of the filter program and untransforms it back to input points
/// </summary>
public Vector2 UntransformPoint(string channel, Vector2 point)
{
for (int i = Filters.Count - 1; i >= 0; i--)
{
var filter = Filters[i];
point = filter.UntransformPoint(channel, point);
}
return point;
}
public class ProgramStep
{
public ProgramStep(ProgramStepType type, object args, string comment = null)
{
this.Type = type;
this.Args = args;
this.Comment = comment;
}
public ProgramStepType Type;
public object Args;
public string Comment;
public override string ToString()
{
if (Type == ProgramStepType.Run)
return string.Format("Run {0} ({1})", (int)Args, Comment);
if (Type == ProgramStepType.NewTarget)
return string.Format("NewTarget {0}", (Size)Args);
if (Type == ProgramStepType.FinalTarget)
return string.Format("FinalTarget");
return null;
}
}
public void Compile(string channel, Size insize, Size outsize, bool finalTarget)
{
RETRY:
Program.Clear();
//prep filters for initialization
foreach (var f in Filters)
{
f.BeginInitialization(this);
f.Initialize();
}
//propagate input size forwards through filter chain to allow a 'flex' filter to determine what its input will be
Size presize = insize;
for (int i = 0; i < Filters.Count; i++)
{
var filter = Filters[i];
presize = filter.PresizeInput(channel, presize);
}
//propagate output size backwards through filter chain to allow a 'flex' filter to determine its output based on the desired output needs
presize = outsize;
for (int i = Filters.Count - 1; i >= 0; i--)
{
var filter = Filters[i];
presize = filter.PresizeOutput(channel, presize);
}
SurfaceState currState = null;
List<SurfaceFormat> RenderTargets = new List<SurfaceFormat>();
for (int i = 0; i < Filters.Count; i++)
{
BaseFilter f = Filters[i];
//check whether this filter needs input. if so, notify it of the current pipeline state
var iosi = f.FindInput(channel);
if (iosi != null)
{
iosi.SurfaceFormat = currState.SurfaceFormat;
f.SetInputFormat(channel, currState);
//check if the desired disposition needs to change from texture to render target
//(if so, insert a render filter)
if (iosi.SurfaceDisposition == SurfaceDisposition.RenderTarget && currState.SurfaceDisposition == SurfaceDisposition.Texture)
{
var renderer = new Render();
Filters.Insert(i, renderer);
goto RETRY;
}
//check if the desired disposition needs to change from a render target to a texture
//(if so, the current render target gets resolved, and made no longer current
else if (iosi.SurfaceDisposition == SurfaceDisposition.Texture && currState.SurfaceDisposition == SurfaceDisposition.RenderTarget)
{
var resolver = new Resolve();
Filters.Insert(i, resolver);
goto RETRY;
}
}
//now, the filter will have set its output state depending on its input state. check if it outputs:
iosi = f.FindOutput(channel);
if (iosi != null)
{
if (currState == null)
{
currState = new SurfaceState();
currState.SurfaceFormat = iosi.SurfaceFormat;
currState.SurfaceDisposition = iosi.SurfaceDisposition;
}
else
{
//if output disposition is unspecified, change it to whatever we've got right now
if (iosi.SurfaceDisposition == SurfaceDisposition.Unspecified)
{
iosi.SurfaceDisposition = currState.SurfaceDisposition;
}
bool newTarget = false;
if (iosi.SurfaceFormat.Size != currState.SurfaceFormat.Size)
newTarget = true;
else if (currState.SurfaceDisposition == SurfaceDisposition.Texture && iosi.SurfaceDisposition == SurfaceDisposition.RenderTarget)
newTarget = true;
if (newTarget)
{
currState = new SurfaceState();
iosi.SurfaceFormat = currState.SurfaceFormat = iosi.SurfaceFormat;
iosi.SurfaceDisposition = currState.SurfaceDisposition = iosi.SurfaceDisposition;
Program.Add(new ProgramStep(ProgramStepType.NewTarget, currState.SurfaceFormat.Size));
}
else
{
currState.SurfaceDisposition = iosi.SurfaceDisposition;
}
}
}
Program.Add(new ProgramStep(ProgramStepType.Run, i, f.GetType().Name));
} //filter loop
//if the current output disposition is a texture, we need to render it
if (currState.SurfaceDisposition == SurfaceDisposition.Texture)
{
var renderer = new Render();
Filters.Insert(Filters.Count, renderer);
goto RETRY;
}
//patch the program so that the final rendertarget set operation is the framebuffer instead
if (finalTarget)
{
for (int i = Program.Count - 1; i >= 0; i--)
{
var ps = Program[i];
if (ps.Type == ProgramStepType.NewTarget)
{
var size = (Size)ps.Args;
Debug.Assert(size == outsize);
ps.Type = ProgramStepType.FinalTarget;
ps.Args = size;
break;
}
}
}
}
}
}