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: >k::GLArea) {
}
pub fn on_render(_gl_area: >k::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:
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.
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: >k::GLArea) {}
pub fn on_render(_gl_area: >k::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
.