T4 (BR-6): ONE visibility gate - ACME BFS deleted from the frame, legacy second render path deleted
The one-gate rule (feedback_render_one_gate) is now structural: - The per-frame ACME BFS (CellVisibility.ComputeVisibilityFromRoot) is GONE from the frame. Its only production consumer was the cameraInsideCell boolean - which is exactly 'viewerRoot is not null' (the TryGetCell that produced viewerRoot already proves cells are loaded; ComputeVisibilityFromRoot returned null iff root was null). A full second visibility computation ran every frame to derive a boolean we already had. The method + its tests remain as quarantined non-production code (dual-live-visibility-computations, confirmed). - The clipRoot==null mini-pipeline is DELETED (legacy-outdoor-branch- remnant, adjusted-confirmed): the outdoor partition draw, the Chebyshev look-in gather, the DrawPortal invocation and the dynamics fallback. clipRoot is null only when NO viewer cell exists (pre-login, fly/debug cameras, transient gaps) - those frames draw flat through the dispatcher; every normal outdoor frame is the outdoor node. - DELETED with it: InteriorRenderer (class file - its only caller was the legacy branch), RetailPViewRenderer.DrawPortal + RetailPViewPortalDrawContext (the look-in product; outdoor-root frames flood buildings via MergeNearbyBuildingFloods inside DrawInside), the _exteriorPortal*/_outdoorRootNoCells fields. Per frame there is now exactly ONE visibility computation (PortalVisibilityBuilder) and ONE render path (DrawInside). Suites: build green, App 226 green, Core baseline (1398 + 4 pre-existing #99-era). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
a6aec8c32f
commit
4a307d33b5
3 changed files with 20 additions and 360 deletions
|
|
@ -169,7 +169,6 @@ public sealed class GameWindow : IDisposable
|
|||
// R1 (render redesign): the per-cell DrawInside flood + its per-frame entity partition.
|
||||
// _interiorRenderer is constructed once both renderers exist; _interiorPartition is rebuilt
|
||||
// each frame on an indoor root (null on the outdoor root).
|
||||
private AcDream.App.Rendering.InteriorRenderer? _interiorRenderer;
|
||||
private AcDream.App.Rendering.RetailPViewRenderer? _retailPViewRenderer;
|
||||
private AcDream.App.Rendering.PortalDepthMaskRenderer? _portalDepthMask;
|
||||
private AcDream.App.Rendering.InteriorEntityPartition.Result? _interiorPartition;
|
||||
|
|
@ -182,9 +181,6 @@ public sealed class GameWindow : IDisposable
|
|||
// three renderers so each re-binds binding=2 immediately before its own draw.
|
||||
// U.4 replaces the NoClip() frame with one built from the portal-visibility result.
|
||||
private ClipFrame? _clipFrame;
|
||||
private readonly HashSet<uint> _outdoorRootNoCells = new(0);
|
||||
private readonly HashSet<uint> _exteriorPortalLandblocks = new();
|
||||
private readonly List<LoadedCell> _exteriorPortalCandidateCells = new();
|
||||
|
||||
// Phase 3 (render unification, 2026-06-07): the synthetic outdoor cell node — the outdoor
|
||||
// world as a flood-graph cell (spec 2026-06-07-render-unification-outdoor-as-cell). Rebuilt
|
||||
|
|
@ -1840,8 +1836,6 @@ public sealed class GameWindow : IDisposable
|
|||
_gl, _wbMeshAdapter!.MeshManager!, _envCellFrustum);
|
||||
_envCellRenderer.Initialize(_meshShader!);
|
||||
|
||||
// R1: the per-cell DrawInside flood. Both renderers exist here (just constructed).
|
||||
_interiorRenderer = new AcDream.App.Rendering.InteriorRenderer(_envCellRenderer!, _wbDrawDispatcher!);
|
||||
_clipFrame ??= ClipFrame.NoClip();
|
||||
_retailPViewRenderer = new AcDream.App.Rendering.RetailPViewRenderer(
|
||||
_gl, _clipFrame, _envCellRenderer!, _wbDrawDispatcher!);
|
||||
|
|
@ -7314,8 +7308,13 @@ public sealed class GameWindow : IDisposable
|
|||
LoadedCell? viewerRoot = null;
|
||||
if (viewerCellId != 0u && _cellVisibility.TryGetCell(viewerCellId, out var viewerRegCell))
|
||||
viewerRoot = viewerRegCell;
|
||||
var visibility = _cellVisibility.ComputeVisibilityFromRoot(viewerRoot, viewerEyePos);
|
||||
bool cameraInsideCell = visibility?.CameraCell is not null;
|
||||
// T4 (BR-6): the per-frame ACME BFS (ComputeVisibilityFromRoot) is
|
||||
// DELETED from the frame — it ran a full second visibility
|
||||
// computation whose only production consumer was this boolean,
|
||||
// which is exactly "the viewer root resolved to a loaded interior
|
||||
// cell" (TryGetCell above already proves cells are loaded). The
|
||||
// PView flood is the ONE visibility gate (feedback_render_one_gate).
|
||||
bool cameraInsideCell = viewerRoot is not null;
|
||||
|
||||
// Retail render routing is owned by the collided camera/viewer cell.
|
||||
// The player cell still owns lighting state, but it must not force an
|
||||
|
|
@ -7732,114 +7731,19 @@ public sealed class GameWindow : IDisposable
|
|||
}
|
||||
else
|
||||
{
|
||||
if (_interiorRenderer is not null)
|
||||
{
|
||||
_outdoorRootNoCells.Clear();
|
||||
var outdoorPartition = AcDream.App.Rendering.InteriorEntityPartition.Partition(
|
||||
_outdoorRootNoCells, _worldState.LandblockEntries);
|
||||
sigOutdoorRootObjectCount = outdoorPartition.OutdoorStatic.Count;
|
||||
|
||||
// T1: static world first (shells + scenery)…
|
||||
if (outdoorPartition.OutdoorStatic.Count > 0)
|
||||
{
|
||||
_interiorRenderer.DrawEntityBucket(
|
||||
camera,
|
||||
frustum,
|
||||
playerLb,
|
||||
animatedIds,
|
||||
outdoorPartition.OutdoorStatic,
|
||||
visibleCellIds: null);
|
||||
}
|
||||
|
||||
_exteriorPortalLandblocks.Clear();
|
||||
_exteriorPortalCandidateCells.Clear();
|
||||
// FPS (2026-06-07): the outdoor look-in (DrawPortal -> BuildFromExterior) seeds only
|
||||
// from exit portals within MaxSeedDistance (48 m) of the camera. A landblock is 192 m,
|
||||
// so any cell that could seed is in the player's landblock or an immediate neighbour;
|
||||
// cells further out are already discarded by BuildFromExterior's per-portal cutoff.
|
||||
// Iterating EVERY cell in EVERY loaded landblock (near radius 4 = up to 81 LBs) just to
|
||||
// discard them is an O(all loaded cells) sweep every outdoor frame — the cause of the
|
||||
// "FPS drops as soon as I look out" report. Restrict candidates to the 1-ring around the
|
||||
// player (Chebyshev <= 1 in landblock grid). No behaviour change: the excluded cells are
|
||||
// all > 48 m away and were already culled by the seed-distance cutoff.
|
||||
int playerGridX = playerLb.HasValue ? (int)((playerLb.Value >> 24) & 0xFFu) : -1;
|
||||
int playerGridY = playerLb.HasValue ? (int)((playerLb.Value >> 16) & 0xFFu) : -1;
|
||||
foreach (var entry in _worldState.LandblockEntries)
|
||||
{
|
||||
uint lbPrefix = (entry.LandblockId >> 16) & 0xFFFFu;
|
||||
if (playerLb.HasValue)
|
||||
{
|
||||
int gX = (int)((lbPrefix >> 8) & 0xFFu);
|
||||
int gY = (int)(lbPrefix & 0xFFu);
|
||||
if (Math.Max(Math.Abs(gX - playerGridX), Math.Abs(gY - playerGridY)) > 1)
|
||||
continue;
|
||||
}
|
||||
if (!_exteriorPortalLandblocks.Add(lbPrefix))
|
||||
continue;
|
||||
|
||||
foreach (var cell in _cellVisibility.GetCellsForLandblock(lbPrefix))
|
||||
_exteriorPortalCandidateCells.Add(cell);
|
||||
}
|
||||
|
||||
if (_exteriorPortalCandidateCells.Count > 0 && _retailPViewRenderer is not null)
|
||||
{
|
||||
var portalResult = _retailPViewRenderer.DrawPortal(
|
||||
new AcDream.App.Rendering.RetailPViewPortalDrawContext
|
||||
{
|
||||
CandidateCells = _exteriorPortalCandidateCells,
|
||||
ViewerEyePos = viewerEyePos,
|
||||
ViewProjection = envCellViewProj,
|
||||
CellLookup = id => _cellVisibility.TryGetCell(id, out var c) ? c : null,
|
||||
Camera = camera,
|
||||
CameraWorldPosition = camPos,
|
||||
Frustum = frustum,
|
||||
PlayerLandblockId = playerLb,
|
||||
AnimatedEntityIds = animatedIds,
|
||||
RenderCenterLbX = renderCenterLbX,
|
||||
RenderCenterLbY = renderCenterLbY,
|
||||
RenderRadius = _nearRadius,
|
||||
MaxSeedDistance = 48f,
|
||||
LandblockEntries = _worldState.LandblockEntries,
|
||||
SetTerrainClipUbo = uboId => _terrain?.SetClipUbo(uboId),
|
||||
// T1: look-in — PUNCH building entry apertures to far-Z so
|
||||
// the flooded interior shows through the doorway. Safe:
|
||||
// dynamics draw after this whole block.
|
||||
DrawExitPortalMasks = sliceCtx =>
|
||||
DrawRetailPViewPortalDepthWrite(sliceCtx, envCellViewProj,
|
||||
forceFarZ: true),
|
||||
});
|
||||
|
||||
if (portalResult is not null)
|
||||
{
|
||||
sigOutdoorPortalDrawn = true;
|
||||
sigExteriorPvFrame = portalResult.PortalFrame;
|
||||
sigExteriorClipAssembly = portalResult.ClipAssembly;
|
||||
sigExteriorDrawableCells = portalResult.DrawableCells;
|
||||
sigExteriorPartition = portalResult.Partition;
|
||||
}
|
||||
}
|
||||
|
||||
// T1: …then ALL dynamics last (after the look-in punched +
|
||||
// drew interiors), depth-tested, never hard-clipped.
|
||||
if (outdoorPartition.Dynamics.Count > 0)
|
||||
{
|
||||
sigLiveDynamicDrawnCount = outdoorPartition.Dynamics.Count;
|
||||
_interiorRenderer.DrawEntityBucket(
|
||||
camera,
|
||||
frustum,
|
||||
playerLb,
|
||||
animatedIds,
|
||||
outdoorPartition.Dynamics,
|
||||
visibleCellIds: null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntries, frustum,
|
||||
neverCullLandblockId: playerLb,
|
||||
visibleCellIds: null,
|
||||
animatedEntityIds: animatedIds);
|
||||
}
|
||||
// T4 (BR-6): the old clipRoot==null mini-pipeline (outdoor
|
||||
// partition + Chebyshev look-in gather + DrawPortal + dynamics
|
||||
// fallback) is DELETED — it was the SECOND render path the
|
||||
// one-gate rule forbids (legacy-outdoor-branch-remnant,
|
||||
// adjusted-confirmed). clipRoot is null only when NO viewer
|
||||
// cell exists at all (pre-login, fly/debug cameras, transient
|
||||
// streaming gaps — the outdoor node covers every normal outdoor
|
||||
// frame): draw the world flat through the dispatcher; floods
|
||||
// resume the moment a viewer cell resolves.
|
||||
_wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntries, frustum,
|
||||
neverCullLandblockId: playerLb,
|
||||
visibleCellIds: null,
|
||||
animatedEntityIds: animatedIds);
|
||||
}
|
||||
|
||||
// Phase U.3: close the world-geometry clip bracket opened above. From here down the
|
||||
|
|
|
|||
|
|
@ -1,164 +0,0 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>Per-frame inputs for one <see cref="InteriorRenderer.DrawInside"/> flood.</summary>
|
||||
public sealed class InteriorRenderContext
|
||||
{
|
||||
/// <summary>Visible cells, closest-first (retail cell_draw_list). From PortalVisibilityFrame.</summary>
|
||||
public required IReadOnlyList<uint> OrderedVisibleCells { get; init; }
|
||||
|
||||
/// <summary>The cells the assembler mapped a clip slot for (ClipFrameAssembly.CellIdToSlot.Keys =
|
||||
/// the GameWindow envCellShellFilter). A cell may appear in <see cref="OrderedVisibleCells"/> but
|
||||
/// reduce to IsNothingVisible in the assembler (no slot) — those are skipped. This is the
|
||||
/// membership filter; <see cref="OrderedVisibleCells"/> supplies the draw ORDER.</summary>
|
||||
public required IReadOnlySet<uint> DrawableCells { get; init; }
|
||||
|
||||
/// <summary>Per-cell portal_view slots, in the same order retail setup_view(cell, i)
|
||||
/// selects them inside PView::DrawCells.</summary>
|
||||
public required IReadOnlyDictionary<uint, int[]> CellClipSlots { get; init; }
|
||||
|
||||
public required int OutdoorSlot { get; init; }
|
||||
public required bool OutdoorVisible { get; init; }
|
||||
|
||||
/// <summary>The 3-bucket entity split (<see cref="InteriorEntityPartition"/>). Only ByCell +
|
||||
/// LiveDynamic are used here; Outdoor scenery is drawn by the caller's landscape-through-door
|
||||
/// step (clipped to OutsideView).</summary>
|
||||
public required InteriorEntityPartition.Result Partition { get; init; }
|
||||
|
||||
public required ICamera Camera { get; init; }
|
||||
public required FrustumPlanes? Frustum { get; init; }
|
||||
|
||||
/// <summary>The full FFFF-suffixed landblock id of the player. Used as BOTH the synthetic
|
||||
/// per-cell entry id AND neverCullLandblockId so the degenerate (zero) synthetic AABB is never
|
||||
/// landblock-culled — per-entity frustum culling inside Draw still applies.</summary>
|
||||
public required uint? PlayerLandblockId { get; init; }
|
||||
|
||||
public required HashSet<uint>? AnimatedEntityIds { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The interior render flood, matching retail PView::DrawCells @ 0x005a4840:
|
||||
/// after the caller handles outside_view terrain + the depth-only clear, DrawCells
|
||||
/// walks cell_draw_list from the end back to zero in separate stages: cell shells,
|
||||
/// then each cell's object_list. The transparent shell pass is split out because
|
||||
/// the modern renderer batches opaque/transparent surfaces separately.
|
||||
/// </summary>
|
||||
public sealed class InteriorRenderer
|
||||
{
|
||||
private readonly EnvCellRenderer _envCells;
|
||||
private readonly WbDrawDispatcher _entities;
|
||||
|
||||
// Reused single-cell filter set — cleared + repopulated per cell to avoid per-frame allocs.
|
||||
private readonly HashSet<uint> _oneCell = new(1);
|
||||
public InteriorRenderer(EnvCellRenderer envCells, WbDrawDispatcher entities)
|
||||
{
|
||||
_envCells = envCells;
|
||||
_entities = entities;
|
||||
}
|
||||
|
||||
public void DrawInside(InteriorRenderContext ctx)
|
||||
{
|
||||
// Retail Loop 2: DrawEnvCell for each drawable cell, farthest-to-nearest
|
||||
// (cell_draw_list[cell_draw_num - 1] down to 0).
|
||||
for (int i = ctx.OrderedVisibleCells.Count - 1; i >= 0; i--)
|
||||
{
|
||||
uint cellId = ctx.OrderedVisibleCells[i];
|
||||
if (!TryBeginCell(ctx, cellId, out _)) continue;
|
||||
_oneCell.Clear();
|
||||
_oneCell.Add(cellId);
|
||||
ApplyMembershipOnlyRouting();
|
||||
_envCells.Render(WbRenderPass.Opaque, _oneCell);
|
||||
}
|
||||
|
||||
// Retail Loop 3: Render::PortalList = cell->portal_view; DrawObjCellForDummies(cell).
|
||||
for (int i = ctx.OrderedVisibleCells.Count - 1; i >= 0; i--)
|
||||
{
|
||||
uint cellId = ctx.OrderedVisibleCells[i];
|
||||
if (!TryBeginCell(ctx, cellId, out _)) continue;
|
||||
_oneCell.Clear();
|
||||
_oneCell.Add(cellId);
|
||||
if (ctx.Partition.ByCell.TryGetValue(cellId, out var cellEntities) && cellEntities.Count > 0)
|
||||
{
|
||||
ApplyMembershipOnlyRouting();
|
||||
DrawEntityBucket(ctx, cellEntities, visibleCellIds: _oneCell);
|
||||
}
|
||||
}
|
||||
|
||||
// Modern split of DrawEnvCell's transparent/additive batches, same reverse cell order.
|
||||
for (int i = ctx.OrderedVisibleCells.Count - 1; i >= 0; i--)
|
||||
{
|
||||
uint cellId = ctx.OrderedVisibleCells[i];
|
||||
if (!TryBeginCell(ctx, cellId, out _)) continue;
|
||||
_oneCell.Clear();
|
||||
_oneCell.Add(cellId);
|
||||
ApplyMembershipOnlyRouting();
|
||||
_envCells.Render(WbRenderPass.Transparent, _oneCell);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryBeginCell(InteriorRenderContext ctx, uint cellId, out int[] slots)
|
||||
{
|
||||
if (ctx.DrawableCells.Contains(cellId))
|
||||
{
|
||||
ctx.CellClipSlots.TryGetValue(cellId, out slots!);
|
||||
slots ??= System.Array.Empty<int>();
|
||||
return true;
|
||||
}
|
||||
|
||||
slots = System.Array.Empty<int>();
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ApplyMembershipOnlyRouting()
|
||||
{
|
||||
// PView membership controls which cell shell/object bucket is visited.
|
||||
// Do not turn the 2D portal view into gl_ClipDistance for indoor meshes:
|
||||
// that slices avatars and shell triangles at stairs/doorways instead of
|
||||
// matching retail's DrawMesh view-check-then-draw behavior.
|
||||
_envCells.SetClipRouting(null);
|
||||
_entities.ClearClipRouting();
|
||||
}
|
||||
|
||||
// Draws one bucket of entities via the existing dispatcher, scoped to a synthetic single-entry
|
||||
// landblock list. visibleCellIds gates which entities pass the cell-membership walk (a single-cell
|
||||
// set for per-cell objects; null only for fallback/outdoor buckets where clip-slot routing owns cull).
|
||||
// The clip slot per entity comes from the SetClipRouting the caller installed (cellIdToSlot +
|
||||
// outdoorSlot + outdoorVisible) via ResolveEntitySlot.
|
||||
private void DrawEntityBucket(
|
||||
InteriorRenderContext ctx, IReadOnlyList<WorldEntity> bucket, HashSet<uint>? visibleCellIds)
|
||||
=> DrawEntityBucket(
|
||||
ctx.Camera,
|
||||
ctx.Frustum,
|
||||
ctx.PlayerLandblockId,
|
||||
ctx.AnimatedEntityIds,
|
||||
bucket,
|
||||
visibleCellIds);
|
||||
|
||||
public void DrawEntityBucket(
|
||||
ICamera camera,
|
||||
FrustumPlanes? frustum,
|
||||
uint? playerLandblockId,
|
||||
HashSet<uint>? animatedEntityIds,
|
||||
IReadOnlyList<WorldEntity> bucket,
|
||||
HashSet<uint>? visibleCellIds)
|
||||
{
|
||||
// LandblockId == neverCullLandblockId (PlayerLandblockId) ⇒ the degenerate (zero) AABB is
|
||||
// never landblock-frustum-culled; per-entity AABB culling inside Draw still applies.
|
||||
uint lbId = playerLandblockId ?? 0u;
|
||||
var entry = (lbId, Vector3.Zero, Vector3.Zero,
|
||||
(IReadOnlyList<WorldEntity>)bucket,
|
||||
(IReadOnlyDictionary<uint, WorldEntity>?)null);
|
||||
|
||||
_entities.Draw(
|
||||
camera,
|
||||
new[] { entry },
|
||||
frustum,
|
||||
neverCullLandblockId: playerLandblockId,
|
||||
visibleCellIds: visibleCellIds,
|
||||
animatedEntityIds: animatedEntityIds);
|
||||
}
|
||||
}
|
||||
|
|
@ -186,62 +186,6 @@ public sealed class RetailPViewRenderer
|
|||
}
|
||||
}
|
||||
|
||||
public RetailPViewFrameResult? DrawPortal(RetailPViewPortalDrawContext ctx)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(ctx);
|
||||
|
||||
var pvFrame = PortalVisibilityBuilder.BuildFromExterior(
|
||||
ctx.CandidateCells,
|
||||
ctx.ViewerEyePos,
|
||||
ctx.CellLookup,
|
||||
ctx.ViewProjection,
|
||||
ctx.MaxSeedDistance);
|
||||
|
||||
if (pvFrame.OrderedVisibleCells.Count == 0)
|
||||
{
|
||||
RestoreNoClip(ctx.SetTerrainClipUbo);
|
||||
return null;
|
||||
}
|
||||
|
||||
var clipAssembly = ClipFrameAssembler.Assemble(_clipFrame, pvFrame);
|
||||
UploadClipFrame(ctx.SetTerrainClipUbo);
|
||||
|
||||
var drawableCells = new HashSet<uint>(clipAssembly.CellIdToSlot.Keys);
|
||||
UseIndoorMembershipOnlyRouting();
|
||||
|
||||
_envCells.PrepareRenderBatches(
|
||||
ctx.ViewProjection,
|
||||
ctx.CameraWorldPosition,
|
||||
filter: drawableCells,
|
||||
centerLbX: ctx.RenderCenterLbX,
|
||||
centerLbY: ctx.RenderCenterLbY,
|
||||
renderRadius: ctx.RenderRadius);
|
||||
|
||||
var partition = InteriorEntityPartition.Partition(drawableCells, ctx.LandblockEntries);
|
||||
var result = new RetailPViewFrameResult
|
||||
{
|
||||
PortalFrame = pvFrame,
|
||||
ClipAssembly = clipAssembly,
|
||||
DrawableCells = drawableCells,
|
||||
Partition = partition,
|
||||
};
|
||||
|
||||
ctx.EmitDiagnostics?.Invoke(result);
|
||||
|
||||
// T1: look-in order — punch the apertures, then interior cells WHOLE,
|
||||
// then the looked-into building's per-cell statics. Dynamics are NOT
|
||||
// drawn here: they belong exclusively to the frame's single last
|
||||
// entity pass (the outdoor root's DrawDynamicsLast), which prevents
|
||||
// double-draws of entities inside looked-into buildings.
|
||||
var viewcone = ViewconeCuller.Build(clipAssembly, ctx.ViewProjection);
|
||||
DrawExitPortalMasks(ctx, pvFrame, clipAssembly, drawableCells);
|
||||
DrawEnvCellShells(pvFrame);
|
||||
DrawCellObjectLists(ctx, pvFrame, clipAssembly, drawableCells, partition, viewcone);
|
||||
RestoreNoClip(ctx.SetTerrainClipUbo);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void DrawLandscapeThroughOutsideView(
|
||||
RetailPViewDrawContext ctx,
|
||||
ClipFrameAssembly clipAssembly,
|
||||
|
|
@ -644,30 +588,6 @@ public sealed class RetailPViewDrawContext : IRetailPViewCellDrawContext
|
|||
public Action<RetailPViewFrameResult>? EmitDiagnostics { get; init; }
|
||||
}
|
||||
|
||||
public sealed class RetailPViewPortalDrawContext : IRetailPViewCellDrawContext
|
||||
{
|
||||
public required IEnumerable<LoadedCell> CandidateCells { get; init; }
|
||||
public required Vector3 ViewerEyePos { get; init; }
|
||||
public required Matrix4x4 ViewProjection { get; init; }
|
||||
public required Func<uint, LoadedCell?> CellLookup { get; init; }
|
||||
public required ICamera Camera { get; init; }
|
||||
public required Vector3 CameraWorldPosition { get; init; }
|
||||
public required FrustumPlanes? Frustum { get; init; }
|
||||
public required uint? PlayerLandblockId { get; init; }
|
||||
public required HashSet<uint>? AnimatedEntityIds { get; init; }
|
||||
public required int RenderCenterLbX { get; init; }
|
||||
public required int RenderCenterLbY { get; init; }
|
||||
public required int RenderRadius { get; init; }
|
||||
public required float MaxSeedDistance { get; init; }
|
||||
public required IEnumerable<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax,
|
||||
IReadOnlyList<WorldEntity> Entities,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)> LandblockEntries { get; init; }
|
||||
public required Action<uint> SetTerrainClipUbo { get; init; }
|
||||
public Action<RetailPViewCellSliceContext>? DrawExitPortalMasks { get; init; }
|
||||
public Action<RetailPViewCellSliceContext>? DrawCellParticles { get; init; }
|
||||
public Action<RetailPViewFrameResult>? EmitDiagnostics { get; init; }
|
||||
}
|
||||
|
||||
public sealed class RetailPViewFrameResult
|
||||
{
|
||||
public required PortalVisibilityFrame PortalFrame { get; init; }
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue