Advertisement

[Help] How to multithreading glfw CursorPos callback?

Started by November 22, 2019 05:05 PM
2 comments, last by Vahe 4 years, 9 months ago

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();
        }
        _ => {}
    }
}

 

Sorry, i don't have enough rust knowledge to comment on the programm.

I use glfw3.3 and c++, glfwwaitevent() doesn not exist there (any more ?). Instead, i call glfwpollevents once in the mainloop, which is pretty peformant. Searching the glfw documentation on 3.0 i find that waitevents apparently puts the thread to sleep until something happens, which might cause lag if nothing happens :-). It is also warned that some operations may cause the application to lag.

What if you simply use pollevents instead befor swapping buffers, which only processes pending events and returns immediately.

But maybe somebody with more experience comes along with more specific help ...

---------

And a question out of personal interest: is there also a 3.3 version of glfw for rust that you knew of ?

Edit: oh, it apparently depends on the installed glfw version ...

 

Advertisement

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.

This topic is closed to new replies.

Advertisement