glium_sdl2/
lib.rs

1// Copyright (c) 2016 glium_sdl2 developers
2// Licensed under the Apache License, Version 2.0
3// <LICENSE-APACHE or
4// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
5// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
6// at your option. All files in the project carrying such
7// notice may not be copied, modified, or distributed except
8// according to those terms.
9
10//! An SDL2 backend for [Glium](https://github.com/tomaka/glium) - a high-level
11//! OpenGL wrapper for the Rust language.
12//!
13//! # Example
14//! ```no_run
15//! # #[macro_use] extern crate glium;
16//! # extern crate glium_sdl2;
17//! # extern crate sdl2;
18//! # fn main() {
19//! use glium_sdl2::DisplayBuild;
20//!
21//! let sdl_context = sdl2::init().unwrap();
22//! let video_subsystem = sdl_context.video().unwrap();
23//!
24//! let display = video_subsystem.window("My window", 800, 600)
25//!     .resizable()
26//!     .build_glium()
27//!     .unwrap();
28//!
29//! let mut running = true;
30//! let mut event_pump = sdl_context.event_pump().unwrap();
31//!
32//! while running {
33//!     let mut target = display.draw();
34//!     // do drawing here...
35//!     target.finish().unwrap();
36//!
37//!     // Event loop: includes all windows
38//!
39//!     for event in event_pump.poll_iter() {
40//!         use sdl2::event::Event;
41//!
42//!         match event {
43//!             Event::Quit { .. } => {
44//!                 running = false;
45//!             },
46//!             _ => ()
47//!         }
48//!     }
49//! }
50//! # }
51//! ```
52
53extern crate glium;
54extern crate sdl2;
55
56use std::mem;
57use std::cell::UnsafeCell;
58use std::ops::Deref;
59use std::rc::Rc;
60use std::os::raw::c_void;
61
62use glium::SwapBuffersError;
63use glium::debug;
64use glium::backend::{Backend, Context, Facade};
65use glium::IncompatibleOpenGl;
66use sdl2::VideoSubsystem;
67use sdl2::video::{Window, WindowBuildError};
68
69pub type Display = SDL2Facade;
70
71
72#[derive(Debug)]
73pub enum GliumSdl2Error {
74    WindowBuildError(WindowBuildError),
75    ContextCreationError(String)
76}
77
78impl From<String> for GliumSdl2Error {
79    fn from(s : String) -> GliumSdl2Error {
80        return GliumSdl2Error::ContextCreationError(s);
81    }
82}
83
84impl From<WindowBuildError> for GliumSdl2Error {
85    fn from(err : WindowBuildError) -> GliumSdl2Error {
86        return GliumSdl2Error::WindowBuildError(err);
87    }
88}
89
90impl From<IncompatibleOpenGl> for GliumSdl2Error {
91    fn from(err : IncompatibleOpenGl) -> GliumSdl2Error {
92        GliumSdl2Error::ContextCreationError(err.to_string())
93    }
94}
95
96impl std::error::Error for GliumSdl2Error {
97    fn description(&self) -> &str {
98        return match *self {
99            GliumSdl2Error::WindowBuildError(ref err) => err.description(),
100            GliumSdl2Error::ContextCreationError(ref s) => s
101        }
102    }
103
104    fn cause(&self) -> Option<&std::error::Error> {
105        match *self {
106            GliumSdl2Error::WindowBuildError(ref err) => err.cause(),
107            GliumSdl2Error::ContextCreationError(_) => None
108        }
109    }
110}
111
112impl std::fmt::Display for GliumSdl2Error {
113    fn fmt(&self, formatter: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> {
114        match *self {
115            GliumSdl2Error::WindowBuildError(ref err) => err.fmt(formatter),
116            GliumSdl2Error::ContextCreationError(ref err) => err.fmt(formatter),
117        }
118    }
119}
120
121
122/// Facade implementation for an SDL2 window.
123#[derive(Clone)]
124pub struct SDL2Facade {
125    // contains everything related to the current context and its state
126    context: Rc<Context>,
127
128    backend: Rc<SDL2WindowBackend>,
129}
130
131impl Facade for SDL2Facade {
132    fn get_context(&self) -> &Rc<Context> { &self.context }
133}
134
135impl Deref for SDL2Facade {
136    type Target = Context;
137
138    fn deref(&self) -> &Context { &self.context }
139}
140
141impl SDL2Facade {
142    pub fn window(&self) -> &Window {
143        self.backend.window()
144    }
145
146    pub fn window_mut(&mut self) -> &mut Window {
147        self.backend.window_mut()
148    }
149
150    /// Start drawing on the backbuffer.
151    ///
152    /// This function returns a `Frame`, which can be used to draw on it.
153    /// When the `Frame` is destroyed, the buffers are swapped.
154    ///
155    /// Note that destroying a `Frame` is immediate, even if vsync is enabled.
156    pub fn draw(&self) -> glium::Frame {
157        glium::Frame::new(self.context.clone(), self.backend.get_framebuffer_dimensions())
158    }
159}
160
161/// An object that can build a facade object.
162///
163/// This trait is different from `glium::DisplayBuild` because Rust doesn't allow trait
164/// implementations on types from external crates, unless the trait is in the same crate as the impl.
165/// To clarify, both `glium::DisplayBuild` and `sdl2::video::WindowBuilder` are in different crates
166/// than `glium_sdl2`.
167pub trait DisplayBuild {
168    /// The object that this `DisplayBuild` builds.
169    type Facade: glium::backend::Facade;
170
171    /// The type of error that initialization can return.
172    type Err;
173
174    /// Build a context and a facade to draw on it.
175    ///
176    /// Performs a compatibility check to make sure that all core elements of glium
177    /// are supported by the implementation.
178    fn build_glium(self) -> Result<Self::Facade, Self::Err> where Self: Sized {
179        self.build_glium_debug(Default::default())
180    }
181
182    /// Build a context and a facade to draw on it.
183    ///
184    /// Performs a compatibility check to make sure that all core elements of glium
185    /// are supported by the implementation.
186    fn build_glium_debug(self, debug::DebugCallbackBehavior) -> Result<Self::Facade, Self::Err>;
187
188    /// Build a context and a facade to draw on it
189    ///
190    /// This function does the same as `build_glium`, except that the resulting context
191    /// will assume that the current OpenGL context will never change.
192    unsafe fn build_glium_unchecked(self) -> Result<Self::Facade, Self::Err> where Self: Sized {
193        self.build_glium_unchecked_debug(Default::default())
194    }
195
196    /// Build a context and a facade to draw on it
197    ///
198    /// This function does the same as `build_glium`, except that the resulting context
199    /// will assume that the current OpenGL context will never change.
200    unsafe fn build_glium_unchecked_debug(self, debug::DebugCallbackBehavior)
201        -> Result<Self::Facade, Self::Err>;
202
203    // TODO
204    // Changes the settings of an existing facade.
205    // fn rebuild_glium(self, &Self::Facade) -> Result<(), Self::Err>;
206}
207
208impl<'a> DisplayBuild for &'a mut sdl2::video::WindowBuilder {
209    type Facade = SDL2Facade;
210    type Err = GliumSdl2Error;
211
212    fn build_glium_debug(self, debug: debug::DebugCallbackBehavior) -> Result<SDL2Facade, GliumSdl2Error> {
213        let backend = Rc::new(try!(SDL2WindowBackend::new(self)));
214        let context = try!(unsafe { Context::new(backend.clone(), true, debug) });
215
216        let display = SDL2Facade {
217            context: context,
218            backend: backend
219        };
220
221        Ok(display)
222    }
223
224    unsafe fn build_glium_unchecked_debug(self, debug: debug::DebugCallbackBehavior) -> Result<SDL2Facade, GliumSdl2Error> {
225        let backend = Rc::new(try!(SDL2WindowBackend::new(self)));
226        let context = try!(Context::new(backend.clone(), false, debug));
227
228        let display = SDL2Facade {
229            context: context,
230            backend: backend
231        };
232
233        Ok(display)
234    }
235}
236
237pub struct SDL2WindowBackend {
238    window: UnsafeCell<sdl2::video::Window>,
239    context: sdl2::video::GLContext
240}
241
242impl SDL2WindowBackend {
243    fn subsystem(&self) -> &VideoSubsystem {
244        let ptr = self.window.get();
245        let window: &Window = unsafe { mem::transmute(ptr) };
246        window.subsystem()
247    }
248
249    fn window(&self) -> &Window {
250        let ptr = self.window.get();
251        let window: &Window = unsafe { mem::transmute(ptr) };
252        window
253    }
254
255    fn window_mut(&self) -> &mut Window {
256        let ptr = self.window.get();
257        let window: &mut Window = unsafe { mem::transmute(ptr) };
258        window
259    }
260
261    pub fn new(window_builder: &mut sdl2::video::WindowBuilder) -> Result<SDL2WindowBackend, GliumSdl2Error> {
262        let window = try!(window_builder.opengl().build());
263        let context = try!(window.gl_create_context());
264
265        Ok(SDL2WindowBackend {
266            window: UnsafeCell::new(window),
267            context: context
268        })
269    }
270}
271
272unsafe impl Backend for SDL2WindowBackend {
273    fn swap_buffers(&self) -> Result<(), SwapBuffersError> {
274        self.window().gl_swap_window();
275
276        // AFAIK, SDL or `SDL_GL_SwapWindow` doesn't have any way to detect context loss.
277        // TODO: Find out if context loss is an issue in SDL2 (especially for the Android port).
278
279        Ok(())
280    }
281
282    unsafe fn get_proc_address(&self, symbol: &str) -> *const c_void {
283        // Assumes the appropriate context for the window has been set before this call.
284
285        self.subsystem().gl_get_proc_address(symbol) as *const c_void
286    }
287
288    fn get_framebuffer_dimensions(&self) -> (u32, u32) {
289        let (width, height) = self.window().drawable_size();
290        (width as u32, height as u32)
291    }
292
293    fn is_current(&self) -> bool {
294        self.context.is_current()
295    }
296
297    unsafe fn make_current(&self) {
298        self.window().gl_make_current(&self.context).unwrap()
299    }
300}