Vulkan is the sole, user-signed-off backend (V10 landed) and step 1 already removed ImGui/Studio/DevTools. This step deletes the GL rendering backend itself: every Gpu/Gl/** implementation, the Wb ManagedGL*/GLHelpers/GLSLShader/GLStateScope/RenderStateCache/ BindlessSupport family, Shader/ShaderProgramConstruction/SamplerCache, RenderBootstrap, and RenderFrameGlStateController. GameWindow.cs's Run()/CreateGraphics()/CreateBackbufferReader()/ OnLoad() collapse to their Vulkan-only arm; GameWindowGraphics loses its OpenGlGameWindowGraphics subclass. RuntimeOptions.RenderBackend and RenderBackendKind (incl. the Gl member of GpuBackendKind) are gone — there is nothing left to select between. The five world-draw dual-arm renderers (WbDrawDispatcher, EnvCellRenderer, TerrainModernRenderer, ParticleRenderer, SkyRenderer) and the composition roots (WorldRenderComposition, HostInputCameraComposition, LivePresentationComposition, FrameRootComposition) collapse to their RHI-only arm. GL-only diagnostic properties with a live external reader (DynamicBufferCount and friends) simplify to a documented `=> 0`/no-op rather than disappearing, since the reader is out of this commit's scope. A few GL-flavored mechanisms turned out to be backend-neutral once isolated: GlConstructionCleanupLedger is renamed ResourceConstructionCleanupLedger (exception-chain walking has nothing to do with GL), and GlfwNativePlatformProbe moved out of the otherwise GL-only GraphicalCapabilityRecord.cs into GraphicalWindowBackendSelection.cs before the rest of that file was deleted. Test files with no surviving subject are deleted outright (GraphicalCapabilityRequirementsTests, ShaderProgramConstructionTests, PortalDepthShaderParityTests, TextureCacheBindlessTests, TextRendererFailureSafetyTests, ClipFrameUploadTests, every Gpu/Gl/*Tests, GlTextureOwnershipTests, RenderFrameGlStateControllerTests); others get their dead GL-only members trimmed while their live assertions stay (ClipFrameLayoutTests' MeshClipSsboBinding check now reads GpuBindingModel.StorageClipRegions, the same binding index under its new backend-neutral name; GpuResourceRetirementTransactionTests drops its OpenGLGraphicsDevice-subclassing test double and the two GL queue tests it existed for). EnvCellRendererTests' construction helper now builds a real ObjectMeshManager via VulkanMeshPipelineDevice instead of passing null through a null-forgiving operator, since the RHI constructor never tolerated a null mesh manager and the old GL constructor (which did) is gone. Deferred to the next two steps, deliberately not touched here: the Silk.NET.OpenGL/.Extensions.ARB package references, IMeshPipelineDevice.Gl (WbMeshAdapter's GL? threading stays in place), Chorizite.Core's stale csproj comment (the package itself is still load-bearing — TextureFormat and friends are used well beyond the deleted ManagedGLUniformBuffer), and the CI/gate scripts. Build: `dotnet build AcDream.slnx -c Release` — 0 warnings, 0 errors. Tests: full-solution `dotnet test` green across every project (App.Tests 3937/3940 + 3 skips, Core.Tests 3296/3298 + 2 skips, all others 100%); the 2 App.Tests names that flake under full-suite parallel execution (#250-family, documented pre-existing) pass in isolation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1217 lines
52 KiB
C#
1217 lines
52 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.Windowing;
|
|
|
|
namespace AcDream.App.Composition;
|
|
|
|
internal sealed record LivePresentationDependencies(
|
|
RuntimeOptions Options,
|
|
GameWindowGraphics Graphics,
|
|
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<GameWindowGraphics, 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<GameWindowGraphics, 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.Graphics, 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
|
|
?? throw new InvalidOperationException(
|
|
"The landblock spawn ledger requires the mesh pipeline, which "
|
|
+ "Campaign V slice V6i-3 made backend-neutral."));
|
|
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));
|
|
// Campaign V slice V6j: the world dispatcher records into the pass the
|
|
// world scene phase publishes on this scope. The raw-GL arm was
|
|
// deleted at slice V11.
|
|
IWorldPassScope? worldPassScope = d.Graphics.WorldPassScope;
|
|
var dispatcherLease = scope.Acquire(
|
|
"WB draw dispatcher",
|
|
() => new WbDrawDispatcher(
|
|
host.GpuDevice,
|
|
host.GpuFrameLifetime,
|
|
worldPassScope
|
|
?? throw new InvalidOperationException(
|
|
"The graphics backend must publish a world pass scope."),
|
|
foundation.TextureCache,
|
|
foundation.MeshAdapter!,
|
|
entitySpawnAdapter,
|
|
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);
|
|
if (dispatcherLease.Resource is { } alphaDispatcher)
|
|
{
|
|
alphaDispatcher.AlphaToCoverage =
|
|
d.Settings.ResolvedQuality.AlphaToCoverage;
|
|
}
|
|
|
|
CompositionAcquisitionScope.CompositionAcquisitionLease<
|
|
PaperdollViewportRenderer>? paperdollLease = null;
|
|
PaperdollFramePresenter? paperdollPresenter = null;
|
|
// Campaign V slice V6l: both retained-UI viewports exist on BOTH arms.
|
|
// The RHI arm needed two backend fixes first — a layered sampled view per
|
|
// render target, so a colour attachment can legally enter the
|
|
// sampler2DArray table, and sample-count pipeline variants in
|
|
// WbDrawDispatcher, because a world pipeline is built at the backbuffer's
|
|
// count and an offscreen target is single-sampled by contract (plan
|
|
// §5.5.16 defect 3).
|
|
if (dispatcherLease.Resource is { } paperdollDispatcher
|
|
&& interaction.RetainedUi?.Runtime.PaperdollViewportWidget is { } viewport
|
|
&& interaction.RetainedUi.Runtime.InventoryFrame is { } inventoryFrame)
|
|
{
|
|
paperdollLease = scope.Acquire(
|
|
"paperdoll viewport",
|
|
() => new PaperdollViewportRenderer(
|
|
worldPassScope
|
|
?? throw new InvalidOperationException(
|
|
"The graphics backend must publish a world pass scope."),
|
|
host.GpuDevice,
|
|
host.GpuFrameLifetime,
|
|
paperdollDispatcher,
|
|
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)),
|
|
new RetailPaperdollDollFactory(
|
|
new LivePaperdollEntityLookup(liveEntities),
|
|
d.PlayerIdentity,
|
|
new RetailPaperdollPoseApplicator(
|
|
content.Dats,
|
|
content.AnimationLoader,
|
|
d.DatLock)));
|
|
}
|
|
|
|
CompositionAcquisitionScope.CompositionAcquisitionLease<
|
|
CreatureAppraisalViewportRenderer>? creatureAppraisalLease = null;
|
|
CreatureAppraisalFramePresenter? creatureAppraisalPresenter = null;
|
|
if (dispatcherLease.Resource is { } appraisalDispatcher
|
|
&& 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(
|
|
worldPassScope
|
|
?? throw new InvalidOperationException(
|
|
"The graphics backend must publish a world pass scope."),
|
|
host.GpuDevice,
|
|
host.GpuFrameLifetime,
|
|
appraisalDispatcher,
|
|
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),
|
|
new RetailCreatureAppraisalCloneFactory(
|
|
new LiveCreatureAppraisalEntityLookup(liveEntities)));
|
|
}
|
|
Fault(LivePresentationCompositionPoint.PrivateCreatureViewportsCreated);
|
|
|
|
var envCellFrustum = new WbFrustum();
|
|
var envCellLease = scope.Acquire(
|
|
"environment-cell renderer",
|
|
() => new EnvCellRenderer(
|
|
host.GpuDevice,
|
|
host.GpuFrameLifetime,
|
|
worldPassScope
|
|
?? throw new InvalidOperationException(
|
|
"The graphics backend must publish a world pass scope."),
|
|
foundation.MeshAdapter!.MeshManager!,
|
|
envCellFrustum),
|
|
static value => value.Dispose());
|
|
// The three pipelines ARE its program, built at construction — the
|
|
// raw-GL arm's separate Initialize(Shader) step was deleted at V11.
|
|
Fault(LivePresentationCompositionPoint.EnvironmentCellsCreated);
|
|
|
|
// The streaming pipeline itself is backend-neutral and runs on both
|
|
// arms: landblocks load, heightfields and collision build, and the
|
|
// spatial index fills. Only publication into GPU state is renderer-
|
|
// owned, so the Vulkan arm publishes into nothing until the world arm
|
|
// lands.
|
|
TerrainModernRenderer? terrainRenderer = foundation.Terrain;
|
|
EnvCellRenderer? envCells = envCellLease.Resource;
|
|
var landblockRenderPublisher = new LandblockRenderPublisher(
|
|
(landblockId, meshData, origin) =>
|
|
terrainRenderer?.AddLandblockWithMesh(
|
|
landblockId,
|
|
meshData,
|
|
origin),
|
|
landblockId => terrainRenderer?.RemoveLandblock(landblockId),
|
|
d.CellVisibility,
|
|
worldState,
|
|
prepareEnvCells: build =>
|
|
{
|
|
if (foundation.MeshAdapter?.MeshManager is { } envCellMeshes)
|
|
EnvCellMeshPreparationScheduler.Schedule(build, envCellMeshes);
|
|
},
|
|
removeEnvCells: landblockId => envCells?.RemoveLandblock(landblockId),
|
|
envCellPublisher: envCells);
|
|
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());
|
|
// Campaign V slice V6l: the portal depth mask compiles portal_depth
|
|
// from SPIR-V into three pipelines and records into the pass the
|
|
// world scene phase publishes on this scope. The raw-GL inline
|
|
// program and raw draws were deleted at slice V11.
|
|
var portalDepthLease = scope.Acquire(
|
|
"portal depth mask",
|
|
() => new PortalDepthMaskRenderer(
|
|
host.GpuDevice,
|
|
host.GpuFrameLifetime,
|
|
worldPassScope
|
|
?? throw new InvalidOperationException(
|
|
"The graphics backend must publish a world pass scope.")),
|
|
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;
|
|
CompositionAcquisitionScope.CompositionAcquisitionLease<
|
|
PortalTunnelPresentation>? portalTunnelLease = null;
|
|
// Campaign V slice V6m: portal space opens a backbuffer pass of its
|
|
// own and publishes it on the scope for the span of the draw, the way
|
|
// the two offscreen viewports do. The raw-GL arm was deleted at V11.
|
|
if (dispatcherLease.Resource is { } portalDispatcher)
|
|
{
|
|
PortalTunnelPresentation portalTunnel;
|
|
try
|
|
{
|
|
portalTunnel = d.PortalTunnelFallback.AcquirePrepared(
|
|
() => PortalTunnelPresentation.CreateRequired(
|
|
worldPassScope
|
|
?? throw new InvalidOperationException(
|
|
"The graphics backend must publish a world pass scope."),
|
|
host.GpuFrameLifetime,
|
|
content.Dats,
|
|
content.AnimationLoader,
|
|
d.HookRouter,
|
|
portalDispatcher,
|
|
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;
|
|
}
|
|
portalTunnelLease = scope.Own(
|
|
"portal tunnel fallback",
|
|
portalTunnel,
|
|
_ => d.PortalTunnelFallback.ReleaseFallback());
|
|
}
|
|
Fault(LivePresentationCompositionPoint.PortalResourcesCreated);
|
|
|
|
// Campaign V slice V6k: the sky compiles its pair from SPIR-V and
|
|
// records into the pass the world scene phase publishes on this
|
|
// scope. The raw-GL arm — its own shader pair (acquired through
|
|
// GameRenderResourceLifetime.AcquireSkyShader, deleted with it) and
|
|
// its own bindless/device-table wiring — was deleted at slice V11.
|
|
var skyLease = scope.Acquire(
|
|
"sky renderer",
|
|
() => new SkyRenderer(
|
|
host.GpuDevice,
|
|
host.GpuFrameLifetime,
|
|
worldPassScope
|
|
?? throw new InvalidOperationException(
|
|
"The graphics backend must publish a world pass scope."),
|
|
content.Dats,
|
|
foundation.TextureCache)
|
|
{
|
|
// Campaign V slice V7: null unless ACDREAM_SKY_PHASE_SECONDS
|
|
// is set, which is every run but a differential gate's.
|
|
AnimationPhaseSecondsOverride = d.Options.SkyAnimationPhaseSeconds,
|
|
},
|
|
static value => value.Dispose());
|
|
// Campaign V slice V6l: the RHI arm compiles the two particle pairs
|
|
// from SPIR-V, draws instances through the vertex binding the V6l
|
|
// contract amendment added, and records into the pass the world scene
|
|
// phase publishes on this scope. The raw-GL arm was deleted at V11.
|
|
var particleLease = scope.AcquireOptional(
|
|
"particle renderer",
|
|
() => new ParticleRenderer(
|
|
host.GpuDevice,
|
|
host.GpuFrameLifetime,
|
|
worldPassScope
|
|
?? throw new InvalidOperationException(
|
|
"The graphics backend must publish a world pass scope."),
|
|
content.ParticleSystem,
|
|
foundation.TextureCache,
|
|
content.Dats,
|
|
foundation.MeshAdapter!,
|
|
d.RetailAlphaQueue,
|
|
alphaScratchBudgets.ParticleBytes),
|
|
static value => value.Dispose());
|
|
Fault(LivePresentationCompositionPoint.SkyAndParticlesCreated);
|
|
|
|
IRenderFrameResourceDiagnosticsSource? resourceDiagnostics =
|
|
d.Options.UiProbeDump
|
|
&& dispatcherLease.Resource is { } diagnosticDispatcher
|
|
&& envCellLease.Resource is { } diagnosticEnvCells
|
|
&& particleLease.Resource is { } diagnosticParticles
|
|
&& portalDepthLease.Resource is { } diagnosticPortalDepth
|
|
? new RuntimeRenderFrameResourceDiagnosticsSource(
|
|
content.ParticleSystem,
|
|
content.ParticleSink,
|
|
diagnosticDispatcher,
|
|
diagnosticEnvCells,
|
|
diagnosticParticles,
|
|
interaction.RetainedUi?.Host.TextRenderer,
|
|
diagnosticPortalDepth,
|
|
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 ?? 0)
|
|
+ (particleLease.Resource?.RetainedAlphaScratchBytes ?? 0))),
|
|
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();
|
|
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;
|
|
}
|
|
}
|
|
}
|