acdream/docs/render-packs/semantic-bindings-v1.md

22 KiB
Raw Blame History

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 14. Pack shaders must not redeclare or alias it. Set 3 is strictly opt-in: retail pipeline layouts contain only sets 02, 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

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 01.

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

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

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 03 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, 14
uShadowTextureAndFlags.z Square shadow-map resolution in pixels
uShadowTextureAndFlags.w Flags; bit 0 means directional shadows are valid/enabled; bits 811 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:

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:

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:

#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.