1use core::ptr::addr_of_mut;
39
40use psx_gpu::prim::FillRect;
41use psx_gpu::Gpu;
42use psx_hw::gpu::gp1;
43use psx_hw::mdec::{DECODE_15BPP, DECODE_24BPP};
44use psx_io::periph::{Cd, GpuDma, MdecDma};
45use psx_pack::cd::{SectorReader, SECTOR_WORDS};
46use psx_rt::{interrupts, Peripherals};
47use psx_spu::CdVolume;
48use psx_vram::VramRect;
49
50use crate::mdec::Mdec;
51use crate::stream::{Chunk, FrameAssembler, CHUNK_PAYLOAD_BYTES, MAX_CHUNKS};
52use crate::{bitstream, mdec};
53
54#[derive(Copy, Clone, Debug)]
56pub struct Movie {
57 pub lba: u32,
59 pub frames: u32,
61 pub width: u16,
63 pub height: u16,
65 pub max_chunks: u16,
67 pub rle_words: usize,
70 pub xa: Option<(u8, u8)>,
72}
73
74pub const DEFAULT_BUFFERS: [(u16, u16); 3] = [(0, 0), (0, 256), (512, 0)];
77
78#[derive(Copy, Clone, Debug, PartialEq, Eq)]
80pub enum Depth {
81 Rgb15,
84 Rgb24,
89}
90
91#[derive(Copy, Clone, Debug)]
93pub struct Config {
94 pub depth: Depth,
96 pub buffers: [(u16, u16); 3],
100 pub screen_width: u16,
102 pub screen_height: u16,
104 pub latency_vblanks: u32,
107 pub stall_vblanks: u32,
109}
110
111#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
113pub enum Stop {
114 #[default]
116 End,
117 Skipped,
119 Stall,
121 CdError,
123 Wedged,
125 Setup,
127}
128
129#[derive(Copy, Clone, Debug, Default)]
131pub struct Outcome {
132 pub stop: Stop,
134 pub shown: u32,
136 pub skipped: u32,
138 pub late: u32,
140 pub worst_late_vblanks: u32,
142 pub dropped: u32,
145 pub decode_errors: u32,
147 pub last_frame: u32,
149 pub vblanks: u32,
151 pub kcyc_vlc: u32,
154 pub kcyc_mdec: u32,
156 pub kcyc_wait: u32,
158}
159
160pub const WEDGE_ERRORS: u32 = 8;
162const SLOTS: usize = 3;
164const CHUNK_WORDS: usize = CHUNK_PAYLOAD_BYTES / 4;
166const DECODE_TIMEOUT_VBLANKS: u32 = 30;
169const PUMP_MACROBLOCKS: u32 = 2;
171const MODE_XA: u8 = 0x80 | 0x40 | 0x08;
173const MODE_DATA: u8 = 0x80;
175const FOREIGN_SECTORS: u32 = 32;
178const GP1_DISPLAY_MODE: u32 = 0x0800_0000;
180const DISPLAY_24BIT: u32 = 1 << 4;
181
182fn display_mode() -> u32 {
184 let stat = psx_io::gpu::status().bits();
185 ((stat >> 17) & 3)
186 | ((stat >> 19) & 1) << 2
187 | ((stat >> 20) & 1) << 3
188 | ((stat >> 21) & 1) << 4
189 | ((stat >> 22) & 1) << 5
190 | ((stat >> 16) & 1) << 6
191 | ((stat >> 14) & 1) << 7
192}
193
194const BUFFERS: usize = 3;
198
199fn slot_words(m: &Movie) -> usize {
200 m.max_chunks as usize * CHUNK_WORDS
201}
202
203fn rle_words(m: &Movie) -> usize {
206 m.rle_words.div_ceil(mdec::DMA_BLOCK_WORDS) * mdec::DMA_BLOCK_WORDS
207}
208
209fn halfwords_x2(depth: Depth) -> u16 {
211 match depth {
212 Depth::Rgb15 => 2,
213 Depth::Rgb24 => 3,
214 }
215}
216
217fn column_words(m: &Movie, depth: Depth) -> usize {
219 4 * halfwords_x2(depth) as usize * m.height as usize
220}
221
222pub fn memory_words(movie: &Movie, depth: Depth) -> usize {
224 SLOTS * slot_words(movie) + 2 * rle_words(movie) + 2 * column_words(movie, depth)
225}
226
227const PHASE_VLC: usize = 0;
229const PHASE_MDEC: usize = 1;
230const PHASE_WAIT: usize = 2;
231
232struct Clock {
233 phase: usize,
234 last: u16,
235 acc: [u32; 3],
236}
237
238impl Clock {
239 fn switch(&mut self, next: usize) {
240 let now = psx_io::timers::counter(psx_io::timers::Timer::Timer2);
241 self.acc[self.phase] += now.wrapping_sub(self.last) as u32;
242 self.last = now;
243 self.phase = next;
244 }
245}
246
247#[derive(Copy, Clone)]
250struct Buffers {
251 slots: *mut u32,
252 slot_words: usize,
253 rle: *mut u32,
254 rle_words: usize,
255 columns: *mut u32,
256 column_words: usize,
257}
258
259impl Buffers {
260 fn slot(&self, i: usize) -> &'static mut [u8] {
261 unsafe {
264 core::slice::from_raw_parts_mut(
265 self.slots.add(i * self.slot_words) as *mut u8,
266 self.slot_words * 4,
267 )
268 }
269 }
270 fn rle(&self, i: usize) -> &'static mut [u32] {
271 unsafe { core::slice::from_raw_parts_mut(self.rle.add(i * self.rle_words), self.rle_words) }
274 }
275 fn rle16(&self, i: usize) -> &'static mut [u16] {
276 let words = self.rle(i);
277 unsafe { core::slice::from_raw_parts_mut(words.as_mut_ptr() as *mut u16, words.len() * 2) }
279 }
280 fn column(&self, i: usize) -> *mut u32 {
281 unsafe { self.columns.add(i * self.column_words) }
283 }
284}
285
286static mut READER: Option<SectorReader> = None;
287static mut MDEC: Option<Mdec> = None;
288static mut SECTOR: [u32; SECTOR_WORDS] = [0; SECTOR_WORDS];
289
290fn install_devices(cd: Cd, mdec: MdecDma) {
292 unsafe {
294 *addr_of_mut!(READER) = Some(SectorReader::with_cd(cd));
295 *addr_of_mut!(MDEC) = Some(Mdec::new(mdec));
296 }
297}
298
299fn reader() -> &'static mut SectorReader {
300 unsafe { (*addr_of_mut!(READER)).as_mut().expect("reader installed") }
303}
304
305fn mdec() -> &'static mut Mdec {
306 unsafe { (*addr_of_mut!(MDEC)).as_mut().expect("mdec installed") }
308}
309
310fn read_one(lba: u32) -> Option<&'static [u8]> {
312 let r = reader();
313 unsafe {
315 if !r.start_read(lba) {
316 return None;
317 }
318 let ok = r.read_sector(&mut *addr_of_mut!(SECTOR));
319 r.stop();
320 if !ok {
321 return None;
322 }
323 Some(core::slice::from_raw_parts(
324 addr_of_mut!(SECTOR) as *const u8,
325 SECTOR_WORDS * 4,
326 ))
327 }
328}
329
330pub fn find_root_file(name: &str) -> Option<(u32, u32)> {
333 let devices = unsafe { Peripherals::steal() };
336 install_devices(devices.cd, devices.mdec_dma);
337 if !reader().prepare() {
338 return None;
339 }
340 let (root, _) = crate::iso::root_directory(read_one(crate::iso::PVD_LBA)?)?;
341 crate::iso::find_in_directory(read_one(root)?, name)
342}
343
344struct Stream {
346 buf: Buffers,
347 last_frame: u32,
348 frames: u32,
349 done: bool,
350 cd_error: bool,
351 started: bool,
352 foreign: u32,
354 filling: usize,
355 ready: Option<(usize, u32, u32)>,
358 decoding: Option<usize>,
359 asm: FrameAssembler,
360 skipped: u32,
361 last_sector_vblank: u32,
362}
363
364impl Stream {
365 fn pump(&mut self) {
366 while !self.done {
367 let got = unsafe { reader().try_read_sector(&mut *addr_of_mut!(SECTOR)) };
369 match got {
370 Ok(true) => {}
371 Ok(false) => return,
372 Err(()) => {
373 self.cd_error = true;
374 self.done = true;
375 return;
376 }
377 }
378 let now = interrupts::vblank_count();
379 self.last_sector_vblank = now;
380 let sector = unsafe {
382 core::slice::from_raw_parts(addr_of_mut!(SECTOR) as *const u8, SECTOR_WORDS * 4)
383 };
384 if Chunk::parse(sector).is_none() {
385 self.foreign += 1;
389 if self.started && self.foreign > FOREIGN_SECTORS {
390 self.done = true;
391 }
392 continue;
393 }
394 self.foreign = 0;
395 self.started = true;
396 if let Some(frame) = self.asm.add(sector, self.buf.slot(self.filling)) {
397 self.last_frame = frame.number;
398 if frame.number >= self.frames {
399 self.done = true;
400 }
401 let done = self.filling;
402 let next = match self.ready.replace((done, frame.size, now)) {
403 Some((stale, _, _)) => {
404 self.skipped += 1;
405 stale
406 }
407 None => (0..SLOTS)
408 .find(|&s| s != done && Some(s) != self.decoding)
409 .unwrap_or(done),
410 };
411 self.filling = next;
412 }
413 }
414 }
415}
416
417struct Columns {
420 x: u16,
422 step: u16,
423 y: u16,
424 count: u16,
425 next: u16,
426 reading: Option<(u16, usize)>,
427 filled: Option<(u16, usize)>,
428 uploading: Option<usize>,
429 started: u32,
430 broken: bool,
432}
433
434impl Columns {
435 fn start(x: u16, step: u16, y: u16, count: u16) -> Self {
436 Columns {
437 x,
438 step,
439 y,
440 count,
441 next: 0,
442 reading: None,
443 filled: None,
444 uploading: None,
445 started: interrupts::vblank_count(),
446 broken: false,
447 }
448 }
449
450 fn service(&mut self, dma: &mut GpuDma, buf: &Buffers, height: u16) -> bool {
452 if let Some(done) = self.reading {
453 if mdec().column_done() {
454 self.reading = None;
455 self.filled = Some(done);
456 }
457 }
458 if self.uploading.is_some() && psx_vram::dma_copy_to_vram_done() {
459 self.uploading = None;
460 }
461 if self.uploading.is_none() {
462 if let Some((c, b)) = self.filled.take() {
463 let rect = VramRect::new(self.x + c * self.step, self.y, self.step, height);
464 if unsafe { psx_vram::dma_copy_to_vram_start(dma, rect, buf.column(b)) } {
470 self.uploading = Some(b);
471 } else {
472 let words =
474 unsafe { core::slice::from_raw_parts(buf.column(b), buf.column_words) };
475 psx_vram::upload_words(rect, words);
476 }
477 }
478 }
479 if self.reading.is_none() && self.next < self.count {
480 let held = |b: usize| self.filled.map(|f| f.1) == Some(b) || self.uploading == Some(b);
481 if let Some(b) = (0..2).find(|&b| !held(b)) {
482 if unsafe { mdec().read_column_start(buf.column(b), buf.column_words) } {
485 self.reading = Some((self.next, b));
486 self.next += 1;
487 } else {
488 self.next = self.count;
491 self.broken = true;
492 }
493 }
494 }
495 self.next == self.count
496 && self.reading.is_none()
497 && self.filled.is_none()
498 && self.uploading.is_none()
499 }
500}
501
502struct Player {
503 dma: GpuDma,
506 st: Stream,
507 buf: Buffers,
508 movie: Movie,
509 x: u16,
511 step: u16,
512 y_offset: u16,
513 latency: u32,
514 origins: [(u16, u16); BUFFERS],
516 front: usize,
518 decoding: Option<(usize, usize, u32)>,
520 cols: Columns,
521 waiting: [Option<(usize, u32)>; 2],
524 queued: Option<usize>,
526 pending: Option<(usize, usize, u32)>,
529 shown: u32,
530 late: u32,
531 worst_late: u32,
532 errors: u32,
533 in_a_row: u32,
534 wedged: bool,
535 clock: Clock,
536}
537
538impl Player {
539 fn idle_phase(&self) -> usize {
540 if self.decoding.is_some() {
541 PHASE_MDEC
542 } else {
543 PHASE_WAIT
544 }
545 }
546
547 fn free_buffer(&self) -> Option<usize> {
550 (0..BUFFERS).find(|&b| {
551 b != self.front
552 && self.queued != Some(b)
553 && self.decoding.map(|d| d.1) != Some(b)
554 && self.waiting.iter().all(|w| w.map(|w| w.0) != Some(b))
555 })
556 }
557
558 fn drained(&self) -> bool {
560 self.decoding.is_none()
561 && self.pending.is_none()
562 && self.waiting[0].is_none()
563 && self.queued.is_none()
564 }
565
566 fn tick(&mut self) {
569 self.clock.switch(self.clock.phase);
572 self.st.pump();
573 if let Some(b) = self.queued {
574 if !interrupts::is_display_control_queued() {
575 self.front = b;
576 self.queued = None;
577 self.shown += 1;
578 }
579 }
580 if let Some((_, b, due)) = self.decoding {
581 if self
582 .cols
583 .service(&mut self.dma, &self.buf, self.movie.height)
584 && !self.cols.broken
585 {
586 self.decoding = None;
587 if mdec().decode_finish() {
588 self.in_a_row = 0;
589 let slot = if self.waiting[0].is_none() { 0 } else { 1 };
590 self.waiting[slot] = Some((b, due));
591 } else {
592 self.decode_failed();
593 }
594 } else if self.cols.broken
595 || interrupts::vblank_count().wrapping_sub(self.cols.started)
596 > DECODE_TIMEOUT_VBLANKS
597 {
598 mdec().abort_decode();
599 psx_io::dma::abort(psx_io::dma::Channel::Gpu);
600 psx_io::gpu::write_display_control(gp1::RESET_CMD_BUFFER);
603 self.decoding = None;
604 self.decode_failed();
605 }
606 }
607 if self.queued.is_none() {
608 if let Some((b, due)) = self.waiting[0] {
609 let now = interrupts::vblank_count();
610 let edge = now.wrapping_add(1);
611 let behind = self.waiting[1].is_some()
615 || (self.pending.is_some() && self.st.ready.is_some());
616 let gp0_free = self.cols.uploading.is_none() && psx_vram::dma_copy_to_vram_done();
618 if gp0_free && (behind || edge.wrapping_sub(due) as i32 >= 0) {
619 let gpu = Gpu::from_dma_mut(&mut self.dma);
623 gpu.arm_draw_done();
624 gpu.signal_draw_done();
625 let (x, y) = self.origins[b];
626 interrupts::queue_display_control_at_vblank(gp1::display_start(
627 x as u32, y as u32,
628 ));
629 let late = edge.wrapping_sub(due) as i32;
630 if late > 0 {
631 self.late += 1;
632 self.worst_late = self.worst_late.max(late as u32);
633 }
634 self.queued = Some(b);
635 self.waiting = [self.waiting[1], None];
636 }
637 }
638 }
639 }
640
641 fn decode_failed(&mut self) {
642 self.errors += 1;
643 self.in_a_row += 1;
644 if self.in_a_row >= WEDGE_ERRORS {
645 self.wedged = true;
646 }
647 let _ = setup_mdec();
648 }
649}
650
651fn setup_mdec() -> bool {
652 mdec().reset() && mdec().load_tables().is_some_and(|t| t.enable_writes != 0)
653}
654
655fn clear_buffers(dma: &mut GpuDma, config: &Config) {
658 let gpu = Gpu::from_dma_mut(dma);
659 gpu.wait_idle();
661 let line = config.screen_width * halfwords_x2(config.depth) / 2;
662 for &(x, y) in &config.buffers {
663 gpu.draw(&FillRect::new(
664 (x, y),
665 (line, config.screen_height),
666 (0, 0, 0),
667 ));
668 }
669 gpu.wait_idle();
670}
671
672fn leave_display(dma: &mut GpuDma, config: &Config, mode: u32) {
675 if config.depth == Depth::Rgb24 {
676 clear_buffers(dma, config);
677 psx_io::gpu::write_display_control(GP1_DISPLAY_MODE | mode);
678 }
679}
680
681fn setup_failed() -> Outcome {
682 Outcome {
683 stop: Stop::Setup,
684 ..Outcome::default()
685 }
686}
687
688pub fn play(
696 movie: &Movie,
697 config: &Config,
698 memory: &mut [u32],
699 poll: &mut dyn FnMut() -> bool,
700) -> Outcome {
701 if memory.len() < memory_words(movie, config.depth)
702 || movie.width == 0
703 || movie.height == 0
704 || !movie.width.is_multiple_of(16)
705 || !movie.height.is_multiple_of(16)
706 || movie.width > config.screen_width
707 || movie.height > config.screen_height
708 || movie.max_chunks == 0
709 || movie.max_chunks > MAX_CHUNKS
710 {
711 return setup_failed();
712 }
713 let base = memory.as_mut_ptr();
714 let sw = slot_words(movie);
715 let rw = rle_words(movie);
716 let buf = Buffers {
717 slots: base,
718 slot_words: sw,
719 rle: unsafe { base.add(SLOTS * sw) },
721 rle_words: rw,
722 columns: unsafe { base.add(SLOTS * sw + 2 * rw) },
724 column_words: column_words(movie, config.depth),
725 };
726
727 let devices = unsafe { Peripherals::steal() };
730 install_devices(devices.cd, devices.mdec_dma);
731 let mut dma = devices.gpu_dma;
732
733 let r = reader();
734 let ready = {
737 match movie.xa {
738 Some((file, channel)) => {
739 r.prepare_mode(MODE_XA) && r.unmute() && r.set_filter(file, channel)
740 }
741 None => r.prepare_mode(MODE_DATA),
742 }
743 };
744 if !ready || !setup_mdec() {
745 return setup_failed();
746 }
747 if movie.xa.is_some() {
748 reader().cd_mut().set_audio_mixer(0x80, 0, 0x80, 0);
749 psx_spu::set_cd_volume(CdVolume::MAX, CdVolume::MAX);
750 psx_spu::enable_cd_audio(true);
751 }
752
753 let x2 = halfwords_x2(config.depth);
756 let _ = interrupts::take_queued_display_control();
757 clear_buffers(&mut dma, config);
758 let (x0, y0) = config.buffers[0];
759 psx_io::gpu::write_display_control(gp1::display_start(x0 as u32, y0 as u32));
760 let mode = display_mode();
761 if config.depth == Depth::Rgb24 {
762 psx_io::gpu::write_display_control(GP1_DISPLAY_MODE | mode | DISPLAY_24BIT);
763 }
764
765 if !r.start_read(movie.lba) {
767 leave_display(&mut dma, config, mode);
768 return setup_failed();
769 }
770
771 psx_io::timers::set_mode(psx_io::timers::Timer::Timer2, 0x0200);
772 let start = interrupts::vblank_count();
773 let mut p = Player {
774 dma,
775 st: Stream {
776 buf,
777 last_frame: 0,
778 frames: movie.frames,
779 done: false,
780 cd_error: false,
781 started: false,
782 foreign: 0,
783 filling: 0,
784 ready: None,
785 decoding: None,
786 asm: FrameAssembler::new(),
787 skipped: 0,
788 last_sector_vblank: start,
789 },
790 buf,
791 movie: *movie,
792 x: (config.screen_width - movie.width) / 2 * x2 / 2,
793 step: 8 * x2,
794 y_offset: (config.screen_height - movie.height) / 2,
795 latency: config.latency_vblanks,
796 origins: config.buffers,
797 front: 0,
798 decoding: None,
799 cols: Columns::start(0, 0, 0, 0),
800 waiting: [None, None],
801 queued: None,
802 pending: None,
803 shown: 0,
804 late: 0,
805 worst_late: 0,
806 errors: 0,
807 in_a_row: 0,
808 wedged: false,
809 clock: Clock {
810 phase: PHASE_WAIT,
811 last: psx_io::timers::counter(psx_io::timers::Timer::Timer2),
812 acc: [0; 3],
813 },
814 };
815 let mut polled = start;
816 let mut skipped = false;
817 let rows = movie.height as u32 / 16;
818 let macroblocks = movie.width as u32 / 16 * rows;
819 loop {
820 p.tick();
821 if p.wedged {
822 break;
823 }
824 let now = interrupts::vblank_count();
825 if now != polled {
826 polled = now;
827 if poll() {
828 skipped = true;
829 break;
830 }
831 }
832 if p.pending.is_none() {
834 if let Some((slot, bytes, arrived)) = p.st.ready.take() {
835 p.clock.switch(PHASE_VLC);
836 p.st.decoding = Some(slot);
837 let rle = match p.decoding {
838 Some((r, _, _)) => 1 - r,
839 None => 0,
840 };
841 let frame_bytes = &p.buf.slot(slot)[..(bytes as usize).min(p.buf.slot_words * 4)];
842 let out = p.buf.rle16(rle);
843 let decoded = bitstream::decode_frame(
844 frame_bytes,
845 out,
846 macroblocks,
847 PUMP_MACROBLOCKS,
848 &mut || p.tick(),
849 );
850 p.st.decoding = None;
851 match decoded {
852 Ok(words) => p.pending = Some((rle, words, arrived.wrapping_add(p.latency))),
853 Err(_) => p.errors += 1,
854 }
855 let phase = p.idle_phase();
856 p.clock.switch(phase);
857 continue;
858 }
859 }
860 if p.decoding.is_none() && p.pending.is_some() {
861 if let Some(b) = p.free_buffer() {
862 let Some((rle, words, due)) = p.pending.take() else {
863 continue;
864 };
865 p.clock.switch(PHASE_MDEC);
866 let depth = match config.depth {
867 Depth::Rgb15 => DECODE_15BPP,
868 Depth::Rgb24 => DECODE_24BPP,
869 };
870 if unsafe { mdec().decode_start(p.buf.rle(rle), words, depth) }.is_err() {
873 p.errors += 1;
876 continue;
877 }
878 let (x, y) = p.origins[b];
879 p.cols = Columns::start(x + p.x, p.step, y + p.y_offset, movie.width / 16);
880 p.decoding = Some((rle, b, due));
881 continue;
882 }
883 }
884 let idle = now.wrapping_sub(p.st.last_sector_vblank);
885 if p.drained() && p.st.ready.is_none() && (p.st.done || idle > config.stall_vblanks) {
886 break;
887 }
888 let phase = p.idle_phase();
889 p.clock.switch(phase);
890 }
891 let _ = interrupts::take_queued_display_control();
893 mdec().abort_decode();
894 psx_io::dma::abort(psx_io::dma::Channel::Gpu);
895 if p.cols.uploading.is_some() {
896 psx_io::gpu::write_display_control(gp1::RESET_CMD_BUFFER);
899 }
900 reader().stop();
902 if movie.xa.is_some() {
903 psx_spu::set_cd_volume(CdVolume(0), CdVolume(0));
904 }
905 if config.depth == Depth::Rgb24 {
906 leave_display(&mut p.dma, config, mode);
907 }
908 let stop = if skipped {
909 Stop::Skipped
910 } else if p.wedged {
911 Stop::Wedged
912 } else if p.st.cd_error {
913 Stop::CdError
914 } else if p.st.done {
915 Stop::End
916 } else {
917 Stop::Stall
918 };
919 let per = (p.shown + p.st.skipped).max(1);
920 let kcyc = |phase: usize| p.clock.acc[phase] / per * 8 / 1000;
921 Outcome {
922 stop,
923 shown: p.shown,
924 skipped: p.st.skipped,
925 late: p.late,
926 worst_late_vblanks: p.worst_late,
927 dropped: p.st.asm.dropped,
928 decode_errors: p.errors,
929 last_frame: p.st.last_frame,
930 vblanks: interrupts::vblank_count().wrapping_sub(start),
931 kcyc_vlc: kcyc(PHASE_VLC),
932 kcyc_mdec: kcyc(PHASE_MDEC),
933 kcyc_wait: kcyc(PHASE_WAIT),
934 }
935}