perf(rendering): draw retained frame product

Make the incremental render scene the production entity source at the existing retail PView stages while retaining the accepted dispatcher upload and draw executor. Keep diagnostics consumer-gated, retain ordered indices across unchanged frames, refresh only dirty records, and preserve exact mesh-load, selection, alpha, lighting, and route lifecycle semantics.
This commit is contained in:
Erik 2026-07-25 04:12:23 +02:00
parent c7d7848dd2
commit ef1d263337
13 changed files with 1333 additions and 267 deletions

View file

@ -1466,41 +1466,10 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable
HashSet<uint>? animatedEntityIds = null,
EntitySet set = EntitySet.All)
{
_shader.Use();
_selectionLighting?.TickLighting();
_indoorProbeFrameCounter++;
var vp = camera.View * camera.Projection;
_shader.SetMatrix4("uViewProjection", vp);
// A7 Fix D D-3/D-4: object path — plain Lambert points + sun. MUST set
// explicitly (shared GL uniform; EnvCellRenderer sets it to 1).
_shader.SetInt("uLightingMode", 0);
// #176 stripe-hunt isolation (ACDREAM_LIGHT_DEBUG) — throwaway diagnostic.
_shader.SetInt("uLightDebug", AcDream.Core.Rendering.RenderingDiagnostics.LightDebugMode);
// #128 self-heal: fresh re-request dedup per Draw pass.
_missRequested.Clear();
bool diag = string.Equals(Environment.GetEnvironmentVariable("ACDREAM_WB_DIAG"), "1", StringComparison.Ordinal);
if (diag && !_gpuQueriesInitialized)
{
for (int i = 0; i < GpuQueryRingDepth; i++)
{
_gpuQueryOpaque[i] = _gl.GenQuery();
_gpuQueryTransparent[i] = _gl.GenQuery();
}
_gpuQueriesInitialized = true;
}
// Always run the CPU stopwatch — cheap; only logged under diag.
_cpuStopwatch.Restart();
// Camera world-space position for front-to-back sort (perf #2). The view
// matrix is the inverse of the camera's world transform, so the world
// translation lives in the inverse's translation row.
Vector3 camPos = Vector3.Zero;
if (Matrix4x4.Invert(camera.View, out var invView))
camPos = invView.Translation;
bool diag = BeginEntityDispatch(
camera,
out Matrix4x4 vp,
out Vector3 camPos);
// ── Phase 1: clear groups, walk entities, build groups ──────────────
// Draw is invoked several times per frame (landscape slices, late
@ -2041,11 +2010,71 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable
if (RenderingDiagnostics.ProbeClipRouteEnabled && _clipRoutingActive)
EmitClipRouteDispatchProbe(probeCulledEntities);
ExecuteClassifiedGroups(
vp,
camPos,
anyVao,
_groups.Values,
set,
walkResult.EntitiesWalked,
_walkScratch.Count,
diag,
observeCurrentPath: true);
}
private bool BeginEntityDispatch(
ICamera camera,
out Matrix4x4 viewProjection,
out Vector3 cameraWorldPosition)
{
_shader.Use();
_selectionLighting?.TickLighting();
_indoorProbeFrameCounter++;
viewProjection = camera.View * camera.Projection;
_shader.SetMatrix4("uViewProjection", viewProjection);
_shader.SetInt("uLightingMode", 0);
_shader.SetInt(
"uLightDebug",
RenderingDiagnostics.LightDebugMode);
_missRequested.Clear();
bool diagnosticsEnabled = string.Equals(
Environment.GetEnvironmentVariable("ACDREAM_WB_DIAG"),
"1",
StringComparison.Ordinal);
if (diagnosticsEnabled && !_gpuQueriesInitialized)
{
for (int index = 0; index < GpuQueryRingDepth; index++)
{
_gpuQueryOpaque[index] = _gl.GenQuery();
_gpuQueryTransparent[index] = _gl.GenQuery();
}
_gpuQueriesInitialized = true;
}
_cpuStopwatch.Restart();
cameraWorldPosition = Vector3.Zero;
if (Matrix4x4.Invert(camera.View, out Matrix4x4 inverseView))
cameraWorldPosition = inverseView.Translation;
return diagnosticsEnabled;
}
private void ExecuteClassifiedGroups(
Matrix4x4 vp,
Vector3 camPos,
uint anyVao,
IEnumerable<InstanceGroup> groups,
EntitySet set,
int entitiesWalked,
int tupleCount,
bool diag,
bool observeCurrentPath)
{
// Nothing visible — skip the GL pass entirely.
if (anyVao == 0)
{
LastDrawStats = new DrawStats(set, walkResult.EntitiesWalked, _walkScratch.Count, 0, 0, 0, 0, 0, 0);
ObserveCurrentDispatcherSubmission(
LastDrawStats = new DrawStats(set, entitiesWalked, tupleCount, 0, 0, 0, 0, 0, 0);
ObserveClassifiedDispatcherSubmission(observeCurrentPath,
visibleInstanceCount: 0,
immediateInstanceCount: 0,
deferTransparent: false,
@ -2058,7 +2087,7 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable
// ── Phase 3: assign FirstInstance per group, lay matrices contiguously, sort opaque ──
bool deferTransparent = _alphaQueue?.IsCollecting == true;
var instanceCounts = PartitionInstanceGroups(
_groups.Values,
groups,
deferTransparent,
camPos,
_opaqueDraws,
@ -2067,8 +2096,8 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable
int immediateInstances = instanceCounts.ImmediateInstances;
if (totalInstances == 0)
{
LastDrawStats = new DrawStats(set, walkResult.EntitiesWalked, _walkScratch.Count, 0, 0, 0, 0, 0, 0);
ObserveCurrentDispatcherSubmission(
LastDrawStats = new DrawStats(set, entitiesWalked, tupleCount, 0, 0, 0, 0, 0, 0);
ObserveClassifiedDispatcherSubmission(observeCurrentPath,
visibleInstanceCount: 0,
immediateInstanceCount: 0,
deferTransparent,
@ -2185,15 +2214,15 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable
_transparentByteOffset = layout.TransparentByteOffset;
LastDrawStats = new DrawStats(
set,
walkResult.EntitiesWalked,
_walkScratch.Count,
entitiesWalked,
tupleCount,
totalInstances,
totalDraws,
cullRuns,
_opaqueDrawCount,
_transparentDrawCount,
totalTriangles);
ObserveCurrentDispatcherSubmission(
ObserveClassifiedDispatcherSubmission(observeCurrentPath,
totalInstances,
immediateInstances,
deferTransparent,
@ -2404,6 +2433,7 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable
}
}
/// <summary>
/// Phase A8 RR5 (2026-05-26): per-building draw overload. Walks only
/// entities whose ParentCellId is in <paramref name="cellIds"/>, plus
@ -2559,6 +2589,23 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable
observer.ObserveDispatcherSubmission(in submission);
}
private void ObserveClassifiedDispatcherSubmission(
bool observeCurrentPath,
int visibleInstanceCount,
int immediateInstanceCount,
bool deferTransparent,
Vector3 cameraWorldPosition)
{
if (observeCurrentPath)
{
ObserveCurrentDispatcherSubmission(
visibleInstanceCount,
immediateInstanceCount,
deferTransparent,
cameraWorldPosition);
}
}
internal static CurrentRenderDispatcherSubmission
CreateDispatcherSubmission(
int visibleInstanceCount,