GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
src/draw/mod.rs (10.7K)
1 //! What a renderer draws, with no renderer in it: the frame
2 //! ([`Frame2D`] and its [`Batch2D`]s, [`PlatePush`] parameter blocks and the
3 //! one way to lay a batch's block out, [`batch_push_constants`]), text runs
4 //! ([`TextSpan`]), image draws ([`ImageQuad`]) and the image-id queue apps
5 //! upload through ([`images`]).
6 //!
7 //! These lived in `vk/` because the Vulkan renderer was the only one. They
8 //! are plain data, and `backend::frame` builds them for any renderer — the
9 //! Vulkan one on Linux, a WebGPU one in the browser — so they live here, and
10 //! `vk` re-exports every one at its old path.
11
12 use crate::backend::tessellate::Vertex;
13 use cosmic_text::Buffer as TextBuffer;
14
15 pub mod glyphs;
16 pub mod images;
17 pub mod lit;
18 pub mod rt;
19 pub mod scene;
20 pub mod shaders;
21
22 pub use images::{
23 free_image, recycle_buffer, renderer_epoch, update_pixel_regions, update_pixels,
24 upload_pixels, upload_rgba, upload_rgba_mipmapped, ImageQuad, PixelFormat, Region,
25 };
26
27 /// One scissored draw range of a 2D frame. `scissor` is (x, y, w, h) in
28 /// physical pixels; None draws with the full-surface scissor. `clip_rrect` is an
29 /// optional rounded-rect clip `[cx, cy, bx, by, r]` (center, SDF half-extents, corner
30 /// radius; physical px) applied via push constants — fragments outside it discard, so a
31 /// plate's children cut off at its rounded corners.
32 pub struct Batch2D {
33 pub scissor: Option<(u32, u32, u32, u32)>,
34 pub clip_rrect: Option<[f32; 5]>,
35 pub start: u32,
36 pub end: u32,
37 /// When set, this batch is a single SDF-lit plate cover quad: the params go
38 /// out as push constants and shader2d's plate branch lights it per pixel.
39 pub plate: Option<PlatePush>,
40 /// A blur-behind plate (negative-alpha color): the renderer suspends the UI
41 /// pass, copies the swapchain-so-far into its snapshot image, and resumes —
42 /// so the plate's blur samples everything painted beneath it (background,
43 /// widgets, wires), not just the 3D scene backdrop.
44 pub blur_behind: bool,
45 }
46
47 /// Floats in the fragment push-constant block: the rounded-rect clip (`rect0`,
48 /// `rect1` — 6 clip/flag floats plus the plate mode and corner shape) followed
49 /// by [`PlatePush`]'s six vec4s. Field for field, this is shader2d's `RRectClip`.
50 pub const PUSH_CONSTANT_FLOATS: usize = 32;
51
52 /// The fragment push-constant block for one batch: its rounded-rect clip,
53 /// and its plate block when it is a plate cover quad. `feature_base` is the
54 /// frame slot's first entry in the feature UBO, added to a plate's (or a
55 /// union carve's) feature offset. Shared by every renderer and the offscreen
56 /// test harness (`vk::plate_probe`), so none can push a different block. (Vulkan
57 /// carries it as push constants; a renderer without them puts it in a uniform.)
58 pub fn batch_push_constants(batch: &Batch2D, clip_shape: f32, feature_base: usize) -> [f32; PUSH_CONSTANT_FLOATS] {
59 let rr = batch.clip_rrect.unwrap_or([0.0; 5]);
60 let enabled = if batch.clip_rrect.is_some() { 1.0f32 } else { 0.0 };
61 let mut pc = [0.0f32; PUSH_CONSTANT_FLOATS];
62 pc[..5].copy_from_slice(&rr);
63 pc[5] = enabled;
64 pc[7] = clip_shape;
65 if let Some(p) = &batch.plate {
66 pc[6] = p.mode;
67 pc[7] = p.shape;
68 pc[8..12].copy_from_slice(&p.rect);
69 pc[12..16].copy_from_slice(&p.radii);
70 pc[16..20].copy_from_slice(&p.light);
71 pc[20..24].copy_from_slice(&p.material);
72 pc[24..28].copy_from_slice(&p.host);
73 pc[28..32].copy_from_slice(&p.specular_tint);
74 if p.mode == 1.0 || p.mode == 14.0 || p.mode == 17.0 {
75 // Rebase the feature offset onto this frame's UBO slot (a plate's
76 // CSG carves, or a union carve's boxes).
77 pc[24] += feature_base as f32;
78 }
79 }
80 pc
81 }
82
83 /// Push-constant block for one SDF-lit plate batch (physical px throughout).
84 /// Mirrors the `p_*` fields of shader2d's `RRectClip`.
85 #[derive(Clone, Copy, PartialEq, Debug)]
86 pub struct PlatePush {
87 /// SDF box: center + half-extents. May extend past the cover quad — that is
88 /// how a recess suppresses a wall.
89 pub rect: [f32; 4],
90 /// Per-corner radii [tl, tr, br, bl].
91 pub radii: [f32; 4],
92 /// xyz = unit vector toward the light (+z out of the screen), w = roll width px.
93 pub light: [f32; 4],
94 /// [shading strength, specular strength, shininess, curvature/AO strength].
95 pub material: [f32; 4],
96 /// Mode 1: `[feature offset, feature count, frost z, frost w]` — xy into
97 /// the frame's `plate_features`, the carves CSG'd out of this plate (the
98 /// renderer adds the frame slot's base offset at record time); zw the
99 /// plate's frost recipe, `scene::material::Frost::pack` (compression and
100 /// refraction packed in z, the blur sigma in physical px in w). Mode 14 uses the same
101 /// `[offset, count]` for the union's boxes. Mode 2: the host-plate box
102 /// (center + half-extents) a free recess fades out against; far-away sides
103 /// (±1e5) disable the fade.
104 pub host: [f32; 4],
105 /// RGB multiplies the roll's specular color. Neutral white normally; the
106 /// focused-pane bevel carries the highlight color here, with w = 1 marking
107 /// the plate (or carve) as accent-tinted — the shader's focus branch,
108 /// which recolours the light AND the shadow (see shader2d's FOCUS_*).
109 pub specular_tint: [f32; 4],
110 /// 1.0 = raised lit plate, 2.0 = recess overlay, 3.0 = boss, 4.0 = ridge,
111 /// 5.0 = sphere, 6.0/7.0 = concave fillet (recessed/raised), 8.0 = groove
112 /// (slab carve about a line: `rect` = [cx, cy, half-width, _], `radii.xy` =
113 /// the line's unit normal, `host` = the surface it is engraved into),
114 /// 9.0 = trough, 10.0 = droplet (`radii` = [sag, belly r, belly half-w,
115 /// blend k] px, `host` = [sheet corner r px, clarity, dome amplitude,
116 /// attach r px], `material.w` = fresnel rim, `specular_tint` = [core
117 /// density, _, _, bottom-bow rise px] — droplet glints are always white,
118 /// so the tint RGB is repurposed; see shader2d's MODE_DROPLET).
119 pub mode: f32,
120 /// Corner shape exponent: 2.0 = circular arcs, > 2 = superellipse
121 /// (continuous-curvature) corners — see shader2d's `plate_sdf_grad`.
122 pub shape: f32,
123 }
124
125 /// A full 2D frame: the display-list vertices (optionally split into scissored
126 /// batches), overlay vertices drawn after text, and the clear color (linear;
127 /// only used on frames without a backdrop copy).
128 pub struct Frame2D<'a> {
129 pub verts: &'a [Vertex],
130 pub batches: &'a [Batch2D],
131 pub overlay_verts: &'a [Vertex],
132 /// User images drawn interleaved with `verts` by each quad's `z_before`.
133 pub images: &'a [ImageQuad],
134 /// Carves CSG'd into this frame's SDF-lit plates, 12 floats each (rect
135 /// center+half-extents, per-corner radii, [width px, depth px, 0, 0]).
136 /// Plate batches reference them by offset+count in `PlatePush::host`.
137 pub plate_features: &'a [[f32; 12]],
138 pub clear_color: [f32; 4],
139 /// The only part of the surface that differs from the previous frame,
140 /// (x, y, w, h) in physical pixels; None = all of it. With a rect the
141 /// renderer keeps the pixels outside it (Vulkan's `ImageAge`) and tells the
142 /// compositor that only the rect changed. The caller vouches for it: a
143 /// pixel that changed outside the rect stays as it was.
144 pub damage: Option<(u32, u32, u32, u32)>,
145 }
146
147 /// Max plate-carve features per frame; the shader's UBO holds one slot of this
148 /// size per frame in flight.
149 pub const MAX_PLATE_FEATURES: usize = 64;
150
151 /// One shaped text run to draw. `left`/`top` are physical pixels and `scale`
152 /// multiplies the shaped (logical) glyph positions — the same contract as
153 /// the old glyphon::TextArea, where callers pass `label.x * scale`.
154 pub struct TextSpan<'a> {
155 pub buffer: &'a TextBuffer,
156 pub left: f32,
157 pub top: f32,
158 pub scale: f32,
159 /// Physical-pixel clip rect (left, top, right, bottom); None = whole surface.
160 pub bounds: Option<[i32; 4]>,
161 /// 0..=1 sRGB + alpha, applied to glyphs without their own color.
162 pub default_color: [f32; 4],
163 /// Rotate the span's glyph quads by (radians, center_x, center_y) in
164 /// physical pixels — the circular network pane's curved rim labels.
165 pub rotation: Option<(f32, f32, f32)>,
166 /// Fragment circle clip (center_x, center_y, radius) in physical pixels;
167 /// zero radius disables (matches shader.wgsl's clip_circle).
168 pub clip_circle: [f32; 3],
169 /// Rounded-rect clip half-extents (physical px). Zero keeps `clip_circle` a plain
170 /// circle; non-zero reinterprets it as a rounded-rect SDF clip — center
171 /// `clip_circle.xy`, corner radius `clip_circle.z`, inner box half-size
172 /// `clip_extents` — so plate children (labels included) cut off at rounded corners.
173 pub clip_extents: [f32; 2],
174 }
175
176 /// The bytes of one plate feature (a carve CSG'd into a plate): three vec4s,
177 /// `[f32; 12]` in `Frame2D::plate_features`.
178 pub const PLATE_FEATURE_BYTES: usize = 48;
179
180 /// shader2d's WindowInfo UBO: [size/clip vec4][bevel-profile meta vec4]
181 /// [8 vec4 of profile slope samples].
182 // [size/clip vec4][carve profile meta + 8 vec4][roll profile meta + 8 vec4].
183 // [size/clip vec4][carve profile meta][8 carve slopes][roll profile meta]
184 // [8 roll slopes][relief heights][backdrop meta] = 21 vec4. Grows only at the
185 // END — every offset above is addressed by index from both sides.
186 pub const WINDOW_INFO_BYTES: usize = 320;
187
188 /// The pinned relief heights (carve, roll) in physical px at `scale`, 0 =
189 /// follow the width.
190 pub fn relief_px_at(scale: f32) -> (f32, f32) {
191 let s = scale.max(0.001);
192 (
193 crate::layout::bevel_height().map_or(0.0, |h| h * s),
194 crate::layout::roll_height().map_or(0.0, |h| h * s),
195 )
196 }
197
198 /// shader2d's `WindowInfo` block for a `width` x `height` target whose corners clip
199 /// at `clip_corner_radius` (physical px), with the pinned relief heights
200 /// `relief` (carve, roll; physical px, 0 = unpinned) — the profiles and the
201 /// corner shape from the live style. Shared by every renderer and the offscreen
202 /// test harness.
203 pub fn window_info_data(width: u32, height: u32, clip_corner_radius: f32, relief: (f32, f32)) -> [f32; WINDOW_INFO_BYTES / 4] {
204 let mut data = [0.0f32; WINDOW_INFO_BYTES / 4];
205 data[0] = width as f32;
206 data[1] = height as f32;
207 data[2] = clip_corner_radius;
208 data[3] = crate::layout::corner_shape();
209 if let Some(slopes) = crate::layout::bevel_profile_slopes() {
210 data[4] = 1.0;
211 data[5] = crate::layout::BEVEL_PROFILE_SAMPLES as f32;
212 data[8..8 + slopes.len()].copy_from_slice(&slopes);
213 }
214 if let Some(slopes) = crate::layout::roll_profile_slopes() {
215 data[40] = 1.0;
216 data[41] = crate::layout::BEVEL_PROFILE_SAMPLES as f32;
217 data[44..44 + slopes.len()].copy_from_slice(&slopes);
218 }
219 data[76] = relief.0;
220 data[77] = relief.1;
221 data
222 }