Skip to content

Commit

Permalink
WIP fixing uniforms layout, Fix some codegen formatting
Browse files Browse the repository at this point in the history
Still crashing frequently/unpredicatbly when switching between some
shaders. Still need to fix name order of input uniforms.
  • Loading branch information
mitchmindtree committed Apr 17, 2021
1 parent 089e988 commit 86b6757
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 54 deletions.
2 changes: 1 addition & 1 deletion assets/isf/Test-Audio.fs
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ void main() {
vec4 waveAdd = (1.0 - smoothstep(0.0, waveSize, abs(wave - loc.x)));
waveAdd.a = 1.0;
gl_FragColor = waveAdd;
}
}
8 changes: 6 additions & 2 deletions examples/isf/isf_demo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,9 @@ fn update(app: &App, model: &mut Model, update: Update) {
let mut encoder = device.create_command_encoder(&desc);
isf.pipeline
.encode_update(device, &mut encoder, &images_dir, touched_shaders);
shader_window.swap_chain_queue().submit(&[encoder.finish()]);
shader_window
.swap_chain_queue()
.submit(Some(encoder.finish()));
isf.time.time = update.since_start.secs() as _;
isf.time.time_delta = update.since_last.secs() as _;
}
Expand Down Expand Up @@ -391,7 +393,9 @@ fn create_isf_state(shader_window: &Window, fs_path: PathBuf, images_path: &Path
dst_sample_count,
images_path,
);
shader_window.swap_chain_queue().submit(&[encoder.finish()]);
shader_window
.swap_chain_queue()
.submit(Some(encoder.finish()));
let time = Default::default();
IsfState {
path: fs_path,
Expand Down
134 changes: 90 additions & 44 deletions nannou_isf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,61 @@ pub fn isf_from_shader_path(path: &Path) -> Result<isf::Isf, isf::ParseError> {
isf::parse(&glsl_string)
}

/// The associated uniform type for the given input type.
///
/// Returns `None` for variants that are not stored within the uniform buffer.
pub fn input_type_uniform_type(ty: &isf::InputType) -> Option<&'static str> {
let s = match *ty {
isf::InputType::Event | isf::InputType::Bool(_) => "bool",
isf::InputType::Long(_) => "int",
isf::InputType::Float(_) => "float",
isf::InputType::Point2d(_) => "vec2",
isf::InputType::Color(_) => "vec4",
isf::InputType::Image | isf::InputType::Audio(_) | isf::InputType::AudioFft(_) => {
return None
}
};
Some(s)
}

/// The size in bytes of the input type when laid out within the uniform struct.
///
/// Returns `None` for variants that are not stored within the uniform buffer.
pub fn input_type_uniform_size_bytes(ty: &isf::InputType) -> Option<usize> {
let size = match *ty {
isf::InputType::Color(_) => 4 * 4,
isf::InputType::Point2d(_) => 2 * 2,
isf::InputType::Long(_)
| isf::InputType::Float(_)
| isf::InputType::Bool(_)
| isf::InputType::Event { .. } => 1 * 4,
isf::InputType::Image | isf::InputType::Audio(_) | isf::InputType::AudioFft(_) => {
return None
}
};
Some(size)
}

