Reapply "perf(rendering): draw retained frame product"
This reverts commit 2c848d4167.
This commit is contained in:
parent
823936ec31
commit
20f9fadb12
13 changed files with 1333 additions and 267 deletions
|
|
@ -1,4 +1,5 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Rendering;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
using AcDream.App.Rendering.Selection;
|
||||
using AcDream.Core.Lighting;
|
||||
|
|
@ -10,10 +11,10 @@ using DatReaderWriter.Enums;
|
|||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
/// <summary>
|
||||
/// G2 compare-only classification of the scene-built frame product. This file
|
||||
/// deliberately shares the production dispatcher's exact mesh, texture,
|
||||
/// light, translucency, selection, grouping, and ordering owners while keeping
|
||||
/// its group storage separate. It never uploads or draws.
|
||||
/// Scene-frame classification and production submission. The retained group
|
||||
/// storage is also consumed by the G2/G3 compare oracle, while production
|
||||
/// routes reuse the dispatcher's exact mesh, texture, light, translucency,
|
||||
/// selection, upload, and draw owners.
|
||||
/// </summary>
|
||||
public sealed unsafe partial class WbDrawDispatcher
|
||||
{
|
||||
|
|
@ -35,6 +36,10 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
private long _packedGroupFrame;
|
||||
private long _nextPackedGroupRegistration = 1;
|
||||
private int _nextPackedInstanceSubmissionOrder;
|
||||
private bool _packedProductionFrameOpen;
|
||||
private RenderSceneGeneration _packedProductionGeneration;
|
||||
private ulong _packedProductionFrameSequence;
|
||||
private int _packedProductionNextRange;
|
||||
|
||||
internal IReadOnlyList<CurrentRenderDispatcherSubmission>
|
||||
PackedDispatcherSubmissions => _packedSubmissions;
|
||||
|
|
@ -51,104 +56,28 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
uint tupleLandblockId,
|
||||
Vector3 cameraWorldPosition)
|
||||
{
|
||||
if (_packedGroupFrame == long.MaxValue)
|
||||
if (_packedProductionFrameOpen)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Packed dispatcher frame identity was exhausted.");
|
||||
"The packed dispatcher oracle cannot replace an active production frame.");
|
||||
}
|
||||
|
||||
_packedGroupFrame++;
|
||||
PruneInstanceGroupsUnusedBeforeFrame(
|
||||
_packedGroups,
|
||||
_packedRetiredGroupKeys,
|
||||
_packedGroupFrame - 1);
|
||||
_packedEntityById.Clear();
|
||||
_packedSubmissions.Clear();
|
||||
_packedSelectionParts.Clear();
|
||||
_packedSelectionKeys.Clear();
|
||||
_packedClassificationCache.BeginFrame(view.Generation);
|
||||
|
||||
ReadOnlySpan<RenderFrameEntityCandidate> entities =
|
||||
view.EntityCandidates;
|
||||
for (int i = 0; i < entities.Length; i++)
|
||||
{
|
||||
if (!_packedEntityById.TryAdd(
|
||||
entities[i].Projection.Id,
|
||||
entities[i]))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Packed dispatcher received duplicate projection "
|
||||
+ $"{entities[i].Projection.Id}.");
|
||||
}
|
||||
}
|
||||
|
||||
ReadOnlySpan<RenderFrameMeshPart> meshParts = view.MeshParts;
|
||||
ReadOnlySpan<RenderProjectionRecord> routeCandidates =
|
||||
view.RouteCandidates;
|
||||
BeginPackedFrameStorage(in view);
|
||||
ReadOnlySpan<RenderFrameCandidateRange> ranges =
|
||||
view.RouteRanges;
|
||||
for (int rangeIndex = 0;
|
||||
rangeIndex < ranges.Length;
|
||||
rangeIndex++)
|
||||
{
|
||||
_nextPackedInstanceSubmissionOrder = 0;
|
||||
uint anyVao = 0;
|
||||
foreach (InstanceGroup group in _packedGroups.Values)
|
||||
group.ClearPerInstanceData();
|
||||
|
||||
RenderFrameCandidateRange range = ranges[rangeIndex];
|
||||
int end = checked(range.Offset + range.Count);
|
||||
if ((uint)range.Offset > (uint)routeCandidates.Length
|
||||
|| (uint)end > (uint)routeCandidates.Length)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Packed dispatcher route {rangeIndex} exceeds "
|
||||
+ "candidate storage.");
|
||||
}
|
||||
|
||||
for (int routeIndex = range.Offset;
|
||||
routeIndex < end;
|
||||
routeIndex++)
|
||||
{
|
||||
RenderProjectionRecord projection =
|
||||
routeCandidates[routeIndex];
|
||||
if ((projection.Flags & RenderProjectionFlags.Draw) == 0)
|
||||
continue;
|
||||
if (!_packedEntityById.TryGetValue(
|
||||
projection.Id,
|
||||
out RenderFrameEntityCandidate source))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Packed route references absent entity "
|
||||
+ $"{projection.Id}.");
|
||||
}
|
||||
|
||||
int meshEnd = checked(
|
||||
source.MeshPartOffset + source.MeshPartCount);
|
||||
if ((uint)source.MeshPartOffset
|
||||
> (uint)meshParts.Length
|
||||
|| (uint)meshEnd > (uint)meshParts.Length)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Packed entity {projection.Id} exceeds mesh "
|
||||
+ "part storage.");
|
||||
}
|
||||
|
||||
RenderInstanceCandidate candidate =
|
||||
RenderInstanceCandidate.FromFrame(
|
||||
in source,
|
||||
tupleLandblockId);
|
||||
ClassifyPackedEntity(
|
||||
in projection,
|
||||
in candidate,
|
||||
meshParts.Slice(
|
||||
source.MeshPartOffset,
|
||||
source.MeshPartCount),
|
||||
ref anyVao);
|
||||
}
|
||||
PackedRangeClassification classified =
|
||||
ClassifyPackedRange(
|
||||
in view,
|
||||
rangeIndex,
|
||||
tupleLandblockId,
|
||||
publishSelection: false);
|
||||
|
||||
bool deferTransparent = ShouldDeferPackedTransparent(
|
||||
anyVao,
|
||||
classified.AnyVao,
|
||||
_alphaQueue?.IsCollecting == true);
|
||||
InstanceLayoutCounts counts = PartitionInstanceGroups(
|
||||
_packedGroups.Values,
|
||||
|
|
@ -176,11 +105,236 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
_packedClassificationCache.EndFrame();
|
||||
}
|
||||
|
||||
internal void BeginPackedProductionFrame(
|
||||
in RenderFrameView view)
|
||||
{
|
||||
if (_packedProductionFrameOpen)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The packed dispatcher cannot begin a second production frame.");
|
||||
}
|
||||
|
||||
BeginPackedFrameStorage(in view);
|
||||
_packedProductionFrameOpen = true;
|
||||
_packedProductionGeneration = view.Generation;
|
||||
_packedProductionFrameSequence = view.FrameSequence;
|
||||
_packedProductionNextRange = 0;
|
||||
}
|
||||
|
||||
internal bool DrawPackedProductionRoute(
|
||||
ICamera camera,
|
||||
in RenderFrameView view,
|
||||
RenderFrameCandidateRoute route,
|
||||
int routeIndex,
|
||||
uint cellId,
|
||||
uint tupleLandblockId)
|
||||
{
|
||||
ValidatePackedProductionView(in view);
|
||||
ReadOnlySpan<RenderFrameCandidateRange> ranges =
|
||||
view.RouteRanges;
|
||||
if (_packedProductionNextRange >= ranges.Length)
|
||||
return false;
|
||||
|
||||
RenderFrameCandidateRange range =
|
||||
ranges[_packedProductionNextRange];
|
||||
if (range.Route != route
|
||||
|| range.RouteIndex != routeIndex
|
||||
|| range.CellId != cellId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int rangePosition = _packedProductionNextRange++;
|
||||
bool diagnosticsEnabled = BeginEntityDispatch(
|
||||
camera,
|
||||
out Matrix4x4 viewProjection,
|
||||
out Vector3 cameraWorldPosition);
|
||||
PackedRangeClassification classified =
|
||||
ClassifyPackedRange(
|
||||
in view,
|
||||
rangePosition,
|
||||
tupleLandblockId,
|
||||
publishSelection: true);
|
||||
ExecuteClassifiedGroups(
|
||||
viewProjection,
|
||||
cameraWorldPosition,
|
||||
classified.AnyVao,
|
||||
_packedGroups.Values,
|
||||
EntitySet.All,
|
||||
classified.CandidateCount,
|
||||
classified.MeshPartCount,
|
||||
diagnosticsEnabled,
|
||||
observeCurrentPath: false);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void CompletePackedProductionFrame(
|
||||
in RenderFrameView view)
|
||||
{
|
||||
ValidatePackedProductionView(in view);
|
||||
int expected = view.RouteRanges.Length;
|
||||
if (_packedProductionNextRange != expected)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The packed dispatcher did not consume the complete route stream: "
|
||||
+ $"consumed={_packedProductionNextRange} expected={expected}.");
|
||||
}
|
||||
|
||||
_packedClassificationCache.EndFrame();
|
||||
ResetPackedProductionFrame();
|
||||
}
|
||||
|
||||
internal void AbortPackedProductionFrame()
|
||||
{
|
||||
if (!_packedProductionFrameOpen)
|
||||
return;
|
||||
|
||||
_packedClassificationCache.EndFrame();
|
||||
ResetPackedProductionFrame();
|
||||
}
|
||||
|
||||
private void BeginPackedFrameStorage(
|
||||
in RenderFrameView view)
|
||||
{
|
||||
if (_packedGroupFrame == long.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Packed dispatcher frame identity was exhausted.");
|
||||
}
|
||||
|
||||
_packedGroupFrame++;
|
||||
PruneInstanceGroupsUnusedBeforeFrame(
|
||||
_packedGroups,
|
||||
_packedRetiredGroupKeys,
|
||||
_packedGroupFrame - 1);
|
||||
_packedEntityById.Clear();
|
||||
_packedSubmissions.Clear();
|
||||
_packedSelectionParts.Clear();
|
||||
_packedSelectionKeys.Clear();
|
||||
_packedClassificationCache.BeginFrame(view.Generation);
|
||||
|
||||
ReadOnlySpan<RenderFrameEntityCandidate> entities =
|
||||
view.EntityCandidates;
|
||||
for (int index = 0; index < entities.Length; index++)
|
||||
{
|
||||
if (!_packedEntityById.TryAdd(
|
||||
entities[index].Projection.Id,
|
||||
entities[index]))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Packed dispatcher received duplicate projection "
|
||||
+ $"{entities[index].Projection.Id}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PackedRangeClassification ClassifyPackedRange(
|
||||
in RenderFrameView view,
|
||||
int rangeIndex,
|
||||
uint tupleLandblockId,
|
||||
bool publishSelection)
|
||||
{
|
||||
ReadOnlySpan<RenderFrameCandidateRange> ranges =
|
||||
view.RouteRanges;
|
||||
if ((uint)rangeIndex >= (uint)ranges.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(rangeIndex));
|
||||
|
||||
_nextPackedInstanceSubmissionOrder = 0;
|
||||
foreach (InstanceGroup group in _packedGroups.Values)
|
||||
group.ClearPerInstanceData();
|
||||
|
||||
uint anyVao = 0;
|
||||
int meshPartCount = 0;
|
||||
RenderFrameCandidateRange range = ranges[rangeIndex];
|
||||
ReadOnlySpan<RenderProjectionRecord> routeCandidates =
|
||||
view.RouteCandidates;
|
||||
ReadOnlySpan<RenderFrameMeshPart> meshParts = view.MeshParts;
|
||||
int end = checked(range.Offset + range.Count);
|
||||
if ((uint)range.Offset > (uint)routeCandidates.Length
|
||||
|| (uint)end > (uint)routeCandidates.Length)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Packed dispatcher route {rangeIndex} exceeds candidate storage.");
|
||||
}
|
||||
|
||||
for (int candidateIndex = range.Offset;
|
||||
candidateIndex < end;
|
||||
candidateIndex++)
|
||||
{
|
||||
RenderProjectionRecord projection =
|
||||
routeCandidates[candidateIndex];
|
||||
if ((projection.Flags & RenderProjectionFlags.Draw) == 0)
|
||||
continue;
|
||||
if (!_packedEntityById.TryGetValue(
|
||||
projection.Id,
|
||||
out RenderFrameEntityCandidate source))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Packed route references absent entity {projection.Id}.");
|
||||
}
|
||||
|
||||
int meshEnd = checked(
|
||||
source.MeshPartOffset + source.MeshPartCount);
|
||||
if ((uint)source.MeshPartOffset > (uint)meshParts.Length
|
||||
|| (uint)meshEnd > (uint)meshParts.Length)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Packed entity {projection.Id} exceeds mesh part storage.");
|
||||
}
|
||||
|
||||
meshPartCount = checked(
|
||||
meshPartCount + source.MeshPartCount);
|
||||
RenderInstanceCandidate candidate =
|
||||
RenderInstanceCandidate.FromFrame(
|
||||
in source,
|
||||
tupleLandblockId);
|
||||
ClassifyPackedEntity(
|
||||
in projection,
|
||||
in candidate,
|
||||
meshParts.Slice(
|
||||
source.MeshPartOffset,
|
||||
source.MeshPartCount),
|
||||
ref anyVao,
|
||||
publishSelection);
|
||||
}
|
||||
|
||||
return new PackedRangeClassification(
|
||||
anyVao,
|
||||
range.Count,
|
||||
meshPartCount);
|
||||
}
|
||||
|
||||
private void ValidatePackedProductionView(
|
||||
in RenderFrameView view)
|
||||
{
|
||||
if (!_packedProductionFrameOpen
|
||||
|| view.Generation != _packedProductionGeneration
|
||||
|| view.FrameSequence != _packedProductionFrameSequence)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The packed dispatcher received a stale or foreign production frame.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetPackedProductionFrame()
|
||||
{
|
||||
_packedProductionFrameOpen = false;
|
||||
_packedProductionGeneration = default;
|
||||
_packedProductionFrameSequence = 0;
|
||||
_packedProductionNextRange = 0;
|
||||
}
|
||||
|
||||
private readonly record struct PackedRangeClassification(
|
||||
uint AnyVao,
|
||||
int CandidateCount,
|
||||
int MeshPartCount);
|
||||
|
||||
private void ClassifyPackedEntity(
|
||||
in RenderProjectionRecord projection,
|
||||
in RenderInstanceCandidate entity,
|
||||
ReadOnlySpan<RenderFrameMeshPart> meshParts,
|
||||
ref uint anyVao)
|
||||
ref uint anyVao,
|
||||
bool publishSelection)
|
||||
{
|
||||
(uint slot, bool culled) = ResolveSlotForFrame(
|
||||
_clipRoutingActive,
|
||||
|
|
@ -231,7 +385,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
slot,
|
||||
lights,
|
||||
indoor,
|
||||
selectionLighting);
|
||||
selectionLighting,
|
||||
publishSelection);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -272,6 +427,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
if (renderData is null)
|
||||
{
|
||||
reusableAcrossFrames = false;
|
||||
if (_missRequested.Add(meshRef.GfxObjId))
|
||||
_meshAdapter.EnsureLoaded(meshRef.GfxObjId);
|
||||
continue;
|
||||
}
|
||||
if (anyVao == 0)
|
||||
|
|
@ -291,6 +448,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
if (partData is null)
|
||||
{
|
||||
reusableAcrossFrames = false;
|
||||
if (_missRequested.Add(gfxObjId))
|
||||
_meshAdapter.EnsureLoaded(gfxObjId);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -334,7 +493,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
in entity,
|
||||
selectionPartIndex,
|
||||
(uint)gfxObjId,
|
||||
model);
|
||||
model,
|
||||
publishSelection);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -374,7 +534,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
in entity,
|
||||
partIndex,
|
||||
meshRef.GfxObjId,
|
||||
model);
|
||||
model,
|
||||
publishSelection);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -481,7 +642,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
uint slot,
|
||||
InstanceLightSet lights,
|
||||
bool indoor,
|
||||
Vector2 selectionLighting)
|
||||
Vector2 selectionLighting,
|
||||
bool publishSelection)
|
||||
{
|
||||
for (int i = 0; i < entry.Batches.Count; i++)
|
||||
{
|
||||
|
|
@ -504,7 +666,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
in entity,
|
||||
part.PartIndex,
|
||||
part.GfxObjId,
|
||||
part.RestPose * entity.RootWorld);
|
||||
part.RestPose * entity.RootWorld,
|
||||
publishSelection);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -593,13 +756,29 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
in RenderInstanceCandidate entity,
|
||||
int partIndex,
|
||||
uint gfxObjId,
|
||||
Matrix4x4 localToWorld)
|
||||
Matrix4x4 localToWorld,
|
||||
bool publishSelection)
|
||||
{
|
||||
if (_selectionSink is not IRetailSelectionRenderOracle oracle
|
||||
|| !_packedSelectionKeys.Add(new PackedSelectionKey(
|
||||
if (!_packedSelectionKeys.Add(new PackedSelectionKey(
|
||||
entity.LocalEntityId,
|
||||
partIndex,
|
||||
gfxObjId))
|
||||
gfxObjId)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (publishSelection)
|
||||
{
|
||||
_selectionSink?.AddVisiblePart(
|
||||
entity.ServerGuid,
|
||||
entity.LocalEntityId,
|
||||
partIndex,
|
||||
gfxObjId,
|
||||
localToWorld);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_selectionSink is not IRetailSelectionRenderOracle oracle
|
||||
|| !oracle.TryCreateVisiblePart(
|
||||
entity.ServerGuid,
|
||||
entity.LocalEntityId,
|
||||
|
|
|
|||
|
|
@ -1483,41 +1483,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
|
||||
|
|
@ -2058,11 +2027,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,
|
||||
|
|
@ -2075,7 +2104,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,
|
||||
|
|
@ -2084,8 +2113,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,
|
||||
|
|
@ -2202,15 +2231,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,
|
||||
|
|
@ -2421,6 +2450,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
|
||||
|
|
@ -2576,6 +2606,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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue