Skip to content

Commit 1d0fc99

Browse files
authored
Add support for blend modes (#97)
1 parent 3ea19d0 commit 1d0fc99

File tree

16 files changed

+800
-67
lines changed

16 files changed

+800
-67
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ path = "examples/custom_material.rs"
128128
name = "input"
129129
path = "examples/input.rs"
130130

131+
[[example]]
132+
name = "blend_modes"
133+
path = "examples/blend_modes.rs"
134+
131135
[profile.wasm-release]
132136
inherits = "release"
133137
opt-level = "z"

crates/processing_ffi/src/color.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use bevy::color::{LinearRgba, Srgba};
1+
use bevy::color::LinearRgba;
22
use processing::prelude::color::{ColorMode, ColorSpace};
33

44
/// A color with 4 float components and its color space.

crates/processing_ffi/src/lib.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,6 +466,36 @@ pub extern "C" fn processing_shear_y(graphics_id: u64, angle: f32) {
466466
error::check(|| graphics_record_command(graphics_entity, DrawCommand::ShearY { angle }));
467467
}
468468

469+
#[unsafe(no_mangle)]
470+
pub extern "C" fn processing_set_blend_mode(graphics_id: u64, mode: u8) {
471+
error::clear_error();
472+
let graphics_entity = Entity::from_bits(graphics_id);
473+
error::check(|| {
474+
let blend_state = processing::prelude::BlendMode::try_from(mode)?.to_blend_state();
475+
graphics_record_command(graphics_entity, DrawCommand::BlendMode(blend_state))
476+
});
477+
}
478+
479+
#[unsafe(no_mangle)]
480+
pub extern "C" fn processing_set_custom_blend_mode(
481+
graphics_id: u64,
482+
color_src: u8,
483+
color_dst: u8,
484+
color_op: u8,
485+
alpha_src: u8,
486+
alpha_dst: u8,
487+
alpha_op: u8,
488+
) {
489+
error::clear_error();
490+
let graphics_entity = Entity::from_bits(graphics_id);
491+
error::check(|| {
492+
let blend_state = custom_blend_state(
493+
color_src, color_dst, color_op, alpha_src, alpha_dst, alpha_op,
494+
)?;
495+
graphics_record_command(graphics_entity, DrawCommand::BlendMode(Some(blend_state)))
496+
});
497+
}
498+
469499
/// Draw a rectangle.
470500
///
471501
/// SAFETY:
@@ -767,6 +797,35 @@ pub const PROCESSING_STROKE_JOIN_ROUND: u8 = 0;
767797
pub const PROCESSING_STROKE_JOIN_MITER: u8 = 1;
768798
pub const PROCESSING_STROKE_JOIN_BEVEL: u8 = 2;
769799

800+
pub const PROCESSING_BLEND_MODE_BLEND: u8 = 0;
801+
pub const PROCESSING_BLEND_MODE_ADD: u8 = 1;
802+
pub const PROCESSING_BLEND_MODE_SUBTRACT: u8 = 2;
803+
pub const PROCESSING_BLEND_MODE_DARKEST: u8 = 3;
804+
pub const PROCESSING_BLEND_MODE_LIGHTEST: u8 = 4;
805+
pub const PROCESSING_BLEND_MODE_DIFFERENCE: u8 = 5;
806+
pub const PROCESSING_BLEND_MODE_EXCLUSION: u8 = 6;
807+
pub const PROCESSING_BLEND_MODE_MULTIPLY: u8 = 7;
808+
pub const PROCESSING_BLEND_MODE_SCREEN: u8 = 8;
809+
pub const PROCESSING_BLEND_MODE_REPLACE: u8 = 9;
810+
811+
pub const PROCESSING_BLEND_FACTOR_ZERO: u8 = 0;
812+
pub const PROCESSING_BLEND_FACTOR_ONE: u8 = 1;
813+
pub const PROCESSING_BLEND_FACTOR_SRC: u8 = 2;
814+
pub const PROCESSING_BLEND_FACTOR_ONE_MINUS_SRC: u8 = 3;
815+
pub const PROCESSING_BLEND_FACTOR_SRC_ALPHA: u8 = 4;
816+
pub const PROCESSING_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA: u8 = 5;
817+
pub const PROCESSING_BLEND_FACTOR_DST: u8 = 6;
818+
pub const PROCESSING_BLEND_FACTOR_ONE_MINUS_DST: u8 = 7;
819+
pub const PROCESSING_BLEND_FACTOR_DST_ALPHA: u8 = 8;
820+
pub const PROCESSING_BLEND_FACTOR_ONE_MINUS_DST_ALPHA: u8 = 9;
821+
pub const PROCESSING_BLEND_FACTOR_SRC_ALPHA_SATURATED: u8 = 10;
822+
823+
pub const PROCESSING_BLEND_OP_ADD: u8 = 0;
824+
pub const PROCESSING_BLEND_OP_SUBTRACT: u8 = 1;
825+
pub const PROCESSING_BLEND_OP_REVERSE_SUBTRACT: u8 = 2;
826+
pub const PROCESSING_BLEND_OP_MIN: u8 = 3;
827+
pub const PROCESSING_BLEND_OP_MAX: u8 = 4;
828+
770829
#[unsafe(no_mangle)]
771830
pub extern "C" fn processing_geometry_layout_create() -> u64 {
772831
error::clear_error();
@@ -1564,6 +1623,16 @@ pub extern "C" fn processing_key_is_down(key_code: u32) -> bool {
15641623
.unwrap_or(false)
15651624
}
15661625

1626+
#[unsafe(no_mangle)]
1627+
pub extern "C" fn processing_key_just_pressed(key_code: u32) -> bool {
1628+
error::clear_error();
1629+
error::check(|| {
1630+
let kc = key_code_from_u32(key_code)?;
1631+
input_key_just_pressed(kc)
1632+
})
1633+
.unwrap_or(false)
1634+
}
1635+
15671636
#[unsafe(no_mangle)]
15681637
pub extern "C" fn processing_key() -> u32 {
15691638
error::clear_error();

crates/processing_input/src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,15 @@ pub fn input_key_is_pressed() -> error::Result<bool> {
241241
})
242242
}
243243

244+
pub fn input_key_just_pressed(key_code: KeyCode) -> error::Result<bool> {
245+
app_mut(|app| {
246+
Ok(app
247+
.world()
248+
.resource::<ButtonInput<KeyCode>>()
249+
.just_pressed(key_code))
250+
})
251+
}
252+
244253
pub fn input_key_is_down(key_code: KeyCode) -> error::Result<bool> {
245254
app_mut(|app| {
246255
Ok(app
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from mewnala import *
2+
3+
MODES = [BLEND, ADD, SUBTRACT, DARKEST, LIGHTEST, DIFFERENCE, EXCLUSION, MULTIPLY, SCREEN, REPLACE]
4+
index = 0
5+
6+
def setup():
7+
size(500, 500)
8+
9+
def draw():
10+
global index
11+
12+
if key_just_pressed(RIGHT_ARROW) or key_just_pressed(SPACE):
13+
index = (index + 1) % len(MODES)
14+
elif key_just_pressed(LEFT_ARROW):
15+
index = (index - 1) % len(MODES)
16+
17+
background(38)
18+
no_stroke()
19+
blend_mode(MODES[index])
20+
21+
fill(230, 51, 51, 191)
22+
rect(80, 100, 200, 250)
23+
24+
fill(51, 204, 51, 191)
25+
rect(180, 80, 200, 250)
26+
27+
fill(51, 77, 230, 191)
28+
rect(130, 200, 200, 200)
29+
30+
run()

crates/processing_pyo3/src/graphics.rs

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,86 @@ use pyo3::{
1515
types::{PyDict, PyTuple},
1616
};
1717

18+
#[pyclass(name = "BlendMode", from_py_object)]
19+
#[derive(Clone)]
20+
pub struct PyBlendMode {
21+
pub(crate) blend_state: Option<bevy::render::render_resource::BlendState>,
22+
name: Option<&'static str>,
23+
}
24+
25+
impl PyBlendMode {
26+
pub(crate) fn from_preset(mode: BlendMode) -> Self {
27+
Self {
28+
blend_state: mode.to_blend_state(),
29+
name: Some(mode.name()),
30+
}
31+
}
32+
}
33+
34+
#[pymethods]
35+
impl PyBlendMode {
36+
#[new]
37+
#[pyo3(signature = (*, color_src, color_dst, color_op, alpha_src, alpha_dst, alpha_op))]
38+
fn new(
39+
color_src: u8,
40+
color_dst: u8,
41+
color_op: u8,
42+
alpha_src: u8,
43+
alpha_dst: u8,
44+
alpha_op: u8,
45+
) -> PyResult<Self> {
46+
let blend_state = custom_blend_state(
47+
color_src, color_dst, color_op, alpha_src, alpha_dst, alpha_op,
48+
)
49+
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
50+
Ok(Self {
51+
blend_state: Some(blend_state),
52+
name: None,
53+
})
54+
}
55+
56+
fn __repr__(&self) -> String {
57+
match self.name {
58+
Some(name) => format!("BlendMode.{name}"),
59+
None => "BlendMode(custom)".to_string(),
60+
}
61+
}
62+
63+
#[classattr]
64+
const ZERO: u8 = 0;
65+
#[classattr]
66+
const ONE: u8 = 1;
67+
#[classattr]
68+
const SRC_COLOR: u8 = 2;
69+
#[classattr]
70+
const ONE_MINUS_SRC_COLOR: u8 = 3;
71+
#[classattr]
72+
const SRC_ALPHA: u8 = 4;
73+
#[classattr]
74+
const ONE_MINUS_SRC_ALPHA: u8 = 5;
75+
#[classattr]
76+
const DST_COLOR: u8 = 6;
77+
#[classattr]
78+
const ONE_MINUS_DST_COLOR: u8 = 7;
79+
#[classattr]
80+
const DST_ALPHA: u8 = 8;
81+
#[classattr]
82+
const ONE_MINUS_DST_ALPHA: u8 = 9;
83+
#[classattr]
84+
const SRC_ALPHA_SATURATED: u8 = 10;
85+
86+
#[classattr]
87+
const OP_ADD: u8 = 0;
88+
#[classattr]
89+
const OP_SUBTRACT: u8 = 1;
90+
#[classattr]
91+
const OP_REVERSE_SUBTRACT: u8 = 2;
92+
#[classattr]
93+
const OP_MIN: u8 = 3;
94+
#[classattr]
95+
const OP_MAX: u8 = 4;
96+
}
97+
1898
#[pyclass(unsendable)]
1999
pub struct Surface {
20100
pub(crate) entity: Entity,
@@ -524,6 +604,11 @@ impl Graphics {
524604
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
525605
}
526606

607+
pub fn blend_mode(&self, mode: &PyBlendMode) -> PyResult<()> {
608+
graphics_record_command(self.entity, DrawCommand::BlendMode(mode.blend_state))
609+
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
610+
}
611+
527612
pub fn set_material(&self, material: &crate::material::Material) -> PyResult<()> {
528613
graphics_record_command(self.entity, DrawCommand::Material(material.entity))
529614
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))

crates/processing_pyo3/src/input.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,12 @@ pub fn key_is_down(key_code: u32) -> PyResult<bool> {
6262
processing::prelude::input_key_is_down(kc).map_err(|e| PyRuntimeError::new_err(format!("{e}")))
6363
}
6464

65+
pub fn key_just_pressed(key_code: u32) -> PyResult<bool> {
66+
let kc = u32_to_key_code(key_code)?;
67+
processing::prelude::input_key_just_pressed(kc)
68+
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))
69+
}
70+
6571
pub fn key() -> PyResult<Option<String>> {
6672
processing::prelude::input_key()
6773
.map(|opt| opt.map(String::from))

crates/processing_pyo3/src/lib.rs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ pub(crate) mod shader;
2020
#[cfg(feature = "webcam")]
2121
mod webcam;
2222

23-
use graphics::{Geometry, Graphics, Image, Light, Topology, get_graphics, get_graphics_mut};
23+
use graphics::{
24+
Geometry, Graphics, Image, Light, PyBlendMode, Topology, get_graphics, get_graphics_mut,
25+
};
2426
use material::Material;
2527

2628
use pyo3::{
@@ -127,6 +129,8 @@ mod mewnala {
127129
#[pymodule_export]
128130
use super::Material;
129131
#[pymodule_export]
132+
use super::PyBlendMode;
133+
#[pymodule_export]
130134
use super::Shader;
131135
#[pymodule_export]
132136
use super::Topology;
@@ -341,6 +345,25 @@ mod mewnala {
341345
#[pymodule_export]
342346
const XYZ: u8 = 9;
343347

348+
#[pymodule_init]
349+
fn init(module: &Bound<'_, PyModule>) -> PyResult<()> {
350+
use processing::prelude::BlendMode;
351+
module.add("BLEND", PyBlendMode::from_preset(BlendMode::Blend))?;
352+
module.add("ADD", PyBlendMode::from_preset(BlendMode::Add))?;
353+
module.add("SUBTRACT", PyBlendMode::from_preset(BlendMode::Subtract))?;
354+
module.add("DARKEST", PyBlendMode::from_preset(BlendMode::Darkest))?;
355+
module.add("LIGHTEST", PyBlendMode::from_preset(BlendMode::Lightest))?;
356+
module.add(
357+
"DIFFERENCE",
358+
PyBlendMode::from_preset(BlendMode::Difference),
359+
)?;
360+
module.add("EXCLUSION", PyBlendMode::from_preset(BlendMode::Exclusion))?;
361+
module.add("MULTIPLY", PyBlendMode::from_preset(BlendMode::Multiply))?;
362+
module.add("SCREEN", PyBlendMode::from_preset(BlendMode::Screen))?;
363+
module.add("REPLACE", PyBlendMode::from_preset(BlendMode::Replace))?;
364+
Ok(())
365+
}
366+
344367
#[pymodule]
345368
mod math {
346369
use super::*;
@@ -804,6 +827,12 @@ mod mewnala {
804827
graphics!(module).stroke_join(join)
805828
}
806829

830+
#[pyfunction]
831+
#[pyo3(pass_module, signature = (mode))]
832+
fn blend_mode(module: &Bound<'_, PyModule>, mode: &Bound<'_, PyBlendMode>) -> PyResult<()> {
833+
graphics!(module).blend_mode(&*mode.extract::<PyRef<PyBlendMode>>()?)
834+
}
835+
807836
#[pyfunction]
808837
#[pyo3(pass_module, signature = (x, y, w, h, tl=0.0, tr=0.0, br=0.0, bl=0.0))]
809838
fn rect(
@@ -979,4 +1008,9 @@ mod mewnala {
9791008
fn key_is_down(key_code: u32) -> PyResult<bool> {
9801009
input::key_is_down(key_code)
9811010
}
1011+
1012+
#[pyfunction]
1013+
fn key_just_pressed(key_code: u32) -> PyResult<bool> {
1014+
input::key_just_pressed(key_code)
1015+
}
9821016
}

crates/processing_render/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ pub struct ProcessingRenderPlugin;
3434

3535
impl Plugin for ProcessingRenderPlugin {
3636
fn build(&self, app: &mut App) {
37-
use render::material::{add_custom_materials, add_standard_materials};
37+
use render::material::{add_custom_materials, add_processing_materials};
3838
use render::{activate_cameras, clear_transient_meshes, flush_draw_commands};
3939

4040
let config = app.world().resource::<Config>().clone();
@@ -66,7 +66,7 @@ impl Plugin for ProcessingRenderPlugin {
6666
surface::SurfacePlugin,
6767
geometry::GeometryPlugin,
6868
light::LightPlugin,
69-
material::MaterialPlugin,
69+
material::ProcessingMaterialPlugin,
7070
bevy::pbr::wireframe::WireframePlugin::default(),
7171
material::custom::CustomMaterialPlugin,
7272
));
@@ -76,7 +76,7 @@ impl Plugin for ProcessingRenderPlugin {
7676
Update,
7777
(
7878
flush_draw_commands,
79-
add_standard_materials,
79+
add_processing_materials,
8080
add_custom_materials,
8181
)
8282
.chain()

0 commit comments

Comments
 (0)