Opus dual-lens review of the three VM6 commits (0930c35d,39e8408c,6cc5e183) found two blockers and two should-fix issues; all landed here along with the review's nits and documentation corrections. Blockers: - A1: the procedural-scenery classifier tested bit 31 alone instead of the full top nibble (0xF000_0000 == 0x8000_0000), so it also matched LandblockStaticEntityIdAllocator's 0xC... namespace (fences/gates/ building shells with a cutout subset), the 0xDA11_D0xx paperdoll id, and the 0xFFFF_FF01 portal-tunnel id as procedural scenery — all three would have swayed. ProceduralSceneryIdAllocator.IsInNamespace now does the exact top-nibble test; FoliageWindClassification delegates to it. - A2: GroupKey (the receiver's instance-batching key) did not carry FoliageFlags while the caster's dedup key already did, so a scenery instance and a non-scenery instance sharing a mesh subset coalesced into one receiver InstanceGroup whose flags were last-writer-wins — disagreeing with the correctly-keyed caster. GroupKey now carries FoliageFlags, computed before key construction and set exactly once at group creation; the imperative re-stamp is gone, and CachedBatch's now-redundant FoliageFlags field is removed. Should-fix: - A3: the world receiver pass bound UniformAtmosphericFrame only by accident (leftover from the caster pass, which runs first each frame, since Vulkan binding state isn't reset between passes). DirectionalShadowFrameBinding now carries the caster's exact AtmosphericFrameBufferBinding and BindDirectionalShadowReceiver binds it explicitly. - A4: a Setup-composed tree's opaque trunk part never got the trunk flag because HasCutoutSubset is cached per GfxObj part, not per entity. FoliageWindClassification.ComputeEntityHasCutoutSubset now ORs HasCutoutSubset across an entity's resolved sibling parts once per entity, threaded into ClassifyBatches/AddDirectionalShadowBatches via a new optional override parameter. Nits: A5 hashes the per-vertex flutter seed relative to the instance origin instead of absolute world XY (fp32 sin() precision loss at far landblock corners), mirrored in both foliage_wind.glsl and FoliageWindModel; A7 documents the max(maxHeight, 0.5) divide-guard as a deliberate pseudocode divergence; A8 switches FoliageWindExclusions' construction to ToFrozenSet() and softens the "never stale" doc comment to "no slower than one frame behind." Tests added: top-nibble classification (0xFFFFFFFFu now correctly false), GroupKey inequality across entity-driven scenery/landblock- static classification, a caster-batch test proving the same pairing never coalesces, ComputeEntityHasCutoutSubset unit + end-to-end two-part-Setup tests, the caster→receiver AtmosphericFrame binding carry-through, flutter-hash translation invariance relative to instance origin, and a Storm-wind mid-height displacement floor guarding against a "no motion" regression. Docs: plan VM6 body corrected to the five-row WeatherKind table, "bits 1 and 2", "all four" caster shaders, and top-nibble wording throughout; the owner gate checklist's Rain/Storm step; the stale v1-only shader- interface compatibility entry; semantic-bindings-v1.md's v2 members folded into the main 192-byte block; the IA-25 register row's top- nibble wording; AtmosphericFrameInputs.cs's ABI size reference. foliage_wind.glsl's A5 change recompiled exactly the five shaders that include it (mesh_atmospheric.vert, the four directional_shadow_world_* casters) plus the manifest; no other .spv changed. Verify: Release build 0 warnings/0 errors. App hermetic-lane filter 6,041/0 failed (no environment-specific failures this run). RenderPackValidator 30/30. Full hermetic-filtered solution: 15,269/0 failed across 15 projects. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
424 lines
24 KiB
Markdown
424 lines
24 KiB
Markdown
# Render-pack shader ABI and semantic bindings v1
|
||
|
||
**Campaign phase id:** **Campaign AR**
|
||
**Render-pack API:** `1`
|
||
|
||
This is a SPIR-V binary contract over renderer-owned descriptors. It does not
|
||
expose Vulkan descriptor sets, descriptor handles, images, buffers, samplers,
|
||
command buffers, devices, queues, or fences to managed pack code. A pack only
|
||
declares semantic inputs and supplies SPIR-V; the renderer validates the
|
||
interface and binds immutable frame data.
|
||
|
||
## Semantic execution and stable identity
|
||
|
||
`RenderResourceSemantic`, `RenderPassSemantic`,
|
||
`RenderPipelineVariantSemantic`, `RenderQualitySemantic`, and
|
||
`RenderSettingSemantic` select renderer-owned execution roles. Pack-owned IDs
|
||
remain stable persistence, UI, graph-edge, and diagnostic keys; the executor
|
||
never recognizes a role by comparing an ID or asset-name string. Custom
|
||
fullscreen declarations retain `Custom` semantics and are executed from their
|
||
declared hooks and edges.
|
||
|
||
Every non-custom semantic is unique within its declaration kind. The complete
|
||
Tier-2+ atmospheric executor requires its exact v1 pass/resource/pipeline-
|
||
variant roles, hook order, scene replay, and graph edges. A Tier-2 pack may
|
||
instead declare the directional-shadow component plus the technical custom
|
||
`WorldColor` tone-map copy needed to present the HDR world. That profile still
|
||
requires the exact shadow depth resource/pass, five caster/receiver variants,
|
||
headline-caster replay, capabilities, settings, and elevation policy; it does
|
||
not require bloom, rays, grading, vignette, or volumetric shafts. Any other
|
||
partial or malformed semantic graph fails validation even when all pack-owned
|
||
IDs remain syntactically valid.
|
||
|
||
Directional-shadow declarations use
|
||
`SelectedCelestialDirectionalLight` together with the required
|
||
`AuthoredCelestialDirectionalLight` capability and the descriptor's
|
||
`DirectionalShadowLightElevationResponse`. `SunDirection`,
|
||
`SunElevationResponse`, and `VolumetricShaftSunElevationResponse` remain the
|
||
separate sun-specific atmosphere contract for rays and shafts.
|
||
The public v1 point record remains named `SunElevationResponsePoint` for ABI
|
||
compatibility; points stored in `DirectionalShadowLightElevationResponse` are
|
||
interpreted against the selected celestial light's elevation.
|
||
|
||
## Fixed descriptor ownership
|
||
|
||
| Set | Binding | Shader declaration | Owner and use |
|
||
|---:|---:|---|---|
|
||
| 3 | 5 | `AtmosphericFrame` std140 uniform block | Renderer-owned camera/reconstruction, authored sun/day/weather and frame facts |
|
||
| 3 | 6 | `DirectionalShadow` std140 uniform block | Renderer-owned cascade matrices, splits, shadow texture slot, shadow policy, and selected authored celestial direction |
|
||
| 3 | 7 | `PackPass` std140 uniform block | Renderer-resolved pass resource slots, output facts and pass-local parameters |
|
||
| 3 | 8 | `PackSettings` std140 uniform block | Renderer-resolved declaration-order scalar values for the selected preset |
|
||
| 2 | 0 | `sampler2DArray uTextures[]` combined-image-sampler array | Existing global sampled-texture table; index with host-supplied slot IDs and `nonuniformEXT` |
|
||
|
||
Set 0 remains the renderer's existing storage-buffer set. A declared base
|
||
pipeline variant inherits the exact renderer pipeline ABI it specializes; it
|
||
does not gain arbitrary set-0 storage access. In particular,
|
||
`ShadowCasterTransforms` reuses the renderer's existing per-instance transform
|
||
publication rather than publishing a second animation pose.
|
||
|
||
Set 1 remains the current retail uniform layout at bindings 1–4. Pack shaders
|
||
must not redeclare or alias it. Set 3 is strictly opt-in: retail pipeline
|
||
layouts contain only sets 0–2, and the host creates no set-3 Vulkan object
|
||
until a validated pack pipeline is activated. Bindings other than those in the
|
||
table are reserved and validation rejects them.
|
||
|
||
## SPIR-V interface validation
|
||
|
||
Candidate activation and the standalone authoring validator inspect the
|
||
actual SPIR-V binary before any shader module or pipeline is created. Each
|
||
asset must expose exactly the declared vertex or fragment stage with entry
|
||
point `main`. A fullscreen pass may declare only the sampled table at set 2,
|
||
binding 0 when its declaration supplies a sampled semantic/resource input,
|
||
plus the role-appropriate set-3 blocks. It may not access renderer-private set
|
||
0 or retail set 1. A retained-scene pipeline variant may use only the base
|
||
set-0/set-1 bindings documented for that exact semantic role, plus its allowed
|
||
set-2/set-3 bindings.
|
||
|
||
Validation checks descriptor type and count, all set-3 uniform-block member
|
||
types, offsets, strides, and total shapes, and any declared push block against
|
||
the exact 96-byte retail layout below. Renderer storage buffers inherited by a
|
||
variant must be read-only; storage images, arbitrary storage descriptors, and
|
||
`OpImageWrite` are forbidden. An absent, malformed, aliased, writable, or
|
||
undeclared interface rejects the whole candidate atomically to retail with a
|
||
specific reason. Validation never exposes or accepts a Vulkan handle.
|
||
|
||
The binary member layout is validated against matching host structs. The
|
||
checked-in shared render-pack GLSL includes are the byte-offset SSOT; authors
|
||
include those definitions rather than maintaining a private copy. The tables
|
||
below state the same values for review and tool diagnostics.
|
||
|
||
### `AtmosphericFrame` — set 3, binding 5, 192 bytes (ABI v2; see below)
|
||
|
||
```glsl
|
||
layout(std140, set = 3, binding = 5) uniform AtmosphericFrame {
|
||
vec4 uAtmosphereSunScreen; // @0: uv.xy, resolved ray strength, elevation degrees
|
||
vec4 uAtmosphereSunColor; // @16: authored display-space rgb (retail has no linear pipeline), combined ray-policy multiplier
|
||
vec4 uAtmosphereViewport; // @32: width, height, 1/width, 1/height
|
||
vec4 uAtmosphereWeather; // @48: WeatherKind numeric, intensity, delta seconds, outdoor 0/1
|
||
vec4 uAtmosphereSunDirection; // @64: surface-to-sun xyz, authored direction brightness
|
||
vec4 uAtmospherePolicy; // @80: day group, group factor, shadow factor, shaft factor
|
||
mat4 uAtmosphereInverseViewProjection; // @96
|
||
// ABI v2 (Campaign VM VM6) — additive, see below:
|
||
vec4 uAtmosphereClockWind; // @160: elapsed seconds, wind mean [0..1], wind gust [0..1], wind direction radians
|
||
vec4 uAtmosphereWindAmplitude; // @176: lean amplitude m, branch amplitude m, flutter amplitude m, max canopy height m
|
||
};
|
||
```
|
||
|
||
`uAtmosphereSunScreen.xy` uses normalized main-world viewport coordinates. The two
|
||
strength fields are host-evaluated authored/policy facts; they do not create a
|
||
second sky or weather owner. `uAtmosphereWeather.x` is numerically integral and must be
|
||
interpreted with this v1 table, not guessed from colour or time:
|
||
|
||
| Numeric value | Weather kind |
|
||
|---:|---|
|
||
| 0 | Clear |
|
||
| 1 | Overcast |
|
||
| 2 | Rain |
|
||
| 3 | Snow |
|
||
| 4 | Storm |
|
||
|
||
All other values are reserved. `uAtmosphereWeather.y` is the transition
|
||
intensity in the inclusive range 0–1.
|
||
|
||
`uAtmosphereSunDirection.xyz` is normalized and points from a lit surface
|
||
toward the authored sun. `uAtmosphereSunScreen.z` is the resolved visible ray
|
||
strength; `uAtmosphereSunColor.w` is the combined ray elevation/day-group/
|
||
weather policy multiplier before a pass's own declared setting. In
|
||
`uAtmospherePolicy`, `.x` is the numerically integral active day group, `.y` is
|
||
that group's declared multiplier, `.z` is the declared directional-shadow
|
||
elevation factor, and `.w` is the declared volumetric-shaft elevation factor.
|
||
Shadow curves interpolate in sine-of-elevation space; shaft curves use
|
||
smoothstep interpolation in elevation-degree space. These are exact values
|
||
from the selected pack's `AtmospherePolicyDeclaration`, not built-in fallback
|
||
curves. An accepted directional-shadow curve resolves to exactly zero at and
|
||
below the authored 0-degree horizon; non-positive points must be zero, and a
|
||
curve without an exact 0-degree point must make its first positive point zero.
|
||
`uAtmosphereInverseViewProjection` reconstructs main-world positions
|
||
from scene depth and the normalized viewport coordinates. Matrix convention
|
||
and depth range match the shared push-block `viewProjection`.
|
||
|
||
### ABI v2 (additive) — Campaign VM VM6
|
||
|
||
`AtmosphericFrame` grew from 160 to 192 bytes by appending the two members
|
||
shown above after `uAtmosphereInverseViewProjection`. Nothing before offset
|
||
160 moved or changed meaning.
|
||
|
||
`uAtmosphereClockWind`/`uAtmosphereWindAmplitude` feed the shared
|
||
`foliage_wind.glsl` include, which `mesh_atmospheric.vert` and the four
|
||
`directional_shadow_world_*` (opaque/cutout, base and multiview) caster
|
||
vertex shaders call identically so a displaced leaf's shadow moves with it.
|
||
No other pass reads these members.
|
||
|
||
**Compatibility rule:** the host always allocates and binds the full 192-byte
|
||
v2 block (`RenderPackShaderAbi.AtmosphericFrameSizeBytes`), but a v1 shader —
|
||
one compiled before this campaign, declaring only the original seven members
|
||
— binds and reads correctly against it: a bound range only needs to be at
|
||
least as large as the block's declared size, so the shader simply never sees
|
||
the appended bytes. `RenderPackSpirvValidator.ValidateAtmosphericFrame`
|
||
accepts either the v1 shape (seven members, 160 bytes) or the v2 shape (nine
|
||
members, 192 bytes); any other member count is rejected naming both. This is
|
||
why the external sample packs under `samples/*/Shaders/*.spv` — whose GLSL
|
||
sources are not in this tree and are never recompiled — needed no rebuild for
|
||
this change.
|
||
|
||
### Colour space
|
||
|
||
The main-world colour target, `uAtmosphereSunColor` (sun-ray input), and any
|
||
pack-written sun-ray or volumetric-shaft colour are all **display-space**
|
||
(retail's 2013 client has no linear lighting pipeline — its fixed-function
|
||
output is gamma-encoded for direct display). A pack that does linear-space
|
||
math — bloom thresholding, ACES or another filmic tonemap, luma-weighted
|
||
saturation, a contrast pivot — must decode each such input before that math
|
||
and encode its final output before writing to the UNORM swapchain, or the
|
||
math is operating on the wrong numbers (Campaign VM VM3, closing finding F4 of
|
||
the Campaign AR review). The built-in Atmospheric pack's
|
||
`acdreamDecodeDisplay`/`acdreamEncodeDisplay` helpers in
|
||
`atmospheric_common.glsl` (`pow(c, 2.2)` / `pow(c, 1/2.2)`) are the reference
|
||
implementation; 2.2 is the retail-era display-gamma assumption, deliberately
|
||
not the sRGB piecewise curve.
|
||
|
||
### `PackPass` — set 3, binding 7, 64 bytes
|
||
|
||
```glsl
|
||
layout(std140, set = 3, binding = 7) uniform PackPass {
|
||
vec4 uPackParams0; // @0
|
||
vec4 uPackParams1; // @16
|
||
vec4 uPackParams2; // @32
|
||
vec4 uPackParams3; // @48
|
||
};
|
||
```
|
||
|
||
The active semantic pass defines these sixteen scalar meanings. Unused values
|
||
are zero. A pass cannot reinterpret values owned by a different pass. Sampled
|
||
pass inputs use logical `textureIndexA` through `textureIndexD` in the shared push block;
|
||
binding 7 carries scalar/vector policy and filter parameters, not descriptors.
|
||
|
||
The optional external-pack Low-preset `FusedAtmosphericPostProcess` execution
|
||
hint uses these fixed values. The built-in Low preset does not declare it:
|
||
|
||
| Semantic pass | Values |
|
||
|---|---|
|
||
| `SunRays` | `uPackParams1 = (1, logicalMaskWidth, logicalMaskHeight, 0)`; input A is scene depth and the shader reconstructs the declared RGBA8 sun mask before radial integration |
|
||
| `BloomDownsample` / both `BloomBlur` passes | Declared for the standard graph but not recorded for this preset; their threshold, knee, strength, offsets, and weights remain authoritative inputs to filmic |
|
||
| `FilmicComposite` | `uPackParams1.z = 1`; `uPackParams2 = (bloomStrength, threshold, knee, hasVolumetric)`, `uPackParams3.xy = logicalBloomTexelStep`; inputs A/B/C are world color, sun rays, and optional volumetric shafts, and filmic evaluates the full separable bloom kernel before composition |
|
||
|
||
Zero flags retain the ordinary six-pass atmospheric graph. The built-in Low
|
||
preset uses that zero-flag path; Medium, High, and external packs that do not
|
||
opt in never use this fused ABI.
|
||
|
||
### Multiview directional-shadow cascades
|
||
|
||
The optional Low-preset `MultiviewDirectionalShadowCascades` execution
|
||
hint requires the three `*MultiviewDirectionalShadowCaster` variants and the
|
||
matching capability in the Low preset. The host begins one layered depth pass
|
||
with `viewMask = 0b11`; each vertex shader indexes `uShadowWorldToClip` with
|
||
`gl_ViewIndex`. Commands retain exact order, `BaseInstance`, the shared N.5
|
||
world-transform arena, texture index/layer, fixed-function culling, alpha cutoff
|
||
`0.05`, and both fitted cascade matrices. With no hint the host records the
|
||
ordinary one-pass-per-cascade path. Unsupported hardware makes the hinted preset
|
||
unavailable rather than silently selecting an over-budget execution form.
|
||
|
||
### `DirectionalShadow` — set 3, binding 6, 336 bytes
|
||
|
||
```glsl
|
||
layout(std140, set = 3, binding = 6) uniform DirectionalShadow {
|
||
mat4 uShadowWorldToClip[4]; // @0, @64, @128, @192
|
||
vec4 uShadowSplitFarMeters; // @256
|
||
vec4 uShadowControl; // @272
|
||
vec4 uShadowBiasMeters; // @288
|
||
uvec4 uShadowTextureAndFlags; // @304
|
||
vec4 uShadowLightDirectionAndSource; // @320
|
||
} directionalShadow;
|
||
```
|
||
|
||
Field meanings are fixed:
|
||
|
||
| Field/component | Meaning |
|
||
|---|---|
|
||
| `uShadowWorldToClip[0..3]` | Texel-stabilized world-to-shadow-clip matrices; only the first `cascadeCount` entries are active |
|
||
| `uShadowSplitFarMeters` | Far distance of cascades 0–3 in camera-eye metres |
|
||
| `uShadowControl.x` | Directional shadow strength |
|
||
| `uShadowControl.y` | Filter softness |
|
||
| `uShadowControl.z` | Maximum shadow reach in metres, clamped to resident data |
|
||
| `uShadowControl.w` | Cascade blend width in metres |
|
||
| `uShadowBiasMeters.x` | Constant receiver/caster bias in world metres |
|
||
| `uShadowBiasMeters.y` | Slope-scaled bias in world metres |
|
||
| `uShadowBiasMeters.z` | Normal offset in world metres |
|
||
| `uShadowBiasMeters.w` | Caster depth padding in world metres |
|
||
| `uShadowTextureAndFlags.x` | Directional-depth array slot in set 2 |
|
||
| `uShadowTextureAndFlags.y` | Active cascade count, 1–4 |
|
||
| `uShadowTextureAndFlags.z` | Square shadow-map resolution in pixels |
|
||
| `uShadowTextureAndFlags.w` | Flags; bit 0 means directional shadows are valid/enabled; bits 8–11 carry the fixed receiver PCF radius; remaining v1 bits are reserved and zero |
|
||
| `uShadowLightDirectionAndSource.xyz` | Normalized direction from a lit surface toward the one authored celestial body selected for this shadow frame |
|
||
| `uShadowLightDirectionAndSource.w` | Numerically integral selected-source kind from the table below |
|
||
|
||
Selected-source kinds are stable ABI values:
|
||
|
||
| Numeric value | Selected celestial source |
|
||
|---:|---|
|
||
| 0 | None / unavailable; the enabled flag must be clear |
|
||
| 1 | Authored sun |
|
||
| 2 | Dominant authored Dereth moon |
|
||
| 3 | Secondary authored Dereth moon |
|
||
|
||
All other values are reserved. The selected source is a renderer-owned fact
|
||
resolved from the current immutable Dereth sky frame. A pack does not identify
|
||
sky objects by private index or create a second celestial clock. The host still
|
||
publishes only one directional-depth array: sun and moons are alternative
|
||
sources for the same bounded cascade work, not simultaneous shadow maps.
|
||
|
||
When producing a cascade, its zero-based cascade index uses the existing
|
||
`uRenderPass` push-constant member. Consumer shaders choose a cascade from the
|
||
eye-space distance and split values. No available selected celestial source, a
|
||
selected source at or below its accepted horizon, no authored directional
|
||
energy, indoors, and portal/login cover clear the enabled bit; shaders must not
|
||
sample stale maps when it is zero. Binding 5 remains sun-specific for sun rays
|
||
and volumetric shafts. Such passes must not substitute the selected moon
|
||
direction for `uAtmosphereSunDirection`; when binding 6 selects a moon they
|
||
treat its shadow map as unrelated to sun-shaft occlusion.
|
||
|
||
## Shared push constants
|
||
|
||
Every pipeline retains retail's exact shared 96-byte push-constant range. API
|
||
v1 does not enlarge it:
|
||
|
||
```glsl
|
||
layout(push_constant) uniform AcdreamPushBlock {
|
||
mat4 viewProjection; // byte 0
|
||
int drawIdOffset; // byte 64
|
||
int lightingMode; // byte 68
|
||
int renderPass; // byte 72
|
||
int lightDebug; // byte 76
|
||
uint textureIndexA; // byte 80
|
||
uint textureIndexB; // byte 84
|
||
float paramA; // byte 88
|
||
float paramB; // byte 92
|
||
} acdreamPush;
|
||
```
|
||
|
||
Pack shader source may use the logical aliases `uTextureIndexC` and
|
||
`uTextureIndexD`. The host stores their uint slot bits in the existing
|
||
`paramA` and `paramB` words, and the shared Vulkan preamble exposes them as
|
||
`floatBitsToUint(acdreamPush.paramA)` and
|
||
`floatBitsToUint(acdreamPush.paramB)`. This is an exact bit reinterpretation,
|
||
not numeric float conversion. Pack pass scalar/vector parameters belong in
|
||
binding 7, so the two spare retail words are available for these input slots.
|
||
|
||
Do not reshape existing fields. A future additive growth requires matching
|
||
host/shader layout tests and must remain inside the 128-byte Vulkan guarantee.
|
||
A pack pass receives scalar/vector values through binding 7. Base pipeline
|
||
variants use `viewProjection`, draw offset, texture slots and existing mode
|
||
fields according to that base pipeline's contract.
|
||
|
||
## PackSettings (set 3, binding 8, 256 bytes)
|
||
|
||
`RenderSettingDeclaration` values use one fixed declaration-order block:
|
||
|
||
```glsl
|
||
layout(std140, set = 3, binding = 8) uniform PackSettings {
|
||
vec4 uPackSettings[16];
|
||
};
|
||
```
|
||
|
||
The descriptor may declare at most 64 settings, which the authoring validator
|
||
and graphical host both enforce. Setting index `i` is its zero-based position in
|
||
`RenderPackDescriptor.Settings`; it maps to
|
||
`uPackSettings[i / 4][i % 4]`. Declaration order is therefore shader ABI and
|
||
must remain stable within a compatible pack version. The resolved value is one
|
||
IEEE-754 float: Boolean is `0.0` or `1.0`, Choice is its zero-based index in
|
||
`Choices`, and Integer/Float parse with invariant culture before float
|
||
conversion. Integer values are limited to the exactly representable inclusive
|
||
range -16,777,216..16,777,216; Float values must remain finite in float32. A
|
||
matching persisted user override wins a selected-preset `SettingOverride`,
|
||
which wins the declaration default. User values remain invariant strings keyed
|
||
by stable pack ID plus setting ID; before candidate activation the host rejects
|
||
unknown IDs and values that fail kind, range, step, or choice validation. That
|
||
failure retires the complete candidate to retail with an exact reason. The host
|
||
zero-initializes the complete block, so unused slots and any value that fails
|
||
defensive parsing are `0.0`; ordinary descriptor/selection validation prevents
|
||
invalid values from reaching the bind.
|
||
|
||
Binding 7 remains pass-local host dynamics and filter parameters. It must not
|
||
be overloaded with pack settings: doing so would make the same setting occupy
|
||
different components in different passes and would prevent one stable public
|
||
mapping. This fixed block is the complete v1 contract because it adds no
|
||
descriptor handles, storage buffers, per-frame managed callbacks, or
|
||
pass-specific setting schemas.
|
||
|
||
## Logical semantic table
|
||
|
||
The descriptor must list every semantic the shader reads. Listing a semantic
|
||
does not guarantee device support; the corresponding `RenderCapability` must
|
||
also be required when the table says so.
|
||
|
||
| `RenderSemanticInput` | Logical shader value | Source / lifetime | Capability prerequisite |
|
||
|---|---|---|---|
|
||
| `WorldColor` | Sampled main-world colour slot, excluding retained UI and private viewports | `textureIndexA-D` into set 2; current main-world frame | `MainWorldColorIntermediate` |
|
||
| `SceneDepth` | Sampled main-world depth slot plus reconstruction matrix | `textureIndexA-D` and binding 5; current main-world frame | `SceneDepthSampling` |
|
||
| `SceneNormals` | Sampled main-world normal slot | `textureIndexA-D` into set 2; current main-world frame | `SceneNormalSampling` |
|
||
| `SunDirection` | Normalized authored surface-to-sun direction; no second clock | Binding 5 `uAtmosphereSunDirection`; current immutable world frame | `AuthoredSunDirection` |
|
||
| `SelectedCelestialDirectionalLight` | Normalized direction and stable source kind for the one authored sun/moon selected to cast this frame's directional shadows | Binding 6 `uShadowLightDirectionAndSource`; current immutable world/sky frame | `AuthoredCelestialDirectionalLight` |
|
||
| `SunScreenPosition` | Authored sun projected for the main-world viewport, plus valid/in-front state | Binding 5; current camera/world frame | `AuthoredSunScreenPosition` |
|
||
| `ActiveDayGroup` | AC's categorical group plus descriptor-declared group/elevation multipliers | Binding 5 `uAtmospherePolicy`; current Runtime environment frame | `AuthoredWeather` |
|
||
| `Weather` | Numeric `WeatherKind`, intensity and outdoor state | Binding 5 `uAtmosphereWeather`; current Runtime environment frame | `AuthoredWeather` |
|
||
| `CameraMatrices` | Main-world view-projection and inverse, or directional cascade transforms required by the hook | Push `viewProjection` + binding 5 inverse; binding 6 for cascades | None beyond the hook's feature capability |
|
||
| `ShadowCasterTransforms` | Exact existing per-instance/per-part transforms for retained eligible casters | Inherited base-pipeline set-0 ABI; current retained scene | `AnimatedCasterTransforms` |
|
||
| `DirectionalShadowMaps` | Directional-depth table slot, active cascade count, matrices, splits and valid state | Binding 6 plus set 2; current outdoor shadow frame | `DirectionalShadowMaps` |
|
||
| `FrameTime` | Monotonic frame delta in seconds; never a gameplay clock | Binding 5 `uAtmosphereWeather.z`; current frame | None |
|
||
|
||
Pack-declared `ResourceReads` are resolved deterministically to the pass input
|
||
slots supplied by the host; v1 exposes up to four sampled inputs through
|
||
`textureIndexA-D`. `DirectionalShadowMaps` uses the binding-6 texture slot and
|
||
does not consume A-D. Slot assignment first walks sampled-image entries in
|
||
`SemanticInputs` declaration order (`WorldColor`, `SceneDepth`, and
|
||
`SceneNormals` when present), then sampled `ResourceReads` declaration order.
|
||
The first input receives A, then B, C, and D. Duplicate inputs are invalid. The
|
||
pack never chooses a global texture-table index. Resource IDs describe graph
|
||
edges, not binding numbers. `ResourceWrites` are render targets chosen by the
|
||
host and are not simultaneously sampled by the same pass.
|
||
|
||
`RenderResourceKind.Buffer`, `RenderFormatClass.StructuredData`, and
|
||
`RenderResourceUsage.Storage` are reserved for an additive future contract.
|
||
They have no public v1 descriptor binding and the v1 authoring validator
|
||
rejects them instead of accepting an unbindable graph. V1 image arrays are
|
||
reserved for `DirectionalDepth`; ordinary colour intermediates are `Image2D`.
|
||
Each pass writes at most one declared attachment. A zero-write pass is valid
|
||
only at `ToneMap` or `AfterToneMapBeforePrivateViewports`, where the host-owned
|
||
main-world target is implicit.
|
||
|
||
## Texture-table sampling
|
||
|
||
Vulkan pack SPIR-V targets the same global table as retail shaders:
|
||
|
||
```glsl
|
||
#extension GL_EXT_nonuniform_qualifier : require
|
||
layout(set = 2, binding = 0) uniform sampler2DArray uTextures[];
|
||
|
||
vec4 sample2D(uint slot, vec2 uv) {
|
||
return texture(uTextures[nonuniformEXT(slot)], vec3(uv, 0.0));
|
||
}
|
||
```
|
||
|
||
An ordinary 2-D texture is a one-layer array at layer zero. Array resources use
|
||
their declared layer. Directional depth may be sampled as ordinary depth and
|
||
compared/filtered in shader according to the declared shadow policy; the pack
|
||
does not create a private sampler or descriptor. `0xFFFFFFFFu` is the
|
||
unassigned texture-slot sentinel and must be checked before sampling an
|
||
optional input.
|
||
|
||
## Hooks and availability
|
||
|
||
| `RenderPassHook` | Inputs valid at the hook | Output boundary |
|
||
|---|---|---|
|
||
| `ShadowDepthBeforeWorld` | Camera/selected celestial source/environment, cascade block, retained caster transforms | Declared directional-depth resources only; outdoor gating applies |
|
||
| `AtmosphereBeforeToneMap` | HDR world colour when required, depth/normals when required, authored atmosphere and earlier declared resources | HDR pack intermediates; rays/shafts composite here |
|
||
| `ToneMap` | HDR world colour and earlier atmosphere resources | Main-world display colour |
|
||
| `AfterToneMapBeforePrivateViewports` | Tonemapped main-world colour and declared resources | Main world only; private viewports and retained UI remain outside |
|
||
|
||
Pass order is the descriptor order within a hook and never moves backward
|
||
through this table. Discovery does not bind any of these blocks. Bindings exist
|
||
only in the fully validated candidate and retire through normal frame-flight
|
||
fences on fallback, resize, portal, reconnect, unload, or the teardown phase of
|
||
device recreation. Recreation means a complete renderer/context/device
|
||
teardown followed by a fresh context/device; it is not live recovery from
|
||
`VK_ERROR_DEVICE_LOST`.
|