feat(render): Campaign V slice V2b - terrain texture-index migration
Continues the V2a mesh-path conversion onto TerrainModernRenderer: its two per-pass bindless texture handles (the terrain atlas and the alpha-mask atlas) now travel as table indices instead of raw 64-bit ARB_bindless_texture handles, with zero pixel change. Terrain differs structurally from the mesh path: it has no per-batch SSBO at all, just two handles set once per draw as plain uniforms (terrain_modern.frag's uTerrainHandle/uAlphaHandle, reconstructed via the sampler2DArray(handle) macros uTerrain/uAlpha). So instead of a BatchData struct field, the two uniforms became uTextureIndexA/uTextureIndexB - named to match the pinned GpuPushConstants.TextureIndexA/B fields (campaign doc section 3.4) so V4d's eventual move to push constants is a rename, not a redesign. There is no push-constant plumbing yet, so these stay plain uniforms for now, set via ProgramUniform1 instead of ProgramUniform2. TerrainModernRenderer owns its own GlBindlessHandleTable and binding=9 SSBO (the same GL-only handle-table emulation V2a introduced), independent of WbDrawDispatcher's and EnvCellRenderer's - nothing requires index agreement between renderers, and terrain only ever registers two handles per draw (the atlas's terrain/alpha textures), so its table is dirty only once, on first draw. Unlike WbDrawDispatcher/EnvCellRenderer, TerrainModernRenderer already eagerly creates its other GL resources in the constructor with a ResourceCleanupGroup rollback, so the texture-table SSBO is created there too rather than lazily. TerrainAtlas needed no change: GetBindlessHandles() keeps returning the raw (ulong terrain, ulong alpha) pair unchanged - the table lookup is entirely a TerrainModernRenderer-side concern, added at the one draw-call site that already converts those handles into shader state. Shader-side: terrain_modern.frag's uTerrain/uAlpha macros now expand through common.glsl's ACDREAM_TEXTURE_HANDLE(idx) lookup; both terrain_modern.vert and .frag opted into the common.glsl preamble (Shader's includeCommonPreamble, introduced at V2a) so their SceneLighting UBO declarations could also pick up the ACDREAM_UBO_SET scaffolding macro - terrain_modern.vert doesn't touch the texture table itself, but sharing the same preamble across both stages of a technique is simpler to reason about than deciding per-stage. Gate: dotnet build -c Release green, dotnet test tests/AcDream.App.Tests -c Release green (3843 passed / 3 skipped, matching V2a), and tools/run-offline-pixel-gate.ps1 passed against the V2a commit's build with a 2.49e-05 differing-pixel fraction - within the documented ~33x same-commit noise margin. No divergence-register row: this introduces no retail behavior deviation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
d365476ebb
commit
1f1f6c088b
4 changed files with 106 additions and 19 deletions
|
|
@ -209,7 +209,8 @@ internal sealed class RetailWorldRenderCompositionFactory
|
|||
new(
|
||||
gl,
|
||||
Path.Combine(shadersDirectory, "terrain_modern.vert"),
|
||||
Path.Combine(shadersDirectory, "terrain_modern.frag"));
|
||||
Path.Combine(shadersDirectory, "terrain_modern.frag"),
|
||||
includeCommonPreamble: true);
|
||||
|
||||
public SceneLightingUboBinding CreateSceneLighting(GL gl) => new(gl);
|
||||
|
||||
|
|
|
|||
|
|
@ -27,11 +27,17 @@ flat in float vBaseTexIdx;
|
|||
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform uvec2 uTerrainHandle;
|
||||
uniform uvec2 uAlphaHandle;
|
||||
// Campaign V slice V2b (2026-07-27): uTerrainHandle/uAlphaHandle (uvec2, raw
|
||||
// ARB_bindless_texture handles) became uTextureIndexA/uTextureIndexB (slots
|
||||
// into the binding=9 handle table, ACDREAM_TEXTURE_HANDLE in common.glsl).
|
||||
// Named to match the pinned GpuPushConstants.TextureIndexA/B fields so V4d's
|
||||
// move to push constants is a rename, not a redesign — there is no
|
||||
// push-constant plumbing yet, so these stay plain uniforms for now.
|
||||
uniform uint uTextureIndexA;
|
||||
uniform uint uTextureIndexB;
|
||||
uniform float uTexTiling[36];
|
||||
#define uTerrain sampler2DArray(uTerrainHandle)
|
||||
#define uAlpha sampler2DArray(uAlphaHandle)
|
||||
#define uTerrain sampler2DArray(ACDREAM_TEXTURE_HANDLE(uTextureIndexA))
|
||||
#define uAlpha sampler2DArray(ACDREAM_TEXTURE_HANDLE(uTextureIndexB))
|
||||
|
||||
struct Light {
|
||||
vec4 posAndKind;
|
||||
|
|
@ -39,7 +45,7 @@ struct Light {
|
|||
vec4 colorAndIntensity;
|
||||
vec4 coneAngleEtc;
|
||||
};
|
||||
layout(std140, binding = 1) uniform SceneLighting {
|
||||
layout(std140, ACDREAM_UBO_SET binding = 1) uniform SceneLighting {
|
||||
Light uLights[8];
|
||||
vec4 uCellAmbient;
|
||||
vec4 uFogParams;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ struct Light {
|
|||
vec4 colorAndIntensity;
|
||||
vec4 coneAngleEtc;
|
||||
};
|
||||
layout(std140, binding = 1) uniform SceneLighting {
|
||||
layout(std140, ACDREAM_UBO_SET binding = 1) uniform SceneLighting {
|
||||
Light uLights[8];
|
||||
vec4 uCellAmbient;
|
||||
vec4 uFogParams;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.Core.Terrain;
|
||||
using Silk.NET.OpenGL;
|
||||
|
|
@ -81,12 +82,22 @@ public sealed unsafe class TerrainModernRenderer : IDisposable
|
|||
private TerrainClipBufferBinding _sharedClipBinding;
|
||||
private uint _fallbackClipUbo;
|
||||
|
||||
// Cached uvec2-handle uniform locations (matrix uniforms are set by name via Shader.SetMatrix4).
|
||||
private int _uTerrainHandleLoc;
|
||||
private int _uAlphaHandleLoc;
|
||||
// Campaign V slice V2b (2026-07-27): uTerrainHandle/uAlphaHandle (uvec2)
|
||||
// became uTextureIndexA/uTextureIndexB (uint table slots) — cached
|
||||
// uniform locations (matrix uniforms are set by name via Shader.SetMatrix4).
|
||||
private int _uTextureIndexALoc;
|
||||
private int _uTextureIndexBLoc;
|
||||
private int _uTexTilingLoc;
|
||||
private bool _textureTilingUploaded;
|
||||
|
||||
// GL-only emulation of the eventual Vulkan global texture descriptor array
|
||||
// (binding=9, GpuBindingModel.StorageTextureTable). Owns its own table —
|
||||
// see GlBindlessHandleTable's doc comment and the campaign doc's §5.2 for
|
||||
// why terrain doesn't share WbDrawDispatcher's/EnvCellRenderer's tables.
|
||||
private readonly GlBindlessHandleTable _textureTable = new();
|
||||
private uint _textureTableSsbo;
|
||||
private int _textureTableSsboCapacityBytes;
|
||||
|
||||
// Reusable per-frame buffers.
|
||||
private readonly List<int> _visibleSlots = new();
|
||||
private readonly HashSet<uint> _visibleCellIds = new();
|
||||
|
|
@ -138,8 +149,8 @@ public sealed unsafe class TerrainModernRenderer : IDisposable
|
|||
_alloc = new GpuRetiredTerrainSlotAllocator(initialSlotCapacity, resourceRetirement);
|
||||
_slots = new SlotData?[initialSlotCapacity];
|
||||
|
||||
_uTerrainHandleLoc = _gl.GetUniformLocation(_shader.Program, "uTerrainHandle");
|
||||
_uAlphaHandleLoc = _gl.GetUniformLocation(_shader.Program, "uAlphaHandle");
|
||||
_uTextureIndexALoc = _gl.GetUniformLocation(_shader.Program, "uTextureIndexA");
|
||||
_uTextureIndexBLoc = _gl.GetUniformLocation(_shader.Program, "uTextureIndexB");
|
||||
_uTexTilingLoc = _gl.GetUniformLocation(_shader.Program, "uTexTiling[0]");
|
||||
if (_uTexTilingLoc < 0)
|
||||
throw new InvalidOperationException("terrain_modern.frag is missing the required uTexTiling uniform.");
|
||||
|
|
@ -147,6 +158,21 @@ public sealed unsafe class TerrainModernRenderer : IDisposable
|
|||
var constructionResources = new ResourceCleanupGroup();
|
||||
try
|
||||
{
|
||||
// Campaign V slice V2b: binding=9 texture-table SSBO (GL-only
|
||||
// emulation of the eventual Vulkan descriptor array).
|
||||
_textureTableSsbo = TrackedGlResource.CreateBuffer(
|
||||
_gl,
|
||||
"creating terrain texture-table SSBO");
|
||||
RetryableGpuResourceRelease textureTableRelease =
|
||||
TrackedGlResource.CreateRetryableBufferDeletion(
|
||||
_gl,
|
||||
_textureTableSsbo,
|
||||
() => _textureTableSsboCapacityBytes,
|
||||
"rolling back terrain texture-table SSBO");
|
||||
constructionResources.Add(
|
||||
"terrain texture-table SSBO",
|
||||
textureTableRelease.Run);
|
||||
|
||||
_globalVao = TrackedGlResource.CreateVertexArray(
|
||||
_gl,
|
||||
"creating terrain global VAO");
|
||||
|
|
@ -479,13 +505,15 @@ public sealed unsafe class TerrainModernRenderer : IDisposable
|
|||
_shader.SetMatrix4("uProjection", camera.Projection);
|
||||
|
||||
var (terrainHandle, alphaHandle) = _atlas.GetBindlessHandles();
|
||||
// Pass each 64-bit handle as a uvec2 (low 32 bits, high 32 bits).
|
||||
// GLSL constructs sampler2DArray(uTerrainHandle) at the use site —
|
||||
// see terrain_modern.frag for why this is the safe pattern.
|
||||
_gl.ProgramUniform2(_shader.Program, _uTerrainHandleLoc,
|
||||
(uint)(terrainHandle & 0xFFFFFFFFu), (uint)(terrainHandle >> 32));
|
||||
_gl.ProgramUniform2(_shader.Program, _uAlphaHandleLoc,
|
||||
(uint)(alphaHandle & 0xFFFFFFFFu), (uint)(alphaHandle >> 32));
|
||||
// Campaign V slice V2b: pass each handle's binding=9 table slot
|
||||
// instead of the raw uvec2 handle. GLSL reconstructs
|
||||
// sampler2DArray(ACDREAM_TEXTURE_HANDLE(uTextureIndexA)) at the use
|
||||
// site — see terrain_modern.frag.
|
||||
uint terrainSlot = _textureTable.GetOrAdd(terrainHandle);
|
||||
uint alphaSlot = _textureTable.GetOrAdd(alphaHandle);
|
||||
FlushAndBindTextureTable();
|
||||
_gl.ProgramUniform1(_shader.Program, _uTextureIndexALoc, terrainSlot);
|
||||
_gl.ProgramUniform1(_shader.Program, _uTextureIndexBLoc, alphaSlot);
|
||||
|
||||
// Phase U.3: bind the terrain clip UBO (binding=2). Shared ClipFrame UBO
|
||||
// when wired, else the no-clip fallback (count 0 = ungated terrain).
|
||||
|
|
@ -589,6 +617,16 @@ public sealed unsafe class TerrainModernRenderer : IDisposable
|
|||
"deleting terrain fallback clip UBO");
|
||||
releases.Add(("fallback-clip-ubo", release.Run));
|
||||
}
|
||||
if (_textureTableSsbo != 0)
|
||||
{
|
||||
RetryableGpuResourceRelease release =
|
||||
TrackedGlResource.CreateRetryableBufferDeletion(
|
||||
_gl,
|
||||
_textureTableSsbo,
|
||||
_textureTableSsboCapacityBytes,
|
||||
"deleting terrain texture-table SSBO");
|
||||
releases.Add(("texture-table", release.Run));
|
||||
}
|
||||
_disposeResources = new RetryableResourceReleaseLedger(releases);
|
||||
}
|
||||
|
||||
|
|
@ -610,6 +648,8 @@ public sealed unsafe class TerrainModernRenderer : IDisposable
|
|||
_indirectCapacity = 0;
|
||||
_dynamicFrameStarted = false;
|
||||
_fallbackClipUbo = 0;
|
||||
_textureTableSsbo = 0;
|
||||
_textureTableSsboCapacityBytes = 0;
|
||||
_disposeResources = null;
|
||||
_disposed = true;
|
||||
|
||||
|
|
@ -652,6 +692,46 @@ public sealed unsafe class TerrainModernRenderer : IDisposable
|
|||
_textureTilingUploaded = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign V slice V2b: uploads <see cref="_textureTable"/>'s handles to
|
||||
/// <see cref="_textureTableSsbo"/> when a new one was registered since the
|
||||
/// last flush, then (re)binds it at
|
||||
/// <see cref="GpuBindingModel.StorageTextureTable"/>. Terrain registers at
|
||||
/// most two handles per draw (the terrain and alpha atlases), so this is
|
||||
/// dirty only on the atlas's first draw and stays clean afterward.
|
||||
/// </summary>
|
||||
private unsafe void FlushAndBindTextureTable()
|
||||
{
|
||||
if (_textureTable.Dirty)
|
||||
{
|
||||
ReadOnlySpan<ulong> handles = _textureTable.Handles;
|
||||
int byteCount = handles.Length * sizeof(ulong);
|
||||
fixed (ulong* p = handles)
|
||||
{
|
||||
if (_textureTableSsboCapacityBytes < byteCount)
|
||||
{
|
||||
int grown = DynamicBufferCapacity.Grow(_textureTableSsboCapacityBytes, byteCount);
|
||||
TrackedGlResource.AllocateBufferStorage(
|
||||
_gl,
|
||||
GLEnum.ShaderStorageBuffer,
|
||||
_textureTableSsbo,
|
||||
_textureTableSsboCapacityBytes,
|
||||
grown,
|
||||
GLEnum.DynamicDraw,
|
||||
"growing terrain texture-table SSBO");
|
||||
_textureTableSsboCapacityBytes = grown;
|
||||
}
|
||||
_gl.BindBuffer(GLEnum.ShaderStorageBuffer, _textureTableSsbo);
|
||||
_gl.BufferSubData(GLEnum.ShaderStorageBuffer, 0, (nuint)byteCount, p);
|
||||
}
|
||||
_textureTable.MarkFlushed();
|
||||
}
|
||||
_gl.BindBufferBase(
|
||||
GLEnum.ShaderStorageBuffer,
|
||||
GpuBindingModel.StorageTextureTable,
|
||||
_textureTableSsbo);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phase U.3: bind the terrain clip UBO to binding=2. Prefers the shared
|
||||
/// <see cref="ClipFrame"/> UBO range (<see cref="SetClipUbo"/>); otherwise lazily
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue