From 3938d791c1526413aa0d49cd33896456557ffc6f Mon Sep 17 00:00:00 2001 From: Daria Sukhonina Date: Wed, 8 Jun 2022 04:59:24 +0300 Subject: [PATCH 1/8] fix: atomic operations in `SimpleVramAllocator` --- psp/src/vram_alloc.rs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/psp/src/vram_alloc.rs b/psp/src/vram_alloc.rs index f343bdcb..86939b00 100644 --- a/psp/src/vram_alloc.rs +++ b/psp/src/vram_alloc.rs @@ -79,7 +79,8 @@ impl SimpleVramAllocator { /// previously allocated `VramMemChunk`s since they have the lifetime of the /// `&Self` that allocated them. pub fn free_all(&mut self) { - self.offset.store(0, Ordering::Relaxed); + // Store is required to occure after all previous bumps and before any next bumps, so SeqCst + self.offset.store(0, Ordering::SeqCst); } // TODO: return a Result instead of panicking @@ -88,13 +89,13 @@ impl SimpleVramAllocator { /// The returned VRAM chunk has the same lifetime as the /// `SimpleVramAllocator` borrow (i.e. `&self`) that allocated it. pub fn alloc<'a>(&'a self, size: u32) -> VramMemChunk<'a> { - let old_offset = self.offset.load(Ordering::Relaxed); - let new_offset = old_offset + size; - self.offset.store(new_offset, Ordering::Relaxed); - - if new_offset > self.total_mem() { - panic!("Total VRAM size exceeded!"); - } + // Atomically bump offset, no ordering required + let old_offset = self + .offset + .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |old| { + old.checked_add(size).filter(|new| *new <= self.total_mem()) + }) + .unwrap_or_else(|_| panic!("Total VRAM size exceeded!")); VramMemChunk::new(old_offset, size) } From 1b99276ff398e06cad1b547912b273c453550d06 Mon Sep 17 00:00:00 2001 From: Daria Sukhonina Date: Wed, 8 Jun 2022 07:39:36 +0300 Subject: [PATCH 2/8] refactor: add todos --- psp/src/vram_alloc.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/psp/src/vram_alloc.rs b/psp/src/vram_alloc.rs index 86939b00..97f4821c 100644 --- a/psp/src/vram_alloc.rs +++ b/psp/src/vram_alloc.rs @@ -7,14 +7,18 @@ use core::sync::atomic::{AtomicU32, Ordering}; type VramAllocator = SimpleVramAllocator; +// TODO: implement some traits #[derive(Debug)] pub struct VramAllocatorInUseError {} static mut VRAM_ALLOCATOR: VramAllocatorSingleton = VramAllocatorSingleton { - alloc: Some(VramAllocator::new()), + alloc: Some(VramAllocator::new_()), }; +// TODO: rename using `take` verb +// TODO: static method pub fn get_vram_allocator() -> Result { + // TODO: this unsafe is not thread-safe let opt_alloc = unsafe { VRAM_ALLOCATOR.get_vram_alloc() }; opt_alloc.ok_or(VramAllocatorInUseError {}) } @@ -37,7 +41,7 @@ pub struct VramMemChunk<'a> { } impl VramMemChunk<'_> { - fn new(start: u32, len: u32) -> Self { + fn new_(start: u32, len: u32) -> Self { Self { start, len, @@ -59,14 +63,17 @@ impl VramMemChunk<'_> { } // A dead-simple VRAM bump allocator. -// TODO: pin? +// There could be only one value of this type +// WARNING: should be instantiated only via [`VramAllocator::new`] +// TODO: remove Debug #[derive(Debug)] pub struct SimpleVramAllocator { offset: AtomicU32, } impl SimpleVramAllocator { - const fn new() -> Self { + // WARNING: should only be callsed inside of [`get_vram_allocator`] + const fn new_() -> Self { Self { offset: AtomicU32::new(0), } @@ -75,7 +82,8 @@ impl SimpleVramAllocator { /// Frees all previously allocated VRAM chunks. /// /// This resets the allocator's counter, but does not change the contents of - /// VRAM. Since this method requires `&mut Self`, it cannot overlap with any + /// VRAM. Since this method requires `&mut Self` and there are no other + /// `SimpleVramAllocator` values to be swapped with, it cannot overlap with any /// previously allocated `VramMemChunk`s since they have the lifetime of the /// `&Self` that allocated them. pub fn free_all(&mut self) { @@ -84,6 +92,7 @@ impl SimpleVramAllocator { } // TODO: return a Result instead of panicking + // TODO: handle alignment /// Allocates `size` bytes of VRAM /// /// The returned VRAM chunk has the same lifetime as the @@ -97,7 +106,7 @@ impl SimpleVramAllocator { }) .unwrap_or_else(|_| panic!("Total VRAM size exceeded!")); - VramMemChunk::new(old_offset, size) + VramMemChunk::new_(old_offset, size) } // TODO: ensure 16-bit alignment? From 1aa30843e84066920a568f4c41e9fc7db64c9604 Mon Sep 17 00:00:00 2001 From: Daria Sukhonina Date: Wed, 8 Jun 2022 07:53:44 +0300 Subject: [PATCH 3/8] fix: get_vram_allocator --- psp/src/vram_alloc.rs | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/psp/src/vram_alloc.rs b/psp/src/vram_alloc.rs index 97f4821c..f9a4ad8e 100644 --- a/psp/src/vram_alloc.rs +++ b/psp/src/vram_alloc.rs @@ -3,7 +3,7 @@ use crate::sys::{sceGeEdramGetAddr, sceGeEdramGetSize}; use core::marker::PhantomData; use core::mem::size_of; use core::ptr::null_mut; -use core::sync::atomic::{AtomicU32, Ordering}; +use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; type VramAllocator = SimpleVramAllocator; @@ -11,25 +11,16 @@ type VramAllocator = SimpleVramAllocator; #[derive(Debug)] pub struct VramAllocatorInUseError {} -static mut VRAM_ALLOCATOR: VramAllocatorSingleton = VramAllocatorSingleton { - alloc: Some(VramAllocator::new_()), -}; - // TODO: rename using `take` verb // TODO: static method pub fn get_vram_allocator() -> Result { - // TODO: this unsafe is not thread-safe - let opt_alloc = unsafe { VRAM_ALLOCATOR.get_vram_alloc() }; - opt_alloc.ok_or(VramAllocatorInUseError {}) -} - -pub struct VramAllocatorSingleton { - alloc: Option, -} + static IS_TAKEN: AtomicBool = AtomicBool::new(false); -impl VramAllocatorSingleton { - pub fn get_vram_alloc(&mut self) -> Option { - self.alloc.take() + if !IS_TAKEN.swap(true, Ordering::Relaxed) { + // new and empty, since old one droped and cannot have any references to it + Ok(VramAllocator::new_()) + } else { + Err(VramAllocatorInUseError {}) } } From 54c5f81a7423a9dea55d3d83d2215c30cea30a2c Mon Sep 17 00:00:00 2001 From: Daria Sukhonina Date: Wed, 8 Jun 2022 07:59:24 +0300 Subject: [PATCH 4/8] refactor: implement Drop for SimpleVramAllocator to return ownership --- psp/src/vram_alloc.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/psp/src/vram_alloc.rs b/psp/src/vram_alloc.rs index f9a4ad8e..f66abf8c 100644 --- a/psp/src/vram_alloc.rs +++ b/psp/src/vram_alloc.rs @@ -11,12 +11,13 @@ type VramAllocator = SimpleVramAllocator; #[derive(Debug)] pub struct VramAllocatorInUseError {} +// WARNING: should only be used in [`get_vram_allocator`] and [`::drop`] +static VRAM_ALLOCATOR_IS_TAKEN: AtomicBool = AtomicBool::new(false); + // TODO: rename using `take` verb // TODO: static method pub fn get_vram_allocator() -> Result { - static IS_TAKEN: AtomicBool = AtomicBool::new(false); - - if !IS_TAKEN.swap(true, Ordering::Relaxed) { + if !VRAM_ALLOCATOR_IS_TAKEN.swap(true, Ordering::Relaxed) { // new and empty, since old one droped and cannot have any references to it Ok(VramAllocator::new_()) } else { @@ -132,6 +133,12 @@ impl SimpleVramAllocator { } } +impl Drop for SimpleVramAllocator { + fn drop(&mut self) { + VRAM_ALLOCATOR_IS_TAKEN.store(false, Ordering::Relaxed) + } +} + fn total_vram_size() -> u32 { unsafe { sceGeEdramGetSize() } } From a1b7bfacad9f2af62c9a573b5f38660764d2356d Mon Sep 17 00:00:00 2001 From: Daria Sukhonina Date: Wed, 8 Jun 2022 08:19:50 +0300 Subject: [PATCH 5/8] refactor!: improve errors --- psp/src/vram_alloc.rs | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/psp/src/vram_alloc.rs b/psp/src/vram_alloc.rs index f66abf8c..35683d63 100644 --- a/psp/src/vram_alloc.rs +++ b/psp/src/vram_alloc.rs @@ -7,9 +7,14 @@ use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; type VramAllocator = SimpleVramAllocator; -// TODO: implement some traits -#[derive(Debug)] -pub struct VramAllocatorInUseError {} +#[derive(Copy, Clone, Debug)] +pub struct VramAllocatorInUseError; + +impl core::fmt::Display for VramAllocatorInUseError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("ownership of `VramAllocator` already taken") + } +} // WARNING: should only be used in [`get_vram_allocator`] and [`::drop`] static VRAM_ALLOCATOR_IS_TAKEN: AtomicBool = AtomicBool::new(false); @@ -21,7 +26,7 @@ pub fn get_vram_allocator() -> Result { // new and empty, since old one droped and cannot have any references to it Ok(VramAllocator::new_()) } else { - Err(VramAllocatorInUseError {}) + Err(VramAllocatorInUseError) } } @@ -63,6 +68,15 @@ pub struct SimpleVramAllocator { offset: AtomicU32, } +#[derive(Copy, Clone, Debug)] +pub struct VramAllocError; + +impl core::fmt::Display for VramAllocError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.write_str("Total VRAM size exceeded") + } +} + impl SimpleVramAllocator { // WARNING: should only be callsed inside of [`get_vram_allocator`] const fn new_() -> Self { @@ -89,20 +103,23 @@ impl SimpleVramAllocator { /// /// The returned VRAM chunk has the same lifetime as the /// `SimpleVramAllocator` borrow (i.e. `&self`) that allocated it. - pub fn alloc<'a>(&'a self, size: u32) -> VramMemChunk<'a> { + pub fn alloc<'a>(&'a self, size: u32) -> Result, VramAllocError> { // Atomically bump offset, no ordering required let old_offset = self .offset .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |old| { old.checked_add(size).filter(|new| *new <= self.total_mem()) }) - .unwrap_or_else(|_| panic!("Total VRAM size exceeded!")); + .map_err(|_| VramAllocError)?; - VramMemChunk::new_(old_offset, size) + Ok(VramMemChunk::new_(old_offset, size)) } // TODO: ensure 16-bit alignment? - pub fn alloc_sized<'a, T: Sized>(&'a self, count: u32) -> VramMemChunk<'a> { + pub fn alloc_sized<'a, T: Sized>( + &'a self, + count: u32, + ) -> Result, VramAllocError> { let size = size_of::() as u32; self.alloc(count * size) } @@ -112,20 +129,20 @@ impl SimpleVramAllocator { width: u32, height: u32, psm: TexturePixelFormat, - ) -> VramMemChunk<'a> { + ) -> Result, VramAllocError> { let size = get_memory_size(width, height, psm); self.alloc(size) } // TODO: write, or write_volatile? - // TODO: result instead of unwrap? + // TODO: panic instead of result? // TODO: Keep track of the allocated chunk // TODO: determine unsafety of this - pub unsafe fn move_to_vram(&mut self, obj: T) -> &mut T { - let chunk = self.alloc_sized::(1); + pub unsafe fn move_to_vram(&mut self, obj: T) -> Result<&mut T, VramAllocError> { + let chunk = self.alloc_sized::(1)?; let ptr = chunk.as_mut_ptr_direct_to_vram() as *mut T; ptr.write(obj); - ptr.as_mut().unwrap() + Ok(&mut *ptr) } fn total_mem(&self) -> u32 { From ad98b5781f470e71c00ede8f39f4154607b39f11 Mon Sep 17 00:00:00 2001 From: Daria Sukhonina Date: Wed, 8 Jun 2022 08:31:13 +0300 Subject: [PATCH 6/8] refactor!: rename `get_vram_allocator` to `VramAllocator::take` And isolate unsafety inside of `vram_allocator_singleton` --- psp/src/vram_alloc.rs | 52 +++++++++++++++++++++---------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/psp/src/vram_alloc.rs b/psp/src/vram_alloc.rs index 35683d63..6c09b904 100644 --- a/psp/src/vram_alloc.rs +++ b/psp/src/vram_alloc.rs @@ -3,7 +3,7 @@ use crate::sys::{sceGeEdramGetAddr, sceGeEdramGetSize}; use core::marker::PhantomData; use core::mem::size_of; use core::ptr::null_mut; -use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; +use core::sync::atomic::{AtomicU32, Ordering}; type VramAllocator = SimpleVramAllocator; @@ -16,17 +16,29 @@ impl core::fmt::Display for VramAllocatorInUseError { } } -// WARNING: should only be used in [`get_vram_allocator`] and [`::drop`] -static VRAM_ALLOCATOR_IS_TAKEN: AtomicBool = AtomicBool::new(false); - -// TODO: rename using `take` verb -// TODO: static method -pub fn get_vram_allocator() -> Result { - if !VRAM_ALLOCATOR_IS_TAKEN.swap(true, Ordering::Relaxed) { - // new and empty, since old one droped and cannot have any references to it - Ok(VramAllocator::new_()) - } else { - Err(VramAllocatorInUseError) +mod vram_allocator_singleton { + use super::{SimpleVramAllocator, VramAllocator, VramAllocatorInUseError}; + use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; + + static VRAM_ALLOCATOR_IS_TAKEN: AtomicBool = AtomicBool::new(false); + + impl VramAllocator { + pub fn take() -> Result { + if !VRAM_ALLOCATOR_IS_TAKEN.swap(true, Ordering::Relaxed) { + // new and empty, since old one droped and cannot have any references to it + Ok(VramAllocator { + offset: AtomicU32::new(0), + }) + } else { + Err(VramAllocatorInUseError) + } + } + } + + impl Drop for SimpleVramAllocator { + fn drop(&mut self) { + VRAM_ALLOCATOR_IS_TAKEN.store(false, Ordering::Relaxed) + } } } @@ -61,7 +73,7 @@ impl VramMemChunk<'_> { // A dead-simple VRAM bump allocator. // There could be only one value of this type -// WARNING: should be instantiated only via [`VramAllocator::new`] +// WARNING: should be instantiated only within [`vram_allocator_singleton`] private module // TODO: remove Debug #[derive(Debug)] pub struct SimpleVramAllocator { @@ -78,13 +90,6 @@ impl core::fmt::Display for VramAllocError { } impl SimpleVramAllocator { - // WARNING: should only be callsed inside of [`get_vram_allocator`] - const fn new_() -> Self { - Self { - offset: AtomicU32::new(0), - } - } - /// Frees all previously allocated VRAM chunks. /// /// This resets the allocator's counter, but does not change the contents of @@ -97,7 +102,6 @@ impl SimpleVramAllocator { self.offset.store(0, Ordering::SeqCst); } - // TODO: return a Result instead of panicking // TODO: handle alignment /// Allocates `size` bytes of VRAM /// @@ -150,12 +154,6 @@ impl SimpleVramAllocator { } } -impl Drop for SimpleVramAllocator { - fn drop(&mut self) { - VRAM_ALLOCATOR_IS_TAKEN.store(false, Ordering::Relaxed) - } -} - fn total_vram_size() -> u32 { unsafe { sceGeEdramGetSize() } } From 705458b68770eafed20e1a581aec688739e690a3 Mon Sep 17 00:00:00 2001 From: Daria Sukhonina Date: Wed, 8 Jun 2022 08:42:04 +0300 Subject: [PATCH 7/8] refactor: public `VramAllocator` type alias, fix examples --- examples/cube/src/main.rs | 70 +++++++++++++++++++++-------- examples/gu-background/src/main.rs | 38 ++++++++++------ examples/gu-debug-print/src/main.rs | 11 +++-- psp/src/vram_alloc.rs | 2 +- 4 files changed, 84 insertions(+), 37 deletions(-) diff --git a/examples/cube/src/main.rs b/examples/cube/src/main.rs index 81b46df2..23d4e94f 100644 --- a/examples/cube/src/main.rs +++ b/examples/cube/src/main.rs @@ -1,16 +1,15 @@ #![no_std] #![no_main] -use core::{ptr, f32::consts::PI}; -use psp::Align16; +use core::{f32::consts::PI, ptr}; use psp::sys::{ - self, ScePspFVector3, DisplayPixelFormat, GuContextType, GuSyncMode, GuSyncBehavior, - GuPrimitive, TextureFilter, TextureEffect, TextureColorComponent, - FrontFaceDirection, ShadingModel, GuState, TexturePixelFormat, DepthFunc, - VertexType, ClearBuffer, MipmapLevel, + self, ClearBuffer, DepthFunc, DisplayPixelFormat, FrontFaceDirection, GuContextType, + GuPrimitive, GuState, GuSyncBehavior, GuSyncMode, MipmapLevel, ScePspFVector3, ShadingModel, + TextureColorComponent, TextureEffect, TextureFilter, TexturePixelFormat, VertexType, }; -use psp::vram_alloc::get_vram_allocator; -use psp::{BUF_WIDTH, SCREEN_WIDTH, SCREEN_HEIGHT}; +use psp::vram_alloc::VramAllocator; +use psp::Align16; +use psp::{BUF_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH}; psp::module!("sample_cube", 1, 1); @@ -18,7 +17,8 @@ psp::module!("sample_cube", 1, 1); const IMAGE_SIZE: usize = 128; // The image data *must* be aligned to a 16 byte boundary. -static FERRIS: Align16<[u8; IMAGE_SIZE * IMAGE_SIZE * 4]> = Align16(*include_bytes!("../ferris.bin")); +static FERRIS: Align16<[u8; IMAGE_SIZE * IMAGE_SIZE * 4]> = + Align16(*include_bytes!("../ferris.bin")); static mut LIST: Align16<[u32; 0x40000]> = Align16([0; 0x40000]); @@ -31,6 +31,7 @@ struct Vertex { z: f32, } +#[rustfmt::skip] static VERTICES: Align16<[Vertex; 12 * 3]> = Align16([ Vertex { u: 0.0, v: 0.0, x: -1.0, y: -1.0, z: 1.0}, // 0 Vertex { u: 1.0, v: 0.0, x: -1.0, y: 1.0, z: 1.0}, // 4 @@ -88,10 +89,16 @@ fn psp_main() { unsafe fn psp_main_inner() { psp::enable_home_button(); - let mut allocator = get_vram_allocator().unwrap(); - let fbp0 = allocator.alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm8888); - let fbp1 = allocator.alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm8888); - let zbp = allocator.alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm4444); + let allocator = VramAllocator::take().unwrap(); + let fbp0 = allocator + .alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm8888) + .unwrap(); + let fbp1 = allocator + .alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm8888) + .unwrap(); + let zbp = allocator + .alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm4444) + .unwrap(); // Attempting to free the three VRAM chunks at this point would give a // compile-time error since fbp0, fbp1 and zbp are used later on //allocator.free_all(); @@ -100,9 +107,21 @@ unsafe fn psp_main_inner() { sys::sceGuInit(); - sys::sceGuStart(GuContextType::Direct, &mut LIST.0 as *mut [u32; 0x40000] as *mut _); - sys::sceGuDrawBuffer(DisplayPixelFormat::Psm8888, fbp0.as_mut_ptr_from_zero() as _, BUF_WIDTH as i32); - sys::sceGuDispBuffer(SCREEN_WIDTH as i32, SCREEN_HEIGHT as i32, fbp1.as_mut_ptr_from_zero() as _, BUF_WIDTH as i32); + sys::sceGuStart( + GuContextType::Direct, + &mut LIST.0 as *mut [u32; 0x40000] as *mut _, + ); + sys::sceGuDrawBuffer( + DisplayPixelFormat::Psm8888, + fbp0.as_mut_ptr_from_zero() as _, + BUF_WIDTH as i32, + ); + sys::sceGuDispBuffer( + SCREEN_WIDTH as i32, + SCREEN_HEIGHT as i32, + fbp1.as_mut_ptr_from_zero() as _, + BUF_WIDTH as i32, + ); sys::sceGuDepthBuffer(zbp.as_mut_ptr_from_zero() as _, BUF_WIDTH as i32); sys::sceGuOffset(2048 - (SCREEN_WIDTH / 2), 2048 - (SCREEN_HEIGHT / 2)); sys::sceGuViewport(2048, 2048, SCREEN_WIDTH as i32, SCREEN_HEIGHT as i32); @@ -128,7 +147,10 @@ unsafe fn psp_main_inner() { let mut val = 0.0; loop { - sys::sceGuStart(GuContextType::Direct, &mut LIST.0 as *mut [u32; 0x40000] as *mut _); + sys::sceGuStart( + GuContextType::Direct, + &mut LIST.0 as *mut [u32; 0x40000] as *mut _, + ); // clear screen sys::sceGuClearColor(0xff554433); @@ -148,7 +170,11 @@ unsafe fn psp_main_inner() { sys::sceGumLoadIdentity(); { - let pos = ScePspFVector3 { x: 0.0, y: 0.0, z: -2.5 }; + let pos = ScePspFVector3 { + x: 0.0, + y: 0.0, + z: -2.5, + }; let rot = ScePspFVector3 { x: val * 0.79 * (PI / 180.0), y: val * 0.98 * (PI / 180.0), @@ -162,7 +188,13 @@ unsafe fn psp_main_inner() { // setup texture sys::sceGuTexMode(TexturePixelFormat::Psm8888, 0, 0, 0); - sys::sceGuTexImage(MipmapLevel::None, 128, 128, 128, &FERRIS as *const _ as *const _); + sys::sceGuTexImage( + MipmapLevel::None, + 128, + 128, + 128, + &FERRIS as *const _ as *const _, + ); sys::sceGuTexFunc(TextureEffect::Replace, TextureColorComponent::Rgb); sys::sceGuTexFilter(TextureFilter::Linear, TextureFilter::Linear); sys::sceGuTexScale(1.0, 1.0); diff --git a/examples/gu-background/src/main.rs b/examples/gu-background/src/main.rs index 9004c091..c510c67a 100644 --- a/examples/gu-background/src/main.rs +++ b/examples/gu-background/src/main.rs @@ -4,9 +4,9 @@ #![no_main] use core::ffi::c_void; -use psp::sys::{self, GuState, TexturePixelFormat, DisplayPixelFormat}; -use psp::vram_alloc::get_vram_allocator; -use psp::{BUF_WIDTH, SCREEN_WIDTH, SCREEN_HEIGHT}; +use psp::sys::{self, DisplayPixelFormat, GuState, TexturePixelFormat}; +use psp::vram_alloc::VramAllocator; +use psp::{BUF_WIDTH, SCREEN_HEIGHT, SCREEN_WIDTH}; psp::module!("sample_gu_background", 1, 1); @@ -15,22 +15,35 @@ static mut LIST: psp::Align16<[u32; 0x40000]> = psp::Align16([0; 0x40000]); fn psp_main() { psp::enable_home_button(); - let mut allocator = get_vram_allocator().unwrap(); - let fbp0 = allocator.alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm8888).as_mut_ptr_from_zero(); - let fbp1 = allocator.alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm8888).as_mut_ptr_from_zero(); - let zbp = allocator.alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm4444).as_mut_ptr_from_zero(); + let allocator = VramAllocator::take().unwrap(); + let fbp0 = allocator + .alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm8888) + .unwrap() + .as_mut_ptr_from_zero(); + let fbp1 = allocator + .alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm8888) + .unwrap() + .as_mut_ptr_from_zero(); + let zbp = allocator + .alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm4444) + .unwrap() + .as_mut_ptr_from_zero(); unsafe { - sys::sceGuInit(); sys::sceGuStart( sys::GuContextType::Direct, &mut LIST as *mut _ as *mut c_void, ); sys::sceGuDrawBuffer(DisplayPixelFormat::Psm8888, fbp0 as _, BUF_WIDTH as i32); - sys::sceGuDispBuffer(SCREEN_WIDTH as i32, SCREEN_HEIGHT as i32, fbp1 as _, BUF_WIDTH as i32); + sys::sceGuDispBuffer( + SCREEN_WIDTH as i32, + SCREEN_HEIGHT as i32, + fbp1 as _, + BUF_WIDTH as i32, + ); sys::sceGuDepthBuffer(zbp as _, BUF_WIDTH as i32); - sys::sceGuOffset(2048 - (SCREEN_WIDTH/2), 2048 - (SCREEN_HEIGHT/2)); + sys::sceGuOffset(2048 - (SCREEN_WIDTH / 2), 2048 - (SCREEN_HEIGHT / 2)); sys::sceGuViewport(2048, 2048, SCREEN_WIDTH as i32, SCREEN_HEIGHT as i32); sys::sceGuDepthRange(65535, 0); sys::sceGuScissor(0, 0, SCREEN_WIDTH as i32, SCREEN_HEIGHT as i32); @@ -43,13 +56,12 @@ fn psp_main() { loop { sys::sceGuStart( sys::GuContextType::Direct, - &mut LIST as *mut _ as *mut c_void + &mut LIST as *mut _ as *mut c_void, ); sys::sceGuClearColor(0xff554433); sys::sceGuClearDepth(0); sys::sceGuClear( - sys::ClearBuffer::COLOR_BUFFER_BIT | - sys::ClearBuffer::DEPTH_BUFFER_BIT + sys::ClearBuffer::COLOR_BUFFER_BIT | sys::ClearBuffer::DEPTH_BUFFER_BIT, ); sys::sceGuFinish(); sys::sceGuSync(sys::GuSyncMode::Finish, sys::GuSyncBehavior::Wait); diff --git a/examples/gu-debug-print/src/main.rs b/examples/gu-debug-print/src/main.rs index d7f06c7f..b744eb8c 100644 --- a/examples/gu-debug-print/src/main.rs +++ b/examples/gu-debug-print/src/main.rs @@ -5,8 +5,8 @@ #![no_main] use core::ffi::c_void; -use psp::sys::{self, TexturePixelFormat, DisplayPixelFormat}; -use psp::vram_alloc::get_vram_allocator; +use psp::sys::{self, DisplayPixelFormat, TexturePixelFormat}; +use psp::vram_alloc::VramAllocator; use psp::{BUF_WIDTH, SCREEN_HEIGHT}; psp::module!("sample_gu_debug", 1, 1); @@ -16,8 +16,11 @@ static mut LIST: psp::Align16<[u32; 0x40000]> = psp::Align16([0; 0x40000]); fn psp_main() { psp::enable_home_button(); - let mut allocator = get_vram_allocator().unwrap(); - let fbp0 = allocator.alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm8888).as_mut_ptr_from_zero(); + let allocator = VramAllocator::take().unwrap(); + let fbp0 = allocator + .alloc_texture_pixels(BUF_WIDTH, SCREEN_HEIGHT, TexturePixelFormat::Psm8888) + .unwrap() + .as_mut_ptr_from_zero(); unsafe { sys::sceGuInit(); diff --git a/psp/src/vram_alloc.rs b/psp/src/vram_alloc.rs index 6c09b904..3fdfe7e3 100644 --- a/psp/src/vram_alloc.rs +++ b/psp/src/vram_alloc.rs @@ -5,7 +5,7 @@ use core::mem::size_of; use core::ptr::null_mut; use core::sync::atomic::{AtomicU32, Ordering}; -type VramAllocator = SimpleVramAllocator; +pub type VramAllocator = SimpleVramAllocator; #[derive(Copy, Clone, Debug)] pub struct VramAllocatorInUseError; From 467a6c15f2e856fa243440041aefb321a178636b Mon Sep 17 00:00:00 2001 From: Daria Sukhonina Date: Wed, 6 Jul 2022 15:02:43 +0300 Subject: [PATCH 8/8] latest try at vram mod --- examples/cube/src/main.rs | 21 +-- psp/src/lib.rs | 94 ++++++----- psp/src/vram_alloc.rs | 346 ++++++++++++++++++++++++++------------ 3 files changed, 298 insertions(+), 163 deletions(-) diff --git a/examples/cube/src/main.rs b/examples/cube/src/main.rs index 23d4e94f..9e9feeeb 100644 --- a/examples/cube/src/main.rs +++ b/examples/cube/src/main.rs @@ -107,22 +107,19 @@ unsafe fn psp_main_inner() { sys::sceGuInit(); - sys::sceGuStart( - GuContextType::Direct, - &mut LIST.0 as *mut [u32; 0x40000] as *mut _, - ); + sys::sceGuStart(GuContextType::Direct, &mut LIST.0 as *mut _); sys::sceGuDrawBuffer( DisplayPixelFormat::Psm8888, - fbp0.as_mut_ptr_from_zero() as _, + fbp0.as_mut_ptr_from_zero().cast(), BUF_WIDTH as i32, ); sys::sceGuDispBuffer( SCREEN_WIDTH as i32, SCREEN_HEIGHT as i32, - fbp1.as_mut_ptr_from_zero() as _, + fbp1.as_mut_ptr_from_zero().cast(), BUF_WIDTH as i32, ); - sys::sceGuDepthBuffer(zbp.as_mut_ptr_from_zero() as _, BUF_WIDTH as i32); + sys::sceGuDepthBuffer(zbp.as_mut_ptr_from_zero().cast(), BUF_WIDTH as i32); sys::sceGuOffset(2048 - (SCREEN_WIDTH / 2), 2048 - (SCREEN_HEIGHT / 2)); sys::sceGuViewport(2048, 2048, SCREEN_WIDTH as i32, SCREEN_HEIGHT as i32); sys::sceGuDepthRange(65535, 0); @@ -188,13 +185,7 @@ unsafe fn psp_main_inner() { // setup texture sys::sceGuTexMode(TexturePixelFormat::Psm8888, 0, 0, 0); - sys::sceGuTexImage( - MipmapLevel::None, - 128, - 128, - 128, - &FERRIS as *const _ as *const _, - ); + sys::sceGuTexImage(MipmapLevel::None, 128, 128, 128, &FERRIS as *const _); sys::sceGuTexFunc(TextureEffect::Replace, TextureColorComponent::Rgb); sys::sceGuTexFilter(TextureFilter::Linear, TextureFilter::Linear); sys::sceGuTexScale(1.0, 1.0); @@ -207,7 +198,7 @@ unsafe fn psp_main_inner() { VertexType::TEXTURE_32BITF | VertexType::VERTEX_32BITF | VertexType::TRANSFORM_3D, 12 * 3, ptr::null_mut(), - &VERTICES as *const Align16<_> as *const _, + &VERTICES as *const _, ); sys::sceGuFinish(); diff --git a/psp/src/lib.rs b/psp/src/lib.rs index 0b0236e5..4e2b23ac 100644 --- a/psp/src/lib.rs +++ b/psp/src/lib.rs @@ -8,45 +8,59 @@ const_loop, const_if_match, c_variadic, - lang_items, + lang_items )] - // For unwinding support #![feature(std_internals, panic_info_message, panic_internals, c_unwind)] #![cfg_attr(not(feature = "stub-only"), feature(panic_unwind))] - // For the `const_generics` feature. #![allow(incomplete_features)] - #![cfg_attr(not(feature = "std"), no_std)] +#![cfg_attr(not(feature = "stub-only"), feature(strict_provenance))] + +#![cfg_attr(not(feature = "stub-only"), feature(strict_provenance))] -#[macro_use] extern crate paste; -#[cfg(not(feature = "stub-only"))] extern crate alloc; -#[cfg(not(feature = "stub-only"))] extern crate panic_unwind; +#[macro_use] +extern crate paste; +#[cfg(not(feature = "stub-only"))] +extern crate alloc; +#[cfg(not(feature = "stub-only"))] +extern crate panic_unwind; #[macro_use] #[doc(hidden)] #[cfg(not(feature = "stub-only"))] pub mod debug; -#[macro_use] mod vfpu; +#[macro_use] +mod vfpu; mod eabi; pub mod math; pub mod sys; -#[cfg(not(feature = "stub-only"))] pub mod test_runner; -#[cfg(not(feature = "stub-only"))] pub mod vram_alloc; +#[cfg(not(feature = "stub-only"))] +pub mod test_runner; +#[cfg(not(feature = "stub-only"))] +pub mod vram_alloc; -#[cfg(not(feature = "stub-only"))] mod alloc_impl; -#[cfg(not(feature = "stub-only"))] pub mod panic; +#[cfg(not(feature = "stub-only"))] +mod alloc_impl; +#[cfg(not(feature = "stub-only"))] +pub mod panic; -#[cfg(not(feature = "stub-only"))] mod screenshot; -#[cfg(not(feature = "stub-only"))] pub use screenshot::*; +#[cfg(not(feature = "stub-only"))] +mod screenshot; +#[cfg(not(feature = "stub-only"))] +pub use screenshot::*; -#[cfg(not(feature = "stub-only"))] mod benchmark; -#[cfg(not(feature = "stub-only"))] pub use benchmark::*; +#[cfg(not(feature = "stub-only"))] +mod benchmark; +#[cfg(not(feature = "stub-only"))] +pub use benchmark::*; -#[cfg(not(feature = "stub-only"))] mod constants; -#[cfg(not(feature = "stub-only"))] pub use constants::*; +#[cfg(not(feature = "stub-only"))] +mod constants; +#[cfg(not(feature = "stub-only"))] +pub use constants::*; #[doc(hidden)] pub use unstringify::unstringify; @@ -54,11 +68,15 @@ pub use unstringify::unstringify; #[cfg(not(feature = "std"))] #[cfg(feature = "stub-only")] #[panic_handler] -fn panic(_: &core::panic::PanicInfo) -> ! { loop {} } +fn panic(_: &core::panic::PanicInfo) -> ! { + loop {} +} #[cfg(not(feature = "std"))] #[no_mangle] -extern "C" fn __rust_foreign_exception() -> ! { loop {} } +extern "C" fn __rust_foreign_exception() -> ! { + loop {} +} #[cfg(feature = "std")] pub use std::panic::catch_unwind; @@ -66,7 +84,7 @@ pub use std::panic::catch_unwind; #[cfg(all(not(feature = "std"), not(feature = "stub-only")))] pub use panic::catch_unwind; -#[cfg(feature="embedded-graphics")] +#[cfg(feature = "embedded-graphics")] pub mod embedded_graphics; #[repr(align(16))] @@ -112,8 +130,8 @@ macro_rules! module { #[no_mangle] #[link_section = ".rodata.sceModuleInfo"] #[used] - static MODULE_INFO: $crate::Align16<$crate::sys::SceModuleInfo> = $crate::Align16( - $crate::sys::SceModuleInfo { + static MODULE_INFO: $crate::Align16<$crate::sys::SceModuleInfo> = + $crate::Align16($crate::sys::SceModuleInfo { mod_attribute: 0, mod_version: [$version_major, $version_minor], mod_name: $crate::sys::SceModuleInfo::name($name), @@ -123,10 +141,9 @@ macro_rules! module { stub_end: unsafe { &__lib_stub_bottom }, ent_top: unsafe { &__lib_ent_top }, ent_end: unsafe { &__lib_ent_bottom }, - } - ); + }); - extern { + extern "C" { static _gp: u8; static __lib_ent_bottom: u8; static __lib_ent_top: u8; @@ -151,20 +168,21 @@ macro_rules! module { #[no_mangle] #[link_section = ".rodata.sceResident"] #[used] - static LIB_ENT_TABLE: $crate::sys::SceLibraryEntryTable = $crate::sys::SceLibraryEntryTable { - module_start_nid: 0xd632acdb, // module_start - module_info_nid: 0xf01d73a7, // SceModuleInfo - module_start: module_start, - module_info: &MODULE_INFO.0, - }; + static LIB_ENT_TABLE: $crate::sys::SceLibraryEntryTable = + $crate::sys::SceLibraryEntryTable { + module_start_nid: 0xd632acdb, // module_start + module_info_nid: 0xf01d73a7, // SceModuleInfo + module_start: module_start, + module_info: &MODULE_INFO.0, + }; #[no_mangle] extern "C" fn module_start(_argc: isize, _argv: *const *const u8) -> isize { - use $crate::sys::ThreadAttributes; use core::ffi::c_void; + use $crate::sys::ThreadAttributes; unsafe { - extern fn main_thread(_argc: usize, _argv: *mut c_void) -> i32 { + extern "C" fn main_thread(_argc: usize, _argv: *mut c_void) -> i32 { // TODO: Maybe print any error to debug screen? let _ = $crate::catch_unwind(|| { super::psp_main(); @@ -190,7 +208,7 @@ macro_rules! module { 0 } } - } + }; } /// Enable the home button. @@ -198,12 +216,12 @@ macro_rules! module { /// This API does not have destructor support yet. You can manually setup an /// exit callback if you need this, see the source code of this function. pub fn enable_home_button() { - use core::{ptr, ffi::c_void}; + use core::{ffi::c_void, ptr}; use sys::ThreadAttributes; unsafe { - unsafe extern fn exit_thread(_args: usize, _argp: *mut c_void) -> i32 { - unsafe extern fn exit_callback(_arg1: i32, _arg2: i32, _arg: *mut c_void) -> i32 { + unsafe extern "C" fn exit_thread(_args: usize, _argp: *mut c_void) -> i32 { + unsafe extern "C" fn exit_callback(_arg1: i32, _arg2: i32, _arg: *mut c_void) -> i32 { sys::sceKernelExitGame(); 0 } diff --git a/psp/src/vram_alloc.rs b/psp/src/vram_alloc.rs index 3fdfe7e3..95895289 100644 --- a/psp/src/vram_alloc.rs +++ b/psp/src/vram_alloc.rs @@ -1,9 +1,8 @@ +use core::sync::atomic::AtomicPtr; +use core::{alloc, mem, ptr}; + use crate::sys::TexturePixelFormat; use crate::sys::{sceGeEdramGetAddr, sceGeEdramGetSize}; -use core::marker::PhantomData; -use core::mem::size_of; -use core::ptr::null_mut; -use core::sync::atomic::{AtomicU32, Ordering}; pub type VramAllocator = SimpleVramAllocator; @@ -16,68 +15,140 @@ impl core::fmt::Display for VramAllocatorInUseError { } } +#[cfg(feature = "std")] +impl std::error::Error for VramAllocatorInUseError {} + mod vram_allocator_singleton { - use super::{SimpleVramAllocator, VramAllocator, VramAllocatorInUseError}; - use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; + use super::{ + total_vram_size, vram_base_ptr, SimpleVramAllocator, VramAllocError, + VramAllocatorInUseError, VramBox, + }; + use core::{ + alloc, + ptr::{self, NonNull}, + sync::atomic::{self, AtomicBool, AtomicPtr, AtomicUsize}, + }; - static VRAM_ALLOCATOR_IS_TAKEN: AtomicBool = AtomicBool::new(false); + /// Indicates VRAM ownership is free for anyone to acquire + static VRAM_OWNERSHIP: AtomicBool = AtomicBool::new(true); - impl VramAllocator { - pub fn take() -> Result { - if !VRAM_ALLOCATOR_IS_TAKEN.swap(true, Ordering::Relaxed) { - // new and empty, since old one droped and cannot have any references to it - Ok(VramAllocator { - offset: AtomicU32::new(0), + impl SimpleVramAllocator { + /// # Safety + /// + /// No other code should assume it can borrow or access VRAM. + /// Allocator assumes ownership over VRAM (or at least + /// over memory allocated by it). + pub unsafe fn take() -> Result { + let direct_base = vram_base_ptr(); + VRAM_OWNERSHIP + .fetch_update(atomic::Ordering::Acquire, atomic::Ordering::Relaxed, |t| { + (!t).then(|| false) }) - } else { - Err(VramAllocatorInUseError) - } + .map_err(|_| VramAllocatorInUseError)?; + + // fresh new allocator, there should be no active allocations + // to vram at this point + Ok(SimpleVramAllocator { + vram_direct_base: direct_base, + cursor: direct_base, + vram_size: total_vram_size(), + }) } - } - impl Drop for SimpleVramAllocator { - fn drop(&mut self) { - VRAM_ALLOCATOR_IS_TAKEN.store(false, Ordering::Relaxed) + /// Frees all previously allocated VRAM. + pub fn reset(&mut self) { + *self.cursor.get_mut() = self.vram_direct_base.get(); } - } -} -pub struct VramMemChunk<'a> { - start: u32, - len: u32, - // Needed since VramMemChunk has a lifetime, but doesn't contain references - vram: PhantomData<&'a mut ()>, -} + // TODO: handle alignment + /// Allocates `size` bytes TODO + /// + /// The returned VRAM chunk has the same lifetime as the + /// `SimpleVramAllocator` borrow (i.e. `&self`) that allocated it. + pub fn alloc_box(&self, value: T) -> Result, VramAllocError> { + // SAFETY: Creating a (unique) mutable VRAM byte slice, see + // [`VramAllocator::take`] safety section. + Ok(VramBox { + inner: unsafe { + core::slice::from_raw_parts_mut(vram_base_ptr().add(old_offset), size) + }, + }) + } -impl VramMemChunk<'_> { - fn new_(start: u32, len: u32) -> Self { - Self { - start, - len, - vram: PhantomData, + pub fn alloc_boxed_slice( + &self, + iter: I, + ) -> Result, VramAllocError> + where + I: Iterator + ExactSizeIterator, + { + self.alloc(alloc::Layout::new::()); } - } - pub fn as_mut_ptr_from_zero(&self) -> *mut u8 { - unsafe { vram_start_addr_zero().add(self.start as usize) } - } + /// # Safety + /// + /// Returned memory blocks point to valid memory and retain + /// their validity until the allocator is dropped or + /// [`SimpleVramAllocator::free_all`] is called. + pub fn allocate(&self, layout: alloc::Layout) -> Result, VramAllocError> { + // Atomically bump a cursor, no order required + let mut ptr = NonNull::dangling(); + + // ZSTs get dangling pointers + if layout.size() == 0 { + return Ok(ptr); + } + + let old = self + .cursor + .fetch_update( + atomic::Ordering::Relaxed, + atomic::Ordering::Relaxed, + |mut old| { + let offset = old.align_offset(layout.align()); - pub fn as_mut_ptr_direct_to_vram(&self) -> *mut u8 { - unsafe { vram_start_addr_direct().add(self.start as usize) } + // SAFETY: `cursor` and `vram_direct_base` fields + // come from the same place pointer + let spare_capacity = unsafe { + self.vram_direct_base + .as_ptr() + .add(self.vram_size) + .offset_from(old) as usize + }; + + offset + <= (spare_capacity as usize) + .checked_sub(layout.size())? + .then(|| { + // SAFETY: performed in-bounds check above + ptr = unsafe { old.add(offset) }; + ptr.add(layout.size()) + }) + }, + ) + .map_err(|_| VramAllocError)?; + + Ok(ptr) + } } - pub fn len(&self) -> u32 { - self.len + impl Drop for SimpleVramAllocator { + fn drop(&mut self) { + // all mem chunks at this point are no longer used, releasing + // vram ownership + VRAM_OWNERSHIP.store(true, atomic::Ordering::Release); + } } } // A dead-simple VRAM bump allocator. -// There could be only one value of this type -// WARNING: should be instantiated only within [`vram_allocator_singleton`] private module -// TODO: remove Debug -#[derive(Debug)] +// There could be only one instance of this type +// WARNING: should be instantiated only within [`vram_allocator_singleton`] +// private module pub struct SimpleVramAllocator { - offset: AtomicU32, + vram_direct_base: ptr::NonNull, + cursor: AtomicPtr, + vram_size: usize, } #[derive(Copy, Clone, Debug)] @@ -89,89 +160,144 @@ impl core::fmt::Display for VramAllocError { } } +#[cfg(feature = "std")] +impl std::error::Error for VramAllocError {} + impl SimpleVramAllocator { - /// Frees all previously allocated VRAM chunks. - /// - /// This resets the allocator's counter, but does not change the contents of - /// VRAM. Since this method requires `&mut Self` and there are no other - /// `SimpleVramAllocator` values to be swapped with, it cannot overlap with any - /// previously allocated `VramMemChunk`s since they have the lifetime of the - /// `&Self` that allocated them. - pub fn free_all(&mut self) { - // Store is required to occure after all previous bumps and before any next bumps, so SeqCst - self.offset.store(0, Ordering::SeqCst); + pub fn alloc_texture_pixels( + &self, + width: u32, + height: u32, + psm: TexturePixelFormat, + ) -> Result, VramAllocError> { + let size = get_memory_size(width, height, psm); + self.alloc(size) } - // TODO: handle alignment - /// Allocates `size` bytes of VRAM - /// - /// The returned VRAM chunk has the same lifetime as the - /// `SimpleVramAllocator` borrow (i.e. `&self`) that allocated it. - pub fn alloc<'a>(&'a self, size: u32) -> Result, VramAllocError> { - // Atomically bump offset, no ordering required - let old_offset = self - .offset - .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |old| { - old.checked_add(size).filter(|new| *new <= self.total_mem()) - }) - .map_err(|_| VramAllocError)?; + /// Total capacity of `SimpleVramAllocator`, equals to total VRAM + fn capacity(&self) -> usize { + total_vram_size() + } +} + +pub struct VramBox<'a, T: ?Sized> { + inner: &'a mut mem::ManuallyDrop, +} - Ok(VramMemChunk::new_(old_offset, size)) +impl Drop for VramBox<'_, T> { + fn drop(&mut self) { + // SAFETY: `VramBox` assumes ownership over the inner value. + // Dropped value isn't accessable to anyone after, because + // it's in the `Drop::drop` implementation. + unsafe { mem::ManuallyDrop::drop(self.inner) } } +} - // TODO: ensure 16-bit alignment? - pub fn alloc_sized<'a, T: Sized>( - &'a self, - count: u32, - ) -> Result, VramAllocError> { - let size = size_of::() as u32; - self.alloc(count * size) +impl<'a, T: ?Sized> VramBox<'a, T> { + fn into_inner(boxed: VramBox<'a, T>) -> T { + // SAFETY: `VramBox` assumes ownership over the inner value. + // `boxed` isn't accessable to anyone after, because + // function call owns it and then forgets it to avoid a + // double free. + unsafe { mem::ManuallyDrop::take(boxed.inner) }; + mem::forget(boxed); } +} - pub fn alloc_texture_pixels<'a>( - &'a self, - width: u32, - height: u32, - psm: TexturePixelFormat, - ) -> Result, VramAllocError> { - let size = get_memory_size(width, height, psm); - self.alloc(size) +impl core::ops::Deref for VramBox<'_, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + self.inner } +} - // TODO: write, or write_volatile? - // TODO: panic instead of result? - // TODO: Keep track of the allocated chunk - // TODO: determine unsafety of this - pub unsafe fn move_to_vram(&mut self, obj: T) -> Result<&mut T, VramAllocError> { - let chunk = self.alloc_sized::(1)?; - let ptr = chunk.as_mut_ptr_direct_to_vram() as *mut T; - ptr.write(obj); - Ok(&mut *ptr) +impl core::ops::DerefMut for VramBox<'_, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + self.inner } +} - fn total_mem(&self) -> u32 { - total_vram_size() +// See GE_EDRAM_ADDRESS in src/sys/gu.rs for that offset being used. +// +/// Converion trait for vram pointers. +/// +/// **Direct** VRAM addresses start at 0x4000000, as returned by +/// sceGeEdramGetAddr. You can work with direct VRAM like it's regular +/// memory. +/// +/// On the other hand, **zero-based** VRAM adresses start at 0x0 instead. +/// These pointers are used with `sys::sceGu*` functions, but outside of +/// that you cannot do anything until you convert them back to a direct +/// VRAM pointer. +/// +/// # Non-volatile access to direct VRAM +/// +/// Writes to and reads from VRAM are not required to be volatile. +/// +/// Volatile reads and writes are guaranteed to not be reordered, +/// combined, or eliminated. This stands from requirement for memory +/// mapped io, as there can be side effects before individual reads or +/// after individual writes. +/// +/// Provenance of VRAM's direct pointer comes from `sceGeEdramGetAddr`, +/// which is actually a stub function generated via `global_asm!` macro, +/// and because of this the compiler cannot infer pointer proviance any +/// further. It is assumed that the proviance of direct VRAM pointers +/// may be accessable to any unreachable to the compiler code. This +/// means the compiler is obligated to actually perform write before +/// any call to undefined (or stub) function. +pub trait VramConvPtr { + fn vram_direct_into_zero_based(self) -> Self; + + fn vram_zero_based_into_direct(self) -> Self; +} + +impl VramConvPtr for *mut T { + fn vram_direct_into_zero_based(self) -> Self { + self.cast::() + .wrapping_sub(vram_base_ptr() as usize) + .cast::() + } + + fn vram_zero_based_into_direct(self) -> Self { + self.cast::() + .wrapping_add(vram_base_ptr() as usize) + .cast::() } } -fn total_vram_size() -> u32 { - unsafe { sceGeEdramGetSize() } +impl VramConvPtr for *const T { + fn vram_direct_into_zero_based(self) -> Self { + self.cast::() + .wrapping_sub(vram_base_ptr() as usize) + .cast::() + } + + fn vram_zero_based_into_direct(self) -> Self { + self.cast::() + .wrapping_add(vram_base_ptr() as usize) + .cast::() + } } -// NOTE: VRAM actually starts at 0x4000000, as returned by sceGeEdramGetAddr. -// The Gu functions take that into account, and start their pointer -// indices at 0. See GE_EDRAM_ADDRESS in gu.rs for that offset being used. -fn vram_start_addr_zero() -> *mut u8 { - null_mut() +/// A direct VRAM pointer +fn vram_base_ptr() -> ptr::NonNull { + // We assume the returned pointer is not null, panic if we are wrong. + // If you would like to eliminate (probably unused) code for this panic for this panic but this + ptr::NonNull::new(unsafe { sceGeEdramGetAddr() }) + .expect("`sceGeEdramGetAddr` returned a null pointer") } -fn vram_start_addr_direct() -> *mut u8 { - unsafe { sceGeEdramGetAddr() } +fn total_vram_size() -> usize { + unsafe { sceGeEdramGetSize() as usize } } -fn get_memory_size(width: u32, height: u32, psm: TexturePixelFormat) -> u32 { - match psm { - TexturePixelFormat::PsmT4 => (width * height) >> 1, +// TODO: Add checks for width and height values, or mark as unsafe +fn get_memory_size(width: u32, height: u32, psm: TexturePixelFormat) -> usize { + (match psm { + // Want to eliminate modulo? Solve the todo! + TexturePixelFormat::PsmT4 => (width * height) / 2 + (width * height) % 2, TexturePixelFormat::PsmT8 => width * height, TexturePixelFormat::Psm5650 @@ -182,5 +308,5 @@ fn get_memory_size(width: u32, height: u32, psm: TexturePixelFormat) -> u32 { TexturePixelFormat::Psm8888 | TexturePixelFormat::PsmT32 => 4 * width * height, _ => unimplemented!(), - } + }) as usize }