bevy_lit 0.9.1

A lighting 2d library for Bevy
Documentation
use bevy::{
    ecs::{query::QueryItem, system::lifetimeless::Read},
    prelude::*,
    render::{
        camera::ExtractedCamera,
        extract_component::{ComponentUniforms, DynamicUniformIndex},
        render_graph::{NodeRunError, RenderGraphContext, ViewNode},
        render_resource::{
            BindGroupEntries, CachedRenderPipelineId, Operations, PipelineCache,
            RenderPassColorAttachment, RenderPassDescriptor, SamplerDescriptor, UniformBuffer,
        },
        renderer::{RenderContext, RenderQueue},
        view::{ExtractedView, ViewTarget, ViewUniformOffset, ViewUniforms},
    },
};

use crate::{
    post_process::render::{
        ExtractedLighting2dSettings, Lighting2dCompositePipeline, Lighting2dCompositePipelineId,
        Lighting2dPostProcessPipelines,
    },
    render::{FlipTexture, LightingTextures, VoronoiTextures},
    settings::PenetrationSettings,
};

pub fn run_penetration_pass<'w>(
    world: &'w World,
    render_context: &mut RenderContext<'w>,
    camera: &ExtractedCamera,
    lighting_texture: &mut FlipTexture,
    voronoi_texture: &FlipTexture,
    view_uniform_offset: u32,
    settings_uniform_offset: u32,
) {
    let post_process_pipelines = world.resource::<Lighting2dPostProcessPipelines>();
    let pipeline_cache = world.resource::<PipelineCache>();

    let (Some(pipeline), Some(view_uniforms), Some(lighting_settings_uniforms)) = (
        pipeline_cache.get_render_pipeline(post_process_pipelines.penetration_pipeline),
        world.resource::<ViewUniforms>().uniforms.binding(),
        world
            .resource::<ComponentUniforms<ExtractedLighting2dSettings>>()
            .binding(),
    ) else {
        return;
    };

    let sampler = render_context
        .render_device()
        .create_sampler(&SamplerDescriptor::default());

    let bind_group = render_context.render_device().create_bind_group(
        "penetration_bind_group",
        &post_process_pipelines.penetration_layout,
        &BindGroupEntries::sequential((
            view_uniforms,
            lighting_settings_uniforms,
            &lighting_texture.input().default_view,
            &voronoi_texture.input().default_view,
            &sampler,
        )),
    );

    let mut pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
        label: Some("penetration_pass"),
        color_attachments: &[Some(RenderPassColorAttachment {
            view: &lighting_texture.output().default_view,
            resolve_target: None,
            ops: Operations::default(),
            depth_slice: None,
        })],
        ..default()
    });

    if let Some(viewport) = camera.viewport.as_ref() {
        pass.set_camera_viewport(viewport);
    }

    pass.set_render_pipeline(pipeline);
    pass.set_bind_group(
        0,
        &bind_group,
        &[view_uniform_offset, settings_uniform_offset],
    );
    pass.draw(0..3, 0..1);

    lighting_texture.flip()
}

pub fn run_blur_pass<'w>(
    world: &'w World,
    render_context: &mut RenderContext<'w>,
    lighting_texture: &mut FlipTexture,
    settings_uniform_offset: u32,
    direction: IVec2,
) {
    let post_process_pipelines = world.resource::<Lighting2dPostProcessPipelines>();
    let pipeline_cache = world.resource::<PipelineCache>();

    let mut direction = UniformBuffer::from(direction);
    direction.write_buffer(
        render_context.render_device(),
        world.resource::<RenderQueue>(),
    );
    let (Some(pipeline), Some(lighting_settings_uniforms), Some(direction)) = (
        pipeline_cache.get_render_pipeline(post_process_pipelines.blur_pipeline),
        world
            .resource::<ComponentUniforms<ExtractedLighting2dSettings>>()
            .binding(),
        direction.binding(),
    ) else {
        return;
    };

    let bind_group = render_context.render_device().create_bind_group(
        "blur_bind_group",
        &post_process_pipelines.blur_layout,
        &BindGroupEntries::sequential((
            lighting_settings_uniforms,
            direction,
            &lighting_texture.input().default_view,
        )),
    );

    let mut pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
        label: Some("blur_pass"),
        color_attachments: &[Some(RenderPassColorAttachment {
            view: &lighting_texture.output().default_view,
            resolve_target: None,
            ops: Operations::default(),
            depth_slice: None,
        })],
        ..default()
    });

    pass.set_render_pipeline(pipeline);
    pass.set_bind_group(0, &bind_group, &[settings_uniform_offset]);
    pass.draw(0..3, 0..1);

    lighting_texture.flip();
}

