phase(N.5) Task 8: InstanceGroup + GroupKey carry bindless handle + layer

Replaces uint TextureHandle (32-bit GL name) with ulong
BindlessTextureHandle (64-bit) in InstanceGroup + GroupKey + ResolveTexture
return type. Adds TextureLayer (always 0 for per-instance composites,
becomes meaningful when WB atlas is adopted in N.6).

ClassifyBatches now calls TextureCache.GetOrUpload*Bindless variants —
these return Texture2DArray-backed bindless handles (Task 3 work).

DrawGroup body throws NotImplementedException — Task 10 rewrites the
whole Draw() method to use glMultiDrawElementsIndirect, which makes
DrawGroup obsolete. CPU-only tests don't invoke DrawGroup so the build
+ test gates stay green; visual launch fails until Task 10 (intentional).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-08 20:32:38 +02:00
parent 1b6995d2df
commit 424d7b9015

View file

@ -398,21 +398,10 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
private void DrawGroup(InstanceGroup grp) private void DrawGroup(InstanceGroup grp)
{ {
_gl.ActiveTexture(TextureUnit.Texture0); throw new NotImplementedException(
_gl.BindTexture(TextureTarget.Texture2D, grp.TextureHandle); "DrawGroup is being removed in Task 10 — the dispatcher rewrites Draw() " +
_gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, grp.Ibo); "to use glMultiDrawElementsIndirect instead of per-group draws. " +
"If this throws at runtime, Task 10 hasn't landed yet.");
// BaseInstance offsets the per-instance attribute fetches into our
// shared instance VBO so each group reads its own slice. Requires
// GL_ARB_base_instance (GL 4.2+); WB requires 4.3 so this is available.
_gl.DrawElementsInstancedBaseVertexBaseInstance(
PrimitiveType.Triangles,
(uint)grp.IndexCount,
DrawElementsType.UnsignedShort,
(void*)(grp.FirstIndex * sizeof(ushort)),
(uint)grp.InstanceCount,
grp.BaseVertex,
(uint)grp.FirstInstance);
} }
private void MaybeFlushDiag() private void MaybeFlushDiag()
@ -452,12 +441,16 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
: TranslucencyKind.Opaque; : TranslucencyKind.Opaque;
} }
uint texHandle = ResolveTexture(entity, meshRef, batch, palHash); ulong texHandle = ResolveTexture(entity, meshRef, batch, palHash);
if (texHandle == 0) continue; if (texHandle == 0) continue;
// TextureLayer is always 0 for per-instance composites; non-zero when
// WB atlas is adopted in N.6+ and batches reference a shared atlas layer.
uint texLayer = 0;
var key = new GroupKey( var key = new GroupKey(
batch.IBO, batch.FirstIndex, (int)batch.BaseVertex, batch.IBO, batch.FirstIndex, (int)batch.BaseVertex,
batch.IndexCount, texHandle, translucency); batch.IndexCount, texHandle, texLayer, translucency);
if (!_groups.TryGetValue(key, out var grp)) if (!_groups.TryGetValue(key, out var grp))
{ {
@ -467,7 +460,8 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
FirstIndex = batch.FirstIndex, FirstIndex = batch.FirstIndex,
BaseVertex = (int)batch.BaseVertex, BaseVertex = (int)batch.BaseVertex,
IndexCount = batch.IndexCount, IndexCount = batch.IndexCount,
TextureHandle = texHandle, BindlessTextureHandle = texHandle,
TextureLayer = texLayer,
Translucency = translucency, Translucency = translucency,
}; };
_groups[key] = grp; _groups[key] = grp;
@ -476,10 +470,8 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
} }
} }
private uint ResolveTexture(WorldEntity entity, MeshRef meshRef, ObjectRenderBatch batch, ulong palHash) private ulong ResolveTexture(WorldEntity entity, MeshRef meshRef, ObjectRenderBatch batch, ulong palHash)
{ {
// WB stores the surface id on batch.Key.SurfaceId (TextureKey struct);
// batch.SurfaceId is unset (zero) for batches built by ObjectMeshManager.
uint surfaceId = batch.Key.SurfaceId; uint surfaceId = batch.Key.SurfaceId;
if (surfaceId == 0 || surfaceId == 0xFFFFFFFF) return 0; if (surfaceId == 0 || surfaceId == 0xFFFFFFFF) return 0;
@ -490,19 +482,16 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
if (entity.PaletteOverride is not null) if (entity.PaletteOverride is not null)
{ {
// perf #4: pass the entity-precomputed palette hash so TextureCache return _textures.GetOrUploadWithPaletteOverrideBindless(
// can skip its internal HashPaletteOverride for repeat lookups
// within the same character.
return _textures.GetOrUploadWithPaletteOverride(
surfaceId, origTexOverride, entity.PaletteOverride, palHash); surfaceId, origTexOverride, entity.PaletteOverride, palHash);
} }
else if (hasOrigTexOverride) else if (hasOrigTexOverride)
{ {
return _textures.GetOrUploadWithOrigTextureOverride(surfaceId, overrideOrigTex); return _textures.GetOrUploadWithOrigTextureOverrideBindless(surfaceId, overrideOrigTex);
} }
else else
{ {
return _textures.GetOrUpload(surfaceId); return _textures.GetOrUploadBindless(surfaceId);
} }
} }
@ -545,7 +534,8 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
uint FirstIndex, uint FirstIndex,
int BaseVertex, int BaseVertex,
int IndexCount, int IndexCount,
uint TextureHandle, ulong BindlessTextureHandle,
uint TextureLayer,
TranslucencyKind Translucency); TranslucencyKind Translucency);
private sealed class InstanceGroup private sealed class InstanceGroup
@ -554,7 +544,8 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
public uint FirstIndex; public uint FirstIndex;
public int BaseVertex; public int BaseVertex;
public int IndexCount; public int IndexCount;
public uint TextureHandle; public ulong BindlessTextureHandle; // 64-bit (was uint TextureHandle in N.4)
public uint TextureLayer; // 0 for per-instance composites; non-zero when WB atlas is adopted in N.6+
public TranslucencyKind Translucency; public TranslucencyKind Translucency;
public int FirstInstance; // offset into the shared instance VBO (in instances, not bytes) public int FirstInstance; // offset into the shared instance VBO (in instances, not bytes)
public int InstanceCount; public int InstanceCount;