use std::path::Path; use std::sync::LazyLock; use ecs::actions::Actions; use ecs::component::local::Local; use ecs::error::Context as _; use ecs::pair::ChildOf; use ecs::query::term::{Traverse, TraverseUp}; use ecs::uid::Uid; use ecs::Component; use crate::asset::{Assets, Label as AssetLabel}; use crate::camera::{Active as ActiveCamera, Camera}; use crate::color::Rgba; use crate::data_types::dimens::{Dimens, Dimens3}; use crate::draw_flags::{DrawFlags, NoDraw, PolygonModeConfig}; use crate::ecs::query::term::{With, Without}; use crate::ecs::sole::Single; use crate::ecs::Query; use crate::error; use crate::image::Image; use crate::lighting::{ DirectionalLight, Environmental as EnvironmentalLighting, PointLight, }; use crate::material::{Flags as MaterialFlags, Material}; use crate::mesh::Mesh; use crate::model::{MaterialSearchResult, Model}; use crate::projection::{ClipVolume as ProjectionClipVolume, Perspective, Projection}; use crate::rendering::object::{Id as ObjectId, Store as ObjectStore}; use crate::rendering::shader::cursor::{ BindingTextureKind as ShaderBindingTextureKind, BindingValue as ShaderBindingValue, Cursor as ShaderCursor, }; use crate::rendering::shader::{ Context as ShaderContext, EntrypointFlags as ShaderEntrypointFlags, ModuleSource as ShaderModuleSource, Program as ShaderProgram, }; use crate::rendering::{ AssetOrValue, BufferClearMask, Command, DepthFunction, DrawMeshOptions, DrawProperties, DrawPropertiesUpdateFlags, MeshUsage, RenderPass, RenderPasses, RgbaTextureDataType, Surface, TargetWindow, TextureCreation, TexturePixelDataFormat, }; use crate::scene::{Active as ActiveScene, Scene}; use crate::sky_box::SkyBox; use crate::texture::{ Filtering as TextureFiltering, Properties as TextureProperties, Texture, Wrapping as TextureWrapping, }; use crate::transform::Transform; use crate::vector::Vec3; use crate::windowing::window::Window; type RenderableEntity<'a> = ( &'a Model, Traverse<(With, With), TraverseUp, ChildOf>, Option<&'a MaterialFlags>, Option<&'a Transform>, Option<&'a DrawFlags>, Without, ); pub static SKY_BOX_SHADER_ASSET_LABEL: LazyLock = LazyLock::new(|| AssetLabel { path: Path::new("").into(), name: Some("sky_box_shader".into()), }); pub static MAIN_3D_SHADER_ASSET_LABEL: LazyLock = LazyLock::new(|| AssetLabel { path: Path::new("").into(), name: Some("main_3d_shader".into()), }); struct SkyBoxIds { texture_object: ObjectId, mesh_object: ObjectId, } #[derive(Debug, Default, Component)] pub struct MiscObjectIds { white_1x1_tex_obj_id: Option, } #[tracing::instrument(skip_all)] pub fn add_main_render_pass( renderable_query: Query>, camera_query: Query<( &Camera, &Transform, &ActiveCamera, Traverse<(With, With), TraverseUp, ChildOf>, )>, window_query: Query<(&Window, &Surface, With)>, scene_query: Query<( Option<&EnvironmentalLighting>, Option<&SkyBox>, Option<&SkyBoxState>, With, With, )>, point_light_query: Query<( &PointLight, &Transform, Traverse<(With, With), TraverseUp, ChildOf>, )>, directional_light_query: Query<( &DirectionalLight, Traverse<(With, With), TraverseUp, ChildOf>, )>, shader_context: Single, mut assets: Single, mut render_passes: Single, mut object_store: Single, mut actions: Actions, mut misc_object_ids: Local, ) -> Result<(), crate::Error> { let assets = assets.get_mut()?; let shader_context = shader_context.get()?; let render_passes = render_passes.get_mut()?; let object_store = object_store.get_mut()?; let Some((scene_ent_id, (scene_env_lighting, scene_skybox, scene_skybox_state))) = scene_query.iter_with_euids().next() else { return Ok(()); }; let Some((camera, camera_transform, _)) = camera_query.iter().next() else { tracing::trace!("No active camera"); return Ok(()); }; let render_pass = render_passes.passes.push_front_mut(RenderPass { commands: Vec::with_capacity(30), draw_properties: DrawProperties::default(), }); for (model, ..) in &renderable_query { add_renderable_creation_commands(assets, object_store, render_pass, &model); } let shaders = match get_or_create_shaders(assets, shader_context, object_store, render_pass) { Ok(ShadersCreationStatus::Ready(ready_shaders)) => ready_shaders, Ok(ShadersCreationStatus::ProcessingRequired) => return Ok(()), Err(err) => { tracing::error!("Failed to create shaders: {err}"); return Ok(()); } }; let white_1x1_tex_obj_id = *misc_object_ids.white_1x1_tex_obj_id.get_or_insert_with(|| { create_white_1x1_texture_object(object_store, render_pass) }); let main_3d_shader_cursor = ShaderCursor::new( shaders .main_3d_shader_program .reflection(0) .context("Unable to get reflection for main 3D shader")? .global_params_var_layout() .ok_or_else(|| { crate::Error::message( "Unable to get reflection for main 3D shader's global parameters", ) })?, ); add_set_3d_shader_point_light_bindings( render_pass, &point_light_query, shaders.main_3d_shader_obj_id, &main_3d_shader_cursor, )?; add_set_3d_shader_dir_lights_bindings( render_pass, &directional_light_query, shaders.main_3d_shader_obj_id, &main_3d_shader_cursor, )?; for (window, window_surface) in &window_query { render_pass .commands .push(Command::MakeCurrent(window_surface.id)); add_set_3d_shader_camera_bindings( render_pass, (&camera, &camera_transform), &window, shaders.main_3d_shader_obj_id, &main_3d_shader_cursor, )?; let mut buf_clear_mask = BufferClearMask::DEPTH; buf_clear_mask.set(BufferClearMask::COLOR, scene_skybox.is_none()); render_pass .commands .push(Command::ClearBuffers(buf_clear_mask)); for (model, material_flags, transform, draw_flags) in &renderable_query { let Some(model_spec) = assets.get(&model.spec_asset) else { continue; }; let Some(mesh_asset) = &model_spec.mesh_asset else { continue; }; if assets.get(mesh_asset).is_none() { continue; } let model_material = match model_spec.find_first_material(&assets) { MaterialSearchResult::Found(model_material_asset) if let Some(model_material) = assets.get(&model_material_asset) => { model_material } MaterialSearchResult::Found(_) | MaterialSearchResult::NotFound => { continue; } MaterialSearchResult::NoMaterials => &Material::builder().build(), }; if model_material .textures() .any(|texture_asset| !assets.is_loaded_and_has_type(&texture_asset)) { continue; } add_set_3d_shader_renderable_bindings( render_pass, ( model_material, material_flags.as_deref(), transform.as_deref(), ), scene_env_lighting.as_deref(), shaders.main_3d_shader_obj_id, &main_3d_shader_cursor, white_1x1_tex_obj_id, )?; render_pass .commands .push(Command::ActivateShader(shaders.main_3d_shader_obj_id)); if let Some(draw_flags) = draw_flags.as_deref().and_then(|draw_flags| { if draw_flags.polygon_mode_config != PolygonModeConfig::default() { Some(draw_flags) } else { None } }) { render_pass.commands.push(Command::UpdateDrawProperties( DrawProperties { polygon_mode_config: draw_flags.polygon_mode_config.clone(), ..Default::default() }, DrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG, )); } render_pass.commands.push(Command::DrawMesh( ObjectId::Asset(mesh_asset.id()), DrawMeshOptions::default(), )); if draw_flags.as_deref().is_some_and(|draw_flags| { draw_flags.polygon_mode_config != PolygonModeConfig::default() }) { render_pass.commands.push(Command::UpdateDrawProperties( DrawProperties { polygon_mode_config: PolygonModeConfig::default(), ..Default::default() }, DrawPropertiesUpdateFlags::POLYGON_MODE_CONFIG, )); } } if let Some(scene_skybox) = &scene_skybox { let Some(sky_box_ids) = load_sky_box( scene_ent_id, &scene_skybox, scene_skybox_state.as_deref(), assets, object_store, render_pass, &mut actions, )? else { continue; }; add_sky_box_commands( render_pass, &sky_box_ids, &camera, &camera_transform, &window, shaders.sky_box_shader_obj_id, shaders.sky_box_shader_program, )?; } } Ok(()) } fn create_white_1x1_texture_object( object_store: &mut ObjectStore, render_pass: &mut RenderPass, ) -> ObjectId { let white_1x1_tex_obj_id = ObjectId::new_sequential(); object_store.insert_pending(white_1x1_tex_obj_id); render_pass.commands.push(Command::CreateTexture { obj_id: white_1x1_tex_obj_id, pixel_data_format: TexturePixelDataFormat::Rgba( RgbaTextureDataType::UnsignedByte, ), creation: TextureCreation::Texture2D { size: Dimens { width: 1, height: 1 }, image: Some( Image::from_color(Rgba::::white(), Dimens { width: 1, height: 1 }) .into_bytes() .into_boxed_slice(), ), }, properties: TextureProperties::default(), }); white_1x1_tex_obj_id } #[derive(Debug)] enum ShadersCreationStatus<'shader_ctx> { ProcessingRequired, Ready(ReadyShaders<'shader_ctx>), } #[derive(Debug)] struct ReadyShaders<'shader_ctx> { main_3d_shader_obj_id: ObjectId, main_3d_shader_program: &'shader_ctx ShaderProgram, sky_box_shader_obj_id: ObjectId, sky_box_shader_program: &'shader_ctx ShaderProgram, } fn get_or_create_shaders<'shader_ctx>( assets: &mut Assets, shader_context: &'shader_ctx ShaderContext, object_store: &mut ObjectStore, render_pass: &mut RenderPass, ) -> Result, crate::Error> { let main_3d_shader_asset = assets .get_handle_to_loaded::(MAIN_3D_SHADER_ASSET_LABEL.clone()) .or_else(|| { assets.store_with_label( MAIN_3D_SHADER_ASSET_LABEL.clone(), ShaderModuleSource { name: "main_3d_shader.slang".into(), file_path: Path::new("@engine/main_3d_shader").into(), source: include_str!("../../res/main_3d_shader.slang").into(), link_entrypoints: ShaderEntrypointFlags::VERTEX | ShaderEntrypointFlags::FRAGMENT, }, ); None }); let sky_box_shader_asset = assets .get_handle_to_loaded::(SKY_BOX_SHADER_ASSET_LABEL.clone()) .or_else(|| { assets.store_with_label( SKY_BOX_SHADER_ASSET_LABEL.clone(), ShaderModuleSource { name: "sky_box_shader.slang".into(), file_path: Path::new("@engine/sky_box_shader").into(), source: include_str!("../../res/sky_box_shader.slang").into(), link_entrypoints: ShaderEntrypointFlags::VERTEX | ShaderEntrypointFlags::FRAGMENT, }, ); None }); let (Some(main_3d_shader_asset), Some(sky_box_shader_asset)) = (main_3d_shader_asset, sky_box_shader_asset) else { return Ok(ShadersCreationStatus::ProcessingRequired); }; let main_3d_shader_program = shader_context.get_program(&main_3d_shader_asset.id()); let main_3d_shader_program = main_3d_shader_program.ok_or_else(|| { error!("Shader context doesn't have a program for main 3D shader") })?; let main_3d_shader_obj_id = ObjectId::Asset(main_3d_shader_asset.id()); if !object_store.contains_maybe_pending_with_id(&main_3d_shader_obj_id) { object_store.insert_pending(main_3d_shader_obj_id); render_pass.commands.push(Command::CreateShaderProgram( main_3d_shader_obj_id, main_3d_shader_program.clone(), )); } let sky_box_shader_program = shader_context.get_program(&sky_box_shader_asset.id()); let sky_box_shader_program = sky_box_shader_program.ok_or_else(|| { error!("Shader context doesn't have a program for sky box shader") })?; let sky_box_shader_obj_id = ObjectId::Asset(sky_box_shader_asset.id()); if !object_store.contains_maybe_pending_with_id(&sky_box_shader_obj_id) { object_store.insert_pending(sky_box_shader_obj_id); render_pass.commands.push(Command::CreateShaderProgram( sky_box_shader_obj_id, sky_box_shader_program.clone(), )); } Ok(ShadersCreationStatus::Ready(ReadyShaders { main_3d_shader_obj_id, main_3d_shader_program, sky_box_shader_obj_id, sky_box_shader_program, })) } fn add_renderable_creation_commands( assets: &Assets, object_store: &mut ObjectStore, render_pass: &mut RenderPass, model: &Model, ) { let Some(model_spec) = assets.get(&model.spec_asset) else { return; }; let Some(mesh_asset) = &model_spec.mesh_asset else { return; }; if assets.get(mesh_asset).is_none() { return; } debug_assert!(model_spec.material_names.len() <= 1); let model_material = match model_spec.find_first_material(&assets) { MaterialSearchResult::Found(model_material_asset) => { let Some(model_material) = assets.get(&model_material_asset) else { return; }; model_material } MaterialSearchResult::NotFound => { return; } MaterialSearchResult::NoMaterials => &Material::builder().build(), }; for texture_asset in model_material.textures() { let Some(texture) = assets.get(texture_asset) else { return; }; let Texture::Texture2D(texture) = texture else { tracing::error!( texture_asset_id = ?texture_asset.id(), texture_asset_label = ?assets.get_label(texture_asset), "Material texture map is not 2D" ); return; }; let texture_object_id = ObjectId::Asset(texture_asset.id()); if object_store.contains_maybe_pending_with_id(&texture_object_id) { return; } let Some(tex_pixel_data_format) = TexturePixelDataFormat::for_image(&texture.image) else { tracing::error!("No texture pixel data format is available for image"); return; }; object_store.insert_pending(texture_object_id); render_pass.commands.push(Command::CreateTexture { obj_id: texture_object_id, pixel_data_format: tex_pixel_data_format, creation: TextureCreation::Texture2D { size: texture.image.dimensions(), image: Some(texture.image.as_bytes().to_vec().into_boxed_slice()), }, properties: texture.properties.clone(), }); } if !object_store.contains_maybe_pending_with_id(&ObjectId::Asset(mesh_asset.id())) { object_store.insert_pending(ObjectId::Asset(mesh_asset.id())); render_pass.commands.push(Command::CreateMesh { obj_id: ObjectId::Asset(mesh_asset.id()), mesh: AssetOrValue::Asset(mesh_asset.clone()), usage: MeshUsage::Static, }); } } fn add_set_3d_shader_point_light_bindings( render_pass: &mut RenderPass, point_light_query: &Query<( &PointLight, &Transform, Traverse<(With, With), TraverseUp, ChildOf>, )>, main_3d_shader_obj_id: ObjectId, main_3d_shader_cursor: &ShaderCursor<'_>, ) -> Result<(), crate::Error> { let lighting_shader_cursor = main_3d_shader_cursor.field("Uniforms").field("lighting"); render_pass .commands .reserve(1 + point_light_query.iter().count() * 6); render_pass.commands.push(Command::SetShaderBinding( main_3d_shader_obj_id, lighting_shader_cursor.field("point_light_cnt").binding( u32::try_from(point_light_query.iter().count()) .expect("Point light count does not fit in 32-bit unsigned integer") .into(), )?, )); for (point_light_index, (point_light, point_light_transform)) in point_light_query.iter().enumerate() { let point_light_shader_cursor = lighting_shader_cursor .field("point_lights") .element(point_light_index); let phong_shader_cursor = point_light_shader_cursor.field("phong"); let attenuation_props_shader_cursor = point_light_shader_cursor.field("attenuation_props"); render_pass.commands.extend( [ phong_shader_cursor .field("diffuse") .binding(point_light.diffuse.to_rgb_lossy().into())?, phong_shader_cursor .field("specular") .binding(point_light.specular.to_rgb_lossy().into())?, point_light_shader_cursor.field("position").binding( (point_light_transform.position + point_light.local_position).into(), )?, attenuation_props_shader_cursor .field("constant") .binding(point_light.attenuation_params.constant.into())?, attenuation_props_shader_cursor .field("linear") .binding(point_light.attenuation_params.linear.into())?, attenuation_props_shader_cursor .field("quadratic") .binding(point_light.attenuation_params.quadratic.into())?, ] .map(|binding| Command::SetShaderBinding(main_3d_shader_obj_id, binding)), ); } Ok(()) } fn add_set_3d_shader_dir_lights_bindings( render_pass: &mut RenderPass, directional_light_query: &Query<( &DirectionalLight, Traverse<(With, With), TraverseUp, ChildOf>, )>, main_3d_shader_obj_id: ObjectId, main_3d_shader_cursor: &ShaderCursor<'_>, ) -> Result<(), crate::Error> { let lighting_shader_cursor = main_3d_shader_cursor.field("Uniforms").field("lighting"); render_pass .commands .reserve(1 + directional_light_query.iter().count() * 3); render_pass.commands.push(Command::SetShaderBinding( main_3d_shader_obj_id, lighting_shader_cursor .field("directional_light_cnt") .binding( u32::try_from(directional_light_query.iter().count()) .expect( "Directional light count does not fit in 32-bit unsigned integer", ) .into(), )?, )); for (directional_light_index, (directional_light,)) in directional_light_query.iter().enumerate() { let directional_light_shader_cursor = lighting_shader_cursor .field("directional_lights") .element(directional_light_index); let phong_shader_cursor = directional_light_shader_cursor.field("phong"); render_pass.commands.extend( [ phong_shader_cursor .field("diffuse") .binding(directional_light.diffuse.to_rgb_lossy().into())?, phong_shader_cursor .field("specular") .binding(directional_light.specular.to_rgb_lossy().into())?, directional_light_shader_cursor .field("direction") .binding(directional_light.direction.into())?, ] .map(|binding| Command::SetShaderBinding(main_3d_shader_obj_id, binding)), ); } Ok(()) } fn add_set_3d_shader_camera_bindings( render_pass: &mut RenderPass, (camera, camera_transform): (&Camera, &Transform), window: &Window, main_3d_shader_obj_id: ObjectId, main_3d_shader_cursor: &ShaderCursor<'_>, ) -> Result<(), crate::Error> { let model_3d_shader_cursor = main_3d_shader_cursor.field("Uniforms").field("model_3d"); let lighting_shader_cursor = main_3d_shader_cursor.field("Uniforms").field("lighting"); render_pass.commands.extend( [ model_3d_shader_cursor .field("view") .binding(camera.to_view_matrix(camera_transform.position).into())?, model_3d_shader_cursor.field("projection").binding( camera .projection .to_matrix_rh(window.inner_size, ProjectionClipVolume::NegOneToOne) .into(), )?, lighting_shader_cursor .field("view_pos") .binding(camera_transform.position.into())?, ] .into_iter() .map(|binding| Command::SetShaderBinding(main_3d_shader_obj_id, binding)), ); Ok(()) } fn add_set_3d_shader_renderable_bindings( render_pass: &mut RenderPass, renderable: (&Material, Option<&MaterialFlags>, Option<&Transform>), scene_env_lighting: Option<&EnvironmentalLighting>, main_3d_shader_obj_id: ObjectId, main_3d_shader_cursor: &ShaderCursor<'_>, white_1x1_tex_obj_id: ObjectId, ) -> Result<(), crate::Error> { let (material, material_flags, transform) = renderable; let transform = match transform.as_deref() { Some(transform) => transform, None => &Transform::default(), }; let model_matrix = transform.to_matrix(); let inverted_model_matrix = model_matrix.inverse(); let material_flags = material_flags .as_deref() .unwrap_or(&const { MaterialFlags::builder().build() }); let env_lighting = match &scene_env_lighting { Some(env_lighting) => &env_lighting, None => &EnvironmentalLighting::default(), }; let model_3d_shader_cursor = main_3d_shader_cursor.field("Uniforms").field("model_3d"); let material_shader_cursor = main_3d_shader_cursor .field("Uniforms") .field("lighting") .field("material"); render_pass.commands.extend( [ model_3d_shader_cursor .field("model") .binding(model_matrix.into())?, model_3d_shader_cursor .field("model_inverted") .binding(inverted_model_matrix.into())?, material_shader_cursor.field("ambient").binding( material_flags .use_ambient_color .then_some(&material.ambient) .unwrap_or(&env_lighting.ambient_color) .to_rgb_lossy() .into(), )?, material_shader_cursor .field("diffuse") .binding(material.diffuse.to_rgb_lossy().into())?, material_shader_cursor .field("specular") .binding(material.specular.to_rgb_lossy().into())?, main_3d_shader_cursor.field("ambient_map").binding( ShaderBindingValue::Texture( material .ambient_map .as_ref() .map(|ambient_map| ObjectId::Asset(ambient_map.id())) .unwrap_or(white_1x1_tex_obj_id), ShaderBindingTextureKind::Texture2D, ), )?, main_3d_shader_cursor.field("diffuse_map").binding( ShaderBindingValue::Texture( material .diffuse_map .as_ref() .map(|diffuse_map| ObjectId::Asset(diffuse_map.id())) .unwrap_or(white_1x1_tex_obj_id), ShaderBindingTextureKind::Texture2D, ), )?, main_3d_shader_cursor.field("specular_map").binding( ShaderBindingValue::Texture( material .specular_map .as_ref() .map(|specular_map| ObjectId::Asset(specular_map.id())) .unwrap_or(white_1x1_tex_obj_id), ShaderBindingTextureKind::Texture2D, ), )?, material_shader_cursor .field("shininess") .binding(material.shininess.into())?, ] .into_iter() .map(|binding| Command::SetShaderBinding(main_3d_shader_obj_id, binding)), ); Ok(()) } fn add_sky_box_commands( render_pass: &mut RenderPass, sky_box_ids: &SkyBoxIds, camera: &Camera, camera_transform: &Transform, window: &Window, sky_box_shader_obj_id: ObjectId, sky_box_shader_program: &ShaderProgram, ) -> Result<(), crate::Error> { render_pass .commands .push(Command::ActivateShader(sky_box_shader_obj_id)); let sky_box_shader_cursor = ShaderCursor::new( sky_box_shader_program .reflection(0) .unwrap() .global_params_var_layout() .unwrap(), ); let mut view = camera.to_view_matrix(camera_transform.position); view.translate(&Vec3 { x: 0.0, y: 0.0, z: 0.0 }); let sky_box_shader_bindings = [ sky_box_shader_cursor .field("Uniforms") .field("projection") .binding( (match camera.projection { Projection::Perspective(_) => camera.projection.to_matrix_rh( window.inner_size, ProjectionClipVolume::NegOneToOne, ), Projection::Orthographic(_) => { // A orthographic projection cannot be used for a sky box Perspective::default().to_matrix_rh( window.inner_size.width as f32 / window.inner_size.height as f32, ProjectionClipVolume::NegOneToOne, ) } }) .into(), )?, sky_box_shader_cursor .field("Uniforms") .field("view") .binding(view.into())?, sky_box_shader_cursor.field("cube_texture").binding( ShaderBindingValue::Texture( sky_box_ids.texture_object, ShaderBindingTextureKind::Cube, ), )?, ]; for binding in sky_box_shader_bindings { render_pass .commands .push(Command::SetShaderBinding(sky_box_shader_obj_id, binding)); } render_pass.commands.push(Command::UpdateDrawProperties( DrawProperties { depth_function: DepthFunction::LessOrEqual, ..Default::default() }, DrawPropertiesUpdateFlags::DEPTH_FUNCTION, )); render_pass.commands.push(Command::DrawMesh( sky_box_ids.mesh_object, DrawMeshOptions::default(), )); Ok(()) } fn load_sky_box( scene_ent_id: Uid, sky_box: &SkyBox, sky_box_state: Option<&SkyBoxState>, assets: &mut Assets, object_store: &mut ObjectStore, render_pass: &mut RenderPass, actions: &mut Actions, ) -> Result, crate::Error> { let mesh_object_id = match &sky_box_state { Some(sky_box_state) => sky_box_state.mesh_object_id, None => { let sky_box_mesh = Mesh::cube(Dimens3 { width: 1.0, height: 1.0, depth: 1.0 }); let mesh_object_id = ObjectId::new_sequential(); object_store.insert_pending(mesh_object_id); render_pass.commands.push(Command::CreateMesh { obj_id: mesh_object_id, mesh: AssetOrValue::Value(sky_box_mesh), usage: MeshUsage::Static, }); mesh_object_id } }; let texture_object_id = match &sky_box { SkyBox::AssetPerCubeMapFace(cube_map_part_assets) => { if let Some(sky_box_state) = sky_box_state { sky_box_state.texture_object_id } else { let Some(part_images) = cube_map_part_assets .iter() .map_while(|(face, part_texture_asset)| { let part_texture = assets.get(part_texture_asset)?; match part_texture { Texture::Texture2D(part_texture) => { Some((*face, &part_texture.image)) } Texture::CubeMap(_) => { tracing::warn!( "Cube map texture cannot be used as cube map part" ); None } } }) .collect::>() .as_array::<6>() .cloned() else { return Ok(None); }; let texture_object_id = ObjectId::new_sequential(); object_store.insert_pending(texture_object_id); render_pass.commands.push(Command::CreateTexture { obj_id: texture_object_id, pixel_data_format: TexturePixelDataFormat::for_image( part_images[0].1, ) .expect("No texture pixel data format is available for image"), creation: TextureCreation::CubeMap { size: part_images[0].1.dimensions(), images: Some(part_images.map(|(face, image)| { (face, image.as_bytes().to_vec().into_boxed_slice()) })), }, properties: TextureProperties::builder() .minifying_filter(TextureFiltering::Linear) .magnifying_filter(TextureFiltering::Linear) .wrap(TextureWrapping::ClampToEdge) .build(), }); texture_object_id } } }; if sky_box_state.is_none() { actions.add_components( scene_ent_id, (SkyBoxState { texture_object_id, mesh_object_id },), ); } Ok(Some(SkyBoxIds { texture_object: texture_object_id, mesh_object: mesh_object_id, })) } #[derive(Debug, Component)] pub struct SkyBoxState { texture_object_id: ObjectId, mesh_object_id: ObjectId, }