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

@ -149,4 +149,26 @@ public sealed class TranslucencyFadeManagerTests
Assert.Equal(1f, part0); // part 0's 1s ramp is done
Assert.Equal(0.5f, part1, 5); // part 1's 2s ramp is halfway
}
[Fact]
public void Revision_AdvancesOnlyWhenCommittedCasterOpacityChanges()
{
var mgr = new TranslucencyFadeManager();
ulong initial = mgr.Revision;
mgr.StartPartFade(1, 0, start: 0f, end: 1f, time: 1f);
ulong started = mgr.Revision;
Assert.True(started > initial);
mgr.AdvanceAll(0f);
Assert.Equal(started, mgr.Revision);
mgr.AdvanceAll(0.5f);
Assert.True(mgr.Revision > started);
ulong advanced = mgr.Revision;
mgr.ClearEntity(999);
Assert.Equal(advanced, mgr.Revision);
mgr.ClearEntity(1);
Assert.True(mgr.Revision > advanced);
}
}

View file

@ -124,6 +124,112 @@ public sealed class WbDrawDispatcherIndirectBuilderTests
Assert.Equal(0, result.TransparentCount);
}
[Fact]
public void EveryBuiltMeshMaterialSubsetIsDetailEligible()
{
TranslucencyKind[] kinds =
[
TranslucencyKind.Opaque,
TranslucencyKind.ClipMap,
TranslucencyKind.AlphaBlend,
TranslucencyKind.Additive,
TranslucencyKind.InvAlpha,
];
var groups = kinds.Select((kind, index) => new WbDrawDispatcher.IndirectGroupInput(
IndexCount: 3,
FirstIndex: (uint)(index * 3),
BaseVertex: 0,
InstanceCount: 1,
FirstInstance: index,
TextureIndex: (uint)index,
TextureLayer: 0,
Translucency: kind)).ToList();
var indirect = new DrawElementsIndirectCommand[kinds.Length];
var batches = new WbDrawDispatcher.BatchDataPublic[kinds.Length];
WbDrawDispatcher.BuildIndirectArrays(groups, indirect, batches);
Assert.All(batches, batch => Assert.Equal(1u, batch.Flags));
}
[Fact]
public void DetailCategoryPredicateCoversEveryInstanceInAnIndirectCommand()
{
var command = new DrawElementsIndirectCommand
{
BaseInstance = 2,
InstanceCount = 3,
};
Assert.True(WbDrawDispatcher.CommandContainsDetailCategory(
command,
[0u, 0u, 0u, 1u, 0u]));
Assert.False(WbDrawDispatcher.CommandContainsDetailCategory(
command,
[1u, 1u, 0u, 0u, 0u]));
Assert.Throws<ArgumentOutOfRangeException>(() =>
WbDrawDispatcher.CommandContainsDetailCategory(
command,
[0u, 0u, 0u, 1u]));
}
[Fact]
public void OpaqueDetailRunsSkipNonbuildingCommandsAndKeepMixedCommands()
{
DrawElementsIndirectCommand[] commands =
[
new() { BaseInstance = 0, InstanceCount = 2 },
new() { BaseInstance = 2, InstanceCount = 2 },
new() { BaseInstance = 4, InstanceCount = 1 },
];
// Command 1 is mixed: instance 2 is ordinary and instance 3 is a
// building. It must be submitted once, then mesh_detail filters the
// ordinary instance. Commands 0 and 2 must never reach the detail pipe.
uint[] mixedCategories = [0u, 0u, 0u, 1u, 0u];
Assert.True(WbDrawDispatcher.TryGetNextDetailCommandRun(
commands,
mixedCategories,
searchStart: 0,
exclusiveEnd: commands.Length,
out WbDrawDispatcher.DetailCommandRun mixedRun));
Assert.Equal(new WbDrawDispatcher.DetailCommandRun(1, 1), mixedRun);
Assert.False(WbDrawDispatcher.TryGetNextDetailCommandRun(
commands,
mixedCategories,
searchStart: mixedRun.FirstCommand + mixedRun.CommandCount,
exclusiveEnd: commands.Length,
out _));
Assert.False(WbDrawDispatcher.TryGetNextDetailCommandRun(
commands,
new uint[5],
searchStart: 0,
exclusiveEnd: commands.Length,
out _));
}
[Fact]
public void OpaqueDetailRunsCoalesceConsecutiveEligibleCommands()
{
DrawElementsIndirectCommand[] commands =
[
new() { BaseInstance = 0, InstanceCount = 1 },
new() { BaseInstance = 1, InstanceCount = 1 },
new() { BaseInstance = 2, InstanceCount = 1 },
new() { BaseInstance = 3, InstanceCount = 1 },
];
uint[] categories = [0u, 1u, 1u, 0u];
Assert.True(WbDrawDispatcher.TryGetNextDetailCommandRun(
commands,
categories,
searchStart: 0,
exclusiveEnd: commands.Length,
out WbDrawDispatcher.DetailCommandRun run));
Assert.Equal(new WbDrawDispatcher.DetailCommandRun(1, 2), run);
}
[Fact]
public void BatchDataPublic_LayoutMatchesPrivateBatchData()
{