diff options
Diffstat (limited to 'engine')
-rw-r--r-- | engine/Cargo.toml | 1 | ||||
-rw-r--r-- | engine/fragment-color.glsl (renamed from engine/fragment.glsl) | 0 | ||||
-rw-r--r-- | engine/fragment-texture.glsl | 11 | ||||
-rw-r--r-- | engine/src/lib.rs | 1 | ||||
-rw-r--r-- | engine/src/object.rs | 43 | ||||
-rw-r--r-- | engine/src/opengl/mod.rs | 1 | ||||
-rw-r--r-- | engine/src/opengl/texture.rs | 144 | ||||
-rw-r--r-- | engine/src/renderer/mod.rs | 44 | ||||
-rw-r--r-- | engine/src/texture.rs | 100 | ||||
-rw-r--r-- | engine/src/vector.rs | 2 | ||||
-rw-r--r-- | engine/src/vertex.rs | 26 | ||||
-rw-r--r-- | engine/vertex.glsl | 3 |
12 files changed, 349 insertions, 27 deletions
diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 023c711..d3de446 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -13,3 +13,4 @@ gl = "0.14.0" bitflags = "2.4.0" cstr = "0.2.11" tracing = { version = "0.1.39", optional = true } +image = "0.24.7" diff --git a/engine/fragment.glsl b/engine/fragment-color.glsl index 8b209c4..8b209c4 100644 --- a/engine/fragment.glsl +++ b/engine/fragment-color.glsl diff --git a/engine/fragment-texture.glsl b/engine/fragment-texture.glsl new file mode 100644 index 0000000..a4d378b --- /dev/null +++ b/engine/fragment-texture.glsl @@ -0,0 +1,11 @@ +#version 330 core +out vec4 FragColor; + +in vec2 in_texture_coords; + +uniform sampler2D input_texture; + +void main() +{ + FragColor = texture(input_texture, in_texture_coords); +} diff --git a/engine/src/lib.rs b/engine/src/lib.rs index ec969f8..9f82b26 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -19,6 +19,7 @@ mod transform; pub mod camera; pub mod color; pub mod object; +pub mod texture; pub mod vector; pub mod vertex; diff --git a/engine/src/object.rs b/engine/src/object.rs index f2591f2..a8cd076 100644 --- a/engine/src/object.rs +++ b/engine/src/object.rs @@ -5,16 +5,21 @@ use crate::opengl::shader::{ Shader, }; use crate::renderer::Renderable; +use crate::texture::Texture; use crate::transform::Transform; use crate::vector::Vec3; use crate::vertex::Vertex; +const FRAG_SHADER_COLOR: &str = include_str!("../fragment-color.glsl"); +const FRAG_SHADER_TEXTURE: &str = include_str!("../fragment-texture.glsl"); + #[derive(Debug)] pub struct Object { id: Id, renderable: Renderable, transform: Transform, + texture: Option<Texture>, } impl Object @@ -42,6 +47,12 @@ impl Object self.transform.set_scaling(scaling); } + #[must_use] + pub fn texture(&self) -> Option<&Texture> + { + self.texture.as_ref() + } + pub(crate) fn renderable(&self) -> &Renderable { &self.renderable @@ -54,11 +65,12 @@ impl Object } /// Object builder. -#[derive(Debug, Clone, Default)] +#[derive(Debug, Default)] pub struct Builder { vertices: Vec<Vertex>, indices: Option<Vec<u32>>, + texture: Option<Texture>, } impl Builder @@ -86,21 +98,35 @@ impl Builder self } + #[must_use] + pub fn texture(mut self, texture: Texture) -> Self + { + self.texture = Some(texture); + + self + } + /// Builds a new [`Object`]. /// /// # Errors /// Will return `Err` if: /// - Creating shaders fails /// - Linking the shader program fails - pub fn build(&self, id: Id) -> Result<Object, Error> + pub fn build(self, id: Id) -> Result<Object, Error> { let vertex_shader = Self::create_shader(ShaderKind::Vertex, include_str!("../vertex.glsl")) .map_err(Error::CreateVertexShaderFailed)?; - let fragment_shader = - Self::create_shader(ShaderKind::Fragment, include_str!("../fragment.glsl")) - .map_err(Error::CreateFragmentShaderFailed)?; + let fragment_shader = Self::create_shader( + ShaderKind::Fragment, + if self.texture.is_some() { + FRAG_SHADER_TEXTURE + } else { + FRAG_SHADER_COLOR + }, + ) + .map_err(Error::CreateFragmentShaderFailed)?; let shader_program = ShaderProgram::new(); @@ -116,7 +142,12 @@ impl Builder let transform = Transform::new(); - Ok(Object { id, renderable, transform }) + Ok(Object { + id, + renderable, + transform, + texture: self.texture, + }) } fn create_shader(kind: ShaderKind, source: &str) -> Result<Shader, ShaderError> diff --git a/engine/src/opengl/mod.rs b/engine/src/opengl/mod.rs index 4f4f96f..d58ca50 100644 --- a/engine/src/opengl/mod.rs +++ b/engine/src/opengl/mod.rs @@ -6,6 +6,7 @@ use crate::vector::Vec2; pub mod buffer; pub mod currently_bound; pub mod shader; +pub mod texture; pub mod vertex_array; mod util; diff --git a/engine/src/opengl/texture.rs b/engine/src/opengl/texture.rs new file mode 100644 index 0000000..c7bf75b --- /dev/null +++ b/engine/src/opengl/texture.rs @@ -0,0 +1,144 @@ +use crate::opengl::currently_bound::CurrentlyBound; +use crate::vector::Vec2; + +#[derive(Debug)] +pub struct Texture +{ + texture: gl::types::GLuint, +} + +impl Texture +{ + pub fn new() -> Self + { + let mut texture = gl::types::GLuint::default(); + + unsafe { + gl::GenTextures(1, &mut texture); + }; + + Self { texture } + } + + pub fn bind(&self, cb: impl FnOnce(CurrentlyBound<'_, Self>)) + { + unsafe { + gl::BindTexture(gl::TEXTURE_2D, self.texture); + } + + // SAFETY: The buffer object is bound above + let currently_bound = unsafe { CurrentlyBound::new() }; + + cb(currently_bound); + } + + pub fn generate(_: &CurrentlyBound<Self>, dimens: &Vec2<u32>, data: &[u8]) + { + #[allow(clippy::cast_possible_wrap)] + unsafe { + gl::TexImage2D( + gl::TEXTURE_2D, + 0, + gl::RGB as i32, + dimens.x as i32, + dimens.y as i32, + 0, + gl::RGB, + gl::UNSIGNED_BYTE, + data.as_ptr().cast(), + ); + + gl::GenerateMipmap(gl::TEXTURE_2D); + } + } + + pub fn set_wrap(_: CurrentlyBound<Self>, wrapping: Wrapping) + { + let wrapping_gl = wrapping.to_gl(); + + #[allow(clippy::cast_possible_wrap)] + unsafe { + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, wrapping_gl as i32); + gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, wrapping_gl as i32); + } + } + + pub fn set_magnifying_filter(_: CurrentlyBound<Self>, filtering: Filtering) + { + let filtering_gl = filtering.to_gl(); + + #[allow(clippy::cast_possible_wrap)] + unsafe { + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_MAG_FILTER, + filtering_gl as i32, + ); + } + } + + pub fn set_minifying_filter(_: CurrentlyBound<Self>, filtering: Filtering) + { + let filtering_gl = filtering.to_gl(); + + #[allow(clippy::cast_possible_wrap)] + unsafe { + gl::TexParameteri( + gl::TEXTURE_2D, + gl::TEXTURE_MIN_FILTER, + filtering_gl as i32, + ); + } + } +} + +impl Drop for Texture +{ + fn drop(&mut self) + { + unsafe { + gl::DeleteTextures(1, &self.texture); + } + } +} + +/// Texture wrapping. +#[derive(Debug, Clone, Copy)] +pub enum Wrapping +{ + Repeat, + MirroredRepeat, + ClampToEdge, + ClampToBorder, +} + +impl Wrapping +{ + fn to_gl(self) -> gl::types::GLenum + { + match self { + Self::Repeat => gl::REPEAT, + Self::MirroredRepeat => gl::MIRRORED_REPEAT, + Self::ClampToEdge => gl::CLAMP_TO_EDGE, + Self::ClampToBorder => gl::CLAMP_TO_BORDER, + } + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Filtering +{ + Nearest, + Linear, +} + +impl Filtering +{ + fn to_gl(self) -> gl::types::GLenum + { + match self { + Self::Linear => gl::LINEAR, + Self::Nearest => gl::NEAREST, + } + } +} diff --git a/engine/src/renderer/mod.rs b/engine/src/renderer/mod.rs index 6c56964..3691f76 100644 --- a/engine/src/renderer/mod.rs +++ b/engine/src/renderer/mod.rs @@ -116,23 +116,33 @@ pub fn render<'obj>( apply_transformation_matrices(obj, camera, window_size); - obj.renderable().vertex_arr.bind(|vert_arr_curr_bound| { - if let Some(index_info) = &obj.renderable().index_info { - VertexArray::draw_elements( - &vert_arr_curr_bound, - PrimitiveKind::Triangles, - 0, - index_info.cnt, - ); - } else { - VertexArray::draw_arrays( - &vert_arr_curr_bound, - PrimitiveKind::Triangles, - 0, - 3, - ); - } - }); + let draw = || { + obj.renderable().vertex_arr.bind(|vert_arr_curr_bound| { + if let Some(index_info) = &obj.renderable().index_info { + VertexArray::draw_elements( + &vert_arr_curr_bound, + PrimitiveKind::Triangles, + 0, + index_info.cnt, + ); + } else { + VertexArray::draw_arrays( + &vert_arr_curr_bound, + PrimitiveKind::Triangles, + 0, + 3, + ); + } + }); + }; + + if let Some(texture) = obj.texture() { + texture.inner().bind(|_| { + draw(); + }); + } else { + draw(); + } } } diff --git a/engine/src/texture.rs b/engine/src/texture.rs new file mode 100644 index 0000000..7874df4 --- /dev/null +++ b/engine/src/texture.rs @@ -0,0 +1,100 @@ +use std::path::Path; + +use image::io::Reader as ImageReader; +use image::{DynamicImage, ImageError}; + +use crate::opengl::texture::Texture as InnerTexture; +use crate::vector::Vec2; + +mod reexports +{ + pub use crate::opengl::texture::{Filtering, Wrapping}; +} + +pub use reexports::*; + +#[derive(Debug)] +pub struct Texture +{ + inner: InnerTexture, +} + +impl Texture +{ + /// Opens a texture image. + /// + /// # Errors + /// Will return `Err` if: + /// - Opening the image fails + /// - The image data is not 8-bit/color RGB + #[allow(clippy::new_without_default)] + pub fn open(path: &Path) -> Result<Self, Error> + { + let image = ImageReader::open(path) + .map_err(Error::OpenImageFailed)? + .decode() + .map_err(Error::DecodeImageFailed)?; + + if !matches!(image, DynamicImage::ImageRgb8(_)) { + return Err(Error::UnsupportedImageDataKind); + } + + let inner = InnerTexture::new(); + + inner.bind(|texture_curr_bound| { + InnerTexture::generate( + &texture_curr_bound, + &Vec2 { x: image.width(), y: image.height() }, + image.as_bytes(), + ); + }); + + let me = Self { inner }; + + me.set_wrap(Wrapping::Repeat); + me.set_magnifying_filter(Filtering::Linear); + me.set_minifying_filter(Filtering::Nearest); + + Ok(me) + } + + pub fn set_wrap(&self, wrapping: Wrapping) + { + self.inner.bind(|texture_curr_bound| { + InnerTexture::set_wrap(texture_curr_bound, wrapping); + }); + } + + pub fn set_magnifying_filter(&self, filtering: Filtering) + { + self.inner.bind(|texture_curr_bound| { + InnerTexture::set_magnifying_filter(texture_curr_bound, filtering); + }); + } + + pub fn set_minifying_filter(&self, filtering: Filtering) + { + self.inner.bind(|texture_curr_bound| { + InnerTexture::set_minifying_filter(texture_curr_bound, filtering); + }); + } + + pub(crate) fn inner(&self) -> &InnerTexture + { + &self.inner + } +} + +/// Texture error. +#[derive(Debug, thiserror::Error)] +pub enum Error +{ + #[error("Failed to open texture image")] + OpenImageFailed(#[source] std::io::Error), + + #[error("Failed to decode texture image")] + DecodeImageFailed(#[source] ImageError), + + #[error("Unsupported image data kind")] + UnsupportedImageDataKind, +} diff --git a/engine/src/vector.rs b/engine/src/vector.rs index e5947d5..00d6a6f 100644 --- a/engine/src/vector.rs +++ b/engine/src/vector.rs @@ -1,6 +1,6 @@ use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign}; -#[derive(Debug)] +#[derive(Debug, Default, Clone)] pub struct Vec2<Value> { pub x: Value, diff --git a/engine/src/vertex.rs b/engine/src/vertex.rs index 62f629b..9df646f 100644 --- a/engine/src/vertex.rs +++ b/engine/src/vertex.rs @@ -1,7 +1,7 @@ use std::mem::size_of; use crate::color::Color; -use crate::vector::Vec3; +use crate::vector::{Vec2, Vec3}; #[derive(Debug, Clone, Default)] #[repr(C)] @@ -9,6 +9,7 @@ pub struct Vertex { pos: Vec3<f32>, color: Color<f32>, + texture_coords: Vec2<f32>, } #[derive(Debug, Default)] @@ -16,6 +17,7 @@ pub struct Builder { pos: Option<Vec3<f32>>, color: Option<Color<f32>>, + texture_coords: Vec2<f32>, } impl Builder @@ -23,7 +25,7 @@ impl Builder #[must_use] pub fn new() -> Self { - Self { pos: None, color: None } + Self::default() } #[must_use] @@ -43,12 +45,24 @@ impl Builder } #[must_use] + pub fn texture_coords(mut self, texture_coords: Vec2<f32>) -> Self + { + self.texture_coords = texture_coords; + + self + } + + #[must_use] pub fn build(self) -> Option<Vertex> { let pos = self.pos?; let color = self.color?; - Some(Vertex { pos, color }) + Some(Vertex { + pos, + color, + texture_coords: self.texture_coords, + }) } } @@ -69,6 +83,12 @@ impl Vertex component_cnt: AttributeComponentCnt::Three, component_size: size_of::<f32>(), }, + Attribute { + index: 2, + component_type: AttributeComponentType::Float, + component_cnt: AttributeComponentCnt::Two, + component_size: size_of::<f32>(), + }, ] } } diff --git a/engine/vertex.glsl b/engine/vertex.glsl index b0a2a13..61782c2 100644 --- a/engine/vertex.glsl +++ b/engine/vertex.glsl @@ -1,8 +1,10 @@ #version 330 core layout (location = 0) in vec3 pos; layout (location = 1) in vec3 color; +layout (location = 2) in vec2 texture_coords; out vec3 in_frag_color; +out vec2 in_texture_coords; uniform mat4 model; uniform mat4 view; @@ -13,4 +15,5 @@ void main() gl_Position = projection * view * model * vec4(pos, 1.0); in_frag_color = color; + in_texture_coords = texture_coords; } |