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