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 GL? _gl;
|
||||
private IInputContext? _input;
|
||||
private TerrainChunkRenderer? _terrain;
|
||||
private TerrainModernRenderer? _terrain;
|
||||
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 IMouse? _capturedMouse;
|
||||
private DatCollection? _dats;
|
||||
|
|
@ -68,6 +73,15 @@ public sealed class GameWindow : IDisposable
|
|||
private string _lastNearestObjLabel = "-";
|
||||
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.
|
||||
private AcDream.App.Streaming.LandblockStreamer? _streamer;
|
||||
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.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
|
||||
// binding=1 for the lifetime of the process — every shader that
|
||||
// declares `layout(std140, binding = 1) uniform SceneLighting`
|
||||
|
|
@ -1385,10 +1406,44 @@ public sealed class GameWindow : IDisposable
|
|||
// TimeSync arrives.
|
||||
WorldTime.SyncFromServer(AcDream.Core.World.DerethDateTime.DayTicks / 16.0); // = 476.25 = Midsong (noon)
|
||||
|
||||
// Build the terrain atlas once from the Region dat.
|
||||
var terrainAtlas = AcDream.App.Rendering.TerrainAtlas.Build(_gl, _dats);
|
||||
// N.5: detect ARB_bindless_texture + ARB_shader_draw_parameters BEFORE
|
||||
// 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 centerY = (int)((centerLandblockId >> 16) & 0xFFu);
|
||||
|
|
@ -1418,35 +1473,8 @@ public sealed class GameWindow : IDisposable
|
|||
_heightTable = heightTable;
|
||||
_surfaceCache = new Dictionary<uint, AcDream.Core.Terrain.SurfaceInfo>();
|
||||
|
||||
// N.5: detect ARB_bindless_texture + ARB_shader_draw_parameters.
|
||||
// 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");
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
// (Bindless detection moved above — must precede TerrainAtlas.Build /
|
||||
// TerrainModernRenderer ctor so they can consume BindlessSupport.)
|
||||
|
||||
// Mesh shader always loads (modern path is the only path).
|
||||
_meshShader = new Shader(_gl,
|
||||
|
|
@ -6314,7 +6342,15 @@ public sealed class GameWindow : IDisposable
|
|||
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);
|
||||
_terrainCpuStopwatch.Stop();
|
||||
_terrainCpuSamples[_terrainCpuSampleCursor] = (long)(_terrainCpuStopwatch.Elapsed.TotalMicroseconds);
|
||||
_terrainCpuSampleCursor = (_terrainCpuSampleCursor + 1) % _terrainCpuSamples.Length;
|
||||
MaybeFlushTerrainDiag();
|
||||
|
||||
// Conditional depth clear: when camera is inside a building, clear
|
||||
// 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()
|
||||
{
|
||||
// Phase A.1: join the streamer worker thread before tearing down GL
|
||||
|
|
@ -8733,6 +8814,7 @@ public sealed class GameWindow : IDisposable
|
|||
_meshShader?.Dispose();
|
||||
_terrain?.Dispose();
|
||||
_shader?.Dispose();
|
||||
_terrainModernShader?.Dispose();
|
||||
_sceneLightingUbo?.Dispose();
|
||||
_particleRenderer?.Dispose();
|
||||
_debugLines?.Dispose();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue