Skip to main content

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        /// Additional advance between glyphs in logical px (CSS
82        /// `letter-spacing`), pre-scaled like `size`. Lifted from the
83        /// source El's `text_letter_spacing`; backends fold it into
84        /// the synthesized [`RunStyle`] so paint advances match the
85        /// layout measurement. `0.0` = natural tracking.
86        letter_spacing: f32,
87    },
88    /// An attributed paragraph: a sequence of styled runs that flow
89    /// together inside one `rect`. The runtime hands `runs` straight to
90    /// [`crate::text::atlas::GlyphAtlas::shape_and_rasterize_runs`] so
91    /// wrapping decisions cross run boundaries (real prose, not glued
92    /// segments). `layout` is an approximate pre-shaping measurement
93    /// from `text::metrics` — backends shape for accurate placement;
94    /// SVG uses it to lay tspan baselines.
95    AttributedText {
96        id: std::sync::Arc<str>,
97        rect: Rect,
98        scissor: Option<Rect>,
99        shader: ShaderHandle,
100        /// Source-order styled spans. Each `String` may contain
101        /// embedded `\n` to express in-paragraph hard breaks.
102        runs: Vec<(String, RunStyle)>,
103        size: f32,
104        line_height: f32,
105        wrap: TextWrap,
106        anchor: TextAnchor,
107        layout: TextLayout,
108    },
109    /// A vector icon scaled into `rect`. The `source` is either a
110    /// built-in [`crate::tree::IconName`] (24x24 lucide-style) or an
111    /// app-supplied [`crate::SvgIcon`]. SVG bundle output renders the
112    /// vector paths directly; wgpu/vulkano backends bake an MTSDF (or
113    /// tessellate for non-flat materials); backends without a native
114    /// vector painter fall back to a glyph for built-ins.
115    Icon {
116        id: std::sync::Arc<str>,
117        rect: Rect,
118        scissor: Option<Rect>,
119        source: IconSource,
120        color: Color,
121        size: f32,
122        stroke_width: f32,
123    },
124    /// A raster image painted into `rect`. The `image` carries the
125    /// pixel data (Arc-shared with the source El) and is keyed by
126    /// `image.content_hash()` in backend texture caches. `rect` is the
127    /// post-`fit` destination; for `Cover` it can extend past the El's
128    /// content area and is clipped via `scissor`. SVG bundle output
129    /// emits a placeholder rect labelled with the image's hash.
130    Image {
131        id: std::sync::Arc<str>,
132        rect: Rect,
133        scissor: Option<Rect>,
134        image: Image,
135        tint: Option<Color>,
136        radius: Corners,
137        fit: ImageFit,
138        /// HDR headroom policy (CSS `dynamic-range-limit`): how the
139        /// backend remasters content brighter than the output can show.
140        range_limit: crate::image::DynamicRangeLimit,
141    },
142    /// An app-owned GPU texture composited into the paint stream.
143    /// Unlike `DrawOp::Image`, the backend does not upload pixels —
144    /// it samples the existing texture identified by `texture` during
145    /// paint, keying its bind-group cache on
146    /// [`crate::surface::AppTextureId`]. `rect` is the post-`fit`
147    /// destination rect; for `Cover` it can extend past the El's
148    /// content area and is clipped via `scissor`. `transform` is an
149    /// affine applied to the textured quad in destination space,
150    /// around the centre of `rect`. `alpha` selects the blend path.
151    /// SVG bundle output emits a placeholder rect labelled with the
152    /// texture's id.
153    AppTexture {
154        id: std::sync::Arc<str>,
155        rect: Rect,
156        scissor: Option<Rect>,
157        texture: crate::surface::AppTexture,
158        alpha: crate::surface::SurfaceAlpha,
159        fit: ImageFit,
160        transform: crate::affine::Affine2,
161    },
162    /// An app-supplied vector asset. `render_mode` decides whether the
163    /// backend preserves authored paint or treats the asset as a
164    /// one-colour mask. SVG bundle output emits a placeholder rect
165    /// labelled with the asset's hash.
166    Vector {
167        id: std::sync::Arc<str>,
168        rect: Rect,
169        scissor: Option<Rect>,
170        asset: std::sync::Arc<crate::vector::VectorAsset>,
171        render_mode: VectorRenderMode,
172    },
173    /// A backend-neutral 3D scene (closed-scope graph/model: point
174    /// scatter, small lit meshes, lines) composited into `rect`. Unlike
175    /// the other content ops, the backend does not tessellate or sample a
176    /// texture from core — it renders [`Scene3DData`] with its own scene
177    /// pipelines (points/mesh/lines), resolves MSAA, and composites the
178    /// result into `rect`. `scene` is `Arc`-shared and carries
179    /// revision-keyed geometry handles so backends cache GPU buffers
180    /// across frames. SVG bundle output projects point/line geometry
181    /// through the resolved camera into `<polyline>`/`<circle>` primitives
182    /// (exact for the 2D-plot ortho camera, a wireframe preview for true
183    /// 3D); meshes have no SVG analogue and keep a labelled placeholder
184    /// rect. See `docs/SCENE3D_PLAN.md`.
185    Scene3D {
186        id: std::sync::Arc<str>,
187        rect: Rect,
188        scissor: Option<Rect>,
189        scene: Arc<Scene3DData>,
190    },
191    /// Mid-frame snapshot of the current target into a sampled texture,
192    /// scheduled before any backdrop-sampling pass.
193    BackdropSnapshot,
194}
195
196#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
197pub enum TextAnchor {
198    Start,
199    Middle,
200    End,
201}
202
203impl DrawOp {
204    pub fn id(&self) -> &str {
205        match self {
206            DrawOp::Quad { id, .. }
207            | DrawOp::GlyphRun { id, .. }
208            | DrawOp::AttributedText { id, .. }
209            | DrawOp::Icon { id, .. }
210            | DrawOp::Image { id, .. }
211            | DrawOp::AppTexture { id, .. }
212            | DrawOp::Vector { id, .. }
213            | DrawOp::Scene3D { id, .. } => id,
214            DrawOp::BackdropSnapshot => "<backdrop-snapshot>",
215        }
216    }
217    pub fn shader(&self) -> Option<&ShaderHandle> {
218        match self {
219            DrawOp::Quad { shader, .. }
220            | DrawOp::GlyphRun { shader, .. }
221            | DrawOp::AttributedText { shader, .. } => Some(shader),
222            DrawOp::Icon { .. }
223            | DrawOp::Image { .. }
224            | DrawOp::AppTexture { .. }
225            | DrawOp::Vector { .. }
226            | DrawOp::Scene3D { .. } => None,
227            DrawOp::BackdropSnapshot => None,
228        }
229    }
230    pub fn scissor(&self) -> Option<Rect> {
231        match self {
232            DrawOp::Quad { scissor, .. }
233            | DrawOp::GlyphRun { scissor, .. }
234            | DrawOp::AttributedText { scissor, .. }
235            | DrawOp::Icon { scissor, .. }
236            | DrawOp::Image { scissor, .. }
237            | DrawOp::AppTexture { scissor, .. }
238            | DrawOp::Vector { scissor, .. }
239            | DrawOp::Scene3D { scissor, .. } => *scissor,
240            DrawOp::BackdropSnapshot => None,
241        }
242    }
243}