acdream/tools/ShaderCompiler/GlslIncludeExpander.cs

51 lines
2 KiB
C#

using System.Text;
namespace AcDream.Tools.ShaderCompiler;
/// <summary>
/// Tiny deterministic include expander for checked-in shader ABI snippets.
/// Only quoted, shader-directory-relative includes are accepted; traversal and
/// cycles fail the compile rather than reaching shaderc with host-dependent
/// search paths.
/// </summary>
internal static class GlslIncludeExpander
{
internal static string Expand(string source, string sourceDirectory) =>
Expand(source, Path.GetFullPath(sourceDirectory), []);
private static string Expand(
string source,
string sourceDirectory,
HashSet<string> active)
{
var output = new StringBuilder();
foreach (string line in source.Replace("\r\n", "\n").Split('\n'))
{
string trimmed = line.Trim();
if (!trimmed.StartsWith("#include \"", StringComparison.Ordinal)
|| !trimmed.EndsWith('"'))
{
output.AppendLine(line);
continue;
}
string key = trimmed[10..^1];
if (key.Length == 0
|| Path.IsPathRooted(key)
|| key.Contains("..", StringComparison.Ordinal)
|| key.Contains('\\'))
throw new InvalidDataException($"Unsafe GLSL include '{key}'.");
string path = Path.GetFullPath(Path.Combine(sourceDirectory, key));
if (!path.StartsWith(sourceDirectory + Path.DirectorySeparatorChar, StringComparison.Ordinal)
|| !File.Exists(path))
throw new FileNotFoundException($"GLSL include '{key}' was not found.", path);
if (!active.Add(path))
throw new InvalidDataException($"Cyclic GLSL include '{key}'.");
output.AppendLine($"// ---- begin include: {key} ----");
output.Append(Expand(File.ReadAllText(path), sourceDirectory, active));
output.AppendLine($"// ---- end include: {key} ----");
active.Remove(path);
}
return output.ToString();
}
}