diff --git a/crates/molotov-core/src/graphics/c_api.rs b/crates/molotov-core/src/graphics/c_api.rs index fd73328..716185e 100644 --- a/crates/molotov-core/src/graphics/c_api.rs +++ b/crates/molotov-core/src/graphics/c_api.rs @@ -2,27 +2,61 @@ #![allow(non_snake_case)] use super::surface::GraphicsSurface; use super::types::{ANativeWindow_Buffer, ARect, WindowFormat}; +use std::collections::HashMap; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, RwLock}; + +static NEXT_WINDOW_ID: AtomicUsize = AtomicUsize::new(2001); + +lazy_static::lazy_static! { + static ref WINDOW_TABLE: RwLock>> = RwLock::new(HashMap::new()); +} + +pub fn register_window_surface(surface: Arc) -> *mut () { + let id = NEXT_WINDOW_ID.fetch_add(1, Ordering::Relaxed); + if let Ok(mut table) = WINDOW_TABLE.write() { + table.insert(id, surface); + id as *mut () + } else { + std::ptr::null_mut() + } +} + +pub fn get_window_surface(window: *mut ()) -> Option> { + let id = window as usize; + if let Ok(table) = WINDOW_TABLE.read() { + table.get(&id).cloned() + } else { + None + } +} #[no_mangle] pub unsafe extern "C" fn ANativeWindow_acquire(window: *mut ()) { - if let Some(s) = (window as *mut GraphicsSurface).as_ref() { + if let Some(s) = get_window_surface(window) { s.acquire(); } } #[no_mangle] pub unsafe extern "C" fn ANativeWindow_release(window: *mut ()) { - if !window.is_null() { - let ptr = window as *mut GraphicsSurface; - if (*ptr).release() == 0 { - drop(Box::from_raw(ptr)); + let id = window as usize; + let should_remove = if let Some(s) = get_window_surface(window) { + s.release() == 0 + } else { + false + }; + + if should_remove { + if let Ok(mut table) = WINDOW_TABLE.write() { + table.remove(&id); } } } #[no_mangle] pub unsafe extern "C" fn ANativeWindow_getWidth(window: *mut ()) -> i32 { - if let Some(s) = (window as *mut GraphicsSurface).as_ref() { + if let Some(s) = get_window_surface(window) { s.width() } else { 0 @@ -31,7 +65,7 @@ pub unsafe extern "C" fn ANativeWindow_getWidth(window: *mut ()) -> i32 { #[no_mangle] pub unsafe extern "C" fn ANativeWindow_getHeight(window: *mut ()) -> i32 { - if let Some(s) = (window as *mut GraphicsSurface).as_ref() { + if let Some(s) = get_window_surface(window) { s.height() } else { 0 @@ -40,7 +74,7 @@ pub unsafe extern "C" fn ANativeWindow_getHeight(window: *mut ()) -> i32 { #[no_mangle] pub unsafe extern "C" fn ANativeWindow_getFormat(window: *mut ()) -> i32 { - if let Some(s) = (window as *mut GraphicsSurface).as_ref() { + if let Some(s) = get_window_surface(window) { s.format() as i32 } else { 0 @@ -54,10 +88,14 @@ pub unsafe extern "C" fn ANativeWindow_setBuffersGeometry( height: i32, format: i32, ) -> i32 { - if let Some(s) = (window as *mut GraphicsSurface).as_mut() { - let fmt = WindowFormat::from_i32(format).unwrap_or(WindowFormat::Rgba8888); - s.set_geometry(width, height, fmt); - 0 + let fmt = match WindowFormat::from_i32(format) { + Some(f) => f, + None if format == 0 => WindowFormat::Rgba8888, + None => return -libc::EINVAL, + }; + + if let Some(s) = get_window_surface(window) { + s.set_geometry(width, height, fmt) } else { -libc::EINVAL } @@ -69,7 +107,7 @@ pub unsafe extern "C" fn ANativeWindow_lock( outBuffer: *mut ANativeWindow_Buffer, inOutDirtyBounds: *mut ARect, ) -> i32 { - if let Some(s) = (window as *mut GraphicsSurface).as_mut() { + if let Some(s) = get_window_surface(window) { s.lock(outBuffer, inOutDirtyBounds) } else { -libc::EINVAL @@ -78,7 +116,7 @@ pub unsafe extern "C" fn ANativeWindow_lock( #[no_mangle] pub unsafe extern "C" fn ANativeWindow_unlockAndPost(window: *mut ()) -> i32 { - if let Some(s) = (window as *mut GraphicsSurface).as_mut() { + if let Some(s) = get_window_surface(window) { s.unlock_and_post() } else { -libc::EINVAL diff --git a/crates/molotov-core/src/graphics/surface.rs b/crates/molotov-core/src/graphics/surface.rs index 5714930..d6af2dc 100644 --- a/crates/molotov-core/src/graphics/surface.rs +++ b/crates/molotov-core/src/graphics/surface.rs @@ -1,42 +1,54 @@ // SPDX-License-Identifier: GPL-3.0-or-later use super::types::{ANativeWindow_Buffer, ARect, WindowFormat}; use std::ffi::c_void; -use std::sync::atomic::{AtomicU32, Ordering}; +use std::sync::atomic::{fence, AtomicU32, Ordering}; +use std::sync::Mutex; -pub struct GraphicsSurface { +struct SurfaceInner { width: i32, height: i32, format: WindowFormat, back_buffer: Vec, front_buffer: Vec, is_locked: bool, +} + +pub struct GraphicsSurface { + inner: Mutex, ref_count: AtomicU32, } impl GraphicsSurface { pub fn new(width: i32, height: i32, format: WindowFormat) -> Self { - let size = (width as usize) * (height as usize) * format.bytes_per_pixel(); - Self { - width, - height, + let w = width.max(1); + let h = height.max(1); + let size = (w as usize) * (h as usize) * format.bytes_per_pixel(); + + let inner = SurfaceInner { + width: w, + height: h, format, back_buffer: vec![0u8; size], front_buffer: vec![0u8; size], is_locked: false, + }; + + Self { + inner: Mutex::new(inner), ref_count: AtomicU32::new(1), } } pub fn width(&self) -> i32 { - self.width + self.inner.lock().map(|i| i.width).unwrap_or(0) } pub fn height(&self) -> i32 { - self.height + self.inner.lock().map(|i| i.height).unwrap_or(0) } pub fn format(&self) -> WindowFormat { - self.format + self.inner.lock().map(|i| i.format).unwrap_or(WindowFormat::Rgba8888) } pub fn acquire(&self) -> u32 { @@ -44,52 +56,91 @@ impl GraphicsSurface { } pub fn release(&self) -> u32 { - self.ref_count.fetch_sub(1, Ordering::Release) - 1 + let prev = self.ref_count.fetch_sub(1, Ordering::Release); + if prev == 1 { + fence(Ordering::Acquire); + 0 + } else if prev == 0 { + 0 + } else { + prev - 1 + } } - pub fn set_geometry(&mut self, width: i32, height: i32, format: WindowFormat) { + pub fn set_geometry(&self, width: i32, height: i32, format: WindowFormat) -> i32 { + let mut inner = match self.inner.lock() { + Ok(g) => g, + Err(_) => return -libc::EINVAL, + }; + + if inner.is_locked { + return -libc::EBUSY; + } + if width > 0 && height > 0 { - self.width = width; - self.height = height; - self.format = format; + inner.width = width; + inner.height = height; + inner.format = format; let size = (width as usize) * (height as usize) * format.bytes_per_pixel(); - self.back_buffer.resize(size, 0); - self.front_buffer.resize(size, 0); + inner.back_buffer.resize(size, 0); + inner.front_buffer.resize(size, 0); + 0 + } else { + -libc::EINVAL } } pub unsafe fn lock( - &mut self, + &self, out_buffer: *mut ANativeWindow_Buffer, - _in_out_dirty: *mut ARect, + in_out_dirty: *mut ARect, ) -> i32 { if out_buffer.is_null() { return -libc::EINVAL; } - if self.is_locked { + + let mut inner = match self.inner.lock() { + Ok(g) => g, + Err(_) => return -libc::EINVAL, + }; + + if inner.is_locked { return -libc::EBUSY; } - self.is_locked = true; + inner.is_locked = true; - (*out_buffer).width = self.width; - (*out_buffer).height = self.height; - (*out_buffer).stride = self.width; - (*out_buffer).format = self.format as i32; - (*out_buffer).bits = self.back_buffer.as_mut_ptr() as *mut c_void; + // Clamp dirty bounds safely + if let Some(dirty) = in_out_dirty.as_mut() { + dirty.left = dirty.left.clamp(0, inner.width); + dirty.top = dirty.top.clamp(0, inner.height); + dirty.right = dirty.right.clamp(dirty.left, inner.width); + dirty.bottom = dirty.bottom.clamp(dirty.top, inner.height); + } + + (*out_buffer).width = inner.width; + (*out_buffer).height = inner.height; + (*out_buffer).stride = inner.width; + (*out_buffer).format = inner.format as i32; + (*out_buffer).bits = inner.back_buffer.as_mut_ptr() as *mut c_void; (*out_buffer).reserved = [0; 6]; 0 } - pub fn unlock_and_post(&mut self) -> i32 { - if !self.is_locked { + pub fn unlock_and_post(&self) -> i32 { + let mut guard = match self.inner.lock() { + Ok(g) => g, + Err(_) => return -libc::EINVAL, + }; + + if !guard.is_locked { return -libc::EINVAL; } - // Swap front and back buffer for presentation - std::mem::swap(&mut self.front_buffer, &mut self.back_buffer); - self.is_locked = false; + let inner = &mut *guard; + std::mem::swap(&mut inner.front_buffer, &mut inner.back_buffer); + inner.is_locked = false; 0 } } diff --git a/crates/molotov-core/src/graphics/window.rs b/crates/molotov-core/src/graphics/window.rs index 6051ff5..385a2ff 100644 --- a/crates/molotov-core/src/graphics/window.rs +++ b/crates/molotov-core/src/graphics/window.rs @@ -1,6 +1,9 @@ // SPDX-License-Identifier: GPL-3.0-or-later +use super::c_api::register_window_surface; use super::surface::GraphicsSurface; use super::types::WindowFormat; +use crate::jni::activity::ActivityRunner; +use std::sync::{Arc, Mutex}; #[derive(Debug, Clone)] pub struct WindowConfig { @@ -25,13 +28,32 @@ impl Default for WindowConfig { pub struct WindowManager { config: WindowConfig, - pub surface: GraphicsSurface, + pub surface: Arc, + pub window_handle: *mut (), + activity_runner: Option>>, } impl WindowManager { pub fn new(config: WindowConfig) -> Self { - let surface = GraphicsSurface::new(config.width, config.height, WindowFormat::Rgba8888); - Self { config, surface } + Self::new_with_activity(config, None) + } + + pub fn new_with_activity( + config: WindowConfig, + activity_runner: Option>>, + ) -> Self { + let surface = Arc::new(GraphicsSurface::new( + config.width, + config.height, + WindowFormat::Rgba8888, + )); + let window_handle = register_window_surface(surface.clone()); + Self { + config, + surface, + window_handle, + activity_runner, + } } pub fn width(&self) -> i32 { @@ -46,8 +68,15 @@ impl WindowManager { if width > 0 && height > 0 { self.config.width = width; self.config.height = height; - self.surface.set_geometry(width, height, self.surface.format()); + let _ = self.surface.set_geometry(width, height, self.surface.format()); tracing::info!("WindowManager: surface resized to {}x{}", width, height); + + // Notify activity runner if attached + if let Some(runner_arc) = &self.activity_runner { + if let Ok(mut runner) = runner_arc.lock() { + runner.trigger_window_resized(self.window_handle as *mut _); + } + } } } } diff --git a/crates/molotov-core/tests/anativewindow_c_api_test.rs b/crates/molotov-core/tests/anativewindow_c_api_test.rs index 394e83a..3a58950 100644 --- a/crates/molotov-core/tests/anativewindow_c_api_test.rs +++ b/crates/molotov-core/tests/anativewindow_c_api_test.rs @@ -2,18 +2,19 @@ use molotov_core::graphics::c_api::*; use molotov_core::graphics::surface::GraphicsSurface; use molotov_core::graphics::types::{ANativeWindow_Buffer, WindowFormat}; +use std::sync::Arc; #[test] fn test_anativewindow_c_api_lifecycle() { - let surface = Box::new(GraphicsSurface::new(640, 480, WindowFormat::Rgba8888)); - let handle = Box::into_raw(surface) as *mut (); + let surface = Arc::new(GraphicsSurface::new(640, 480, WindowFormat::Rgba8888)); + let handle = register_window_surface(surface); unsafe { assert_eq!(ANativeWindow_getWidth(handle), 640); assert_eq!(ANativeWindow_getHeight(handle), 480); assert_eq!(ANativeWindow_getFormat(handle), 1); - ANativeWindow_setBuffersGeometry(handle, 800, 600, 1); + assert_eq!(ANativeWindow_setBuffersGeometry(handle, 800, 600, 1), 0); assert_eq!(ANativeWindow_getWidth(handle), 800); assert_eq!(ANativeWindow_getHeight(handle), 600); diff --git a/crates/molotov-core/tests/graphics_fixes_test.rs b/crates/molotov-core/tests/graphics_fixes_test.rs new file mode 100644 index 0000000..89e8441 --- /dev/null +++ b/crates/molotov-core/tests/graphics_fixes_test.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: GPL-3.0-or-later +use molotov_core::graphics::c_api::*; +use molotov_core::graphics::surface::GraphicsSurface; +use molotov_core::graphics::types::{ANativeWindow_Buffer, ARect, WindowFormat}; +use molotov_core::graphics::window::{WindowConfig, WindowManager}; +use molotov_core::jni::activity::ActivityRunner; +use std::sync::{Arc, Mutex}; + +#[test] +fn test_dirty_rect_clamping_on_lock() { + let surface = Arc::new(GraphicsSurface::new(800, 600, WindowFormat::Rgba8888)); + let handle = register_window_surface(surface); + + let mut buf = std::mem::MaybeUninit::::uninit(); + let mut dirty = ARect { + left: -50, + top: -20, + right: 1500, + bottom: 2000, + }; + + unsafe { + assert_eq!(ANativeWindow_lock(handle, buf.as_mut_ptr(), &mut dirty), 0); + // Clamping check + assert_eq!(dirty.left, 0); + assert_eq!(dirty.top, 0); + assert_eq!(dirty.right, 800); + assert_eq!(dirty.bottom, 600); + + // While locked, setBuffersGeometry must return -EBUSY + assert_eq!(ANativeWindow_setBuffersGeometry(handle, 1024, 768, 1), -libc::EBUSY); + + assert_eq!(ANativeWindow_unlockAndPost(handle), 0); + ANativeWindow_release(handle); + } +} + +#[test] +fn test_window_manager_syncs_with_activity_runner() { + let runner = Arc::new(Mutex::new(ActivityRunner::new())); + let config = WindowConfig { + title: "Test".to_string(), + width: 1280, + height: 720, + fullscreen: false, + vulkan: true, + }; + + let mut manager = WindowManager::new_with_activity(config, Some(runner.clone())); + manager.handle_resize(1920, 1080); + + assert_eq!(manager.width(), 1920); + assert_eq!(manager.height(), 1080); +} diff --git a/crates/molotov-core/tests/graphics_surface_test.rs b/crates/molotov-core/tests/graphics_surface_test.rs index a86ecf8..f3c891f 100644 --- a/crates/molotov-core/tests/graphics_surface_test.rs +++ b/crates/molotov-core/tests/graphics_surface_test.rs @@ -3,12 +3,13 @@ use molotov_core::graphics::c_api::*; use molotov_core::graphics::surface::GraphicsSurface; use molotov_core::graphics::types::{ANativeWindow_Buffer, WindowFormat}; use molotov_core::jni::activity::ActivityRunner; +use std::sync::Arc; #[test] fn test_end_to_end_graphics_surface_and_activity_binding() { let mut runner = ActivityRunner::new(); - let surface = Box::new(GraphicsSurface::new(1280, 720, WindowFormat::Rgba8888)); - let window_handle = Box::into_raw(surface) as *mut (); + let surface = Arc::new(GraphicsSurface::new(1280, 720, WindowFormat::Rgba8888)); + let window_handle = register_window_surface(surface); unsafe { // 1. Notify activity that native window was created diff --git a/crates/molotov-core/tests/surface_buffer_test.rs b/crates/molotov-core/tests/surface_buffer_test.rs index d637393..e92b7c6 100644 --- a/crates/molotov-core/tests/surface_buffer_test.rs +++ b/crates/molotov-core/tests/surface_buffer_test.rs @@ -4,7 +4,7 @@ use molotov_core::graphics::types::WindowFormat; #[test] fn test_surface_lock_and_unlock_post() { - let mut surface = GraphicsSurface::new(800, 600, WindowFormat::Rgba8888); + let surface = GraphicsSurface::new(800, 600, WindowFormat::Rgba8888); assert_eq!(surface.width(), 800); assert_eq!(surface.height(), 600);