use crate::brush::eval::{BrushNodeEvaluator, EvalContext};
use crate::brush::node::BrushNodeRegistration;
use crate::brush::wgsl::{CompileWgslCtx, NodeWgsl};
use crate::brush::wire::BrushWireType;
use crate::brush::wire::ScalarValue;
use crate::gpu::params::ParamDef;
use crate::nodegraph::{NodeRegistration, PortDef};
pub const TYPE_ID: &str = "image";
pub fn register() -> BrushNodeRegistration {
BrushNodeRegistration::compute(
NodeRegistration {
type_id: TYPE_ID,
category: "texture",
display_name: "Image",
description: "Uses a picture from the brush bundle as the brush tip shape.",
ports: vec![PortDef::output("color", BrushWireType::Vec4)
.with_description("RGBA value sampled from the named texture at the fragment's canvas-pixel position")],
params: &[
ParamDef::String {
name: "texture_name",
default: "",
},
ParamDef::Float {
name: "scale",
min: 1.0,
max: 4096.0,
default: 512.0,
},
],
is_gpu: false,
is_terminal: false,
supports_erase: true,
},
|| Box::new(ImageEvaluator),
)
}
pub struct ImageEvaluator;
impl BrushNodeEvaluator for ImageEvaluator {
fn evaluate_cpu(&self, _ctx: &EvalContext) -> Vec<(String, ScalarValue)> {
vec![("color".into(), ScalarValue::Vec4([0.5, 0.5, 0.5, 1.0]))]
}
fn compile_wgsl(&self, cctx: &CompileWgslCtx) -> Result<NodeWgsl, String> {
let mut wgsl = NodeWgsl::default();
if !cctx.consumed_outputs.contains("color") {
return Ok(wgsl);
}
let texture_name = cctx
.params
.first()
.and_then(|p| match p {
crate::gpu::params::ParamValue::String(s) => Some(s.as_str()),
_ => None,
})
.unwrap_or("");
if texture_name.is_empty() {
return Err("image node: `texture_name` is empty".into());
}
let scale = cctx
.params
.get(1)
.and_then(|p| match p {
crate::gpu::params::ParamValue::Float(v) => Some(*v),
crate::gpu::params::ParamValue::Int(v) => Some(*v as f32),
_ => None,
})
.unwrap_or(512.0)
.max(1.0);
let slot = cctx.request_texture(texture_name);
let var = cctx.ident("img_c");
wgsl.body = format!(
" let {var} = textureSample(graph_tex_{slot}, graph_smp, \
fract(target_pos / {scale:.6}));\n"
);
wgsl.outputs.insert("color".into(), var);
Ok(wgsl)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registration_shape() {
let reg = register();
assert_eq!(reg.node.type_id, "image");
assert_eq!(reg.node.category, "texture");
assert_eq!(reg.node.ports.len(), 1);
assert_eq!(reg.node.ports[0].name, "color");
assert_eq!(reg.node.params.len(), 2);
}
}