diff options
Diffstat (limited to 'engine/src/rendering/backend/opengl')
| -rw-r--r-- | engine/src/rendering/backend/opengl/glutin_compat.rs | 256 | ||||
| -rw-r--r-- | engine/src/rendering/backend/opengl/graphics_mesh.rs | 263 |
2 files changed, 519 insertions, 0 deletions
diff --git a/engine/src/rendering/backend/opengl/glutin_compat.rs b/engine/src/rendering/backend/opengl/glutin_compat.rs new file mode 100644 index 0000000..bba8c55 --- /dev/null +++ b/engine/src/rendering/backend/opengl/glutin_compat.rs @@ -0,0 +1,256 @@ +// Original file: +// https://github.com/rust-windowing/glutin/blob/ +// 0433af9018febe0696c485ed9d66c40dad41f2d4/glutin-winit/src/lib.rs +// +// Copyright © 2022 Kirill Chibisov +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the “Software”), to deal +// in the Software without restriction, including without limitation the rights to +// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +// of the Software, and to permit persons to whom the Software is furnished to do +// so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! This library provides helpers for cross-platform [`glutin`] bootstrapping +//! with [`winit`]. + +#![deny(rust_2018_idioms)] +#![deny(rustdoc::broken_intra_doc_links)] +#![deny(clippy::all)] +#![deny(missing_debug_implementations)] +#![deny(missing_docs)] +#![cfg_attr(clippy, deny(warnings))] + +use glutin::config::{Config, ConfigTemplateBuilder}; +use glutin::display::{Display, DisplayApiPreference}; +use glutin::error::Error as GlutinError; +use glutin::prelude::*; +use raw_window_handle::{DisplayHandle, RawWindowHandle, WindowHandle}; + +use crate::windowing::window::CreationAttributes as WindowCreationAttributes; + +#[cfg(all(not(windows), not(target_os = "macos"), not(target_os = "linux")))] +compile_error!("Unsupported platform"); + +#[cfg(target_family = "wasm")] +compile_error!("Wasm targets are not supported"); + +/// The helper to perform [`Display`] creation and OpenGL platform +/// bootstrapping with the help of [`winit`] with little to no platform specific +/// code. +/// +/// This is only required for the initial setup. If you want to create +/// additional windows just use the [`finalize_window`] function and the +/// configuration you've used either for the original window or picked with the +/// existing [`Display`]. +/// +/// [`winit`]: winit +/// [`Display`]: glutin::display::Display +#[derive(Default, Debug, Clone)] +pub struct DisplayBuilder +{ + preference: ApiPreference, + window_attributes: WindowCreationAttributes, +} + +impl DisplayBuilder +{ + /// The preference in picking the configuration. + #[allow(dead_code)] + pub fn with_preference(mut self, preference: ApiPreference) -> Self + { + self.preference = preference; + self + } + + /// The window attributes to use when building a window. + /// + /// By default no window is created. + pub fn with_window_attributes( + mut self, + window_creation_attrs: WindowCreationAttributes, + ) -> Self + { + self.window_attributes = window_creation_attrs; + self + } + + /// Initialize the OpenGL platform and create a compatible window to use + /// with it when the [`WindowAttributes`] was passed with + /// [`Self::with_window_attributes()`]. It's optional, since on some + /// platforms like `Android` it is not available early on, so you want to + /// find configuration and later use it with the [`finalize_window`]. + /// But if you don't care about such platform you can always pass + /// [`WindowAttributes`]. + /// + /// # Api-specific + /// + /// **WGL:** - [`WindowAttributes`] **must** be passed in + /// [`Self::with_window_attributes()`] if modern OpenGL(ES) is desired, + /// otherwise only builtin functions like `glClear` will be available. + pub fn build<ConfigPickerFn>( + self, + window_handle: Option<WindowHandle<'_>>, + display_handle: &DisplayHandle<'_>, + template_builder: ConfigTemplateBuilder, + config_picker_fn: ConfigPickerFn, + ) -> Result<(WindowCreationAttributes, Config), Error> + where + ConfigPickerFn: FnOnce(Box<dyn Iterator<Item = Config> + '_>) -> Option<Config>, + { + // XXX with WGL backend window should be created first. + let raw_window_handle = if cfg!(windows) { + let Some(window_handle) = window_handle else { + return Err(Error::WindowRequired); + }; + + Some(window_handle.as_raw()) + } else { + None + }; + + let gl_display = + create_display(display_handle, self.preference, raw_window_handle) + .map_err(Error::CreateDisplayFailed)?; + + // XXX the native window must be passed to config picker when WGL is used + // otherwise very limited OpenGL features will be supported. + let template_builder = if let Some(raw_window_handle) = + raw_window_handle.filter(|_| cfg!(windows)) + { + template_builder.compatible_with_native_window(raw_window_handle) + } else { + template_builder + }; + + let template = template_builder.build(); + + // SAFETY: The RawWindowHandle passed on the config template + // (when cfg(windows)) will always point to a valid object since it is + // derived from the window_handle argument which when Some is a WindowHandle and + // WindowHandles always point to a valid object + let gl_configs = unsafe { gl_display.find_configs(template) } + .map_err(Error::FindConfigsFailed)?; + + let picked_gl_config = + config_picker_fn(gl_configs).ok_or(Error::NoConfigPicked)?; + + let window_attrs = cfg_select! { + windows => self.window_attributes, + _ => { + finalize_window_creation_attrs(self.window_attributes, &picked_gl_config) + } + }; + + Ok((window_attrs, picked_gl_config)) + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("Failed to create display")] + CreateDisplayFailed(#[source] GlutinError), + + #[error("Failed to find configs")] + FindConfigsFailed(#[source] GlutinError), + + #[error("No config was picked by config picker function")] + NoConfigPicked, + + #[error("Window required for building display on current platform")] + WindowRequired, +} + +#[allow(unused_variables)] +fn create_display( + display_handle: &DisplayHandle<'_>, + api_preference: ApiPreference, + raw_window_handle: Option<RawWindowHandle>, +) -> Result<Display, GlutinError> +{ + let preference = cfg_select! { + windows => match api_preference { + ApiPreference::PreferEgl => { + DisplayApiPreference::EglThenWgl(raw_window_handle) + } + ApiPreference::FallbackEgl => { + DisplayApiPreference::WglThenEgl(raw_window_handle) + } + }, + target_os = "linux" => match api_preference { + ApiPreference::PreferEgl => DisplayApiPreference::EglThenGlx(Box::new( + crate::windowing::window::platform::x11::register_xlib_error_hook, + )), + ApiPreference::FallbackEgl => DisplayApiPreference::GlxThenEgl(Box::new( + crate::windowing::window::platform::x11::register_xlib_error_hook, + )), + }, + target_os = "macos" => DisplayApiPreference::Cgl, + }; + + let handle = display_handle.as_raw(); + + unsafe { Display::new(handle, preference) } +} + +/// Finalize [`Window`] creation by applying the options from the [`Config`], be +/// aware that it could remove incompatible options from the window builder like +/// `transparency`, when the provided config doesn't support it. +/// +/// [`Window`]: winit::window::Window +/// [`Config`]: glutin::config::Config +#[cfg(not(windows))] +fn finalize_window_creation_attrs( + mut attributes: WindowCreationAttributes, + gl_config: &Config, +) -> WindowCreationAttributes +{ + // Disable transparency if the end config doesn't support it. + if gl_config.supports_transparency() == Some(false) { + attributes = attributes.with_transparent(false); + } + + #[cfg(target_os = "linux")] + if let Some(x11_visual) = glutin::platform::x11::X11GlConfigExt::x11_visual(gl_config) + { + return attributes.with_x_visual_id(Some( + crate::windowing::window::XVisualID::try_from(x11_visual.visual_id()) + .expect("X visual ID is too large"), + )); + } + + attributes +} + +/// Simplified version of the [`DisplayApiPreference`] which is used to simplify +/// cross platform window creation. +/// +/// To learn about platform differences the [`DisplayApiPreference`] variants. +/// +/// [`DisplayApiPreference`]: glutin::display::DisplayApiPreference +#[allow(dead_code)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub enum ApiPreference +{ + /// Prefer `EGL` over system provider like `GLX` and `WGL`. + PreferEgl, + + /// Fallback to `EGL` when failed to create the system profile. + /// + /// This behavior is used by default. However consider using + /// [`Self::PreferEgl`] if you don't care about missing EGL features. + #[default] + FallbackEgl, +} diff --git a/engine/src/rendering/backend/opengl/graphics_mesh.rs b/engine/src/rendering/backend/opengl/graphics_mesh.rs new file mode 100644 index 0000000..899f50f --- /dev/null +++ b/engine/src/rendering/backend/opengl/graphics_mesh.rs @@ -0,0 +1,263 @@ +use std::hint::cold_path; + +use opengl_bindings::buffer::{Buffer as GlBuffer, Usage as GlBufferUsage}; +use opengl_bindings::vertex_array::{ + AttributeFormat as GlVertexArrayAttributeFormat, + BindVertexBufferError as GlVertexArrayBindVertexBufferError, + DataType as GlVertexArrayDataType, + VertexArray as GlVertexArray, + VertexBufferSpec as GlVertexArrayVertexBufferSpec, +}; +use opengl_bindings::MaybeCurrentContextWithFns as GlCurrentContextWithFns; + +use crate::mesh::vertex_buffer::VertexAttrProperties as MeshVertexAttrProperties; +use crate::mesh::{Mesh, VertexAttrType}; +use crate::rendering::shader::{ + VertexDescription as ShaderVertexDescription, + VertexInputSemName, +}; +use crate::rendering::MeshUsage; + +const VERTEX_BUF_BINDING_INDEX: u32 = 0; + +#[derive(Debug)] +pub struct GraphicsMesh +{ + /// Vertex and index buffer has to live as long as the vertex array + vertex_buffer: GlBuffer<u8>, + vertex_attr_props: Vec<MeshVertexAttrProperties>, + last_max_vertex_attr_index: u32, + pub index_buffer: Option<GlBuffer<u32>>, + pub element_cnt: u32, + pub vertex_arr: GlVertexArray, +} + +impl GraphicsMesh +{ + #[tracing::instrument(skip_all)] + pub fn new( + current_context: &GlCurrentContextWithFns, + mesh: &Mesh, + mesh_usage: MeshUsage, + ) -> Result<Self, Error> + { + let buffer_usage = mesh_usage_to_gl_buffer_usage(mesh_usage); + + let vertex_arr = GlVertexArray::new(current_context); + let vertex_buffer = GlBuffer::new(current_context); + + vertex_buffer + .store(current_context, mesh.vertex_buf().as_bytes(), buffer_usage) + .map_err(Error::StoreVerticesFailed)?; + + if let Err(err) = vertex_arr.bind_vertex_buffer( + current_context, + VERTEX_BUF_BINDING_INDEX, + &vertex_buffer, + GlVertexArrayVertexBufferSpec { + offset: 0, + vertex_size: mesh.vertex_buf().vertex_size(), + }, + ) { + match err { + GlVertexArrayBindVertexBufferError::OffsetValueTooLarge { + value: _, + max_value: _, + } => unreachable!(), + GlVertexArrayBindVertexBufferError::VertexSizeValueTooLarge { + value, + max_value, + } => { + panic!( + "Size of vertex ({value}) is too large. Must be less than {max_value}" + ); + } + } + } + + if let Some(indices) = mesh.indices() { + let index_buffer = GlBuffer::new(current_context); + + index_buffer + .store(current_context, indices, buffer_usage) + .map_err(Error::StoreIndicesFailed)?; + + vertex_arr.bind_element_buffer(current_context, &index_buffer); + + return Ok(Self { + vertex_buffer, + vertex_attr_props: mesh.vertex_buf().vertex_attr_props().to_vec(), + last_max_vertex_attr_index: 0, + index_buffer: Some(index_buffer), + element_cnt: indices + .len() + .try_into() + .expect("Mesh index count does not fit into a 32-bit unsigned int"), + vertex_arr, + }); + } + + Ok(Self { + vertex_buffer, + vertex_attr_props: mesh.vertex_buf().vertex_attr_props().to_vec(), + last_max_vertex_attr_index: 0, + index_buffer: None, + element_cnt: mesh + .vertex_buf() + .len() + .try_into() + .expect("Mesh vertex count does not fit into a 32-bit unsigned int"), + vertex_arr, + }) + } + + pub fn update( + &mut self, + current_context: &GlCurrentContextWithFns, + mesh: &Mesh, + mesh_usage: MeshUsage, + ) -> Result<(), Error> + { + let buffer_usage = mesh_usage_to_gl_buffer_usage(mesh_usage); + + self.vertex_buffer + .store(current_context, mesh.vertex_buf().as_bytes(), buffer_usage) + .map_err(Error::StoreVerticesFailed)?; + + if let Some(indices) = mesh.indices() { + let index_buffer = self + .index_buffer + .get_or_insert_with(|| GlBuffer::new(current_context)); + + index_buffer + .store(current_context, indices, buffer_usage) + .map_err(Error::StoreIndicesFailed)?; + + self.vertex_arr + .bind_element_buffer(current_context, index_buffer); + + self.element_cnt = indices + .len() + .try_into() + .expect("Mesh index count does not fit into a 32-bit unsigned int"); + + return Ok(()); + } + + if let Some(index_buffer) = self.index_buffer.take() { + index_buffer.delete(current_context); + self.vertex_arr.unbind_element_buffer(current_context); + } + + self.element_cnt = mesh + .vertex_buf() + .len() + .try_into() + .expect("Mesh vertex count does not fit into a 32-bit unsigned int"); + + Ok(()) + } + + pub fn update_vertex_attrs_for_shader( + &mut self, + curr_gl_ctx: &GlCurrentContextWithFns, + shader_vertex_desc: &ShaderVertexDescription, + ) -> Result<(), VertexAttrsUpdatingError> + { + for index in 0..self.last_max_vertex_attr_index { + self.vertex_arr.disable_attrib(curr_gl_ctx, index); + } + + let mut max_vertex_attr_index = 0u32; + + for vertex_input_desc in &*shader_vertex_desc.inputs { + let Some(vertex_attr_props) = + self.vertex_attr_props.iter().find(|vertex_attr_props| { + vertex_input_desc + .semantic_name + .matches_vertex_label(&vertex_attr_props.label) + }) + else { + cold_path(); + return Err(VertexAttrsUpdatingError::MissingVertexAttr( + vertex_input_desc.semantic_name.clone(), + )); + }; + + let attrib_index: u32 = vertex_input_desc.index.try_into().unwrap(); + + self.vertex_arr.enable_attrib(curr_gl_ctx, attrib_index); + + self.vertex_arr.set_attrib_format( + curr_gl_ctx, + attrib_index, + match &vertex_attr_props.ty { + VertexAttrType::Float32 => GlVertexArrayAttributeFormat { + data_type: GlVertexArrayDataType::Float, + count: 1, + normalized: false, + offset: vertex_attr_props.byte_offset.try_into().unwrap(), + }, + VertexAttrType::Float32Array { length } => { + GlVertexArrayAttributeFormat { + data_type: GlVertexArrayDataType::Float, + count: (*length).try_into().unwrap(), + normalized: false, + offset: vertex_attr_props.byte_offset.try_into().unwrap(), + } + } + }, + ); + + self.vertex_arr.set_attrib_vertex_buf_binding( + curr_gl_ctx, + attrib_index, + VERTEX_BUF_BINDING_INDEX, + ); + + max_vertex_attr_index = max_vertex_attr_index.max(attrib_index); + } + + self.last_max_vertex_attr_index = max_vertex_attr_index; + + Ok(()) + } + + pub fn destroy(&self, curr_gl_ctx: &GlCurrentContextWithFns) + { + self.vertex_arr.delete(curr_gl_ctx); + self.vertex_buffer.delete(curr_gl_ctx); + + if let Some(index_buffer) = &self.index_buffer { + index_buffer.delete(curr_gl_ctx); + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("Failed to store vertices in vertex buffer")] + StoreVerticesFailed(#[source] opengl_bindings::buffer::Error), + + #[error("Failed to store indices in index buffer")] + StoreIndicesFailed(#[source] opengl_bindings::buffer::Error), +} + +#[derive(Debug, thiserror::Error)] +pub enum VertexAttrsUpdatingError +{ + #[error( + "Mesh is missing equivalent vertex attribute for shader's vertex input: {0}" + )] + MissingVertexAttr(VertexInputSemName), +} + +fn mesh_usage_to_gl_buffer_usage(mesh_usage: MeshUsage) -> GlBufferUsage +{ + match mesh_usage { + MeshUsage::Stream => GlBufferUsage::Stream, + MeshUsage::Static => GlBufferUsage::Static, + MeshUsage::Dynamic => GlBufferUsage::Dynamic, + } +} |
