1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
use std::collections::HashMap;
use std::sync::Arc;
pub use super::effect::{EffectCache, EffectPipeline};
pub use super::params::{ParamDef, ParamValue};
/// Viewport-level post-processing effect ("veil").
/// Veils run on the fully-presented image at screen resolution,
/// after the view transform has been applied. They are ephemeral
/// editor state — they don't serialize with the document.
///
/// Unlike filters (which the compositor drives pass-by-pass),
/// veils get full control over their render passes via `encode()`.
/// This allows multi-resolution intermediate passes (e.g., downscale+upscale).
pub trait Veil: std::fmt::Debug {
fn type_id(&self) -> &'static str;
fn clone_boxed(&self) -> Box<dyn Veil>;
/// Return the current parameter values, in the same order as the
/// type's `ParamDef` array in `VeilRegistration`.
fn param_values(&self) -> Vec<ParamValue>;
/// Create GPU resources for this veil instance.
/// `ping_pong_views` are the veil chain's render textures — veils read
/// from and write to these at whatever resolution the chain provides.
/// When `rendering.veil_scale` is below 1.0 the chain passes smaller
/// textures automatically; veils never need to know about the distinction.
fn create_cache(
&self,
device: &wgpu::Device,
queue: &wgpu::Queue,
ping_pong_views: &[wgpu::TextureView; 2],
sampler: &wgpu::Sampler,
render_width: u32,
render_height: u32,
) -> EffectCache;
/// Per-veil resolution scale, applied on top of the global
/// `rendering.veil_scale`. Veils whose per-pixel cost is too high at
/// full viewport resolution override this to a value below 1.0; the
/// chain renders the veil at the reduced resolution and bilinearly
/// upscales the result. Effective scale is
/// `global_scale * perf_scale_factor`. Default `1.0` (no extra scaling).
fn perf_scale_factor(&self) -> f32 {
1.0
}
/// Whether this veil uses time-based animation.
/// When true (and speed > 0 and visible), the compositor drives
/// continuous re-rendering via `needs_present`.
fn needs_animation(&self) -> bool {
false
}
/// Called each frame with the delta time (seconds since last frame).
/// Animated veils should multiply `dt` by their speed param,
/// accumulate into their internal time, and write to the uniform buffer.
/// Default is a no-op for non-animated veils.
fn update_time(&mut self, _queue: &wgpu::Queue, _cache: &EffectCache, _dt: f32) {}
/// Encode all render passes into the command encoder.
/// The veil reads from `ping_pong[src_idx]` (via pre-built bind groups)
/// and must write its final output to `dst_view`.
/// Internal intermediate passes (e.g., to aux textures) are the veil's concern.
fn encode(
&self,
encoder: &mut wgpu::CommandEncoder,
cache: &EffectCache,
src_idx: usize,
dst_view: &wgpu::TextureView,
);
}
/// What each veil module returns from its `register()` function.
pub struct VeilRegistration {
pub type_id: &'static str,
pub display_name: &'static str,
pub params: &'static [ParamDef],
pub create_pipeline: fn(&wgpu::Device, wgpu::TextureFormat) -> EffectPipeline,
pub from_params: fn(&[ParamValue], Arc<EffectPipeline>) -> Box<dyn Veil>,
}
/// Auto-discovered veil registry with lazy pipeline caching.
pub struct VeilRegistry {
entries: HashMap<&'static str, RegistryEntry>,
}
struct RegistryEntry {
display_name: &'static str,
create_pipeline: fn(&wgpu::Device, wgpu::TextureFormat) -> EffectPipeline,
params: &'static [ParamDef],
from_params: fn(&[ParamValue], Arc<EffectPipeline>) -> Box<dyn Veil>,
cached_pipeline: Option<Arc<EffectPipeline>>,
}
impl Default for VeilRegistry {
fn default() -> Self {
Self::new()
}
}
impl VeilRegistry {
pub fn new() -> Self {
let mut entries = HashMap::new();
for reg in super::veils::registrations() {
entries.insert(
reg.type_id,
RegistryEntry {
display_name: reg.display_name,
create_pipeline: reg.create_pipeline,
params: reg.params,
from_params: reg.from_params,
cached_pipeline: None,
},
);
}
VeilRegistry { entries }
}
/// Return all registered veil type IDs with display name and parameter definitions.
pub fn types(&self) -> Vec<(&'static str, &'static str, &'static [ParamDef])> {
let mut types: Vec<_> = self
.entries
.iter()
.map(|(&id, e)| (id, e.display_name, e.params))
.collect();
types.sort_by_key(|(id, _, _)| *id);
types
}
/// Get the static parameter definitions for a veil type.
pub fn param_defs(&self, type_id: &str) -> &'static [ParamDef] {
self.entries.get(type_id).map(|e| e.params).unwrap_or(&[])
}
/// Resolve a runtime `&str` type id to the registry's `&'static str` key,
/// or `None` if the type is unknown. Callers that need to key long-lived
/// state by type id (e.g. the veil preview cache + its readback context)
/// use this to obtain a `'static` id without leaking.
pub fn static_type_id(&self, type_id: &str) -> Option<&'static str> {
self.entries.get_key_value(type_id).map(|(k, _)| *k)
}
/// True when this registry knows the given `type_id`. Used by the
/// `.darkly` load pre-check to refuse files that name veils the
/// binary doesn't ship — see [`crate::format::error::LoadError`].
pub fn has(&self, type_id: &str) -> bool {
self.entries.contains_key(type_id)
}
/// Get the human-friendly display name for a veil type, falling back to
/// the `type_id` literal when the type is unknown.
pub fn display_name(&self, type_id: &str) -> &'static str {
self.entries
.get(type_id)
.map(|e| e.display_name)
.unwrap_or("")
}
/// Get or create the shared pipeline for a veil type.
pub fn pipeline(
&mut self,
type_id: &str,
device: &wgpu::Device,
format: wgpu::TextureFormat,
) -> Arc<EffectPipeline> {
let entry = self
.entries
.get_mut(type_id)
.unwrap_or_else(|| panic!("Unknown veil type: {type_id}"));
entry
.cached_pipeline
.get_or_insert_with(|| Arc::new((entry.create_pipeline)(device, format)))
.clone()
}
/// Create a veil instance from a type string and parameter values.
pub fn create_veil(
&mut self,
type_id: &str,
params: &[ParamValue],
device: &wgpu::Device,
format: wgpu::TextureFormat,
) -> Box<dyn Veil> {
let entry = self
.entries
.get_mut(type_id)
.unwrap_or_else(|| panic!("Unknown veil type: {type_id}"));
let pipeline = entry
.cached_pipeline
.get_or_insert_with(|| Arc::new((entry.create_pipeline)(device, format)))
.clone();
(entry.from_params)(params, pipeline)
}
}