Skip to main content

gstreamer/subclass/
allocator.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::ptr;
4
5use glib::{prelude::*, subclass::prelude::*, translate::*};
6
7use super::prelude::*;
8use crate::{AllocationParams, Allocator, ffi};
9
10pub unsafe trait AllocatorImpl:
11    GstObjectImpl + ObjectSubclass<Type: IsA<Allocator>>
12{
13    /// Use `allocator` to allocate a new memory block with memory that is at least
14    /// `size` big.
15    ///
16    /// The optional `params` can specify the prefix and padding for the memory. If
17    /// [`None`] is passed, no flags, no extra prefix/padding and a default alignment is
18    /// used.
19    ///
20    /// The prefix/padding will be filled with 0 if flags contains
21    /// [`MemoryFlags::ZERO_PREFIXED`][crate::MemoryFlags::ZERO_PREFIXED] and [`MemoryFlags::ZERO_PADDED`][crate::MemoryFlags::ZERO_PADDED] respectively.
22    ///
23    /// When `allocator` is [`None`], the default allocator will be used.
24    ///
25    /// The alignment in `params` is given as a bitmask so that `align` + 1 equals
26    /// the amount of bytes to align to. For example, to align to 8 bytes,
27    /// use an alignment of 7.
28    /// ## `size`
29    /// size of the visible memory area
30    /// ## `params`
31    /// optional parameters
32    ///
33    /// # Returns
34    ///
35    /// a new [`Memory`][crate::Memory].
36    unsafe fn alloc(&self, size: usize, params: &AllocationParams) -> *mut ffi::GstMemory {
37        unsafe { self.parent_alloc(size, params) }
38    }
39
40    unsafe fn free(&self, memory: *mut ffi::GstMemory) {
41        unsafe { self.parent_free(memory) }
42    }
43}
44
45pub trait AllocatorImplExt: AllocatorImpl {
46    unsafe fn parent_alloc(&self, size: usize, params: &AllocationParams) -> *mut ffi::GstMemory {
47        unsafe {
48            let data = Self::type_data();
49            let parent_class = data.as_ref().parent_class() as *mut ffi::GstAllocatorClass;
50
51            if let Some(f) = (*parent_class).alloc {
52                f(
53                    self.obj().unsafe_cast_ref::<Allocator>().to_glib_none().0,
54                    size,
55                    mut_override(params.to_glib_none().0),
56                )
57            } else {
58                ptr::null_mut()
59            }
60        }
61    }
62
63    unsafe fn parent_free(&self, memory: *mut ffi::GstMemory) {
64        unsafe {
65            let data = Self::type_data();
66            let parent_class = data.as_ref().parent_class() as *mut ffi::GstAllocatorClass;
67
68            if let Some(f) = (*parent_class).free {
69                f(
70                    self.obj().unsafe_cast_ref::<Allocator>().to_glib_none().0,
71                    memory,
72                )
73            }
74        }
75    }
76}
77
78impl<T: AllocatorImpl> AllocatorImplExt for T {}
79
80unsafe impl<T: AllocatorImpl> IsSubclassable<T> for Allocator {
81    fn class_init(klass: &mut glib::Class<Self>) {
82        Self::parent_class_init::<T>(klass);
83        let klass = klass.as_mut();
84        klass.alloc = Some(alloc::<T>);
85        klass.free = Some(free::<T>);
86    }
87}
88
89unsafe extern "C" fn alloc<T: AllocatorImpl>(
90    ptr: *mut ffi::GstAllocator,
91    size: usize,
92    params: *mut ffi::GstAllocationParams,
93) -> *mut ffi::GstMemory {
94    unsafe {
95        let instance = &*(ptr as *mut T::Instance);
96        let imp = instance.imp();
97
98        let params = &*(params as *mut AllocationParams);
99
100        imp.alloc(size, params)
101    }
102}
103
104unsafe extern "C" fn free<T: AllocatorImpl>(
105    ptr: *mut ffi::GstAllocator,
106    memory: *mut ffi::GstMemory,
107) {
108    unsafe {
109        debug_assert_eq!((*memory).mini_object.refcount, 0);
110
111        let instance = &*(ptr as *mut T::Instance);
112        let imp = instance.imp();
113
114        imp.free(memory);
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121    use crate::prelude::*;
122
123    // The test allocator below is basically replicating GStreamer's default
124    // sysmem allocator except that the memory allocation is separate from the
125    // memory struct for clarity.
126
127    pub mod imp {
128        use std::alloc;
129
130        use super::*;
131
132        #[repr(C)]
133        struct Memory {
134            mem: ffi::GstMemory,
135            layout: alloc::Layout,
136            data: *mut u8,
137        }
138
139        const LAYOUT: alloc::Layout = alloc::Layout::new::<Memory>();
140
141        #[derive(Default)]
142        pub struct TestAllocator;
143
144        impl ObjectImpl for TestAllocator {}
145        impl GstObjectImpl for TestAllocator {}
146        unsafe impl AllocatorImpl for TestAllocator {
147            unsafe fn alloc(&self, size: usize, params: &AllocationParams) -> *mut ffi::GstMemory {
148                unsafe {
149                    let Some(maxsize) = size
150                        .checked_add(params.prefix())
151                        .and_then(|s| s.checked_add(params.padding()))
152                    else {
153                        return ptr::null_mut();
154                    };
155
156                    let align = params.align() | crate::Memory::default_alignment();
157                    let Ok(layout) = alloc::Layout::from_size_align(maxsize, align + 1) else {
158                        return ptr::null_mut();
159                    };
160
161                    let mem = alloc::alloc(LAYOUT) as *mut Memory;
162
163                    let data = alloc::alloc(layout);
164
165                    if params.prefix() > 0
166                        && params.flags().contains(crate::MemoryFlags::ZERO_PREFIXED)
167                    {
168                        ptr::write_bytes(data, 0, params.prefix());
169                    }
170
171                    if params.flags().contains(crate::MemoryFlags::ZERO_PADDED) {
172                        ptr::write_bytes(data.add(params.prefix()).add(size), 0, params.padding());
173                    }
174
175                    ffi::gst_memory_init(
176                        ptr::addr_of_mut!((*mem).mem),
177                        params.flags().into_glib(),
178                        self.obj().as_ptr() as *mut ffi::GstAllocator,
179                        ptr::null_mut(),
180                        maxsize,
181                        params.align(),
182                        params.prefix(),
183                        size,
184                    );
185                    ptr::write(ptr::addr_of_mut!((*mem).layout), layout);
186                    ptr::write(ptr::addr_of_mut!((*mem).data), data);
187
188                    mem as *mut ffi::GstMemory
189                }
190            }
191
192            unsafe fn free(&self, mem: *mut ffi::GstMemory) {
193                unsafe {
194                    let mem = mem as *mut Memory;
195
196                    if (*mem).mem.parent.is_null() {
197                        alloc::dealloc((*mem).data, (*mem).layout);
198                        ptr::drop_in_place(ptr::addr_of_mut!((*mem).layout));
199                    }
200                    alloc::dealloc(mem as *mut u8, LAYOUT);
201                }
202            }
203        }
204
205        #[glib::object_subclass]
206        impl ObjectSubclass for TestAllocator {
207            const NAME: &'static str = "TestAllocator";
208            type Type = super::TestAllocator;
209            type ParentType = Allocator;
210
211            fn instance_init(obj: &glib::subclass::InitializingObject<Self>) {
212                static ALLOCATOR_TYPE: &[u8] = b"TestAllocatorMemory\0";
213
214                unsafe {
215                    let allocator = obj.as_ptr() as *mut ffi::GstAllocator;
216
217                    // TODO: This should all be in some kind of trait ideally
218                    (*allocator).mem_type = ALLOCATOR_TYPE.as_ptr() as *const _;
219                    (*allocator).mem_map = Some(TestAllocator::mem_map);
220                    (*allocator).mem_unmap = Some(TestAllocator::mem_unmap);
221                    // mem_copy not set because the fallback already does the right thing
222                    (*allocator).mem_share = Some(TestAllocator::mem_share);
223                    (*allocator).mem_is_span = Some(TestAllocator::mem_is_span);
224                }
225            }
226        }
227
228        impl TestAllocator {
229            unsafe extern "C" fn mem_map(
230                mem: *mut ffi::GstMemory,
231                _maxsize: usize,
232                _flags: ffi::GstMapFlags,
233            ) -> glib::ffi::gpointer {
234                unsafe {
235                    let mem = mem as *mut Memory;
236
237                    let parent = if (*mem).mem.parent.is_null() {
238                        mem
239                    } else {
240                        (*mem).mem.parent as *mut Memory
241                    };
242
243                    // `(*mem).offset` is added to the pointer by `gst_memory_map()`
244                    (*parent).data as *mut _
245                }
246            }
247
248            unsafe extern "C" fn mem_unmap(_mem: *mut ffi::GstMemory) {}
249
250            unsafe extern "C" fn mem_share(
251                mem: *mut ffi::GstMemory,
252                offset: isize,
253                size: isize,
254            ) -> *mut ffi::GstMemory {
255                unsafe {
256                    let mem = mem as *mut Memory;
257
258                    // Basically a re-implementation of _sysmem_share()
259
260                    let parent = if (*mem).mem.parent.is_null() {
261                        mem
262                    } else {
263                        (*mem).mem.parent as *mut Memory
264                    };
265
266                    // Offset and size are actually usizes and the API assumes that negative values simply wrap
267                    // around, so let's cast to usizes here and do wrapping arithmetic.
268                    let offset = offset as usize;
269                    let mut size = size as usize;
270
271                    let new_offset = (*mem).mem.offset.wrapping_add(offset);
272                    debug_assert!(new_offset < (*mem).mem.maxsize);
273
274                    if size == usize::MAX {
275                        size = (*mem).mem.size.wrapping_sub(offset);
276                    }
277                    debug_assert!(new_offset <= usize::MAX - size);
278                    debug_assert!(new_offset + size <= (*mem).mem.maxsize);
279
280                    let sub = alloc::alloc(LAYOUT) as *mut Memory;
281
282                    ffi::gst_memory_init(
283                        sub as *mut ffi::GstMemory,
284                        (*mem).mem.mini_object.flags | ffi::GST_MINI_OBJECT_FLAG_LOCK_READONLY,
285                        (*mem).mem.allocator,
286                        parent as *mut ffi::GstMemory,
287                        (*mem).mem.maxsize,
288                        (*mem).mem.align,
289                        new_offset,
290                        size,
291                    );
292                    // This is never actually accessed
293                    ptr::write(ptr::addr_of_mut!((*sub).data), ptr::null_mut());
294
295                    sub as *mut ffi::GstMemory
296                }
297            }
298
299            unsafe extern "C" fn mem_is_span(
300                mem1: *mut ffi::GstMemory,
301                mem2: *mut ffi::GstMemory,
302                offset: *mut usize,
303            ) -> glib::ffi::gboolean {
304                unsafe {
305                    let mem1 = mem1 as *mut Memory;
306                    let mem2 = mem2 as *mut Memory;
307
308                    // Same parent is checked by `gst_memory_is_span()` already
309                    let parent1 = (*mem1).mem.parent as *mut Memory;
310                    let parent2 = (*mem2).mem.parent as *mut Memory;
311                    debug_assert_eq!(parent1, parent2);
312
313                    if !offset.is_null() {
314                        // Offset that can be used on the parent memory to create a
315                        // shared memory that starts with `mem1`.
316                        //
317                        // This needs to use wrapping arithmetic too as in `mem_share()`.
318                        *offset = (*mem1).mem.offset.wrapping_sub((*parent1).mem.offset);
319                    }
320
321                    // Check if both memories are contiguous.
322                    let is_span = ((*mem1).mem.offset + ((*mem1).mem.size)) == (*mem2).mem.offset;
323
324                    is_span.into_glib()
325                }
326            }
327        }
328    }
329
330    glib::wrapper! {
331        pub struct TestAllocator(ObjectSubclass<imp::TestAllocator>) @extends Allocator, crate::Object;
332    }
333
334    impl Default for TestAllocator {
335        fn default() -> Self {
336            glib::Object::new()
337        }
338    }
339
340    #[test]
341    fn test_allocator_registration() {
342        crate::init().unwrap();
343
344        const TEST_ALLOCATOR_NAME: &str = "TestAllocator";
345
346        let allocator = TestAllocator::default();
347        Allocator::register(TEST_ALLOCATOR_NAME, allocator);
348
349        let allocator = Allocator::find(Some(TEST_ALLOCATOR_NAME));
350
351        assert!(allocator.is_some());
352    }
353
354    #[test]
355    fn test_allocator_alloc() {
356        crate::init().unwrap();
357
358        const SIZE: usize = 1024;
359
360        let allocator = TestAllocator::default();
361
362        let memory = allocator.alloc(SIZE, None).unwrap();
363
364        assert_eq!(memory.size(), SIZE);
365    }
366
367    #[test]
368    fn test_allocator_mem_ops() {
369        crate::init().unwrap();
370
371        let data = [0, 1, 2, 3, 4, 5, 6, 7];
372
373        let allocator = TestAllocator::default();
374
375        let mut memory = allocator.alloc(data.len(), None).unwrap();
376        assert_eq!(memory.size(), data.len());
377
378        {
379            let memory = memory.get_mut().unwrap();
380            let mut map = memory.map_writable().unwrap();
381            map.copy_from_slice(&data);
382        }
383
384        let copy = memory.copy();
385        assert!(copy.parent().is_none());
386
387        {
388            let map1 = memory.map_readable().unwrap();
389            let map2 = copy.map_readable().unwrap();
390            assert_eq!(map1.as_slice(), map2.as_slice());
391        }
392
393        let share = memory.share(..);
394        assert_eq!(share.parent().unwrap().as_ptr(), memory.as_ptr());
395
396        {
397            let map1 = memory.map_readable().unwrap();
398            let map2 = share.map_readable().unwrap();
399            assert_eq!(map1.as_slice(), map2.as_slice());
400        }
401
402        let sub1 = memory.share(..2);
403        assert_eq!(sub1.size(), 2);
404        assert_eq!(sub1.parent().unwrap().as_ptr(), memory.as_ptr());
405
406        {
407            let map = sub1.map_readable().unwrap();
408            assert_eq!(map.as_slice(), &data[..2]);
409        }
410
411        let sub2 = memory.share(2..);
412        assert_eq!(sub2.size(), 6);
413        assert_eq!(sub2.parent().unwrap().as_ptr(), memory.as_ptr());
414
415        {
416            let map = sub2.map_readable().unwrap();
417            assert_eq!(map.as_slice(), &data[2..]);
418        }
419
420        let offset = sub1.is_span(&sub2).unwrap();
421        assert_eq!(offset, 0);
422
423        let sub3 = sub2.share(2..);
424        assert_eq!(sub3.size(), 4);
425        assert_eq!(sub3.parent().unwrap().as_ptr(), memory.as_ptr());
426
427        {
428            let map = sub3.map_readable().unwrap();
429            assert_eq!(map.as_slice(), &data[4..]);
430        }
431    }
432}