Glutin v0.21.0 Migration Guide

Full disclosure: The examples provided don’t compile. They are not meant to be used “as-is”, but instead as aids in conveying the methods discussed.

So, you want to upgrade to glutin v0.21.0, huh? Too many changes? Not sure where to start? Want a rationale for all the changes? You’re in the right place today!

No more ContextTrait

Why?

To my knowledge, the primary purpose for ContextTrait (formerly GlContext) was to allow users to store contexts as a Box<ContextTrait>, however, in v0.19.0, a Self: Sized requirement was added, making such things impossible.

The only remaining use for the trait, as far as I could tell, was with generics, when doing things like where T: ContextTrait, however, I don’t think I’ve ever seen any code base which uses this. Then again, I only skimmed roughly 3 code bases, so I apologize if I missed yours.

Anyways, with this new release, I was faced with the decision of either removing ContextTrait, or splitting its functions between {Possibly,Not}CurrentContextTrait. I, in the end, went with the former choice.

How do I transition, Gentz?

For most users this will be very simple. Just remove all instances of use glutin::ContextTrait and you’re set!

If you used the trait as the requirement for some generic, just make your own ContextTrait and implement it yourself on Glutin’s types

Renamed OsMesaContextExt to HeadlessContextExt.

Why?

I wanted to add functions for making surefaceless EGL contexts. Having two separate traits, OsMesaContextExt and EglSurfacelessContextExt, felt pointless.

How do I transition, Gentz?

Just rename all instances of OsMesaContextExt to HeadlessContextExt.

Removed {,Windowed}Context::new_* functions in favor of ContextBuilder::build_*.

Why?

Previously, we only had Context::new_headless and WindowedContext::new_windowed (or in v0.19.0, two news). In v0.20.0, we added the ContextBuilder::build_* variants for those two functions, for ergonomic reasons. We also added Context::new_* functions for OSMesa contexts and raw contexts, but not their ContextBuilder::build_* variants as I did not wish to add two variants of each of the OsMesaContextExt and RawContextExt traits, with one variant implemented on ContextBuilder and the other on Context.

With the addition of EGL surefaceless support, I felt it would not be consistent to have some functions with both a ContextBuilder::build_* and {,Windowed}Context::new_* variant, and others with only a {,Windowed}Context::new_* variant.

Now, since the ContextBuilder::build_* functions are more ergonomic than the {,Windowed}Context::new_* functions, and I do not wish to maintain two variants of each function, the {,Windowed}Context::new_* functions have been removed, in favor of their ContextBuilder::build_* variants.

How do I transition, Gentz?

Instead of doing things like this:

let cb = glutin::ContextBuilder::new();
let context = glutin::Context::new_*(...)

Do this:

let context = glutin::ContextBuilder::new().build_*(...);

WindowedContexts now dereference to Context, not Window.

Why?

Nearly everybody needs to access the underlying context. Many don’t need to access the underlying window. This is, in my opinion, a more sensible default, and brings WindowedContext more inline with RawContext.

How do I transition, Gentz?

Instead of doing things like this:

context.some_window_function(...)

Do this:

context.window().some_window_function(...)

Additionally, consider getting rid of those, now unnecessary, context() calls.

Replaced CreationErrorPair enum variant with CreationErrors.

Why?

Sometimes, more than two CreationErrors are generated, and layering CreationErrorPairs isn’t a good solution.

How do I transition, Gentz?

Unless you were matching against CreationErrorPair, you won’t need to change anything! If you were, that will depend on what you were doing. This is a non-breaking change for most people.

New context currency system

What changed?

  • Added two new types, NotCurrent and PossiblyCurrent, which RawContext, WindowedContext and Context are now generic over.
  • ContextBuilder is also generic over those two types.
  • Which type it’s generic over depends on the currency state of the context you intend to share it with, not, as some have mistakenly thought, the currency in which the new context will be made. The new context will always be NotCurrent.
  • If you will not be sharing the context with a preexisting context, then the ContextBuilder will be generic over NotCurrent. This was chosen with the toss of a coin and is completely arbitrary.
  • make_current now takes self instead of &self.
  • Added the functions make_not_current, treat_as_current and treat_as_not_current.

Why?

I swear I typed up an in depth justification for these changes somewhere, alas I cannot find it. Basically, I want users to have an easier time juggling contexts.

How do I transition, Gentz?

First, please read the docs.

Done? Great, let’s get cracking!

I only have one context and one thread.

Splendid! Create the context as you normally do, call make_current on it, then store the {Windowed,Raw,}Context<PossiblyCurrent> as you normally would.

Example:

let context = glutin::ContextBuilder::new().build_*(...);
let context = context.make_current();

struct stuff {
    // ...
    context: WindowedContext<PossiblyCurrent>,
}

let stuff = Stuff { ..., context };

// Do stuff

I only have one context but I want to move it between threads.

Create the contexts as you normally do. Just store the context as a {Windowed,Raw,}Context<PossiblyCurrent>. When you want to send it to another thread, make it not current, send it, then make it current on that thread.

Example:

let context = glutin::ContextBuilder::new().build_*(...);
let context = context.make_current();

// Do stuff

let context = context.make_not_current();

let handler = thread::spawn(move || {
    let context = context.make_current();
    // Do more stuff.
});

Now, what if the context is stored in a struct? Well, let me suggest a couple methods

Use Option, or better yet, Takeable from takeable-option.

I prefer Takeable, because it automatically dereferences into the underlying context, and spares your code form being littered with unwraps.

Example:

let context = glutin::ContextBuilder::new().build_*(...);
let context = context.make_current();

struct Stuff {
    // ...
    context: Takeable<Context<PossiblyCurrent>>,
    // Or:
    // context: Option<Context<PossiblyCurrent>>,
}

let stuff = Stuff { 
    ..., 
    context: Takeable::new(context),
    // Or:
    // context: Some(context),
};

// Do stuff

let context = Takeable::take(&mut stuff.context).make_not_current();
// Or:
// let context = stuff.context.take().unwrap().make_not_current();

let handler = thread::spawn(move || {
    let context = context.make_current();
    Takeable::insert(&mut stuff.context, context);
    // Or:
    // stuff.context = Takeable::new(context);
    // Or:
    // stuff.context = Some(context);

    // Do more stuff.
});

// I won't mention `Option` anymore after this example. 
// Just remember, anything that I show being done with `Takeable` can also be done with `Option`.
// `Takeable`, after all, is a thin wrapper around `Option`.
Store a Takeable<{Windowed,Raw,}Context<NotCurrent>> instead.

Then just take it out, make it current, use it, make it not current, and place it back in.

Example:

let context = glutin::ContextBuilder::new().build_*(...);

struct Stuff {
    // ...
    context: Takeable<Context<NotCurrent>>,
}

let stuff = Stuff { 
    ..., 
    context: Takeable::new(context),
};

let context = Takeable::take(&mut stuff.context).make_current();

// Do stuff

Takeable::insert(&mut stuff.context, context.make_not_current());

let handler = thread::spawn(move || {
    let context = Takeable::take(&mut stuff.context).make_current();

    // Do more stuff

    Takeable::insert(&mut stuff.context, context.make_not_current());
});
Make the struct generic.

Example:

let context = glutin::ContextBuilder::new().build_*(...);
let context = context.make_current();

struct Stuff<T: ContextCurrentState> {
    // ...
    context: Context<T>,
}

impl<T: ContextCurrentState> for Stuff<T> {
    fn map<T2, F>(self, f: F) -> Stuff<T2>
    where
        T2: ContextCurrentState,
        F: FnOnce(Context<T>) -> Context<T2>,
    {
        Stuff {
            // ...
            context: f(self.context),
        }
    }
}

let stuff = Stuff { 
    ..., 
    context,
};

// Do stuff

let stuff = stuff.map(|context| context.make_not_current());

let handler = thread::spawn(move || {
    let stuff = stuff.map(|context| context.make_current());

    // Do more stuff
});

Ok, but how about more than one context?

You can use any of the methods above. Just be sure to mark the old context as not current.

Alternatively, you could also wrap everything in an enum, or series of enums, as demonstrated in the examples.

I’m struggling to put my context into an Arc and move it between threads.

As I’ve mentioned on GitHub, this request doesn’t really make sense.

First of all, if what you’ve got is a WindowedContext<T> (rather than a RawContext<T>, or a plain Context<T>) then you should first call split() on the WindowedContext<T> to split it into a (RawContext<T>, Window), and only send the RawContext<T>.

Why? Well, on some platforms (MacOS comes to mind) the Window is not Send + Sync.

Now, let’s consider the two possibilities for T:

  • If T is PossiblyCurrent:
    • For performance reasons you should consider using Rc instead. This context can’t be moved to another thread, so why deal with the additional overhead that comes with Arc?
    • If you will only use one context and never want to transfer it to another thread, just store it as is in the Rc.
    • Otherwise, use something like Rc<Cell<Takeable<...> and just apply one of the methods you saw above, if and as necessary.
  • If T is NotCurrent:
    • Please use Arc<Mutex<Takeable<...>>>. Example:
let arc_context: Arc<Mutex<Takeable<_>>> = ...;
let arc_context = arc_context.lock().unwrap();
let context = Takeable::take(&mut *arc_context).make_current();

// use context

Takeable::insert(&mut *arc_context, context.make_not_current());
std::mem::drop(arc_context); // drop the lock

Conclusions

I hope I’ve covered everything. If you feel I missed something, spot an error, or think something is unclear, drop a comment and I’ll try to correct it.

Leave a Reply

Your email address will not be published. Required fields are marked *