I want to perform a render on click event on the GLArea. The problem is that when I call the drawing method from the event handler, the "canvas" does not change.
There is the my_draw_func() method in the Renderer struct, that contains the render logic. For now it just fills the GliumArea black and draws triangle over it.
My actions
I call the Renderer::my_draw_func() method from the click event handler:
let gl_area = Rc::new(GliumArea::new());
let click = GestureClick::new();
click.connect_pressed(|_, _, _, _| {
g.inner().renderer.borrow_mut().as_mut().unwrap().my_draw_func();
});
gl_area.add_controller(click);
The my_draw_func() method is successfully executed (the debug print works), but an image on the GliumArea does not change.
Details
- GliumArea has the inner() method that allows to get the inner GliumGLArea for access to the Renderer.
- my_draw_func() method works fine if it run from GLAreaImpl::render().
impl GLAreaImpl for GliumGLArea {
fn render(&self, _context: >k::gdk::GLContext) -> bool {
// works fine!
self.renderer.borrow().as_ref().unwrap().my_draw_foo();
true
}
}
Full code
main.rs
use std::ptr;
use std::rc::Rc;
use gtk::{Application, ApplicationWindow, GestureClick};
use gtk::gdk::prelude::*;
use gtk::prelude::*;
mod glium_area;
use glium_area::GliumArea;
fn load_gl_function() {
// Load GL pointers from epoxy (GL context management library used by GTK).
#[cfg(target_os = "macos")]
let library = unsafe { libloading::os::unix::Library::new("libepoxy.0.dylib") }.unwrap();
#[cfg(all(unix, not(target_os = "macos")))]
let library = unsafe { libloading::os::unix::Library::new("libepoxy.so.0") }.unwrap();
#[cfg(windows)]
let library = libloading::os::windows::Library::open_already_loaded("libepoxy-0.dll")
.or_else(|_| libloading::os::windows::Library::open_already_loaded("epoxy-0.dll"))
.unwrap();
epoxy::load_with(|name| {
unsafe { library.get::<_>(name.as_bytes()) }
.map(|symbol| *symbol)
.unwrap_or(ptr::null())
});
}
fn main() {
load_gl_function();
let app = Application::builder()
.application_id("org.company.application")
.build();
app.connect_activate(build_ui);
app.run();
}
fn build_ui(app: &Application) {
let gl_area = Rc::new(GliumArea::new());
let click = GestureClick::new();
let g = gl_area.clone();
click.connect_pressed(move |_, _, x, y| {
g.inner().renderer.borrow().as_ref().unwrap().my_draw_func();
});
gl_area.add_controller(click);
let window = ApplicationWindow::builder()
.application(app)
.child(gl_area.as_ref())
.title("Application")
.build();
window.present();
}
glium_area/imp.rs
use std::cell::RefCell;
use gtk::glib;
use gtk::prelude::*;
use gtk::subclass::prelude::*;
use std::rc::Rc;
use glium::{Frame, implement_vertex, IndexBuffer, program, Surface, uniform, VertexBuffer};
use glium::index::PrimitiveType;
#[derive(Copy, Clone)]
pub(super) struct Vertex {
position: [f32; 2],
color: [f32; 3],
}
implement_vertex!(Vertex, position, color);
pub struct Renderer {
context: Rc<glium::backend::Context>,
vertex_buffer: VertexBuffer<Vertex>,
index_buffer: IndexBuffer<u16>,
program: glium::Program,
}
impl Renderer {
pub fn new(context: Rc<glium::backend::Context>) -> Self {
// The following code is based on glium's triangle example:
// https://github.com/glium/glium/blob/2ff5a35f6b097889c154b42ad0233c6cdc6942f4/examples/triangle.rs
let vertex_buffer = VertexBuffer::new(
&context,
&[
Vertex { position: [-0.5, -0.5], color: [0., 1., 0.] },
Vertex { position: [0., 0.5], color: [0., 0., 1.] },
Vertex { position: [0.5, -0.5], color: [1., 0., 0.] },
],
).unwrap();
let index_buffer = IndexBuffer::new(&context, PrimitiveType::TrianglesList, &[0u16, 1, 2]).unwrap();
let program = program!(&context,
140 => {
vertex: "
#version 140
uniform mat4 matrix;
in vec2 position;
in vec3 color;
out vec3 vColor;
void main() {
gl_Position = vec4(position, 0.0, 1.0) * matrix;
vColor = color;
}
",
fragment: "
#version 140
in vec3 vColor;
out vec4 f_color;
void main() {
f_color = vec4(vColor, 1.0);
}
"
},
).unwrap();
Renderer {
context,
vertex_buffer,
index_buffer,
program
}
}
pub fn draw(&self) {
let mut frame = Frame::new(
self.context.clone(),
self.context.get_framebuffer_dimensions(),
);
let uniforms = uniform!{
matrix: [
[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1f32]
]
};
frame.clear_color(0., 0., 0., 0.);
frame
.draw(
&self.vertex_buffer,
&self.index_buffer,
&self.program,
&uniforms,
&Default::default(),
).unwrap();
frame.finish().unwrap();
}
pub fn my_draw_func(&self) {
println!("my_draw_func");
let mut frame = Frame::new(
self.context.clone(),
self.context.get_framebuffer_dimensions(),
);
let uniforms = uniform!{
matrix: [
[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1f32]
]
};
frame.clear_color(0., 0., 0., 1.); // change bg color to black
frame
.draw(
&self.vertex_buffer,
&self.index_buffer,
&self.program,
&uniforms,
&Default::default(),
).unwrap();
frame.finish().unwrap();
}
}
#[derive(Default)]
pub struct GliumGLArea {
pub renderer: RefCell<Option<Renderer>>
}
#[glib::object_subclass]
impl ObjectSubclass for GliumGLArea {
const NAME: &'static str = "GliumGLArea";
type Type = super::GliumArea;
type ParentType = gtk::GLArea;
}
impl ObjectImpl for GliumGLArea {}
impl WidgetImpl for GliumGLArea {
fn realize(&self) {
self.parent_realize();
let widget = self.obj();
if widget.error().is_some() { return; }
let context = unsafe {
glium::backend::Context::new(widget.clone(), true, Default::default())
}.unwrap();
*self.renderer.borrow_mut() = Some(Renderer::new(context));
}
fn unrealize(&self) {
*self.renderer.borrow_mut() = None;
self.parent_unrealize();
}
}
impl GLAreaImpl for GliumGLArea {
fn render(&self, _context: >k::gdk::GLContext) -> bool {
self.renderer.borrow().as_ref().unwrap().draw();
true
}
}
glum_area/mod.rs
mod imp;
use gtk::{gdk, glib};
use gtk::prelude::{GLAreaExt, WidgetExt};
use gtk::subclass::prelude::ObjectSubclassExt;
use self::imp::GliumGLArea;
glib::wrapper! {
pub struct GliumArea(ObjectSubclass<imp::GliumGLArea>)
@extends gtk::GLArea, gtk::Widget;
}
impl Default for GliumArea {
fn default() -> Self {
Self::new()
}
}
impl GliumArea {
pub fn new() -> Self {
glib::Object::new()
}
pub fn inner(&self) -> &GliumGLArea {
GliumGLArea::from_obj(self)
}
}
unsafe impl glium::backend::Backend for GliumArea {
fn swap_buffers(&self) -> Result<(), glium::SwapBuffersError> {
// We're supposed to draw (and hence swap buffers) only inside the `render()` vfunc or
// signal, which means that GLArea will handle buffer swaps for us.
Ok(())
}
unsafe fn get_proc_address(&self, symbol: &str) -> *const std::ffi::c_void {
epoxy::get_proc_addr(symbol)
}
fn get_framebuffer_dimensions(&self) -> (u32, u32) {
let scale = self.scale_factor();
let width = self.width();
let height = self.height();
((width * scale) as u32, (height * scale) as u32)
}
fn is_current(&self) -> bool {
match self.context() {
Some(context) => gdk::GLContext::current() == Some(context),
None => false,
}
}
unsafe fn make_current(&self) {
GLAreaExt::make_current(self);
}
}