Files
2025-11-30 08:35:03 +02:00

183 lines
5.7 KiB
C#

using System;
using System.IO;
using System.Text;
using System.Text.RegularExpressions;
namespace Needle.Engine.Shaders
{
public class ThreeShaderPostProcessor
{
public void PostProcessShaders(ExtensionData data)
{
if (data == null) return;
foreach (var sh in data.shaders)
PostProcessShader(sh);
}
// https://regex101.com/r/ji65Gq/1
private static readonly Regex blockStartRegex = new Regex(@".*?uniform\s+.+?\s+\{", RegexOptions.Compiled);
private static readonly Regex replaceLayoutRegex = new Regex(@"layout\(.+?\)\s+?");
// https://regex101.com/r/5966Ku/1
private static readonly Regex removeUpperCaseDefinesWithArg = new Regex(@"[A-Z_]+\(\d\)\s+?");
public void PostProcessShader(ExtensionData.Shader shader)
{
if (shader == null) throw new ArgumentNullException(nameof(shader));
var code = shader.code;
var didHaveCode = !string.IsNullOrWhiteSpace(code);
if (string.IsNullOrEmpty(code))
{
if (string.IsNullOrEmpty(shader.uri))
{
code = ExtensionData.Shader.GetCode(shader.uri);
}
}
if (string.IsNullOrWhiteSpace(code)) throw new Exception("No shader code provided for " + shader.name);
var reader = new StringReader(code);
var res = new StringBuilder();
var foundAnyText = false;
string line;
var isInUnsupportedUniformBlock = false;
var isInMain = false;
var skipNextLine = false;
do
{
line = reader.ReadLine();
if (skipNextLine) {
skipNextLine = false;
continue;
}
if (line != null)
{
if (line.Contains("#version"))
{
line = line.Replace("310", "300");
line += "\n// downgraded version from 310 (unsupported shader version)";
}
if (!foundAnyText && string.IsNullOrWhiteSpace(line)) continue;
var isInvalidLine = false;
if (line.StartsWith("#define") || line.StartsWith("#if") || line.StartsWith("#endif") || line.StartsWith("#else"))
{
if(!line.StartsWith("#define SV_TARGET0 gl_FragData[0]")) // WebGL1-specific
{
line = "// " + line;
isInvalidLine = true;
}
}
if (line.Contains("precision mediump float;")) // WebGL1-specific
{
line = "// " + line;
isInvalidLine = true;
}
if (line.Contains("uniform") && blockStartRegex.IsMatch(line))
{
isInUnsupportedUniformBlock = true;
line = "\n// removed unsupported block: " + line;
isInvalidLine = true;
}
else if (isInUnsupportedUniformBlock)
{
if (line.StartsWith("}"))
{
isInUnsupportedUniformBlock = false;
continue;
}
if (!isInvalidLine)
{
line = line.Replace("UNITY_UNIFORM", "");
line = "uniform " + line.TrimStart();
}
}
line = replaceLayoutRegex.Replace(line, "");
line = removeUpperCaseDefinesWithArg.Replace(line, "");
if (line.Contains("GL_EXT_shader_texture_lod") || line.Contains("GL_EXT_shader_framebuffer_fetch") || line.TrimStart().StartsWith("inout "))
{
line = "// " + line;
isInvalidLine = true;
}
// if (line.StartsWith("uniform")) line = line.Replace("uniform", "uniform highp");
// if (line.StartsWith("attribute highp vec"))
// line = "//" + line;
line = line.Replace("in_NORMAL0", "normal");
line = line.Replace("in_TANGENT0", "tangent");
line = line.Replace("in_POSITION0", "position");
line = line.Replace("in_TEXCOORD0", "uv");
line = line.Replace("in_TEXCOORD1", "uv2");
line = line.Replace("in_TEXCOORD2", "uv3");
line = line.Replace("in_TEXCOORD3", "uv4");
line = line.Replace("in_COLOR0", "color");
if (shader.type == ExtensionData.ShaderType.Vertex)
{
// insert any other defines right before the position is declared.
if (line.Contains("in highp vec3 position;"))
{
res.AppendLine("#ifdef USE_INSTANCING");
res.AppendLine(" in mat4 instanceMatrix;");
res.AppendLine("#endif");
res.AppendLine(line);
foundAnyText = true;
continue;
}
// right after main(), we want to add any code that modifies input variables.
// we also need to make sure that we introduce intermediary variables that we can modify;
// the in variables are read-only.
if (line.Contains("void main()"))
{
isInMain = true;
foundAnyText = true;
res.AppendLine(line + " {");
skipNextLine = true;
// We're moving the object2world matrix into a variable so that we can optionally apply instancing etc.
res.AppendLine("vec4 osw[4] = hlslcc_mtx4x4unity_ObjectToWorld;");
// apply instancing matrix multiply to osw[4] with instanceMatrix - could also apply skinning matrix here probably
res.AppendLine("#ifdef USE_INSTANCING");
res.AppendLine("mat4 _osw;");
res.AppendLine("_osw[0] = hlslcc_mtx4x4unity_ObjectToWorld[0];");
res.AppendLine("_osw[1] = hlslcc_mtx4x4unity_ObjectToWorld[1];");
res.AppendLine("_osw[2] = hlslcc_mtx4x4unity_ObjectToWorld[2];");
res.AppendLine("_osw[3] = hlslcc_mtx4x4unity_ObjectToWorld[3];");
res.AppendLine("_osw = instanceMatrix * _osw;");
res.AppendLine("osw[0] = _osw[0];");
res.AppendLine("osw[1] = _osw[1];");
res.AppendLine("osw[2] = _osw[2];");
res.AppendLine("osw[3] = _osw[3];");
res.AppendLine("#endif");
continue;
}
if (isInMain)
{
// use the new variable in further code paths
line = line.Replace("hlslcc_mtx4x4unity_ObjectToWorld", "osw");
}
}
foundAnyText = true;
res.AppendLine(line);
}
} while (line != null);
var newCode = res.ToString();
if (didHaveCode)
shader.code = newCode;
shader.uri = ExtensionData.Shader.GetUri(newCode);
}
}
}