diff options
Diffstat (limited to 'engine/src/rendering/main_render_pass.rs')
| -rw-r--r-- | engine/src/rendering/main_render_pass.rs | 1006 |
1 files changed, 1006 insertions, 0 deletions
diff --git a/engine/src/rendering/main_render_pass.rs b/engine/src/rendering/main_render_pass.rs new file mode 100644 index 0000000..c762231 --- /dev/null +++ b/engine/src/rendering/main_render_pass.rs @@ -0,0 +1,1006 @@ +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<Scene>, With<ActiveScene>), TraverseUp, ChildOf>, + Option<&'a MaterialFlags>, + Option<&'a Transform>, + Option<&'a DrawFlags>, + Without<NoDraw>, +); + +pub static SKY_BOX_SHADER_ASSET_LABEL: LazyLock<AssetLabel> = + LazyLock::new(|| AssetLabel { + path: Path::new("").into(), + name: Some("sky_box_shader".into()), + }); + +pub static MAIN_3D_SHADER_ASSET_LABEL: LazyLock<AssetLabel> = + 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<ObjectId>, +} + +#[tracing::instrument(skip_all)] +pub fn add_main_render_pass( + renderable_query: Query<RenderableEntity<'_>>, + camera_query: Query<( + &Camera, + &Transform, + &ActiveCamera, + Traverse<(With<Scene>, With<ActiveScene>), TraverseUp, ChildOf>, + )>, + window_query: Query<(&Window, &Surface, With<TargetWindow>)>, + scene_query: Query<( + Option<&EnvironmentalLighting>, + Option<&SkyBox>, + Option<&SkyBoxState>, + With<Scene>, + With<ActiveScene>, + )>, + point_light_query: Query<( + &PointLight, + &Transform, + Traverse<(With<Scene>, With<ActiveScene>), TraverseUp, ChildOf>, + )>, + directional_light_query: Query<( + &DirectionalLight, + Traverse<(With<Scene>, With<ActiveScene>), TraverseUp, ChildOf>, + )>, + shader_context: Single<ShaderContext>, + mut assets: Single<Assets>, + mut render_passes: Single<RenderPasses>, + mut object_store: Single<ObjectStore>, + mut actions: Actions, + mut misc_object_ids: Local<MiscObjectIds>, +) -> 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::<u8>::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<ShadersCreationStatus<'shader_ctx>, crate::Error> +{ + let main_3d_shader_asset = assets + .get_handle_to_loaded::<ShaderModuleSource>(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::<ShaderModuleSource>(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.materials.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<Scene>, With<ActiveScene>), 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<Scene>, With<ActiveScene>), 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"); + + let diffuse_map_obj_id = material + .diffuse_map + .as_ref() + .map(|diffuse_map| ObjectId::Asset(diffuse_map.id())) + .unwrap_or(white_1x1_tex_obj_id); + + 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(diffuse_map_obj_id), + ShaderBindingTextureKind::Texture2D, + ), + )?, + main_3d_shader_cursor.field("diffuse_map").binding( + ShaderBindingValue::Texture( + diffuse_map_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<Option<SkyBoxIds>, 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::<Vec<_>>() + .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, +} |
