summaryrefslogtreecommitdiff
path: root/engine/src/renderer/opengl.rs
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2024-05-23 19:12:53 +0200
committerHampusM <hampus@hampusmat.com>2024-05-23 19:12:53 +0200
commitb414b3e2ce8ea515915dc8798790d37b57d81441 (patch)
tree236e690eb3ce6ca6711117066f94014d56bc648a /engine/src/renderer/opengl.rs
parent6d53e3b175fbaf379ef8400cdd1efeb725cf4ecd (diff)
refactor(engine): rename renderer module to opengl.rs
Diffstat (limited to 'engine/src/renderer/opengl.rs')
-rw-r--r--engine/src/renderer/opengl.rs531
1 files changed, 531 insertions, 0 deletions
diff --git a/engine/src/renderer/opengl.rs b/engine/src/renderer/opengl.rs
new file mode 100644
index 0000000..c97d8dd
--- /dev/null
+++ b/engine/src/renderer/opengl.rs
@@ -0,0 +1,531 @@
+//! OpenGL renderer.
+
+use std::collections::HashMap;
+use std::ffi::{c_void, CString};
+use std::process::abort;
+
+use cstr::cstr;
+use ecs::component::local::Local;
+use ecs::sole::Single;
+use ecs::system::{Into as _, System};
+use ecs::{Component, Query};
+
+use crate::camera::Camera;
+use crate::data_types::dimens::Dimens;
+use crate::event::{Present as PresentEvent, Start as StartEvent};
+use crate::lighting::{GlobalLight, PointLight};
+use crate::material::{Flags as MaterialFlags, Material};
+use crate::matrix::Matrix;
+use crate::mesh::Mesh;
+use crate::opengl::buffer::{Buffer, Usage as BufferUsage};
+#[cfg(feature = "debug")]
+use crate::opengl::debug::{MessageSeverity, MessageSource, MessageType};
+use crate::opengl::shader::{
+ Error as GlShaderError,
+ Program as GlShaderProgram,
+ Shader as GlShader,
+};
+use crate::opengl::texture::{
+ set_active_texture_unit,
+ Texture as GlTexture,
+ TextureUnit,
+};
+use crate::opengl::vertex_array::{
+ DataType as VertexArrayDataType,
+ PrimitiveKind,
+ VertexArray,
+};
+use crate::opengl::{clear_buffers, enable, BufferClearMask, Capability};
+use crate::projection::{new_perspective_matrix, Projection};
+use crate::shader::Program as ShaderProgram;
+use crate::texture::{Id as TextureId, Texture};
+use crate::transform::Transform;
+use crate::vector::Vec2;
+use crate::vertex::{AttributeComponentType, Vertex};
+use crate::window::Window;
+
+#[derive(Debug, Default)]
+pub struct Extension {}
+
+impl ecs::extension::Extension for Extension
+{
+ fn collect(self, mut collector: ecs::extension::Collector<'_>)
+ {
+ collector.add_system(StartEvent, initialize);
+
+ collector.add_system(
+ PresentEvent,
+ render.into_system().initialize((GlObjects::default(),)),
+ );
+ }
+}
+
+fn initialize(window: Single<Window>)
+{
+ window
+ .make_context_current()
+ .expect("Failed to make window context current");
+
+ gl::load_with(|symbol| match window.get_proc_address(symbol) {
+ Ok(addr) => addr as *const c_void,
+ Err(err) => {
+ println!(
+ "FATAL ERROR: Failed to get adress of OpenGL function {symbol}: {err}",
+ );
+
+ abort();
+ }
+ });
+
+ #[cfg(feature = "debug")]
+ initialize_debug();
+
+ let window_size = window.size().expect("Failed to get window size");
+
+ set_viewport(Vec2 { x: 0, y: 0 }, window_size);
+
+ window.set_framebuffer_size_callback(|new_window_size| {
+ set_viewport(Vec2::ZERO, new_window_size);
+ });
+
+ enable(Capability::DepthTest);
+}
+
+fn render(
+ query: Query<(
+ Mesh,
+ ShaderProgram,
+ Material,
+ Option<MaterialFlags>,
+ Transform,
+ )>,
+ point_light_query: Query<(PointLight, Transform)>,
+ camera_query: Query<(Camera,)>,
+ window: Single<Window>,
+ global_light: Single<GlobalLight>,
+ mut gl_objects: Local<GlObjects>,
+)
+{
+ let Some(camera) = camera_query.iter().find_map(|(camera,)| {
+ if !camera.current {
+ return None;
+ }
+
+ Some(camera)
+ }) else {
+ #[cfg(feature = "debug")]
+ tracing::warn!("No current camera. Nothing will be rendered");
+ return;
+ };
+
+ // TODO: Maybe find a way to not clone here? Cloning here is needed since a transform
+ // can be in both the object query and the light source query
+ let point_lights = point_light_query
+ .iter()
+ .map(|(point_light, transform)| ((*point_light).clone(), (*transform).clone()))
+ .collect::<Vec<_>>();
+
+ let GlObjects {
+ shader_programs: gl_shader_programs,
+ textures: gl_textures,
+ } = &mut *gl_objects;
+
+ clear_buffers(BufferClearMask::COLOR | BufferClearMask::DEPTH);
+
+ for (mesh, shader_program, material, material_flags, transform) in &query {
+ let material_flags = material_flags
+ .map(|material_flags| material_flags.clone())
+ .unwrap_or_default();
+
+ let shader_program = gl_shader_programs
+ .entry(shader_program.u64_hash())
+ .or_insert_with(|| create_gl_shader_program(&shader_program).unwrap());
+
+ apply_transformation_matrices(
+ &transform,
+ shader_program,
+ &camera,
+ window.size().expect("Failed to get window size"),
+ );
+
+ apply_light(
+ &material,
+ &material_flags,
+ &global_light,
+ shader_program,
+ point_lights.as_slice(),
+ &camera,
+ );
+
+ for texture in &material.textures {
+ let gl_texture = gl_textures
+ .entry(texture.id())
+ .or_insert_with(|| create_gl_texture(texture));
+
+ let texture_unit =
+ TextureUnit::from_texture_id(texture.id()).unwrap_or_else(|| {
+ panic!("Texture id {} is a invalid texture unit", texture.id());
+ });
+
+ set_active_texture_unit(texture_unit);
+
+ gl_texture.bind();
+ }
+
+ shader_program.activate();
+
+ draw_mesh(&mesh);
+ }
+}
+
+#[derive(Debug, Default, Component)]
+struct GlObjects
+{
+ shader_programs: HashMap<u64, GlShaderProgram>,
+ textures: HashMap<TextureId, GlTexture>,
+}
+
+fn set_viewport(position: Vec2<u32>, size: Dimens<u32>)
+{
+ crate::opengl::set_viewport(position, size);
+}
+
+#[cfg(feature = "debug")]
+fn initialize_debug()
+{
+ use crate::opengl::debug::{
+ enable_debug_output,
+ set_debug_message_callback,
+ set_debug_message_control,
+ MessageIdsAction,
+ };
+
+ enable_debug_output();
+
+ set_debug_message_callback(opengl_debug_message_cb);
+
+ set_debug_message_control(None, None, None, &[], MessageIdsAction::Disable);
+}
+
+fn draw_mesh(mesh: &Mesh)
+{
+ // TODO: Creating a new vertex buffer each draw is really dumb and slow this
+ // should be rethinked
+ let renderable = Renderable::new(mesh.vertices(), mesh.indices());
+
+ renderable.vertex_arr.bind();
+
+ if let Some(index_info) = &renderable.index_info {
+ VertexArray::draw_elements(PrimitiveKind::Triangles, 0, index_info.cnt);
+ } else {
+ VertexArray::draw_arrays(PrimitiveKind::Triangles, 0, 3);
+ }
+}
+
+fn create_gl_texture(texture: &Texture) -> GlTexture
+{
+ let mut gl_texture = GlTexture::new();
+
+ gl_texture.generate(
+ *texture.dimensions(),
+ texture.image().as_bytes(),
+ texture.pixel_data_format(),
+ );
+
+ gl_texture.apply_properties(texture.properties());
+
+ gl_texture
+}
+
+fn create_gl_shader_program(
+ shader_program: &ShaderProgram,
+) -> Result<GlShaderProgram, GlShaderError>
+{
+ let gl_shaders = shader_program
+ .shaders()
+ .iter()
+ .map(|shader| {
+ let gl_shader = GlShader::new(shader.kind());
+
+ gl_shader.set_source(shader.source())?;
+ gl_shader.compile()?;
+
+ Ok(gl_shader)
+ })
+ .collect::<Result<Vec<_>, _>>()?;
+
+ let gl_shader_program = GlShaderProgram::new();
+
+ for gl_shader in &gl_shaders {
+ gl_shader_program.attach(gl_shader);
+ }
+
+ gl_shader_program.link()?;
+
+ Ok(gl_shader_program)
+}
+
+#[derive(Debug)]
+struct Renderable
+{
+ vertex_arr: VertexArray,
+
+ /// Vertex and index buffer has to live as long as the vertex array
+ _vertex_buffer: Buffer<Vertex>,
+ index_info: Option<IndexInfo>,
+}
+
+impl Renderable
+{
+ fn new(vertices: &[Vertex], indices: Option<&[u32]>) -> Self
+ {
+ let mut vertex_arr = VertexArray::new();
+ let mut vertex_buffer = Buffer::new();
+
+ let mut index_info = None;
+
+ vertex_buffer.store(vertices, BufferUsage::Static);
+
+ vertex_arr.bind_vertex_buffer(0, &vertex_buffer, 0);
+
+ let mut offset = 0u32;
+
+ for attrib in Vertex::attrs() {
+ vertex_arr.enable_attrib(attrib.index);
+
+ vertex_arr.set_attrib_format(
+ attrib.index,
+ match attrib.component_type {
+ AttributeComponentType::Float => VertexArrayDataType::Float,
+ },
+ false,
+ offset,
+ );
+
+ vertex_arr.set_attrib_vertex_buf_binding(attrib.index, 0);
+
+ offset += attrib.component_size * attrib.component_cnt as u32;
+ }
+
+ if let Some(indices) = indices {
+ let mut index_buffer = Buffer::new();
+
+ index_buffer.store(indices, BufferUsage::Static);
+
+ vertex_arr.bind_element_buffer(&index_buffer);
+
+ index_info = Some(IndexInfo {
+ _buffer: index_buffer,
+ cnt: indices.len().try_into().unwrap(),
+ });
+ }
+
+ Self {
+ vertex_arr,
+ _vertex_buffer: vertex_buffer,
+ index_info,
+ }
+ }
+}
+
+fn apply_transformation_matrices(
+ transform: &Transform,
+ gl_shader_program: &mut GlShaderProgram,
+ camera: &Camera,
+ window_size: Dimens<u32>,
+)
+{
+ gl_shader_program.set_uniform_matrix_4fv(cstr!("model"), &transform.as_matrix());
+
+ let view = create_view(camera);
+
+ gl_shader_program.set_uniform_matrix_4fv(cstr!("view"), &view);
+
+ #[allow(clippy::cast_precision_loss)]
+ let projection = match &camera.projection {
+ Projection::Perspective(perspective) => new_perspective_matrix(
+ perspective,
+ window_size.width as f32 / window_size.height as f32,
+ ),
+ };
+
+ gl_shader_program.set_uniform_matrix_4fv(cstr!("projection"), &projection);
+}
+
+fn apply_light(
+ material: &Material,
+ material_flags: &MaterialFlags,
+ global_light: &GlobalLight,
+ gl_shader_program: &mut GlShaderProgram,
+ point_lights: &[(PointLight, Transform)],
+ camera: &Camera,
+)
+{
+ debug_assert!(
+ point_lights.len() < 64,
+ "Shader cannot handle more than 64 point lights"
+ );
+
+ for (point_light_index, (point_light, point_light_transform)) in
+ point_lights.iter().enumerate()
+ {
+ set_point_light_uniforms(
+ gl_shader_program,
+ point_light_index,
+ point_light,
+ point_light_transform,
+ )
+ }
+
+ gl_shader_program.set_uniform_1i(cstr!("point_light_cnt"), point_lights.len() as i32);
+
+ gl_shader_program.set_uniform_vec_3fv(
+ cstr!("material.ambient"),
+ &if material_flags.use_ambient_color {
+ material.ambient.clone()
+ } else {
+ global_light.ambient.clone().into()
+ }
+ .into(),
+ );
+
+ gl_shader_program
+ .set_uniform_vec_3fv(cstr!("material.diffuse"), &material.diffuse.clone().into());
+
+ #[allow(clippy::cast_possible_wrap)]
+ gl_shader_program.set_uniform_vec_3fv(
+ cstr!("material.specular"),
+ &material.specular.clone().into(),
+ );
+
+ #[allow(clippy::cast_possible_wrap)]
+ gl_shader_program.set_uniform_1i(
+ cstr!("material.ambient_map"),
+ material.ambient_map.into_inner() as i32,
+ );
+
+ #[allow(clippy::cast_possible_wrap)]
+ gl_shader_program.set_uniform_1i(
+ cstr!("material.diffuse_map"),
+ material.diffuse_map.into_inner() as i32,
+ );
+
+ #[allow(clippy::cast_possible_wrap)]
+ gl_shader_program.set_uniform_1i(
+ cstr!("material.specular_map"),
+ material.specular_map.into_inner() as i32,
+ );
+
+ gl_shader_program.set_uniform_1fv(cstr!("material.shininess"), material.shininess);
+
+ gl_shader_program.set_uniform_vec_3fv(cstr!("view_pos"), &camera.position);
+}
+
+fn set_point_light_uniforms(
+ gl_shader_program: &mut GlShaderProgram,
+ point_light_index: usize,
+ point_light: &PointLight,
+ point_light_transform: &Transform,
+)
+{
+ gl_shader_program.set_uniform_vec_3fv(
+ &create_point_light_uniform_name(point_light_index, "position"),
+ &point_light_transform.position,
+ );
+
+ gl_shader_program.set_uniform_vec_3fv(
+ &create_point_light_uniform_name(point_light_index, "diffuse"),
+ &point_light.diffuse.clone().into(),
+ );
+
+ gl_shader_program.set_uniform_vec_3fv(
+ &create_point_light_uniform_name(point_light_index, "specular"),
+ &point_light.specular.clone().into(),
+ );
+
+ gl_shader_program.set_uniform_1fv(
+ &create_point_light_uniform_name(point_light_index, "constant"),
+ point_light.attenuation_params.constant,
+ );
+
+ gl_shader_program.set_uniform_1fv(
+ &create_point_light_uniform_name(point_light_index, "linear"),
+ point_light.attenuation_params.linear,
+ );
+
+ gl_shader_program.set_uniform_1fv(
+ &create_point_light_uniform_name(point_light_index, "quadratic"),
+ point_light.attenuation_params.quadratic,
+ );
+}
+
+fn create_point_light_uniform_name(
+ point_light_index: usize,
+ point_light_field: &str,
+) -> CString
+{
+ unsafe {
+ CString::from_vec_with_nul_unchecked(
+ format!("point_lights[{point_light_index}].{point_light_field}\0").into(),
+ )
+ }
+}
+
+fn create_view(camera: &Camera) -> Matrix<f32, 4, 4>
+{
+ let mut view = Matrix::new();
+
+ view.look_at(&camera.position, &camera.target, &camera.global_up);
+
+ view
+}
+
+#[cfg(feature = "debug")]
+#[tracing::instrument(skip_all)]
+fn opengl_debug_message_cb(
+ source: MessageSource,
+ ty: MessageType,
+ id: u32,
+ severity: MessageSeverity,
+ message: &str,
+)
+{
+ use std::backtrace::{Backtrace, BacktraceStatus};
+
+ use tracing::{event, Level};
+
+ macro_rules! create_event {
+ ($level: expr) => {
+ event!($level, ?source, ?ty, id, ?severity, message);
+ };
+ }
+
+ if matches!(severity, MessageSeverity::Notification) {
+ return;
+ }
+
+ match ty {
+ MessageType::Error => {
+ create_event!(Level::ERROR);
+
+ let backtrace = Backtrace::capture();
+
+ if matches!(backtrace.status(), BacktraceStatus::Captured) {
+ event!(Level::TRACE, "{backtrace}");
+ }
+ }
+ MessageType::Other => {
+ create_event!(Level::INFO);
+ }
+ _ => {
+ create_event!(Level::WARN);
+ }
+ };
+}
+
+#[derive(Debug)]
+struct IndexInfo
+{
+ _buffer: Buffer<u32>,
+ cnt: u32,
+}