Can't draw a triangle using OpenGL in Rust

90 views Asked by At

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.

1

There are 1 answers

2
frankenapps On BEST ANSWER

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.