Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 43 additions & 12 deletions crates/processing_ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2908,6 +2908,49 @@ pub extern "C" fn processing_material(window_id: u64, mat_id: u64) {
error::check(|| graphics_record_command(window_entity, DrawCommand::Material(mat_entity)));
}

#[unsafe(no_mangle)]
pub extern "C" fn processing_material_set_alpha_mode(mat_id: u64, mode: u8, cutoff: f32) {
error::clear_error();
error::check(|| material_set_alpha_mode(Entity::from_bits(mat_id), mode, cutoff));
}

#[unsafe(no_mangle)]
pub extern "C" fn processing_material_set_double_sided(mat_id: u64, value: bool) {
error::clear_error();
error::check(|| material_set_double_sided(Entity::from_bits(mat_id), value));
}

#[unsafe(no_mangle)]
pub extern "C" fn processing_material_set_unlit(mat_id: u64, value: bool) {
error::clear_error();
error::check(|| material_set_unlit(Entity::from_bits(mat_id), value));
}

#[unsafe(no_mangle)]
pub extern "C" fn processing_material_set_depth_write(mat_id: u64, value: bool) {
error::clear_error();
error::check(|| material_set_depth_write(Entity::from_bits(mat_id), value));
}

#[unsafe(no_mangle)]
pub extern "C" fn processing_material_set_custom_blend_mode(
mat_id: u64,
color_src: u8,
color_dst: u8,
color_op: u8,
alpha_src: u8,
alpha_dst: u8,
alpha_op: u8,
) {
error::clear_error();
error::check(|| {
let blend_state = custom_blend_state(
color_src, color_dst, color_op, alpha_src, alpha_dst, alpha_op,
)?;
material_set_custom_blend(Entity::from_bits(mat_id), blend_state)
});
}

/// Create a shader from WGSL source.
///
/// # Safety
Expand Down Expand Up @@ -3583,18 +3626,6 @@ pub extern "C" fn processing_fill_buffer(graphics_id: u64, buffer_id: u64) {
});
}

#[unsafe(no_mangle)]
pub extern "C" fn processing_material_set_albedo_color(
mat_id: u64,
r: f32,
g: f32,
b: f32,
a: f32,
) {
error::clear_error();
error::check(|| material_set_albedo_color(Entity::from_bits(mat_id), [r, g, b, a]));
}

