phase(N.5b): wire TerrainModernRenderer into GameWindow
Swap TerrainChunkRenderer → TerrainModernRenderer (drop-in: same AddLandblock/RemoveLandblock/Draw interface). Pass BindlessSupport to TerrainAtlas.Build so GetBindlessHandles() is callable. Load the new terrain_modern shader pair and pass to the renderer ctor. Add [TERRAIN-DIAG] rollup mirroring the existing [WB-DIAG] pattern. Bindless detection moved above terrain construction so atlas + ctor can consume BindlessSupport (was previously detected after — order required for N.5b). Visual verification at four scenes (Holtburg flat + sloped, Foundry, sloped landblock) is the next gate. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3418f65462
commit
75913c1c97
1 changed files with 115 additions and 33 deletions
|
|
@ -18,8 +18,13 @@ public sealed class GameWindow : IDisposable
|
||||||
private IWindow? _window;
|
private IWindow? _window;
|
||||||
private GL? _gl;
|
private GL? _gl;
|
||||||
private IInputContext? _input;
|
private IInputContext? _input;
|
||||||
private TerrainChunkRenderer? _terrain;
|
private TerrainModernRenderer? _terrain;
|
||||||
private Shader? _shader;
|
private Shader? _shader;
|
||||||
|
/// <summary>Phase N.5b: terrain_modern.vert/.frag program. Owned by
|
||||||
|
/// <see cref="_terrain"/> at draw time but allocated + disposed here. Lives
|
||||||
|
/// in parallel with <see cref="_shader"/> (legacy terrain.vert/.frag) until
|
||||||
|
/// Task 9 deletes the legacy renderer.</summary>
|
||||||
|
private Shader? _terrainModernShader;
|
||||||
private CameraController? _cameraController;
|
private CameraController? _cameraController;
|
||||||
private IMouse? _capturedMouse;
|
private IMouse? _capturedMouse;
|
||||||
private DatCollection? _dats;
|
private DatCollection? _dats;
|
||||||
|
|
@ -68,6 +73,15 @@ public sealed class GameWindow : IDisposable
|
||||||
private string _lastNearestObjLabel = "-";
|
private string _lastNearestObjLabel = "-";
|
||||||
private bool _lastColliding;
|
private bool _lastColliding;
|
||||||
|
|
||||||
|
// Phase N.5b: CPU timing for [TERRAIN-DIAG] under ACDREAM_WB_DIAG=1
|
||||||
|
// (parallel diagnostic to [WB-DIAG] in WbDrawDispatcher — same env var
|
||||||
|
// gate so flipping one switch turns on both dispatcher rollups). Mirrors
|
||||||
|
// the rolling-256-sample buffer pattern from WbDrawDispatcher.
|
||||||
|
private readonly System.Diagnostics.Stopwatch _terrainCpuStopwatch = new();
|
||||||
|
private readonly long[] _terrainCpuSamples = new long[256]; // microseconds
|
||||||
|
private int _terrainCpuSampleCursor;
|
||||||
|
private long _terrainLastDiagTick;
|
||||||
|
|
||||||
// Phase A.1: streaming fields replacing the one-shot _entities list.
|
// Phase A.1: streaming fields replacing the one-shot _entities list.
|
||||||
private AcDream.App.Streaming.LandblockStreamer? _streamer;
|
private AcDream.App.Streaming.LandblockStreamer? _streamer;
|
||||||
private AcDream.App.Streaming.GpuWorldState _worldState = new();
|
private AcDream.App.Streaming.GpuWorldState _worldState = new();
|
||||||
|
|
@ -969,6 +983,13 @@ public sealed class GameWindow : IDisposable
|
||||||
Path.Combine(shadersDir, "terrain.vert"),
|
Path.Combine(shadersDir, "terrain.vert"),
|
||||||
Path.Combine(shadersDir, "terrain.frag"));
|
Path.Combine(shadersDir, "terrain.frag"));
|
||||||
|
|
||||||
|
// Phase N.5b: terrain_modern shader pair — bindless texture handles +
|
||||||
|
// glMultiDrawElementsIndirect dispatch path. Loaded in parallel with
|
||||||
|
// the legacy `_shader`; Task 9 will retire the legacy program.
|
||||||
|
_terrainModernShader = new Shader(_gl,
|
||||||
|
Path.Combine(shadersDir, "terrain_modern.vert"),
|
||||||
|
Path.Combine(shadersDir, "terrain_modern.frag"));
|
||||||
|
|
||||||
// Phase G.1/G.2: shared scene-lighting UBO. Stays bound at
|
// Phase G.1/G.2: shared scene-lighting UBO. Stays bound at
|
||||||
// binding=1 for the lifetime of the process — every shader that
|
// binding=1 for the lifetime of the process — every shader that
|
||||||
// declares `layout(std140, binding = 1) uniform SceneLighting`
|
// declares `layout(std140, binding = 1) uniform SceneLighting`
|
||||||
|
|
@ -1385,10 +1406,44 @@ public sealed class GameWindow : IDisposable
|
||||||
// TimeSync arrives.
|
// TimeSync arrives.
|
||||||
WorldTime.SyncFromServer(AcDream.Core.World.DerethDateTime.DayTicks / 16.0); // = 476.25 = Midsong (noon)
|
WorldTime.SyncFromServer(AcDream.Core.World.DerethDateTime.DayTicks / 16.0); // = 476.25 = Midsong (noon)
|
||||||
|
|
||||||
// Build the terrain atlas once from the Region dat.
|
// N.5: detect ARB_bindless_texture + ARB_shader_draw_parameters BEFORE
|
||||||
var terrainAtlas = AcDream.App.Rendering.TerrainAtlas.Build(_gl, _dats);
|
// building the terrain atlas / renderer — both consume BindlessSupport
|
||||||
|
// (atlas via Texture2DArray bindless handles, renderer for SSBO uploads).
|
||||||
|
// The modern path (SSBO + glMultiDrawElementsIndirect + bindless textures)
|
||||||
|
// is mandatory as of Phase N.5 — missing extensions throw at startup with
|
||||||
|
// a clear error so users can file a real bug report rather than silently
|
||||||
|
// falling back to a half-working renderer.
|
||||||
|
if (AcDream.App.Rendering.Wb.BindlessSupport.TryCreate(_gl, out var bindless))
|
||||||
|
{
|
||||||
|
if (bindless!.HasShaderDrawParameters(_gl))
|
||||||
|
{
|
||||||
|
_bindlessSupport = bindless;
|
||||||
|
Console.WriteLine("[N.5] modern path capabilities present (bindless + ARB_shader_draw_parameters)");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("[N.5] GL_ARB_shader_draw_parameters not present — modern path not available");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("[N.5] GL_ARB_bindless_texture not present — modern path not available");
|
||||||
|
}
|
||||||
|
|
||||||
_terrain = new TerrainChunkRenderer(_gl, _shader, terrainAtlas);
|
if (_bindlessSupport is null)
|
||||||
|
{
|
||||||
|
throw new NotSupportedException(
|
||||||
|
"acdream requires GL_ARB_bindless_texture + GL_ARB_shader_draw_parameters " +
|
||||||
|
"(GL 4.3+ with bindless support). Your GPU/driver does not expose these extensions. " +
|
||||||
|
"If this is unexpected, please file a bug report with your GPU vendor + driver version.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the terrain atlas once from the Region dat. Phase N.5b: the
|
||||||
|
// atlas exposes bindless handles for the modern terrain path, so
|
||||||
|
// BindlessSupport is threaded through.
|
||||||
|
var terrainAtlas = AcDream.App.Rendering.TerrainAtlas.Build(_gl, _dats, _bindlessSupport);
|
||||||
|
|
||||||
|
_terrain = new TerrainModernRenderer(_gl, _bindlessSupport, _terrainModernShader!, terrainAtlas);
|
||||||
|
|
||||||
int centerX = (int)((centerLandblockId >> 24) & 0xFFu);
|
int centerX = (int)((centerLandblockId >> 24) & 0xFFu);
|
||||||
int centerY = (int)((centerLandblockId >> 16) & 0xFFu);
|
int centerY = (int)((centerLandblockId >> 16) & 0xFFu);
|
||||||
|
|
@ -1418,35 +1473,8 @@ public sealed class GameWindow : IDisposable
|
||||||
_heightTable = heightTable;
|
_heightTable = heightTable;
|
||||||
_surfaceCache = new Dictionary<uint, AcDream.Core.Terrain.SurfaceInfo>();
|
_surfaceCache = new Dictionary<uint, AcDream.Core.Terrain.SurfaceInfo>();
|
||||||
|
|
||||||
// N.5: detect ARB_bindless_texture + ARB_shader_draw_parameters.
|
// (Bindless detection moved above — must precede TerrainAtlas.Build /
|
||||||
// The modern path (SSBO + glMultiDrawElementsIndirect + bindless textures)
|
// TerrainModernRenderer ctor so they can consume BindlessSupport.)
|
||||||
// is mandatory as of Phase N.5 — missing extensions throw at startup with
|
|
||||||
// a clear error so users can file a real bug report rather than silently
|
|
||||||
// falling back to a half-working renderer.
|
|
||||||
if (AcDream.App.Rendering.Wb.BindlessSupport.TryCreate(_gl, out var bindless))
|
|
||||||
{
|
|
||||||
if (bindless!.HasShaderDrawParameters(_gl))
|
|
||||||
{
|
|
||||||
_bindlessSupport = bindless;
|
|
||||||
Console.WriteLine("[N.5] modern path capabilities present (bindless + ARB_shader_draw_parameters)");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine("[N.5] GL_ARB_shader_draw_parameters not present — modern path not available");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Console.WriteLine("[N.5] GL_ARB_bindless_texture not present — modern path not available");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_bindlessSupport is null)
|
|
||||||
{
|
|
||||||
throw new NotSupportedException(
|
|
||||||
"acdream requires GL_ARB_bindless_texture + GL_ARB_shader_draw_parameters " +
|
|
||||||
"(GL 4.3+ with bindless support). Your GPU/driver does not expose these extensions. " +
|
|
||||||
"If this is unexpected, please file a bug report with your GPU vendor + driver version.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mesh shader always loads (modern path is the only path).
|
// Mesh shader always loads (modern path is the only path).
|
||||||
_meshShader = new Shader(_gl,
|
_meshShader = new Shader(_gl,
|
||||||
|
|
@ -6314,7 +6342,15 @@ public sealed class GameWindow : IDisposable
|
||||||
goto SkipWorldGeometry;
|
goto SkipWorldGeometry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase N.5b: wrap Draw in CPU stopwatch for [TERRAIN-DIAG] rollup
|
||||||
|
// (gated on ACDREAM_WB_DIAG=1, same env var as [WB-DIAG]). Stopwatch
|
||||||
|
// is cheap; only the periodic Console.WriteLine is gated.
|
||||||
|
_terrainCpuStopwatch.Restart();
|
||||||
_terrain?.Draw(camera, frustum, neverCullLandblockId: playerLb);
|
_terrain?.Draw(camera, frustum, neverCullLandblockId: playerLb);
|
||||||
|
_terrainCpuStopwatch.Stop();
|
||||||
|
_terrainCpuSamples[_terrainCpuSampleCursor] = (long)(_terrainCpuStopwatch.Elapsed.TotalMicroseconds);
|
||||||
|
_terrainCpuSampleCursor = (_terrainCpuSampleCursor + 1) % _terrainCpuSamples.Length;
|
||||||
|
MaybeFlushTerrainDiag();
|
||||||
|
|
||||||
// Conditional depth clear: when camera is inside a building, clear
|
// Conditional depth clear: when camera is inside a building, clear
|
||||||
// depth (not color) so interior geometry writes fresh Z values on top
|
// depth (not color) so interior geometry writes fresh Z values on top
|
||||||
|
|
@ -8713,6 +8749,51 @@ public sealed class GameWindow : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Phase N.5b: emits [TERRAIN-DIAG] once per ~5s under
|
||||||
|
/// ACDREAM_WB_DIAG=1. Mirrors <c>WbDrawDispatcher.MaybeFlushDiag</c>:
|
||||||
|
/// rolling 256-sample buffer of microseconds, median + p95 reported.
|
||||||
|
/// Sample buffer is NOT cleared on flush — it's a moving window so the
|
||||||
|
/// next 5s window already has 256 frames of recent history.</summary>
|
||||||
|
private void MaybeFlushTerrainDiag()
|
||||||
|
{
|
||||||
|
if (!string.Equals(Environment.GetEnvironmentVariable("ACDREAM_WB_DIAG"), "1", StringComparison.Ordinal))
|
||||||
|
return;
|
||||||
|
|
||||||
|
long now = Environment.TickCount64;
|
||||||
|
if (now - _terrainLastDiagTick <= 5000) return;
|
||||||
|
|
||||||
|
long cpuMedUs = TerrainDiagMedianMicros(_terrainCpuSamples);
|
||||||
|
long cpuP95Us = TerrainDiagPercentile95Micros(_terrainCpuSamples);
|
||||||
|
Console.WriteLine(
|
||||||
|
$"[TERRAIN-DIAG] cpu_ms={cpuMedUs / 1000.0:F2}/{cpuP95Us / 1000.0:F2} " +
|
||||||
|
$"draws={_terrain?.VisibleSlots ?? 0}/frame " +
|
||||||
|
$"visible={_terrain?.VisibleSlots ?? 0} " +
|
||||||
|
$"loaded={_terrain?.LoadedSlots ?? 0} " +
|
||||||
|
$"capacity={_terrain?.CapacitySlots ?? 0}");
|
||||||
|
_terrainLastDiagTick = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long TerrainDiagMedianMicros(long[] samples)
|
||||||
|
{
|
||||||
|
var copy = (long[])samples.Clone();
|
||||||
|
Array.Sort(copy);
|
||||||
|
int nz = 0;
|
||||||
|
foreach (var v in copy) if (v > 0) nz++;
|
||||||
|
if (nz == 0) return 0;
|
||||||
|
return copy[copy.Length - nz / 2];
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long TerrainDiagPercentile95Micros(long[] samples)
|
||||||
|
{
|
||||||
|
var copy = (long[])samples.Clone();
|
||||||
|
Array.Sort(copy);
|
||||||
|
int nz = 0;
|
||||||
|
foreach (var v in copy) if (v > 0) nz++;
|
||||||
|
if (nz == 0) return 0;
|
||||||
|
int idx = copy.Length - 1 - (int)(nz * 0.05);
|
||||||
|
return copy[idx];
|
||||||
|
}
|
||||||
|
|
||||||
private void OnClosing()
|
private void OnClosing()
|
||||||
{
|
{
|
||||||
// Phase A.1: join the streamer worker thread before tearing down GL
|
// Phase A.1: join the streamer worker thread before tearing down GL
|
||||||
|
|
@ -8733,6 +8814,7 @@ public sealed class GameWindow : IDisposable
|
||||||
_meshShader?.Dispose();
|
_meshShader?.Dispose();
|
||||||
_terrain?.Dispose();
|
_terrain?.Dispose();
|
||||||
_shader?.Dispose();
|
_shader?.Dispose();
|
||||||
|
_terrainModernShader?.Dispose();
|
||||||
_sceneLightingUbo?.Dispose();
|
_sceneLightingUbo?.Dispose();
|
||||||
_particleRenderer?.Dispose();
|
_particleRenderer?.Dispose();
|
||||||
_debugLines?.Dispose();
|
_debugLines?.Dispose();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue