├── .gitignore ├── src ├── window │ └── main_window.rs ├── gfa.rs ├── gui │ ├── windows.rs │ ├── windows │ │ ├── settings │ │ │ ├── gui.rs │ │ │ ├── debug.rs │ │ │ └── main_view.rs │ │ ├── settings.rs │ │ ├── util.rs │ │ ├── filters.rs │ │ ├── graph_picker.rs │ │ ├── repl.rs │ │ ├── annotations │ │ │ └── filter.rs │ │ └── file.rs │ ├── debug.rs │ └── util.rs ├── lib.rs ├── overlays.rs ├── vulkan │ ├── draw_system.rs │ ├── msg.rs │ ├── debug.rs │ ├── draw_system │ │ └── nodes │ │ │ └── vertices.rs │ ├── compute.rs │ └── compute │ │ ├── node_motion.rs │ │ └── selection.rs ├── app │ ├── channels.rs │ ├── settings.rs │ └── shared_state.rs ├── universe │ ├── selection.rs │ ├── graph_layout.rs │ ├── physics.rs │ └── config.rs ├── asynchronous.rs ├── reactor │ └── paired.rs ├── gfa │ └── load.rs ├── reactor.rs └── view.rs ├── shaders ├── gui │ ├── gui.vert.spv │ ├── gui_1d.frag.spv │ ├── gui_2d.frag.spv │ ├── gui_rgba.frag.spv │ ├── gui_1d.frag │ ├── gui_rgba.frag │ ├── gui_2d.frag │ └── gui.vert ├── edges │ ├── edge.comp.spv │ ├── edges.frag.spv │ ├── edges.tesc.spv │ ├── edges.tese.spv │ ├── edges.vert.spv │ ├── quads.tesc.spv │ ├── quads.tese.spv │ ├── quads.vert.spv │ ├── edge_preprocess.comp.spv │ ├── ubo.glsl │ ├── quads.vert │ ├── edges.frag │ ├── edges.vert │ ├── edge_orient.glsl │ ├── edges.tese │ ├── edge_preprocess.comp │ ├── quads.tesc │ ├── edges.tesc │ ├── edge.comp │ └── quads.tese ├── nodes │ ├── base.tesc.spv │ ├── base.tese.spv │ ├── base.vert.spv │ ├── quad.vert.spv │ ├── themed.frag.spv │ ├── overlay_rgb.frag.spv │ ├── overlay_value.frag.spv │ ├── base.tesc │ ├── base.vert │ ├── overlay_rgb.frag │ ├── overlay_value.frag │ ├── themed.frag │ ├── quad.vert │ └── base.tese ├── post │ ├── post.vert.spv │ ├── example.frag.spv │ ├── post_blur.frag.spv │ ├── post_blur.vert.spv │ ├── post_edge.frag.spv │ ├── pixels_buffer_copy.frag.spv │ ├── post_blur.vert │ ├── post.vert │ ├── example.frag │ ├── pixels_buffer_copy.frag │ ├── post_blur.frag │ └── post_edge.frag └── compute │ ├── bin1.comp.spv │ ├── bin2.comp.spv │ ├── bin3.comp.spv │ ├── path_view.comp.spv │ ├── rect_select.comp.spv │ ├── node_translate.comp.spv │ ├── path_view_val.comp.spv │ ├── node_translate.comp │ ├── rect_select.comp │ ├── bin3.comp │ ├── bin2.comp │ ├── bin1.comp │ ├── path_view_val.comp │ └── path_view.comp ├── test.rhai ├── scripts ├── transparent.rhai ├── hash_seq_test.rhai ├── context_actions │ ├── 01-copy-node-id.rhai │ ├── pan-to-node-modal.rhai │ ├── 03-copy-path-name.rhai │ └── 02-copy-node-seq.rhai ├── handle_step_count.rhai ├── util │ ├── path_steps.rhai │ ├── animate_example.rhai │ └── points_of_interest.rhai ├── handle_path_set.rhai └── mhc_test.rhai ├── test2.rhai ├── a3105.test.bed ├── overlays.md ├── theme_test.rhai ├── filter_records_example.rhai ├── LICENSE ├── Cargo.toml ├── rustfmt.toml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /src/window/main_window.rs: -------------------------------------------------------------------------------- 1 | use crossbeam::atomic::AtomicCell; 2 | use std::sync::Arc; 3 | -------------------------------------------------------------------------------- /shaders/gui/gui.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/gui/gui.vert.spv -------------------------------------------------------------------------------- /shaders/edges/edge.comp.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/edges/edge.comp.spv -------------------------------------------------------------------------------- /shaders/gui/gui_1d.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/gui/gui_1d.frag.spv -------------------------------------------------------------------------------- /shaders/gui/gui_2d.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/gui/gui_2d.frag.spv -------------------------------------------------------------------------------- /shaders/nodes/base.tesc.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/nodes/base.tesc.spv -------------------------------------------------------------------------------- /shaders/nodes/base.tese.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/nodes/base.tese.spv -------------------------------------------------------------------------------- /shaders/nodes/base.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/nodes/base.vert.spv -------------------------------------------------------------------------------- /shaders/nodes/quad.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/nodes/quad.vert.spv -------------------------------------------------------------------------------- /shaders/post/post.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/post/post.vert.spv -------------------------------------------------------------------------------- /test.rhai: -------------------------------------------------------------------------------- 1 | set("background_color_light", rgb(sin(time_since_start), cos(time_since_start), 1.0)); 2 | -------------------------------------------------------------------------------- /shaders/compute/bin1.comp.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/compute/bin1.comp.spv -------------------------------------------------------------------------------- /shaders/compute/bin2.comp.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/compute/bin2.comp.spv -------------------------------------------------------------------------------- /shaders/compute/bin3.comp.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/compute/bin3.comp.spv -------------------------------------------------------------------------------- /shaders/edges/edges.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/edges/edges.frag.spv -------------------------------------------------------------------------------- /shaders/edges/edges.tesc.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/edges/edges.tesc.spv -------------------------------------------------------------------------------- /shaders/edges/edges.tese.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/edges/edges.tese.spv -------------------------------------------------------------------------------- /shaders/edges/edges.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/edges/edges.vert.spv -------------------------------------------------------------------------------- /shaders/edges/quads.tesc.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/edges/quads.tesc.spv -------------------------------------------------------------------------------- /shaders/edges/quads.tese.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/edges/quads.tese.spv -------------------------------------------------------------------------------- /shaders/edges/quads.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/edges/quads.vert.spv -------------------------------------------------------------------------------- /shaders/gui/gui_rgba.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/gui/gui_rgba.frag.spv -------------------------------------------------------------------------------- /shaders/nodes/themed.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/nodes/themed.frag.spv -------------------------------------------------------------------------------- /shaders/post/example.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/post/example.frag.spv -------------------------------------------------------------------------------- /scripts/transparent.rhai: -------------------------------------------------------------------------------- 1 | fn node_color(id) { 2 | let color = rgba(0.0, 0.0, 0.0, 0.0); 3 | 4 | color 5 | } -------------------------------------------------------------------------------- /shaders/post/post_blur.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/post/post_blur.frag.spv -------------------------------------------------------------------------------- /shaders/post/post_blur.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/post/post_blur.vert.spv -------------------------------------------------------------------------------- /shaders/post/post_edge.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/post/post_edge.frag.spv -------------------------------------------------------------------------------- /shaders/compute/path_view.comp.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/compute/path_view.comp.spv -------------------------------------------------------------------------------- /shaders/nodes/overlay_rgb.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/nodes/overlay_rgb.frag.spv -------------------------------------------------------------------------------- /shaders/compute/rect_select.comp.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/compute/rect_select.comp.spv -------------------------------------------------------------------------------- /shaders/nodes/overlay_value.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/nodes/overlay_value.frag.spv -------------------------------------------------------------------------------- /shaders/compute/node_translate.comp.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/compute/node_translate.comp.spv -------------------------------------------------------------------------------- /shaders/compute/path_view_val.comp.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/compute/path_view_val.comp.spv -------------------------------------------------------------------------------- /shaders/edges/edge_preprocess.comp.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/edges/edge_preprocess.comp.spv -------------------------------------------------------------------------------- /shaders/post/pixels_buffer_copy.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chfi/gfaestus/HEAD/shaders/post/pixels_buffer_copy.frag.spv -------------------------------------------------------------------------------- /test2.rhai: -------------------------------------------------------------------------------- 1 | let old_color = get("background_color_light"); 2 | set("background_color_light", rgb(old_color.r, old_color.g, cos(time_since_start * 0.2))); -------------------------------------------------------------------------------- /shaders/edges/ubo.glsl: -------------------------------------------------------------------------------- 1 | struct UBO { 2 | vec4 edge_color; 3 | float edge_width; 4 | 5 | float tess_levels[5]; 6 | 7 | float curve_offset; 8 | }; 9 | -------------------------------------------------------------------------------- /scripts/hash_seq_test.rhai: -------------------------------------------------------------------------------- 1 | fn node_color(id) { 2 | let h = handle(id, false); 3 | let seq = graph.sequence(h); 4 | let hash = hash_bytes(seq); 5 | let color = hash_color(hash); 6 | color 7 | } 8 | -------------------------------------------------------------------------------- /scripts/context_actions/01-copy-node-id.rhai: -------------------------------------------------------------------------------- 1 | export const name = "Copy node ID"; 2 | export const context_types = ["NodeId"]; 3 | 4 | fn action(context) { 5 | let node = context.get("NodeId"); 6 | app::set_clipboard_contents(node.to_string()); 7 | } -------------------------------------------------------------------------------- /scripts/handle_step_count.rhai: -------------------------------------------------------------------------------- 1 | fn node_color(id) { 2 | let h = handle(id, false); 3 | 4 | let steps = graph.steps_on_handle(h); 5 | let count = 0.0; 6 | 7 | for step in steps { 8 | count += 1.0; 9 | } 10 | 11 | count 12 | } -------------------------------------------------------------------------------- /a3105.test.bed: -------------------------------------------------------------------------------- 1 | Consensus_0 100 500 record0 0 . 0 0 200,50,50 2 | Consensus_0 600 1000 record1 0 . 0 0 200,50,50 3 | Consensus_1 300 400 record2 0 . 0 0 50,50,250 4 | Consensus_1 500 700 record3 0 . 0 0 10,250,50 5 | Consensus_2 100 2000 record4 0 . 0 0 0,1,0 -------------------------------------------------------------------------------- /scripts/context_actions/pan-to-node-modal.rhai: -------------------------------------------------------------------------------- 1 | export const name = "Pan to node"; 2 | export const context_types = []; 3 | 4 | fn action(context) { 5 | let node = modal::get_node_id(); 6 | let msg = msg::goto_node(node); 7 | app::send_msg(msg); 8 | } 9 | -------------------------------------------------------------------------------- /scripts/context_actions/03-copy-path-name.rhai: -------------------------------------------------------------------------------- 1 | export const name = "Copy path name"; 2 | export const context_types = ["PathId"]; 3 | 4 | fn action(context) { 5 | let path = context.get("PathId"); 6 | let name = app::graph.get_path_name(path); 7 | app::set_clipboard_contents(name); 8 | } 9 | -------------------------------------------------------------------------------- /shaders/post/post_blur.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (location = 0) in vec2 position; 4 | 5 | layout (push_constant) uniform Dims { 6 | float width; 7 | float height; 8 | bool enabled; 9 | } dims; 10 | 11 | void main() { 12 | gl_Position = vec4(position, 0.0, 1.0); 13 | } 14 | -------------------------------------------------------------------------------- /scripts/context_actions/02-copy-node-seq.rhai: -------------------------------------------------------------------------------- 1 | export const name = "Copy node sequence"; 2 | export const context_types = ["NodeId"]; 3 | 4 | fn action(context) { 5 | let node = context.get("NodeId"); 6 | let seq = app::graph.sequence(handle(node, false)); 7 | app::set_clipboard_contents(seq.to_string()); 8 | } 9 | -------------------------------------------------------------------------------- /src/gfa.rs: -------------------------------------------------------------------------------- 1 | pub mod load; 2 | 3 | #[allow(unused_imports)] 4 | use handlegraph::{ 5 | handle::{Direction, Edge, Handle, NodeId}, 6 | handlegraph::*, 7 | mutablehandlegraph::*, 8 | packed::*, 9 | pathhandlegraph::*, 10 | }; 11 | 12 | #[allow(unused_imports)] 13 | use handlegraph::packedgraph::PackedGraph; 14 | -------------------------------------------------------------------------------- /shaders/gui/gui_1d.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (set = 0, binding = 0) uniform sampler1D u_sampler; 4 | 5 | layout (location = 0) in vec4 vs_color; 6 | layout (location = 1) in vec2 vs_uv; 7 | 8 | layout (location = 0) out vec4 f_color; 9 | 10 | void main() { 11 | vec4 color = texture(u_sampler, vs_uv.x); 12 | 13 | f_color = color; 14 | } 15 | -------------------------------------------------------------------------------- /shaders/edges/quads.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (location = 0) in vec2 position; 4 | 5 | layout (push_constant) uniform NodePC { 6 | mat4 view_transform; 7 | float node_width; 8 | float scale; 9 | vec2 viewport_dims; 10 | uint texture_period; 11 | } node_uniform; 12 | 13 | void main() { 14 | gl_Position = vec4(position.xy, 0.0, 1.0); 15 | } 16 | -------------------------------------------------------------------------------- /shaders/post/post.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | out gl_PerVertex 4 | { 5 | vec4 gl_Position; 6 | }; 7 | 8 | layout (push_constant) uniform Dims { 9 | float width; 10 | float height; 11 | uint enabled; 12 | } dims; 13 | 14 | void main() { 15 | vec2 pos = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); 16 | 17 | gl_Position = vec4(pos * 2.0f - 1.0f, 0.0f, 1.0f); 18 | } 19 | -------------------------------------------------------------------------------- /shaders/gui/gui_rgba.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (set = 0, binding = 0) uniform sampler2D u_sampler; 4 | 5 | layout (location = 0) in vec4 vs_color; 6 | layout (location = 1) in vec2 vs_uv; 7 | 8 | layout (location = 0) out vec4 f_color; 9 | 10 | void main() { 11 | // vec2 uv = vec2(vs_uv.x, vs_uv.y); 12 | vec4 color = texture(u_sampler, vs_uv); 13 | 14 | f_color = vs_color * color; 15 | } 16 | -------------------------------------------------------------------------------- /scripts/util/path_steps.rhai: -------------------------------------------------------------------------------- 1 | fn path_steps(path) { 2 | let graph = get_graph(); 3 | let nodes = []; 4 | 5 | let step = graph.path_first_step(path); 6 | 7 | loop { 8 | let handle = graph.path_handle_at_step(path, step); 9 | nodes.push(handle.id()); 10 | if !graph.has_next_step(path, step) { 11 | break; 12 | } 13 | step = graph.next_step(path, step); 14 | } 15 | 16 | return nodes; 17 | } 18 | -------------------------------------------------------------------------------- /overlays.md: -------------------------------------------------------------------------------- 1 | # Overlays 2 | 3 | In `gfaestus` an "overlay" is a color scheme that gives each node in 4 | the graph a color based on some property, or set of properties, of the 5 | node. 6 | 7 | Some examples are coloring each node based on a hash of its sequence, 8 | a hash of the paths on the node, or from an external data source such 9 | as a BED file. 10 | 11 | Overlays are added to `gfaestus` at runtime with 12 | [Rhai](https://rhai.rs/) scripts. 13 | -------------------------------------------------------------------------------- /src/gui/windows.rs: -------------------------------------------------------------------------------- 1 | pub mod annotations; 2 | pub mod file; 3 | pub mod filters; 4 | pub mod graph_details; 5 | pub mod graph_picker; 6 | pub mod overlays; 7 | pub mod path_position; 8 | pub mod paths; 9 | pub mod settings; 10 | pub mod util; 11 | 12 | pub use annotations::*; 13 | pub use file::*; 14 | pub use filters::*; 15 | pub use graph_details::*; 16 | pub use graph_picker::*; 17 | pub use overlays::*; 18 | pub use path_position::*; 19 | pub use paths::*; 20 | pub use settings::*; 21 | pub use util::*; 22 | -------------------------------------------------------------------------------- /shaders/gui/gui_2d.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (set = 0, binding = 0) uniform sampler2D u_sampler; 4 | 5 | layout (location = 0) in vec4 vs_color; 6 | layout (location = 1) in vec2 vs_uv; 7 | 8 | layout (location = 0) out vec4 f_color; 9 | 10 | void main() { 11 | vec2 uv = vec2(vs_uv.x, vs_uv.y); 12 | vec4 color = texture(u_sampler, uv); 13 | 14 | vec4 tex_color = vec4(1.0, 1.0, 1.0, color.r); 15 | // vec4 tex_color = vec4(color.r, color.r, color.r, 1.0); 16 | f_color = vs_color * tex_color; 17 | } 18 | -------------------------------------------------------------------------------- /shaders/edges/edges.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (std140, set = 0, binding = 0) uniform UBO 4 | { 5 | vec4 edge_color; 6 | float edge_width; 7 | 8 | float tess_levels[5]; 9 | 10 | float curve_offset; 11 | } ubo; 12 | 13 | layout (location = 0) out vec4 f_color; 14 | 15 | layout (push_constant) uniform NodePC { 16 | mat4 view_transform; 17 | float node_width; 18 | float scale; 19 | vec2 viewport_dims; 20 | uint texture_period; 21 | } node_uniform; 22 | 23 | void main() { 24 | f_color = ubo.edge_color; 25 | } 26 | -------------------------------------------------------------------------------- /scripts/handle_path_set.rhai: -------------------------------------------------------------------------------- 1 | fn node_color(id) { 2 | let h = handle(id, false); 3 | 4 | let steps = graph.steps_on_handle(h); 5 | 6 | let paths = []; 7 | for step in steps { 8 | let path = step.path_id; 9 | paths.push(path); 10 | } 11 | 12 | paths.sort(|x, y| { 13 | unwrap_path_id(x) - unwrap_path_id(y) 14 | }); 15 | 16 | let hasher = create_hasher(); 17 | 18 | for path in paths { 19 | hasher.hash(path); 20 | } 21 | 22 | let hash = hasher.finish(); 23 | let color = hash_color(hash); 24 | 25 | color 26 | } 27 | -------------------------------------------------------------------------------- /src/gui/windows/settings/gui.rs: -------------------------------------------------------------------------------- 1 | pub struct GuiSettings { 2 | pub(crate) show_fps: bool, 3 | pub(crate) show_graph_stats: bool, 4 | } 5 | 6 | impl std::default::Default for GuiSettings { 7 | fn default() -> Self { 8 | Self { 9 | show_fps: false, 10 | show_graph_stats: false, 11 | } 12 | } 13 | } 14 | 15 | impl GuiSettings { 16 | pub fn ui(&mut self, ui: &mut egui::Ui) { 17 | ui.checkbox(&mut self.show_fps, "Display FPS"); 18 | ui.checkbox(&mut self.show_graph_stats, "Display graph stats"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /shaders/post/example.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (set = 0, binding = 0) uniform sampler2D u_color_sampler; 4 | 5 | layout (location = 0) out vec4 f_color; 6 | 7 | layout (push_constant) uniform Dims { 8 | vec2 texture_size; 9 | vec2 screen_size; 10 | bool enabled; 11 | } dims; 12 | 13 | // vec2 uv_coord(vec2 coord) { 14 | // return (coord / vec2(dims.width, dims.height)); 15 | // } 16 | 17 | void main() { 18 | vec2 fc = gl_FragCoord.xy / dims.texture_size; 19 | 20 | vec2 uv = fc; 21 | 22 | vec4 color = texture(u_color_sampler, uv); 23 | 24 | f_color = color; 25 | } 26 | -------------------------------------------------------------------------------- /theme_test.rhai: -------------------------------------------------------------------------------- 1 | // initializes a global counter variable, and uses it to step through 2 | // three different background colors 3 | 4 | fn next_theme() { 5 | 6 | let bg_index = 0; 7 | let bg_colors = [rgb(0.9, 0.7, 0.7), rgb(0.7, 0.9, 0.7), rgb(0.7, 0.7, 0.9)]; 8 | 9 | try { 10 | bg_index = get_var("bg_index"); 11 | } 12 | catch { 13 | } 14 | 15 | let new_index = if bg_index < bg_colors.len() - 1 { 16 | bg_index + 1 17 | } else { 18 | 0 19 | }; 20 | 21 | set_var("bg_index", new_index); 22 | 23 | let cur_color = bg_colors[bg_index]; 24 | set("background_color_light", cur_color); 25 | } 26 | -------------------------------------------------------------------------------- /shaders/compute/node_translate.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in; 4 | 5 | layout (set = 0, binding = 0) readonly buffer Selection { 6 | int data[]; 7 | } selection; 8 | 9 | layout (set = 0, binding = 1) buffer Nodes { 10 | vec4 pos[]; 11 | } nodes; 12 | 13 | layout (push_constant) uniform Delta { 14 | vec2 d; 15 | } delta; 16 | 17 | void main() { 18 | uint index = gl_GlobalInvocationID.x; 19 | 20 | if (selection.data[index] != 0) { 21 | vec4 node_delta = vec4(delta.d.x, delta.d.y, delta.d.x, delta.d.y); 22 | nodes.pos[index] += node_delta; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /filter_records_example.rhai: -------------------------------------------------------------------------------- 1 | let cseqid = gff3_column("SeqId"); 2 | let cstart = gff3_column("Start"); 3 | let cend = gff3_column("End"); 4 | let cname = gff3_column("Name"); 5 | let ctype = gff3_column("Type"); 6 | 7 | let record_indices = []; 8 | 9 | for ix in range(0, coll.len()) { 10 | let record = coll.get_record(ix); 11 | 12 | let seq_id = record.get(cseqid); 13 | 14 | if seq_id == ["6"] { 15 | let start = record.get(cstart); 16 | let end = record.get(cend); 17 | 18 | if start >= 28510128 && end <= 33480000 { 19 | 20 | let type_ = record.get(ctype); 21 | 22 | if type_ == ["gene"] { 23 | record_indices.push(ix); 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /shaders/nodes/base.tesc: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (vertices = 4) out; 4 | 5 | layout (location = 0) in int[] vs_node_id; 6 | 7 | layout (location = 0) out int[] node_id; 8 | 9 | layout (push_constant) uniform NodePC { 10 | mat4 view_transform; 11 | float node_width; 12 | float scale; 13 | vec2 viewport_dims; 14 | uint texture_period; 15 | } node_uniform; 16 | 17 | 18 | void main() { 19 | 20 | gl_TessLevelInner[0] = 1.0; 21 | gl_TessLevelInner[1] = 1.0; 22 | 23 | gl_TessLevelOuter[0] = 1.0; 24 | gl_TessLevelOuter[1] = 1.0; 25 | gl_TessLevelOuter[2] = 1.0; 26 | gl_TessLevelOuter[3] = 1.0; 27 | 28 | node_id[gl_InvocationID] = vs_node_id[gl_InvocationID % 2]; 29 | gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID % 2].gl_Position; 30 | } 31 | -------------------------------------------------------------------------------- /shaders/edges/edges.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #define VERTICES_PER_NODE 2 4 | 5 | layout (location = 0) in vec2 position; 6 | 7 | layout (push_constant) uniform NodePC { 8 | mat4 view_transform; 9 | float node_width; 10 | float scale; 11 | vec2 viewport_dims; 12 | uint texture_period; 13 | } node_uniform; 14 | 15 | void main() { 16 | vec4 pos = node_uniform.view_transform * vec4(position, 0.0, 1.0); 17 | // vec4 pos = vo.view * vec4(position, 0.0, 1.0); 18 | // gl_Position = vo.view * vec4(position, 0.0, 1.0); 19 | 20 | // NodeIds are 1-indexed 21 | // int id = 1 + (gl_VertexIndex / VERTICES_PER_NODE); 22 | // node_id = id; 23 | 24 | // float z = float(node_id) / 1500.0; 25 | gl_Position = vec4(pos.x, pos.y, 0.0, pos.w); 26 | // gl_Position = vec4(pos.x, pos.y, 0.6, pos.w); 27 | } 28 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod app; 2 | pub mod context; 3 | pub mod reactor; 4 | 5 | pub mod geometry; 6 | pub mod vulkan; 7 | 8 | pub mod annotations; 9 | pub mod graph_query; 10 | pub mod gui; 11 | pub mod overlays; 12 | 13 | pub mod gfa; 14 | pub mod quad_tree; 15 | pub mod universe; 16 | 17 | pub mod input; 18 | pub mod view; 19 | 20 | pub mod asynchronous; 21 | // pub mod gluon; 22 | pub mod script; 23 | 24 | pub mod window; 25 | 26 | #[macro_export] 27 | macro_rules! include_shader { 28 | ($file:expr) => { 29 | include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/shaders/", $file)) 30 | }; 31 | } 32 | 33 | #[macro_export] 34 | macro_rules! load_shader { 35 | ($path:literal) => {{ 36 | let buf = crate::include_shader!($path); 37 | let mut cursor = std::io::Cursor::new(buf); 38 | ash::util::read_spv(&mut cursor).unwrap() 39 | }}; 40 | } 41 | -------------------------------------------------------------------------------- /shaders/post/pixels_buffer_copy.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (set = 0, binding = 0) readonly buffer Pixels { 4 | uint pixel[]; 5 | } pixels; 6 | 7 | layout (location = 0) out vec4 f_color; 8 | 9 | layout (push_constant) uniform Dims { 10 | vec2 texture_size; 11 | vec2 screen_size; 12 | bool enabled; 13 | } dims; 14 | 15 | // vec2 uv_coord(vec2 coord) { 16 | // return (coord / vec2(dims.width, dims.height)); 17 | // } 18 | 19 | void main() { 20 | vec2 fc = gl_FragCoord.xy / dims.texture_size; 21 | 22 | // vec2 uv = fc; 23 | 24 | // vec4 color = texture(u_color_sampler, uv); 25 | 26 | uvec2 uv = uvec2(fc); 27 | 28 | uvec2 u_fc = uvec2(gl_FragCoord.xy); 29 | 30 | uint index = (u_fc.y * uint(dims.texture_size.x)) + u_fc.x; 31 | 32 | uint pixel = pixels.pixel[index]; 33 | 34 | float value = float(pixel) / 255.0; 35 | 36 | vec4 color = vec4(0.0, 0.0, 0.0, value); 37 | 38 | f_color = color; 39 | } 40 | -------------------------------------------------------------------------------- /shaders/edges/edge_orient.glsl: -------------------------------------------------------------------------------- 1 | uvec2 oriented_edge_ixs(uvec2 handles) { 2 | 3 | uint left_id = (handles.x >> 1) - 1; 4 | uint right_id = (handles.y >> 1) - 1; 5 | 6 | bool left_rev = (handles.x & 1) == 1; 7 | bool right_rev = (handles.x & 1) == 1; 8 | 9 | 10 | uint left_l = left_id * 2; 11 | uint left_r = left_l + 1; 12 | 13 | uint right_l = right_id * 2; 14 | uint right_r = right_l + 1; 15 | 16 | 17 | uint left_ix; 18 | uint right_ix; 19 | 20 | if (!left_rev && !right_rev) { 21 | left_ix = left_r; 22 | right_ix = right_l; 23 | 24 | } else if (left_rev && !right_rev) { 25 | left_ix = left_l; 26 | right_ix = right_l; 27 | 28 | } else if (!left_rev && right_rev) { 29 | left_ix = left_r; 30 | right_ix = right_r; 31 | 32 | } else if (left_rev && right_rev) { 33 | left_ix = left_l; 34 | right_ix = right_r; 35 | 36 | } 37 | 38 | return uvec2(left_ix, right_ix); 39 | } 40 | -------------------------------------------------------------------------------- /src/overlays.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 2 | /// Defines the type of mapping from node ID to colors used by an 3 | /// overlay script 4 | pub enum OverlayKind { 5 | /// Overlay scripts that produce an RGB color for each node 6 | RGB, 7 | /// Overlay scripts that produce a single value for each node, 8 | /// that can then be mapped to a color, e.g. using a perceptual 9 | /// color scheme 10 | Value, 11 | } 12 | 13 | pub enum OverlayData { 14 | RGB(Vec>), 15 | Value(Vec), 16 | } 17 | 18 | pub fn hash_node_color(hash: u64) -> (f32, f32, f32) { 19 | let r_u16 = ((hash >> 32) & 0xFFFFFFFF) as u16; 20 | let g_u16 = ((hash >> 16) & 0xFFFFFFFF) as u16; 21 | let b_u16 = (hash & 0xFFFFFFFF) as u16; 22 | 23 | let max = r_u16.max(g_u16).max(b_u16) as f32; 24 | let r = (r_u16 as f32) / max; 25 | let g = (g_u16 as f32) / max; 26 | let b = (b_u16 as f32) / max; 27 | (r, g, b) 28 | } 29 | -------------------------------------------------------------------------------- /scripts/util/animate_example.rhai: -------------------------------------------------------------------------------- 1 | fn animate(delay_ms, paths) { 2 | let graph = get_graph(); 3 | 4 | for path in paths { 5 | let steps = path_steps(path); 6 | let selection = build_selection(steps); 7 | set_selection(selection); 8 | goto_selection(); 9 | 10 | thread_sleep(delay_ms); 11 | } 12 | } 13 | 14 | fn anim_follow_path(delay_ms, speed, path) { 15 | let graph = get_graph(); 16 | 17 | let step = graph.path_first_step(path); 18 | 19 | if speed < 100 { 20 | speed = 100; 21 | } 22 | 23 | loop { 24 | 25 | let selection = build_selection([]); 26 | 27 | for count in range(0, speed) { 28 | let handle = graph.path_handle_at_step(path, step); 29 | 30 | selection.add_one(handle.id()); 31 | 32 | step = graph.next_step(path, step); 33 | if !graph.has_next_step(path, step) { 34 | break; 35 | } 36 | } 37 | 38 | set_selection(selection); 39 | goto_selection(); 40 | 41 | thread_sleep(delay_ms); 42 | 43 | } 44 | } -------------------------------------------------------------------------------- /shaders/nodes/base.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #define VERTICES_PER_NODE 2 4 | 5 | layout (location = 0) in vec2 position; 6 | layout (location = 0) out int node_id; 7 | 8 | layout (push_constant) uniform NodePC { 9 | mat4 view_transform; 10 | float node_width; 11 | float scale; 12 | vec2 viewport_dims; 13 | uint texture_period; 14 | } node_uniform; 15 | 16 | void main() { 17 | 18 | int id = 1 + (gl_VertexIndex / VERTICES_PER_NODE); 19 | node_id = id; 20 | 21 | gl_Position = vec4(position.xy, 0.0, 1.0); 22 | 23 | /* 24 | vec4 pos = node_uniform.view_transform * vec4(position, 0.0, 1.0); 25 | // vec4 pos = vo.view * vec4(position, 0.0, 1.0); 26 | // gl_Position = vo.view * vec4(position, 0.0, 1.0); 27 | 28 | // NodeIds are 1-indexed 29 | int id = 1 + (gl_VertexIndex / VERTICES_PER_NODE); 30 | node_id = id; 31 | 32 | // float z = float(node_id) / 1500.0; 33 | gl_Position = vec4(pos.x, pos.y, 0.0, pos.w); 34 | // gl_Position = vec4(pos.x, pos.y, 0.6, pos.w); 35 | */ 36 | } 37 | -------------------------------------------------------------------------------- /shaders/nodes/overlay_rgb.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (early_fragment_tests) in; 4 | 5 | flat layout (location = 0) in int node_id; 6 | 7 | layout (location = 0) out vec4 f_color; 8 | layout (location = 1) out uint f_id; 9 | layout (location = 2) out vec4 f_mask; 10 | 11 | layout (set = 0, binding = 0) uniform samplerBuffer overlay; 12 | 13 | layout (set = 1, binding = 0) readonly buffer Selection { 14 | uint flag[]; 15 | } selection; 16 | 17 | layout (push_constant) uniform NodePC { 18 | mat4 view_transform; 19 | float node_width; 20 | float scale; 21 | vec2 viewport_dims; 22 | uint texture_period; 23 | } node_uniform; 24 | 25 | void main() { 26 | 27 | uint is_selected = selection.flag[node_id - 1]; 28 | 29 | f_id = uint(node_id); 30 | 31 | if ((is_selected & 1) == 1) { 32 | f_mask = vec4(1.0, 1.0, 1.0, 1.0); 33 | } else { 34 | f_mask = vec4(0.0, 0.0, 0.0, 0.0); 35 | } 36 | 37 | 38 | int color_u = node_id - 1; 39 | f_color = texelFetch(overlay, color_u); 40 | } 41 | -------------------------------------------------------------------------------- /shaders/compute/rect_select.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (set = 0, binding = 0) buffer Selection { 4 | int data[]; 5 | } selection; 6 | 7 | layout (set = 0, binding = 1) readonly buffer Nodes { 8 | vec4 pos[]; 9 | } nodes; 10 | 11 | layout (push_constant) uniform Rect { 12 | vec2 top_left; 13 | vec2 bottom_right; 14 | uint node_count; 15 | } rect; 16 | 17 | layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in; 18 | 19 | bool point_in_rect(vec2 point, vec2 tl, vec2 br) { 20 | return point.x >= tl.x && point.x <= br.x 21 | && point.y >= tl.y && point.y <= br.y; 22 | } 23 | 24 | void main() { 25 | uint index = gl_GlobalInvocationID.x; 26 | 27 | if (index < rect.node_count) { 28 | vec2 p0 = nodes.pos[index].xy; 29 | vec2 p1 = nodes.pos[index].zw; 30 | 31 | if (point_in_rect(p0, rect.top_left, rect.bottom_right) || 32 | point_in_rect(p1, rect.top_left, rect.bottom_right)) { 33 | selection.data[index] = 1; 34 | } else { 35 | selection.data[index] = 0; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/gui/windows/settings/debug.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, Copy)] 2 | pub struct DebugSettings { 3 | pub(crate) view_info: bool, 4 | pub(crate) cursor_info: bool, 5 | 6 | pub(crate) egui_inspection: bool, 7 | pub(crate) egui_settings: bool, 8 | pub(crate) egui_memory: bool, 9 | } 10 | 11 | impl std::default::Default for DebugSettings { 12 | fn default() -> Self { 13 | Self { 14 | view_info: false, 15 | cursor_info: false, 16 | 17 | egui_inspection: false, 18 | egui_settings: false, 19 | egui_memory: false, 20 | } 21 | } 22 | } 23 | 24 | impl DebugSettings { 25 | pub fn ui(&mut self, ui: &mut egui::Ui) { 26 | ui.checkbox(&mut self.view_info, "Viewport Info"); 27 | ui.checkbox(&mut self.cursor_info, "Cursor Info"); 28 | 29 | ui.separator(); 30 | ui.label("Egui Debug Windows"); 31 | 32 | ui.checkbox(&mut self.egui_inspection, "Inspection"); 33 | ui.checkbox(&mut self.egui_settings, "Settings"); 34 | ui.checkbox(&mut self.egui_memory, "Memory"); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021 Christian Fischer 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /shaders/nodes/overlay_value.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (early_fragment_tests) in; 4 | 5 | flat layout (location = 0) in int node_id; 6 | 7 | layout (location = 0) out vec4 f_color; 8 | layout (location = 1) out uint f_id; 9 | layout (location = 2) out vec4 f_mask; 10 | 11 | layout (set = 0, binding = 0) uniform sampler1D overlay; 12 | 13 | layout (set = 0, binding = 1) readonly buffer OverlayValue { 14 | float value[]; 15 | } node_value; 16 | 17 | layout (set = 1, binding = 0) readonly buffer Selection { 18 | uint flag[]; 19 | } selection; 20 | 21 | layout (push_constant) uniform NodePC { 22 | mat4 view_transform; 23 | float node_width; 24 | float scale; 25 | vec2 viewport_dims; 26 | uint texture_period; 27 | } node_uniform; 28 | 29 | void main() { 30 | uint is_selected = selection.flag[node_id - 1]; 31 | 32 | f_id = uint(node_id); 33 | 34 | if ((is_selected & 1) == 1) { 35 | f_mask = vec4(1.0, 1.0, 1.0, 1.0); 36 | } else { 37 | f_mask = vec4(0.0, 0.0, 0.0, 0.0); 38 | } 39 | 40 | 41 | float node_val = node_value.value[node_id - 1]; 42 | f_color = texture(overlay, node_val); 43 | } 44 | -------------------------------------------------------------------------------- /shaders/edges/edges.tese: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | // #include "ubo.glsl" 4 | 5 | // layout (isolines, equal_spacing, ccw) in; 6 | layout (isolines, fractional_odd_spacing, ccw) in; 7 | 8 | // layout (set = 0, binding = 0) uniform UBOStruct 9 | // { 10 | // UBO ubo; 11 | // } ubo; 12 | 13 | /* 14 | layout (std140, set = 0, binding = 0) uniform UBO 15 | { 16 | // UBO ubo; 17 | vec4 edge_color; 18 | float edge_width; 19 | 20 | float tess_levels[5]; 21 | 22 | float curve_offset; 23 | } ubo; 24 | */ 25 | 26 | float curve_modulation(float x) { 27 | return -0.8 * (x * x - x); 28 | } 29 | 30 | vec2 norm_diff(vec2 v0, vec2 v1) { 31 | vec2 diff = v1 - v0; 32 | return mat2x2(0.0, 1.0, -1.0, 0.0) * diff; 33 | } 34 | 35 | void main() { 36 | 37 | float u = gl_TessCoord.x; 38 | float v = gl_TessCoord.y; 39 | 40 | vec2 curvature = curve_modulation(u) * 41 | norm_diff(gl_in[0].gl_Position.xy, 42 | gl_in[1].gl_Position.xy); 43 | 44 | 45 | gl_Position = (u * gl_in[0].gl_Position) + 46 | ((1.0 - u) * gl_in[1].gl_Position) + 47 | vec4(curvature, 0.0, 0.0); 48 | } 49 | -------------------------------------------------------------------------------- /shaders/nodes/themed.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (early_fragment_tests) in; 4 | 5 | flat layout (location = 0) in int node_id; 6 | 7 | layout (location = 0) out vec4 f_color; 8 | layout (location = 1) out uint f_id; 9 | layout (location = 2) out vec4 f_mask; 10 | 11 | layout (set = 0, binding = 0) uniform sampler1D theme_sampler; 12 | 13 | layout (set = 1, binding = 0) readonly buffer Selection { 14 | uint flag[]; 15 | } selection; 16 | 17 | // layout (set = 1, binding = 0) buffer Data { 18 | // uint data[]; 19 | // } data; 20 | 21 | layout (push_constant) uniform NodePC { 22 | mat4 view_transform; 23 | float node_width; 24 | float scale; 25 | vec2 viewport_dims; 26 | uint texture_period; 27 | } node_uniform; 28 | 29 | void main() { 30 | uint is_selected = selection.flag[node_id - 1]; 31 | 32 | f_id = uint(node_id); 33 | 34 | if ((is_selected & 1) == 1) { 35 | f_mask = vec4(1.0, 1.0, 1.0, 1.0); 36 | } else { 37 | f_mask = vec4(0.0, 0.0, 0.0, 0.0); 38 | } 39 | 40 | float color_u = float((node_id - 1) % node_uniform.texture_period) / node_uniform.texture_period; 41 | f_color = texture(theme_sampler, color_u); 42 | } 43 | -------------------------------------------------------------------------------- /shaders/edges/edge_preprocess.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #include "edge_orient.glsl" 4 | 5 | layout (local_size_x = 1024) in; 6 | 7 | layout (set = 0, binding = 0) readonly buffer Nodes { 8 | vec2 pos[]; 9 | } nodes; 10 | 11 | layout (set = 0, binding = 1) readonly buffer Edges { 12 | uvec2 edge[]; 13 | } edges; 14 | 15 | layout (set = 0, binding = 2) writeonly buffer EdgeCurves { 16 | uint edge_count; 17 | vec2 curve[]; 18 | } curves; 19 | 20 | layout (push_constant) uniform PushConstants { 21 | uint edge_count; 22 | vec4 visible_area; 23 | vec2 viewport_size; 24 | } pc; 25 | 26 | 27 | void main() { 28 | if (gl_GlobalInvocationID.xyz == uvec3(0)) { 29 | curves.edge_count = 0; 30 | } 31 | 32 | barrier(); 33 | 34 | uint edge_ix = gl_LocalInvocationIndex; 35 | 36 | uvec2 this_edge = edges.edge[edge_ix]; 37 | uvec2 oriented_ixs = oriented_edge_ixs(this_edge); 38 | 39 | vec2 p0 = nodes.pos[oriented_ixs.x]; 40 | vec2 p1 = nodes.pos[oriented_ixs.y]; 41 | 42 | // TODO compare points to viewport & filter; 43 | // need to either calculate or pass the visible area 44 | 45 | uint offset = atomicAdd(curves.edge_count, 1); 46 | 47 | curves.curve[offset] = p0; 48 | curves.curve[offset + 1] = p1; 49 | } 50 | -------------------------------------------------------------------------------- /shaders/edges/quads.tesc: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (vertices = 4) out; 4 | 5 | layout (std140, set = 0, binding = 0) uniform UBO 6 | { 7 | vec4 edge_color; 8 | float edge_width; 9 | 10 | float tess_levels[5]; 11 | 12 | float curve_offset; 13 | } ubo; 14 | 15 | 16 | layout (push_constant) uniform NodePC { 17 | mat4 view_transform; 18 | float node_width; 19 | float scale; 20 | vec2 viewport_dims; 21 | uint texture_period; 22 | } node_uniform; 23 | 24 | 25 | float tess_level(float len) { 26 | if (len < 0.001) { 27 | return 0.0; 28 | } else if (len < 0.01) { 29 | return 4.0; 30 | } else if (len < 0.05) { 31 | return 8.0; 32 | } else if (len < 0.1) { 33 | return 16.0; 34 | } else if (len < 0.4) { 35 | return 24.0; 36 | } else { 37 | return 32.0; 38 | } 39 | } 40 | 41 | void main() { 42 | 43 | float len = length(gl_in[0].gl_Position - gl_in[1].gl_Position); 44 | 45 | float tess = tess_level(len); 46 | 47 | gl_TessLevelInner[0] = tess; 48 | gl_TessLevelInner[1] = tess; 49 | 50 | gl_TessLevelOuter[0] = tess; 51 | gl_TessLevelOuter[1] = tess; 52 | gl_TessLevelOuter[2] = tess; 53 | gl_TessLevelOuter[3] = tess; 54 | 55 | gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID % 2].gl_Position; 56 | } 57 | -------------------------------------------------------------------------------- /shaders/compute/bin3.comp: -------------------------------------------------------------------------------- 1 | /* 2 | Insert each node position into the bins, filling the 3 | bin buffer with the bins represented as subarrays 4 | 5 | Calculated over the node indices 6 | */ 7 | 8 | #version 450 9 | 10 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 11 | 12 | layout (set = 0, binding = 0) readonly buffer Nodes { 13 | vec2 pos[]; 14 | } nodes; 15 | 16 | layout (set = 0, binding = 1) buffer NodeBins { 17 | int bin[]; 18 | } node_bins; 19 | 20 | layout (set = 0, binding = 2) buffer NodeBinOffsets { 21 | uint offset[]; 22 | } node_bin_offset; 23 | 24 | layout (set = 0, binding = 3) buffer BinOffsets { 25 | uint offset_len[]; 26 | } bin_offsets; 27 | 28 | layout (set = 0, binding = 4) buffer Bins { 29 | uint node[]; 30 | } bins; 31 | 32 | layout (push_constant) uniform BinDefinition { 33 | vec2 top_left; 34 | uint rows; 35 | uint columns; 36 | uint node_count; 37 | } bin_def; 38 | 39 | 40 | void main() { 41 | 42 | uint index = gl_GlobalInvocationID.x; 43 | 44 | int bin_id = node_bins.bin[index]; 45 | 46 | if (bin_id == -1) { 47 | return; 48 | } 49 | 50 | uint bin_offset = bin_offsets.offset_len[bin_id]; 51 | uint inner_offset = node_bin_offset.offset[index]; 52 | 53 | bins.node[bin_offset + inner_offset] = index; 54 | } 55 | -------------------------------------------------------------------------------- /shaders/gui/gui.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | // #extension GL_EXT_debug_printf : enable 3 | 4 | layout (location = 0) in vec2 pos; 5 | layout (location = 1) in vec2 uv; 6 | layout (location = 2) in vec4 color; 7 | 8 | layout (location = 0) out vec4 vs_color; 9 | layout (location = 1) out vec2 vs_uv; 10 | 11 | layout (push_constant) uniform ScreenSize { 12 | float width; 13 | float height; 14 | } screen_size; 15 | 16 | // layout (set = 0, binding = 0) uniform View { 17 | // mat4 view; 18 | // } vo; 19 | 20 | // taken from the egui glium example 21 | // 0-1 linear from 0-255 sRGB 22 | vec3 linear_from_srgb(vec3 srgb) { 23 | bvec3 cutoff = lessThan(srgb, vec3(10.31475)); 24 | vec3 lower = srgb / vec3(3294.6); 25 | vec3 higher = pow((srgb + vec3(14.025)) / vec3(269.025), vec3(2.4)); 26 | return mix(higher, lower, cutoff); 27 | } 28 | 29 | vec4 linear_from_srgba(vec4 srgba) { 30 | vec3 srgb = srgba.xyz * 255.0; 31 | return vec4(linear_from_srgb(srgb), srgba.a); 32 | } 33 | 34 | void main() { 35 | 36 | gl_Position = vec4( 37 | 2.0 * pos.x / screen_size.width - 1.0, 38 | 2.0 * pos.y / screen_size.height - 1.0, 39 | 0.0, 40 | 1.0); 41 | 42 | vs_color = color; 43 | // vs_color = linear_from_srgba(color); 44 | 45 | vs_uv = uv; 46 | } 47 | -------------------------------------------------------------------------------- /src/vulkan/draw_system.rs: -------------------------------------------------------------------------------- 1 | use ash::version::DeviceV1_0; 2 | use ash::{vk, Device}; 3 | 4 | use bytemuck::{Pod, Zeroable}; 5 | 6 | pub mod edges; 7 | pub mod gui; 8 | pub mod nodes; 9 | pub mod post; 10 | pub mod selection; 11 | 12 | #[derive(Clone, Copy, Zeroable, Pod)] 13 | #[repr(C)] 14 | pub struct Vertex { 15 | pub position: [f32; 2], 16 | } 17 | 18 | impl Vertex { 19 | fn get_binding_desc() -> vk::VertexInputBindingDescription { 20 | vk::VertexInputBindingDescription::builder() 21 | .binding(0) 22 | .stride(std::mem::size_of::() as u32) 23 | .input_rate(vk::VertexInputRate::VERTEX) 24 | .build() 25 | } 26 | 27 | fn get_attribute_descs() -> [vk::VertexInputAttributeDescription; 1] { 28 | let pos_desc = vk::VertexInputAttributeDescription::builder() 29 | .binding(0) 30 | .location(0) 31 | .format(vk::Format::R32G32_SFLOAT) 32 | .offset(0) 33 | .build(); 34 | 35 | [pos_desc] 36 | } 37 | } 38 | 39 | pub(crate) fn create_shader_module( 40 | device: &Device, 41 | code: &[u32], 42 | ) -> vk::ShaderModule { 43 | let create_info = vk::ShaderModuleCreateInfo::builder().code(code).build(); 44 | unsafe { device.create_shader_module(&create_info, None).unwrap() } 45 | } 46 | -------------------------------------------------------------------------------- /shaders/edges/edges.tesc: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (vertices = 2) out; 4 | 5 | layout (std140, set = 0, binding = 0) uniform UBO 6 | { 7 | vec4 edge_color; 8 | float edge_width; 9 | 10 | float tess_levels[5]; 11 | 12 | float curve_offset; 13 | } ubo; 14 | 15 | 16 | layout (push_constant) uniform NodePC { 17 | mat4 view_transform; 18 | float node_width; 19 | float scale; 20 | vec2 viewport_dims; 21 | uint texture_period; 22 | } node_uniform; 23 | 24 | 25 | int tess_level_ix(float len) { 26 | if (len < 0.001) { 27 | return -1; 28 | } else if (len < 0.01) { 29 | return 0; 30 | } else if (len < 0.05) { 31 | return 1; 32 | } else if (len < 0.1) { 33 | return 2; 34 | } else if (len < 0.4) { 35 | return 3; 36 | } else { 37 | return 4; 38 | } 39 | } 40 | 41 | void main() { 42 | 43 | float len = length(gl_in[0].gl_Position - gl_in[1].gl_Position); 44 | 45 | int index = tess_level_ix(len); 46 | 47 | if (index == -1) { 48 | gl_TessLevelOuter[0] = 0.0; 49 | } else { 50 | float tess = ubo.tess_levels[tess_level_ix(len)]; 51 | if (gl_InvocationID == 0) { 52 | gl_TessLevelInner[0] = 1.0; 53 | gl_TessLevelOuter[0] = 2.0; 54 | gl_TessLevelOuter[1] = tess; 55 | } 56 | } 57 | 58 | gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position; 59 | } 60 | -------------------------------------------------------------------------------- /shaders/compute/bin2.comp: -------------------------------------------------------------------------------- 1 | /* 2 | Calculate the index ranges for each bin in the Bins buffer 3 | 4 | Computed over the bins 5 | */ 6 | 7 | #version 450 8 | 9 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 10 | 11 | layout (set = 0, binding = 0) readonly buffer Nodes { 12 | vec2 pos[]; 13 | } nodes; 14 | 15 | layout (set = 0, binding = 1) buffer NodeBins { 16 | int bin[]; 17 | } node_bins; 18 | 19 | layout (set = 0, binding = 2) buffer NodeBinOffsets { 20 | uint offset[]; 21 | } node_bin_offset; 22 | 23 | layout (set = 0, binding = 3) buffer BinOffsets { 24 | uint offset_len[]; 25 | } bin_offsets; 26 | 27 | /* 28 | layout (set = 0, binding = 3) buffer Bins { 29 | uint node[]; 30 | } bins; 31 | */ 32 | 33 | 34 | layout (push_constant) uniform BinDefinition { 35 | vec2 top_left; 36 | uint rows; 37 | uint columns; 38 | uint node_count; 39 | } bin_def; 40 | 41 | 42 | void main() { 43 | uint index = gl_GlobalInvocationID.x; 44 | 45 | if (index >= bin_def.rows * bin_def.columns 46 | || index == 0) { 47 | return; 48 | } 49 | 50 | uint len_ix = uint(index * 2) + 1; 51 | uint offset = 0; 52 | 53 | for (uint x = 0; x < index; x++) { 54 | uint sub_len_ix = uint(x * 2) + 1; 55 | 56 | offset += bin_offsets.offset_len[sub_len_ix]; 57 | } 58 | 59 | bin_offsets.offset_len[index * 2] = offset; 60 | 61 | } 62 | -------------------------------------------------------------------------------- /shaders/edges/edge.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (local_size_x = 16, local_size_y = 16, local_size_z = 1) in; 4 | 5 | layout (rgba8, set = 0, binding = 0) writeonly uniform image2D dst; 6 | 7 | layout (push_constant) uniform Dimensions { 8 | vec2 screen_size; 9 | vec2 tile_texture_size; 10 | } dims; 11 | 12 | void main() { 13 | 14 | vec2 pos = vec2(gl_GlobalInvocationID.xy); 15 | 16 | 17 | // vec2 uv = 2.0 * ((pos / dims.screen_size) - vec2(0.5)); 18 | 19 | vec2 uv = (pos) / dims.tile_texture_size; 20 | // uv = uv - vec2(1.0); 21 | // uv = uv * 2.0; 22 | 23 | // uv = uv * (dims.screen_size / dims.tile_texture_size); 24 | 25 | ivec2 pixel = ivec2(pos); 26 | 27 | /* 28 | 29 | vec4 color; 30 | 31 | if ((gl_WorkGroupID.x + gl_WorkGroupID.y) % 2 == 0) { 32 | color = vec4(1.0, 1.0, 1.0, 0.7); 33 | } else { 34 | color = vec4(0.0, 0.0, 0.0, 0.7); 35 | } 36 | */ 37 | 38 | 39 | /* 40 | for (uint y = 0; y < 16; y++) { 41 | for (uint x = 0; x < 16; x++) { 42 | 43 | ivec2 point = ivec2(gl_WorkGroupID.xy); 44 | point = point + ivec2(x, y); 45 | imageStore(dst, point, color); 46 | } 47 | } 48 | */ 49 | 50 | 51 | if ((gl_WorkGroupID.x + gl_WorkGroupID.y) % 2 == 0) { 52 | // if (gl_WorkGroupID.x % 2 == 0 53 | // || gl_WorkGroupID.y % 2 == 0) { 54 | // imageStore(dst, pixel, color); 55 | imageStore(dst, pixel, vec4(0.0, 0.0, 0.0, 0.5)); 56 | } else { 57 | imageStore(dst, pixel, vec4(0.0, 0.0, 0.0, 0.0)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /scripts/mhc_test.rhai: -------------------------------------------------------------------------------- 1 | // replace the path and names as appropriate 2 | // these assignments aren't necessary, just make the code cleaner 3 | load_collection("/home/christian/data/Homo_sapiens.GRCh38.103.chr.gff3"); 4 | let gff3_name = "Homo_sapiens.GRCh38.103.chr.gff3"; 5 | let ref_path_name = "grch38#chr6:28510128-33480000"; 6 | 7 | // all these assignments aren't necessary, just make the code cleaner 8 | let graph = get_graph(); let ref_path = 9 | graph.get_path_id(ref_path_name); let coll = 10 | get_collection(gff3_name); 11 | 12 | let cseqid = gff3_column("SeqId"); 13 | let cstart = gff3_column("Start"); 14 | let cend = gff3_column("End"); 15 | let cname = gff3_column("Name"); 16 | let ctype = gff3_column("Type"); 17 | 18 | let record_indices = []; 19 | 20 | // this loop filters the records by chromosome and position 21 | for ix in range(0, coll.len()) { 22 | let record = coll.get_record(ix); 23 | 24 | let seq_id = record.get(cseqid); 25 | 26 | if seq_id == ["6"] { 27 | let start = record.get(cstart); 28 | let end = record.get(cend); 29 | 30 | if start >= 28510128 && end <= 33480000 { 31 | 32 | let type_ = record.get(ctype); 33 | 34 | if type_ == ["gene"] { 35 | record_indices.push(ix); 36 | } 37 | } 38 | } 39 | } 40 | 41 | // this function takes an annotation collection, a filtered list of 42 | // record indices, a reference path ID, a column to use, and the name of 43 | // the label set 44 | 45 | create_label_set(coll, record_indices, ref_path, cname, "genes"); -------------------------------------------------------------------------------- /shaders/nodes/quad.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | #define VERTICES_PER_NODE 6 4 | 5 | layout (location = 0) in vec2 position; 6 | layout (location = 0) out int node_id; 7 | 8 | layout (push_constant) uniform NodePC { 9 | mat4 view_transform; 10 | float node_width; 11 | float scale; 12 | vec2 viewport_dims; 13 | uint texture_period; 14 | } node_uniform; 15 | 16 | void main() { 17 | 18 | int id = 1 + (gl_VertexIndex / VERTICES_PER_NODE); 19 | node_id = id; 20 | 21 | int vx_mod = gl_VertexIndex % VERTICES_PER_NODE; 22 | 23 | // assuming the node is seen lying horizontally from left to right, 24 | // 0 -> bottom left 25 | // 1 -> top right 26 | // 2 -> top left 27 | // 28 | // 3 -> bottom left 29 | // 4 -> bottom right 30 | // 5 -> top right 31 | 32 | float del = 0.01; 33 | 34 | vec2 offset; 35 | 36 | switch (vx_mod) { 37 | case 0: 38 | offset = vec2(0.0, -del); 39 | break; 40 | case 1: 41 | offset = vec2(0.0, del); 42 | break; 43 | case 2: 44 | offset = vec2(0.0, del); 45 | break; 46 | case 3: 47 | offset = vec2(0.0, -del); 48 | break; 49 | case 4: 50 | offset = vec2(0.0, -del); 51 | break; 52 | case 5: 53 | offset = vec2(0.0, del); 54 | break; 55 | default: 56 | offset = vec2(0.0, 0.0); 57 | break; 58 | } 59 | 60 | vec4 pos = node_uniform.view_transform * vec4(position.xy, 0.0, 1.0); 61 | pos.x += offset.x; 62 | pos.y += offset.y; 63 | 64 | 65 | gl_Position = pos; 66 | } 67 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gfaestus" 3 | version = "0.0.1" 4 | authors = ["christian "] 5 | edition = "2018" 6 | license = "MIT" 7 | repository = "https://github.com/chfi/gfaestus" 8 | readme = "README.md" 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | ash = "0.32" 14 | ash-window = "0.6" 15 | vk-mem = "0.2.2" 16 | 17 | winit = "0.25" 18 | # winit = { version = "0.25", default-features = false, features = ["x11"] } 19 | egui = "0.15" 20 | clipboard = "0.5" 21 | 22 | crossbeam = "0.8" 23 | parking_lot = "0.11.1" 24 | rayon = "1.5" 25 | futures = { version = "0.3", features = ["thread-pool"] } 26 | futures-timer = { version = "3.0.2" } 27 | 28 | rgb = "0.8.25" 29 | colorous = "1.0.5" 30 | 31 | nalgebra = "0.24" 32 | nalgebra-glm = "0.10" 33 | 34 | gfa = { version = "0.10" } 35 | handlegraph = "0.7.0-alpha.7" 36 | 37 | lazy_static = "1.4.0" 38 | anyhow = "1.0" 39 | 40 | rustc-hash = "1.1" 41 | 42 | bstr = "0.2" 43 | 44 | rhai = { version = "1.7", features = ["sync", "f32_float", "metadata", "internals"] } 45 | 46 | version_check = { version = "0.9.4" } 47 | 48 | bytemuck = { version = "1.7", features = ["derive"] } 49 | 50 | num_cpus = "1.13.0" 51 | 52 | log = "0.4" 53 | flexi_logger = { version = "0.18", features = ["async"] } 54 | argh = "0.1.5" 55 | 56 | rand = "0.8" 57 | 58 | [profile.dev] 59 | opt-level = 2 60 | 61 | 62 | [patch.crates-io] 63 | handlegraph = { git = "https://github.com/chfi/rs-handlegraph" } 64 | -------------------------------------------------------------------------------- /scripts/util/points_of_interest.rhai: -------------------------------------------------------------------------------- 1 | // `add_point_of_interest()` will add a numbered label to the center of the current selection 2 | // `next_point_of_interest()` will translate the view so that the next 3 | // label is centered (repeating endlessly) 4 | 5 | // in the console, you can bind these to keys, with 6 | // :import scripts/util/points_of_interest.rhai 7 | // bind_key("C", "add_point_of_interest"); 8 | // bind_key("N", "next_point_of_interest"); 9 | 10 | // the number of points can be reset: 11 | // set_var("_points", []); 12 | 13 | fn add_point_of_interest() { 14 | try_initialize(); 15 | 16 | let point = get_selection_center(); 17 | let id = label_count().to_string(); 18 | let label_text = id; 19 | 20 | let points = get_var("_points"); 21 | points.push(point); 22 | 23 | set_var("_points", points); 24 | 25 | add_label(id, label_text, point); 26 | } 27 | 28 | fn next_point_of_interest() { 29 | try_initialize(); 30 | 31 | let ix = get_var("_count"); 32 | let points = get_var("_points"); 33 | 34 | try { 35 | let pt = points[ix]; 36 | set_view_origin(pt); 37 | 38 | let new_ix = ix + 1; 39 | if new_ix >= points.len() { 40 | new_ix = 0; 41 | } 42 | 43 | set_var("_count", new_ix); 44 | } 45 | catch { 46 | set_var("_count", 0); 47 | } 48 | } 49 | 50 | fn try_initialize() { 51 | try { 52 | let counter = get_var("_count"); 53 | let points = get_var("_points"); 54 | } 55 | catch { 56 | set_var("_count", 0); 57 | set_var("_points", []); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /shaders/nodes/base.tese: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | // layout (quads, equal_spacing, ccw) in; 4 | // layout (isolines, equal_spacing, ccw) in; 5 | layout (quads, equal_spacing, ccw) in; 6 | 7 | layout (location = 0) in int[] in_node_id; 8 | 9 | layout (location = 0) out int node_id; 10 | 11 | layout (push_constant) uniform NodePC { 12 | mat4 view_transform; 13 | float node_width; 14 | float scale; 15 | vec2 viewport_dims; 16 | uint texture_period; 17 | } node_uniform; 18 | 19 | void main() { 20 | float u = gl_TessCoord.x; 21 | float v = gl_TessCoord.y; 22 | 23 | vec2 p = gl_in[0].gl_Position.xy; 24 | vec2 q = gl_in[1].gl_Position.xy; 25 | 26 | float node_width = node_uniform.node_width / (node_uniform.scale * 27 | max(node_uniform.viewport_dims.x, 28 | node_uniform.viewport_dims.y)); 29 | 30 | vec4 p_ = node_uniform.view_transform * gl_in[0].gl_Position; 31 | vec4 q_ = node_uniform.view_transform * gl_in[1].gl_Position; 32 | 33 | vec2 diff = q_.xy - p_.xy; 34 | vec2 n_diff = normalize(diff); 35 | 36 | vec2 rn_diff = vec2(-n_diff.y, n_diff.x); 37 | vec4 rot_diff = vec4(rn_diff.xy, 0.0, 0.0); 38 | 39 | vec4 tl = p_ + rot_diff * node_width; 40 | vec4 tr = p_ - rot_diff * node_width; 41 | vec4 bl = q_ + rot_diff * node_width; 42 | vec4 br = q_ - rot_diff * node_width; 43 | 44 | vec4 pos1 = mix(tl, tr, gl_TessCoord.x); 45 | vec4 pos2 = mix(bl, br, gl_TessCoord.x); 46 | vec4 pos = mix(pos1, pos2, gl_TessCoord.y); 47 | 48 | gl_Position = pos; 49 | // gl_Position = node_uniform.view_transform * pos; 50 | 51 | node_id = in_node_id[0]; 52 | } 53 | -------------------------------------------------------------------------------- /src/app/channels.rs: -------------------------------------------------------------------------------- 1 | use crossbeam::channel::{self, Receiver, Sender}; 2 | use winit::event::VirtualKeyCode; 3 | 4 | use crate::app::mainview::MainViewMsg; 5 | use crate::app::AppMsg; 6 | use crate::gui::GuiMsg; 7 | use crate::overlays::OverlayData; 8 | 9 | pub type BindMsg = ( 10 | VirtualKeyCode, 11 | Option>, 12 | ); 13 | 14 | pub enum OverlayCreatorMsg { 15 | NewOverlay { name: String, data: OverlayData }, 16 | } 17 | 18 | #[derive(Clone)] 19 | pub struct AppChannels { 20 | pub app_tx: Sender, 21 | pub app_rx: Receiver, 22 | 23 | pub main_view_tx: Sender, 24 | pub main_view_rx: Receiver, 25 | 26 | pub gui_tx: Sender, 27 | pub gui_rx: Receiver, 28 | 29 | pub new_overlay_tx: Sender, 30 | pub new_overlay_rx: Receiver, 31 | 32 | pub modal_tx: Sender>, 33 | pub modal_rx: Receiver>, 34 | } 35 | 36 | impl AppChannels { 37 | pub(super) fn new() -> Self { 38 | let (app_tx, app_rx) = channel::unbounded::(); 39 | let (main_view_tx, main_view_rx) = channel::unbounded::(); 40 | let (gui_tx, gui_rx) = channel::unbounded::(); 41 | let (binds_tx, binds_rx) = channel::unbounded::(); 42 | let (new_overlay_tx, new_overlay_rx) = 43 | channel::unbounded::(); 44 | 45 | let (modal_tx, modal_rx) = channel::unbounded(); 46 | 47 | Self { 48 | app_tx, 49 | app_rx, 50 | 51 | main_view_tx, 52 | main_view_rx, 53 | 54 | gui_tx, 55 | gui_rx, 56 | 57 | new_overlay_tx, 58 | new_overlay_rx, 59 | 60 | modal_tx, 61 | modal_rx, 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 80 2 | hard_tabs = false 3 | tab_spaces = 4 4 | newline_style = "Auto" 5 | use_small_heuristics = "Default" 6 | indent_style = "Block" 7 | wrap_comments = false 8 | format_doc_comments = false 9 | comment_width = 80 10 | normalize_comments = false 11 | normalize_doc_attributes = false 12 | license_template_path = "" 13 | format_strings = false 14 | format_macro_matchers = false 15 | format_macro_bodies = true 16 | empty_item_single_line = true 17 | struct_lit_single_line = true 18 | fn_single_line = false 19 | where_single_line = false 20 | imports_indent = "Block" 21 | imports_layout = "Mixed" 22 | merge_imports = false 23 | reorder_imports = true 24 | reorder_modules = true 25 | reorder_impl_items = false 26 | type_punctuation_density = "Wide" 27 | space_before_colon = false 28 | space_after_colon = true 29 | spaces_around_ranges = false 30 | binop_separator = "Front" 31 | remove_nested_parens = true 32 | combine_control_expr = true 33 | overflow_delimited_expr = false 34 | struct_field_align_threshold = 0 35 | enum_discrim_align_threshold = 0 36 | match_arm_blocks = true 37 | force_multiline_blocks = false 38 | fn_args_density = "Tall" 39 | brace_style = "SameLineWhere" 40 | control_brace_style = "AlwaysSameLine" 41 | trailing_semicolon = true 42 | trailing_comma = "Vertical" 43 | match_block_trailing_comma = false 44 | blank_lines_upper_bound = 1 45 | blank_lines_lower_bound = 0 46 | edition = "2015" 47 | version = "One" 48 | merge_derives = true 49 | use_try_shorthand = false 50 | use_field_init_shorthand = false 51 | force_explicit_abi = true 52 | condense_wildcard_suffixes = false 53 | color = "Auto" 54 | required_version = "1.0.3" 55 | unstable_features = false 56 | disable_all_formatting = false 57 | skip_children = false 58 | hide_parse_errors = false 59 | error_on_line_overflow = false 60 | error_on_unformatted = false 61 | report_todo = "Never" 62 | report_fixme = "Never" 63 | ignore = [] 64 | emit_mode = "Files" 65 | make_backup = false 66 | -------------------------------------------------------------------------------- /shaders/compute/bin1.comp: -------------------------------------------------------------------------------- 1 | /* 2 | Calculate the bin membership for each node position, 3 | the size of each bin, 4 | and the offset into each bin for each node. 5 | 6 | Computed over the nodes 7 | */ 8 | 9 | #version 450 10 | 11 | layout(local_size_x = 64, local_size_y = 1, local_size_z = 1) in; 12 | 13 | layout (set = 0, binding = 0) readonly buffer Nodes { 14 | vec2 pos[]; 15 | } nodes; 16 | 17 | layout (set = 0, binding = 1) buffer NodeBins { 18 | int bin[]; 19 | } node_bins; 20 | 21 | layout (set = 0, binding = 2) buffer NodeBinOffsets { 22 | uint offset[]; 23 | } node_bin_offset; 24 | 25 | layout (set = 0, binding = 3) buffer BinOffsets { 26 | uint offset_len[]; 27 | } bin_offsets; 28 | 29 | /* 30 | layout (set = 0, binding = 3) buffer Bins { 31 | uint node[]; 32 | } bins; 33 | */ 34 | 35 | layout (push_constant) uniform BinDefinition { 36 | vec2 top_left; 37 | uint rows; 38 | uint columns; 39 | uint node_count; 40 | } bin_def; 41 | 42 | 43 | int bin_for_pos(vec2 pos) { 44 | if (pos.x < bin_def.top_left.x || pos.y < bin_def.top_left.y) { 45 | return -1; 46 | } 47 | 48 | vec2 local_pos = pos - bin_def.top_left; 49 | 50 | uint column = uint(local_pos.x / float(bin_def.columns)); 51 | uint row = uint(local_pos.y / float(bin_def.rows)); 52 | 53 | if (column >= bin_def.columns || row >= bin_def.rows) { 54 | return -1; 55 | } 56 | 57 | return int((row * bin_def.columns) + column); 58 | } 59 | 60 | 61 | void main() { 62 | 63 | uint index = gl_GlobalInvocationID.x; 64 | 65 | if (index < bin_def.node_count) { 66 | vec2 node_pos = nodes.pos[index]; 67 | 68 | int bin_ix = bin_for_pos(node_pos); 69 | 70 | node_bins.bin[index] = bin_ix; 71 | 72 | if (bin_ix == -1) { 73 | return; 74 | } 75 | 76 | uint len_ix = uint(bin_ix * 2) + 1; 77 | 78 | uint offset = atomicAdd(bin_offsets.offset_len[len_ix], 1); 79 | 80 | node_bin_offset.offset[index] = offset; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/universe/selection.rs: -------------------------------------------------------------------------------- 1 | use handlegraph::handle::NodeId; 2 | use rustc_hash::FxHashSet; 3 | 4 | use crate::geometry::*; 5 | 6 | use super::Node; 7 | 8 | pub struct Selection { 9 | bounding_box: Rect, 10 | nodes: FxHashSet, 11 | } 12 | 13 | impl Selection { 14 | pub fn singleton(node_positions: &[Node], node: NodeId) -> Self { 15 | let ix = (node.0 - 1) as usize; 16 | 17 | let node_pos = node_positions[ix]; 18 | let bounding_box = Rect::new(node_pos.p0, node_pos.p1); 19 | 20 | let mut nodes = FxHashSet::default(); 21 | nodes.insert(node); 22 | 23 | Self { 24 | bounding_box, 25 | nodes, 26 | } 27 | } 28 | 29 | pub fn from_iter(node_positions: &[Node], nodes_iter: I) -> Self 30 | where 31 | I: Iterator, 32 | { 33 | let mut bounding_box = Rect::nowhere(); 34 | let mut nodes = FxHashSet::default(); 35 | 36 | for node in nodes_iter { 37 | let ix = (node.0 - 1) as usize; 38 | let node_pos = node_positions[ix]; 39 | 40 | let rect = Rect::new(node_pos.p0, node_pos.p1); 41 | bounding_box = bounding_box.union(rect); 42 | 43 | nodes.insert(node); 44 | } 45 | 46 | Self { 47 | bounding_box, 48 | nodes, 49 | } 50 | } 51 | 52 | pub fn union(self, other: Self) -> Self { 53 | let bounding_box = self.bounding_box.union(other.bounding_box); 54 | 55 | let nodes = self 56 | .nodes 57 | .union(&other.nodes) 58 | .copied() 59 | .collect::>(); 60 | 61 | Self { 62 | bounding_box, 63 | nodes, 64 | } 65 | } 66 | 67 | pub fn union_from(&mut self, other: &Self) { 68 | self.bounding_box = self.bounding_box.union(other.bounding_box); 69 | self.nodes.extend(other.nodes.iter().copied()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /shaders/post/post_blur.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (set = 0, binding = 0) uniform sampler2D u_color_sampler; 4 | 5 | layout (location = 0) out vec4 f_color; 6 | 7 | layout (push_constant) uniform Dims { 8 | float width; 9 | float height; 10 | bool enabled; 11 | } dims; 12 | 13 | vec2 uv_coord(vec2 coord) { 14 | return (coord / vec2(dims.width, dims.height)); 15 | } 16 | 17 | void main() { 18 | 19 | vec2 uv = gl_FragCoord.xy / vec2(dims.width, dims.height); 20 | vec4 fc = gl_FragCoord; 21 | 22 | vec4 color = texture(u_color_sampler, uv); 23 | 24 | float row0[3]; 25 | row0[0] = 0.077847; 26 | row0[1] = 0.123317; 27 | row0[2] = 0.077847; 28 | 29 | float row1[3]; 30 | row1[0] = 0.123317; 31 | row1[1] = 0.195346; 32 | row1[2] = 0.123317; 33 | 34 | float row2[3]; 35 | row2[0] = 0.077847; 36 | row2[1] = 0.123317; 37 | row2[2] = 0.077847; 38 | 39 | 40 | if (dims.enabled) { 41 | vec3 result = texture(u_color_sampler, uv).rgb * row1[1]; 42 | 43 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(-1.0, -1.0))).rgb * row0[0]; 44 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(-1.0, 0.0))).rgb * row0[1]; 45 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(-1.0, 1.0))).rgb * row0[2]; 46 | 47 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(0.0, -1.0))).rgb * row1[0]; 48 | 49 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(0.0, 1.0))).rgb * row1[2]; 50 | 51 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(1.0, -1.0))).rgb * row2[0]; 52 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(1.0, 0.0))).rgb * row2[1]; 53 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(1.0, 1.0))).rgb * row2[2]; 54 | 55 | f_color = vec4(result, color.a); 56 | // f_color = vec4(1.0, 1.0, 1.0, 1.0); 57 | } else { 58 | // vec3 result = texture(u_color_sampler, uv).rgb; 59 | f_color = color; 60 | // f_color = vec4(result, color.a); 61 | // f_color = vec4(0.0, 0.0, 0.0, 1.0); 62 | // f_color = vec4(1.0, 1.0, 1.0, 1.0); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/universe/graph_layout.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(unused_variables)] 3 | 4 | #[allow(unused_imports)] 5 | use handlegraph::{ 6 | handle::{Direction, Handle, NodeId}, 7 | handlegraph::*, 8 | mutablehandlegraph::*, 9 | packed::*, 10 | pathhandlegraph::*, 11 | }; 12 | 13 | #[allow(unused_imports)] 14 | use handlegraph::packedgraph::PackedGraph; 15 | 16 | use crate::geometry::*; 17 | 18 | #[allow(unused_imports)] 19 | use anyhow::Result; 20 | 21 | pub struct LayoutQuadtree { 22 | // data: Vec, 23 | // node_offsets: Vec, 24 | data: Vec, // use u32 instead of NodeId to ease mapping to GPU 25 | tree_node_offsets: Vec, // same here w/ u32 vs usize 26 | 27 | elements: usize, 28 | leaf_capacity: usize, 29 | depth: usize, 30 | 31 | polynomial_t: usize, 32 | } 33 | 34 | impl LayoutQuadtree { 35 | pub fn truncated(nodes: &[Point], leaf_capacity: usize) -> Self { 36 | /* 37 | let depth = ((nodes.len() / leaf_capacity) as f64).log2().floor(); 38 | let depth = (depth as usize).max(1); 39 | 40 | let elements = nodes.len(); 41 | 42 | // no idea if this is even close to correct; should probably 43 | // take the node count & initial layout size into account here 44 | let polynomial_t = 1_000_000; 45 | 46 | let mut data: Vec = Vec::with_capacity(elements); 47 | let mut tree_node_offsets: Vec = Vec::with_capacity(elements); 48 | */ 49 | 50 | unimplemented!(); 51 | } 52 | 53 | fn map_coordinate( 54 | point: Point, 55 | min_p: Point, 56 | max_p: Point, 57 | poly_t: usize, 58 | node_count: usize, 59 | ) -> (usize, usize) { 60 | let offset_point = point - min_p; 61 | 62 | let x_f = offset_point.x / (max_p.x - min_p.x); 63 | let y_f = offset_point.y / (max_p.y - min_p.y); 64 | 65 | let coef = poly_t * node_count * node_count; 66 | 67 | let x = (x_f * (coef as f32)) as usize; 68 | let y = (y_f * (coef as f32)) as usize; 69 | 70 | (x, y) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /shaders/edges/quads.tese: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (quads, fractional_odd_spacing, ccw) in; 4 | 5 | layout (std140, set = 0, binding = 0) uniform UBO 6 | { 7 | // UBO ubo; 8 | vec4 edge_color; 9 | float edge_width; 10 | 11 | float tess_levels[5]; 12 | 13 | float curve_offset; 14 | } ubo; 15 | 16 | layout (push_constant) uniform NodePC { 17 | mat4 view_transform; 18 | float node_width; 19 | float scale; 20 | vec2 viewport_dims; 21 | uint texture_period; 22 | } node_uniform; 23 | 24 | float curve_modulation(float x) { 25 | return -0.8 * (x * x - x); 26 | } 27 | 28 | vec2 norm_diff(vec2 v0, vec2 v1) { 29 | vec2 diff = v1 - v0; 30 | return mat2x2(0.0, 1.0, -1.0, 0.0) * diff; 31 | } 32 | 33 | void main() { 34 | float u = gl_TessCoord.x; 35 | float v = gl_TessCoord.y; 36 | 37 | vec2 p = gl_in[0].gl_Position.xy; 38 | vec2 q = gl_in[1].gl_Position.xy; 39 | 40 | vec4 p_ = node_uniform.view_transform * gl_in[0].gl_Position; 41 | vec4 q_ = node_uniform.view_transform * gl_in[1].gl_Position; 42 | 43 | vec2 diff = q_.xy - p_.xy; 44 | vec2 n_diff = normalize(diff); 45 | 46 | vec2 rn_diff = vec2(-n_diff.y, n_diff.x); 47 | vec4 rot_diff = vec4(rn_diff.xy, 0.0, 0.0); 48 | 49 | // float edge_width = ubo.edge_width / (node_uniform.scale * max(node_uniform.viewport_dims.x, 50 | // node_uniform.viewport_dims.y)); 51 | 52 | // float edge_width = ubo.edge_width / (node_uniform.scale * 100.0); 53 | // float edge_width = ubo.edge_width / 1000.0; 54 | float edge_width = ubo.edge_width / max(node_uniform.viewport_dims.x, 55 | node_uniform.viewport_dims.y); 56 | 57 | vec4 tl = p_ + rot_diff * edge_width; 58 | vec4 tr = p_ - rot_diff * edge_width; 59 | vec4 bl = q_ + rot_diff * edge_width; 60 | vec4 br = q_ - rot_diff * edge_width; 61 | 62 | vec4 pos1 = mix(tl, tr, gl_TessCoord.x); 63 | vec4 pos2 = mix(bl, br, gl_TessCoord.x); 64 | vec4 pos = mix(pos1, pos2, gl_TessCoord.y); 65 | 66 | vec2 curvature = curve_modulation(v) * 67 | norm_diff(p_.xy, q_.xy); 68 | 69 | 70 | gl_Position = pos + vec4(curvature, 0.0, 0.0); 71 | } 72 | -------------------------------------------------------------------------------- /shaders/compute/path_view_val.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (set = 0, binding = 0) readonly buffer Paths { 4 | uint node[]; 5 | } paths; 6 | 7 | layout (rgba8, set = 0, binding = 1) writeonly uniform image2D image; 8 | 9 | layout (set = 1, binding = 0) uniform sampler1D overlay; 10 | 11 | layout (set = 1, binding = 1) readonly buffer OverlayValue { 12 | float value[]; 13 | } node_value; 14 | 15 | layout (push_constant) uniform PushConstants { 16 | uint path_count; 17 | uint width; 18 | uint height; 19 | uint dummy; 20 | float translation; 21 | float scaling; 22 | } pc; 23 | 24 | layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in; 25 | 26 | uint max_ix = 2048 * 64; 27 | 28 | void main() { 29 | 30 | uint path = gl_GlobalInvocationID.y; 31 | 32 | uint ix = (path * 2048) + gl_GlobalInvocationID.x; 33 | 34 | ivec2 pixel = ivec2(gl_GlobalInvocationID.xy); 35 | 36 | pixel.x += int(pc.translation); 37 | float del = 512.0; 38 | float x_ = float(pixel.x) - del; 39 | x_ = x_ / pc.scaling; 40 | x_ += del; 41 | pixel.x = clamp(int(floor(x_)), 0, 2047); 42 | 43 | if (path < pc.path_count) { 44 | // read node at this pixel in the path 45 | uint node = paths.node[ix]; 46 | 47 | int x = pixel.x; 48 | 49 | uint l_ix = x == 0 ? ix : ix - 1; 50 | uint r_ix = x >= 2047 ? ix : ix + 1; 51 | 52 | uint l_n = paths.node[l_ix]; 53 | uint c_n = paths.node[ix]; 54 | uint r_n = paths.node[r_ix]; 55 | 56 | if (c_n == 0) { 57 | imageStore(image, pixel, vec4(1.0, 1.0, 1.0, 0.0)); 58 | 59 | } else { 60 | int mid = int(c_n) - 1; 61 | int left = int(l_n) - 1; 62 | int right = int(r_n) - 1; 63 | 64 | float vm = node_value.value[mid]; 65 | float vl = node_value.value[left]; 66 | float vr = node_value.value[right]; 67 | 68 | vec4 cm = texture(overlay, vm); 69 | vec4 cl = texture(overlay, vl); 70 | vec4 cr = texture(overlay, vr); 71 | 72 | // vec4 color = cm; 73 | // vec4 color = (0.3 * cl) + (0.3 * cr) + (0.5 * cm); 74 | vec4 color = (0.2 * cl) + (0.2 * cr) + (0.6 * cm); 75 | // vec4 color = (0.1 * cl) + (0.1 * cr) + (0.8 * cm); 76 | 77 | imageStore(image, pixel, color); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/gui/debug.rs: -------------------------------------------------------------------------------- 1 | use crate::view::View; 2 | use crate::{geometry::Point, view::ScreenDims}; 3 | 4 | pub struct ViewDebugInfo; 5 | 6 | impl ViewDebugInfo { 7 | pub fn ui(ctx: &egui::CtxRef, view: View) { 8 | let screen_rect = ctx.input().screen_rect(); 9 | 10 | egui::Area::new("view_debug_info") 11 | .movable(true) 12 | .show(ctx, |ui| { 13 | ui.label(format!( 14 | "Center: ({}, {})", 15 | view.center.x, view.center.y 16 | )); 17 | ui.label(format!("Scale: {}", view.scale)); 18 | 19 | ui.separator(); 20 | 21 | let dims = Point { 22 | x: screen_rect.width(), 23 | y: screen_rect.height(), 24 | }; 25 | 26 | let visible_top_left = 27 | view.screen_point_to_world(dims, Point::ZERO); 28 | let visible_bottom_right = 29 | view.screen_point_to_world(dims, dims); 30 | 31 | ui.label("Visible area"); 32 | ui.label(format!( 33 | "Top left: ({}, {})", 34 | visible_top_left.x, visible_top_left.y, 35 | )); 36 | ui.label(format!( 37 | "Bottom right: ({}, {})", 38 | visible_bottom_right.x, visible_bottom_right.y, 39 | )); 40 | }); 41 | } 42 | } 43 | 44 | pub struct MouseDebugInfo; 45 | 46 | impl MouseDebugInfo { 47 | pub fn ui(ctx: &egui::CtxRef, view: View, mouse_screen: Point) { 48 | let screen_rect = ctx.input().screen_rect(); 49 | 50 | let dims = ScreenDims { 51 | width: screen_rect.width(), 52 | height: screen_rect.height(), 53 | }; 54 | 55 | let screen = mouse_screen; 56 | let world = view.screen_point_to_world(dims, screen); 57 | 58 | egui::Area::new("mouse_debug_info") 59 | .movable(true) 60 | .show(ctx, |ui| { 61 | ui.label("Cursor position"); 62 | 63 | ui.separator(); 64 | 65 | ui.label(format!("Screen: ({}, {})", screen.x, screen.y)); 66 | ui.label(format!("World: ({}, {})", world.x, world.y)); 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/asynchronous.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crossbeam::atomic::AtomicCell; 4 | use futures::{ 5 | executor::ThreadPool, 6 | future::{Future, RemoteHandle}, 7 | task::SpawnExt, 8 | }; 9 | 10 | pub struct AsyncResult { 11 | future: Option>, 12 | result: Option, 13 | ready: Arc>, 14 | } 15 | 16 | impl AsyncResult { 17 | pub fn new(thread_pool: &ThreadPool, future: Fut) -> Self 18 | where 19 | Fut: Future + Send + 'static, 20 | { 21 | let is_ready = Arc::new(AtomicCell::new(false)); 22 | let inner_is_ready = is_ready.clone(); 23 | 24 | let future = async move { 25 | let output = future.await; 26 | inner_is_ready.store(true); 27 | output 28 | }; 29 | 30 | let handle = thread_pool.spawn_with_handle(future).unwrap(); 31 | 32 | Self { 33 | future: Some(handle), 34 | result: None, 35 | 36 | ready: is_ready, 37 | } 38 | } 39 | 40 | pub fn is_ready(&self) -> bool { 41 | self.ready.load() 42 | } 43 | } 44 | 45 | impl AsyncResult { 46 | pub fn get_result_if_ready(&mut self) -> Option<&T> { 47 | if !self.is_ready() { 48 | return None; 49 | } 50 | 51 | if self.result.is_some() { 52 | return self.result.as_ref(); 53 | } 54 | 55 | if let Some(future) = self.future.take() { 56 | let value = futures::executor::block_on(future); 57 | self.result = Some(value); 58 | } 59 | 60 | self.result.as_ref() 61 | } 62 | 63 | pub fn move_result_if_ready(&mut self) { 64 | if !self.is_ready() || self.result.is_some() { 65 | return; 66 | } 67 | 68 | if let Some(future) = self.future.take() { 69 | let value = futures::executor::block_on(future); 70 | self.result = Some(value); 71 | } 72 | } 73 | 74 | pub fn get_result(&self) -> Option<&T> { 75 | self.result.as_ref() 76 | } 77 | 78 | pub fn take_result_if_ready(&mut self) -> Option { 79 | if !self.is_ready() { 80 | return None; 81 | } 82 | 83 | self.move_result_if_ready(); 84 | 85 | self.result.take() 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/universe/physics.rs: -------------------------------------------------------------------------------- 1 | // use crate::geometry::*; 2 | 3 | /* 4 | use super::Spine; 5 | 6 | pub fn repulsion_spines(t: f32, spines: &mut [Spine]) { 7 | let force_mult = 1000.0; 8 | 9 | let mut forces: Vec<(usize, Vec)> = Vec::new(); 10 | 11 | // let mut forces: Vec<(usize, Point)> = Vec::new(); 12 | 13 | for (ix, spine) in spines.iter().enumerate() { 14 | let mut spine_forces: Vec = Vec::new(); 15 | 16 | let s_offset = spine.offset; 17 | let _s_angle = spine.angle; 18 | 19 | for (n_ix, node) in spine.nodes.iter().enumerate() { 20 | let mut force = Point { x: 0.0, y: 0.0 }; 21 | let n_mid = node.center() + s_offset; 22 | 23 | for (o_ix, o_spine) in spines.iter().enumerate() { 24 | let _o_angle = o_spine.angle; 25 | 26 | for (o_n_ix, other) in o_spine.nodes.iter().enumerate() { 27 | if ix == o_ix && n_ix == o_n_ix { 28 | continue; 29 | } 30 | 31 | let o_mid = other.center() + o_spine.offset; 32 | let toward = n_mid.toward(o_mid); 33 | 34 | let dist = n_mid.dist(o_mid); 35 | let dist_clamp = 1.0_f32.max(dist); 36 | 37 | // let mag = t * (force_mult / dist.powi(2)); 38 | // let mag = t * (force_mult / dist_clamp.powi(2)); 39 | let mag = t * (force_mult / dist_clamp.powi(2)); 40 | 41 | let this_force = toward * mag; 42 | force += this_force; 43 | if force.x.is_nan() || force.y.is_nan() { 44 | println!(" node.p0: {}, {}", node.p0.x, node.p0.y); 45 | println!(" node.p1: {}, {}", node.p1.x, node.p1.y); 46 | println!("other.p0: {}, {}", other.p0.x, other.p0.y); 47 | println!("other.p1: {}, {}", other.p1.x, other.p1.y); 48 | std::process::exit(1); 49 | } 50 | } 51 | } 52 | spine_forces.push(force); 53 | } 54 | forces.push((ix, spine_forces)); 55 | } 56 | 57 | for (ix, spine) in spines.iter_mut().enumerate() { 58 | let (_, spine_forces) = &forces[ix]; 59 | for (n_ix, node) in spine.nodes.iter_mut().enumerate() { 60 | let force = spine_forces[n_ix]; 61 | node.p0 += force; 62 | node.p1 += force; 63 | } 64 | } 65 | } 66 | 67 | */ 68 | -------------------------------------------------------------------------------- /shaders/compute/path_view.comp: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (set = 0, binding = 0) readonly buffer Paths { 4 | uint node[]; 5 | } paths; 6 | 7 | layout (rgba8, set = 0, binding = 1) writeonly uniform image2D image; 8 | 9 | layout (set = 1, binding = 0) uniform samplerBuffer overlay; 10 | 11 | layout (push_constant) uniform PushConstants { 12 | uint path_count; 13 | uint width; 14 | uint height; 15 | uint dummy; 16 | float translation; 17 | float scaling; 18 | } pc; 19 | 20 | layout(local_size_x = 256, local_size_y = 1, local_size_z = 1) in; 21 | 22 | uint max_ix = 2048 * 64; 23 | 24 | uint clamped(uint ix) { 25 | return clamp(ix, 0, max_ix); 26 | } 27 | 28 | void main() { 29 | 30 | uint path = gl_GlobalInvocationID.y; 31 | 32 | uint ix = (path * 2048) + gl_GlobalInvocationID.x; 33 | 34 | uint leftmost = path * 2048; 35 | uint rightmost = leftmost + 2047; 36 | 37 | ivec2 pixel = ivec2(gl_GlobalInvocationID.xy); 38 | 39 | int orig_x = pixel.x; 40 | 41 | pixel.x += int(pc.translation); 42 | float del = 512.0; 43 | float x_ = float(pixel.x) - del; 44 | x_ = x_ / pc.scaling; 45 | x_ += del; 46 | pixel.x = clamp(int(floor(x_)), 0, 2047); 47 | 48 | 49 | if (path < pc.path_count) { 50 | // read node at this pixel in the path 51 | int x = pixel.x; 52 | 53 | uint l_ix = x == 0 ? ix : ix - 1; 54 | uint r_ix = x >= 2047 ? ix : ix + 1; 55 | 56 | uint l_n = paths.node[l_ix]; 57 | uint c_n = paths.node[ix]; 58 | uint r_n = paths.node[r_ix]; 59 | 60 | if (c_n == 0) { 61 | imageStore(image, pixel, vec4(1.0, 1.0, 1.0, 0.0)); 62 | 63 | } else { 64 | int mid = int(c_n) - 1; 65 | int left = int(l_n) - 1; 66 | int right = int(r_n) - 1; 67 | 68 | vec4 cm = texelFetch(overlay, mid); 69 | vec4 cl = cm; 70 | vec4 cr = cm; 71 | 72 | if (l_n != 0) { 73 | cl = texelFetch(overlay, left); 74 | } 75 | 76 | if (r_n != 0) { 77 | cr = texelFetch(overlay, right); 78 | } 79 | 80 | // vec4 color = cm; 81 | // vec4 color = (0.3 * cl) + (0.3 * cr) + (0.5 * cm); 82 | vec4 color = (0.2 * cl) + (0.2 * cr) + (0.6 * cm); 83 | // vec4 color = (0.1 * cl) + (0.1 * cr) + (0.8 * cm); 84 | 85 | if (pc.translation < 0.0 && orig_x < pc.translation) { 86 | int n = int(paths.node[leftmost]); 87 | color = texelFetch(overlay, n); 88 | } else if (pc.translation < 0.0 && orig_x >= (2048.0 - pc.translation)) { 89 | int n = int(paths.node[rightmost]); 90 | color = texelFetch(overlay, n); 91 | } 92 | 93 | imageStore(image, pixel, color); 94 | } 95 | 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /shaders/post/post_edge.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | 3 | layout (set = 0, binding = 0) uniform sampler2D u_color_sampler; 4 | 5 | layout (location = 0) out vec4 f_color; 6 | 7 | layout (push_constant) uniform Dims { 8 | float width; 9 | float height; 10 | bool enabled; 11 | } dims; 12 | 13 | vec2 uv_coord(vec2 coord) { 14 | return (coord / vec2(dims.width, dims.height)); 15 | } 16 | 17 | 18 | float edge_ver(vec4 fc, vec2 uv) { 19 | 20 | float row0[3]; 21 | row0[0] = 1.0; 22 | row0[1] = 0.0; 23 | row0[2] = -1.0; 24 | 25 | float row1[3]; 26 | row1[0] = 2.0; 27 | row1[1] = 0.0; 28 | row1[2] = -2.0; 29 | 30 | float row2[3]; 31 | row2[0] = 1.0; 32 | row2[1] = 0.0; 33 | row2[2] = -1.0; 34 | 35 | float result = texture(u_color_sampler, uv).r * row1[1]; 36 | 37 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(-1.0, -1.0))).r * row0[0]; 38 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(-1.0, 1.0))).r * row0[2]; 39 | 40 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(0.0, -1.0))).r * row1[0]; 41 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(0.0, 1.0))).r * row1[2]; 42 | 43 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(1.0, -1.0))).r * row2[0]; 44 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(1.0, 1.0))).r * row2[2]; 45 | 46 | return result; 47 | } 48 | 49 | float edge_hor(vec4 fc, vec2 uv) { 50 | 51 | float row0[3]; 52 | row0[0] = 1.0; 53 | row0[1] = 2.0; 54 | row0[2] = 1.0; 55 | 56 | float row2[3]; 57 | row2[0] = -1.0; 58 | row2[1] = -2.0; 59 | row2[2] = -1.0; 60 | 61 | float result = 0.0; 62 | 63 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(-1.0, -1.0))).r * row0[0]; 64 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(-1.0, 0.0))).r * row0[1]; 65 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(-1.0, 1.0))).r * row0[2]; 66 | 67 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(1.0, -1.0))).r * row2[0]; 68 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(1.0, 0.0))).r * row2[1]; 69 | result += texture(u_color_sampler, uv_coord(fc.xy + vec2(1.0, 1.0))).r * row2[2]; 70 | 71 | return result; 72 | } 73 | 74 | void main() { 75 | 76 | vec2 uv = gl_FragCoord.xy / vec2(dims.width, dims.height); 77 | vec4 fc = gl_FragCoord; 78 | 79 | vec4 color = texture(u_color_sampler, uv); 80 | 81 | if (dims.enabled) { 82 | float ver = abs(edge_ver(fc, uv)); 83 | float hor = abs(edge_hor(fc, uv)); 84 | 85 | float result = max(hor, ver); 86 | 87 | f_color = vec4(result, result, result, result); 88 | 89 | } else { 90 | f_color = color; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/gui/windows/settings.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | app::{AppSettings, SharedState}, 3 | geometry::Point, 4 | }; 5 | 6 | pub mod debug; 7 | pub mod gui; 8 | pub mod main_view; 9 | 10 | use debug::*; 11 | use gui::*; 12 | use main_view::*; 13 | 14 | pub struct SettingsWindow { 15 | current_tab: SettingsTab, 16 | 17 | pub(crate) debug: DebugSettings, 18 | pub(crate) gui: GuiSettings, 19 | pub(crate) main_view: MainViewSettings, 20 | } 21 | 22 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Eq, Ord)] 23 | enum SettingsTab { 24 | MainView, 25 | Debug, 26 | Gui, 27 | } 28 | 29 | impl SettingsWindow { 30 | pub const ID: &'static str = "settings_window"; 31 | 32 | pub fn new(settings: &AppSettings, shared_state: &SharedState) -> Self { 33 | let current_tab = SettingsTab::MainView; 34 | 35 | let main_view = 36 | MainViewSettings::new(settings, shared_state.edges_enabled.clone()); 37 | 38 | Self { 39 | current_tab, 40 | 41 | debug: Default::default(), 42 | gui: Default::default(), 43 | main_view, 44 | } 45 | } 46 | 47 | pub fn ui( 48 | &mut self, 49 | ctx: &egui::CtxRef, 50 | open: &mut bool, 51 | // ) -> Option { 52 | ) -> Option>> { 53 | egui::Window::new("Settings") 54 | .id(egui::Id::new(Self::ID)) 55 | .open(open) 56 | .default_pos(Point::new(300.0, 300.0)) 57 | .show(ctx, |ui| { 58 | ui.horizontal(|ui| { 59 | ui.selectable_value( 60 | &mut self.current_tab, 61 | SettingsTab::MainView, 62 | "Main View", 63 | ); 64 | ui.selectable_value( 65 | &mut self.current_tab, 66 | SettingsTab::Gui, 67 | "GUI", 68 | ); 69 | ui.selectable_value( 70 | &mut self.current_tab, 71 | SettingsTab::Debug, 72 | "Debug", 73 | ); 74 | }); 75 | 76 | match self.current_tab { 77 | SettingsTab::MainView => { 78 | self.main_view.ui(ui); 79 | } 80 | SettingsTab::Debug => { 81 | self.debug.ui(ui); 82 | } 83 | SettingsTab::Gui => { 84 | self.gui.ui(ui); 85 | } 86 | } 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gfaestus - Vulkan-accelerated GFA visualization 2 | 3 | ### Demo: https://youtu.be/TOJZeeCqatk 4 | 5 | `gfaestus` is a tool for visualizing and interacting with genome graphs 6 | in the [GFA format](http://gfa-spec.github.io/GFA-spec/GFA1.html). 7 | 8 | It can display GFA graphs using a provided 2D layout (produced with 9 | [odgi's](https://github.com/vgteam/odgi) `layout` command), and is 10 | intended to deliver an interactive visual interface for exploring 11 | genome graphs that is fast, powerful, and easy to use. 12 | 13 | 14 | In addition to the 2D layout, a 15 | [handlegraph](https://github.com/chfi/rs-handlegraph) representation 16 | of the GFA is created, which will enable visualizations and 17 | interactivity that take advantage of the graph topology, paths, and 18 | more. 19 | 20 | 21 | `gfaestus` uses Vulkan for hardware-acceleration, via the 22 | [`ash`](https://crates.io/crates/ash) crate. 23 | 24 | 25 | 26 | ## Requirements 27 | 28 | Compiling `gfaestus` requires the Vulkan SDK, available here: https://vulkan.lunarg.com/sdk/home 29 | 30 | To run `gfaestus`, you must have a GPU with drivers that support 31 | 32 | Vulkan. If you're on Windows or Linux, and have an AMD, Nvidia, or 33 | integrated Intel GPU, you should be good to go. 34 | 35 | If you're on Mac, you'll need to install [MoltenVK](https://github.com/KhronosGroup/MoltenVK). 36 | 37 | 38 | ## Usage 39 | 40 | With a working Vulkan SDK environment (make sure you have `glslc` on 41 | your path), you can build `gfaestus` using `cargo`. 42 | 43 | 44 | ```sh 45 | cargo build --release 46 | ``` 47 | 48 | Due to technical reasons, `gfaestus` must be run from the repo 49 | directory for shaders and scripts to be found. An easy way to 50 | do this is to use the generated shell script, which can be 51 | copied to your PATH: 52 | 53 | ```sh 54 | ./gfaestus-release 55 | 56 | # use whichever directory on your path you like 57 | cp gfaestus-release ~/.local/bin/gfaestus 58 | gfaestus 59 | ``` 60 | 61 | 62 | ### Instructions 63 | 64 | The easiest way to display annotations is via the `BED Label Wizard` 65 | under `Tools` in the top menubar. 66 | 67 | You can also load a custom TSV format, which makes it easy to assign 68 | colors and labels to nodes. 69 | 70 | Each line assigns either a color, in CSS-style hexadecimal format, or 71 | a text string, to a node. For example, for node 1223: 72 | 73 | ``` 74 | 1223 #44BBCC 75 | 1223 this line is a label 76 | ``` 77 | 78 | #### Keyboard 79 | 80 | * `Space`: Reset view 81 | * `Arrow keys`: Pan view 82 | * `Escape`: Clear selection 83 | * `F9` - Toggle light/dark mode 84 | 85 | #### Mouse 86 | 87 | * `Left Mouse`: Select node 88 | * `Left Mouse (click and drag)`: Pan view 89 | * `Shift + Left Mouse (click and drag)`: Rectangle select 90 | * `Scroll wheel`: Zoom view 91 | 92 | * `Right Mouse`: Context menu 93 | -------------------------------------------------------------------------------- /src/gui/windows/util.rs: -------------------------------------------------------------------------------- 1 | pub struct SlotList { 2 | // display: Box FnMut(&'a egui::Ui, T) -> egui::Response> 3 | display: Box egui::Response>, 4 | 5 | offset: usize, 6 | slot_count: usize, 7 | 8 | indices: Vec, 9 | } 10 | 11 | impl SlotList { 12 | pub fn new(slot_count: usize, display: F) -> Self 13 | where 14 | F: Fn(&mut egui::Ui, &T) -> egui::Response + 'static, 15 | { 16 | Self { 17 | display: Box::new(display), 18 | 19 | offset: 0, 20 | slot_count, 21 | 22 | indices: Vec::new(), 23 | } 24 | } 25 | 26 | pub fn display(&self, ui: &mut egui::Ui, value: &T) -> egui::Response { 27 | let function = &self.display; 28 | function(ui, value) 29 | } 30 | 31 | pub fn ui_list( 32 | &self, 33 | ui: &mut egui::Ui, 34 | values: &[T], 35 | ) -> Vec { 36 | let res: Vec = (0..self.slot_count) 37 | .filter_map(|ix| { 38 | let val = if self.indices.is_empty() { 39 | values.get(self.offset + ix) 40 | } else { 41 | let ix = self.indices.get(self.offset + ix)?; 42 | values.get(*ix) 43 | }?; 44 | 45 | Some((&self.display)(ui, val)) 46 | }) 47 | .collect(); 48 | 49 | res 50 | } 51 | } 52 | 53 | /// Creates a popup that, unlike the built-in egui one, doesn't 54 | /// disappear when the user clicks inside the popup 55 | pub fn popup_below_widget( 56 | ui: &egui::Ui, 57 | popup_id: egui::Id, 58 | widget_response: &egui::Response, 59 | add_contents: impl FnOnce(&mut egui::Ui), 60 | ) { 61 | if ui.memory().is_popup_open(popup_id) { 62 | let parent_clip_rect = ui.clip_rect(); 63 | 64 | let popup_response = egui::Area::new(popup_id) 65 | .order(egui::Order::Foreground) 66 | .fixed_pos(widget_response.rect.left_bottom()) 67 | .show(ui.ctx(), |ui| { 68 | ui.set_clip_rect(parent_clip_rect); // for when the combo-box is in a scroll area. 69 | let frame = egui::Frame::popup(ui.style()); 70 | let frame_margin = frame.margin; 71 | frame.show(ui, |ui| { 72 | ui.with_layout( 73 | egui::Layout::top_down_justified(egui::Align::LEFT), 74 | |ui| { 75 | ui.set_width( 76 | widget_response.rect.width() 77 | - 2.0 * frame_margin.x, 78 | ); 79 | add_contents(ui) 80 | }, 81 | ); 82 | }); 83 | }); 84 | 85 | let popup_response = popup_response.response; 86 | 87 | if ui.input().key_pressed(egui::Key::Escape) 88 | || (popup_response.clicked_elsewhere() 89 | && widget_response.clicked_elsewhere()) 90 | { 91 | ui.memory().close_popup(); 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/app/settings.rs: -------------------------------------------------------------------------------- 1 | use crossbeam::atomic::AtomicCell; 2 | use std::sync::Arc; 3 | 4 | use crate::vulkan::draw_system::edges::EdgesUBO; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct AppSettings { 8 | node_width: Arc, 9 | 10 | edge_renderer: Arc>, 11 | 12 | label_radius: Arc>, 13 | 14 | background_color_light: Arc>>, 15 | background_color_dark: Arc>>, 16 | } 17 | 18 | impl std::default::Default for AppSettings { 19 | fn default() -> Self { 20 | Self { 21 | node_width: Default::default(), 22 | edge_renderer: Default::default(), 23 | label_radius: Arc::new(50.0.into()), 24 | 25 | background_color_light: Arc::new( 26 | rgb::RGB::new(1.0, 1.0, 1.0).into(), 27 | ), 28 | background_color_dark: Arc::new( 29 | rgb::RGB::new(0.1, 0.1, 0.2).into(), 30 | ), 31 | } 32 | } 33 | } 34 | 35 | impl AppSettings { 36 | pub fn node_width(&self) -> &Arc { 37 | &self.node_width 38 | } 39 | 40 | pub fn edge_renderer(&self) -> &Arc> { 41 | &self.edge_renderer 42 | } 43 | 44 | pub fn update_edge_renderer(&self, conf: EdgesUBO) { 45 | self.edge_renderer.store(conf); 46 | } 47 | 48 | pub fn label_radius(&self) -> &Arc> { 49 | &self.label_radius 50 | } 51 | 52 | pub fn background_color_light(&self) -> &Arc>> { 53 | &self.background_color_light 54 | } 55 | 56 | pub fn background_color_dark(&self) -> &Arc>> { 57 | &self.background_color_dark 58 | } 59 | } 60 | 61 | #[derive(Debug)] 62 | pub struct NodeWidth { 63 | min_node_width: AtomicCell, 64 | max_node_width: AtomicCell, 65 | 66 | min_node_scale: AtomicCell, 67 | max_node_scale: AtomicCell, 68 | } 69 | 70 | impl NodeWidth { 71 | pub fn min_node_width(&self) -> f32 { 72 | self.min_node_width.load() 73 | } 74 | 75 | pub fn max_node_width(&self) -> f32 { 76 | self.max_node_width.load() 77 | } 78 | pub fn min_node_scale(&self) -> f32 { 79 | self.min_node_scale.load() 80 | } 81 | 82 | pub fn max_node_scale(&self) -> f32 { 83 | self.max_node_scale.load() 84 | } 85 | 86 | pub fn set_min_node_width(&self, width: f32) { 87 | self.min_node_width.store(width); 88 | } 89 | 90 | pub fn set_max_node_width(&self, width: f32) { 91 | self.max_node_width.store(width); 92 | } 93 | 94 | pub fn set_min_node_scale(&self, width: f32) { 95 | self.min_node_scale.store(width); 96 | } 97 | 98 | pub fn set_max_node_scale(&self, width: f32) { 99 | self.max_node_scale.store(width); 100 | } 101 | } 102 | 103 | impl std::default::Default for NodeWidth { 104 | fn default() -> Self { 105 | Self { 106 | min_node_width: AtomicCell::new(5.0), 107 | max_node_width: AtomicCell::new(150.0), 108 | 109 | min_node_scale: AtomicCell::new(1.0), 110 | max_node_scale: AtomicCell::new(50.0), 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/reactor/paired.rs: -------------------------------------------------------------------------------- 1 | use futures::future::BoxFuture; 2 | use parking_lot::Mutex; 3 | use std::sync::Arc; 4 | 5 | use futures::channel::mpsc; 6 | use futures::prelude::*; 7 | 8 | pub fn create_host_pair( 9 | func: Box, I) -> T + Send + Sync + 'static>, 10 | ) -> (Host, Processor) 11 | where 12 | I: Send + Sync + 'static, 13 | T: Send + Sync + 'static, 14 | { 15 | let (input_send, input_recv) = mpsc::channel(8); 16 | 17 | let (outbox, inbox) = create_box_pair::(); 18 | 19 | ( 20 | Host { inbox, input_send }, 21 | Processor { 22 | outbox, 23 | input_recv, 24 | func, 25 | }, 26 | ) 27 | } 28 | 29 | pub struct Host 30 | where 31 | I: Send + Sync + 'static, 32 | T: Send + Sync + 'static, 33 | { 34 | inbox: Inbox, 35 | input_send: mpsc::Sender, 36 | } 37 | 38 | pub struct Processor 39 | where 40 | I: Send + Sync + 'static, 41 | T: Send + Sync + 'static, 42 | { 43 | outbox: Outbox, 44 | input_recv: mpsc::Receiver, 45 | func: Box, I) -> T + Send + Sync + 'static>, 46 | } 47 | 48 | impl Host 49 | where 50 | I: Send + Sync + 'static, 51 | T: Send + Sync + 'static, 52 | { 53 | pub fn call(&mut self, input: I) -> anyhow::Result<()> { 54 | futures::executor::block_on(async { 55 | self.input_send.send(input).await 56 | })?; 57 | 58 | Ok(()) 59 | } 60 | 61 | pub fn take(&self) -> Option { 62 | self.inbox.take() 63 | } 64 | } 65 | 66 | pub trait ProcTrait: Send + Sync + 'static { 67 | fn process(&mut self) -> BoxFuture<()>; 68 | } 69 | 70 | impl ProcTrait for Processor 71 | where 72 | I: Send + Sync + 'static, 73 | T: Send + Sync + 'static, 74 | { 75 | fn process(&mut self) -> BoxFuture<()> { 76 | let future = async move { 77 | if let Some(input) = self.input_recv.next().await { 78 | let func = &self.func; 79 | let output = func(&self.outbox, input); 80 | self.outbox.insert_blocking(output); 81 | } 82 | }; 83 | 84 | future.boxed() 85 | } 86 | } 87 | 88 | // this is basically an unbounded channel except worse, i guess 89 | fn create_box_pair() -> (Outbox, Inbox) { 90 | let value = Arc::new(Mutex::new(None)); 91 | 92 | let outbox = Outbox { 93 | value: value.clone(), 94 | }; 95 | let inbox = Inbox { value }; 96 | 97 | (outbox, inbox) 98 | } 99 | 100 | pub struct Inbox { 101 | value: Arc>>, 102 | } 103 | 104 | pub struct Outbox { 105 | value: Arc>>, 106 | } 107 | 108 | impl Inbox { 109 | /// If the value is filled, consume and return the contents; otherwise returns None 110 | pub fn take(&self) -> Option { 111 | self.value.try_lock().and_then(|mut v| v.take()) 112 | } 113 | } 114 | 115 | impl Outbox { 116 | /// Block the thread and replace the value with 117 | pub fn insert_blocking(&self, value: T) { 118 | // log::warn!("enter guard"); 119 | let mut guard = self.value.lock(); 120 | *guard = Some(value); 121 | // log::warn!("leave guard"); 122 | } 123 | 124 | pub fn try_insert(&self, value: T) -> Result<(), T> { 125 | if let Some(mut guard) = self.value.try_lock() { 126 | *guard = Some(value); 127 | Ok(()) 128 | } else { 129 | Err(value) 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/vulkan/msg.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use ash::{ 3 | extensions::khr::{Surface, Swapchain}, 4 | version::DeviceV1_0, 5 | vk, Device, Entry, 6 | }; 7 | 8 | use bytemuck::{Pod, Zeroable}; 9 | use futures::Future; 10 | use parking_lot::{Mutex, MutexGuard}; 11 | use std::{mem::size_of, sync::Arc}; 12 | use vk_mem::Allocator; 13 | 14 | #[cfg(target_os = "linux")] 15 | use winit::platform::unix::*; 16 | use winit::{ 17 | event_loop::EventLoop, 18 | window::{Window, WindowBuilder}, 19 | }; 20 | 21 | #[allow(unused_imports)] 22 | use log::{debug, error, info, trace, warn}; 23 | 24 | use crate::{app::Args, view::ScreenDims}; 25 | 26 | use super::GfaestusVk; 27 | 28 | pub struct GpuTasks { 29 | task_rx: crossbeam::channel::Receiver, 30 | task_tx: crossbeam::channel::Sender, 31 | // tasks: Vec, 32 | } 33 | 34 | impl std::default::Default for GpuTasks { 35 | fn default() -> Self { 36 | let (task_tx, task_rx) = crossbeam::channel::unbounded(); 37 | GpuTasks { task_tx, task_rx } 38 | } 39 | } 40 | 41 | impl GpuTasks { 42 | // pub fn queue_task(&self, task: GpuTask) -> impl Future { 43 | pub fn queue_task( 44 | &self, 45 | task: GpuTask, 46 | ) -> Result> { 47 | let (tx, rx) = futures::channel::oneshot::channel::<()>(); 48 | let task_pkg = TaskPkg { task, signal: tx }; 49 | self.task_tx.send(task_pkg)?; 50 | Ok(rx) 51 | } 52 | 53 | pub fn execute_all( 54 | &self, 55 | app: &GfaestusVk, 56 | command_pool: vk::CommandPool, 57 | queue: vk::Queue, 58 | ) -> Result<()> { 59 | while let Ok(TaskPkg { task, signal }) = self.task_rx.try_recv() { 60 | let task_res = execute_task(app, command_pool, queue, task); 61 | 62 | if let Err(msg) = task_res { 63 | log::error!("GPU task error: {:?}", msg); 64 | } 65 | 66 | let signal_res = signal.send(()); 67 | 68 | if let Err(msg) = signal_res { 69 | log::error!("GPU task signal error: {:?}", msg); 70 | } 71 | } 72 | 73 | Ok(()) 74 | } 75 | 76 | pub fn execute_next( 77 | &self, 78 | app: &GfaestusVk, 79 | command_pool: vk::CommandPool, 80 | queue: vk::Queue, 81 | ) -> Result<()> { 82 | if let Ok(TaskPkg { task, signal }) = self.task_rx.try_recv() { 83 | let task_res = execute_task(app, command_pool, queue, task); 84 | 85 | if let Err(msg) = task_res { 86 | log::error!("GPU task error: {:?}", msg); 87 | } 88 | 89 | let signal_res = signal.send(()); 90 | 91 | if let Err(msg) = signal_res { 92 | log::error!("GPU task signal error: {:?}", msg); 93 | } 94 | } 95 | 96 | Ok(()) 97 | } 98 | } 99 | 100 | pub struct TaskPkg { 101 | task: GpuTask, 102 | signal: futures::channel::oneshot::Sender<()>, 103 | } 104 | 105 | pub enum GpuTask { 106 | CopyDataToBuffer { 107 | data: Arc>, 108 | dst: vk::Buffer, 109 | }, 110 | // CopyImageToBuffer { image: vk::Image, buffer: vk::Buffer, extent: vk::Extent2D }, 111 | } 112 | 113 | pub fn execute_task( 114 | app: &GfaestusVk, 115 | command_pool: vk::CommandPool, 116 | queue: vk::Queue, 117 | task: GpuTask, 118 | ) -> Result<()> { 119 | match task { 120 | GpuTask::CopyDataToBuffer { data, dst } => { 121 | app.copy_data_to_buffer::(&data, dst) 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/universe/config.rs: -------------------------------------------------------------------------------- 1 | use crate::geometry::*; 2 | 3 | #[allow(unused_imports)] 4 | use super::{grid, Universe}; 5 | 6 | #[allow(unused_imports)] 7 | use handlegraph::{ 8 | handle::{Direction, Handle, NodeId}, 9 | handlegraph::*, 10 | mutablehandlegraph::*, 11 | packed::*, 12 | pathhandlegraph::*, 13 | }; 14 | 15 | #[allow(unused_imports)] 16 | use handlegraph::packedgraph::PackedGraph; 17 | 18 | #[derive(Debug, Clone, Copy)] 19 | pub struct UniverseBuilder { 20 | pub bp_per_world_unit: f32, 21 | pub physics_config: PhysicsConfig, 22 | pub layout_config: LayoutConfig, 23 | pub cell_size: f32, 24 | pub grid_columns: usize, 25 | pub grid_rows: usize, 26 | pub view_config: ViewConfig, 27 | } 28 | 29 | /* 30 | impl UniverseBuilder { 31 | pub fn build(self) -> Universe { 32 | let grid_dims = grid::GridDims { 33 | columns: self.grid_columns, 34 | rows: self.grid_rows, 35 | }; 36 | 37 | let cell_dims = grid::CellDims { 38 | width: self.cell_size, 39 | height: self.cell_size, 40 | }; 41 | 42 | let offset = Point { x: 0.0, y: 0.0 }; 43 | 44 | let grid = grid::Grid::new(offset, grid_dims, cell_dims); 45 | 46 | Universe { 47 | bp_per_world_unit: self.bp_per_world_unit, 48 | grid, 49 | offset, 50 | angle: 0.0, 51 | physics_config: self.physics_config, 52 | layout_config: self.layout_config, 53 | view_config: self.view_config, 54 | } 55 | } 56 | } 57 | */ 58 | 59 | #[derive(Debug, Clone, Copy)] 60 | pub struct LayoutConfig { 61 | pub node_width: f32, 62 | pub neighbor_node_pad: f32, 63 | pub parallel_node_pad: f32, 64 | } 65 | 66 | #[derive(Debug, Clone, Copy)] 67 | pub struct ViewConfig { 68 | pub init_view_offset: Point, 69 | pub init_view_scale: f32, 70 | pub min_view_scale: f32, 71 | pub max_view_scale: Option, 72 | } 73 | 74 | #[derive(Debug, Clone, Copy)] 75 | pub struct PhysicsConfig { 76 | pub enable_physics: bool, 77 | pub tick_len: std::time::Duration, 78 | pub tick_rate_hz: f32, 79 | pub max_interact_dist: f32, 80 | pub charge_per_bp: f32, 81 | pub min_node_charge: f32, 82 | pub charge_dist_mult: f32, 83 | pub repulsion_mult: f32, 84 | pub attraction_mult: f32, 85 | // pub mass_per_bp: f32, 86 | // pub min_node_mass: f32, 87 | // pub gravity_dist_mult: f32, 88 | // pub gravity_mult: f32, 89 | // pub edge_min_len: f32, 90 | // pub edge_max_len: f32, 91 | // pub edge_force_mult: f32, 92 | // pub edge_stiffness: f32, 93 | // pub edge_angle_stiffness: f32, 94 | } 95 | 96 | impl std::default::Default for PhysicsConfig { 97 | fn default() -> Self { 98 | Self { 99 | enable_physics: true, 100 | tick_len: std::time::Duration::from_millis(20), 101 | tick_rate_hz: 60.0, 102 | max_interact_dist: 10000.0, 103 | charge_per_bp: 1.0, 104 | min_node_charge: 1.0, 105 | charge_dist_mult: 1.0, 106 | repulsion_mult: 1.0, 107 | attraction_mult: 1.0, 108 | } 109 | } 110 | } 111 | 112 | impl std::default::Default for LayoutConfig { 113 | fn default() -> Self { 114 | Self { 115 | node_width: 20.0, 116 | neighbor_node_pad: 20.0, 117 | parallel_node_pad: 20.0, 118 | } 119 | } 120 | } 121 | 122 | impl std::default::Default for ViewConfig { 123 | fn default() -> Self { 124 | ViewConfig { 125 | init_view_offset: Point { x: 0.0, y: 0.0 }, 126 | init_view_scale: 1.0, 127 | min_view_scale: 1.0, 128 | max_view_scale: None, 129 | } 130 | } 131 | } 132 | 133 | impl std::default::Default for UniverseBuilder { 134 | fn default() -> Self { 135 | Self { 136 | bp_per_world_unit: 1.0, 137 | physics_config: PhysicsConfig::default(), 138 | layout_config: LayoutConfig::default(), 139 | cell_size: 1024.0, 140 | grid_columns: 10, 141 | grid_rows: 10, 142 | view_config: ViewConfig::default(), 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/gui/windows/settings/main_view.rs: -------------------------------------------------------------------------------- 1 | use crossbeam::atomic::AtomicCell; 2 | use std::sync::Arc; 3 | 4 | use crate::{ 5 | app::{AppSettings, NodeWidth}, 6 | vulkan::draw_system::edges::EdgesUBO, 7 | }; 8 | 9 | pub struct MainViewSettings { 10 | node_width: Arc, 11 | label_radius: Arc>, 12 | 13 | edges_enabled: Arc>, 14 | edges_ubo: Arc>, 15 | } 16 | 17 | impl MainViewSettings { 18 | pub fn new( 19 | settings: &AppSettings, 20 | edges_enabled: Arc>, 21 | ) -> Self { 22 | let node_width = settings.node_width().clone(); 23 | let label_radius = settings.label_radius().clone(); 24 | 25 | let edges_ubo = settings.edge_renderer().clone(); 26 | 27 | Self { 28 | node_width, 29 | label_radius, 30 | 31 | edges_enabled, 32 | edges_ubo, 33 | } 34 | } 35 | 36 | pub fn ui(&mut self, ui: &mut egui::Ui) { 37 | let mut min_width = self.node_width.min_node_width(); 38 | let mut max_width = self.node_width.max_node_width(); 39 | 40 | let mut min_scale = self.node_width.min_node_scale(); 41 | let mut max_scale = self.node_width.max_node_scale(); 42 | 43 | let min_node_width_slider = ui 44 | .add( 45 | egui::Slider::new::(&mut min_width, 0.1..=max_width) 46 | .text("Min node width"), 47 | ) 48 | .on_hover_text( 49 | "The minimum node width, in pixels at scale 1.0. Default: 0.1", 50 | ); 51 | 52 | let max_node_width_slider = ui.add( 53 | egui::Slider::new::(&mut max_width, min_width..=300.0).text("Max node width"), 54 | ).on_hover_text("The maximum node width, in pixels at scale 1.0. Default: 100.0"); 55 | 56 | let min_scale_slider = ui.add( 57 | egui::Slider::new::(&mut min_scale, 1.0..=max_scale).text("Min node width scale"), 58 | ).on_hover_text("The scale below which the minimum node width will be used. Default: 0.1"); 59 | 60 | let max_scale_slider = ui.add( 61 | egui::Slider::new::(&mut max_scale, min_scale..=1000.0).text("Max node width scale"), 62 | ).on_hover_text("The scale above which the maximum node width will be used. Default: 200.0"); 63 | 64 | let edges_enabled = self.edges_enabled.load(); 65 | let edges_button = ui.selectable_label(edges_enabled, "Show Edges"); 66 | 67 | let mut edges_ubo = self.edges_ubo.load(); 68 | 69 | let mut edge_width = edges_ubo.edge_width; 70 | 71 | let mut edge_color = [ 72 | edges_ubo.edge_color.r, 73 | edges_ubo.edge_color.g, 74 | edges_ubo.edge_color.b, 75 | ]; 76 | 77 | let edge_width_slider = ui.add( 78 | egui::Slider::new::(&mut edge_width, 0.5..=5.0) 79 | .text("Edge width"), 80 | ); 81 | 82 | if edge_width_slider.changed() { 83 | edges_ubo.edge_width = edge_width; 84 | 85 | self.edges_ubo.store(edges_ubo); 86 | } 87 | 88 | let edge_color_picker = ui.color_edit_button_rgb(&mut edge_color); 89 | 90 | if edge_color_picker.changed() { 91 | let new_color = 92 | rgb::RGB::new(edge_color[0], edge_color[1], edge_color[2]); 93 | 94 | edges_ubo.edge_color = new_color; 95 | 96 | self.edges_ubo.store(edges_ubo); 97 | } 98 | 99 | if edges_button.clicked() { 100 | self.edges_enabled.store(!edges_enabled); 101 | } 102 | 103 | if min_node_width_slider.changed() { 104 | self.node_width.set_min_node_width(min_width); 105 | } 106 | 107 | if max_node_width_slider.changed() { 108 | self.node_width.set_max_node_width(max_width); 109 | } 110 | 111 | if min_scale_slider.changed() { 112 | self.node_width.set_min_node_scale(min_scale); 113 | } 114 | 115 | if max_scale_slider.changed() { 116 | self.node_width.set_max_node_scale(max_scale); 117 | } 118 | 119 | let mut label_radius = self.label_radius.load(); 120 | 121 | let label_radius_slider = ui.add( 122 | egui::Slider::new::(&mut label_radius, 10.0..=200.0) 123 | .text("Label stacking radius"), 124 | ); 125 | 126 | if label_radius_slider.changed() { 127 | self.label_radius.store(label_radius); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /src/gui/windows/filters.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use handlegraph::{ 3 | handle::{Direction, Handle, NodeId}, 4 | handlegraph::*, 5 | mutablehandlegraph::*, 6 | packed::*, 7 | packedgraph::index::OneBasedIndex, 8 | packedgraph::*, 9 | path_position::*, 10 | pathhandlegraph::*, 11 | }; 12 | 13 | use bstr::ByteSlice; 14 | 15 | use egui::emath::Numeric; 16 | 17 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] 18 | pub enum FilterStringOp { 19 | None, 20 | Equal, 21 | Contains, 22 | ContainedIn, 23 | } 24 | 25 | #[derive(Debug, Clone, PartialEq, PartialOrd)] 26 | pub struct FilterString { 27 | pub op: FilterStringOp, 28 | pub arg: String, 29 | } 30 | 31 | impl std::default::Default for FilterString { 32 | fn default() -> Self { 33 | Self { 34 | op: FilterStringOp::None, 35 | arg: String::new(), 36 | } 37 | } 38 | } 39 | 40 | impl FilterString { 41 | pub fn filter_str(&self, string: &str) -> bool { 42 | match self.op { 43 | FilterStringOp::None => true, 44 | FilterStringOp::Equal => string == self.arg, 45 | FilterStringOp::Contains => string.contains(&self.arg), 46 | FilterStringOp::ContainedIn => self.arg.contains(string), 47 | } 48 | } 49 | 50 | pub fn filter_bytes(&self, string: &[u8]) -> bool { 51 | match self.op { 52 | FilterStringOp::None => true, 53 | FilterStringOp::Equal => { 54 | let bytes = self.arg.as_bytes(); 55 | string == bytes 56 | } 57 | FilterStringOp::Contains => { 58 | let bytes = self.arg.as_bytes(); 59 | string.contains_str(bytes) 60 | } 61 | FilterStringOp::ContainedIn => { 62 | let bytes = self.arg.as_bytes(); 63 | bytes.contains_str(string) 64 | } 65 | } 66 | } 67 | 68 | pub fn ui(&mut self, ui: &mut egui::Ui) -> Option { 69 | let op = &mut self.op; 70 | let arg = &mut self.arg; 71 | 72 | ui.horizontal(|ui| { 73 | let _op_none = ui.radio_value(op, FilterStringOp::None, "None"); 74 | let _op_equal = ui.radio_value(op, FilterStringOp::Equal, "Equal"); 75 | let _op_contains = 76 | ui.radio_value(op, FilterStringOp::Contains, "Contains"); 77 | let _op_contained_in = 78 | ui.radio_value(op, FilterStringOp::ContainedIn, "Contained in"); 79 | }); 80 | 81 | if *op != FilterStringOp::None { 82 | let arg_edit = ui.text_edit_singleline(arg); 83 | return Some(arg_edit); 84 | } 85 | 86 | None 87 | } 88 | } 89 | 90 | #[derive(Debug, PartialEq, PartialOrd)] 91 | pub enum FilterNumOp { 92 | None, 93 | Equal, 94 | LessThan, 95 | MoreThan, 96 | InRange, 97 | } 98 | 99 | #[derive(Debug, PartialEq, PartialOrd)] 100 | pub struct FilterNum { 101 | pub op: FilterNumOp, 102 | pub arg1: T, 103 | pub arg2: T, 104 | } 105 | 106 | impl std::default::Default for FilterNum { 107 | fn default() -> Self { 108 | Self { 109 | op: FilterNumOp::None, 110 | arg1: T::from_f64(0.0), 111 | arg2: T::from_f64(0.0), 112 | } 113 | } 114 | } 115 | 116 | impl FilterNum { 117 | pub fn filter(&self, val: T) -> bool { 118 | match self.op { 119 | FilterNumOp::None => true, 120 | FilterNumOp::Equal => val == self.arg1, 121 | FilterNumOp::LessThan => val < self.arg1, 122 | FilterNumOp::MoreThan => val > self.arg1, 123 | FilterNumOp::InRange => self.arg1 <= val && val < self.arg2, 124 | } 125 | } 126 | 127 | pub fn ui(&mut self, ui: &mut egui::Ui) { 128 | let op = &mut self.op; 129 | let arg1 = &mut self.arg1; 130 | let arg2 = &mut self.arg2; 131 | 132 | ui.horizontal(|ui| { 133 | let _op_none = ui.radio_value(op, FilterNumOp::None, "None"); 134 | let _op_equal = ui.radio_value(op, FilterNumOp::Equal, "Equal"); 135 | let _op_less = 136 | ui.radio_value(op, FilterNumOp::LessThan, "Less than"); 137 | let _op_more = 138 | ui.radio_value(op, FilterNumOp::MoreThan, "More than"); 139 | let _op_in_range = 140 | ui.radio_value(op, FilterNumOp::InRange, "In range"); 141 | }); 142 | 143 | let arg1_drag = egui::DragValue::new::(arg1); 144 | // egui::DragValue::new::(from_pos).clamp_range(from_range); 145 | 146 | let arg2_drag = egui::DragValue::new::(arg2); 147 | 148 | if *op != FilterNumOp::None { 149 | ui.horizontal(|ui| { 150 | let _arg1_edit = ui.add(arg1_drag); 151 | if *op == FilterNumOp::InRange { 152 | let _arg2_edit = ui.add(arg2_drag); 153 | } 154 | }); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/app/shared_state.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crossbeam::atomic::AtomicCell; 4 | use handlegraph::handle::NodeId; 5 | use handlegraph::pathhandlegraph::PathId; 6 | 7 | use crate::{geometry::*, gui::GuiFocusState}; 8 | use crate::{view::*, vulkan::texture::GradientName}; 9 | 10 | #[derive(Clone)] 11 | pub struct SharedState { 12 | pub mouse_pos: Arc>, 13 | pub screen_dims: Arc>, 14 | 15 | pub view: Arc>, 16 | 17 | pub hover_node: Arc>>, 18 | 19 | pub mouse_rect: MouseRect, 20 | 21 | pub overlay_state: OverlayState, 22 | 23 | pub gui_focus_state: GuiFocusState, 24 | 25 | pub edges_enabled: Arc>, 26 | 27 | pub dark_mode: Arc>, 28 | 29 | pub show_modal: Arc>, 30 | } 31 | 32 | impl SharedState { 33 | pub fn new>(screen_dims: Dims) -> Self { 34 | Self { 35 | mouse_pos: Arc::new(Point::ZERO.into()), 36 | screen_dims: Arc::new(screen_dims.into().into()), 37 | 38 | view: Arc::new(View::default().into()), 39 | 40 | hover_node: Arc::new(None.into()), 41 | 42 | mouse_rect: MouseRect::default(), 43 | 44 | overlay_state: OverlayState::default(), 45 | 46 | gui_focus_state: GuiFocusState::default(), 47 | 48 | edges_enabled: Arc::new(true.into()), 49 | dark_mode: Arc::new(false.into()), 50 | show_modal: Arc::new(false.into()), 51 | } 52 | } 53 | 54 | pub fn mouse_pos(&self) -> Point { 55 | self.mouse_pos.load() 56 | } 57 | 58 | pub fn screen_dims(&self) -> ScreenDims { 59 | self.screen_dims.load() 60 | } 61 | 62 | pub fn view(&self) -> View { 63 | self.view.load() 64 | } 65 | 66 | pub fn hover_node(&self) -> Option { 67 | self.hover_node.load() 68 | } 69 | 70 | pub fn overlay_state(&self) -> &OverlayState { 71 | &self.overlay_state 72 | } 73 | 74 | pub fn edges_enabled(&self) -> bool { 75 | self.edges_enabled.load() 76 | } 77 | 78 | pub fn dark_mode(&self) -> &Arc> { 79 | &self.dark_mode 80 | } 81 | 82 | pub fn start_mouse_rect(&self) { 83 | let view = self.view(); 84 | let screen_pos = self.mouse_pos(); 85 | let screen_dims = self.screen_dims(); 86 | 87 | let world_pos = view.screen_point_to_world(screen_dims, screen_pos); 88 | 89 | self.mouse_rect.screen_pos.store(Some(screen_pos)); 90 | self.mouse_rect.world_pos.store(Some(world_pos)); 91 | } 92 | 93 | pub fn active_mouse_rect_screen(&self) -> Option { 94 | let start_pos = self.mouse_rect.screen_pos.load()?; 95 | 96 | let end_pos = self.mouse_pos(); 97 | 98 | Some(Rect::new(start_pos, end_pos)) 99 | } 100 | 101 | pub fn close_mouse_rect_world(&self) -> Option { 102 | let start_pos = self.mouse_rect.world_pos.load()?; 103 | 104 | let screen_pos = self.mouse_pos(); 105 | let screen_dims = self.screen_dims(); 106 | 107 | let view = self.view(); 108 | 109 | let end_pos = view.screen_point_to_world(screen_dims, screen_pos); 110 | 111 | let rect = Rect::new(start_pos, end_pos); 112 | 113 | self.mouse_rect.world_pos.store(None); 114 | self.mouse_rect.screen_pos.store(None); 115 | 116 | Some(rect) 117 | } 118 | 119 | pub fn is_started_mouse_rect(&self) -> bool { 120 | self.mouse_rect.screen_pos.load().is_some() 121 | } 122 | } 123 | 124 | #[derive(Clone)] 125 | pub struct MouseRect { 126 | pub(super) world_pos: Arc>>, 127 | pub(super) screen_pos: Arc>>, 128 | } 129 | 130 | impl std::default::Default for MouseRect { 131 | fn default() -> Self { 132 | Self { 133 | world_pos: Arc::new(AtomicCell::new(None)), 134 | screen_pos: Arc::new(AtomicCell::new(None)), 135 | } 136 | } 137 | } 138 | 139 | #[derive(Debug, Clone)] 140 | pub struct OverlayState { 141 | pub current_overlay: Arc>>, 142 | 143 | gradient: Arc>, 144 | } 145 | 146 | impl OverlayState { 147 | pub fn current_overlay(&self) -> Option { 148 | self.current_overlay.load() 149 | } 150 | 151 | pub fn gradient(&self) -> GradientName { 152 | self.gradient.load() 153 | } 154 | 155 | pub fn set_current_overlay(&self, overlay_id: Option) { 156 | self.current_overlay.store(overlay_id); 157 | } 158 | 159 | pub fn set_gradient(&self, gradient: GradientName) { 160 | self.gradient.store(gradient); 161 | } 162 | } 163 | 164 | impl std::default::Default for OverlayState { 165 | fn default() -> Self { 166 | let current_overlay = Arc::new(AtomicCell::new(None)); 167 | 168 | let gradient = Arc::new(AtomicCell::new(GradientName::Magma)); 169 | 170 | Self { 171 | current_overlay, 172 | gradient, 173 | } 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/gfa/load.rs: -------------------------------------------------------------------------------- 1 | use handlegraph::{ 2 | handle::{Edge, Handle}, 3 | mutablehandlegraph::*, 4 | pathhandlegraph::*, 5 | }; 6 | 7 | use gfa::mmap::MmapGFA; 8 | 9 | use handlegraph::packedgraph::PackedGraph; 10 | 11 | use gfa::gfa::Line; 12 | 13 | use anyhow::Result; 14 | 15 | use rustc_hash::FxHashMap; 16 | 17 | #[allow(unused_imports)] 18 | use log::{debug, error, info, trace, warn}; 19 | 20 | pub fn packed_graph_from_mmap(mmap_gfa: &mut MmapGFA) -> Result { 21 | let indices = mmap_gfa.build_index()?; 22 | 23 | // let mut graph = 24 | // PackedGraph::with_expected_node_count(indices.segments.len()); 25 | 26 | let mut graph = PackedGraph::default(); 27 | // eprintln!("empty space usage: {} bytes", graph.total_bytes()); 28 | 29 | info!( 30 | "loading GFA with {} nodes, {} edges", 31 | indices.segments.len(), 32 | indices.links.len() 33 | ); 34 | 35 | let mut min_id = std::usize::MAX; 36 | let mut max_id = 0; 37 | 38 | for &offset in indices.segments.iter() { 39 | let _line = mmap_gfa.read_line_at(offset.0)?; 40 | let name = mmap_gfa.current_line_name().unwrap(); 41 | let name_str = std::str::from_utf8(name).unwrap(); 42 | let id = name_str.parse::().unwrap(); 43 | 44 | min_id = id.min(min_id); 45 | max_id = id.max(max_id); 46 | } 47 | 48 | let id_offset = if min_id == 0 { 1 } else { 0 }; 49 | 50 | info!("adding nodes"); 51 | for &offset in indices.segments.iter() { 52 | let _line = mmap_gfa.read_line_at(offset.0)?; 53 | let segment = mmap_gfa.parse_current_line()?; 54 | 55 | if let gfa::gfa::Line::Segment(segment) = segment { 56 | let id = (segment.name + id_offset) as u64; 57 | graph.create_handle(&segment.sequence, id); 58 | } 59 | } 60 | // eprintln!( 61 | // "after segments - space usage: {} bytes", 62 | // graph.total_bytes() 63 | // ); 64 | 65 | info!("adding edges"); 66 | 67 | let edges_iter = indices.links.iter().filter_map(|&offset| { 68 | let _line = mmap_gfa.read_line_at(offset).ok()?; 69 | let link = mmap_gfa.parse_current_line().ok()?; 70 | 71 | if let gfa::gfa::Line::Link(link) = link { 72 | let from_id = (link.from_segment + id_offset) as u64; 73 | let to_id = (link.to_segment + id_offset) as u64; 74 | 75 | let from = Handle::new(from_id, link.from_orient); 76 | let to = Handle::new(to_id, link.to_orient); 77 | 78 | Some(Edge(from, to)) 79 | } else { 80 | None 81 | } 82 | }); 83 | 84 | graph.create_edges_iter(edges_iter); 85 | 86 | // eprintln!( 87 | // "after edges - space usage: {} bytes", 88 | // graph.total_bytes() 89 | // ); 90 | 91 | let mut path_ids: FxHashMap = FxHashMap::default(); 92 | path_ids.reserve(indices.paths.len()); 93 | 94 | info!("adding paths"); 95 | for &offset in indices.paths.iter() { 96 | let line = mmap_gfa.read_line_at(offset)?; 97 | let length = line.len(); 98 | if let Some(path_name) = mmap_gfa.current_line_name() { 99 | let path_id = graph.create_path(path_name, false).unwrap(); 100 | path_ids.insert(path_id, (offset, length)); 101 | } 102 | } 103 | 104 | info!("created path handles"); 105 | 106 | let mmap_gfa_bytes = mmap_gfa.get_ref(); 107 | 108 | let parser = mmap_gfa.get_parser(); 109 | 110 | graph.with_all_paths_mut_ctx_chn_new(|path_id, sender, path_ref| { 111 | let &(offset, length) = path_ids.get(&path_id).unwrap(); 112 | let end = offset + length; 113 | let line = &mmap_gfa_bytes[offset..end]; 114 | if let Some(Line::Path(path)) = parser.parse_gfa_line(line).ok() { 115 | path_ref.append_handles_iter_chn( 116 | sender, 117 | path.iter().map(|(node, orient)| { 118 | let node = node + id_offset; 119 | Handle::new(node, orient) 120 | }), 121 | ); 122 | } 123 | }); 124 | 125 | /* 126 | graph.with_all_paths_mut_ctx_chn(|path_id, path_ref| { 127 | let &(offset, length) = path_ids.get(&path_id).unwrap(); 128 | let end = offset + length; 129 | let line = &mmap_gfa_bytes[offset..end]; 130 | 131 | if let Some(Line::Path(path)) = parser.parse_gfa_line(line).ok() { 132 | path.iter() 133 | .map(|(node, orient)| { 134 | let node = node + id_offset; 135 | let handle = Handle::new(node, orient); 136 | path_ref.append_step(handle) 137 | }) 138 | .collect() 139 | 140 | // path_ref.append_steps_iter(path.iter().map(|(node, orient)| { 141 | // let node = node + id_offset; 142 | // Handle::new(node, orient) 143 | // })) 144 | } else { 145 | Vec::new() 146 | } 147 | }); 148 | */ 149 | 150 | // eprintln!( 151 | // "after paths - space usage: {} bytes", 152 | // graph.total_bytes() 153 | // ); 154 | 155 | Ok(graph) 156 | } 157 | -------------------------------------------------------------------------------- /src/reactor.rs: -------------------------------------------------------------------------------- 1 | use std::pin::Pin; 2 | use std::sync::Arc; 3 | 4 | use clipboard::{ClipboardContext, ClipboardProvider}; 5 | use crossbeam::channel::{Receiver, Sender}; 6 | use futures::{future::RemoteHandle, task::SpawnExt, Future}; 7 | 8 | mod modal; 9 | mod paired; 10 | 11 | pub use modal::*; 12 | pub use paired::{create_host_pair, Host, Inbox, Outbox, Processor}; 13 | 14 | use paired::*; 15 | use parking_lot::lock_api::RawMutex; 16 | use parking_lot::Mutex; 17 | 18 | use crate::app::channels::OverlayCreatorMsg; 19 | use crate::app::AppChannels; 20 | use crate::graph_query::GraphQuery; 21 | use crate::vulkan::GpuTasks; 22 | 23 | pub struct Reactor { 24 | pub thread_pool: futures::executor::ThreadPool, 25 | pub rayon_pool: Arc, 26 | 27 | pub graph_query: Arc, 28 | 29 | pub overlay_create_tx: Sender, 30 | pub overlay_create_rx: Receiver, 31 | 32 | pub gpu_tasks: Arc, 33 | 34 | pub clipboard_ctx: Arc>, 35 | 36 | pub future_tx: 37 | Sender + Send + Sync + 'static>>>, 38 | // pub future_tx: Sender + 'static>>, 39 | 40 | // pub future_tx: Sender>, 41 | // pub task_rx: Receiver>, 42 | _task_thread: std::thread::JoinHandle<()>, 43 | } 44 | 45 | impl Reactor { 46 | pub fn init( 47 | thread_pool: futures::executor::ThreadPool, 48 | rayon_pool: rayon::ThreadPool, 49 | graph_query: Arc, 50 | channels: &AppChannels, 51 | ) -> Self { 52 | let rayon_pool = Arc::new(rayon_pool); 53 | 54 | let (task_tx, task_rx) = crossbeam::channel::unbounded(); 55 | 56 | let thread_pool_ = thread_pool.clone(); 57 | 58 | let _task_thread = std::thread::spawn(move || { 59 | let thread_pool = thread_pool_; 60 | 61 | while let Ok(task) = task_rx.recv() { 62 | thread_pool.spawn(task).unwrap(); 63 | } 64 | }); 65 | 66 | let clipboard_ctx = 67 | Arc::new(Mutex::new(ClipboardProvider::new().unwrap())); 68 | 69 | Self { 70 | thread_pool, 71 | rayon_pool, 72 | 73 | graph_query, 74 | 75 | gpu_tasks: Arc::new(GpuTasks::default()), 76 | 77 | clipboard_ctx, 78 | 79 | overlay_create_tx: channels.new_overlay_tx.clone(), 80 | overlay_create_rx: channels.new_overlay_rx.clone(), 81 | 82 | future_tx: task_tx, 83 | // task_rx, 84 | _task_thread, 85 | } 86 | } 87 | 88 | pub fn set_clipboard_contents(&self, contents: &str, block: bool) { 89 | if block { 90 | let mut ctx = self.clipboard_ctx.lock(); 91 | ctx.set_contents(contents.to_string()).unwrap(); 92 | } else if let Some(mut ctx) = self.clipboard_ctx.try_lock() { 93 | ctx.set_contents(contents.to_string()).unwrap(); 94 | } 95 | } 96 | 97 | pub fn get_clipboard_contents(&self, block: bool) -> Option { 98 | if block { 99 | let mut ctx = self.clipboard_ctx.lock(); 100 | ctx.get_contents().ok() 101 | } else if let Some(mut ctx) = self.clipboard_ctx.try_lock() { 102 | ctx.get_contents().ok() 103 | } else { 104 | None 105 | } 106 | } 107 | 108 | pub fn create_host(&self, func: F) -> Host 109 | where 110 | T: Send + Sync + 'static, 111 | I: Send + Sync + 'static, 112 | F: Fn(&Outbox, I) -> T + Send + Sync + 'static, 113 | { 114 | let boxed_func = Box::new(func) as Box<_>; 115 | 116 | let (host, proc) = create_host_pair(boxed_func); 117 | 118 | let mut processor = Box::new(proc) as Box; 119 | 120 | self.thread_pool 121 | .spawn(async move { 122 | log::debug!("spawning reactor task"); 123 | 124 | loop { 125 | let _result = processor.process().await; 126 | } 127 | }) 128 | .expect("Error when spawning reactor task"); 129 | 130 | host 131 | } 132 | 133 | pub fn spawn_interval( 134 | &self, 135 | mut func: F, 136 | dur: std::time::Duration, 137 | ) -> anyhow::Result> 138 | where 139 | F: FnMut() + Send + Sync + 'static, 140 | { 141 | use futures_timer::Delay; 142 | 143 | let result = self.thread_pool.spawn_with_handle(async move { 144 | /* 145 | let looper = || { 146 | let delay = Delay::new(dur); 147 | async { 148 | delay.await; 149 | func(); 150 | } 151 | }; 152 | */ 153 | 154 | loop { 155 | let delay = Delay::new(dur); 156 | delay.await; 157 | func(); 158 | } 159 | })?; 160 | Ok(result) 161 | } 162 | 163 | pub fn spawn(&self, fut: F) -> anyhow::Result> 164 | where 165 | F: Future + Send + Sync + 'static, 166 | T: Send + Sync + 'static, 167 | { 168 | let handle = self.thread_pool.spawn_with_handle(fut)?; 169 | Ok(handle) 170 | } 171 | 172 | pub fn spawn_forget(&self, fut: F) -> anyhow::Result<()> 173 | where 174 | F: Future + Send + Sync + 'static, 175 | { 176 | let fut = Box::pin(fut) as _; 177 | self.future_tx.send(fut)?; 178 | Ok(()) 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/gui/util.rs: -------------------------------------------------------------------------------- 1 | use crossbeam::atomic::AtomicCell; 2 | use std::cell::RefCell; 3 | use std::rc::Rc; 4 | // use parking_lot::RefCell 5 | 6 | #[derive(Default)] 7 | pub struct ColumnWidthsVec { 8 | widths_hdr: Rc>>, 9 | widths: Rc>>, 10 | } 11 | 12 | impl ColumnWidthsVec { 13 | pub fn get(&self) -> Vec { 14 | let prev_hdr = self.widths_hdr.borrow(); 15 | let prev = self.widths.borrow(); 16 | // let mut ws = [0.0f32; N]; 17 | 18 | // let prev_hdr = self.widths_hdr.load(); 19 | // let prev = self.widths.load(); 20 | 21 | let prevs = prev_hdr.iter().zip(prev.iter()).map(|(h, r)| h.max(*r)); 22 | 23 | prevs.collect() 24 | } 25 | 26 | pub fn set_hdr(&self, widths: &[f32]) { 27 | let mut ws = self.widths_hdr.borrow_mut(); 28 | ws.clear(); 29 | ws.extend_from_slice(widths); 30 | } 31 | 32 | pub fn set(&self, widths: &[f32]) { 33 | let mut ws = self.widths.borrow_mut(); 34 | ws.clear(); 35 | ws.extend_from_slice(widths); 36 | } 37 | } 38 | 39 | pub struct ColumnWidths { 40 | widths_hdr: AtomicCell<[f32; N]>, 41 | widths: AtomicCell<[f32; N]>, 42 | } 43 | 44 | impl ColumnWidths { 45 | pub fn get(&self) -> [f32; N] { 46 | let mut ws = [0.0f32; N]; 47 | 48 | let prev_hdr = self.widths_hdr.load(); 49 | let prev = self.widths.load(); 50 | 51 | let prevs = prev_hdr.iter().zip(prev).map(|(h, r)| h.max(r)); 52 | 53 | for (w, prev) in ws.iter_mut().zip(prevs) { 54 | *w = prev 55 | } 56 | 57 | ws 58 | } 59 | 60 | pub fn set_hdr(&self, widths: &[f32]) { 61 | let mut ws = self.widths_hdr.load(); 62 | 63 | for (ix, w) in ws.iter_mut().enumerate() { 64 | if let Some(new_w) = widths.get(ix).copied() { 65 | *w = new_w; 66 | } 67 | } 68 | self.widths_hdr.store(ws); 69 | } 70 | 71 | pub fn set(&self, widths: &[f32]) { 72 | let mut ws = self.widths.load(); 73 | 74 | for (ix, w) in ws.iter_mut().enumerate() { 75 | if let Some(new_w) = widths.get(ix).copied() { 76 | *w = new_w; 77 | } 78 | } 79 | self.widths.store(ws); 80 | } 81 | } 82 | 83 | impl std::default::Default for ColumnWidths { 84 | fn default() -> Self { 85 | let arr = [0.0; N]; 86 | Self { 87 | widths_hdr: arr.into(), 88 | widths: arr.into(), 89 | } 90 | } 91 | } 92 | 93 | fn add_label_width( 94 | ui: &mut egui::Ui, 95 | width: f32, 96 | text: &str, 97 | ) -> (f32, egui::Response) { 98 | let label = egui::Label::new(text); 99 | let galley = label.layout(ui); 100 | let size = galley.size(); 101 | let real_width = size.x; 102 | 103 | let resp = ui 104 | .with_layout(egui::Layout::right_to_left(), |ui| { 105 | ui.set_min_width(width.max(real_width)); 106 | ui.add(label) 107 | }) 108 | .response; 109 | 110 | (real_width, resp) 111 | } 112 | 113 | pub fn grid_row_label( 114 | ui: &mut egui::Ui, 115 | id: egui::Id, 116 | fields: &[&str], 117 | with_separator: bool, 118 | prev_widths: Option<&[f32]>, 119 | ) -> egui::InnerResponse> { 120 | assert!(!fields.is_empty()); 121 | 122 | let mut row: Option = None; 123 | 124 | let cols = fields.len(); 125 | let mut prev_widths = prev_widths 126 | .map(|ws| Vec::from(ws)) 127 | .unwrap_or(vec![0.0f32; cols]); 128 | 129 | if prev_widths.len() < fields.len() { 130 | for _ in 0..(cols - prev_widths.len()) { 131 | prev_widths.push(0.0); 132 | } 133 | } 134 | 135 | let mut widths = vec![0.0f32; cols]; 136 | 137 | for (ix, (field, width)) in fields.into_iter().zip(prev_widths).enumerate() 138 | { 139 | if with_separator { 140 | if let Some(r) = row.as_mut() { 141 | *r = r.union(ui.separator()); 142 | } 143 | }; 144 | 145 | let (w, resp) = add_label_width(ui, width, field); 146 | 147 | widths[ix] = w; 148 | 149 | if let Some(r) = row.as_mut() { 150 | *r = r.union(resp); 151 | } else { 152 | row = Some(resp); 153 | } 154 | } 155 | 156 | let row = ui.interact( 157 | row.unwrap().rect, 158 | id, 159 | egui::Sense::click().union(egui::Sense::hover()), 160 | ); 161 | 162 | let visuals = ui.style().interact_selectable(&row, false); 163 | 164 | ui.end_row(); 165 | 166 | if row.hovered() { 167 | // let mut rect = row.rect; 168 | // rect.max.x = ui.max_rect().right(); 169 | 170 | let rect = row.rect.expand(visuals.expansion); 171 | 172 | ui.painter().rect_stroke(rect, 0.0, visuals.bg_stroke); 173 | } 174 | 175 | egui::InnerResponse { 176 | inner: widths, 177 | response: row, 178 | } 179 | } 180 | 181 | pub fn add_scroll_buttons(ui: &mut egui::Ui) -> Option { 182 | ui.horizontal(|ui| { 183 | let mut r = None; 184 | if ui.button("Top").clicked() { 185 | r = Some(egui::Align::TOP); 186 | } 187 | 188 | if ui.button("Bottom").clicked() { 189 | r = Some(egui::Align::BOTTOM); 190 | } 191 | 192 | r 193 | }) 194 | .inner 195 | } 196 | 197 | pub fn scrolled_area( 198 | ui: &mut egui::Ui, 199 | num_rows: usize, 200 | scroll_align: Option, 201 | ) -> egui::ScrollArea { 202 | let text_style = egui::TextStyle::Body; 203 | let row_height = ui.fonts()[text_style].row_height(); 204 | let spacing = ui.style().spacing.item_spacing.y; 205 | 206 | let mut scroll_area = egui::ScrollArea::vertical(); 207 | 208 | if let Some(align) = scroll_align { 209 | let h = row_height + spacing; 210 | let offset = match align { 211 | egui::Align::Min => 0.0, 212 | egui::Align::Max => h * (num_rows + 1) as f32, 213 | _ => 0.0, 214 | }; 215 | scroll_area = scroll_area.scroll_offset(offset); 216 | } 217 | 218 | scroll_area 219 | } 220 | -------------------------------------------------------------------------------- /src/view.rs: -------------------------------------------------------------------------------- 1 | use crate::geometry::Point; 2 | 3 | use nalgebra_glm as glm; 4 | 5 | #[rustfmt::skip] 6 | #[inline] 7 | pub fn viewport_scale(width: f32, height: f32) -> glm::Mat4 { 8 | let w = width; 9 | let h = height; 10 | glm::mat4(2.0 / w, 0.0, 0.0, 0.0, 11 | 0.0, 2.0 / h, 0.0, 0.0, 12 | 0.0, 0.0, 1.0, 0.0, 13 | 0.0, 0.0, 0.0, 1.0) 14 | } 15 | 16 | #[derive(Debug, Default, Clone, Copy, PartialEq, PartialOrd)] 17 | pub struct ScreenDims { 18 | pub width: f32, 19 | pub height: f32, 20 | } 21 | 22 | impl From for ScreenDims { 23 | #[inline] 24 | fn from(point: Point) -> Self { 25 | Self { 26 | width: point.x, 27 | height: point.y, 28 | } 29 | } 30 | } 31 | 32 | impl Into for ScreenDims { 33 | #[inline] 34 | fn into(self) -> Point { 35 | Point { 36 | x: self.width, 37 | y: self.height, 38 | } 39 | } 40 | } 41 | 42 | impl From<(f32, f32)> for ScreenDims { 43 | #[inline] 44 | fn from((width, height): (f32, f32)) -> Self { 45 | Self { width, height } 46 | } 47 | } 48 | 49 | impl From<[f32; 2]> for ScreenDims { 50 | #[inline] 51 | fn from(dims: [f32; 2]) -> Self { 52 | Self { 53 | width: dims[0], 54 | height: dims[1], 55 | } 56 | } 57 | } 58 | 59 | impl From<[u32; 2]> for ScreenDims { 60 | #[inline] 61 | fn from(dims: [u32; 2]) -> Self { 62 | Self { 63 | width: dims[0] as f32, 64 | height: dims[1] as f32, 65 | } 66 | } 67 | } 68 | 69 | impl Into<[f32; 2]> for ScreenDims { 70 | #[inline] 71 | fn into(self) -> [f32; 2] { 72 | [self.width, self.height] 73 | } 74 | } 75 | 76 | impl Into<[u32; 2]> for ScreenDims { 77 | #[inline] 78 | fn into(self) -> [u32; 2] { 79 | [self.width as u32, self.height as u32] 80 | } 81 | } 82 | 83 | #[derive(Debug, Clone, Copy, PartialEq, PartialOrd)] 84 | pub struct View { 85 | pub center: Point, 86 | pub scale: f32, 87 | } 88 | 89 | impl Default for View { 90 | #[inline] 91 | fn default() -> Self { 92 | Self { 93 | center: Point::new(0.0, 0.0), 94 | scale: 10.0, 95 | } 96 | } 97 | } 98 | 99 | impl View { 100 | pub fn from_dims_and_target>( 101 | screen_dims: D, 102 | p_0: Point, 103 | p_1: Point, 104 | ) -> Self { 105 | let dims = screen_dims.into(); 106 | 107 | let top_left = Point { 108 | x: p_0.x.min(p_1.x), 109 | y: p_0.y.min(p_1.y), 110 | }; 111 | 112 | let bottom_right = Point { 113 | x: p_0.x.max(p_1.x), 114 | y: p_0.y.max(p_1.y), 115 | }; 116 | 117 | let target_dims = Point { 118 | x: bottom_right.x - top_left.x, 119 | y: bottom_right.y - top_left.y, 120 | }; 121 | 122 | let scale = if target_dims.x > target_dims.y { 123 | let width = target_dims.x; 124 | width / dims.width 125 | } else { 126 | let height = target_dims.y; 127 | height / dims.height 128 | }; 129 | 130 | let scale = scale * 1.05; // add a bit extra so everything fits 131 | 132 | let center = top_left + (target_dims * 0.5); 133 | 134 | View { center, scale } 135 | } 136 | 137 | #[rustfmt::skip] 138 | #[inline] 139 | pub fn to_scaled_matrix(&self) -> glm::Mat4 { 140 | 141 | let scale = 1.0 / self.scale; 142 | 143 | let scaling = 144 | glm::mat4(scale, 0.0, 0.0, 0.0, 145 | 0.0, scale, 0.0, 0.0, 146 | 0.0, 0.0, 1.0, 1.0, 147 | 0.0, 0.0, 0.0, 1.0); 148 | 149 | let x_ = self.center.x; 150 | let y_ = self.center.y; 151 | 152 | let translation = 153 | glm::mat4(1.0, 0.0, 0.0, -x_, 154 | 0.0, 1.0, 0.0, -y_, 155 | 0.0, 0.0, 1.0, 0.0, 156 | 0.0, 0.0, 0.0, 1.0); 157 | 158 | scaling * translation 159 | } 160 | 161 | #[rustfmt::skip] 162 | #[inline] 163 | pub fn world_to_screen_map(&self) -> glm::Mat4 { 164 | let s = 1.0 / self.scale; 165 | let vcx = self.center.x; 166 | 167 | let view_scale_screen = 168 | glm::mat4(s, 0.0, 0.0, (s * 0.5) - vcx, 169 | 0.0, s, 0.0, (s * 0.5) - vcx, 170 | 0.0, 0.0, 1.0, 0.0, 171 | 0.0, 0.0, 0.0, 1.0); 172 | 173 | view_scale_screen 174 | } 175 | 176 | #[rustfmt::skip] 177 | #[inline] 178 | pub fn screen_to_world_map>(&self, dims: Dims) -> glm::Mat4 { 179 | let dims = dims.into(); 180 | 181 | let w = dims.width; 182 | let h = dims.height; 183 | 184 | let s = self.scale; 185 | let vcx = self.center.x; 186 | let vcy = self.center.y; 187 | 188 | // transform from screen coords (top left (0, 0), bottom right (w, h)) 189 | // to screen center = (0, 0), bottom right (w/2, h/2); 190 | // 191 | // then scale so bottom right = (s*w/2, s*h/2); 192 | // 193 | // finally translate by view center to world coordinates 194 | // 195 | // i.e. view_offset * scale * screen_center 196 | let view_scale_screen = 197 | glm::mat4(s, 0.0, 0.0, vcx - (w * s * 0.5), 198 | 0.0, s, 0.0, vcy - (h * s * 0.5), 199 | 0.0, 0.0, 1.0, 0.0, 200 | 0.0, 0.0, 0.0, 1.0); 201 | 202 | view_scale_screen 203 | } 204 | 205 | #[rustfmt::skip] 206 | #[inline] 207 | pub fn screen_point_to_world>(&self, dims: Dims, screen_point: Point) -> Point { 208 | let to_world_mat = self.screen_to_world_map(dims); 209 | 210 | let projected = to_world_mat * glm::vec4(screen_point.x, screen_point.y, 0.0, 1.0); 211 | 212 | Point { x: projected[0], y: projected[1] } 213 | } 214 | 215 | pub fn world_point_to_screen(&self, world: Point) -> Point { 216 | let to_screen_mat = self.to_scaled_matrix(); 217 | 218 | let projected = to_screen_mat * glm::vec4(world.x, world.y, 0.0, 1.0); 219 | 220 | Point { 221 | x: projected[0], 222 | y: projected[1], 223 | } 224 | } 225 | } 226 | 227 | #[inline] 228 | pub fn mat4_to_array(matrix: &glm::Mat4) -> [[f32; 4]; 4] { 229 | let s = glm::value_ptr(matrix); 230 | 231 | let col0 = [s[0], s[1], s[2], s[3]]; 232 | let col1 = [s[4], s[5], s[6], s[7]]; 233 | let col2 = [s[8], s[9], s[10], s[11]]; 234 | let col3 = [s[12], s[13], s[14], s[15]]; 235 | 236 | [col0, col1, col2, col3] 237 | } 238 | -------------------------------------------------------------------------------- /src/gui/windows/graph_picker.rs: -------------------------------------------------------------------------------- 1 | #[allow(unused_imports)] 2 | use handlegraph::{ 3 | handle::{Direction, Handle, NodeId}, 4 | handlegraph::*, 5 | mutablehandlegraph::*, 6 | packed::*, 7 | packedgraph::index::OneBasedIndex, 8 | packedgraph::*, 9 | path_position::*, 10 | pathhandlegraph::*, 11 | }; 12 | 13 | use std::sync::Arc; 14 | 15 | use bstr::ByteSlice; 16 | 17 | use anyhow::Result; 18 | 19 | use crate::graph_query::GraphQuery; 20 | 21 | use super::filters::FilterString; 22 | 23 | pub struct PathPickerSource { 24 | paths: Arc>, 25 | 26 | id_counter: usize, 27 | } 28 | 29 | pub struct PathPicker { 30 | paths: Arc>, 31 | filtered_paths: Vec, 32 | 33 | name_filter: FilterString, 34 | 35 | id: usize, 36 | active_path_index: Option, 37 | 38 | offset: usize, 39 | slot_count: usize, 40 | } 41 | 42 | impl PathPickerSource { 43 | pub fn new(graph_query: &GraphQuery) -> Result { 44 | let graph = graph_query.graph(); 45 | let paths_vec = graph 46 | .path_ids() 47 | .filter_map(|id| { 48 | let name = graph.get_path_name_vec(id)?; 49 | let name = name.to_str().ok()?; 50 | 51 | Some((id, name.to_string())) 52 | }) 53 | .collect::>(); 54 | 55 | let paths = Arc::new(paths_vec); 56 | 57 | Ok(Self { 58 | paths, 59 | id_counter: 0, 60 | }) 61 | } 62 | 63 | pub fn create_picker(&mut self) -> PathPicker { 64 | let paths = self.paths.clone(); 65 | let filtered_paths = Vec::with_capacity(paths.len()); 66 | 67 | let offset = 0; 68 | let slot_count = 20; 69 | 70 | let id = self.id_counter; 71 | self.id_counter += 1; 72 | 73 | PathPicker { 74 | paths, 75 | filtered_paths, 76 | name_filter: Default::default(), 77 | id, 78 | active_path_index: None, 79 | offset, 80 | slot_count, 81 | } 82 | } 83 | } 84 | 85 | impl PathPicker { 86 | pub fn ui( 87 | &mut self, 88 | ctx: &egui::CtxRef, 89 | open: &mut bool, 90 | ) -> Option>> { 91 | egui::Window::new("Path picker") 92 | .id(egui::Id::new(("Path picker", self.id))) 93 | .open(open) 94 | .collapsible(false) 95 | .show(ctx, |mut ui| { 96 | self.name_filter.ui(ui); 97 | 98 | ui.horizontal(|ui| { 99 | if ui.button("Apply filter").clicked() { 100 | self.apply_filter(); 101 | } 102 | 103 | if ui.button("Clear filter").clicked() { 104 | self.clear_filter(); 105 | } 106 | }); 107 | 108 | let grid = egui::Grid::new("path_picker_list_grid") 109 | .striped(true) 110 | .show(&mut ui, |ui| { 111 | let active_path_index = self.active_path_index; 112 | 113 | if self.filtered_paths.is_empty() { 114 | for i in 0..self.slot_count { 115 | let index = self.offset + i; 116 | 117 | if let Some((_path_id, name)) = 118 | self.paths.get(index) 119 | { 120 | if ui 121 | .selectable_label( 122 | active_path_index == Some(index), 123 | name, 124 | ) 125 | .clicked() 126 | { 127 | self.active_path_index = Some(index); 128 | } 129 | ui.end_row(); 130 | } 131 | } 132 | } else { 133 | for i in 0..self.slot_count { 134 | if let Some((index, name)) = self 135 | .filtered_paths 136 | .get(self.offset + i) 137 | .and_then(|&ix| { 138 | let (_, name) = self.paths.get(ix)?; 139 | Some((ix, name)) 140 | }) 141 | { 142 | if ui 143 | .selectable_label( 144 | active_path_index == Some(index), 145 | name, 146 | ) 147 | .clicked() 148 | { 149 | self.active_path_index = Some(index); 150 | } 151 | ui.end_row(); 152 | } 153 | } 154 | } 155 | }); 156 | 157 | if grid.response.hover_pos().is_some() { 158 | let scroll = ctx.input().scroll_delta; 159 | if scroll.y.abs() >= 4.0 { 160 | let sig = (scroll.y.signum() as isize) * -1; 161 | let delta = sig * ((scroll.y.abs() as isize) / 4); 162 | 163 | let mut offset = self.offset as isize; 164 | 165 | offset += delta; 166 | 167 | let path_count = self.paths.len() as isize; 168 | let slot_count = self.slot_count as isize; 169 | 170 | offset = offset.clamp(0, path_count - slot_count); 171 | self.offset = offset as usize; 172 | } 173 | } 174 | }) 175 | } 176 | 177 | pub fn active_path(&self) -> Option<(PathId, &str)> { 178 | let ix = self.active_path_index?; 179 | let (id, name) = self.paths.get(ix)?; 180 | 181 | Some((*id, name)) 182 | } 183 | 184 | fn apply_filter(&mut self) { 185 | self.filtered_paths.clear(); 186 | 187 | let paths = &self.paths; 188 | let filter = &self.name_filter; 189 | let filtered_paths = &mut self.filtered_paths; 190 | 191 | filtered_paths.extend(paths.iter().enumerate().filter_map( 192 | |(ix, (_, path_name))| { 193 | if filter.filter_str(path_name) { 194 | Some(ix) 195 | } else { 196 | None 197 | } 198 | }, 199 | )); 200 | 201 | self.offset = 0; 202 | } 203 | 204 | fn clear_filter(&mut self) { 205 | self.filtered_paths.clear(); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/gui/windows/repl.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | 3 | use futures::executor::ThreadPool; 4 | use futures::task::SpawnExt; 5 | 6 | use std::sync::Arc; 7 | 8 | use crate::geometry::Point; 9 | 10 | use crate::gluon::repl::GluonRepl; 11 | 12 | pub struct ReplWindow { 13 | repl: GluonRepl, 14 | 15 | line_input: String, 16 | output: String, 17 | } 18 | 19 | impl ReplWindow { 20 | pub const ID: &'static str = "repl_window"; 21 | 22 | pub fn new(repl: GluonRepl) -> Result { 23 | let line_input = String::new(); 24 | let output = String::new(); 25 | 26 | let future = async { 27 | repl.gluon_vm 28 | .eval_line("let io @ { ? } = import! std.io") 29 | .await; 30 | repl.gluon_vm.eval_line("let repl = import! repl").await; 31 | }; 32 | 33 | futures::executor::block_on(future); 34 | 35 | Ok(Self { 36 | repl, 37 | 38 | line_input, 39 | output, 40 | }) 41 | } 42 | 43 | pub fn ui( 44 | &mut self, 45 | open: &mut bool, 46 | ctx: &egui::CtxRef, 47 | thread_pool: &ThreadPool, 48 | ) -> Option { 49 | let scr = ctx.input().screen_rect(); 50 | 51 | let pos = egui::pos2(scr.center().x + 150.0, scr.center().y - 60.0); 52 | 53 | while let Ok(msg) = self.repl.output_rx.try_recv() { 54 | self.output.push_str(&msg); 55 | } 56 | 57 | let line_count = self.output.lines().count(); 58 | if line_count > 20 { 59 | let mut new_output = String::new(); 60 | 61 | let lines = self.output.lines().skip(line_count - 20); 62 | 63 | for line in lines { 64 | new_output.push_str(line); 65 | new_output.push_str("\n"); 66 | } 67 | 68 | self.output = new_output; 69 | } 70 | 71 | egui::Window::new("REPL") 72 | .id(egui::Id::new(Self::ID)) 73 | .open(open) 74 | .default_pos(pos) 75 | .show(ctx, |ui| { 76 | // ui.set_max_width(1000.0); 77 | 78 | ui.vertical(|ui| { 79 | egui::ScrollArea::auto_sized().show(ui, |ui| { 80 | let history = 81 | egui::TextEdit::multiline(&mut self.output) 82 | .text_style(egui::TextStyle::Monospace) 83 | .enabled(false); 84 | 85 | ui.add(history); 86 | }); 87 | 88 | let input_box = ui.add( 89 | egui::TextEdit::multiline(&mut self.line_input) 90 | .text_style(egui::TextStyle::Monospace) 91 | .desired_rows(1), 92 | ); 93 | 94 | if ui.button("Submit").clicked() 95 | || (input_box.has_focus() 96 | && ui.input().key_pressed(egui::Key::Enter)) 97 | { 98 | let future = self.repl.eval_line(&self.line_input); 99 | 100 | self.line_input.clear(); 101 | 102 | thread_pool 103 | .spawn(async move { 104 | future.await; 105 | }) 106 | .unwrap(); 107 | } 108 | }); 109 | }) 110 | } 111 | } 112 | 113 | #[derive(Debug)] 114 | pub struct HistoryBox { 115 | lines: Vec, 116 | desired_width: Option, 117 | desired_height_rows: usize, 118 | 119 | history_lines: usize, 120 | } 121 | 122 | // impl HistoryBox { 123 | // fn content_ui(self, ui: &mut egui::Ui) -> egui::Response { 124 | impl egui::Widget for HistoryBox { 125 | fn ui(self, ui: &mut egui::Ui) -> egui::Response { 126 | use egui::epaint::text::Galley; 127 | use egui::Sense; 128 | 129 | let text_style = egui::TextStyle::Monospace; 130 | let line_spacing = ui.fonts().row_height(text_style); 131 | let available_width = ui.available_width(); 132 | 133 | let start_ix = if self.lines.len() >= self.history_lines { 134 | self.lines.len() - self.history_lines 135 | } else { 136 | 0 137 | }; 138 | 139 | let mut galleys: Vec<(f32, Arc)> = 140 | Vec::with_capacity(self.history_lines); 141 | let mut row_offset = 0.0f32; 142 | 143 | let mut desired_size = Point::ZERO; 144 | 145 | for line in self.lines[start_ix..].iter() { 146 | let galley = ui.fonts().layout_multiline( 147 | text_style, 148 | line.to_owned(), 149 | available_width, 150 | ); 151 | let offset = row_offset; 152 | row_offset += galley.size.y + line_spacing; 153 | 154 | desired_size.x = desired_size.x.max(galley.size.x); 155 | desired_size.y += galley.size.y + line_spacing; 156 | 157 | galleys.push((offset, galley)); 158 | } 159 | 160 | // let desired_width = 400.0; 161 | // let desired_height = (20 as f32) * line_spacing; 162 | let (auto_id, rect) = ui.allocate_space(desired_size.into()); 163 | 164 | let response = ui.interact(rect, auto_id, Sense::hover()); 165 | 166 | egui::ScrollArea::auto_sized().show(ui, |ui| { 167 | for (offset, galley) in galleys { 168 | let mut pos = response.rect.min; 169 | pos += (Point { x: 0.0, y: offset }).into(); 170 | 171 | ui.painter().galley( 172 | pos, 173 | galley, 174 | egui::Color32::from_rgb(200, 200, 200), 175 | ); 176 | } 177 | }); 178 | 179 | response 180 | } 181 | } 182 | 183 | /* 184 | impl egui::Widget for HistoryBox { 185 | fn ui(self, ui: &mut egui::Ui) -> egui::Response { 186 | use egui::{Sense, Shape, Vec2}; 187 | 188 | let frame = self.frame; 189 | let where_to_put_background = ui.painter().add(Shape::Noop); 190 | 191 | let margin = Vec2::new(4.0, 2.0); 192 | let max_rect = ui.available_rect_before_wrap().shrink2(margin); 193 | let mut content_ui = ui.child_ui(max_rect, *ui.layout()); 194 | let response = self.content_ui(&mut content_ui); 195 | let frame_rect = response.rect.expand2(margin); 196 | let response = response | ui.allocate_rect(frame_rect, Sense::hover()); 197 | 198 | if frame { 199 | let visuals = ui.style().interact(&response); 200 | let frame_rect = response.rect.expand(visuals.expansion); 201 | let shape = if response.has_focus() { 202 | Shape::Rect { 203 | rect: frame_rect, 204 | corner_radius: visuals.corner_radius, 205 | // fill: ui.visuals().selection.bg_fill, 206 | fill: ui.visuals().extreme_bg_color, 207 | stroke: ui.visuals().selection.stroke, 208 | } 209 | } else { 210 | Shape::Rect { 211 | rect: frame_rect, 212 | corner_radius: visuals.corner_radius, 213 | fill: ui.visuals().extreme_bg_color, 214 | stroke: visuals.bg_stroke, // TODO: we want to show something here, or a text-edit field doesn't "pop". 215 | } 216 | }; 217 | 218 | ui.painter().set(where_to_put_background, shape); 219 | } 220 | 221 | response 222 | } 223 | } 224 | 225 | 226 | */ 227 | -------------------------------------------------------------------------------- /src/vulkan/debug.rs: -------------------------------------------------------------------------------- 1 | use ash::version::EntryV1_0; 2 | use ash::{vk, Entry, Instance}; 3 | use std::{ 4 | ffi::{CStr, CString}, 5 | os::raw::{c_char, c_void}, 6 | }; 7 | 8 | use ash::extensions::ext::DebugUtils; 9 | 10 | #[allow(unused_imports)] 11 | use log::{debug, error, info, trace, warn}; 12 | 13 | #[cfg(debug_assertions)] 14 | pub const ENABLE_VALIDATION_LAYERS: bool = true; 15 | #[cfg(not(debug_assertions))] 16 | pub const ENABLE_VALIDATION_LAYERS: bool = false; 17 | 18 | const REQUIRED_LAYERS: [&str; 1] = ["VK_LAYER_KHRONOS_validation"]; 19 | 20 | unsafe extern "system" fn vulkan_debug_utils_callback( 21 | msg_severity: vk::DebugUtilsMessageSeverityFlagsEXT, 22 | msg_type: vk::DebugUtilsMessageTypeFlagsEXT, 23 | callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT, 24 | _user_data: *mut c_void, 25 | ) -> u32 { 26 | use vk::DebugUtilsMessageSeverityFlagsEXT as MsgSeverity; 27 | // use vk::DebugUtilsMessageTypeFlagsEXT as MsgType; 28 | 29 | // might be better to use the ordering like this, but i'll fix 30 | // that later if it's worthwhile 31 | 32 | // if msg_severity <= MsgSeverity::VERBOSE { 33 | // } else if msg_severity <= MsgSeverity::INFO { 34 | // } else if msg_severity <= MsgSeverity::WARNING { 35 | // } else if msg_severity <= MsgSeverity::ERROR { 36 | // } 37 | 38 | let p_message_id = (*callback_data).p_message_id_name as *const c_char; 39 | let p_message = (*callback_data).p_message as *const c_char; 40 | 41 | let _queue_labels = { 42 | let queue_label_count = (*callback_data).queue_label_count as usize; 43 | let ptr = (*callback_data).p_queue_labels; 44 | std::slice::from_raw_parts(ptr, queue_label_count) 45 | }; 46 | 47 | let cmd_buf_labels = { 48 | let cmd_buf_label_count = (*callback_data).cmd_buf_label_count as usize; 49 | let ptr = (*callback_data).p_cmd_buf_labels; 50 | std::slice::from_raw_parts(ptr, cmd_buf_label_count) 51 | }; 52 | 53 | let objects = { 54 | let object_count = (*callback_data).object_count as usize; 55 | let ptr = (*callback_data).p_objects; 56 | std::slice::from_raw_parts(ptr, object_count) 57 | }; 58 | 59 | let p_msg_id_str = if p_message_id.is_null() { 60 | "0".to_string() 61 | } else { 62 | format!("{:?}", CStr::from_ptr(p_message_id)) 63 | }; 64 | 65 | let p_msg_str = if p_message.is_null() { 66 | "-".to_string() 67 | } else { 68 | format!("{:?}", CStr::from_ptr(p_message)) 69 | }; 70 | 71 | let mut message_string = 72 | format!("{} - {:?} - {}", p_msg_id_str, msg_type, p_msg_str,); 73 | 74 | if !cmd_buf_labels.is_empty() { 75 | message_string.push_str("\n Command buffers: "); 76 | for cmd_buf in cmd_buf_labels { 77 | if !cmd_buf.p_label_name.is_null() { 78 | message_string.push_str(&format!( 79 | "{:?}", 80 | CStr::from_ptr(cmd_buf.p_label_name) 81 | )); 82 | } 83 | } 84 | 85 | message_string.push_str("\n"); 86 | } 87 | 88 | if !objects.is_empty() { 89 | let mut first = true; 90 | for obj in objects { 91 | if !obj.p_object_name.is_null() { 92 | if first { 93 | message_string.push_str("\n Objects: \n"); 94 | first = false; 95 | } 96 | message_string.push_str(&format!( 97 | " {:#x} - {:?}\n", 98 | obj.object_handle, 99 | CStr::from_ptr(obj.p_object_name), 100 | )); 101 | } 102 | } 103 | } 104 | 105 | match msg_severity { 106 | MsgSeverity::VERBOSE => { 107 | debug!("{}", message_string); 108 | } 109 | MsgSeverity::INFO => { 110 | info!("{}", message_string); 111 | } 112 | MsgSeverity::WARNING => { 113 | warn!("{}", message_string); 114 | } 115 | MsgSeverity::ERROR => { 116 | error!("{}", message_string); 117 | } 118 | _ => { 119 | error!("{}", message_string); 120 | } 121 | } 122 | 123 | vk::FALSE 124 | } 125 | 126 | /// Get the pointers to the validation layers names. 127 | /// Also return the corresponding `CString` to avoid dangling pointers. 128 | pub fn get_layer_names_and_pointers() -> (Vec, Vec<*const c_char>) { 129 | let layer_names = REQUIRED_LAYERS 130 | .iter() 131 | .map(|name| CString::new(*name).unwrap()) 132 | .collect::>(); 133 | let layer_names_ptrs = layer_names 134 | .iter() 135 | .map(|name| name.as_ptr()) 136 | .collect::>(); 137 | (layer_names, layer_names_ptrs) 138 | } 139 | 140 | /// Check if the required validation set in `REQUIRED_LAYERS` 141 | /// are supported by the Vulkan instance. 142 | /// 143 | /// # Panics 144 | /// 145 | /// Panic if at least one on the layer is not supported. 146 | pub fn check_validation_layer_support(entry: &Entry) { 147 | for required in REQUIRED_LAYERS.iter() { 148 | let found = entry 149 | .enumerate_instance_layer_properties() 150 | .unwrap() 151 | .iter() 152 | .any(|layer| { 153 | let name = unsafe { CStr::from_ptr(layer.layer_name.as_ptr()) }; 154 | let name = 155 | name.to_str().expect("Failed to get layer name pointer"); 156 | required == &name 157 | }); 158 | 159 | if !found { 160 | panic!("Validation layer not supported: {}", required); 161 | } 162 | } 163 | } 164 | 165 | /// Setup the DebugUtils messenger if validation layers are enabled. 166 | pub fn setup_debug_utils( 167 | entry: &Entry, 168 | instance: &Instance, 169 | ) -> Option<(DebugUtils, vk::DebugUtilsMessengerEXT)> { 170 | if !ENABLE_VALIDATION_LAYERS { 171 | return None; 172 | } 173 | 174 | let severity = { 175 | use vk::DebugUtilsMessageSeverityFlagsEXT as Severity; 176 | // TODO use the flexi_logger configuration here 177 | Severity::all() 178 | }; 179 | 180 | let types = { 181 | use vk::DebugUtilsMessageTypeFlagsEXT as Type; 182 | // TODO maybe some customization here too 183 | Type::all() 184 | }; 185 | 186 | let create_info = vk::DebugUtilsMessengerCreateInfoEXT::builder() 187 | .message_severity(severity) 188 | .message_type(types) 189 | .pfn_user_callback(Some(vulkan_debug_utils_callback)) 190 | .build(); 191 | 192 | let debug_utils = DebugUtils::new(entry, instance); 193 | 194 | // TODO this should probably return Result, but i need to handle 195 | // the return at the top of this function first 196 | let messenger = unsafe { 197 | debug_utils 198 | .create_debug_utils_messenger(&create_info, None) 199 | .ok() 200 | }?; 201 | 202 | Some((debug_utils, messenger)) 203 | } 204 | 205 | pub fn begin_cmd_buf_label( 206 | utils: Option<&DebugUtils>, 207 | cmd_buf: vk::CommandBuffer, 208 | label: &str, 209 | ) { 210 | if let Some(utils) = utils { 211 | let name = CString::new(label.as_bytes()).unwrap(); 212 | let label = vk::DebugUtilsLabelEXT::builder().label_name(&name).build(); 213 | unsafe { 214 | utils.cmd_begin_debug_utils_label(cmd_buf, &label); 215 | } 216 | } 217 | } 218 | 219 | pub fn end_cmd_buf_label( 220 | utils: Option<&DebugUtils>, 221 | cmd_buf: vk::CommandBuffer, 222 | ) { 223 | if let Some(utils) = utils { 224 | unsafe { 225 | utils.cmd_end_debug_utils_label(cmd_buf); 226 | } 227 | } 228 | } 229 | -------------------------------------------------------------------------------- /src/gui/windows/annotations/filter.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use bstr::ByteSlice; 4 | 5 | use crate::{ 6 | annotations::{AnnotationCollection, AnnotationRecord, ColumnKey}, 7 | gui::windows::filters::{ 8 | FilterNum, FilterNumOp, FilterString, FilterStringOp, 9 | }, 10 | }; 11 | 12 | use super::ColumnPickerMany; 13 | 14 | #[derive(Debug, Clone, PartialEq)] 15 | pub struct QuickFilter { 16 | filter: FilterString, 17 | columns: ColumnPickerMany, 18 | column_picker_open: bool, 19 | } 20 | 21 | impl QuickFilter { 22 | pub fn new(id: egui::Id) -> Self { 23 | let column_picker_id = id.with("column_picker"); 24 | Self { 25 | filter: Default::default(), 26 | columns: ColumnPickerMany::new(column_picker_id), 27 | column_picker_open: false, 28 | } 29 | } 30 | 31 | pub fn column_picker_mut(&mut self) -> &mut ColumnPickerMany { 32 | &mut self.columns 33 | } 34 | 35 | pub fn filter_record(&self, record: &R) -> bool 36 | where 37 | R: AnnotationRecord, 38 | { 39 | if self.filter.op == FilterStringOp::None { 40 | return true; 41 | } 42 | 43 | let enabled_cols = self 44 | .columns 45 | .enabled_columns 46 | .iter() 47 | .filter_map(|(c, enabled)| if *enabled { Some(c) } else { None }) 48 | .collect::>(); 49 | 50 | if enabled_cols.is_empty() { 51 | return true; 52 | } 53 | 54 | enabled_cols.into_iter().any(|column| { 55 | let values = record.get_all(column); 56 | values.iter().any(|v| self.filter.filter_bytes(v)) 57 | }) 58 | } 59 | 60 | pub fn ui_compact(&mut self, ui: &mut egui::Ui) -> bool { 61 | let filter_resp = self.filter.ui(ui); 62 | 63 | let open = &mut self.column_picker_open; 64 | let column_picker = &mut self.columns; 65 | 66 | let ctx = ui.ctx(); 67 | column_picker.ui(ctx, None, open, "Quick filter columns"); 68 | 69 | if let Some(resp) = filter_resp { 70 | resp.has_focus() && ctx.input().key_pressed(egui::Key::Enter) 71 | } else { 72 | false 73 | } 74 | } 75 | 76 | pub fn ui(&mut self, ui: &mut egui::Ui) -> bool { 77 | ui.horizontal(|ui| { 78 | ui.heading("Quick filter"); 79 | if ui 80 | .selectable_label(self.column_picker_open, "Choose columns") 81 | .clicked() 82 | { 83 | self.column_picker_open = !self.column_picker_open; 84 | } 85 | }); 86 | 87 | let filter_resp = self.filter.ui(ui); 88 | 89 | let open = &mut self.column_picker_open; 90 | let column_picker = &mut self.columns; 91 | 92 | let ctx = ui.ctx(); 93 | column_picker.ui(ctx, None, open, "Quick filter columns"); 94 | 95 | if let Some(resp) = filter_resp { 96 | resp.has_focus() && ctx.input().key_pressed(egui::Key::Enter) 97 | } else { 98 | false 99 | } 100 | } 101 | } 102 | 103 | pub struct RecordFilter { 104 | seq_id: FilterString, 105 | start: FilterNum, 106 | end: FilterNum, 107 | 108 | columns: HashMap, 109 | 110 | pub quick_filter: QuickFilter, 111 | } 112 | 113 | impl RecordFilter { 114 | pub fn new(id: egui::Id, records: &C) -> Self 115 | where 116 | C: AnnotationCollection, 117 | { 118 | let id = id.with("record_filter"); 119 | let mut columns: HashMap = HashMap::new(); 120 | 121 | let to_remove = [T::seq_id(), T::start(), T::end()]; 122 | 123 | let mut to_add = records.all_columns(); 124 | to_add.retain(|c| !to_remove.contains(c)); 125 | 126 | for column in to_add { 127 | columns.insert(column.to_owned(), FilterString::default()); 128 | } 129 | 130 | let mut quick_filter = QuickFilter::new(id); 131 | quick_filter.column_picker_mut().update_columns(records); 132 | 133 | Self { 134 | seq_id: FilterString::default(), 135 | start: FilterNum::default(), 136 | end: FilterNum::default(), 137 | 138 | columns, 139 | 140 | quick_filter, 141 | } 142 | } 143 | 144 | pub fn range_filter(&mut self, mut start: usize, mut end: usize) { 145 | if start > 0 { 146 | start -= 1; 147 | } 148 | 149 | end += 1; 150 | 151 | self.start.op = FilterNumOp::MoreThan; 152 | self.start.arg1 = start; 153 | 154 | self.end.op = FilterNumOp::LessThan; 155 | self.end.arg1 = end; 156 | } 157 | 158 | pub fn chr_range_filter( 159 | &mut self, 160 | seq_id: &[u8], 161 | start: usize, 162 | end: usize, 163 | ) { 164 | if let Ok(seq_id) = seq_id.to_str().map(String::from) { 165 | self.seq_id.op = FilterStringOp::ContainedIn; 166 | self.seq_id.arg = seq_id; 167 | } 168 | self.range_filter(start, end); 169 | } 170 | 171 | pub fn filter_record(&self, record: &R) -> bool 172 | where 173 | R: AnnotationRecord, 174 | { 175 | let in_range = self.seq_id.filter_bytes(record.seq_id()) 176 | && self.start.filter(record.start()) 177 | && self.end.filter(record.end()); 178 | 179 | in_range 180 | && self.quick_filter.filter_record(record) 181 | && self.columns.iter().all(|(column, filter)| { 182 | if filter.op == FilterStringOp::None { 183 | return true; 184 | } 185 | let values = record.get_all(column); 186 | values.into_iter().any(|value| filter.filter_bytes(value)) 187 | }) 188 | } 189 | 190 | // TODO: Returns `true` if the filter has been updated and should be applied 191 | // pub fn ui(&mut self, ui: &mut egui::Ui) -> bool { 192 | pub fn ui(&mut self, ui: &mut egui::Ui) { 193 | let (optional, mandatory): (Vec<_>, Vec<_>) = self 194 | .columns 195 | .iter_mut() 196 | .partition(|(col, _filter)| T::is_column_optional(col)); 197 | 198 | ui.label(T::seq_id().to_string()); 199 | self.seq_id.ui(ui); 200 | ui.separator(); 201 | 202 | ui.label(T::start().to_string()); 203 | self.start.ui(ui); 204 | ui.separator(); 205 | 206 | ui.label(T::end().to_string()); 207 | self.end.ui(ui); 208 | ui.separator(); 209 | 210 | let max_height = ui.input().screen_rect.height() - 250.0; 211 | let scroll_height = (max_height / 2.0) - 50.0; 212 | 213 | ui.collapsing("Mandatory fields", |ui| { 214 | egui::ScrollArea::from_max_height(scroll_height).show(ui, |ui| { 215 | for (column, filter) in mandatory.into_iter() { 216 | ui.label(column.to_string()); 217 | filter.ui(ui); 218 | ui.separator(); 219 | } 220 | }); 221 | }); 222 | ui.collapsing("Optional fields", |ui| { 223 | egui::ScrollArea::from_max_height(scroll_height).show(ui, |ui| { 224 | for (column, filter) in optional.into_iter() { 225 | ui.label(column.to_string()); 226 | filter.ui(ui); 227 | ui.separator(); 228 | } 229 | }); 230 | }); 231 | } 232 | 233 | pub fn add_quick_filter(&mut self, ui: &mut egui::Ui) -> bool { 234 | self.quick_filter.ui_compact(ui) 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/vulkan/draw_system/nodes/vertices.rs: -------------------------------------------------------------------------------- 1 | use ash::vk; 2 | 3 | use anyhow::*; 4 | 5 | use crate::vulkan::{ 6 | context::NodeRendererType, draw_system::Vertex, GfaestusVk, 7 | }; 8 | 9 | pub struct NodeVertices { 10 | pub(crate) vertex_count: usize, 11 | 12 | pub(crate) vertex_buffer: vk::Buffer, 13 | 14 | allocation: vk_mem::Allocation, 15 | allocation_info: Option, 16 | 17 | renderer_type: NodeRendererType, 18 | } 19 | 20 | impl NodeVertices { 21 | pub fn new(renderer_type: NodeRendererType) -> Self { 22 | let vertex_count = 0; 23 | let vertex_buffer = vk::Buffer::null(); 24 | 25 | let allocation = vk_mem::Allocation::null(); 26 | let allocation_info = None; 27 | 28 | Self { 29 | vertex_count, 30 | vertex_buffer, 31 | allocation, 32 | allocation_info, 33 | 34 | renderer_type, 35 | } 36 | } 37 | 38 | pub fn buffer(&self) -> vk::Buffer { 39 | self.vertex_buffer 40 | } 41 | 42 | pub fn has_vertices(&self) -> bool { 43 | self.allocation_info.is_some() 44 | } 45 | 46 | pub fn destroy(&mut self, app: &GfaestusVk) -> Result<()> { 47 | if self.has_vertices() { 48 | app.allocator 49 | .destroy_buffer(self.vertex_buffer, &self.allocation)?; 50 | 51 | self.vertex_buffer = vk::Buffer::null(); 52 | self.allocation = vk_mem::Allocation::null(); 53 | self.allocation_info = None; 54 | 55 | self.vertex_count = 0; 56 | } 57 | 58 | Ok(()) 59 | } 60 | 61 | /// `line` as in the vertex input to the node tessellation stage is 62 | /// one line, or one pair of points, per node 63 | /// 64 | /// the input is one pair of vertices per node 65 | fn upload_line_vertices( 66 | &mut self, 67 | app: &GfaestusVk, 68 | vertices: &[Vertex], 69 | ) -> Result<()> { 70 | assert!(self.renderer_type == NodeRendererType::TessellationQuads); 71 | 72 | if self.has_vertices() { 73 | self.destroy(app)?; 74 | } 75 | 76 | let usage = vk::BufferUsageFlags::VERTEX_BUFFER 77 | | vk::BufferUsageFlags::STORAGE_BUFFER 78 | | vk::BufferUsageFlags::TRANSFER_SRC; 79 | let memory_usage = vk_mem::MemoryUsage::GpuOnly; 80 | 81 | let (buffer, allocation, allocation_info) = 82 | app.create_buffer_with_data(usage, memory_usage, false, &vertices)?; 83 | 84 | app.set_debug_object_name(buffer, "Node Vertex Buffer (Lines)")?; 85 | 86 | self.vertex_count = vertices.len(); 87 | 88 | self.vertex_buffer = buffer; 89 | self.allocation = allocation; 90 | self.allocation_info = Some(allocation_info); 91 | 92 | Ok(()) 93 | } 94 | 95 | /// `quad` as in the vertex input to the node pipeline that doesn't 96 | /// use tessellation is one quad (2 triangles) per node 97 | /// 98 | /// the input is the same as `upload_line_vertices`, but the 99 | /// function repeats vertices to produce two triangles per node -- 100 | /// so the uploaded vertices are at the same points as in the line 101 | /// case 102 | fn upload_quad_vertices( 103 | &mut self, 104 | app: &GfaestusVk, 105 | vertices: &[Vertex], 106 | ) -> Result<()> { 107 | assert!(self.renderer_type == NodeRendererType::VertexOnly); 108 | 109 | if self.has_vertices() { 110 | self.destroy(app)?; 111 | } 112 | 113 | let usage = vk::BufferUsageFlags::VERTEX_BUFFER 114 | | vk::BufferUsageFlags::STORAGE_BUFFER 115 | | vk::BufferUsageFlags::TRANSFER_SRC; 116 | let memory_usage = vk_mem::MemoryUsage::GpuOnly; 117 | 118 | let mut quad_vertices: Vec = 119 | Vec::with_capacity(vertices.len() * 3); 120 | 121 | // NB: first triangle, if p0 is the left side, goes "bottom 122 | // left, top right, top left"; second is "bottom left, bottom 123 | // right, top right" 124 | for chunk in vertices.chunks_exact(2) { 125 | if let &[p0, p1] = chunk { 126 | quad_vertices.push(p0); 127 | quad_vertices.push(p1); 128 | quad_vertices.push(p0); 129 | 130 | quad_vertices.push(p0); 131 | quad_vertices.push(p1); 132 | quad_vertices.push(p1); 133 | } 134 | } 135 | 136 | let vertices = quad_vertices; 137 | 138 | let (buffer, allocation, allocation_info) = 139 | app.create_buffer_with_data(usage, memory_usage, false, &vertices)?; 140 | 141 | app.set_debug_object_name(buffer, "Node Vertex Buffer (Quads)")?; 142 | 143 | self.vertex_count = vertices.len(); 144 | 145 | self.vertex_buffer = buffer; 146 | self.allocation = allocation; 147 | self.allocation_info = Some(allocation_info); 148 | 149 | Ok(()) 150 | } 151 | 152 | pub fn upload_vertices( 153 | &mut self, 154 | app: &GfaestusVk, 155 | vertices: &[Vertex], 156 | ) -> Result<()> { 157 | match self.renderer_type { 158 | NodeRendererType::VertexOnly => { 159 | self.upload_quad_vertices(app, vertices) 160 | } 161 | NodeRendererType::TessellationQuads => { 162 | self.upload_line_vertices(app, vertices) 163 | } 164 | } 165 | } 166 | 167 | pub fn download_vertices( 168 | &self, 169 | app: &GfaestusVk, 170 | node_count: usize, 171 | target: &mut Vec, 172 | ) -> Result<()> { 173 | target.clear(); 174 | let cap = target.capacity(); 175 | if cap < node_count { 176 | target.reserve(node_count - cap); 177 | } 178 | 179 | let alloc_info = self.allocation_info.as_ref().unwrap(); 180 | 181 | let staging_buffer_info = vk::BufferCreateInfo::builder() 182 | .size(alloc_info.get_size() as u64) 183 | .usage(vk::BufferUsageFlags::TRANSFER_DST) 184 | .sharing_mode(vk::SharingMode::EXCLUSIVE) 185 | .build(); 186 | 187 | let staging_create_info = vk_mem::AllocationCreateInfo { 188 | usage: vk_mem::MemoryUsage::GpuToCpu, 189 | flags: vk_mem::AllocationCreateFlags::MAPPED, 190 | ..Default::default() 191 | }; 192 | 193 | let (staging_buf, staging_alloc, staging_alloc_info) = app 194 | .allocator 195 | .create_buffer(&staging_buffer_info, &staging_create_info)?; 196 | 197 | app.set_debug_object_name( 198 | staging_buf, 199 | "Node Position Download Staging Buffer", 200 | )?; 201 | 202 | GfaestusVk::copy_buffer( 203 | app.vk_context().device(), 204 | app.transient_command_pool, 205 | app.graphics_queue, 206 | self.buffer(), 207 | staging_buf, 208 | staging_alloc_info.get_size() as u64, 209 | ); 210 | 211 | unsafe { 212 | let mapped_ptr = staging_alloc_info.get_mapped_data(); 213 | 214 | let val_ptr = mapped_ptr as *const crate::universe::Node; 215 | 216 | let sel_slice = std::slice::from_raw_parts(val_ptr, node_count); 217 | 218 | match self.renderer_type { 219 | NodeRendererType::TessellationQuads => { 220 | // if it uses just two vertices per node, copy the entire slice 221 | target.extend_from_slice(sel_slice); 222 | } 223 | NodeRendererType::VertexOnly => { 224 | // if it uses six vertices per node, only read every 225 | // third pair of vertices (per the vx order in 226 | // upload_vertices) 227 | 228 | // TODO this probably works; but untested (could be 229 | // parallelized too) 230 | for n in 0..node_count { 231 | let ix = n * 3; 232 | target.push(sel_slice[ix]); 233 | } 234 | } 235 | } 236 | } 237 | 238 | app.allocator.destroy_buffer(staging_buf, &staging_alloc)?; 239 | 240 | target.shrink_to_fit(); 241 | 242 | Ok(()) 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/vulkan/compute.rs: -------------------------------------------------------------------------------- 1 | use ash::version::DeviceV1_0; 2 | use ash::{vk, Device}; 3 | 4 | use std::{collections::HashMap, ffi::CString}; 5 | 6 | use anyhow::Result; 7 | 8 | use super::GfaestusVk; 9 | 10 | pub mod edges; 11 | pub mod node_motion; 12 | pub mod path_view; 13 | pub mod selection; 14 | 15 | pub use edges::*; 16 | pub use node_motion::*; 17 | pub use selection::*; 18 | 19 | pub struct ComputeManager { 20 | pub(super) compute_cmd_pool: vk::CommandPool, 21 | 22 | compute_queue: vk::Queue, 23 | 24 | fences: HashMap, 25 | command_buffers: HashMap, 26 | 27 | next_fence: usize, 28 | 29 | pub(super) device: Device, 30 | } 31 | 32 | impl ComputeManager { 33 | pub fn new( 34 | device: Device, 35 | queue_ix: u32, 36 | queue: vk::Queue, 37 | ) -> Result { 38 | let command_pool = GfaestusVk::create_command_pool( 39 | &device, 40 | queue_ix, 41 | vk::CommandPoolCreateFlags::empty(), 42 | )?; 43 | 44 | Ok(Self { 45 | compute_cmd_pool: command_pool, 46 | 47 | compute_queue: queue, 48 | 49 | fences: HashMap::default(), 50 | command_buffers: HashMap::default(), 51 | 52 | next_fence: 0, 53 | 54 | device, 55 | }) 56 | } 57 | 58 | pub fn is_fence_ready(&self, fence_id: usize) -> Result { 59 | let fence = *self.fences.get(&fence_id).unwrap(); 60 | let status = unsafe { self.device.get_fence_status(fence) }?; 61 | 62 | Ok(status) 63 | } 64 | 65 | pub fn block_on_fence(&self, fence_id: usize) -> Result<()> { 66 | let fence = *self.fences.get(&fence_id).unwrap(); 67 | let fences = [fence]; 68 | let _status = 69 | unsafe { self.device.wait_for_fences(&fences, true, 100_000_000) }?; 70 | 71 | Ok(()) 72 | } 73 | 74 | pub fn free_fence(&mut self, fence_id: usize, block: bool) -> Result<()> { 75 | let fence = *self.fences.get(&fence_id).unwrap(); 76 | 77 | if block { 78 | let fences = [fence]; 79 | let _status = 80 | unsafe { self.device.wait_for_fences(&fences, true, 0) }?; 81 | } 82 | 83 | let cmd_buf = *self.command_buffers.get(&fence_id).unwrap(); 84 | 85 | unsafe { 86 | let cmd_bufs = [cmd_buf]; 87 | self.device 88 | .free_command_buffers(self.compute_cmd_pool, &cmd_bufs); 89 | self.device.destroy_fence(fence, None); 90 | } 91 | 92 | Ok(()) 93 | } 94 | 95 | pub fn dispatch_with(&mut self, commands: F) -> Result 96 | where 97 | F: FnOnce(&Device, vk::CommandBuffer), 98 | { 99 | let device = &self.device; 100 | 101 | let fence = { 102 | let fence_info = vk::FenceCreateInfo::builder() 103 | .flags(vk::FenceCreateFlags::SIGNALED) 104 | .build(); 105 | unsafe { device.create_fence(&fence_info, None).unwrap() } 106 | }; 107 | 108 | let fences = [fence]; 109 | 110 | unsafe { device.reset_fences(&fences).unwrap() }; 111 | 112 | let cmd_buf = { 113 | let alloc_info = vk::CommandBufferAllocateInfo::builder() 114 | .level(vk::CommandBufferLevel::PRIMARY) 115 | .command_pool(self.compute_cmd_pool) 116 | .command_buffer_count(1) 117 | .build(); 118 | 119 | let bufs = unsafe { device.allocate_command_buffers(&alloc_info) }?; 120 | bufs[0] 121 | }; 122 | 123 | { 124 | let begin_info = vk::CommandBufferBeginInfo::builder() 125 | .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT) 126 | .build(); 127 | 128 | unsafe { device.begin_command_buffer(cmd_buf, &begin_info) }?; 129 | } 130 | 131 | commands(device, cmd_buf); 132 | 133 | unsafe { device.end_command_buffer(cmd_buf) }?; 134 | 135 | { 136 | let submit_info = vk::SubmitInfo::builder() 137 | .command_buffers(&[cmd_buf]) 138 | .build(); 139 | 140 | unsafe { 141 | device.queue_submit( 142 | self.compute_queue, 143 | &[submit_info], 144 | fence, 145 | )?; 146 | } 147 | } 148 | 149 | self.fences.insert(self.next_fence, fence); 150 | self.command_buffers.insert(self.next_fence, cmd_buf); 151 | 152 | let fence_id = self.next_fence; 153 | 154 | self.next_fence += 1; 155 | 156 | Ok(fence_id) 157 | } 158 | } 159 | 160 | pub struct ComputePipeline { 161 | pub(super) descriptor_pool: vk::DescriptorPool, 162 | pub(super) descriptor_set_layout: vk::DescriptorSetLayout, 163 | 164 | pub(super) pipeline_layout: vk::PipelineLayout, 165 | pub(super) pipeline: vk::Pipeline, 166 | 167 | pub(super) device: Device, 168 | } 169 | 170 | impl ComputePipeline { 171 | pub fn new_with_pool_size( 172 | device: &Device, 173 | descriptor_set_layout: vk::DescriptorSetLayout, 174 | pool_sizes: &[vk::DescriptorPoolSize], 175 | pipeline_layout: vk::PipelineLayout, 176 | shader: &[u8], 177 | ) -> Result { 178 | let pipeline = Self::create_pipeline(device, pipeline_layout, shader)?; 179 | 180 | let descriptor_pool = { 181 | // let pool_sizes = [pool_size]; 182 | 183 | let pool_info = vk::DescriptorPoolCreateInfo::builder() 184 | .pool_sizes(pool_sizes) 185 | .max_sets(1) 186 | .build(); 187 | 188 | unsafe { device.create_descriptor_pool(&pool_info, None) } 189 | }?; 190 | 191 | Ok(Self { 192 | descriptor_pool, 193 | descriptor_set_layout, 194 | 195 | pipeline_layout, 196 | pipeline, 197 | 198 | device: device.clone(), 199 | }) 200 | } 201 | 202 | pub fn new( 203 | device: &Device, 204 | descriptor_set_layout: vk::DescriptorSetLayout, 205 | pipeline_layout: vk::PipelineLayout, 206 | shader: &[u8], 207 | ) -> Result { 208 | let pipeline = Self::create_pipeline(device, pipeline_layout, shader)?; 209 | 210 | let descriptor_pool = { 211 | let pool_size = vk::DescriptorPoolSize { 212 | ty: vk::DescriptorType::STORAGE_BUFFER, 213 | descriptor_count: 2, 214 | }; 215 | 216 | let pool_sizes = [pool_size]; 217 | 218 | let pool_info = vk::DescriptorPoolCreateInfo::builder() 219 | .pool_sizes(&pool_sizes) 220 | .max_sets(1) 221 | .build(); 222 | 223 | unsafe { device.create_descriptor_pool(&pool_info, None) } 224 | }?; 225 | 226 | Ok(Self { 227 | descriptor_pool, 228 | descriptor_set_layout, 229 | 230 | pipeline_layout, 231 | pipeline, 232 | 233 | device: device.clone(), 234 | }) 235 | } 236 | 237 | pub(crate) fn create_pipeline( 238 | device: &Device, 239 | pipeline_layout: vk::PipelineLayout, 240 | shader: &[u8], 241 | ) -> Result { 242 | let comp_src = { 243 | let mut cursor = std::io::Cursor::new(shader); 244 | ash::util::read_spv(&mut cursor) 245 | }?; 246 | 247 | let comp_module = 248 | super::draw_system::create_shader_module(device, &comp_src); 249 | 250 | let entry_point = CString::new("main").unwrap(); 251 | 252 | let comp_state_info = vk::PipelineShaderStageCreateInfo::builder() 253 | .stage(vk::ShaderStageFlags::COMPUTE) 254 | .module(comp_module) 255 | .name(&entry_point) 256 | .build(); 257 | 258 | let pipeline_info = vk::ComputePipelineCreateInfo::builder() 259 | .layout(pipeline_layout) 260 | .stage(comp_state_info) 261 | .build(); 262 | 263 | let pipeline_infos = [pipeline_info]; 264 | 265 | let pipeline = unsafe { 266 | device 267 | .create_compute_pipelines( 268 | vk::PipelineCache::null(), 269 | &pipeline_infos, 270 | None, 271 | ) 272 | .unwrap()[0] 273 | }; 274 | 275 | unsafe { 276 | device.destroy_shader_module(comp_module, None); 277 | } 278 | 279 | Ok(pipeline) 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/vulkan/compute/node_motion.rs: -------------------------------------------------------------------------------- 1 | use crate::geometry::Point; 2 | use ash::version::DeviceV1_0; 3 | use ash::{vk, Device}; 4 | 5 | use anyhow::Result; 6 | 7 | #[allow(unused_imports)] 8 | use log::{debug, error, info, trace, warn}; 9 | 10 | use crate::app::selection::SelectionBuffer; 11 | 12 | use crate::vulkan::{draw_system::nodes::NodeVertices, GfaestusVk}; 13 | 14 | use super::{ComputeManager, ComputePipeline}; 15 | 16 | pub struct NodeTranslation { 17 | compute_pipeline: ComputePipeline, 18 | 19 | descriptor_set: vk::DescriptorSet, 20 | 21 | node_count: usize, 22 | } 23 | 24 | impl NodeTranslation { 25 | pub fn new(app: &GfaestusVk, node_count: usize) -> Result { 26 | let device = app.vk_context().device(); 27 | 28 | let desc_set_layout = Self::create_descriptor_set_layout(device)?; 29 | 30 | let pipeline_layout = { 31 | use vk::ShaderStageFlags as Flags; 32 | 33 | let pc_range = vk::PushConstantRange::builder() 34 | .stage_flags(Flags::COMPUTE) 35 | .offset(0) 36 | .size(8) 37 | .build(); 38 | 39 | let pc_ranges = [pc_range]; 40 | 41 | let layouts = [desc_set_layout]; 42 | 43 | let layout_info = vk::PipelineLayoutCreateInfo::builder() 44 | .set_layouts(&layouts) 45 | .push_constant_ranges(&pc_ranges) 46 | .build(); 47 | 48 | unsafe { device.create_pipeline_layout(&layout_info, None) } 49 | }?; 50 | 51 | let compute_pipeline = ComputePipeline::new( 52 | device, 53 | desc_set_layout, 54 | pipeline_layout, 55 | crate::include_shader!("compute/node_translate.comp.spv"), 56 | )?; 57 | 58 | let descriptor_sets = { 59 | let layouts = vec![desc_set_layout]; 60 | 61 | let alloc_info = vk::DescriptorSetAllocateInfo::builder() 62 | .descriptor_pool(compute_pipeline.descriptor_pool) 63 | .set_layouts(&layouts) 64 | .build(); 65 | 66 | unsafe { device.allocate_descriptor_sets(&alloc_info) } 67 | }?; 68 | 69 | // let selection_buffer = SelectionBuffer::new(app, node_count)?; 70 | 71 | Ok(Self { 72 | compute_pipeline, 73 | 74 | descriptor_set: descriptor_sets[0], 75 | // selection_buffer, 76 | node_count, 77 | }) 78 | } 79 | 80 | pub fn translate_nodes( 81 | &self, 82 | comp_manager: &mut ComputeManager, 83 | vertices: &NodeVertices, 84 | selection_buffer: &SelectionBuffer, 85 | delta: Point, 86 | ) -> Result { 87 | self.write_descriptor_set(selection_buffer, vertices); 88 | 89 | let fence_id = comp_manager.dispatch_with(|_device, cmd_buf| { 90 | self.translate_cmd(cmd_buf, delta).unwrap(); 91 | })?; 92 | 93 | Ok(fence_id) 94 | } 95 | 96 | pub fn translate_cmd( 97 | &self, 98 | cmd_buf: vk::CommandBuffer, 99 | delta: Point, 100 | ) -> Result<()> { 101 | let device = &self.compute_pipeline.device; 102 | 103 | unsafe { 104 | device.cmd_bind_pipeline( 105 | cmd_buf, 106 | vk::PipelineBindPoint::COMPUTE, 107 | self.compute_pipeline.pipeline, 108 | ) 109 | }; 110 | 111 | unsafe { 112 | let desc_sets = [self.descriptor_set]; 113 | 114 | let null = []; 115 | device.cmd_bind_descriptor_sets( 116 | cmd_buf, 117 | vk::PipelineBindPoint::COMPUTE, 118 | self.compute_pipeline.pipeline_layout, 119 | 0, 120 | &desc_sets[0..=0], 121 | &null, 122 | ); 123 | }; 124 | 125 | trace!("Translating selected nodes by {}, {}", delta.x, delta.y); 126 | 127 | let push_constants = DeltaPushConstants::new(delta); 128 | let pc_bytes = push_constants.bytes(); 129 | 130 | unsafe { 131 | use vk::ShaderStageFlags as Flags; 132 | device.cmd_push_constants( 133 | cmd_buf, 134 | self.compute_pipeline.pipeline_layout, 135 | Flags::COMPUTE, 136 | 0, 137 | &pc_bytes, 138 | ) 139 | }; 140 | 141 | let x_group_count = { 142 | let div = self.node_count / 256; 143 | let rem = self.node_count % 256; 144 | 145 | let mut count = div; 146 | if rem > 0 { 147 | count += 1; 148 | } 149 | count as u32 150 | }; 151 | 152 | trace!( 153 | "Dispatching node translation with x_group_count {}", 154 | x_group_count 155 | ); 156 | 157 | unsafe { device.cmd_dispatch(cmd_buf, x_group_count, 1, 1) }; 158 | 159 | Ok(()) 160 | } 161 | 162 | pub fn write_descriptor_set( 163 | &self, 164 | selection_buffer: &SelectionBuffer, 165 | vertices: &NodeVertices, 166 | ) { 167 | let sel_buf_info = vk::DescriptorBufferInfo::builder() 168 | .buffer(selection_buffer.buffer) 169 | .offset(0) 170 | .range(vk::WHOLE_SIZE) 171 | .build(); 172 | 173 | let sel_buf_infos = [sel_buf_info]; 174 | 175 | let sel_write = vk::WriteDescriptorSet::builder() 176 | .dst_set(self.descriptor_set) 177 | .dst_binding(0) 178 | .dst_array_element(0) 179 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 180 | .buffer_info(&sel_buf_infos) 181 | .build(); 182 | 183 | let node_buf_info = vk::DescriptorBufferInfo::builder() 184 | .buffer(vertices.buffer()) 185 | .offset(0) 186 | .range(vk::WHOLE_SIZE) 187 | .build(); 188 | 189 | let node_buf_infos = [node_buf_info]; 190 | 191 | let node_write = vk::WriteDescriptorSet::builder() 192 | .dst_set(self.descriptor_set) 193 | .dst_binding(1) 194 | .dst_array_element(0) 195 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 196 | .buffer_info(&node_buf_infos) 197 | .build(); 198 | 199 | let desc_writes = [sel_write, node_write]; 200 | 201 | unsafe { 202 | self.compute_pipeline 203 | .device 204 | .update_descriptor_sets(&desc_writes, &[]) 205 | }; 206 | } 207 | 208 | fn layout_binding() -> [vk::DescriptorSetLayoutBinding; 2] { 209 | use vk::ShaderStageFlags as Stages; 210 | 211 | let selection = vk::DescriptorSetLayoutBinding::builder() 212 | .binding(0) 213 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 214 | .descriptor_count(1) 215 | .stage_flags(Stages::COMPUTE) 216 | .build(); 217 | 218 | let node_vertices = vk::DescriptorSetLayoutBinding::builder() 219 | .binding(1) 220 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 221 | .descriptor_count(1) 222 | .stage_flags(Stages::COMPUTE) 223 | .build(); 224 | 225 | [selection, node_vertices] 226 | } 227 | 228 | fn create_descriptor_set_layout( 229 | device: &Device, 230 | ) -> Result { 231 | let bindings = Self::layout_binding(); 232 | 233 | let layout_info = vk::DescriptorSetLayoutCreateInfo::builder() 234 | .bindings(&bindings) 235 | .build(); 236 | 237 | let layout = 238 | unsafe { device.create_descriptor_set_layout(&layout_info, None) }?; 239 | 240 | Ok(layout) 241 | } 242 | } 243 | 244 | pub struct DeltaPushConstants { 245 | delta: Point, 246 | } 247 | 248 | impl DeltaPushConstants { 249 | #[inline] 250 | pub fn new(delta: Point) -> Self { 251 | Self { delta } 252 | } 253 | 254 | #[inline] 255 | pub fn bytes(&self) -> [u8; 8] { 256 | let mut bytes = [0u8; 8]; 257 | 258 | { 259 | let mut offset = 0; 260 | 261 | { 262 | let mut add_float = |f: f32| { 263 | let f_bytes = f.to_ne_bytes(); 264 | for i in 0..4 { 265 | bytes[offset] = f_bytes[i]; 266 | offset += 1; 267 | } 268 | }; 269 | add_float(self.delta.x); 270 | add_float(self.delta.y); 271 | } 272 | } 273 | 274 | bytes 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /src/gui/windows/file.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | use std::path::{Path, PathBuf}; 3 | 4 | use std::str::FromStr; 5 | 6 | use anyhow::Result; 7 | 8 | use crate::reactor::{ModalError, ModalSuccess}; 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct FilePicker { 12 | id: egui::Id, 13 | 14 | pub pwd: PathBuf, 15 | pub current_dir: PathBuf, 16 | pub current_dir_text: String, 17 | 18 | pub highlighted_dir: Option, 19 | pub selected_path: Option, 20 | 21 | pub dir_list: Vec, 22 | pub history: Vec, 23 | 24 | pub extensions: HashSet, 25 | } 26 | 27 | impl FilePicker { 28 | pub fn new>(id: egui::Id, pwd: P) -> Result { 29 | let pwd = pwd.as_ref().to_owned(); 30 | let current_dir = pwd.clone(); 31 | let current_dir_text = current_dir.as_os_str().to_str().unwrap(); 32 | let current_dir_text = current_dir_text.to_owned(); 33 | 34 | let mut result = Self { 35 | id, 36 | 37 | pwd, 38 | current_dir, 39 | current_dir_text, 40 | 41 | highlighted_dir: None, 42 | selected_path: None, 43 | 44 | dir_list: Vec::new(), 45 | history: Vec::new(), 46 | 47 | extensions: HashSet::default(), 48 | }; 49 | 50 | result.load_current_dir()?; 51 | 52 | Ok(result) 53 | } 54 | 55 | pub fn set_visible_extensions( 56 | &mut self, 57 | extensions: &[&str], 58 | ) -> Result<()> { 59 | let extensions = extensions.iter().map(|s| s.to_string()).collect(); 60 | self.extensions = extensions; 61 | self.load_current_dir()?; 62 | Ok(()) 63 | } 64 | 65 | pub fn selected_path(&self) -> Option<&Path> { 66 | let path = self.selected_path.as_ref()?; 67 | Some(path.as_ref()) 68 | } 69 | 70 | pub fn reset_selection(&mut self) { 71 | self.selected_path = None; 72 | self.highlighted_dir = None; 73 | } 74 | 75 | pub fn reset(&mut self) { 76 | self.current_dir.clone_from(&self.pwd); 77 | self.selected_path = None; 78 | self.dir_list.clear(); 79 | self.history.clear(); 80 | } 81 | 82 | fn load_current_dir(&mut self) -> Result<()> { 83 | let current_dir_text = self.current_dir.as_os_str().to_str().unwrap(); 84 | self.current_dir_text = current_dir_text.to_owned(); 85 | 86 | self.selected_path = None; 87 | self.dir_list.clear(); 88 | 89 | let dirs = std::fs::read_dir(&self.current_dir)?; 90 | 91 | for dir in dirs { 92 | let entry = dir?; 93 | let path = entry.path(); 94 | 95 | if self.extensions.is_empty() 96 | || self.extensions.iter().any(|ext| { 97 | if path.is_dir() { 98 | return true; 99 | } 100 | 101 | if let Some(file_ext) = 102 | path.extension().and_then(|f_ext| f_ext.to_str()) 103 | { 104 | file_ext == ext 105 | } else { 106 | false 107 | } 108 | }) 109 | { 110 | self.dir_list.push(path); 111 | } 112 | } 113 | 114 | self.dir_list.sort(); 115 | 116 | Ok(()) 117 | } 118 | 119 | pub fn goto_dir>( 120 | &mut self, 121 | new_dir: P, 122 | add_history: bool, 123 | ) -> Result<()> { 124 | let new_dir = new_dir.as_ref(); 125 | 126 | if add_history { 127 | self.history.push(self.current_dir.clone()); 128 | } 129 | 130 | self.current_dir = new_dir.to_owned(); 131 | self.load_current_dir()?; 132 | 133 | Ok(()) 134 | } 135 | 136 | fn goto_prev(&mut self) -> Result<()> { 137 | if let Some(new_dir) = self.history.pop() { 138 | self.goto_dir(new_dir, false)?; 139 | } 140 | Ok(()) 141 | } 142 | 143 | fn go_up(&mut self) -> Result<()> { 144 | if let Some(parent) = self.current_dir.parent().map(|p| p.to_owned()) { 145 | self.goto_dir(parent, true)?; 146 | } 147 | Ok(()) 148 | } 149 | 150 | fn goto_path_in_text_box(&mut self) -> Result<()> { 151 | let path = PathBuf::from_str(&self.current_dir_text)?; 152 | 153 | if path.exists() && path.is_dir() { 154 | self.goto_dir(path, true)?; 155 | } 156 | 157 | Ok(()) 158 | } 159 | 160 | pub fn ui_impl( 161 | &mut self, 162 | ui: &mut egui::Ui, 163 | force_accept: bool, 164 | ) -> std::result::Result { 165 | let max_height = ui.input().screen_rect.height() - 100.0; 166 | /* 167 | ui.set_max_height(max_height); 168 | */ 169 | 170 | ui.horizontal(|ui| { 171 | let text_box = ui.text_edit_singleline(&mut self.current_dir_text); 172 | 173 | if ui.button("Goto").clicked() 174 | || (text_box.lost_focus() 175 | // || (text_box.has_focus() 176 | && ui.input().key_pressed(egui::Key::Enter)) 177 | { 178 | self.goto_path_in_text_box().unwrap(); 179 | } 180 | }); 181 | 182 | ui.horizontal(|ui| { 183 | if ui.button("Prev").clicked() { 184 | self.goto_prev().unwrap(); 185 | } 186 | 187 | if ui.button("Up").clicked() { 188 | self.go_up().unwrap(); 189 | } 190 | }); 191 | 192 | let mut goto_dir: Option = None; 193 | 194 | let mut choose_path: Option = None; 195 | 196 | egui::ScrollArea::from_max_height(max_height - 100.0).show( 197 | ui, 198 | |mut ui| { 199 | egui::Grid::new("file_list").striped(true).show( 200 | &mut ui, 201 | |ui| { 202 | for dir_path in self.dir_list.iter() { 203 | if let Some(name) = 204 | dir_path.file_name().and_then(|n| n.to_str()) 205 | { 206 | let checked = if let Some(sel_name) = 207 | &self.highlighted_dir 208 | { 209 | sel_name == dir_path 210 | } else { 211 | false 212 | }; 213 | let row = ui.selectable_label(checked, name); 214 | 215 | if row.clicked() { 216 | self.highlighted_dir = 217 | Some(dir_path.clone()); 218 | } 219 | 220 | if row.double_clicked() { 221 | if dir_path.is_dir() { 222 | goto_dir = Some(dir_path.to_owned()); 223 | } else if dir_path.is_file() { 224 | choose_path = Some(dir_path.to_owned()); 225 | } 226 | } 227 | 228 | ui.end_row(); 229 | } 230 | } 231 | }, 232 | ); 233 | }, 234 | ); 235 | 236 | if force_accept { 237 | if let Some(dir_path) = self.highlighted_dir.as_ref() { 238 | if dir_path.is_dir() { 239 | goto_dir = Some(dir_path.to_owned()); 240 | } else if dir_path.is_file() { 241 | choose_path = Some(dir_path.to_owned()); 242 | } 243 | } 244 | } 245 | 246 | if let Some(dir) = goto_dir { 247 | self.goto_dir(&dir, true).unwrap(); 248 | ui.scroll_to_cursor(egui::Align::TOP); 249 | } 250 | 251 | if let Some(path) = choose_path { 252 | self.selected_path = Some(path); 253 | return Ok(ModalSuccess::Success); 254 | } 255 | 256 | Err(ModalError::Continue) 257 | } 258 | 259 | pub fn ui( 260 | &mut self, 261 | ctx: &egui::CtxRef, 262 | open: &mut bool, 263 | ) -> Option>> { 264 | egui::Window::new("File picker") 265 | .id(self.id) 266 | .collapsible(false) 267 | .open(open) 268 | .show(ctx, |ui| { 269 | let max_height = ui.input().screen_rect.height() - 100.0; 270 | 271 | ui.set_max_height(max_height); 272 | 273 | let _ = self.ui_impl(ui, false); 274 | 275 | if ui.button("Ok").clicked() { 276 | self.selected_path = self.highlighted_dir.clone(); 277 | } 278 | }) 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/vulkan/compute/selection.rs: -------------------------------------------------------------------------------- 1 | use crate::geometry::{Point, Rect}; 2 | 3 | use ash::version::DeviceV1_0; 4 | use ash::{vk, Device}; 5 | 6 | use anyhow::Result; 7 | 8 | #[allow(unused_imports)] 9 | use log::{debug, error, info, trace, warn}; 10 | 11 | use crate::app::selection::SelectionBuffer; 12 | 13 | use crate::vulkan::{draw_system::nodes::NodeVertices, GfaestusVk}; 14 | 15 | use super::{ComputeManager, ComputePipeline}; 16 | 17 | pub struct GpuSelection { 18 | compute_pipeline: ComputePipeline, 19 | 20 | descriptor_set: vk::DescriptorSet, 21 | 22 | pub selection_buffer: SelectionBuffer, 23 | 24 | node_count: usize, 25 | } 26 | 27 | impl GpuSelection { 28 | pub fn new(app: &GfaestusVk, node_count: usize) -> Result { 29 | let device = app.vk_context().device(); 30 | 31 | let desc_set_layout = Self::create_descriptor_set_layout(device)?; 32 | 33 | let pipeline_layout = { 34 | use vk::ShaderStageFlags as Flags; 35 | 36 | let pc_range = vk::PushConstantRange::builder() 37 | .stage_flags(Flags::COMPUTE) 38 | .offset(0) 39 | .size(20) 40 | .build(); 41 | 42 | let pc_ranges = [pc_range]; 43 | 44 | let layouts = [desc_set_layout]; 45 | 46 | let layout_info = vk::PipelineLayoutCreateInfo::builder() 47 | .set_layouts(&layouts) 48 | .push_constant_ranges(&pc_ranges) 49 | .build(); 50 | 51 | unsafe { device.create_pipeline_layout(&layout_info, None) } 52 | }?; 53 | 54 | let compute_pipeline = ComputePipeline::new( 55 | device, 56 | desc_set_layout, 57 | pipeline_layout, 58 | crate::include_shader!("compute/rect_select.comp.spv"), 59 | )?; 60 | 61 | let descriptor_sets = { 62 | let layouts = vec![desc_set_layout]; 63 | 64 | let alloc_info = vk::DescriptorSetAllocateInfo::builder() 65 | .descriptor_pool(compute_pipeline.descriptor_pool) 66 | .set_layouts(&layouts) 67 | .build(); 68 | 69 | unsafe { device.allocate_descriptor_sets(&alloc_info) } 70 | }?; 71 | 72 | let selection_buffer = SelectionBuffer::new(app, node_count)?; 73 | 74 | Ok(Self { 75 | compute_pipeline, 76 | 77 | descriptor_set: descriptor_sets[0], 78 | 79 | selection_buffer, 80 | 81 | node_count, 82 | }) 83 | } 84 | 85 | pub fn rectangle_select( 86 | &self, 87 | comp_manager: &mut ComputeManager, 88 | vertices: &NodeVertices, 89 | rect: Rect, 90 | // returns fence ID 91 | ) -> Result { 92 | self.write_descriptor_set(vertices); 93 | 94 | let fence_id = comp_manager.dispatch_with(|_device, cmd_buf| { 95 | self.rectangle_select_cmd(cmd_buf, rect).unwrap(); 96 | })?; 97 | 98 | Ok(fence_id) 99 | } 100 | 101 | fn rectangle_select_cmd( 102 | &self, 103 | // comp_manager: &mut ComputeManager, 104 | // vertices: &NodeVertices, 105 | cmd_buf: vk::CommandBuffer, 106 | rect: Rect, 107 | ) -> Result<()> { 108 | let device = &self.compute_pipeline.device; 109 | 110 | unsafe { 111 | device.cmd_bind_pipeline( 112 | cmd_buf, 113 | vk::PipelineBindPoint::COMPUTE, 114 | self.compute_pipeline.pipeline, 115 | ) 116 | }; 117 | 118 | unsafe { 119 | let desc_sets = [self.descriptor_set]; 120 | 121 | let null = []; 122 | device.cmd_bind_descriptor_sets( 123 | cmd_buf, 124 | vk::PipelineBindPoint::COMPUTE, 125 | self.compute_pipeline.pipeline_layout, 126 | 0, 127 | &desc_sets[0..=0], 128 | &null, 129 | ); 130 | }; 131 | 132 | let push_constants = RectPushConstants::new( 133 | self.node_count as u32, 134 | rect.min(), 135 | rect.max(), 136 | ); 137 | let pc_bytes = push_constants.bytes(); 138 | 139 | unsafe { 140 | use vk::ShaderStageFlags as Flags; 141 | device.cmd_push_constants( 142 | cmd_buf, 143 | self.compute_pipeline.pipeline_layout, 144 | Flags::COMPUTE, 145 | 0, 146 | &pc_bytes, 147 | ) 148 | }; 149 | 150 | let x_group_count = { 151 | let div = self.node_count / 256; 152 | let rem = self.node_count % 256; 153 | 154 | let mut count = div; 155 | if rem > 0 { 156 | count += 1; 157 | } 158 | count as u32 159 | }; 160 | 161 | trace!( 162 | "Node selection dispatch with x_group_count {}", 163 | x_group_count 164 | ); 165 | 166 | unsafe { device.cmd_dispatch(cmd_buf, x_group_count, 1, 1) }; 167 | 168 | Ok(()) 169 | } 170 | 171 | pub fn write_descriptor_set(&self, vertices: &NodeVertices) { 172 | let sel_buf_info = vk::DescriptorBufferInfo::builder() 173 | .buffer(self.selection_buffer.buffer) 174 | .offset(0) 175 | .range(vk::WHOLE_SIZE) 176 | .build(); 177 | 178 | let sel_buf_infos = [sel_buf_info]; 179 | 180 | let sel_write = vk::WriteDescriptorSet::builder() 181 | .dst_set(self.descriptor_set) 182 | .dst_binding(0) 183 | .dst_array_element(0) 184 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 185 | .buffer_info(&sel_buf_infos) 186 | .build(); 187 | 188 | let node_buf_info = vk::DescriptorBufferInfo::builder() 189 | .buffer(vertices.buffer()) 190 | .offset(0) 191 | .range(vk::WHOLE_SIZE) 192 | .build(); 193 | 194 | let node_buf_infos = [node_buf_info]; 195 | 196 | let node_write = vk::WriteDescriptorSet::builder() 197 | .dst_set(self.descriptor_set) 198 | .dst_binding(1) 199 | .dst_array_element(0) 200 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 201 | .buffer_info(&node_buf_infos) 202 | .build(); 203 | 204 | let desc_writes = [sel_write, node_write]; 205 | 206 | unsafe { 207 | self.compute_pipeline 208 | .device 209 | .update_descriptor_sets(&desc_writes, &[]) 210 | }; 211 | } 212 | 213 | fn layout_binding() -> [vk::DescriptorSetLayoutBinding; 2] { 214 | use vk::ShaderStageFlags as Stages; 215 | 216 | let selection = vk::DescriptorSetLayoutBinding::builder() 217 | .binding(0) 218 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 219 | .descriptor_count(1) 220 | .stage_flags(Stages::COMPUTE) 221 | .build(); 222 | 223 | let node_vertices = vk::DescriptorSetLayoutBinding::builder() 224 | .binding(1) 225 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 226 | .descriptor_count(1) 227 | .stage_flags(Stages::COMPUTE) 228 | .build(); 229 | 230 | [selection, node_vertices] 231 | } 232 | 233 | fn create_descriptor_set_layout( 234 | device: &Device, 235 | ) -> Result { 236 | let bindings = Self::layout_binding(); 237 | 238 | let layout_info = vk::DescriptorSetLayoutCreateInfo::builder() 239 | .bindings(&bindings) 240 | .build(); 241 | 242 | let layout = 243 | unsafe { device.create_descriptor_set_layout(&layout_info, None) }?; 244 | 245 | Ok(layout) 246 | } 247 | } 248 | 249 | pub struct RectPushConstants { 250 | node_count: u32, 251 | rect: Rect, 252 | } 253 | 254 | impl RectPushConstants { 255 | #[inline] 256 | pub fn new(node_count: u32, p0: Point, p1: Point) -> Self { 257 | let rect = Rect::new(p0, p1); 258 | 259 | Self { node_count, rect } 260 | } 261 | 262 | #[inline] 263 | pub fn bytes(&self) -> [u8; 20] { 264 | let mut bytes = [0u8; 20]; 265 | 266 | { 267 | let mut offset = 0; 268 | 269 | { 270 | let mut add_float = |f: f32| { 271 | let f_bytes = f.to_ne_bytes(); 272 | for i in 0..4 { 273 | bytes[offset] = f_bytes[i]; 274 | offset += 1; 275 | } 276 | }; 277 | add_float(self.rect.min().x); 278 | add_float(self.rect.min().y); 279 | 280 | add_float(self.rect.max().x); 281 | add_float(self.rect.max().y); 282 | } 283 | 284 | { 285 | let nc_bytes = self.node_count.to_ne_bytes(); 286 | for i in 0..4 { 287 | bytes[offset] = nc_bytes[i]; 288 | offset += 1; 289 | } 290 | } 291 | } 292 | 293 | bytes 294 | } 295 | } 296 | --------------------------------------------------------------------------------