diff --git a/Cargo.lock b/Cargo.lock index 636fc72..1dfbaf5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1258,7 +1258,6 @@ dependencies = [ "nom_locate", "num-traits", "thiserror", - "vec_extract_if_polyfill", ] [[package]] @@ -2349,12 +2348,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "vec_extract_if_polyfill" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40c9cb5fb67c2692310b6eb3fce7dd4b6e4c9a75be4f2f46b27f0b2b7799759c" - [[package]] name = "vec_map" version = "0.8.2" diff --git a/librashader-presets/Cargo.toml b/librashader-presets/Cargo.toml index 5fe2113..88a610a 100644 --- a/librashader-presets/Cargo.toml +++ b/librashader-presets/Cargo.toml @@ -17,7 +17,6 @@ nom = "7.1.1" nom_locate = "4.0.0" librashader-common = { path = "../librashader-common", version = "0.2.0-beta.2" } num-traits = "0.2" -vec_extract_if_polyfill = "0.1" [features] parse_legacy_glsl = [] diff --git a/librashader-presets/src/extract_if.rs b/librashader-presets/src/extract_if.rs new file mode 100644 index 0000000..5cc403d --- /dev/null +++ b/librashader-presets/src/extract_if.rs @@ -0,0 +1,317 @@ +//! This is a stable polyfill for [`Vec::extract_if`](https://github.com/rust-lang/rust/issues/43244). + +use core::ptr; +use core::slice; + +/// Polyfill trait for [`Vec::extract_if`](https://github.com/rust-lang/rust/issues/43244). +pub(crate) trait MakeExtractIf { + + /// Creates an iterator which uses a closure to determine if an element should be removed. + /// + /// If the closure returns true, then the element is removed and yielded. + /// If the closure returns false, the element will remain in the vector and will not be yielded + /// by the iterator. + /// + /// If the returned `ExtractIf` is not exhausted, e.g. because it is dropped without iterating + /// or the iteration short-circuits, then the remaining elements will be retained. + /// + /// Note that `extract_if` also lets you mutate every element in the filter closure, + /// regardless of whether you choose to keep or remove it. + /// + /// # Examples + /// + /// Splitting an array into evens and odds, reusing the original allocation: + /// + /// ``` + /// use vec_extract_if_polyfill::MakeExtractIf; + /// let mut numbers = vec![1, 2, 3, 4, 5, 6, 8, 9, 11, 13, 14, 15]; + /// + /// let evens = numbers.extract_if(|x| *x % 2 == 0).collect::>(); + /// let odds = numbers; + /// + /// assert_eq!(evens, vec![2, 4, 6, 8, 14]); + /// assert_eq!(odds, vec![1, 3, 5, 9, 11, 13, 15]); + /// ``` + fn extract_if(&mut self, filter: F) -> ExtractIf + where + F: FnMut(&mut T) -> bool; +} + +impl MakeExtractIf for Vec { + fn extract_if(&mut self, filter: F) -> ExtractIf + where + F: FnMut(&mut T) -> bool, + { + let old_len = self.len(); + + // Guard against us getting leaked (leak amplification) + unsafe { + self.set_len(0); + } + + ExtractIf { + vec: self, + idx: 0, + del: 0, + old_len, + pred: filter, + } + } +} +/// An iterator which uses a closure to determine if an element should be removed. +/// +/// This struct is created by [`Vec::extract_if`]. +/// See its documentation for more. +/// +/// # Example +/// +/// ``` +/// use vec_extract_if_polyfill::MakeExtractIf; +/// +/// let mut v = vec![0, 1, 2]; +/// let iter = v.extract_if(|x| *x % 2 == 0); +/// ``` +#[derive(Debug)] +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct ExtractIf<'a, T, F> + where + F: FnMut(&mut T) -> bool, +{ + vec: &'a mut Vec, + /// The index of the item that will be inspected by the next call to `next`. + idx: usize, + /// The number of items that have been drained (removed) thus far. + del: usize, + /// The original length of `vec` prior to draining. + old_len: usize, + /// The filter test predicate. + pred: F, +} + +impl Iterator for ExtractIf<'_, T, F> + where + F: FnMut(&mut T) -> bool, +{ + type Item = T; + + fn next(&mut self) -> Option { + unsafe { + while self.idx < self.old_len { + let i = self.idx; + let v = slice::from_raw_parts_mut(self.vec.as_mut_ptr(), self.old_len); + let drained = (self.pred)(&mut v[i]); + // Update the index *after* the predicate is called. If the index + // is updated prior and the predicate panics, the element at this + // index would be leaked. + self.idx += 1; + if drained { + self.del += 1; + return Some(ptr::read(&v[i])); + } else if self.del > 0 { + let del = self.del; + let src: *const T = &v[i]; + let dst: *mut T = &mut v[i - del]; + ptr::copy_nonoverlapping(src, dst, 1); + } + } + None + } + } + + fn size_hint(&self) -> (usize, Option) { + (0, Some(self.old_len - self.idx)) + } +} + +impl Drop for ExtractIf<'_, T, F> + where + F: FnMut(&mut T) -> bool, +{ + fn drop(&mut self) { + unsafe { + if self.idx < self.old_len && self.del > 0 { + // This is a pretty messed up state, and there isn't really an + // obviously right thing to do. We don't want to keep trying + // to execute `pred`, so we just backshift all the unprocessed + // elements and tell the vec that they still exist. The backshift + // is required to prevent a double-drop of the last successfully + // drained item prior to a panic in the predicate. + let ptr = self.vec.as_mut_ptr(); + let src = ptr.add(self.idx); + let dst = src.sub(self.del); + let tail_len = self.old_len - self.idx; + src.copy_to(dst, tail_len); + } + self.vec.set_len(self.old_len - self.del); + } + } +} + +#[cfg(test)] +mod test { + use crate::MakeExtractIf; + #[test] + fn drain_filter_empty() { + let mut vec: Vec = vec![]; + + { + let mut iter = vec.extract_if(|_| true); + assert_eq!(iter.size_hint(), (0, Some(0))); + assert_eq!(iter.next(), None); + assert_eq!(iter.size_hint(), (0, Some(0))); + assert_eq!(iter.next(), None); + assert_eq!(iter.size_hint(), (0, Some(0))); + } + assert_eq!(vec.len(), 0); + assert_eq!(vec, vec![]); + } + + #[test] + fn drain_filter_zst() { + let mut vec = vec![(), (), (), (), ()]; + let initial_len = vec.len(); + let mut count = 0; + { + let mut iter = vec.extract_if(|_| true); + assert_eq!(iter.size_hint(), (0, Some(initial_len))); + while let Some(_) = iter.next() { + count += 1; + assert_eq!(iter.size_hint(), (0, Some(initial_len - count))); + } + assert_eq!(iter.size_hint(), (0, Some(0))); + assert_eq!(iter.next(), None); + assert_eq!(iter.size_hint(), (0, Some(0))); + } + + assert_eq!(count, initial_len); + assert_eq!(vec.len(), 0); + assert_eq!(vec, vec![]); + } + + #[test] + fn drain_filter_false() { + let mut vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + let initial_len = vec.len(); + let mut count = 0; + { + let mut iter = vec.extract_if(|_| false); + assert_eq!(iter.size_hint(), (0, Some(initial_len))); + for _ in iter.by_ref() { + count += 1; + } + assert_eq!(iter.size_hint(), (0, Some(0))); + assert_eq!(iter.next(), None); + assert_eq!(iter.size_hint(), (0, Some(0))); + } + + assert_eq!(count, 0); + assert_eq!(vec.len(), initial_len); + assert_eq!(vec, vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + } + + #[test] + fn drain_filter_true() { + let mut vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + + let initial_len = vec.len(); + let mut count = 0; + { + let mut iter = vec.extract_if(|_| true); + assert_eq!(iter.size_hint(), (0, Some(initial_len))); + while let Some(_) = iter.next() { + count += 1; + assert_eq!(iter.size_hint(), (0, Some(initial_len - count))); + } + assert_eq!(iter.size_hint(), (0, Some(0))); + assert_eq!(iter.next(), None); + assert_eq!(iter.size_hint(), (0, Some(0))); + } + + assert_eq!(count, initial_len); + assert_eq!(vec.len(), 0); + assert_eq!(vec, vec![]); + } + + #[test] + fn drain_filter_complex() { + + { // [+xxx++++++xxxxx++++x+x++] + let mut vec = vec![1, + 2, 4, 6, + 7, 9, 11, 13, 15, 17, + 18, 20, 22, 24, 26, + 27, 29, 31, 33, + 34, + 35, + 36, + 37, 39]; + + let removed = vec.extract_if(|x| *x % 2 == 0).collect::>(); + assert_eq!(removed.len(), 10); + assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]); + + assert_eq!(vec.len(), 14); + assert_eq!(vec, vec![1, 7, 9, 11, 13, 15, 17, 27, 29, 31, 33, 35, 37, 39]); + } + + { // [xxx++++++xxxxx++++x+x++] + let mut vec = vec![2, 4, 6, + 7, 9, 11, 13, 15, 17, + 18, 20, 22, 24, 26, + 27, 29, 31, 33, + 34, + 35, + 36, + 37, 39]; + + let removed = vec.extract_if(|x| *x % 2 == 0).collect::>(); + assert_eq!(removed.len(), 10); + assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]); + + assert_eq!(vec.len(), 13); + assert_eq!(vec, vec![7, 9, 11, 13, 15, 17, 27, 29, 31, 33, 35, 37, 39]); + } + + { // [xxx++++++xxxxx++++x+x] + let mut vec = vec![2, 4, 6, + 7, 9, 11, 13, 15, 17, + 18, 20, 22, 24, 26, + 27, 29, 31, 33, + 34, + 35, + 36]; + + let removed = vec.extract_if(|x| *x % 2 == 0).collect::>(); + assert_eq!(removed.len(), 10); + assert_eq!(removed, vec![2, 4, 6, 18, 20, 22, 24, 26, 34, 36]); + + assert_eq!(vec.len(), 11); + assert_eq!(vec, vec![7, 9, 11, 13, 15, 17, 27, 29, 31, 33, 35]); + } + + { // [xxxxxxxxxx+++++++++++] + let mut vec = vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20, + 1, 3, 5, 7, 9, 11, 13, 15, 17, 19]; + + let removed = vec.extract_if(|x| *x % 2 == 0).collect::>(); + assert_eq!(removed.len(), 10); + assert_eq!(removed, vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20]); + + assert_eq!(vec.len(), 10); + assert_eq!(vec, vec![1, 3, 5, 7, 9, 11, 13, 15, 17, 19]); + } + + { // [+++++++++++xxxxxxxxxx] + let mut vec = vec![1, 3, 5, 7, 9, 11, 13, 15, 17, 19, + 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]; + + let removed = vec.extract_if(|x| *x % 2 == 0).collect::>(); + assert_eq!(removed.len(), 10); + assert_eq!(removed, vec![2, 4, 6, 8, 10, 12, 14, 16, 18, 20]); + + assert_eq!(vec.len(), 10); + assert_eq!(vec, vec![1, 3, 5, 7, 9, 11, 13, 15, 17, 19]); + } + } +} \ No newline at end of file diff --git a/librashader-presets/src/lib.rs b/librashader-presets/src/lib.rs index f317a46..ac19c5f 100644 --- a/librashader-presets/src/lib.rs +++ b/librashader-presets/src/lib.rs @@ -13,5 +13,7 @@ mod error; mod parse; mod preset; +mod extract_if; + pub use error::*; pub use preset::*; diff --git a/librashader-presets/src/parse/preset.rs b/librashader-presets/src/parse/preset.rs index b9f392c..cb904d0 100644 --- a/librashader-presets/src/parse/preset.rs +++ b/librashader-presets/src/parse/preset.rs @@ -1,7 +1,7 @@ use crate::parse::remove_if; use crate::parse::value::Value; use crate::{ParameterConfig, Scale2D, Scaling, ShaderPassConfig, ShaderPreset, TextureConfig}; -use vec_extract_if_polyfill::MakeExtractIf; +use crate::extract_if::MakeExtractIf; pub fn resolve_values(mut values: Vec) -> ShaderPreset { let textures: Vec = values diff --git a/librashader-presets/src/parse/value.rs b/librashader-presets/src/parse/value.rs index 6037d6b..0e17d15 100644 --- a/librashader-presets/src/parse/value.rs +++ b/librashader-presets/src/parse/value.rs @@ -16,7 +16,8 @@ use std::io::Read; use std::path::{Path, PathBuf}; use std::str::FromStr; -use vec_extract_if_polyfill::MakeExtractIf; +use crate::extract_if::MakeExtractIf; + #[derive(Debug)] pub enum Value { ShaderCount(i32), diff --git a/librashader-runtime-vk/tests/triangle.rs b/librashader-runtime-vk/tests/triangle.rs index f5e3f38..68a3a71 100644 --- a/librashader-runtime-vk/tests/triangle.rs +++ b/librashader-runtime-vk/tests/triangle.rs @@ -11,8 +11,8 @@ fn triangle_vk() { unsafe { let filter = FilterChainVulkan::load_from_path( - // "../test/slang-shaders/crt/crt-royale.slangp", - "../test/Mega_Bezel_Packs/Duimon-Mega-Bezel/Presets/Advanced/Nintendo_GBA_SP/GBA_SP-[ADV]-[LCD-GRID]-[Night].slangp", + "../test/shaders_slang/crt/crt-royale.slangp", + // "../test/Mega_Bezel_Packs/Duimon-Mega-Bezel/Presets/Advanced/Nintendo_GBA_SP/GBA_SP-[ADV]-[LCD-GRID]-[Night].slangp", &base, // "../test/slang-shaders/test/feedback.slancargogp", // "../test/basic.slangp", diff --git a/librashader-runtime/src/array_chunks_mut.rs b/librashader-runtime/src/array_chunks_mut.rs new file mode 100644 index 0000000..4e111ab --- /dev/null +++ b/librashader-runtime/src/array_chunks_mut.rs @@ -0,0 +1,86 @@ +/// An iterator over a slice in (non-overlapping) mutable chunks (`N` elements +/// at a time), starting at the beginning of the slice. +/// +/// When the slice len is not evenly divided by the chunk size, the last +/// up to `N-1` elements will be omitted but can be retrieved from +/// the [`into_remainder`] function from the iterator. +/// +/// This struct is created by the [`array_chunks_mut`] method on [slices]. +/// +/// +/// [`array_chunks_mut`]: slice::array_chunks_mut +/// [`into_remainder`]: ../../std/slice/struct.ArrayChunksMut.html#method.into_remainder +/// [slices]: slice +#[derive(Debug)] +#[must_use = "iterators are lazy and do nothing unless consumed"] +pub struct ArrayChunksMut<'a, T: 'a, const N: usize> { + iter: core::slice::IterMut<'a, [T; N]>, +} + +impl<'a, T, const N: usize> ArrayChunksMut<'a, T, N> { + #[inline] + pub(super) fn new(slice: &'a mut [T]) -> Self { + let (array_slice, _rem) = as_chunks_mut(slice); + Self { iter: array_slice.iter_mut() } + } +} + +impl<'a, T, const N: usize> Iterator for ArrayChunksMut<'a, T, N> { + type Item = &'a mut [T; N]; + + #[inline] + fn next(&mut self) -> Option<&'a mut [T; N]> { + self.iter.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.iter.size_hint() + } + + #[inline] + fn count(self) -> usize { + self.iter.count() + } + + #[inline] + fn nth(&mut self, n: usize) -> Option { + self.iter.nth(n) + } + + #[inline] + fn last(self) -> Option { + self.iter.last() + } +} + + +/// Splits the slice into a slice of `N`-element arrays, +/// starting at the beginning of the slice, +/// and a remainder slice with length strictly less than `N`. +/// +/// # Panics +/// +/// Panics if `N` is 0. This check will most probably get changed to a compile time +/// error before this method gets stabilized. +/// +#[inline] +#[must_use] +fn as_chunks_mut(slice: &mut [T]) -> (&mut [[T; N]], &mut [T]) { + unsafe fn as_chunks_unchecked_mut(slice: &mut [T]) -> &mut [[T; N]] { + // SAFETY: Caller must guarantee that `N` is nonzero and exactly divides the slice length + let new_len = slice.len() / N; + + // SAFETY: We cast a slice of `new_len * N` elements into + // a slice of `new_len` many `N` elements chunks. + unsafe { core::slice::from_raw_parts_mut(slice.as_mut_ptr().cast(), new_len) } + } + + assert!(N != 0, "chunk size must be non-zero"); + let len = slice.len() / N; + let (multiple_of_n, remainder) = slice.split_at_mut(len * N); + // SAFETY: We already panicked for zero, and ensured by construction + // that the length of the subslice is a multiple of N. + let array_slice = unsafe { as_chunks_unchecked_mut(multiple_of_n) }; + (array_slice, remainder) +} \ No newline at end of file diff --git a/librashader-runtime/src/image.rs b/librashader-runtime/src/image.rs index bdd2430..98e4eab 100644 --- a/librashader-runtime/src/image.rs +++ b/librashader-runtime/src/image.rs @@ -3,6 +3,7 @@ use librashader_common::Size; use std::marker::PhantomData; use std::path::Path; +use crate::array_chunks_mut::ArrayChunksMut; /// An uncompressed raw image ready to upload to GPU buffers. pub struct Image { @@ -37,7 +38,8 @@ impl PixelFormat for RGBA8 { impl PixelFormat for BGRA8 { fn convert(pixels: &mut Vec) { - for [r, _g, b, _a] in pixels.array_chunks_mut::<4>() { + assert!(pixels.len() % 4 == 0); + for [r, _g, b, _a] in ArrayChunksMut::new(pixels) { std::mem::swap(b, r) } } diff --git a/librashader-runtime/src/lib.rs b/librashader-runtime/src/lib.rs index 04509c8..4702df3 100644 --- a/librashader-runtime/src/lib.rs +++ b/librashader-runtime/src/lib.rs @@ -6,7 +6,6 @@ //! //! If you are _writing_ a librashader runtime implementation, using these traits and helpers will //! help in maintaining consistent behaviour in binding semantics and image handling. -#![feature(array_chunks)] /// Scaling helpers. pub mod scaling; @@ -37,3 +36,6 @@ pub mod render_target; /// Helpers for handling framebuffers. pub mod framebuffer; + +/// array_chunks_mut polyfill +mod array_chunks_mut;