diff --git a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs index 56f8e724..27604cbc 100644 --- a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs +++ b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs @@ -2569,10 +2569,21 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable Vector3 cameraWorldPosition, List alphaScratch) { - int opaqueGroupCount = - visibleInstanceCount == 0 ? 0 : opaque.Count; - int transparentGroupCount = - visibleInstanceCount == 0 ? 0 : transparent.Count; + // The no-VAO production early-out deliberately skips group + // partitioning, so its reusable opaque/transparent lists may still + // contain the preceding draw's entries. No instances were accepted; + // those stale scratch entries are not part of this submission and + // must not leak into its diagnostic identity. + IReadOnlyList acceptedOpaque = + visibleInstanceCount == 0 + ? Array.Empty() + : opaque; + IReadOnlyList acceptedTransparent = + visibleInstanceCount == 0 + ? Array.Empty() + : transparent; + int opaqueGroupCount = acceptedOpaque.Count; + int transparentGroupCount = acceptedTransparent.Count; StableRenderHash128 hash = StableRenderHash128.Create(); hash.Add(visibleInstanceCount); hash.Add(immediateInstanceCount); @@ -2580,14 +2591,14 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable hash.Add(transparentGroupCount); hash.Add(deferTransparent); RenderSceneHash128 opaqueDigest = - BuildOpaqueSubmissionDigest(opaque); + BuildOpaqueSubmissionDigest(acceptedOpaque); RenderSceneHash128 transparentDigest = BuildTransparentSubmissionDigest( - transparent, + acceptedTransparent, cameraWorldPosition, alphaScratch); RenderSceneHash128 transparentSetDigest = - BuildOpaqueSubmissionDigest(transparent); + BuildOpaqueSubmissionDigest(acceptedTransparent); hash.Add(opaqueDigest.Low); hash.Add(opaqueDigest.High); hash.Add(transparentDigest.Low); diff --git a/tests/AcDream.App.Tests/Rendering/Wb/InstanceGroupClearTests.cs b/tests/AcDream.App.Tests/Rendering/Wb/InstanceGroupClearTests.cs index fb2f002d..4a8852fa 100644 --- a/tests/AcDream.App.Tests/Rendering/Wb/InstanceGroupClearTests.cs +++ b/tests/AcDream.App.Tests/Rendering/Wb/InstanceGroupClearTests.cs @@ -1,4 +1,5 @@ using System.Numerics; +using AcDream.App.Rendering.Scene; using AcDream.App.Rendering.Wb; using AcDream.Core.Meshing; using Xunit; @@ -138,6 +139,36 @@ public class InstanceGroupClearTests reversedSubmission.TransparentSetDigest); } + [Fact] + public void DispatcherFingerprint_ZeroVisibleInstancesIgnoresStaleGroupStorage() + { + WbDrawDispatcher.InstanceGroup stale = MakeCompleteGroup( + textureHandle: 0xAA, + submissionOrder: 0); + var scratch = new List(); + + CurrentRenderDispatcherSubmission expected = + WbDrawDispatcher.CreateDispatcherSubmission( + visibleInstanceCount: 0, + immediateInstanceCount: 0, + deferTransparent: false, + opaque: [], + transparent: [], + cameraWorldPosition: Vector3.Zero, + alphaScratch: scratch); + CurrentRenderDispatcherSubmission actual = + WbDrawDispatcher.CreateDispatcherSubmission( + visibleInstanceCount: 0, + immediateInstanceCount: 0, + deferTransparent: false, + opaque: [stale], + transparent: [stale], + cameraWorldPosition: Vector3.Zero, + alphaScratch: scratch); + + Assert.Equal(expected, actual); + } + [Fact] public void CachedGroupHandle_RequiresLiveMatchingRegistration() {