Campaign V slice V6e, last of three. Sky was the hardest of the four pairs
because it was the only one that still worked the way a 2004 shader works: a
dozen loose uniforms pushed one glUniform call at a time, and a texture bound to
unit 0 with a sampler object chosen per submesh. Vulkan GLSL has neither a
default uniform block nor a way to declare a bare sampler, so both had to move —
and the second one had a sting in it.
The uniforms go into a `SkyParams` std140 block at uniform binding 4, the new
pre-authorized constant in GpuBindingModel (1, 2 and 3 are SceneLighting, the
terrain clip block and terrain tiling; the contract test now proves the three
constants and that literal 2 do not collide). Three matrices are 192 bytes on
their own, so the 96-byte push-constant block was never in the running. The
block's member order IS its layout: std140 aligns a vec3 to 16 bytes while using
12, so each of the three lighting vectors is followed by the float that rides in
its pad word, which is why colours and per-surface scalars interleave rather
than grouping by meaning. SkyParamsLayoutTests asserts all twelve offsets and
the 256-byte size, because getting one member wrong would read the sun direction
as a colour with no compile error, no link error and no GL error to say so.
The texture is the interesting half. sky.frag now reads through the shared table
(ACDREAM_SAMPLE_2D), and a bindless handle BAKES its sampler — so the
per-submesh Repeat-versus-ClampToEdge choice, which used to be a glBindSampler
on unit 0, becomes which slot the submesh asks for. SkyRenderer interns one
handle per (texture, wrap) pair, exactly as ManagedGLTextureArray has done since
the world path went bindless, and exactly the shape Vulkan's table has, where an
entry is a combined image sampler. Same two SamplerCache objects, same wrap
behaviour, consulted once at interning instead of once per draw. A pleasant
consequence: the sky no longer touches texture unit 0, so the load-bearing
`BindSampler(0, 0)` restore at the end of the pass — there because the binding
was global state that would otherwise force ClampToEdge on the next renderer —
has nothing left to undo and is gone.
Gates. Release build clean; App tests 4,072 passed / 3 skipped (4,057 baseline,
plus the sentinel guard from the previous commit and fourteen sky-layout
assertions). Offline pixel gate against 95f8c25f: 18 px of 563,200 compared
(3.20e-05), inside the documented 15–23 px band.
That gate masks the sky for determinism, so it proves nothing about this commit
and the sky renderer has no automated pixel coverage at all. What was done
instead: a base-versus-head offline capture at ALL SEVEN day groups, built by
stashing the change and rebuilding so the two runs differ only in this commit.
Every pair matches in gradient, cloud sheet, horizon band and fog — including
day group 2's salmon cloud band and day group 6's green one, which between them
exercise texture sampling, per-vertex tint, blend mode and fog. Then 3/3
RENDERED on the desktop-witness repeat-connected gate.
That bounds the risk; it does not close it. The offline camera is fixed and
looks down, so a thin band of dome is all it ever sees: the sun and moon
(additive, high) and the rain cylinder (the one sky mesh that surrounds the
camera, and the one whose REPEAT wrap is most visible) remain unproven. Recorded
as user-gate debt in §5.1 alongside V2c's and V4e's particles — check it by
standing outside at dawn or dusk, and by standing in rain.
Manifest: 8/9 pairs compile. `terrain_modern` is the last production pair, and
it is blocked on V4d's content rather than on dialect — details in §5.5's slice
table. `mesh` has no consumer.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1150 lines
48 KiB
C#
1150 lines
48 KiB
C#
using System.Numerics;
|
|
using AcDream.Content;
|
|
using AcDream.App.Diagnostics;
|
|
using AcDream.App.Input;
|
|
using AcDream.App.Interaction;
|
|
using AcDream.App.Physics;
|
|
using AcDream.App.Rendering;
|
|
using AcDream.App.Rendering.Residency;
|
|
using AcDream.App.Rendering.Scene;
|
|
using AcDream.App.Rendering.Selection;
|
|
using AcDream.App.Rendering.Sky;
|
|
using AcDream.App.Rendering.Vfx;
|
|
using AcDream.App.Rendering.Wb;
|
|
using AcDream.App.Settings;
|
|
using AcDream.App.Streaming;
|
|
using AcDream.App.UI;
|
|
using AcDream.App.UI.Layout;
|
|
using AcDream.App.World;
|
|
using AcDream.Core.Audio;
|
|
using AcDream.Core.Items;
|
|
using AcDream.Core.Lighting;
|
|
using AcDream.Core.Physics;
|
|
using AcDream.Core.Plugins;
|
|
using AcDream.Core.Rendering;
|
|
using AcDream.Core.Selection;
|
|
using AcDream.Core.Vfx;
|
|
using AcDream.Core.World;
|
|
using AcDream.Runtime;
|
|
using AcDream.Runtime.Entities;
|
|
using AcDream.Runtime.World;
|
|
using DatReaderWriter;
|
|
using DatReaderWriter.DBObjs;
|
|
using Silk.NET.OpenGL;
|
|
using Silk.NET.Windowing;
|
|
|
|
namespace AcDream.App.Composition;
|
|
|
|
internal sealed record LivePresentationDependencies(
|
|
RuntimeOptions Options,
|
|
GL Gl,
|
|
IWindow Window,
|
|
object DatLock,
|
|
RuntimeSettingsController Settings,
|
|
HostQuiescenceGate HostQuiescence,
|
|
PhysicsEngine PhysicsEngine,
|
|
PhysicsDataCache PhysicsDataCache,
|
|
WorldGameState WorldGameState,
|
|
WorldEvents WorldEvents,
|
|
GameRuntime Runtime,
|
|
LiveEntityRuntimeSlot RuntimeSlot,
|
|
DeferredLiveEntityMotionRuntimeBindings MotionBindings,
|
|
DeferredEntityEffectAdvanceSource EffectAdvance,
|
|
EntityEffectPoseRegistry EffectPoses,
|
|
RemotePhysicsUpdater RemotePhysicsUpdater,
|
|
LocalPlayerShadowState LocalPlayerShadow,
|
|
LiveEntityAnimationRuntimeView<LiveEntityAnimationState> AnimatedEntities,
|
|
AnimationPresentationDiagnostics AnimationDiagnostics,
|
|
EntityClassificationCache ClassificationCache,
|
|
TranslucencyFadeManager TranslucencyFades,
|
|
RetailAlphaQueue RetailAlphaQueue,
|
|
CellVisibility CellVisibility,
|
|
LiveWorldOriginState WorldOrigin,
|
|
LocalPlayerIdentityState PlayerIdentity,
|
|
PointerPositionState PointerPosition,
|
|
PlayerApproachCompletionState PlayerApproachCompletions,
|
|
GameRenderResourceLifetime RenderResourceLifetime,
|
|
TransferableResourceSlot<PortalTunnelPresentation> PortalTunnelFallback,
|
|
AnimationHookRouter HookRouter,
|
|
IRenderFrameDiagnosticLog RenderDiagnosticLog,
|
|
WorldTimeService WorldTime,
|
|
DeferredCanonicalWorldEntityCountSource? DevWorldEntities,
|
|
DeferredRenderFrameDiagnosticsSource? DevFrameDiagnostics,
|
|
DeferredRenderFrameDiagnosticsSource UiFrameDiagnostics,
|
|
Action<string> Log,
|
|
Action<string>? Toast)
|
|
{
|
|
public SelectionState Selection => Runtime.ActionOwner.Selection;
|
|
|
|
public RuntimeEntityObjectLifetime EntityObjects =>
|
|
Runtime.EntityObjects;
|
|
|
|
public RuntimeWorldTransitState WorldTransit => Runtime.TransitOwner;
|
|
|
|
public RuntimeLocalPlayerMovementState PlayerController =>
|
|
Runtime.MovementOwner;
|
|
}
|
|
|
|
internal sealed record LivePresentationResult(
|
|
DeferredLiveEntityRuntimeComponentLifecycle ComponentLifecycle,
|
|
LiveEntityMotionRuntimeController MotionRuntime,
|
|
DeferredLiveEntityParentAcceptance ParentAcceptance,
|
|
EntitySpawnAdapter EntitySpawnAdapter,
|
|
EntityScriptActivator EntityScriptActivator,
|
|
RetailStaticAnimatingObjectScheduler StaticAnimationScheduler,
|
|
RuntimeWorldTransitState WorldTransit,
|
|
WorldGenerationAvailabilityState WorldAvailability,
|
|
GpuWorldState WorldState,
|
|
RenderSceneShadowRuntime? RenderSceneShadow,
|
|
LiveEntityRuntime LiveEntities,
|
|
ProjectileController ProjectileController,
|
|
LiveEntityProjectionWithdrawalController ProjectionWithdrawal,
|
|
LiveEntityLightController Lights,
|
|
LiveEntityAnimationScheduler AnimationScheduler,
|
|
LiveEntityAnimationPresenter AnimationPresenter,
|
|
EquippedChildRenderController EquippedChildren,
|
|
EntityEffectController EntityEffects,
|
|
LiveEntityPresentationController Presentation,
|
|
RemoteTeleportController RemoteTeleport,
|
|
WbDrawDispatcher DrawDispatcher,
|
|
RetailSelectionScene SelectionScene,
|
|
WorldSelectionQuery SelectionQuery,
|
|
SelectionInteractionController SelectionInteractions,
|
|
RetainedUiGameplayBinding? RetainedGameplay,
|
|
PaperdollViewportRenderer? PaperdollRenderer,
|
|
PaperdollFramePresenter? PaperdollPresenter,
|
|
CreatureAppraisalViewportRenderer? CreatureAppraisalRenderer,
|
|
CreatureAppraisalFramePresenter? CreatureAppraisalPresenter,
|
|
WbFrustum EnvCellFrustum,
|
|
EnvCellRenderer EnvCellRenderer,
|
|
LandblockPresentationPipeline LandblockPipeline,
|
|
ClipFrame ClipFrame,
|
|
PortalDepthMaskRenderer PortalDepthMask,
|
|
SkyRenderer SkyRenderer,
|
|
ParticleRenderer ParticleRenderer,
|
|
RenderFrameDiagnosticsController FrameDiagnostics,
|
|
LivePresentationRuntimeBindings RuntimeBindings,
|
|
DeferredLiveEntityLandblockLoadedSink LandblockLoaded);
|
|
|
|
internal interface IGameWindowLivePresentationPublication
|
|
{
|
|
void PublishLivePresentation(LivePresentationResult result);
|
|
}
|
|
|
|
internal enum LivePresentationCompositionPoint
|
|
{
|
|
CanonicalRuntimeCreated,
|
|
CanonicalRuntimeBound,
|
|
MotionRuntimeBound,
|
|
ProjectionVisibilityBound,
|
|
CorePresentationCreated,
|
|
EffectRoutingBound,
|
|
SelectionAndRadarBound,
|
|
RetainedGameplayBound,
|
|
PrivateCreatureViewportsCreated,
|
|
EnvironmentCellsCreated,
|
|
LandblockPipelineCreated,
|
|
PortalResourcesCreated,
|
|
SkyAndParticlesCreated,
|
|
DiagnosticsBound,
|
|
ResultPublished,
|
|
}
|
|
|
|
internal sealed class LivePresentationCompositionPhase
|
|
: ILivePresentationCompositionPhase<
|
|
GameWindowPlatformResult<GL, Silk.NET.Input.IInputContext>,
|
|
HostInputCameraResult,
|
|
ContentEffectsAudioResult,
|
|
SettingsDevToolsResult,
|
|
WorldRenderResult,
|
|
InteractionRetainedUiResult,
|
|
LivePresentationResult>
|
|
{
|
|
private readonly LivePresentationDependencies _dependencies;
|
|
private readonly IGameWindowLivePresentationPublication _publication;
|
|
private readonly Action<LivePresentationCompositionPoint>? _faultInjection;
|
|
|
|
public LivePresentationCompositionPhase(
|
|
LivePresentationDependencies dependencies,
|
|
IGameWindowLivePresentationPublication publication,
|
|
Action<LivePresentationCompositionPoint>? faultInjection = null)
|
|
{
|
|
_dependencies = dependencies
|
|
?? throw new ArgumentNullException(nameof(dependencies));
|
|
_publication = publication
|
|
?? throw new ArgumentNullException(nameof(publication));
|
|
_faultInjection = faultInjection;
|
|
}
|
|
|
|
public LivePresentationResult Compose(
|
|
GameWindowPlatformResult<GL, Silk.NET.Input.IInputContext> platform,
|
|
HostInputCameraResult host,
|
|
ContentEffectsAudioResult content,
|
|
SettingsDevToolsResult settings,
|
|
WorldRenderResult world,
|
|
InteractionRetainedUiResult interaction)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(platform);
|
|
ArgumentNullException.ThrowIfNull(host);
|
|
ArgumentNullException.ThrowIfNull(content);
|
|
ArgumentNullException.ThrowIfNull(settings);
|
|
ArgumentNullException.ThrowIfNull(world);
|
|
ArgumentNullException.ThrowIfNull(interaction);
|
|
if (!ReferenceEquals(_dependencies.Gl, platform.Graphics))
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Live-presentation dependencies do not match the ordered platform result.");
|
|
}
|
|
return ComposeCore(host, content, world, interaction);
|
|
}
|
|
|
|
private LivePresentationResult ComposeCore(
|
|
HostInputCameraResult host,
|
|
ContentEffectsAudioResult content,
|
|
WorldRenderResult world,
|
|
InteractionRetainedUiResult interaction)
|
|
{
|
|
LivePresentationDependencies d = _dependencies;
|
|
WorldRenderFoundation foundation = world.Foundation;
|
|
var scope = new CompositionAcquisitionScope();
|
|
LivePresentationRuntimeBindings? bindings = null;
|
|
bool bindingsOwnedByScope = false;
|
|
|
|
try
|
|
{
|
|
CompositionAcquisitionScope.CompositionAcquisitionLease<
|
|
RenderSceneShadowRuntime>? renderSceneShadowLease = null;
|
|
renderSceneShadowLease = scope.Acquire(
|
|
"render scene",
|
|
() => new RenderSceneShadowRuntime(
|
|
RenderSceneGeneration.FromRaw(1)),
|
|
static value => value.Dispose());
|
|
RenderSceneShadowRuntime? renderSceneShadow =
|
|
renderSceneShadowLease?.Resource;
|
|
|
|
var componentLifecycle =
|
|
new DeferredLiveEntityRuntimeComponentLifecycle();
|
|
var wbSpawnAdapter = new LandblockSpawnAdapter(foundation.MeshAdapter);
|
|
Setup? LoadPreparedSetup(uint sourceId)
|
|
{
|
|
if (!content.Dats.TryResolvePreferred(
|
|
sourceId,
|
|
out IDatDatabase? database,
|
|
out DatReaderWriter.Enums.DBObjType type)
|
|
|| type != DatReaderWriter.Enums.DBObjType.Setup)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return database.TryGet<Setup>(
|
|
sourceId,
|
|
out Setup? setup)
|
|
? setup
|
|
: null;
|
|
}
|
|
|
|
var setupResolver = new PreparedSetupResolver(
|
|
content.PreparedAssets,
|
|
LoadPreparedSetup,
|
|
message => Console.Error.WriteLine(
|
|
$"setup activation: {message}"));
|
|
|
|
AnimationSequencer SequencerFactory(WorldEntity entity)
|
|
{
|
|
if (setupResolver.TryResolve(
|
|
entity.SourceGfxObjOrSetupId,
|
|
out Setup? setup))
|
|
{
|
|
uint motionTableId = (uint)setup.DefaultMotionTable;
|
|
if (motionTableId != 0
|
|
&& content.Dats.Get<MotionTable>(motionTableId) is { } motionTable)
|
|
{
|
|
return new AnimationSequencer(
|
|
setup,
|
|
motionTable,
|
|
content.AnimationLoader);
|
|
}
|
|
|
|
return new AnimationSequencer(
|
|
setup,
|
|
new MotionTable(),
|
|
content.AnimationLoader);
|
|
}
|
|
|
|
return new AnimationSequencer(
|
|
new Setup(),
|
|
new MotionTable(),
|
|
NullAnimationLoader.Instance);
|
|
}
|
|
|
|
var entitySpawnAdapter = new EntitySpawnAdapter(
|
|
foundation.TextureCache,
|
|
SequencerFactory,
|
|
foundation.MeshAdapter);
|
|
EntityEffectController? entityEffects = null;
|
|
LiveEntityRuntime? liveEntities = null;
|
|
var staticRootCommitter = new StaticLiveRootCommitter(
|
|
d.RuntimeSlot,
|
|
d.PhysicsEngine.ShadowObjects,
|
|
d.WorldOrigin,
|
|
d.EffectPoses);
|
|
var staticResidency = new LiveStaticAnimationResidency(d.RuntimeSlot);
|
|
var staticAnimationScheduler =
|
|
new RetailStaticAnimatingObjectScheduler(
|
|
content.AnimationLoader,
|
|
content.AnimationHookFrames.Capture,
|
|
d.EffectPoses.Publish,
|
|
staticResidency.IsResident,
|
|
(entity, body) => _ = staticRootCommitter.Commit(entity, body),
|
|
staticResidency.ProjectionVersion);
|
|
|
|
ScriptActivationInfo? ResolveActivation(WorldEntity entity)
|
|
{
|
|
if (!setupResolver.TryResolve(
|
|
entity.SourceGfxObjOrSetupId,
|
|
out Setup? setup))
|
|
{
|
|
return null;
|
|
}
|
|
uint scriptId = setup.DefaultScript.DataId;
|
|
if (entity.IndexedPartTransforms.Count == 0)
|
|
{
|
|
var indexed = IndexedSetupPartPoseBuilder.Build(setup, entity);
|
|
entity.SetIndexedPartPoses(indexed.Poses, indexed.Available);
|
|
}
|
|
|
|
bool usesStaticAnimationWorkset = entity.ServerGuid == 0
|
|
|| (liveEntities?.TryGetRecord(
|
|
entity.ServerGuid,
|
|
out LiveEntityRecord liveRecord) == true
|
|
&& (liveRecord.FinalPhysicsState
|
|
& PhysicsStateFlags.Static) != 0);
|
|
return new ScriptActivationInfo(
|
|
scriptId,
|
|
entity.IndexedPartTransforms,
|
|
EntityEffectProfile.CreateDatStatic(setup),
|
|
entity.IndexedPartAvailable,
|
|
setup,
|
|
(uint)setup.DefaultAnimation,
|
|
usesStaticAnimationWorkset);
|
|
}
|
|
|
|
var entityScriptActivator = new EntityScriptActivator(
|
|
content.ScriptRunner,
|
|
content.ParticleSink,
|
|
d.EffectPoses,
|
|
ResolveActivation,
|
|
(ownerId, entity, profile) =>
|
|
entityEffects?.OnDatStaticEntityReady(ownerId, entity, profile),
|
|
ownerId =>
|
|
{
|
|
entityEffects?.OnDatStaticEntityRemoved(ownerId);
|
|
content.LightingSink.UnregisterOwner(ownerId);
|
|
d.TranslucencyFades.ClearEntity(ownerId);
|
|
},
|
|
(entity, info) => staticAnimationScheduler.Register(entity, info),
|
|
staticAnimationScheduler.Unregister,
|
|
(entity, info) => staticAnimationScheduler.Rebind(entity, info));
|
|
|
|
RuntimeWorldTransitState worldTransit = d.WorldTransit;
|
|
var worldAvailability =
|
|
new WorldGenerationAvailabilityState(worldTransit);
|
|
var worldState = new GpuWorldState(
|
|
wbSpawnAdapter,
|
|
d.ClassificationCache.InvalidateLandblock,
|
|
entityScriptActivator,
|
|
worldAvailability);
|
|
bindings = new LivePresentationRuntimeBindings();
|
|
if (d.DevWorldEntities is { } devWorldEntities)
|
|
{
|
|
bindings.Adopt(
|
|
"developer world-entity source",
|
|
devWorldEntities.BindOwned(worldState));
|
|
}
|
|
var resourceOwners =
|
|
new List<CompositeLiveEntityResourceLifecycle.Owner>
|
|
{
|
|
new(
|
|
entity =>
|
|
{
|
|
if (liveEntities is null
|
|
|| !liveEntities.TryGetRecordByLocalEntityId(
|
|
entity.Id,
|
|
out LiveEntityRecord record)
|
|
|| record.ProjectionKey is not { } key)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Live presentation registration requires an exact Runtime projection key.");
|
|
}
|
|
_ = entitySpawnAdapter.OnCreate(key, entity);
|
|
},
|
|
entity => _ = entitySpawnAdapter.OnRemove(entity)),
|
|
new(
|
|
entityScriptActivator.OnCreate,
|
|
entityScriptActivator.OnRemove),
|
|
};
|
|
if (renderSceneShadow is not null)
|
|
{
|
|
resourceOwners.Add(new(
|
|
renderSceneShadow.OnLiveResourceRegistered,
|
|
renderSceneShadow.OnLiveResourceUnregistered));
|
|
}
|
|
liveEntities = new LiveEntityRuntime(
|
|
worldState,
|
|
new CompositeLiveEntityResourceLifecycle(
|
|
[.. resourceOwners]),
|
|
componentLifecycle,
|
|
d.EntityObjects);
|
|
var liveRuntimeLease = scope.Own(
|
|
"canonical live-entity runtime",
|
|
liveEntities,
|
|
static runtime => runtime.Clear());
|
|
LiveRenderProjectionJournal? liveRenderProjections =
|
|
renderSceneShadow?.BindLiveRuntime(
|
|
liveEntities,
|
|
new GpuWorldRenderTraversalOrderSource(worldState));
|
|
Fault(LivePresentationCompositionPoint.CanonicalRuntimeCreated);
|
|
|
|
bindings.Adopt(
|
|
"canonical live-runtime slot",
|
|
d.RuntimeSlot.BindOwned(liveEntities));
|
|
Fault(LivePresentationCompositionPoint.CanonicalRuntimeBound);
|
|
|
|
var selectionInteractionSource =
|
|
new DeferredSelectionInteractionSource();
|
|
var motionRuntime = new LiveEntityMotionRuntimeController(
|
|
liveEntities,
|
|
d.PhysicsDataCache,
|
|
() => selectionInteractionSource.Current,
|
|
d.Selection,
|
|
d.WorldOrigin);
|
|
bindings.Adopt(
|
|
"live motion runtime",
|
|
d.MotionBindings.BindOwned(motionRuntime));
|
|
Fault(LivePresentationCompositionPoint.MotionRuntimeBound);
|
|
|
|
Action<LiveEntityRecord, bool> wbVisibility = (record, visible) =>
|
|
{
|
|
if (record.WorldEntity is { } entity)
|
|
entitySpawnAdapter.SetPresentationResident(entity, visible);
|
|
};
|
|
bindings.BindProjectionVisibility(
|
|
liveEntities,
|
|
wbVisibility,
|
|
"WB projection visibility");
|
|
if (liveRenderProjections is not null)
|
|
{
|
|
bindings.BindProjectionVisibility(
|
|
liveEntities,
|
|
liveRenderProjections.OnProjectionVisibilityChanged,
|
|
"shadow render projection visibility");
|
|
}
|
|
Action<LiveEntityRecord, bool> particleVisibility = (record, visible) =>
|
|
{
|
|
if (record.WorldEntity is { } entity)
|
|
content.ParticleSink.SetEntityPresentationVisible(entity.Id, visible);
|
|
};
|
|
bindings.BindProjectionVisibility(
|
|
liveEntities,
|
|
particleVisibility,
|
|
"particle projection visibility");
|
|
Fault(LivePresentationCompositionPoint.ProjectionVisibilityBound);
|
|
|
|
var projectileController = new ProjectileController(
|
|
liveEntities,
|
|
new DatProjectileSetupResolver(content.Dats, d.DatLock),
|
|
new EntityRootPosePublisher(d.EffectPoses),
|
|
d.WorldOrigin)
|
|
{
|
|
DiagnosticSink = message =>
|
|
Console.Error.WriteLine($"projectile: {message}"),
|
|
};
|
|
var projectionWithdrawal =
|
|
new LiveEntityProjectionWithdrawalController(
|
|
liveEntities,
|
|
projectileController,
|
|
d.WorldGameState,
|
|
d.WorldEvents,
|
|
d.PhysicsEngine.ShadowObjects,
|
|
d.EffectPoses,
|
|
d.LocalPlayerShadow);
|
|
var lightsLease = scope.Acquire(
|
|
"live-entity lights",
|
|
() => new LiveEntityLightController(
|
|
liveEntities,
|
|
d.EffectPoses,
|
|
content.LightingSink,
|
|
setupId => content.Dats.Get<Setup>(setupId)),
|
|
static value => value.Dispose());
|
|
var ordinaryPhysicsUpdater = new LiveEntityOrdinaryPhysicsUpdater(
|
|
d.EntityObjects.Physics,
|
|
d.MotionBindings.GetSetupCylinder);
|
|
var animationScheduler = new LiveEntityAnimationScheduler(
|
|
liveEntities,
|
|
d.PlayerIdentity,
|
|
d.RemotePhysicsUpdater,
|
|
ordinaryPhysicsUpdater,
|
|
projectileController,
|
|
new EntityRootPosePublisher(d.EffectPoses),
|
|
new AnimationHookCaptureSink(content.AnimationHookFrames));
|
|
var animationPresenter = new LiveEntityAnimationPresenter(
|
|
liveEntities,
|
|
staticAnimationScheduler,
|
|
d.EffectPoses,
|
|
new LiveAnimationPresentationContext(
|
|
liveEntities,
|
|
d.PlayerIdentity,
|
|
d.PlayerController),
|
|
d.AnimationDiagnostics,
|
|
d.Options.HidePartIndex);
|
|
var parentAcceptance = new DeferredLiveEntityParentAcceptance();
|
|
var equippedLease = scope.Acquire(
|
|
"equipped-child renderer",
|
|
() => new EquippedChildRenderController(
|
|
content.Dats,
|
|
d.DatLock,
|
|
d.EntityObjects.Objects,
|
|
liveEntities,
|
|
d.EffectPoses,
|
|
parentAcceptance.TryAccept,
|
|
(childRecord, positionVersion, projectionVersion) =>
|
|
projectionWithdrawal.WithdrawExact(
|
|
childRecord,
|
|
positionVersion,
|
|
projectionVersion,
|
|
d.PlayerIdentity.ServerGuid)),
|
|
static value => value.Dispose());
|
|
Fault(LivePresentationCompositionPoint.CorePresentationCreated);
|
|
|
|
var tableResolver = new PhysicsScriptTableResolver(
|
|
id => content.Dats.Get<PhysicsScriptTable>(id));
|
|
entityEffects = new EntityEffectController(
|
|
liveEntities,
|
|
content.ScriptRunner,
|
|
tableResolver,
|
|
d.EffectPoses,
|
|
(parentLocalId, partIndex) =>
|
|
equippedLease.Resource.FindChildLocalIdAtPart(
|
|
parentLocalId,
|
|
partIndex),
|
|
equippedLease.Resource.FindParentLocalId,
|
|
ownerId => content.Audio?.EntitySoundTables.Remove(ownerId),
|
|
(ownerId, soundTableDid) =>
|
|
{
|
|
content.Audio?.EntitySoundTables.Remove(ownerId);
|
|
if (soundTableDid is { } did)
|
|
content.Audio?.EntitySoundTables.Set(ownerId, did);
|
|
});
|
|
bindings.Adopt(
|
|
"entity-effect advance",
|
|
d.EffectAdvance.BindOwned(entityEffects));
|
|
entityEffects.DiagnosticSink = message =>
|
|
Console.Error.WriteLine($"vfx: {message}");
|
|
var partArrayLifecycle = new LiveEntityPartArrayLifecycle(
|
|
d.AnimatedEntities);
|
|
var presentationLease = scope.Acquire(
|
|
"live-entity presentation",
|
|
() => new LiveEntityPresentationController(
|
|
liveEntities,
|
|
d.PhysicsEngine.ShadowObjects,
|
|
entityEffects.PlayTypedFromHiddenTransition,
|
|
new LiveEntityPartArrayEnterWorldPort(
|
|
partArrayLifecycle.HandleEnterWorld),
|
|
equippedLease.Resource.SetDirectChildrenNoDraw,
|
|
d.MotionBindings.ClearTargetForHiddenEntity,
|
|
d.WorldOrigin.GetCenter),
|
|
static value => value.Dispose());
|
|
var remoteShadowPlacement = new RemoteShadowPlacementSynchronizer(
|
|
d.RemotePhysicsUpdater,
|
|
d.WorldOrigin);
|
|
var remoteTeleportPresentation =
|
|
new RemoteTeleportPlacementPresentation(presentationLease.Resource);
|
|
var remoteTeleportLease = scope.Acquire(
|
|
"remote teleport",
|
|
() => new RemoteTeleportController(
|
|
d.PhysicsEngine,
|
|
liveEntities,
|
|
d.MotionBindings.GetSetupCylinder,
|
|
d.WorldOrigin.CellLocalForSeed,
|
|
remoteShadowPlacement.Sync,
|
|
remoteTeleportPresentation.Complete,
|
|
remoteTeleportPresentation.Begin),
|
|
static value => value.Dispose());
|
|
bindings.BindProjectionPoseReady(
|
|
equippedLease.Resource,
|
|
lightsLease.Resource.OnAttachedPoseReady);
|
|
if (liveRenderProjections is not null)
|
|
{
|
|
bindings.BindProjectionPoseReady(
|
|
equippedLease.Resource,
|
|
liveRenderProjections.OnProjectionPoseReady);
|
|
bindings.BindProjectionRemoved(
|
|
equippedLease.Resource,
|
|
liveRenderProjections.OnProjectionRemoved);
|
|
}
|
|
bindings.Adopt(
|
|
"entity-effect animation hooks",
|
|
content.HookRegistrations.RegisterOwned(entityEffects));
|
|
Fault(LivePresentationCompositionPoint.EffectRoutingBound);
|
|
|
|
return CompletePresentation(
|
|
host,
|
|
content,
|
|
world,
|
|
interaction,
|
|
componentLifecycle,
|
|
motionRuntime,
|
|
parentAcceptance,
|
|
entitySpawnAdapter,
|
|
entityScriptActivator,
|
|
staticAnimationScheduler,
|
|
worldTransit,
|
|
worldAvailability,
|
|
worldState,
|
|
renderSceneShadow,
|
|
renderSceneShadowLease,
|
|
liveEntities,
|
|
projectileController,
|
|
projectionWithdrawal,
|
|
lightsLease,
|
|
animationScheduler,
|
|
animationPresenter,
|
|
equippedLease,
|
|
entityEffects,
|
|
presentationLease,
|
|
remoteTeleportLease,
|
|
selectionInteractionSource,
|
|
bindings,
|
|
scope,
|
|
ref bindingsOwnedByScope,
|
|
liveRuntimeLease);
|
|
}
|
|
catch (Exception failure)
|
|
{
|
|
if (bindings is not null && !bindingsOwnedByScope)
|
|
{
|
|
scope.Own(
|
|
"live-presentation runtime bindings",
|
|
bindings,
|
|
static value => value.Dispose());
|
|
bindingsOwnedByScope = true;
|
|
}
|
|
|
|
scope.RollbackAndThrow(failure);
|
|
throw new System.Diagnostics.UnreachableException();
|
|
}
|
|
}
|
|
|
|
private LivePresentationResult CompletePresentation(
|
|
HostInputCameraResult host,
|
|
ContentEffectsAudioResult content,
|
|
WorldRenderResult world,
|
|
InteractionRetainedUiResult interaction,
|
|
DeferredLiveEntityRuntimeComponentLifecycle componentLifecycle,
|
|
LiveEntityMotionRuntimeController motionRuntime,
|
|
DeferredLiveEntityParentAcceptance parentAcceptance,
|
|
EntitySpawnAdapter entitySpawnAdapter,
|
|
EntityScriptActivator entityScriptActivator,
|
|
RetailStaticAnimatingObjectScheduler staticAnimationScheduler,
|
|
RuntimeWorldTransitState worldTransit,
|
|
WorldGenerationAvailabilityState worldAvailability,
|
|
GpuWorldState worldState,
|
|
RenderSceneShadowRuntime? renderSceneShadow,
|
|
CompositionAcquisitionScope.CompositionAcquisitionLease<
|
|
RenderSceneShadowRuntime>? renderSceneShadowLease,
|
|
LiveEntityRuntime liveEntities,
|
|
ProjectileController projectileController,
|
|
LiveEntityProjectionWithdrawalController projectionWithdrawal,
|
|
CompositionAcquisitionScope.CompositionAcquisitionLease<LiveEntityLightController> lightsLease,
|
|
LiveEntityAnimationScheduler animationScheduler,
|
|
LiveEntityAnimationPresenter animationPresenter,
|
|
CompositionAcquisitionScope.CompositionAcquisitionLease<EquippedChildRenderController> equippedLease,
|
|
EntityEffectController entityEffects,
|
|
CompositionAcquisitionScope.CompositionAcquisitionLease<LiveEntityPresentationController> presentationLease,
|
|
CompositionAcquisitionScope.CompositionAcquisitionLease<RemoteTeleportController> remoteTeleportLease,
|
|
DeferredSelectionInteractionSource selectionInteractionSource,
|
|
LivePresentationRuntimeBindings bindings,
|
|
CompositionAcquisitionScope scope,
|
|
ref bool bindingsOwnedByScope,
|
|
CompositionAcquisitionScope.CompositionAcquisitionLease<LiveEntityRuntime> liveRuntimeLease)
|
|
{
|
|
LivePresentationDependencies d = _dependencies;
|
|
WorldRenderFoundation foundation = world.Foundation;
|
|
AlphaScratchBudgetProfile alphaScratchBudgets =
|
|
AlphaScratchBudgetProfile.Create(
|
|
d.Options.ResidencyBudgets.AlphaScratchBytes);
|
|
|
|
var selectionScene = new RetailSelectionScene(
|
|
new RetailSelectionGeometryCache(content.Dats, d.DatLock));
|
|
var dispatcherLease = scope.Acquire(
|
|
"WB draw dispatcher",
|
|
() => new WbDrawDispatcher(
|
|
d.Gl,
|
|
foundation.MeshShader,
|
|
foundation.TextureCache,
|
|
foundation.MeshAdapter,
|
|
entitySpawnAdapter,
|
|
foundation.Bindless,
|
|
d.ClassificationCache,
|
|
d.TranslucencyFades,
|
|
selectionScene,
|
|
d.RetailAlphaQueue,
|
|
alphaScratchBudgets.DispatcherBytes),
|
|
static value => value.Dispose());
|
|
var selectionQuery = new WorldSelectionQuery(
|
|
liveEntities,
|
|
d.EntityObjects.Objects,
|
|
selectionScene,
|
|
() => d.PlayerIdentity.ServerGuid,
|
|
interaction.LateBindings.SelectionCamera.Snapshot,
|
|
() => new Vector2(
|
|
d.PointerPosition.X,
|
|
d.PointerPosition.Y),
|
|
() => d.PlayerController.Controller is { } player
|
|
? new PlayerInteractionPose(player.CellId, player.Position)
|
|
: null,
|
|
d.MotionBindings.GetSetupCylinder,
|
|
setupId =>
|
|
{
|
|
lock (d.DatLock)
|
|
{
|
|
if (!content.Dats.TryGet<Setup>(setupId, out Setup? setup)
|
|
|| setup.SelectionSphere is not { } sphere)
|
|
{
|
|
return null;
|
|
}
|
|
return (sphere.Origin, sphere.Radius);
|
|
}
|
|
});
|
|
var radarSnapshotProvider = new RadarSnapshotProvider(
|
|
d.EntityObjects.Objects,
|
|
liveEntities,
|
|
() => liveEntities.Snapshots,
|
|
playerGuid: () => d.PlayerIdentity.ServerGuid,
|
|
playerYawRadians: () => d.PlayerController.Controller?.Yaw ?? 0f,
|
|
playerCellId: () => d.PlayerController.Controller?.CellId ?? 0u,
|
|
selectedGuid: () => d.Selection.SelectedObjectId,
|
|
coordinatesOnRadar: () => d.Settings.Gameplay.CoordinatesOnRadar,
|
|
uiLocked: () => d.Settings.Gameplay.LockUI,
|
|
spatialQuery: () => worldState);
|
|
bindings.Adopt(
|
|
"radar snapshot",
|
|
interaction.LateBindings.Radar.Bind(radarSnapshotProvider));
|
|
var selectionInteractions = new SelectionInteractionController(
|
|
d.Selection,
|
|
selectionQuery,
|
|
interaction.ItemInteraction,
|
|
new WorldSessionSelectionInteractionTransport(
|
|
() => interaction.LateBindings.Session.CurrentSession),
|
|
new PlayerInteractionMovementSink(
|
|
() => d.PlayerController.Controller,
|
|
d.PlayerApproachCompletions),
|
|
d.Toast,
|
|
d.PlayerApproachCompletions);
|
|
selectionInteractionSource.Bind(selectionInteractions);
|
|
bindings.Adopt(
|
|
"world selection",
|
|
interaction.LateBindings.Selection.Bind(
|
|
selectionQuery,
|
|
selectionInteractions));
|
|
Fault(LivePresentationCompositionPoint.SelectionAndRadarBound);
|
|
|
|
CompositionAcquisitionScope.CompositionAcquisitionLease<
|
|
RetainedUiGameplayBinding>? retainedGameplayLease = null;
|
|
if (interaction.RetainedUi is { } retainedUi)
|
|
{
|
|
retainedGameplayLease = scope.Acquire(
|
|
"retained gameplay binding",
|
|
() => RetainedUiGameplayBinding.Create(
|
|
retainedUi.Host.Root,
|
|
(item, x, y) =>
|
|
selectionInteractions.PlaceDraggedItem(item, x, y),
|
|
d.HostQuiescence),
|
|
static value => value.Dispose());
|
|
retainedGameplayLease.Resource.Attach();
|
|
}
|
|
Fault(LivePresentationCompositionPoint.RetainedGameplayBound);
|
|
dispatcherLease.Resource.AlphaToCoverage =
|
|
d.Settings.ResolvedQuality.AlphaToCoverage;
|
|
|
|
CompositionAcquisitionScope.CompositionAcquisitionLease<
|
|
PaperdollViewportRenderer>? paperdollLease = null;
|
|
PaperdollFramePresenter? paperdollPresenter = null;
|
|
if (interaction.RetainedUi?.Runtime.PaperdollViewportWidget is { } viewport
|
|
&& interaction.RetainedUi.Runtime.InventoryFrame is { } inventoryFrame)
|
|
{
|
|
paperdollLease = scope.Acquire(
|
|
"paperdoll viewport",
|
|
() => new PaperdollViewportRenderer(
|
|
d.Gl,
|
|
dispatcherLease.Resource,
|
|
foundation.SceneLighting,
|
|
foundation.TextureCache,
|
|
foundation.MeshAdapter),
|
|
static value => value.Dispose());
|
|
IUiViewportRenderer? previousRenderer = viewport.Renderer;
|
|
viewport.Renderer = paperdollLease.Resource;
|
|
bindings.AdoptRelease(
|
|
"paperdoll viewport target",
|
|
() =>
|
|
{
|
|
if (ReferenceEquals(viewport.Renderer, paperdollLease.Resource))
|
|
viewport.Renderer = previousRenderer;
|
|
});
|
|
paperdollPresenter = new PaperdollFramePresenter(
|
|
paperdollLease.Resource,
|
|
new RetailPaperdollFrameView(
|
|
viewport,
|
|
new PaperdollInventoryVisibility(inventoryFrame),
|
|
host.GpuDevice),
|
|
new RetailPaperdollDollFactory(
|
|
new LivePaperdollEntityLookup(liveEntities),
|
|
d.PlayerIdentity,
|
|
new RetailPaperdollPoseApplicator(
|
|
content.Dats,
|
|
content.AnimationLoader,
|
|
d.DatLock)));
|
|
}
|
|
|
|
CompositionAcquisitionScope.CompositionAcquisitionLease<
|
|
CreatureAppraisalViewportRenderer>? creatureAppraisalLease = null;
|
|
CreatureAppraisalFramePresenter? creatureAppraisalPresenter = null;
|
|
if (interaction.RetainedUi?.Runtime.CreatureAppraisalViewportWidget
|
|
is { } creatureViewport
|
|
&& interaction.RetainedUi.Runtime.ExaminationFrame
|
|
is { } examinationFrame
|
|
&& interaction.RetainedUi.Runtime.AppraisalController
|
|
is { } appraisalController)
|
|
{
|
|
creatureAppraisalLease = scope.Acquire(
|
|
"creature appraisal viewport",
|
|
() => new CreatureAppraisalViewportRenderer(
|
|
d.Gl,
|
|
dispatcherLease.Resource,
|
|
foundation.SceneLighting,
|
|
foundation.TextureCache,
|
|
foundation.MeshAdapter),
|
|
static value => value.Dispose());
|
|
IUiViewportRenderer? previousRenderer = creatureViewport.Renderer;
|
|
creatureViewport.Renderer = creatureAppraisalLease.Resource;
|
|
bindings.AdoptRelease(
|
|
"creature appraisal viewport target",
|
|
() =>
|
|
{
|
|
if (ReferenceEquals(
|
|
creatureViewport.Renderer,
|
|
creatureAppraisalLease.Resource))
|
|
{
|
|
creatureViewport.Renderer = previousRenderer;
|
|
}
|
|
});
|
|
creatureAppraisalPresenter = new CreatureAppraisalFramePresenter(
|
|
creatureAppraisalLease.Resource,
|
|
new RetailCreatureAppraisalFrameView(
|
|
creatureViewport,
|
|
examinationFrame,
|
|
appraisalController,
|
|
host.GpuDevice),
|
|
new RetailCreatureAppraisalCloneFactory(
|
|
new LiveCreatureAppraisalEntityLookup(liveEntities)));
|
|
}
|
|
Fault(LivePresentationCompositionPoint.PrivateCreatureViewportsCreated);
|
|
|
|
var envCellFrustum = new WbFrustum();
|
|
var envCellLease = scope.Acquire(
|
|
"environment-cell renderer",
|
|
() => new EnvCellRenderer(
|
|
d.Gl,
|
|
foundation.MeshAdapter.MeshManager!,
|
|
envCellFrustum),
|
|
static value => value.Dispose());
|
|
envCellLease.Resource.Initialize(foundation.MeshShader);
|
|
Fault(LivePresentationCompositionPoint.EnvironmentCellsCreated);
|
|
|
|
var landblockRenderPublisher = new LandblockRenderPublisher(
|
|
(landblockId, meshData, origin) =>
|
|
foundation.Terrain.AddLandblockWithMesh(
|
|
landblockId,
|
|
meshData,
|
|
origin),
|
|
foundation.Terrain.RemoveLandblock,
|
|
d.CellVisibility,
|
|
worldState,
|
|
prepareEnvCells: build => EnvCellMeshPreparationScheduler.Schedule(
|
|
build,
|
|
foundation.MeshAdapter.MeshManager!),
|
|
removeEnvCells: envCellLease.Resource.RemoveLandblock,
|
|
envCellPublisher: envCellLease.Resource);
|
|
var landblockPhysicsPublisher = new LandblockPhysicsPublisher(
|
|
d.EntityObjects.Physics,
|
|
world.TerrainBuild.HeightTable);
|
|
var landblockStaticPublisher =
|
|
new LandblockStaticPresentationPublisher(
|
|
content.LightingSink,
|
|
d.TranslucencyFades,
|
|
d.WorldGameState,
|
|
d.WorldEvents);
|
|
var landblockRetirementOwner =
|
|
new LandblockPresentationRetirementOwner(
|
|
landblockRenderPublisher,
|
|
landblockPhysicsPublisher,
|
|
landblockStaticPublisher,
|
|
content.LightingSink,
|
|
d.TranslucencyFades);
|
|
var landblockLoaded = new DeferredLiveEntityLandblockLoadedSink();
|
|
var landblockPipeline = new LandblockPresentationPipeline(
|
|
landblockRenderPublisher,
|
|
landblockPhysicsPublisher,
|
|
landblockStaticPublisher,
|
|
worldState,
|
|
landblockRetirementOwner,
|
|
landblockLoaded.OnLandblockLoaded,
|
|
landblockRenderPublisher.PrepareAfterRenderPins,
|
|
renderSceneShadow?.StaticProjections);
|
|
Fault(LivePresentationCompositionPoint.LandblockPipelineCreated);
|
|
|
|
var clipFrameLease = scope.Acquire(
|
|
"portal clip frame",
|
|
ClipFrame.NoClip,
|
|
static value => value.Dispose());
|
|
var portalDepthLease = scope.Acquire(
|
|
"portal depth mask",
|
|
() => new PortalDepthMaskRenderer(d.Gl),
|
|
static value => value.Dispose());
|
|
CompositionAcquisitionScope.CompositionAcquisitionLease<
|
|
PortalWaitNoticeController>? portalWaitNoticeLease = null;
|
|
if (interaction.RetainedUi is { } portalRetainedUi)
|
|
{
|
|
portalWaitNoticeLease = scope.Acquire(
|
|
"portal wait notice",
|
|
() => new PortalWaitNoticeController(
|
|
portalRetainedUi.Host.Root),
|
|
static value => value.Dispose());
|
|
}
|
|
Action<string?>? displayPortalWaitNotice =
|
|
portalWaitNoticeLease is { } waitNoticeLease
|
|
? waitNoticeLease.Resource.Set
|
|
: null;
|
|
PortalTunnelPresentation portalTunnel;
|
|
try
|
|
{
|
|
portalTunnel = d.PortalTunnelFallback.AcquirePrepared(
|
|
() => PortalTunnelPresentation.CreateRequired(
|
|
d.Gl,
|
|
content.Dats,
|
|
content.AnimationLoader,
|
|
d.HookRouter,
|
|
dispatcherLease.Resource,
|
|
foundation.SceneLighting,
|
|
foundation.MeshAdapter,
|
|
displayPortalWaitNotice,
|
|
portalWaitNoticeLease?.Resource),
|
|
static tunnel => tunnel.PrepareResources());
|
|
}
|
|
catch (Exception acquisitionFailure)
|
|
{
|
|
try
|
|
{
|
|
d.PortalTunnelFallback.ReleaseFallback();
|
|
}
|
|
catch (Exception cleanupFailure)
|
|
{
|
|
throw new AggregateException(
|
|
"Portal-tunnel construction and fallback rollback both failed.",
|
|
acquisitionFailure,
|
|
cleanupFailure);
|
|
}
|
|
|
|
throw;
|
|
}
|
|
var portalTunnelLease = scope.Own(
|
|
"portal tunnel fallback",
|
|
portalTunnel,
|
|
_ => d.PortalTunnelFallback.ReleaseFallback());
|
|
Fault(LivePresentationCompositionPoint.PortalResourcesCreated);
|
|
|
|
AcDream.App.Rendering.Shader skyShader =
|
|
d.RenderResourceLifetime.AcquireSkyShader(
|
|
() => new AcDream.App.Rendering.Shader(
|
|
d.Gl,
|
|
Path.Combine(foundation.ShadersDirectory, "sky.vert"),
|
|
Path.Combine(foundation.ShadersDirectory, "sky.frag"),
|
|
// Campaign V slice V6e: sky reads the shared texture table and
|
|
// ACDREAM_UBO_SET now, both of which common.glsl declares.
|
|
includeCommonPreamble: true));
|
|
var skyShaderLease = scope.Own(
|
|
"sky shader lifetime",
|
|
skyShader,
|
|
_ => d.RenderResourceLifetime.ReleaseSkyShader());
|
|
var skyLease = scope.Acquire(
|
|
"sky renderer",
|
|
() => new SkyRenderer(
|
|
d.Gl,
|
|
content.Dats,
|
|
skyShader,
|
|
foundation.TextureCache,
|
|
foundation.Samplers,
|
|
// Slice V6e: the sky samples through the binding=9 handle table,
|
|
// so it needs the same bindless entry point the world path uses.
|
|
foundation.Bindless),
|
|
static value => value.Dispose());
|
|
var particleLease = scope.Acquire(
|
|
"particle renderer",
|
|
() => new ParticleRenderer(
|
|
d.Gl,
|
|
foundation.ShadersDirectory,
|
|
content.ParticleSystem,
|
|
foundation.TextureCache,
|
|
content.Dats,
|
|
foundation.MeshAdapter,
|
|
d.RetailAlphaQueue,
|
|
alphaScratchBudgets.ParticleBytes),
|
|
static value => value.Dispose());
|
|
Fault(LivePresentationCompositionPoint.SkyAndParticlesCreated);
|
|
|
|
IRenderFrameResourceDiagnosticsSource? resourceDiagnostics =
|
|
d.Options.UiProbeDump
|
|
? new RuntimeRenderFrameResourceDiagnosticsSource(
|
|
content.ParticleSystem,
|
|
content.ParticleSink,
|
|
dispatcherLease.Resource,
|
|
envCellLease.Resource,
|
|
particleLease.Resource,
|
|
interaction.RetainedUi?.Host.TextRenderer,
|
|
portalDepthLease.Resource,
|
|
clipFrameLease.Resource,
|
|
foundation.Terrain,
|
|
foundation.SceneLighting,
|
|
foundation.MeshAdapter,
|
|
foundation.TextureCache,
|
|
content.PreparedAssets)
|
|
: null;
|
|
var frameDiagnostics = new RenderFrameDiagnosticsController(
|
|
new RuntimeRenderFrameTitleFactsSource(
|
|
worldState,
|
|
d.AnimatedEntities,
|
|
d.WorldTime),
|
|
new SilkRenderFrameTitleSink(d.Window),
|
|
d.RenderDiagnosticLog,
|
|
d.Options.UiProbeDump,
|
|
resourceDiagnostics);
|
|
if (d.DevFrameDiagnostics is { } devFrameDiagnostics)
|
|
{
|
|
bindings.Adopt(
|
|
"developer frame diagnostics",
|
|
devFrameDiagnostics.BindOwned(frameDiagnostics));
|
|
}
|
|
bindings.Adopt(
|
|
"retained-UI frame diagnostics",
|
|
d.UiFrameDiagnostics.BindOwned(frameDiagnostics));
|
|
Fault(LivePresentationCompositionPoint.DiagnosticsBound);
|
|
|
|
var bindingsLease = scope.Own(
|
|
"live-presentation runtime bindings",
|
|
bindings,
|
|
static value => value.Dispose());
|
|
bindingsOwnedByScope = true;
|
|
var result = new LivePresentationResult(
|
|
componentLifecycle,
|
|
motionRuntime,
|
|
parentAcceptance,
|
|
entitySpawnAdapter,
|
|
entityScriptActivator,
|
|
staticAnimationScheduler,
|
|
worldTransit,
|
|
worldAvailability,
|
|
worldState,
|
|
renderSceneShadow,
|
|
liveEntities,
|
|
projectileController,
|
|
projectionWithdrawal,
|
|
lightsLease.Resource,
|
|
animationScheduler,
|
|
animationPresenter,
|
|
equippedLease.Resource,
|
|
entityEffects,
|
|
presentationLease.Resource,
|
|
remoteTeleportLease.Resource,
|
|
dispatcherLease.Resource,
|
|
selectionScene,
|
|
selectionQuery,
|
|
selectionInteractions,
|
|
retainedGameplayLease?.Resource,
|
|
paperdollLease?.Resource,
|
|
paperdollPresenter,
|
|
creatureAppraisalLease?.Resource,
|
|
creatureAppraisalPresenter,
|
|
envCellFrustum,
|
|
envCellLease.Resource,
|
|
landblockPipeline,
|
|
clipFrameLease.Resource,
|
|
portalDepthLease.Resource,
|
|
skyLease.Resource,
|
|
particleLease.Resource,
|
|
frameDiagnostics,
|
|
bindings,
|
|
landblockLoaded);
|
|
foundation.Residency.RegisterDomainSource(
|
|
new DelegateResidencyDomainSource(
|
|
ResidencyDomain.AlphaScratch,
|
|
() => new ResidencyDomainSnapshot(
|
|
ResidencyDomain.AlphaScratch,
|
|
EntryCount: 3,
|
|
OwnerCount: 3,
|
|
Charges: new ResidencyCharges(
|
|
ScratchBytes: checked(
|
|
d.RetailAlphaQueue.RetainedScratchBytes
|
|
+ dispatcherLease.Resource.RetainedAlphaScratchBytes
|
|
+ particleLease.Resource.RetainedAlphaScratchBytes)),
|
|
BudgetBytes: alphaScratchBudgets.TotalBytes)));
|
|
_publication.PublishLivePresentation(result);
|
|
|
|
liveRuntimeLease.Transfer();
|
|
renderSceneShadowLease?.Transfer();
|
|
lightsLease.Transfer();
|
|
equippedLease.Transfer();
|
|
presentationLease.Transfer();
|
|
remoteTeleportLease.Transfer();
|
|
dispatcherLease.Transfer();
|
|
retainedGameplayLease?.Transfer();
|
|
paperdollLease?.Transfer();
|
|
creatureAppraisalLease?.Transfer();
|
|
envCellLease.Transfer();
|
|
clipFrameLease.Transfer();
|
|
portalDepthLease.Transfer();
|
|
portalTunnelLease.Transfer();
|
|
portalWaitNoticeLease?.Transfer();
|
|
skyLease.Transfer();
|
|
skyShaderLease.Transfer();
|
|
particleLease.Transfer();
|
|
bindingsLease.Transfer();
|
|
Fault(LivePresentationCompositionPoint.ResultPublished);
|
|
scope.Complete();
|
|
return result;
|
|
}
|
|
|
|
private void Fault(LivePresentationCompositionPoint point) =>
|
|
_faultInjection?.Invoke(point);
|
|
|
|
private sealed class NullAnimationLoader : IAnimationLoader
|
|
{
|
|
public static NullAnimationLoader Instance { get; } = new();
|
|
public Animation? LoadAnimation(uint id) => null;
|
|
}
|
|
|
|
private sealed class DeferredSelectionInteractionSource
|
|
{
|
|
public SelectionInteractionController? Current { get; private set; }
|
|
|
|
public void Bind(SelectionInteractionController value)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(value);
|
|
if (Current is not null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
"Live motion selection interactions are already bound.");
|
|
}
|
|
Current = value;
|
|
}
|
|
}
|
|
}
|