1use std::{
16 collections::HashMap,
17 fmt::Write as _,
18 hash::{DefaultHasher, Hasher},
19};
20
21use cranpose_render_common::{brush_sampling::color_to_rgba, graph_scene::RenderDiagnostics};
22use cranpose_ui_graphics::{
23 BlendMode, Brush, Color, Point, Rect, StrokeCap, StrokeJoin, TileMode, whole,
24};
25
26use crate::{
27 draw::{
28 ClipBounds, RenderItem, ShapeCoverage, Target, TextRaster, clip_rect_to_window, draw_item,
29 place_shape, snap_delta_for_anchor,
30 },
31 scene::{DrawShape, RasterScene},
32};
33
34pub const SPRITE_FLOATS: usize = 36;
36
37pub mod field {
39 pub const FRAME: usize = 0;
41 pub const KIND: usize = 4;
45 pub const GEOMETRY: usize = 8;
49 pub const DETAIL: usize = 12;
53 pub const STROKE: usize = 16;
57 pub const COLOR: usize = 20;
59 pub const COLOR_END: usize = 24;
61 pub const GRADIENT: usize = 28;
64 pub const STOPS: usize = 32;
66}
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
70#[repr(u8)]
71pub enum SpriteKind {
72 Texture = 0,
74 Fill = 1,
76 Stroke = 2,
78 Arc = 3,
80 Line = 4,
82}
83
84#[derive(Clone, Debug, PartialEq, Eq)]
87pub struct TextureUpload {
88 pub id: u32,
90 pub width: u32,
92 pub height: u32,
94 pub pixels: Vec<u8>,
97}
98
99#[derive(Default)]
102pub struct SpriteFrame {
103 pub(crate) scene: RasterScene,
105 sprites: Vec<f32>,
106 uploads: Vec<TextureUpload>,
107 releases: Vec<u32>,
108 textures: HashMap<u64, CachedTexture>,
109 next_texture: u32,
110 generation: u64,
111}
112
113struct CachedTexture {
114 id: u32,
115 placement: Rect,
117 last_used: u64,
118}
119
120const TEXTURE_RETENTION_FRAMES: u64 = 60;
123
124impl SpriteFrame {
125 pub fn sprites(&self) -> &[f32] {
127 &self.sprites
128 }
129
130 pub fn sprite_count(&self) -> usize {
132 self.sprites.len() / SPRITE_FLOATS
133 }
134
135 pub fn uploads(&self) -> &[TextureUpload] {
137 &self.uploads
138 }
139
140 pub fn releases(&self) -> &[u32] {
142 &self.releases
143 }
144
145 pub(crate) fn compose(
146 &mut self,
147 scene: &RasterScene,
148 width: u32,
149 height: u32,
150 diagnostics: &RenderDiagnostics,
151 text: &TextRaster,
152 ) {
153 self.sprites.clear();
154 self.uploads.clear();
155 self.releases.clear();
156 self.generation += 1;
157 let screen = ClipBounds {
158 min_x: 0,
159 min_y: 0,
160 max_x: width as i32,
161 max_y: height as i32,
162 };
163 for &item in &scene.order {
164 if let RenderItem::Shape(index) = item
165 && let Some(sprite) = shape_sprite(&scene.shapes[index], screen)
166 {
167 self.sprites.extend_from_slice(&sprite);
168 continue;
169 }
170 self.texture_sprite(scene, item, screen, diagnostics, text);
171 }
172 let generation = self.generation;
173 let releases = &mut self.releases;
174 self.textures.retain(|_, texture| {
175 let keep = generation - texture.last_used <= TEXTURE_RETENTION_FRAMES;
176 if !keep {
177 releases.push(texture.id);
178 }
179 keep
180 });
181 }
182
183 fn texture_sprite(
186 &mut self,
187 scene: &RasterScene,
188 item: RenderItem,
189 screen: ClipBounds,
190 diagnostics: &RenderDiagnostics,
191 text: &TextRaster,
192 ) {
193 let Some((canonical, offset, clip)) = Canonical::of(scene, item) else {
194 return;
195 };
196 let key = canonical.key();
197 let generation = self.generation;
198 let (id, placement) = match self.textures.get_mut(&key) {
199 Some(texture) => {
200 texture.last_used = generation;
201 (texture.id, texture.placement)
202 }
203 None => {
204 let Some((placement, pixels)) = canonical.rasterize(diagnostics, text) else {
205 return;
206 };
207 self.next_texture = self.next_texture.wrapping_add(1).max(1);
208 let id = self.next_texture;
209 self.uploads.push(TextureUpload {
210 id,
211 width: placement.width as u32,
212 height: placement.height as u32,
213 pixels,
214 });
215 self.textures.insert(
216 key,
217 CachedTexture {
218 id,
219 placement,
220 last_used: generation,
221 },
222 );
223 (id, placement)
224 }
225 };
226 let placement = placement.translate(offset.x, offset.y);
227 let Some(frame) = clip_rect_to_window(placement, clip, screen) else {
228 return;
229 };
230 let mut sprite = [0.0; SPRITE_FLOATS];
231 put_frame(&mut sprite, frame);
232 sprite[field::KIND] = SpriteKind::Texture as u8 as f32;
233 sprite[field::KIND + 1] = id as f32;
234 put_rect(&mut sprite, field::GEOMETRY, placement);
235 self.sprites.extend_from_slice(&sprite);
236 }
237}
238
239fn put_frame(sprite: &mut [f32; SPRITE_FLOATS], frame: ClipBounds) {
240 sprite[field::FRAME] = frame.min_x as f32;
241 sprite[field::FRAME + 1] = frame.min_y as f32;
242 sprite[field::FRAME + 2] = (frame.max_x - frame.min_x) as f32;
243 sprite[field::FRAME + 3] = (frame.max_y - frame.min_y) as f32;
244}
245
246fn put_rect(sprite: &mut [f32; SPRITE_FLOATS], at: usize, rect: Rect) {
247 sprite[at..at + 4].copy_from_slice(&[rect.x, rect.y, rect.width, rect.height]);
248}
249
250fn put_color(sprite: &mut [f32; SPRITE_FLOATS], at: usize, color: Color) {
251 sprite[at..at + 4].copy_from_slice(&color_to_rgba(color));
252}
253
254fn cap_code(cap: StrokeCap) -> f32 {
255 match cap {
256 StrokeCap::Butt => 0.0,
257 StrokeCap::Round => 1.0,
258 StrokeCap::Square => 2.0,
259 }
260}
261
262fn join_code(join: StrokeJoin) -> f32 {
263 match join {
264 StrokeJoin::Miter => 0.0,
265 StrokeJoin::Round => 1.0,
266 StrokeJoin::Bevel => 2.0,
267 }
268}
269
270fn tile_code(tile_mode: TileMode) -> f32 {
271 match tile_mode {
272 TileMode::Clamp => 0.0,
273 TileMode::Repeated => 1.0,
274 TileMode::Mirror => 2.0,
275 TileMode::Decal => 3.0,
276 }
277}
278
279fn shape_sprite(draw: &DrawShape, screen: ClipBounds) -> Option<[f32; SPRITE_FLOATS]> {
282 let blend = match draw.blend_mode {
283 BlendMode::DstOut => 1.0,
284 _ => 0.0,
285 };
286 let placed = place_shape(draw)?;
287 let mut sprite = [0.0; SPRITE_FLOATS];
288 match placed.coverage {
289 ShapeCoverage::Fill { rect, radii } => {
290 sprite[field::KIND] = SpriteKind::Fill as u8 as f32;
291 put_rect(&mut sprite, field::GEOMETRY, rect);
292 if let Some(radii) = radii {
293 sprite[field::DETAIL..field::DETAIL + 4].copy_from_slice(&[
294 radii.top_left,
295 radii.top_right,
296 radii.bottom_right,
297 radii.bottom_left,
298 ]);
299 sprite[field::STROKE] = 1.0;
300 }
301 }
302 ShapeCoverage::Stroke {
303 rect,
304 radii,
305 half_width,
306 join,
307 } => {
308 sprite[field::KIND] = SpriteKind::Stroke as u8 as f32;
309 put_rect(&mut sprite, field::GEOMETRY, rect);
310 let radii = radii.unwrap_or_default();
311 sprite[field::DETAIL..field::DETAIL + 4].copy_from_slice(&[
312 radii.top_left,
313 radii.top_right,
314 radii.bottom_right,
315 radii.bottom_left,
316 ]);
317 sprite[field::STROKE] = half_width;
318 sprite[field::STROKE + 1] = join_code(join);
319 }
320 ShapeCoverage::Arc(arc) => {
321 sprite[field::KIND] = SpriteKind::Arc as u8 as f32;
322 sprite[field::GEOMETRY..field::GEOMETRY + 4].copy_from_slice(&[
323 arc.center.x,
324 arc.center.y,
325 arc.inner_radius,
326 arc.outer_radius,
327 ]);
328 sprite[field::DETAIL..field::DETAIL + 3].copy_from_slice(&[
329 arc.start_angle,
330 arc.sweep_angle,
331 cap_code(arc.cap),
332 ]);
333 }
334 ShapeCoverage::Line(line) => {
335 sprite[field::KIND] = SpriteKind::Line as u8 as f32;
336 sprite[field::GEOMETRY..field::GEOMETRY + 4].copy_from_slice(&[
337 line.start.x,
338 line.start.y,
339 line.end.x,
340 line.end.y,
341 ]);
342 sprite[field::DETAIL] = line.half_width;
343 sprite[field::DETAIL + 1] = cap_code(line.cap);
344 }
345 ShapeCoverage::Trapezoid(_) => return None,
346 }
347 put_brush(&mut sprite, &draw.brush, placed.rect)?;
348 sprite[field::KIND + 2] = blend;
349 let frame = clip_rect_to_window(placed.coverage.reach(placed.rect), draw.clip, screen)?;
350 put_frame(&mut sprite, frame);
351 sprite[field::STOPS + 2] = placed.dither_origin.x;
353 sprite[field::STOPS + 3] = placed.dither_origin.y;
354 Some(sprite)
355}
356
357fn put_brush(sprite: &mut [f32; SPRITE_FLOATS], brush: &Brush, rect: Rect) -> Option<()> {
360 let two_colours = |colors: &[Color], stops: Option<&[f32]>| match (colors, stops) {
361 ([only], _) => Some((*only, *only, [0.0, 1.0])),
362 ([start, end], Some([from, to])) => Some((*start, *end, [*from, *to])),
363 ([start, end], _) => Some((*start, *end, [0.0, 1.0])),
364 _ => None,
365 };
366 let resolve = |origin: f32, extent: f32, value: f32| {
367 if value.is_finite() {
368 origin + value
369 } else if value.is_sign_positive() {
370 origin + extent
371 } else {
372 origin
373 }
374 };
375 let (code, (start, end, stops), gradient, tile) = match brush {
376 Brush::Solid(color) => (0.0, (*color, *color, [0.0, 1.0]), [0.0; 4], TileMode::Clamp),
377 Brush::LinearGradient {
378 colors,
379 stops,
380 start,
381 end,
382 tile_mode,
383 } => (
384 1.0,
385 two_colours(colors, stops.as_deref())?,
386 [
387 resolve(rect.x, rect.width, start.x),
388 resolve(rect.y, rect.height, start.y),
389 resolve(rect.x, rect.width, end.x),
390 resolve(rect.y, rect.height, end.y),
391 ],
392 *tile_mode,
393 ),
394 Brush::RadialGradient {
395 colors,
396 stops,
397 center,
398 radius,
399 tile_mode,
400 } => (
401 2.0,
402 two_colours(colors, stops.as_deref())?,
403 [
404 rect.x + center.x,
405 rect.y + center.y,
406 radius.max(f32::EPSILON),
407 0.0,
408 ],
409 *tile_mode,
410 ),
411 Brush::SweepGradient {
412 colors,
413 stops,
414 center,
415 } => (
416 3.0,
417 two_colours(colors, stops.as_deref())?,
418 [rect.x + center.x, rect.y + center.y, 0.0, 0.0],
419 TileMode::Clamp,
420 ),
421 };
422 sprite[field::KIND + 3] = code;
423 put_color(sprite, field::COLOR, start);
424 put_color(sprite, field::COLOR_END, end);
425 sprite[field::GRADIENT..field::GRADIENT + 4].copy_from_slice(&gradient);
426 sprite[field::STOPS] = stops[0];
427 sprite[field::STOPS + 1] = stops[1];
428 sprite[field::STROKE + 2] = tile_code(tile);
429 Some(())
430}
431
432struct Canonical {
436 scene: RasterScene,
437 item: RenderItem,
438}
439
440impl Canonical {
441 fn of(scene: &RasterScene, item: RenderItem) -> Option<(Self, Point, Option<Rect>)> {
444 let mut canonical = RasterScene::new();
445 let (origin, clip) = match item {
446 RenderItem::Shape(index) => {
447 let draw = &scene.shapes[index];
448 (Point::new(draw.rect.x, draw.rect.y), draw.clip)
449 }
450 RenderItem::Image(index) => {
451 let draw = &scene.images[index];
452 (Point::new(draw.rect.x, draw.rect.y), draw.clip)
453 }
454 RenderItem::Text(index) => {
455 let draw = &scene.texts[index];
456 (Point::new(draw.rect.x, draw.rect.y), draw.clip)
457 }
458 RenderItem::Shadow(index) => {
459 let draw = &scene.shadows[index];
460 let snap = draw
461 .snap_anchor
462 .map(snap_delta_for_anchor)
463 .unwrap_or_default();
464 (
465 Point::new(draw.shadow.bounds.x, draw.shadow.bounds.y),
466 draw.clip.map(|clip| clip.translate(snap.x, snap.y)),
467 )
468 }
469 };
470 if !(origin.x.is_finite() && origin.y.is_finite()) {
471 return None;
472 }
473 let shift = Point::new(whole::floor(origin.x), whole::floor(origin.y));
474 let item = match item {
475 RenderItem::Shape(index) => {
476 canonical
477 .shapes
478 .push(scene.shapes[index].translated(-shift.x, -shift.y));
479 RenderItem::Shape(0)
480 }
481 RenderItem::Image(index) => {
482 canonical
483 .images
484 .push(scene.images[index].translated(-shift.x, -shift.y));
485 RenderItem::Image(0)
486 }
487 RenderItem::Text(index) => {
488 canonical
489 .texts
490 .push(scene.texts[index].translated(-shift.x, -shift.y));
491 RenderItem::Text(0)
492 }
493 RenderItem::Shadow(index) => {
494 canonical
495 .shadows
496 .push(scene.shadows[index].translated(-shift.x, -shift.y));
497 RenderItem::Shadow(0)
498 }
499 };
500 Some((
501 Self {
502 scene: canonical,
503 item,
504 },
505 shift,
506 clip,
507 ))
508 }
509
510 fn key(&self) -> u64 {
512 let Self { scene, item } = self;
513 let mut hasher = HashWriter(DefaultHasher::new());
514 let _ = match item {
517 RenderItem::Shape(_) => write!(hasher, "shape {:?}", scene.shapes[0]),
518 RenderItem::Text(_) => write!(hasher, "text {:?}", scene.texts[0]),
519 RenderItem::Shadow(_) => write!(hasher, "shadow {:?}", scene.shadows[0]),
520 RenderItem::Image(_) => {
521 let draw = &scene.images[0];
522 write!(
523 hasher,
524 "image {} {:?} {:?} {} {:?} {:?} {:?} {:?}",
525 draw.image.id(),
526 draw.rect,
527 draw.snap_anchor,
528 draw.alpha,
529 draw.color_filter,
530 draw.sampling,
531 draw.blend_mode,
532 draw.src_rect,
533 )
534 }
535 };
536 hasher.0.finish()
537 }
538
539 fn rasterize(
542 &self,
543 diagnostics: &RenderDiagnostics,
544 text: &TextRaster,
545 ) -> Option<(Rect, Vec<u8>)> {
546 let Self { scene, item } = self;
547 if let RenderItem::Text(_) = item {
548 return rasterize_text(&scene.texts[0], text);
549 }
550 let reach = scene.item_reach(*item)?;
551 let window = clip_rect_to_window(
552 reach,
553 None,
554 ClipBounds {
555 min_x: i32::MIN / 4,
556 min_y: i32::MIN / 4,
557 max_x: i32::MAX / 4,
558 max_y: i32::MAX / 4,
559 },
560 )?;
561 let width = (window.max_x - window.min_x) as u32;
562 let height = (window.max_y - window.min_y) as u32;
563 let mut pixels = vec![0u8; width as usize * height as usize * 4];
564 let mut target = Target {
565 pixels: &mut pixels,
566 left: window.min_x,
567 top: window.min_y,
568 width,
569 height,
570 };
571 draw_item(&mut target, scene, *item, diagnostics, text);
572 Some((
573 Rect {
574 x: window.min_x as f32,
575 y: window.min_y as f32,
576 width: width as f32,
577 height: height as f32,
578 },
579 pixels,
580 ))
581 }
582}
583
584fn rasterize_text(draw: &crate::scene::TextDraw, text: &TextRaster) -> Option<(Rect, Vec<u8>)> {
586 let (text, rect) = crate::draw::rasterize_text(draw, text)?;
587 let mut pixels = text.pixels;
588 for pixel in pixels.as_chunks_mut::<4>().0 {
589 let alpha = u32::from(pixel[3]);
590 for channel in &mut pixel[..3] {
591 *channel = ((u32::from(*channel) * alpha + 127) / 255) as u8;
592 }
593 }
594 Some((rect, pixels))
595}
596
597struct HashWriter(DefaultHasher);
598
599impl std::fmt::Write for HashWriter {
600 fn write_str(&mut self, text: &str) -> std::fmt::Result {
601 self.0.write(text.as_bytes());
602 Ok(())
603 }
604}