Skip to content

Commit 5ae5558

Browse files
committed
.
1 parent e4a0101 commit 5ae5558

20 files changed

Lines changed: 1399 additions & 218 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,18 @@ path = "examples/field_lifecycle.rs"
197197
name = "field_from_mesh"
198198
path = "examples/field_from_mesh.rs"
199199

200+
[[example]]
201+
name = "field_noise"
202+
path = "examples/field_noise.rs"
203+
204+
[[example]]
205+
name = "field_emit_gpu"
206+
path = "examples/field_emit_gpu.rs"
207+
208+
[[example]]
209+
name = "field_stress"
210+
path = "examples/field_stress.rs"
211+
200212
[profile.wasm-release]
201213
inherits = "release"
202214
opt-level = "z"

crates/processing_core/src/lib.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,22 @@ thread_local! {
1414
}
1515

1616
pub fn app_mut<T>(cb: impl FnOnce(&mut App) -> error::Result<T>) -> error::Result<T> {
17-
let res = APP.with(|app_cell| {
17+
// `try_with` rather than `with` so callers (especially `Drop`s running
18+
// during pyo3 module teardown) get a graceful error instead of a panic
19+
// when the thread-local has already been destroyed.
20+
let res = APP.try_with(|app_cell| {
1821
let mut app_borrow = app_cell
1922
.try_borrow_mut()
2023
.map_err(|_| error::ProcessingError::AppAccess)?;
2124
let app = app_borrow
2225
.as_mut()
2326
.ok_or(error::ProcessingError::AppAccess)?;
2427
cb(app)
25-
})?;
26-
Ok(res)
28+
});
29+
match res {
30+
Ok(inner) => inner,
31+
Err(_) => Err(error::ProcessingError::AppAccess),
32+
}
2733
}
2834

2935
pub fn is_already_init() -> error::Result<bool> {

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"] }
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from mewnala import *
2+
3+
field_obj = None
4+
sphere = None
5+
mat = None
6+
spin = None
7+
8+
SPIN_SHADER = """
9+
struct Params {
10+
dt: f32,
11+
}
12+
13+
@group(0) @binding(0) var<storage, read_write> position: array<f32>;
14+
@group(0) @binding(1) var<uniform> params: Params;
15+
16+
@compute @workgroup_size(64)
17+
fn main(@builtin(global_invocation_id) gid: vec3<u32>) {
18+
let i = gid.x;
19+
let count = arrayLength(&position) / 3u;
20+
if i >= count {
21+
return;
22+
}
23+
let cs = cos(params.dt);
24+
let sn = sin(params.dt);
25+
let x = position[i * 3u + 0u];
26+
let z = position[i * 3u + 2u];
27+
position[i * 3u + 0u] = x * cs - z * sn;
28+
position[i * 3u + 2u] = x * sn + z * cs;
29+
}
30+
"""
31+
32+
33+
def setup():
34+
global field_obj, sphere, mat, spin
35+
36+
size(900, 700)
37+
mode_3d()
38+
39+
create_directional_light((0.9, 0.85, 0.8), 300.0)
40+
41+
sphere = Geometry.sphere(0.25, 12, 8)
42+
43+
capacity = 1000
44+
positions = []
45+
for x in range(10):
46+
for y in range(10):
47+
for z in range(10):
48+
positions.extend([x - 4.5, y - 4.5, z - 4.5])
49+
50+
field_obj = Field(capacity=capacity, attributes=[Attribute.position()])
51+
pos_buf = field_obj.pbuffer(Attribute.position())
52+
pos_buf.write(positions)
53+
54+
mat = Material(roughness=0.4)
55+
spin = Compute(Shader(SPIN_SHADER))
56+
57+
58+
def draw():
59+
camera_position(0.0, 8.0, 25.0)
60+
camera_look_at(0.0, 0.0, 0.0)
61+
background(15, 15, 20)
62+
fill(230, 128, 75)
63+
64+
use_material(mat)
65+
draw_field(field_obj, sphere)
66+
67+
spin.set(dt=0.01)
68+
field_obj.apply(spin)
69+
70+
71+
run()
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
from mewnala import *
2+
3+
field_obj = None
4+
particle = None
5+
mat = None
6+
7+
8+
def setup():
9+
global field_obj, particle, mat
10+
11+
size(900, 700)
12+
mode_3d()
13+
14+
create_directional_light((0.95, 0.9, 0.85), 600.0)
15+
16+
# Source mesh whose vertices become particle positions; uvs come along for
17+
# free and we use them to color each particle.
18+
source = Geometry.sphere(5.0, 32, 24)
19+
field_obj = Field(
20+
geometry=source,
21+
attributes=[Attribute.position(), Attribute.uv(), Attribute.color()],
22+
)
23+
24+
# Read uvs back, build per-particle colors, write to color PBuffer.
25+
color_buf = field_obj.pbuffer(Attribute.color())
26+
uv_buf = field_obj.pbuffer(Attribute.uv())
27+
colors = []
28+
for uv in uv_buf.read():
29+
u = uv[0]
30+
h = u * 6.0
31+
c = h - int(h)
32+
if h < 1:
33+
colors.append([1.0, c, 0.0, 1.0])
34+
elif h < 2:
35+
colors.append([1.0 - c, 1.0, 0.0, 1.0])
36+
elif h < 3:
37+
colors.append([0.0, 1.0, c, 1.0])
38+
elif h < 4:
39+
colors.append([0.0, 1.0 - c, 1.0, 1.0])
40+
elif h < 5:
41+
colors.append([c, 0.0, 1.0, 1.0])
42+
else:
43+
colors.append([1.0, 0.0, 1.0 - c, 1.0])
44+
color_buf.write(colors)
45+
46+
particle = Geometry.sphere(0.18, 10, 8)
47+
mat = Material.field_pbr(color_buf)
48+
49+
50+
def draw():
51+
camera_position(0.0, 4.0, 18.0)
52+
camera_look_at(0.0, 0.0, 0.0)
53+
background(15, 15, 20)
54+
55+
use_material(mat)
56+
draw_field(field_obj, particle)
57+
58+
59+
run()

crates/processing_pyo3/src/compute.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,20 @@ pub struct Buffer {
1818
size: u64,
1919
}
2020

21+
impl Buffer {
22+
/// Wrap an existing buffer entity (e.g., one owned by a Field's PBuffer).
23+
/// `size` is queried from the buffer; `element_type` is supplied so typed
24+
/// reads / `__getitem__` work correctly.
25+
pub(crate) fn from_entity(entity: Entity, element_type: Option<ShaderValue>) -> Self {
26+
let size = buffer_size(entity).unwrap_or(0);
27+
Self {
28+
entity,
29+
element_type,
30+
size,
31+
}
32+
}
33+
}
34+
2135
#[pymethods]
2236
impl Buffer {
2337
#[new]
@@ -245,6 +259,14 @@ pub struct Compute {
245259
pub(crate) entity: Entity,
246260
}
247261

262+
impl Compute {
263+
/// Wrap an existing compute entity (e.g., one created by a Rust-side
264+
/// factory like `field_kernel_noise`). Not exposed to Python directly.
265+
pub(crate) fn from_entity(entity: Entity) -> Self {
266+
Self { entity }
267+
}
268+
}
269+
248270
#[pymethods]
249271
impl Compute {
250272
#[new]

0 commit comments

Comments
 (0)