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;
7use pico_engine_hardware::Display;
8
9use crate::components::_render_component::RendererComponent;
10use crate::types::_tile_map::TileMap;
11
12const TILE_SIZE: u16 = 16;
22const SCREEN_WIDTH_TILES: u16 = 20; const SCREEN_HEIGHT_TILES: u16 = 15; const MAX_TILEMAP_SIZE: u16 = 30;
25
26pub struct RenderPipeline {
28 prev_cam_x: u16,
29 prev_cam_y: u16,
30 first_frame: bool,
31}
32
33impl RenderPipeline {
34 pub fn new() -> Self {
35 Self {
36 prev_cam_x: 0,
37 prev_cam_y: 0,
38 first_frame: true,
39 }
40 }
41
42 #[inline]
55 pub fn render_direct(
56 &mut self,
57 world: &World,
58 position_storage: &ComponentStorage<Position>,
59 renderer_storage: &ComponentStorage<RendererComponent>,
60 size_storage: &ComponentStorage<Size>,
61 pivot_storage: &ComponentStorage<Pivot>,
62 background_tilemap: &'static TileMap,
63 foreground_tilemap: &'static TileMap,
64 display: &mut impl Display,
65 ) {
66 self.render_tilemap_layer(
68 world,
69 position_storage,
70 size_storage,
71 pivot_storage,
72 background_tilemap,
73 display,
74 true, false, );
77
78 self.render_entities(
80 world,
81 position_storage,
82 renderer_storage,
83 size_storage,
84 pivot_storage,
85 display,
86 );
87
88 self.render_tilemap_layer(
90 world,
91 position_storage,
92 size_storage,
93 pivot_storage,
94 foreground_tilemap,
95 display,
96 false, true, );
99
100 self.prev_cam_x = world.camera.x;
102 self.prev_cam_y = world.camera.y;
103 self.first_frame = false;
104
105 #[cfg(feature = "is_debug")]
107 {
108 }
111
112 display.display_buffer();
114 }
115
116 #[inline]
122 fn render_entities(
123 &self,
124 world: &World,
125 position_storage: &ComponentStorage<Position>,
126 renderer_storage: &ComponentStorage<RendererComponent>,
127 size_storage: &ComponentStorage<Size>,
128 pivot_storage: &ComponentStorage<Pivot>,
129 display: &mut impl Display,
130 ) {
131 for (_entity, transform, renderer, size, pivot) in world.query4(
132 position_storage,
133 renderer_storage,
134 size_storage,
135 pivot_storage,
136 ) {
137 let sprite_x = transform.x as i16 - pivot.x as i16 - world.camera.x as i16;
139 let sprite_y = transform.y as i16 - pivot.y as i16 - world.camera.y as i16;
140
141 draw_sprite(
142 display,
143 &renderer.texture[renderer.sprite.data_start..renderer.sprite.data_end],
144 sprite_x,
145 sprite_y,
146 size.width as usize,
147 size.height as usize,
148 renderer.sprite.is_opaque,
149 );
150 }
151 }
152
153 #[inline(never)]
164 fn render_tilemap_layer(
165 &mut self,
166 world: &World,
167 position_storage: &ComponentStorage<Position>,
168 size_storage: &ComponentStorage<Size>,
169 pivot_storage: &ComponentStorage<Pivot>,
170 tilemap: &'static TileMap,
171 display: &mut impl Display,
172 allow_buffer_shift: bool,
173 is_foreground: bool,
174 ) {
175 let cam_x = world.camera.x;
176 let cam_y = world.camera.y;
177 let delta_x = cam_x as i16 - self.prev_cam_x as i16;
178 let delta_y = cam_y as i16 - self.prev_cam_y as i16;
179
180 if delta_x == 0 && delta_y == 0 && !self.first_frame {
182 self.redraw_tiles_under_entities(
183 world,
184 position_storage,
185 size_storage,
186 pivot_storage,
187 tilemap,
188 display,
189 cam_x,
190 cam_y,
191 is_foreground,
192 );
193 return;
194 }
195
196 if allow_buffer_shift
198 && delta_x.abs() <= 2
199 && delta_y.abs() <= 2
200 && (delta_x != 0 || delta_y != 0)
201 {
202 shift_buffer(display, delta_x, delta_y);
203
204 self.redraw_edge_tiles_after_shift(
205 world,
206 tilemap,
207 display,
208 cam_x,
209 cam_y,
210 delta_x,
211 delta_y,
212 is_foreground,
213 );
214
215 self.redraw_tiles_under_entities(
217 world,
218 position_storage,
219 size_storage,
220 pivot_storage,
221 tilemap,
222 display,
223 cam_x,
224 cam_y,
225 is_foreground,
226 );
227 return;
228 }
229
230 self.redraw_visible_tilemap(tilemap, display, cam_x, cam_y, is_foreground);
232 }
233
234 #[inline(never)]
237 fn redraw_tiles_under_entities(
238 &self,
239 world: &World,
240 position_storage: &ComponentStorage<Position>,
241 size_storage: &ComponentStorage<Size>,
242 pivot_storage: &ComponentStorage<Pivot>,
243 tilemap: &'static TileMap,
244 display: &mut impl Display,
245 cam_x: u16,
246 cam_y: u16,
247 is_foreground: bool,
248 ) {
249 for (_entity, transform, size, pivot) in
250 world.query3(position_storage, size_storage, pivot_storage)
251 {
252 let entity_left = transform.x - pivot.x as u16;
253 let entity_top = transform.y - pivot.y as u16;
254 let entity_right = entity_left + size.width;
255 let entity_bottom = entity_top + size.height;
256
257 let tile_start_x = entity_left / TILE_SIZE;
259 let tile_start_y = if is_foreground {
260 if entity_top >= TILE_SIZE {
261 (entity_top + TILE_SIZE - 1) / TILE_SIZE
262 } else {
263 0
264 }
265 } else {
266 entity_top / TILE_SIZE
267 };
268
269 let tile_end_x = min((entity_right + TILE_SIZE - 1) / TILE_SIZE, MAX_TILEMAP_SIZE);
270 let tile_end_y = min(
271 (entity_bottom + if is_foreground { TILE_SIZE } else { 0 } + TILE_SIZE - 1) / TILE_SIZE,
272 MAX_TILEMAP_SIZE,
273 );
274
275 for ty in tile_start_y..tile_end_y {
277 for tx in tile_start_x..tile_end_x {
278 self.draw_tile_at(
279 tx, ty,
280 cam_x as i16,
281 cam_y as i16,
282 tilemap,
283 display,
284 is_foreground,
285 );
286 }
287 }
288 }
289 }
290
291 #[inline(never)]
294 fn redraw_edge_tiles_after_shift(
295 &self,
296 _world: &World,
297 tilemap: &'static TileMap,
298 display: &mut impl Display,
299 cam_x: u16,
300 cam_y: u16,
301 delta_x: i16,
302 delta_y: i16,
303 is_foreground: bool,
304 ) {
305 let start_x = cam_x / TILE_SIZE;
306 let start_y = cam_y / TILE_SIZE;
307 let end_x = min((cam_x + 320) / TILE_SIZE + 1, MAX_TILEMAP_SIZE);
308 let end_y = min((cam_y + 240) / TILE_SIZE + 1, MAX_TILEMAP_SIZE);
309
310 if delta_x > 0 {
312 for y in start_y..end_y {
314 if end_x > 0 {
315 self.draw_tile_at(end_x - 1, y, cam_x as i16, cam_y as i16, tilemap, display, is_foreground);
316 }
317 if end_x > 1 {
318 self.draw_tile_at(end_x - 2, y, cam_x as i16, cam_y as i16, tilemap, display, is_foreground);
319 }
320 }
321 } else if delta_x < 0 {
322 for y in start_y..end_y {
324 self.draw_tile_at(start_x, y, cam_x as i16, cam_y as i16, tilemap, display, is_foreground);
325 if start_x + 1 < MAX_TILEMAP_SIZE {
326 self.draw_tile_at(start_x + 1, y, cam_x as i16, cam_y as i16, tilemap, display, is_foreground);
327 }
328 }
329 }
330
331 if delta_y > 0 {
333 for x in start_x..end_x {
335 if end_y > 0 {
336 self.draw_tile_at(x, end_y - 1, cam_x as i16, cam_y as i16, tilemap, display, is_foreground);
337 }
338 if end_y > 1 {
339 self.draw_tile_at(x, end_y - 2, cam_x as i16, cam_y as i16, tilemap, display, is_foreground);
340 }
341 }
342 } else if delta_y < 0 {
343 for x in start_x..end_x {
345 self.draw_tile_at(x, start_y, cam_x as i16, cam_y as i16, tilemap, display, is_foreground);
346 if start_y + 1 < MAX_TILEMAP_SIZE {
347 self.draw_tile_at(x, start_y + 1, cam_x as i16, cam_y as i16, tilemap, display, is_foreground);
348 }
349 }
350 }
351 }
352
353 #[inline(never)]
356 fn redraw_visible_tilemap(
357 &self,
358 tilemap: &'static TileMap,
359 display: &mut impl Display,
360 cam_x: u16,
361 cam_y: u16,
362 is_foreground: bool,
363 ) {
364 let start_x = cam_x / TILE_SIZE;
365 let start_y = cam_y / TILE_SIZE;
366 let end_x = min(start_x + SCREEN_WIDTH_TILES + 1, MAX_TILEMAP_SIZE);
367 let end_y = min(start_y + SCREEN_HEIGHT_TILES + 2, MAX_TILEMAP_SIZE);
368
369 for y in start_y..end_y {
370 for x in start_x..end_x {
371 self.draw_tile_at(x, y, cam_x as i16, cam_y as i16, tilemap, display, is_foreground);
372 }
373 }
374 }
375
376 #[inline]
383 fn draw_tile_at(
384 &self,
385 x: u16,
386 y: u16,
387 cam_x: i16,
388 cam_y: i16,
389 tilemap: &'static TileMap,
390 display: &mut impl Display,
391 is_foreground: bool,
392 ) {
393 if x >= tilemap.width || y >= tilemap.height {
395 return;
396 }
397
398 let cell = &tilemap.cells[x as usize][y as usize];
400 let cell_tiles = &tilemap.tiles[cell.start..cell.end];
401
402 for tile in cell_tiles {
404 let sprite = tile.sprite;
405
406 let pos_x = (x * TILE_SIZE) as i16 - cam_x;
409 let pos_y = (y * TILE_SIZE) as i16 - cam_y - if is_foreground { 16 } else { 0 };
410
411 draw_sprite(
412 display,
413 &tilemap.texture[sprite.data_start..sprite.data_end],
414 pos_x,
415 pos_y,
416 TILE_SIZE as usize,
417 TILE_SIZE as usize,
418 sprite.is_opaque,
419 );
420 }
421 }
422}
423
424#[inline]
437pub fn draw_sprite(
438 display: &mut impl Display,
439 data: &[u16],
440 x: i16,
441 y: i16,
442 width: usize,
443 height: usize,
444 is_opaque: bool,
445) {
446 if x >= display.get_screen_width() as i16
448 || y >= display.get_screen_height() as i16
449 || x + width as i16 <= 0
450 || y + height as i16 <= 0
451 {
452 return;
453 }
454
455 if is_opaque {
456 draw_sprite_opaque(display, data, x, y, width, height);
457 } else {
458 draw_sprite_strip_encoded(display, data, x, y, width, height);
459 }
460}
461
462#[inline]
464fn draw_sprite_opaque(
465 display: &mut impl Display,
466 data: &[u16],
467 x: i16,
468 y: i16,
469 width: usize,
470 height: usize,
471) {
472 let start_y = if y < 0 { -y } else { 0 };
474 let end_y = ((y + height as i16).min(display.get_screen_height() as i16) - y).max(0) as usize;
475 let start_x = if x < 0 { -x } else { 0 };
476 let end_x = ((x + width as i16).min(display.get_screen_width() as i16) - x).max(0) as usize;
477
478 if start_y == 0 && end_y == height && start_x == 0 && end_x == width {
480 for row in 0..height {
481 let screen_y = (y + row as i16) as usize;
482 let dst_start = screen_y * display.get_screen_width() + x as usize;
483 let src_start = row * width;
484 unsafe {
485 core::ptr::copy_nonoverlapping(
486 data.as_ptr().add(src_start),
487 display.frame_buffer().as_mut_ptr().add(dst_start),
488 width,
489 );
490 }
491 }
492 } else {
493 for row in start_y as usize..end_y {
495 let screen_y = (y + row as i16) as usize;
496 let dst_start = screen_y * display.get_screen_width() + (x + start_x) as usize;
497 let src_start = row * width + start_x as usize;
498 let copy_len = end_x - start_x as usize;
499
500 unsafe {
501 core::ptr::copy_nonoverlapping(
502 data.as_ptr().add(src_start),
503 display.frame_buffer().as_mut_ptr().add(dst_start),
504 copy_len,
505 );
506 }
507 }
508 }
509}
510
511#[inline(never)]
513fn draw_sprite_strip_encoded(
514 display: &mut impl Display,
515 data: &[u16],
516 x: i16,
517 y: i16,
518 width: usize,
519 height: usize,
520) {
521 let mut data_idx = 0;
522 let mut row = 0;
523 let mut col = 0;
524 let data_len = data.len();
525 let fb_ptr = display.frame_buffer().as_mut_ptr();
526 let data_ptr = data.as_ptr();
527
528 unsafe {
529 while data_idx < data_len {
530 let header = *data_ptr.add(data_idx);
532 data_idx += 1;
533
534 let transparent_count = (header & 0xFF) as usize;
535 let opaque_count = ((header >> 8) & 0xFF) as usize;
536
537 let mut skip = transparent_count;
539 while skip > 0 {
540 let available = width - col;
541 if skip >= available {
542 skip -= available;
543 col = 0;
544 row += 1;
545 if row >= height {
546 return;
547 }
548 } else {
549 col += skip;
550 break;
551 }
552 }
553
554 let mut remaining = opaque_count;
556
557 while remaining > 0 && row < height {
558 let pixels_in_row = width - col;
559 let batch_size = remaining.min(pixels_in_row);
560
561 let screen_x = x + col as i16;
562 let screen_y = y + row as i16;
563
564 if screen_y >= 0
566 && screen_y < display.get_screen_height() as i16
567 && screen_x >= 0
568 && screen_x + batch_size as i16 <= display.get_screen_width() as i16
569 {
570 let fb_idx = screen_y as usize * display.get_screen_width() + screen_x as usize;
572 core::ptr::copy_nonoverlapping(
573 data_ptr.add(data_idx),
574 fb_ptr.add(fb_idx),
575 batch_size,
576 );
577 } else if screen_y >= 0 && screen_y < display.get_screen_height() as i16 {
578 for i in 0..batch_size {
580 let px = screen_x + i as i16;
581 if px >= 0 && px < display.get_screen_width() as i16 {
582 let fb_idx =
583 screen_y as usize * display.get_screen_width() + px as usize;
584 *fb_ptr.add(fb_idx) = *data_ptr.add(data_idx + i);
585 }
586 }
587 }
588
589 data_idx += batch_size;
590 col += batch_size;
591 remaining -= batch_size;
592
593 if col >= width {
594 col = 0;
595 row += 1;
596 }
597 }
598 }
599 }
600}
601
602pub fn draw_fps(display: &mut impl Display, fps: u32) {
604 const DIGIT_WIDTH: usize = 4;
606 const DIGIT_HEIGHT: usize = 7;
607 const DIGITS: [[u8; DIGIT_HEIGHT]; 10] = [
608 [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], ];
619
620 const FG_COLOR: u16 = 0xFFFF; const BG_COLOR: u16 = 0x0000; const START_X: usize = 4;
623 const START_Y: usize = 4;
624 const PADDING: usize = 2;
625
626 const BG_WIDTH: usize = 3 * DIGIT_WIDTH + 2 + PADDING * 2; const BG_HEIGHT: usize = DIGIT_HEIGHT + PADDING * 2;
629
630 for y in 0..BG_HEIGHT {
632 for x in 0..BG_WIDTH {
633 let fb_x = START_X + x;
634 let fb_y = START_Y + y;
635 if fb_x < display.get_screen_width() && fb_y < display.get_screen_height() {
636 let fb_idx = fb_y * display.get_screen_width() + fb_x;
637 display.frame_buffer()[fb_idx] = BG_COLOR;
638 }
639 }
640 }
641
642 let mut digits = [0u8; 3];
644 let mut temp = fps.min(999); digits[2] = (temp % 10) as u8;
646 temp /= 10;
647 digits[1] = (temp % 10) as u8;
648 temp /= 10;
649 digits[0] = (temp % 10) as u8;
650
651 for (digit_idx, &digit) in digits.iter().enumerate() {
653 let x_offset = START_X + PADDING + digit_idx * (DIGIT_WIDTH + 1);
654
655 for y in 0..DIGIT_HEIGHT {
656 let row_data = DIGITS[digit as usize][y];
657 for x in 0..DIGIT_WIDTH {
658 if (row_data >> (DIGIT_WIDTH - 1 - x)) & 1 == 1 {
659 let fb_x = x_offset + x;
660 let fb_y = START_Y + PADDING + y;
661 if fb_x < display.get_screen_width() && fb_y < display.get_screen_height() {
662 let fb_idx = fb_y * display.get_screen_width() + fb_x;
663 display.frame_buffer()[fb_idx] = FG_COLOR;
664 }
665 }
666 }
667 }
668 }
669}
670#[inline(never)]
673pub fn shift_buffer(display: &mut impl Display, delta_x: i16, delta_y: i16) {
674 if delta_x == 0 && delta_y == 0 {
675 return;
676 }
677
678 let fb_ptr = display.frame_buffer().as_mut_ptr();
679
680 unsafe {
681 if delta_y != 0 {
682 let shift_amount = delta_y.abs() as usize;
685 if delta_y > 0 {
686 for y in 0..(display.get_screen_height() - shift_amount) {
688 let src_idx = (y + shift_amount) * display.get_screen_width();
689 let dst_idx = y * display.get_screen_width();
690 core::ptr::copy(
691 fb_ptr.add(src_idx),
692 fb_ptr.add(dst_idx),
693 display.get_screen_width(),
694 );
695 }
696 } else {
697 for y in (shift_amount..display.get_screen_height()).rev() {
699 let src_idx = (y - shift_amount) * display.get_screen_width();
700 let dst_idx = y * display.get_screen_width();
701 core::ptr::copy(
702 fb_ptr.add(src_idx),
703 fb_ptr.add(dst_idx),
704 display.get_screen_width(),
705 );
706 }
707 }
708 }
709
710 if delta_x != 0 {
711 let shift_amount = delta_x.abs() as usize;
714 if delta_x > 0 {
715 for y in 0..display.get_screen_height() {
717 let row_start = y * display.get_screen_width();
718 core::ptr::copy(
719 fb_ptr.add(row_start + shift_amount),
720 fb_ptr.add(row_start),
721 display.get_screen_width() - shift_amount,
722 );
723 }
724 } else {
725 for y in 0..display.get_screen_height() {
727 let row_start = y * display.get_screen_width();
728 core::ptr::copy(
729 fb_ptr.add(row_start),
730 fb_ptr.add(row_start + shift_amount),
731 display.get_screen_width() - shift_amount,
732 );
733 }
734 }
735 }
736 }
737}