Skip to content

Commit

Permalink
Begin adding widgets for ISF input data uniforms to isf_demo.rs
Browse files Browse the repository at this point in the history
  • Loading branch information
mitchmindtree committed Apr 19, 2021
1 parent 86b6757 commit 6edea2b
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 23 deletions.
95 changes: 90 additions & 5 deletions examples/isf/isf_demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use fps_ticker::Fps;
use nannou::prelude::*;
use nannou::ui::prelude::*;
use nannou_isf::{IsfPipeline, IsfTime};
use nannou_isf::{IsfInputData, IsfPipeline, IsfTime};
use std::path::{Path, PathBuf};

fn main() {
Expand Down Expand Up @@ -49,6 +49,9 @@ widget_ids! {
fps_max_text,
separator1,
separator2,
input_separators[],
input_sliders[],
input_texts[],
}
}

Expand Down Expand Up @@ -123,7 +126,7 @@ fn update(app: &App, model: &mut Model, update: Update) {
{
let Model {
ref mut ui,
ref ids,
ref mut ids,
ref mut isf,
ref isf_shader_paths,
ref fps,
Expand Down Expand Up @@ -168,9 +171,11 @@ fn ui_view(app: &App, model: &Model, frame: Frame) {
model.ui.draw_to_frame(app, &frame).unwrap();
}

fn gui(ui: &mut UiCell, ids: &Ids, state: State) {
fn gui(ui: &mut UiCell, ids: &mut Ids, state: State) {
const PAD: Scalar = 20.0;
const STATUS_FONT_SIZE: u32 = 14;
const SLIDER_H: Scalar = 30.0;
const BACK_COLOR: ui::color::Color = ui::color::Color::Rgba(0.06, 0.065, 0.07, 1.0);

fn status_text(s: &str) -> widget::Text {
widget::Text::new(s).font_size(STATUS_FONT_SIZE)
Expand All @@ -183,6 +188,16 @@ fn gui(ui: &mut UiCell, ids: &Ids, state: State) {
(r, g, b)
}

fn slider<'a>(name: &'a str, f: f32, min: f32, max: f32) -> widget::Slider<'a, f32> {
widget::Slider::new(f, min, max)
.label(name)
.label_font_size(STATUS_FONT_SIZE)
.label_color(ui::color::WHITE)
.border(0.0)
.border_color(BACK_COLOR)
.color(ui::color::CHARCOAL)
}

widget::Canvas::new()
.border(0.0)
.rgb(0.11, 0.12, 0.13)
Expand Down Expand Up @@ -242,7 +257,7 @@ fn gui(ui: &mut UiCell, ids: &Ids, state: State) {
.unwrap_or("<invalid-file_name>");
let color = match Some(item.i) == selected_ix {
true => ui::color::DARK_BLUE,
false => ui::color::rgb(0.06, 0.065, 0.07),
false => BACK_COLOR,
};
let button = widget::Button::new()
.border(0.0)
Expand All @@ -258,6 +273,10 @@ fn gui(ui: &mut UiCell, ids: &Ids, state: State) {
Event::Selection(new_ix) => {
let isf_path = state.isf_shader_paths[new_ix].clone();
let images_path = images_dir(state.assets);

// Free existing GPU resources *before* creating new ones.
std::mem::drop(state.isf.take());

*state.isf = Some(create_isf_state(
state.shader_window,
isf_path,
Expand Down Expand Up @@ -337,7 +356,73 @@ fn gui(ui: &mut UiCell, ids: &Ids, state: State) {

separator().set(ids.separator1, ui);

// TODO: ISF Data Inputs
// ISF Data Input uniforms.

let isf = match state.isf {
Some(isf) => isf,
None => return,
};
let mut slider_ix = 0;
for (ix, (name, data)) in isf.pipeline.isf_data().inputs().iter().enumerate() {
if ix >= ids.input_texts.len() {
ids.input_texts
.resize(ix + 1, &mut ui.widget_id_generator());
}

status_text(name)
.padded_w_of(ids.background_canvas, PAD)
.color(ui::color::WHITE)
.down(PAD)
.set(ids.input_texts[ix], ui);

match *data {
// TODO: Button.
// Events in shaders are represented by a `bool` that is `true` only for the frame when
// the update occurred, so need to also remember to turn it back to `false` next frame
// somehow... Maybe can be done automatically by the ISF pipeline when rendering.
IsfInputData::Event { .. } => {}
// TODO: Toggle.
IsfInputData::Bool(b) => {}
// TODO: Slider OR Dropdownlist? Long is weird, see the isf docs...
IsfInputData::Long(i) => {}
IsfInputData::Float(f) => {
if slider_ix >= ids.input_sliders.len() {
ids.input_sliders
.resize(slider_ix + 1, &mut ui.widget_id_generator());
}

// TODO: We need min/max here! Possibly default on double click too.
let label = format!("{:.3}", f);
if let Some(new_f) = slider(&label, f, 0.0, 1.0)
.parent(bg_id)
.padded_w_of(ids.background_canvas, PAD)
.h(SLIDER_H)
.down(PAD * 0.5)
.set(ids.input_sliders[slider_ix], ui)
{
// TODO: Update the value in the ISF pipeline.
}

slider_ix += 1;
}
// TODO: XY Pad.
IsfInputData::Point2d(p) => {}
// TODO: Sliders / color picker.
IsfInputData::Color(lin_srgba) => {}
// TODO: Audio waveform (+ file drag and drop square?).
IsfInputData::Audio { .. } => {}
// TODO: Fft display?
IsfInputData::AudioFft { .. } => {}
// TODO: Show the image - make it drag and droppable with files.
IsfInputData::Image { .. } => {}
}

if ix >= ids.input_separators.len() {
ids.input_separators
.resize(ix + 1, &mut ui.widget_id_generator());
}
separator().set(ids.input_separators[ix], ui);
}
}

fn shader_view(_app: &App, model: &Model, frame: Frame) {
Expand Down
39 changes: 24 additions & 15 deletions nannou_isf/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! A crate aimed at making it easy to set up an ISF hot-loading environment with nannou.
pub use crate::pipeline::{IsfPipeline, IsfTime};
pub use crate::pipeline::{IsfInputData, IsfPipeline, IsfTime};
use std::path::Path;

mod pipeline;
Expand Down Expand Up @@ -48,6 +48,26 @@ pub fn input_type_uniform_size_bytes(ty: &isf::InputType) -> Option<usize> {
Some(size)
}

/// Produces a `Vec` containing the given inputs in the order that they should be laid out within
/// the glsl `uniforms`.
///
/// This *must* match the `pipeline::isf_inputs_by_uniform_order` order.
pub fn inputs_by_uniform_order(inputs: &[isf::Input]) -> Vec<&isf::Input> {
let mut inputs: Vec<_> = inputs
.iter()
.filter(|i| input_type_uniform_size_bytes(&i.ty).is_some())
.collect();
inputs.sort_by(|a, b| {
let a_s = input_type_uniform_size_bytes(&a.ty);
let b_s = input_type_uniform_size_bytes(&b.ty);
match b_s.cmp(&a_s) {
std::cmp::Ordering::Equal => a.name.cmp(&b.name),
ord => ord,
}
});
inputs
}

/// Generate the necessary GLSL declarations from the given ISF to be prefixed to the GLSL string
/// from which the ISF was parsed.
///
Expand Down Expand Up @@ -82,20 +102,9 @@ layout(set = 1, binding = 0) uniform IsfDataInputs {\n\

// Input uniforms should be sorted by name and input type uniform size.

// Must layout from largest to smallest types to avoid padding holes.
let b16 = isf
.inputs
.iter()
.filter(|i| input_type_uniform_size_bytes(&i.ty) == Some(16));
let b8 = isf
.inputs
.iter()
.filter(|i| input_type_uniform_size_bytes(&i.ty) == Some(8));
let b4 = isf
.inputs
.iter()
.filter(|i| input_type_uniform_size_bytes(&i.ty) == Some(4));
for input in b16.chain(b8).chain(b4) {
// Must layout from largest to smallest types to avoid padding holes, and then ordered
// by `name` to match the `BTreeMap` stored in the `IsfPipeline`.
for input in inputs_by_uniform_order(&isf.inputs) {
dbg!(&input.ty);
let ty_str = match input_type_uniform_type(&input.ty) {
Some(s) => s,
Expand Down
13 changes: 10 additions & 3 deletions nannou_isf/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,13 @@ impl IsfData {
&self.inputs
}

/// An iterator yielding only the uniform inputs.
///
/// Yields inputs in the order in which they are laid out in the generated shader.
pub fn uniform_inputs<'a>(&'a self) -> impl Iterator<Item = (&'a InputName, &'a IsfInputData)> {
isf_inputs_by_uniform_order(&self.inputs)
}

/// The texture stored for each pass.
pub fn passes(&self) -> &[wgpu::Texture] {
&self.passes
Expand Down Expand Up @@ -922,7 +929,7 @@ fn create_pipeline_layout(
bind_group_layouts: &[&wgpu::BindGroupLayout],
) -> wgpu::PipelineLayout {
let desc = wgpu::PipelineLayoutDescriptor {
label: None,
label: Some("nannou_isf-pipeline_layout"),
bind_group_layouts,
push_constant_ranges: &[],
};
Expand Down Expand Up @@ -1025,7 +1032,7 @@ fn sync_isf_data(
// types must be aligned to 16 bytes, 8-byte types must be aligned to 8-bytes, etc.
//
// This must match the order specified in the generated glsl shader.
fn isf_input_uniform_layout_order(
fn isf_inputs_by_uniform_order(
inputs: &BTreeMap<InputName, IsfInputData>,
) -> impl Iterator<Item = (&InputName, &IsfInputData)> {
let b16 = inputs
Expand All @@ -1043,7 +1050,7 @@ fn isf_input_uniform_layout_order(
// Encodes the ISF inputs to a slice of `u32` values, ready for uploading to the GPU.
fn isf_inputs_to_uniform_data(inputs: &BTreeMap<InputName, IsfInputData>) -> Vec<u32> {
let mut u32s: Vec<u32> = vec![];
for (_k, v) in isf_input_uniform_layout_order(inputs) {
for (_k, v) in isf_inputs_by_uniform_order(inputs) {
dbg!((_k, v));
match *v {
IsfInputData::Event { happening } => {
Expand Down

0 comments on commit 6edea2b

Please sign in to comment.