Welcome back!

Previously, we have create safe (ish) wrappers for VBO and VAO.

This time, we will continue along this path and remove the rest of unsafe code from our run function.

A structure to hold data for Triangle

The data that is necessary for Triangle is the shader program, VAO and VBO.

We can easily group them together into Triangle struct, and move this struct to another file.

(src/main.rs, added module reference)

mod triangle;

(src/triangle.rs, added new file)

pub struct Triangle {
    program: render_gl::Program,
    _vbo: buffer::ArrayBuffer, // _ to disable warning about not used vbo
    vao: buffer::VertexArray,
}

impl Triangle {
    pub fn new(res: &Resources, gl: &gl::Gl) -> Result<Triangle, failure::Error> {
        // initialization code that uses resources to
        // load data for triangle and wrap this data in
        // Triangle struct, or return an error
    }

    pub fn render(&self, gl: &gl::Gl) {
        // function that renders the triangle based on loaded data
    }
}

The new function uses the Resources to load the data for the triangle into OpenGL objects, and then store handles to those objects inside the Triangle struct.

Loading can fail, therefore the new returns a Result type. If we wanted to care a bit more about performance here, like, if this kind of object would be created many times, we would return a custom error type that would implement Fail. We have discussed error performance and types back in the Failure lesson.

The render renders, not much to add here.

The triangle also depends on the Vertex type, which we can copy-pase here together with the new and render implementation:

(src/triangle.rs, full file, modified)

use gl;
use failure;
use render_gl::{self, data, buffer};
use resources::Resources;

#[derive(VertexAttribPointers)]
#[derive(Copy, Clone, Debug)]
#[repr(C, packed)]
struct Vertex {
    #[location = "0"]
    pos: data::f32_f32_f32,
    #[location = "1"]
    clr: data::u2_u10_u10_u10_rev_float,
}

pub struct Triangle {
    program: render_gl::Program,
    _vbo: buffer::ArrayBuffer, // _ to disable warning about not used vbo
    vao: buffer::VertexArray,
}

impl Triangle {
    pub fn new(res: &Resources, gl: &gl::Gl) -> Result<Triangle, failure::Error> {

        // set up shader program

        let program = render_gl::Program::from_res(gl, res, "shaders/triangle")?;

        // set up vertex buffer object

        let vertices: Vec<Vertex> = vec![
            Vertex {
                pos: (0.5, -0.5, 0.0).into(),
                clr: (1.0, 0.0, 0.0, 1.0).into()
            }, // bottom right
            Vertex {
                pos: (-0.5, -0.5, 0.0).into(),
                clr: (0.0, 1.0, 0.0, 1.0).into()
            }, // bottom left
            Vertex {
                pos: (0.0,  0.5, 0.0).into(),
                clr: (0.0, 0.0, 1.0, 1.0).into()
            }  // top
        ];

        let vbo = buffer::ArrayBuffer::new(gl);
        vbo.bind();
        vbo.static_draw_data(&vertices);
        vbo.unbind();

        // set up vertex array object

        let vao = buffer::VertexArray::new(gl);

        vao.bind();
        vbo.bind();
        Vertex::vertex_attrib_pointers(gl);
        vbo.unbind();
        vao.unbind();

        Ok(Triangle {
            program,
            _vbo: vbo,
            vao,
        })
    }

    pub fn render(&self, gl: &gl::Gl) {
        self.program.set_used();
        self.vao.bind();

        unsafe {
            gl.DrawArrays(
                gl::TRIANGLES, // mode
                0, // starting index in the enabled arrays
                3 // number of indices to be rendered
            );
        }
    }
}

In the main function, we can now create the triangle with this simple call:

let triangle = triangle::Triangle::new(&res, &gl)?;

And render replace rendering with this:

triangle.render(&gl);

This should build and run. We can also clean up unused use statements if we get any warnings.

Side Note

That failure_to_string function at the end of main does not look nice, so we can hide it in src/debug.rs submodule.

Discussion: dependencies in render loop

We have built a simple triangle wrapper, and it looks like this will be the way we will create further simple “components” that we will hook into initialization and rendering logic.

These components will depend on each other and some will even modify the state of each other.

In Object-Oriented languages, it is a common pattern to inject such components into each other at the time of initialization. However, in Rust, if we inject one mutable reference of, say, a triangle somewhere, we can’t do that for another component - the code won’t compile:

(example)

let mut triangle = triangle::Triangle::new(&res, &gl)?;
let config = Config::new(&mut triangle)?;
let controller = Controller::new(&mut triangle)?; // triangle is already borrowed as mutable

Here we assume that hypothetical Config and Controller keeps the mutable pointer to triangle inside:

(example)

struct Config<'a> {
    triangle: &'a mut Triangle,
}