#[unsafe(no_mangle)]
pub extern "C" fn processing_material_set_albedo_buffer(mat_id: u64, buffer_id: u64) {
error::clear_error();
Expand Down
51 changes: 31 additions & 20 deletions crates/processing_pyo3/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,20 +60,18 @@ fn apply_albedo(entity: Entity, value: &Bound<'_, PyAny>) -> PyResult<()> {
return material_set_albedo_buffer(entity, buf.entity)
.map_err(|e| PyRuntimeError::new_err(format!("{e}")));
}
if let Ok(c) = value.extract::<PyRef<PyColor>>() {
let rgba = if let Ok(c) = value.extract::<PyRef<PyColor>>() {
let srgba: bevy::color::Srgba = c.0.into();
return material_set_albedo_color(
entity,
[srgba.red, srgba.green, srgba.blue, srgba.alpha],
)
.map_err(|e| PyRuntimeError::new_err(format!("{e}")));
}
if let Ok(rgba) = value.extract::<[f32; 4]>() {
return material_set_albedo_color(entity, rgba)
.map_err(|e| PyRuntimeError::new_err(format!("{e}")));
}
if let Ok(rgb) = value.extract::<[f32; 3]>() {
return material_set_albedo_color(entity, [rgb[0], rgb[1], rgb[2], 1.0])
Some([srgba.red, srgba.green, srgba.blue, srgba.alpha])
} else if let Ok(rgba) = value.extract::<[f32; 4]>() {
Some(rgba)
} else if let Ok(rgb) = value.extract::<[f32; 3]>() {
Some([rgb[0], rgb[1], rgb[2], 1.0])
} else {
None
};
if let Some(rgba) = rgba {
return material_set(entity, "color", shader_value::ShaderValue::Float4(rgba))
.map_err(|e| PyRuntimeError::new_err(format!("{e}")));
}
Err(PyRuntimeError::new_err(format!(
Expand All @@ -82,15 +80,29 @@ fn apply_albedo(entity: Entity, value: &Bound<'_, PyAny>) -> PyResult<()> {
)))
}

fn py_truthy(value: &Bound<'_, PyAny>) -> PyResult<bool> {
value
.extract::<bool>()
.or_else(|_| value.extract::<f64>().map(|f| f > 0.5))
}

fn apply_kwargs(entity: Entity, kwargs: &Bound<'_, PyDict>) -> PyResult<()> {
for (key, value) in kwargs.iter() {
let name: String = key.extract()?;
if name == "albedo" {
apply_albedo(entity, &value)?;
continue;
let rt = |e| PyRuntimeError::new_err(format!("{e}"));
match name.as_str() {
"albedo" => apply_albedo(entity, &value)?,
"unlit" => material_set_unlit(entity, py_truthy(&value)?).map_err(rt)?,
"double_sided" => material_set_double_sided(entity, py_truthy(&value)?).map_err(rt)?,
"depth_write" => material_set_depth_write(entity, py_truthy(&value)?).map_err(rt)?,
"alpha_mode" => {
material_set_alpha_mode(entity, value.extract::<u8>()?, 0.5).map_err(rt)?
}
_ => {
let v = py_to_shader_value(&value)?;
material_set(entity, &name, v).map_err(rt)?;
}
}
let v = py_to_shader_value(&value)?;
material_set(entity, &name, v).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
}
Ok(())
}
Expand Down Expand Up @@ -127,8 +139,7 @@ impl Material {
#[pyo3(signature = (**kwargs))]
pub fn unlit(kwargs: Option<&Bound<'_, PyDict>>) -> PyResult<Self> {
let entity = material_create_pbr().map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
material_set(entity, "unlit", shader_value::ShaderValue::Float(1.0))
.map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
material_set_unlit(entity, true).map_err(|e| PyRuntimeError::new_err(format!("{e}")))?;
if let Some(kwargs) = kwargs {
apply_kwargs(entity, kwargs)?;
}
Expand Down
5 changes: 4 additions & 1 deletion crates/processing_render/src/gltf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,10 @@ pub fn material(
.resource_mut::<Assets<ProcessingExtendedMaterial>>()
.add(ExtendedMaterial {
base: standard,
extension: ProcessingMaterial { blend_state: None },
extension: ProcessingMaterial {
blend_state: None,
depth_write: None,
},
});
let entity = world.spawn(UntypedMaterial(handle.untyped())).id();
Ok(entity)
Expand Down
100 changes: 47 additions & 53 deletions crates/processing_render/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1721,65 +1721,16 @@ pub fn material_create_pbr() -> error::Result<Entity> {
/// `material_create_pbr` with `unlit = true` set on the base StandardMaterial.
pub fn material_create_unlit() -> error::Result<Entity> {
let entity = material_create_pbr()?;
material_set(entity, "unlit", shader_value::ShaderValue::Float(1.0))?;
material_set_unlit(entity, true)?;
Ok(entity)
}

/// set the albedo source to a constant srgba color. If the material is
/// currently buffer-backed, swaps the asset back to plain PBR while
/// preserving every other `StandardMaterial` field.
pub fn material_set_albedo_color(entity: Entity, color: [f32; 4]) -> error::Result<()> {
use crate::material::ProcessingMaterial;
use crate::particles::material::ParticlesMaterial;
use crate::render::material::UntypedMaterial;
use bevy::pbr::ExtendedMaterial;

type DefaultMat = ExtendedMaterial<StandardMaterial, ProcessingMaterial>;

app_mut(|app| {
let untyped = app
.world()
.get::<UntypedMaterial>(entity)
.ok_or(error::ProcessingError::MaterialNotFound)?
.0
.clone();
let new_color = Color::srgba(color[0], color[1], color[2], color[3]);

if let Ok(handle) = untyped.clone().try_typed::<DefaultMat>() {
let mut mats = app.world_mut().resource_mut::<Assets<DefaultMat>>();
let mat = mats
.get_mut(&handle)
.ok_or(error::ProcessingError::MaterialNotFound)?;
mat.into_inner().base.base_color = new_color;
return Ok(());
}

let Ok(handle) = untyped.try_typed::<ParticlesMaterial>() else {
return Err(error::ProcessingError::MaterialNotFound);
};
let world = app.world_mut();
let preserved = {
let mut mats = world.resource_mut::<Assets<ParticlesMaterial>>();
let mat = mats
.get(&handle)
.ok_or(error::ProcessingError::MaterialNotFound)?;
let mut base = mat.base.clone();
base.base_color = new_color;
mats.remove(&handle);
base
};
let new_handle = world
.resource_mut::<Assets<DefaultMat>>()
.add(ExtendedMaterial {
base: preserved,
extension: ProcessingMaterial { blend_state: None },
});
world
.entity_mut(entity)
.insert(UntypedMaterial(new_handle.untyped()));
Ok(())
})
}
// NOTE: constant albedo/emissive are plain PBR uniforms — set them via
// `material_set(entity, "color" | "emissive", Float4(..))`. Only the
// per-particle *buffer* variants are special (see material_set_*_buffer below).

#[derive(Copy, Clone)]
enum ParticlesBufferSlot {
Expand Down Expand Up @@ -1893,6 +1844,49 @@ pub fn material_set(
})
}

pub fn material_set_alpha_mode(entity: Entity, mode: u8, cutoff: f32) -> error::Result<()> {
app_mut(|app| {
app.world_mut()
.run_system_cached_with(material::set_alpha_mode, (entity, mode, cutoff))
.unwrap()
})
}

pub fn material_set_double_sided(entity: Entity, value: bool) -> error::Result<()> {
app_mut(|app| {
app.world_mut()
.run_system_cached_with(material::set_double_sided, (entity, value))
.unwrap()
})
}

pub fn material_set_unlit(entity: Entity, value: bool) -> error::Result<()> {
app_mut(|app| {
app.world_mut()
.run_system_cached_with(material::set_unlit, (entity, value))
.unwrap()
})
}

pub fn material_set_depth_write(entity: Entity, value: bool) -> error::Result<()> {
app_mut(|app| {
app.world_mut()
.run_system_cached_with(material::set_depth_write, (entity, value))
.unwrap()
})
}

pub fn material_set_custom_blend(
entity: Entity,
blend: bevy::render::render_resource::BlendState,
) -> error::Result<()> {
app_mut(|app| {
app.world_mut()
.run_system_cached_with(material::set_custom_blend, (entity, blend))
.unwrap()
})
}

pub fn material_destroy(entity: Entity) -> error::Result<()> {
app_mut(|app| {
app.world_mut()
Expand Down
31 changes: 22 additions & 9 deletions crates/processing_render/src/material/custom.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ use bevy::{
render_asset::RenderAssets,
render_phase::DrawFunctions,
render_resource::{
BindGroupLayoutDescriptor, BindingResources, BlendState, UnpreparedBindGroup,
BindGroupLayoutDescriptor, BindingResources, BlendState, Face, UnpreparedBindGroup,
},
renderer::RenderDevice,
storage::GpuShaderBuffer,
Expand All @@ -59,6 +59,8 @@ use processing_core::error::{ProcessingError, Result};
#[derive(Clone, Hash, PartialEq)]
struct CustomMaterialKey {
blend_state: Option<BlendState>,
double_sided: Option<bool>,
depth_write: Option<bool>,
}

fn specialize(
Expand All @@ -67,12 +69,10 @@ fn specialize(
_layout: &MeshVertexBufferLayoutRef,
_pipeline_key: ErasedMaterialPipelineKey,
) -> std::result::Result<(), SpecializedMeshPipelineError> {
if let Some(key) = key.downcast_ref::<CustomMaterialKey>()
&& let Some(blend_state) = key.blend_state
&& let Some(fragment_state) = &mut descriptor.fragment
{
for target in fragment_state.targets.iter_mut().flatten() {
target.blend = Some(blend_state);
if let Some(key) = key.downcast_ref::<CustomMaterialKey>() {
crate::material::apply_pipeline_state(descriptor, key.blend_state, key.depth_write);
if let Some(double_sided) = key.double_sided {
descriptor.primitive.cull_mode = if double_sided { None } else { Some(Face::Back) };
}
}
Ok(())
Expand All @@ -85,6 +85,9 @@ pub struct CustomMaterial {
pub has_vertex: bool,
pub has_fragment: bool,
pub blend_state: Option<BlendState>,
pub alpha_mode: AlphaMode,
pub double_sided: Option<bool>,
pub depth_write: Option<bool>,
}

#[derive(Component)]
Expand Down Expand Up @@ -267,6 +270,9 @@ pub fn create_custom(
has_vertex,
has_fragment,
blend_state: None,
alpha_mode: AlphaMode::Opaque,
double_sided: None,
depth_write: None,
};
let handle = custom_materials.add(material);
Ok(commands.spawn(UntypedMaterial(handle.untyped())).id())
Expand Down Expand Up @@ -455,12 +461,19 @@ impl ErasedRenderAsset for CustomMaterial {
mesh_pipeline_key_bits: ErasedMeshPipelineKey::new(MeshPipelineKey::empty()),
base_specialize: Some(base_specialize),
material_layout: Some(bind_group_layout),
material_key: ErasedMaterialKey::new(CustomMaterialKey { blend_state }),
material_key: ErasedMaterialKey::new(CustomMaterialKey {
blend_state,
double_sided: source_asset.double_sided,
depth_write: source_asset.depth_write,
}),
user_specialize: Some(specialize),
// A custom blend forces the sorted transparent phase (an arbitrary
// blend equation can't be assumed commutative); otherwise honor the
// explicitly-set alpha mode.
alpha_mode: if blend_state.is_some() {
AlphaMode::Blend
} else {
AlphaMode::Opaque
source_asset.alpha_mode
},
..Default::default()
};
Expand Down
Loading
Loading