perf(streaming): cursor publication across frame budgets

This commit is contained in:
Erik 2026-07-24 19:10:18 +02:00
parent bb16f74fd4
commit 98f1ac8934
32 changed files with 2215 additions and 823 deletions

View file

@ -29,8 +29,11 @@ using Silk.NET.OpenGL;
namespace AcDream.App.Rendering.Wb;
public sealed unsafe class EnvCellRenderer : IDisposable
public sealed unsafe class EnvCellRenderer :
IDisposable,
IEnvCellLandblockPublisher
{
private readonly object _publicationOwner = new();
private readonly GL _gl;
private readonly ObjectMeshManager _meshManager;
private readonly WbFrustum _frustum;
@ -381,8 +384,80 @@ public sealed unsafe class EnvCellRenderer : IDisposable
/// </summary>
public void CommitLandblock(EnvCellLandblockBuild build)
{
_landblocks[build.LandblockId] = CreateCommittedSnapshot(build);
EnvCellLandblockPublication publication = PreparePublication(build);
while (!AdvancePreparationOne(publication))
{
}
CommitPublication(publication);
}
EnvCellLandblockPublication IEnvCellLandblockPublisher.PreparePublication(
EnvCellLandblockBuild build) =>
PreparePublication(build);
bool IEnvCellLandblockPublisher.AdvancePreparationOne(
EnvCellLandblockPublication publication) =>
AdvancePreparationOne(publication);
void IEnvCellLandblockPublisher.CommitPublication(
EnvCellLandblockPublication publication) =>
CommitPublication(publication);
internal EnvCellLandblockPublication PreparePublication(
EnvCellLandblockBuild build)
{
ArgumentNullException.ThrowIfNull(build);
return new EnvCellLandblockPublication(_publicationOwner, build);
}
internal bool AdvancePreparationOne(
EnvCellLandblockPublication publication)
{
ValidatePublication(publication);
if (publication.PreparationCommitted)
return true;
if (publication.ShellCursor < publication.Build.Shells.Length)
{
AddShell(
publication.Replacement,
publication.Build.Shells[publication.ShellCursor]);
WbBoundingBox bounds =
publication.Build.Shells[publication.ShellCursor].WorldBounds;
publication.TotalBounds =
publication.ShellCursor == 0
? bounds
: WbBoundingBox.Union(
publication.TotalBounds,
bounds);
publication.ShellCursor++;
return false;
}
publication.Replacement.TotalEnvCellBounds =
publication.Replacement.EnvCellBounds.Count == 0
? new WbBoundingBox(Vector3.Zero, Vector3.Zero)
: publication.TotalBounds;
publication.Replacement.InstancesReady = true;
publication.Replacement.MeshDataReady = true;
publication.Replacement.GpuReady = true;
publication.PreparationCommitted = true;
return true;
}
internal void CommitPublication(
EnvCellLandblockPublication publication)
{
ValidatePublication(publication);
if (!publication.PreparationCommitted)
throw new InvalidOperationException(
"EnvCell landblock publication cannot commit before preparation.");
if (publication.PublicationCommitted)
return;
_landblocks[publication.Build.LandblockId] = publication.Replacement;
NeedsPrepare = true;
publication.PublicationCommitted = true;
}
/// <summary>
@ -398,35 +473,7 @@ public sealed unsafe class EnvCellRenderer : IDisposable
};
foreach (var shell in build.Shells)
{
replacement.Instances.Add(new EnvCellSceneryInstance
{
ObjectId = shell.GeometryId,
InstanceId = shell.CellId,
IsBuilding = true,
IsEntryCell = false,
WorldPosition = shell.WorldPosition,
LocalPosition = Vector3.Zero,
Rotation = shell.Rotation,
Scale = Vector3.One,
Transform = shell.Transform,
LocalBoundingBox = shell.LocalBounds,
BoundingBox = shell.WorldBounds,
});
replacement.EnvCellBounds[shell.CellId] = shell.WorldBounds;
if (!replacement.BuildingPartGroups.TryGetValue(shell.GeometryId, out var instances))
{
instances = new List<InstanceData>();
replacement.BuildingPartGroups[shell.GeometryId] = instances;
}
instances.Add(new InstanceData
{
Transform = shell.Transform,
CellId = shell.CellId,
Flags = 0,
});
}
AddShell(replacement, shell);
var total = new WbBoundingBox(new Vector3(float.MaxValue), new Vector3(float.MinValue));
foreach (var bounds in replacement.EnvCellBounds.Values)
@ -441,6 +488,53 @@ public sealed unsafe class EnvCellRenderer : IDisposable
return replacement;
}
private static void AddShell(
EnvCellLandblock replacement,
EnvCellShellPlacement shell)
{
replacement.Instances.Add(new EnvCellSceneryInstance
{
ObjectId = shell.GeometryId,
InstanceId = shell.CellId,
IsBuilding = true,
IsEntryCell = false,
WorldPosition = shell.WorldPosition,
LocalPosition = Vector3.Zero,
Rotation = shell.Rotation,
Scale = Vector3.One,
Transform = shell.Transform,
LocalBoundingBox = shell.LocalBounds,
BoundingBox = shell.WorldBounds,
});
replacement.EnvCellBounds[shell.CellId] = shell.WorldBounds;
if (!replacement.BuildingPartGroups.TryGetValue(
shell.GeometryId,
out var instances))
{
instances = new List<InstanceData>();
replacement.BuildingPartGroups[shell.GeometryId] = instances;
}
instances.Add(new InstanceData
{
Transform = shell.Transform,
CellId = shell.CellId,
Flags = 0,
});
}
private void ValidatePublication(
EnvCellLandblockPublication publication)
{
ArgumentNullException.ThrowIfNull(publication);
if (!ReferenceEquals(publication.Owner, _publicationOwner))
{
throw new ArgumentException(
"The EnvCell publication receipt belongs to another renderer.",
nameof(publication));
}
}
/// <summary>
/// Removes a landblock from the renderer. Future PrepareRenderBatches will exclude it.
/// </summary>