mirror of
https://github.com/storytold/deckcraft.git
synced 2026-11-09 11:59:36 -06:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fca95a18d3 |
@@ -216,6 +216,9 @@ pub struct Decoder {
|
||||
out: VecDeque<Output>,
|
||||
/// pts of the temporal unit being decoded.
|
||||
pts: i64,
|
||||
/// Bytes of the repeated-frame (`show_existing_frame`) pictures queued by the current
|
||||
/// [`Decoder::decode_pts`] call; each is copied out in full when it is returned.
|
||||
chunk_output_bytes: u64,
|
||||
}
|
||||
|
||||
/// Whether `pictures` frames of `width` x `height`, padded as `FrameBuf::new` pads them, fit in
|
||||
@@ -298,6 +301,7 @@ impl Decoder {
|
||||
memory_budget: deckcraft_bitstream::budget::DECODE_MEMORY_BUDGET,
|
||||
out: VecDeque::new(),
|
||||
pts: 0,
|
||||
chunk_output_bytes: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -330,6 +334,7 @@ impl Decoder {
|
||||
/// [`Decoder::decode`] with a timestamp that the pictures shown by this chunk carry.
|
||||
pub fn decode_pts(&mut self, data: &[u8], pts: i64) -> Result<Vec<Picture>> {
|
||||
self.pts = pts;
|
||||
self.chunk_output_bytes = 0;
|
||||
let parsed = self.parse(data);
|
||||
// Hand out what is finished, then report a parse error.
|
||||
let mut pics = Vec::new();
|
||||
@@ -500,6 +505,20 @@ impl Decoder {
|
||||
let idx = fh.frame_to_show_map_idx;
|
||||
let slot = self.ref_frames[idx].clone().ok_or(Error::Invalid("show_existing_frame of an empty slot"))?;
|
||||
let info = self.refs[idx].clone();
|
||||
// Every repeat is copied out whole, so a compact header must not queue more
|
||||
// pictures than half the decoder budget allows.
|
||||
let shape = deckcraft_bitstream::budget::PictureShape {
|
||||
width: u64::from(info.upscaled_width),
|
||||
height: u64::from(info.frame_height),
|
||||
shift_x: u32::from(seq.color.subsampling_x),
|
||||
shift_y: u32::from(seq.color.subsampling_y),
|
||||
chroma_planes: if seq.color.mono_chrome { 0 } else { 2 },
|
||||
bytes_per_sample: 2,
|
||||
};
|
||||
self.chunk_output_bytes = self.chunk_output_bytes.saturating_add(shape.bytes().unwrap_or(u64::MAX));
|
||||
if self.chunk_output_bytes > self.memory_budget / 2 {
|
||||
return Err(Error::Invalid("repeated frame outputs exceed the decoder memory budget"));
|
||||
}
|
||||
let o = Output::Existing {
|
||||
slot: slot.clone(),
|
||||
seq: seq.clone(),
|
||||
|
||||
@@ -404,7 +404,9 @@ impl Decoder {
|
||||
chroma_planes: if chroma == 0 { 0 } else { 2 },
|
||||
bytes_per_sample: 2,
|
||||
};
|
||||
let pictures = u64::from(sps.max_dec_pic_buffering) + 1 + self.max_in_flight as u64;
|
||||
// Output frames waiting to be returned stay allocated after they leave the DPB.
|
||||
let queued = self.out_queue.iter().filter(|o| !self.dpb.entries.iter().any(|e| Arc::ptr_eq(&e.frame, &o.frame))).count();
|
||||
let pictures = u64::from(sps.max_dec_pic_buffering) + 1 + self.max_in_flight as u64 + queued as u64;
|
||||
ensure!(
|
||||
shape.fits(pictures, self.memory_budget),
|
||||
"{}x{} pictures with {pictures} in the decoder exceed the memory budget",
|
||||
|
||||
@@ -166,11 +166,17 @@ impl Decoder {
|
||||
if frames.len() > 1 {
|
||||
self.stats.superframes += 1;
|
||||
}
|
||||
let queued = self.pending.len();
|
||||
for f in frames {
|
||||
if f.is_empty() {
|
||||
continue;
|
||||
}
|
||||
self.decode_frame(f, pts)?;
|
||||
if let Err(e) = self.decode_frame(f, pts) {
|
||||
// A failed sample outputs nothing: drop what its earlier subframes queued so a
|
||||
// later drain does not expand each repeat into its own picture.
|
||||
self.pending.truncate(queued);
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
Ok(self.drain(false))
|
||||
}
|
||||
|
||||
+7
-23
@@ -61,23 +61,16 @@ pub fn missing(files: &[String], assets_md: &str) -> Vec<String> {
|
||||
files.iter().filter(|f| is_asset(f) && !patterns.iter().any(|p| glob(p, f))).cloned().collect()
|
||||
}
|
||||
|
||||
/// `*` matches any run of characters except `/`; everything else literally. Matched with a
|
||||
/// table over the pattern, so a pattern with many `*` costs pattern length times name length
|
||||
/// rather than exploring every way to split the name.
|
||||
/// `*` matches any run of characters except `/`; everything else literally.
|
||||
fn glob(pat: &str, s: &str) -> bool {
|
||||
let t: Vec<char> = s.chars().collect();
|
||||
// `row[k]`: the pattern so far matches the first k characters of the name.
|
||||
let mut row = vec![false; t.len() + 1];
|
||||
row[0] = true;
|
||||
for pc in pat.chars() {
|
||||
let mut next = vec![false; t.len() + 1];
|
||||
next[0] = pc == '*' && row[0];
|
||||
for k in 1..=t.len() {
|
||||
next[k] = if pc == '*' { row[k] || (next[k - 1] && t[k - 1] != '/') } else { row[k - 1] && t[k - 1] == pc };
|
||||
match pat.split_once('*') {
|
||||
None => pat == s,
|
||||
Some((head, tail)) => {
|
||||
let Some(rest) = s.strip_prefix(head) else { return false };
|
||||
let ends = rest.char_indices().map(|(i, _)| i).chain(std::iter::once(rest.len()));
|
||||
ends.take_while(|&i| !rest[..i].contains('/')).any(|i| glob(tail, &rest[i..]))
|
||||
}
|
||||
row = next;
|
||||
}
|
||||
row[t.len()]
|
||||
}
|
||||
|
||||
/// Splits `git ls-files -z` output into real file names (no display quoting of Unicode or special characters).
|
||||
@@ -127,15 +120,6 @@ mod tests {
|
||||
assert_eq!(missing(&["assets/é.png".into()], "`assets/*.svg`"), vec!["assets/é.png".to_string()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn many_wildcards_on_a_failing_name_finish_at_once() {
|
||||
let pat = format!("{}b", "a*".repeat(40));
|
||||
assert!(!glob(&pat, &"a".repeat(60)));
|
||||
assert!(glob(&pat, &format!("{}b", "a".repeat(60))));
|
||||
assert!(!glob("a*b", "a/b"));
|
||||
assert!(glob("a*b", "ab"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ls_files_output_is_nul_separated_and_unquoted() {
|
||||
let names = parse_ls_files("assets/zażółć.png\0docs/images/a b\nc.png\0".as_bytes());
|
||||
|
||||
Reference in New Issue
Block a user