feat(render): implement Campaign AR and terrain fidelity

This commit is contained in:
Erik 2026-08-22 13:13:29 +02:00
parent 99cf26e00c
commit 7a5f96ede5
368 changed files with 50611 additions and 950 deletions

View file

@ -28,15 +28,21 @@ internal static class Program
{
private static unsafe int Main(string[] args)
{
if (args.Length < 2)
if (args.Length < 2
|| args.Length > 3
|| (args.Length == 3 && !string.Equals(args[2], "--force", StringComparison.Ordinal)))
{
Console.Error.WriteLine("usage: ShaderCompiler <shaders-dir> <output-dir>");
Console.Error.WriteLine("usage: ShaderCompiler <shaders-dir> <output-dir> [--force]");
return 2;
}
string sourceDirectory = Path.GetFullPath(args[0]);
string outputDirectory = Path.GetFullPath(args[1]);
bool force = args.Length == 3;
Directory.CreateDirectory(outputDirectory);
string manifestPath = Path.Combine(outputDirectory, "shaders.manifest.json");
IReadOnlyDictionary<(string Name, string Stage), ShaderStageResult> previousStages =
force ? new Dictionary<(string, string), ShaderStageResult>() : LoadPreviousStages(manifestPath);
string[] names = Directory
.EnumerateFiles(sourceDirectory, "*.vert")
@ -70,7 +76,23 @@ internal static class Program
{
string path = Path.Combine(sourceDirectory, $"{name}.{stage}");
string source = File.ReadAllText(path);
if (source.Contains("#include \"", StringComparison.Ordinal))
source = GlslIncludeExpander.Expand(source, sourceDirectory);
string hash = Sha256(source);
string target = Path.Combine(outputDirectory, $"{name}.{stage}.spv");
// An unchanged source already has the exact committed artifact
// described by the prior manifest. Keeping it avoids compiler-
// version-only SPIR-V decoration reordering on the retail path;
// a real source edit changes the hash and recompiles normally.
if (previousStages.TryGetValue((name, stage), out ShaderStageResult? previous)
&& previous.Compiled
&& string.Equals(previous.SourceSha256, hash, StringComparison.Ordinal)
&& File.Exists(target))
{
stages.Add(new ShaderStageResult(stage, hash, true, null));
continue;
}
string transformed;
try
@ -88,13 +110,11 @@ internal static class Program
if (TryCompile(shaderc, compiler, options, transformed, $"{name}.{stage}", stage, out byte[] spirv, out string message))
{
string target = Path.Combine(outputDirectory, $"{name}.{stage}.spv");
File.WriteAllBytes(target, spirv);
stages.Add(new ShaderStageResult(stage, hash, true, null));
}
else
{
string target = Path.Combine(outputDirectory, $"{name}.{stage}.spv");
if (File.Exists(target))
File.Delete(target);
stages.Add(new ShaderStageResult(stage, hash, false, Summarise(message)));
@ -140,7 +160,6 @@ internal static class Program
var manifest = new ShaderManifest(
"Campaign V slice V6c. Regenerate with tools/compile-shaders.ps1.",
entries.OrderBy(entry => entry.Name, StringComparer.Ordinal).ToList());
string manifestPath = Path.Combine(outputDirectory, "shaders.manifest.json");
File.WriteAllText(
manifestPath,
JsonSerializer.Serialize(manifest, ShaderManifestJson.Options) + Environment.NewLine);
@ -218,6 +237,30 @@ internal static class Program
byte[] bytes = Encoding.UTF8.GetBytes(text.Replace("\r\n", "\n"));
return Convert.ToHexStringLower(SHA256.HashData(bytes));
}
private static IReadOnlyDictionary<(string Name, string Stage), ShaderStageResult> LoadPreviousStages(
string manifestPath)
{
if (!File.Exists(manifestPath))
return new Dictionary<(string, string), ShaderStageResult>();
try
{
ShaderManifest? manifest = JsonSerializer.Deserialize<ShaderManifest>(
File.ReadAllText(manifestPath),
ShaderManifestJson.Options);
return manifest?.Shaders
.SelectMany(shader => shader.Stages.Select(stage => (shader.Name, Stage: stage)))
.ToDictionary(item => (item.Name, item.Stage.Stage), item => item.Stage)
?? new Dictionary<(string, string), ShaderStageResult>();
}
catch (JsonException)
{
// A malformed or obsolete manifest is not an authority. Recompile
// everything and replace it with the current deterministic schema.
return new Dictionary<(string, string), ShaderStageResult>();
}
}
}
internal sealed record ShaderStageResult(