pub fn run_composite_pass<'w>(
    world: &'w World,
    render_context: &mut RenderContext<'w>,
    lighting_texture: &mut FlipTexture,
    view_target: &ViewTarget,
    pipeline_id: CachedRenderPipelineId,
    settings_uniform_offset: u32,
) {
    let pipeline_cache = world.resource::<PipelineCache>();

    let (Some(pipeline), Some(lighting_settings_uniforms)) = (
        pipeline_cache.get_render_pipeline(pipeline_id),
        world
            .resource::<ComponentUniforms<ExtractedLighting2dSettings>>()
            .binding(),
    ) else {
        return;
    };

    let post_process = view_target.post_process_write();
    let sampler = render_context
        .render_device()
        .create_sampler(&SamplerDescriptor::default());

    let bind_group = render_context.render_device().create_bind_group(
        "composite_bind_group",
        &world.resource::<Lighting2dCompositePipeline>().layout,
        &BindGroupEntries::sequential((
            lighting_settings_uniforms,
            post_process.source,
            &lighting_texture.input().default_view,
            &sampler,
        )),
    );

    let mut pass = render_context.begin_tracked_render_pass(RenderPassDescriptor {
        label: Some("composite_pass"),
        color_attachments: &[Some(RenderPassColorAttachment {
            view: post_process.destination,
            resolve_target: None,
            ops: Operations::default(),
            depth_slice: None,
        })],
        ..default()
    });

    pass.set_render_pipeline(pipeline);
    pass.set_bind_group(0, &bind_group, &[settings_uniform_offset]);
    pass.draw(0..3, 0..1);
}

#[derive(Default)]
pub struct Light2dPostProcessDrawNode;
impl ViewNode for Light2dPostProcessDrawNode {
    type ViewQuery = (
        Read<ExtractedView>,
        Read<ViewTarget>,
        Read<ExtractedCamera>,
        Read<ViewUniformOffset>,
        Read<Lighting2dCompositePipelineId>,
        Read<DynamicUniformIndex<ExtractedLighting2dSettings>>,
        Read<ExtractedLighting2dSettings>,
    );

    fn run<'w>(
        &self,
        _: &mut RenderGraphContext,
        render_context: &mut RenderContext<'w>,
        (
            view,
            view_target,
            camera,
            view_uniform_offset,
            composite_pipeline_id,
            settings_uniform_index,
            lighting_settings,
        ): QueryItem<'w, '_, Self::ViewQuery>,
        world: &'w World,
    ) -> std::result::Result<(), NodeRunError> {
        let mut lighting_texture = world
            .resource::<LightingTextures>()
            .get(&view.retained_view_entity)
            .expect(&format!(
                "Expected the lighting texture for view {:?} to exist",
                view.retained_view_entity.main_entity.id()
            ))
            .clone();
        let voronoi_texture = world
            .resource::<VoronoiTextures>()
            .get(&view.retained_view_entity)
            .expect(&format!(
                "Expected the voronoi texture for view {:?} to exist",
                view.retained_view_entity.main_entity.id()
            ))
            .clone();

        if should_run_penetration_pass(&lighting_settings.penetration) {
            run_penetration_pass(
                world,
                render_context,
                camera,
                &mut lighting_texture,
                &voronoi_texture,
                view_uniform_offset.offset,
                settings_uniform_index.index(),
            );
        }

        if lighting_settings.blur > 0 {
            run_blur_pass(
                world,
                render_context,
                &mut lighting_texture,
                settings_uniform_index.index(),
                IVec2::new(1, 0),
            );
            run_blur_pass(
                world,
                render_context,
                &mut lighting_texture,
                settings_uniform_index.index(),
                IVec2::new(0, 1),
            );
        }

        run_composite_pass(
            world,
            render_context,
            &mut lighting_texture,
            view_target,
            composite_pipeline_id.0,
            settings_uniform_index.index(),
        );

        Ok(())
    }
}

fn should_run_penetration_pass(penetration: &PenetrationSettings) -> bool {
    penetration.max > 0.0
        && penetration.intensity > 0.0
        && penetration.sample_directions > 0
        && penetration.sample_steps > 0
}