fix(render): skip empty groups in instanced draw to prevent crash on Tab
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6a55838a10
commit
787e0f0aff
2 changed files with 98 additions and 0 deletions
96
docs/plans/2026-04-13-rendering-rebuild.md
Normal file
96
docs/plans/2026-04-13-rendering-rebuild.md
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# Rendering Rebuild from ACME
|
||||
|
||||
Port ACME's rendering pipeline to acdream. Each step produces a
|
||||
visually testable result. The animation system stays unchanged (ACME
|
||||
has none — ours is ported from the decompiled client).
|
||||
|
||||
## Step 1: Port StaticObject shader + instanced rendering
|
||||
|
||||
The biggest performance win. Replace per-entity DrawElements with
|
||||
instanced rendering using a shared instance VBO.
|
||||
|
||||
**From ACME:**
|
||||
- StaticObject.vert: aInstanceMatrix (mat4 at locations 3-6, divisor=1)
|
||||
- StaticObject.frag: sampler2DArray + alpha cutout
|
||||
- Instance buffer pattern: single float[] upload per frame
|
||||
|
||||
**Changes:**
|
||||
- New shader: mesh_instanced.vert/frag (from ACME StaticObject.vert/frag)
|
||||
- Rewrite StaticMeshRenderer to use instance buffer pattern
|
||||
- Group entities by (GfxObjId, textureAtlas) → one DrawElementsInstanced per group
|
||||
- Per-GfxObj TextureAtlasManager (grow-on-demand, starts at 32 slots)
|
||||
- ushort indices (not uint32) for objects
|
||||
|
||||
**Test:** Same visual output, fewer draw calls (check perf overlay).
|
||||
|
||||
## Step 2: Port Landscape shader + terrain chunks
|
||||
|
||||
Replace per-landblock terrain draws with chunk batching.
|
||||
|
||||
**From ACME:**
|
||||
- Landscape.vert/frag: 8 packed uvec4 attributes, sampler2DArray terrain + alpha
|
||||
- TerrainChunk: N×N landblocks baked into one VBO/IBO
|
||||
- TerrainGPUResourceManager: buffer creation + partial updates
|
||||
|
||||
**Changes:**
|
||||
- New shader: terrain_acme.vert/frag (from ACME Landscape.vert/frag)
|
||||
- New TerrainChunkRenderer (replaces TerrainRenderer)
|
||||
- LandblockMesh.Build outputs VertexLandscape-compatible structs
|
||||
- Single DrawElements per chunk (multiple landblocks)
|
||||
|
||||
**Test:** Same terrain appearance, one draw call per chunk.
|
||||
|
||||
## Step 3: Port AdjustPlanes lighting
|
||||
|
||||
Replace guessed sun direction with decompiled retail values.
|
||||
|
||||
**From decompiled:**
|
||||
- FUN_00532440 (AdjustPlanes): face-normal accumulation + per-vertex lighting
|
||||
- DAT constants: sun direction, ambient, diffuse
|
||||
|
||||
**Changes:**
|
||||
- LandblockMesh: face-normal accumulation (replaces central differences)
|
||||
- Shader uniforms: xLightDirection, xAmbient from decompiled constants
|
||||
- Static objects: same lighting model
|
||||
|
||||
**Test:** Side-by-side with retail client shows matching lighting.
|
||||
|
||||
## Step 4: Port EnvCell portal visibility
|
||||
|
||||
Render only visible interior cells.
|
||||
|
||||
**From ACME:**
|
||||
- EnvCellManager: portal visibility BFS from camera cell
|
||||
- Portal occluder pass: depth-only draw of portal polygons
|
||||
- Conditional depth clear when camera is inside a cell
|
||||
|
||||
**Changes:**
|
||||
- New CellVisibility system (BFS through CellPortals)
|
||||
- Portal occluder depth pass before EnvCell geometry
|
||||
- Conditional depth clear in render order
|
||||
|
||||
**Test:** Enter a building — only visible rooms render.
|
||||
|
||||
## Step 5: Wire animation into instanced pipeline
|
||||
|
||||
The AnimationSequencer outputs per-part transforms. These need to flow
|
||||
into the instance buffer alongside static transforms.
|
||||
|
||||
**Changes:**
|
||||
- Animated entities write their per-part instance matrices into the
|
||||
shared instance buffer every frame (same buffer as static objects)
|
||||
- AnimationSequencer.Advance(dt) is called BEFORE the instance buffer
|
||||
upload so the latest frame's transforms are included
|
||||
|
||||
**Test:** NPCs breathe, player walks — all through the instanced pipeline.
|
||||
|
||||
## Render Order (target)
|
||||
|
||||
```
|
||||
1. Terrain (one DrawElements per chunk, PolygonOffset on)
|
||||
2. Conditional depth clear (if camera inside EnvCell)
|
||||
3. EnvCell geometry (DrawElementsInstanced, portal visibility culled)
|
||||
4. Static objects opaque (DrawElementsInstanced, alpha cutout)
|
||||
5. Static objects translucent (DrawElementsInstanced, blend on)
|
||||
6. Particles (additive blend — future)
|
||||
```
|
||||
|
|
@ -227,6 +227,7 @@ public sealed unsafe class InstancedMeshRenderer : IDisposable
|
|||
// group share the same GfxObj so they have compatible overrides
|
||||
// only in the degenerate case of mixed-palette entities using the
|
||||
// same GfxObj — rare enough to accept the approximation here).
|
||||
if (grp.Count == 0) continue;
|
||||
var firstEntry = grp.Entries[0];
|
||||
uint tex = ResolveTex(firstEntry.Entity, firstEntry.MeshRef, sub);
|
||||
_gl.ActiveTexture(TextureUnit.Texture0);
|
||||
|
|
@ -295,6 +296,7 @@ public sealed unsafe class InstancedMeshRenderer : IDisposable
|
|||
false, 64, (void*)(byteOffset + row * 16));
|
||||
}
|
||||
|
||||
if (grp.Count == 0) continue;
|
||||
var firstEntry = grp.Entries[0];
|
||||
uint tex = ResolveTex(firstEntry.Entity, firstEntry.MeshRef, sub);
|
||||
_gl.ActiveTexture(TextureUnit.Texture0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue