nightshade 0.14.1

A cross-platform data-oriented game engine.
Documentation
use super::passes;
use crate::render::wgpu::rendergraph::{
    render_graph_get_pass_mut, render_graph_resize_all_transient_proportional,
};

impl super::WgpuRenderer {
    pub(super) fn cleanup_unused_camera_viewports(
        &mut self,
        active_camera_entities: &std::collections::HashSet<freecs::Entity>,
    ) {
        self.camera_viewports
            .retain(|entity, _| active_camera_entities.contains(entity));
    }

    pub(super) fn extract_frame_dirty_state(&mut self, world: &mut crate::ecs::world::World) {
        use crate::ecs::prefab::resources::{mesh_cache_take_dirty, mesh_cache_take_dirty_skinned};

        let frame_state = world.resources.mesh_render_state.take_frame_state();
        let dirty_meshes = mesh_cache_take_dirty(&mut world.resources.assets.mesh_cache);
        let dirty_skinned = mesh_cache_take_dirty_skinned(&mut world.resources.assets.mesh_cache);

        if let Some(mesh_pass) = render_graph_get_pass_mut(&mut self.graph, "mesh_pass")
            && let Some(pass) =
                (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
        {
            pass.frame_dirty = Some(frame_state);
            let dirty_ids: std::collections::HashSet<u32> = dirty_meshes
                .iter()
                .filter_map(|name| pass.mesh_id_for_name(name))
                .collect();
            pass.dirty_mesh_ids = dirty_ids;
        }

        if let Some(skinned_pass) = render_graph_get_pass_mut(&mut self.graph, "skinned_mesh_pass")
            && let Some(pass) = (skinned_pass as &mut dyn std::any::Any)
                .downcast_mut::<passes::geometry::SkinnedMeshPass>()
        {
            pass.dirty_skinned_mesh_names = dirty_skinned;
        }
    }

    pub(super) fn resize_render_buffers_for_window(&mut self, width: u32, height: u32) {
        let already_dispatched_at_this_size = self
            .window_render_state
            .recent_buffer_sizes
            .contains(&(width, height));
        if already_dispatched_at_this_size && self.render_buffer_size == (width, height) {
            return;
        }
        if !already_dispatched_at_this_size {
            self.window_render_state
                .recent_buffer_sizes
                .push((width, height));
            if self.window_render_state.recent_buffer_sizes.len()
                > super::RECENT_BUFFER_SIZE_CAPACITY
            {
                self.window_render_state.recent_buffer_sizes.remove(0);
            }
        }
        self.resize_render_buffers(width, height);
    }

    pub(super) fn record_window_dispatch(&mut self, settings_version: u64) {
        self.window_render_state.last_settings_version = Some(settings_version);
    }

    pub(super) fn resize_render_buffers(&mut self, width: u32, height: u32) {
        if self.render_buffer_size == (width, height) {
            return;
        }

        let width = width.max(1);
        let height = height.max(1);

        let (old_width, old_height) = self.render_buffer_size;
        let _ = render_graph_resize_all_transient_proportional(
            &mut self.graph,
            old_width,
            old_height,
            width,
            height,
        );

        if let Some(mesh_pass) = render_graph_get_pass_mut(&mut self.graph, "mesh_pass")
            && let Some(mesh_pass) =
                (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
        {
            mesh_pass.resize(&self.device, width, height);
        }

        if let Some(bloom_pass) = render_graph_get_pass_mut(&mut self.graph, "bloom_pass")
            && let Some(bloom_pass) =
                (bloom_pass as &mut dyn std::any::Any).downcast_mut::<passes::BloomPass>()
        {
            bloom_pass.resize(&self.device, &self.queue, width, height);
        }

        #[cfg(feature = "debug_render")]
        if let Some(outline_pass) = render_graph_get_pass_mut(&mut self.graph, "outline_pass")
            && let Some(outline_pass) =
                (outline_pass as &mut dyn std::any::Any).downcast_mut::<passes::OutlinePass>()
        {
            outline_pass.resize(width, height);
        }

        self.render_buffer_size = (width, height);
    }

    pub fn device(&self) -> &wgpu::Device {
        &self.device
    }

    pub fn queue(&self) -> &wgpu::Queue {
        &self.queue
    }

    pub fn surface_format(&self) -> wgpu::TextureFormat {
        self.surface_format
    }

    pub fn surface_size(&self) -> (u32, u32) {
        (self.surface_config.width, self.surface_config.height)
    }

    pub fn cleanup_world(&mut self, world_id: u64) {
        if let Some(mesh_pass) = render_graph_get_pass_mut(&mut self.graph, "mesh_pass")
            && let Some(mesh_pass) =
                (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
        {
            mesh_pass.cleanup_world_state(world_id);
        }
    }

    pub fn cleanup_stale_worlds(&mut self, max_age_frames: u64) {
        if let Some(mesh_pass) = render_graph_get_pass_mut(&mut self.graph, "mesh_pass")
            && let Some(mesh_pass) =
                (mesh_pass as &mut dyn std::any::Any).downcast_mut::<passes::MeshPass>()
        {
            mesh_pass.cleanup_stale_world_states(max_age_frames);
        }
    }
}

fn compute_ui_text_signature(text_instance: &crate::ecs::ui::types::UiTextInstance) -> u64 {
    use std::hash::{Hash, Hasher};
    let mut hasher = std::collections::hash_map::DefaultHasher::new();
    text_instance.text.hash(&mut hasher);
    text_instance.font_size.to_bits().hash(&mut hasher);
    (text_instance.alignment as u8).hash(&mut hasher);
    (text_instance.vertical_alignment as u8).hash(&mut hasher);
    text_instance.monospace.hash(&mut hasher);
    text_instance
        .wrap_width
        .map(|w| w.to_bits())
        .unwrap_or(u32::MAX)
        .hash(&mut hasher);
    hasher.finish()
}

impl super::WgpuRenderer {
    pub(super) fn prepare_text_meshes(&mut self, world: &mut crate::ecs::world::World) {
        let pre_revision = self.glyph_atlas.revision;

        let mut dirty_entries: Vec<(
            freecs::Entity,
            usize,
            crate::ecs::text::components::TextProperties,
        )> = Vec::new();
        world
            .core
            .query()
            .with(crate::ecs::world::TEXT)
            .iter(|entity, table, idx| {
                let text = &table.text[idx];
                if text.dirty {
                    dirty_entries.push((entity, text.text_index, text.properties.clone()));
                }
            });
        for (entity, text_index, properties) in dirty_entries {
            let text_content = match world.resources.text.cache.get_text(text_index) {
                Some(t) => t.to_string(),
                None => continue,
            };
            let mesh = crate::render::wgpu::text_mesh::generate_text_mesh_world(
                &text_content,
                &mut crate::render::wgpu::text_mesh::TextMeshContext {
                    font_engine: &mut world.resources.text.font_engine,
                    glyph_atlas: &mut self.glyph_atlas,
                    device: &self.device,
                    queue: &self.queue,
                },
                &properties,
            );
            if let Some(text) = world.core.get_text_mut(entity) {
                text.cached_mesh = Some(mesh);
                text.dirty = false;
            }
        }

        let mut frame_text = std::mem::take(&mut world.resources.retained_ui.frame.text_meshes);
        let mut active_entities: std::collections::HashSet<freecs::Entity> =
            std::collections::HashSet::new();
        for instance in &mut frame_text {
            let signature = compute_ui_text_signature(instance);
            let cached = instance
                .entity
                .and_then(|e| self.text_mesh_signatures.get(&e).copied());
            if cached == Some(signature) {
                if let Some(entity) = instance.entity {
                    active_entities.insert(entity);
                }
                continue;
            }
            let properties = crate::ecs::text::components::TextProperties {
                font_size: instance.font_size,
                color: instance.color,
                alignment: instance.alignment,
                vertical_alignment: instance.vertical_alignment,
                line_height: 1.2,
                outline_color: instance.outline_color,
                outline_width: instance.outline_width,
                monospace_width: if instance.monospace { Some(0.0) } else { None },
                font_kind: instance.font_kind,
                ..crate::ecs::text::components::TextProperties::default()
            };
            instance.mesh = crate::render::wgpu::text_mesh::generate_text_mesh_ui(
                &instance.text,
                &mut crate::render::wgpu::text_mesh::TextMeshContext {
                    font_engine: &mut world.resources.text.font_engine,
                    glyph_atlas: &mut self.glyph_atlas,
                    device: &self.device,
                    queue: &self.queue,
                },
                &properties,
                instance.wrap_width,
            );
            if let Some(entity) = instance.entity {
                self.text_mesh_signatures.insert(entity, signature);
                active_entities.insert(entity);
            }
        }
        let stale: Vec<freecs::Entity> = self
            .text_mesh_signatures
            .keys()
            .copied()
            .filter(|e| !active_entities.contains(e))
            .collect();
        for entity in stale {
            self.text_mesh_signatures.remove(&entity);
        }
        world.resources.retained_ui.frame.text_meshes = frame_text;

        if self.glyph_atlas.revision != pre_revision {
            if let Some(text_pass) = render_graph_get_pass_mut(&mut self.graph, "text_pass")
                && let Some(text_pass) =
                    (text_pass as &mut dyn std::any::Any).downcast_mut::<passes::TextPass>()
            {
                text_pass.update_glyph_atlas(&self.device, &self.glyph_atlas.texture_view);
            }
            if let Some(ui_pass) = render_graph_get_pass_mut(&mut self.graph, "ui_pass")
                && let Some(ui_pass) =
                    (ui_pass as &mut dyn std::any::Any).downcast_mut::<passes::UiPass>()
            {
                ui_pass.update_glyph_atlas(&self.device, &self.glyph_atlas.texture_view);
            }
        }
    }
}