From 5f84c37a4e95503f28540780c3257f8689cccef9 Mon Sep 17 00:00:00 2001 From: yum Date: Fri, 28 Mar 2025 19:35:57 -0700 Subject: add shader inliner --- Editor/shader_inliner_v2.cs | 192 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 Editor/shader_inliner_v2.cs (limited to 'Editor') diff --git a/Editor/shader_inliner_v2.cs b/Editor/shader_inliner_v2.cs new file mode 100644 index 0000000..7b66c6a --- /dev/null +++ b/Editor/shader_inliner_v2.cs @@ -0,0 +1,192 @@ +// !! AI ARTIFACT !! +// This code was originally generated by Claude 3.5 Sonnet. +// I wanted to write this tooling like I want a fucking hole in the head so I +// kindly asked Claude to write it for me. It's shitty and poorly designed, but +// it works well enough for my purposes. +// It has been slightly tweaked by me, and validated on *this* codebase. It is +// provided with no warranty. +using UnityEngine; +using UnityEditor; +using System.IO; +using System.Text.RegularExpressions; +using System.Collections.Generic; +using System.Linq; + +public class ShaderInlinerV2 : EditorWindow +{ + private string inputShaderPath; + private string outputShaderPath; + private int maxOutputLines = 10000000; // 10 million lines limit + private int currentLineCount = 0; + + [MenuItem("Tools/yum_food/Shader Inliner (v2)")] + public static void ShowWindow() + { + GetWindow("Shader Inliner"); + } + + private void OnGUI() + { + GUILayout.Label("Shader Inliner", EditorStyles.boldLabel); + + inputShaderPath = EditorGUILayout.TextField("Input Shader Path", inputShaderPath); + if (GUILayout.Button("Select Input Shader")) + { + inputShaderPath = EditorUtility.OpenFilePanel("Select Shader", "", "shader"); + } + + maxOutputLines = EditorGUILayout.IntField("Max Output Lines", maxOutputLines); + + if (GUILayout.Button("Inline Shader")) + { + if (string.IsNullOrEmpty(inputShaderPath)) + { + EditorUtility.DisplayDialog("Error", "Please select an input shader.", "OK"); + return; + } + + InlineShader(); + } + } + + private void InlineShader() + { + currentLineCount = 0; + string shaderContent = File.ReadAllText(inputShaderPath); + string inlinedShader = ProcessShader(shaderContent, Path.GetDirectoryName(inputShaderPath)); + + string fileName = Path.GetFileNameWithoutExtension(inputShaderPath); + outputShaderPath = Path.Combine(Path.GetDirectoryName(inputShaderPath), $"{fileName}_inlined.shader"); + File.WriteAllText(outputShaderPath, inlinedShader); + + AssetDatabase.Refresh(); + EditorUtility.DisplayDialog("Success", + $"Inlined shader saved to:\n{outputShaderPath}\nTotal lines: {currentLineCount}", "OK"); + } + + private string ProcessShader(string content, string basePath) + { + // Update shader name + content = Regex.Replace(content, @"Shader\s+""(.+?)""", match => + { + string shaderName = match.Groups[1].Value; + return $"Shader \"{shaderName}_inlined\""; + }); + + // Count initial lines + currentLineCount += content.Split('\n').Length; + if (currentLineCount > maxOutputLines) + { + Debug.LogError($"Maximum line count exceeded: {currentLineCount}"); + return content; + } + + // Process all includes, regardless of whether they're in CGPROGRAM blocks + content = ProcessIncludes(content, basePath); + + // Check for mismatched preprocessor macros in the entire shader + CheckMismatchedMacros(content); + + return content; + } + + private string ProcessIncludes(string content, string basePath) + { + string pattern = @"#include\s+""(.+?)"""; + return Regex.Replace(content, pattern, match => + { + string includePath = match.Groups[1].Value; + string fullPath = Path.Combine(basePath, includePath); + + if (File.Exists(fullPath)) + { + string includeContent = File.ReadAllText(fullPath); + + // Count the lines in the included file + int includeLines = includeContent.Split('\n').Length; + currentLineCount += includeLines; + + // Check if we've exceeded the line limit + if (currentLineCount > maxOutputLines) + { + Debug.LogError($"Maximum line count exceeded ({currentLineCount} lines) while including: {includePath}"); + return $"// ERROR: Maximum line count exceeded while including: {includePath}"; + } + + // Process includes recursively + return ProcessIncludes(includeContent, Path.GetDirectoryName(fullPath)); + } + else + { + Debug.LogWarning($"Include file not found: {fullPath}"); + return match.Value; + } + }); + } + + private void CheckMismatchedMacros(string content) + { + var stack = new Stack(); + var lines = content.Split('\n'); + var macroPattern = @"^\s*#(if|ifdef|ifndef|elif|else|endif|if\s+defined)"; + + for (int i = 0; i < lines.Length; i++) + { + var line = lines[i].Trim(); + var match = Regex.Match(line, macroPattern); + + if (match.Success) + { + var directive = match.Groups[1].Value; + + switch (directive) + { + case "if": + case "ifdef": + case "ifndef": + case "if defined": + stack.Push(directive); + break; + case "elif": + if (stack.Count == 0 || (stack.Peek() != "if" && stack.Peek() != "elif")) + { + Debug.LogError($"Mismatched #elif at line {i + 1}"); + } + else + { + stack.Pop(); + stack.Push("elif"); + } + break; + case "else": + if (stack.Count == 0 || (stack.Peek() != "if" && stack.Peek() != "elif")) + { + Debug.LogError($"Mismatched #else at line {i + 1}"); + } + else + { + stack.Pop(); + stack.Push("else"); + } + break; + case "endif": + if (stack.Count == 0) + { + Debug.LogError($"Mismatched #endif at line {i + 1}"); + } + else + { + stack.Pop(); + } + break; + } + } + } + + if (stack.Count > 0) + { + Debug.LogError($"Unclosed preprocessor directives: {string.Join(", ", stack)}"); + } + } +} + -- cgit v1.2.3