use std::{
collections::BTreeMap,
ffi::OsStr,
fs,
path::PathBuf,
time::{
Duration,
Instant,
},
};
use glium::{
backend::Facade,
glutin::{
event::{
Event,
WindowEvent,
},
event_loop::ControlFlow,
},
Surface,
};
use shadergraph::{
lisp,
map,
png,
reload,
util,
};
use structopt::{
clap::AppSettings,
StructOpt,
};
fn wait_nanos(nanos: u64) -> ControlFlow {
let wait = Instant::now() + Duration::from_nanos(nanos);
ControlFlow::WaitUntil(wait)
}
pub fn handle_event(event: Event<()>, control_flow: &mut ControlFlow) {
if let Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} = event
{
*control_flow = ControlFlow::Exit;
}
}
pub fn dir(path: &OsStr) -> PathBuf {
if path == "." {
std::env::current_dir().expect("Can not determine package directory")
} else {
PathBuf::from(path)
}
}
pub fn package_dir(path: &OsStr) -> PathBuf {
if path == "." {
std::env::current_dir().expect("Can not determine package directory")
} else {
PathBuf::from(path)
}
}
#[derive(StructOpt, Debug)]
struct New {
#[structopt(default_value = ".", parse(from_os_str = package_dir))]
project: PathBuf,
}
#[derive(StructOpt, Debug)]
struct Run {
#[structopt(default_value = ".", parse(from_os_str = package_dir))]
project: PathBuf,
#[structopt(short, long)]
graph: Option<PathBuf>,
#[structopt(short, long, default_value = "512")]
width: u32,
#[structopt(short, long, default_value = "512")]
height: u32,
#[structopt(short, long)]
inputs: Vec<PathBuf>,
}
#[derive(StructOpt, Debug)]
struct Render {
#[structopt(flatten)]
run: Run,
#[structopt(short, long)]
output: PathBuf,
#[structopt(short, long, default_value = "0")]
start: u64,
#[structopt(short, long, default_value = "150")]
end: u64,
#[structopt(long, default_value = "30")]
fps: f64,
}
#[derive(StructOpt, Debug)]
#[structopt(name = "Shader Graph", bin_name = "shadergraph", about, global_settings(&[AppSettings::ColoredHelp, AppSettings::DeriveDisplayOrder]))]
enum Cli {
New(New),
Run(Run),
Render(Render),
}
fn main() {
let args = Cli::from_args();
match args {
Cli::Run(r) => run(r),
Cli::Render(r) => render(r),
Cli::New(n) => new(n.project),
}
}
fn new(path: PathBuf) {
fs::create_dir_all(&path).unwrap();
if let Ok(mut dir) = fs::read_dir(&path) {
if dir.next().is_some() {
eprintln!("[fatal] Specified project path is not empty");
panic!();
}
}
if reload::BASE_PROJECT.extract(&path).is_err() {
eprintln!("[fatal] Could not create base project");
panic!();
}
eprintln!(
"[info] successfully created new project in `{}`",
path.display()
);
}
fn render(render: Render) {
let args = render.run;
let lisp_config = args
.graph
.to_owned()
.unwrap_or_else(|| args.project.join("shader.graph"));
let inputs = args.inputs;
let (event_loop, display) = util::create(
"Shader Graph Renderer".to_string(),
args.width as f64,
args.height as f64,
);
let shader_dir = reload::ShaderDir::new_from_dir(args.project, lisp_config)
.expect("Could not load initial shader directory");
let mut graph =
lisp::graph_from_sexp(display.get_context(), shader_dir, map! {})
.map_err(|e| {
eprintln!("[fatal] Could not build initial graph:");
eprintln!("{}", e);
panic!();
})
.unwrap();
eprintln!("[info] Built initial graph");
let mut input_textures =
util::input_textures(&display, &inputs, args.width, args.height);
eprintln!("[info] Starting Render...");
let mut frame_number = 0;
let frames_output = render.output;
let frame_start = render.start;
let frame_end = render.end;
let frame_nanos = (1000000000.0 / render.fps) as u64;
event_loop.run(move |event, _, mut control_flow| {
*control_flow = wait_nanos(0);
handle_event(event, &mut control_flow);
let input_nodes = graph.get_inputs();
let output = if let [output] = graph.get_outputs().as_slice() {
*output
} else {
eprintln!("[fatal] Graph has invalid output signature.");
panic!();
};
assert_eq!(
input_nodes.len(),
input_textures.len(),
"The number of graph inputs and provided textures does not match up",
);
let mut input_map = BTreeMap::new();
for (node_id, texture) in input_nodes.iter().zip(input_textures.iter_mut()) {
input_map.insert(*node_id, texture.next_frame());
}
graph.created = std::time::Instant::now()
- std::time::Duration::from_nanos(frame_nanos * frame_number);
let output_map = graph.forward(input_map);
let mut target = display.draw();
target.clear_color(0.0, 0.0, 0.0, 1.0);
let texture = output_map[&output];
util::texture(&display, &mut target, texture);
target.finish().unwrap();
if frame_number >= frame_start {
png::write_png(texture, &frames_output.join(
format!("frame-{:0>4}.png", frame_number - frame_start)
));
}
if frame_number > frame_end {
panic!("Finished render, bailing pathetically");
}
frame_number += 1
});
}
fn run(args: Run) {
let lisp_config = args
.graph
.to_owned()
.unwrap_or_else(|| args.project.join("shader.graph"));
let inputs = args.inputs;
let (event_loop, display) = util::create(
"Shader Graph Playground".to_string(),
args.width as f64,
args.height as f64,
);
let mut watcher = reload::ShaderGraphWatcher::new_watch_dir(
display.get_context(),
args.project,
lisp_config,
)
.map_err(|e| {
eprintln!("[fatal] Could not build initial graph:");
eprintln!("{}", e);
panic!();
})
.unwrap();
eprintln!("[info] Built initial graph");
let mut input_textures =
util::input_textures(&display, &inputs, args.width, args.height);
eprintln!("[info] Starting...");
event_loop.run(move |event, _, mut control_flow| {
*control_flow = wait_nanos(16_666_667);
handle_event(event, &mut control_flow);
let (graph, watch_result) = watcher.graph();
match watch_result {
reload::WatchResult::NoChange => (),
reload::WatchResult::Rebuilt => eprintln!("[info] Graph rebuilt"),
reload::WatchResult::Err(e) => {
eprintln!("[warn] Could not rebuild graph:");
eprintln!("{}", e);
}
}
let input_nodes = graph.get_inputs();
let output = if let [output] = graph.get_outputs().as_slice() {
*output
} else {
eprintln!("[fatal] Graph has invalid output signature.");
panic!();
};
assert_eq!(
input_nodes.len(),
input_textures.len(),
"The number of graph inputs and provided textures does not match up",
);
let mut input_map = BTreeMap::new();
for (node_id, texture) in input_nodes.iter().zip(input_textures.iter_mut()) {
input_map.insert(*node_id, texture.next_frame());
}
let output_map = graph.forward(input_map);
let mut target = display.draw();
target.clear_color(0.0, 0.0, 0.0, 1.0);
util::texture(&display, &mut target, output_map[&output]);
target.finish().unwrap();
});
}