damascene_core/paint/ir.rs
1//! Backend-neutral draw-op IR.
2//!
3//! Every visual fact in the laid-out tree resolves to a [`DrawOp`] bound
4//! to a [`ShaderHandle`] and a uniform block. The wgpu renderer dispatches
5//! by shader handle; the SVG fallback (`crate::bundle::svg`) interprets stock
6//! shaders best-effort and emits placeholder rects for custom ones.
7//!
8//! `BackdropSnapshot` is emitted by [`crate::runtime::RunnerCore`] when
9//! the resolved paint stream first needs a backdrop-sampling shader. See
10//! `docs/SHADER_VISION.md` for the backend contract.
11//!
12//! # Why DrawOp over RenderCmd
13//!
14//! Damascene keeps visual material decisions in shader handles and uniform blocks
15//! instead of baking CSS-shaped fields into the IR. Rect colors, gradients,
16//! shadows, focus rings, and glass effects resolve to stock shader uniforms or
17//! custom shader bindings before a backend records GPU commands.
18
19use std::sync::Arc;
20
21use crate::icons::svg::IconSource;
22use crate::image::{Image, ImageFit};
23use crate::scene::Scene3DData;
24use crate::shader::{ShaderHandle, UniformBlock};
25use crate::text::atlas::RunStyle;
26use crate::text::metrics::TextLayout;
27use crate::tree::{Color, Corners, FontFamily, FontWeight, Rect, TextWrap};
28use crate::vector::VectorRenderMode;
29
30/// One paint operation in the laid-out frame.
31#[derive(Clone, Debug)]
32pub enum DrawOp {
33 /// A rectangular region painted by a shader (typically
34 /// `stock::rounded_rect`, but custom shaders also emit `Quad`).
35 Quad {
36 id: std::sync::Arc<str>,
37 rect: Rect,
38 scissor: Option<Rect>,
39 shader: ShaderHandle,
40 uniforms: UniformBlock,
41 },
42 /// A run of text. The draw op carries the author text and measured layout;
43 /// backends shape/rasterize through the shared glyph atlas path.
44 GlyphRun {
45 id: std::sync::Arc<str>,
46 rect: Rect,
47 scissor: Option<Rect>,
48 shader: ShaderHandle,
49 /// Carried explicitly on the op for SVG fallback and backend text
50 /// shaping.
51 color: Color,
52 text: String,
53 size: f32,
54 line_height: f32,
55 family: FontFamily,
56 /// Monospace face used when `mono` is set. Stamped from the
57 /// source El's `mono_font_family` (themed via
58 /// `Theme::mono_font_family`).
59 mono_family: FontFamily,
60 weight: FontWeight,
61 mono: bool,
62 wrap: TextWrap,
63 anchor: TextAnchor,
64 layout: TextLayout,
65 /// Underline / strikethrough state lifted from the source El's
66 /// `text_underline` / `text_strikethrough`. Backends fold them
67 /// into the synthesized [`RunStyle`] before shaping so the
68 /// decoration pass in [`crate::text::atlas`] runs uniformly
69 /// for standalone leaves and attributed paragraphs.
70 underline: bool,
71 strikethrough: bool,
72 /// Optional link URL from the El's `text_link`. Carried for
73 /// future hit-test work; today it just pins color + underline
74 /// via [`RunStyle::with_link`].
75 link: Option<String>,
76 /// Shape digits with OpenType `tnum` (tabular figures). Lifted
77 /// from the source El's `text_tabular_numerals`; backends fold
78 /// it into the synthesized [`RunStyle`] before shaping so paint
79 /// advances match the layout measurement.
80 tabular_numerals: bool,
81 },
82 /// An attributed paragraph: a sequence of styled runs that flow
83 /// together inside one `rect`. The runtime hands `runs` straight to
84 /// [`crate::text::atlas::GlyphAtlas::shape_and_rasterize_runs`] so
85 /// wrapping decisions cross run boundaries (real prose, not glued
86 /// segments). `layout` is an approximate pre-shaping measurement
87 /// from `text::metrics` — backends shape for accurate placement;
88 /// SVG uses it to lay tspan baselines.
89 AttributedText {
90 id: std::sync::Arc<str>,
91 rect: Rect,
92 scissor: Option<Rect>,
93 shader: ShaderHandle,
94 /// Source-order styled spans. Each `String` may contain
95 /// embedded `\n` to express in-paragraph hard breaks.
96 runs: Vec<(String, RunStyle)>,
97 size: f32,
98 line_height: f32,
99 wrap: TextWrap,
100 anchor: TextAnchor,
101 layout: TextLayout,
102 },
103 /// A vector icon scaled into `rect`. The `source` is either a
104 /// built-in [`crate::tree::IconName`] (24x24 lucide-style) or an
105 /// app-supplied [`crate::SvgIcon`]. SVG bundle output renders the
106 /// vector paths directly; wgpu/vulkano backends bake an MTSDF (or
107 /// tessellate for non-flat materials); backends without a native
108 /// vector painter fall back to a glyph for built-ins.
109 Icon {
110 id: std::sync::Arc<str>,
111 rect: Rect,
112 scissor: Option<Rect>,
113 source: IconSource,
114 color: Color,
115 size: f32,
116 stroke_width: f32,
117 },
118 /// A raster image painted into `rect`. The `image` carries the
119 /// pixel data (Arc-shared with the source El) and is keyed by
120 /// `image.content_hash()` in backend texture caches. `rect` is the
121 /// post-`fit` destination; for `Cover` it can extend past the El's
122 /// content area and is clipped via `scissor`. SVG bundle output
123 /// emits a placeholder rect labelled with the image's hash.
124 Image {
125 id: std::sync::Arc<str>,
126 rect: Rect,
127 scissor: Option<Rect>,
128 image: Image,
129 tint: Option<Color>,
130 radius: Corners,
131 fit: ImageFit,
132 /// HDR headroom policy (CSS `dynamic-range-limit`): how the
133 /// backend remasters content brighter than the output can show.
134 range_limit: crate::image::DynamicRangeLimit,
135 },
136 /// An app-owned GPU texture composited into the paint stream.
137 /// Unlike `DrawOp::Image`, the backend does not upload pixels —
138 /// it samples the existing texture identified by `texture` during
139 /// paint, keying its bind-group cache on
140 /// [`crate::surface::AppTextureId`]. `rect` is the post-`fit`
141 /// destination rect; for `Cover` it can extend past the El's
142 /// content area and is clipped via `scissor`. `transform` is an
143 /// affine applied to the textured quad in destination space,
144 /// around the centre of `rect`. `alpha` selects the blend path.
145 /// SVG bundle output emits a placeholder rect labelled with the
146 /// texture's id.
147 AppTexture {
148 id: std::sync::Arc<str>,
149 rect: Rect,
150 scissor: Option<Rect>,
151 texture: crate::surface::AppTexture,
152 alpha: crate::surface::SurfaceAlpha,
153 fit: ImageFit,
154 transform: crate::affine::Affine2,
155 },
156 /// An app-supplied vector asset. `render_mode` decides whether the
157 /// backend preserves authored paint or treats the asset as a
158 /// one-colour mask. SVG bundle output emits a placeholder rect
159 /// labelled with the asset's hash.
160 Vector {
161 id: std::sync::Arc<str>,
162 rect: Rect,
163 scissor: Option<Rect>,
164 asset: std::sync::Arc<crate::vector::VectorAsset>,
165 render_mode: VectorRenderMode,
166 },
167 /// A backend-neutral 3D scene (closed-scope graph/model: point
168 /// scatter, small lit meshes, lines) composited into `rect`. Unlike
169 /// the other content ops, the backend does not tessellate or sample a
170 /// texture from core — it renders [`Scene3DData`] with its own scene
171 /// pipelines (points/mesh/lines), resolves MSAA, and composites the
172 /// result into `rect`. `scene` is `Arc`-shared and carries
173 /// revision-keyed geometry handles so backends cache GPU buffers
174 /// across frames. SVG bundle output projects point/line geometry
175 /// through the resolved camera into `<polyline>`/`<circle>` primitives
176 /// (exact for the 2D-plot ortho camera, a wireframe preview for true
177 /// 3D); meshes have no SVG analogue and keep a labelled placeholder
178 /// rect. See `docs/SCENE3D_PLAN.md`.
179 Scene3D {
180 id: std::sync::Arc<str>,
181 rect: Rect,
182 scissor: Option<Rect>,
183 scene: Arc<Scene3DData>,
184 },
185 /// Mid-frame snapshot of the current target into a sampled texture,
186 /// scheduled before any backdrop-sampling pass.
187 BackdropSnapshot,
188}
189
190#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
191pub enum TextAnchor {
192 Start,
193 Middle,
194 End,
195}
196
197impl DrawOp {
198 pub fn id(&self) -> &str {
199 match self {
200 DrawOp::Quad { id, .. }
201 | DrawOp::GlyphRun { id, .. }
202 | DrawOp::AttributedText { id, .. }
203 | DrawOp::Icon { id, .. }
204 | DrawOp::Image { id, .. }
205 | DrawOp::AppTexture { id, .. }
206 | DrawOp::Vector { id, .. }
207 | DrawOp::Scene3D { id, .. } => id,
208 DrawOp::BackdropSnapshot => "<backdrop-snapshot>",
209 }
210 }
211 pub fn shader(&self) -> Option<&ShaderHandle> {
212 match self {
213 DrawOp::Quad { shader, .. }
214 | DrawOp::GlyphRun { shader, .. }
215 | DrawOp::AttributedText { shader, .. } => Some(shader),
216 DrawOp::Icon { .. }
217 | DrawOp::Image { .. }
218 | DrawOp::AppTexture { .. }
219 | DrawOp::Vector { .. }
220 | DrawOp::Scene3D { .. } => None,
221 DrawOp::BackdropSnapshot => None,
222 }
223 }
224 pub fn scissor(&self) -> Option<Rect> {
225 match self {
226 DrawOp::Quad { scissor, .. }
227 | DrawOp::GlyphRun { scissor, .. }
228 | DrawOp::AttributedText { scissor, .. }
229 | DrawOp::Icon { scissor, .. }
230 | DrawOp::Image { scissor, .. }
231 | DrawOp::AppTexture { scissor, .. }
232 | DrawOp::Vector { scissor, .. }
233 | DrawOp::Scene3D { scissor, .. } => *scissor,
234 DrawOp::BackdropSnapshot => None,
235 }
236 }
237}