Skip to content

Commit 1bf2725

Browse files
committed
Add compute shader and buffer API.
1 parent a9f0774 commit 1bf2725

23 files changed

Lines changed: 1395 additions & 105 deletions

File tree

Cargo.lock

Lines changed: 16 additions & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,10 @@ path = "examples/blend_modes.rs"
161161
name = "camera_controllers"
162162
path = "examples/camera_controllers.rs"
163163

164+
[[example]]
165+
name = "compute_readback"
166+
path = "examples/compute_readback.rs"
167+
164168
[profile.wasm-release]
165169
inherits = "release"
166170
opt-level = "z"

crates/processing_core/src/error.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ pub enum ProcessingError {
3232
TransformNotFound,
3333
#[error("Material not found")]
3434
MaterialNotFound,
35-
#[error("Unknown material property: {0}")]
36-
UnknownMaterialProperty(String),
35+
#[error("Unknown shader property: {0}")]
36+
UnknownShaderProperty(String),
3737
#[error("GLTF load error: {0}")]
3838
GltfLoadError(String),
3939
#[error("Webcam not connected")]
@@ -46,4 +46,14 @@ pub enum ProcessingError {
4646
MidiPortNotFound(usize),
4747
#[error("CUDA error: {0}")]
4848
CudaError(String),
49+
#[error("Compute shader not found")]
50+
ComputeNotFound,
51+
#[error("Buffer not found")]
52+
BufferNotFound,
53+
#[error("Buffer map error: {0}")]
54+
BufferMapError(String),
55+
#[error("Pipeline compile error: {0}")]
56+
PipelineCompileError(String),
57+
#[error("Pipeline not ready after {0} frames")]
58+
PipelineNotReady(u32),
4959
}

crates/processing_core/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ thread_local! {
1515

1616
pub fn app_mut<T>(cb: impl FnOnce(&mut App) -> error::Result<T>) -> error::Result<T> {
1717
let res = APP.with(|app_cell| {
18-
let mut app_borrow = app_cell.borrow_mut();
18+
let mut app_borrow = app_cell
19+
.try_borrow_mut()
20+
.map_err(|_| error::ProcessingError::AppAccess)?;
1921
let app = app_borrow
2022
.as_mut()
2123
.ok_or(error::ProcessingError::AppAccess)?;

crates/processing_ffi/src/lib.rs

Lines changed: 180 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,12 @@ use crate::color::Color;
1010
mod color;
1111
mod error;
1212

13+
unsafe fn cstr_to_str<'a>(ptr: *const std::ffi::c_char) -> Result<&'a str, ProcessingError> {
14+
unsafe { std::ffi::CStr::from_ptr(ptr) }
15+
.to_str()
16+
.map_err(|_| ProcessingError::InvalidArgument("non-UTF8 C string".to_string()))
17+
}
18+
1319
/// Initialize libProcessing.
1420
///
1521
/// SAFETY:
@@ -1776,12 +1782,12 @@ pub unsafe extern "C" fn processing_material_set_float(
17761782
value: f32,
17771783
) {
17781784
error::clear_error();
1779-
let name = unsafe { std::ffi::CStr::from_ptr(name) }.to_str().unwrap();
17801785
error::check(|| {
1786+
let name = unsafe { cstr_to_str(name) }?;
17811787
material_set(
17821788
Entity::from_bits(mat_id),
17831789
name,
1784-
material::MaterialValue::Float(value),
1790+
shader_value::ShaderValue::Float(value),
17851791
)
17861792
});
17871793
}
@@ -1800,12 +1806,12 @@ pub unsafe extern "C" fn processing_material_set_float4(
18001806
a: f32,
18011807
) {
18021808
error::clear_error();
1803-
let name = unsafe { std::ffi::CStr::from_ptr(name) }.to_str().unwrap();
18041809
error::check(|| {
1810+
let name = unsafe { cstr_to_str(name) }?;
18051811
material_set(
18061812
Entity::from_bits(mat_id),
18071813
name,
1808-
material::MaterialValue::Float4([r, g, b, a]),
1814+
shader_value::ShaderValue::Float4([r, g, b, a]),
18091815
)
18101816
});
18111817
}
@@ -1824,6 +1830,176 @@ pub extern "C" fn processing_material(window_id: u64, mat_id: u64) {
18241830
error::check(|| graphics_record_command(window_entity, DrawCommand::Material(mat_entity)));
18251831
}
18261832

1833+
// Shader
1834+
1835+
/// Create a shader from WGSL source.
1836+
///
1837+
/// # Safety
1838+
/// - `source` must be non-null
1839+
#[unsafe(no_mangle)]
1840+
pub unsafe extern "C" fn processing_shader_create(source: *const std::ffi::c_char) -> u64 {
1841+
error::clear_error();
1842+
error::check(|| {
1843+
let source = unsafe { cstr_to_str(source) }?;
1844+
shader_create(source)
1845+
})
1846+
.map(|e| e.to_bits())
1847+
.unwrap_or(0)
1848+
}
1849+
1850+
/// Load a shader from a file path.
1851+
///
1852+
/// # Safety
1853+
/// - `path` must be non-null
1854+
#[unsafe(no_mangle)]
1855+
pub unsafe extern "C" fn processing_shader_load(path: *const std::ffi::c_char) -> u64 {
1856+
error::clear_error();
1857+
error::check(|| {
1858+
let path = unsafe { cstr_to_str(path) }?;
1859+
shader_load(path)
1860+
})
1861+
.map(|e| e.to_bits())
1862+
.unwrap_or(0)
1863+
}
1864+
1865+
#[unsafe(no_mangle)]
1866+
pub extern "C" fn processing_shader_destroy(shader_id: u64) {
1867+
error::clear_error();
1868+
error::check(|| shader_destroy(Entity::from_bits(shader_id)));
1869+
}
1870+
1871+
// Buffer
1872+
1873+
#[unsafe(no_mangle)]
1874+
pub extern "C" fn processing_buffer_create(size: u64) -> u64 {
1875+
error::clear_error();
1876+
error::check(|| buffer_create(size))
1877+
.map(|e| e.to_bits())
1878+
.unwrap_or(0)
1879+
}
1880+
1881+
/// Create a buffer initialized with data.
1882+
///
1883+
/// # Safety
1884+
/// - `data` must point to `len` valid bytes
1885+
#[unsafe(no_mangle)]
1886+
pub unsafe extern "C" fn processing_buffer_create_with_data(data: *const u8, len: u64) -> u64 {
1887+
error::clear_error();
1888+
let bytes = unsafe { std::slice::from_raw_parts(data, len as usize) }.to_vec();
1889+
error::check(|| buffer_create_with_data(bytes))
1890+
.map(|e| e.to_bits())
1891+
.unwrap_or(0)
1892+
}
1893+
1894+
/// Write data to a buffer.
1895+
///
1896+
/// # Safety
1897+
/// - `data` must point to `len` valid bytes
1898+
#[unsafe(no_mangle)]
1899+
pub unsafe extern "C" fn processing_buffer_write(buf_id: u64, data: *const u8, len: u64) {
1900+
error::clear_error();
1901+
let bytes = unsafe { std::slice::from_raw_parts(data, len as usize) }.to_vec();
1902+
error::check(|| buffer_write(Entity::from_bits(buf_id), bytes));
1903+
}
1904+
1905+
/// Returns the byte length of a buffer, or 0 if the buffer does not exist
1906+
/// (in which case the error is set).
1907+
#[unsafe(no_mangle)]
1908+
pub extern "C" fn processing_buffer_size(buf_id: u64) -> u64 {
1909+
error::clear_error();
1910+
error::check(|| buffer_size(Entity::from_bits(buf_id))).unwrap_or(0)
1911+
}
1912+
1913+
/// Read buffer contents into a caller-provided buffer.
1914+
///
1915+
/// Returns the buffer's byte length. If the returned size is `<= out_len`, the
1916+
/// data has been written to `out`; otherwise `out` is left untouched and the
1917+
/// caller should reallocate and retry. Returns 0 if the buffer does not exist
1918+
/// or the GPU readback failed (in which case the error is set).
1919+
///
1920+
/// # Safety
1921+
/// - `out` must be valid for writes of `out_len` bytes (may be null if
1922+
/// `out_len == 0`, in which case this acts as a size query).
1923+
#[unsafe(no_mangle)]
1924+
pub unsafe extern "C" fn processing_buffer_read(buf_id: u64, out: *mut u8, out_len: u64) -> u64 {
1925+
error::clear_error();
1926+
let Some(data) = error::check(|| buffer_read(Entity::from_bits(buf_id))) else {
1927+
return 0;
1928+
};
1929+
let needed = data.len() as u64;
1930+
if needed <= out_len {
1931+
unsafe { std::ptr::copy_nonoverlapping(data.as_ptr(), out, data.len()) };
1932+
}
1933+
needed
1934+
}
1935+
1936+
#[unsafe(no_mangle)]
1937+
pub extern "C" fn processing_buffer_destroy(buf_id: u64) {
1938+
error::clear_error();
1939+
error::check(|| buffer_destroy(Entity::from_bits(buf_id)));
1940+
}
1941+
1942+
// Compute
1943+
1944+
#[unsafe(no_mangle)]
1945+
pub extern "C" fn processing_compute_create(shader_id: u64) -> u64 {
1946+
error::clear_error();
1947+
error::check(|| compute_create(Entity::from_bits(shader_id)))
1948+
.map(|e| e.to_bits())
1949+
.unwrap_or(0)
1950+
}
1951+
1952+
/// Set a float property on a compute shader.
1953+
///
1954+
/// # Safety
1955+
/// - `name` must be non-null
1956+
#[unsafe(no_mangle)]
1957+
pub unsafe extern "C" fn processing_compute_set_float(
1958+
compute_id: u64,
1959+
name: *const std::ffi::c_char,
1960+
value: f32,
1961+
) {
1962+
error::clear_error();
1963+
error::check(|| {
1964+
let name = unsafe { cstr_to_str(name) }?;
1965+
compute_set(
1966+
Entity::from_bits(compute_id),
1967+
name,
1968+
shader_value::ShaderValue::Float(value),
1969+
)
1970+
});
1971+
}
1972+
1973+
/// Set a buffer property on a compute shader.
1974+
#[unsafe(no_mangle)]
1975+
pub unsafe extern "C" fn processing_compute_set_buffer(
1976+
compute_id: u64,
1977+
name: *const std::ffi::c_char,
1978+
buf_id: u64,
1979+
) {
1980+
error::clear_error();
1981+
error::check(|| {
1982+
let name = unsafe { cstr_to_str(name) }?;
1983+
compute_set(
1984+
Entity::from_bits(compute_id),
1985+
name,
1986+
shader_value::ShaderValue::Buffer(Entity::from_bits(buf_id)),
1987+
)
1988+
});
1989+
}
1990+
1991+
#[unsafe(no_mangle)]
1992+
pub extern "C" fn processing_compute_dispatch(compute_id: u64, x: u32, y: u32, z: u32) {
1993+
error::clear_error();
1994+
error::check(|| compute_dispatch(Entity::from_bits(compute_id), x, y, z));
1995+
}
1996+
1997+
#[unsafe(no_mangle)]
1998+
pub extern "C" fn processing_compute_destroy(compute_id: u64) {
1999+
error::clear_error();
2000+
error::check(|| compute_destroy(Entity::from_bits(compute_id)));
2001+
}
2002+
18272003
// Mouse buttons
18282004
pub const PROCESSING_MOUSE_LEFT: u8 = 0;
18292005
pub const PROCESSING_MOUSE_MIDDLE: u8 = 1;

0 commit comments

Comments
 (0)