Skip to content
Open
Show file tree
Hide file tree
Changes from all 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