381 lines
22 KiB
Markdown
381 lines
22 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, 160 bytes
|
||
|
||
```glsl
|
||
layout(std140, set = 3, binding = 5) uniform AtmosphericFrame {
|
||
vec4 uAtmosphereSunScreen; // @0: uv.xy, resolved ray strength, elevation degrees
|
||
vec4 uAtmosphereSunColor; // @16: linear rgb, 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
|
||
};
|
||
```
|
||
|
||
`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`.
|
||
|
||
### `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`.
|