Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file added assets/images/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 9 additions & 0 deletions crates/processing_ffi/src/color.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use bevy::color::LinearRgba;

/// A sRGB (?) color
#[repr(C)]
#[derive(Debug, Clone, Copy)]
Expand All @@ -13,3 +15,10 @@ impl From<Color> for bevy::color::Color {
bevy::color::Color::srgba(color.r, color.g, color.b, color.a)
}
}

impl From<LinearRgba> for Color {
fn from(lin: LinearRgba) -> Self {
let srgb = bevy::color::Color::srgba(lin.red, lin.green, lin.blue, lin.alpha);
srgb.into()
}
}
2 changes: 1 addition & 1 deletion crates/processing_ffi/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{
panic,
};

use processing::prelude::error::ProcessingError;
pub(crate) use processing::prelude::error::ProcessingError;

thread_local! {
static LAST_ERROR: RefCell<Option<CString>> = const { RefCell::new(None) };
Expand Down
175 changes: 151 additions & 24 deletions crates/processing_ffi/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use bevy::prelude::Entity;
use bevy::{
prelude::Entity,
render::render_resource::{Extent3d, TextureFormat},
};
use processing::prelude::*;

use crate::color::Color;
Expand Down Expand Up @@ -26,43 +29,43 @@ pub extern "C" fn processing_init() {
/// - window_handle is a valid GLFW window pointer.
/// - This is called from the same thread as init.
#[unsafe(no_mangle)]
pub extern "C" fn processing_create_surface(
pub extern "C" fn processing_surface_create(
window_handle: u64,
display_handle: u64,
width: u32,
height: u32,
scale_factor: f32,
) -> u64 {
error::clear_error();
error::check(|| create_surface(window_handle, display_handle, width, height, scale_factor))
error::check(|| surface_create(window_handle, display_handle, width, height, scale_factor))
.map(|e| e.to_bits())
.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.
/// - Init and surface_create have been called.
/// - window_id is a valid ID returned from surface_create.
/// - This is called from the same thread as init.
#[unsafe(no_mangle)]
pub extern "C" fn processing_destroy_surface(window_id: u64) {
pub extern "C" fn processing_surface_destroy(window_id: u64) {
error::clear_error();
let window_entity = Entity::from_bits(window_id);
error::check(|| destroy_surface(window_entity));
error::check(|| surface_destroy(window_entity));
}

/// Update window size when resized.
///
/// SAFETY:
/// - Init and create_surface have been called.
/// - window_id is a valid ID returned from create_surface.
/// - Init and surface_create have been called.
/// - window_id is a valid ID returned from surface_create.
/// - This is called from the same thread as init.
#[unsafe(no_mangle)]
pub extern "C" fn processing_resize_surface(window_id: u64, width: u32, height: u32) {
pub extern "C" fn processing_surface_resize(window_id: u64, width: u32, height: u32) {
error::clear_error();
let window_entity = Entity::from_bits(window_id);
error::check(|| resize_surface(window_entity, width, height));
error::check(|| surface_resize(window_entity, width, height));
}

/// Set the background color for the given window.
Expand All @@ -73,7 +76,21 @@ pub extern "C" fn processing_resize_surface(window_id: u64, width: u32, height:
pub extern "C" fn processing_background_color(window_id: u64, color: Color) {
error::clear_error();
let window_entity = Entity::from_bits(window_id);
error::check(|| background_color(window_entity, color.into()));
error::check(|| record_command(window_entity, DrawCommand::BackgroundColor(color.into())));
}

/// Set the background image for the given window.
///
/// SAFETY:
/// - This is called from the same thread as init.
/// - image_id is a valid ID returned from processing_image_create.
/// - The image has been fully uploaded.
#[unsafe(no_mangle)]
pub extern "C" fn processing_background_image(window_id: u64, image_id: u64) {
error::clear_error();
let window_entity = Entity::from_bits(window_id);
let image_entity = Entity::from_bits(image_id);
error::check(|| record_command(window_entity, DrawCommand::BackgroundImage(image_entity)));
}

/// Begins the draw for the given window.
Expand Down Expand Up @@ -126,8 +143,8 @@ pub extern "C" fn processing_exit(exit_code: u8) {
/// Set the fill color.
///
/// SAFETY:
/// - Init and create_surface have been called.
/// - window_id is a valid ID returned from create_surface.
/// - Init and surface_create have been called.
/// - window_id is a valid ID returned from surface_create.
/// - This is called from the same thread as init.
#[unsafe(no_mangle)]
pub extern "C" fn processing_set_fill(window_id: u64, r: f32, g: f32, b: f32, a: f32) {
Expand All @@ -140,8 +157,8 @@ pub extern "C" fn processing_set_fill(window_id: u64, r: f32, g: f32, b: f32, a:
/// Set the stroke color.
///
/// SAFETY:
/// - Init and create_surface have been called.
/// - window_id is a valid ID returned from create_surface.
/// - Init and surface_create have been called.
/// - window_id is a valid ID returned from surface_create.
/// - This is called from the same thread as init.
#[unsafe(no_mangle)]
pub extern "C" fn processing_set_stroke_color(window_id: u64, r: f32, g: f32, b: f32, a: f32) {
Expand All @@ -154,8 +171,8 @@ pub extern "C" fn processing_set_stroke_color(window_id: u64, r: f32, g: f32, b:
/// Set the stroke weight.
///
/// SAFETY:
/// - Init and create_surface have been called.
/// - window_id is a valid ID returned from create_surface.
/// - Init and surface_create have been called.
/// - window_id is a valid ID returned from surface_create.
/// - This is called from the same thread as init.
#[unsafe(no_mangle)]
pub extern "C" fn processing_set_stroke_weight(window_id: u64, weight: f32) {
Expand All @@ -167,8 +184,8 @@ pub extern "C" fn processing_set_stroke_weight(window_id: u64, weight: f32) {
/// Disable fill for subsequent shapes.
///
/// SAFETY:
/// - Init and create_surface have been called.
/// - window_id is a valid ID returned from create_surface.
/// - Init and surface_create have been called.
/// - window_id is a valid ID returned from surface_create.
/// - This is called from the same thread as init.
#[unsafe(no_mangle)]
pub extern "C" fn processing_no_fill(window_id: u64) {
Expand All @@ -180,8 +197,8 @@ pub extern "C" fn processing_no_fill(window_id: u64) {
/// Disable stroke for subsequent shapes.
///
/// SAFETY:
/// - Init and create_surface have been called.
/// - window_id is a valid ID returned from create_surface.
/// - Init and surface_create have been called.
/// - window_id is a valid ID returned from surface_create.
/// - This is called from the same thread as init.
#[unsafe(no_mangle)]
pub extern "C" fn processing_no_stroke(window_id: u64) {
Expand All @@ -193,8 +210,8 @@ pub extern "C" fn processing_no_stroke(window_id: u64) {
/// Draw a rectangle.
///
/// SAFETY:
/// - Init and create_surface have been called.
/// - window_id is a valid ID returned from create_surface.
/// - Init and surface_create have been called.
/// - window_id is a valid ID returned from surface_create.
/// - This is called from the same thread as init.
#[unsafe(no_mangle)]
pub extern "C" fn processing_rect(
Expand Down Expand Up @@ -223,3 +240,113 @@ pub extern "C" fn processing_rect(
)
});
}

/// Create an image from raw pixel data.
///
/// SAFETY:
/// - Init has been called.
/// - data is a valid pointer to data_len bytes of RGBA pixel data.
/// - This is called from the same thread as init.
#[unsafe(no_mangle)]
pub extern "C" fn processing_image_create(
width: u32,
height: u32,
data: *const u8,
data_len: usize,
) -> u64 {
error::clear_error();
// SAFETY: Caller must ensure that `data` is valid for `data_len` bytes.
let data = unsafe { std::slice::from_raw_parts(data, data_len) };
error::check(|| {
let size = Extent3d {
width,
height,
depth_or_array_layers: 1,
};
image_create(size, data.to_vec(), TextureFormat::Rgba8UnormSrgb)
})
.map(|entity| entity.to_bits())
.unwrap_or(0)
}

/// Load an image from a file path.
///
/// SAFETY:
/// - Init has been called.
/// - path is a valid null-terminated C string.
/// - This is called from the same thread as init.
///
/// Note: This function is currently synchronous but Bevy's asset loading is async.
/// The image may not be immediately available. This needs to be improved.
#[unsafe(no_mangle)]
pub extern "C" fn processing_image_load(path: *const std::ffi::c_char) -> u64 {
error::clear_error();

// SAFETY: Caller guarantees path is a valid C string
let c_str = unsafe { std::ffi::CStr::from_ptr(path) };
let path_str = match c_str.to_str() {
Ok(s) => s,
Err(_) => {
error::set_error("Invalid UTF-8 in image path");
return 0;
}
};

error::check(|| image_load(path_str))
.map(|entity| entity.to_bits())
.unwrap_or(0)
}

#[unsafe(no_mangle)]
pub extern "C" fn processing_image_resize(image_id: u64, new_width: u32, new_height: u32) {
error::clear_error();
let image_entity = Entity::from_bits(image_id);
let new_size = Extent3d {
width: new_width,
height: new_height,
depth_or_array_layers: 1,
};
error::check(|| image_resize(image_entity, new_size));
}

/// Load pixels from an image into a caller-provided buffer.
///
/// SAFETY:
/// - Init and image_create have been called.
/// - image_id is a valid ID returned from image_create.
/// - buffer is a valid pointer to at least buffer_len Color elements.
/// - buffer_len must equal width * height of the image.
/// - This is called from the same thread as init.
#[unsafe(no_mangle)]
pub extern "C" fn processing_image_load_pixels(
image_id: u64,
buffer: *mut Color,
buffer_len: usize,
) {
error::clear_error();
let image_entity = Entity::from_bits(image_id);
error::check(|| {
let colors = image_load_pixels(image_entity)?;

// Validate buffer size
if colors.len() != buffer_len {
let error_msg = format!(
"Buffer size mismatch: expected {}, got {}",
colors.len(),
buffer_len
);
error::set_error(&error_msg);
return Err(error::ProcessingError::InvalidArgument(error_msg));
}

// SAFETY: Caller guarantees buffer is valid for buffer_len elements
unsafe {
let buffer_slice = std::slice::from_raw_parts_mut(buffer, buffer_len);
for (i, color) in colors.iter().enumerate() {
buffer_slice[i] = Color::from(*color);
}
}

Ok(())
});
}
2 changes: 2 additions & 0 deletions crates/processing_render/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ raw-window-handle = "0.6"
thiserror = "2"
tracing = "0.1"
tracing-subscriber = "0.3"
half = "2.7"
crossbeam-channel = "0.5"

[target.'cfg(target_os = "macos")'.dependencies]
objc2 = { version = "0.6", default-features = false }
Expand Down
6 changes: 6 additions & 0 deletions crates/processing_render/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,10 @@ pub enum ProcessingError {
HandleError(#[from] raw_window_handle::HandleError),
#[error("Invalid window handle provided")]
InvalidWindowHandle,
#[error("Image not found")]
ImageNotFound,
#[error("Unsupported texture format")]
UnsupportedTextureFormat,
#[error("Invalid argument: {0}")]
InvalidArgument(String),
}
Loading
Loading