Skip to content

Commit c3469aa

Browse files
committed
Implement blend mode support.
1 parent 7f908d0 commit c3469aa

File tree

13 files changed

+220
-150
lines changed

13 files changed

+220
-150
lines changed

crates/processing_ffi/src/lib.rs

Lines changed: 46 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -466,24 +466,16 @@ 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-
/// Set the blend mode.
470-
///
471-
/// Mode values: 0=BLEND, 1=ADD, 2=SUBTRACT, 3=DARKEST, 4=LIGHTEST,
472-
/// 5=DIFFERENCE, 6=EXCLUSION, 7=MULTIPLY, 8=SCREEN, 9=REPLACE
473469
#[unsafe(no_mangle)]
474470
pub extern "C" fn processing_set_blend_mode(graphics_id: u64, mode: u8) {
475471
error::clear_error();
476472
let graphics_entity = Entity::from_bits(graphics_id);
477-
let blend_state = processing::prelude::BlendMode::from(mode).to_blend_state();
478-
error::check(|| graphics_record_command(graphics_entity, DrawCommand::BlendMode(blend_state)));
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+
});
479477
}
480478

481-
/// Set a custom blend mode by specifying individual blend components.
482-
///
483-
/// Each factor/operation is a u8 mapping to the WebGPU BlendFactor/BlendOperation enums.
484-
/// BlendFactor: 0=Zero, 1=One, 2=Src, 3=OneMinusSrc, 4=SrcAlpha, 5=OneMinusSrcAlpha,
485-
/// 6=Dst, 7=OneMinusDst, 8=DstAlpha, 9=OneMinusDstAlpha, 10=SrcAlphaSaturated
486-
/// BlendOperation: 0=Add, 1=Subtract, 2=ReverseSubtract, 3=Min, 4=Max
487479
#[unsafe(no_mangle)]
488480
pub extern "C" fn processing_set_custom_blend_mode(
489481
graphics_id: u64,
@@ -496,10 +488,10 @@ pub extern "C" fn processing_set_custom_blend_mode(
496488
) {
497489
error::clear_error();
498490
let graphics_entity = Entity::from_bits(graphics_id);
499-
let blend_state = custom_blend_state(
500-
color_src, color_dst, color_op, alpha_src, alpha_dst, alpha_op,
501-
);
502491
error::check(|| {
492+
let blend_state = custom_blend_state(
493+
color_src, color_dst, color_op, alpha_src, alpha_dst, alpha_op,
494+
)?;
503495
graphics_record_command(graphics_entity, DrawCommand::BlendMode(Some(blend_state)))
504496
});
505497
}
@@ -805,6 +797,35 @@ pub const PROCESSING_STROKE_JOIN_ROUND: u8 = 0;
805797
pub const PROCESSING_STROKE_JOIN_MITER: u8 = 1;
806798
pub const PROCESSING_STROKE_JOIN_BEVEL: u8 = 2;
807799

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+
808829
#[unsafe(no_mangle)]
809830
pub extern "C" fn processing_geometry_layout_create() -> u64 {
810831
error::clear_error();
@@ -1602,6 +1623,16 @@ pub extern "C" fn processing_key_is_down(key_code: u32) -> bool {
16021623
.unwrap_or(false)
16031624
}
16041625

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+
16051636
#[unsafe(no_mangle)]
16061637
pub extern "C" fn processing_key() -> u32 {
16071638
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: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
use crate::color::{ColorMode, extract_color_with_mode};
2-
use crate::color::{ColorMode, extract_color_with_mode};
3-
use crate::glfw::GlfwContext;
42
use crate::glfw::GlfwContext;
53
use crate::input;
64
use crate::math::{extract_vec2, extract_vec3, extract_vec4};
7-
use crate::math::{extract_vec2, extract_vec3, extract_vec4};
85
use bevy::{
96
color::{ColorToPacked, Srgba},
107
math::Vec4,
@@ -18,9 +15,6 @@ use pyo3::{
1815
types::{PyDict, PyTuple},
1916
};
2017

21-
use crate::glfw::GlfwContext;
22-
use crate::math::{extract_vec2, extract_vec3, extract_vec4};
23-
2418
#[pyclass(name = "BlendMode")]
2519
#[derive(Clone)]
2620
pub struct PyBlendMode {
@@ -39,11 +33,6 @@ impl PyBlendMode {
3933

4034
#[pymethods]
4135
impl PyBlendMode {
42-
/// Create a custom blend mode by specifying individual blend components.
43-
///
44-
/// All arguments are keyword-only. Use the blend factor constants (ZERO, ONE,
45-
/// SRC_COLOR, SRC_ALPHA, DST_COLOR, etc.) and blend operation constants
46-
/// (OP_ADD, OP_SUBTRACT, OP_REVERSE_SUBTRACT, OP_MIN, OP_MAX).
4736
#[new]
4837
#[pyo3(signature = (*, color_src, color_dst, color_op, alpha_src, alpha_dst, alpha_op))]
4938
fn new(
@@ -53,13 +42,15 @@ impl PyBlendMode {
5342
alpha_src: u8,
5443
alpha_dst: u8,
5544
alpha_op: u8,
56-
) -> Self {
57-
Self {
58-
blend_state: Some(custom_blend_state(
59-
color_src, color_dst, color_op, alpha_src, alpha_dst, alpha_op,
60-
)),
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),
6152
name: None,
62-
}
53+
})
6354
}
6455

6556
fn __repr__(&self) -> String {
@@ -68,8 +59,41 @@ impl PyBlendMode {
6859
None => "BlendMode(custom)".to_string(),
6960
}
7061
}
71-
}
7262

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+
}
7397

7498
#[pyclass(unsendable)]
7599
pub struct Surface {

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: 5 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -345,43 +345,6 @@ mod mewnala {
345345
#[pymodule_export]
346346
const XYZ: u8 = 9;
347347

348-
// Blend factor constants (for BlendMode custom constructor)
349-
#[pymodule_export]
350-
const ZERO: u8 = 0;
351-
#[pymodule_export]
352-
const ONE: u8 = 1;
353-
#[pymodule_export]
354-
const SRC_COLOR: u8 = 2;
355-
#[pymodule_export]
356-
const ONE_MINUS_SRC_COLOR: u8 = 3;
357-
#[pymodule_export]
358-
const SRC_ALPHA: u8 = 4;
359-
#[pymodule_export]
360-
const ONE_MINUS_SRC_ALPHA: u8 = 5;
361-
#[pymodule_export]
362-
const DST_COLOR: u8 = 6;
363-
#[pymodule_export]
364-
const ONE_MINUS_DST_COLOR: u8 = 7;
365-
#[pymodule_export]
366-
const DST_ALPHA: u8 = 8;
367-
#[pymodule_export]
368-
const ONE_MINUS_DST_ALPHA: u8 = 9;
369-
#[pymodule_export]
370-
const SRC_ALPHA_SATURATED: u8 = 10;
371-
372-
// Blend operation constants (for BlendMode custom constructor)
373-
#[pymodule_export]
374-
const OP_ADD: u8 = 0;
375-
#[pymodule_export]
376-
const OP_SUBTRACT: u8 = 1;
377-
#[pymodule_export]
378-
const OP_REVERSE_SUBTRACT: u8 = 2;
379-
#[pymodule_export]
380-
const OP_MIN: u8 = 3;
381-
#[pymodule_export]
382-
const OP_MAX: u8 = 4;
383-
384-
// Blend mode preset constants (added in pymodule_init)
385348
#[pymodule_init]
386349
fn init(module: &Bound<'_, PyModule>) -> PyResult<()> {
387350
use processing::prelude::BlendMode;
@@ -1045,4 +1008,9 @@ mod mewnala {
10451008
fn key_is_down(key_code: u32) -> PyResult<bool> {
10461009
input::key_is_down(key_code)
10471010
}
1011+
1012+
#[pyfunction]
1013+
fn key_just_pressed(key_code: u32) -> PyResult<bool> {
1014+
input::key_just_pressed(key_code)
1015+
}
10481016
}

crates/processing_render/src/graphics.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,6 @@ impl ProcessingProjection {
132132

133133
impl CameraProjection for ProcessingProjection {
134134
fn get_clip_from_view(&self) -> Mat4 {
135-
// near/far swapped for Bevy's reverse-Z depth
136135
Mat4::orthographic_rh(
137136
0.0,
138137
self.width,

crates/processing_render/src/material/custom.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ struct CustomMaterialKey {
6060
blend_state: Option<BlendState>,
6161
}
6262

63-
fn custom_blend_specialize(
63+
fn specialize(
6464
key: &dyn Any,
6565
descriptor: &mut RenderPipelineDescriptor,
6666
_layout: &MeshVertexBufferLayoutRef,
@@ -432,7 +432,7 @@ impl ErasedRenderAsset for CustomMaterial {
432432
base_specialize: Some(base_specialize),
433433
material_layout: Some(bind_group_layout),
434434
material_key: ErasedMaterialKey::new(CustomMaterialKey { blend_state }),
435-
user_specialize: Some(custom_blend_specialize),
435+
user_specialize: Some(specialize),
436436
alpha_mode: if blend_state.is_some() {
437437
AlphaMode::Blend
438438
} else {

crates/processing_render/src/material/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use bevy::pbr::{
1111
use bevy::prelude::*;
1212
use bevy::render::render_resource::{AsBindGroup, BlendState};
1313
use bevy::shader::ShaderRef;
14-
use processing_core::error::ProcessingError;
14+
use processing_core::error::{self, ProcessingError};
1515

1616
pub struct ProcessingMaterialPlugin;
1717

@@ -70,7 +70,7 @@ pub fn set_property(
7070
material_handles: Query<&UntypedMaterial>,
7171
mut standard_materials: ResMut<Assets<StandardMaterial>>,
7272
mut custom_materials: ResMut<Assets<custom::CustomMaterial>>,
73-
) -> processing_core::error::Result<()> {
73+
) -> error::Result<()> {
7474
let untyped = material_handles
7575
.get(entity)
7676
.map_err(|_| ProcessingError::MaterialNotFound)?;
@@ -100,7 +100,7 @@ pub fn destroy(
100100
material_handles: Query<&UntypedMaterial>,
101101
mut standard_materials: ResMut<Assets<StandardMaterial>>,
102102
mut custom_materials: ResMut<Assets<custom::CustomMaterial>>,
103-
) -> processing_core::error::Result<()> {
103+
) -> error::Result<()> {
104104
let untyped = material_handles
105105
.get(entity)
106106
.map_err(|_| ProcessingError::MaterialNotFound)?;

0 commit comments

Comments
 (0)