summaryrefslogtreecommitdiff
path: root/engine/src/rendering/main_render_pass.rs
diff options
context:
space:
mode:
Diffstat (limited to 'engine/src/rendering/main_render_pass.rs')
-rw-r--r--engine/src/rendering/main_render_pass.rs1006
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,
+}