Skip to content
hikari
RenderingEditorAIToolchainDocumentation
Download

© 2026 Flying Rat Studio.
All rights reserved.

Explore the engineDocumentationContributorsDownloadLicenseBack to top
Documentation / Systems
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
Systems8 min read

Pipeline compilation

On this page
On this pageAsynchronous pipelinesWho waits, and whenScene readinessDeferring what the config does not useWarm-upShader strippingD3D12 pipeline libraryVerifyingRay-tracing build scratch Back to top

A shader artifact is not GPU code. A metallib holds AIR and a D3D12 shader holds DXIL; the driver translates either into the GPU's instruction set when a pipeline is built from it. That translation is what a player waits on when a pipeline is built on the frame's critical path: behind the startup cover on first launch, or as a hitch when a setting or a new kind of content first needs a pipeline.

Hikari does not ship pre-translated GPU binaries, and packaging runs nothing. Like Unreal's PSO precaching and Unity's warm-up collections, it builds pipelines on the player's machine, in parallel, before they are needed, and relies on a persistent cache so the work happens once per machine:

BackendParallel buildsPersistent cache
MetalMTL4Compiler asynchronous builds, shouldMaximizeConcurrentCompilationthe OS per-app shader cache ($(getconf DARWIN_USER_CACHE_DIR)/<bundle id>/com.apple.metal)
D3D12the device's pipeline builder, a worker pool sized to the CPUan ID3D12PipelineLibrary per adapter, <writable>/pipelines/d3d12-<vendor>-<device>.plib

On an M-series Mac, the 69 engine compute kernels take about 15 s to build one after another from a cold cache, and about 4.5 s in parallel. The longest single kernel (ray-traced reflections, about 3.3 s) is the floor. With a warm cache the same set builds in under 10 ms.

Asynchronous pipelines

Pipeline creation is asynchronous at the native layer (native/macOS/src/MetalPipelineBuild.m, native/Windows/src/D3D12PipelineBuild.cpp). A create call queues the build and returns a pipeline at once. The pipeline has a status: pending, ready, or failed.

  • Binding a pipeline waits for its build if it is still running. On D3D12, a build no worker has started yet runs on the binding thread instead of waiting in line. Every wait is counted in the pipeline stats.
  • A failed build binds as "nothing": the draws and dispatches that follow it are skipped, and the failure is logged once. The renderer also stops naming it (renderer_pipelines.dropFailedPipelines), so feature readiness, which checks pipeline handles, degrades that feature to off. Failures never degrade to a pass that binds nothing.
  • Releasing a pipeline that is still building never blocks. The build holds its own reference and frees the pipeline when it ends.

Because every pipeline goes through this path, every creation site builds in parallel: engine passes, compute kernels, GPU culling, particles, skinning, opacity micromaps, and materials.

Who waits, and when

PipelinesBuiltWaited on
Startup set: engine passes and compute kernels the project's config enables, shadow casters, GPU cullingqueued during device initonce, at the end of device init (settlePipelines), behind the startup cover
Scene materialswhen the material loads (material/pipeline_preparation.zig)never. Draws use a stand-in until the build is ready, and gated scene loads count pending material pipelines as not ready.
Particle program kernelswhen the particle system is published to the renderer, before it is drawn (Executor.precacheRegistered)never. Gated loads and the world-partition first ring count them as not ready (see below); an emitter that runs sooner joins the simulation once both kernels are ready.
Particle lightingon first use, or by warm-upnever. Receivers fall back until it is ready.
Everything else a player can reach laterbackground warm-up, after startuponly if used before warm-up reaches it

A pipeline the startup set cannot do without (an eager engine pass the config enables) fails device init if it cannot be built, as it did when pipelines were built one at a time.

Scene readiness

A scene layer reaches .gpu only when everything it will draw can draw without a stall (Session services — readiness stages). For pipelines that means:

  • Materials: each material's pending-pipeline count, held until the native build reports ready.
  • Particle systems: the program's specialized kernels and the sprite uploads. Both start when the component publishes its program (scene/components/particle.zig publishProgram), not on first draw, because a gated layer is not drawn until it is ready. The component holds a ProgramReadiness (graphics/particles/program_readiness.zig) that the render residency resolves once the executor has built every kernel of the program, and the layer counts it while pending. An unbuilt program also counts as pending pipeline work, so an idle editor viewport keeps encoding until it resolves.
  • Failures resolve. A failed build, a refused registration or a program released before it built answers failed: the effect degrades, the gate opens.

World partition uses the same bar. Cells load .immediate, and World.streamWarmingUp stays true until the first ring is resident and every loaded layer is at .gpu (Scenes and gameplay — World partition).

Deferring what the config does not use

Engine passes and compute kernels carry a feature gate (engine_pso.Gate; engine_pso.computeGate for compute). Device init builds only what the current config opens, and only on a device that supports it: a ray-traced gate stays closed on a GPU that cannot trace. A ray-traced feature that is switched off does not cost startup its multi-second kernels.

