feat(render): Campaign V slice V4a - port TextRenderer/BitmapFont/DebugLineRenderer/TextureCache onto IGpuDevice
TextRenderer, BitmapFont, DebugLineRenderer, and TextureCache's UI-texture
upload path (GetOrUploadRenderSurface/UploadRgba8) now issue every draw and
resource creation through the pinned IGpuDevice/IGpuFrame/IGpuPassEncoder
RHI contract instead of raw GL. This is the RHI's first real production
consumer - V0-V3 only established the contract, GL backend skeleton, and a
shader-dialect migration with no live GL exercise. TextRenderer owns one
IGpuPipeline (ui_text shader, straight-alpha blend, depth disabled) and
allocates a per-bucket ring each Flush; BitmapFont's atlas texture is
created and uploaded via device.CreateTexture/.Upload; DebugLineRenderer
mirrors the same one-pipeline-per-Flush shape for its line-list draws.
World-path TextureCache methods (GetOrUpload, the raw-GL layer-array
upload) are untouched - still legacy GL, still out of scope.
Frame lifecycle: GpuDeviceFrameLifetime (RenderFrameOrchestrator.cs) wraps
IGpuDevice.BeginFrame()/IGpuFrame.End() inside the existing
IRenderFrameLifetime bracket HostInputCameraCompositionPhase already opens
per callback, additively - no frame-graph restructuring. Ported renderers
reach the frame via ICurrentGpuFrameSource, a plain interface (not a
delegate field) so WorldSceneDiagnosticsController keeps passing its
existing "no stored window/delegate" architectural-conformance test.
Two real bugs surfaced by actually exercising the RHI against a live GL
context (nothing here was previously reachable before this slice):
- GlGpuDevice.BeginFrame() now resets the render-state cache every frame.
The cache assumes it is the sole writer of GL program/blend/depth/cull
state, which was true while it had zero real consumers, but every
still-legacy renderer (WbDrawDispatcher, terrain, particles, EnvCells)
mutates that same GL state directly and never informs the cache. Once a
legacy renderer ran between two RHI binds, the cache's belief about the
current GL program went stale, so a later BindPipeline(text shader)
skipped re-issuing glUseProgram and the following push-constant upload
threw GL_INVALID_OPERATION against whatever program was actually bound.
Reset() at the frame boundary is the same defensive move BeginPass
already makes after a forced clear (see its comment); it costs one
redundant state application on the frame's first bind.
- GL_MULTISAMPLE has no representation in the pinned contract. Added a
GL-backend-internal Multisample field to GlRenderStateSnapshot/Changes,
computed from GpuPipelineDescription.SampleCount at BindPipeline time -
mirrors how Vulkan bakes MSAA into the pipeline instead of a separate
toggle.
Collateral, scoped to keep the port real rather than a stub:
- GpuTextureSlot (Unassigned = uint.MaxValue, NOT 0) now flows through
every consumer of TextureCache.GetOrUploadRenderSurface/UploadRgba8 and
TextRenderer.DrawSprite - the entire retained UI layer, since a pervasive
Func<uint,(uint,int,int)> sprite-resolve delegate threads through nearly
every UI element/controller. Every prior `== 0` / `!= 0` "no texture"
check became `.IsAssigned` / `!.IsAssigned`; slot 0 is a real assigned
slot (the device's default white texture), so the old sentinel would
have produced live visual regressions if left in place.
- GpuTextureSlot/IGpuDevice/IGpuFrame are internal, so ~270 previously
public AcDream.App types that touched them (directly or transitively)
are now internal too - safe, since AcDream.App is an exe with no
external project references; only the two test projects consume it, via
InternalsVisibleTo. A handful of unrelated types the sweep caught
(ElementInfo/ImportedLayout's property-bag hierarchy, several enums used
as public [Theory] parameters, CursorFeedbackSnapshot's DragAcceptState)
were reverted back to public where making them internal would have
either cascaded into unrelated files or broken xUnit's public-member
discovery.
- ExternalViewportTextureBridge (new) registers the still-raw-GL FBO
color textures PrivateEntityViewportRenderer/PaperdollViewportRenderer
produce (V4g's scope) into the device's texture table for
UiViewport.TextureHandle, via a temporary
GlGpuDevice.RegisterExternalColorTexture escape hatch (internal, not
part of IGpuDevice) deleted when V4g ports those viewports.
- TextRenderGlStateScope.cs and its test deleted: the pipeline description
now bakes what it used to restore by hand.
- ResourceCleanupGroupTests/GlTextureOwnershipTests: the two source-text
conformance tests keyed to TextRenderer's old multi-resource
construction shape (Shader + per-flight FrameBufferSet array + white
texture + tracked VAO/VBO, all via ResourceCleanupGroup) no longer apply
- that shape is gone, replaced by one IGpuPipeline created through
IGpuDevice. The construction-order test is deleted; the checked-commit
texture-creation check now targets GlGpuTexture (which already used
the same GlResourceCommand.CreateName primitive before this slice).
Gates:
- dotnet build -c Release: 0 warnings, 0 errors (AcDream.App has
TreatWarningsAsErrors).
- dotnet test tests/AcDream.App.Tests -c Release: 3,840 passed / 3
skipped (was 3,843/3 entering this slice - net 3 fewer tests:
TextRendererFailureSafetyTests.cs deleted (2, tested the now-deleted
TextRenderGlStateScope) plus the one retired ResourceCleanupGroupTests
method). Full solution: 8,908 passed / 5 skipped across all nine test
projects.
- Offline pixel gate (tools/run-offline-pixel-gate.ps1, parent ec414d60
vs this commit): differing fraction 0.318% (1,791/563,200 compared
pixels), above the 0.001 threshold. Investigated pixel-by-pixel rather
than waved through: a diff heatmap plus 4x crops at the differing
clusters show zero differences anywhere in the retained UI, terrain,
scenery, or static meshes - every differing pixel sits on continuously-
animated ambient content (flying-insect sprites over the swamp, foliage
sparkle/dew glints) whose exact phase depends on elapsed wall-clock
time, the same category the gate's own sky-masking rationale already
documents and the campaign doc's coverage table explicitly excludes
("Not covered - particles"). Confirming evidence: two same-commit
captures at HEAD compare clean against each other (0.0025%), and two
same-commit captures at the parent compare clean against each other
(0.0044%) - only base-vs-head is consistently elevated, which is what
frame-pacing drift from genuinely new per-frame RHI work (BeginFrame,
ring resets, the render-state reset above) would produce against a
fixed wall-clock capture deadline, not a rendering defect. Recommend a
quick user visual check of this capture pair alongside the automated
result, matching how V2c's particle work was already handled in this
campaign (flagged for user visual confirmation rather than blocked on
an automated gate that cannot cover animated content).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
ec414d60cd
commit
ceec3bc440
334 changed files with 3660 additions and 3840 deletions
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using AcDream.Core.Physics;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
|
@ -8,7 +8,7 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// equipped items). Holds AC-specific per-instance customizations the WB
|
||||
/// atlas cache doesn't carry: <c>AnimPartChange</c> override map +
|
||||
/// <c>HiddenParts</c> bitmask. Also holds a reference to acdream's existing
|
||||
/// <see cref="AnimationSequencer"/> — Phase N.4 explicitly does not touch
|
||||
/// <see cref="AnimationSequencer"/> — Phase N.4 explicitly does not touch
|
||||
/// the sequencer; we just route through it at draw time.
|
||||
///
|
||||
/// <para>
|
||||
|
|
@ -16,11 +16,11 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// a server <c>CreateObject</c> is processed; destroyed by
|
||||
/// <c>EntitySpawnAdapter.OnRemove</c> on <c>RemoveObject</c>. The mesh
|
||||
/// data backing each part is cached in WB's <c>ObjectMeshManager</c>;
|
||||
/// per-instance customizations don't go through the atlas — they overlay
|
||||
/// per-instance customizations don't go through the atlas — they overlay
|
||||
/// at draw time.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class AnimatedEntityState
|
||||
internal sealed class AnimatedEntityState
|
||||
{
|
||||
private readonly Dictionary<int, ulong> _partGfxObjOverrides = new();
|
||||
private ulong _hiddenMask = 0;
|
||||
|
|
@ -49,7 +49,7 @@ public sealed class AnimatedEntityState
|
|||
}
|
||||
|
||||
/// <summary>Override the GfxObj id for a Setup part. Used for
|
||||
/// AnimPartChange — e.g. wielding a weapon swaps the hand-part's
|
||||
/// AnimPartChange — e.g. wielding a weapon swaps the hand-part's
|
||||
/// GfxObj.</summary>
|
||||
public void SetPartOverride(int partIdx, ulong gfxObjId)
|
||||
=> _partGfxObjOverrides[partIdx] = gfxObjId;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Silk.NET.OpenGL;
|
||||
using Silk.NET.OpenGL;
|
||||
using Silk.NET.OpenGL.Extensions.ARB;
|
||||
using AcDream.App.Rendering;
|
||||
|
||||
|
|
@ -9,7 +9,7 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// for the modern rendering path. Constructed once at startup via
|
||||
/// <see cref="TryCreate"/>, which returns false if the extension isn't present.
|
||||
/// </summary>
|
||||
public sealed class BindlessSupport
|
||||
internal sealed class BindlessSupport
|
||||
{
|
||||
private readonly GL _gl;
|
||||
private readonly ArbBindlessTexture _ext;
|
||||
|
|
@ -63,7 +63,7 @@ public sealed class BindlessSupport
|
|||
/// make it resident. Idempotent per (texture, sampler) pair.
|
||||
///
|
||||
/// Added for Campaign V slice V1's <c>GlGpuDevice.RegisterTexture</c>,
|
||||
/// which registers a (texture, sampler) pair per the RHI contract — "the
|
||||
/// which registers a (texture, sampler) pair per the RHI contract — "the
|
||||
/// same texture registered with two samplers occupies two slots." The
|
||||
/// texture-only <see cref="GetResidentHandle(uint)"/> above cannot express
|
||||
/// that; <c>ManagedGLTextureArray</c> already calls the equivalent
|
||||
|
|
@ -117,7 +117,7 @@ public sealed class BindlessSupport
|
|||
// and removed when terrain rendering surfaced GL_INVALID_OPERATION on
|
||||
// NVIDIA Windows for the `uniform sampler2DArray` + glProgramUniformHandleARB
|
||||
// combination. The replacement pattern (uvec2 handle uniform + GLSL
|
||||
// sampler-from-handle constructor — see terrain_modern.frag) lives at the
|
||||
// sampler-from-handle constructor — see terrain_modern.frag) lives at the
|
||||
// call site via plain `_gl.ProgramUniform2(program, loc, low, high)`. If
|
||||
// you re-introduce a sampler-handle helper, restrict it to drivers known
|
||||
// to accept the direct sampler-uniform path.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Chorizite.Core.Render.Enums;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using Silk.NET.OpenGL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
@ -7,7 +7,7 @@ using System.Text;
|
|||
using System.Threading.Tasks;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb {
|
||||
public static class BufferUsageExtensions {
|
||||
internal static class BufferUsageExtensions {
|
||||
/// <summary>
|
||||
/// Converts a BufferUsage to a GL BufferUsageARB
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
/// <summary>
|
||||
/// Phase A8 (2026-05-26): a logical building — one or more EnvCells linked
|
||||
/// Phase A8 (2026-05-26): a logical building — one or more EnvCells linked
|
||||
/// via the dat-level <c>LandBlockInfo.Buildings</c> entry. Building shells (cottage
|
||||
/// walls, inn walls — <c>IsBuildingShell=true</c> entities) are scoped to this
|
||||
/// walls, inn walls — <c>IsBuildingShell=true</c> entities) are scoped to this
|
||||
/// building's cells via their dat-derived anchor. The exit portal polygons are
|
||||
/// stencil-marked so outdoor visibility leaks through portal silhouettes only.
|
||||
///
|
||||
|
|
@ -19,7 +19,7 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// Retail reference: <c>docs/research/named-retail/acclient.h:32035</c>
|
||||
/// (<c>BuildInfo</c>) + <c>32094</c> (<c>CBldPortal</c>).</para>
|
||||
/// </summary>
|
||||
public sealed class Building
|
||||
internal sealed class Building
|
||||
{
|
||||
/// <summary>Unique within a landblock; allocated sequentially by <see cref="BuildingLoader"/>
|
||||
/// starting at 1 (0 is reserved for "no building" semantics on <c>LoadedCell</c>).</summary>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
using DatReaderWriter.DBObjs;
|
||||
|
|
@ -14,11 +14,11 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// <para>Algorithm (mirrors WB's <c>PortalService.GetPortalsByBuilding</c> at
|
||||
/// <c>WorldBuilder.Shared/Services/PortalService.cs:43-97</c>):</para>
|
||||
/// <list type="bullet">
|
||||
/// <item>Step A — seed the cell set from <c>BuildingInfo.Portals</c> entry portals.</item>
|
||||
/// <item>Step B — BFS through <see cref="LoadedCell.Portals"/> to discover all
|
||||
/// <item>Step A — seed the cell set from <c>BuildingInfo.Portals</c> entry portals.</item>
|
||||
/// <item>Step B — BFS through <see cref="LoadedCell.Portals"/> to discover all
|
||||
/// interior cells reachable from the entry portals (interior portals only;
|
||||
/// exit portals — <c>OtherCellId == 0xFFFF</c> — terminate each BFS branch).</item>
|
||||
/// <item>Step C — collect exit portal polygons in world space for the stencil
|
||||
/// exit portals — <c>OtherCellId == 0xFFFF</c> — terminate each BFS branch).</item>
|
||||
/// <item>Step C — collect exit portal polygons in world space for the stencil
|
||||
/// pipeline (Phase A8 Steps 1+2, RR7 scope).</item>
|
||||
/// </list>
|
||||
///
|
||||
|
|
@ -57,7 +57,7 @@ internal sealed class BuildingRegistryPublication
|
|||
internal bool PublicationCommitted { get; set; }
|
||||
}
|
||||
|
||||
public static class BuildingLoader
|
||||
internal static class BuildingLoader
|
||||
{
|
||||
/// <summary>
|
||||
/// Builds a <see cref="BuildingRegistry"/> from the supplied landblock data.
|
||||
|
|
|
|||
|
|
@ -1,16 +1,16 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
/// <summary>
|
||||
/// Phase A8 (2026-05-26): per-landblock registry of <see cref="Building"/>s.
|
||||
/// Two-way indexed for O(1) cell→building and building-id→building lookups.
|
||||
/// Two-way indexed for O(1) cell→building and building-id→building lookups.
|
||||
/// Built once per landblock at load time by <see cref="BuildingLoader"/>;
|
||||
/// no mutations occur after initial population.
|
||||
///
|
||||
/// <para>The cell→building index uses a <c>List<Building></c> value type
|
||||
/// to handle the (rare but valid) case where two buildings share an EnvCell —
|
||||
/// <para>The cell→building index uses a <c>List<Building></c> value type
|
||||
/// to handle the (rare but valid) case where two buildings share an EnvCell —
|
||||
/// each building performs its own BFS so a shared boundary cell ends up in both
|
||||
/// <c>EnvCellIds</c> sets. <see cref="GetBuildingsContainingCell"/> returns all
|
||||
/// owners so RR7's render path can pick the correct one.</para>
|
||||
|
|
@ -19,13 +19,13 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// (<c>BuildingPortalGroup</c>). Design:
|
||||
/// <c>docs/superpowers/specs/2026-05-26-phase-a8-wb-full-port-design.md</c>.</para>
|
||||
/// </summary>
|
||||
public sealed class BuildingRegistry
|
||||
internal sealed class BuildingRegistry
|
||||
{
|
||||
// Index 1: cell-id → list of buildings containing that cell.
|
||||
// Index 1: cell-id → list of buildings containing that cell.
|
||||
// Cells may belong to multiple buildings (rare; handled via List<Building>).
|
||||
private readonly Dictionary<uint, List<Building>> _byCellId = new();
|
||||
|
||||
// Index 2: building-id → Building.
|
||||
// Index 2: building-id → Building.
|
||||
private readonly Dictionary<uint, Building> _byBuildingId = new();
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb {
|
||||
// Extracted verbatim from WorldBuilder.Shared/Models/DebugRenderSettings.cs.
|
||||
// LandscapeColorsSettings dependency (editor-only, CommunityToolkit.Mvvm) stripped;
|
||||
// default color values inlined from LandscapeColorsSettings field initializers.
|
||||
public class DebugRenderSettings {
|
||||
internal class DebugRenderSettings {
|
||||
public bool ShowBoundingBoxes { get; set; } = false;
|
||||
public bool SelectVertices { get; set; } = true;
|
||||
public bool SelectBuildings { get; set; } = true;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// Total size 20 bytes; arrays are typically uploaded with stride = sizeof(this).
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct DrawElementsIndirectCommand
|
||||
internal struct DrawElementsIndirectCommand
|
||||
{
|
||||
public uint Count; // index count for this draw
|
||||
public uint InstanceCount; // number of instances
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.World;
|
||||
|
|
@ -11,7 +11,7 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// logical removal has been queued and must be retried by the live-entity
|
||||
/// teardown owner after the transition unwinds.
|
||||
/// </summary>
|
||||
public sealed class EntityPresentationRemovalDeferredException(uint serverGuid)
|
||||
internal sealed class EntityPresentationRemovalDeferredException(uint serverGuid)
|
||||
: InvalidOperationException(
|
||||
$"Live entity 0x{serverGuid:X8} presentation removal is deferred until its active reference transition completes.");
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ public sealed class EntityPresentationRemovalDeferredException(uint serverGuid)
|
|||
/// both to the created <see cref="AnimatedEntityState"/>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class EntitySpawnAdapter
|
||||
internal sealed class EntitySpawnAdapter
|
||||
{
|
||||
private readonly IEntityTextureLifetime _textureLifetime;
|
||||
private readonly Func<WorldEntity, AnimationSequencer> _sequencerFactory;
|
||||
|
|
@ -161,14 +161,14 @@ public sealed class EntitySpawnAdapter
|
|||
}
|
||||
|
||||
// A.5 T18: populate cached AABB so WalkEntities reads from the cache
|
||||
// rather than recomputing Position±5 per frame. Called here because
|
||||
// rather than recomputing Position±5 per frame. Called here because
|
||||
// all entity-state initialization (position, rotation) is complete
|
||||
// by this point via the WorldEntity passed in.
|
||||
entity.RefreshAabb();
|
||||
|
||||
// Build the per-entity AnimatedEntityState. The sequencer factory
|
||||
// may return a stub (in tests) or a fully-constructed sequencer from
|
||||
// the MotionTable (in production). Factory must not return null —
|
||||
// the MotionTable (in production). Factory must not return null —
|
||||
// if the entity has no motion table the factory should construct a
|
||||
// no-op sequencer (Setup + empty MotionTable + NullAnimationLoader).
|
||||
var sequencer = _sequencerFactory(entity);
|
||||
|
|
@ -185,7 +185,7 @@ public sealed class EntitySpawnAdapter
|
|||
|
||||
// Snapshot each unique GfxObj id for the shorter presentation lifetime.
|
||||
// Includes both the entity's natural MeshRefs AND any server-sent
|
||||
// PartOverride GfxObjs (weapons, clothing, helmets) — those replace the
|
||||
// PartOverride GfxObjs (weapons, clothing, helmets) — those replace the
|
||||
// Setup default and need their own mesh data uploaded.
|
||||
// Construct the replacement completely before displacing a live owner.
|
||||
// Sequencer/appearance construction is allowed to fail; in that case
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Immutable;
|
||||
using System.Collections.Immutable;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Rendering.Wb;
|
||||
using DatReaderWriter.DBObjs;
|
||||
|
|
@ -12,7 +12,7 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// placement data; the render thread schedules mesh preparation when it commits
|
||||
/// the containing <see cref="EnvCellLandblockBuild"/>.
|
||||
/// </summary>
|
||||
public sealed record EnvCellShellPlacement(
|
||||
internal sealed record EnvCellShellPlacement(
|
||||
uint CellId,
|
||||
ulong GeometryId,
|
||||
uint EnvironmentId,
|
||||
|
|
@ -29,7 +29,7 @@ public sealed record EnvCellShellPlacement(
|
|||
/// both portal-visibility cells and drawable shell placements so neither can be
|
||||
/// drained by, or mixed with, another streaming completion.
|
||||
/// </summary>
|
||||
public sealed class EnvCellLandblockBuild
|
||||
internal sealed class EnvCellLandblockBuild
|
||||
{
|
||||
public EnvCellLandblockBuild(
|
||||
uint landblockId,
|
||||
|
|
@ -57,7 +57,7 @@ public sealed class EnvCellLandblockBuild
|
|||
/// global pending bags, instances of this class are never shared between jobs or
|
||||
/// observed by the render thread before <see cref="Build"/> returns.
|
||||
/// </summary>
|
||||
public sealed class EnvCellLandblockBuildBuilder
|
||||
internal sealed class EnvCellLandblockBuildBuilder
|
||||
{
|
||||
private readonly uint _landblockId;
|
||||
private readonly List<LoadedCell> _visibilityCells = new();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
|
|
@ -7,7 +7,7 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// one private shell at a time; commit publishes the completed immutable
|
||||
/// landblock snapshot with one owner dictionary replacement.
|
||||
/// </summary>
|
||||
public interface IEnvCellLandblockPublisher
|
||||
internal interface IEnvCellLandblockPublisher
|
||||
{
|
||||
EnvCellLandblockPublication PreparePublication(
|
||||
EnvCellLandblockBuild build);
|
||||
|
|
@ -17,7 +17,7 @@ public interface IEnvCellLandblockPublisher
|
|||
void CommitPublication(EnvCellLandblockPublication publication);
|
||||
}
|
||||
|
||||
public sealed class EnvCellLandblockPublication
|
||||
internal sealed class EnvCellLandblockPublication
|
||||
{
|
||||
internal EnvCellLandblockPublication(
|
||||
object owner,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
namespace AcDream.App.Rendering.Wb;
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
/// <summary>
|
||||
/// Starts CPU mesh extraction after the completed EnvCell build has been
|
||||
|
|
@ -7,7 +7,7 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// current streaming generation; stale portal destinations never keep decoder
|
||||
/// jobs or surface lists alive.
|
||||
/// </summary>
|
||||
public static class EnvCellMeshPreparationScheduler
|
||||
internal static class EnvCellMeshPreparationScheduler
|
||||
{
|
||||
public static void Schedule(
|
||||
EnvCellLandblockBuild build,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Phase A8 (2026-05-28): port of WB's EnvCellRenderManager. This is the
|
||||
// Phase A8 (2026-05-28): port of WB's EnvCellRenderManager. This is the
|
||||
// production cell-rendering pipeline for indoor visibility, replacing the
|
||||
// broken "cell as WorldEntity with MeshRef(envCellId)" approach that the
|
||||
// four reverted RR7 variants couldn't fix.
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
//
|
||||
// Note: we do NOT inherit from WB's ObjectRenderManagerBase. That base
|
||||
// class owns the landblock-streaming loop (Update, _pendingGeneration,
|
||||
// _uploadQueue). acdream's StreamingController already does that work —
|
||||
// _uploadQueue). acdream's StreamingController already does that work —
|
||||
// running a parallel loop would compete for dat I/O. Instead, streaming builds
|
||||
// a private EnvCellLandblockBuild and CommitLandblock publishes the completed
|
||||
// snapshot on the render thread.
|
||||
|
|
@ -29,7 +29,7 @@ using Silk.NET.OpenGL;
|
|||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
public sealed unsafe class EnvCellRenderer :
|
||||
internal sealed unsafe class EnvCellRenderer :
|
||||
IDisposable,
|
||||
IEnvCellLandblockPublisher
|
||||
{
|
||||
|
|
@ -39,7 +39,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
private readonly WbFrustum _frustum;
|
||||
|
||||
// Per-landblock storage. Key = full 32-bit landblock dat id (e.g. 0xA9B4FFFF).
|
||||
// WB EnvCellRenderManager.cs:75 uses ConcurrentDictionary<ushort, ObjectLandblock> _landblocks —
|
||||
// WB EnvCellRenderManager.cs:75 uses ConcurrentDictionary<ushort, ObjectLandblock> _landblocks —
|
||||
// we use uint (full LB id) because acdream uses 32-bit landblock keys throughout.
|
||||
private readonly ConcurrentDictionary<uint, EnvCellLandblock> _landblocks = new();
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
private Matrix4x4 _lastViewProjection = Matrix4x4.Identity;
|
||||
private bool _initialized;
|
||||
|
||||
// List pool — copied from WB ObjectRenderManagerBase.
|
||||
// List pool — copied from WB ObjectRenderManagerBase.
|
||||
// WB ObjectRenderManagerBase.cs:83-86: protected readonly List<List<InstanceData>> _listPool = new(); protected int _poolIndex = 0;
|
||||
private readonly List<List<InstanceData>> _listPool = new();
|
||||
private int _poolIndex = 0;
|
||||
|
|
@ -78,7 +78,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
private readonly ThreadLocal<PrepareScratch> _prepareScratch =
|
||||
new(() => new PrepareScratch(), trackAllValues: true);
|
||||
|
||||
// Modern-MDI scratch buffers (single slot — we re-upload every frame).
|
||||
// Modern-MDI scratch buffers (single slot — we re-upload every frame).
|
||||
// WB BaseObjectRenderManager.cs:43-48: _scratchMdiCommandBuffers, _scratchModernBatchBuffers, _modernInstanceBuffers
|
||||
// We collapse the ring-of-3 to a single slot since we have no persistent/consolidated draws.
|
||||
private uint _mdiCommandBuffer;
|
||||
|
|
@ -95,7 +95,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
// Phase U.3: per-instance clip-slot SSBO (binding=3), parallel to
|
||||
// _modernInstanceBuffer. One uint per instance selecting its CellClip slot,
|
||||
// indexed by the same BaseInstance + gl_InstanceID the shader uses for
|
||||
// binding=0. ALL ZEROS in U.3 ⇒ slot 0 ⇒ no-clip. U.4 populates real slots.
|
||||
// binding=0. ALL ZEROS in U.3 ⇒ slot 0 ⇒ no-clip. U.4 populates real slots.
|
||||
private uint _clipSlotBuffer;
|
||||
private int _clipSlotCapacity;
|
||||
private uint[] _clipSlotData = Array.Empty<uint>();
|
||||
|
|
@ -156,17 +156,17 @@ public sealed unsafe class EnvCellRenderer :
|
|||
// Campaign V slice V2 (2026-07-27): GL-only emulation of the eventual
|
||||
// Vulkan global texture descriptor array (binding=9,
|
||||
// GpuBindingModel.StorageTextureTable). Owns its own table rather than
|
||||
// sharing WbDrawDispatcher's — EnvCellRenderer never had a TextureCache
|
||||
// sharing WbDrawDispatcher's — EnvCellRenderer never had a TextureCache
|
||||
// dependency and nothing requires index agreement between renderers (each
|
||||
// rebinds its own buffer to binding=9 immediately before its own draw
|
||||
// call). See GlBindlessHandleTable's doc comment and the campaign doc's
|
||||
// §5.2. Lazily created; grown/uploaded only when a genuinely new handle
|
||||
// appears (rare — see FlushAndBindTextureTable).
|
||||
// §5.2. Lazily created; grown/uploaded only when a genuinely new handle
|
||||
// appears (rare — see FlushAndBindTextureTable).
|
||||
private readonly GlBindlessHandleTable _textureTable = new();
|
||||
private uint _textureTableSsbo;
|
||||
private int _textureTableSsboCapacityBytes;
|
||||
|
||||
// Reusable scratch arrays — avoid per-frame allocation.
|
||||
// Reusable scratch arrays — avoid per-frame allocation.
|
||||
// WB BaseObjectRenderManager.cs:58-59: private DrawElementsIndirectCommand[] _commands = Array.Empty<...>()
|
||||
private DrawElementsIndirectCommand[] _commands = Array.Empty<DrawElementsIndirectCommand>();
|
||||
private ModernBatchData[] _modernBatches = Array.Empty<ModernBatchData>();
|
||||
|
|
@ -192,7 +192,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
private readonly Dictionary<ulong, List<InstanceData>> _activeSnapshotGlobalGroups = new();
|
||||
private readonly List<ulong> _activeSnapshotGlobalGfxObjIds = new();
|
||||
|
||||
// Static render-state tracking — matches WB BaseObjectRenderManager.cs:24-28.
|
||||
// Static render-state tracking — matches WB BaseObjectRenderManager.cs:24-28.
|
||||
// Shared across all manager instances on the same GL context.
|
||||
private static uint _currentVao;
|
||||
private static CullMode? _currentCullMode;
|
||||
|
|
@ -204,8 +204,8 @@ public sealed unsafe class EnvCellRenderer :
|
|||
// inputs changed: landblock commits/removals (NeedsPrepare), the visible-cell
|
||||
// filter, the trim window, mesh render-data availability (the snapshot bakes
|
||||
// per-cell transparency from TryGetRenderData), or the view-projection.
|
||||
// NeedsPrepare existed since A8 but was never read — this wires it. The VP
|
||||
// tolerance must swallow the ~36 µm eye rest jitter (RetailPViewRenderer
|
||||
// NeedsPrepare existed since A8 but was never read — this wires it. The VP
|
||||
// tolerance must swallow the ~36 µm eye rest jitter (RetailPViewRenderer
|
||||
// R-A2 note) while any real camera motion crosses it in the same frame.
|
||||
private Matrix4x4 _preparedViewProjection;
|
||||
private Vector3 _preparedCameraPosition;
|
||||
|
|
@ -223,14 +223,14 @@ public sealed unsafe class EnvCellRenderer :
|
|||
public bool IsDisposed { get; private set; }
|
||||
|
||||
public LastFrameStats Stats => _lastFrameStats;
|
||||
public struct LastFrameStats { public int CellsRendered; public int TrianglesDrawn; }
|
||||
internal struct LastFrameStats { public int CellsRendered; public int TrianglesDrawn; }
|
||||
private LastFrameStats _lastFrameStats;
|
||||
|
||||
/// <summary>
|
||||
/// Diagnostic accessor for the [envcells] probe (Phase A8 apparatus 2026-05-28).
|
||||
/// Returns (pool-list count total, snapshot's PostPreparePoolIndex high-water).
|
||||
/// A divergence between expected and actual values would indicate a pool-
|
||||
/// management regression — exactly the bug class the 2026-05-28 audit caught.
|
||||
/// management regression — exactly the bug class the 2026-05-28 audit caught.
|
||||
/// </summary>
|
||||
public (int PoolTotal, int SnapshotPoolHwm) GetPoolDiagnostics()
|
||||
{
|
||||
|
|
@ -338,20 +338,20 @@ public sealed unsafe class EnvCellRenderer :
|
|||
public void SetClipRegionSsbo(uint sharedClipRegionSsbo)
|
||||
=> _sharedClipRegionSsbo = sharedClipRegionSsbo;
|
||||
|
||||
// Phase U.4: per-frame cellId→CellClip-slot map for the cell shells. When
|
||||
// Phase U.4: per-frame cellId→CellClip-slot map for the cell shells. When
|
||||
// non-null, RenderModernMDIInternal writes instanceClipSlot[i] =
|
||||
// _cellIdToSlot[allInstances[i].CellId] so each cell's shell instances are
|
||||
// gated to that cell's portal-clip region. When null (U.3 path), every
|
||||
// instance maps to slot 0 (no-clip). A cell absent from the map writes slot 0
|
||||
// (no-clip) — but the caller's Render filter already restricts the draw to the
|
||||
// (no-clip) — but the caller's Render filter already restricts the draw to the
|
||||
// map's keys, so that fallback should not fire in practice.
|
||||
private IReadOnlyDictionary<uint, int>? _cellIdToSlot;
|
||||
|
||||
/// <summary>
|
||||
/// Phase U.4: install the per-frame cellId→slot map used to gate cell shells
|
||||
/// Phase U.4: install the per-frame cellId→slot map used to gate cell shells
|
||||
/// to their portal-clip regions. Call once per frame BEFORE
|
||||
/// <see cref="Render(WbRenderPass, HashSet{uint}?)"/>. Pass null to revert to
|
||||
/// the U.3 no-clip behavior (every shell instance → slot 0).
|
||||
/// the U.3 no-clip behavior (every shell instance → slot 0).
|
||||
/// </summary>
|
||||
public void SetClipRouting(IReadOnlyDictionary<uint, int>? cellIdToSlot)
|
||||
=> _cellIdToSlot = cellIdToSlot;
|
||||
|
|
@ -386,7 +386,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
surfaces);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CommitLandblock — render-thread transaction boundary
|
||||
// CommitLandblock — render-thread transaction boundary
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -586,7 +586,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
int? renderRadius = null)
|
||||
{
|
||||
// Phase U.4 fix: stash the view-projection so Render() can upload it itself.
|
||||
// Stashed even when the gate below skips the rebuild — Render must always
|
||||
// Stashed even when the gate below skips the rebuild — Render must always
|
||||
// project with the CURRENT frame's matrix (the U.4 stale-matrix root cause).
|
||||
_lastViewProjection = viewProjection;
|
||||
|
||||
|
|
@ -614,7 +614,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
return;
|
||||
}
|
||||
|
||||
// Prepare gate: every snapshot input unchanged → keep the active snapshot.
|
||||
// Prepare gate: every snapshot input unchanged → keep the active snapshot.
|
||||
// (Same-thread discipline makes the version sample exact: publish, release
|
||||
// tickets, and this method all run on the render thread.)
|
||||
if (_hasPreparedSnapshot
|
||||
|
|
@ -633,7 +633,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
lock (_renderLock) { _poolIndex = 0; }
|
||||
|
||||
// WB skips _cameraLbX/Y update (from LandscapeDoc.Region) here in our variant
|
||||
// because we don't need camera-LB tracking for the snapshot — just frustum tests.
|
||||
// because we don't need camera-LB tracking for the snapshot — just frustum tests.
|
||||
|
||||
// WB EnvCellRenderManager.cs:262:
|
||||
// Filter loaded landblocks by GpuReady + Instances non-empty.
|
||||
|
|
@ -674,7 +674,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
|
||||
PrepareScratch scratch = _prepareScratch.Value!;
|
||||
|
||||
// WB EnvCellRenderManager.cs:279-295: fast path — LB fully inside.
|
||||
// WB EnvCellRenderManager.cs:279-295: fast path — LB fully inside.
|
||||
if (testResult == FrustumTestResult.Inside)
|
||||
{
|
||||
foreach (var (gfxObjId, instances) in lb.BuildingPartGroups)
|
||||
|
|
@ -692,7 +692,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
return;
|
||||
}
|
||||
|
||||
// WB EnvCellRenderManager.cs:298-324: slow path — per-cell frustum test.
|
||||
// WB EnvCellRenderManager.cs:298-324: slow path — per-cell frustum test.
|
||||
HashSet<uint> visibleCells = scratch.VisibleCells;
|
||||
visibleCells.Clear();
|
||||
foreach (var kvp in lb.EnvCellBounds)
|
||||
|
|
@ -804,13 +804,13 @@ public sealed unsafe class EnvCellRenderer :
|
|||
/// <summary>
|
||||
/// Pure half of the prepare gate's camera test (regression-tested without a
|
||||
/// GL context, same pattern as <see cref="CreateCommittedSnapshot"/>).
|
||||
/// Eye position uses a 1 mm ABSOLUTE epsilon: it swallows the ~36 µm rest
|
||||
/// Eye position uses a 1 mm ABSOLUTE epsilon: it swallows the ~36 µm rest
|
||||
/// jitter but dirties on any real movement (a slow walk moves 20+ mm/frame).
|
||||
/// Position must not be tested through the matrix — the view-projection's
|
||||
/// Position must not be tested through the matrix — the view-projection's
|
||||
/// translation row scales with world coordinates (~5e4 in AC), where a
|
||||
/// relative tolerance would mask sub-meter motion. Rows 1–3 of
|
||||
/// view × projection are position-independent (rotation × projection), so a
|
||||
/// relative 1e-5 there dirties at ≈0.001° of rotation and on any
|
||||
/// relative tolerance would mask sub-meter motion. Rows 1–3 of
|
||||
/// view × projection are position-independent (rotation × projection), so a
|
||||
/// relative 1e-5 there dirties at ≈0.001° of rotation and on any
|
||||
/// projection (FOV/aspect/near/far) change.
|
||||
/// </summary>
|
||||
internal static bool CameraApproximatelyEqual(
|
||||
|
|
@ -926,7 +926,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
// Verbatim port of WB EnvCellRenderManager.cs:395-511.
|
||||
// Deviations from WB (all documented):
|
||||
// - Drop the _useModernRendering branch (our codebase asserts modern at startup per Phase N.5).
|
||||
// - Drop SelectedInstance/HoveredInstance highlight block (lines 486-510) — no editor state.
|
||||
// - Drop SelectedInstance/HoveredInstance highlight block (lines 486-510) — no editor state.
|
||||
// - Replace RenderModernMDI(base) with private RenderModernMDIInternal.
|
||||
// - shader.Bind() / SetUniform API: mapped to acdream's legacy Shader
|
||||
// class (Use() + SetInt/SetVec4/SetMatrix4) to match the existing
|
||||
|
|
@ -947,7 +947,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
/// filter (the drawable visible cells from the PView traversal; each cell's
|
||||
/// shell instances are clip-gated to its CellClip slot by the caller's
|
||||
/// binding=3 map). NOTE: this is NOT the old two-pipe RenderInsideOut approach
|
||||
/// — that flat camera-inside-building stencil pass was deleted in Phase U.1.
|
||||
/// — that flat camera-inside-building stencil pass was deleted in Phase U.1.
|
||||
/// Source: WB EnvCellRenderManager.cs:399-511 (verbatim minus selection highlights).
|
||||
/// </summary>
|
||||
public void Render(WbRenderPass renderPass, HashSet<uint>? filter)
|
||||
|
|
@ -979,7 +979,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
// WB EnvCellRenderManager.cs:403-404:
|
||||
_shader.Use();
|
||||
// FIX 2026-05-28 (pool aliasing root cause): mirror WB
|
||||
// EnvCellRenderManager.cs:405 — restore the pool cursor to the
|
||||
// EnvCellRenderManager.cs:405 — restore the pool cursor to the
|
||||
// high-water mark Prepare's merge phase reached, so any
|
||||
// GetPooledList calls below return lists past the snapshot's
|
||||
// owned region. Original code used `snapshot.BatchedByCell.Count`
|
||||
|
|
@ -999,7 +999,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
// RenderInsideOutAcdream stencil pipeline) change the actual GL
|
||||
// state without updating these caches. The cache then lies, and
|
||||
// the per-batch SetCullMode in RenderModernMDIInternal skips its
|
||||
// glCullFace call — leaving stale cull state from the prior
|
||||
// glCullFace call — leaving stale cull state from the prior
|
||||
// consumer. For a cottage with mixed CullMode batches, half the
|
||||
// walls end up culled and the user sees "missing walls".
|
||||
//
|
||||
|
|
@ -1012,15 +1012,15 @@ public sealed unsafe class EnvCellRenderer :
|
|||
_shader.SetInt("uRenderPass", (int)renderPass);
|
||||
_shader.SetInt("uFilterByCell", 0);
|
||||
_shader.SetInt("uLightingMode", 1); // A7 Fix D D-3/D-4: EnvCell bake (wrap points, no sun)
|
||||
// #176 stripe-hunt isolation (ACDREAM_LIGHT_DEBUG) — throwaway diagnostic.
|
||||
// #176 stripe-hunt isolation (ACDREAM_LIGHT_DEBUG) — throwaway diagnostic.
|
||||
_shader.SetInt("uLightDebug", AcDream.Core.Rendering.RenderingDiagnostics.LightDebugMode);
|
||||
|
||||
// Phase U.4 ROOT-CAUSE FIX (cell-shell flicker / "transparent walls when
|
||||
// moving"): upload uViewProjection HERE rather than inheriting it from
|
||||
// WbDrawDispatcher. The opaque shell pass runs BEFORE the dispatcher's
|
||||
// Draw (GameWindow ~7411 vs ~7418, the only other setter), so without
|
||||
// this the opaque shells used the PREVIOUS frame's matrix — a stale
|
||||
// gl_Position against this frame's clip planes → pose-dependent clipping,
|
||||
// this the opaque shells used the PREVIOUS frame's matrix — a stale
|
||||
// gl_Position against this frame's clip planes → pose-dependent clipping,
|
||||
// worst while moving. Same self-contained-GL-state precedent as the
|
||||
// 2026-05-28 cull-state cache fix above.
|
||||
_shader.SetMatrix4("uViewProjection", _lastViewProjection);
|
||||
|
|
@ -1059,7 +1059,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
else if (filter is null)
|
||||
{
|
||||
RebuildUnfilteredGroups(snapshot);
|
||||
// WB EnvCellRenderManager.cs:418-429: optimized path — global groups.
|
||||
// WB EnvCellRenderManager.cs:418-429: optimized path — global groups.
|
||||
foreach (var gfxObjId in _activeSnapshotGlobalGfxObjIds)
|
||||
{
|
||||
if (_activeSnapshotGlobalGroups.TryGetValue(gfxObjId, out var transforms))
|
||||
|
|
@ -1144,7 +1144,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
renderPass);
|
||||
}
|
||||
|
||||
// WB EnvCellRenderManager.cs:486-510: selection/hover highlights — DROPPED (no editor state).
|
||||
// WB EnvCellRenderManager.cs:486-510: selection/hover highlights — DROPPED (no editor state).
|
||||
|
||||
// WB EnvCellRenderManager.cs:506-509: cleanup.
|
||||
_shader.SetVec4("uHighlightColor", new System.Numerics.Vector4(0, 0, 0, 0));
|
||||
|
|
@ -1170,12 +1170,12 @@ public sealed unsafe class EnvCellRenderer :
|
|||
? dc.renderData.Batches[0].IndexCount / 3
|
||||
: 0) * dc.count;
|
||||
|
||||
// Issue #78 (2026-05-31) [shell] probe (ACDREAM_PROBE_SHELL) — THROWAWAY.
|
||||
// Issue #78 (2026-05-31) [shell] probe (ACDREAM_PROBE_SHELL) — THROWAWAY.
|
||||
// Per opaque-pass call: totals + per visible (filtered) cell whether it is
|
||||
// present in the prepared snapshot, and its geometry/flags. Answers why the
|
||||
// interior walls/ceiling don't appear: NOSNAP / gfx=0 ⇒ no shell geometry
|
||||
// prepared for the cell; idx>0 + zh>0 ⇒ prepared but missing bindless texture
|
||||
// (invisible); idx>0 + zh=0 + tr=0 ⇒ opaque geometry drawn (fault is depth/
|
||||
// interior walls/ceiling don't appear: NOSNAP / gfx=0 ⇒ no shell geometry
|
||||
// prepared for the cell; idx>0 + zh>0 ⇒ prepared but missing bindless texture
|
||||
// (invisible); idx>0 + zh=0 + tr=0 ⇒ opaque geometry drawn (fault is depth/
|
||||
// occlusion or the geometry isn't the wall). Opaque pass only (halves noise).
|
||||
if (renderPass == WbRenderPass.Opaque
|
||||
&& AcDream.Core.Rendering.RenderingDiagnostics.ProbeShellEnabled)
|
||||
|
|
@ -1217,7 +1217,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
/// <summary>
|
||||
/// True if the cell's prepared snapshot has any transparent render batch.
|
||||
/// The pview shell pass uses this to skip the (heavy per-frame) transparent
|
||||
/// <see cref="Render"/> call for opaque-only cells — most cell geometry is
|
||||
/// <see cref="Render"/> call for opaque-only cells — most cell geometry is
|
||||
/// opaque walls/floors/ceilings, so this removes the bulk of the per-cell
|
||||
/// transparent draws. Read-only; mirrors the [shell] probe's batch scan.
|
||||
/// </summary>
|
||||
|
|
@ -1227,7 +1227,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
// ---------------------------------------------------------------------------
|
||||
// GetCellLightSet (A7 Fix D D-2 helper)
|
||||
// Per-cell up-to-8 point lights, cached per frame. Camera-independent, like
|
||||
// WbDrawDispatcher.ComputeEntityLightSet — keyed on the cell's world bounds.
|
||||
// WbDrawDispatcher.ComputeEntityLightSet — keyed on the cell's world bounds.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// A7 Fix D (D-2): the up-to-8 point lights reaching a cell, by the cell's world
|
||||
|
|
@ -1248,21 +1248,21 @@ public sealed unsafe class EnvCellRenderer :
|
|||
|
||||
var snap = _pointSnapshot;
|
||||
// Landblocks are keyed by the streaming landblock id 0xXXYYFFFF
|
||||
// (GameWindow: (x<<24)|(y<<16)|0xFFFF), NOT 0xXXYY0000 — so the landblock
|
||||
// (GameWindow: (x<<24)|(y<<16)|0xFFFF), NOT 0xXXYY0000 — so the landblock
|
||||
// key is (cellId & 0xFFFF0000) | 0xFFFF. The old `cellId & 0xFFFF0000` key
|
||||
// (0xXXYY0000) NEVER matched a registered landblock, so this lookup always
|
||||
// missed: SelectForObject never ran and every EnvCell wall received ZERO
|
||||
// point lights (the entire "indoor torches/lanterns don't light the room"
|
||||
// bug — confirmed by the [cell-light] probe: inBounds=False for every cell).
|
||||
// bug — confirmed by the [cell-light] probe: inBounds=False for every cell).
|
||||
if (snap is { Count: > 0 } &&
|
||||
_landblocks.TryGetValue((cellId & 0xFFFF0000u) | 0xFFFFu, out var lb) &&
|
||||
lb.EnvCellBounds.TryGetValue(cellId, out var b))
|
||||
{
|
||||
Vector3 center = (b.Min + b.Max) * 0.5f;
|
||||
float radius = (b.Max - b.Min).Length() * 0.5f;
|
||||
// #176 flap fix: cells use SelectForCell (retail minimize_envcell_lighting) — ALL
|
||||
// #176 flap fix: cells use SelectForCell (retail minimize_envcell_lighting) — ALL
|
||||
// dynamic lights on every cell (stable), not the per-object sphere-overlap cull that
|
||||
// let the portal set flip as the flood shifted → floor-lighting flap.
|
||||
// let the portal set flip as the flood shifted → floor-lighting flap.
|
||||
AcDream.Core.Lighting.LightManager.SelectForCell(snap, center, radius, set);
|
||||
}
|
||||
cached.FrameGeneration = _lightFrameGeneration;
|
||||
|
|
@ -1414,9 +1414,9 @@ public sealed unsafe class EnvCellRenderer :
|
|||
int passIdx = (int)renderPass;
|
||||
if (passIdx < 0 || passIdx > 2) return;
|
||||
|
||||
// §4 outdoor full-world flap (2026-06-10): hoisted from below the SSBO uploads.
|
||||
// §4 outdoor full-world flap (2026-06-10): hoisted from below the SSBO uploads.
|
||||
// Without the global VAO nothing can draw, and returning AFTER the pass state
|
||||
// was established leaked it (same early-out shape as the totalDraws==0 leak —
|
||||
// was established leaked it (same early-out shape as the totalDraws==0 leak —
|
||||
// see the comment on the state-establish block below).
|
||||
var globalVao = _meshManager.GlobalBuffer?.VAO ?? 0u;
|
||||
if (globalVao == 0) return;
|
||||
|
|
@ -1468,14 +1468,14 @@ public sealed unsafe class EnvCellRenderer :
|
|||
// transparent). Restored to opaque defaults at the end of the draw loop so a
|
||||
// Transparent pass can't leak into later draws.
|
||||
//
|
||||
// §4 outdoor full-world flap fix (2026-06-10): this block MOVED below the
|
||||
// §4 outdoor full-world flap fix (2026-06-10): this block MOVED below the
|
||||
// totalDraws==0 early-out above. It used to run before the batch grouping, so a
|
||||
// Transparent pass over a cell whose batches are ALL opaque (a plain cottage
|
||||
// interior) set Blend-on/DepthMask-off and then returned at the count check
|
||||
// WITHOUT reaching the restore. The frame ended with dmask=0; the NEXT frame's
|
||||
// glClear(DEPTH) silently no-oped (depth clears honor glDepthMask), every world
|
||||
// fragment failed GL_LESS against its own previous-frame depth ghost, and the
|
||||
// whole screen dropped to the fog-tinted clear color — onset-locked to the
|
||||
// whole screen dropped to the fog-tinted clear color — onset-locked to the
|
||||
// building-flood merge (the first frame a flooded building shell draws), holding
|
||||
// until camera rotation dropped the cell from the flood. From here down every
|
||||
// path reaches the end-of-pass restore.
|
||||
|
|
@ -1689,14 +1689,14 @@ public sealed unsafe class EnvCellRenderer :
|
|||
// Phase U.4: upload the per-instance clip-slot buffer (binding=3). When
|
||||
// _cellIdToSlot is set (indoor routing), each cell shell instance is gated
|
||||
// to its cell's CellClip slot via allInstances[i].CellId; cells absent from
|
||||
// the map (shouldn't happen — the Render filter is the map's keys) and the
|
||||
// the map (shouldn't happen — the Render filter is the map's keys) and the
|
||||
// U.3 path both map to slot 0 (no-clip). allInstances is laid out in the
|
||||
// SAME order as the binding=0 transforms (_gpuInstanceTransforms below), so
|
||||
// instanceClipSlot[i] tracks Instances[i] through the MDI BaseInstance.
|
||||
if (_clipSlotData.Length < uniqueInstanceCount)
|
||||
_clipSlotData = new uint[Math.Max(_clipSlotData.Length * 2, uniqueInstanceCount)];
|
||||
// #176 stripe-hunt isolation (ACDREAM_CLIP_DEBUG=1): force every shell
|
||||
// instance to slot 0 (no-clip) — retail draws cell shells WHOLE.
|
||||
// instance to slot 0 (no-clip) — retail draws cell shells WHOLE.
|
||||
if (_cellIdToSlot is null
|
||||
|| AcDream.Core.Rendering.RenderingDiagnostics.ClipDebugNoShellTrim)
|
||||
{
|
||||
|
|
@ -1728,7 +1728,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
|
||||
// #176 seam-draw probe: emitted HERE (not in Render) so the per-cell light
|
||||
// sets read through the just-cleared cache against THIS frame's
|
||||
// _pointSnapshot — the exact data the SSBO upload below carries.
|
||||
// _pointSnapshot — the exact data the SSBO upload below carries.
|
||||
if (renderPass == WbRenderPass.Opaque
|
||||
&& AcDream.Core.Rendering.RenderingDiagnostics.ProbeSeamDrawEnabled)
|
||||
EmitSeamDrawProbe(drawCalls, allInstances, _seamProbeFilter);
|
||||
|
|
@ -1766,7 +1766,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
PersistActiveDynamicBufferCapacities();
|
||||
|
||||
// WB BaseObjectRenderManager.cs:807-818: bind VAO + SSBOs + barrier.
|
||||
// (globalVao validated at the top of the method — a return here would leak the
|
||||
// (globalVao validated at the top of the method — a return here would leak the
|
||||
// pass state established above.)
|
||||
if (_currentVao != globalVao)
|
||||
{
|
||||
|
|
@ -1865,16 +1865,16 @@ public sealed unsafe class EnvCellRenderer :
|
|||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// #176 seam-draw probe (ACDREAM_PROBE_SEAMDRAW) — throwaway apparatus.
|
||||
// #176 seam-draw probe (ACDREAM_PROBE_SEAMDRAW) — throwaway apparatus.
|
||||
// The in-engine replacement for the RenderDoc pixel-history the pipeline
|
||||
// can't have (RenderDoc hides GL_ARB_bindless_texture → our mandatory-modern
|
||||
// startup gate throws). Per opaque pass: for each target cell — flood
|
||||
// can't have (RenderDoc hides GL_ARB_bindless_texture → our mandatory-modern
|
||||
// startup gate throws). Per opaque pass: for each target cell — flood
|
||||
// membership, every shell instance (count + translation, F3 z shows the
|
||||
// +0.02 lift; n≥2 for one (cell,gfx) = the runtime double-draw), and the
|
||||
// +0.02 lift; n≥2 for one (cell,gfx) = the runtime double-draw), and the
|
||||
// cell's 8-light set resolved to stable IDENTITIES (owner-cell low16 +
|
||||
// intensity; raw indices shuffle when the pool rebuilds). Plus the
|
||||
// snapshot's HOT lights (intensity ≥ 50 — the portal purples; fixtures are
|
||||
// ~1–2). Change-deduped block with a 2 s heartbeat: a purple identity
|
||||
// snapshot's HOT lights (intensity ≥ 50 — the portal purples; fixtures are
|
||||
// ~1–2). Change-deduped block with a 2 s heartbeat: a purple identity
|
||||
// flipping with flood membership = the snapshot-scope mechanism; two
|
||||
// coincident instances = the z-fight. See RenderingDiagnostics.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -1995,8 +1995,8 @@ public sealed unsafe class EnvCellRenderer :
|
|||
/// Uploads <see cref="_textureTable"/>'s handles to <see cref="_textureTableSsbo"/>
|
||||
/// when a new one was registered since the last flush, then (re)binds it at
|
||||
/// <see cref="AcDream.App.Rendering.Gpu.GpuBindingModel.StorageTextureTable"/>.
|
||||
/// A genuinely new handle is rare — new dat surfaces/atlases, not every
|
||||
/// frame — so this is not part of the ring-buffered per-frame SSBO set;
|
||||
/// A genuinely new handle is rare — new dat surfaces/atlases, not every
|
||||
/// frame — so this is not part of the ring-buffered per-frame SSBO set;
|
||||
/// see GlBindlessHandleTable's doc comment.
|
||||
/// </summary>
|
||||
private void FlushAndBindTextureTable()
|
||||
|
|
@ -2068,7 +2068,7 @@ public sealed unsafe class EnvCellRenderer :
|
|||
GLEnum.DynamicDraw,
|
||||
"allocating EnvCell fallback clip SSBO");
|
||||
allocated = true;
|
||||
// One CellClip slot, all zeros: count 0 ⇒ shader passes every plane.
|
||||
// One CellClip slot, all zeros: count 0 ⇒ shader passes every plane.
|
||||
Span<byte> zero = stackalloc byte[AcDream.App.Rendering.ClipFrame.CellClipStrideBytes];
|
||||
zero.Clear();
|
||||
fixed (byte* p = zero)
|
||||
|
|
@ -2103,12 +2103,12 @@ public sealed unsafe class EnvCellRenderer :
|
|||
|
||||
private List<InstanceData> GetPooledList()
|
||||
{
|
||||
// Mirrors WB ObjectRenderManagerBase.cs:1221-1233 — the reuse
|
||||
// Mirrors WB ObjectRenderManagerBase.cs:1221-1233 — the reuse
|
||||
// branch MUST clear the list before returning. PrepareRenderBatches'
|
||||
// merge phase pattern is `gfxDict[k] = list; list.AddRange(...)`,
|
||||
// which assumes the list is empty. Without the clear, lists grow
|
||||
// unbounded across frames and each frame's draw includes all prior
|
||||
// frames' stale data. Original port omitted the Clear() call — root
|
||||
// frames' stale data. Original port omitted the Clear() call — root
|
||||
// cause of post-Wave-5 visual chaos (FIX 2026-05-28). See
|
||||
// docs/research/2026-05-28-a8-env-cell-renderer-audit-findings.md.
|
||||
lock (_listPool)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
// Ported from references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/SceneryInstance.cs
|
||||
// Ported from references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/SceneryInstance.cs
|
||||
// Phase A8 extraction (2026-05-28). Verbatim port; adaptations:
|
||||
// - SceneryInstance -> EnvCellSceneryInstance (scope-narrow to env-cell rendering)
|
||||
// - ObjectLandblock -> EnvCellLandblock
|
||||
|
|
@ -17,7 +17,7 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// Lightweight data for a single placed env-cell scenery object.
|
||||
/// Source: references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/SceneryInstance.cs (lines 11-56)
|
||||
/// </summary>
|
||||
public struct EnvCellSceneryInstance
|
||||
internal struct EnvCellSceneryInstance
|
||||
{
|
||||
/// <summary>GfxObj or Setup ID from DAT.</summary>
|
||||
public ulong ObjectId;
|
||||
|
|
@ -67,7 +67,7 @@ public struct EnvCellSceneryInstance
|
|||
/// Shared by both scenery and static object render managers.
|
||||
/// Source: references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/SceneryInstance.cs (lines 62-160)
|
||||
/// </summary>
|
||||
public class EnvCellLandblock
|
||||
internal class EnvCellLandblock
|
||||
{
|
||||
/// <summary>Grid X coordinate of this landblock.</summary>
|
||||
public int GridX { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
|
|
@ -9,14 +9,14 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// narrowed to the fields <see cref="EnvCellRenderer"/> actually consumes
|
||||
/// (<c>BatchedByCell</c> + <c>VisibleLandblocks</c> + <c>PostPreparePoolIndex</c>).
|
||||
/// The scenery-side <c>VisibleGroups</c> / <c>VisibleGfxObjIds</c> /
|
||||
/// <c>IntersectingLandblocks</c> are dropped — we render scenery through
|
||||
/// <c>IntersectingLandblocks</c> are dropped — we render scenery through
|
||||
/// <see cref="WbDrawDispatcher"/>, not through this snapshot.
|
||||
///
|
||||
/// <para>Used as an immutable snapshot atomically swapped under the
|
||||
/// renderer's render lock so PrepareRenderBatches (worker-driven) and
|
||||
/// Render (render-thread-driven) can't race on a half-populated dict.</para>
|
||||
/// </summary>
|
||||
public sealed class EnvCellVisibilitySnapshot
|
||||
internal sealed class EnvCellVisibilitySnapshot
|
||||
{
|
||||
/// <summary>Landblocks fully or partially inside the frustum at prepare time.</summary>
|
||||
public List<EnvCellLandblock> VisibleLandblocks { get; init; } = new();
|
||||
|
|
@ -38,7 +38,7 @@ public sealed class EnvCellVisibilitySnapshot
|
|||
/// cursor to a safe region past the snapshot's owned lists, so any
|
||||
/// <c>GetPooledList</c> calls inside Render don't trample data the
|
||||
/// snapshot still references. Dropping this field caused the post-Wave-5
|
||||
/// visual chaos — see
|
||||
/// visual chaos — see
|
||||
/// <c>docs/research/2026-05-28-a8-env-cell-renderer-audit-findings.md</c>.</para>
|
||||
/// </summary>
|
||||
public int PostPreparePoolIndex { get; init; }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Silk.NET.Core.Native;
|
||||
using Silk.NET.OpenGL;
|
||||
using System;
|
||||
|
|
@ -6,7 +6,7 @@ using System.Runtime.CompilerServices;
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb {
|
||||
public static class GLHelpers {
|
||||
internal static class GLHelpers {
|
||||
public static OpenGLGraphicsDevice? Device { get; set; }
|
||||
public static ILogger? Logger { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Chorizite.Core.Render;
|
||||
using Chorizite.Core.Render;
|
||||
using AcDream.App.Rendering;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Silk.NET.OpenGL;
|
||||
|
|
@ -14,7 +14,7 @@ using System.Xml.Linq;
|
|||
|
||||
namespace AcDream.App.Rendering.Wb {
|
||||
|
||||
public unsafe class GLSLShader : BaseShader, IDisposable {
|
||||
internal unsafe class GLSLShader : BaseShader, IDisposable {
|
||||
private OpenGLGraphicsDevice _device;
|
||||
private Dictionary<string, int> _uniformLocations = [];
|
||||
private Dictionary<int, object> _uniformValues = [];
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
|
||||
// Phase O-T7: verbatim copy of WorldBuilder.Shared.Lib.GeometryUtils into
|
||||
// the AcDream.App.Rendering.Wb namespace so the WorldBuilder.Shared project
|
||||
|
|
@ -8,7 +8,7 @@ using System.Numerics;
|
|||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
public static class GeometryUtils {
|
||||
internal static class GeometryUtils {
|
||||
|
||||
public static bool RayIntersectsBox(Vector3 rayOrigin, Vector3 rayDirection, Vector3 min, Vector3 max, out float distance) {
|
||||
distance = 0;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using AcDream.Content;
|
||||
using AcDream.Content;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using Silk.NET.OpenGL;
|
||||
using AcDream.App.Rendering;
|
||||
|
|
@ -73,7 +73,7 @@ internal enum GlobalMeshCapacityResult
|
|||
/// ObjectMeshManager owns allocation lifetime and releases a mesh's ranges
|
||||
/// when its zero-reference LRU entry is evicted.
|
||||
/// </summary>
|
||||
public sealed class GlobalMeshBuffer : IDisposable
|
||||
internal sealed class GlobalMeshBuffer : IDisposable
|
||||
{
|
||||
internal const int InitialVertexCapacity = 1024 * 1024;
|
||||
internal const int InitialIndexCapacity = 3 * 1024 * 1024;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
|
@ -8,7 +8,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
/// <summary>
|
||||
/// Resource types for GPU memory tracking.
|
||||
/// </summary>
|
||||
public enum GpuResourceType {
|
||||
internal enum GpuResourceType {
|
||||
Texture,
|
||||
Buffer,
|
||||
Shader,
|
||||
|
|
@ -21,17 +21,17 @@ namespace AcDream.App.Rendering.Wb {
|
|||
/// <summary>
|
||||
/// Details about a GPU resource type.
|
||||
/// </summary>
|
||||
public record GpuResourceDetails(GpuResourceType Type, int Count, long Bytes);
|
||||
internal record GpuResourceDetails(GpuResourceType Type, int Count, long Bytes);
|
||||
|
||||
/// <summary>
|
||||
/// Details about a specific named buffer.
|
||||
/// </summary>
|
||||
public record NamedBufferDetails(string Name, long CapacityBytes, long UsedBytes);
|
||||
internal record NamedBufferDetails(string Name, long CapacityBytes, long UsedBytes);
|
||||
|
||||
/// <summary>
|
||||
/// Tracks manual VRAM allocations for buffers and textures.
|
||||
/// </summary>
|
||||
public static class GpuMemoryTracker {
|
||||
internal static class GpuMemoryTracker {
|
||||
private static long _allocatedBytes;
|
||||
private static readonly long[] _allocatedBytesByType = new long[Enum.GetValues<GpuResourceType>().Length];
|
||||
private static readonly int[] _resourceCountsByType = new int[Enum.GetValues<GpuResourceType>().Length];
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
namespace AcDream.App.Rendering.Wb;
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
/// <summary>
|
||||
/// Logical-owner lifetime seam for per-entity texture composites. Retail
|
||||
/// <c>CSurface::Destroy</c> (0x005361F0) releases its current <c>ImgTex</c>;
|
||||
/// live-object teardown must do the same for modern bindless composites.
|
||||
/// </summary>
|
||||
public interface IEntityTextureLifetime
|
||||
internal interface IEntityTextureLifetime
|
||||
{
|
||||
/// <summary>Release every composite acquired by one local entity id.</summary>
|
||||
void ReleaseOwner(uint localEntityId);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
namespace AcDream.App.Rendering.Wb;
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
/// <summary>
|
||||
/// Reports the physical outcome when a mesh-reference callback cannot provide
|
||||
|
|
@ -6,7 +6,7 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// a transactional owner reconcile its marker without guessing whether a
|
||||
/// throwing backend already changed the reference count.
|
||||
/// </summary>
|
||||
public sealed class MeshReferenceMutationException : Exception
|
||||
internal sealed class MeshReferenceMutationException : Exception
|
||||
{
|
||||
public MeshReferenceMutationException(
|
||||
string message,
|
||||
|
|
@ -25,7 +25,7 @@ public sealed class MeshReferenceMutationException : Exception
|
|||
/// drive ref-count lifecycle (e.g. LandblockSpawnAdapter, EntitySpawnAdapter)
|
||||
/// can be unit-tested without a real WB pipeline behind them.
|
||||
/// </summary>
|
||||
public interface IWbMeshAdapter
|
||||
internal interface IWbMeshAdapter
|
||||
{
|
||||
/// <summary>
|
||||
/// Acquires one logical reference. A normal exception guarantees that no
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb {
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 16)]
|
||||
public struct InstanceData {
|
||||
internal struct InstanceData {
|
||||
public const uint INSTANCE_FLAG_DISQUALIFIED = 1u;
|
||||
|
||||
public Matrix4x4 Transform; // 64 bytes
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
|
@ -8,9 +8,9 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// reference-count lifecycle. <b>Tier-aware by design</b>: only atlas-tier
|
||||
/// entities (procedural / dat-hydrated, identified by
|
||||
/// <c>ServerGuid == 0</c>) drive ref counts. Server-spawned entities
|
||||
/// (per-instance tier) are skipped — those go through
|
||||
/// (per-instance tier) are skipped — those go through
|
||||
/// <c>EntitySpawnAdapter</c> and the owner-scoped texture path
|
||||
/// (see Phase N.4 spec, Architecture → Two-tier rendering split).
|
||||
/// (see Phase N.4 spec, Architecture → Two-tier rendering split).
|
||||
///
|
||||
/// <para>
|
||||
/// On load: walks the landblock's atlas-tier entities, collects unique
|
||||
|
|
@ -44,7 +44,7 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// on the owning render/update thread.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class LandblockSpawnAdapter
|
||||
internal sealed class LandblockSpawnAdapter
|
||||
{
|
||||
private sealed class ReferenceRegistration
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Chorizite.Core.Render;
|
||||
using Chorizite.Core.Render;
|
||||
using Silk.NET.OpenGL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
|
@ -10,7 +10,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
/// <summary>
|
||||
/// Implementation of a framebuffer for OpenGL ES 3.0 using Silk.NET.
|
||||
/// </summary>
|
||||
public class ManagedGLFramebuffer : IFramebuffer {
|
||||
internal class ManagedGLFramebuffer : IFramebuffer {
|
||||
private readonly OpenGLGraphicsDevice _device;
|
||||
private GL _gl => _device.GL;
|
||||
private readonly uint _fboId;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Chorizite.Core.Render.Enums;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using Chorizite.Core.Render.Vertex;
|
||||
using Silk.NET.OpenGL;
|
||||
using BufferUsage = Chorizite.Core.Render.Enums.BufferUsage;
|
||||
|
|
@ -7,7 +7,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
/// <summary>
|
||||
/// OpenGL index buffer
|
||||
/// </summary>
|
||||
public unsafe class ManagedGLIndexBuffer : IIndexBuffer {
|
||||
internal unsafe class ManagedGLIndexBuffer : IIndexBuffer {
|
||||
private uint bufferId;
|
||||
private readonly OpenGLGraphicsDevice _device;
|
||||
private void* _mappedPtr;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using Chorizite.Core.Render;
|
||||
using Chorizite.Core.Render;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb {
|
||||
public unsafe class ManagedGLTexture : ITexture {
|
||||
internal unsafe class ManagedGLTexture : ITexture {
|
||||
private uint _texture;
|
||||
private readonly OpenGLGraphicsDevice _device;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
using AcDream.Core.Rendering.Wb;
|
||||
using AcDream.Core.Rendering.Wb;
|
||||
using Chorizite.Core.Render;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
// Use our extracted TextureHelpers (T3), not the WB original — disambiguate explicitly
|
||||
// Use our extracted TextureHelpers (T3), not the WB original — disambiguate explicitly
|
||||
using TextureHelpers = AcDream.Core.Rendering.Wb.TextureHelpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Silk.NET.OpenGL;
|
||||
|
|
@ -9,7 +9,7 @@ using System.Runtime.InteropServices;
|
|||
using AcDream.App.Rendering;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb {
|
||||
public class ManagedGLTextureArray : ITextureArray {
|
||||
internal class ManagedGLTextureArray : ITextureArray {
|
||||
private readonly bool[] _usedLayers;
|
||||
private readonly GL GL;
|
||||
private readonly OpenGLGraphicsDevice _device;
|
||||
|
|
@ -46,7 +46,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
/// #105 diagnostic: staged layer updates (retained decoded payloads) not yet
|
||||
/// applied to the GL texture by <see cref="ProcessDirtyUpdates"/>. Layers with
|
||||
/// a pending update sample UNDEFINED content (TexStorage3D contents) until the
|
||||
/// flush runs — a stuck non-zero count at standstill is the white-walls mechanism.
|
||||
/// flush runs — a stuck non-zero count at standstill is the white-walls mechanism.
|
||||
/// </summary>
|
||||
public int PendingUpdateCount {
|
||||
get { lock (_mipmapLock) { return _pendingUpdates.Count; } }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Chorizite.Core.Render;
|
||||
using Chorizite.Core.Render;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using Silk.NET.OpenGL;
|
||||
using System.Runtime.InteropServices;
|
||||
|
|
@ -10,7 +10,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
/// <summary>
|
||||
/// OpenGL uniform buffer
|
||||
/// </summary>
|
||||
public unsafe class ManagedGLUniformBuffer : IUniformBuffer {
|
||||
internal unsafe class ManagedGLUniformBuffer : IUniformBuffer {
|
||||
private uint bufferId;
|
||||
private readonly OpenGLGraphicsDevice _device;
|
||||
private GL GL => _device.GL;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Chorizite.Core.Render.Enums;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using Chorizite.Core.Render.Vertex;
|
||||
using Silk.NET.OpenGL;
|
||||
using System;
|
||||
|
|
@ -9,7 +9,7 @@ using System.Threading.Tasks;
|
|||
using VertexAttribType = Silk.NET.OpenGL.VertexAttribType;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb {
|
||||
public unsafe class ManagedGLVertexArray : IVertexArray {
|
||||
internal unsafe class ManagedGLVertexArray : IVertexArray {
|
||||
private readonly OpenGLGraphicsDevice _device;
|
||||
private GL GL => _device.GL;
|
||||
private uint _vaoId = 0;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Chorizite.Core.Render.Enums;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using Chorizite.Core.Render.Vertex;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Silk.NET.OpenGL;
|
||||
|
|
@ -10,7 +10,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
/// <summary>
|
||||
/// OpenGL vertex buffer
|
||||
/// </summary>
|
||||
public unsafe class ManagedGLVertexBuffer : IVertexBuffer {
|
||||
internal unsafe class ManagedGLVertexBuffer : IVertexBuffer {
|
||||
private uint bufferId;
|
||||
private readonly OpenGLGraphicsDevice _device;
|
||||
private void* _mappedPtr;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using DatReaderWriter.Enums;
|
||||
using Chorizite.Core.Render;
|
||||
|
||||
|
|
@ -9,17 +9,17 @@ namespace AcDream.App.Rendering.Wb {
|
|||
/// bindless handle) and the layer index within the shared/pooled array.
|
||||
/// Indexed by gl_DrawIDARB in the vertex shader. Same 16-byte std430 shape
|
||||
/// as mesh_modern.vert's BatchData: TextureTableIndex/Reserved/TextureIndex/
|
||||
/// Flags at offsets 0/4/8/12 — see ModernBatchDataLayoutTests.
|
||||
/// Flags at offsets 0/4/8/12 — see ModernBatchDataLayoutTests.
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||
public struct ModernBatchData {
|
||||
public uint TextureTableIndex; // 4 bytes — slot into the binding=9 handle table
|
||||
public uint Reserved; // 4 bytes — pad, keeps TextureIndex/Flags at offsets 8/12
|
||||
public uint TextureIndex; // 4 bytes — layer within the texture array
|
||||
public uint Flags; // 4 bytes — reserved, matches mesh_modern.vert's BatchData.flags
|
||||
internal struct ModernBatchData {
|
||||
public uint TextureTableIndex; // 4 bytes — slot into the binding=9 handle table
|
||||
public uint Reserved; // 4 bytes — pad, keeps TextureIndex/Flags at offsets 8/12
|
||||
public uint TextureIndex; // 4 bytes — layer within the texture array
|
||||
public uint Flags; // 4 bytes — reserved, matches mesh_modern.vert's BatchData.flags
|
||||
}
|
||||
|
||||
public struct LandblockMdiCommand {
|
||||
internal struct LandblockMdiCommand {
|
||||
public ulong SortKey;
|
||||
public ulong ObjectId;
|
||||
public DrawElementsIndirectCommand Command;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Chorizite.Core.Lib;
|
||||
using Chorizite.Core.Lib;
|
||||
using Chorizite.Core.Render;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using DatReaderWriter.DBObjs;
|
||||
|
|
@ -26,7 +26,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
/// <summary>
|
||||
/// GPU-side render data created on the main thread.
|
||||
/// </summary>
|
||||
public class ObjectRenderData
|
||||
internal class ObjectRenderData
|
||||
{
|
||||
public uint VAO { get; set; }
|
||||
public uint VBO { get; set; }
|
||||
|
|
@ -74,7 +74,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
/// <summary>
|
||||
/// A single GPU draw batch: IBO + texture array layer.
|
||||
/// </summary>
|
||||
public class ObjectRenderBatch
|
||||
internal class ObjectRenderBatch
|
||||
{
|
||||
public uint IBO { get; set; }
|
||||
public int IndexCount { get; set; }
|
||||
|
|
@ -101,7 +101,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
/// Key design: mesh data is prepared on background threads via PrepareMeshData(),
|
||||
/// then GPU resources are created on the main thread via UploadMeshData().
|
||||
/// </summary>
|
||||
public class ObjectMeshManager : IDisposable
|
||||
internal class ObjectMeshManager : IDisposable
|
||||
{
|
||||
private readonly OpenGLGraphicsDevice _graphicsDevice;
|
||||
private readonly IPreparedAssetSource _preparedAssets;
|
||||
|
|
@ -171,7 +171,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
private volatile bool _arenaBackpressured;
|
||||
|
||||
/// <summary>#125: how many times a failed GL upload is re-staged before
|
||||
/// giving up loudly. Small — a transient GL error clears on the next
|
||||
/// giving up loudly. Small — a transient GL error clears on the next
|
||||
/// frame; anything that fails this many times is a genuine defect to
|
||||
/// surface, not retry forever. See <see cref="ObjectMeshData.UploadAttempts"/>.</summary>
|
||||
public const int MaxUploadRetries = 3;
|
||||
|
|
@ -179,8 +179,8 @@ namespace AcDream.App.Rendering.Wb
|
|||
/// <summary>
|
||||
/// #125: drain one staged upload, returning whether it should be
|
||||
/// re-staged for a later frame. The caller (the per-frame Tick drain)
|
||||
/// collects the re-stages and re-enqueues them AFTER the drain loop —
|
||||
/// never inside it — so a deterministic failure can't spin the queue in
|
||||
/// collects the re-stages and re-enqueues them AFTER the drain loop —
|
||||
/// never inside it — so a deterministic failure can't spin the queue in
|
||||
/// a single frame. <see cref="UploadMeshData"/> increments the mesh
|
||||
/// data's own counter only when new upload work actually starts (not
|
||||
/// while a prior rollback waits); this drain gives up loudly past
|
||||
|
|
@ -197,7 +197,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
if (UploadMeshData(meshData) is not null)
|
||||
{
|
||||
_stagedMeshData.Complete(item);
|
||||
return false; // success (incl. legitimate 0-vertex → empty render data)
|
||||
return false; // success (incl. legitimate 0-vertex → empty render data)
|
||||
}
|
||||
if (HasRenderData(meshData.ObjectId))
|
||||
{
|
||||
|
|
@ -215,7 +215,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
if (meshData.UploadAttempts < MaxUploadRetries)
|
||||
return true; // re-stage for next frame
|
||||
_stagedMeshData.Complete(item);
|
||||
Console.WriteLine($"[up-retry] 0x{meshData.ObjectId:X10} upload failed {meshData.UploadAttempts}x — giving up (was the #125 silent sticky drop; a GL error is being surfaced, not hidden)");
|
||||
Console.WriteLine($"[up-retry] 0x{meshData.ObjectId:X10} upload failed {meshData.UploadAttempts}x — giving up (was the #125 silent sticky drop; a GL error is being surfaced, not hidden)");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -570,7 +570,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
/// <summary>
|
||||
/// #105 diagnostic: counts staged-but-unflushed texture layer updates across all
|
||||
/// shared atlases (see <see cref="ManagedGLTextureArray.PendingUpdateCount"/>).
|
||||
/// Render thread only — <c>_globalAtlases</c> is render-thread-owned.
|
||||
/// Render thread only — <c>_globalAtlases</c> is render-thread-owned.
|
||||
/// </summary>
|
||||
public (int PendingUpdates, int ArraysWithPending, int TotalArrays) GetPendingTextureUpdateStats()
|
||||
{
|
||||
|
|
@ -838,7 +838,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
_cpuMeshCache.Clear();
|
||||
}
|
||||
|
||||
public struct EnvCellGeomRequest
|
||||
internal struct EnvCellGeomRequest
|
||||
{
|
||||
public uint SourceCellId;
|
||||
public uint EnvironmentId;
|
||||
|
|
@ -1520,14 +1520,14 @@ namespace AcDream.App.Rendering.Wb
|
|||
{
|
||||
// 0-vertex mesh: every polygon was gated out at extraction. #119
|
||||
// (2026-06-11) dat-verified this is LEGITIMATE for all-no-draw
|
||||
// models (all polys NoPos + Base1Solid surfaces — retail's
|
||||
// models (all polys NoPos + Base1Solid surfaces — retail's
|
||||
// skipNoTexture never draws them either; 0x010002B4/0x010008A8
|
||||
// are this class, Issue119UpNullGfxObjDumpTests). The empty
|
||||
// cache is the correct terminal state for those. The line stays
|
||||
// as a tripwire for the OTHER way to get here (extraction
|
||||
// dropped textured polys — a real defect; dat-verify with the
|
||||
// dropped textured polys — a real defect; dat-verify with the
|
||||
// dump test before treating as one).
|
||||
Console.WriteLine($"[up-null] 0x{meshData.ObjectId:X10} produced a 0-vertex mesh — caching empty render data (legitimate for all-no-draw models; dat-verify via Issue119UpNullGfxObjDumpTests)");
|
||||
Console.WriteLine($"[up-null] 0x{meshData.ObjectId:X10} produced a 0-vertex mesh — caching empty render data (legitimate for all-no-draw models; dat-verify via Issue119UpNullGfxObjDumpTests)");
|
||||
renderData = new ObjectRenderData();
|
||||
}
|
||||
|
||||
|
|
@ -1611,7 +1611,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
/// <summary>
|
||||
/// Plans the actual GL work the next object would trigger against the
|
||||
/// current atlas inventory. This includes array storage, global-buffer
|
||||
/// growth/copies, and one full mip generation per newly-dirtied array—not merely the
|
||||
/// growth/copies, and one full mip generation per newly-dirtied array—not merely the
|
||||
/// source byte arrays held by ObjectMeshData.
|
||||
/// </summary>
|
||||
internal MeshUploadCost PlanUploadCost(
|
||||
|
|
@ -1852,7 +1852,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
#region Private: Background Preparation
|
||||
|
||||
/// <summary>
|
||||
/// #113: the set of polygon ids referenced by the GfxObj's drawing BSP —
|
||||
/// #113: the set of polygon ids referenced by the GfxObj's drawing BSP —
|
||||
/// the polys retail actually renders (D3DPolyRender traverses the BSP;
|
||||
/// dictionary-orphaned polys are physics/no-draw geometry). Returns null
|
||||
/// when the model has no drawing BSP (caller draws everything).
|
||||
|
|
@ -1985,7 +1985,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
}
|
||||
atlasManager.LastUseSequence = ++_atlasUseSequence;
|
||||
|
||||
// MP1a: AcDream.Content is Silk.NET-free — the extraction records
|
||||
// MP1a: AcDream.Content is Silk.NET-free — the extraction records
|
||||
// carry Content-owned UploadPixelFormat/UploadPixelType enums whose
|
||||
// underlying values are the GL ABI constants (numerically identical
|
||||
// to Silk.NET.OpenGL.PixelFormat/PixelType), so this lifted nullable
|
||||
|
|
@ -2591,7 +2591,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
// disposes the DatCollection right after this adapter chain, which
|
||||
// unmaps the dats' memory-mapped views. A worker still inside
|
||||
// MemoryMappedBlockAllocator.ReadBlock at that point dereferences the
|
||||
// dead view pointer — an uncatchable, process-fatal AccessViolation
|
||||
// dead view pointer — an uncatchable, process-fatal AccessViolation
|
||||
// (dat-race investigation 2026-06-09). Setting IsDisposed under the
|
||||
// queue lock publishes it to workers, which re-check it before every
|
||||
// dequeue; draining the queue means each worker exits after at most
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Chorizite.Core.Render;
|
||||
using Chorizite.Core.Render;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using Chorizite.Core.Render.Vertex;
|
||||
using AcDream.App.Rendering;
|
||||
|
|
@ -20,7 +20,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
/// <summary>
|
||||
/// OpenGL graphics device
|
||||
/// </summary>
|
||||
public unsafe class OpenGLGraphicsDevice : BaseGraphicsDevice {
|
||||
internal unsafe class OpenGLGraphicsDevice : BaseGraphicsDevice {
|
||||
private readonly ILogger _log;
|
||||
private readonly DebugRenderSettings _renderSettings;
|
||||
private readonly AcDream.App.Rendering.IGpuResourceRetirementQueue _resourceRetirement;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
namespace AcDream.App.Rendering.Wb;
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks currently-bound GL state to skip redundant rebinds across the
|
||||
|
|
@ -7,9 +7,9 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// here in Phase O-T7 to eliminate the WorldBuilder project reference.
|
||||
///
|
||||
/// Semantics are identical to the WB originals:
|
||||
/// <c>CurrentAtlas</c> — slot index of the currently bound texture atlas.
|
||||
/// <c>CurrentVAO</c> — OpenGL name of the currently bound vertex array object.
|
||||
/// <c>CurrentIBO</c> — OpenGL name of the currently bound index buffer object.
|
||||
/// <c>CurrentAtlas</c> — slot index of the currently bound texture atlas.
|
||||
/// <c>CurrentVAO</c> — OpenGL name of the currently bound vertex array object.
|
||||
/// <c>CurrentIBO</c> — OpenGL name of the currently bound index buffer object.
|
||||
/// Sentinel value 0 means "no valid binding cached."
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
|
|
@ -18,7 +18,7 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// corresponding glBind* call and read by the next dispatch on the
|
||||
/// same thread.
|
||||
/// </remarks>
|
||||
public static class RenderStateCache
|
||||
internal static class RenderStateCache
|
||||
{
|
||||
public static uint CurrentAtlas = 0;
|
||||
public static uint CurrentVAO = 0;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb {
|
||||
|
|
@ -6,7 +6,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
/// Global scene data for Uniform Buffer Object (UBO)
|
||||
/// </summary>
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 16)]
|
||||
public struct SceneData {
|
||||
internal struct SceneData {
|
||||
public Matrix4x4 View; // 64 bytes
|
||||
public Matrix4x4 Projection; // 64 bytes
|
||||
public Matrix4x4 ViewProjection; // 64 bytes
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using AcDream.Content;
|
||||
using AcDream.Content;
|
||||
using Chorizite.Core.Render;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using DatReaderWriter.Enums;
|
||||
|
|
@ -42,7 +42,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
/// Manages texture arrays grouped by (Width, Height, Format).
|
||||
/// Deduplicates textures by a TextureKey and supports reference counting.
|
||||
/// </summary>
|
||||
public class TextureAtlasManager : IDisposable {
|
||||
internal class TextureAtlasManager : IDisposable {
|
||||
private static uint _nextSlot = 1;
|
||||
private readonly OpenGLGraphicsDevice _graphicsDevice;
|
||||
private readonly int _textureWidth;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using Chorizite.Core.Render.Enums;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using Silk.NET.OpenGL;
|
||||
using System;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb {
|
||||
public static class TextureFormatExtensions {
|
||||
internal static class TextureFormatExtensions {
|
||||
public static SizedInternalFormat ToGL(this Chorizite.Core.Render.Enums.TextureFormat format) {
|
||||
return format switch {
|
||||
TextureFormat.RGBA8 => SizedInternalFormat.Rgba8,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using Silk.NET.OpenGL;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb {
|
||||
/// <summary>
|
||||
/// Configurable OpenGL texture parameters for wrap mode, filtering, mipmaps, and anisotropic filtering.
|
||||
/// </summary>
|
||||
public struct TextureParameters {
|
||||
internal struct TextureParameters {
|
||||
public TextureWrapMode WrapS;
|
||||
public TextureWrapMode WrapT;
|
||||
public TextureMinFilter MinFilter;
|
||||
|
|
@ -12,7 +12,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
public bool EnableMipmaps;
|
||||
public bool EnableAnisotropicFiltering;
|
||||
|
||||
/// <summary>Standard tiling textures — Repeat + trilinear + aniso.</summary>
|
||||
/// <summary>Standard tiling textures — Repeat + trilinear + aniso.</summary>
|
||||
public static readonly TextureParameters Default = new() {
|
||||
WrapS = TextureWrapMode.Repeat,
|
||||
WrapT = TextureWrapMode.Repeat,
|
||||
|
|
@ -22,7 +22,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
EnableAnisotropicFiltering = true,
|
||||
};
|
||||
|
||||
/// <summary>Non-tiling textures (alpha maps, fonts, UI, object atlases) — ClampToEdge + trilinear + aniso.</summary>
|
||||
/// <summary>Non-tiling textures (alpha maps, fonts, UI, object atlases) — ClampToEdge + trilinear + aniso.</summary>
|
||||
public static readonly TextureParameters ClampToEdge = new() {
|
||||
WrapS = TextureWrapMode.ClampToEdge,
|
||||
WrapT = TextureWrapMode.ClampToEdge,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System.Numerics;
|
||||
using System.Numerics;
|
||||
using AcDream.Core.Rendering;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
using AcDream.App.Rendering.Selection;
|
||||
|
|
@ -16,7 +16,7 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// routes reuse the dispatcher's exact mesh, texture, light, translucency,
|
||||
/// selection, upload, and draw owners.
|
||||
/// </summary>
|
||||
public sealed unsafe partial class WbDrawDispatcher
|
||||
internal sealed unsafe partial class WbDrawDispatcher
|
||||
{
|
||||
private readonly Dictionary<RenderProjectionId, RenderFrameEntityCandidate>
|
||||
_packedEntityById = [];
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +1,4 @@
|
|||
// Ported from references/WorldBuilder/Chorizite.OpenGLSDLBackend/Frustum.cs
|
||||
// Ported from references/WorldBuilder/Chorizite.OpenGLSDLBackend/Frustum.cs
|
||||
// Phase A8 extraction (2026-05-28). Verbatim algorithm; adaptations:
|
||||
// - Namespace: AcDream.App.Rendering.Wb
|
||||
// - Class renamed Frustum -> WbFrustum
|
||||
|
|
@ -10,7 +10,7 @@ using System.Numerics;
|
|||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
public struct WbBoundingBox
|
||||
internal struct WbBoundingBox
|
||||
{
|
||||
public Vector3 Min;
|
||||
public Vector3 Max;
|
||||
|
|
@ -27,7 +27,7 @@ public struct WbBoundingBox
|
|||
Vector3.Max(a.Max, b.Max));
|
||||
}
|
||||
|
||||
public enum FrustumTestResult
|
||||
internal enum FrustumTestResult
|
||||
{
|
||||
Outside,
|
||||
Inside,
|
||||
|
|
@ -39,7 +39,7 @@ public enum FrustumTestResult
|
|||
/// Source: references/WorldBuilder/Chorizite.OpenGLSDLBackend/Frustum.cs
|
||||
/// Phase A8 extraction (2026-05-28).
|
||||
/// </summary>
|
||||
public sealed class WbFrustum
|
||||
internal sealed class WbFrustum
|
||||
{
|
||||
private struct Plane
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AcDream.Content;
|
||||
using AcDream.App.Rendering.Residency;
|
||||
|
|
@ -17,11 +17,11 @@ namespace AcDream.App.Rendering.Wb;
|
|||
///
|
||||
/// <para>
|
||||
/// As of Phase O-T7, all DAT I/O routes through the runtime-owned shared
|
||||
/// <see cref="IDatReaderWriter"/> facade — the separate
|
||||
/// <see cref="IDatReaderWriter"/> facade — the separate
|
||||
/// <c>DefaultDatReaderWriter</c> file-handle set has been removed.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class WbMeshAdapter
|
||||
internal sealed class WbMeshAdapter
|
||||
: IDisposable,
|
||||
IWbMeshAdapter
|
||||
{
|
||||
|
|
@ -291,12 +291,12 @@ public sealed class WbMeshAdapter
|
|||
|
||||
private WbMeshAdapter()
|
||||
{
|
||||
// Uninitialized constructor — only for tests / flag-off cases where
|
||||
// Uninitialized constructor — only for tests / flag-off cases where
|
||||
// the caller wants a Dispose-safe no-op instance.
|
||||
_isUninitialized = true;
|
||||
}
|
||||
|
||||
/// <summary>Test/init helper — produces a Dispose-safe instance with no
|
||||
/// <summary>Test/init helper — produces a Dispose-safe instance with no
|
||||
/// underlying mesh manager. Public methods are all no-ops.</summary>
|
||||
public static WbMeshAdapter CreateUninitialized() => new();
|
||||
|
||||
|
|
@ -310,7 +310,7 @@ public sealed class WbMeshAdapter
|
|||
/// <summary>
|
||||
/// Returns the WB render data for <paramref name="id"/>, or null if not
|
||||
/// yet uploaded or if this adapter is uninitialized. Increments WB's
|
||||
/// internal usage counter — use <see cref="TryGetRenderData"/> for
|
||||
/// internal usage counter — use <see cref="TryGetRenderData"/> for
|
||||
/// render-loop lookups that should not affect lifecycle.
|
||||
/// </summary>
|
||||
public ObjectRenderData? GetRenderData(ulong id)
|
||||
|
|
@ -353,15 +353,15 @@ public sealed class WbMeshAdapter
|
|||
// auto-enqueues into _stagedMeshData (ObjectMeshManager line 510),
|
||||
// which Tick() drains onto the GPU. Until that completes,
|
||||
// TryGetRenderData(id) returns null and the dispatcher silently
|
||||
// skips the entity — standard streaming flicker.
|
||||
// skips the entity — standard streaming flicker.
|
||||
//
|
||||
// #128 (2026-06-11): Prepare must RE-ARM whenever the id has no render
|
||||
// data — NOT only on the first-ever registration. A first-only gate
|
||||
// data — NOT only on the first-ever registration. A first-only gate
|
||||
// permanently lost any id whose initial read was cancelled before completing
|
||||
// (landblock unload → CancelStagedUploads during login/teleport
|
||||
// (landblock unload → CancelStagedUploads during login/teleport
|
||||
// churn) or whose upload was later LRU-evicted: every subsequent
|
||||
// registration skipped Prepare, so the mesh stayed invisible for the
|
||||
// session with zero log output — the dispatcher's slow path just
|
||||
// session with zero log output — the dispatcher's slow path just
|
||||
// counted meshMissing forever (issue #55's 1.45M/5s mountain was this
|
||||
// bug's heartbeat). User-visible: the AAB3 tower staircase rendering
|
||||
// partially or not at all depending on the session's landblock
|
||||
|
|
@ -370,7 +370,7 @@ public sealed class WbMeshAdapter
|
|||
// on existing render data, returns the in-flight task when already
|
||||
// pending, and dedups via _preparationTasks.
|
||||
//
|
||||
// isSetup: false — acdream's MeshRefs already carry expanded
|
||||
// isSetup: false — acdream's MeshRefs already carry expanded
|
||||
// per-part GfxObj ids (0x01XXXXXX). WB's Setup-expansion path is
|
||||
// unused.
|
||||
if (_meshManager.TryGetRenderData(id) is null)
|
||||
|
|
@ -419,15 +419,15 @@ public sealed class WbMeshAdapter
|
|||
|
||||
/// <summary>
|
||||
/// #128 self-heal (2026-06-11): re-request a mesh load at the POINT OF
|
||||
/// USE. Registration-time re-arming was insufficient — a preparation
|
||||
/// USE. Registration-time re-arming was insufficient — a preparation
|
||||
/// cancelled by landblock churn AFTER the last registration event
|
||||
/// (running across blocks loads/unloads them repeatedly) left the mesh
|
||||
/// permanently unloadable with no later event to re-fire it. The draw
|
||||
/// dispatcher touches every missing-but-referenced mesh every frame (the
|
||||
/// meshMissing slow path) — that is the one place a retry can never be
|
||||
/// meshMissing slow path) — that is the one place a retry can never be
|
||||
/// missed. Cheap and idempotent: PrepareMeshDataAsync early-outs on
|
||||
/// existing render data and returns the in-flight task when pending.
|
||||
/// Retail-equivalence: retail loads content synchronously — geometry is
|
||||
/// Retail-equivalence: retail loads content synchronously — geometry is
|
||||
/// never permanently absent; this converges our async pipeline to the
|
||||
/// same guarantee.
|
||||
/// </summary>
|
||||
|
|
@ -466,7 +466,7 @@ public sealed class WbMeshAdapter
|
|||
_graphicsDevice!.ProcessGLQueue();
|
||||
// #125: drain staged uploads; a FAILED upload (UploadMeshData returned
|
||||
// null from its catch) is re-staged for a LATER frame, not dropped. The
|
||||
// re-stages are collected and re-enqueued AFTER the loop — re-enqueuing
|
||||
// re-stages are collected and re-enqueued AFTER the loop — re-enqueuing
|
||||
// inside the while would let a deterministic failure spin the queue in a
|
||||
// single frame. UploadOrRequeue bounds the retries (MaxUploadRetries) so
|
||||
// a genuine defect surfaces loudly instead of the old silent sticky drop.
|
||||
|
|
@ -594,14 +594,14 @@ public sealed class WbMeshAdapter
|
|||
: default;
|
||||
|
||||
// #105 root cause (2026-06-10): TextureAtlasManager.AddTexture only STAGES
|
||||
// immutable decoded payloads in ManagedGLTextureArray._pendingUpdates — the
|
||||
// immutable decoded payloads in ManagedGLTextureArray._pendingUpdates — the
|
||||
// actual TexSubImage3D copies + mipmap regeneration happen in
|
||||
// ProcessDirtyUpdates, which WB drives ONCE PER FRAME from its render loop
|
||||
// (WB GameScene.cs:975 `_meshManager?.GenerateMipmaps()`, just before the
|
||||
// opaque pass). That call site lived in the GameScene file the N.4/O-T4
|
||||
// extraction replaced with GameWindow, so the driver was silently dropped:
|
||||
// staged updates never reached TexSubImage3D, leaving undefined
|
||||
// TexStorage3D content behind valid resident bindless handles — the
|
||||
// TexStorage3D content behind valid resident bindless handles — the
|
||||
// intermittent white indoor walls (#105). Pre-fix evidence: 126 updates
|
||||
// stuck across 34/34 arrays at standstill (texflush-prefix.log). Tick()
|
||||
// runs before all draw passes (GameWindow OnRender), so this is the exact
|
||||
|
|
@ -662,7 +662,7 @@ public sealed class WbMeshAdapter
|
|||
meshManager.RejectUnsupportedStagedUpload(rejected, error);
|
||||
}
|
||||
|
||||
// #105 apparatus state — see RenderingDiagnostics.ProbeTexFlushEnabled.
|
||||
// #105 apparatus state — see RenderingDiagnostics.ProbeTexFlushEnabled.
|
||||
private int _lastTexFlushBefore = -1;
|
||||
private int _texFlushHeartbeat;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
namespace AcDream.App.Rendering.Wb;
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
/// <summary>
|
||||
/// Phase A8 (2026-05-28): WB's RenderPass enum, extracted verbatim from
|
||||
|
|
@ -9,7 +9,7 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// Consumed by <see cref="EnvCellRenderer"/> and matches the
|
||||
/// <c>uRenderPass</c> uniform in the modern mesh shaders.
|
||||
/// </summary>
|
||||
public enum WbRenderPass
|
||||
internal enum WbRenderPass
|
||||
{
|
||||
/// <summary>
|
||||
/// The opaque pass. Only non-transparent objects are rendered.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue