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
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
//! Veil (post-processing filter) management and query methods.
use super::types::{
node_to_layer_info, BlendModeTypeInfo, LayerInfo, LayerKindTypeInfo, ModifierTypeInfo,
ParamInfo, ToolTypeInfo, VeilInfo, VeilTypeInfo,
};
use super::DarklyEngine;
use super::PreviewJob;
use super::PreviewKind;
use super::ReadbackContext;
use crate::coord::LayerRect;
use crate::gpu::params::{ParamDef, ParamValue};
use crate::gpu::preview::ANIMATED_FRAMES;
use crate::gpu::veil::Veil;
impl DarklyEngine {
// --- Veils ---
pub fn add_veil(&mut self, veil_type: &str, params: &[ParamValue]) {
let chain = self.compositor.veil_chain_mut();
let format = chain.accum_format();
let veil = chain
.registry_mut()
.create_veil(veil_type, params, &self.gpu.device, format);
chain.add_veil(&self.gpu.device, &self.gpu.queue, veil);
}
pub fn remove_veil(&mut self, index: usize) {
self.compositor.veil_chain_mut().remove_veil(index);
}
pub fn clear_veils(&mut self) {
self.compositor.veil_chain_mut().clear_veils();
}
pub fn set_veil_visible(&mut self, index: usize, visible: bool) {
self.compositor
.veil_chain_mut()
.set_veil_visible(index, visible);
}
pub fn move_veil(&mut self, from: usize, to: usize) {
self.compositor.veil_chain_mut().move_veil(from, to);
}
pub fn update_veil(&mut self, index: usize, params: &[ParamValue]) {
let type_id: &'static str = match self.compositor.veil_chain().type_id(index) {
Some(t) => t,
None => return,
};
let chain = self.compositor.veil_chain_mut();
let format = chain.accum_format();
let new_veil = chain
.registry_mut()
.create_veil(type_id, params, &self.gpu.device, format);
chain.update_veil(&self.gpu.device, &self.gpu.queue, index, new_veil);
}
// --- Picker previews ---
/// Begin generating the looping thumbnail preview for `type_id` — the veil
/// applied to the **current canvas**, captured as a sequence of frames.
/// Regenerates on every call (a no-op only while a generation is already in
/// flight) so the picker always reflects the live document. Frames land
/// asynchronously; retrieve them with
/// [`poll_veil_preview`](Self::poll_veil_preview).
///
/// Fully isolated from the live veil chain: it downscales a snapshot of the
/// composite into the preview renderer's own textures and runs a fresh veil
/// instance over it, so the user's active veils, compositor surface, and
/// document are never mutated.
pub fn start_veil_preview(&mut self, type_id: &str) {
// Resolve to the registry's 'static id — it keys both the preview job
// and the readback context.
let Some(static_id) = self
.compositor
.veil_chain()
.registry()
.static_type_id(type_id)
else {
return;
};
// Don't queue a duplicate generation while one is in flight. (We do not
// skip when frames already exist — each open re-renders the live
// canvas, which may have changed since last time.)
if self.readbacks.any(|c| {
matches!(
c,
ReadbackContext::PreviewFrame { kind: PreviewKind::Veil, type_id: t, .. }
if *t == static_id
)
}) {
return;
}
// Refresh the composite so the preview reflects the current document,
// even with no surface present yet (mirrors `start_export`).
self.compositor
.render_offscreen(&self.gpu.device, &self.gpu.queue, &mut self.doc);
let canvas_w = self.compositor.canvas_width();
let canvas_h = self.compositor.canvas_height();
let format = self.compositor.veil_chain().accum_format();
// Downscale the live composite into the preview input texture. Holds an
// immutable borrow of `compositor` (via the texture view) alongside the
// mutable renderer borrow — disjoint fields, so they don't alias.
{
let source = self.compositor.composited_texture();
let source_view = source.create_view(&wgpu::TextureViewDescriptor::default());
self.veil_preview_renderer.load_source(
&self.gpu.device,
&self.gpu.queue,
&source_view,
canvas_w,
canvas_h,
format,
);
}
let defaults: Vec<ParamValue> = self
.compositor
.veil_chain()
.registry()
.param_defs(static_id)
.iter()
.map(|d| d.default_value())
.collect();
// Build the veil + cache over the loaded composite. Borrow-split the
// disjoint fields (registry on the compositor, the GPU context, the
// renderer) so they don't alias `self`.
let (mut veil, cache) = {
let Self {
compositor,
gpu,
veil_preview_renderer,
..
} = self;
let registry = compositor.veil_chain_mut().registry_mut();
veil_preview_renderer.build_veil(
&gpu.device,
&gpu.queue,
registry,
static_id,
&defaults,
format,
)
};
let (pw, ph) = self.veil_preview_renderer.preview_size();
let total = if veil.needs_animation() {
ANIMATED_FRAMES
} else {
1
};
let dt = self.veil_preview_renderer.frame_dt();
self.previews.insert(
(PreviewKind::Veil, static_id),
PreviewJob {
width: pw,
height: ph,
frames: vec![None; total as usize],
},
);
let rect = LayerRect::from_xywh(0, 0, pw, ph);
for frame_idx in 0..total {
// Frame 0 captures the initial state; advance animated veils between
// frames so the loop shows motion. Each frame renders into the same
// output texture and copies to its own staging buffer in the same
// submission, so the readback captures that frame before the next
// overwrites it.
if frame_idx > 0 && veil.needs_animation() {
veil.update_time(&self.gpu.queue, &cache, dt);
}
let veil_ref: &dyn Veil = veil.as_ref();
let Self {
gpu,
readbacks,
veil_preview_renderer,
..
} = self;
let output = veil_preview_renderer.output_texture();
Self::encode_preview_frame(
gpu,
readbacks,
PreviewKind::Veil,
static_id,
frame_idx,
total,
output,
format,
rect,
|encoder| veil_preview_renderer.encode_frame(encoder, veil_ref, &cache),
);
}
}
// --- Queries ---
pub fn layer_tree(&self) -> Vec<LayerInfo> {
self.doc
.children_of(self.doc.root_id())
.iter()
.rev()
.filter_map(|id| node_to_layer_info(&self.doc, self.compositor.void_registry(), *id))
.collect()
}
pub fn veil_list(&self) -> Vec<VeilInfo> {
let chain = self.compositor.veil_chain();
let count = chain.count();
let mut list = Vec::with_capacity(count);
for i in (0..count).rev() {
if let Some((type_id, visible)) = chain.info(i) {
let param_defs = chain.registry().param_defs(type_id);
let values = chain.param_values(i).unwrap_or_default();
let params = param_defs
.iter()
.enumerate()
.map(|(j, def)| ParamInfo::from_def(def, values.get(j)))
.collect();
list.push(VeilInfo {
type_id: type_id.to_string(),
visible,
index: i,
params,
});
}
}
list
}
/// Return all registered veil types with their parameter definitions.
pub fn veil_types(&self) -> Vec<VeilTypeInfo> {
self.compositor
.veil_chain()
.registry()
.types()
.into_iter()
.map(|(type_id, display_name, defs)| VeilTypeInfo {
type_id,
display_name,
params: defs.iter().map(|d| ParamInfo::from_def(d, None)).collect(),
})
.collect()
}
/// Get the parameter definitions for a veil type.
pub fn veil_param_defs(&self, type_id: &str) -> &'static [ParamDef] {
self.compositor.veil_chain().registry().param_defs(type_id)
}
/// Return all registered tool types with display name and parameter definitions.
/// Backs the WASM bridge so the UI can render tool names without hardcoding them.
pub fn tool_types(&self) -> Vec<ToolTypeInfo> {
crate::tool::registry()
.types()
.into_iter()
.map(|(type_id, display_name, defs)| ToolTypeInfo {
type_id,
display_name,
params: defs.iter().map(|d| ParamInfo::from_def(d, None)).collect(),
})
.collect()
}
/// Return all registered blend modes in GPU-value order, with display name
/// and category. Backs the WASM bridge so the UI populates the blend-mode
/// dropdown from the registry instead of a hardcoded table.
pub fn blend_mode_types(&self) -> Vec<BlendModeTypeInfo> {
crate::gpu::blend_mode::registry()
.all()
.into_iter()
.map(|reg| BlendModeTypeInfo {
type_id: reg.type_id,
display_name: reg.display_name,
category: reg.category,
})
.collect()
}
/// Return all registered filter kinds. UI uses this to resolve
/// `ModifierInfo.kind` to a display label and to populate the
/// "Add filter" menu.
pub fn modifier_types(&self) -> Vec<ModifierTypeInfo> {
crate::document::filter::registry()
.all()
.into_iter()
.map(|reg| ModifierTypeInfo {
type_id: reg.type_id,
display_name: reg.display_name,
})
.collect()
}
/// Return all registered layer kinds. UI uses this to resolve a layer's
/// `type` discriminator to a display label (e.g. "Raster Layer", "Group").
pub fn layer_kind_types(&self) -> Vec<LayerKindTypeInfo> {
crate::document::layer_kind::registry()
.all()
.into_iter()
.map(|reg| LayerKindTypeInfo {
type_id: reg.type_id,
display_name: reg.display_name,
})
.collect()
}
}