rustopenglgtkgtk4

Can't draw a triangle using OpenGL in Rust


Using various code samples I've created a trivial OpenGL app, project dependencies below:

[dependencies]
epoxy = "0.1.0"
gl = "0.14.0"
gtk = { version = "0.7.3", package = "gtk4", features = ["v4_12"] }
libloading = "0.8.1"

The logics is also trivial: initialize a window using GTK+ v4.12 and an OpenGL context and try to draw a triangle.

main.rs

mod renderer;

use gtk::prelude::*;
use gtk::{glib, Application, ApplicationWindow};
use renderer::{on_render, on_realize};

const APP_ID: &str = "com.noobie.teapot";
const APP_NAME: &str = "Teapot";

fn main() -> glib::ExitCode {
    init_gl();
    // Create a new application
    let app = Application::builder()
        .application_id(APP_ID)
        .build();

    // Connect to "activate" signal of `app`
    app.connect_activate(on_activate);

    // Run the application
    app.run()
}

fn init_gl() {
    #[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("epoxy-0.dll").unwrap();

    epoxy::load_with(|name| {
        unsafe { library.get::<_>(name.as_bytes()) }
        .map(|symbol| *symbol)
            .unwrap_or(std::ptr::null())
    });
    gl::load_with(|s| epoxy::get_proc_addr(s));
}

fn on_activate(app: &Application) {
    // Create a window and set the title
    let window = ApplicationWindow::builder()
        .application(app)
        .title(APP_NAME)
        .default_width(800)
        .default_height(600)
        .build();

    let container = gtk::Paned::builder()
        .orientation(gtk::Orientation::Vertical)
        .shrink_end_child(true)
        .build();

    let gl_area = gtk::GLArea::builder()
        .auto_render(false)
        .height_request(8)
        .build();
    gl_area.connect_realize(on_realize);
    gl_area.connect_render(on_render);
    container.set_start_child(Some(&gl_area));

    let grid_view = gtk::GridView::builder()
        .height_request(4)
        .build();
    container.set_end_child(Some(&grid_view));

    window.set_child(Some(&container));
    // Present window
    window.present();
}

renderer.rs

use std::mem::{size_of_val, size_of};

use gtk::prelude::*;
use gtk::{glib::Propagation, gdk::GLContext};

type Vertex = [f32; 3];
const VERTICES: [Vertex; 3] = [
        [-0.5, -0.5, 0.0], 
        [0.5, -0.5, 0.0], 
        [0.0, 0.5, 0.0]
    ];
const VERT_SHADER: &str = r#"#version 330 core
        layout (location = 0) in vec3 pos;
        void main() {
            gl_Position = vec4(pos.x, pos.y, pos.z, 1.0);
        }
    "#;
const FRAG_SHADER: &str = r#"#version 330 core
        out vec4 final_color;
    
        void main() {
        final_color = vec4(1.0, 0.5, 0.2, 1.0);
        }
    "#;

pub fn on_realize(_gl_area: &gtk::GLArea) {
}