struct Controller<'a> {
    triangle: &'a mut Triangle,
}

One solution would be to put triangle behind a reference-counted pointer:

(example)

let triangle = Rc::new(triangle::Triangle::new(&res, &gl)?);
let config = Config::new(triangle.clone())?;
let controller = Controller::new(triangle.clone())?;

...

// somewhere else

struct Config {
    triangle: Rc<Triangle>,
}

struct Controller {
    triangle: Rc<Triangle>,
}

This way, the triangle is shared, we get rid of lifetimes, but the triangle is still immutable! To make it mutable, we would have to use what is called interior mutability: additionally wrap the triangle in a RefCell that makes borrowing checks dynamically at runtime instead of compile time:

(example)

let triangle = Rc::new(RefCell::new(triangle::Triangle::new(&res, &gl)?));
let config = Config::new(triangle.clone())?;
let controller = Controller::new(triangle.clone())?;

...

// somewhere else

struct Config {
    triangle: Rc<RefCell<Triangle>>,
}

struct Controller {
    triangle: Rc<RefCell<Triangle>>,
}

If we ignore the general uglyness for a moment, this would work. Let’s say somewhere bellow in run function we use both Config and Controller this way:

(example)

config.save_triangle_configuration();
..
controller.shake_triangle();

Inside of these functions, there would be some code that mutably borrows the triangle to save and shake it:

(example)

impl Config {
    fn save_triangle_configuration(&self) {
        self.triangle.borrow_mut().save();
    }
}

impl Controller {
    fn shake_triangle(&self) {
        self.triangle.borrow_mut().harlem();
    }
}

It would work, but it is a bit annoying to have even this overhead in a tight rendering loop. Not to mention it looks ugly!

The insight here would be that if the triangle is only used in these functions, we can limit how it is used by borrowing it only in the method call:

(example)

impl Config {
    fn save_triangle_configuration(&self, triangle: &mut Triangle) {
        triangle.save();
    }
}

impl Controller {
    fn shake_triangle(&self, triangle: &mut Triangle) {
        triangle.harlem();
    }
}

The Config and Controller no longer stores a reference to triangle. The setup and usage looks like this:

(example)

let mut triangle = triangle::Triangle::new(&res, &gl)?;
let config = Config::new();
let controller = Controller::new();

..

config.save_triangle_configuration(&mut triangle);
..
controller.shake_triangle(&mut triangle);

The upside of this approach is that we can clearly see the dependencies and what mutates what inside the render loop, which would be hard to track with the RefCell approach.

The downside is that we can’t pull OO shenanigans and create a sea of objects with unclear data flow patterns where any component can be modified to mutate whatever and whenever without a need to change an external component signature. Ok, maybe this is not a downside. The downside is that we can’t implement something like a “Drawable” interface, because a draw on one component might need to borrow a completely different thing than a draw on another component, making function signatures different.

Is this a big issue? It is hard to tell now, when we are just starting. However, we may always rememeber the RefCell, if only some use case justifies it.

Viewport

Great, we have a better idea of how we want to continue. Our “components” are going to be quite cheap, so we may not hesitate and create one for every OpenGL use case.

For example, the viewport: right now, our window is resizable, therefore we should update the viewport size on a window resize.

So our viewport struct should start with an initial width/height, have a method to update width/height, and a function which changes OpenGL state based on width/height.

Then, the viewport is really an utility that belongs to render_gl submodule.

(srs/render_gl/viewport.rs)

use gl;

pub struct Viewport {
    pub x: i32,
    pub y: i32,
    pub w: i32,
    pub h: i32,
}

impl Viewport {
    pub fn for_window(w: i32, h: i32) -> Viewport {
        Viewport {
            x: 0,
            y: 0,
            w,
            h
        }
    }

    pub fn update_size(&mut self, w: i32, h: i32) {
        self.w = w;
        self.h = h;
    }

    pub fn set_used(&self, gl: &gl::Gl) {
        unsafe {
            gl.Viewport(self.x, self.y, self.w, self.h);
        }
    }
}

Inside the render_gl, we may hide the fact that Viewport lives in its own separate file by not making the module public and re-exporting the name with pub use (like we did with the shader):

(src/render_gl/mod.rs, added)

mod viewport;
pub use self::viewport::Viewport;

I have not used new for Viewport constructor, and instead settled for for_window where we don’t need to specify x and y. It is common in Rust to see different names for constructors that customise initialization. And we can skip the new (which should probably have parameters to initialize every variable) until it is needed.

We will likely adopt a convention to use set_used name for functions that modify global open gl state. In this case, it actually switches viewport configuration.

Inside the main, we can initialize the viewport:

(src/main.rs, modified)

