Skip to main content
← PSoXide SDK guides and examples · SDK f2fec1c301f1 · All Cargo features enabled; check feature gates before use.

psx_fmv/
rle.rs

1// SPDX-License-Identifier: GPL-2.0-or-later
2//! Run-length data: what [`crate::bitstream::decode_frame`] produces and the
3//! MDEC decodes, and the lengths the MDEC can be handed.
4//!
5//! The decode command carries the run-length length as a 16-bit word count,
6//! and DMA0 feeds it in 32-word blocks, so a length the MDEC can take is a
7//! non-zero multiple of 32 words no larger than [`MAX_WORDS`]. A zero block
8//! count would not mean "nothing": the DMA controller reads it as 65,536
9//! blocks.
10
11/// DMA block size the MDEC channels use, in words.
12pub const BLOCK_WORDS: usize = 32;
13
14/// Longest run-length input one decode command can announce: the largest
15/// multiple of [`BLOCK_WORDS`] that fits the command's 16-bit word count.
16pub const MAX_WORDS: usize = 0xFFFF / BLOCK_WORDS * BLOCK_WORDS;
17
18/// A run-length length the MDEC cannot be handed.
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum RleLengthError {
21    /// No words: the DMA block count would be 0, which means 65,536 blocks.
22    Empty,
23    /// Not a whole number of 32-word DMA blocks, so the decode command's word
24    /// count and the DMA length would disagree.
25    NotWholeBlocks,
26    /// More than [`MAX_WORDS`]: the decode command's 16-bit word count would
27    /// wrap.
28    TooLong,
29    /// More words than the buffer holds.
30    PastBuffer,
31}
32
33impl core::fmt::Display for RleLengthError {
34    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
35        f.write_str(match self {
36            Self::Empty => "no run-length words",
37            Self::NotWholeBlocks => "run-length words not a multiple of 32",
38            Self::TooLong => "run-length words exceed the decode command's 16-bit count",
39            Self::PastBuffer => "run-length words past the end of the buffer",
40        })
41    }
42}
43
44/// DMA0 block count for `words` run-length words, or why the MDEC cannot be
45/// handed that many.
46pub const fn dma_block_count(words: usize) -> Result<u16, RleLengthError> {
47    if words == 0 {
48        return Err(RleLengthError::Empty);
49    }
50    if !words.is_multiple_of(BLOCK_WORDS) {
51        return Err(RleLengthError::NotWholeBlocks);
52    }
53    if words > MAX_WORDS {
54        return Err(RleLengthError::TooLong);
55    }
56    // At most MAX_WORDS / 32 = 2047, so the cast is exact.
57    Ok((words / BLOCK_WORDS) as u16)
58}
59
60/// Owned run-length storage of `WORDS` words: written as halfwords by
61/// [`crate::bitstream::decode_frame`], read as words by the MDEC.
62///
63/// One value owns the memory and hands out one view at a time, so the
64/// halfword writer and the word reader can never alias the way two slices
65/// cast from one buffer do.
66#[derive(Clone, Debug)]
67#[repr(transparent)]
68pub struct RleBuffer<const WORDS: usize>([u32; WORDS]);
69
70impl<const WORDS: usize> RleBuffer<WORDS> {
71    /// A zeroed buffer; `const`, so it can be a `static`.
72    pub const fn new() -> Self {
73        Self([0; WORDS])
74    }
75
76    /// The buffer as `2 * WORDS` halfwords, for [`crate::bitstream::decode_frame`].
77    pub fn as_halfwords_mut(&mut self) -> &mut [u16] {
78        // SAFETY: `[u32; WORDS]` is `4 * WORDS` initialised bytes aligned
79        // for `u16`, every bit pattern is a valid `u16`, and the exclusive
80        // borrow of `self` keeps any other view out while this one lives.
81        unsafe { core::slice::from_raw_parts_mut(self.0.as_mut_ptr().cast::<u16>(), WORDS * 2) }
82    }
83
84    /// The buffer as words, for the MDEC decode (`mdec::decode`, guest only).
85    pub const fn as_words(&self) -> &[u32] {
86        &self.0
87    }
88}
89
90impl<const WORDS: usize> Default for RleBuffer<WORDS> {
91    fn default() -> Self {
92        Self::new()
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn block_counts_cover_every_boundary() {
102        assert_eq!(dma_block_count(0), Err(RleLengthError::Empty));
103        assert_eq!(dma_block_count(8), Err(RleLengthError::NotWholeBlocks));
104        assert_eq!(dma_block_count(31), Err(RleLengthError::NotWholeBlocks));
105        assert_eq!(dma_block_count(32), Ok(1));
106        assert_eq!(dma_block_count(33), Err(RleLengthError::NotWholeBlocks));
107        assert_eq!(dma_block_count(MAX_WORDS), Ok(2047));
108        assert_eq!(MAX_WORDS, 0xFFE0);
109        assert_eq!(
110            dma_block_count(MAX_WORDS + BLOCK_WORDS),
111            Err(RleLengthError::TooLong)
112        );
113        // 32 * 65,536 words: the old `as u16` cast made this 0 blocks.
114        assert_eq!(dma_block_count(32 << 16), Err(RleLengthError::TooLong));
115    }
116
117    /// Halfwords written through one view are the little-endian halves of
118    /// the words the other view reads (Miri checks the two never alias).
119    #[test]
120    fn buffer_views_share_storage_without_aliasing() {
121        let mut buffer = RleBuffer::<4>::new();
122        let halfwords = buffer.as_halfwords_mut();
123        assert_eq!(halfwords.len(), 8);
124        halfwords[0] = 0x1111;
125        halfwords[1] = 0x2222;
126        halfwords[7] = 0xFE00;
127        let expected = if cfg!(target_endian = "little") {
128            [0x2222_1111, 0, 0, 0xFE00_0000]
129        } else {
130            [0x1111_2222, 0, 0, 0x0000_FE00]
131        };
132        assert_eq!(buffer.as_words(), &expected);
133        buffer.as_halfwords_mut()[2] = 0xAAAA;
134        assert_eq!(buffer.as_words()[1] & 0xFFFF, 0xAAAA);
135    }
136}