feat(render): Campaign V slice V2c - particle texture-index migration
Completes Campaign V slice V2 by moving both particle render paths - billboard (particle.vert/.frag) and mesh-emitter (particle_mesh.vert/.frag) - from raw 64-bit ARB_bindless_texture handles to binding=9 handle-table indices, matching V2a's mesh path and V2b's terrain path. Particles differ from both prior sub-slices in HOW the handle reaches the shader: - Billboard particles carry it as a per-INSTANCE vertex attribute (not an SSBO batch or a per-draw uniform), because each particle can use a different texture within one instanced draw. aTextureHandle (location 6, uvec2) became aTextureIndex (uint); particle.vert looks it up via ACDREAM_TEXTURE_HANDLE and reconstructs the SAME uvec2 into vTextureHandle exactly as before, so particle.frag - including its zero-handle check for the procedural circle fallback - needed no change at all. The per-instance ABI struct BillboardGpuInstance shrank by 4 bytes (one uint slot instead of two uint handle halves); ParticleBindlessInstanceTests updated for the new 68-byte layout and the vertex attribute declaration text. - Mesh-emitter particles carry it as a per-draw uniform (uTextureHandle, uvec2) exactly like terrain's pattern from V2b: one texture per draw call, set right before it. Became uTextureIndex (uint) + the same ACDREAM_TEXTURE_HANDLE lookup. ParticleRenderer owns its own GlBindlessHandleTable and binding=9 SSBO, independent of the other three renderers' tables, created eagerly in the constructor alongside the other GL resources it already creates there. Unlike the other three renderers, flushing/binding the table happens immediately before EVERY individual draw call (four call sites: immediate billboard, immediate mesh, and both halves of the deferred/prepared RetailAlphaQueue path) rather than once per pipeline-state switch - a run of consecutive mesh-particle sub-batches can register a new handle partway through (each sub-batch has its own texture), and the table must be current for each one, not just the first. TextureCache's particle-texture cache (AcquireParticleTexture, StandaloneBindlessTextureCache) needed no change: it only ever hands back a raw ulong handle, and both ParticleGfxInfo.TextureHandle and ParticleInstance.TextureHandle keep carrying that raw value - the table lookup is added exactly where each path already converts its handle into GPU-visible state (WriteBillboardGpuInstance and the two ProgramUniform call sites). Coverage caveat (flagged per the campaign doc's slice table): the offline pixel gate's fixed outdoor view has no particles in frame, so it does not exercise this slice - it only confirms nothing else regressed. This change is correspondingly kept strictly mechanical (indirection only, no logic change), but it still needs a user visual check with live particle emitters before being trusted as pixel-identical. Gate: dotnet build -c Release green, dotnet test tests/AcDream.App.Tests -c Release green (3843 passed / 3 skipped on a clean run - one unrelated pre-existing flaky allocation test, UiDatFontTests, failed once and passed on immediate re-run in isolation and in the full suite, confirmed unrelated to this change), and tools/run-offline-pixel-gate.ps1 passed against the V2b commit's build with a 4.62e-05 differing-pixel fraction (a tripwire only, per the coverage caveat above). 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
1f1f6c088b
commit
a85743f70d
4 changed files with 130 additions and 32 deletions
|
|
@ -9,7 +9,10 @@ layout(location = 2) in vec4 aCenter;
|
|||
layout(location = 3) in vec4 aAxisX;
|
||||
layout(location = 4) in vec4 aAxisY;
|
||||
layout(location = 5) in vec4 aColor;
|
||||
layout(location = 6) in uvec2 aTextureHandle;
|
||||
// Campaign V slice V2c (2026-07-27): was uvec2 aTextureHandle (a raw
|
||||
// ARB_bindless_texture handle); now a slot into the binding=9 handle table
|
||||
// (ACDREAM_TEXTURE_HANDLE, common.glsl).
|
||||
layout(location = 6) in uint aTextureIndex;
|
||||
|
||||
uniform mat4 uViewProjection;
|
||||
|
||||
|
|
@ -24,6 +27,8 @@ void main() {
|
|||
|
||||
vTex = aTex;
|
||||
vColor = aColor;
|
||||
vTextureHandle = aTextureHandle;
|
||||
// Reconstruct the SAME uvec2 handle particle.frag used to receive
|
||||
// directly — one binding=9 lookup, identical value downstream.
|
||||
vTextureHandle = ACDREAM_TEXTURE_HANDLE(aTextureIndex);
|
||||
gl_Position = uViewProjection * vec4(world, 1.0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ in vec2 vTexCoord;
|
|||
in vec4 vColor;
|
||||
out vec4 fragColor;
|
||||
|
||||
uniform uvec2 uTextureHandle;
|
||||
// Campaign V slice V2c (2026-07-27): was uvec2 uTextureHandle (a raw
|
||||
// ARB_bindless_texture handle); now a slot into the binding=9 handle table.
|
||||
uniform uint uTextureIndex;
|
||||
uniform int uTextureLayer;
|
||||
|
||||
void main() {
|
||||
sampler2DArray tex = sampler2DArray(uTextureHandle);
|
||||
sampler2DArray tex = sampler2DArray(ACDREAM_TEXTURE_HANDLE(uTextureIndex));
|
||||
vec4 color = texture(tex, vec3(vTexCoord, float(uTextureLayer))) * vColor;
|
||||
if (color.a < 0.02)
|
||||
discard;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue