phase(N.4): WbMeshAdapter.Tick — drain WB pipeline queues per frame
Without this, ObjectMeshManager.StagedMeshData and OpenGLGraphicsDevice._glThreadQueue grow unbounded as background workers prep mesh data + queue GL actions. Visual stress test of flag-on at radius 7 showed real FPS drop and rising frame latency from this leak. Tick() drains both queues: 1. _graphicsDevice.ProcessGLQueue() applies pending GL state. 2. Loop _meshManager.StagedMeshData.TryDequeue -> UploadMeshData to materialize VAO/VBO/IBO for each prepared mesh. Wired into GameWindow's render loop before draw work begins. No-op when adapter is uninitialized or disposed. Pattern matches WB's reference ObjectRenderManagerBase.ProcessUploads without the prioritization heuristics (we're not yet drawing the results — Task 22's WbDrawDispatcher will add prioritization when visual budget matters). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f4f0101d2c
commit
bf53cb4fce
3 changed files with 54 additions and 0 deletions
|
|
@ -6076,6 +6076,12 @@ public sealed class GameWindow : IDisposable
|
||||||
|
|
||||||
_gl!.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
_gl!.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
|
||||||
|
|
||||||
|
// Phase N.4: drain WB pipeline queues (staged mesh data +
|
||||||
|
// GL thread queue). Must happen before any draw work so that
|
||||||
|
// resources uploaded this frame are available immediately.
|
||||||
|
// No-op when ACDREAM_USE_WB_FOUNDATION is off (_wbMeshAdapter is null).
|
||||||
|
_wbMeshAdapter?.Tick();
|
||||||
|
|
||||||
// Phase D.2a — begin ImGui frame. Paired with the Render() call
|
// Phase D.2a — begin ImGui frame. Paired with the Render() call
|
||||||
// after the scene draws (below). ImGuiController.Update()
|
// after the scene draws (below). ImGuiController.Update()
|
||||||
// consumes buffered Silk.NET input events and calls ImGui.NewFrame.
|
// consumes buffered Silk.NET input events and calls ImGui.NewFrame.
|
||||||
|
|
|
||||||
|
|
@ -94,6 +94,38 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
||||||
_meshManager.DecrementRefCount(id);
|
_meshManager.DecrementRefCount(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Per-frame drain of the WB pipeline's main-thread work queues. MUST be
|
||||||
|
/// called once per frame from the render thread. Without this, the staged
|
||||||
|
/// mesh data queue grows unbounded (memory leak) and queued GL actions
|
||||||
|
/// never execute.
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// Order matters: <c>ProcessGLQueue</c> runs first to apply any pending GL
|
||||||
|
/// state changes (e.g., texture uploads queued by background workers
|
||||||
|
/// during mesh prep). Then we drain staged mesh data, calling
|
||||||
|
/// <c>UploadMeshData</c> on each item to materialize the actual GL VAO /
|
||||||
|
/// VBO / IBO resources. After Tick, <c>GetRenderData</c> for any id
|
||||||
|
/// previously passed to <c>IncrementRefCount</c> may return non-null.
|
||||||
|
/// </para>
|
||||||
|
///
|
||||||
|
/// <para>
|
||||||
|
/// No-op when the adapter is uninitialized (e.g., flag is off and the
|
||||||
|
/// adapter was constructed via <c>CreateUninitialized</c>).
|
||||||
|
/// </para>
|
||||||
|
/// </summary>
|
||||||
|
public void Tick()
|
||||||
|
{
|
||||||
|
if (_isUninitialized) return;
|
||||||
|
if (_disposed) return;
|
||||||
|
|
||||||
|
_graphicsDevice!.ProcessGLQueue();
|
||||||
|
while (_meshManager!.StagedMeshData.TryDequeue(out var meshData))
|
||||||
|
{
|
||||||
|
_meshManager.UploadMeshData(meshData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -46,4 +46,20 @@ public sealed class WbMeshAdapterTests
|
||||||
var adapter = WbMeshAdapter.CreateUninitialized();
|
var adapter = WbMeshAdapter.CreateUninitialized();
|
||||||
Assert.Null(adapter.GetRenderData(0x01000001ul));
|
Assert.Null(adapter.GetRenderData(0x01000001ul));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Tick_OnUninitializedAdapter_DoesNotThrow()
|
||||||
|
{
|
||||||
|
var adapter = WbMeshAdapter.CreateUninitialized();
|
||||||
|
adapter.Tick(); // no-op, no throw
|
||||||
|
adapter.Tick(); // idempotent
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Tick_AfterDispose_DoesNotThrow()
|
||||||
|
{
|
||||||
|
var adapter = WbMeshAdapter.CreateUninitialized();
|
||||||
|
adapter.Dispose();
|
||||||
|
adapter.Tick(); // no-op, no throw
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue