diff options
| author | HampusM <hampus@hampusmat.com> | 2026-08-06 20:15:23 +0200 |
|---|---|---|
| committer | HampusM <hampus@hampusmat.com> | 2026-08-06 20:33:47 +0200 |
| commit | 88e04dab6a8cb1c63840242cabec3d90326a6552 (patch) | |
| tree | 66db7180b3e2d71dc7f1bfff88c52bde250a5d39 | |
| parent | 2cc47806a9af4bceb9ef34a328cdf39d9f4ce1fc (diff) | |
feat(engine): add support for cube map textures
| -rw-r--r-- | engine/src/rendering.rs | 1 | ||||
| -rw-r--r-- | engine/src/rendering/backend/opengl.rs | 212 | ||||
| -rw-r--r-- | engine/src/rendering/shader/cursor.rs | 15 | ||||
| -rw-r--r-- | engine/src/texture.rs | 35 | ||||
| -rw-r--r-- | engine/src/ui/dear_imgui.rs | 6 |
5 files changed, 214 insertions, 55 deletions
diff --git a/engine/src/rendering.rs b/engine/src/rendering.rs index 3af1ab8..f40f774 100644 --- a/engine/src/rendering.rs +++ b/engine/src/rendering.rs @@ -216,6 +216,7 @@ pub enum Command ActivateShader(ObjectId), SetShaderBinding(ShaderBinding), CreateTexture(ObjectId, AssetOrValue<Texture>), + // TODO: Make UpdateTexture command able to handle textures other than 2D textures UpdateTexture { obj_id: ObjectId, diff --git a/engine/src/rendering/backend/opengl.rs b/engine/src/rendering/backend/opengl.rs index c5e3217..12aaaf0 100644 --- a/engine/src/rendering/backend/opengl.rs +++ b/engine/src/rendering/backend/opengl.rs @@ -51,6 +51,7 @@ use opengl_bindings::shader::{ }; use opengl_bindings::texture::{ ColorSpace as GlTextureColorSpace, + CubeMapFace as GlCubeMapTextureFace, Error as GlTextureError, Filtering as GlTextureFiltering, PixelDataFormat as GlTexturePixelDataFormat, @@ -121,8 +122,10 @@ use crate::rendering::{ RENDER_PHASE, }; use crate::texture::{ + CubeMapFace as CubeMapTextureFace, Filtering as TextureFiltering, Properties as TextureProperties, + TexCubeMap, Texture, Wrapping as TextureWrapping, }; @@ -1141,28 +1144,64 @@ enum CreateGlContextError MakeContextCurrent(#[source] GlMakeContextCurrentError), } -fn retrieve_texture<'a>( - assets: &'a Assets, - texture: &'a AssetOrValue<Texture>, -) -> Option<(BorrowedOrOwned<'a, Image>, &'a TextureProperties)> +enum TextureCreationData<'a> { - let Texture { image, properties } = match &texture { - AssetOrValue::Asset(texture_asset) => { - let Some(texture) = assets.get(&texture_asset) else { - tracing::error!( - texture_asset_id=?texture_asset.id(), - "Texture asset does not exist" - ); - return None; - }; + Texture2D + { + image: BorrowedOrOwned<'a, Image> + }, + CubeMap + { + images: [(CubeMapTextureFace, BorrowedOrOwned<'a, Image>); 6], + color_type: ImageColorType, + is_srgb: bool, + size: Dimens<u32>, + }, +} - texture - } - AssetOrValue::Value(texture) => texture, - }; +fn validate_cube_map_texture(texture: &TexCubeMap) -> bool +{ + let first_image_color_type = texture.images[0].1.color_type(); - let texture_image = match image.color_type() { - ImageColorType::Rgb8 if (image.dimensions().width * 3) % 4 != 0 => { + if !texture.images[1..] + .into_iter() + .all(|(_, image)| image.color_type() == first_image_color_type) + { + tracing::error!("All texture images must have same color type"); + return false; + } + + let first_image_size = texture.images[0].1.dimensions(); + + if !texture.images[1..] + .into_iter() + .all(|(_, image)| image.dimensions() == first_image_size) + { + tracing::error!("All texture images must have same dimensions"); + return false; + } + + let first_image_is_srgb = texture.images[0].1.color_space_is_srgb(); + + if !texture.images[1..] + .into_iter() + .all(|(_, image)| image.color_space_is_srgb() == first_image_is_srgb) + { + tracing::error!("All texture images must have same color space"); + return false; + } + + true +} + +fn convert_textures_if_unaligned<const IMAGE_CNT: usize, Extra>( + images: [(Extra, &Image); IMAGE_CNT], + color_type: ImageColorType, + size: Dimens<u32>, +) -> [(Extra, BorrowedOrOwned<'_, Image>); IMAGE_CNT] +{ + match color_type { + ImageColorType::Rgb8 if (size.width * 3) % 4 != 0 => { // The texture will be corrupted if the alignment of each horizontal line of // the texture pixel array is not multiple of 4. // @@ -1179,12 +1218,10 @@ fn retrieve_texture<'a>( "texture image's pixel format to RGBA8" )); - BorrowedOrOwned::Owned(image.to_rgba8()) + images.map(|(extra, image)| (extra, BorrowedOrOwned::Owned(image.to_rgba8()))) } - _ => BorrowedOrOwned::Borrowned(image), - }; - - Some((texture_image, properties)) + _ => images.map(|(extra, image)| (extra, BorrowedOrOwned::Borrowned(image))), + } } #[tracing::instrument(skip_all)] @@ -1204,15 +1241,69 @@ fn create_texture_object( return Ok(()); } - let Some((texture_image, texture_properties)) = retrieve_texture(assets, &texture) - else { - return Ok(()); + let texture = match &texture { + AssetOrValue::Asset(texture_asset) => { + let Some(texture) = assets.get(&texture_asset) else { + tracing::error!( + texture_asset_id=?texture_asset.id(), + "Texture asset does not exist" + ); + return Ok(()); + }; + + texture + } + AssetOrValue::Value(texture) => texture, + }; + + let (texture_things, texture_properties) = match texture { + Texture::Texture2D(texture) => { + let [(_, image)] = convert_textures_if_unaligned( + [((), &texture.image)], + texture.image.color_type(), + texture.image.dimensions(), + ); + + ( + TextureCreationData::Texture2D { image }, + &texture.properties, + ) + } + Texture::CubeMap(texture) => { + if !validate_cube_map_texture(texture) { + return Ok(()); + } + + let first_image_color_type = texture.images[0].1.color_type(); + let first_image_size = texture.images[0].1.dimensions(); + let first_image_is_srgb = texture.images[0].1.color_space_is_srgb(); + + let images = convert_textures_if_unaligned( + std::array::from_fn::<_, 6, _>(|index| { + let (face, image) = &texture.images[index]; + + (*face, image) + }), + first_image_color_type, + first_image_size, + ); + + ( + TextureCreationData::CubeMap { + images, + color_type: first_image_color_type, + is_srgb: first_image_is_srgb, + size: first_image_size, + }, + &texture.properties, + ) + } }; object_store.insert( texture_object_id, Object::from_raw( - create_gl_texture(curr_gl_ctx, &*texture_image, texture_properties)? + create_gl_texture(curr_gl_ctx, texture_things, texture_properties)? .into_raw(), ObjectKind::Texture, ), @@ -1253,7 +1344,7 @@ fn update_texture_object( 0, offset.into(), texture_image.dimensions().into(), - get_image_pixel_data_format(&texture_image), + color_type_to_pixel_data_format(texture_image.color_type()), texture_image.as_bytes(), )?; @@ -1292,9 +1383,10 @@ fn draw_mesh( Ok(()) } -fn get_image_pixel_data_format(image: &Image) -> GlTexturePixelDataFormat +fn color_type_to_pixel_data_format(color_type: ImageColorType) + -> GlTexturePixelDataFormat { - match image.color_type() { + match color_type { ImageColorType::Rgb8 => GlTexturePixelDataFormat::Rgb8, ImageColorType::Rgba8 => GlTexturePixelDataFormat::Rgba8, _ => { @@ -1305,22 +1397,42 @@ fn get_image_pixel_data_format(image: &Image) -> GlTexturePixelDataFormat fn create_gl_texture( curr_gl_context: &MaybeCurrentContextWithFns, - image: &Image, + texture_things: TextureCreationData<'_>, texture_properties: &TextureProperties, ) -> Result<GlTexture, GlTextureError> { - let gl_texture = GlTexture::builder() - .size(image.dimensions().into()) - .color_space(if image.color_space_is_srgb() { - GlTextureColorSpace::Srgb - } else { - GlTextureColorSpace::Linear - }) - .create_2d( - curr_gl_context, - image.as_bytes(), - get_image_pixel_data_format(image), - )?; + let gl_texture = match texture_things { + TextureCreationData::Texture2D { image } => GlTexture::builder() + .size(image.dimensions().into()) + .color_space(if image.color_space_is_srgb() { + GlTextureColorSpace::Srgb + } else { + GlTextureColorSpace::Linear + }) + .create_2d( + curr_gl_context, + image.as_bytes(), + color_type_to_pixel_data_format(image.color_type()), + ), + TextureCreationData::CubeMap { images, color_type, is_srgb, size } => { + GlTexture::builder() + .size(size.into()) + .color_space(if is_srgb { + GlTextureColorSpace::Srgb + } else { + GlTextureColorSpace::Linear + }) + .create_cube_map( + curr_gl_context, + std::array::from_fn::<_, 6, _>(|index| { + let (face, image) = &images[index]; + + (cube_map_texture_face_to_gl(*face), image.as_bytes()) + }), + color_type_to_pixel_data_format(color_type), + ) + } + }?; gl_texture.set_wrap( curr_gl_context, @@ -1340,6 +1452,18 @@ fn create_gl_texture( Ok(gl_texture) } +fn cube_map_texture_face_to_gl(face: CubeMapTextureFace) -> GlCubeMapTextureFace +{ + match face { + CubeMapTextureFace::PositiveX => GlCubeMapTextureFace::PositiveX, + CubeMapTextureFace::NegativeX => GlCubeMapTextureFace::NegativeX, + CubeMapTextureFace::PositiveY => GlCubeMapTextureFace::PositiveY, + CubeMapTextureFace::NegativeY => GlCubeMapTextureFace::NegativeY, + CubeMapTextureFace::PositiveZ => GlCubeMapTextureFace::PositiveZ, + CubeMapTextureFace::NegativeZ => GlCubeMapTextureFace::NegativeZ, + } +} + fn create_shader_program( current_context: &MaybeCurrentContextWithFns, shader_program: &ShaderProgram, diff --git a/engine/src/rendering/shader/cursor.rs b/engine/src/rendering/shader/cursor.rs index 169ad0b..5cc742d 100644 --- a/engine/src/rendering/shader/cursor.rs +++ b/engine/src/rendering/shader/cursor.rs @@ -275,15 +275,23 @@ impl BindingValue ( Self::Texture(_, BindingTextureKind::Texture2D), TypeKind::Resource, - _, - _, - _, + .., Some(res_shape), ) if (res_shape & ResourceShape::BASE) .contains(ResourceShape::TEXTURE_2D) => { Ok(()) } + ( + Self::Texture(_, BindingTextureKind::Cube), + TypeKind::Resource, + .., + Some(res_shape), + ) if (res_shape & ResourceShape::BASE) + .contains(ResourceShape::TEXTURE_CUBE) => + { + Ok(()) + } _ => { cold_path(); Err(BindingError::IncorrectValueType { @@ -356,6 +364,7 @@ pub struct Binding pub enum BindingTextureKind { Texture2D, + Cube, } #[derive(Debug, thiserror::Error)] diff --git a/engine/src/texture.rs b/engine/src/texture.rs index af56e97..3bfe171 100644 --- a/engine/src/texture.rs +++ b/engine/src/texture.rs @@ -13,12 +13,37 @@ pub static WHITE_1X1_ASSET_LABEL: LazyLock<AssetLabel> = LazyLock::new(|| AssetL }); #[derive(Debug, Clone)] -pub struct Texture +pub enum Texture +{ + Texture2D(Tex2D), + CubeMap(TexCubeMap), +} + +#[derive(Debug, Clone)] +pub struct Tex2D { pub image: Image, pub properties: Properties, } +#[derive(Debug, Clone)] +pub struct TexCubeMap +{ + pub images: [(CubeMapFace, Image); 6], + pub properties: Properties, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub enum CubeMapFace +{ + PositiveX, + NegativeX, + PositiveY, + NegativeY, + PositiveZ, + NegativeZ, +} + builder! { /// Texture properties #[builder(name = PropertiesBuilder, derives=(Debug, Clone))] @@ -110,10 +135,10 @@ pub(crate) fn initialize(assets: &mut Assets) assets.store_with_label( WHITE_1X1_ASSET_LABEL.clone(), - Texture { + Texture::Texture2D(Tex2D { image: Image::from_color(Rgba::<u8>::white(), Dimens { width: 1, height: 1 }), properties: Properties::default(), - }, + }), ); } @@ -123,12 +148,12 @@ fn import( settings: Option<&'_ ImportSettings>, ) -> Result<(), ImageError> { - asset_submitter.submit_store(Texture { + asset_submitter.submit_store(Texture::Texture2D(Tex2D { image: Image::open(path)?, properties: settings .map(|settings| settings.properties.clone()) .unwrap_or_default(), - }); + })); Ok(()) } diff --git a/engine/src/ui/dear_imgui.rs b/engine/src/ui/dear_imgui.rs index b9775ea..60067e1 100644 --- a/engine/src/ui/dear_imgui.rs +++ b/engine/src/ui/dear_imgui.rs @@ -74,7 +74,7 @@ use crate::rendering::{ SurfaceSpec, PRE_RENDER_PHASE, }; -use crate::texture::{Properties as TextureProperties, Texture}; +use crate::texture::{Properties as TextureProperties, Tex2D, Texture}; use crate::util::MapVec; use crate::vector::Vec2; use crate::windowing::dpi::PhysicalSize; @@ -542,10 +542,10 @@ fn add_drawing_render_pass( render_pass.commands.push(RenderingCommand::CreateTexture( texture_object_id, - AssetOrValue::Value(Texture { + AssetOrValue::Value(Texture::Texture2D(Tex2D { image, properties: TextureProperties::default(), - }), + })), )); let texture_lookup_id = TextureLookupId::new(); |
