Getting Started

To discuss

  • winit + glutin vs sdl2
    • creating a window
    • create a context, loading gl functions
    • debug callbacks + debug context
  • framework for rest of tutorial
    • to cope with differences between winit/sdl2 if I choose to provide both

Before you can start rendering anything, first you need a window and an OpenGL context. There are several ways to do this each with different pros and cons, but for the sake of simplicity I will just be sticking to sdl2.

SDL2 is a C library that has been used for windowing and co since antiquity, so it has the advantage that it is very easy to get started with. However, being a C library means that including it in your project will require either you have the SDL2 library available on your system, or you have cmake and a C compiler installed. Using C libraries in rust projects always adds some friction, so I will be structuring my examples such that SDL2 can be swapped out for something else later if need be.

The alternative, rust-only option is to use the winit and glutin crates - which would make building much easier, but in my opinion are significantly more difficult to get started with, so I will skip over them for now.

Getting SDL2 set up

To start you need to add the sdl2 crate as a dependency. So in your Cargo.toml add the following:

[dependencies.sdl2]
version = "0.35"
features = ["bundled", "static-link"]

The version is not super important - 0.35 is just the latest minor version available to me at time of writing. The important part here are the features: bundled and static-link. Long story short, these features together instruct the sdl2 crate to fetch the SDL2 source, build it with the local compiler, and statically link it into your binary. As mentioned previously, this requires that you have cmake and a C compiler available on your system, but will ensure that we can build our project on any platform that SDL2 supports without much hassle.

Create a window

Next its time to actually use sdl2. Add the following to your main.rs:

#[allow(dead_code)]
pub fn main() {
	// These two calls are more or less equivalent to `SDL_Init(SDL_INIT_VIDEO)`
	let sdl_ctx = sdl2::init().expect("SDL init failed");
	let sdl_video = sdl_ctx.video().expect("video subsystem init failed");

	let _window = sdl_video.window("Tutorial", 1366, 768)
		.position_centered()
		.build()
		.expect("Failed to create window");

	std::thread::sleep(std::time::Duration::from_secs(3));
}

If everything is set up correctly, then when you build and run this you should see a window appear for three seconds.

Create an OpenGL context

Next we have to create an OpenGL context. This happens in a few parts:

  • First, before we make a window we have to describe to sdl2 what kind of context we want.
    • e.g., what version? what profile? whats our backbuffer format? etc etc.
  • Then, after we've created our window, we can go about actually asking for the context.
    • Note that before we use it we have to make it 'current'.
  • Finally, once our new context has been created and made current, we can load our OpenGL functions from it.

Describing the context

#[allow(dead_code)]
pub fn main() {
	let sdl_ctx = sdl2::init().expect("SDL init failed");
	let sdl_video = sdl_ctx.video().expect("video subsystem init failed");

	// Describe the version of OpenGL we want a context for _before_ creating the window.
	// We are using modern OpenGL, so 4.5+ and core profile is what we want.
	let gl_attr = sdl_video.gl_attr();
	gl_attr.set_context_profile(sdl2::video::GLProfile::Core);
	gl_attr.set_context_version(4, 5);

	let window = sdl_video.window("Tutorial", 1366, 768)
		.position_centered()
		.resizable()
		.opengl() // This is important!
		.build()
		.expect("Failed to create window");

	// Create our context
	let gl_ctx = window.gl_create_context().unwrap();
	window.gl_make_current(&gl_ctx).unwrap();

	// Load GL functions
	gl::load_with(|s| sdl_video.gl_get_proc_address(s) as *const _);

	// Render an empty frame to make sure _something_ is working.
	unsafe {
		gl::ClearColor(1.0, 0.0, 1.0, 1.0);
		gl::Clear(gl::COLOR_BUFFER_BIT);
	}

	window.gl_swap_window();

	std::thread::sleep(std::time::Duration::from_secs(3));
}

Creating the context

