#version 430 core #extension GL_ARB_shader_draw_parameters : require layout(location = 0) in vec3 aPosition; layout(location = 1) in vec3 aNormal; layout(location = 2) in vec2 aTexCoord; struct InstanceData { mat4 transform; // Reserved for Phase B.4 follow-up (selection-blink retail-faithful // highlight): vec4 highlightColor; — extend stride here, increase the // _instanceSsbo upload size in WbDrawDispatcher, add a flat varying out, // and consume in mesh_modern.frag. }; struct BatchData { uvec2 textureHandle; // bindless handle for sampler2DArray uint textureLayer; // layer index (always 0 for per-instance composites) uint flags; // reserved — N.5 dispatcher owns all blend state // (glBlendFunc per pass). If a future phase wants // shader-side per-batch additive flag (Decision 2 // fallback), encode it here as bit 0. }; layout(std430, binding = 0) readonly buffer InstanceBuffer { InstanceData Instances[]; }; // binding=1 here is the SSBO namespace — distinct from the UBO namespace. // SceneLighting UBO also uses binding=1 in the fragment shader; GL keeps // GL_SHADER_STORAGE_BUFFER and GL_UNIFORM_BUFFER binding tables separate. // Task 10 dispatcher binds: // glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 0, instanceSsbo) // glBindBufferBase(GL_SHADER_STORAGE_BUFFER, 1, batchSsbo) // Existing SceneLightingUboBinding handles the UBO side. layout(std430, binding = 1) readonly buffer BatchBuffer { BatchData Batches[]; }; uniform mat4 uViewProjection; // Phase Post-A.5 (ISSUE #52, 2026-05-10): per-pass offset into Batches[]. // gl_DrawIDARB resets to 0 at the start of each glMultiDrawElementsIndirect // call, so the transparent pass — which begins later in the indirect buffer // — was fetching Batches[0..transparentCount) instead of its actual section // at Batches[opaqueCount..end). The lifestone crystal (a transparent draw) // ended up reading the FIRST OPAQUE batch's TextureHandle every frame. As // the camera moved and the opaque front-to-back sort reordered which group // landed at BatchData[0], the lifestone's apparent texture flickered to // whatever was first — frequently the player character's body parts. // // WbDrawDispatcher.Draw sets this to 0 before the opaque MDI call and to // _opaqueDrawCount before the transparent MDI call, matching WorldBuilder's // uDrawIDOffset pattern in BaseObjectRenderManager.cs line 845. uniform int uDrawIDOffset; out vec3 vNormal; out vec2 vTexCoord; out vec3 vWorldPos; out flat uvec2 vTextureHandle; out flat uint vTextureLayer; void main() { int instanceIndex = gl_BaseInstanceARB + gl_InstanceID; mat4 model = Instances[instanceIndex].transform; vec4 worldPos = model * vec4(aPosition, 1.0); gl_Position = uViewProjection * worldPos; vWorldPos = worldPos.xyz; vNormal = normalize(mat3(model) * aNormal); vTexCoord = aTexCoord; BatchData b = Batches[uDrawIDOffset + gl_DrawIDARB]; vTextureHandle = b.textureHandle; vTextureLayer = b.textureLayer; }