1use cvkg_core::{ElapsedTime, Material3D, Mesh, Rect, Renderer, Transform3D};
27use std::time::Instant;
28
29#[derive(Debug, Clone)]
33pub struct Framebuffer {
34 width: u32,
35 height: u32,
36 pixels: Vec<u32>, depth: Vec<f32>, }
39
40impl Framebuffer {
41 pub fn new(width: u32, height: u32) -> Self {
43 let size = (width * height) as usize;
44 Self {
45 width,
46 height,
47 pixels: vec![0; size],
48 depth: vec![0.0; size],
49 }
50 }
51
52 pub fn with_color(width: u32, height: u32, color: [f32; 4]) -> Self {
54 let mut fb = Self::new(width, height);
55 let packed = pack_rgba(color);
56 fb.pixels.fill(packed);
57 fb
58 }
59
60 pub fn width(&self) -> u32 { self.width }
61 pub fn height(&self) -> u32 { self.height }
62
63 pub fn pixels(&self) -> &[u32] { &self.pixels }
65
66 pub fn pixels_mut(&mut self) -> &mut [u32] { &mut self.pixels }
68
69 pub fn clear(&mut self) {
71 self.pixels.fill(0);
72 self.depth.fill(0.0);
73 }
74
75 pub fn clear_color(&mut self, color: [f32; 4]) {
77 let packed = pack_rgba(color);
78 self.pixels.fill(packed);
79 }
80
81 fn blend_pixel(&mut self, x: u32, y: u32, color: [f32; 4]) {
83 if x >= self.width || y >= self.height {
84 return;
85 }
86 let idx = (y * self.width + x) as usize;
87 if color[3] >= 1.0 {
89 self.pixels[idx] = pack_rgba(color);
90 return;
91 }
92 let src = color;
93 let dst = unpack_rgba(self.pixels[idx]);
94
95 let ao = src[3] + dst[3] * (1.0 - src[3]);
97 if ao < 0.001 {
98 return;
99 }
100 let out = [
101 (src[0] * src[3] + dst[0] * dst[3] * (1.0 - src[3])) / ao,
102 (src[1] * src[3] + dst[1] * dst[3] * (1.0 - src[3])) / ao,
103 (src[2] * src[3] + dst[2] * dst[3] * (1.0 - src[3])) / ao,
104 ao,
105 ];
106 self.pixels[idx] = pack_rgba(out);
107 }
108
109}
110
111fn pack_rgba(c: [f32; 4]) -> u32 {
112 let r = (c[0].clamp(0.0, 1.0) * 255.0) as u32;
113 let g = (c[1].clamp(0.0, 1.0) * 255.0) as u32;
114 let b = (c[2].clamp(0.0, 1.0) * 255.0) as u32;
115 let a = (c[3].clamp(0.0, 1.0) * 255.0) as u32;
116 r | (g << 8) | (b << 16) | (a << 24)
117}
118
119fn unpack_rgba(packed: u32) -> [f32; 4] {
120 [
121 (packed & 0xFF) as f32 / 255.0,
122 ((packed >> 8) & 0xFF) as f32 / 255.0,
123 ((packed >> 16) & 0xFF) as f32 / 255.0,
124 ((packed >> 24) & 0xFF) as f32 / 255.0,
125 ]
126}
127
128pub struct SoftwareRenderer {
135 fb: Framebuffer,
136 start_time: Instant,
137 last_frame: Instant,
138 #[cfg(feature = "text")]
140 text_engine: cvkg_runic_text::RunicTextEngine,
141 memoize_cache: Option<(u64, u64)>,
144}
145
146impl SoftwareRenderer {
147 pub fn new(width: u32, height: u32) -> Self {
149 let now = Instant::now();
150 Self {
151 fb: Framebuffer::new(width, height),
152 start_time: now,
153 last_frame: now,
154 #[cfg(feature = "text")]
155 text_engine: {
156 let mut engine = cvkg_runic_text::RunicTextEngine::new_light();
157 engine.load_font_data(
158 include_bytes!("../Fonts/Jupiteroid.ttf").to_vec(),
159 );
160 engine
161 },
162 memoize_cache: None,
163 }
164 }
165
166 pub fn with_color(width: u32, height: u32, color: [f32; 4]) -> Self {
168 let now = Instant::now();
169 Self {
170 fb: Framebuffer::with_color(width, height, color),
171 start_time: now,
172 last_frame: now,
173 #[cfg(feature = "text")]
174 text_engine: {
175 let mut engine = cvkg_runic_text::RunicTextEngine::new_light();
176 engine.load_font_data(include_bytes!("../Fonts/Jupiteroid.ttf").to_vec());
177 engine
178 },
179 memoize_cache: None,
180 }
181 }
182
183 pub fn framebuffer(&self) -> &Framebuffer {
185 &self.fb
186 }
187
188 pub fn width(&self) -> u32 {
192 self.fb.width()
193 }
194
195 pub fn height(&self) -> u32 {
197 self.fb.height()
198 }
199
200 fn fill_rect_internal(&mut self, rect: Rect, color: [f32; 4]) {
201 let x0 = rect.x.max(0.0) as u32;
202 let y0 = rect.y.max(0.0) as u32;
203 let x1 = (rect.x + rect.width).min(self.fb.width() as f32) as u32;
204 let y1 = (rect.y + rect.height).min(self.fb.height() as f32) as u32;
205 for y in y0..y1 {
206 for x in x0..x1 {
207 self.fb.blend_pixel(x, y, color);
208 }
209 }
210 }
211
212 fn fill_rounded_rect_internal(&mut self, rect: Rect, radius: f32, color: [f32; 4]) {
213 let r = radius.min(rect.width * 0.5).min(rect.height * 0.5);
214 let x0 = rect.x.max(0.0) as u32;
215 let y0 = rect.y.max(0.0) as u32;
216 let x1 = (rect.x + rect.width).min(self.fb.width() as f32) as u32;
217 let y1 = (rect.y + rect.height).min(self.fb.height() as f32) as u32;
218
219 for py in y0..y1 {
220 for px in x0..x1 {
221 let fx = px as f32 + 0.5;
222 let fy = py as f32 + 0.5;
223 let dx = (fx - rect.x).max(rect.x + rect.width - fx).max(0.0) - rect.width * 0.5;
225 let dy = (fy - rect.y).max(rect.y + rect.height - fy).max(0.0) - rect.height * 0.5;
226 let d = (dx.max(0.0) * dx.max(0.0) + dy.max(0.0) * dy.max(0.0)).sqrt() - r;
228 if d <= 0.0 {
229 let alpha = if d > -1.0 {
230 (1.0 + d).clamp(0.0, 1.0)
231 } else {
232 1.0
233 };
234 let mut c = color;
235 c[3] *= alpha;
236 self.fb.blend_pixel(px, py, c);
237 }
238 }
239 }
240 }
241}
242
243impl ElapsedTime for SoftwareRenderer {
244 fn elapsed_time(&self) -> f32 {
245 self.start_time.elapsed().as_secs_f32()
246 }
247
248 fn delta_time(&self) -> f32 {
249 self.last_frame.elapsed().as_secs_f32()
250 }
251}
252
253impl Renderer for SoftwareRenderer {
254 fn fill_rect(&mut self, rect: Rect, color: [f32; 4]) {
255 self.fill_rect_internal(rect, color);
256 }
257
258 fn fill_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4]) {
259 self.fill_rounded_rect_internal(rect, radius, color);
260 }
261
262 fn fill_ellipse(&mut self, rect: Rect, color: [f32; 4]) {
263 let cx = rect.x + rect.width * 0.5;
264 let cy = rect.y + rect.height * 0.5;
265 let rx = rect.width * 0.5;
266 let ry = rect.height * 0.5;
267 if rx <= 0.0 || ry <= 0.0 {
268 return;
269 }
270
271 let x0 = (cx - rx).max(0.0) as u32;
272 let y0 = (cy - ry).max(0.0) as u32;
273 let x1 = (cx + rx).min(self.fb.width() as f32) as u32;
274 let y1 = (cy + ry).min(self.fb.height() as f32) as u32;
275
276 for py in y0..y1 {
277 for px in x0..x1 {
278 let fx = px as f32 + 0.5;
279 let fy = py as f32 + 0.5;
280 let dx = (fx - cx) / rx;
281 let dy = (fy - cy) / ry;
282 let dist = dx * dx + dy * dy;
283 if dist <= 1.0 {
284 let alpha = if dist > 0.75 {
285 ((1.0 - dist) * 4.0).clamp(0.0, 1.0)
286 } else {
287 1.0
288 };
289 let mut c = color;
290 c[3] *= alpha;
291 self.fb.blend_pixel(px, py, c);
292 }
293 }
294 }
295 }
296
297 fn fill_glass_rect(&mut self, rect: Rect, radius: f32, blur_radius: f32) {
298 let alpha = (0.3 + blur_radius * 0.01).min(0.8);
300 let tint = [1.0, 1.0, 1.0, alpha];
301 self.fill_rounded_rect_internal(rect, radius, tint);
302 }
303
304 fn fill_glass_rect_with_intensity(
305 &mut self,
306 rect: Rect,
307 radius: f32,
308 blur_radius: f32,
309 glass_intensity: f32,
310 ) {
311 let alpha = (0.3 + blur_radius * 0.01 * glass_intensity).min(0.8) * glass_intensity;
312 let tint = [1.0, 1.0, 1.0, alpha];
313 self.fill_rounded_rect_internal(rect, radius, tint);
314 }
315
316 fn stroke_rect(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
317 let sw = stroke_width.max(0.5);
318 self.fill_rect_internal(
320 Rect { x: rect.x, y: rect.y, width: rect.width, height: sw },
321 color,
322 );
323 self.fill_rect_internal(
325 Rect { x: rect.x, y: rect.y + rect.height - sw, width: rect.width, height: sw },
326 color,
327 );
328 self.fill_rect_internal(
330 Rect { x: rect.x, y: rect.y, width: sw, height: rect.height },
331 color,
332 );
333 self.fill_rect_internal(
335 Rect { x: rect.x + rect.width - sw, y: rect.y, width: sw, height: rect.height },
336 color,
337 );
338 }
339
340 fn stroke_rounded_rect(&mut self, rect: Rect, radius: f32, color: [f32; 4], stroke_width: f32) {
341 let r = radius.min(rect.width * 0.5).min(rect.height * 0.5);
342 let sw = stroke_width.max(0.5);
343 let x0 = rect.x.max(0.0) as u32;
344 let y0 = rect.y.max(0.0) as u32;
345 let x1 = (rect.x + rect.width).min(self.fb.width() as f32) as u32;
346 let y1 = (rect.y + rect.height).min(self.fb.height() as f32) as u32;
347
348 for py in y0..y1 {
349 for px in x0..x1 {
350 let fx = px as f32 + 0.5;
351 let fy = py as f32 + 0.5;
352 let dx = (fx - (rect.x + r)).max(0.0) + (rect.x + rect.width - r - fx).max(0.0) - r;
353 let dy = (fy - (rect.y + r)).max(0.0) + (rect.y + rect.height - r - fy).max(0.0) - r;
354 let outside = (dx * dx + dy * dy).sqrt();
355 if outside <= r && outside >= r - sw {
356 let alpha = if outside > r - 1.0 {
357 (r - outside).clamp(0.0, 1.0)
358 } else if outside < r - sw + 1.0 {
359 (outside - (r - sw)).clamp(0.0, 1.0)
360 } else {
361 1.0
362 };
363 let mut c = color;
364 c[3] *= alpha;
365 self.fb.blend_pixel(px, py, c);
366 }
367 }
368 }
369 }
370
371 fn stroke_ellipse(&mut self, rect: Rect, color: [f32; 4], stroke_width: f32) {
372 let cx = rect.x + rect.width * 0.5;
373 let cy = rect.y + rect.height * 0.5;
374 let rx = rect.width * 0.5;
375 let ry = rect.height * 0.5;
376 let sw = stroke_width.max(0.5);
377
378 if rx <= 0.0 || ry <= 0.0 {
379 return;
380 }
381
382 let x0 = (cx - rx).max(0.0) as u32;
383 let y0 = (cy - ry).max(0.0) as u32;
384 let x1 = (cx + rx).min(self.fb.width() as f32) as u32;
385 let y1 = (cy + ry).min(self.fb.height() as f32) as u32;
386
387 for py in y0..y1 {
388 for px in x0..x1 {
389 let fx = px as f32 + 0.5;
390 let fy = py as f32 + 0.5;
391 let dx = (fx - cx) / rx;
392 let dy = (fy - cy) / ry;
393 let dist = dx * dx + dy * dy;
394 if dist <= 1.0 && dist >= (1.0 - sw / rx.max(ry)).powi(2) {
395 self.fb.blend_pixel(px, py, color);
396 }
397 }
398 }
399 }
400
401 fn draw_line(&mut self, x1: f32, y1: f32, x2: f32, y2: f32, color: [f32; 4], stroke_width: f32) {
402 let dx = (x2 - x1).abs();
404 let dy = (y2 - y1).abs();
405 let steps = (dx.max(dy) as u32).max(1);
406 let sw = (stroke_width * 0.5).max(0.5);
407
408 for i in 0..=steps {
409 let t = i as f32 / steps as f32;
410 let x = x1 + (x2 - x1) * t;
411 let y = y1 + (y2 - y1) * t;
412 let r = Rect { x: x - sw, y: y - sw, width: stroke_width, height: stroke_width };
414 self.fill_rect_internal(r, color);
415 }
416 }
417
418 fn draw_focus_ring(
419 &mut self,
420 rect: Rect,
421 radius: f32,
422 offset: f32,
423 width: f32,
424 color: [f32; 4],
425 ) {
426 let ring_rect = Rect {
427 x: rect.x - offset,
428 y: rect.y - offset,
429 width: rect.width + 2.0 * offset,
430 height: rect.height + 2.0 * offset,
431 };
432 self.stroke_rounded_rect(ring_rect, radius + offset, color, width);
433 }
434
435 fn draw_linear_gradient(
436 &mut self,
437 rect: Rect,
438 start_color: [f32; 4],
439 end_color: [f32; 4],
440 _angle: f32,
441 ) {
442 let x0 = rect.x.max(0.0) as u32;
444 let x1 = (rect.x + rect.width).min(self.fb.width() as f32) as u32;
445 let w = rect.width.max(1.0);
446
447 for px in x0..x1 {
448 let t = (px as f32 - rect.x) / w;
449 let color = [
450 start_color[0] + (end_color[0] - start_color[0]) * t,
451 start_color[1] + (end_color[1] - start_color[1]) * t,
452 start_color[2] + (end_color[2] - start_color[2]) * t,
453 start_color[3] + (end_color[3] - start_color[3]) * t,
454 ];
455 let col = Rect { x: px as f32, y: rect.y, width: 1.0, height: rect.height };
456 self.fill_rect_internal(col, color);
457 }
458 }
459
460 fn measure_text(&mut self, text: &str, size: f32) -> (f32, f32) {
472 (text.len() as f32 * size * 0.6, size)
473 }
474
475 fn shape_rich_text(
478 &mut self,
479 spans: &[cvkg_runic_text::TextSpan],
480 max_width: Option<f32>,
481 align: cvkg_runic_text::TextAlign,
482 overflow: cvkg_runic_text::TextOverflow,
483 ) -> Option<cvkg_runic_text::ShapedText> {
484 #[cfg(feature = "text")]
485 {
486 self.text_engine.shape_layout(spans, max_width, align, overflow).ok()
488 }
489 #[cfg(not(feature = "text"))]
490 {
491 None
492 }
493 }
494
495 fn draw_shaped_text(&mut self, _text: &cvkg_runic_text::ShapedText, _x: f32, _y: f32) {
497 }
499
500 fn draw_texture(&mut self, texture_id: u32, _rect: Rect) {
501 log::warn!(
502 "[SoftwareRenderer] draw_texture({}) is not implemented in software. \
503 The texture will not appear in the output.",
504 texture_id
505 );
506 }
507
508 fn draw_image(&mut self, image_name: &str, _rect: Rect) {
509 log::warn!(
510 "[SoftwareRenderer] draw_image('{}') is not implemented in software. \
511 The image will not appear in the output.",
512 image_name
513 );
514 }
515
516 fn draw_svg(&mut self, name: &str, _rect: Rect) {
517 log::warn!(
518 "[SoftwareRenderer] draw_svg('{}') is not implemented in software. \
519 The SVG will not appear in the output.",
520 name
521 );
522 }
523
524 fn draw_mesh(&mut self, _mesh: &Mesh, _color: [f32; 4], _transform: glam::Mat4) {
525 log::warn!(
526 "[SoftwareRenderer] draw_mesh() is not implemented in software. \
527 The mesh will not appear in the output."
528 );
529 }
530
531 fn draw_mesh_3d(
532 &mut self,
533 _mesh: &Mesh,
534 _material: &Material3D,
535 _transform: &Transform3D,
536 ) {
537 log::warn!(
538 "[SoftwareRenderer] draw_mesh_3d() is not implemented in software. \
539 The 3D mesh will not appear in the output."
540 );
541 }
542
543 fn fill_glass_rect_with_pressure(
544 &mut self,
545 _rect: Rect,
546 _radius: f32,
547 _blur_radius: f32,
548 _pressure: f32,
549 ) {
550 self.fill_glass_rect(_rect, _radius, _blur_radius);
552 }
553
554 fn draw_hologram(&mut self, _rect: Rect, hologram_id: &str, _time: f32) {
555 log::warn!(
556 "[SoftwareRenderer] draw_hologram('{}') is not implemented in software. \
557 Holograms require GPU compute shaders.",
558 hologram_id
559 );
560 }
561
562 fn memoize(&mut self, id: u64, data_hash: u64, render_fn: &dyn Fn(&mut dyn Renderer)) {
563 if let Some(&(cached_id, cached_hash)) = self.memoize_cache.as_ref() {
567 if cached_id == id && cached_hash == data_hash {
568 return; }
570 }
571 self.memoize_cache = Some((id, data_hash));
572 render_fn(self);
573 }
574}
575
576#[cfg(test)]
579mod tests {
580 use super::*;
581
582 #[test]
583 fn framebuffer_new() {
584 let fb = Framebuffer::new(100, 100);
585 assert_eq!(fb.width(), 100);
586 assert_eq!(fb.height(), 100);
587 assert_eq!(fb.pixels().len(), 10000);
588 }
589
590 #[test]
591 fn framebuffer_with_color() {
592 let fb = Framebuffer::with_color(10, 10, [1.0, 0.0, 0.0, 1.0]);
593 for &px in fb.pixels() {
594 let c = unpack_rgba(px);
595 assert!((c[0] - 1.0).abs() < 0.01);
596 assert!((c[1]).abs() < 0.01);
597 assert!((c[2]).abs() < 0.01);
598 assert!((c[3] - 1.0).abs() < 0.01);
599 }
600 }
601
602 #[test]
603 fn software_fill_rect() {
604 let mut r = SoftwareRenderer::new(100, 100);
605 r.fill_rect(Rect { x: 10.0, y: 10.0, width: 20.0, height: 20.0 }, [1.0, 0.0, 0.0, 1.0]);
606
607 let fb = r.framebuffer();
608 let idx = (15 * 100 + 15) as usize;
610 let c = unpack_rgba(fb.pixels()[idx]);
611 assert!((c[0] - 1.0).abs() < 0.01);
612
613 let idx2 = (5 * 100 + 5) as usize;
615 let c2 = unpack_rgba(fb.pixels()[idx2]);
616 assert!(c2[3] < 0.01);
617 }
618
619 #[test]
620 fn software_fill_rounded_rect() {
621 let mut r = SoftwareRenderer::new(100, 100);
622 r.fill_rounded_rect(
623 Rect { x: 10.0, y: 10.0, width: 40.0, height: 40.0 },
624 8.0,
625 [0.0, 1.0, 0.0, 1.0],
626 );
627 let fb = r.framebuffer();
628 let idx = (30 * 100 + 30) as usize;
630 let c = unpack_rgba(fb.pixels()[idx]);
631 assert!((c[1] - 1.0).abs() < 0.01);
632 }
633
634 #[test]
635 fn software_fill_ellipse() {
636 let mut r = SoftwareRenderer::new(100, 100);
637 r.fill_ellipse(
638 Rect { x: 20.0, y: 20.0, width: 60.0, height: 60.0 },
639 [0.0, 0.0, 1.0, 1.0],
640 );
641 let fb = r.framebuffer();
642 let idx = (50 * 100 + 50) as usize;
644 let c = unpack_rgba(fb.pixels()[idx]);
645 assert!((c[2] - 1.0).abs() < 0.01);
646 }
647
648 #[test]
649 fn software_glass_degrades_to_solid() {
650 let mut r = SoftwareRenderer::new(100, 100);
651 r.fill_glass_rect(Rect { x: 10.0, y: 10.0, width: 40.0, height: 40.0 }, 8.0, 16.0);
652 let fb = r.framebuffer();
653 let idx = (30 * 100 + 30) as usize;
655 let c = unpack_rgba(fb.pixels()[idx]);
656 assert!(c[3] > 0.1, "glass should have some opacity");
657 assert!(c[3] < 0.9, "glass should not be fully opaque");
658 }
659
660 #[test]
661 fn software_stroke_rect() {
662 let mut r = SoftwareRenderer::new(100, 100);
663 r.stroke_rect(
664 Rect { x: 10.0, y: 10.0, width: 30.0, height: 30.0 },
665 [1.0, 1.0, 1.0, 1.0],
666 2.0,
667 );
668 let fb = r.framebuffer();
669 let idx = (10 * 100 + 10) as usize;
671 let c = unpack_rgba(fb.pixels()[idx]);
672 assert!(c[0] > 0.5);
673 }
674
675 #[test]
676 fn software_clear_color() {
677 let r = SoftwareRenderer::with_color(10, 10, [0.5, 0.5, 0.5, 1.0]);
678 let fb = r.framebuffer();
679 let c = unpack_rgba(fb.pixels()[0]);
681 assert!((c[0] - 0.5).abs() < 0.02);
682 }
683
684 #[test]
685 fn software_measure_text() {
686 let mut r = SoftwareRenderer::new(100, 100);
687 let (w, h) = r.measure_text("Hello", 14.0);
688 assert!(w > 0.0);
689 assert!((h - 14.0).abs() < 0.01);
690 }
691
692 #[test]
693 fn software_elapsed_time() {
694 let r = SoftwareRenderer::new(100, 100);
695 assert!(r.elapsed_time() >= 0.0);
696 }
697
698 #[test]
699 fn software_gradient() {
700 let mut r = SoftwareRenderer::new(100, 100);
701 r.draw_linear_gradient(
702 Rect { x: 0.0, y: 0.0, width: 100.0, height: 1.0 },
703 [1.0, 0.0, 0.0, 1.0],
704 [0.0, 0.0, 1.0, 1.0],
705 0.0,
706 );
707 let fb = r.framebuffer();
708 let left = unpack_rgba(fb.pixels()[0]);
709 let right = unpack_rgba(fb.pixels()[99]);
710 assert!((left[0] - 1.0).abs() < 0.02); assert!((right[2] - 1.0).abs() < 0.02); }
713
714 #[test]
724 fn p1_8_draw_image_does_not_panic() {
725 let mut r = SoftwareRenderer::new(100, 100);
726 r.draw_image("test.png", cvkg_core::Rect { x: 0.0, y: 0.0, width: 50.0, height: 50.0 });
727 let fb = r.framebuffer();
729 for pixel in fb.pixels() {
730 assert_eq!(*pixel, 0, "draw_image should not modify the framebuffer");
731 }
732 }
733
734 #[test]
735 fn p1_8_draw_svg_does_not_panic() {
736 let mut r = SoftwareRenderer::new(100, 100);
737 r.draw_svg("icon", cvkg_core::Rect { x: 0.0, y: 0.0, width: 50.0, height: 50.0 });
738 }
740
741 #[test]
742 fn p1_8_draw_texture_does_not_panic() {
743 let mut r = SoftwareRenderer::new(100, 100);
744 r.draw_texture(1, cvkg_core::Rect { x: 0.0, y: 0.0, width: 50.0, height: 50.0 });
745 }
747}