#[allow(dead_code)]
pub fn main() {
	let sdl_ctx = sdl2::init().expect("SDL init failed");
	let sdl_video = sdl_ctx.video().expect("video subsystem init failed");

	// Describe the version of OpenGL we want a context for _before_ creating the window.
	// We are using modern OpenGL, so 4.5+ and core profile is what we want.
	let gl_attr = sdl_video.gl_attr();
	gl_attr.set_context_profile(sdl2::video::GLProfile::Core);
	gl_attr.set_context_version(4, 5);

	let window = sdl_video.window("Tutorial", 1366, 768)
		.position_centered()
		.resizable()
		.opengl() // This is important!
		.build()
		.expect("Failed to create window");

	// Create our context
	let gl_ctx = window.gl_create_context().unwrap();
	window.gl_make_current(&gl_ctx).unwrap();

	// Load GL functions
	gl::load_with(|s| sdl_video.gl_get_proc_address(s) as *const _);

	// Render an empty frame to make sure _something_ is working.
	unsafe {
		gl::ClearColor(1.0, 0.0, 1.0, 1.0);
		gl::Clear(gl::COLOR_BUFFER_BIT);
	}

	window.gl_swap_window();

	std::thread::sleep(std::time::Duration::from_secs(3));
}

Loading functions

#[allow(dead_code)]
pub fn main() {
	let sdl_ctx = sdl2::init().expect("SDL init failed");
	let sdl_video = sdl_ctx.video().expect("video subsystem init failed");

	// Describe the version of OpenGL we want a context for _before_ creating the window.
	// We are using modern OpenGL, so 4.5+ and core profile is what we want.
	let gl_attr = sdl_video.gl_attr();
	gl_attr.set_context_profile(sdl2::video::GLProfile::Core);
	gl_attr.set_context_version(4, 5);

	let window = sdl_video.window("Tutorial", 1366, 768)
		.position_centered()
		.resizable()
		.opengl() // This is important!
		.build()
		.expect("Failed to create window");

	// Create our context
	let gl_ctx = window.gl_create_context().unwrap();
	window.gl_make_current(&gl_ctx).unwrap();

	// Load GL functions
	gl::load_with(|s| sdl_video.gl_get_proc_address(s) as *const _);

	// Render an empty frame to make sure _something_ is working.
	unsafe {
		gl::ClearColor(1.0, 0.0, 1.0, 1.0);
		gl::Clear(gl::COLOR_BUFFER_BIT);
	}

	window.gl_swap_window();

	std::thread::sleep(std::time::Duration::from_secs(3));
}

Making sure it works

#[allow(dead_code)]
pub fn main() {
	let sdl_ctx = sdl2::init().expect("SDL init failed");
	let sdl_video = sdl_ctx.video().expect("video subsystem init failed");

	// Describe the version of OpenGL we want a context for _before_ creating the window.
	// We are using modern OpenGL, so 4.5+ and core profile is what we want.
	let gl_attr = sdl_video.gl_attr();
	gl_attr.set_context_profile(sdl2::video::GLProfile::Core);
	gl_attr.set_context_version(4, 5);

	let window = sdl_video.window("Tutorial", 1366, 768)
		.position_centered()
		.resizable()
		.opengl() // This is important!
		.build()
		.expect("Failed to create window");

	// Create our context
	let gl_ctx = window.gl_create_context().unwrap();
	window.gl_make_current(&gl_ctx).unwrap();

	// Load GL functions
	gl::load_with(|s| sdl_video.gl_get_proc_address(s) as *const _);

	// Render an empty frame to make sure _something_ is working.
	unsafe {
		gl::ClearColor(1.0, 0.0, 1.0, 1.0);
		gl::Clear(gl::COLOR_BUFFER_BIT);
	}

	window.gl_swap_window();

	std::thread::sleep(std::time::Duration::from_secs(3));
}

Debug contexts

More resources