perf(render): consume prepared mesh package at runtime
This commit is contained in:
parent
c42f93b323
commit
f05afc07c1
22 changed files with 328 additions and 370 deletions
|
|
@ -1,10 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AcDream.Content;
|
||||
using AcDream.Core.Meshing;
|
||||
using AcDream.Core.Rendering;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using Silk.NET.OpenGL;
|
||||
|
|
@ -48,9 +46,7 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
private readonly OpenGLGraphicsDevice? _graphicsDevice;
|
||||
private readonly ObjectMeshManager? _meshManager;
|
||||
private readonly AcDream.App.Rendering.IGpuResourceRetirementQueue? _resourceRetirement;
|
||||
private readonly IDatReaderWriter? _dats;
|
||||
private readonly AcSurfaceMetadataTable _metadataTable = new();
|
||||
private readonly HashSet<ulong> _metadataPopulated = new();
|
||||
private readonly IPreparedAssetSource? _ownedPreparedAssets;
|
||||
private readonly MeshUploadFrameBudget _uploadBudget = new(new MeshUploadBudgetLimits(
|
||||
MaximumUploadsPerFrame,
|
||||
MaximumUploadBytesPerFrame,
|
||||
|
|
@ -95,35 +91,72 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
_meshManager?.CpuCacheDiagnostics ?? default;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs the full WB pipeline: OpenGLGraphicsDevice → shared bounded
|
||||
/// DAT facade → ObjectMeshManager.
|
||||
/// Constructs the UI-Studio/tooling WB pipeline. Production composition
|
||||
/// supplies the validated pak source through the internal overload below;
|
||||
/// this explicit tooling seam retains live-DAT extraction.
|
||||
/// </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="dats">acdream's shared runtime DAT facade, 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>
|
||||
/// <param name="dats">acdream's shared runtime DAT facade. Tooling uses it
|
||||
/// through an explicitly owned <see cref="DatPreparedAssetSource"/>.</param>
|
||||
/// <param name="logger">Logger for the adapter; ObjectMeshManager uses
|
||||
/// NullLogger internally.</param>
|
||||
public WbMeshAdapter(GL gl, IDatReaderWriter dats, ILogger<WbMeshAdapter> logger)
|
||||
: this(gl, dats, logger, AcDream.App.Rendering.ImmediateGpuResourceRetirementQueue.Instance)
|
||||
: this(
|
||||
gl,
|
||||
dats,
|
||||
preparedAssets: null,
|
||||
logger,
|
||||
AcDream.App.Rendering.ImmediateGpuResourceRetirementQueue.Instance,
|
||||
ownsPreparedAssets: true)
|
||||
{
|
||||
}
|
||||
|
||||
internal static WbMeshAdapter CreateWithLiveDatPreparedAssets(
|
||||
GL gl,
|
||||
IDatReaderWriter dats,
|
||||
ILogger<WbMeshAdapter> logger,
|
||||
AcDream.App.Rendering.IGpuResourceRetirementQueue resourceRetirement) =>
|
||||
new(
|
||||
gl,
|
||||
dats,
|
||||
preparedAssets: null,
|
||||
logger,
|
||||
resourceRetirement,
|
||||
ownsPreparedAssets: true);
|
||||
|
||||
internal WbMeshAdapter(
|
||||
GL gl,
|
||||
IDatReaderWriter dats,
|
||||
IPreparedAssetSource preparedAssets,
|
||||
ILogger<WbMeshAdapter> logger,
|
||||
AcDream.App.Rendering.IGpuResourceRetirementQueue resourceRetirement)
|
||||
: this(
|
||||
gl,
|
||||
dats,
|
||||
preparedAssets,
|
||||
logger,
|
||||
resourceRetirement,
|
||||
ownsPreparedAssets: false)
|
||||
{
|
||||
}
|
||||
|
||||
private WbMeshAdapter(
|
||||
GL gl,
|
||||
IDatReaderWriter dats,
|
||||
IPreparedAssetSource? preparedAssets,
|
||||
ILogger<WbMeshAdapter> logger,
|
||||
AcDream.App.Rendering.IGpuResourceRetirementQueue resourceRetirement,
|
||||
bool ownsPreparedAssets)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(gl);
|
||||
ArgumentNullException.ThrowIfNull(dats);
|
||||
ArgumentNullException.ThrowIfNull(logger);
|
||||
|
||||
_dats = dats;
|
||||
_resourceRetirement = resourceRetirement;
|
||||
var resources = new AcDream.App.Rendering.ResourceCleanupGroup();
|
||||
OpenGLGraphicsDevice? graphicsDevice = null;
|
||||
IPreparedAssetSource? resolvedPreparedAssets = preparedAssets;
|
||||
ObjectMeshManager? meshManager = null;
|
||||
try
|
||||
{
|
||||
|
|
@ -145,11 +178,20 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
}
|
||||
});
|
||||
resources.Add("WB graphics device", graphicsDeviceRelease.Run);
|
||||
if (resolvedPreparedAssets is null)
|
||||
{
|
||||
resolvedPreparedAssets = new DatPreparedAssetSource(
|
||||
dats,
|
||||
new ConsoleErrorLogger<ObjectMeshManager>());
|
||||
resources.Add(
|
||||
"WB tooling prepared asset source",
|
||||
resolvedPreparedAssets.Dispose);
|
||||
}
|
||||
// ConsoleErrorLogger surfaces WB's silently-caught exceptions
|
||||
// (ObjectMeshManager.PrepareMeshData try/catch at line ~589).
|
||||
meshManager = new ObjectMeshManager(
|
||||
graphicsDevice,
|
||||
dats,
|
||||
resolvedPreparedAssets,
|
||||
new ConsoleErrorLogger<ObjectMeshManager>());
|
||||
resources.Add("WB object mesh manager", meshManager.Dispose);
|
||||
resources.TransferAll();
|
||||
|
|
@ -163,6 +205,9 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
|
||||
_graphicsDevice = graphicsDevice;
|
||||
_meshManager = meshManager;
|
||||
_ownedPreparedAssets = ownsPreparedAssets
|
||||
? resolvedPreparedAssets
|
||||
: null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -213,13 +258,6 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
/// underlying mesh manager. Public methods are all no-ops.</summary>
|
||||
public static WbMeshAdapter CreateUninitialized() => new();
|
||||
|
||||
/// <summary>
|
||||
/// The surface metadata side-table populated on each first
|
||||
/// <see cref="IncrementRefCount"/>. Queried by the draw dispatcher
|
||||
/// to determine translucency, luminosity, and fog behavior per batch.
|
||||
/// </summary>
|
||||
public AcSurfaceMetadataTable MetadataTable => _metadataTable;
|
||||
|
||||
/// <summary>
|
||||
/// Phase A8 (2026-05-28): exposes the underlying <see cref="ObjectMeshManager"/>
|
||||
/// so <c>EnvCellRenderer</c> can share the same global mesh buffer (VAO/VBO/IBO).
|
||||
|
|
@ -265,13 +303,8 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
if (_isUninitialized || _meshManager is null) return;
|
||||
_meshManager.IncrementRefCount(id);
|
||||
|
||||
bool metadataClaimed = false;
|
||||
try
|
||||
{
|
||||
metadataClaimed = _metadataPopulated.Add(id);
|
||||
if (metadataClaimed)
|
||||
PopulateMetadata(id);
|
||||
|
||||
// WB's IncrementRefCount alone only bumps a usage counter; it does
|
||||
// NOT trigger mesh loading. We must explicitly call PrepareMeshDataAsync
|
||||
// so the background workers actually decode the GfxObj. The result
|
||||
|
|
@ -281,9 +314,8 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
// 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. The old
|
||||
// first-ever-only gate (`if (_metadataPopulated.Add(id))`) permanently
|
||||
// lost any id whose initial decode was cancelled before completing
|
||||
// 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
|
||||
// churn) or whose upload was later LRU-evicted: every subsequent
|
||||
// registration skipped Prepare, so the mesh stayed invisible for the
|
||||
|
|
@ -299,19 +331,13 @@ 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.
|
||||
if (metadataClaimed || _meshManager.TryGetRenderData(id) is null)
|
||||
if (_meshManager.TryGetRenderData(id) is null)
|
||||
_meshManager.PrepareMeshDataAsync(id, isSetup: false);
|
||||
}
|
||||
catch (Exception acquireFailure)
|
||||
{
|
||||
if (metadataClaimed)
|
||||
{
|
||||
_metadataPopulated.Remove(id);
|
||||
_metadataTable.Remove(id);
|
||||
}
|
||||
|
||||
// IncrementRefCount is a public ownership boundary. Metadata/DAT
|
||||
// preparation happens after ObjectMeshManager commits its count,
|
||||
// IncrementRefCount is a public ownership boundary. Prepared-data
|
||||
// acquisition happens after ObjectMeshManager commits its count,
|
||||
// so compensate before reporting failure. ObjectMeshManager's
|
||||
// decrement has a strong exception guarantee; if compensation
|
||||
// itself fails, expose that the increment remains committed so a
|
||||
|
|
@ -612,21 +638,6 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
$" (arraysBefore={before.ArraysWithPending})");
|
||||
}
|
||||
|
||||
private void PopulateMetadata(ulong id)
|
||||
{
|
||||
if (_dats is null) return;
|
||||
if (!_dats.Portal.TryGet<GfxObj>((uint)id, out var gfxObj)) return;
|
||||
|
||||
var subMeshes = GfxObjMesh.Build(gfxObj, _dats);
|
||||
for (int i = 0; i < subMeshes.Count; i++)
|
||||
{
|
||||
var sm = subMeshes[i];
|
||||
_metadataTable.Add(id, i, new AcSurfaceMetadata(
|
||||
sm.Translucency, sm.Luminosity, sm.Diffuse,
|
||||
sm.SurfOpacity, sm.NeedsUvRepeat, sm.DisableFog));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
{
|
||||
|
|
@ -646,6 +657,7 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
frameFlights.WaitForSubmittedWork();
|
||||
},
|
||||
() => _meshManager?.Dispose(),
|
||||
() => _ownedPreparedAssets?.Dispose(),
|
||||
() => DrainGraphicsQueue("publishing mesh resource retirements"),
|
||||
() =>
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue