summaryrefslogtreecommitdiff
path: root/opengl-bindings/src
diff options
context:
space:
mode:
Diffstat (limited to 'opengl-bindings/src')
-rw-r--r--opengl-bindings/src/data_types.rs14
-rw-r--r--opengl-bindings/src/texture.rs313
2 files changed, 272 insertions, 55 deletions
diff --git a/opengl-bindings/src/data_types.rs b/opengl-bindings/src/data_types.rs
index 7ead0ab..a6c65fc 100644
--- a/opengl-bindings/src/data_types.rs
+++ b/opengl-bindings/src/data_types.rs
@@ -1,3 +1,5 @@
+use std::fmt::Display;
+
use safer_ffi::derive_ReprC;
use safer_ffi::layout::ReprC;
@@ -20,7 +22,7 @@ pub struct Vec3<Value: ReprC>
pub z: Value,
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Copy)]
#[derive_ReprC]
#[repr(C)]
pub struct Vec2<Value>
@@ -29,9 +31,17 @@ pub struct Vec2<Value>
pub y: Value,
}
-#[derive(Debug, Clone)]
+#[derive(Debug, Clone, Copy, Default)]
pub struct Dimens<Value>
{
pub width: Value,
pub height: Value,
}
+
+impl<Value: Display> Display for Dimens<Value>
+{
+ fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
+ {
+ write!(formatter, "{}x{}", self.width, self.height)
+ }
+}
diff --git a/opengl-bindings/src/texture.rs b/opengl-bindings/src/texture.rs
index e0035b6..be52135 100644
--- a/opengl-bindings/src/texture.rs
+++ b/opengl-bindings/src/texture.rs
@@ -1,28 +1,113 @@
-use crate::data_types::Dimens;
+use std::borrow::Cow;
+
+use crate::data_types::{Dimens, Vec2};
use crate::MaybeCurrentContextWithFns;
#[derive(Debug)]
-pub struct Texture
+pub struct Builder<'image>
{
- texture: crate::sys::types::GLuint,
+ image: Cow<'image, [u8]>,
+ pixel_data_format: PixelDataFormat,
+ size: Dimens<u32>,
+ color_space: ColorSpace,
+ mipmap_levels: u16,
}
-impl Texture
+impl<'image> Builder<'image>
{
+ pub fn image(
+ mut self,
+ image: impl Into<Cow<'image, [u8]>>,
+ pixel_data_format: PixelDataFormat,
+ ) -> Self
+ {
+ self.image = image.into();
+ self.pixel_data_format = pixel_data_format;
+ self
+ }
+
+ pub fn size(mut self, size: Dimens<u32>) -> Self
+ {
+ self.size = size;
+ self
+ }
+
+ pub fn color_space(mut self, color_space: ColorSpace) -> Self
+ {
+ self.color_space = color_space;
+ self
+ }
+
+ pub fn mipmap_levels(mut self, mipmap_levels: u16) -> Self
+ {
+ self.mipmap_levels = mipmap_levels;
+ self
+ }
+
#[must_use]
- pub fn new(current_context: &MaybeCurrentContextWithFns) -> Self
+ pub fn create(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ ) -> Result<Texture, Error>
{
- let mut texture = crate::sys::types::GLuint::default();
+ check_image_buffer_len_correct_for_size(
+ self.image.as_ref(),
+ self.size,
+ self.pixel_data_format,
+ )?;
- unsafe {
- current_context.fns().CreateTextures(
- crate::sys::TEXTURE_2D,
- 1,
- &raw mut texture,
- );
- };
+ let size = try_convert_size(self.size.clone())?;
- Self { texture }
+ let texture = Texture::new(current_context);
+
+ texture.alloc(
+ current_context,
+ self.mipmap_levels,
+ self.pixel_data_format,
+ self.color_space,
+ size,
+ );
+
+ texture.sub_image(
+ current_context,
+ 0,
+ Vec2 { x: 0, y: 0 },
+ size,
+ self.pixel_data_format,
+ self.image.as_ref(),
+ );
+
+ texture.generate_mipmap(current_context);
+
+ Ok(texture)
+ }
+}
+
+impl<'image> Default for Builder<'image>
+{
+ fn default() -> Self
+ {
+ Self {
+ image: Cow::Borrowed(&[]),
+ pixel_data_format: PixelDataFormat::default(),
+ size: Dimens::default(),
+ color_space: ColorSpace::default(),
+ mipmap_levels: 1,
+ }
+ }
+}
+
+#[derive(Debug)]
+pub struct Texture
+{
+ texture: crate::sys::types::GLuint,
+}
+
+impl Texture
+{
+ pub fn builder<'image>() -> Builder<'image>
+ {
+ Builder::default()
}
pub fn bind_to_texture_unit(
@@ -38,39 +123,36 @@ impl Texture
}
}
- /// Allocates the texture storage, stores pixel data & generates a mipmap.
- ///
- /// # Errors
- /// Returns `Err` if any value in `size` does not fit into a `i32`.
- pub fn generate(
+ pub fn generate_mipmap(&self, current_context: &MaybeCurrentContextWithFns)
+ {
+ unsafe {
+ current_context.fns().GenerateTextureMipmap(self.texture);
+ }
+ }
+
+ pub fn store_image(
&self,
current_context: &MaybeCurrentContextWithFns,
- size: &Dimens<u32>,
- data: &[u8],
+ mipmap_level: u16,
+ offset: Vec2<u32>,
+ size: Dimens<u32>,
pixel_data_format: PixelDataFormat,
- color_space: ColorSpace,
- ) -> Result<(), GenerateError>
+ image: &[u8],
+ ) -> Result<(), Error>
{
- let size = Dimens::<crate::sys::types::GLsizei> {
- width: size.width.try_into().map_err(|_| {
- GenerateError::SizeWidthValueTooLarge {
- value: size.width,
- max_value: crate::sys::types::GLsizei::MAX as u32,
- }
- })?,
- height: size.height.try_into().map_err(|_| {
- GenerateError::SizeHeightValueTooLarge {
- value: size.height,
- max_value: crate::sys::types::GLsizei::MAX as u32,
- }
- })?,
- };
+ check_image_buffer_len_correct_for_size(image, size, pixel_data_format)?;
- self.alloc_image(current_context, pixel_data_format, color_space, &size, data);
+ let offset = try_convert_offset(offset)?;
+ let size = try_convert_size(size)?;
- unsafe {
- current_context.fns().GenerateTextureMipmap(self.texture);
- }
+ self.sub_image(
+ current_context,
+ mipmap_level,
+ offset,
+ size,
+ pixel_data_format,
+ image,
+ );
Ok(())
}
@@ -145,34 +227,62 @@ impl Texture
self.texture
}
- fn alloc_image(
+ fn new(current_context: &MaybeCurrentContextWithFns) -> Self
+ {
+ let mut texture = crate::sys::types::GLuint::default();
+
+ unsafe {
+ current_context.fns().CreateTextures(
+ crate::sys::TEXTURE_2D,
+ 1,
+ &raw mut texture,
+ );
+ };
+
+ Self { texture }
+ }
+
+ fn alloc(
&self,
current_context: &MaybeCurrentContextWithFns,
+ mipmap_levels: u16,
pixel_data_format: PixelDataFormat,
color_space: ColorSpace,
- size: &Dimens<crate::sys::types::GLsizei>,
- data: &[u8],
+ size: Dimens<crate::sys::types::GLsizei>,
)
{
unsafe {
current_context.fns().TextureStorage2D(
self.texture,
- 1,
+ mipmap_levels.into(),
pixel_data_format.to_sized_internal_format(color_space),
size.width,
size.height,
);
+ }
+ }
+ fn sub_image(
+ &self,
+ current_context: &MaybeCurrentContextWithFns,
+ mipmap_level: u16,
+ offset: Vec2<crate::sys::types::GLint>,
+ size: Dimens<crate::sys::types::GLsizei>,
+ pixel_data_format: PixelDataFormat,
+ image: &[u8],
+ )
+ {
+ unsafe {
current_context.fns().TextureSubImage2D(
self.texture,
- 0,
- 0,
- 0,
+ mipmap_level.into(),
+ offset.x,
+ offset.y,
size.width,
size.height,
pixel_data_format.to_format(),
crate::sys::UNSIGNED_BYTE,
- data.as_ptr().cast(),
+ image.as_ptr().cast(),
);
}
}
@@ -205,10 +315,12 @@ pub enum Filtering
}
/// Texture pixel data format.
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, Default)]
pub enum PixelDataFormat
{
Rgb8,
+
+ #[default]
Rgba8,
}
@@ -234,19 +346,27 @@ impl PixelDataFormat
Self::Rgba8 => crate::sys::RGBA,
}
}
+
+ fn pixel_width(&self) -> usize
+ {
+ match self {
+ Self::Rgb8 => 3,
+ Self::Rgba8 => 4,
+ }
+ }
}
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, Default)]
#[non_exhaustive]
pub enum ColorSpace
{
+ #[default]
Linear,
Srgb,
}
-/// Error generating texture.
#[derive(Debug, thiserror::Error)]
-pub enum GenerateError
+pub enum Error
{
#[error("Size width value ({value}) is too large. Must be < {max_value}")]
SizeWidthValueTooLarge
@@ -258,4 +378,91 @@ pub enum GenerateError
{
value: u32, max_value: u32
},
+
+ #[error("Offset X value ({value}) is too large. Must be < {max_value}")]
+ OffsetXValueTooLarge
+ {
+ value: u32, max_value: u32
+ },
+ #[error("Offset Y value ({value}) is too large. Must be < {max_value}")]
+ OffsetYValueTooLarge
+ {
+ value: u32, max_value: u32
+ },
+
+ #[error(
+ "Incorrect image buffer length for size {size}. Expected {}, found {}",
+ expected_buffer_len,
+ buffer_len
+ )]
+ IncorrectImageBufferLengthForSize
+ {
+ expected_buffer_len: usize,
+ buffer_len: usize,
+ size: Dimens<u32>,
+ },
+}
+
+fn try_convert_size(
+ size: Dimens<u32>,
+) -> Result<Dimens<crate::sys::types::GLsizei>, Error>
+{
+ Ok(Dimens::<crate::sys::types::GLsizei> {
+ width: size
+ .width
+ .try_into()
+ .map_err(|_| Error::SizeWidthValueTooLarge {
+ value: size.width,
+ max_value: crate::sys::types::GLsizei::MAX as u32,
+ })?,
+ height: size
+ .height
+ .try_into()
+ .map_err(|_| Error::SizeHeightValueTooLarge {
+ value: size.height,
+ max_value: crate::sys::types::GLsizei::MAX as u32,
+ })?,
+ })
+}
+
+fn try_convert_offset(offset: Vec2<u32>)
+ -> Result<Vec2<crate::sys::types::GLint>, Error>
+{
+ Ok(Vec2::<crate::sys::types::GLint> {
+ x: offset
+ .x
+ .try_into()
+ .map_err(|_| Error::OffsetXValueTooLarge {
+ value: offset.x,
+ max_value: crate::sys::types::GLint::MAX as u32,
+ })?,
+ y: offset
+ .y
+ .try_into()
+ .map_err(|_| Error::OffsetYValueTooLarge {
+ value: offset.y,
+ max_value: crate::sys::types::GLint::MAX as u32,
+ })?,
+ })
+}
+
+fn check_image_buffer_len_correct_for_size(
+ image: &[u8],
+ size: Dimens<u32>,
+ pixel_data_format: PixelDataFormat,
+) -> Result<(), Error>
+{
+ let pixel_width = pixel_data_format.pixel_width();
+
+ let expected_buffer_len = size.width as usize * size.height as usize * pixel_width;
+
+ if expected_buffer_len != image.len() {
+ return Err(Error::IncorrectImageBufferLengthForSize {
+ expected_buffer_len,
+ buffer_len: image.len(),
+ size,
+ });
+ }
+
+ Ok(())
}