/// Generate the necessary GLSL declarations from the given ISF to be prefixed to the GLSL string
/// from which the ISF was parsed.
///
/// This string should be inserted directly after the version preprocessor.
pub fn glsl_string_from_isf(isf: &isf::Isf) -> String {
// The normalised coords passed through from the vertex shader.
let frag_norm_coord_str = "
layout(location = 0) in vec2 isf_FragNormCoord;
layout(location = 0) in vec2 isf_FragNormCoord;\n\
";

// Create the `IsfData` uniform buffer with time, date, etc.
let isf_data_str = "
layout(set = 0, binding = 0) uniform IsfData {
int PASSINDEX;
vec2 RENDERSIZE;
float TIME;
float TIMEDELTA;
vec4 DATE;
int FRAMEINDEX;
};
layout(set = 0, binding = 0) uniform IsfData {
vec4 DATE;
vec2 RENDERSIZE;
float TIME;
float TIMEDELTA;
int PASSINDEX;
int FRAMEINDEX;
};\n\
";

// Create the `IsfDataInputs` uniform buffer with a field for each event, float, long, bool,
Expand All @@ -41,21 +76,32 @@ pub fn glsl_string_from_isf(isf: &isf::Isf) -> String {
false => None,
true => {
let mut isf_data_input_string = "
layout(set = 1, binding = 0) uniform IsfDataInputs {\n
layout(set = 1, binding = 0) uniform IsfDataInputs {\n\
"
.to_string();
for input in &isf.inputs {
let ty_str = match input.ty {
isf::InputType::Event | isf::InputType::Bool(_) => "bool",
isf::InputType::Long(_) => "int",
isf::InputType::Float(_) => "float",
isf::InputType::Point2d(_) => "vec2",
isf::InputType::Color(_) => "vec4",
isf::InputType::Image
| isf::InputType::Audio(_)
| isf::InputType::AudioFft(_) => continue,

// 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) {
dbg!(&input.ty);
let ty_str = match input_type_uniform_type(&input.ty) {
Some(s) => s,
None => continue,
};
isf_data_input_string.push_str(&format!("{} {};\n", ty_str, input.name));
isf_data_input_string.push_str(&format!(" {} {};\n", ty_str, input.name));
}
isf_data_input_string.push_str("};\n");
Some(isf_data_input_string)
Expand All @@ -64,7 +110,7 @@ pub fn glsl_string_from_isf(isf: &isf::Isf) -> String {

// Create the `img_sampler` binding, used for sampling all input images.
let img_sampler_str = "
layout(set = 2, binding = 0) uniform sampler img_sampler;
layout(set = 2, binding = 0) uniform sampler img_sampler;\n\
";

// Create the textures for the "IMPORTED" images.
Expand Down Expand Up @@ -111,32 +157,32 @@ pub fn glsl_string_from_isf(isf: &isf::Isf) -> String {

// Image functions.
let img_fns_str = "
// ISF provided short-hand for retrieving image size.
ivec2 IMG_SIZE(texture2D img) {
return textureSize(sampler2D(img, img_sampler), 0);
}
// ISF provided short-hand for retrieving image size.
ivec2 IMG_SIZE(texture2D img) {
return textureSize(sampler2D(img, img_sampler), 0);
}
// ISF provided short-hand for retrieving image color.
vec4 IMG_NORM_PIXEL(texture2D img, vec2 norm_px_coord) {
return texture(sampler2D(img, img_sampler), norm_px_coord);
}
// ISF provided short-hand for retrieving image color.
vec4 IMG_NORM_PIXEL(texture2D img, vec2 norm_px_coord) {
return texture(sampler2D(img, img_sampler), norm_px_coord);
}
// ISF provided short-hand for retrieving image color.
vec4 IMG_PIXEL(texture2D img, vec2 px_coord) {
ivec2 s = IMG_SIZE(img);
vec2 norm_px_coord = vec2(px_coord.x / float(s.x), px_coord.y / float(s.y));
return IMG_NORM_PIXEL(img, px_coord);
}
// ISF provided short-hand for retrieving image color.
vec4 IMG_PIXEL(texture2D img, vec2 px_coord) {
ivec2 s = IMG_SIZE(img);
vec2 norm_px_coord = vec2(px_coord.x / float(s.x), px_coord.y / float(s.y));
return IMG_NORM_PIXEL(img, px_coord);
}
// ISF provided short-hand for retrieving image color.
vec4 IMG_THIS_NORM_PIXEL(texture2D img) {
return IMG_NORM_PIXEL(img, isf_FragNormCoord);
}
// ISF provided short-hand for retrieving image color.
vec4 IMG_THIS_NORM_PIXEL(texture2D img) {
return IMG_NORM_PIXEL(img, isf_FragNormCoord);
}
// ISF provided short-hand for retrieving image color.
vec4 IMG_THIS_PIXEL(texture2D img) {
return IMG_THIS_NORM_PIXEL(img);
}
// ISF provided short-hand for retrieving image color.
vec4 IMG_THIS_PIXEL(texture2D img) {
return IMG_THIS_NORM_PIXEL(img);
}
";

// Combine all the declarations together.
Expand Down
59 changes: 52 additions & 7 deletions nannou_isf/src/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ pub struct IsfPipeline {
#[repr(C)]
#[derive(Clone, Copy, Debug)]
struct IsfUniforms {
pass_index: i32,
date: [f32; 4],
render_size: [f32; 2],
time: f32,
time_delta: f32,
date: [f32; 4],
pass_index: i32,
frame_index: i32,
}

Expand Down Expand Up @@ -357,6 +357,22 @@ impl IsfInputData {
}
}

/// The size of this field when laid out within the dynamically sized input uniform buffer.
pub fn uniform_size_bytes(&self) -> Option<usize> {
let size = match *self {
IsfInputData::Color(_) => 4 * 4,
IsfInputData::Point2d(_) => 2 * 2,
IsfInputData::Long(_)
| IsfInputData::Float(_)
| IsfInputData::Bool(_)
| IsfInputData::Event { .. } => 1 * 4,
IsfInputData::Image(_) | IsfInputData::Audio { .. } | IsfInputData::AudioFft { .. } => {
return None
}
};
Some(size)
}

/// Update an existing instance ISF input data instance with the given input.
fn update(
&mut self,
Expand Down Expand Up @@ -407,6 +423,9 @@ pub fn compile_isf_shader(
.and_then(|(old_str, isf)| {
let isf_str = crate::glsl_string_from_isf(&isf);
let new_str = crate::prefix_isf_glsl_str(&isf_str, old_str);
println!("---------- {}", path.display());
println!("{}", new_str);
println!("----------");
let ty = hotglsl::ShaderType::Fragment;
hotglsl::compile_str(&new_str, ty).map_err(From::from)
});
Expand Down Expand Up @@ -513,11 +532,11 @@ impl IsfPipeline {
// Prepare the uniform data.
let [dst_tex_w, dst_tex_h] = dst_texture_size;
let isf_uniforms = IsfUniforms {
pass_index: 0,
date: [0.0; 4],
render_size: [dst_tex_w as f32, dst_tex_h as f32],
time: 0.0,
time_delta: 0.0,
date: [0.0; 4],
pass_index: 0,
frame_index: 0,
};
let isf_input_uniforms = isf_inputs_to_uniform_data(&isf_data.inputs);
Expand Down Expand Up @@ -791,7 +810,11 @@ impl IsfPipeline {
let isf_input_uniforms = isf_inputs_to_uniform_data(&self.isf_data.inputs);
let isf_input_uniforms_bytes = isf_input_uniforms_as_bytes(&isf_input_uniforms);
let usage = wgpu::BufferUsage::COPY_SRC;
let new_buffer = device.create_buffer_with_data(&isf_input_uniforms_bytes, usage);
let new_buffer = device.create_buffer_init(&BufferInitDescriptor {
label: Some("nannou_isf-input_uniforms"),
contents: &isf_input_uniforms_bytes,
usage,
});
let size = isf_input_uniforms_bytes.len() as wgpu::BufferAddress;
encoder.copy_buffer_to_buffer(
&new_buffer,
Expand Down Expand Up @@ -996,10 +1019,32 @@ fn sync_isf_data(
});
}

// The order in which inputs are laid out in the uniform buffer.
//
// This is important to meet the conditions required by wgpu uniform layout. Specifically, 16-byte
// 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(
inputs: &BTreeMap<InputName, IsfInputData>,
) -> impl Iterator<Item = (&InputName, &IsfInputData)> {
let b16 = inputs
.iter()
.filter(|(_k, v)| v.uniform_size_bytes() == Some(16));
let b8 = inputs
.iter()
.filter(|(_k, v)| v.uniform_size_bytes() == Some(8));
let b4 = inputs
.iter()
.filter(|(_k, v)| v.uniform_size_bytes() == Some(4));
b16.chain(b8).chain(b4)
}

// 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 v in inputs.values() {
for (_k, v) in isf_input_uniform_layout_order(inputs) {
dbg!((_k, v));
match *v {
IsfInputData::Event { happening } => {
u32s.push(if happening { 1 } else { 0 });
Expand All @@ -1008,7 +1053,7 @@ fn isf_inputs_to_uniform_data(inputs: &BTreeMap<InputName, IsfInputData>) -> Vec
u32s.push(if b { 1 } else { 0 });
}
IsfInputData::Long(l) => {
u32s.push(unsafe { std::mem::transmute(l) });
u32s.push(u32::from_le_bytes(l.to_le_bytes()));
}
IsfInputData::Float(f) => {
u32s.push(f.to_bits());
Expand Down

0 comments on commit 86b6757

Please sign in to comment.