diff --git a/imgui-glow-renderer/examples/glow_05_framebufferobject.rs b/imgui-glow-renderer/examples/glow_05_framebufferobject.rs new file mode 100644 index 0000000..f851745 --- /dev/null +++ b/imgui-glow-renderer/examples/glow_05_framebufferobject.rs @@ -0,0 +1,150 @@ +//! A basic example showing imgui rendering on top of a simple custom scene. + +use std::{cell::RefCell, rc::Rc, time::Instant}; + +mod utils; + +use glow::HasContext; +use utils::Triangler; + +struct UserData { + gl: Rc, + fbo: glow::NativeFramebuffer, + _rbo: glow::NativeRenderbuffer, +} + +const FBO_SIZE: i32 = 128; + +fn main() { + let (event_loop, window) = utils::create_window("Hello, FBO!", 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::AutoRenderer::initialize(gl, &mut imgui_context) + .expect("failed to create renderer"); + let tri_renderer = Triangler::new(ig_renderer.gl_context(), "#version 330"); + + let fbo; + let rbo; + unsafe { + let gl = ig_renderer.gl_context(); + fbo = gl.create_framebuffer().unwrap(); + rbo = gl.create_renderbuffer().unwrap(); + + gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, Some(fbo)); + gl.bind_renderbuffer(glow::RENDERBUFFER, Some(rbo)); + gl.renderbuffer_storage(glow::RENDERBUFFER, glow::RGBA8, FBO_SIZE, FBO_SIZE); + gl.framebuffer_renderbuffer( + glow::DRAW_FRAMEBUFFER, + glow::COLOR_ATTACHMENT0, + glow::RENDERBUFFER, + Some(rbo), + ); + gl.bind_renderbuffer(glow::RENDERBUFFER, None); + + gl.viewport(0, 0, FBO_SIZE, FBO_SIZE); + tri_renderer.render(gl); + + gl.bind_framebuffer(glow::DRAW_FRAMEBUFFER, None); + } + + let data = Rc::new(RefCell::new(UserData { + gl: Rc::clone(ig_renderer.gl_context()), + fbo, + _rbo: rbo, + })); + + let mut last_frame = Instant::now(); + event_loop.run(move |event, _, control_flow| { + 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(_) => { + // Render your custom scene, note we need to borrow the OpenGL + // context from the `AutoRenderer`, which takes ownership of it. + unsafe { + ig_renderer.gl_context().clear(glow::COLOR_BUFFER_BIT); + } + + let ui = imgui_context.frame(); + ui.show_demo_window(&mut true); + ui.window("FBO").resizable(false).build(|| { + let pos = ui.cursor_screen_pos(); + ui.set_cursor_screen_pos([pos[0] + FBO_SIZE as f32, pos[1] + FBO_SIZE as f32]); + + let draws = ui.get_window_draw_list(); + let scale = ui.io().display_framebuffer_scale; + let dsp_size = ui.io().display_size; + draws + .add_callback({ + let data = Rc::clone(&data); + move || { + let data = data.borrow(); + let gl = &*data.gl; + unsafe { + let x = pos[0] * scale[0]; + let y = (dsp_size[1] - pos[1]) * scale[1]; + let dst_x0 = x as i32; + let dst_y0 = (y - FBO_SIZE as f32 * scale[1]) as i32; + let dst_x1 = (x + FBO_SIZE as f32 * scale[0]) as i32; + let dst_y1 = y as i32; + gl.scissor(dst_x0, dst_y0, dst_x1 - dst_x0, dst_y1 - dst_y0); + gl.enable(glow::SCISSOR_TEST); + gl.bind_framebuffer(glow::READ_FRAMEBUFFER, Some(data.fbo)); + gl.blit_framebuffer( + 0, + 0, + FBO_SIZE, + FBO_SIZE, + dst_x0, + dst_y0, + dst_x1, + dst_y1, + glow::COLOR_BUFFER_BIT, + glow::NEAREST, + ); + gl.bind_framebuffer(glow::READ_FRAMEBUFFER, None); + } + } + }) + .build(); + }); + + winit_platform.prepare_render(ui, window.window()); + let draw_data = imgui_context.render(); + + // Render imgui on top of it + 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/src/lib.rs b/imgui-glow-renderer/src/lib.rs index c0131a3..e178fa6 100644 --- a/imgui-glow-renderer/src/lib.rs +++ b/imgui-glow-renderer/src/lib.rs @@ -45,7 +45,7 @@ //! is sRGB (if you don't know, it probably is) the `internal_format` is //! one of the `SRGB*` values. -use std::{borrow::Cow, error::Error, fmt::Display, mem::size_of, num::NonZeroU32}; +use std::{borrow::Cow, error::Error, fmt::Display, mem::size_of, num::NonZeroU32, rc::Rc}; use imgui::{internal::RawWrapper, DrawCmd, DrawData, DrawVert}; @@ -71,7 +71,7 @@ type GlUniformLocation = ::UniformLocation; /// OpenGL context is still available to the rest of the application through /// the [`gl_context`](Self::gl_context) method. pub struct AutoRenderer { - gl: glow::Context, + gl: Rc, texture_map: SimpleTextureMap, renderer: Renderer, } @@ -87,7 +87,7 @@ impl AutoRenderer { let mut texture_map = SimpleTextureMap::default(); let renderer = Renderer::initialize(&gl, imgui_context, &mut texture_map, true)?; Ok(Self { - gl, + gl: Rc::new(gl), texture_map, renderer, }) @@ -96,7 +96,7 @@ impl AutoRenderer { /// Note: no need to provide a `mut` version of this, as all methods on /// [`glow::HasContext`] are immutable. #[inline] - pub fn gl_context(&self) -> &glow::Context { + pub fn gl_context(&self) -> &Rc { &self.gl } diff --git a/imgui/src/draw_list.rs b/imgui/src/draw_list.rs index 2f7a097..3ca6eee 100644 --- a/imgui/src/draw_list.rs +++ b/imgui/src/draw_list.rs @@ -16,7 +16,7 @@ use bitflags::bitflags; use crate::{math::MintVec2, ImColor32}; -use sys::ImDrawList; +use sys::{ImDrawCmd, ImDrawList}; use super::Ui; use crate::render::renderer::TextureId; @@ -468,6 +468,14 @@ impl<'ui> DrawListMut<'ui> { ) -> ImageRounded<'_> { ImageRounded::new(self, texture_id, p_min, p_max, rounding) } + + /// Draw the specified callback. + /// + /// Note: if this DrawList is never rendered the callback will leak because DearImGui + /// does not provide a method to clean registered callbacks. + pub fn add_callback(&'ui self, callback: F) -> Callback<'ui, F> { + Callback::new(self, callback) + } } /// Represents a line about to be drawn @@ -1186,3 +1194,45 @@ impl<'ui> ImageRounded<'ui> { } } } + +#[must_use = "should call .build() to draw the object"] +pub struct Callback<'ui, F> { + draw_list: &'ui DrawListMut<'ui>, + callback: F, +} + +impl<'ui, F: FnOnce() + 'static> Callback<'ui, F> { + /// Typically constructed by [`DrawListMut::add_callback`] + pub fn new(draw_list: &'ui DrawListMut<'_>, callback: F) -> Self { + Callback { + draw_list, + callback, + } + } + /// Adds the callback to the draw-list so it will be run when the window is drawn + pub fn build(self) { + use std::os::raw::c_void; + // F is Sized, so *mut F must be a thin pointer. + let callback: *mut F = Box::into_raw(Box::new(self.callback)); + + unsafe { + sys::ImDrawList_AddCallback( + self.draw_list.draw_list, + Some(Self::run_callback), + callback as *mut c_void, + ); + } + } + unsafe extern "C" fn run_callback(_parent_list: *const ImDrawList, cmd: *const ImDrawCmd) { + // We are modifying through a C const pointer, but that should be harmless. + let cmd = &mut *(cmd as *mut ImDrawCmd); + // Consume the pointer and leave a NULL behind to avoid a double-free or + // calling twice an FnOnce. It should not happen, but better safe than sorry. + let callback = std::mem::replace(&mut cmd.UserCallbackData, std::ptr::null_mut()); + if callback.is_null() { + return; + } + let callback = Box::from_raw(callback as *mut F); + callback(); + } +}