pub fn on_render(_gl_area: &gtk::GLArea, _ctx: &GLContext) -> Propagation {
    unsafe {
        gl::ClearColor(0.3, 0.3, 0.3, 1.0);

        let mut vao = 0;
        gl::GenVertexArrays(1, &mut vao);
        assert_ne!(vao, 0);

        let mut vbo = 0;
        gl::GenBuffers(1, &mut vbo);
        assert_ne!(vbo, 0);

        gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
        gl::BufferData(gl::ARRAY_BUFFER, size_of_val(&VERTICES) as isize, VERTICES.as_ptr().cast(), gl::STATIC_DRAW);
        gl::VertexAttribPointer(0, 3, gl::FLOAT, gl::FALSE, size_of::<Vertex>().try_into().unwrap(), 0 as *const _);
        gl::EnableVertexAttribArray(0);

        let vertex_shader = gl::CreateShader(gl::VERTEX_SHADER);
        assert_ne!(vertex_shader, 0);

        gl::ShaderSource(
                vertex_shader,
                1,
                &(VERT_SHADER.as_bytes().as_ptr().cast()),
                &(VERT_SHADER.len().try_into().unwrap()),
            );

        gl::CompileShader(vertex_shader);

        let mut success = 0;
        gl::GetShaderiv(vertex_shader, gl::COMPILE_STATUS, &mut success);

        if success == 0 {
            let mut v: Vec<u8> = Vec::with_capacity(1024);
            let mut log_len = 0_i32;
            gl::GetShaderInfoLog(
                    vertex_shader,
                    1024,
                    &mut log_len,
                    v.as_mut_ptr().cast(),
                );
            v.set_len(log_len.try_into().unwrap());
            panic!("Vertex Compile Error: {}", String::from_utf8_lossy(&v));
        }

        let fragment_shader = gl::CreateShader(gl::FRAGMENT_SHADER);
        assert_ne!(fragment_shader, 0);

        gl::ShaderSource(
                fragment_shader,
                1,
                &(FRAG_SHADER.as_bytes().as_ptr().cast()),
                &(FRAG_SHADER.len().try_into().unwrap()),
            );
        gl::CompileShader(fragment_shader);

        gl::GetShaderiv(fragment_shader, gl::COMPILE_STATUS, &mut success);
        if success == 0 {
            let mut v: Vec<u8> = Vec::with_capacity(1024);
            let mut log_len = 0_i32;
            gl::GetShaderInfoLog(
                    fragment_shader,
                    1024,
                    &mut log_len,
                    v.as_mut_ptr().cast(),
                );
            v.set_len(log_len.try_into().unwrap());
            panic!("Fragment Compile Error: {}", String::from_utf8_lossy(&v));
        }

        let shader_program = gl::CreateProgram();
        gl::AttachShader(shader_program, vertex_shader);
        gl::AttachShader(shader_program, fragment_shader);
        gl::LinkProgram(shader_program);

        let mut success = 0;
        gl::GetProgramiv(shader_program, gl::LINK_STATUS, &mut success);
        if success == 0 {
            let mut v: Vec<u8> = Vec::with_capacity(1024);
            let mut log_len = 0_i32;
            gl::GetProgramInfoLog(
                    shader_program,
                    1024,
                    &mut log_len,
                    v.as_mut_ptr().cast(),
                );
            v.set_len(log_len.try_into().unwrap());
            panic!("Program Link Error: {}", String::from_utf8_lossy(&v));
        }

        gl::DeleteShader(vertex_shader);
        gl::DeleteShader(fragment_shader);

        gl::UseProgram(shader_program);

        gl::Clear(gl::COLOR_BUFFER_BIT);
        gl::DrawArrays(gl::TRIANGLES, 0, 3);

        _gl_area.queue_draw();
    }

    return Propagation::Proceed;
}

However, when I launch my program it just shows a solid gray color:
Screenshot

I am a total noobie in OpenGL, so I can't even get what's the problem. There are no any errors in the console output.


