fix(rendering): port retail shared alpha list
Queue translucent world GfxObj batches and scene particles in one stable far-to-near stream using transformed DAT sort centers, then drain it at retail's landscape and final-world boundaries. Preserve authored blend, cull, lighting, opacity, and adjacent-only batching so particles behind lifestones are composited through the crystal instead of overpainting it. Release build succeeds and all 5,914 tests pass with five intentional skips. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
ec1bb19609
commit
6b0472ee32
14 changed files with 1083 additions and 34 deletions
|
|
@ -13,7 +13,10 @@ using Silk.NET.OpenGL;
|
|||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Instanced renderer for retail particle emitters.
|
||||
/// Instanced renderer for retail particle emitters. Scene particles submit to
|
||||
/// <see cref="RetailAlphaQueue"/> while a world frame is active so their
|
||||
/// compositing order is shared with ordinary translucent GfxObj parts. Sky and
|
||||
/// sealed off-screen passes retain their independent immediate path.
|
||||
/// </summary>
|
||||
public sealed unsafe class ParticleRenderer : IDisposable
|
||||
{
|
||||
|
|
@ -24,6 +27,11 @@ public sealed unsafe class ParticleRenderer : IDisposable
|
|||
MeshBatchKey Key,
|
||||
ObjectRenderBatch Batch,
|
||||
MeshParticleInstance Instance);
|
||||
private readonly record struct DeferredParticleDraw(
|
||||
ParticleSubmissionKind Kind,
|
||||
ParticleDraw Billboard,
|
||||
MeshParticleDraw Mesh,
|
||||
Matrix4x4 ViewProjection);
|
||||
|
||||
private readonly struct ParticleInstance
|
||||
{
|
||||
|
|
@ -64,6 +72,8 @@ public sealed unsafe class ParticleRenderer : IDisposable
|
|||
private readonly DatCollection? _dats;
|
||||
private readonly WbMeshAdapter? _meshAdapter;
|
||||
private readonly ParticleSystem _particles;
|
||||
private readonly RetailAlphaQueue? _alphaQueue;
|
||||
private readonly AlphaDrawSource _alphaSource;
|
||||
private readonly Dictionary<uint, ParticleGfxInfo> _particleGfxInfoByGfxObj = new();
|
||||
private readonly Dictionary<uint, RetailParticleGeometryKind> _geometryKindByGfxObj = new();
|
||||
private readonly Dictionary<uint, TranslucencyKind> _meshBlendBySurface = new();
|
||||
|
|
@ -97,20 +107,33 @@ public sealed unsafe class ParticleRenderer : IDisposable
|
|||
private readonly List<MeshParticleDraw> _meshDrawListScratch = new(64);
|
||||
private readonly List<MeshParticleInstance> _meshRunScratch = new(64);
|
||||
private readonly List<ParticleSubmission> _submissionScratch = new(128);
|
||||
private readonly List<DeferredParticleDraw> _deferredAlpha = new(128);
|
||||
|
||||
public ParticleRenderer(
|
||||
private sealed class AlphaDrawSource(ParticleRenderer owner) : IRetailAlphaDrawSource
|
||||
{
|
||||
public void DrawAlphaBatch(ReadOnlySpan<int> tokens)
|
||||
=> owner.DrawDeferredAlphaBatch(tokens);
|
||||
|
||||
public void ResetAlphaSubmissions()
|
||||
=> owner._deferredAlpha.Clear();
|
||||
}
|
||||
|
||||
internal ParticleRenderer(
|
||||
GL gl,
|
||||
string shadersDir,
|
||||
ParticleSystem particles,
|
||||
TextureCache? textures = null,
|
||||
DatCollection? dats = null,
|
||||
WbMeshAdapter? meshAdapter = null)
|
||||
WbMeshAdapter? meshAdapter = null,
|
||||
RetailAlphaQueue? alphaQueue = null)
|
||||
{
|
||||
_gl = gl ?? throw new ArgumentNullException(nameof(gl));
|
||||
_textures = textures;
|
||||
_dats = dats;
|
||||
_meshAdapter = meshAdapter;
|
||||
_particles = particles ?? throw new ArgumentNullException(nameof(particles));
|
||||
_alphaQueue = alphaQueue;
|
||||
_alphaSource = new AlphaDrawSource(this);
|
||||
if (_meshAdapter is not null)
|
||||
{
|
||||
_meshReferences = new ParticleMeshReferenceTracker(
|
||||
|
|
@ -221,10 +244,43 @@ public sealed unsafe class ParticleRenderer : IDisposable
|
|||
Vector3 cameraRight = Vector3.Normalize(new Vector3(invView.M11, invView.M12, invView.M13));
|
||||
Vector3 cameraUp = Vector3.Normalize(new Vector3(invView.M21, invView.M22, invView.M23));
|
||||
BuildDrawLists(cameraWorldPos, renderPass, cameraRight, cameraUp, emitterFilter);
|
||||
if (_submissionScratch.Count > 0)
|
||||
if (_submissionScratch.Count == 0)
|
||||
return;
|
||||
|
||||
if (renderPass == ParticleRenderPass.Scene && _alphaQueue?.IsCollecting == true)
|
||||
DeferToRetailAlphaQueue(camera);
|
||||
else
|
||||
DrawOrdered(camera);
|
||||
}
|
||||
|
||||
private void DeferToRetailAlphaQueue(ICamera camera)
|
||||
{
|
||||
RetailAlphaQueue queue = _alphaQueue!;
|
||||
Matrix4x4 viewProjection = camera.View * camera.Projection;
|
||||
for (int i = 0; i < _submissionScratch.Count; i++)
|
||||
{
|
||||
ParticleSubmission submission = _submissionScratch[i];
|
||||
DeferredParticleDraw deferred = submission.Kind == ParticleSubmissionKind.Billboard
|
||||
? new DeferredParticleDraw(
|
||||
submission.Kind,
|
||||
_drawListScratch[submission.DrawIndex],
|
||||
default,
|
||||
viewProjection)
|
||||
: new DeferredParticleDraw(
|
||||
submission.Kind,
|
||||
default,
|
||||
_meshDrawListScratch[submission.DrawIndex],
|
||||
viewProjection);
|
||||
|
||||
int token = _deferredAlpha.Count;
|
||||
_deferredAlpha.Add(deferred);
|
||||
queue.Submit(
|
||||
_alphaSource,
|
||||
token,
|
||||
MathF.Sqrt(MathF.Max(0f, submission.DistanceSq)));
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawOrdered(ICamera camera)
|
||||
{
|
||||
ParticleSubmissionOrdering.Sort(_submissionScratch);
|
||||
|
|
@ -245,7 +301,7 @@ public sealed unsafe class ParticleRenderer : IDisposable
|
|||
BatchKey key = draw.Key;
|
||||
if (activeKind != ParticleSubmissionKind.Billboard)
|
||||
{
|
||||
PrepareBillboardPipeline(camera);
|
||||
PrepareBillboardPipeline(camera.View * camera.Projection);
|
||||
activeKind = ParticleSubmissionKind.Billboard;
|
||||
}
|
||||
|
||||
|
|
@ -281,7 +337,7 @@ public sealed unsafe class ParticleRenderer : IDisposable
|
|||
ObjectRenderBatch batch = meshDraw.Batch;
|
||||
if (activeKind != ParticleSubmissionKind.Mesh)
|
||||
{
|
||||
PrepareMeshPipeline(camera, global);
|
||||
PrepareMeshPipeline(camera.View * camera.Projection, global);
|
||||
activeKind = ParticleSubmissionKind.Mesh;
|
||||
}
|
||||
|
||||
|
|
@ -334,19 +390,137 @@ public sealed unsafe class ParticleRenderer : IDisposable
|
|||
_gl.Disable(EnableCap.CullFace);
|
||||
}
|
||||
|
||||
private void PrepareBillboardPipeline(ICamera camera)
|
||||
private void DrawDeferredAlphaBatch(ReadOnlySpan<int> tokens)
|
||||
{
|
||||
if (tokens.Length == 0)
|
||||
return;
|
||||
|
||||
GlobalMeshBuffer? global = _meshAdapter?.MeshManager?.GlobalBuffer;
|
||||
_gl.Enable(EnableCap.DepthTest);
|
||||
_gl.Enable(EnableCap.Blend);
|
||||
_gl.DepthMask(false);
|
||||
_gl.ActiveTexture(TextureUnit.Texture0);
|
||||
|
||||
ParticleSubmissionKind? activeKind = null;
|
||||
Matrix4x4 activeViewProjection = default;
|
||||
int i = 0;
|
||||
while (i < tokens.Length)
|
||||
{
|
||||
DeferredParticleDraw deferred = _deferredAlpha[tokens[i]];
|
||||
if (deferred.Kind == ParticleSubmissionKind.Billboard)
|
||||
{
|
||||
ParticleDraw draw = deferred.Billboard;
|
||||
BatchKey key = draw.Key;
|
||||
if (activeKind != ParticleSubmissionKind.Billboard
|
||||
|| activeViewProjection != deferred.ViewProjection)
|
||||
{
|
||||
PrepareBillboardPipeline(deferred.ViewProjection);
|
||||
activeKind = ParticleSubmissionKind.Billboard;
|
||||
activeViewProjection = deferred.ViewProjection;
|
||||
}
|
||||
|
||||
_runScratch.Clear();
|
||||
do
|
||||
{
|
||||
_runScratch.Add(deferred.Billboard.Instance);
|
||||
i++;
|
||||
if (i >= tokens.Length)
|
||||
break;
|
||||
deferred = _deferredAlpha[tokens[i]];
|
||||
}
|
||||
while (deferred.Kind == ParticleSubmissionKind.Billboard
|
||||
&& deferred.Billboard.Key == key
|
||||
&& deferred.ViewProjection == activeViewProjection);
|
||||
|
||||
_gl.BlendFunc(
|
||||
BlendingFactor.SrcAlpha,
|
||||
key.Additive ? BlendingFactor.One : BlendingFactor.OneMinusSrcAlpha);
|
||||
_shader.SetInt("uUseTexture", key.UseTexture ? 1 : 0);
|
||||
_gl.BindTexture(TextureTarget.Texture2D, key.UseTexture ? key.TextureHandle : 0);
|
||||
DrawInstances(_runScratch);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (_meshShader is null || global is null)
|
||||
{
|
||||
i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
MeshParticleDraw meshDraw = deferred.Mesh;
|
||||
MeshBatchKey meshKey = meshDraw.Key;
|
||||
ObjectRenderBatch batch = meshDraw.Batch;
|
||||
if (activeKind != ParticleSubmissionKind.Mesh
|
||||
|| activeViewProjection != deferred.ViewProjection)
|
||||
{
|
||||
PrepareMeshPipeline(deferred.ViewProjection, global);
|
||||
activeKind = ParticleSubmissionKind.Mesh;
|
||||
activeViewProjection = deferred.ViewProjection;
|
||||
}
|
||||
|
||||
_meshRunScratch.Clear();
|
||||
do
|
||||
{
|
||||
_meshRunScratch.Add(deferred.Mesh.Instance);
|
||||
i++;
|
||||
if (i >= tokens.Length)
|
||||
break;
|
||||
deferred = _deferredAlpha[tokens[i]];
|
||||
}
|
||||
while (deferred.Kind == ParticleSubmissionKind.Mesh
|
||||
&& deferred.Mesh.Key == meshKey
|
||||
&& deferred.ViewProjection == activeViewProjection);
|
||||
|
||||
ApplyMeshCullMode(batch.CullMode);
|
||||
TranslucencyKind blend = ResolveMeshBlend(batch);
|
||||
_gl.BlendFunc(
|
||||
blend == TranslucencyKind.InvAlpha
|
||||
? BlendingFactor.OneMinusSrcAlpha
|
||||
: BlendingFactor.SrcAlpha,
|
||||
blend switch
|
||||
{
|
||||
TranslucencyKind.Additive => BlendingFactor.One,
|
||||
TranslucencyKind.InvAlpha => BlendingFactor.SrcAlpha,
|
||||
_ => BlendingFactor.OneMinusSrcAlpha,
|
||||
});
|
||||
|
||||
_gl.ProgramUniform2(
|
||||
_meshShader.Program,
|
||||
_meshTextureHandleLoc,
|
||||
(uint)(batch.BindlessTextureHandle & 0xFFFFFFFFu),
|
||||
(uint)(batch.BindlessTextureHandle >> 32));
|
||||
_gl.ProgramUniform1(_meshShader.Program, _meshTextureLayerLoc, batch.TextureIndex);
|
||||
|
||||
UploadMeshInstances(_meshRunScratch);
|
||||
_gl.DrawElementsInstancedBaseVertex(
|
||||
PrimitiveType.Triangles,
|
||||
(uint)batch.IndexCount,
|
||||
DrawElementsType.UnsignedShort,
|
||||
(void*)(batch.FirstIndex * sizeof(ushort)),
|
||||
(uint)_meshRunScratch.Count,
|
||||
(int)batch.BaseVertex);
|
||||
}
|
||||
|
||||
_gl.BindTexture(TextureTarget.Texture2D, 0);
|
||||
_gl.BindVertexArray(0);
|
||||
_gl.DepthMask(true);
|
||||
_gl.Disable(EnableCap.Blend);
|
||||
_gl.Disable(EnableCap.CullFace);
|
||||
}
|
||||
|
||||
private void PrepareBillboardPipeline(Matrix4x4 viewProjection)
|
||||
{
|
||||
_shader.Use();
|
||||
_shader.SetMatrix4("uViewProjection", camera.View * camera.Projection);
|
||||
_shader.SetMatrix4("uViewProjection", viewProjection);
|
||||
_shader.SetInt("uParticleTexture", 0);
|
||||
_gl.Disable(EnableCap.CullFace);
|
||||
_gl.BindVertexArray(_quadVao);
|
||||
}
|
||||
|
||||
private void PrepareMeshPipeline(ICamera camera, GlobalMeshBuffer global)
|
||||
private void PrepareMeshPipeline(Matrix4x4 viewProjection, GlobalMeshBuffer global)
|
||||
{
|
||||
_meshShader!.Use();
|
||||
_meshShader.SetMatrix4("uViewProjection", camera.View * camera.Projection);
|
||||
_meshShader.SetMatrix4("uViewProjection", viewProjection);
|
||||
_gl.FrontFace(FrontFaceDirection.CW);
|
||||
_gl.BindVertexArray(_meshVao);
|
||||
|
||||
|
|
@ -415,11 +589,10 @@ public sealed unsafe class ParticleRenderer : IDisposable
|
|||
// 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))
|
||||
&& TryAppendMeshDraws(em, p, gfxObjId, cameraWorldPos, ref sequence))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
|
@ -447,6 +620,8 @@ public sealed unsafe class ParticleRenderer : IDisposable
|
|||
axisY = Vector3.Transform(gfxInfo.AxisY, orientation) * (gfxInfo.Size.Y * p.Size);
|
||||
}
|
||||
|
||||
float distSq = Vector3.DistanceSquared(pos, cameraWorldPos);
|
||||
|
||||
int drawIndex = draws.Count;
|
||||
draws.Add(new ParticleDraw(key, new ParticleInstance(pos, axisX, axisY, p.ColorArgb, distSq)));
|
||||
_submissionScratch.Add(new ParticleSubmission(
|
||||
|
|
@ -462,7 +637,7 @@ public sealed unsafe class ParticleRenderer : IDisposable
|
|||
AcDream.Core.Vfx.ParticleEmitter emitter,
|
||||
Particle particle,
|
||||
uint gfxObjId,
|
||||
float distanceSq,
|
||||
Vector3 cameraWorldPosition,
|
||||
ref int sequence)
|
||||
{
|
||||
if (_meshAdapter is null || _meshShader is null)
|
||||
|
|
@ -481,6 +656,11 @@ public sealed unsafe class ParticleRenderer : IDisposable
|
|||
Matrix4x4 model = Matrix4x4.CreateScale(particle.Size)
|
||||
* Matrix4x4.CreateFromQuaternion(orientation)
|
||||
* Matrix4x4.CreateTranslation(particle.Position);
|
||||
float viewerDistance = RetailAlphaOrdering.ComputeViewerDistance(
|
||||
renderData.SortCenter,
|
||||
model,
|
||||
cameraWorldPosition);
|
||||
float distanceSq = viewerDistance * viewerDistance;
|
||||
var instance = new MeshParticleInstance(model, particle.ColorArgb, distanceSq);
|
||||
|
||||
for (int batchIndex = 0; batchIndex < renderData.Batches.Count; batchIndex++)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue