How to multithreading glfw CursorPos callback?

Hello guys i am trying to make a square and update it every time the cursor is moved.In fact everything works fine. i try to prevent a pause in rendering during some window interactions (e.g. resizing). When I move the cursor, rendering becomes slow (I mean the square is not catching up with the cursor). I’m sure I didn’t set up the thread correctly.Here is my code. my problem is similar to this one https://stackoverflow.com/questions/32255136/glfw-pollevents-really-really-slow.

// Rust / glfw / gl
extern crate gl;
extern crate glfw;

use self::gl::types::*;

use std::ffi::CString;
use std::mem;
use std::os::raw::c_void;
use std::ptr;
use std::str;

use glfw::Context;
use std::sync::mpsc::channel;
use std::sync::mpsc::{Receiver, Sender};
use std::thread::Builder;

#[derive(Debug)]
enum Act {
    MouseMove { x: f64, y: f64 },
    Close,
}

fn make_div(W: f32, H: f32, x: f32, y: f32, width: f32, height: f32) -> Vec<f32> {
    let left: f32 = ((x / (width / 2.0)) - 1.0);
    let top: f32 = -((y / (height / 2.0)) - 1.0);
    let bottom: f32 = -((y + H) / (height / 2.0) - 1.0);
    let right: f32 = ((x + W) / (width / 2.0) - 1.0);

    let mut shape: Vec<f32> = vec![];

    shape.append(&mut vec![left, bottom]);

    shape.append(&mut vec![right, bottom]);

    shape.append(&mut vec![right, top]);

    shape.append(&mut vec![left, top]);

    shape
}

const vertexShaderSource: &str = r#"
    #version 330 core
    layout (location = 0) in vec3 aPos;
    void main() {
       gl_Position = vec4(aPos.x, aPos.y, aPos.z, 1.0);
    }
"#;

const fragmentShaderSource: &str = r#"
    #version 330 core
    out vec4 FragColor;
    void main() {
       FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
    }
"#;

fn main() {
    let mut glfw = glfw::init(glfw::FAIL_ON_ERRORS).unwrap();
    glfw.window_hint(glfw::WindowHint::ContextVersion(3, 3));
    glfw.window_hint(glfw::WindowHint::OpenGlProfile(
        glfw::OpenGlProfileHint::Core,
    ));
    #[cfg(target_os = "macos")]
    glfw.window_hint(glfw::WindowHint::OpenGlForwardCompat(true));

    let (mut window, events) = glfw
        .create_window(300, 300, "Hello this is window", glfw::WindowMode::Windowed)
        .expect("Failed to create GLFW window.");

    gl::load_with(|symbol| window.get_proc_address(symbol) as *const _);

    window.set_cursor_pos_polling(true);

    let render_context = window.render_context();
    let (mut tx, rx): (Sender<Act>, Receiver<Act>) = channel();

    let render_task = Builder::new().name("render task".to_string());

    let render_task_done = render_task.spawn(move || {
        render(render_context, rx);
    });

    while !window.should_close() {
        // glfw.poll_events();
        glfw.wait_events();
        for (_, event) in glfw::flush_messages(&events) {
            handle_window_event(&mut window, event, &mut tx);
        }
    }

    tx.send(Act::Close)
        .ok()
        .expect("Failed signal to render thread.");

    let _ = render_task_done;
}

fn render(mut context: glfw::RenderContext, finish: Receiver<Act>) {
    context.make_current();

    let mut state_vao: u32 = 0;

    let (shaderProgram, VAO) = unsafe {
        let vertexShader = gl::CreateShader(gl::VERTEX_SHADER);
        let c_str_vert = CString::new(vertexShaderSource.as_bytes()).unwrap();
        gl::ShaderSource(vertexShader, 1, &c_str_vert.as_ptr(), ptr::null());
        gl::CompileShader(vertexShader);

        let mut success = gl::FALSE as GLint;
        let mut infoLog = Vec::with_capacity(512);
        infoLog.set_len(512 - 1); // subtract 1 to skip the trailing null character
        gl::GetShaderiv(vertexShader, gl::COMPILE_STATUS, &mut success);
        if success != gl::TRUE as GLint {
            gl::GetShaderInfoLog(
                vertexShader,
                512,
                ptr::null_mut(),
                infoLog.as_mut_ptr() as *mut GLchar,
            );
            println!(
                "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n{}",
                str::from_utf8(&infoLog).unwrap()
            );
        }

        let fragmentShader = gl::CreateShader(gl::FRAGMENT_SHADER);
        let c_str_frag = CString::new(fragmentShaderSource.as_bytes()).unwrap();
        gl::ShaderSource(fragmentShader, 1, &c_str_frag.as_ptr(), ptr::null());
        gl::CompileShader(fragmentShader);
        gl::GetShaderiv(fragmentShader, gl::COMPILE_STATUS, &mut success);
        if success != gl::TRUE as GLint {
            gl::GetShaderInfoLog(
                fragmentShader,
                512,
                ptr::null_mut(),
                infoLog.as_mut_ptr() as *mut GLchar,
            );
            println!(
                "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n{}",
                str::from_utf8(&infoLog).unwrap()
            );
        }

        let shaderProgram = gl::CreateProgram();

        gl::AttachShader(shaderProgram, vertexShader);
        gl::AttachShader(shaderProgram, fragmentShader);
        gl::LinkProgram(shaderProgram);
        gl::GetProgramiv(shaderProgram, gl::LINK_STATUS, &mut success);
        if success != gl::TRUE as GLint {
            gl::GetProgramInfoLog(
                shaderProgram,
                512,
                ptr::null_mut(),
                infoLog.as_mut_ptr() as *mut GLchar,
            );
            println!(
                "ERROR::SHADER::PROGRAM::COMPILATION_FAILED\n{}",
                str::from_utf8(&infoLog).unwrap()
            );
        }
        gl::DeleteShader(vertexShader);
        gl::DeleteShader(fragmentShader);

        let mut vertices: Vec<f32> = Vec::new();
        vertices.append(&mut make_div(100.0, 100.0, 10.0, 10.0, 300.0, 300.0));

        let indices = [
            // note that we start from 0!
            0, 1, 2, // first Triangle
            2, 3, 0, // second Triangle
        ];
        let (mut VBO, mut VAO, mut EBO) = (0, 0, 0);
        gl::GenVertexArrays(1, &mut VAO);
        gl::GenBuffers(1, &mut VBO);
        gl::GenBuffers(1, &mut EBO);
        gl::BindVertexArray(VAO);

        gl::BindBuffer(gl::ARRAY_BUFFER, VBO);
        gl::BufferData(
            gl::ARRAY_BUFFER,
            (vertices.len() * mem::size_of::<GLfloat>()) as GLsizeiptr,
            &vertices[0] as *const f32 as *const c_void,
            gl::STATIC_DRAW,
        );

        gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, EBO);
        gl::BufferData(
            gl::ELEMENT_ARRAY_BUFFER,
            (indices.len() * mem::size_of::<GLfloat>()) as GLsizeiptr,
            &indices[0] as *const i32 as *const c_void,
            gl::STATIC_DRAW,
        );

        gl::VertexAttribPointer(
            0,
            2,
            gl::FLOAT,
            gl::FALSE,
            2 * mem::size_of::<GLfloat>() as GLsizei,
            ptr::null(),
        );
        gl::EnableVertexAttribArray(0);

        gl::BindBuffer(gl::ARRAY_BUFFER, 0);

        gl::BindVertexArray(0);

        (shaderProgram, VAO)
    };

    state_vao = VAO;

    // repeating ..
    fn update(x: f32, y: f32) -> u32 {
        unsafe {
            let mut vertices: Vec<f32> = Vec::new();
            vertices.append(&mut make_div(100.0, 100.0, x, y, 300.0, 300.0));

            let indices = [
                // note that we start from 0!
                0, 1, 2, // first Triangle
                2, 3, 0, // second Triangle
            ];

            let (mut VBO, mut VAO, mut EBO) = (0, 0, 0);
            gl::GenVertexArrays(1, &mut VAO);
            gl::GenBuffers(1, &mut VBO);
            gl::GenBuffers(1, &mut EBO);
            gl::BindVertexArray(VAO);

            gl::BindBuffer(gl::ARRAY_BUFFER, VBO);
            gl::BufferData(
                gl::ARRAY_BUFFER,
                (vertices.len() * mem::size_of::<GLfloat>()) as GLsizeiptr,
                &vertices[0] as *const f32 as *const c_void,
                gl::STATIC_DRAW,
            );

            gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, EBO);
            gl::BufferData(
                gl::ELEMENT_ARRAY_BUFFER,
                (indices.len() * mem::size_of::<GLfloat>()) as GLsizeiptr,
                &indices[0] as *const i32 as *const c_void,
                gl::STATIC_DRAW,
            );

            gl::VertexAttribPointer(
                0,
                2,
                gl::FLOAT,
                gl::FALSE,
                2 * mem::size_of::<GLfloat>() as GLsizei,
                ptr::null(),
            );
            gl::EnableVertexAttribArray(0);

            gl::BindBuffer(gl::ARRAY_BUFFER, 0);

            gl::BindVertexArray(0);
            VAO
        }
    }

    loop {
        let open = finish.try_recv();

        if !open.is_err() {
            match open.unwrap() {
                Act::MouseMove { x, y } => {
                    state_vao = update(x as f32, y as f32);
                }
                Act::Close => {
                    break;
                }
            }
        }

        unsafe {
            gl::ClearColor(0.2, 0.3, 0.3, 1.0);
            gl::Clear(gl::COLOR_BUFFER_BIT);
            gl::Scissor(0, 0, 300, 300);
            gl::Enable(gl::SCISSOR_TEST);

            gl::UseProgram(shaderProgram);
            gl::BindVertexArray(state_vao);
            gl::DrawElements(gl::TRIANGLE_FAN, 200, gl::UNSIGNED_INT, ptr::null());
        }
        context.swap_buffers();
    }

    glfw::make_context_current(None);
}

fn handle_window_event(window: &mut glfw::Window, event: glfw::WindowEvent, ch: &mut Sender<Act>) {
    match event {
        glfw::WindowEvent::CursorPos(xpos, ypos) => {
            ch.send(Act::MouseMove { x: xpos, y: ypos }).unwrap();
        }
        _ => {}
    }
}

Hi @vaheqelyan welcome to the GLFW forums.

Sadly I don’t know enough Rust to help you with your program details.

If you think your issue is the same as the one linked then profiling your code should show that glfwPollEvents is occasionally taking substantial amounts of time, is it?

From what I can see it looks like you’re sending all mouse movements received by the pollevents function to a queue, then updating a buffer for every mouse event. I’m not sure if the mouse events received are all being processed before you call DrawElements or if only one is processed though.

If you’re getting lots of mouse events this could be slow, and if you’re only processing one event per frame then the mouse events will lag behind the rendering. Rather than using a channel you might want to update some form of shared mouse x,y state in a thread safe way.

1 Like

I kinda solved this, instead of creating a channel and sending messages, I now use a mutable shared-state between glfw and opengl. and that solved my problem. there are no lags and duration between the movement of the square.

struct MousePos {
    x: Arc<Mutex<f32>>, 
    y: Arc<Mutex<f32>>,
}

// later ...

let state = Arc::new(
  x: Arc::new(Mutex::new(0.0)),
  y: Arc::new(Mutex::new(0.0)),
});

let clone_state = Arc::clone(&state);
 
let render_task_done = render_task.spawn(move || {
  render(render_context, state);
});


// glfw loop ( main thread )
...
  handle_window_event(&mut window, event, &clone_state);
...

// handle_window_event

    let mut x = counter.x.lock().unwrap();
    let mut y = counter.y.lock().unwrap();
    match event {
        glfw::WindowEvent::CursorPos(xpos, ypos) => {
            *x = xpos as f32;
            *y = ypos as f32;
        }
        _ => {}
    }

// render loop ( render thread )

state_vao = update( *counter.x.lock().unwrap(), *counter.y.lock().unwrap());


I hope this is correct

I found some related link https://github.com/LWJGL/lwjgl3-demos/blob/master/src/org/lwjgl/demo/opengl/glfw/Multithreaded.java

I’m glad that fixed your problem - sadly I don’t know enough Rust to be able to advise you in detail.

For your example I’d say that if it’s working it’s probably ‘correct enough’. You might want to ask more detailed questions on a Rust gamedev forum as the issue of getting data from one thread to another isn’t GLFW specific.

You might find that as you add more input state you need to move from mutexes for each input variable to a double buffered struct approach.

1 Like