Adding OpenGL Bindings and Drawing a Background Colour
In this chapter we will add the OpenGl bindings to our project, use our OpenGL context and draw a background colour on our window using OpenGL.
OpenGL Bindings
First of all, we will need to generate the OpenGL bindings so we can use them in Rust.
We will add a build script to our project so that we can generate the OpenGL bindings when building our project. We will tell Cargo to cache the bindings and only regenerate them, if the build script changes.
We need the package gl_generator
as a build dependency. Add the following two lines to your Cargo.toml
:
[build-dependencies]
gl_generator = "0.14"
Now, add a new file called build.rs
and add the following contents:
use std::env;
use std::fs::File;
use std::path::PathBuf;
use gl_generator::{Api, Fallbacks, Profile, Registry, StructGenerator};
fn main() {
let dest = PathBuf::from(&env::var("OUT_DIR").unwrap());
println!("cargo:rerun-if-changed=build.rs");
let mut file = File::create(dest.join("gl_bindings.rs")).unwrap();
Registry::new(Api::Gles2, (3, 0), Profile::Core, Fallbacks::All, [])
.write_bindings(StructGenerator, &mut file)
.unwrap();
}
Note: If you're interested in what the output of this buildscript is, just run
cargo build
. Afterwards, you will find a file calledtarget/debug/build/background-????????????????/out/gl_bindings.rs
(where the questionmarks are a hexadecimal string, e. g. in my casecf930aed0e6f0fe0
).
Add the following lines to your main.rs
to make the OpenGL bindings available to your code:
pub mod gl {
#![allow(clippy::all)]
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
pub use Gles2 as Gl;
}
OpenGL Renderer
Now, let's initialise our OpenGL renderer.
In main.rs
, our struct Renderer
needs to store a Gl
handle:
pub struct Renderer {
gl: gl::Gl,
}
Now we can implement the functions new
, draw
and add new function resize
for the trait implementation of glwindow::AppRenderer
for Renderer
.
This will need a new import:
use std::ffi::CString;
The new
function will call gl::Gl::load_with
and store the result in Renderer::gl
:
fn new<D: glwindow::GlDisplay>(gl_display: &D) -> Self {
let gl = gl::Gl::load_with(|symbol| {
let symbol = CString::new(symbol).unwrap();
gl_display.get_proc_address(symbol.as_c_str()).cast()
});
Self { gl }
}
The draw
function will just render the background for our app. We do that by setting the background colour as RGBA quadruple (0.1, 0.1, 0.1, 0.9), which is a dark gray with slight transparency. After setting the background colour using ClearColor
, we need to actually draw the background using Clear
:
fn draw(&self, _state: &mut State) {
unsafe {
self.gl.ClearColor(0.1, 0.1, 0.1, 0.9);
self.gl.Clear(gl::COLOR_BUFFER_BIT);
}
}
And finally the resize
function will just tell OpenGL about the new size of our OpenGL context:
fn resize(&mut self, width: i32, height: i32) {
unsafe {
self.gl.Viewport(0, 0, width, height);
}
}
See It in Action
This is all we need for now. Try it out! Run
cargo run
and lo and behold

our window is now drawing a slightly transparent dark gray background on our window—using OpenGL!
And it even redraws when we move the window!
Now we have everything prepared to draw our first triangle in the next chapter.
Play Around With It
Definitely try two things:
- Change the colour of the background.
- Change the amount of transparency.
The Full Code for This Chapter
As always, here you can find the complete code for this chapter. You can use this to get up too speed quickly!
Cargo.toml
[package]
name = "background"
edition = "2021"
[dependencies]
glwindow = "0.1"
[build-dependencies]
gl_generator = "0.14"
build.rs
use std::env;
use std::fs::File;
use std::path::PathBuf;
use gl_generator::{Api, Fallbacks, Profile, Registry, StructGenerator};
fn main() {
let dest = PathBuf::from(&env::var("OUT_DIR").unwrap());
println!("cargo:rerun-if-changed=build.rs");
let mut file = File::create(dest.join("gl_bindings.rs")).unwrap();
Registry::new(Api::Gles2, (3, 0), Profile::Core, Fallbacks::All, [])
.write_bindings(StructGenerator, &mut file)
.unwrap();
}
src/main.rs
use std::error::Error;
use std::ffi::CString;
use glwindow::AppControl;
use glwindow::event::{WindowEvent, KeyEvent};
use glwindow::keyboard::{Key, NamedKey::Escape};
pub mod gl {
#![allow(clippy::all)]
include!(concat!(env!("OUT_DIR"), "/gl_bindings.rs"));
pub use Gles2 as Gl;
}
pub struct State {}
pub struct Renderer {
gl: gl::Gl,
}
impl glwindow::AppRenderer for Renderer {
type AppState = State;
fn new<D: glwindow::GlDisplay>(gl_display: &D) -> Self {
let gl = gl::Gl::load_with(|symbol| {
let symbol = CString::new(symbol).unwrap();
gl_display.get_proc_address(symbol.as_c_str()).cast()
});
Self { gl }
}
fn draw(&self, _state: &mut State) {
unsafe {
self.gl.ClearColor(0.1, 0.1, 0.1, 0.9);
self.gl.Clear(gl::COLOR_BUFFER_BIT);
}
}
fn resize(&mut self, width: i32, height: i32) {
unsafe {
self.gl.Viewport(0, 0, width, height);
}
}
}
fn handle_event(_app_state: &mut State, event: WindowEvent)
-> Result<AppControl, Box<dyn Error>> {
let mut exit = false;
match event {
WindowEvent::CloseRequested => {
exit = true;
}
WindowEvent::KeyboardInput { event: KeyEvent { logical_key: Key::Named(Escape), .. }, .. } => {
exit = true;
}
_ => (),
}
Ok(if exit { AppControl::Exit } else { AppControl::Continue })
}
fn main() -> Result<(), Box<dyn Error>> {
let app_state = State{};
glwindow::Window::<_,_,Renderer>::new()
.run(app_state, handle_event as glwindow::HandleFn<_>)
}