khora_core/renderer/traits/pipeline_system.rs
1// Copyright 2025 eraflo
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! The shader/pipeline backend contract.
16//!
17//! `PipelineSystem` is a **backend** in the SAA sense — abstract trait here in
18//! `khora-core`, concrete impl in `khora-infra` (naga_oil + the wgpu device),
19//! injected via `runtime.backends`, consumed via the trait by render lanes and
20//! the material projection. Exactly the pattern of [`GraphicsDevice`] /
21//! [`RenderSystem`](super::RenderSystem): it keeps the lanes pure consumers —
22//! they no longer hand-roll bind-group layouts or pipelines, nor depend on a
23//! shader compiler.
24//!
25//! It centralizes:
26//! - **Layout cache** — canonical [`LayoutKey`] layouts (Camera/Model/Material/
27//! Lighting) created once and shared; bespoke layouts cached by label.
28//! - **Pipeline cache** — keyed by `(shader, variant, color_format)`, deduped.
29//! - **Shader variants** — specialization defs (`#ifdef`) keyed by
30//! [`ShaderVariantKey`].
31//! - **Hot-reload** — overlay sources + recompose (Phase 3).
32
33use crate::renderer::api::command::{BindGroupLayoutEntry, BindGroupLayoutId};
34use crate::renderer::api::pipeline::{
35 ComputePipelineId, ComputePipelineSpec, LayoutKey, PipelineSpec, RenderPipelineId,
36 ShaderVariantKey,
37};
38use crate::renderer::error::RenderError;
39use crate::renderer::traits::GraphicsDevice;
40
41/// Backend that compiles shaders and builds/caches bind-group layouts and
42/// render pipelines on demand. See the module docs.
43pub trait PipelineSystem: Send + Sync {
44 /// Returns the canonical bind-group layout for `key` under `variant`,
45 /// creating + caching it on first request. Both lit lanes (pipeline slot)
46 /// and the material projection (group-2 bind group) obtain layouts here, so
47 /// they share the same id.
48 fn layout(
49 &self,
50 device: &dyn GraphicsDevice,
51 key: LayoutKey,
52 variant: &ShaderVariantKey,
53 ) -> Result<BindGroupLayoutId, RenderError>;
54
55 /// Returns a bespoke (inline) bind-group layout, identified + cached by its
56 /// stable `label`, creating it on first request. Lanes with non-canonical
57 /// layouts (shadow / UI / overlay / unlit) obtain their layout ids here so
58 /// they can build ring buffers / bind groups against the same id the
59 /// pipeline was built with.
60 fn inline_layout(
61 &self,
62 device: &dyn GraphicsDevice,
63 label: &'static str,
64 entries: &[BindGroupLayoutEntry],
65 ) -> Result<BindGroupLayoutId, RenderError>;
66
67 /// Returns the render pipeline for `spec`, creating + caching it on first
68 /// request (keyed by `(shader, variant, color_format)`). Resolves the
69 /// spec's bind-group layouts, compiles the shader for the variant, and
70 /// builds the pipeline. Lanes call this every frame; it is a cache hit
71 /// after the first.
72 fn pipeline(
73 &self,
74 device: &dyn GraphicsDevice,
75 spec: &PipelineSpec,
76 ) -> Result<RenderPipelineId, RenderError>;
77
78 /// Returns the compute pipeline for `spec`, creating + caching it on first
79 /// request (keyed by `(shader, variant)`). Resolves the spec's bind-group
80 /// layouts, compiles the shader for the variant, and builds the pipeline.
81 /// Used by Forward+ light culling.
82 fn compute_pipeline(
83 &self,
84 device: &dyn GraphicsDevice,
85 spec: &ComputePipelineSpec,
86 ) -> Result<ComputePipelineId, RenderError>;
87
88 /// Hot-reload: override a logical shader source (lib or pipeline) with new
89 /// text, marking it (and dependent pipelines) for recompose. Default no-op
90 /// until the hot-reload phase wires it.
91 fn set_overlay_source(&self, _logical_path: &str, _source: String) {}
92
93 /// Hot-reload: recompose any modules marked dirty by
94 /// [`set_overlay_source`](Self::set_overlay_source) and rebuild the
95 /// affected cached pipelines in place. Default no-op.
96 fn recompose_dirty(&self, _device: &dyn GraphicsDevice) -> Result<(), RenderError> {
97 Ok(())
98 }
99}