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>
646 lines
21 KiB
C#
646 lines
21 KiB
C#
using System.Numerics;
|
|
using AcDream.App.Rendering.Scene;
|
|
using AcDream.App.Rendering.Sky;
|
|
using AcDream.App.Rendering.Wb;
|
|
using AcDream.Core.Rendering;
|
|
using AcDream.Core.Vfx;
|
|
using AcDream.Core.World;
|
|
using Silk.NET.Windowing;
|
|
|
|
namespace AcDream.App.Rendering;
|
|
|
|
internal readonly record struct RetailPViewFramebufferSize(int Width, int Height);
|
|
|
|
internal interface IRetailPViewFramebufferSource
|
|
{
|
|
RetailPViewFramebufferSize Capture();
|
|
}
|
|
|
|
internal sealed class SilkRetailPViewFramebufferSource(IWindow window) :
|
|
IRetailPViewFramebufferSource
|
|
{
|
|
private readonly IWindow _window = window
|
|
?? throw new ArgumentNullException(nameof(window));
|
|
|
|
public RetailPViewFramebufferSize Capture()
|
|
{
|
|
var size = _window.FramebufferSize;
|
|
return new RetailPViewFramebufferSize(size.X, size.Y);
|
|
}
|
|
}
|
|
|
|
internal sealed class RetailPViewCellSource : IRetailPViewCellSource
|
|
{
|
|
private readonly CellVisibility _cells;
|
|
|
|
public RetailPViewCellSource(CellVisibility cells) =>
|
|
_cells = cells ?? throw new ArgumentNullException(nameof(cells));
|
|
|
|
public LoadedCell? Find(uint cellId) =>
|
|
_cells.TryGetCell(cellId, out LoadedCell? cell) ? cell : null;
|
|
}
|
|
|
|
internal sealed class RetailPViewParticleClassifications
|
|
{
|
|
private readonly HashSet<uint> _outdoor = [];
|
|
private readonly HashSet<uint> _visible = [];
|
|
private readonly HashSet<uint> _dynamics = [];
|
|
|
|
public IReadOnlySet<uint> Outdoor => _outdoor;
|
|
public HashSet<uint> Visible => _visible;
|
|
public HashSet<uint> Dynamics => _dynamics;
|
|
|
|
public void BeginFrame()
|
|
{
|
|
_outdoor.Clear();
|
|
_visible.Clear();
|
|
_dynamics.Clear();
|
|
}
|
|
|
|
public void ReplaceOutdoor(IReadOnlyList<WorldEntity> owners)
|
|
{
|
|
_outdoor.Clear();
|
|
foreach (WorldEntity owner in owners)
|
|
_outdoor.Add(owner.Id);
|
|
}
|
|
|
|
public void ReplaceOutdoor(IReadOnlySet<uint> ownerIds)
|
|
{
|
|
_outdoor.Clear();
|
|
_outdoor.UnionWith(ownerIds);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Concrete GL implementation of the named passes ordered by
|
|
/// <see cref="RetailPViewRenderer"/>. It owns reusable pass-local particle
|
|
/// classifications but borrows every renderer and world source.
|
|
/// The order it implements is retail <c>PView::DrawCells @ 0x005A4840</c>:
|
|
/// landscape, delayed-alpha flush, optional interior depth clear, exit masks,
|
|
/// cell shells/objects, then surviving dynamics. Landscape sky/terrain/weather
|
|
/// placement follows <c>LScape::draw @ 0x00506330</c>.
|
|
/// </summary>
|
|
internal interface IOutdoorSceneParticleOwnerSource
|
|
{
|
|
IReadOnlySet<uint> OutdoorSceneParticleEntityIds { get; }
|
|
}
|
|
|
|
internal readonly record struct RenderFrameEntityDrawRequest(
|
|
RenderFrameView View,
|
|
RenderFrameCandidateRoute Route,
|
|
int RouteIndex,
|
|
uint CellId,
|
|
uint TupleLandblockId);
|
|
|
|
internal interface IRenderFrameEntityPassExecutor
|
|
{
|
|
void BeginEntityFrame(in RenderFrameView view);
|
|
|
|
bool DrawEntityRoute(
|
|
ICamera camera,
|
|
in RenderFrameView view,
|
|
RenderFrameCandidateRoute route,
|
|
int routeIndex,
|
|
uint cellId,
|
|
uint tupleLandblockId);
|
|
|
|
void CompleteEntityFrame(in RenderFrameView view);
|
|
|
|
void AbortEntityFrame();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Campaign V slice V6j: backend-neutral. The order it implements is retail's and
|
|
/// is written once; the four places it touches graphics state directly are owned
|
|
/// by <see cref="IWorldPassSurface"/>.
|
|
/// </summary>
|
|
internal sealed class RetailPViewPassExecutor :
|
|
IRetailPViewPassExecutor,
|
|
IRenderFrameEntityPassExecutor,
|
|
IOutdoorSceneParticleOwnerSource
|
|
{
|
|
private readonly IWorldPassSurface _surface;
|
|
private readonly IRenderFrameGlState _frameGlState;
|
|
private readonly ClipFrame _clipFrame;
|
|
private readonly TerrainModernRenderer? _terrain;
|
|
private readonly EnvCellRenderer _envCells;
|
|
private readonly WbDrawDispatcher _entities;
|
|
private readonly SkyRenderer? _sky;
|
|
private readonly ParticleSystem? _particles;
|
|
private readonly ParticleRenderer? _particleRenderer;
|
|
private readonly PortalDepthMaskRenderer? _portalDepthMask;
|
|
private readonly RetailAlphaQueue _alpha;
|
|
private readonly WorldRenderDiagnostics _diagnostics;
|
|
private readonly TerrainDrawDiagnosticsController _terrainDiagnostics;
|
|
private readonly RetailPViewParticleClassifications _particleClassifications = new();
|
|
private readonly HashSet<uint> _noSceneParticleEntityIds = [];
|
|
|
|
/// <summary>
|
|
/// Borrowed until the next late landscape pass. The outdoor-root post-world
|
|
/// particle pass consumes this synchronously before another PView frame.
|
|
/// </summary>
|
|
public IReadOnlySet<uint> OutdoorSceneParticleEntityIds =>
|
|
_particleClassifications.Outdoor;
|
|
|
|
public RetailPViewPassExecutor(
|
|
IWorldPassSurface surface,
|
|
IRenderFrameGlState frameGlState,
|
|
ClipFrame clipFrame,
|
|
TerrainModernRenderer? terrain,
|
|
EnvCellRenderer envCells,
|
|
WbDrawDispatcher entities,
|
|
SkyRenderer? sky,
|
|
ParticleSystem? particles,
|
|
ParticleRenderer? particleRenderer,
|
|
PortalDepthMaskRenderer? portalDepthMask,
|
|
RetailAlphaQueue alpha,
|
|
WorldRenderDiagnostics diagnostics,
|
|
TerrainDrawDiagnosticsController terrainDiagnostics)
|
|
{
|
|
_surface = surface ?? throw new ArgumentNullException(nameof(surface));
|
|
_frameGlState = frameGlState
|
|
?? throw new ArgumentNullException(nameof(frameGlState));
|
|
_clipFrame = clipFrame ?? throw new ArgumentNullException(nameof(clipFrame));
|
|
_terrain = terrain;
|
|
_envCells = envCells ?? throw new ArgumentNullException(nameof(envCells));
|
|
_entities = entities ?? throw new ArgumentNullException(nameof(entities));
|
|
_sky = sky;
|
|
_particles = particles;
|
|
_particleRenderer = particleRenderer;
|
|
_portalDepthMask = portalDepthMask;
|
|
_alpha = alpha ?? throw new ArgumentNullException(nameof(alpha));
|
|
_diagnostics = diagnostics ?? throw new ArgumentNullException(nameof(diagnostics));
|
|
_terrainDiagnostics = terrainDiagnostics
|
|
?? throw new ArgumentNullException(nameof(terrainDiagnostics));
|
|
}
|
|
|
|
public void BeginFrame()
|
|
{
|
|
_particleClassifications.BeginFrame();
|
|
}
|
|
|
|
public void BeginEntityFrame(in RenderFrameView view) =>
|
|
_entities.BeginPackedProductionFrame(in view);
|
|
|
|
public bool DrawEntityRoute(
|
|
ICamera camera,
|
|
in RenderFrameView view,
|
|
RenderFrameCandidateRoute route,
|
|
int routeIndex,
|
|
uint cellId,
|
|
uint tupleLandblockId) =>
|
|
_entities.DrawPackedProductionRoute(
|
|
camera,
|
|
in view,
|
|
route,
|
|
routeIndex,
|
|
cellId,
|
|
tupleLandblockId);
|
|
|
|
public void CompleteEntityFrame(in RenderFrameView view) =>
|
|
_entities.CompletePackedProductionFrame(in view);
|
|
|
|
public void AbortEntityFrame() =>
|
|
_entities.AbortPackedProductionFrame();
|
|
|
|
public void AbortFrame()
|
|
{
|
|
List<Exception>? failures = null;
|
|
TryAbort(_frameGlState.RestoreFrameDefaults);
|
|
TryAbort(_particleClassifications.BeginFrame);
|
|
TryAbort(_noSceneParticleEntityIds.Clear);
|
|
if (failures is { Count: > 0 })
|
|
throw new AggregateException("Retail PView pass abort failed.", failures);
|
|
|
|
void TryAbort(Action operation)
|
|
{
|
|
try
|
|
{
|
|
operation();
|
|
}
|
|
catch (Exception error)
|
|
{
|
|
(failures ??= []).Add(error);
|
|
}
|
|
}
|
|
}
|
|
|
|
public ClipFrameAssembly AssembleClipFrame(
|
|
PortalVisibilityFrame portalFrame,
|
|
ClipFrameAssembly reuseAssembly) =>
|
|
ClipFrameAssembler.Assemble(_clipFrame, portalFrame, reuseAssembly);
|
|
|
|
public void PrepareClipFrame(int terrainUploadCount) =>
|
|
_surface.PrepareClipFrame(terrainUploadCount);
|
|
|
|
public void SetTerrainClip(ReadOnlySpan<Vector4> planes) =>
|
|
_surface.SetTerrainClip(planes);
|
|
|
|
public void ClearClipRouting() => _entities.ClearClipRouting();
|
|
|
|
public void UseIndoorMembershipOnlyRouting()
|
|
{
|
|
// Retail viewcone-checks meshes and draws whole cell shells. This clears
|
|
// any terrain-slice routing before the indoor membership passes.
|
|
_envCells.SetClipRouting(null);
|
|
_entities.ClearClipRouting();
|
|
}
|
|
|
|
public void PrepareCellBatches(
|
|
RetailPViewFrameInput frame,
|
|
HashSet<uint> visibleCellIds) =>
|
|
_envCells.PrepareRenderBatches(
|
|
frame.ViewProjection,
|
|
frame.CameraWorldPosition,
|
|
filter: visibleCellIds,
|
|
centerLbX: frame.RenderCenterLbX,
|
|
centerLbY: frame.RenderCenterLbY,
|
|
renderRadius: frame.RenderRadius);
|
|
|
|
public void DrawOpaqueCellShells(HashSet<uint> cellIds) =>
|
|
_envCells.Render(WbRenderPass.Opaque, cellIds);
|
|
|
|
public bool CellHasTransparentShell(uint cellId) =>
|
|
_envCells.CellHasTransparent(cellId);
|
|
|
|
public void DrawTransparentCellShells(HashSet<uint> cellIds) =>
|
|
_envCells.Render(WbRenderPass.Transparent, cellIds);
|
|
|
|
public void DrawTransparentCellShellsOrdered(IReadOnlyList<uint> cellIds) =>
|
|
_envCells.RenderTransparentOrdered(cellIds);
|
|
|
|
public void DrawEntityBucket(
|
|
RetailPViewFrameInput frame,
|
|
IReadOnlyList<WorldEntity> entities,
|
|
HashSet<uint>? visibleCellIds)
|
|
{
|
|
uint landblockId = frame.PlayerLandblockId ?? 0u;
|
|
var entry = (
|
|
landblockId,
|
|
Vector3.Zero,
|
|
Vector3.Zero,
|
|
entities,
|
|
(IReadOnlyDictionary<uint, WorldEntity>?)null);
|
|
|
|
_entities.Draw(
|
|
frame.Camera,
|
|
new[] { entry },
|
|
frame.Frustum,
|
|
neverCullLandblockId: frame.PlayerLandblockId,
|
|
visibleCellIds: visibleCellIds,
|
|
animatedEntityIds: frame.AnimatedEntityIds);
|
|
}
|
|
|
|
public void EmitClipRouteProbe(
|
|
ClipFrameAssembly clipAssembly,
|
|
ClipViewSlice slice,
|
|
int sliceIndex) =>
|
|
_diagnostics.EmitClipRouteProbe(
|
|
RenderingDiagnostics.ProbeClipRouteEnabled,
|
|
_clipFrame,
|
|
clipAssembly,
|
|
slice,
|
|
sliceIndex);
|
|
|
|
public void EmitOutStageOwner(
|
|
WorldEntity entity,
|
|
Vector3 sphereCenter,
|
|
float sphereRadius,
|
|
int sliceIndex,
|
|
bool passed) =>
|
|
_diagnostics.EmitOutStageOwner(
|
|
RenderingDiagnostics.ProbeOutStageEnabled,
|
|
RenderingDiagnostics.DumpEntitySourceIds,
|
|
entity,
|
|
sphereCenter,
|
|
sphereRadius,
|
|
sliceIndex,
|
|
passed);
|
|
|
|
public void EmitOutStageRouting(
|
|
int sliceIndex,
|
|
IReadOnlyList<WorldEntity> entities,
|
|
ViewconeCuller viewcone) =>
|
|
_diagnostics.EmitOutStageRouting(
|
|
RenderingDiagnostics.ProbeOutStageEnabled,
|
|
sliceIndex,
|
|
entities,
|
|
viewcone);
|
|
|
|
public void EmitPhantomObjects(uint cellId, int survivorCount) =>
|
|
_diagnostics.EmitPhantomObjects(
|
|
RenderingDiagnostics.ProbePhantomEnabled,
|
|
cellId,
|
|
survivorCount);
|
|
|
|
public void DrawLandscapeSlice(
|
|
RetailPViewFrameInput frame,
|
|
RetailPViewLandscapeSliceContext context)
|
|
{
|
|
ClipViewSlice slice = context.Slice;
|
|
bool scissor = BeginDoorwayScissor(slice.NdcAabb);
|
|
_diagnostics.EmitClipRouteScissorProbe(
|
|
RenderingDiagnostics.ProbeClipRouteEnabled,
|
|
scissor,
|
|
slice.NdcAabb);
|
|
|
|
_surface.BindTerrainClip();
|
|
EnableClipDistances();
|
|
if (frame.RenderSky)
|
|
{
|
|
_sky?.RenderSky(
|
|
frame.Camera,
|
|
frame.CameraWorldPosition,
|
|
frame.DayFraction,
|
|
frame.ActiveDayGroup,
|
|
frame.SkyKeyframe,
|
|
frame.EnvironOverrideActive);
|
|
}
|
|
|
|
DisableClipDistances();
|
|
if (frame.RenderSky && _particles is not null && _particleRenderer is not null)
|
|
{
|
|
_particleRenderer.Draw(
|
|
frame.Camera,
|
|
frame.CameraWorldPosition,
|
|
ParticleRenderPass.SkyPreScene);
|
|
}
|
|
|
|
EnableClipDistances();
|
|
_terrainDiagnostics.Begin();
|
|
_terrain?.Draw(
|
|
frame.Camera,
|
|
frame.Frustum,
|
|
neverCullLandblockId: frame.PlayerLandblockId,
|
|
clipPlanes: slice.Planes,
|
|
ndcClipAabb: slice.NdcAabb);
|
|
_terrainDiagnostics.Complete();
|
|
|
|
DisableClipDistances();
|
|
if (context.EntityDraw is RenderFrameEntityDrawRequest request)
|
|
{
|
|
RenderFrameView drawView = request.View;
|
|
_entities.DrawPackedProductionRoute(
|
|
frame.Camera,
|
|
in drawView,
|
|
request.Route,
|
|
request.RouteIndex,
|
|
request.CellId,
|
|
request.TupleLandblockId);
|
|
}
|
|
else if (context.OutdoorEntities.Count > 0)
|
|
{
|
|
var sceneryEntry = (
|
|
frame.PlayerLandblockId ?? 0u,
|
|
Vector3.Zero,
|
|
Vector3.Zero,
|
|
context.OutdoorEntities,
|
|
(IReadOnlyDictionary<uint, WorldEntity>?)null);
|
|
_entities.Draw(
|
|
frame.Camera,
|
|
new[] { sceneryEntry },
|
|
frame.Frustum,
|
|
neverCullLandblockId: frame.PlayerLandblockId,
|
|
visibleCellIds: null,
|
|
animatedEntityIds: frame.AnimatedEntityIds);
|
|
}
|
|
|
|
if (scissor)
|
|
_surface.EndScissor();
|
|
DisableClipDistances();
|
|
}
|
|
|
|
public void DrawLandscapeSliceLate(
|
|
RetailPViewFrameInput frame,
|
|
RetailPViewLandscapeLateSliceContext context)
|
|
{
|
|
ClipViewSlice slice = context.Slice;
|
|
bool scissor = BeginDoorwayScissor(slice.NdcAabb);
|
|
_surface.BindTerrainClip();
|
|
|
|
DisableClipDistances();
|
|
if (context.EntityDraw is RenderFrameEntityDrawRequest request)
|
|
{
|
|
RenderFrameView drawView = request.View;
|
|
_entities.DrawPackedProductionRoute(
|
|
frame.Camera,
|
|
in drawView,
|
|
request.Route,
|
|
request.RouteIndex,
|
|
request.CellId,
|
|
request.TupleLandblockId);
|
|
}
|
|
else if (context.Dynamics.Count > 0)
|
|
{
|
|
var dynamicsEntry = (
|
|
frame.PlayerLandblockId ?? 0u,
|
|
Vector3.Zero,
|
|
Vector3.Zero,
|
|
context.Dynamics,
|
|
(IReadOnlyDictionary<uint, WorldEntity>?)null);
|
|
_entities.Draw(
|
|
frame.Camera,
|
|
new[] { dynamicsEntry },
|
|
frame.Frustum,
|
|
neverCullLandblockId: frame.PlayerLandblockId,
|
|
visibleCellIds: null,
|
|
animatedEntityIds: frame.AnimatedEntityIds);
|
|
}
|
|
|
|
_particleClassifications.ReplaceOutdoor(context.ParticleOwnerIds);
|
|
|
|
_diagnostics.EmitOutStageParticles(
|
|
RenderingDiagnostics.ProbeOutStageEnabled,
|
|
_particles,
|
|
_particleClassifications.Outdoor);
|
|
|
|
if (!frame.RootCell.IsOutdoorNode
|
|
&& _particleClassifications.Outdoor.Count > 0
|
|
&& _particles is not null
|
|
&& _particleRenderer is not null)
|
|
{
|
|
_particleRenderer.DrawForOwners(
|
|
frame.Camera,
|
|
frame.CameraWorldPosition,
|
|
ParticleRenderPass.Scene,
|
|
_particleClassifications.Outdoor);
|
|
}
|
|
|
|
EnableClipDistances();
|
|
if (frame.RenderSky && frame.RenderWeather)
|
|
{
|
|
_sky?.RenderWeather(
|
|
frame.Camera,
|
|
frame.CameraWorldPosition,
|
|
frame.DayFraction,
|
|
frame.ActiveDayGroup,
|
|
frame.SkyKeyframe,
|
|
frame.EnvironOverrideActive);
|
|
DisableClipDistances();
|
|
if (_particles is not null && _particleRenderer is not null)
|
|
{
|
|
_particleRenderer.Draw(
|
|
frame.Camera,
|
|
frame.CameraWorldPosition,
|
|
ParticleRenderPass.SkyPostScene);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
DisableClipDistances();
|
|
}
|
|
|
|
if (scissor)
|
|
_surface.EndScissor();
|
|
DisableClipDistances();
|
|
}
|
|
|
|
public void ClearInteriorDepth() => _surface.ClearInteriorDepth();
|
|
|
|
public void DrawExitPortalMask(
|
|
RetailPViewFrameInput frame,
|
|
RetailPViewCellSliceContext context) =>
|
|
DrawPortalDepthWrite(context, frame, forceFarZ: frame.RootCell.IsOutdoorNode);
|
|
|
|
public void DrawLookInPortalPunch(
|
|
RetailPViewFrameInput frame,
|
|
RetailPViewCellSliceContext context) =>
|
|
DrawPortalDepthWrite(context, frame, forceFarZ: true);
|
|
|
|
public void DrawUnattachedSceneParticles(RetailPViewFrameInput frame)
|
|
{
|
|
if (_particles is null || _particleRenderer is null)
|
|
return;
|
|
|
|
DisableClipDistances();
|
|
_particleRenderer.DrawForOwners(
|
|
frame.Camera,
|
|
frame.CameraWorldPosition,
|
|
ParticleRenderPass.Scene,
|
|
_noSceneParticleEntityIds,
|
|
includeUnattached: true);
|
|
}
|
|
|
|
public void FlushLandscapeAlpha() => _alpha.Flush();
|
|
|
|
public void DrawCellParticles(
|
|
RetailPViewFrameInput frame,
|
|
RetailPViewCellSliceContext context)
|
|
{
|
|
if (_particles is null
|
|
|| _particleRenderer is null
|
|
|| context.ParticleOwnerIds.Count == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
HashSet<uint> visible = _particleClassifications.Visible;
|
|
visible.Clear();
|
|
visible.UnionWith(context.ParticleOwnerIds);
|
|
if (visible.Count == 0)
|
|
return;
|
|
|
|
DisableClipDistances();
|
|
_particleRenderer.DrawForOwners(
|
|
frame.Camera,
|
|
frame.CameraWorldPosition,
|
|
ParticleRenderPass.Scene,
|
|
visible);
|
|
DisableClipDistances();
|
|
}
|
|
|
|
public void DrawDynamicsParticles(
|
|
RetailPViewFrameInput frame,
|
|
IReadOnlySet<uint> ownerIds)
|
|
{
|
|
if (_particles is null || _particleRenderer is null || ownerIds.Count == 0)
|
|
return;
|
|
|
|
HashSet<uint> dynamics = _particleClassifications.Dynamics;
|
|
dynamics.Clear();
|
|
dynamics.UnionWith(ownerIds);
|
|
if (dynamics.Count == 0)
|
|
return;
|
|
|
|
DisableClipDistances();
|
|
_particleRenderer.DrawForOwners(
|
|
frame.Camera,
|
|
frame.CameraWorldPosition,
|
|
ParticleRenderPass.Scene,
|
|
dynamics);
|
|
DisableClipDistances();
|
|
}
|
|
|
|
public void EmitDiagnostics(
|
|
RetailPViewFrameInput frame,
|
|
RetailPViewFrameResult result) =>
|
|
_diagnostics.EmitRetailPViewDiagnostics(
|
|
RenderingDiagnostics.ProbeViewerEnabled,
|
|
RenderingDiagnostics.ProbeVisibilityEnabled,
|
|
RenderingDiagnostics.ProbeFlapEnabled,
|
|
result,
|
|
frame.RootCell,
|
|
frame.ViewerCellId,
|
|
frame.PlayerCellId,
|
|
frame.CameraWorldPosition,
|
|
frame.PlayerViewPosition,
|
|
frame.CameraView,
|
|
frame.CameraCellResolution);
|
|
|
|
private void DrawPortalDepthWrite(
|
|
RetailPViewCellSliceContext context,
|
|
RetailPViewFrameInput frame,
|
|
bool forceFarZ)
|
|
{
|
|
// Retail D3DPolyRender::DrawPortalPolyInternal @ 0x0059BC90.
|
|
// Main interior roots stamp true depth (seal); outdoor and look-in
|
|
// apertures stamp far depth (punch). The renderer owns that choice.
|
|
if (_portalDepthMask is null)
|
|
return;
|
|
LoadedCell? cell = frame.Cells.Find(context.CellId);
|
|
if (cell is null)
|
|
return;
|
|
|
|
Span<Vector3> world = stackalloc Vector3[32];
|
|
for (int index = 0; index < cell.Portals.Count; index++)
|
|
{
|
|
if (cell.Portals[index].OtherCellId != 0xFFFF)
|
|
continue;
|
|
if (index >= cell.PortalPolygons.Count)
|
|
break;
|
|
Vector3[] localVertices = cell.PortalPolygons[index];
|
|
if (localVertices.Length < 3)
|
|
continue;
|
|
|
|
int count = Math.Min(localVertices.Length, world.Length);
|
|
for (int vertex = 0; vertex < count; vertex++)
|
|
{
|
|
world[vertex] = Vector3.Transform(
|
|
localVertices[vertex],
|
|
cell.WorldTransform);
|
|
world[vertex].Z += PortalVisibilityBuilder.ShellDrawLiftZ;
|
|
}
|
|
|
|
_diagnostics.EmitSeamMask(
|
|
RenderingDiagnostics.ProbeSeamDrawEnabled,
|
|
RenderingDiagnostics.SeamDrawTargetCells,
|
|
context.CellId,
|
|
index,
|
|
forceFarZ,
|
|
world[..count]);
|
|
|
|
_portalDepthMask.DrawDepthFan(
|
|
world[..count],
|
|
frame.ViewProjection,
|
|
context.Slice.Planes,
|
|
forceFarZ);
|
|
}
|
|
}
|
|
|
|
private bool BeginDoorwayScissor(Vector4 ndcAabb) =>
|
|
_surface.BeginScissor(ndcAabb);
|
|
|
|
private void EnableClipDistances() => _surface.EnableClipDistances();
|
|
|
|
private void DisableClipDistances() => _surface.DisableClipDistances();
|
|
}
|