The config device init reads is the one the player launches with, not the project default:

  1. the project's configs/render.json;
  2. the player's saved settings: the sparse configs/render.json in the user layer, written by hi.config (Project file — environments), composed before the renderer is created;
  3. graphics settings the game applies while it launches (hi.render().update from onLaunch or a world-attach hook). Device init takes these from the world's command queue before it builds anything (graphics_ops.initializeRenderDevice).

So a game whose player turned ray tracing on builds its ray-traced passes behind the startup cover, and a game whose player turned it off never builds them at startup.

When a config change opens a gate, the pipelines it needs are created at once (ensureOptionalPipelines, and the next warm-up tick for compute kernels). Frame features resolve against pipeline availability (featureAvailability), so a feature whose pipelines do not exist yet is simply off until they do.

Warm-up

graphics/renderer/pipeline_warmup.zig runs once per frame on the render thread. It starts as soon as the startup scene's material pipelines are built (or after about five seconds if content keeps streaming). It then adds work in steps, likeliest first:

  1. subsystems a scene reaches without a settings change: particle simulation, raster, shadows and lighting, skinning writeback, and the opacity-micromap bake on devices that trace;
  2. the features on the other side of the ray-tracing switch. From a config that traces, that is ray tracing off. From one that does not, it is ray tracing on with every enabled feature at its ray-traced mode: screen-space GI becomes ray-traced GI, raster shadows become ray-traced shadows. A feature the config leaves off is not in this step;
  3. every other feature this device supports and the config keeps closed.

Each feature is one step: its compute kernels and its engine passes together, so a feature becomes usable as a unit instead of waiting for every other feature's kernels. A step is added only while few builds are in flight, so a scene's material pipelines never queue behind warm-up and a running game keeps its CPU.

The order decides what is ready when a player opens the options menu early, not how long the whole set takes. The Metal compiler runs about 18 builds at once, so submitting the slowest kernels first does not shorten a batch: on an M-series Mac all 69 engine kernels take 4.5 s cold in directory order, longest first, or shortest first.

Warm-up builds through the same code that builds on demand, so what warms is exactly what runs. When everything has built, D3D12 persists its pipeline library, and the log reports how many pipelines warm-up built and how long it took.

Warm-up skips the HDR display set of an output mode that is not active. Switching HDR builds that set in parallel and waits for it before the swapchain flips.

Shader stripping

Warm-up and the startup set only build engine pipelines, and engine renderer shaders are runtime roots of shader stripping: a runtime-selectable feature always ships its shaders. Content pipelines (materials, particle programs) are built only from content that loads, and content can only reference shaders the product ships. Nothing builds a pipeline from a stripped package.

D3D12 pipeline library

Every D3D12 build first asks the library (native/Windows/src/D3D12PipelineCache.cpp). Entries are named by a 128-bit hash of the root-signature identity, the shader bytecode, and every state input. The runtime re-validates the desc on load, so a stale entry costs a compile, never a wrong pipeline. A driver update invalidates the whole library, and it is rebuilt that launch. The library is written when warm-up finishes and again at device release. Bump kTranslationVersion whenever the hikari desc → D3D12 desc translation in D3D12Pipeline.cpp changes.

Verifying

  • A standalone game logs one line when the startup cover lifts:

    text
    pipelines: 214 built (0 from cache, 0 failed, 0 still building); 0 binds waited 0.0 ms

    Bind waits after startup mean something was created too late for warm-up to hide it. The startup log's pipeline_creation stage is the time device init waited for the startup set.

  • pipeline warm-up: N pipelines in X ms marks the end of warm-up.

  • To reproduce a player's first launch on macOS, move the per-app OS cache ($(getconf DARWIN_USER_CACHE_DIR)/<bundle id>/com.apple.metal) aside and launch. On Windows, delete <writable>/pipelines. Both drivers also keep their own caches, so a truly cold launch may also need the driver cache cleared.

Ray-tracing build scratch

Metal acceleration-structure submission contexts own their allocator and scratch until completion. Scratch starts at the aligned hardware requirement and grows geometrically with batch demand, up to a 128 MiB arena target (or a larger single-build requirement). Growth retains any buffer already referenced by recorded builds until their submission completes. Independent ranges remain disjoint; wrapping the arena inserts the acceleration-stage barrier before reuse. Completed contexts cache their earned capacity, so tiny batches no longer reserve a 128 MiB minimum per context.

PreviousFrame governorNext Skeletal animation

Documentation follows the current engine checkout.

Snapshot 86f12d68Source docs/systems/pipeline-compilation.md
On this pageAsynchronous pipelinesWho waits, and whenScene readinessDeferring what the config does not useWarm-upShader strippingD3D12 pipeline libraryVerifyingRay-tracing build scratch Back to top