bevy_dither_post_process 0.3.1

A post-process black and white ordered dithering effect for the Bevy game engine.
Documentation
#![warn(missing_docs)]

//! A plugin for the Bevy game engine which provides a black and white dither post-process effect
//! using Bayer ordered dithering.

use bevy::{
    asset::embedded_asset,
    core_pipeline::core_3d::graph::{Core3d, Node3d},
    prelude::*,
    render::{
        extract_component::ExtractComponentPlugin,
        render_graph::{RenderGraphApp, ViewNodeRunner},
        render_resource::{PipelineCache, SpecializedRenderPipelines, TextureFormat},
        view::{ExtractedView, ViewTarget},
        Render, RenderApp, RenderSet,
    },
};
use components::DitherPostProcessPipelineId;
use resources::{DitherPostProcessPipeline, DitherPostProcessingPipelineKey};

use crate::components::DitherPostProcessSettings;

pub use nodes::DitherRenderLabel;

/// Plugin which provides dither post-processing functionality
pub struct DitherPostProcessPlugin;

/// Components used by this plugin
pub mod components;

mod nodes;
mod resources;

impl Plugin for DitherPostProcessPlugin {
    fn build(&self, app: &mut App) {
        embedded_asset!(app, "../assets/shaders/dither_post_process.wgsl");

        app.add_plugins((ExtractComponentPlugin::<DitherPostProcessSettings>::default(),));

        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
            return;
        };

        render_app
            .add_systems(
                Render,
                prepare_post_processing_pipelines.in_set(RenderSet::Prepare),
            )
            .init_resource::<SpecializedRenderPipelines<DitherPostProcessPipeline>>()
            .add_render_graph_node::<ViewNodeRunner<nodes::DitherRenderNode>>(
                Core3d,
                nodes::DitherRenderLabel,
            )
            .add_render_graph_edges(
                Core3d,
                (
                    Node3d::Tonemapping,
                    nodes::DitherRenderLabel,
                    Node3d::EndMainPassPostProcessing,
                ),
            );
    }

    fn finish(&self, app: &mut App) {
        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
            return;
        };

        render_app.init_resource::<resources::DitherPostProcessPipeline>();
    }
}

fn prepare_post_processing_pipelines(
    mut commands: Commands,
    pipeline_cache: Res<PipelineCache>,
    mut pipelines: ResMut<SpecializedRenderPipelines<DitherPostProcessPipeline>>,
    post_processing_pipeline: Res<DitherPostProcessPipeline>,
    views: Query<(Entity, &ExtractedView), With<DitherPostProcessSettings>>,
) {
    for (entity, view) in views.iter() {
        let pipeline_id = pipelines.specialize(
            &pipeline_cache,
            &post_processing_pipeline,
            DitherPostProcessingPipelineKey {
                texture_format: if view.hdr {
                    ViewTarget::TEXTURE_FORMAT_HDR
                } else {
                    TextureFormat::bevy_default()
                },
            },
        );

        commands
            .entity(entity)
            .insert(DitherPostProcessPipelineId(pipeline_id));
    }
}