1use core::cmp::min;
2use pico_ecs::_component_storage::ComponentStorage;
3use pico_ecs::_world::World;
4use pico_transform::_pivot::Pivot;
5use pico_transform::_position::Position;
6use pico_transform::_size::Size;
7
8use pico_engine_hardware::Display;
13
14use crate::components::_render_component::RendererComponent;
15use crate::types::_cell::Cell;
16use crate::types::_tile::Tile;
17use crate::types::_tile_map::TileMap;
18
19pub struct RenderPipeline {
21 prev_cam_x: u16,
22 prev_cam_y: u16,
23 first_frame: bool,
24}
25
26impl RenderPipeline {
27 pub fn new() -> Self {
28 Self {
29 prev_cam_x: 0,
30 prev_cam_y: 0,
31 first_frame: true,
32 }
33 }
34
35 #[inline]
37 pub fn render_direct(
38 &mut self,
39 world: &World,
40 position_storage: &ComponentStorage<Position>,
41 renderer_storage: &ComponentStorage<RendererComponent>,
42 size_storage: &ComponentStorage<Size>,
43 pivot_storage: &ComponentStorage<Pivot>,
44 background_tilemap: &'static TileMap,
45 foreground_tilemap: &'static TileMap,
46 display: &mut impl Display,
47 ) {
48 self.draw_background(
50 world,
51 position_storage,
52 size_storage,
53 pivot_storage,
54 background_tilemap,
55 display,
56 );
57
58 for (_entity, transform, renderer, size, pivot) in world.query4(
60 position_storage,
61 renderer_storage,
62 size_storage,
63 pivot_storage,
64 ) {
65 let sprite_x = transform.x as i16 - pivot.x as i16 - world.camera.x as i16;
67 let sprite_y = transform.y as i16 - pivot.y as i16 - world.camera.y as i16;
68
69 draw_sprite(
71 display,
72 &renderer.texture[renderer.sprite.data_start..renderer.sprite.data_end],
73 sprite_x,
74 sprite_y,
75 size.width as usize,
76 size.height as usize,
77 renderer.sprite.is_opaque,
78 );
79 }
80
81 self.draw_foreground(
83 world,
84 position_storage,
85 size_storage,
86 pivot_storage,
87 foreground_tilemap,
88 display,
89 );
90
91 self.prev_cam_x = world.camera.x;
93 self.prev_cam_y = world.camera.y;
94 self.first_frame = false;
95
96 #[cfg(feature = "is_debug")]
97 {
98 let mut fps = 0;
99 if delta_time > 0 {
100 fps = 1000 / delta_time;
101 }
102 draw_fps(display, fps);
104 }
105 display.display_buffer();
107 }
108
109 #[inline(never)]
111 fn draw_background(
112 &mut self,
113 world: &World,
114 position_storage: &ComponentStorage<Position>,
115 size_storage: &ComponentStorage<Size>,
116 pivot_storage: &ComponentStorage<Pivot>,
117 tilemap: &'static TileMap,
118 display: &mut impl Display,
119 ) {
120 self.draw_tilemap(
121 world,
122 position_storage,
123 size_storage,
124 pivot_storage,
125 display,
126 tilemap,
127 true, false, );
130 }
131
132 #[inline(never)]
134 fn draw_foreground(
135 &mut self,
136 world: &World,
137 position_storage: &ComponentStorage<Position>,
138 size_storage: &ComponentStorage<Size>,
139 pivot_storage: &ComponentStorage<Pivot>,
140 tilemap: &'static TileMap,
141 display: &mut impl Display,
142 ) {
143 self.draw_tilemap(
144 world,
145 position_storage,
146 size_storage,
147 pivot_storage,
148 display,
149 tilemap,
150 false, true, );
153 }
154
155 #[inline(never)]
157 fn draw_tilemap(
158 &mut self,
159 world: &World,
160 position_storage: &ComponentStorage<Position>,
161 size_storage: &ComponentStorage<Size>,
162 pivot_storage: &ComponentStorage<Pivot>,
163 display: &mut impl Display,
164 tilemap: &'static TileMap,
165 allow_buffer_shift: bool,
166 is_foreground: bool,
167 ) {
168 const TILE_SIZE: u16 = 16;
169 const HORIZONTAL_TILES: u16 = 20;
170 const VERTICAL_TILES: u16 = 15;
171
172 let cam_x = world.camera.x;
173 let cam_y = world.camera.y;
174 let delta_x = cam_x as i16 - self.prev_cam_x as i16;
175 let delta_y = cam_y as i16 - self.prev_cam_y as i16;
176
177 if delta_x == 0 && delta_y == 0 && !self.first_frame {
179 for (_entity, transform, size, pivot) in
181 world.query3(position_storage, size_storage, pivot_storage)
182 {
183 let entity_left = transform.x - pivot.x as u16;
185 let entity_top = transform.y - pivot.y as u16;
186 let entity_right = entity_left + size.width;
187 let entity_bottom = entity_top + size.height;
188
189 let tile_start_x = entity_left / TILE_SIZE;
192 let tile_start_y = if is_foreground {
193 if entity_top >= TILE_SIZE {
194 (entity_top + TILE_SIZE - 1) / TILE_SIZE
195 } else {
196 0
197 }
198 } else {
199 entity_top / TILE_SIZE
200 };
201 let tile_end_x = min((entity_right + TILE_SIZE - 1) / TILE_SIZE, 30);
202 let tile_end_y = min(
203 (entity_bottom + if is_foreground { TILE_SIZE } else { 0 } + TILE_SIZE - 1)
204 / TILE_SIZE,
205 30,
206 );
207
208 for ty in tile_start_y..tile_end_y {
210 for tx in tile_start_x..tile_end_x {
211 self.draw_tile_column(
212 tx,
213 ty,
214 cam_x as i16,
215 cam_y as i16,
216 display,
217 tilemap.cells,
218 tilemap.tiles,
219 tilemap.texture,
220 is_foreground,
221 );
222 }
223 }
224 }
225 return;
226 }
227
228 if allow_buffer_shift
230 && delta_x.abs() <= 2
231 && delta_y.abs() <= 2
232 && (delta_x != 0 || delta_y != 0)
233 {
234 shift_buffer(display, delta_x, delta_y);
235
236 let start_x = cam_x / TILE_SIZE;
239 let start_y = cam_y / TILE_SIZE;
240 let end_x = min((cam_x + 320) / TILE_SIZE + 1, 30);
241 let end_y = min((cam_y + 240) / TILE_SIZE + 1, 30);
242
243 if delta_x > 0 {
245 for y in start_y..end_y {
247 if end_x > 0 {
248 self.draw_tile_column(
249 end_x - 1,
250 y,
251 cam_x as i16,
252 cam_y as i16,
253 display,
254 tilemap.cells,
255 tilemap.tiles,
256 tilemap.texture,
257 is_foreground,
258 );
259 }
260 if end_x > 1 {
261 self.draw_tile_column(
262 end_x - 2,
263 y,
264 cam_x as i16,
265 cam_y as i16,
266 display,
267 tilemap.cells,
268 tilemap.tiles,
269 tilemap.texture,
270 is_foreground,
271 );
272 }
273 }
274 } else if delta_x < 0 {
275 for y in start_y..end_y {
277 self.draw_tile_column(
278 start_x,
279 y,
280 cam_x as i16,
281 cam_y as i16,
282 display,
283 tilemap.cells,
284 tilemap.tiles,
285 tilemap.texture,
286 is_foreground,
287 );
288 if start_x + 1 < 30 {
289 self.draw_tile_column(
290 start_x + 1,
291 y,
292 cam_x as i16,
293 cam_y as i16,
294 display,
295 tilemap.cells,
296 tilemap.tiles,
297 tilemap.texture,
298 is_foreground,
299 );
300 }
301 }
302 }
303
304 if delta_y > 0 {
306 for x in start_x..end_x {
308 if end_y > 0 {
309 self.draw_tile_column(
310 x,
311 end_y - 1,
312 cam_x as i16,
313 cam_y as i16,
314 display,
315 tilemap.cells,
316 tilemap.tiles,
317 tilemap.texture,
318 is_foreground,
319 );
320 }
321 if end_y > 1 {
322 self.draw_tile_column(
323 x,
324 end_y - 2,
325 cam_x as i16,
326 cam_y as i16,
327 display,
328 tilemap.cells,
329 tilemap.tiles,
330 tilemap.texture,
331 is_foreground,
332 );
333 }
334 }
335 } else if delta_y < 0 {
336 for x in start_x..end_x {
338 self.draw_tile_column(
339 x,
340 start_y,
341 cam_x as i16,
342 cam_y as i16,
343 display,
344 tilemap.cells,
345 tilemap.tiles,
346 tilemap.texture,
347 is_foreground,
348 );
349 if start_y + 1 < 30 {
350 self.draw_tile_column(
351 x,
352 start_y + 1,
353 cam_x as i16,
354 cam_y as i16,
355 display,
356 tilemap.cells,
357 tilemap.tiles,
358 tilemap.texture,
359 is_foreground,
360 );
361 }
362 }
363 }
364
365 for (_entity, transform, size, pivot) in
367 world.query3(position_storage, size_storage, pivot_storage)
368 {
369 let entity_left = transform.x - pivot.x as u16;
371 let entity_top = transform.y - pivot.y as u16;
372 let entity_right = entity_left + size.width;
373 let entity_bottom = entity_top + size.height;
374
375 let tile_start_x = entity_left / TILE_SIZE;
378 let tile_start_y = if is_foreground {
379 if entity_top >= TILE_SIZE {
380 (entity_top + TILE_SIZE - 1) / TILE_SIZE
381 } else {
382 0
383 }
384 } else {
385 entity_top / TILE_SIZE
386 };
387 let tile_end_x = min((entity_right + TILE_SIZE - 1) / TILE_SIZE, 30);
388 let tile_end_y = min(
389 (entity_bottom + if is_foreground { TILE_SIZE } else { 0 } + TILE_SIZE - 1)
390 / TILE_SIZE,
391 30,
392 );
393
394 for ty in tile_start_y..tile_end_y {
396 for tx in tile_start_x..tile_end_x {
397 self.draw_tile_column(
398 tx,
399 ty,
400 cam_x as i16,
401 cam_y as i16,
402 display,
403 tilemap.cells,
404 tilemap.tiles,
405 tilemap.texture,
406 is_foreground,
407 );
408 }
409 }
410 }
411 } else {
412 let start_x = cam_x / TILE_SIZE;
414 let start_y = cam_y / TILE_SIZE;
415 let end_x = min(start_x + HORIZONTAL_TILES + 1, 30);
416 let end_y = min(start_y + VERTICAL_TILES + 2, 30);
417
418 for y in start_y..end_y {
419 for x in start_x..end_x {
420 self.draw_tile_column(
421 x,
422 y,
423 cam_x as i16,
424 cam_y as i16,
425 display,
426 tilemap.cells,
427 tilemap.tiles,
428 tilemap.texture,
429 is_foreground,
430 );
431 }
432 }
433 }
434 }
435
436 #[inline]
437 fn draw_tile_column(
438 &self,
439 x: u16,
440 y: u16,
441 cam_x: i16,
442 cam_y: i16,
443 display: &mut impl Display,
444 cells: &'static [Cell],
445 tiles: &'static [Tile],
446 texture: &'static [u16],
447 is_foreground: bool,
448 ) {
449 let cell_idx = (y as usize * 30) + x as usize;
450 if cell_idx >= cells.len() {
451 return;
452 }
453 let cell = &cells[cell_idx];
454 let cell_tiles = &tiles[cell.start..cell.end];
455
456 for tile in cell_tiles {
457 let sprite = tile.sprite;
458 let pos_x = (x * 16) as i16 - cam_x;
461 let pos_y = (y * 16) as i16 - cam_y - if is_foreground { 16 } else { 0 };
462
463 draw_sprite(
464 display,
465 &texture[sprite.data_start..sprite.data_end],
466 pos_x,
467 pos_y,
468 16,
469 16,
470 sprite.is_opaque,
471 );
472 }
473 }
474}
475
476#[inline]
478pub fn draw_sprite(
479 display: &mut impl Display,
480 data: &[u16],
481 x: i16,
482 y: i16,
483 width: usize,
484 height: usize,
485 is_opaque: bool,
486) {
487 if x >= display.get_screen_width() as i16
489 || y >= display.get_screen_height() as i16
490 || x + width as i16 <= 0
491 || y + height as i16 <= 0
492 {
493 return;
494 }
495
496 if is_opaque {
497 draw_sprite_opaque(display, data, x, y, width, height);
498 } else {
499 draw_sprite_strip_encoded(display, data, x, y, width, height);
500 }
501}
502
503#[inline]
505fn draw_sprite_opaque(
506 display: &mut impl Display,
507 data: &[u16],
508 x: i16,
509 y: i16,
510 width: usize,
511 height: usize,
512) {
513 let start_y = if y < 0 { -y } else { 0 };
515 let end_y = ((y + height as i16).min(display.get_screen_height() as i16) - y).max(0) as usize;
516 let start_x = if x < 0 { -x } else { 0 };
517 let end_x = ((x + width as i16).min(display.get_screen_width() as i16) - x).max(0) as usize;
518
519 if start_y == 0 && end_y == height && start_x == 0 && end_x == width {
521 for row in 0..height {
522 let screen_y = (y + row as i16) as usize;
523 let dst_start = screen_y * display.get_screen_width() + x as usize;
524 let src_start = row * width;
525 unsafe {
526 core::ptr::copy_nonoverlapping(
527 data.as_ptr().add(src_start),
528 display.frame_buffer().as_mut_ptr().add(dst_start),
529 width,
530 );
531 }
532 }
533 } else {
534 for row in start_y as usize..end_y {
536 let screen_y = (y + row as i16) as usize;
537 let dst_start = screen_y * display.get_screen_width() + (x + start_x) as usize;
538 let src_start = row * width + start_x as usize;
539 let copy_len = end_x - start_x as usize;
540
541 unsafe {
542 core::ptr::copy_nonoverlapping(
543 data.as_ptr().add(src_start),
544 display.frame_buffer().as_mut_ptr().add(dst_start),
545 copy_len,
546 );
547 }
548 }
549 }
550}
551
552#[inline(never)]
554fn draw_sprite_strip_encoded(
555 display: &mut impl Display,
556 data: &[u16],
557 x: i16,
558 y: i16,
559 width: usize,
560 height: usize,
561) {
562 let mut data_idx = 0;
563 let mut row = 0;
564 let mut col = 0;
565 let data_len = data.len();
566 let fb_ptr = display.frame_buffer().as_mut_ptr();
567 let data_ptr = data.as_ptr();
568
569 unsafe {
570 while data_idx < data_len {
571 let header = *data_ptr.add(data_idx);
573 data_idx += 1;
574
575 let transparent_count = (header & 0xFF) as usize;
576 let opaque_count = ((header >> 8) & 0xFF) as usize;
577
578 let mut skip = transparent_count;
580 while skip > 0 {
581 let available = width - col;
582 if skip >= available {
583 skip -= available;
584 col = 0;
585 row += 1;
586 if row >= height {
587 return;
588 }
589 } else {
590 col += skip;
591 break;
592 }
593 }
594
595 let mut remaining = opaque_count;
597
598 while remaining > 0 && row < height {
599 let pixels_in_row = width - col;
600 let batch_size = remaining.min(pixels_in_row);
601
602 let screen_x = x + col as i16;
603 let screen_y = y + row as i16;
604
605 if screen_y >= 0
607 && screen_y < display.get_screen_height() as i16
608 && screen_x >= 0
609 && screen_x + batch_size as i16 <= display.get_screen_width() as i16
610 {
611 let fb_idx = screen_y as usize * display.get_screen_width() + screen_x as usize;
613 core::ptr::copy_nonoverlapping(
614 data_ptr.add(data_idx),
615 fb_ptr.add(fb_idx),
616 batch_size,
617 );
618 } else if screen_y >= 0 && screen_y < display.get_screen_height() as i16 {
619 for i in 0..batch_size {
621 let px = screen_x + i as i16;
622 if px >= 0 && px < display.get_screen_width() as i16 {
623 let fb_idx =
624 screen_y as usize * display.get_screen_width() + px as usize;
625 *fb_ptr.add(fb_idx) = *data_ptr.add(data_idx + i);
626 }
627 }
628 }
629
630 data_idx += batch_size;
631 col += batch_size;
632 remaining -= batch_size;
633
634 if col >= width {
635 col = 0;
636 row += 1;
637 }
638 }
639 }
640 }
641}
642
643pub fn draw_fps(display: &mut impl Display, fps: u32) {
645 const DIGIT_WIDTH: usize = 4;
647 const DIGIT_HEIGHT: usize = 7;
648 const DIGITS: [[u8; DIGIT_HEIGHT]; 10] = [
649 [0b1110, 0b1010, 0b1010, 0b1010, 0b1010, 0b1010, 0b1110], [0b0100, 0b1100, 0b0100, 0b0100, 0b0100, 0b0100, 0b1110], [0b1110, 0b0010, 0b0010, 0b1110, 0b1000, 0b1000, 0b1110], [0b1110, 0b0010, 0b0010, 0b1110, 0b0010, 0b0010, 0b1110], [0b1010, 0b1010, 0b1010, 0b1110, 0b0010, 0b0010, 0b0010], [0b1110, 0b1000, 0b1000, 0b1110, 0b0010, 0b0010, 0b1110], [0b1110, 0b1000, 0b1000, 0b1110, 0b1010, 0b1010, 0b1110], [0b1110, 0b0010, 0b0010, 0b0010, 0b0010, 0b0010, 0b0010], [0b1110, 0b1010, 0b1010, 0b1110, 0b1010, 0b1010, 0b1110], [0b1110, 0b1010, 0b1010, 0b1110, 0b0010, 0b0010, 0b1110], ];
660
661 const FG_COLOR: u16 = 0xFFFF; const BG_COLOR: u16 = 0x0000; const START_X: usize = 4;
664 const START_Y: usize = 4;
665 const PADDING: usize = 2;
666
667 const BG_WIDTH: usize = 3 * DIGIT_WIDTH + 2 + PADDING * 2; const BG_HEIGHT: usize = DIGIT_HEIGHT + PADDING * 2;
670
671 for y in 0..BG_HEIGHT {
673 for x in 0..BG_WIDTH {
674 let fb_x = START_X + x;
675 let fb_y = START_Y + y;
676 if fb_x < display.get_screen_width() && fb_y < display.get_screen_height() {
677 let fb_idx = fb_y * display.get_screen_width() + fb_x;
678 display.frame_buffer()[fb_idx] = BG_COLOR;
679 }
680 }
681 }
682
683 let mut digits = [0u8; 3];
685 let mut temp = fps.min(999); digits[2] = (temp % 10) as u8;
687 temp /= 10;
688 digits[1] = (temp % 10) as u8;
689 temp /= 10;
690 digits[0] = (temp % 10) as u8;
691
692 for (digit_idx, &digit) in digits.iter().enumerate() {
694 let x_offset = START_X + PADDING + digit_idx * (DIGIT_WIDTH + 1);
695
696 for y in 0..DIGIT_HEIGHT {
697 let row_data = DIGITS[digit as usize][y];
698 for x in 0..DIGIT_WIDTH {
699 if (row_data >> (DIGIT_WIDTH - 1 - x)) & 1 == 1 {
700 let fb_x = x_offset + x;
701 let fb_y = START_Y + PADDING + y;
702 if fb_x < display.get_screen_width() && fb_y < display.get_screen_height() {
703 let fb_idx = fb_y * display.get_screen_width() + fb_x;
704 display.frame_buffer()[fb_idx] = FG_COLOR;
705 }
706 }
707 }
708 }
709 }
710}
711#[inline(never)]
714pub fn shift_buffer(display: &mut impl Display, delta_x: i16, delta_y: i16) {
715 if delta_x == 0 && delta_y == 0 {
716 return;
717 }
718
719 let fb_ptr = display.frame_buffer().as_mut_ptr();
720
721 unsafe {
722 if delta_y != 0 {
723 let shift_amount = delta_y.abs() as usize;
726 if delta_y > 0 {
727 for y in 0..(display.get_screen_height() - shift_amount) {
729 let src_idx = (y + shift_amount) * display.get_screen_width();
730 let dst_idx = y * display.get_screen_width();
731 core::ptr::copy(
732 fb_ptr.add(src_idx),
733 fb_ptr.add(dst_idx),
734 display.get_screen_width(),
735 );
736 }
737 } else {
738 for y in (shift_amount..display.get_screen_height()).rev() {
740 let src_idx = (y - shift_amount) * display.get_screen_width();
741 let dst_idx = y * display.get_screen_width();
742 core::ptr::copy(
743 fb_ptr.add(src_idx),
744 fb_ptr.add(dst_idx),
745 display.get_screen_width(),
746 );
747 }
748 }
749 }
750
751 if delta_x != 0 {
752 let shift_amount = delta_x.abs() as usize;
755 if delta_x > 0 {
756 for y in 0..display.get_screen_height() {
758 let row_start = y * display.get_screen_width();
759 core::ptr::copy(
760 fb_ptr.add(row_start + shift_amount),
761 fb_ptr.add(row_start),
762 display.get_screen_width() - shift_amount,
763 );
764 }
765 } else {
766 for y in 0..display.get_screen_height() {
768 let row_start = y * display.get_screen_width();
769 core::ptr::copy(
770 fb_ptr.add(row_start),
771 fb_ptr.add(row_start + shift_amount),
772 display.get_screen_width() - shift_amount,
773 );
774 }
775 }
776 }
777 }
778}