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,
NotCurrentandPossiblyCurrent, whichRawContext,WindowedContextandContextare now generic over. ContextBuilderis 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
ContextBuilderwill be generic overNotCurrent. This was chosen with the toss of a coin and is completely arbitrary. make_currentnow takesselfinstead of&self.- Added the functions
make_not_current,treat_as_currentandtreat_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?
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.
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
TisPossiblyCurrent:- For performance reasons you should consider using
Rcinstead. This context can’t be moved to another thread, so why deal with the additional overhead that comes withArc? - 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.
- For performance reasons you should consider using
- If
TisNotCurrent:- Please use
Arc<Mutex<Takeable<...>>>. Example:
- Please use
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.