fix(render): production packed classifier carries the foliage flags; deferred-alpha replay keeps them (Campaign VM VM6 review 2)
Narrow re-review of 43e3abed found every A1-A5/A7/A8 item resolved but
one new blocker in the production packed classifier.
F1 (BLOCKER): RetailPViewPassExecutor.DrawPackedProductionRoute — the
route production world geometry actually draws from — never computed
FoliageFlags at all. WbDrawDispatcher.PackedOracle.cs's
ClassifyPackedBatches built its GroupKey with the field defaulting to
0u, and GetOrCreatePackedGroup never copied it onto the created
InstanceGroup, so production BatchData.flags bits 1/2 were always zero
for every scenery entity: the world geometry never swayed even though
the independently-classified shadow caster did, so shadows visibly
swayed under rigid trees. Both classifier call sites now compute
FoliageFlags via the identical FoliageWindClassification.Classify call
and entity-scoped HasCutoutSubset OR the classic (non-packed) path
uses, and GetOrCreatePackedGroup copies it exactly like
GetOrCreateInstanceGroup always has. The G2/G3 classified-output
digest (AddOpaqueSubmissionGroup/BuildTransparentSubmissionDigest) now
also folds GroupKey.FoliageFlags into its hash — present in the key
since round 1 but never actually read by either digest function, so a
content-level (not just group-count-level) classic-vs-packed
divergence is now caught.
F2 (medium): the delayed-alpha replay path (PrepareDeferredAlphaDraws)
hardcoded Flags = 1, dropping bits 1/2 for any group replayed through
it — a trunk instance promoted into the alpha-blend group mid-fade
(the #188 translucency-promotion case) would stop swaying for the
duration of its fade. Now 1u | key.FoliageFlags.
F3 (nit): ComputeEntityHasCutoutSubset's three call sites (classic,
caster, and the newly-fixed packed classifier) each allocated a
closure over _meshAdapter per Setup entity per frame. A new
context-taking overload passes the mesh adapter as an explicit
argument to a static lambda instead, letting the compiler cache one
delegate for the method's lifetime rather than allocating fresh ones.
A3 test gap: WbDrawDispatcher.BindDirectionalShadowReceiver is now
internal so DirectionalShadowGpuTests can drive it directly with a
bare RecordingGpuDevice pass encoder, proving it emits
UniformAtmosphericFrame with the exact buffer/offset/size a
DirectionalShadowFrameBinding carries — paired with the existing test
proving that binding carries the caster's real bind forward untouched.
F1's missing test: PackedDispatcherOracleTests chains
FoliageWindClassification.Classify (called with the packed
classifier's exact argument shape) for a real 0x8... scenery entity id
through BuildIndirectArrays — the same shared, already-tested
production step both classic and packed group lists feed into BatchData —
proving the resulting flags word carries bit 0x2. Driving
ClassifyPackedBatches/GetOrCreatePackedGroup directly was not a "cheap
test": both are private instance methods reachable only through the
full RetailPViewPassExecutor route, which needs a real IGpuDevice,
world-pass scope, mesh manager, and compiled pipelines to construct —
no test anywhere in the App test project stands one up.
Nits: F4 corrects foliage_wind.glsl's header comment from "bit 31" to
the top-nibble test; F5 documents at the receiver bind site that the
caster's own AtmosphericFrameBufferBinding has its seven ABI v1
members zero/Identity by construction (only the two v2 wind members
are valid) — safe today because mesh_atmospheric.vert reads that
binding solely for wind displacement, flagged as a footgun for a
future v1-reading addition to that shader; F6 notes in the plan
(rather than fixes) that EntityCacheEntry does not proactively
invalidate when FoliageWindExclusions changes on a pack switch —
harmless with the pack off, self-heals on the entry's next natural
eviction.
foliage_wind.glsl's F4 comment-only change updated the SPIR-V
manifest's source hashes for the five includers (mesh_atmospheric.vert
+ four directional_shadow_world_* casters); the compiled .spv bytes
are byte-identical since comments do not affect bytecode.
Verify: Release build 0 warnings/0 errors. App hermetic-lane filter
6,043/0 failed. Core.Tests 4,695/0 failed. RenderPackValidator 30/30.
Full hermetic-filtered solution: 15,271/0 failed across 15 projects.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
43e3abed4d
commit
a82959f1b7
10 changed files with 332 additions and 16 deletions
|
|
@ -437,6 +437,25 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
if (renderData.IsSetup
|
||||
&& renderData.SetupParts.Count > 0)
|
||||
{
|
||||
// Campaign VM VM6 review fix round 2 (F1): same entity-
|
||||
// scoped HasCutoutSubset OR the classic world-receiver loop
|
||||
// computes (WbDrawDispatcher.cs) and the caster loop
|
||||
// (DirectionalShadows.cs) — see
|
||||
// FoliageWindClassification.ComputeEntityHasCutoutSubset's
|
||||
// doc comment. A Setup composite's parts are separate
|
||||
// GfxObjs with independently-cached HasCutoutSubset; without
|
||||
// this bounded per-entity scan, the opaque trunk part of a
|
||||
// production-classified tree would never get the trunk
|
||||
// flag. The context-taking overload passes _meshAdapter
|
||||
// explicitly to a static lambda (F3) — zero allocation per
|
||||
// entity per frame.
|
||||
bool entityHasCutoutSubset = FoliageWindClassification
|
||||
.ComputeEntityHasCutoutSubset(
|
||||
renderData.SetupParts,
|
||||
_meshAdapter,
|
||||
static (adapter, part) => adapter.TryGetRenderData(part.GfxObjId)
|
||||
is { HasCutoutSubset: true });
|
||||
|
||||
for (int setupPartIndex = 0;
|
||||
setupPartIndex < renderData.SetupParts.Count;
|
||||
setupPartIndex++)
|
||||
|
|
@ -477,7 +496,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
indoor,
|
||||
selectionLighting,
|
||||
opacity,
|
||||
cacheEntry))
|
||||
cacheEntry,
|
||||
entityHasCutoutSubset))
|
||||
{
|
||||
reusableAcrossFrames = false;
|
||||
}
|
||||
|
|
@ -593,8 +613,17 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
bool indoor,
|
||||
Vector2 selectionLighting,
|
||||
float opacity,
|
||||
PackedProjectionClassificationEntry? cacheEntry)
|
||||
PackedProjectionClassificationEntry? cacheEntry,
|
||||
// Campaign VM VM6 review fix round 2 (F1): mirrors the classic
|
||||
// dispatcher's ClassifyBatches override exactly. HasCutoutSubset is
|
||||
// a per-PART (per-GfxObj) fact; the caller passes the ENTITY/Setup-
|
||||
// scoped OR across every resolved part for a Setup composite. null
|
||||
// (the default) means "use renderData.HasCutoutSubset directly",
|
||||
// which is already correct for a non-Setup single-mesh entity.
|
||||
bool? entityHasCutoutSubsetOverride = null)
|
||||
{
|
||||
bool entityHasCutoutSubset =
|
||||
entityHasCutoutSubsetOverride ?? renderData.HasCutoutSubset;
|
||||
bool reusableAcrossFrames = true;
|
||||
for (int batchIndex = 0;
|
||||
batchIndex < renderData.Batches.Count;
|
||||
|
|
@ -617,6 +646,22 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
if (!texture.Slot.IsAssigned)
|
||||
continue;
|
||||
|
||||
// Campaign VM VM6 review fix round 2 (F1 BLOCKER): the packed
|
||||
// production classifier never computed FoliageFlags, so the
|
||||
// production BatchData.flags word was always 0 for every
|
||||
// scenery entity — the world geometry never swayed even though
|
||||
// the independently-classified shadow caster did. Classify
|
||||
// BEFORE constructing the key, from the RAW (pre-#188-
|
||||
// promotion) batch.Translucency, exactly as the classic
|
||||
// ClassifyBatches does — see that method's own comment for why
|
||||
// raw translucency is used for classification but the (possibly
|
||||
// promoted) local `translucency` is still what the key/group
|
||||
// partitions draws by.
|
||||
uint foliageFlags = FoliageWindClassification.Classify(
|
||||
entity.LocalEntityId,
|
||||
FoliageWindExclusions.Contains(meshRef.GfxObjId),
|
||||
batch.Translucency,
|
||||
entityHasCutoutSubset);
|
||||
var key = new GroupKey(
|
||||
batch.FirstIndex,
|
||||
(int)batch.BaseVertex,
|
||||
|
|
@ -624,7 +669,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
texture.Slot,
|
||||
texture.Layer,
|
||||
translucency,
|
||||
batch.CullMode);
|
||||
batch.CullMode,
|
||||
foliageFlags);
|
||||
var classified = new PackedClassifiedBatch(
|
||||
key,
|
||||
restPose,
|
||||
|
|
@ -759,6 +805,17 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
TextureLayer = key.TextureLayer,
|
||||
Translucency = key.Translucency,
|
||||
CullMode = key.CullMode,
|
||||
// Campaign VM VM6 review fix round 2 (F1 BLOCKER): this was the
|
||||
// missing copy. GetOrCreateInstanceGroup (the classic route) has
|
||||
// set FoliageFlags from key.FoliageFlags since the round-1
|
||||
// fix; this packed-route sibling never did, so every packed
|
||||
// production InstanceGroup carried FoliageFlags == 0 regardless
|
||||
// of what ClassifyPackedBatches computed into the key —
|
||||
// BatchData.flags bits 1/2 were always 0 for production world
|
||||
// geometry even though the independently-classified shadow
|
||||
// caster set them correctly, so casters swayed while the
|
||||
// meshes they shadowed did not.
|
||||
FoliageFlags = key.FoliageFlags,
|
||||
Registration = _nextPackedGroupRegistration++,
|
||||
LastUsedFrame = _packedGroupFrame,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue