Skip to content

Commit 5d46799

Browse files
authored
Compute shader and buffer support (#154)
1 parent 02ffebd commit 5d46799

61 files changed

Lines changed: 5915 additions & 286 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Cargo.lock

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

Cargo.toml

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ too_many_arguments = "allow"
2424

2525
[workspace.dependencies]
2626
bevy = { git = "https://github.com/processing/bevy", branch = "main", features = ["file_watcher", "shader_format_wesl", "free_camera", "pan_camera"] }
27-
bevy_naga_reflect = { git = "https://github.com/tychedelia/bevy_naga_reflect" }
27+
bevy_naga_reflect = { path = "../../tychedelia/bevy_naga_reflect" }
2828
bevy_cuda = { git = "https://github.com/tychedelia/bevy_cuda" }
2929
naga = { version = "29", features = ["wgsl-in"] }
3030
wesl = { version = "0.3", default-features = false }
@@ -161,6 +161,54 @@ 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+
168+
[[example]]
169+
name = "particles_basic"
170+
path = "examples/particles_basic.rs"
171+
172+
[[example]]
173+
name = "particles_animated"
174+
path = "examples/particles_animated.rs"
175+
176+
[[example]]
177+
name = "particles_oriented"
178+
path = "examples/particles_oriented.rs"
179+
180+
[[example]]
181+
name = "particles_colored"
182+
path = "examples/particles_colored.rs"
183+
184+
[[example]]
185+
name = "particles_colored_pbr"
186+
path = "examples/particles_colored_pbr.rs"
187+
188+
[[example]]
189+
name = "particles_emit"
190+
path = "examples/particles_emit.rs"
191+
192+
[[example]]
193+
name = "particles_lifecycle"
194+
path = "examples/particles_lifecycle.rs"
195+
196+
[[example]]
197+
name = "particles_from_mesh"
198+
path = "examples/particles_from_mesh.rs"
199+
200+
[[example]]
201+
name = "particles_noise"
202+
path = "examples/particles_noise.rs"
203+
204+
[[example]]
205+
name = "particles_emit_gpu"
206+
path = "examples/particles_emit_gpu.rs"
207+
208+
[[example]]
209+
name = "particles_stress"
210+
path = "examples/particles_stress.rs"
211+
164212
[profile.wasm-release]
165213
inherits = "release"
166214
opt-level = "z"

crates/processing_core/src/error.rs

Lines changed: 14 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,16 @@ 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),
59+
#[error("Particles not found")]
60+
ParticlesNotFound,
4961
}

crates/processing_ffi/src/lib.rs

Lines changed: 176 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,172 @@ 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+
/// # Safety
1916+
/// - `out` must be valid for writes of `out_len` bytes (may be null if
1917+
/// `out_len == 0`, in which case this acts as a size query).
1918+
#[unsafe(no_mangle)]
1919+
pub unsafe extern "C" fn processing_buffer_read(buf_id: u64, out: *mut u8, out_len: u64) -> u64 {
1920+
error::clear_error();
1921+
let Some(data) = error::check(|| buffer_read(Entity::from_bits(buf_id))) else {
1922+
return 0;
1923+
};
1924+
let needed = data.len() as u64;
1925+
if needed <= out_len {
1926+
unsafe { std::ptr::copy_nonoverlapping(data.as_ptr(), out, data.len()) };
1927+
}
1928+
needed
1929+
}
1930+
1931+
#[unsafe(no_mangle)]
1932+
pub extern "C" fn processing_buffer_destroy(buf_id: u64) {
1933+
error::clear_error();
1934+
error::check(|| buffer_destroy(Entity::from_bits(buf_id)));
1935+
}
1936+
1937+
// Compute
1938+
1939+
#[unsafe(no_mangle)]
1940+
pub extern "C" fn processing_compute_create(shader_id: u64) -> u64 {
1941+
error::clear_error();
1942+
error::check(|| compute_create(Entity::from_bits(shader_id)))
1943+
.map(|e| e.to_bits())
1944+
.unwrap_or(0)
1945+
}
1946+
1947+
/// Set a float property on a compute shader.
1948+
///
1949+
/// # Safety
1950+
/// - `name` must be non-null
1951+
#[unsafe(no_mangle)]
1952+
pub unsafe extern "C" fn processing_compute_set_float(
1953+
compute_id: u64,
1954+
name: *const std::ffi::c_char,
1955+
value: f32,
1956+
) {
1957+
error::clear_error();
1958+
error::check(|| {
1959+
let name = unsafe { cstr_to_str(name) }?;
1960+
compute_set(
1961+
Entity::from_bits(compute_id),
1962+
name,
1963+
shader_value::ShaderValue::Float(value),
1964+
)
1965+
});
1966+
}
1967+
1968+
/// # Safety
1969+
/// `name` must be a valid null-terminated C string.
1970+
#[unsafe(no_mangle)]
1971+
pub unsafe extern "C" fn processing_compute_set_buffer(
1972+
compute_id: u64,
1973+
name: *const std::ffi::c_char,
1974+
buf_id: u64,
1975+
) {
1976+
error::clear_error();
1977+
error::check(|| {
1978+
let name = unsafe { cstr_to_str(name) }?;
1979+
compute_set(
1980+
Entity::from_bits(compute_id),
1981+
name,
1982+
shader_value::ShaderValue::Buffer(Entity::from_bits(buf_id)),
1983+
)
1984+
});
1985+
}
1986+
1987+
#[unsafe(no_mangle)]
1988+
pub extern "C" fn processing_compute_dispatch(compute_id: u64, x: u32, y: u32, z: u32) {
1989+
error::clear_error();
1990+
error::check(|| compute_dispatch(Entity::from_bits(compute_id), x, y, z));
1991+
}
1992+
1993+
#[unsafe(no_mangle)]
1994+
pub extern "C" fn processing_compute_destroy(compute_id: u64) {
1995+
error::clear_error();
1996+
error::check(|| compute_destroy(Entity::from_bits(compute_id)));
1997+
}
1998+
18271999
// Mouse buttons
18282000
pub const PROCESSING_MOUSE_LEFT: u8 = 0;
18292001
pub const PROCESSING_MOUSE_MIDDLE: u8 = 1;

crates/processing_pyo3/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ cuda = ["dep:processing_cuda", "processing_cuda/cuda", "processing/cuda"]
2121
[dependencies]
2222
pyo3 = { workspace = true, features = ["experimental-inspect", "multiple-pymethods"] }
2323
processing = { workspace = true }
24+
processing_render = { workspace = true }
2425
processing_webcam = { workspace = true, optional = true }
2526
processing_glfw = { workspace = true }
2627
bevy = { workspace = true, features = ["file_watcher"] }

0 commit comments

Comments
 (0)