From 907ff0784be240961befcdbb2160e2564498bc38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?charlotte=20=F0=9F=8C=B8?= Date: Mon, 27 Oct 2025 15:56:12 -0500 Subject: [PATCH] Implement background. --- .../processing/webgpu/PGraphicsWebGPU.java | 19 +++++- core/src/processing/webgpu/PWebGPU.java | 28 ++++++++- libProcessing/Cargo.lock | 1 + libProcessing/Cargo.toml | 6 ++ libProcessing/ffi/Cargo.toml | 1 + libProcessing/ffi/src/color.rs | 14 +++++ libProcessing/ffi/src/lib.rs | 33 ++++++++++- libProcessing/renderer/Cargo.toml | 4 +- libProcessing/renderer/src/lib.rs | 59 +++++++++++++++---- 9 files changed, 147 insertions(+), 18 deletions(-) create mode 100644 libProcessing/ffi/src/color.rs diff --git a/core/src/processing/webgpu/PGraphicsWebGPU.java b/core/src/processing/webgpu/PGraphicsWebGPU.java index 78fb86046..914f73683 100644 --- a/core/src/processing/webgpu/PGraphicsWebGPU.java +++ b/core/src/processing/webgpu/PGraphicsWebGPU.java @@ -26,6 +26,12 @@ public void setSize(int w, int h) { } } + @Override + public void beginDraw() { + super.beginDraw(); + checkSettings(); + } + @Override public void endDraw() { super.endDraw(); @@ -35,7 +41,18 @@ public void endDraw() { @Override public void dispose() { super.dispose(); - + if (windowId != 0) { + PWebGPU.destroySurface(windowId); + windowId = 0; + } PWebGPU.exit(); } + + @Override + protected void backgroundImpl() { + if (windowId == 0) { + return; + } + PWebGPU.backgroundColor(windowId, backgroundR, backgroundG, backgroundB, backgroundA); + } } diff --git a/core/src/processing/webgpu/PWebGPU.java b/core/src/processing/webgpu/PWebGPU.java index b0e76b203..2fde44f71 100644 --- a/core/src/processing/webgpu/PWebGPU.java +++ b/core/src/processing/webgpu/PWebGPU.java @@ -2,10 +2,12 @@ import processing.core.NativeLibrary; +import java.lang.foreign.Arena; import java.lang.foreign.MemorySegment; import static java.lang.foreign.MemorySegment.NULL; import static processing.ffi.processing_h.*; +import processing.ffi.Color; /** * PWebGPU provides the native interface layer for libProcessing's WebGPU support. @@ -47,6 +49,16 @@ public static long createSurface(long windowHandle, int width, int height, float return windowId; } + /** + * Destroys a WebGPU surface. + * + * @param windowId The window ID returned from createSurface + */ + public static void destroySurface(long windowId) { + processing_destroy_surface(windowId); + checkError(); + } + /** * Updates a window's size. * @@ -55,7 +67,7 @@ public static long createSurface(long windowHandle, int width, int height, float * @param height New physical window height in pixels */ public static void windowResized(long windowId, int width, int height) { - processing_window_resized(windowId, width, height); + processing_resize_surface(windowId, width, height); checkError(); } @@ -75,6 +87,20 @@ public static void exit() { checkError(); } + public static void backgroundColor(long windowId, float r, float g, float b, float a) { + try (Arena arena = Arena.ofConfined()) { + MemorySegment color = Color.allocate(arena); + + Color.r(color, r); + Color.g(color, g); + Color.b(color, b); + Color.a(color, a); + + processing_background_color(windowId, color); + checkError(); + } + } + /** * Checks for errors from the native library and throws a PWebGPUException if an error occurred. */ diff --git a/libProcessing/Cargo.lock b/libProcessing/Cargo.lock index cd58b5248..585c43934 100644 --- a/libProcessing/Cargo.lock +++ b/libProcessing/Cargo.lock @@ -2302,6 +2302,7 @@ dependencies = [ name = "ffi" version = "0.1.0" dependencies = [ + "bevy", "cbindgen", "renderer", ] diff --git a/libProcessing/Cargo.toml b/libProcessing/Cargo.toml index 6684a907f..8b8a1ccc7 100644 --- a/libProcessing/Cargo.toml +++ b/libProcessing/Cargo.toml @@ -1,3 +1,9 @@ [workspace] resolver = "3" members = ["ffi","renderer"] + +[workspace.dependencies] +bevy = { version = "0.17", no-default-features = true, features = [ + "bevy_render", + "bevy_color", +] } \ No newline at end of file diff --git a/libProcessing/ffi/Cargo.toml b/libProcessing/ffi/Cargo.toml index 243f499d1..9ea189563 100644 --- a/libProcessing/ffi/Cargo.toml +++ b/libProcessing/ffi/Cargo.toml @@ -9,6 +9,7 @@ crate-type = ["cdylib"] [dependencies] renderer = { path = "../renderer" } +bevy = { workspace = true } [build-dependencies] cbindgen = "0.29" diff --git a/libProcessing/ffi/src/color.rs b/libProcessing/ffi/src/color.rs new file mode 100644 index 000000000..25735675d --- /dev/null +++ b/libProcessing/ffi/src/color.rs @@ -0,0 +1,14 @@ +/// A sRGB (?) color +#[repr(C)] +pub struct Color { + pub r: f32, + pub g: f32, + pub b: f32, + pub a: f32, +} + +impl From for bevy::color::Color { + fn from(color: Color) -> Self { + bevy::color::Color::srgba(color.r, color.g, color.b, color.a) + } +} \ No newline at end of file diff --git a/libProcessing/ffi/src/lib.rs b/libProcessing/ffi/src/lib.rs index 4059b404d..ff62d0361 100644 --- a/libProcessing/ffi/src/lib.rs +++ b/libProcessing/ffi/src/lib.rs @@ -1,3 +1,7 @@ +use crate::color::Color; +use bevy::prelude::Entity; + +mod color; mod error; /// Initialize libProcessing. @@ -31,6 +35,19 @@ pub extern "C" fn processing_create_surface( .unwrap_or(0) } +/// Destroy the surface associated with the given window ID. +/// +/// SAFETY: +/// - Init and create_surface have been called. +/// - window_id is a valid ID returned from create_surface. +/// - This is called from the same thread as init. +#[unsafe(no_mangle)] +pub extern "C" fn processing_destroy_surface(window_id: u64) { + error::clear_error(); + let window_entity = Entity::from_bits(window_id); + error::check(|| renderer::destroy_surface(window_entity)); +} + /// Update window size when resized. /// /// SAFETY: @@ -38,9 +55,21 @@ pub extern "C" fn processing_create_surface( /// - window_id is a valid ID returned from create_surface. /// - This is called from the same thread as init. #[unsafe(no_mangle)] -pub extern "C" fn processing_window_resized(window_id: u64, width: u32, height: u32) { +pub extern "C" fn processing_resize_surface(window_id: u64, width: u32, height: u32) { + error::clear_error(); + let window_entity = Entity::from_bits(window_id); + error::check(|| renderer::resize_surface(window_entity, width, height)); +} + +/// Set the background color for the given window. +/// +/// SAFETY: +/// - This is called from the same thread as init. +#[unsafe(no_mangle)] +pub extern "C" fn processing_background_color(window_id: u64, color: Color) { error::clear_error(); - error::check(|| renderer::window_resized(window_id, width, height)); + let window_entity = Entity::from_bits(window_id); + error::check(|| renderer::background_color(window_entity, color.into())); } /// Step the application forward. diff --git a/libProcessing/renderer/Cargo.toml b/libProcessing/renderer/Cargo.toml index 4dea5a618..8e78f41b1 100644 --- a/libProcessing/renderer/Cargo.toml +++ b/libProcessing/renderer/Cargo.toml @@ -6,9 +6,7 @@ edition = "2024" [dependencies] tracing = "0.1" tracing-subscriber = "0.3" -bevy = { version = "0.17", no-default-features = true, features = [ - "bevy_render" -] } +bevy = { workspace = true } thiserror = "2" raw-window-handle = "0.6" diff --git a/libProcessing/renderer/src/lib.rs b/libProcessing/renderer/src/lib.rs index ddbd5729c..2ccda0288 100644 --- a/libProcessing/renderer/src/lib.rs +++ b/libProcessing/renderer/src/lib.rs @@ -4,24 +4,27 @@ use crate::error::Result; use bevy::app::{App, AppExit}; use bevy::log::tracing_subscriber; use bevy::prelude::*; -use bevy::window::{ - RawHandleWrapper, Window, WindowResolution, WindowWrapper, -}; +use bevy::window::{RawHandleWrapper, Window, WindowRef, WindowResolution, WindowWrapper}; use raw_window_handle::{ DisplayHandle, HandleError, HasDisplayHandle, HasWindowHandle, RawDisplayHandle, RawWindowHandle, WindowHandle, }; use std::cell::RefCell; use std::num::NonZero; +use std::sync::atomic::AtomicU32; use std::sync::OnceLock; +use bevy::camera::RenderTarget; +use bevy::camera::visibility::RenderLayers; use tracing::debug; static IS_INIT: OnceLock<()> = OnceLock::new(); +static WINDOW_COUNT: AtomicU32 = AtomicU32::new(0); thread_local! { static APP: OnceLock> = OnceLock::default(); } + fn app(cb: impl FnOnce(&App) -> Result) -> Result { let res = APP.with(|app_lock| { let app = app_lock @@ -144,7 +147,7 @@ pub fn create_surface( let handle_wrapper = RawHandleWrapper::new(&window_wrapper)?; let entity_id = app_mut(|app| { - let entity = app + let mut window = app .world_mut() .spawn(( Window { @@ -153,22 +156,44 @@ pub fn create_surface( ..default() }, handle_wrapper, - )) - .id(); + )); + + let count = WINDOW_COUNT.fetch_add(1, std::sync::atomic::Ordering::SeqCst); + let render_layer = RenderLayers::none().with(count as usize); - // TODO: spawn a camera for this window with a render target of this window + let window_entity = window.id(); + window.with_children(|parent| { + parent.spawn(( + Camera3d::default(), + Camera { + target: RenderTarget::Window(WindowRef::Entity(window_entity)), + ..default() + }, + Projection::Orthographic(OrthographicProjection::default_3d()), + render_layer, + )); + }); - Ok(entity.to_bits()) + Ok(window_entity.to_bits()) })?; Ok(entity_id) } +pub fn destroy_surface(window_entity: Entity) -> Result<()>{ + app_mut(|app| { + if app.world_mut().get::(window_entity).is_some() { + app.world_mut().despawn(window_entity); + WINDOW_COUNT.fetch_sub(1, std::sync::atomic::Ordering::SeqCst); + } + Ok(()) + }) +} + /// Update window size when resized. -pub fn window_resized(window_id: u64, width: u32, height: u32) -> Result<()> { +pub fn resize_surface(window_entity: Entity, width: u32, height: u32) -> Result<()> { app_mut(|app| { - let entity = Entity::from_bits(window_id); - if let Some(mut window) = app.world_mut().get_mut::(entity) { + if let Some(mut window) = app.world_mut().get_mut::(window_entity) { window.resolution.set_physical_resolution(width, height); Ok(()) } else { @@ -240,6 +265,18 @@ pub fn exit(exit_code: u8) -> Result<()> { }) } +pub fn background_color(window_entity: Entity, color: Color) -> Result<()> { + app_mut(|app| { + let mut camera_query = app.world_mut().query::<(&mut Camera, &ChildOf)>(); + for (mut camera, parent) in camera_query.iter_mut(&mut app.world_mut()) { + if parent.parent() == window_entity { + camera.clear_color = ClearColorConfig::Custom(color); + } + } + Ok(()) + }) +} + fn setup_tracing() -> Result<()> { let subscriber = tracing_subscriber::FmtSubscriber::new(); tracing::subscriber::set_global_default(subscriber)?;