diff --git a/imgui-glow-renderer/examples/basic.rs b/imgui-glow-renderer/examples/01_basic.rs similarity index 98% rename from imgui-glow-renderer/examples/basic.rs rename to imgui-glow-renderer/examples/01_basic.rs index 88d3867..84aebe8 100644 --- a/imgui-glow-renderer/examples/basic.rs +++ b/imgui-glow-renderer/examples/01_basic.rs @@ -1,8 +1,6 @@ //! A basic self-contained example to get you from zero-to-demo-window as fast //! as possible. -use std::time::Instant; - use glow::HasContext; use glutin::{event_loop::EventLoop, WindowedContext}; use imgui_winit_support::WinitPlatform; @@ -26,7 +24,6 @@ fn main() { .expect("failed to create renderer"); // Standard winit event loop - let mut last_frame = Instant::now(); event_loop.run(move |event, _, control_flow| { *control_flow = glutin::event_loop::ControlFlow::Wait; match event { diff --git a/imgui-glow-renderer/examples/02_triangle.rs b/imgui-glow-renderer/examples/02_triangle.rs new file mode 100644 index 0000000..a49bb64 --- /dev/null +++ b/imgui-glow-renderer/examples/02_triangle.rs @@ -0,0 +1,82 @@ +//! A basic example showing imgui rendering together with some custom rendering. +//! +//! Note this example uses `RendererBuilder` rather than `auto_renderer` and +//! (because we're using the default "trivial" `ContextStateManager`) +//! therefore does not attempt to backup/restore OpenGL state. + +use std::time::Instant; + +use glow::HasContext; + +mod utils; + +use utils::Triangler; + +fn main() { + let (event_loop, window) = utils::create_window("Hello, triangle!", glutin::GlRequest::Latest); + let (mut winit_platform, mut imgui_context) = utils::imgui_init(&window); + let gl = utils::glow_context(&window); + + let mut ig_renderer = imgui_glow_renderer::RendererBuilder::new() + .build_owning(gl, &mut imgui_context) + .expect("failed to create renderer"); + let tri_renderer = Triangler::new(ig_renderer.gl_context(), "#version 330"); + + let mut last_frame = Instant::now(); + event_loop.run(move |event, _, control_flow| { + *control_flow = glutin::event_loop::ControlFlow::Wait; + match event { + glutin::event::Event::NewEvents(_) => { + let now = Instant::now(); + imgui_context + .io_mut() + .update_delta_time(now.duration_since(last_frame)); + last_frame = now; + } + glutin::event::Event::MainEventsCleared => { + winit_platform + .prepare_frame(imgui_context.io_mut(), window.window()) + .unwrap(); + + window.window().request_redraw(); + } + glutin::event::Event::RedrawRequested(_) => { + { + let gl = ig_renderer.gl_context(); + // This is required because, without the `StateBackupCsm` + // (which is provided by `auto_renderer` but not + // `RendererBuilder` by default), the OpenGL context is left + // in an arbitrary, dirty state + unsafe { gl.disable(glow::SCISSOR_TEST) }; + tri_renderer.render(gl); + } + + let ui = imgui_context.frame(); + // Safety: internally, this reference just gets passed as a + // pointer to imgui, which handles the null pointer properly. + ui.show_demo_window(unsafe { &mut *std::ptr::null_mut() }); + + winit_platform.prepare_render(&ui, window.window()); + let draw_data = ui.render(); + ig_renderer + .render(&draw_data) + .expect("error rendering imgui"); + + window.swap_buffers().unwrap(); + } + glutin::event::Event::WindowEvent { + event: glutin::event::WindowEvent::CloseRequested, + .. + } => { + *control_flow = glutin::event_loop::ControlFlow::Exit; + } + glutin::event::Event::LoopDestroyed => { + let gl = ig_renderer.gl_context(); + tri_renderer.destroy(gl); + } + event => { + winit_platform.handle_event(imgui_context.io_mut(), window.window(), &event); + } + } + }); +} diff --git a/imgui-glow-renderer/examples/03_triangle_gles.rs b/imgui-glow-renderer/examples/03_triangle_gles.rs new file mode 100644 index 0000000..7609c4e --- /dev/null +++ b/imgui-glow-renderer/examples/03_triangle_gles.rs @@ -0,0 +1,79 @@ +//! A basic example showing imgui rendering together with some custom rendering +//! using OpenGL ES, rather than full-fat OpenGL. +//! +//! Note this example uses `Renderer` rather than `OwningRenderer` and +//! therefore requries more lifetime-management of the OpenGL context. + +use std::time::Instant; + +mod utils; + +use utils::Triangler; + +fn main() { + let (event_loop, window) = utils::create_window( + "Hello, triangle!", + glutin::GlRequest::Specific(glutin::Api::OpenGlEs, (3, 0)), + ); + let (mut winit_platform, mut imgui_context) = utils::imgui_init(&window); + let gl = utils::glow_context(&window); + + let mut ig_renderer = imgui_glow_renderer::RendererBuilder::new() + .with_context_state_manager(imgui_glow_renderer::StateBackupCsm::default()) + .build_borrowing(&gl, &mut imgui_context) + .expect("failed to create renderer"); + // Note the shader header now needs a precision specifier + let tri_renderer = Triangler::new(&gl, "#version 300 es\nprecision mediump float;"); + + let mut last_frame = Instant::now(); + event_loop.run(move |event, _, control_flow| { + *control_flow = glutin::event_loop::ControlFlow::Wait; + match event { + glutin::event::Event::NewEvents(_) => { + let now = Instant::now(); + imgui_context + .io_mut() + .update_delta_time(now.duration_since(last_frame)); + last_frame = now; + } + glutin::event::Event::MainEventsCleared => { + winit_platform + .prepare_frame(imgui_context.io_mut(), window.window()) + .unwrap(); + + window.window().request_redraw(); + } + glutin::event::Event::RedrawRequested(_) => { + tri_renderer.render(&gl); + + let ui = imgui_context.frame(); + // Safety: internally, this reference just gets passed as a + // pointer to imgui, which handles the null pointer properly. + ui.show_demo_window(unsafe { &mut *std::ptr::null_mut() }); + + winit_platform.prepare_render(&ui, window.window()); + let draw_data = ui.render(); + ig_renderer + .render(&gl, &draw_data) + .expect("error rendering imgui"); + + window.swap_buffers().unwrap(); + } + glutin::event::Event::WindowEvent { + event: glutin::event::WindowEvent::CloseRequested, + .. + } => { + *control_flow = glutin::event_loop::ControlFlow::Exit; + } + glutin::event::Event::LoopDestroyed => { + tri_renderer.destroy(&gl); + // Note, to be good citizens we should manually call destroy + // when the renderer does not own the GL context + ig_renderer.destroy(&gl); + } + event => { + winit_platform.handle_event(imgui_context.io_mut(), window.window(), &event); + } + } + }); +} diff --git a/imgui-glow-renderer/examples/utils/mod.rs b/imgui-glow-renderer/examples/utils/mod.rs new file mode 100644 index 0000000..e8bd995 --- /dev/null +++ b/imgui-glow-renderer/examples/utils/mod.rs @@ -0,0 +1,135 @@ +use glow::HasContext; +use glutin::{event_loop::EventLoop, GlRequest}; +use imgui_winit_support::WinitPlatform; + +pub type Window = glutin::WindowedContext; + +pub fn create_window(title: &str, gl_request: GlRequest) -> (EventLoop<()>, Window) { + let event_loop = glutin::event_loop::EventLoop::new(); + let window = glutin::window::WindowBuilder::new() + .with_title(title) + .with_inner_size(glutin::dpi::LogicalSize::new(1024, 768)); + let window = glutin::ContextBuilder::new() + .with_gl(gl_request) + .with_vsync(true) + .build_windowed(window, &event_loop) + .expect("could not create window"); + let window = unsafe { + window + .make_current() + .expect("could not make window context current") + }; + (event_loop, window) +} + +pub fn glow_context(window: &Window) -> glow::Context { + unsafe { glow::Context::from_loader_function(|s| window.get_proc_address(s).cast()) } +} + +pub fn imgui_init(window: &Window) -> (WinitPlatform, imgui::Context) { + let mut imgui_context = imgui::Context::create(); + imgui_context.set_ini_filename(None); + + let mut winit_platform = WinitPlatform::init(&mut imgui_context); + winit_platform.attach_window( + imgui_context.io_mut(), + window.window(), + imgui_winit_support::HiDpiMode::Rounded, + ); + + imgui_context + .fonts() + .add_font(&[imgui::FontSource::DefaultFontData { config: None }]); + + imgui_context.io_mut().font_global_scale = (1.0 / winit_platform.hidpi_factor()) as f32; + + (winit_platform, imgui_context) +} + +pub struct Triangler { + pub program: ::Program, + pub vertex_array: ::VertexArray, +} + +impl Triangler { + pub fn new(gl: &glow::Context, shader_header: &str) -> Self { + const VERTEX_SHADER_SOURCE: &str = r#" +const vec2 verts[3] = vec2[3]( + vec2(0.5f, 1.0f), + vec2(0.0f, 0.0f), + vec2(1.0f, 0.0f) +); + +out vec2 vert; + +void main() { + vert = verts[gl_VertexID]; + gl_Position = vec4(vert - 0.5, 0.0, 1.0); +} +"#; + const FRAGMENT_SHADER_SOURCE: &str = r#" +in vec2 vert; +out vec4 colour; + +void main() { + colour = vec4(vert, 0.5, 1.0); +} +"#; + + let mut shaders = [ + (glow::VERTEX_SHADER, VERTEX_SHADER_SOURCE, 0), + (glow::FRAGMENT_SHADER, FRAGMENT_SHADER_SOURCE, 0), + ]; + + unsafe { + let vertex_array = gl + .create_vertex_array() + .expect("Cannot create vertex array"); + + let program = gl.create_program().expect("Cannot create program"); + + for (kind, source, handle) in &mut shaders { + let shader = gl.create_shader(*kind).expect("Cannot create shader"); + gl.shader_source(shader, &format!("{}\n{}", shader_header, *source)); + gl.compile_shader(shader); + if !gl.get_shader_compile_status(shader) { + panic!("{}", gl.get_shader_info_log(shader)); + } + gl.attach_shader(program, shader); + *handle = shader; + } + + gl.link_program(program); + if !gl.get_program_link_status(program) { + panic!("{}", gl.get_program_info_log(program)); + } + + for &(_, _, shader) in &shaders { + gl.detach_shader(program, shader); + gl.delete_shader(shader); + } + + Self { + program, + vertex_array, + } + } + } + + pub fn render(&self, gl: &glow::Context) { + unsafe { + gl.clear_color(0.05, 0.05, 0.1, 1.0); + gl.clear(glow::COLOR_BUFFER_BIT); + gl.use_program(Some(self.program)); + gl.bind_vertex_array(Some(self.vertex_array)); + gl.draw_arrays(glow::TRIANGLES, 0, 3); + } + } + + pub fn destroy(&self, gl: &glow::Context) { + unsafe { + gl.delete_program(self.program); + gl.delete_vertex_array(self.vertex_array); + } + } +}