let mut viewport = render_gl::Viewport::for_window(900, 700);

Switch to it before the main loop starts:

(src/main.rs, modified)

viewport.set_used(&gl);

And then, something new: we may update the viewport when the window is resized:

(src/main.rs, modified event loop)

for event in event_pump.poll_iter() {
    match event {
        sdl2::event::Event::Quit {..} => break 'main,
        sdl2::event::Event::Window {
            win_event: sdl2::event::WindowEvent::Resized(w, h),
            ..
        } => {
            viewport.update_size(w, h);
            viewport.set_used(&gl);
        },
        _ => {},
    }
}

This should run, and the triangle will now adjust its size on a window resize.

Color Buffer

For the color buffer, we might store 4 floats inside a ColorBuffer struct:

(example)

pub struct ColorBuffer {
    pub r: f32,
    pub g: f32,
    pub b: f32,
    pub a: f32,
}

Or, we may bring in a Vector4 type from some linear algebra crate that is also suitable for games. There are quite a few of those to choose from. In my first OpenGL renderer, I was using cgmath together with collision, and had very few issues.

I have avoided other crates because of their excessive use of generics, which makes auto-generated Rust documentation hard to understand, and harder if we are new to Rust. Compared to that, cgmath is very straightforward, and I still recommend it.

However, one other crate has caught my eye: nalgebra, together with the crate that is build on it, ncollide. Looking further down the road, there is also nphysics, that builds on both, a physics engine written entirely in Rust!

While the reference documentation of nalgebra and ncollide is a bit hard to navigate, the online documentation hands-down wins the contest. Check out nalgebra.org, ncollide.org and nphysics.org!

An assurance that these lower-level functions are suitable for a physics engine contributed the most for decision to bite the bullet (no pun intended) and pick nalgebra for this and further lessons.

Let’s include nalgebra in our project:

(Cargo.toml, added dependency)

nalgebra = "0.16"

(src/main.rs, added crate reference)

extern crate nalgebra;

We can then use ‘nalgebra’s Vector4 type in our ColorBuffer struct:

(src/render_gl/mod.rs, added)

mod color_buffer;
pub use self::color_buffer::ColorBuffer;

(src/render_gl/color_buffer.rs)

use nalgebra as na;
use gl;

pub struct ColorBuffer {
    pub color: na::Vector4<f32>,
}

impl ColorBuffer {
    pub fn from_color(color: na::Vector3<f32>) -> ColorBuffer {
        ColorBuffer {
            color: color.fixed_resize::<na::U4, na::U1>(1.0),
        }
    }

    pub fn update_color(&mut self, color: na::Vector3<f32>) {
        self.color = color.fixed_resize::<na::U4, na::U1>(1.0);
    }

    pub fn set_used(&self, gl: &gl::Gl) {
        unsafe {
            gl.ClearColor(self.color.x, self.color.y, self.color.z, 1.0);
        }
    }

    pub fn clear(&self, gl: &gl::Gl) {
        unsafe {
            gl.Clear(gl::COLOR_BUFFER_BIT);
        }
    }
}

However, I have chosen to go with a simpler constructor that does not specify an alpha, and uses Vector3, while there is Vector4 in the struct. That means we have to convert Vector3 to Vector4.

We could simply construct it:

(example)

ColorBuffer {
    color: na::Vector4::new(color.x, color.y, color.z, 1.0),
}

However, there is a way to extend the vector and add one dimension:

(sample, used in code above)

ColorBuffer {
    color: color.fixed_resize::<na::U4, na::U1>(1.0),
}

What is going on? Well, almost all the types in nalgebra are aliases to the very generic Matrix type (we have created a few aliases ourselves in the previous lesson).

Vector is simply a Matrix with one column. To resize, we use matrix resize function fixed_resize with generic parameters na::U4 and na::U1, which mean 4 rows and 1 column, respectively. Because Rust does not yet support parametrization over integer values, these types are used as a workaround. This is the same approach that we have used in the previous lesson, for our Buffer<BufferTypeArray> and Buffer<BufferTypeElementArray> aliases.

The resized matrix will be bigger, so the function has an argument for a value to fill in these new cells, here we pass 1.0 for alpha.

Now we can initialize color buffer with a color we want:

(src/main.rs)

use nalgebra as na;
...
let color_buffer = render_gl::ColorBuffer::from_color(na::Vector3::new(0.3, 0.3, 0.5));

Then, call the set_used on this ColorBuffer, to change the clear color:

(src/main.rs)

color_buffer.set_used(&gl);

And use the clear to clear the color:

color_buffer.clear(&gl);

This should compile and run.

We have finally created some breathing space inside our main function. Next time, we will try to explore a bit more than triangle!

Full source code is available on github.