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