Solution

  • The reason you only see gray is because that is your current clear color and the triangle is not drawn on top.

    As far as I can tell there are multiple issues with your current implementation and I am not sure if it would make sense to go through all of them.

    If you are only looking to make it work, here is a very basic sample for how renderer.rs could look:

    use gl::types::*;
    use std::ffi::CString;
    use std::mem;
    use std::ptr;
    
    use gtk::{gdk::GLContext, glib::Propagation};
    
    // Vertex data
    static VERTEX_DATA: [GLfloat; 6] = [0.0, 0.5, 0.5, -0.5, -0.5, -0.5];
    
    // Shader sources
    static VS_SRC: &'static str = "
    #version 150
    in vec2 position;
    
    void main() {
        gl_Position = vec4(position, 0.0, 1.0);
    }";
    
    static FS_SRC: &'static str = "
    #version 150
    out vec4 out_color;
    
    void main() {
        out_color = vec4(1.0, 1.0, 1.0, 1.0);
    }";
    
    pub fn on_realize(_gl_area: &gtk::GLArea) {}
    
    pub fn on_render(_gl_area: &gtk::GLArea, _ctx: &GLContext) -> Propagation {
        // Create GLSL shaders
        let vs = compile_shader(VS_SRC, gl::VERTEX_SHADER);
        let fs = compile_shader(FS_SRC, gl::FRAGMENT_SHADER);
        let program = link_program(vs, fs);
    
        let mut vao = 0;
        let mut vbo = 0;
    
        unsafe {
            // Create Vertex Array Object
            gl::GenVertexArrays(1, &mut vao);
            gl::BindVertexArray(vao);
    
            // Create a Vertex Buffer Object and copy the vertex data to it
            gl::GenBuffers(1, &mut vbo);
            gl::BindBuffer(gl::ARRAY_BUFFER, vbo);
            gl::BufferData(
                gl::ARRAY_BUFFER,
                (VERTEX_DATA.len() * mem::size_of::<GLfloat>()) as GLsizeiptr,
                mem::transmute(&VERTEX_DATA[0]),
                gl::STATIC_DRAW,
            );
    
            // Use shader program
            let out_color_cstring = CString::new("out_color").unwrap();
            gl::UseProgram(program);
            gl::BindFragDataLocation(program, 0, out_color_cstring.as_ptr());
    
            // Specify the layout of the vertex data
            let position_cstring = CString::new("position").unwrap();
            let pos_attr = gl::GetAttribLocation(program, position_cstring.as_ptr());
            gl::EnableVertexAttribArray(pos_attr as GLuint);
            gl::VertexAttribPointer(
                pos_attr as GLuint,
                2,
                gl::FLOAT,
                gl::FALSE as GLboolean,
                0,
                ptr::null(),
            );
    
            // Clear the screen to black
            gl::ClearColor(0.3, 0.3, 0.3, 1.0);
            gl::Clear(gl::COLOR_BUFFER_BIT);
            // Draw a triangle from the 3 vertices
            gl::DrawArrays(gl::TRIANGLES, 0, 3);
        }
    
        return Propagation::Proceed;
    }
    
    fn compile_shader(src: &str, ty: GLenum) -> GLuint {
        let shader;
        unsafe {
            shader = gl::CreateShader(ty);
            // Attempt to compile the shader
            let c_str = CString::new(src.as_bytes()).unwrap();
            gl::ShaderSource(shader, 1, &c_str.as_ptr(), ptr::null());
            gl::CompileShader(shader);
    
            // Get the compile status
            let mut status = gl::FALSE as GLint;
            gl::GetShaderiv(shader, gl::COMPILE_STATUS, &mut status);
    
            // Fail on error
            if status != (gl::TRUE as GLint) {
                let mut len = 0;
                gl::GetShaderiv(shader, gl::INFO_LOG_LENGTH, &mut len);
                let mut buf = Vec::with_capacity(len as usize);
                buf.set_len((len as usize) - 1); // subtract 1 to skip the trailing null character
                gl::GetShaderInfoLog(
                    shader,
                    len,
                    ptr::null_mut(),
                    buf.as_mut_ptr() as *mut GLchar,
                );
                panic!(
                    "{}",
                    std::str::from_utf8(&buf)
                        .ok()
                        .expect("ShaderInfoLog not valid utf8")
                );
            }
        }
        shader
    }
    
    fn link_program(vs: GLuint, fs: GLuint) -> GLuint {
        unsafe {
            let program = gl::CreateProgram();
            gl::AttachShader(program, vs);
            gl::AttachShader(program, fs);
            gl::LinkProgram(program);
            // Get the link status
            let mut status = gl::FALSE as GLint;
            gl::GetProgramiv(program, gl::LINK_STATUS, &mut status);
    
            // Fail on error
            if status != (gl::TRUE as GLint) {
                let mut len: GLint = 0;
                gl::GetProgramiv(program, gl::INFO_LOG_LENGTH, &mut len);
                let mut buf = Vec::with_capacity(len as usize);
                buf.set_len((len as usize) - 1); // subtract 1 to skip the trailing null character
                gl::GetProgramInfoLog(
                    program,
                    len,
                    ptr::null_mut(),
                    buf.as_mut_ptr() as *mut GLchar,
                );
                panic!(
                    "{}",
                    std::str::from_utf8(&buf)
                        .ok()
                        .expect("ProgramInfoLog not valid utf8")
                );
            }
            program
        }
    }
    

    which is heavily based on the official triangle example of gl-rs.

    However @BDL's comment among other things still applies of course and is not considered here, which is why I would advise you to look into the (glium based) OpenGL example of gtk4-rs.