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

263 lines
7.7 KiB
C#
Raw Normal View History

//https://github.com/Themaister/RetroArch/wiki/GLSL-shaders
//https://github.com/Themaister/Emulator-Shader-Pack/blob/master/Cg/README
//https://github.com/libretro/common-shaders/
using System;
using System.Collections.Generic;
using System.IO;
using System.Drawing;
using BizHawk.Common;
using BizHawk.Client.Common;
using BizHawk.Bizware.BizwareGL;
using BizHawk.Bizware.BizwareGL.Drivers.OpenTK;
using OpenTK;
using OpenTK.Graphics;
namespace BizHawk.Client.EmuHawk
{
/// <summary>
///
/// </summary>
class RetroShaderChain : IDisposable
{
public RetroShaderChain(IGL owner, RetroShaderPreset preset, string baseDirectory, bool debug = false)
{
Owner = owner;
this.Preset = preset;
Passes = preset.Passes.ToArray();
bool ok = true;
//load up the shaders
Shaders = new RetroShader[preset.Passes.Count];
for(int i=0;i<preset.Passes.Count;i++)
{
RetroShaderPreset.ShaderPass pass = preset.Passes[i];
//acquire content
string path = Path.Combine(baseDirectory, pass.ShaderPath);
string content = File.ReadAllText(path);
var shader = new RetroShader(Owner, content, debug);
Shaders[i] = shader;
if (!shader.Pipeline.Available)
ok = false;
}
Available = ok;
}
public void Dispose()
{
//todo
}
/// <summary>
/// Whether this shader chain is available (it wont be available if some resources failed to load or compile)
/// </summary>
public bool Available { get; private set; }
public readonly IGL Owner;
public readonly RetroShaderPreset Preset;
public readonly RetroShader[] Shaders;
public readonly RetroShaderPreset.ShaderPass[] Passes;
}
class RetroShaderPreset
{
/// <summary>
/// Parses an instance from a stream to a CGP file
/// </summary>
public RetroShaderPreset(Stream stream)
{
var content = new StreamReader(stream).ReadToEnd();
Dictionary<string,string> dict = new Dictionary<string,string>();
//parse the key-value-pair format of the file
content = content.Replace("\r", "");
foreach (var _line in content.Split('\n'))
{
var line = _line.Trim();
if(line.StartsWith("#")) continue; //lines that are solely comments
if (line == "") continue; //empty line
int eq = line.IndexOf('=');
var key = line.Substring(0,eq).Trim();
var value = line.Substring(eq + 1).Trim();
int quote = value.IndexOf('\"');
if (quote != -1)
value = value.Substring(quote + 1, value.IndexOf('\"', quote + 1) - (quote + 1));
else
{
//remove comments from end of value. exclusive from above condition, since comments after quoted strings would be snipped by the quoted string extraction
int hash = value.IndexOf('#');
if (hash != -1)
value = value.Substring(0, hash);
value = value.Trim();
}
dict[key.ToLower()] = value;
}
//process the keys
int nShaders = FetchInt(dict, "shaders", 0);
for (int i = 0; i < nShaders; i++)
{
ShaderPass sp = new ShaderPass();
sp.Index = i;
Passes.Add(sp);
sp.InputFilterLinear = FetchBool(dict, "filter_linear" + i, false); //Should this value not be defined, the filtering option is implementation defined.
sp.OuputFloat = FetchBool(dict, "float_framebuffer" + i, false);
sp.FrameCountMod = FetchInt(dict, "frame_count_mod" + i, 1);
sp.ShaderPath = FetchString(dict, "shader" + i, "?"); //todo - change extension to .cg for better compatibility? just change .cg to .glsl transparently at last second?
//If no scale type is assumed, it is assumed that it is set to "source" with scaleN set to 1.0.
//It is possible to set scale_type_xN and scale_type_yN to specialize the scaling type in either direction. scale_typeN however overrides both of these.
sp.ScaleTypeX = (ScaleType)Enum.Parse(typeof(ScaleType), FetchString(dict, "scale_type_x" + i, "Source"), true);
sp.ScaleTypeY = (ScaleType)Enum.Parse(typeof(ScaleType), FetchString(dict, "scale_type_y" + i, "Source"), true);
ScaleType st = (ScaleType)Enum.Parse(typeof(ScaleType), FetchString(dict, "scale_type" + i, "NotSet"), true);
if (st != ScaleType.NotSet)
sp.ScaleTypeX = sp.ScaleTypeY = st;
//scaleN controls both scaling type in horizontal and vertical directions. If scaleN is defined, scale_xN and scale_yN have no effect.
sp.Scale.X = FetchFloat(dict, "scale_x" + i, 1);
sp.Scale.Y = FetchFloat(dict, "scale_y" + i, 1);
float scale = FetchFloat(dict, "scale" + i, -999);
if(scale != -999)
sp.Scale.X = sp.Scale.Y = FetchFloat(dict,"scale" + i, 1);
//TODO - LUTs
}
}
public List<ShaderPass> Passes = new List<ShaderPass>();
public enum ScaleType
{
NotSet, Source, Viewport, Absolute
}
public class ShaderPass
{
public int Index;
public string ShaderPath;
public bool InputFilterLinear;
public bool OuputFloat;
public int FrameCountMod;
public ScaleType ScaleTypeX;
public ScaleType ScaleTypeY;
public Vector2 Scale;
}
string FetchString(Dictionary<string, string> dict, string key, string @default)
{
string str;
if (dict.TryGetValue(key, out str))
return str;
else return @default;
}
int FetchInt(Dictionary<string, string> dict, string key, int @default)
{
string str;
if (dict.TryGetValue(key, out str))
return int.Parse(str);
else return @default;
}
float FetchFloat(Dictionary<string, string> dict, string key, float @default)
{
string str;
if (dict.TryGetValue(key, out str))
return float.Parse(str);
else return @default;
}
bool FetchBool(Dictionary<string, string> dict, string key, bool @default)
{
string str;
if (dict.TryGetValue(key, out str))
return ParseBool(str);
else return @default;
}
bool ParseBool(string value)
{
if (value == "1") return true;
if (value == "0") return false;
value = value.ToLower();
if (value == "true") return true;
if (value == "false") return false;
throw new InvalidOperationException("Unparseable bool in CGP file content");
}
}
}
//Here, I started making code to support GUI editing of filter chains.
//I decided to go for broke and implement retroarch's system first, and then the GUI editing should be able to internally produce a metashader
//namespace BizHawk.Client.EmuHawk
//{
// class FilterManager
// {
// class PipelineState
// {
// public PipelineState(PipelineState other)
// {
// Size = other.Size;
// Format = other.Format;
// }
// public Size Size;
// public string Format;
// }
// abstract class BaseFilter
// {
// bool Connect(FilterChain chain, BaseFilter parent)
// {
// Chain = chain;
// Parent = parent;
// return OnConnect();
// }
// public PipelineState OutputState;
// public FilterChain Chain;
// public BaseFilter Parent;
// public abstract bool OnConnect();
// }
// class FilterChain
// {
// public void AddFilter(BaseFilter filter)
// {
// }
// }
// class Filter_Grayscale : BaseFilter
// {
// public override bool OnConnect()
// {
// if(Parent.OutputState.Format != "rgb") return false;
// OutputState = new PipelineState { Parent.OutputState; }
// }
// }
// class Filter_EmuOutput_RGBA : BaseFilter
// {
// public Filter_EmuOutput_RGBA(int width, int height)
// {
// OutputState = new PipelineState() { Size = new Size(width, height), Format = "rgb" };
// }
// public override bool OnConnect()
// {
// return true;
// }
// }
// }
//}