diff --git a/examples/cube/src/main.rs b/examples/cube/src/main.rs index 81b46df2..9e9feeeb 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,10 +107,19 @@ 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::sceGuDepthBuffer(zbp.as_mut_ptr_from_zero() as _, BUF_WIDTH as i32); + sys::sceGuStart(GuContextType::Direct, &mut LIST.0 as *mut _); + sys::sceGuDrawBuffer( + DisplayPixelFormat::Psm8888, + 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().cast(), + 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); @@ -128,7 +144,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 +167,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 +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); @@ -175,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/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/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 f343bdcb..95895289 100644 --- a/psp/src/vram_alloc.rs +++ b/psp/src/vram_alloc.rs @@ -1,154 +1,303 @@ +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}; - -type VramAllocator = SimpleVramAllocator; -#[derive(Debug)] -pub struct VramAllocatorInUseError {} +pub type VramAllocator = SimpleVramAllocator; -static mut VRAM_ALLOCATOR: VramAllocatorSingleton = VramAllocatorSingleton { - alloc: Some(VramAllocator::new()), -}; +#[derive(Copy, Clone, Debug)] +pub struct VramAllocatorInUseError; -pub fn get_vram_allocator() -> Result { - let opt_alloc = unsafe { VRAM_ALLOCATOR.get_vram_alloc() }; - opt_alloc.ok_or(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") + } } -pub struct VramAllocatorSingleton { - alloc: Option, -} +#[cfg(feature = "std")] +impl std::error::Error for VramAllocatorInUseError {} -impl VramAllocatorSingleton { - pub fn get_vram_alloc(&mut self) -> Option { - self.alloc.take() - } -} +mod vram_allocator_singleton { + use super::{ + total_vram_size, vram_base_ptr, SimpleVramAllocator, VramAllocError, + VramAllocatorInUseError, VramBox, + }; + use core::{ + alloc, + ptr::{self, NonNull}, + sync::atomic::{self, AtomicBool, AtomicPtr, AtomicUsize}, + }; -pub struct VramMemChunk<'a> { - start: u32, - len: u32, - // Needed since VramMemChunk has a lifetime, but doesn't contain references - vram: PhantomData<&'a mut ()>, -} + /// Indicates VRAM ownership is free for anyone to acquire + static VRAM_OWNERSHIP: AtomicBool = AtomicBool::new(true); + + 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) + }) + .map_err(|_| VramAllocatorInUseError)?; -impl VramMemChunk<'_> { - fn new(start: u32, len: u32) -> Self { - Self { - start, - len, - vram: PhantomData, + // 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(), + }) } - } - pub fn as_mut_ptr_from_zero(&self) -> *mut u8 { - unsafe { vram_start_addr_zero().add(self.start as usize) } - } + /// Frees all previously allocated VRAM. + pub fn reset(&mut self) { + *self.cursor.get_mut() = self.vram_direct_base.get(); + } + + // 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) + }, + }) + } + + pub fn alloc_boxed_slice( + &self, + iter: I, + ) -> Result, VramAllocError> + where + I: Iterator + ExactSizeIterator, + { + self.alloc(alloc::Layout::new::()); + } - pub fn as_mut_ptr_direct_to_vram(&self) -> *mut u8 { - unsafe { vram_start_addr_direct().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()); + + // 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. -// TODO: pin? -#[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)] +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") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for VramAllocError {} + impl SimpleVramAllocator { - const fn new() -> Self { - Self { - offset: AtomicU32::new(0), - } + 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) } - /// 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 - /// 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); + /// Total capacity of `SimpleVramAllocator`, equals to total VRAM + fn capacity(&self) -> usize { + total_vram_size() } +} - // TODO: return a Result instead of panicking - /// 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) -> 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!"); - } +pub struct VramBox<'a, T: ?Sized> { + inner: &'a mut mem::ManuallyDrop, +} - 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) -> VramMemChunk<'a> { - 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, - ) -> VramMemChunk<'a> { - 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: result instead of unwrap? - // 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); - let ptr = chunk.as_mut_ptr_direct_to_vram() as *mut T; - ptr.write(obj); - ptr.as_mut().unwrap() +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 @@ -159,5 +308,5 @@ fn get_memory_size(width: u32, height: u32, psm: TexturePixelFormat) -> u32 { TexturePixelFormat::Psm8888 | TexturePixelFormat::PsmT32 => 4 * width * height, _ => unimplemented!(), - } + }) as usize }