use std::path::Path; use std::sync::LazyLock; use ecs::actions::Actions; use ecs::pair::ChildOf; use ecs::query::term::{Traverse, TraverseUp}; use ecs::uid::Uid; use ecs::Component; use crate::asset::{Assets, Id as AssetId, Label as AssetLabel}; use crate::camera::{Active as ActiveCamera, Camera}; use crate::data_types::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::mesh::Mesh; use crate::model::{MaterialSearchResult, Model}; use crate::projection::ClipVolume as ProjectionClipVolume; 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::default::ASSET_LABEL as DEFAULT_SHADER_ASSET_LABEL; use crate::rendering::shader::{ Context as ShaderContext, EntrypointFlags as ShaderEntrypointFlags, ModuleSource as ShaderModuleSource, Shader, }; use crate::rendering::{ AssetOrValue, BufferClearMask, Command, DepthFunction, DrawMeshOptions, DrawProperties, DrawPropertiesUpdateFlags, MeshUsage, PendingShaderBindings, RenderPass, RenderPasses, SurfaceSpec, TargetWindow, }; use crate::scene::{Active as ActiveScene, Scene}; use crate::sky_box::SkyBox; use crate::texture::{ Filtering as TextureFiltering, Properties as TextureProperties, TexCubeMap, Texture, Wrapping as TextureWrapping, WHITE_1X1_ASSET_LABEL as TEXTURE_WHITE_1X1_ASSET_LABEL, }; use crate::transform::WorldPosition; use crate::vector::Vec3; use crate::windowing::window::Window; type RenderableEntity<'a> = ( &'a Model, Traverse<(With, With), TraverseUp, ChildOf>, Option<&'a DrawFlags>, Option<&'a Shader>, Option<&'a mut PendingShaderBindings>, Without, ); pub static SKY_BOX_SHADER_ASSET_LABEL: LazyLock = LazyLock::new(|| AssetLabel { path: Path::new("").into(), name: Some("sky_box_shader".into()), }); struct SkyBoxIds { texture_object: ObjectId, mesh_object: ObjectId, shader_object: ObjectId, shader_asset: AssetId, } #[tracing::instrument(skip_all)] pub fn add_main_render_pass( renderable_query: Query>, camera_query: Query<( &Camera, &WorldPosition, &ActiveCamera, Traverse<(With, With), TraverseUp, ChildOf>, )>, window_query: Query<(&Window, &SurfaceSpec, With)>, scene_query: Query<( Option<&SkyBox>, Option<&SkyBoxState>, With, With, )>, shader_context: Single, mut assets: Single, mut render_passes: Single, mut object_store: Single, mut actions: Actions, ) -> 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_skybox, scene_skybox_stuff))) = scene_query.iter_with_euids().next() else { return Ok(()); }; let Some((camera, camera_world_pos, _)) = camera_query.iter().next() else { tracing::trace!("No active camera"); return Ok(()); }; let Some(default_shader_asset) = assets .get_handle_to_loaded::(DEFAULT_SHADER_ASSET_LABEL.clone()) else { return Err(error!("Default shader asset is not loaded")); }; let render_pass = render_passes.passes.push_front_mut(RenderPass { commands: Vec::with_capacity(30), draw_properties: DrawProperties::default(), }); for (window, surface_spec) in &window_query { render_pass .commands .push(Command::MakeCurrent(surface_spec.id)); let default_texture_asset = assets .get_handle_to_loaded::(TEXTURE_WHITE_1X1_ASSET_LABEL.clone()) .expect("Not possible"); if !object_store .contains_maybe_pending_with_id(&ObjectId::Asset(default_texture_asset.id())) { object_store.insert_pending(ObjectId::Asset(default_texture_asset.id())); render_pass.commands.push(Command::CreateTexture( ObjectId::Asset(default_texture_asset.id()), AssetOrValue::Asset(default_texture_asset), )); } let sky_box_ids = if let Some(scene_skybox) = &scene_skybox { load_sky_box( scene_ent_id, &scene_skybox, scene_skybox_stuff.as_deref(), assets, object_store, shader_context, render_pass, &mut actions, )? } else { None }; let mut buf_clear_mask = BufferClearMask::DEPTH; buf_clear_mask.set(BufferClearMask::COLOR, sky_box_ids.is_none()); render_pass .commands .push(Command::ClearBuffers(buf_clear_mask)); for (model, draw_flags, shader, mut pending_shader_bindings) in &renderable_query { let shader_asset = match &shader { Some(shader) => &shader.asset_handle, None => &default_shader_asset, }; let Some(pending_shader_bindings) = pending_shader_bindings.as_mut() else { continue; }; if pending_shader_bindings.bindings.is_empty() && pending_shader_bindings.surface_specific_bindings.is_empty() { continue; } 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; } debug_assert!(model_spec.material_names.len() <= 1); let model_material_asset = match model_spec.find_first_material(&assets) { MaterialSearchResult::Found(model_material_asset) => { model_material_asset.clone() // Some(model_material_asset.clone()) } MaterialSearchResult::NotFound | MaterialSearchResult::NoMaterials => { // MaterialSearchResult::NotFound => { continue; } // MaterialSearchResult::NoMaterials => None, }; if !object_store .contains_maybe_pending_with_id(&ObjectId::Asset(shader_asset.id())) { let Some(shader_program) = shader_context.get_program(&shader_asset.id()) else { tracing::error!( "Shader context doesn't have a program for shader asset {:?}", assets.get_label(&shader_asset) ); continue; }; object_store.insert_pending(ObjectId::Asset(shader_asset.id())); render_pass.commands.push(Command::CreateShaderProgram( ObjectId::Asset(shader_asset.id()), shader_program.clone(), )); } render_pass .commands .push(Command::ActivateShader(ObjectId::Asset(shader_asset.id()))); let Some(model_material) = assets.get(&model_material_asset) else { // TODO: Handle this case since it may occur unreachable!(); }; for texture_asset in [ &model_material.ambient_map, &model_material.diffuse_map, &model_material.specular_map, ] .into_iter() .flatten() { if !object_store .contains_maybe_pending_with_id(&ObjectId::Asset(texture_asset.id())) { object_store.insert_pending(ObjectId::Asset(texture_asset.id())); render_pass.commands.push(Command::CreateTexture( ObjectId::Asset(texture_asset.id()), AssetOrValue::Asset(texture_asset.clone()), )); } } for shader_binding in &pending_shader_bindings.bindings { render_pass .commands .push(Command::SetShaderBinding(shader_binding.clone())); } for (shader_binding_surface_id, shader_binding) in &pending_shader_bindings.surface_specific_bindings { if *shader_binding_surface_id != surface_spec.id { continue; } render_pass .commands .push(Command::SetShaderBinding(shader_binding.clone())); } 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, )); } 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, }); } 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(sky_box_ids) = &sky_box_ids { render_pass .commands .push(Command::ActivateShader(sky_box_ids.shader_object)); let Some(sky_box_shader_program) = shader_context.get_program(&sky_box_ids.shader_asset) else { unreachable!(); }; 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_world_pos.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( camera .projection .to_matrix_rh( window.inner_size, 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(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, shader_context: &ShaderContext, render_pass: &mut RenderPass, actions: &mut Actions, ) -> Result, crate::Error> { let Some(sky_box_shader_asset) = assets .get_handle_to_loaded::(SKY_BOX_SHADER_ASSET_LABEL.clone()) 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, }, ); // shader has to be processed return Ok(None); }; if !object_store .contains_maybe_pending_with_id(&ObjectId::Asset(sky_box_shader_asset.id())) { let Some(shader_program) = shader_context.get_program(&sky_box_shader_asset.id()) else { tracing::error!( "Shader context doesn't have a program for sky box shader asset", ); return Ok(None); }; object_store.insert_pending(ObjectId::Asset(sky_box_shader_asset.id())); render_pass.commands.push(Command::CreateShaderProgram( ObjectId::Asset(sky_box_shader_asset.id()), shader_program.clone(), )); } 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( texture_object_id, AssetOrValue::Value(Texture::CubeMap(TexCubeMap { images: part_images.map(|(face, image)| (face, image.clone())), 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, shader_object: ObjectId::Asset(sky_box_shader_asset.id()), shader_asset: sky_box_shader_asset.id(), })) } #[derive(Debug, Component)] pub struct SkyBoxState { texture_object_id: ObjectId, mesh_object_id: ObjectId, }