Creating A Window

This part of the tutorial is very library specific, so I won't focus on it too much. Basically, we have to open a window, and we also need a GL context to go with that window. The details for this depend on what OS and windowing system you're using. In my case, beryllium is based on SDL2, so we have a nice cross-platform abstraction going for us.

Pre-Window Setup

On most platforms, you have to specify that you'll be using GL before you create the window, so that the window itself can be created with the correct settings to support GL once it's made.

First we turn on SDL itself:

use beryllium::*;

fn main() {
  let sdl = Sdl::init(init::InitFlags::EVERYTHING);

Then we set some attributes for the OpenGL Context that we want to use:

#![allow(unused)]
fn main() {
  sdl.set_gl_context_major_version(3).unwrap();
  sdl.set_gl_context_major_version(3).unwrap();
  sdl.set_gl_profile(video::GlProfile::Core).unwrap();
  #[cfg(target_os = "macos")]
  {
    sdl
      .set_gl_context_flags(video::GlContextFlags::FORWARD_COMPATIBLE)
      .unwrap();
  }
}
  • The Core profile is a subset of the full features that the spec allows. An implementation must provide the Core profile, but it can also provide a Compatibility profile, which is the current spec version's features plus all the old stuff from previous versions.
  • The Forward Compatible flag means that all functions that a particular version considers to be "deprecated but available" are instead immediately unavailable. It's needed for Mac if you want to have a Core profile. On other systems you can have it or not and it doesn't make a big difference. The Khronos wiki suggest to only set it if you're on Mac, so that's what I did.

Make The Window

Finally, once GL is all set, we can make our window.

In some libs you might make the window and then make the GL Context as a separate step (technically SDL2 lets you do this), but with beryllium it just sticks the window and the GL Context together as a single thing (glutin also works this way, I don't know about glfw).

#![allow(unused)]
fn main() {
  let win_args = video::CreateWinArgs {
        title: WINDOW_TITLE,
        width: 800,
        height: 600,
        allow_high_dpi: true,
        borderless: false,
        resizable: false,
  };

  let _win = sdl
    .create_gl_window(win_args)
    .expect("couldn't make a window and context");
}

Processing Events

Once we have a window, we can poll for events. In fact if we don't always poll for events promptly the OS will usually think that our application has stalled and tell the user they should kill the program. So we want to always be polling for those events.

Right now we just wait for a quit event (user clicked the X on the window, pressed Alt+F4, etc) and then quit when that happens.

#![allow(unused)]
fn main() {
  'main_loop: loop {
    // handle events this frame
    while let Some(event) = sdl.poll_events() {
        match event {
            (events::Event::Quit, _) => break 'main_loop,
            _ => (),
        }
    }
    // now the events are clear

    // here's where we could change the world state and draw.
  }
}
}

Done!

That's all there is to it for this lesson. Just a milk run.

Extras

I'm developing mostly on Windows, and Windows is where most of your market share of users will end up being, so here's some bonus Windows tips:

Windows Subsystem

I'm going to put the following attribute at the top of the file:

#![allow(unused)]
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
}

This will make is to that a "release" build (with the --release flag) will use the "windows" subsystem on Windows, instead of the "console" subsystem. This makes the process not have a console by default, which prevents a little terminal window from running in the background when the program runs on its own. However, we only want that in release mode because we want the ability to print debug message in debug mode.

Static Linking SDL2

Finally, instead of dynamic linking with SDL2 we could static link with it.

All we have to static link SDL2 instead is change our Cargo.toml file so that instead of saying

beryllium = "0.2.0-alpha.2"

it says

beryllium = { version = "0.2.0-alpha.1", default-features = false, features = ["link_static"] }

However, when we do this, we have to build the SDL2 static lib, which takes longer (about +30 seconds). So I leave it in dynamic link during development because it makes CI go faster.