git.lucas.co / cce-ui
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 }