feat(O-T7): drop WB project references; complete extraction
End of Phase O extraction. Final cleanup: - Dropped <ProjectReference> entries to WorldBuilder.Shared and Chorizite.OpenGLSDLBackend from both AcDream.App.csproj and AcDream.Core.csproj. - Added Chorizite.Core NuGet PackageReference to AcDream.Core.csproj (needed by Core.Rendering.Wb.TextureHelpers for TextureFormat enum; previously transitive through the WB project ref). - Added BCnEncoder.Net.ImageSharp (1.1.2) + SixLabors.ImageSharp (3.1.12) as direct PackageReferences to AcDream.App.csproj — previously transitive via Chorizite.OpenGLSDLBackend project; used directly by ObjectMeshManager. Item A (BaseObjectRenderManager static fields): - Inlined CurrentAtlas/CurrentVAO/CurrentIBO into a new RenderStateCache.cs static class (AcDream.App.Rendering.Wb namespace) — the 4 consumers (ManagedGLIndexBuffer, ManagedGLTexture, ManagedGLTextureArray, ParticleBatcher) all reference RenderStateCache.* instead of BaseObjectRenderManager.*. - Dropped using Chorizite.OpenGLSDLBackend.Lib from all 4 consumers and from WbDrawDispatcher (which had it only as a dead import). Item B (ActiveParticleEmitter.ObjectLandblock): - ObjectLandblock? erased to object?; WorldBuilder.Shared.Models.ObjectId? erased to ulong? — both fields are stored but never read by any consumer in our codebase. - Dropped both WB using directives from ActiveParticleEmitter.cs. Item C (IDatReaderWriter / IDatDatabase): - Verbatim copy of both interfaces into IDatReaderWriter.cs in AcDream.App.Rendering.Wb namespace — DatCollectionAdapter and ObjectMeshManager already live in that namespace, so no using changes needed. - Dropped using WorldBuilder.Shared.Services from DatCollectionAdapter.cs and ObjectMeshManager.cs. Additional extractions required by the reference drop: - GeometryUtils.cs: verbatim copy of WorldBuilder.Shared.Lib.GeometryUtils (float-precision overloads only; Vector3d double-precision overloads omitted — ObjectMeshManager uses only the float versions). - Dropped using WorldBuilder.Shared.Lib from ObjectMeshManager.cs. WbMeshAdapter.cs cleanup (spec O-D12): - Deleted _wbDats (DefaultDatReaderWriter) field + ctor init + Dispose call. - Deleted the [indoor-upload] NULL_RESULT diagnostic block (lines ~205-262) — its Phase 2 cell-resolution investigation is complete; its _wbDats.ResolveId dependency goes with this commit. - Deleted _pendingEnvCellRequests field + isPendingEnvCell tracking in Tick(). - Simplified Tick() to a clean drain loop. Deleted SplitFormulaDivergenceTest.cs — one-time N.5b data-collection sweep; job done. Verified acceptance criteria: - Zero <ProjectReference> to WorldBuilder.* / Chorizite.OpenGLSDLBackend.* in any csproj. - Zero 'using WorldBuilder.*' / 'using Chorizite.OpenGLSDLBackend.*' in src/. - DefaultDatReaderWriter referenced in zero places in src/ (comments only). Build green (0 warnings, 0 errors). Tests: 1154 total (-1 from deleted SplitFormulaDivergenceTest), 1146 pass, 8 pre-existing failures (unchanged from baseline — physics/input tests unrelated to this change). Spec: docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a9ccc5acf5
commit
dc722e70bd
15 changed files with 312 additions and 328 deletions
|
|
@ -18,6 +18,11 @@
|
|||
implement IUniformBuffer from Chorizite.Core; NuGet PackageReferences are not
|
||||
forwarded from ProjectReferences so we must declare it explicitly here. -->
|
||||
<PackageReference Include="Chorizite.Core" Version="0.0.18" />
|
||||
<!-- Phase O-T7: BCnEncoder.Net.ImageSharp + SixLabors.ImageSharp were previously
|
||||
transitive via the WorldBuilder project reference. Now direct deps of
|
||||
ObjectMeshManager.cs (extracted in T2). -->
|
||||
<PackageReference Include="BCnEncoder.Net.ImageSharp" Version="1.1.2" />
|
||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.12" />
|
||||
<PackageReference Include="Silk.NET.OpenGL" Version="2.23.0" />
|
||||
<PackageReference Include="Silk.NET.OpenGL.Extensions.ARB" Version="2.23.0" />
|
||||
<PackageReference Include="Silk.NET.Windowing" Version="2.23.0" />
|
||||
|
|
@ -35,12 +40,6 @@
|
|||
<ProjectReference Include="..\AcDream.Core.Net\AcDream.Core.Net.csproj" />
|
||||
<ProjectReference Include="..\AcDream.UI.Abstractions\AcDream.UI.Abstractions.csproj" />
|
||||
<ProjectReference Include="..\AcDream.UI.ImGui\AcDream.UI.ImGui.csproj" />
|
||||
<!-- Phase N.4 Task 9: WbMeshAdapter constructs the WB GL pipeline directly.
|
||||
AcDream.Core already references these projects, but project references are
|
||||
not transitive in .NET — AcDream.App must list them explicitly to compile
|
||||
against Chorizite.OpenGLSDLBackend and WorldBuilder.Shared types. -->
|
||||
<ProjectReference Include="..\..\references\WorldBuilder\WorldBuilder.Shared\WorldBuilder.Shared.csproj" />
|
||||
<ProjectReference Include="..\..\references\WorldBuilder\Chorizite.OpenGLSDLBackend\Chorizite.OpenGLSDLBackend.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Rendering\Shaders\*.*">
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using System.Numerics;
|
||||
using Chorizite.OpenGLSDLBackend.Lib;
|
||||
using WorldBuilder.Shared.Models;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb {
|
||||
public class ActiveParticleEmitter {
|
||||
|
|
@ -8,11 +6,15 @@ namespace AcDream.App.Rendering.Wb {
|
|||
public uint PartIndex { get; }
|
||||
public Matrix4x4 LocalOffset { get; }
|
||||
|
||||
// Store reference info instead of struct copy
|
||||
public ObjectLandblock? ParentLandblock { get; set; }
|
||||
public ObjectId? ParentInstanceId { get; set; }
|
||||
// Store reference info instead of struct copy.
|
||||
// ParentLandblock was ObjectLandblock (WB type) — typed as object? in Phase O-T7
|
||||
// to remove the WorldBuilder project reference; no consumer reads it.
|
||||
public object? ParentLandblock { get; set; }
|
||||
// ParentInstanceId was WorldBuilder.Shared.Models.ObjectId? — erased to ulong? in T7;
|
||||
// the field is stored but never read by any consumer in our codebase.
|
||||
public ulong? ParentInstanceId { get; set; }
|
||||
|
||||
public ActiveParticleEmitter(ParticleEmitterRenderer renderer, uint partIndex, Matrix4x4 localOffset, ObjectLandblock? parentLandblock = null, ObjectId? parentInstanceId = null) {
|
||||
public ActiveParticleEmitter(ParticleEmitterRenderer renderer, uint partIndex, Matrix4x4 localOffset, object? parentLandblock = null, ulong? parentInstanceId = null) {
|
||||
Renderer = renderer;
|
||||
PartIndex = partIndex;
|
||||
LocalOffset = localOffset;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using System.Collections.Concurrent;
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using WorldBuilder.Shared.Services;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
|
|
|
|||
147
src/AcDream.App/Rendering/Wb/GeometryUtils.cs
Normal file
147
src/AcDream.App/Rendering/Wb/GeometryUtils.cs
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
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
|
||||
// reference can be dropped. Only the float-precision overloads are used by
|
||||
// ObjectMeshManager (RayIntersectsSphere + RayIntersectsTriangle). The
|
||||
// double-precision overloads are kept verbatim for completeness.
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
public static class GeometryUtils {
|
||||
|
||||
public static bool RayIntersectsBox(Vector3 rayOrigin, Vector3 rayDirection, Vector3 min, Vector3 max, out float distance) {
|
||||
distance = 0;
|
||||
float tmin = 0.0f;
|
||||
float tmax = float.MaxValue;
|
||||
|
||||
if (Math.Abs(rayDirection.X) < 1e-7f) {
|
||||
if (rayOrigin.X < min.X || rayOrigin.X > max.X) return false;
|
||||
}
|
||||
else {
|
||||
float invD = 1.0f / rayDirection.X;
|
||||
float t0 = (min.X - rayOrigin.X) * invD;
|
||||
float t1 = (max.X - rayOrigin.X) * invD;
|
||||
if (t0 > t1) (t0, t1) = (t1, t0);
|
||||
tmin = Math.Max(tmin, t0);
|
||||
tmax = Math.Min(tmax, t1);
|
||||
if (tmin > tmax) return false;
|
||||
}
|
||||
|
||||
if (Math.Abs(rayDirection.Y) < 1e-7f) {
|
||||
if (rayOrigin.Y < min.Y || rayOrigin.Y > max.Y) return false;
|
||||
}
|
||||
else {
|
||||
float invD = 1.0f / rayDirection.Y;
|
||||
float t0 = (min.Y - rayOrigin.Y) * invD;
|
||||
float t1 = (max.Y - rayOrigin.Y) * invD;
|
||||
if (t0 > t1) (t0, t1) = (t1, t0);
|
||||
tmin = Math.Max(tmin, t0);
|
||||
tmax = Math.Min(tmax, t1);
|
||||
if (tmin > tmax) return false;
|
||||
}
|
||||
|
||||
if (Math.Abs(rayDirection.Z) < 1e-7f) {
|
||||
if (rayOrigin.Z < min.Z || rayOrigin.Z > max.Z) return false;
|
||||
}
|
||||
else {
|
||||
float invD = 1.0f / rayDirection.Z;
|
||||
float t0 = (min.Z - rayOrigin.Z) * invD;
|
||||
float t1 = (max.Z - rayOrigin.Z) * invD;
|
||||
if (t0 > t1) (t0, t1) = (t1, t0);
|
||||
tmin = Math.Max(tmin, t0);
|
||||
tmax = Math.Min(tmax, t1);
|
||||
if (tmin > tmax) return false;
|
||||
}
|
||||
|
||||
distance = tmin;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static bool RayIntersectsTriangle(Vector3 origin, Vector3 direction, Vector3 v0, Vector3 v1, Vector3 v2, out float t) {
|
||||
t = 0;
|
||||
Vector3 edge1 = v1 - v0;
|
||||
Vector3 edge2 = v2 - v0;
|
||||
Vector3 h = Vector3.Cross(direction, edge2);
|
||||
float a = Vector3.Dot(edge1, h);
|
||||
|
||||
if (a > -0.00001f && a < 0.00001f) return false;
|
||||
|
||||
float f = 1.0f / a;
|
||||
Vector3 s = origin - v0;
|
||||
float u = f * Vector3.Dot(s, h);
|
||||
|
||||
if (u < 0.0f || u > 1.0f) return false;
|
||||
|
||||
Vector3 q = Vector3.Cross(s, edge1);
|
||||
float v = f * Vector3.Dot(direction, q);
|
||||
|
||||
if (v < 0.0f || u + v > 1.0f) return false;
|
||||
|
||||
t = f * Vector3.Dot(edge2, q);
|
||||
return t > 0.00001f;
|
||||
}
|
||||
|
||||
public static bool RayIntersectsSphere(Vector3 rayOrigin, Vector3 rayDirection, Vector3 sphereOrigin, float sphereRadius, out float distance) {
|
||||
distance = 0;
|
||||
Vector3 l = sphereOrigin - rayOrigin;
|
||||
float tca = Vector3.Dot(l, rayDirection);
|
||||
if (tca < 0) return false;
|
||||
float d2 = Vector3.Dot(l, l) - tca * tca;
|
||||
float r2 = sphereRadius * sphereRadius;
|
||||
if (d2 > r2) return false;
|
||||
float thc = MathF.Sqrt(r2 - d2);
|
||||
distance = tca - thc;
|
||||
return true;
|
||||
}
|
||||
|
||||
public static ushort PackKey(int x, int y) => (ushort)((x << 8) | y);
|
||||
|
||||
/// <summary>
|
||||
/// Converts a quaternion to Euler angles (in degrees) using the ZYX convention.
|
||||
/// </summary>
|
||||
public static Vector3 QuaternionToEuler(Quaternion q) {
|
||||
float x = q.X, y = q.Y, z = q.Z, w = q.W;
|
||||
float roll, pitch, yaw;
|
||||
|
||||
float sinr_cosp = 2 * (w * x + y * z);
|
||||
float cosr_cosp = 1 - 2 * (x * x + y * y);
|
||||
roll = (float)Math.Atan2(sinr_cosp, cosr_cosp);
|
||||
|
||||
float sinp = 2 * (w * y - z * x);
|
||||
if (Math.Abs(sinp) >= 1)
|
||||
pitch = (float)Math.CopySign(Math.PI / 2, sinp);
|
||||
else
|
||||
pitch = (float)Math.Asin(sinp);
|
||||
|
||||
float siny_cosp = 2 * (w * z + x * y);
|
||||
float cosy_cosp = 1 - 2 * (y * y + z * z);
|
||||
yaw = (float)Math.Atan2(siny_cosp, cosy_cosp);
|
||||
|
||||
return new Vector3(roll, pitch, yaw) * (180.0f / (float)Math.PI);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts Euler angles (in degrees) to a quaternion using the ZYX convention.
|
||||
/// </summary>
|
||||
public static Quaternion EulerToQuaternion(Vector3 euler) {
|
||||
Vector3 rads = euler * (MathF.PI / 180.0f);
|
||||
float roll = rads.X;
|
||||
float pitch = rads.Y;
|
||||
float yaw = rads.Z;
|
||||
|
||||
float cr = MathF.Cos(roll * 0.5f);
|
||||
float sr = MathF.Sin(roll * 0.5f);
|
||||
float cp = MathF.Cos(pitch * 0.5f);
|
||||
float sp = MathF.Sin(pitch * 0.5f);
|
||||
float cy = MathF.Cos(yaw * 0.5f);
|
||||
float sy = MathF.Sin(yaw * 0.5f);
|
||||
|
||||
return new Quaternion(
|
||||
sr * cp * cy - cr * sp * sy,
|
||||
cr * sp * cy + sr * cp * sy,
|
||||
cr * cp * sy - sr * sp * cy,
|
||||
cr * cp * cy + sr * sp * sy
|
||||
);
|
||||
}
|
||||
}
|
||||
114
src/AcDream.App/Rendering/Wb/IDatReaderWriter.cs
Normal file
114
src/AcDream.App/Rendering/Wb/IDatReaderWriter.cs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
using DatReaderWriter;
|
||||
using DatReaderWriter.Enums;
|
||||
using DatReaderWriter.Lib.IO;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
// Phase O-T7: verbatim copy of WorldBuilder.Shared.Services.IDatReaderWriter +
|
||||
// IDatDatabase into the AcDream.App.Rendering.Wb namespace so the
|
||||
// WorldBuilder.Shared project reference can be dropped.
|
||||
// The only consumer of IDatReaderWriter in acdream is DatCollectionAdapter +
|
||||
// ObjectMeshManager, both already in this namespace.
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
/// <summary>
|
||||
/// Interface for the dat reader/writer
|
||||
/// </summary>
|
||||
public interface IDatReaderWriter : IDisposable {
|
||||
/// <summary>
|
||||
/// Gets the source directory of the DAT files.
|
||||
/// </summary>
|
||||
string SourceDirectory { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Tries to get the raw bytes of a file from a specific region database.
|
||||
/// </summary>
|
||||
bool TryGetFileBytes(uint regionId, uint fileId, ref byte[] bytes, out int bytesRead);
|
||||
|
||||
/// <summary>
|
||||
/// The portal database
|
||||
/// </summary>
|
||||
IDatDatabase Portal { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The cell region databases. Each key is a cell region ID
|
||||
/// </summary>
|
||||
ReadOnlyDictionary<uint, IDatDatabase> CellRegions { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The high res database
|
||||
/// </summary>
|
||||
IDatDatabase HighRes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The language database
|
||||
/// </summary>
|
||||
IDatDatabase Language { get; }
|
||||
|
||||
/// <summary>
|
||||
/// A mapping of region ids to region dat file entry ids. key: region id, value: region dat file entry
|
||||
/// </summary>
|
||||
ReadOnlyDictionary<uint, uint> RegionFileMap { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current portal iteration.
|
||||
/// </summary>
|
||||
int PortalIteration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current cell iteration (from the first cell region).
|
||||
/// </summary>
|
||||
int CellIteration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current high res iteration.
|
||||
/// </summary>
|
||||
int HighResIteration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current language iteration.
|
||||
/// </summary>
|
||||
int LanguageIteration { get; }
|
||||
|
||||
/// <summary>Attempts to save a database object to the appropriate DAT.</summary>
|
||||
bool TrySave<T>(T obj, int iteration = 0) where T : IDBObj;
|
||||
|
||||
/// <summary>Attempts to save a database object to the appropriate DAT for a specific region.</summary>
|
||||
bool TrySave<T>(uint regionId, T obj, int iteration = 0) where T : IDBObj;
|
||||
|
||||
/// <summary>
|
||||
/// Resolution of a data ID to a database and type
|
||||
/// </summary>
|
||||
public record IdResolution(IDatDatabase Database, DBObjType Type);
|
||||
|
||||
/// <summary>
|
||||
/// Resolves a data ID to all possible databases and types.
|
||||
/// </summary>
|
||||
public IEnumerable<IdResolution> ResolveId(uint id);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface for a dat database, providing methods to retrieve files and objects.
|
||||
/// </summary>
|
||||
public interface IDatDatabase : IDisposable {
|
||||
DatDatabase Db { get; }
|
||||
|
||||
/// <summary>Retrieves the current iteration of the database.</summary>
|
||||
int Iteration { get; }
|
||||
|
||||
/// <summary>Retrieves all file IDs of a specific type.</summary>
|
||||
public IEnumerable<uint> GetAllIdsOfType<T>() where T : IDBObj;
|
||||
|
||||
/// <summary>Attempts to retrieve a database object by its file ID.</summary>
|
||||
public bool TryGet<T>(uint fileId, [MaybeNullWhen(false)] out T value) where T : IDBObj;
|
||||
|
||||
/// <summary>Attempts to retrieve the raw bytes of a file by its ID.</summary>
|
||||
bool TryGetFileBytes(uint fileId, [MaybeNullWhen(false)] out byte[] value);
|
||||
|
||||
/// <summary>Attempts to retrieve the raw bytes of a file by its ID into a provided buffer.</summary>
|
||||
bool TryGetFileBytes(uint fileId, ref byte[] bytes, out int bytesRead);
|
||||
|
||||
/// <summary>Attempts to save a database object.</summary>
|
||||
bool TrySave<T>(T obj, int iteration = 0) where T : IDBObj;
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
using Chorizite.Core.Render.Enums;
|
||||
using Chorizite.Core.Render.Vertex;
|
||||
using Chorizite.OpenGLSDLBackend.Lib;
|
||||
using Silk.NET.OpenGL;
|
||||
using BufferUsage = Chorizite.Core.Render.Enums.BufferUsage;
|
||||
|
||||
|
|
@ -158,7 +157,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
|
||||
/// <inheritdoc />
|
||||
public void Bind() {
|
||||
BaseObjectRenderManager.CurrentIBO = 0;
|
||||
RenderStateCache.CurrentIBO = 0;
|
||||
GL.BindBuffer(GLEnum.ElementArrayBuffer, bufferId);
|
||||
GLHelpers.CheckErrors(GL);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
using Chorizite.Core.Render;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using Chorizite.OpenGLSDLBackend.Lib;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb {
|
||||
|
|
@ -108,7 +107,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
GLHelpers.CheckErrors(GL);
|
||||
|
||||
GL.GetInteger(GLEnum.ActiveTexture, out int oldActiveTexture);
|
||||
BaseObjectRenderManager.CurrentAtlas = 0;
|
||||
RenderStateCache.CurrentAtlas = 0;
|
||||
|
||||
GL.GetInteger(GLEnum.TextureBinding2D, out int oldBinding);
|
||||
GL.BindTexture(GLEnum.Texture2D, _texture);
|
||||
|
|
@ -147,7 +146,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
|
||||
public void Bind(int slot = 0) {
|
||||
if (slot == 0) {
|
||||
BaseObjectRenderManager.CurrentAtlas = 0;
|
||||
RenderStateCache.CurrentAtlas = 0;
|
||||
}
|
||||
GL.GetInteger(GLEnum.ActiveTexture, out int oldActiveTexture);
|
||||
GLEnum targetTextureUnit = GLEnum.Texture0 + slot;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using AcDream.Core.Rendering.Wb;
|
||||
using Chorizite.Core.Render;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using Chorizite.OpenGLSDLBackend.Lib;
|
||||
// Use our extracted TextureHelpers (T3), not the WB original — disambiguate explicitly
|
||||
using TextureHelpers = AcDream.Core.Rendering.Wb.TextureHelpers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
|
@ -274,7 +273,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
GLHelpers.CheckErrors(GL);
|
||||
|
||||
GL.GetInteger(GLEnum.ActiveTexture, out int oldActiveTexture);
|
||||
BaseObjectRenderManager.CurrentAtlas = 0;
|
||||
RenderStateCache.CurrentAtlas = 0;
|
||||
|
||||
GL.GetInteger(GLEnum.TextureBinding2DArray, out int oldBinding);
|
||||
GL.BindTexture(GLEnum.Texture2DArray, (uint)NativePtr);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ using System.Numerics;
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using WorldBuilder.Shared.Services;
|
||||
using AcDream.Core.Rendering.Wb;
|
||||
using PixelFormat = Silk.NET.OpenGL.PixelFormat;
|
||||
using BoundingBox = Chorizite.Core.Lib.BoundingBox;
|
||||
|
|
@ -24,7 +23,6 @@ using BCnEncoder.Shared;
|
|||
using BCnEncoder.ImageSharp;
|
||||
using SixLabors.ImageSharp;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using WorldBuilder.Shared.Lib;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb {
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using Chorizite.Core.Render;
|
||||
using Chorizite.OpenGLSDLBackend.Lib;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb {
|
||||
|
|
@ -187,9 +186,7 @@ namespace AcDream.App.Rendering.Wb {
|
|||
|
||||
gl.ActiveTexture(TextureUnit.Texture0);
|
||||
gl.BindTexture(GLEnum.Texture2DArray, (uint)_currentAtlas.NativePtr);
|
||||
// T4 interim: BaseObjectRenderManager state fields stay on the WB type until T7
|
||||
// when the WorldBuilder project reference is dropped entirely.
|
||||
BaseObjectRenderManager.CurrentAtlas = (uint)_currentAtlas.Slot;
|
||||
RenderStateCache.CurrentAtlas = (uint)_currentAtlas.Slot;
|
||||
|
||||
gl.BindBuffer(BufferTargetARB.ArrayBuffer, _instanceVbo);
|
||||
unsafe {
|
||||
|
|
@ -210,9 +207,8 @@ namespace AcDream.App.Rendering.Wb {
|
|||
gl.DepthMask(true);
|
||||
_allParticles.Clear();
|
||||
|
||||
// T4 interim: BaseObjectRenderManager state fields stay on the WB type until T7
|
||||
BaseObjectRenderManager.CurrentVAO = 0;
|
||||
BaseObjectRenderManager.CurrentIBO = 0;
|
||||
RenderStateCache.CurrentVAO = 0;
|
||||
RenderStateCache.CurrentIBO = 0;
|
||||
}
|
||||
|
||||
public void End() {
|
||||
|
|
|
|||
20
src/AcDream.App/Rendering/Wb/RenderStateCache.cs
Normal file
20
src/AcDream.App/Rendering/Wb/RenderStateCache.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
/// <summary>
|
||||
/// Tracks currently-bound GL state to skip redundant rebinds across the
|
||||
/// WB-derived render path. Previously these were static fields on
|
||||
/// <c>BaseObjectRenderManager</c> in the WorldBuilder.Shared project; inlined
|
||||
/// 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.
|
||||
/// Sentinel value 0 means "no valid binding cached."
|
||||
/// </summary>
|
||||
public static class RenderStateCache
|
||||
{
|
||||
public static uint CurrentAtlas = 0;
|
||||
public static uint CurrentVAO = 0;
|
||||
public static uint CurrentIBO = 0;
|
||||
}
|
||||
|
|
@ -6,7 +6,6 @@ using AcDream.Core.Meshing;
|
|||
using AcDream.Core.Rendering;
|
||||
using AcDream.Core.Terrain;
|
||||
using AcDream.Core.World;
|
||||
using Chorizite.OpenGLSDLBackend.Lib;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using AcDream.Core.Meshing;
|
||||
using AcDream.Core.Rendering;
|
||||
using Chorizite.OpenGLSDLBackend.Lib;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Silk.NET.OpenGL;
|
||||
using WorldBuilder.Shared.Services;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
|
|
@ -20,30 +16,19 @@ namespace AcDream.App.Rendering.Wb;
|
|||
/// so the rest of the renderer doesn't need to know about WB's types directly.
|
||||
///
|
||||
/// <para>
|
||||
/// The adapter constructs its own <c>DefaultDatReaderWriter</c> internally; it
|
||||
/// does NOT share file handles with our <c>DatCollection</c>. This duplicates
|
||||
/// index-cache memory (~50–100 MB) but keeps the two subsystems fully decoupled.
|
||||
/// Acceptable for Phase N.4 foundation work (plan Adjustment 1).
|
||||
/// As of Phase O-T7, all DAT I/O routes through <see cref="DatCollectionAdapter"/>
|
||||
/// (backed by our shared <see cref="DatCollection"/>) — the separate
|
||||
/// <c>DefaultDatReaderWriter</c> file-handle set has been removed.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
||||
{
|
||||
private readonly OpenGLGraphicsDevice? _graphicsDevice;
|
||||
private readonly DefaultDatReaderWriter? _wbDats;
|
||||
private readonly ObjectMeshManager? _meshManager;
|
||||
private readonly DatCollection? _dats;
|
||||
private readonly AcSurfaceMetadataTable _metadataTable = new();
|
||||
private readonly HashSet<ulong> _metadataPopulated = new();
|
||||
|
||||
/// <summary>
|
||||
/// EnvCell ids we've requested via PrepareMeshDataAsync but not yet
|
||||
/// seen completion for in Tick(). Used by the [indoor-upload] probe
|
||||
/// to log requested + completed pairs. Cleared per completion;
|
||||
/// missing completions after a few seconds indicate WB silently
|
||||
/// returned null (hypothesis H1 in the design spec).
|
||||
/// </summary>
|
||||
private readonly HashSet<ulong> _pendingEnvCellRequests = new();
|
||||
|
||||
/// <summary>
|
||||
/// True when this instance was created via <see cref="CreateUninitialized"/>;
|
||||
/// all public methods no-op when uninitialized.
|
||||
|
|
@ -53,13 +38,13 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
private bool _disposed;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the full WB pipeline: OpenGLGraphicsDevice → DefaultDatReaderWriter
|
||||
/// Constructs the full WB pipeline: OpenGLGraphicsDevice → DatCollectionAdapter
|
||||
/// → ObjectMeshManager.
|
||||
/// </summary>
|
||||
/// <param name="gl">Active Silk.NET GL context. Must be bound to the current
|
||||
/// thread (construction runs GL queries; call from OnLoad).</param>
|
||||
/// <param name="datDir">Path to the dat directory (same as the one supplied
|
||||
/// to our DatCollection). DefaultDatReaderWriter opens its own file handles.</param>
|
||||
/// <param name="datDir">Path to the dat directory. Retained for API compatibility;
|
||||
/// DatCollectionAdapter routes all DAT I/O through our shared DatCollection.</param>
|
||||
/// <param name="dats">acdream's DatCollection, used to populate the surface
|
||||
/// metadata side-table via <c>GfxObjMesh.Build</c>. Shares file handles with
|
||||
/// the rest of the client; read-only access from the render thread.</param>
|
||||
|
|
@ -75,12 +60,8 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
_dats = dats;
|
||||
_graphicsDevice = new OpenGLGraphicsDevice(gl, logger, new DebugRenderSettings());
|
||||
_graphicsDevice.ParticleBatcher = new ParticleBatcher(_graphicsDevice);
|
||||
_wbDats = new DefaultDatReaderWriter(datDir);
|
||||
// Phase 2 diagnostic — replace NullLogger with a Console-backed
|
||||
// logger so WB's internal catch block at ObjectMeshManager.cs:589
|
||||
// (and similar) surfaces its swallowed exceptions instead of
|
||||
// dropping them. ConsoleErrorLogger filters to LogLevel.Error+
|
||||
// so successful operations stay quiet.
|
||||
// ConsoleErrorLogger surfaces WB's silently-caught exceptions
|
||||
// (ObjectMeshManager.PrepareMeshData try/catch at line ~589).
|
||||
_meshManager = new ObjectMeshManager(
|
||||
_graphicsDevice,
|
||||
new DatCollectionAdapter(dats),
|
||||
|
|
@ -186,80 +167,7 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
// isSetup: false — acdream's MeshRefs already carry expanded
|
||||
// per-part GfxObj ids (0x01XXXXXX). WB's Setup-expansion path is
|
||||
// unused.
|
||||
var prepTask = _meshManager.PrepareMeshDataAsync(id, isSetup: false);
|
||||
|
||||
// [indoor-upload] requested probe — only for EnvCell ids.
|
||||
if (RenderingDiagnostics.IsEnvCellId(id) && RenderingDiagnostics.ProbeIndoorUploadEnabled)
|
||||
{
|
||||
bool hadRenderDataAtRequest = _meshManager.HasRenderData(id);
|
||||
_pendingEnvCellRequests.Add(id);
|
||||
Console.WriteLine($"[indoor-upload] requested cellId=0x{id:X8} hadRenderData={hadRenderDataAtRequest}");
|
||||
|
||||
// Phase 2 — surface what WB's catch block silently swallows.
|
||||
// ObjectMeshManager.PrepareMeshData has a try/catch at line 589
|
||||
// that calls _logger.LogError(ex, ...) — but we construct
|
||||
// ObjectMeshManager with NullLogger.Instance so the log is
|
||||
// dropped. This continuation captures the same data scoped to
|
||||
// EnvCell ids only. Runs on ThreadPool; non-blocking. Zero cost
|
||||
// when the probe is off.
|
||||
ulong cellId = id;
|
||||
_ = prepTask.ContinueWith(t =>
|
||||
{
|
||||
if (t.IsFaulted && t.Exception is not null)
|
||||
{
|
||||
var ex = t.Exception.InnerException ?? t.Exception;
|
||||
var stack = (ex.StackTrace ?? "").Split('\n')
|
||||
.Take(3).Select(s => s.Trim()).Where(s => s.Length > 0);
|
||||
Console.WriteLine(
|
||||
$"[indoor-upload] FAILED cellId=0x{cellId:X8} " +
|
||||
$"exception={ex.GetType().Name}: {ex.Message} " +
|
||||
$"stack=[{string.Join(" | ", stack)}]");
|
||||
}
|
||||
else if (t.IsCompletedSuccessfully && t.Result is null)
|
||||
{
|
||||
// Phase 2 cause-narrowing: WB's PrepareMeshData can return
|
||||
// null for several reasons (ResolveId empty / TryGet<EnvCell>
|
||||
// failed / type Unknown). Cross-check against acdream's own
|
||||
// DatCollection — if WE find the cell but WB doesn't, the
|
||||
// divergence is between dat readers, not a missing record.
|
||||
bool ourCellFound = false;
|
||||
try
|
||||
{
|
||||
ourCellFound = _dats?.Cell.TryGet<DatReaderWriter.DBObjs.EnvCell>(
|
||||
(uint)cellId, out _) ?? false;
|
||||
}
|
||||
catch { /* swallow — this is best-effort diagnostic */ }
|
||||
|
||||
int wbResolveCount = -1;
|
||||
string wbSelectedType = "none";
|
||||
bool wbDbTryGetEnvCell = false;
|
||||
bool wbDbIsPortal = false;
|
||||
try
|
||||
{
|
||||
var wbResolutions = _wbDats?.ResolveId((uint)cellId).ToList();
|
||||
wbResolveCount = wbResolutions?.Count ?? -1;
|
||||
if (wbResolutions is not null && wbResolutions.Count > 0)
|
||||
{
|
||||
var selected = wbResolutions
|
||||
.OrderByDescending(r => r.Database == _wbDats!.Portal)
|
||||
.First();
|
||||
wbSelectedType = selected.Type.ToString();
|
||||
wbDbIsPortal = selected.Database == _wbDats!.Portal;
|
||||
try { wbDbTryGetEnvCell = selected.Database.TryGet<DatReaderWriter.DBObjs.EnvCell>((uint)cellId, out _); } catch {}
|
||||
}
|
||||
}
|
||||
catch { /* swallow — best-effort */ }
|
||||
|
||||
Console.WriteLine(
|
||||
$"[indoor-upload] NULL_RESULT cellId=0x{cellId:X8} " +
|
||||
$"ourCellDb.TryGet={ourCellFound} " +
|
||||
$"wbResolveId.Count={wbResolveCount} " +
|
||||
$"wbSelectedType={wbSelectedType} " +
|
||||
$"wbDbIsPortal={wbDbIsPortal} " +
|
||||
$"wbDbTryGet<EnvCell>={wbDbTryGetEnvCell}");
|
||||
}
|
||||
}, TaskScheduler.Default);
|
||||
}
|
||||
_meshManager.PrepareMeshDataAsync(id, isSetup: false);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -298,26 +206,7 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
_graphicsDevice!.ProcessGLQueue();
|
||||
while (_meshManager!.StagedMeshData.TryDequeue(out var meshData))
|
||||
{
|
||||
// [indoor-upload] completed probe — check BEFORE upload so we
|
||||
// see what WB actually produced (vertex counts, parts) before
|
||||
// any post-upload mutation.
|
||||
bool isPendingEnvCell = RenderingDiagnostics.ProbeIndoorUploadEnabled
|
||||
&& _pendingEnvCellRequests.Remove(meshData.ObjectId);
|
||||
|
||||
var renderData = _meshManager.UploadMeshData(meshData);
|
||||
|
||||
if (isPendingEnvCell)
|
||||
{
|
||||
int parts = meshData.SetupParts?.Count ?? 0;
|
||||
bool hasGeom = meshData.EnvCellGeometry is not null;
|
||||
int cellGeomVerts = meshData.EnvCellGeometry?.Vertices?.Length ?? 0;
|
||||
bool uploadOk = renderData is not null;
|
||||
Console.WriteLine(
|
||||
$"[indoor-upload] completed cellId=0x{meshData.ObjectId:X8} " +
|
||||
$"isSetup={meshData.IsSetup} parts={parts} " +
|
||||
$"hasEnvCellGeom={hasGeom} cellGeomVerts={cellGeomVerts} " +
|
||||
$"uploadOk={uploadOk}");
|
||||
}
|
||||
_meshManager.UploadMeshData(meshData);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -342,7 +231,6 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
if (_disposed) return;
|
||||
_disposed = true;
|
||||
_meshManager?.Dispose();
|
||||
_wbDats?.Dispose();
|
||||
_graphicsDevice?.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue