Rust and OpenGL from scratch - OpenGL Context
Welcome back!
Previously, we have created a window using SDL and started accepting window events in a loop.
In this lesson, we will load up OpenGL context and color the window blue.
OpenGL
If you search for OpenGL in crates.io, you may find
multiple choices of safe OpenGL wrappers, such as glium
, glitter
and maybe others.
The purpose of this series, however, is to learn about the lower level details. That does not mean that we are going to do all the work ourselves!
gl and gl_generator
The cornerstone of many higher level OpenGL projects is gl-rs project, which contains three crates:
khronos_api
- this crate contains XML files for various Khronos APIs, such as EGL, WGL, GLX, GL, and others.gl_generator
- is a tool to generate Rust code bindings fromkhronos_api
XML files. Think of it as your own customizable generator for OpenGL loader (something like GLEW).gl
- is a OpenGL API already generated usinggl_generator
. That was almost correct. In fact,gl
rust code is generated every timegl
is recompiled. More on that in later lesson.
GL
For now, we will use gl
crate.
The gl crate page on crates.io contains information how to set it up. We will follow that.
Add gl
dependency to your Cargo.toml
:
(Cargo.toml, incomplete)
[dependencies]
gl = "0.10.0"
Add reference to gl crate to your project by referencing gl
crate at the top of your
main.rs
file:
(main.rs, incomplete)
extern crate gl;
Browsing documentation of a dependency locally
We can ask cargo to generate and open documentation on any crate our project depends on:
cargo doc -p gl --no-deps --open
-p gl
is a short for--package gl
, specifies that we want documentation for it--no-deps
disables recursive documentation for all dependencies ofgl
. We want to disable that on windows, becausegl
transitively depends onwinapi
, which is extremely large, and generating docs forwinapi
would take too much time.--open
opens generated documentation in a browser window.
OpenGL context
We can tell SDL to create OpenGL context. We will use gl_create_context method on sdl2::video::Window struct.
However, only windows created with opengl
flag can have OpenGL context.
Modify the window creation code to have opengl
flag:
(main.rs, fn main, incomplete)
let window = video_subsystem
.window("Game", 900, 700)
.opengl() // add opengl flag
.resizable()
.build()
.unwrap();
And then create the gl_context
:
(main.rs, fn main, incomplete)
let gl_context = window.gl_create_context().unwrap();
More SDL documentation
It is useful to rememeber that Rust’s SDL is a very thin wrapper over C SDL library. Therefore, online documentation for original SDL maps directly to rust SDL crate.
Let’s open libsdl documentation for SDL_GL_CreateContext.
It has this example code to create a window:
// Window mode MUST include SDL_WINDOW_OPENGL for use with OpenGL.
SDL_Window *window = SDL_CreateWindow(
"SDL2/OpenGL Demo", 0, 0, 640, 480,
SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE);
For SDL_CreateWindow
call to work at all, SDL
’s video
subsystem must be initialized.
Rust sdl2
crate ensures that it is so by making window creation require video_subsystem
:
video_subsystem.window(...)
.
Instead of flags, on the Rust side we have WindowBuilder
, which is used to build up the flags
and other parameters for SDL_CreateWindow
call.
Our calls to make window .opengl().resizable()
will have the same result as
SDL_WINDOW_OPENGL|SDL_WINDOW_RESIZABLE
on the C side.
One thing many C examples are usually missing, is error handling. If SDL_CreateWindow
returns
null, the program exits with segmentation fault and no useful error message. The call
to .unwrap()
is way better, because it panics with an error message retrieved from SDL_GetError()
call. You can try that it is so by omitting .opengl()
flag in our Rust code; window.gl_create_context()
returns error that is sent to the output when unwrap
panics:
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value:
"The specified window isn\'t an OpenGL window"', src\libcore\result.rs:906:4
The SDL_GL_CreateContext
function requires a window:
// Create an OpenGL context associated with the window.
SDL_GLContext glcontext = SDL_GL_CreateContext(window);
In Rust, it becomes a method on the Window
struct:
let gl_context = window.gl_create_context().unwrap();
In conclusion, original SDL tutorials, online resources, and stack overflow answers remain useful in Rust.
Loading function pointers
What does gl
crate actually do? It forwards OpenGL function calls to
the driver.
Gl crate itself does not know how to do that, but SDL does.
When we initialize gl, we provide a function to load function pointer by string:
let gl = gl::load_with(|s| video_subsystem.gl_get_proc_address(s) as *const std::os::raw::c_void);
The |s| video_subsystem.gl_get_proc_address(s)
is a closure with a single s
parameter
that is inferred to be a string slice. The return type of gl_get_proc_address
, however, does
not match *const std::os::raw::c_void
pointer expected by gl
, but we can cast it with
as
operator.
Clear color
We can now use gl functions!
Before the 'main: loop {
, we can set gl “clear” color to blue:
(main.rs, incomplete)
unsafe {
gl::ClearColor(0.3, 0.3, 0.5, 1.0);
}
And replace // render window contents here
with actual call to clear color, as well call
to SDL2 to swap the window pixels with what we have just rendered:
(main.rs, incomplete)
'main: loop {
// .. event handling here
unsafe {
gl::Clear(gl::COLOR_BUFFER_BIT);
}
window.gl_swap_window();
}
Next up, a long and “modern” road to output a triangle to to the screen.