fix(selection): port SmartBox click lighting pulse
Port the retail high/low material-lighting cadence for successful world clicks, keep it instance-scoped in the modern renderer, and restore authored lighting after 0.8 seconds. Correct the selection oracle and pin timing plus per-frame buffer lifecycle with tests. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
146a963aeb
commit
ea4f52ec51
11 changed files with 338 additions and 16 deletions
|
|
@ -87,6 +87,7 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
private readonly WbMeshAdapter _meshAdapter;
|
||||
private readonly EntitySpawnAdapter _entitySpawnAdapter;
|
||||
private readonly IRetailSelectionRenderSink? _selectionSink;
|
||||
private readonly IRetailSelectionLightingSource? _selectionLighting;
|
||||
|
||||
private readonly BindlessSupport _bindless;
|
||||
|
||||
|
|
@ -165,6 +166,10 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
// a clone of _indoorData / _instIndoorSsbo, one binding higher.
|
||||
private uint _instAlphaSsbo;
|
||||
private float[] _alphaData = new float[256];
|
||||
// Retail SmartBox click confirmation: per-instance CMaterial luminosity /
|
||||
// diffuse replacement (binding=8), parallel to the transform buffer.
|
||||
private uint _instSelectionLightingSsbo;
|
||||
private Vector2[] _selectionLightingData = new Vector2[256];
|
||||
// This frame's point-light snapshot, handed in by GameWindow before Draw via
|
||||
// SetSceneLights. Null/empty ⇒ only ambient + sun render (all instance sets -1).
|
||||
private IReadOnlyList<LightSource>? _pointSnapshot;
|
||||
|
|
@ -178,6 +183,7 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
// (ParentCellId is an EnvCell). Appended to InstanceGroup.IndoorFlags in
|
||||
// AppendCurrentLightSet; uploaded as binding=6 instanceIndoor[].
|
||||
private bool _currentEntityIndoor;
|
||||
private Vector2 _currentEntitySelectionLighting = new(0f, 1f);
|
||||
|
||||
// Phase U.3: the SHARED per-cell clip-region SSBO (binding=2), owned by the
|
||||
// GameWindow-level ClipFrame and handed to us via SetClipRegionSsbo. When 0
|
||||
|
|
@ -375,6 +381,7 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
_cache = classificationCache;
|
||||
_translucencyFades = translucencyFades;
|
||||
_selectionSink = selectionSink;
|
||||
_selectionLighting = selectionSink as IRetailSelectionLightingSource;
|
||||
|
||||
_bindless = bindless ?? throw new ArgumentNullException(nameof(bindless));
|
||||
_instanceSsbo = _gl.GenBuffer();
|
||||
|
|
@ -385,6 +392,7 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
_instLightSetSsbo = _gl.GenBuffer(); // Fix B binding=5
|
||||
_instIndoorSsbo = _gl.GenBuffer(); // #142 binding=6
|
||||
_instAlphaSsbo = _gl.GenBuffer(); // #188 binding=7
|
||||
_instSelectionLightingSsbo = _gl.GenBuffer(); // SmartBox binding=8
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -927,6 +935,7 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
EntitySet set = EntitySet.All)
|
||||
{
|
||||
_shader.Use();
|
||||
_selectionLighting?.TickLighting();
|
||||
_indoorProbeFrameCounter++;
|
||||
var vp = camera.View * camera.Projection;
|
||||
_shader.SetMatrix4("uViewProjection", vp);
|
||||
|
|
@ -1131,6 +1140,10 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
// is constant across the entity's parts/tuples), by the entity's
|
||||
// bounding sphere — camera-INDEPENDENT (minimize_object_lighting).
|
||||
ComputeEntityLightSet(entity);
|
||||
_currentEntitySelectionLighting =
|
||||
_selectionLighting?.TryGetLighting(entity.ServerGuid, out var lighting) == true
|
||||
? new Vector2(lighting.Luminosity, lighting.Diffuse)
|
||||
: new Vector2(0f, 1f);
|
||||
|
||||
// #119 decisive probe: one-shot dump (+ change re-emission) for
|
||||
// ACDREAM_DUMP_ENTITY-targeted entities. Before the culled-continue
|
||||
|
|
@ -1508,6 +1521,9 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
if (_alphaData.Length < totalInstances)
|
||||
_alphaData = new float[totalInstances + 256];
|
||||
|
||||
if (_selectionLightingData.Length < totalInstances)
|
||||
_selectionLightingData = new Vector2[totalInstances + 256];
|
||||
|
||||
_opaqueDraws.Clear();
|
||||
_translucentDraws.Clear();
|
||||
|
||||
|
|
@ -1546,6 +1562,8 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
// #188: Opacities[] is parallel to Matrices[]; write at the same
|
||||
// cursor so binding=7 instanceAlpha[] tracks binding=0 instances.
|
||||
_alphaData[cursor] = grp.Opacities[i];
|
||||
// SmartBox CMaterial replacement, parallel to Matrices.
|
||||
_selectionLightingData[cursor] = grp.SelectionLighting[i];
|
||||
cursor++;
|
||||
}
|
||||
|
||||
|
|
@ -1643,6 +1661,12 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
fixed (float* ap = _alphaData)
|
||||
UploadSsbo(_instAlphaSsbo, 7, ap, totalInstances * sizeof(float));
|
||||
|
||||
// SmartBox click lighting: x=luminosity, y=diffuse. mesh_modern.vert
|
||||
// reads this only for the object path (uLightingMode=0), so EnvCell's
|
||||
// independent mode-1 renderer does not consume this binding.
|
||||
fixed (Vector2* hp = _selectionLightingData)
|
||||
UploadSsbo(_instSelectionLightingSsbo, 8, hp, totalInstances * sizeof(float) * 2);
|
||||
|
||||
// Fix B: global point-light buffer (binding=4) + per-instance light-set
|
||||
// buffer (binding=5). The global buffer is this frame's PointSnapshot; the
|
||||
// per-instance buffer holds 8 int indices into it per instance, laid out
|
||||
|
|
@ -2189,6 +2213,7 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
// state for entities whose animation hooks fired — so a cached
|
||||
// instance can never be mid-fade. Always unmodified opacity.
|
||||
grp.Opacities.Add(1.0f);
|
||||
grp.SelectionLighting.Add(_currentEntitySelectionLighting);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -2370,6 +2395,7 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
grp.Slots.Add(_currentEntitySlot); // Phase U.4 — parallel to Matrices
|
||||
AppendCurrentLightSet(grp); // Fix B — 8 ints per instance, parallel to Matrices
|
||||
grp.Opacities.Add(opacityMultiplier); // #188 — parallel to Matrices
|
||||
grp.SelectionLighting.Add(_currentEntitySelectionLighting);
|
||||
collector?.Add(new CachedBatch(key, texHandle, restPose));
|
||||
}
|
||||
}
|
||||
|
|
@ -2453,6 +2479,7 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
if (_instLightSetSsbo != 0) _gl.DeleteBuffer(_instLightSetSsbo); // Fix B binding=5
|
||||
if (_instIndoorSsbo != 0) _gl.DeleteBuffer(_instIndoorSsbo); // #142 binding=6
|
||||
if (_instAlphaSsbo != 0) _gl.DeleteBuffer(_instAlphaSsbo); // #188 binding=7
|
||||
if (_instSelectionLightingSsbo != 0) _gl.DeleteBuffer(_instSelectionLightingSsbo); // SmartBox binding=8
|
||||
if (_gpuQueriesInitialized)
|
||||
{
|
||||
for (int i = 0; i < GpuQueryRingDepth; i++)
|
||||
|
|
@ -2660,6 +2687,10 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
// the same cursor, so the binding=7 instanceAlpha[] tracks binding=0.
|
||||
public readonly List<float> Opacities = new();
|
||||
|
||||
// Retail SmartBox click lighting, parallel to Matrices. Each vec2 is
|
||||
// (luminosity, diffuse) and is uploaded to binding=8.
|
||||
public readonly List<Vector2> SelectionLighting = new();
|
||||
|
||||
/// <summary>
|
||||
/// Resets every per-instance parallel list for a new frame. These lists are
|
||||
/// appended in lockstep (one entry per drawn instance) during group build, so
|
||||
|
|
@ -2677,6 +2708,7 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
LightSets.Clear();
|
||||
IndoorFlags.Clear();
|
||||
Opacities.Clear();
|
||||
SelectionLighting.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue