perf(vfx): port retail particle visibility degradation
Resolve DAT-authored particle ranges from the hardware GfxObj, apply retail distance and completed-cell visibility gates, and preserve the exact finite/infinite off-view update semantics. This removes dense-world simulation work without shortening terrain, entity, fog, or streaming distance. Publish doorway-clipped outdoor cells through a focused frame controller, retain effect cell identity for outdoor statics, reject hidden emitters before particle-slot scans, and offer an explicit opt-in Extended particle range. Release build succeeds and all 5,857 tests pass with five intentional skips. Retail-conformance, architecture, and adversarial review cycles are clean; connected Aerlinthe visual/performance gate pending. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
82789eea88
commit
f1ba147ac5
29 changed files with 1810 additions and 125 deletions
|
|
@ -395,61 +395,66 @@ public sealed unsafe class ParticleRenderer : IDisposable
|
|||
_meshDrawListScratch.Clear();
|
||||
_submissionScratch.Clear();
|
||||
int sequence = 0;
|
||||
foreach (var (em, idx) in _particles.EnumerateLive())
|
||||
foreach (var em in _particles.EnumerateEmitters())
|
||||
{
|
||||
if (!em.PresentationVisible)
|
||||
if (!em.PresentationVisible || !em.ViewEligible)
|
||||
continue;
|
||||
if (em.RenderPass != renderPass)
|
||||
continue;
|
||||
if (emitterFilter is not null && !emitterFilter(em))
|
||||
continue;
|
||||
|
||||
ref var p = ref em.Particles[idx];
|
||||
// `p.Position` is already in world coordinates: AttachLocal
|
||||
// emitters get their AnchorPos refreshed each frame by the
|
||||
// owning subsystem (sky-PES driver, animation tick, etc.) which
|
||||
// mirrors retail's live-parent-frame read at
|
||||
// ParticleEmitter::UpdateParticles 0x0051d2d4 for is_parent_local=1.
|
||||
Vector3 pos = p.Position;
|
||||
float distSq = Vector3.DistanceSquared(pos, cameraWorldPos);
|
||||
uint gfxObjId = em.Desc.HwGfxObjId != 0 ? em.Desc.HwGfxObjId : em.Desc.GfxObjId;
|
||||
if (gfxObjId != 0
|
||||
&& ResolveGeometryKind(gfxObjId) == RetailParticleGeometryKind.FullMesh
|
||||
&& TryAppendMeshDraws(em, p, gfxObjId, distSq, ref sequence))
|
||||
for (int idx = 0; idx < em.Particles.Length; idx++)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
ref var p = ref em.Particles[idx];
|
||||
if (!p.Alive)
|
||||
continue;
|
||||
// `p.Position` is already in world coordinates: AttachLocal
|
||||
// emitters get their AnchorPos refreshed each frame by the
|
||||
// owning subsystem (sky-PES driver, animation tick, etc.) which
|
||||
// mirrors retail's live-parent-frame read at
|
||||
// ParticleEmitter::UpdateParticles 0x0051d2d4 for is_parent_local=1.
|
||||
Vector3 pos = p.Position;
|
||||
float distSq = Vector3.DistanceSquared(pos, cameraWorldPos);
|
||||
uint gfxObjId = em.Desc.HwGfxObjId != 0 ? em.Desc.HwGfxObjId : em.Desc.GfxObjId;
|
||||
if (gfxObjId != 0
|
||||
&& ResolveGeometryKind(gfxObjId) == RetailParticleGeometryKind.FullMesh
|
||||
&& TryAppendMeshDraws(em, p, gfxObjId, distSq, ref sequence))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var gfxInfo = ResolveParticleGfxInfo(em.Desc);
|
||||
uint texture = gfxInfo.TextureHandle;
|
||||
bool useTexture = texture != 0;
|
||||
bool additive = gfxInfo.HasMaterial
|
||||
? gfxInfo.Additive
|
||||
: (em.Desc.Flags & EmitterFlags.Additive) != 0;
|
||||
var key = new BatchKey(texture, useTexture, additive);
|
||||
Vector3 axisX;
|
||||
Vector3 axisY;
|
||||
if (gfxInfo.IsBillboard)
|
||||
{
|
||||
pos += Vector3.UnitZ * (gfxInfo.CenterOffset.Z * p.Size);
|
||||
axisX = cameraRight * (gfxInfo.Size.X * p.Size);
|
||||
axisY = cameraUp * (gfxInfo.Size.Y * p.Size);
|
||||
}
|
||||
else
|
||||
{
|
||||
Quaternion orientation = ParticleOrientation(em, p);
|
||||
pos += Vector3.Transform(gfxInfo.CenterOffset * p.Size, orientation);
|
||||
axisX = Vector3.Transform(gfxInfo.AxisX, orientation) * (gfxInfo.Size.X * p.Size);
|
||||
axisY = Vector3.Transform(gfxInfo.AxisY, orientation) * (gfxInfo.Size.Y * p.Size);
|
||||
}
|
||||
var gfxInfo = ResolveParticleGfxInfo(em.Desc);
|
||||
uint texture = gfxInfo.TextureHandle;
|
||||
bool useTexture = texture != 0;
|
||||
bool additive = gfxInfo.HasMaterial
|
||||
? gfxInfo.Additive
|
||||
: (em.Desc.Flags & EmitterFlags.Additive) != 0;
|
||||
var key = new BatchKey(texture, useTexture, additive);
|
||||
Vector3 axisX;
|
||||
Vector3 axisY;
|
||||
if (gfxInfo.IsBillboard)
|
||||
{
|
||||
pos += Vector3.UnitZ * (gfxInfo.CenterOffset.Z * p.Size);
|
||||
axisX = cameraRight * (gfxInfo.Size.X * p.Size);
|
||||
axisY = cameraUp * (gfxInfo.Size.Y * p.Size);
|
||||
}
|
||||
else
|
||||
{
|
||||
Quaternion orientation = ParticleOrientation(em, p);
|
||||
pos += Vector3.Transform(gfxInfo.CenterOffset * p.Size, orientation);
|
||||
axisX = Vector3.Transform(gfxInfo.AxisX, orientation) * (gfxInfo.Size.X * p.Size);
|
||||
axisY = Vector3.Transform(gfxInfo.AxisY, orientation) * (gfxInfo.Size.Y * p.Size);
|
||||
}
|
||||
|
||||
int drawIndex = draws.Count;
|
||||
draws.Add(new ParticleDraw(key, new ParticleInstance(pos, axisX, axisY, p.ColorArgb, distSq)));
|
||||
_submissionScratch.Add(new ParticleSubmission(
|
||||
ParticleSubmissionKind.Billboard,
|
||||
drawIndex,
|
||||
distSq,
|
||||
sequence++));
|
||||
int drawIndex = draws.Count;
|
||||
draws.Add(new ParticleDraw(key, new ParticleInstance(pos, axisX, axisY, p.ColorArgb, distSq)));
|
||||
_submissionScratch.Add(new ParticleSubmission(
|
||||
ParticleSubmissionKind.Billboard,
|
||||
drawIndex,
|
||||
distSq,
|
||||
sequence++));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue