The piston2d-graphics
crate provides a trait, Graphics
, which allows easy access to some graphics primitives. This would be a nice, easy-to-use API, if it weren't such a pain to end up with something that implements Graphics
; for a time, there wasn't even a consistent set of crates that let you get one (that rendered anything to the screen, anyway).
I worked out a way of getting to a Graphics
-implementing struct
by the successive creation of six objects which take references to each other (one a Builder
, so really five). This was still too convoluted for my needs, though, so I cracked out the Rustonomicon and simplified the interface down to:
use graphics::Graphics;
mod display;
fn main() {
let mut d = display::Display::new();
loop {
let mut f = d.frame();
let mut g = f.graphics();
// g implements Graphics; do something with it
g.clear_color([1.0, 1.0, 1.0, 1.0]);
f.commit().unwrap();
}
}
I'm looking for suggestions for improving the display
module and its API; I'm looking to making it a separate crate to use in other projects, but I want it to be good first.
display.rs
:
use glutin::event_loop::EventLoop;
use glium_graphics::{Glium2d, GliumGraphics, OpenGL};
use glium::{backend::glutin::Display as GDisplay, Frame as GFrame};
pub use graphics::Graphics;
use core::pin::Pin;
use core::mem::drop;
use core::mem::ManuallyDrop;
use core::ptr::NonNull;
const OPENGL_VERSION: OpenGL = OpenGL::V4_5;
// I can't just use owning_ref because these don't deref.
pub struct Display {
el: *mut EventLoop<()>,
gdisplay: *mut GDisplay,
g2d: ManuallyDrop<Glium2d>
}
pub struct Frame<'d, 's> {
graphics: ManuallyDrop<GliumGraphics<'d, 's, GFrame>>,
frame: Option<NonNull<GFrame>>
}
impl Display {
pub fn new() -> Display {
let wb = glutin::window::WindowBuilder::new()
.with_title("Sparkle")
.with_inner_size(glutin::dpi::PhysicalSize::new(800.0, 600.0));
let cb = glutin::ContextBuilder::new();
let el = Box::into_raw(Box::new(EventLoop::new()));
let gdisplay = Box::into_raw(Box::new(GDisplay::new(
wb, cb, unsafe { &*el }
).unwrap()));
let g2d = glium_graphics::Glium2d::new(
OPENGL_VERSION, unsafe { &*gdisplay }
);
Display {
el: el,
gdisplay: gdisplay,
g2d: ManuallyDrop::new(g2d),
}
}
#[must_use]
pub fn frame<'a>(&'a mut self) -> Frame<'a, 'a> {
let gdisplay = unsafe { &*self.gdisplay };
let mut frame = Box::into_raw(Box::new(gdisplay.draw()));
let graphics = GliumGraphics::new(
&mut self.g2d, unsafe { &mut *frame }
);
Frame {
graphics: ManuallyDrop::new(graphics),
frame: NonNull::new(frame)
}
}
pub fn event_loop<'a>(&'a self) -> &'a EventLoop<()> {
// An immutable reference is okay, but there's
// another immutable reference to el (in self.gdisplay)
// so a mutable reference is right out.
unsafe { &*self.el }
}
}
impl Drop for Display {
fn drop(&mut self) {
unsafe {
// The order matters! Each references the next, so they
// must be dropped in that order to prevent use-after-free.
ManuallyDrop::drop(&mut self.g2d);
drop(Box::from_raw(self.gdisplay));
drop(Box::from_raw(self.el));
}
}
}
impl<'d, 's> Frame<'d, 's> {
pub fn commit(mut self) -> Result<(), glium::SwapBuffersError> {
// SEE ALSO: The Drop implementation.
unsafe {
// self.graphics is using self.frame; must drop it first.
ManuallyDrop::drop(&mut self.graphics);
// Now we can take it out of the box.
// Note that self.frame is always Some(_), because this
// function is the only thing that sets it to None.
Box::from_raw(self.frame.unwrap().as_ptr())
}.finish()?;
self.frame = None; // setting H2G2 flag for the Drop trait impl
Ok(())
}
pub fn graphics(&mut self) -> &mut GliumGraphics<'d, 's, GFrame> {
&mut self.graphics
}
}
impl Drop for Frame<'_, '_> {
fn drop(&mut self) {
// SEE ALSO: The .commit() implementation.
match self.frame {
Some(_) => panic!(concat!("sparkle::display::Frame must not ",
"be dropped! .commit() instead")),
None => () // Already cleaned up resources;
// let Rust free the struct.
};
}
}