fix(render): harden portal exit handoff
This commit is contained in:
parent
ddbd7e4096
commit
82e4b4cb6d
17 changed files with 896 additions and 119 deletions
|
|
@ -415,6 +415,23 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
? revealMeshes.SetDestinationRevealUploadPriority
|
||||
: static _ => { },
|
||||
foundation.TextureCache.SetDestinationRevealUploadPriority);
|
||||
IRenderFrameResourceDiagnosticsSource? revealResourceDiagnostics =
|
||||
StreamingDiagnostics.ProbeRevealTiming
|
||||
? new RuntimeRenderFrameResourceDiagnosticsSource(
|
||||
particles: null,
|
||||
particleBindings: null,
|
||||
worldDispatcher: revealDispatcher,
|
||||
environmentCells: null,
|
||||
particleRenderer: null,
|
||||
uiTextRenderer: null,
|
||||
portalDepthMask: null,
|
||||
clipFrame: null,
|
||||
terrain: null,
|
||||
lighting: null,
|
||||
meshes: foundation.MeshAdapter,
|
||||
textures: foundation.TextureCache,
|
||||
preparedAssets: content.PreparedAssets)
|
||||
: null;
|
||||
var worldReveal = new WorldRevealCoordinator(
|
||||
live.WorldTransit,
|
||||
// #280: read the radii LIVE from the streaming controller rather
|
||||
|
|
@ -450,7 +467,8 @@ internal sealed class SessionPlayerCompositionPhase
|
|||
worldQuiescence,
|
||||
streaming,
|
||||
revealRenderResources,
|
||||
() => live.WorldState.LoadedLandblockCount);
|
||||
() => live.WorldState.LoadedLandblockCount,
|
||||
revealResourceDiagnostics);
|
||||
Fault(SessionPlayerCompositionPoint.WorldRevealCreated);
|
||||
|
||||
return CompleteSessionPlayer(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.App.Streaming;
|
||||
using AcDream.App.UI;
|
||||
using AcDream.Content.Vfx;
|
||||
using AcDream.Core.Lighting;
|
||||
|
|
@ -96,6 +97,7 @@ public sealed class PortalTunnelPresentation : IDisposable
|
|||
private float _rotationEndAngle;
|
||||
private float _rotationCurrentAngle;
|
||||
private bool _waitCueVisible;
|
||||
private bool _probeFreezeReached;
|
||||
private bool _disposeRequested;
|
||||
private bool _disposing;
|
||||
private bool _disposed;
|
||||
|
|
@ -256,6 +258,7 @@ public sealed class PortalTunnelPresentation : IDisposable
|
|||
_rotationCurrentAngle = 0f;
|
||||
_camera.DirectionDegrees = 0f;
|
||||
_waitCueVisible = false;
|
||||
_probeFreezeReached = false;
|
||||
_visible = true;
|
||||
RebuildPose();
|
||||
}
|
||||
|
|
@ -267,6 +270,7 @@ public sealed class PortalTunnelPresentation : IDisposable
|
|||
return;
|
||||
_visible = false;
|
||||
_waitCueVisible = false;
|
||||
_probeFreezeReached = false;
|
||||
_animationHooks.Clear();
|
||||
_sequence.ClearAnimations();
|
||||
}
|
||||
|
|
@ -275,11 +279,25 @@ public sealed class PortalTunnelPresentation : IDisposable
|
|||
{
|
||||
if (!_visible || dt < 0f)
|
||||
return;
|
||||
if (_probeFreezeReached)
|
||||
return;
|
||||
|
||||
_sequence.Update(dt, frame: null);
|
||||
RebuildPose();
|
||||
_animationHooks.Drain(Vector3.Zero);
|
||||
TickRotation(dt);
|
||||
|
||||
if (StreamingDiagnostics.TunnelFreezeFrame is not { } freezeFrame
|
||||
|| CurrentAnimationFrame < freezeFrame)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_probeFreezeReached = true;
|
||||
Console.WriteLine(
|
||||
$"[tunnel-freeze] frame={CurrentAnimationFrame} "
|
||||
+ $"target={freezeFrame} setup=0x{_setupDid:X8} "
|
||||
+ $"animation=0x{_animationDid:X8} state=held");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -128,9 +128,10 @@ internal readonly record struct ProcessResourceDiagnostics(
|
|||
int TrackedGpuTextures);
|
||||
|
||||
/// <summary>
|
||||
/// Immutable resource facts captured only when explicit UI-probe dumping is enabled.
|
||||
/// Grouping keeps the diagnostics controller independent from every canonical renderer,
|
||||
/// VFX, mesh, texture, and process owner used to produce the values.
|
||||
/// Immutable resource facts captured only by an explicit low-frequency
|
||||
/// diagnostic such as UI-probe dumping or reveal timing. Grouping keeps the
|
||||
/// consumers independent from every canonical renderer, VFX, mesh, texture,
|
||||
/// and process owner used to produce the values.
|
||||
/// </summary>
|
||||
internal readonly record struct RenderFrameResourceDiagnosticsSnapshot(
|
||||
VfxStreamResourceDiagnostics Vfx,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ public sealed class TeleportViewPlaneController
|
|||
public const float TransitionViewPlaneDistance = 0.001f;
|
||||
|
||||
private float _gameViewPlaneDistance = 1f;
|
||||
private TeleportAnimState _state = TeleportAnimState.Off;
|
||||
private readonly ProjectionOverrideCamera _projectionCamera = new();
|
||||
|
||||
public bool Enabled { get; private set; }
|
||||
|
|
@ -36,6 +37,7 @@ public sealed class TeleportViewPlaneController
|
|||
|
||||
_gameViewPlaneDistance = distance;
|
||||
CurrentViewPlaneDistance = distance;
|
||||
_state = TeleportAnimState.Off;
|
||||
Enabled = false;
|
||||
}
|
||||
|
||||
|
|
@ -48,6 +50,7 @@ public sealed class TeleportViewPlaneController
|
|||
/// </summary>
|
||||
public void Update(TeleportAnimSnapshot snapshot)
|
||||
{
|
||||
_state = snapshot.State;
|
||||
switch (snapshot.State)
|
||||
{
|
||||
case TeleportAnimState.WorldFadeOut:
|
||||
|
|
@ -80,6 +83,7 @@ public sealed class TeleportViewPlaneController
|
|||
|
||||
public void Reset()
|
||||
{
|
||||
_state = TeleportAnimState.Off;
|
||||
Enabled = false;
|
||||
CurrentViewPlaneDistance = _gameViewPlaneDistance;
|
||||
}
|
||||
|
|
@ -110,13 +114,35 @@ public sealed class TeleportViewPlaneController
|
|||
|
||||
float distance = MathF.Max(CurrentViewPlaneDistance, TransitionViewPlaneDistance);
|
||||
float fov = 2f * MathF.Atan(1f / distance);
|
||||
float near = MathF.Max(0.1f, distance * 0.25f);
|
||||
float near = WorldTransitionNearPlane(distance);
|
||||
if (near >= far)
|
||||
near = MathF.Min(0.1f, far * 0.5f);
|
||||
|
||||
return Matrix4x4.CreatePerspectiveFieldOfView(fov, aspect, near, far);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retail's <c>set_vdst</c> keeps <c>znear</c> at 0.1 m below a view-plane
|
||||
/// distance of 0.4. The legacy landscape path still supplied a complete
|
||||
/// projected screen at the singular teleport endpoint. Vulkan clips the
|
||||
/// finite resident terrain geometrically: at distance 0.001, every lower-
|
||||
/// viewport ground ray hits the terrain before 0.1 m and the authored sky
|
||||
/// is exposed underneath it. Scale the near plane with the transition only
|
||||
/// while the world viewport owns the frame. This preserves the exact retail
|
||||
/// X/Y warp, the tunnel projection, and the ordinary game projection while
|
||||
/// giving the modern terrain path the coverage retail visibly produced.
|
||||
/// </summary>
|
||||
private float WorldTransitionNearPlane(float distance)
|
||||
{
|
||||
if (_state is TeleportAnimState.WorldFadeOut or TeleportAnimState.WorldFadeIn
|
||||
&& distance < 0.4f)
|
||||
{
|
||||
return MathF.Max(0.0001f, distance * 0.25f);
|
||||
}
|
||||
|
||||
return MathF.Max(0.1f, distance * 0.25f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Decorate the active camera with the same projection returned by
|
||||
/// <see cref="Apply(Matrix4x4)"/>. Retail's <c>Render::set_vdst</c> is
|
||||
|
|
|
|||
|
|
@ -555,7 +555,20 @@ internal sealed class LocalPlayerTeleportPresentation
|
|||
var (snapshot, events) = _animation.Tick(
|
||||
deltaSeconds,
|
||||
worldReady,
|
||||
CurrentTunnelFrame);
|
||||
CurrentTunnelFrame,
|
||||
holdInTunnel: StreamingDiagnostics.TunnelFreezeFrame.HasValue);
|
||||
|
||||
// Retail hides portal space before publishing the WorldFadeIn view
|
||||
// plane (gmSmartBoxUI::UseTime @ 0x004D73D3). Keep that viewport
|
||||
// swap inside the presentation update: the controller's exit event
|
||||
// performs host work, and allowing the terminal projection to escape
|
||||
// while the tunnel remains visible exposes the finite portal mesh as
|
||||
// a one-frame faceted disk. Hiding first is also safe if a render
|
||||
// boundary lands between these two writes: the destination world is
|
||||
// already materialized and receives the old outgoing projection.
|
||||
if (!snapshot.ShowTunnel && _tunnel.IsVisible)
|
||||
_tunnel.Exit();
|
||||
|
||||
_viewPlane.Update(snapshot);
|
||||
return (snapshot, events);
|
||||
}
|
||||
|
|
@ -1228,16 +1241,19 @@ internal sealed class LocalPlayerTeleportController
|
|||
return;
|
||||
break;
|
||||
case TeleportAnimEvent.PlayExitSound:
|
||||
// gmSmartBoxUI::UseTime @ 0x004D6E30 releases destination
|
||||
// cell blocking at the exact portal/world viewport swap.
|
||||
// LoginComplete remains one WorldFadeIn second later.
|
||||
// Retail first hides portal space, then shows the world,
|
||||
// and only afterwards plays Sound_UI_ExitPortal
|
||||
// (gmSmartBoxUI::UseTime @ 0x004D73D3..0x004D7405).
|
||||
// ExitTunnel is idempotent: the production presentation
|
||||
// normally retired it before publishing this snapshot,
|
||||
// while this call enforces the same order for every host.
|
||||
_presentation.ExitTunnel();
|
||||
if (!IsCurrentLifetime(generation, sequence))
|
||||
return;
|
||||
_worldReveal.RevealWorldViewport();
|
||||
if (!IsCurrentLifetime(generation, sequence))
|
||||
return;
|
||||
_presentation.PlayExitCue();
|
||||
if (!IsCurrentLifetime(generation, sequence))
|
||||
return;
|
||||
_presentation.ExitTunnel();
|
||||
if (!IsCurrentLifetime(generation, sequence))
|
||||
return;
|
||||
break;
|
||||
|
|
@ -1707,17 +1723,16 @@ internal sealed class LocalPlayerTeleportController
|
|||
return;
|
||||
break;
|
||||
case TeleportAnimEvent.PlayExitSound:
|
||||
// Release destination cell blocking at the exact
|
||||
// portal/world viewport swap — same edge as the teleport
|
||||
// pump (gmSmartBoxUI::UseTime @ 0x004D6E30), with
|
||||
// Sound_UI_ExitPortal @ 0x004D7405.
|
||||
// Identical retail viewport order to the teleport pump:
|
||||
// hide portal, show/release the destination world, then
|
||||
// play Sound_UI_ExitPortal @ 0x004D7405.
|
||||
_presentation.ExitTunnel();
|
||||
if (!IsCurrentLoginLifetime(generation, revealGeneration))
|
||||
return;
|
||||
_worldReveal.RevealWorldViewport();
|
||||
if (!IsCurrentLoginLifetime(generation, revealGeneration))
|
||||
return;
|
||||
_presentation.PlayExitCue();
|
||||
if (!IsCurrentLoginLifetime(generation, revealGeneration))
|
||||
return;
|
||||
_presentation.ExitTunnel();
|
||||
if (!IsCurrentLoginLifetime(generation, revealGeneration))
|
||||
return;
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Diagnostics;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.Runtime;
|
||||
|
||||
namespace AcDream.App.Streaming;
|
||||
|
|
@ -20,7 +21,9 @@ namespace AcDream.App.Streaming;
|
|||
/// shape.</item>
|
||||
/// <item>1 Hz progress — elapsed, the three flags, and the resident
|
||||
/// landblock count, so a budget-paced linear drip is visually obvious in the
|
||||
/// log.</item>
|
||||
/// log. A paired <c>[reveal-resource]</c> snapshot attributes the cold render
|
||||
/// barrier to mesh preparation/upload, arena growth, texture composites, or
|
||||
/// process/GPU residency without sampling those owners every frame.</item>
|
||||
/// <item><c>SUMMARY</c> once at the viewport reveal — the per-edge
|
||||
/// timeline on one line.</item>
|
||||
/// </list>
|
||||
|
|
@ -31,6 +34,7 @@ namespace AcDream.App.Streaming;
|
|||
internal sealed class RevealTimingProbe
|
||||
{
|
||||
private readonly Func<int>? _loadedLandblockCount;
|
||||
private readonly IRenderFrameResourceDiagnosticsSource? _renderResources;
|
||||
private readonly Stopwatch _clock = new();
|
||||
private long _generation;
|
||||
private string _kind = "";
|
||||
|
|
@ -49,8 +53,13 @@ internal sealed class RevealTimingProbe
|
|||
private long _lastProgressMs;
|
||||
private int _framesSinceProgress;
|
||||
|
||||
public RevealTimingProbe(Func<int>? loadedLandblockCount) =>
|
||||
public RevealTimingProbe(
|
||||
Func<int>? loadedLandblockCount,
|
||||
IRenderFrameResourceDiagnosticsSource? renderResources = null)
|
||||
{
|
||||
_loadedLandblockCount = loadedLandblockCount;
|
||||
_renderResources = renderResources;
|
||||
}
|
||||
|
||||
public void Begin(
|
||||
string kind,
|
||||
|
|
@ -80,6 +89,7 @@ internal sealed class RevealTimingProbe
|
|||
+ $"cell=0x{destinationCell:X8} window={window.NearRadius}/"
|
||||
+ $"{window.FarRadius} landblocks={_windowLandblocks} "
|
||||
+ $"loaded={_loadedLandblockCount?.Invoke() ?? -1}");
|
||||
EmitResourceSnapshot("begin", elapsedMilliseconds: 0);
|
||||
}
|
||||
|
||||
public void Observe(
|
||||
|
|
@ -91,37 +101,56 @@ internal sealed class RevealTimingProbe
|
|||
|
||||
_framesSinceProgress++;
|
||||
long elapsed = _clock.ElapsedMilliseconds;
|
||||
string? resourceCheckpoint = null;
|
||||
if (!_render && readiness.IsRenderNeighborhoodReady)
|
||||
{
|
||||
_render = true;
|
||||
_renderMs = elapsed;
|
||||
Edge("render-ready", elapsed);
|
||||
resourceCheckpoint = AppendCheckpoint(
|
||||
resourceCheckpoint,
|
||||
"render-ready");
|
||||
}
|
||||
if (!_composites && readiness.AreCompositeTexturesReady)
|
||||
{
|
||||
_composites = true;
|
||||
_compositesMs = elapsed;
|
||||
Edge("composites-ready", elapsed);
|
||||
resourceCheckpoint = AppendCheckpoint(
|
||||
resourceCheckpoint,
|
||||
"composites-ready");
|
||||
}
|
||||
if (!_collision && readiness.IsCollisionReady)
|
||||
{
|
||||
_collision = true;
|
||||
_collisionMs = elapsed;
|
||||
Edge("collision-ready", elapsed);
|
||||
resourceCheckpoint = AppendCheckpoint(
|
||||
resourceCheckpoint,
|
||||
"collision-ready");
|
||||
}
|
||||
if (!_gateReady && readiness.IsReady)
|
||||
{
|
||||
_gateReady = true;
|
||||
_gateReadyMs = elapsed;
|
||||
Edge("gate-ready", elapsed);
|
||||
resourceCheckpoint = AppendCheckpoint(
|
||||
resourceCheckpoint,
|
||||
"gate-ready");
|
||||
}
|
||||
if (!_materialized && portal.Materialized)
|
||||
{
|
||||
_materialized = true;
|
||||
_materializedMs = elapsed;
|
||||
Edge("materialized", elapsed);
|
||||
resourceCheckpoint = AppendCheckpoint(
|
||||
resourceCheckpoint,
|
||||
"materialized");
|
||||
}
|
||||
|
||||
if (resourceCheckpoint is not null)
|
||||
EmitResourceSnapshot(resourceCheckpoint, elapsed);
|
||||
|
||||
if (!_summarized && portal.WorldViewportObserved)
|
||||
{
|
||||
_summarized = true;
|
||||
|
|
@ -132,6 +161,7 @@ internal sealed class RevealTimingProbe
|
|||
+ $"gateReadyMs={_gateReadyMs} "
|
||||
+ $"materializedMs={_materializedMs} "
|
||||
+ $"landblocks={_windowLandblocks}");
|
||||
EmitResourceSnapshot("summary", elapsed);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -151,6 +181,7 @@ internal sealed class RevealTimingProbe
|
|||
+ $"loaded={_loadedLandblockCount?.Invoke() ?? -1}"
|
||||
+ $"/{_windowLandblocks} "
|
||||
+ $"frames={_framesSinceProgress}");
|
||||
EmitResourceSnapshot("progress", elapsed);
|
||||
PublicationTimingProbe.EmitStreamingTickWindow();
|
||||
_framesSinceProgress = 0;
|
||||
}
|
||||
|
|
@ -162,4 +193,77 @@ internal sealed class RevealTimingProbe
|
|||
+ $"elapsedMs={elapsed} "
|
||||
+ $"loaded={_loadedLandblockCount?.Invoke() ?? -1}"
|
||||
+ $"/{_windowLandblocks}");
|
||||
|
||||
private void EmitResourceSnapshot(string checkpoint, long elapsedMilliseconds)
|
||||
{
|
||||
if (_renderResources is null)
|
||||
return;
|
||||
|
||||
RenderFrameResourceDiagnosticsSnapshot snapshot =
|
||||
_renderResources.Capture();
|
||||
Console.WriteLine(FormatResourceLine(
|
||||
checkpoint,
|
||||
_kind,
|
||||
_generation,
|
||||
elapsedMilliseconds,
|
||||
snapshot));
|
||||
}
|
||||
|
||||
private static string AppendCheckpoint(string? current, string next) =>
|
||||
current is null ? next : current + "+" + next;
|
||||
|
||||
internal static string FormatResourceLine(
|
||||
string checkpoint,
|
||||
string kind,
|
||||
long generation,
|
||||
long elapsedMilliseconds,
|
||||
in RenderFrameResourceDiagnosticsSnapshot snapshot)
|
||||
{
|
||||
MeshStreamResourceDiagnostics mesh = snapshot.Mesh;
|
||||
TextureStreamResourceDiagnostics textures = snapshot.Textures;
|
||||
ProcessResourceDiagnostics process = snapshot.Process;
|
||||
return $"[reveal-resource] checkpoint={checkpoint} kind={kind} "
|
||||
+ $"gen={generation} elapsedMs={elapsedMilliseconds} "
|
||||
+ $"meshData={mesh.RenderData} meshAtlases={mesh.AtlasArrays} "
|
||||
+ $"meshUnusedLru={mesh.UnusedLru} meshBytes={mesh.EstimatedBytes} "
|
||||
+ $"globalUploads={mesh.GlobalUploadCount} "
|
||||
+ $"globalUploadBytes={mesh.GlobalUploadedBytes} "
|
||||
+ $"frameUploads={mesh.FrameUploadCount} "
|
||||
+ $"frameUploadBytes={mesh.FrameUploadBytes} "
|
||||
+ $"frameArrayBytes={mesh.FrameArrayAllocationBytes} "
|
||||
+ $"frameMipmapBytes={mesh.FrameMipmapBytes} "
|
||||
+ $"frameBufferUploadBytes={mesh.FrameBufferUploadBytes} "
|
||||
+ $"frameBufferAllocationBytes={mesh.FrameBufferAllocationBytes} "
|
||||
+ $"frameBufferCopyBytes={mesh.FrameBufferCopyBytes} "
|
||||
+ $"frameNewArrays={mesh.FrameNewArrayCount} "
|
||||
+ $"frameNewBuffers={mesh.FrameNewBufferCount} "
|
||||
+ $"frameStaleDiscards={mesh.FrameStaleDiscardCount} "
|
||||
+ $"frameMipmapArrays={mesh.FrameMipmapArrayCount} "
|
||||
+ $"staged={mesh.StagedUploadBacklog} "
|
||||
+ $"stagedBytes={mesh.StagedUploadBytes} "
|
||||
+ $"stagingHighWater={(mesh.StagingAtHighWater ? 1 : 0)} "
|
||||
+ $"cpuMeshCache={mesh.CpuMeshCacheCount} "
|
||||
+ $"cpuMeshCacheBytes={mesh.CpuMeshCacheBytes} "
|
||||
+ $"arenaCapacityBytes={mesh.GlobalCapacityBytes} "
|
||||
+ $"arenaPhysicalBytes={mesh.GlobalPhysicalCapacityBytes} "
|
||||
+ $"arenaMigrating={(mesh.GlobalMigrationInProgress ? 1 : 0)} "
|
||||
+ $"prepared={mesh.PreparedProbes}/{mesh.PreparedReads}/"
|
||||
+ $"{mesh.PreparedLoaded}/{mesh.PreparedMissing}/"
|
||||
+ $"{mesh.PreparedCorrupt} "
|
||||
+ $"ownedTextures={textures.OwnedBindlessTextures} "
|
||||
+ $"textureOwners={textures.TextureOwners} "
|
||||
+ $"composites={textures.CachedCompositeTextures} "
|
||||
+ $"unownedComposites={textures.CachedUnownedComposites} "
|
||||
+ $"unownedCompositeBytes={textures.CachedUnownedCompositeBytes} "
|
||||
+ $"compositeAtlases={textures.CompositeAtlases} "
|
||||
+ $"compositeAtlasBytes={textures.CompositeAtlasBytes} "
|
||||
+ $"compositePending={textures.CompositeWarmupPending} "
|
||||
+ $"frameCompositeUploads={textures.FrameCompositeUploadCount} "
|
||||
+ $"frameCompositeUploadBytes={textures.FrameCompositeUploadBytes} "
|
||||
+ $"managedBytes={process.ManagedBytes} "
|
||||
+ $"managedCommittedBytes={process.ManagedCommittedBytes} "
|
||||
+ $"trackedGpuBytes={process.TrackedGpuBytes} "
|
||||
+ $"trackedGpuBuffers={process.TrackedGpuBuffers} "
|
||||
+ $"trackedGpuTextures={process.TrackedGpuTextures}";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Globalization;
|
||||
using System;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Streaming;
|
||||
|
||||
|
|
@ -11,6 +12,8 @@ namespace AcDream.App.Streaming;
|
|||
/// </summary>
|
||||
internal static class StreamingDiagnostics
|
||||
{
|
||||
internal const int DefaultTunnelFreezeFrame = 72;
|
||||
|
||||
/// <summary>
|
||||
/// #280 A/B measurement probe. When set, the outdoor reveal gate uses this
|
||||
/// landblock radius instead of the derived streaming window, so the same
|
||||
|
|
@ -65,6 +68,20 @@ internal static class StreamingDiagnostics
|
|||
public static bool ProbeRevealTiming { get; } =
|
||||
Environment.GetEnvironmentVariable("ACDREAM_PROBE_REVEAL_TIMING") == "1";
|
||||
|
||||
/// <summary>
|
||||
/// #419 RenderDoc apparatus. When present, the teleport sequencer remains
|
||||
/// in retail's stable <c>Tunnel</c> state even after the destination is
|
||||
/// ready, and the portal-space animation stops once it reaches this frame.
|
||||
/// <c>ACDREAM_PROBE_TUNNEL_FREEZE=1</c> selects the representative frame
|
||||
/// <see cref="DefaultTunnelFreezeFrame"/>; an explicit frame in the range
|
||||
/// 2..<see cref="TeleportAnimSequencer.TunnelEndFrame"/> may be supplied
|
||||
/// instead. This intentionally prevents placement and viewport reveal
|
||||
/// until the transition is cancelled or the process exits. It is
|
||||
/// inspection apparatus, not a user setting.
|
||||
/// </summary>
|
||||
public static int? TunnelFreezeFrame { get; } = ParseTunnelFreezeFrame(
|
||||
Environment.GetEnvironmentVariable("ACDREAM_PROBE_TUNNEL_FREEZE"));
|
||||
|
||||
/// <summary>
|
||||
/// The floor is 1, not 0. An outdoor destination's acknowledgement must
|
||||
/// carry <c>RequiredRenderRadius >= 1</c> or
|
||||
|
|
@ -78,4 +95,23 @@ internal static class StreamingDiagnostics
|
|||
&& value >= 1
|
||||
? value
|
||||
: null;
|
||||
|
||||
internal static int? ParseTunnelFreezeFrame(string? raw)
|
||||
{
|
||||
if (string.Equals(raw, "1", StringComparison.Ordinal)
|
||||
|| string.Equals(raw, "true", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return DefaultTunnelFreezeFrame;
|
||||
}
|
||||
|
||||
return int.TryParse(
|
||||
raw,
|
||||
NumberStyles.Integer,
|
||||
CultureInfo.InvariantCulture,
|
||||
out int frame)
|
||||
&& frame >= 2
|
||||
&& frame <= TeleportAnimSequencer.TunnelEndFrame
|
||||
? frame
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using AcDream.App.Rendering;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.World;
|
||||
|
||||
|
|
@ -85,7 +86,8 @@ internal sealed class WorldRevealCoordinator
|
|||
WorldGenerationQuiescence? quiescence = null,
|
||||
IWorldRevealStreamingScheduler? streaming = null,
|
||||
IWorldRevealRenderResourceScheduler? renderResources = null,
|
||||
Func<int>? loadedLandblockCount = null)
|
||||
Func<int>? loadedLandblockCount = null,
|
||||
IRenderFrameResourceDiagnosticsSource? renderResourceDiagnostics = null)
|
||||
{
|
||||
_transit = transit ?? throw new ArgumentNullException(nameof(transit));
|
||||
_readiness = new WorldRevealReadinessBarrier(
|
||||
|
|
@ -101,7 +103,11 @@ internal sealed class WorldRevealCoordinator
|
|||
_streaming = streaming;
|
||||
_renderResources = renderResources;
|
||||
if (StreamingDiagnostics.ProbeRevealTiming)
|
||||
_timing = new RevealTimingProbe(loadedLandblockCount);
|
||||
{
|
||||
_timing = new RevealTimingProbe(
|
||||
loadedLandblockCount,
|
||||
renderResourceDiagnostics);
|
||||
}
|
||||
}
|
||||
|
||||
public RuntimePortalSnapshot Snapshot => _transit.Snapshot;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue