Skip to content
hikari
RenderingEditorAIToolchainDocumentation
Download

© 2026 Flying Rat Studio.
All rights reserved.

Explore the engineDocumentationContributorsDownloadLicenseBack to top
Documentation / Guides
Browse docs
Overview
Tutorials17
OverviewPlay, Edit, and scenesFirst UI documentFirst game projectFirst entityComponent properties and the inspectorFirst mesh and materialFirst physics body and triggerFirst character controllerFirst session servicesFirst UIFirst input actionFirst messagesFirst runtime spawnFirst motionFirst skeletal animationAssets in Play (soft refs and hot reload)Dynamic editor recompile
Guides25
OverviewWorking in the editorDevelopment guideGameplay APIActor and component lifecycleThreads, jobs, events, and routinesGameplay AI tasksGame AI: perception and movement playgroundBrain assetsBrain execution for swarm populationsLocalizationGame cursorsChoosing component storageBuild and packagingProject filePluginsHikari Plugin APIData-driven content, JSON, and pathsScripting with KawaShader authoringChoosing and scaling shadowsTime of dayUser interfaceUI layoutUI widgetsMigration from Unity / Unreal
Systems32
OverviewArchitectureApplication lifecycleFrontends and driversPlatforms and supportSession services and cross-scene stateScenes and gameplayActor communication (hi.actors)Game-facing refsSave / replication wire versionCoordinate space and camera conventionsRenderingRenderer architecture mapFrame governorPipeline compilationSkeletal animationGPU particlesVisual ZonesVolumetric mediaInputAudioPhysicsMotion KitTemporal KitUI documentsAssets and ShinraPrefabsAsset residencyAsset formats (Shinra pipeline)Document schemasUI and editorEditor asset hot reloadEditor Project Selector
Language reference3
OverviewAkari GPU ABIAkari language referenceKawa language reference
Engine overview
Start exploring
  • No matching sections. Try fewer words or another topic.
NavigateEnter Openesc Close
Overview
Tutorials17
OverviewPlay, Edit, and scenesFirst UI documentFirst game projectFirst entityComponent properties and the inspectorFirst mesh and materialFirst physics body and triggerFirst character controllerFirst session servicesFirst UIFirst input actionFirst messagesFirst runtime spawnFirst motionFirst skeletal animationAssets in Play (soft refs and hot reload)Dynamic editor recompile
Guides25
OverviewWorking in the editorDevelopment guideGameplay APIActor and component lifecycleThreads, jobs, events, and routinesGameplay AI tasksGame AI: perception and movement playgroundBrain assetsBrain execution for swarm populationsLocalizationGame cursorsChoosing component storageBuild and packagingProject filePluginsHikari Plugin APIData-driven content, JSON, and pathsScripting with KawaShader authoringChoosing and scaling shadowsTime of dayUser interfaceUI layoutUI widgetsMigration from Unity / Unreal
Systems32
OverviewArchitectureApplication lifecycleFrontends and driversPlatforms and supportSession services and cross-scene stateScenes and gameplayActor communication (hi.actors)Game-facing refsSave / replication wire versionCoordinate space and camera conventionsRenderingRenderer architecture mapFrame governorPipeline compilationSkeletal animationGPU particlesVisual ZonesVolumetric mediaInputAudioPhysicsMotion KitTemporal KitUI documentsAssets and ShinraPrefabsAsset residencyAsset formats (Shinra pipeline)Document schemasUI and editorEditor asset hot reloadEditor Project Selector
Language reference3
OverviewAkari GPU ABIAkari language referenceKawa language reference
Engine overview
Guides16 min read

Shader authoring

On this page
On this pagePass roles and packagesSource provenance and distribution noticesPSO shareAkari file headersLanguage surface (authoring)Binding manifestDomainsSamplersRender targetsAuthored foliage normalsShader specificationsVulkan binding modelProcessing orderShipping shader packagesValidationFormatAuthoring rules Back to top

Akari (src/akari/) owns the shader product path end-to-end: shader package sources under shaders/akari/packages/*.akari, shared modules under shaders/akari/modules/*.akari, transpile → MSL/HLSL (bindings resolved at emit), platform compile (xcrun metal / DXC / DXC -spirv). Metal metalc uses -std=metal4.0 and -mmacosx-version-min=26.0. Specs: build.akari.metal.json / build.akari.d3d12.json / build.akari.vulkan.json (type KhronosVulkan; same package list as the other two). Vulkan compiles through the vendored DXC in SPIR-V mode, so it cooks on any host — no Vulkan SDK.

Language reference (all syntax and features): Akari language. Grammar: src/akari/syntax.ebnf.
Editor highlighting: tools/vscode-extensions/akari (VS Code / Cursor); tools/nvim-plugins/akari (Neovim / Vim).
Product binary: flat bin/akari/akari (not platform-arch). Shinra invokes via --akari-sc. ABI packing: docs/reference/akari-abi.md.

There is no dual-native metal/ / d3d12/ / *_includes/ authoring tree. Ray tracing uses first-class intrinsics (rt_visibility, rt_trace, bindless loads).

Note

Vulkan shader cooking is available through Akari and DXC even though the Linux/Vulkan runtime is not yet a product peer. Compiling SPIR-V does not imply full runtime support.

One authored package feeds each target's compiler:

Diagram
Diagram source
flowchart TD
    source["Akari packages + shared modules"] --> akari["Akari — validate and emit resolved bindings"]
    spec["Target build specification"] --> akari
    akari --> msl["MSL"]
    akari --> hlsl["HLSL"]
    msl --> metal["Metal compiler — Metal 4 / macOS 26"]
    hlsl --> dxc["DXC — D3D12 target"]
    hlsl --> spirv["DXC -spirv — Vulkan target"]
    metal --> metalout["Metal shader artifacts"]
    dxc --> dxout["D3D12 shader artifacts"]
    spirv --> vkout["SPIR-V shader artifacts"]

Pass roles and packages

One package ≈ one pass role. Materials pick a package; they do not invent keyword sets. Graph pass order and gates: Rendering — Implemented pipeline.

RolePackagePipelineKindLayout
Opaque deferred geometry_engine/gbuffer (or game package)gbuffer_geometrystandard
Soft-ref / missing material_engine/error_materialgbuffer_geometrystandard
Transparent forward_engine/forward_litforward_transparent / forward_transparent_hdrforward_lit
Shadow depth_engine/shadow_depthshadow_depthstandard
Shadow depth, alpha-cut casters_engine/shadow_depth_maskedshadow_depth (same state; the material carries the PS)standard
Deferred lighting_engine/deferred_lightlighting / hdr_lightingfullscreen_shadowed
Hybrid-shadow lighting_engine/hybrid_lightlighting / hdr_lightingfullscreen_hybrid_shadowed
RT-shadow lighting_engine/raytraced_lightlighting / hdr_lightingfullscreen_raytraced
Screen-space AO_engine/screen_space_ambient_occlusion_computeambient_occlusionfullscreen_reflections
Ray-traced AO_engine/ray_traced_ambient_occlusionambient_occlusionfullscreen_raytraced
Screen-space reflections_engine/screen_space_reflectionshdr_lightingfullscreen_reflections
Ray-traced reflections_engine/ray_traced_reflectionshdr_lightingfullscreen_raytraced
TAA resolve_engine/temporalhdr_lightingfullscreen_reflections
Tonemap (ACES)_engine/tonemaptonemap (TONEMAP=aces)fullscreen_texture_table
Tonemap (Reinhard)_engine/tonemap_reinhardtonemap (TONEMAP=reinhard, same source)fullscreen_texture_table
Editor viewport blit_engine/compositetonemapfullscreen_texture_table
Debug G-buffer grid_engine/debug_visualizersdebug_visualizersdebug_visualizers
UI_engine/uiuiui / ui_atlas
Camera meshlet cull (compute)_engine/meshlet_cull(compute PSO, not mesh PipelineKind)standard
RT opacity micromap bake (compute)_engine/opacity_micromap_bake(lazy renderer-owned compute PSO)standard

Other engine packages (compute / post, not material roles) live beside these in src/hikari/shaders/akari/packages/: bloom, exposure, depth of field, motion blur, auto-focus, GI (screen-space, ray-traced, temporal/spatial/bounce), particles, volumetrics, atmosphere and clouds, light binning, skinning writeback, shadow meshlet cull, deferred decals.

Game-authored packages (sample project: hikari-games/example/assets/shaders/) stage under data/shaders/, not _engine/shaders/. Materials may set "binding_layout": "mesh_vertex_texture" and optional "async_compute": { "package", "entry", "resolution", "threads", "format", "lag_frames" } to drive content compute (producer key = package/entry/resolution/threads/format; lag_frames 0|1 only; prefer independent compute queue, fall back to graphics; format omit/r32f, rg16f, or rgba16f). Encode writes the selected ring textures onto GpuMaterialRecord.vertexTextureIndex / previousVertexTextureIndex in the bindless heap, so deforming vertex shaders can emit deformation-correct motion vectors. Producer kernels may sample their own prior ring result through content_compute.history; gate it with frame.historyValid and use simulation-clock frame.deltaTime for temporal evolution. Producers may also consume up to 64 game-published opaque records through content_compute.inputs / frame.inputCount; update them in one batch with hi.render().setContentComputeInputs. The engine tracks opaque vs transparent consumers so async-compute acquisition occurs at the first real sampling pass. Sample: packages water + water_sim.

Temporal geometry contract: opaque GBufferOutput.depth is float2(currentViewDepth, previousViewDepth); compute the previous depth from the same previous pose used for motion and write a nonpositive value when there is no correspondence. Forward/OIT entry points (including fallbacks) declare a third output float4(previousViewDepth, valid, 0, coverage); hikari_oit_encode weights it like colour. Particles without previous poses emit validity zero.

Composability: geometry packages (including content like water) write the deferred G-buffer only. They must not hardcode RT, deferred vs RT lighting, or other materials. Lighting / AO / reflection packages read the G-buffer (and optional TLAS) only. Content compute packages write a storage texture consumed through those bindless material heightfield indices. Full contracts: Rendering — Rendering contracts.

Shared forward BRDF/IBL helpers accept an optional trailing dielectric_f0 (float3(0.04) by default) and specular_occlusion (-1 = use the material AO). Engine forward_lit / forward_lit_rt derive F0 from IOR (hikari_dielectric_f0) and pass that same value to direct lighting, IBL, and the transmission mix. A transmitting interface is a dielectric: metalness is scaled by (1 - transmission) so it cannot steal F0, and IBL specular is not multiplied by baked AO (the Fresnel lobe is the environment, not a cavity). thickness == 0 is thin-walled: the raster path still walks the refracted ray by one pixel of world length at the surface depth (Snell in screen space, no Beer-Lambert). Volumes keep using authored thickness.

Formats, blend, and depth write live on PipelineKind, not shader #ifdef. Optional map presence uses a runtime map mask in the material row (HikariGpuMaterial.material.w, mirror of GpuMaterialRecord) (dynamic branches), not compile-time permutations.

Compile-time choices are const parameters with named instances (e.g. the tonemap operator). Never emit a Cartesian product of material features. Lighting-family scene textures (G-buffer, shadow atlas, skybox, captures, …) are bindless: index them through @bind(lighting.resource_indices) (HikariLightingResources), not fixed HK_TEXTURE(lighting,…) slots. The language still allows resource arrays (TexCube[N] / Tex2D[N]) — see the language reference.

Source provenance and distribution notices

Record adapted shader code and its applicable permission in src/hikari/shaders/PROVENANCE.md; retain complete upstream notices in src/hikari/shaders/THIRD_PARTY_NOTICES.txt. A technique citation alone does not establish permission to copy its reference implementation. Record equation-based implementations separately from source adaptations, and preserve inherited notices.

Kaji includes the shader notices in game and editor products as licenses/HIKARI-SHADER-NOTICES.txt (Contents/Resources/licenses/ inside a macOS app bundle). The bundled editor SDK also carries the notices and provenance registry beside its shader source modules. Notice changes invalidate the relevant staging steps. Keep the notices when distributing shader sources separately. Packaging fails if a registered product notice is missing from the staging tree; rebuild package-host to restore it.

PSO share

Mesh PSOs are keyed by (package, vertex entry, fragment entry, PipelineKind) and shared across materials. Residency still refcounts handles.

Native shader programs are independently cached by package, entry points, binding layout, shader model, and AssetStore content version. Material instances retain the immutable program while keeping per-material compute/culling state. The cache drops its strong reference on shader-source rebind or hot reload; old programs survive only while deferred GPU materials still reference them. Unused programs age out after 120 frames, so streamed chunks do not grow an immortal shader cache.

Akari file headers

One root per file, then optional headers, then items (struct / fn / …):

akari
#shader composite
#layout fullscreen_texture_table
#import preamble, fullscreen_triangle

@vertex
fn composite_vertex(@vertex_id vid: uint) -> V { … }
DirectiveRole
#shader name / #module nameFile kind + name
#version NSyntax version (default 2)
#layout nameBinding layout contract (#shader only)
#import a, bPure modules from akariImportPaths
#require a, bPackage capability declarations (optional; also inferred from use)

Stages are attributes on functions (@vertex / @fragment / @compute(threads=(x,y,z))), not text pragmas. D3D12 emit splits one entry per file because DXC produces one stage artifact per invocation.

Current layouts: standard, fullscreen_texture_table, fullscreen_shadowed, fullscreen_hybrid_shadowed, fullscreen_reflections, forward_lit, forward_lit_rt, fullscreen_raytraced, debug_visualizers, ui / ui_atlas, mesh_vertex_texture.

Language surface (authoring)

Full syntax, tables, and worked examples: Akari language. Grammar: src/akari/syntax.ebnf.
GPU packing: docs/reference/akari-abi.md.

Array textures sample with a packed coord (e.g. Tex2DArray uses float3(uv, layer)); Metal emit splits layer from the coord. Prefer @bind over raw @slot except non-manifest compute experiments.

Binding manifest

Authoring uses @bind(group.name) / @target(group.name) / @sampler(…). @bind and @target resolve slots from bindings.kaji.json. Manifest keys keep the historical HK_* spelling:

text
HK_BUFFER(domain,name)
HK_TEXTURE(domain,name)
HK_TARGET(domain,name)

@sampler(filter, address) (and the comparison form) is not a binding key: it names a state of the engine's static sampler set (static_samplers, below).

Unknown @bind / @target names are errors. Values come from the shared bindingManifest; they are not hardcoded in .akari source.

Domains

Slot numbers live only in bindings.kaji.json; author against the names. The domains you will meet:

DomainNamesWho reads it
frame / draw / geometryuniforms, object, verticesgeometry packages (mesh geometry is meshlet-pulled from meshlet.vis_tris / meshlet.works plus the bindless geometry table; shadow depth uses the same pull)
resourcestexture_table, geometry_samplersmaterial maps via GpuMaterialRecord indices; content-compute heightfields land on the same heap (vertexTextureIndex / previousVertexTextureIndex)
lightingframe, records, clusters, shadows, probes, resource_indicesdeferred / forward lighting, AO, GI, reflections. Scene textures are bindless through resource_indices (HikariLightingResources)
posthdr, lut, bloom_add, exposuretonemap / composite family
uivertices, atlasUI packages
debugalbedo … hdr (eight float slots the active visualizer assigns) plus material (Tex2D<uint>, its own slot)debug visualizers

Samplers

@sampler(...) states come from one engine-wide static sampler set, declared once in bindings.kaji.json under static_samplers:

NameStateD3D12Vulkan (set 2)
point_clamp@sampler(point, clamp)s00
linear_clamp@sampler(linear, clamp)s11
linear_repeat@sampler(linear, repeat)s22
comparison_greater_equal@sampler(comparison, greater_equal, linear, clamp) (reverse-Z shadow compare)s33

A sampler's register follows from its state alone — not from declaration order or from which samplers an entry happens to use — and every D3D12 root signature with space-0 samplers carries the whole set (binding_layout.static_samplers, which a test keeps equal to the manifest). Metal bakes the state as a constexpr sampler. A state outside the set is a compile error on every backend; adding one is a manifest row plus a binding_layout row. Two samplers with the same state in one entry share the register (HLSL aliases the second). The material geometry sampler catalog is separate (space 1, geometry_sample).

Render targets

G-buffer outputs use:

  • HK_TARGET(gbuffer,albedo)
  • HK_TARGET(gbuffer,normal)
  • HK_TARGET(gbuffer,arm)
  • HK_TARGET(gbuffer,depth)
  • HK_TARGET(gbuffer,material) (uint, not float)
  • HK_TARGET(gbuffer,motion)
  • HK_TARGET(gbuffer,anisotropy)
  • HK_TARGET(gbuffer,clearcoat)

They expand to Metal color indices 0 through 7 and HLSL semantics SV_TARGET0 through SV_TARGET7.

metal
float4 albedo [[color(HK_TARGET(gbuffer,albedo))]];
hlsl
float4 albedo : HK_TARGET(gbuffer,albedo);

Ordinary single-target HLSL fragment outputs continue to use native SV_TARGET.

Authored foliage normals

material.flags.preserve_authored_normals opts foliage out of the default two-sided policy (which mirrors the normal on back faces): G-buffer, forward, material-graph, and RT secondary hits keep the authored side of the tangent frame for both faces. Normal maps and clearcoat still perturb it. It is a normal policy only: alpha coverage, shadows, translucency, and the BRDF are unchanged, and double_sided is still set separately. Model sidecars set preserve_authored_normals: true in an exact-name model.material_overrides entry so recooks keep the choice; no name heuristic exists. No shader permutations or GPU record bytes are added (one flag bit in the material word and RT instance descriptor).

Shader specifications

The JSON specification version must be 2:

json
{
  "version": 2,
  "type": "AppleMetal",
  "bindingManifest": "bindings.kaji.json",
  "akariImportPaths": ["akari/modules/"],
  "compilation": [
    {
      "name": "gbuffer",
      "workingDirectory": "akari/packages/",
      "optimizationLevel": 0,
      "includeDebugSymbols": true,
      "sources": ["gbuffer.akari"]
    }
  ]
}

Per-entry optimizationLevel / includeDebugSymbols are author defaults. Product builds override them with Kaji --shaders:config=debug|release (defaults to product --config, unless explicitly overridden). That maps to:

--shaders:configoptimizationdebug symbols
debug0on
release2off

Akari passes the optimization flag explicitly to every native compiler, including -O0 for debug; compiler defaults do not choose the profile. DXC uses explicit HLSL 2021 mode to preserve scalar short-circuit expressions.

akari compile accepts --config=debug|release. Shinra forwards the same profile via --shaders-config= (hyphen; Clap cannot use : in long option names).

Spec entries take only the keys above (unknown keys are an error). One pass that the host loads as several packages — the tonemap operators — is one module plus one tiny package per choice: modules/tonemap_pass.akari holds the template entry with const operator: TonemapOperator, and packages/tonemap.akari, tonemap_reinhard.akari, tonemap_agx.akari and tonemap_filmic.akari each instantiate it as tonemap_fragment.

bindingManifest and akariImportPaths may be omitted when --engine-sdk can resolve bindings.kaji.json and engine akari/modules/ from the SDK tree.

The engine and game specifications both reference src/hikari/shaders/bindings.kaji.json. Each manifest entry maps one complete semantic expression to its Metal and D3D12 token, and optionally a Vulkan one:

json
{
  "version": 1,
  "bindings": {
    "HK_BUFFER(frame,uniforms)": {
      "metal": "1",
      "d3d12": "b0",
      "spirv": "0:1"
    }
  }
}

Keep one shared manifest for all packages so game-authored and built-in shaders cannot drift.

Vulkan binding model

Vulkan has no per-class register namespaces, so Akari fixes one descriptor-set layout (akari_emit_common::vulkan) that every Vulkan pipeline layout mirrors:

SetContentsBinding
0Uniform / Buf / RwBuf / AtomicBuf / AccelStructthe Metal buffer index
1sampled and storage texturesthe Metal texture index
2samplers; @sampler(...) states bind at their static_samplers spirv bindingthe Metal sampler index
3renderer-global: 0 bindless ByteAddressBuffer[], 1 Texture2D[], 2 TextureCube[], 3 Texture3D[], 4 Texture2D<float>[] (depth), 5 geometry sampler catalog SamplerState[18]fixed

An entry without a spirv value therefore needs no edit: its Metal index is the binding inside the class's set. "spirv": "set:binding" overrides that for one entry. A D3D12 register in the spirv column is rejected. Render targets use the Metal attachment index (SV_TargetN → Location N). @slot(N) keeps N in the class set. Storage textures carry [[vk::image_format]] for every format except float4, which stays Unknown (format-less writes). Compilation passes -fvk-use-dx-layout (D3D12 packing), -fvk-use-dx-position-w, and -fvk-invert-y on vertex stages, so CPU-side layouts and clip conventions are the ones D3D12 and Metal already use. Output is one <package>_<vs|ps|cs>.spv per stage entry.

Processing order

For each package, Akari:

  1. Parses the .akari package and transitive #import modules.
  2. Lowers AST → HIR → IR (specializing const, fn and optional parameters).
  3. Emits pure MSL, HLSL, or Vulkan HLSL (bindings resolved; #line + stage metadata).
  4. Validates emit purity (no leftover HK_* / #pragma hikari).
  5. Invokes Metal, DXC, or DXC -spirv.
  6. On failure, prints @akari diag {"sev","msg"} to stderr (machine), then human error: lines — metalc/DXC locations are remapped via structured line maps / #line to .akari when possible; Metal unknown-attribute / candidate-note noise is dropped.

Package failures are isolated. Akari completes every independent package in the spec and exits non-zero only after reporting the failed set. Successful packages publish atomically; a failed package does not replace its previous library. A shared manifest or imported-module error still fails every package that actually depends on that shared input.

Compile units own emitted source and line maps. Native compiler files are private to each invocation, so concurrent builds never read shared generated files. Native bytecode enters the cache from those private files before live artifact publication, keeping fingerprints independent of concurrent output replacement. Use akari transpile --out=<directory> to preserve source for inspection.

Native artifact names come from structured shader and entry metadata. Packages with multiple entries of the same stage retain each entry's full name in the artifact suffix, including when the package name differs from #shader. Cache inventories record every artifact's name, size, and content hash. A hit requires the complete verified set; a missing or corrupt member rebuilds the package. Hits stream each blob once, checking its length and hash while writing a private staging file; staging publishes only after the whole bundle verifies. Bytecode blobs and inventories publish through unique temporary files and atomic replacement, so readers never accept partially written cache data.

Shipping shader packages

Standalone game sealing uses the cooked asset manifest and pack plan to retain shader packages referenced by shipped materials (including generated materials and async_compute.package), plus explicit runtime roots and their transitive dependencies. Editors keep the complete compiled set. Particle-specialized programs remain embedded in their cooked particle asset.

An Akari compilation entry may declare:

json
{
  "shipping": { "root": true, "requires": ["water_sim", "_engine/gbuffer"] }
}

root: true retains shaders reached through runtime code or plugins even without a material reference. root: false retains a package only through references or another package's requires. Authored game packages default to root: true when shipping metadata is omitted; material-generated specs explicitly use root: false. Dependencies use the same package identities as AssetRef of kind shader, with _engine/ for engine packages. Every engine entry must declare its shipping policy; a test keeps policies aligned across the three backend specs. Current renderer packages are runtime roots because quality settings and fallback paths can change while the game runs. Do not mark an existing renderer root unreferenced merely because a startup setting is off.

Akari writes exact per-package artifact ownership to shaders.akari.json. Shinra caches typed material references with the existing manifest hashes and resolves shader closure in packs.shinplan.json. Kaji validates all retained artifacts before staging them from the cook tree, for both loose and bundled output; unresolved references fail sealing. No stage-filename prefix matching or scene-only reachability inference is used. The report at <project>/.engine/build/shader-stripping.json records package reasons and sizes. This pass strips whole packages at seal time; compilation still processes the specs, and native entry-point stripping is not performed.

Validation

Akari can validate metadata, imports, bindings, stages, layouts, and specializations without invoking DXC:

sh
bin/akari/akari compile \
  --spec=src/hikari/shaders/build.akari.d3d12.json \
  --out=build/cache/shader-validation/out \
  --cache=build/cache/shader-validation/cache \
  --engine-sdk=. \
  --validate-only

validate-only does not type-check native HLSL. The Windows build remains the authoritative DXC validation. The Vulkan spec has no such gap: without --validate-only it runs the vendored DXC in SPIR-V mode on every host, and DXC validates each module it writes, so the same command against build.akari.vulkan.json is a full compile check.

Validate the game's shaders against the engine's, not only the engine's. A game package that calls into an engine module is compiled against whatever that module currently declares, so an engine-side change can break a game shader that nobody touched — and the failure surfaces at cook time in the editor console, not in any Zig build. Point the same command at the game's spec with --engine-sdk naming the absolute repository or SDK root:

sh
bin/akari/akari compile \
  --spec=../hikari-games/example/assets/shaders/build.akari.metal.json \
  --engine-sdk="$PWD" \
  --out=build/cache/shader-validation/out \
  --cache=build/cache/shader-validation/cache \
  --validate-only

Always pass --out and --cache pointing somewhere disposable. Without --validate-only this recompiles every package and overwrites the staged metallibs for the given config.

HikariRasterLightCtx is a struct literal game shaders build, so a field added to it is a breaking change. Anything calling evaluate_lighting_forward and friends constructs one by naming every field, and Akari rejects a literal that misses one (missing field 'x' in 'HikariRasterLightCtx' literal) — there is no default, because the pack holds textures and samplers and neither can be zero-initialized. When the engine adds a resource to the light loops, every game shader that lights a surface needs the new field; mirror what akari/packages/forward_lit.akari does for it (the sample's water.akari is the worked example).

Occlusion enters lighting as two separate factors. The light loops take ambient_occlusion: float3, the diffuse occlusion of ambient-kind lights; build it with hikari_ambient_diffuse_occlusion(ao_pass, ao_baked, base_color), or pass float3(baked_ao) from a forward shader, which has no AO pass. hikari_eval_ibl takes the AO pass's visibility and the baked map as separate arguments (1.0 for the pass when none ran). Never pass their product: only the pass's estimate takes the multi-bounce lift.

Format

akari fmt reprints .akari source in the canonical style (4-space indent, semicolons, name: Type spacing, wrapped parameter/argument lists). It parses and pretty-prints; it does not type-check or compile. Comments are preserved.

sh
bin/akari/akari fmt src/hikari/shaders/akari
bin/akari/akari fmt --check src/hikari/shaders/akari
bin/akari/akari fmt --stdin < foo.akari

Directories are walked recursively for *.akari. --check lists files that would change and exits 1. Default writes in place.

Authoring rules

  • Author packages and modules as pure .akari only (#shader / #module, #layout, #import).
  • Bind with @bind(group.name) / @target(group.name) — not raw registers. @sampler(...) picks a state of the engine's static sampler set; its register follows from the state.
  • Keep CPU/GPU structure layouts synchronized with their Zig definitions (akari transpile --dump-layout / ABI rules in docs/reference/akari-abi.md).
  • Add a binding to the shared manifest and this document in the same change.
  • Treat missing imports and unknown bindings as errors; never substitute empty source.
  • Prefer runtime map masks over compile-time permutations for material features; keep const instances finite and named (tonemap operator, etc.).
  • Entry points that differ only by a constant (à-trous step, skinned/rigid, RT/raster) are one template with const parameters plus fn name = template(...) instances; the host still loads each instance by its name. Resources shared by a package's entries are declared once as package bindings (@bind(group.name) let name: T). See the language reference.
PreviousScripting with KawaNext Choosing and scaling shadows

Documentation follows the current engine checkout.

Snapshot 86f12d68Source docs/guides/shaders.md
On this pagePass roles and packagesSource provenance and distribution noticesPSO shareAkari file headersLanguage surface (authoring)Binding manifestDomainsSamplersRender targetsAuthored foliage normalsShader specificationsVulkan binding modelProcessing orderShipping shader packagesValidationFormatAuthoring rules Back to top