yum-archive/2ner

A toon shader for Unity's BIRP.

git clone https://git.yummers.dev/yum-archive/2ner

yumadd shader inliner5f84c37

master
5.2 KiB192 linesraw
1// !! AI ARTIFACT !!
2// This code was originally generated by Claude 3.5 Sonnet.
3// I wanted to write this tooling like I want a fucking hole in the head so I
4// kindly asked Claude to write it for me. It's shitty and poorly designed, but
5// it works well enough for my purposes.
6// It has been slightly tweaked by me, and validated on *this* codebase. It is
7// provided with no warranty.
8using UnityEngine;
9using UnityEditor;
10using System.IO;
11using System.Text.RegularExpressions;
12using System.Collections.Generic;
13using System.Linq;
14
15public class ShaderInlinerV2 : EditorWindow
16{
17	private string inputShaderPath;
18	private string outputShaderPath;
19	private int maxOutputLines = 10000000; // 10 million lines limit
20	private int currentLineCount = 0;
21
22	[MenuItem("Tools/yum_food/Shader Inliner (v2)")]
23	public static void ShowWindow()
24	{
25		GetWindow<ShaderInlinerV2>("Shader Inliner");
26	}
27
28	private void OnGUI()
29	{
30		GUILayout.Label("Shader Inliner", EditorStyles.boldLabel);
31
32		inputShaderPath = EditorGUILayout.TextField("Input Shader Path", inputShaderPath);
33		if (GUILayout.Button("Select Input Shader"))
34		{
35			inputShaderPath = EditorUtility.OpenFilePanel("Select Shader", "", "shader");
36		}
37
38		maxOutputLines = EditorGUILayout.IntField("Max Output Lines", maxOutputLines);
39
40		if (GUILayout.Button("Inline Shader"))
41		{
42			if (string.IsNullOrEmpty(inputShaderPath))
43			{
44				EditorUtility.DisplayDialog("Error", "Please select an input shader.", "OK");
45				return;
46			}
47
48			InlineShader();
49		}
50	}
51
52	private void InlineShader()
53	{
54		currentLineCount = 0;
55		string shaderContent = File.ReadAllText(inputShaderPath);
56		string inlinedShader = ProcessShader(shaderContent, Path.GetDirectoryName(inputShaderPath));
57
58		string fileName = Path.GetFileNameWithoutExtension(inputShaderPath);
59		outputShaderPath = Path.Combine(Path.GetDirectoryName(inputShaderPath), $"{fileName}_inlined.shader");
60		File.WriteAllText(outputShaderPath, inlinedShader);
61
62		AssetDatabase.Refresh();
63		EditorUtility.DisplayDialog("Success", 
64			$"Inlined shader saved to:\n{outputShaderPath}\nTotal lines: {currentLineCount}", "OK");
65	}
66
67	private string ProcessShader(string content, string basePath)
68	{
69		// Update shader name
70		content = Regex.Replace(content, @"Shader\s+""(.+?)""", match =>
71				{
72				string shaderName = match.Groups[1].Value;
73				return $"Shader \"{shaderName}_inlined\"";
74				});
75
76		// Count initial lines
77		currentLineCount += content.Split('\n').Length;
78		if (currentLineCount > maxOutputLines)
79		{
80			Debug.LogError($"Maximum line count exceeded: {currentLineCount}");
81			return content;
82		}
83
84		// Process all includes, regardless of whether they're in CGPROGRAM blocks
85		content = ProcessIncludes(content, basePath);
86
87		// Check for mismatched preprocessor macros in the entire shader
88		CheckMismatchedMacros(content);
89
90		return content;
91	}
92
93	private string ProcessIncludes(string content, string basePath)
94	{
95		string pattern = @"#include\s+""(.+?)""";
96		return Regex.Replace(content, pattern, match =>
97				{
98				string includePath = match.Groups[1].Value;
99				string fullPath = Path.Combine(basePath, includePath);
100
101				if (File.Exists(fullPath))
102				{
103				string includeContent = File.ReadAllText(fullPath);
104				
105				// Count the lines in the included file
106				int includeLines = includeContent.Split('\n').Length;
107				currentLineCount += includeLines;
108				
109				// Check if we've exceeded the line limit
110				if (currentLineCount > maxOutputLines)
111				{
112				Debug.LogError($"Maximum line count exceeded ({currentLineCount} lines) while including: {includePath}");
113				return $"// ERROR: Maximum line count exceeded while including: {includePath}";
114				}
115				
116				// Process includes recursively
117				return ProcessIncludes(includeContent, Path.GetDirectoryName(fullPath));
118				}
119				else
120				{
121				Debug.LogWarning($"Include file not found: {fullPath}");
122				return match.Value;
123				}
124				});
125	}
126
127	private void CheckMismatchedMacros(string content)
128	{
129		var stack = new Stack<string>();
130		var lines = content.Split('\n');
131		var macroPattern = @"^\s*#(if|ifdef|ifndef|elif|else|endif|if\s+defined)";
132
133		for (int i = 0; i < lines.Length; i++)
134		{
135			var line = lines[i].Trim();
136			var match = Regex.Match(line, macroPattern);
137
138			if (match.Success)
139			{
140				var directive = match.Groups[1].Value;
141
142				switch (directive)
143				{
144					case "if":
145					case "ifdef":
146					case "ifndef":
147					case "if defined":
148						stack.Push(directive);
149						break;
150					case "elif":
151						if (stack.Count == 0 || (stack.Peek() != "if" && stack.Peek() != "elif"))
152						{
153							Debug.LogError($"Mismatched #elif at line {i + 1}");
154						}
155						else
156						{
157							stack.Pop();
158							stack.Push("elif");
159						}
160						break;
161					case "else":
162						if (stack.Count == 0 || (stack.Peek() != "if" && stack.Peek() != "elif"))
163						{
164							Debug.LogError($"Mismatched #else at line {i + 1}");
165						}
166						else
167						{
168							stack.Pop();
169							stack.Push("else");
170						}
171						break;
172					case "endif":
173						if (stack.Count == 0)
174						{
175							Debug.LogError($"Mismatched #endif at line {i + 1}");
176						}
177						else
178						{
179							stack.Pop();
180						}
181						break;
182				}
183			}
184		}
185
186		if (stack.Count > 0)
187		{
188			Debug.LogError($"Unclosed preprocessor directives: {string.Join(", ", stack)}");
189		}
190	}
191}
192