summaryrefslogtreecommitdiff
path: root/engine/src
diff options
context:
space:
mode:
authorHampusM <hampus@hampusmat.com>2023-11-02 20:22:33 +0100
committerHampusM <hampus@hampusmat.com>2023-11-02 20:22:33 +0100
commit9aafb51e0be1720019db1c3d0a294ce9a42653df (patch)
tree40881ff26bff5a928280223d8aced87e1370d63b /engine/src
parentf2c54d47e6b61198520824117339aaa21c32accd (diff)
feat(engine): add texturing
Diffstat (limited to 'engine/src')
-rw-r--r--engine/src/lib.rs1
-rw-r--r--engine/src/object.rs43
-rw-r--r--engine/src/opengl/mod.rs1
-rw-r--r--engine/src/opengl/texture.rs144
-rw-r--r--engine/src/renderer/mod.rs44
-rw-r--r--engine/src/texture.rs100
-rw-r--r--engine/src/vector.rs2
-rw-r--r--engine/src/vertex.rs26
8 files changed, 334 insertions, 27 deletions
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>(),
+ },
]
}
}