251 lines
7.2 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |