Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add callbacks to the draw_list. #702

Merged
merged 3 commits into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions imgui-glow-renderer/examples/glow_05_framebufferobject.rs
Original file line number Diff line number Diff line change
@@ -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<glow::Context>,
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);
}
}
});
}
8 changes: 4 additions & 4 deletions imgui-glow-renderer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -71,7 +71,7 @@ type GlUniformLocation = <Context as HasContext>::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<glow::Context>,
texture_map: SimpleTextureMap,
renderer: Renderer,
}
Expand All @@ -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,
})
Expand All @@ -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<glow::Context> {
&self.gl
}

Expand Down
52 changes: 51 additions & 1 deletion imgui/src/draw_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<F: FnOnce() + 'static>(&'ui self, callback: F) -> Callback<'ui, F> {
Callback::new(self, callback)
}
}

/// Represents a line about to be drawn
Expand Down Expand Up @@ -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();
}
}