bevy_fast_light 0.9.0

Simple 2D lighting for Bevy focused on performance over features.
Documentation
/*
 * Heavily inspired by:
 * - https://bevy.org/examples/shaders/custom-post-processing/
 * - https://github.com/jgayfer/bevy_light_2d
 */

//! [`ViewNode`]s for light occlusion.

use bevy::{
    ecs::{query::QueryItem, world::World},
    log::error,
    render::{
        render_graph::{NodeRunError, RenderGraphContext, ViewNode},
        render_phase::ViewBinnedRenderPhases,
        render_resource::*,
        renderer::RenderContext,
        view::ExtractedView,
    },
};

use crate::{extract::prelude::*, occluder::prelude::*};

/// [`ViewNode`] that renders occluders to a texture from [`OccluderTextures`].
#[derive(Default)]
pub(super) struct OccluderNode;
impl ViewNode for OccluderNode {
    type ViewQuery = (&'static ExtractedView, &'static ExtractedAmbientLight2d);

    fn run(
        &self,
        graph: &mut RenderGraphContext,
        render_context: &mut RenderContext,
        (extracted_view, _): QueryItem<Self::ViewQuery>,
        world: &World,
    ) -> Result<(), NodeRunError> {
        let view_entity = graph.view_entity();
        let occluder_phases = world.resource::<ViewBinnedRenderPhases<OccluderPhase>>();
        let occluder_textures = world.resource::<OccluderTextures>();
        let (Some(occluder_phase), Some(occluder_texture)) = (
            occluder_phases.get(&extracted_view.retained_view_entity),
            occluder_textures
                .0
                .get(&extracted_view.retained_view_entity),
        ) else {
            return Ok(());
        };

        let mut render_pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
            label: Some("occluder_render_pass"),
            color_attachments: &[Some(RenderPassColorAttachment {
                view: &occluder_texture.default_view,
                depth_slice: None,
                resolve_target: None,
                // NOTE: We need to load here because we need the value written by `sprite_depth`.
                ops: Operations {
                    load: LoadOp::Load,
                    store: StoreOp::Store,
                },
            })],
            depth_stencil_attachment: None,
            timestamp_writes: None,
            occlusion_query_set: None,
        });

        if let Err(err) = occluder_phase.render(&mut render_pass, world, view_entity) {
            error!("Error for occluder_phase in OccluderNode {err:?}");
        }

        Ok(())
    }
}