With GE_CNTL.BREAK_WAVE_AT_EOI clear, the instances of a tessellated draw share
hull threadgroups: the relative patch index counts on through the instances,
while the host's PrimitiveID restarts with each one. The local stage now passes
its draw-relative instance (InstanceIndex - BaseInstance) to the hull, which
numbers its patches instance * patches-per-instance + PrimitiveID before taking
(mod PRIM_GRP_SIZE) mod NUM_PATCHES. Instances that start a threadgroup anyway
(BREAK_WAVE_AT_EOI, or patches per instance a multiple of the group) keep the
per-instance PrimitiveID.
This replaces #4201's refusal of a hull or domain that uses its relative index
as data while BREAK_WAVE_AT_EOI is clear, and builds on its decode of the bit
(breakAtInstance, request serialization and draw keys).
- Hull and domain cache keys hold the patches per instance instead of the bit;
an indirect draw recompiles its hull and domain for each record's instance
count.
- The device enables shaderDrawParameters when available. Without it, a hull
using its relative index as data in such a draw is refused, as is a domain
doing so (passing the instance on to the domain is not implemented).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A hull with one output control point writes its control point at the
patch record offset alone, so the write has no control-point term and
was taken for a patch constant with no control stride ('the hull shader
writes off-chip memory outside its control points and patch constants').
Take the control stride from the patch term of such writes when the
write fits in one record (its offset is at least 0 and its end within
the patch term), the rule domain programs already apply to control-point
reads; patch-constant records lie outside that, below the top of the
off-chip block or above every control point. With one invocation per
patch, per-patch outputs also need no control-point-0 guard. Resident
Evil Village's quad patches have one output control point.
derive() learned the LS-to-HS stride from the control-point term of hull LDS reads, or from the patch term of reads at a computed control-point index. A hull with one input control point reads it at constant offsets, so neither applies and the stride stays unknown ('the hull shader reads no LDS per control point, so the local stride is unknown'). Every hull read already has a patch term of exactly inputControlPoints * localStride, so take the stride from it for reads without a control-point term too. Resident Evil Village's quad patches have one input control point.
Hull programs whose relative patch index (v1 bits 7:0) reached anything
but a tessellation ring or LDS address threw, because the lowering runs
every Vulkan patch as relative patch 0. The quad terrain hull 0x80002fc700
in Ghost of Yotei is one, and so is the domain 0x8068fbd900 (v7).
Such hulls and domains now take (PrimitiveID mod GE_CNTL.PRIM_GRP_SIZE)
mod VGT_LS_HS_CONFIG.NUM_PATCHES, as if hull threadgroups took NUM_PATCHES
patches at a time out of each primitive group; both stages see the same
PrimitiveID for a patch. This holds only when every instance starts a new
threadgroup, so it needs GE_CNTL.BREAK_WAVE_AT_EOI (bit 18); with the bit
clear the instances share threadgroups and such programs still throw.
PRIM_GRP_SIZE and BREAK_WAVE_AT_EOI join the tessellation configuration
(draw keys, hull and domain cache keys, request version 24). A missing
GE_CNTL throws, and so do local programs, whose relative vertex index
depends on vertex reuse that Vulkan does not expose.
A wave64 hull program narrows EXEC with s_and_saveexec_b64 and restores it
later, so its exec words reach the lane-bit test as ANDs with the host
ballot, as loop or join phis, and through separate ballots for the two
halves. Tessellation lowering refused them as "uses a lane mask other
than its own lane's bit".
The own-lane bit of a mask pair is now rebuilt from the words' operations
(ballots, bitwise ops, selects, phis, constants); both halves must have
the same shape and ballot the same predicate. Dead hull cycles such as a
saved-exec loop are removed before the remaining-ballot check, so a mask
whose other lanes are read (s_bcnt1_i32_b64 exec) is still refused. The
words are replaced through identities and block conditions are resolved,
so a branch on a compare copied to VCC keeps its condition. Local and
domain programs keep their exec words for the half-wave matcher.
Block conditions fix and HullCompareBranch test from EagleBearerYS.
TessellationLowering refused every domain-program read of the off-chip
ring outside its patch's control points, so domain programs that read
the hull's patch constants, like the quad terrain domain in Ghost of
Yotei at 0x8000 - 208 * (rel + 1), aborted.
Such reads now lower to a new PatchInput kind and become Patch inputs.
Hull outputs and domain inputs place a constant by its off-chip address
modulo the record stride, in a record-sized array at location
ceil(hsStride / 16); each program derives both on its own, so the two
interfaces match although they compile separately. Domain reads at an
address that does not move with the relative patch, across more than
one record or inside the control-point region still throw, as does a
domain program that reads patch constants but no control point. Hull
patch-constant writes now take the same bound: above the control points
of all NUM_PATCHES patches, not just one patch's.
TessellationLowering proved ring and LDS addresses affine over a
relative patch index of up to 256. A hull that lays its patch-constant
records out downwards, at 0x8000 - 208 * (rel + 1) like the quad
particle hull in Ghost of Yotei, then reached a negative address for
the last slot and aborted as "not affine". The relative patch index of
hull and domain programs stays below VGT_LS_HS_CONFIG.NUM_PATCHES
(bits 7:0, patches per threadgroup), which is 63 for that draw.
DecodeShaderStages now carries NUM_PATCHES as patchesPerGroup in the
tessellation configuration (draw keys, hull and domain cache keys,
recompile request version 22) and refuses zero, and the lowering bounds
the relative patch index by it. Configurations without it keep 256.
DecodeShaderStages accepted only clockwise triangle patches and no HOS
level clamp narrower than [1, 63], so terrain drawn as TRI_CCW quad
patches with VGT_HOS_MAX_TESS_LEVEL 9 aborted. Quad domains and TRI_CCW
now decode and emit Quads and VertexOrderCcw; isolines, other
partitionings and point or line output still throw.
VGT_HOS_MIN/MAX_TESS_LEVEL are read per draw and pushed in the last 8
bytes of the pre-rasterization push constant slot, where the hull reads
them to clamp its factors; they stay out of every key and the recompile
request, so one hull serves every level. Zero, negative and NaN outer
factors still cull; clamps outside 0 <= min <= 63, max >= 1,
min <= max throw. Quad hulls write four outer and two inner factors at
a 24-byte stride, the layout Mesa's TF ring store uses.
A hull program may read its input control points from LDS at an index it
computes from data, such as an index loaded from memory or min(invocation, n).
The tessellation lowering refused such a read because its address is not
affine in the invocation and relative patch.
The address evaluator now carries a dynamic term for a hull LDS read: a value
that is not affine but does not depend on the relative patch index, with the
multiple it is scaled by. Terms of the same value add up ((t << 1) + t of
t = index << 3 is 24 * index); terms of different values keep the greatest
common divisor of their multiples. The read is lowered from the program's own
address arithmetic, and the relative index may reach it only with the stride
of a patch's inputs. A computed offset that is not a whole number of control
points is refused.
The SPIR-V emitter clamps a hull input control-point index to the patch's last
input, recorded as technical debt: on the console an index past the patch
reads LDS of a neighbouring patch.
DecodeShaderStages accepts LS, HS and an NGG passthrough ES, reads the
3-bit VGT_TF_PARAM fields, refuses its other bits but DISTRIBUTION_MODE,
and needs HOS levels that keep [1, 63]; 0x286-0x287 join the draw key.
Local and domain programs are prepared with their stage group: its
headers are merged over one set of defaults, and a local program left
unbound by the hull header is prepared at its first draw.
The local program ends at its jump into the hull half.
TessellationLowering lowers LDS and ring addresses that are affine in
the control point and relative index to tessellation interfaces with
strides from the code, and refuses accesses outside the patch, per-patch
writes from other control points and a modified primitive export. Hull
lanes read their own saved exec bit, also of host-subgroup-masked
copies; other masks are refused, gl_InvocationID loads as a scalar and
factors clamp to 63.