diff --git a/Cargo.toml b/Cargo.toml index 4615dfd..0a06753 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,4 +25,5 @@ members = [ exclude = [ "implot-sys-bindgen", "implot-examples", + "implot-wgpu-examples", ] diff --git a/implot-sys/build.rs b/implot-sys/build.rs index a4a03f5..5c6b6dc 100644 --- a/implot-sys/build.rs +++ b/implot-sys/build.rs @@ -55,6 +55,7 @@ fn main() -> io::Result<()> { // Taken from the imgui-sys build as well build.flag_if_supported("-Wno-return-type-c-linkage"); + build.flag_if_supported("-Wno-unused-parameter"); build.flag_if_supported("-std=c++11"); for path in CPP_FILES { assert_file_exists(path)?; diff --git a/implot-wgpu-examples/Cargo.toml b/implot-wgpu-examples/Cargo.toml new file mode 100644 index 0000000..f417e33 --- /dev/null +++ b/implot-wgpu-examples/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "wgpu_plotting" +version = "0.1.0" +authors = ["Benedikt Mandelkow ", "imgui-wgpu contributors"] +edition = "2018" + +[dependencies] +implot = { path = "../" } + +wgpu = "^0.6.0" +wgpu-subscriber = "^0.1.0" # tracing, also chrome profiling format support +winit = "^0.22.2" # opening windows and handling input +futures = "^0.3.5" # executing async functions using blocking executor +imgui = "^0.5.0" +imgui-winit-support = "^0.5.0" # connection of input (keys) to imgui +imgui-wgpu = "^0.10.0" # imgui backend for drawing using wgpu diff --git a/implot-wgpu-examples/src/main.rs b/implot-wgpu-examples/src/main.rs new file mode 100644 index 0000000..f95979c --- /dev/null +++ b/implot-wgpu-examples/src/main.rs @@ -0,0 +1,234 @@ +use imgui::{im_str, CollapsingHeader, Condition, FontSource}; +use imgui_wgpu::Renderer; +use std::time::Instant; +use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::Window, +}; + +// the actual implot samples are in there +mod ui; + +async fn run(event_loop: EventLoop<()>, window: Window, swapchain_format: wgpu::TextureFormat) { + let size = window.inner_size(); + let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY); + let surface = unsafe { instance.create_surface(&window) }; + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::Default, + // Request an adapter which can render to our surface + compatible_surface: Some(&surface), + }) + .await + .expect("Failed to find an appropiate adapter"); + + // Create the logical device and command queue + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + features: wgpu::Features::empty(), + limits: wgpu::Limits::default(), + shader_validation: true, + }, + None, + ) + .await + .expect("Failed to create device"); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[], + push_constant_ranges: &[], + }); + + let mut sc_desc = wgpu::SwapChainDescriptor { + usage: wgpu::TextureUsage::OUTPUT_ATTACHMENT, + format: swapchain_format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + }; + + let mut swap_chain = device.create_swap_chain(&surface, &sc_desc); + + // Set up dear imgui + let mut imgui = imgui::Context::create(); + let implot = implot::Context::create(); + + let mut platform = imgui_winit_support::WinitPlatform::init(&mut imgui); + platform.attach_window( + imgui.io_mut(), + &window, + imgui_winit_support::HiDpiMode::Default, + ); + imgui.set_ini_filename(None); + + let hidpi_factor = window.scale_factor(); + + let font_size = (13.0 * hidpi_factor) as f32; + imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; + + imgui.fonts().add_font(&[FontSource::DefaultFontData { + config: Some(imgui::FontConfig { + oversample_h: 1, + pixel_snap_h: true, + size_pixels: font_size, + ..Default::default() + }), + }]); + + let style = imgui.style_mut(); + style.use_classic_colors(); + + let mut renderer = Renderer::new(&mut imgui, &device, &queue, sc_desc.format); + + let mut last_frame = Instant::now(); + let mut last_cursor = None; + + let mut showing_demo = false; + let mut make_fullscreen = false; + + event_loop.run(move |event, _, control_flow| { + // Have the closure take ownership of the resources. + // `event_loop.run` never returns, therefore we must do this to ensure + // the resources are properly cleaned up. + let _ = (&instance, &adapter, &pipeline_layout); + + let plot_ui = implot.get_plot_ui(); + + *control_flow = ControlFlow::Poll; + match event { + Event::WindowEvent { + event: WindowEvent::Resized(size), + .. + } => { + // Recreate the swap chain with the new size + sc_desc.width = size.width; + sc_desc.height = size.height; + swap_chain = device.create_swap_chain(&surface, &sc_desc); + } + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + Event::MainEventsCleared => window.request_redraw(), + Event::RedrawEventsCleared => { + let now = Instant::now(); + imgui.io_mut().update_delta_time(now - last_frame); + last_frame = now; + + let frame = match swap_chain.get_current_frame() { + Ok(frame) => frame, + Err(e) => { + eprintln!("dropped frame: {:?}", e); + return; + } + }; + + platform + .prepare_frame(imgui.io_mut(), &window) + .expect("Failed to prepare frame"); + let ui = imgui.frame(); + + { + let window = imgui::Window::new(im_str!("Hello implot")); + let window = if make_fullscreen { + let border = 10.0; + window.position([0.0, 0.0], Condition::Always).size( + [ + sc_desc.width as f32 / hidpi_factor - border, + sc_desc.height as f32 / hidpi_factor - border, + ], + Condition::Always, + ) + } else { + window.size([400.0, 300.0], Condition::FirstUseEver) + }; + + window.build(&ui, || { + ui.text(im_str!("Hello from implot-rs!")); + ui.text_wrapped(im_str!( + "The headers here demo the line plotting features of the library. \ + Have a look at the example source code to see how they are implemented.\n\ + Check out the demo from ImPlot itself first \ + (by enabling the 'Show demo' checkbox) for instructions \ + on how to interact with ImPlot plots." + )); + + ui.checkbox(im_str!("Show demo"), &mut showing_demo); + ui.checkbox( + im_str!("make the implot window fill the whole outer window"), + &mut make_fullscreen, + ); + + // Show individual examples in collapsed headers + if CollapsingHeader::new(im_str!("Basic lineplot")).build(&ui) { + ui::show_basic_plot(&ui, &plot_ui); + } + if CollapsingHeader::new(im_str!("Configurable lineplot")).build(&ui) { + ui::show_configurable_plot(&ui, &plot_ui); + } + if CollapsingHeader::new(im_str!("Querying a plot")).build(&ui) { + ui::show_query_features_plot(&ui, &plot_ui); + } + if CollapsingHeader::new(im_str!("Styling a plot")).build(&ui) { + ui::show_style_plot(&ui, &plot_ui); + } + if CollapsingHeader::new(im_str!("Colormap selection")).build(&ui) { + ui::show_colormaps_plot(&ui, &plot_ui); + } + }); + } + + if showing_demo { + implot::show_demo_window(&mut showing_demo); + } + + let mut encoder: wgpu::CommandEncoder = + device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + + if last_cursor != Some(ui.mouse_cursor()) { + last_cursor = Some(ui.mouse_cursor()); + platform.prepare_render(&ui, &window); + } + + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor { + attachment: &frame.output.view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.1, + g: 0.2, + b: 0.3, + a: 1.0, + }), + store: true, + }, + }], + depth_stencil_attachment: None, + }); + + renderer + .render(ui.render(), &queue, &device, &mut rpass) + .expect("Rendering failed"); + + drop(rpass); // renders to screen on drop, will probaly be changed in wgpu 0.7 or later + + queue.submit(Some(encoder.finish())); + } + _ => {} + } + + platform.handle_event(imgui.io_mut(), &window, &event); + }); +} + +fn main() { + let event_loop = EventLoop::new(); + let window = winit::window::Window::new(&event_loop).unwrap(); + wgpu_subscriber::initialize_default_subscriber(None); + // Temporarily avoid srgb formats for the swapchain on the web + futures::executor::block_on(run(event_loop, window, wgpu::TextureFormat::Bgra8UnormSrgb)); +} diff --git a/implot-wgpu-examples/src/ui.rs b/implot-wgpu-examples/src/ui.rs new file mode 100644 index 0000000..c851c2a --- /dev/null +++ b/implot-wgpu-examples/src/ui.rs @@ -0,0 +1,226 @@ +use imgui::{im_str, Condition, Ui}; +use implot::{ + get_plot_limits, get_plot_mouse_position, get_plot_query, is_plot_hovered, is_plot_queried, + push_style_color, push_style_var_f32, push_style_var_i32, set_colormap_from_preset, + set_colormap_from_vec, AxisFlags, Colormap, ImPlotLimits, ImPlotPoint, ImPlotRange, ImVec4, + Marker, Plot, PlotColorElement, PlotFlags, PlotLine, PlotUi, StyleVar, +}; + +pub fn show_basic_plot(ui: &Ui, plot_ui: &PlotUi) { + ui.text(im_str!( + "This header just plots a line with as little code as possible." + )); + let content_width = ui.window_content_region_width(); + Plot::new("Simple line plot") + // The size call could also be omitted, though the defaults don't consider window + // width, which is why we're not doing so here. + .size(content_width, 300.0) + .build(plot_ui, || { + // If this is called outside a plot build callback, the program will panic. + let x_positions = vec![0.1, 0.9]; + let y_positions = vec![0.1, 0.9]; + PlotLine::new("legend label").plot(&x_positions, &y_positions); + }); +} + +pub fn show_configurable_plot(ui: &Ui, plot_ui: &PlotUi) { + ui.text(im_str!( + "This header demos what we can configure about plots." + )); + + // Settings for the plot + // - X and Y size in pixels + let x_size = 300.0; + let y_size = 200.0; + // - Strings for the axis labels + let x_label = "X label!"; + let y_label = "Y label!"; + // - Plot limits + let x_min = 2.0; + let x_max = 3.0; + let y_min = 1.0; + let y_max = 2.0; + // - Plot flags, see the PlotFlags docs for more info + let plot_flags = PlotFlags::NONE; + // - Axis flags, see the AxisFlags docs for more info. All flags are bitflags-created, + // so they support a bunch of convenient operations, see https://docs.rs/bitflags + let x_axis_flags = AxisFlags::NONE; + let y_axis_flags = AxisFlags::NONE; + + // - Unlabelled X axis ticks + let x_ticks = vec![2.2, 2.5, 2.8]; + + // - Labelled Y axis ticks + let y_ticks = vec![(1.1, "A".to_owned()), (1.4, "B".to_owned())]; + + // Axis labels + Plot::new("Configured line plot") + .size(x_size, y_size) + .x_label(&x_label) + .y_label(&y_label) + .x_limits( + &ImPlotRange { + Min: x_min, + Max: x_max, + }, + // Always means that the limits stay what we force them to here, even if the user + // scrolls or drags in the plot with the mouse. FirstUseEver sets the limits the + // first time the plot is drawn, but the user can then modify them and the change + // will stick. + Condition::Always, + ) + .y_limits( + &ImPlotRange { + Min: y_min, + Max: y_max, + }, + Condition::Always, + ) + .x_ticks(&x_ticks, false) + .y_ticks_with_labels(&y_ticks, false) + // If any of these flag setting calls are omitted, the defaults are used. + .with_plot_flags(&plot_flags) + .with_x_axis_flags(&x_axis_flags) + .with_y_axis_flags(&y_axis_flags) + .build(plot_ui, || { + PlotLine::new("A line").plot(&vec![2.1, 2.9], &vec![1.1, 1.9]); + }); +} + +pub fn show_query_features_plot(ui: &Ui, plot_ui: &PlotUi) { + ui.text(im_str!( + "This header demos how to use the querying features." + )); + let content_width = ui.window_content_region_width(); + + // Create some containers for exfiltrating data from the closure below + let mut hover_pos: Option = None; + let mut plot_limits: Option = None; + let mut query_limits: Option = None; + + // Draw a plot + Plot::new("Plot querying") + .size(content_width, 300.0) + .x_limits(&ImPlotRange { Min: 0.0, Max: 5.0 }, Condition::FirstUseEver) + .y_limits(&ImPlotRange { Min: 0.0, Max: 5.0 }, Condition::FirstUseEver) + .with_plot_flags(&(PlotFlags::NONE | PlotFlags::QUERY)) + .build(plot_ui, || { + if is_plot_hovered() { + hover_pos = Some(get_plot_mouse_position()); + } + + if is_plot_queried() { + query_limits = Some(get_plot_query()); + } + plot_limits = Some(get_plot_limits()); + }); + + // Print some previously-exfiltrated info. This is because calling + // things like is_plot_hovered or get_plot_mouse_position() outside + // of an actual Plot is not allowed. + if let Some(pos) = hover_pos { + ui.text(im_str!("hovered at {}, {}", pos.x, pos.y)); + } + if let Some(limits) = plot_limits { + ui.text(im_str!("Plot limits are {:#?}", limits)); + } + if let Some(query) = query_limits { + ui.text(im_str!("Query limits are {:#?}", query)); + } +} + +pub fn show_style_plot(ui: &Ui, plot_ui: &PlotUi) { + ui.text(im_str!( + "This header demos how to use the styling features." + )); + let content_width = ui.window_content_region_width(); + + // The style stack works the same as for other imgui things - we can push + // things to have them apply, then pop again to undo the change. In implot-rs, + // pushing returns a value on which we have to call .pop() later. Pushing + // variables can be done outside of plot calls as well. + let style = push_style_color(&PlotColorElement::PlotBg, 1.0, 1.0, 1.0, 0.2); + Plot::new("Style demo plot") + .size(content_width, 300.0) + .x_limits(&ImPlotRange { Min: 0.0, Max: 6.0 }, Condition::Always) + .y_limits( + &ImPlotRange { + Min: -1.0, + Max: 3.0, + }, + Condition::Always, + ) + .with_plot_flags(&(PlotFlags::NONE)) + .with_y_axis_flags(&(AxisFlags::NONE)) + .build(plot_ui, || { + // Markers can be selected as shown here. The markers are internally represented + // as an u32, hence this calling style. + let markerchoice = push_style_var_i32(&StyleVar::Marker, Marker::Cross as i32); + PlotLine::new("Left eye").plot(&vec![2.0, 2.0], &vec![2.0, 1.0]); + // Calling pop() on the return value of the push above will undo the marker choice. + markerchoice.pop(); + + // Line weights can be set the same way, along with some other things - see + // the docs of StyleVar for more info. + let lineweight = push_style_var_f32(&StyleVar::LineWeight, 5.0); + PlotLine::new("Right eye").plot(&vec![4.0, 4.0], &vec![2.0, 1.0]); + lineweight.pop(); + + let x_values = vec![1.0, 2.0, 4.0, 5.0]; + let y_values = vec![1.0, 0.0, 0.0, 1.0]; + PlotLine::new("Mouth").plot(&x_values, &y_values); + }); + + style.pop(); +} + +pub fn show_colormaps_plot(ui: &Ui, plot_ui: &PlotUi) { + ui.text(im_str!("This header demos how to select colormaps.")); + let content_width = ui.window_content_region_width(); + + // Select a colormap from the presets. The presets are listed in the Colormap enum + // and usually have something from 9 to 11 colors in them, with the second number + // being the option to resample the colormap to a custom number of colors if picked + // higher than 1. + set_colormap_from_preset(Colormap::Plasma, 1); + + Plot::new("Colormap demo plot") + .size(content_width, 300.0) + .build(plot_ui, || { + (1..10) + .map(|x| x as f64 * 0.1) + .map(|x| PlotLine::new(&format!("{:3.3}", x)).plot(&vec![0.1, 0.9], &vec![x, x])) + .count(); + }); + + // One can also specify a colormap as a vector of RGBA colors. ImPlot uses ImVec4 for this, + // so we follow suit. Make sure to set the last number (w in ImVec4) to 1.0 to see anything - + // it's the alpha channel. + set_colormap_from_vec(vec![ + ImVec4 { + x: 0.9, + y: 0.9, + z: 0.0, + w: 1.0, + }, + ImVec4 { + x: 0.0, + y: 0.9, + z: 0.9, + w: 1.0, + }, + ]); + + Plot::new("Colormap demo plot #2") + .size(content_width, 300.0) + .build(plot_ui, || { + (1..10) + .map(|x| x as f64 * 0.1) + .map(|x| PlotLine::new(&format!("{:3.3}", x)).plot(&vec![0.1, 0.9], &vec![x, x])) + .count(); + }); + + // Colormaps are not pushed, they are simply set, because they don't stack or anything. + // We can reset to the default by just setting the "Standard" preset. + set_colormap_from_preset(Colormap::Standard, 0); +}