fix(rendering): repair EnvCell retail CLIP state
Exclude deferred EnvCell subsets from the opaque turn, preserve exact CLIP/ALPHA fixed-state groups through leaf replay, and use retail's row-3 override, blend, depth, and texture-class alpha references. Keep the existing building-detail sentinel distinct from the two CLIP references.
Bound rejected source payload, replace no-op allocation proofs with actual EnvCell and particle RHI paths, rebuild checked-in SPIR-V, and correct AP-238/AP-240 plus the S4-c2 evidence record.
Gates: Release 0W/0E; hermetic 16735/0/0; InstalledDat 255 pass/10 known fail/1 skip; shaders 32/32; focused 239/239; allocation 2/2 at 0 B. The evidence/comment repair re-ran Release 0W/0E, shaders 32/32, affected 37/37, and allocation 2/2; mesh_detail.vert.spv remained byte-identical at SHA-256 5346247ab7d606046943e19b28888c814e08dc6cb27cd9750096ac055457eb57.
Mutation proof, with each mutation restored after its named first failure:
1. Restoring the opaque predicate to !IsAdditive fails WholeLeaf_MixedCellDrawsOpaqueAtTurnThenClipAndAlphaAtDrain first at draw count: expected 1, actual 3.
2. Selecting _alphaPipeline for CLIP fails WholeLeaf_ClipDrainBindsExactStateAndTextureClassReference first at the bind sequence: expected [envcell-clip], actual [envcell-alpha].
3. Disabling CLIP depth write fails that production Theory first at Assert.True(clipPipeline.Depth.Write): expected true, actual false.
4. Swapping palette/DDS references fails the DDS row first: expected 0.784313738, actual 0.392156869; the palette row reports the inverse.
5. Mutating mesh_modern.frag from < to <= fails ClipShaders_UseGreaterEqualForThePerRangeReference first at Assert.Contains("if (color.a < alphaCutoff) discard;"): the required source spelling is absent.
6. Restoring row-3 OverrideClipmap=true fails the real-interface clip Theory first at Assert.False: expected false, actual true.
7. Deleting failed-append rollback fails the flush/end/abort rejection-storm rows first at the bounded pending-count assertion: expected 0, actual 9000.
8. Selecting _transparentDetailPipeline for a ClipMap detail contribution fails the leaf detail pin first at the bind sequence: expected second bind envcell-retail-detail-clip, actual envcell-retail-detail-alpha.
9. Resetting detail ParamB to zero fails the same detail pin first at the second pushed reference: expected 0.784313738, actual 0.
10. Classifying CLIP with exact mask equality excludes legal 0x09 and fails WholeLeaf_PositiveStippleClipMaskUsesClipPipelineAndDdsReference first at pipeline: expected envcell-clip, actual envcell-alpha.
11. Mapping the new blend to SRC_ALPHA/INVSRCALPHA fails AllRetailBlendModesAreRepresentable first at the tuple: expected (One, OneMinusSrcAlpha), actual (SrcAlpha, OneMinusSrcAlpha).
12. Restoring mesh_detail.vert's uParamB > 0.5 category predicate fails ClipShaders_UseGreaterEqualForThePerRangeReference first because vDetailCategory = uParamB == 1.0 is absent.
13. Treating every positive detail uParamB as a cutoff fails that source pin first because isRetailClipReference(uParamB) ? uParamB : 0.05 is absent.
14. Adding arbitrary 0.5 as an accepted reference to either mesh_modern.frag or mesh_detail.frag fails that source pin first at Assert.DoesNotContain("value - 0.5"); both mutations were run and reversed independently.
Retail: D3DPolyRender::SetSurface @ 0x0059c4d0; paired binary @ 0x0059c72a, 0x0059c747/0x0059c74f, 0x0059c821, 0x0059c838, 0x0059c866.
This commit is contained in:
parent
0aa166aa09
commit
252886e84f
21 changed files with 812 additions and 277 deletions
|
|
@ -1,3 +1,4 @@
|
|||
using System.Collections;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
|
@ -9,6 +10,7 @@ using AcDream.App.Tests.Rendering.Gpu;
|
|||
using AcDream.Content;
|
||||
using AcDream.Core.Meshing;
|
||||
using AcDream.Core.Vfx;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
|
@ -189,54 +191,110 @@ public sealed class ParticleRendererRouteTests
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public void ProductionDispatch_WarmedAppendAndImmediatePathsDoNotAllocate()
|
||||
public void ProductionImmediateMesh_WarmedDrawImmediateParticleSubmissionRhiDoesNotAllocate()
|
||||
{
|
||||
var queue = new RetailAlphaQueue();
|
||||
var sink = new NoAllocParticleSink();
|
||||
ParticleRenderer.ReserveDeferredParticleDraw reserve = sink.Reserve;
|
||||
ParticleRenderer.DrawImmediateParticle immediate = sink.DrawImmediate;
|
||||
using var device = new RecordingGpuDevice(64 * 1024 * 1024);
|
||||
using var manager = new ObjectMeshManager(
|
||||
new VulkanMeshPipelineDevice(device.Retirement),
|
||||
device,
|
||||
new NullPreparedAssetSource(),
|
||||
NullLogger<ObjectMeshManager>.Instance);
|
||||
ObjectRenderData renderData = Assert.IsType<ObjectRenderData>(manager.UploadMeshData(
|
||||
new ObjectMeshData
|
||||
{
|
||||
ObjectId = 0x010001ECu,
|
||||
Vertices =
|
||||
[
|
||||
new VertexPositionNormalTexture { Position = Vector3.Zero },
|
||||
new VertexPositionNormalTexture { Position = Vector3.UnitX },
|
||||
new VertexPositionNormalTexture { Position = Vector3.UnitY },
|
||||
],
|
||||
TextureBatches =
|
||||
{
|
||||
[(8, 8, TextureFormat.RGBA8)] =
|
||||
[
|
||||
new TextureBatchData
|
||||
{
|
||||
Key = new TextureKey { SurfaceId = 0x08000015u },
|
||||
TextureData = new byte[8 * 8 * 4],
|
||||
Indices = [0, 1, 2],
|
||||
Translucency = TranslucencyKind.Opaque,
|
||||
CullMode = DatReaderWriter.Enums.CullMode.Clockwise,
|
||||
},
|
||||
],
|
||||
},
|
||||
}));
|
||||
var adapter = (WbMeshAdapter)RuntimeHelpers.GetUninitializedObject(typeof(WbMeshAdapter));
|
||||
typeof(WbMeshAdapter).GetField(
|
||||
"_meshManager",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||
.SetValue(adapter, manager);
|
||||
var frames = new GpuDeviceFrameLifetime(device);
|
||||
var scope = new VulkanWorldPassScope(sampleCount: 1);
|
||||
using var renderer = new ParticleRenderer(
|
||||
device,
|
||||
frames,
|
||||
scope,
|
||||
new ParticleSystem(new EmitterDescRegistry(), new Random(42)),
|
||||
meshAdapter: adapter);
|
||||
|
||||
for (int i = 0; i < 64; i++)
|
||||
RunPair(queue, sink, reserve, immediate);
|
||||
Type keyType = typeof(ParticleRenderer).GetNestedType(
|
||||
"MeshBatchKey", BindingFlags.NonPublic)!;
|
||||
Type instanceType = typeof(ParticleRenderer).GetNestedType(
|
||||
"MeshParticleInstance", BindingFlags.NonPublic)!;
|
||||
Type drawType = typeof(ParticleRenderer).GetNestedType(
|
||||
"MeshParticleDraw", BindingFlags.NonPublic)!;
|
||||
object key = Activator.CreateInstance(
|
||||
keyType,
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
|
||||
binder: null,
|
||||
args: [0x010001ECu, 0],
|
||||
culture: null)!;
|
||||
object instance = Activator.CreateInstance(
|
||||
instanceType,
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
|
||||
binder: null,
|
||||
args: [Matrix4x4.Identity, 0xFFFFFFFFu, 0f, 0u],
|
||||
culture: null)!;
|
||||
object draw = Activator.CreateInstance(
|
||||
drawType,
|
||||
BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic,
|
||||
binder: null,
|
||||
args: [key, renderData.Batches[0], instance],
|
||||
culture: null)!;
|
||||
var drawList = (IList)typeof(ParticleRenderer).GetField(
|
||||
"_meshDrawListScratch",
|
||||
BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||
.GetValue(renderer)!;
|
||||
drawList.Add(draw);
|
||||
var immediate = (ParticleRenderer.DrawImmediateParticle)typeof(ParticleRenderer)
|
||||
.GetField("_drawImmediateParticle", BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||
.GetValue(renderer)!;
|
||||
|
||||
long before = GC.GetAllocatedBytesForCurrentThread();
|
||||
for (int i = 0; i < 1_000; i++)
|
||||
RunPair(queue, sink, reserve, immediate);
|
||||
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
|
||||
frames.BeginFrame();
|
||||
renderer.BeginFrame(frameSlot: 0);
|
||||
IGpuFrame frame = frames.CurrentFrame!;
|
||||
using IGpuPassEncoder pass = frame.BeginPass(GpuPassDescription.BackbufferClear(
|
||||
"s4-c2-particle-allocation",
|
||||
Vector4.Zero,
|
||||
sampleCount: 1));
|
||||
using IDisposable publication = scope.Publish(pass);
|
||||
|
||||
device.Clear();
|
||||
device.RecordingEnabled = false;
|
||||
long allocated = ZeroAllocationProbe.MeasureWarmed(
|
||||
() => immediate(
|
||||
Matrix4x4.Identity,
|
||||
ParticleSubmissionKind.Mesh,
|
||||
drawIndex: 0,
|
||||
opaqueDepthState: true),
|
||||
batchSize: 256,
|
||||
warmupBatches: 2,
|
||||
samples: 4);
|
||||
|
||||
Assert.Equal(0, allocated);
|
||||
}
|
||||
|
||||
private static void RunPair(
|
||||
RetailAlphaQueue queue,
|
||||
NoAllocParticleSink sink,
|
||||
ParticleRenderer.ReserveDeferredParticleDraw reserve,
|
||||
ParticleRenderer.DrawImmediateParticle immediate)
|
||||
{
|
||||
queue.BeginFrame();
|
||||
ParticleRenderer.DeferToRetailAlphaQueue(
|
||||
ParticleSubmissionKind.Mesh,
|
||||
TranslucencyKind.Opaque,
|
||||
0x80FFFFFFu,
|
||||
queue,
|
||||
sink,
|
||||
reserve,
|
||||
immediate,
|
||||
Matrix4x4.Identity,
|
||||
drawIndex: 0);
|
||||
ParticleRenderer.DeferToRetailAlphaQueue(
|
||||
ParticleSubmissionKind.Mesh,
|
||||
TranslucencyKind.Opaque,
|
||||
0xFFFFFFFFu,
|
||||
queue,
|
||||
sink,
|
||||
reserve,
|
||||
immediate,
|
||||
Matrix4x4.Identity,
|
||||
drawIndex: 1);
|
||||
queue.EndFrame();
|
||||
}
|
||||
|
||||
private sealed class RecordingSource : IRetailAlphaDrawSource
|
||||
{
|
||||
public int PrepareCount { get; private set; }
|
||||
|
|
@ -252,31 +310,6 @@ public sealed class ParticleRendererRouteTests
|
|||
}
|
||||
}
|
||||
|
||||
private sealed class NoAllocParticleSink : IRetailAlphaDrawSource
|
||||
{
|
||||
private int _nextToken;
|
||||
|
||||
public int Reserve() => _nextToken++;
|
||||
|
||||
public void DrawImmediate(
|
||||
Matrix4x4 viewProjection,
|
||||
ParticleSubmissionKind kind,
|
||||
int drawIndex,
|
||||
bool opaqueDepthState)
|
||||
{
|
||||
}
|
||||
|
||||
public void PrepareAlphaDraws(ReadOnlySpan<int> tokens)
|
||||
{
|
||||
}
|
||||
|
||||
public void DrawPreparedAlphaBatch(int firstPreparedDraw, int drawCount)
|
||||
{
|
||||
}
|
||||
|
||||
public void ResetAlphaSubmissions() => _nextToken = 0;
|
||||
}
|
||||
|
||||
private sealed class NullPreparedAssetSource : IPreparedAssetSource
|
||||
{
|
||||
public PreparedAssetSourceStats Stats => default;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue