use std::{
collections::BTreeMap,
rc::Rc,
time::Instant,
};
use glium::{
backend::Context,
uniforms::AsUniformValue,
Texture2d,
};
use crate::util::{
compile_shader,
default_buffer,
RectStrip,
};
mod compute_node;
mod node;
mod shader_node;
mod uniform;
pub use compute_node::{
ComputeNode,
ComputeNodeFn,
};
pub use node::Node;
pub use shader_node::{
Buffer,
ShaderNode,
};
use uniform::UniformMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct NodeId(usize);
pub struct ShaderGraph {
context: Rc<Context>,
rect_strip: RectStrip,
pub created: std::time::Instant,
nodes: Vec<Option<Box<dyn Node>>>,
inputs: Vec<NodeId>,
outputs: Vec<NodeId>,
}
impl std::fmt::Debug for ShaderGraph {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ShaderGraph")
.field("nodes", &self.nodes)
.field("inputs", &self.inputs)
.field("outputs", &self.outputs)
.finish()
}
}
impl ShaderGraph {
pub fn new(context: &Rc<Context>) -> ShaderGraph {
ShaderGraph {
context: context.clone(),
rect_strip: RectStrip::new(context),
nodes: vec![],
inputs: vec![],
outputs: vec![],
created: Instant::now(),
}
}
pub fn get_inputs(&self) -> &Vec<NodeId> { &self.inputs }
pub fn get_outputs(&self) -> &Vec<NodeId> { &self.outputs }
pub fn add_node(&mut self, node: Option<Box<dyn Node>>) -> NodeId {
if let Some(ref node) = node {
self.assert_dag(&node.inputs())
}
self.nodes.push(node);
NodeId(self.nodes.len() - 1)
}
fn assert_dag(&self, nodes: &[NodeId]) {
for NodeId(input) in nodes {
assert!(input < &self.nodes.len());
}
}
pub fn add_input(&mut self) -> NodeId {
let id = self.add_node(None);
self.inputs.push(id);
id
}
pub fn add_shader(
&mut self,
source: &str,
inputs: Vec<NodeId>,
width: u32,
height: u32,
) -> Result<NodeId, String> {
self._add_shader(
source,
inputs,
Buffer::Single(default_buffer(&self.context, width, height)),
)
}
pub fn add_rec_shader(
&mut self,
source: &str,
inputs: Vec<NodeId>,
width: u32,
height: u32,
) -> Result<NodeId, String> {
self._add_shader(
source,
inputs,
Buffer::new_double(|| default_buffer(&self.context, width, height)),
)
}
fn _add_shader(
&mut self,
source: &str,
inputs: Vec<NodeId>,
buffer: Buffer,
) -> Result<NodeId, String> {
let shader = compile_shader(&self.context, source)?;
let shader_node = ShaderNode {
shader,
inputs,
buffer,
};
Ok(self.add_node(Some(Box::new(shader_node))))
}
pub fn add_compute<T: AsUniformValue + 'static>(
&mut self,
compute_node: ComputeNode<T>,
) -> Result<NodeId, String> {
Ok(self.add_node(Some(Box::new(compute_node))))
}
pub fn mark_output(&mut self, id: NodeId) -> Option<NodeId> {
if let Some(node) = &self.nodes[id.0] {
node.texture()?;
}
if !self.outputs.contains(&id) {
self.outputs.push(id)
}
Some(id)
}
fn time(created: Instant) -> f32 {
((Instant::now() - created).as_millis() as f64 / 1000.0) as f32
}
fn build_inputs<'a>(
mut uniforms: UniformMap<'a>,
previous: &'a [Option<Box<dyn Node>>],
inputs: &'a [NodeId],
input_map: &'a BTreeMap<NodeId, &'a Texture2d>,
) -> UniformMap<'a> {
for input in inputs.iter() {
match &previous[input.0] {
Some(node) => {
let (kind, uniform_value) = node.outputs();
uniforms.add(kind, uniform_value);
},
None => {
uniforms
.add("texture", input_map[input].as_uniform_value());
},
};
}
uniforms
}
fn pull_outputs<'a>(
&'a self,
input_map: BTreeMap<NodeId, &'a Texture2d>,
) -> BTreeMap<NodeId, &'a Texture2d> {
let mut output_map = BTreeMap::new();
for id in self.outputs.iter() {
let texture = match &self.nodes[id.0] {
Some(node) => node.texture().unwrap(),
None => input_map[id],
};
output_map.insert(*id, texture);
}
output_map
}
pub fn forward<'a>(
&'a mut self,
input_map: BTreeMap<NodeId, &'a Texture2d>,
) -> BTreeMap<NodeId, &'a Texture2d> {
for input in self.inputs.iter() {
assert!(input_map.contains_key(input));
}
for split_index in 0..self.nodes.len() {
let (previous, current) = self.nodes.split_at_mut(split_index);
if let Some(ref mut node) = current[0] {
let mut uniforms = UniformMap::new();
let time = Self::time(self.created);
uniforms.add("time", time.as_uniform_value());
let inputs = node.inputs();
let uniforms = Self::build_inputs(
uniforms, &*previous, &inputs, &input_map,
);
node.forward(&self.rect_strip, uniforms);
}
}
self.pull_outputs(input_map)
}
}