0

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: &gtk::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: &gtk::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);
    }
}

1 Answers1

0

The answer turned out to be easier than I thought.

So, to render on a click event, you need to use the GLArea::queue_draw() method. The method calls inner GLAreaImpl::render(), which should contain a render logic - my_draw_func() in my case.

For me, the final solution was the following code:

let gl_area = Rc::new(GliumArea::new());
let click = GestureClick::new();
let g = gl_area.clone();
click.connect_pressed(move |_, _, _, _| {
    g.queue_draw();
});
gl_area.add_controller(click);