AtmosphericFrame (set 3/binding 5) grows additively from 160 to 192 bytes: two appended vec4 members, uAtmosphereClockWind and uAtmosphereWindAmplitude, carry the foliage-wind clock/weather and amplitude inputs VM6b's shader displacement will read. RenderPackShaderAbi renames the old constant to AtmosphericFrameSizeBytesV1 (160), adds AtmosphericFrameSizeBytesV2 (192), keeps AtmosphericFrameSizeBytes pointing at the current (v2) size, and adds ShaderAbiVersion = 2. RenderPackSpirvValidator.ValidateAtmosphericFrame accepts either the v1 (seven-member, 160-byte) or v2 (nine-member, 192-byte) shape and rejects anything else naming both — this is why the frozen external sample packs under samples/*/Shaders/*.spv, whose GLSL sources are not in this tree, need no rebuild: a v1 shader bound to the 192-byte buffer still reads correctly, since a bound range only needs to be >= the block's own declared size. DirectionalSunShadowRenderer's caster pass now binds AtmosphericFrame too (both the multiview and per-cascade sites), through a new AtmosphericFrameBufferBinding the graph owns and supplies via DirectionalSunShadowRenderInput. AtmosphericPostProcessGraph.RenderDirectionalShadows builds its own 192-byte ring allocation for this, separate from the world receiver's frame block, because the caster pass runs before RenderPostProcess constructs that block within the same frame. The four world caster pipeline variants (opaque/cutout, base/multiview) are now allowed to declare binding 5 in the validator; terrain casters are untouched. This commit is plumbing only: the two new members are always written but never read by any shader yet (zero placeholders), so pack-on and pack-off output are both pixel-identical to before. VM6b wires the real weather-driven values and the shader-side displacement. App hermetic filter: 5972/5974 (2 pre-existing failures unrelated to this change, confirmed against the unmodified baseline). Core.Tests hermetic: 4697/4697. RenderPackValidator.Tests: 30/30. VulkanShaderManifestTests (retail oracle set): 7/7, byte-identical. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
236 lines
14 KiB
Markdown
236 lines
14 KiB
Markdown
# Render-pack SDK v1
|
||
|
||
**Campaign:** Atmospheric Rendering / Shader Packs
|
||
**Phase id:** **Campaign AR**
|
||
**Contract version:** `RenderPackApi.Current == 1`
|
||
|
||
Render packs are opt-in, declarative graphics extensions. acdream's current
|
||
retail-faithful renderer is always installed, remains the default and
|
||
authoritative comparison path, and is restored as one complete transaction
|
||
when a selected pack cannot run. A
|
||
pack cannot access Vulkan, renderer internals, gameplay state, world streaming,
|
||
or physics.
|
||
|
||
The public dependency is only
|
||
`AcDream.Plugin.Abstractions`. Do not reference `AcDream.App`, Silk.NET, or a
|
||
Vulkan binding. Three buildable external samples cover the API:
|
||
|
||
- [`AcDream.RenderPacks.NoOp`](../../samples/AcDream.RenderPacks.NoOp/) is the
|
||
smallest discovery and activation conformance pack.
|
||
- [`AcDream.RenderPacks.AtmosphericTier2`](../../samples/AcDream.RenderPacks.AtmosphericTier2/)
|
||
declares the complete semantic atmospheric executor with deliberately
|
||
renamed pack-owned IDs, embeds all referenced SPIR-V, and demonstrates
|
||
moving authored sun-and-moon shadows for terrain, trees, buildings, players, and monsters.
|
||
- [`AcDream.RenderPacks.ShadowsOnlyTier2`](../../samples/AcDream.RenderPacks.ShadowsOnlyTier2/)
|
||
demonstrates that Tier 2 is composable: it requests the same selected-celestial
|
||
caster/receiver semantics without Tier-1 post-processing or volumetric
|
||
shafts.
|
||
|
||
## Quick start
|
||
|
||
1. Target `.NET 10` and reference `AcDream.Plugin.Abstractions` with runtime
|
||
copy disabled. The acdream host supplies that assembly.
|
||
2. Add [`plugin.json`](plugin-manifest-v1.schema.json), include
|
||
`"kinds": ["renderPack"]`, and copy it beside the built entry DLL.
|
||
3. Expose exactly one public, parameterless `IRenderPackPlugin` entry point.
|
||
4. Construct immutable `RenderPackDescriptor` values and register them from
|
||
`Register`. Registration must only publish declarations; do not open assets,
|
||
compile shaders, start threads, or allocate native/GPU resources.
|
||
5. Supply shader bytes lazily through `IRenderPackAssets.OpenRead`. Asset keys
|
||
are forward-slash relative logical paths: never rooted, backslash-based, or
|
||
`.`/`..` traversals.
|
||
6. Build and run the SDK validator:
|
||
|
||
```powershell
|
||
dotnet build samples/AcDream.RenderPacks.NoOp/AcDream.RenderPacks.NoOp.csproj -c Release
|
||
dotnet run --project tools/RenderPackValidator/AcDream.Tools.RenderPackValidator.csproj -c Release -- samples/AcDream.RenderPacks.NoOp/bin/Release/net10.0
|
||
```
|
||
|
||
Substitute `AcDream.RenderPacks.AtmosphericTier2` in both paths to validate
|
||
the complete Tier 2/Tier 2+ example and its embedded shader interfaces.
|
||
|
||
The validator executes the managed registration entry point. Use it only on a
|
||
pack you trust. It loads no App, RHI, or Vulkan assembly and creates no GPU
|
||
objects. It validates the manifest, v1 declarations, referenced asset keys,
|
||
SPIR-V stage/entry point and complete v1 binary interface, managed
|
||
registration, and duplicate pack IDs. Hardware and driver compatibility remain
|
||
client-side activation checks.
|
||
|
||
To launch a visible offline preview of acdream with the built-in Atmospheric
|
||
pack and a disposable settings profile:
|
||
|
||
```powershell
|
||
.\tools\launch-atmospheric-preview.ps1 -Preset High
|
||
```
|
||
|
||
The preview starts `AcDream.App` with audio disabled, clears inherited
|
||
`ACDREAM_*` live/automation/diagnostic state for that child, and leaves the
|
||
user's normal acdream settings untouched. It records the binary identity,
|
||
selected audio mode, and separate stdout/stderr logs beside the disposable
|
||
profile. To exercise OpenAL explicitly, add `-EnableAudio`.
|
||
|
||
Vertex and fragment asset keys are independent opaque keys; they do not need
|
||
matching basenames or a host shader-directory stem. On explicit selection the
|
||
client opens each declared stream, validates it, copies the bytes into the
|
||
isolated candidate, and creates shader modules from those immutable blobs.
|
||
The pipeline retains neither the stream nor a path into the plugin directory.
|
||
Each stage must be little-endian, word-aligned SPIR-V no larger than 16 MiB.
|
||
|
||
Shader-visible pack settings are deliberately capped at 64 declarations. The
|
||
public `PackSettings` binding and value encoding are documented in the
|
||
[`semantic binding table`](semantic-bindings-v1.md#packsettings-set-3-binding-8-256-bytes).
|
||
A persisted user override wins the selected preset override, which wins the
|
||
declaration default. Overrides are stored by stable pack ID plus setting ID;
|
||
the host validates the selected descriptor's kind, invariant numeric grammar,
|
||
range, step, and choice list before supplying the resolved scalars. No renderer
|
||
object is exposed to managed code.
|
||
|
||
When a pack is selected, the retained Config page appends its declared
|
||
Boolean, bounded Float/Integer, and Choice controls under **Graphics
|
||
Enhancements**. Changing packs replaces only that optional tail; retail's 39
|
||
authored Config rows remain unchanged. Numeric controls snap to declared bounds
|
||
and steps, preset changes retain explicit user overrides, and a pack change
|
||
starts with an empty valid override map for the new stable pack identity.
|
||
|
||
## Manifest
|
||
|
||
The authoritative machine-readable schema is
|
||
[`plugin-manifest-v1.schema.json`](plugin-manifest-v1.schema.json).
|
||
|
||
| Field | Meaning |
|
||
|---|---|
|
||
| `id` | Stable lowercase logical plugin ID. It is persisted and must not be localized or reused. |
|
||
| `displayName` | User-visible plugin name. |
|
||
| `version` | Dotted `System.Version`-compatible package version. |
|
||
| `entryDll` | Safe path beneath the plugin directory to the managed entry DLL. |
|
||
| `apiVersion` | General `PluginApi` version. v1 is `1`; this is distinct from `RenderPackApi`. |
|
||
| `dependencies` | Optional plugin IDs that must load first. |
|
||
| `kinds` | Entry-point kinds. Include `renderPack`; omission means legacy `gameplay` only. A hybrid lists both. |
|
||
|
||
Install one plugin directory containing this manifest, the entry DLL, its
|
||
private managed dependencies, and declared shader assets. Do not redistribute
|
||
`AcDream.Plugin.Abstractions.dll` in that directory: type identity is shared
|
||
from the host.
|
||
|
||
## Declaration schema
|
||
|
||
The C# records in `AcDream.Plugin.Abstractions.Rendering` are the public v1
|
||
declaration schema. `RenderPackShaderAbi` publishes the corresponding numeric
|
||
SPIR-V set, binding, block-size, and capacity constants. They are intentionally
|
||
BCL-only and expose no Vulkan handle.
|
||
|
||
| Declaration | What the pack supplies | What the host owns |
|
||
|---|---|---|
|
||
| `RenderPackDescriptor` | Identity/version, highest tier, capabilities, resources, passes, replays, variants, presets, settings, atmosphere policy | Validation, candidate creation, activation and fallback |
|
||
| `RenderResourceDeclaration` | Logical ID, portable format, extent, usage, lifetime, estimated bytes | Images/buffers, allocation, barriers, frame-flight retirement |
|
||
| `RenderPassDeclaration` | Fixed hook, shader asset keys, semantic inputs, logical resource reads/writes | Render graph order, descriptor layout, pipeline, command recording |
|
||
| `SceneReplayDeclaration` | One supported replay semantic, caster flags, 1–4 views | Resident caster selection and existing batched submissions |
|
||
| `PipelineVariantDeclaration` | Base pipeline semantic, shader assets, compatible material flags, inputs | Visibility, mesh/material ownership, fixed renderer state |
|
||
| `RenderQualityPreset` | Capability requirements, resource/setting overrides, optional execution hints, and p50/p99 CPU/GPU/VRAM ceilings | Availability, explicit selection and stable-boundary swaps |
|
||
| `RenderSettingDeclaration` | Stable ID, kind, default, bounds/choices | Persistence and conditional Display UI |
|
||
| `AtmospherePolicyDeclaration` | Ordered sun and selected-light elevation curves plus explicit `activeDayGroup` multipliers | Authored Dereth clock, celestial source, day group, weather and indoor state |
|
||
|
||
IDs use `^[a-z][a-z0-9._-]*$`, are case-insensitively unique within each
|
||
declaration kind, and remain stable across updates. A pack must declare at
|
||
least one quality preset and at most 64 settings. The SDK ceiling is 256 MiB pack-owned resident GPU
|
||
memory, 16 MiB per SPIR-V asset, 16,384 pixels per absolute image dimension,
|
||
256 image layers, and four scene-replay views. A physical device may expose a
|
||
lower ceiling or reject a preset whose mandatory capabilities are absent.
|
||
For API v1 the host admits optional pack memory from one eighth of the selected
|
||
adapter's probed device-local heaps, capped at 256 MiB resident and 512 MiB
|
||
transient multisample storage. Presets remain listed with exact limit reasons.
|
||
Auto requires asynchronous timestamps and uses Low when Medium cannot fit. At
|
||
runtime, Auto alone watches the selected preset's declared inclusive-GPU p99,
|
||
pack-added CPU p99, and resident-GPU budgets. If Low remains over any of those
|
||
budgets for 180 stable samples, the whole pack fails safely to acdream's
|
||
default renderer with the measured and declared limits in the failure reason.
|
||
Explicit Low remains selectable and is not silently disabled by the Auto
|
||
performance policy.
|
||
|
||
A Tier-2 directional-shadow elevation curve must resolve to exactly zero at
|
||
and below the authored 0-degree horizon. Every declared non-positive point
|
||
must therefore have multiplier `0`; if the curve omits an exact 0-degree
|
||
point, its first positive point must also be `0` so endpoint clamping or
|
||
interpolation cannot manufacture a below-horizon directional shadow. The host still
|
||
owns the independent no-selected-light-energy and indoor gates.
|
||
|
||
The built-in Atmospheric Low preset preserves the complete directional-shadow
|
||
caster set (terrain, opaque and alpha-cutout world geometry, and both animated
|
||
classes). It reduces cost with two 768 x 768 shadow maps and the ordinary
|
||
six-pass, quarter-resolution separable post chain: sun occlusion, sun rays,
|
||
bloom downsample, horizontal blur, vertical blur, and filmic composition. It
|
||
does not remove a caster class or use the fused post-process hint.
|
||
|
||
`FusedAtmosphericPostProcess` is an optional external-pack Low-preset execution
|
||
hint for the standard atmospheric graph; it is not built-in Low behavior. An
|
||
opting-in shader pack implements the PackPass ABI below: the host feeds scene
|
||
depth directly to sun rays and asks filmic to evaluate the declared bloom
|
||
extraction and separable filter while composing the final image. This reduces
|
||
command recording without disabling rays, bloom, or filmic composition. The
|
||
host never infers the hint from pack identity; unknown hints, non-Low use, and
|
||
incomplete standard graphs fail validation.
|
||
|
||
`MultiviewDirectionalShadowCascades` is a separate explicit Low-preset
|
||
execution hint. The opting-in pack must implement three multiview caster
|
||
variants. The host records one layered directional-depth
|
||
pass with view mask `0b11`; `gl_ViewIndex` selects the exact two declared Low
|
||
cascade matrices. Terrain, opaque, and alpha-cutout commands retain their
|
||
ordinary pipeline, transform, cull, and cutout semantics. The preset must require
|
||
`MultiviewDirectionalShadowCascades`; unsupported hardware makes that Low preset
|
||
unavailable before allocation. A zero hint retains ordinary per-cascade passes.
|
||
|
||
Resources are declared in execution order: a pass cannot read a pack resource
|
||
before an earlier pass writes it, and one pass cannot read and write the same
|
||
resource. `WorldColor`, `SceneDepth`, and other renderer semantics are not pack
|
||
resources and are named in `SemanticInputs` instead. API v1 exposes four
|
||
sampled pass-input slots; buffers, `StructuredData`, and storage resources are
|
||
reserved enum values and are rejected until a public binding contract exists.
|
||
Colour image arrays are likewise reserved; v1 arrays are directional-depth
|
||
maps. One declared pass writes at most one attachment. Only `ToneMap` and
|
||
`AfterToneMapBeforePrivateViewports` may write directly to the host surface
|
||
without naming a pack resource.
|
||
|
||
Every semantic input implies its capability and the descriptor must list that
|
||
capability as required: world colour, scene depth/normals, authored sun/selected-
|
||
celestial/day/weather facts, animation transforms, and directional maps cannot
|
||
be treated as
|
||
optional after a pass unconditionally declares them.
|
||
|
||
## Lifecycle and versioning
|
||
|
||
- Discovery calls `Register` but does not open assets or allocate GPU objects.
|
||
- Installing a pack never selects it. The user selects a pack ID, version, and
|
||
preset; `acdream default (retail-faithful)` is always available.
|
||
- The client validates every declaration and selected asset, builds the full
|
||
candidate beside the active retail graph, then swaps at a frame boundary.
|
||
- Dispose the registration handle to withdraw the descriptor. The host also
|
||
withdraws every handle before unloading its collectible plugin context.
|
||
- `PluginApi` versions the general managed plugin ABI. `RenderPackApi` versions
|
||
these graphics declarations. Additive enum/record support stays compatible;
|
||
a breaking contract requires a new render-pack API version and explicit
|
||
compatibility path.
|
||
- `RenderPackShaderAbi.ShaderAbiVersion` separately versions the numeric
|
||
SPIR-V interface (set/binding numbers and std140 block layouts) declarations
|
||
are validated against — distinct from `RenderPackApi`/`PluginApi`. Campaign
|
||
VM VM6 shipped v2: `AtmosphericFrame` (set 3, binding 5) grew additively
|
||
from 160 to 192 bytes (see `docs/render-packs/semantic-bindings-v1.md`'s
|
||
"ABI v2 (additive)" section). `RenderPackSpirvValidator` accepts both the
|
||
v1 and v2 shapes, so shader assets compiled before a version bump — the
|
||
external sample packs among them — never need a rebuild for an additive
|
||
change.
|
||
- Persisted identity is pack ID + pack version + preset ID, never list index.
|
||
User-authored setting strings are keyed by the same stable pack identity and
|
||
stable setting ID, never declaration or menu index.
|
||
|
||
"Device recreation" in the v1 SDK means full teardown of the old renderer,
|
||
graphics context, and device, followed by construction and capability probing
|
||
of a fresh context/device. Retail is authoritative until a fresh pack candidate
|
||
validates and activates. The SDK does not promise live recovery of a pack or
|
||
renderer after `VK_ERROR_DEVICE_LOST`; that error is terminal to the old device
|
||
lifetime.
|
||
|
||
The complete campaign contract, budgets, and non-goals remain in
|
||
[`2026-08-21-atmospheric-rendering.md`](../plans/2026-08-21-atmospheric-rendering.md).
|
||
The public shader-facing contracts are the
|
||
[`semantic binding table`](semantic-bindings-v1.md) and
|
||
[`compatibility/failure guide`](compatibility-and-failure-v1.md).
|