3
\$\begingroup\$

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.
        };
    }
}
\$\endgroup\$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.