From 187226f504ddf05f69b23a97f62b505b8dd955f8 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 19 Apr 2026 11:00:34 +0200 Subject: [PATCH] fix(render): shader reserved-word + defensive SkyRenderer dat reads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two runtime blockers discovered after merging the sky/weather/lighting branch: 1. GLSL reserved word: mesh.frag + mesh_instanced.frag used \`int active\` as a local. On GLSL ES / some drivers \`active\` is a reserved identifier and compile fails hard (\"ERROR: 0:38: 'active' : Reserved word\"). Renamed to \`activeLights\`. 2. SkyRenderer.EnsureMeshUploaded called DatCollection.Get without the _datLock that wraps the streaming pipeline's dat reads. DatBinReader has shared buffer state; concurrent reads race and throw ArgumentOutOfRangeException from Vec2Duv.Unpack deep in the mesh parse. Wrapped both Get and GfxObjMesh.Build in try/catch and cache a null entry on failure so we don't retry every frame and crash the render loop. Full fix would plumb _datLock into the sky renderer, left as a TODO. Client now stable end-to-end — in-world, spawn stream flowing, animation + audio + sky + light UBO all live. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.App/Rendering/Shaders/mesh.frag | 4 ++-- .../Rendering/Shaders/mesh_instanced.frag | 4 ++-- src/AcDream.App/Rendering/Sky/SkyRenderer.cs | 22 +++++++++++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/AcDream.App/Rendering/Shaders/mesh.frag b/src/AcDream.App/Rendering/Shaders/mesh.frag index 94cc31f..7765a46 100644 --- a/src/AcDream.App/Rendering/Shaders/mesh.frag +++ b/src/AcDream.App/Rendering/Shaders/mesh.frag @@ -52,9 +52,9 @@ layout(std140, binding = 1) uniform SceneLighting { // light" look relies on crisp boundaries. vec3 accumulateLights(vec3 N, vec3 worldPos) { vec3 lit = uCellAmbient.xyz; - int active = int(uCellAmbient.w); + int activeLights = int(uCellAmbient.w); for (int i = 0; i < 8; ++i) { - if (i >= active) break; + if (i >= activeLights) break; int kind = int(uLights[i].posAndKind.w); vec3 Lcol = uLights[i].colorAndIntensity.xyz * uLights[i].colorAndIntensity.w; diff --git a/src/AcDream.App/Rendering/Shaders/mesh_instanced.frag b/src/AcDream.App/Rendering/Shaders/mesh_instanced.frag index 6199aa4..1719e2f 100644 --- a/src/AcDream.App/Rendering/Shaders/mesh_instanced.frag +++ b/src/AcDream.App/Rendering/Shaders/mesh_instanced.frag @@ -35,9 +35,9 @@ layout(std140, binding = 1) uniform SceneLighting { vec3 accumulateLights(vec3 N, vec3 worldPos) { vec3 lit = uCellAmbient.xyz; - int active = int(uCellAmbient.w); + int activeLights = int(uCellAmbient.w); for (int i = 0; i < 8; ++i) { - if (i >= active) break; + if (i >= activeLights) break; int kind = int(uLights[i].posAndKind.w); vec3 Lcol = uLights[i].colorAndIntensity.xyz * uLights[i].colorAndIntensity.w; diff --git a/src/AcDream.App/Rendering/Sky/SkyRenderer.cs b/src/AcDream.App/Rendering/Sky/SkyRenderer.cs index ba7a58b..34104d4 100644 --- a/src/AcDream.App/Rendering/Sky/SkyRenderer.cs +++ b/src/AcDream.App/Rendering/Sky/SkyRenderer.cs @@ -217,14 +217,32 @@ public sealed unsafe class SkyRenderer : IDisposable { if (_gpuByGfxObj.ContainsKey(gfxObjId)) return; - var gfx = _dats.Get(gfxObjId); + // DatCollection isn't thread-safe and the streaming loader can be + // actively reading a shared DatBinReader buffer; sky meshes are + // loaded on the render thread but GfxObj.Unpack can race with the + // streamer. Cache a null entry on any read failure so we don't + // retry every frame and crash the render loop. A future + // refactor should move all dat access behind the _datLock. + GfxObj? gfx = null; + try { gfx = _dats.Get(gfxObjId); } + catch { gfx = null; } + if (gfx is null) { _gpuByGfxObj[gfxObjId] = new List(); return; } - var subMeshes = GfxObjMesh.Build(gfx, _dats); + System.Collections.Generic.IReadOnlyList? subMeshes = null; + try { subMeshes = GfxObjMesh.Build(gfx, _dats); } + catch { subMeshes = null; } + + if (subMeshes is null) + { + _gpuByGfxObj[gfxObjId] = new List(); + return; + } + var gpuList = new List(subMeshes.Count); foreach (var sm in subMeshes) gpuList.Add(UploadSubMesh(sm));