use crate::gl_version::OpenGlVersion; use crate::renderer::Renderer; #[cfg(feature = "text")] use crate::text::TextRenderer; /// The overarching state of the crate. Intended to live outside of /// the main game loop. /// /// This is the struct you can get a GraphicsContext from, and which /// should live as long as you're drawing anything. Illustrated: /// ```no_run /// use fae::Context; /// let mut fae_context: Context = Context::new(); /// # let (width, height, dpi_factor) = (0.0, 0.0, 0.0); /// # let spritesheet = fae::SpritesheetBuilder::default().build(&mut fae_context); /// /// loop { /// // Here's your gameloop, and now you want to draw something. /// /// // First, create the GraphicsContext with start_frame. /// let mut ctx: fae::GraphicsContext = fae_context.start_frame(width, height, dpi_factor); /// /// // Then do your rendering stuff. /// spritesheet.draw(&mut ctx) /// /* ... */ /// .finish(); /// /// // Finish frame and consume the GraphicsContext. /// ctx.finish_frame(); /// /// // swap buffers, fae_context.synchronize(), etc. /// } /// ``` /// /// This construct makes the state of fae more clear, as you can only /// have access to either the Context or the GraphicsContext, as /// well as providing a good synchronization point (start_frame) where /// the window's state is passed to fae, ensuring that all rendering /// operations are done based on up-to-date information. pub struct Context { pub(crate) renderer: Renderer, #[cfg(feature = "text")] pub(crate) text_renderers: Vec, } impl Context { /// Creates a new GraphicsContext. See the Safety section. /// /// # Safety /// /// Basically everything in fae assumes that it can call OpenGL, /// so please ensure you have called something along the lines of: /// /// ```ignore /// unsafe { fae::gl::load_with(|symbol| context.get_proc_address(symbol) as *const _); } /// ``` /// /// Before creating a Context. /// /// The width, height and dpi_factor are only initial values; they /// are updated in the call to /// [`Context::start_frame()`](struct.Context.html#method.start_frame). pub fn new() -> Context { Context { renderer: Renderer::new(), #[cfg(feature = "text")] text_renderers: Vec::new(), } } /// Returns true when running in legacy mode (OpenGL 3.3+ /// optimizations off). pub fn is_legacy(&self) -> bool { self.renderer.legacy } /// Returns the OpenGL version if it could be parsed. pub fn get_opengl_version(&self) -> &OpenGlVersion { &self.renderer.version } /// Tries to ensure that all the commands queued in the GPU have been processed. /// /// Call this after swap_buffers to ensure that everything after /// this happens only after the frame has been sent to the screen, /// but don't trust this to actually work. Doing vsync properly /// with OpenGL is a mess, as far as I know. pub fn synchronize(&mut self) { self.renderer.synchronize(); } /// Creates a GraphicsContext for this frame. /// /// The parameters `width` and `height` are the dimensions of the /// window, and dpi_factor is a multiplier, such that: `width * /// dpi_factor` is the window's width in physical pixels, and /// `height * dpi_factor` is the height in physical pixels. pub fn start_frame(&mut self, width: f32, height: f32, dpi_factor: f32) -> GraphicsContext { self.renderer.prepare_new_frame(dpi_factor); #[cfg(feature = "text")] for font in &mut self.text_renderers { font.prepare_new_frame(&mut self.renderer, dpi_factor, width, height); } GraphicsContext { renderer: &mut self.renderer, #[cfg(feature = "text")] text_renderers: &mut self.text_renderers, width, height, dpi_factor, } } /// Renders the frame with the given `width`, `height` and /// `clear_color`. /// /// The `clear_color` is defined between 0.0 and 1.0, and the /// components are (red, green, blue, alpha). None if you don't /// want to clear the screen. /// /// See /// [`Context::start_frame`](struct.Context.html#method.start_frame) /// for more information on what `width` and `height` are, /// specifically. /// /// This should generally be called after /// [`GraphicsContext::finish_frame`](struct.GraphicsContext.html#method.finish_frame), /// but can also be used to redraw the previous frame. pub fn render(&mut self, width: f32, height: f32, clear_color: Option<(f32, f32, f32, f32)>) { self.renderer.render(width, height, clear_color); } } /// Draw stuff on the screen with this. /// /// Create this struct with /// [`Context::start_frame()`](struct.Context.html#method.start_frame). /// /// Then, pass it to: /// - [`Spritesheet::draw`](struct.Spritesheet.html#method.draw) to draw sprites, /// - [`Font::draw`](struct.Font.html#method.draw) to draw text. /// /// And after doing all the drawing, call /// [`GraphicsContext::finish_frame()`](struct.GraphicsContext.html#method.finish_frame) /// to flush all the rendering operations. pub struct GraphicsContext<'a> { pub(crate) renderer: &'a mut Renderer, #[cfg(feature = "text")] pub(crate) text_renderers: &'a mut Vec, /// The width of the window in logical coordinates. Multiply with /// `dpi_factor` to get the width in physical pixels. pub width: f32, /// The height of the window in logical coordinates. Multiply with /// `dpi_factor` to get the height in physical pixels. pub height: f32, /// The dpi multiplier of the window. pub dpi_factor: f32, } impl GraphicsContext<'_> { /// Consume this GraphicsContext to render everything that has /// been queued with `draw` calls so far. Call /// [`Context::render()`](struct.Context.html#method.render) /// and swap buffers after this. pub fn finish_frame(self) { #[cfg(feature = "text")] for font in self.text_renderers { font.compose_draw_call(self.renderer); } self.renderer.finish_frame(); } }