use std::any::TypeId;
use bevy::anti_alias::fxaa::fxaa;
use bevy::anti_alias::smaa::smaa;
use bevy::asset::{load_internal_asset, AssetEventSystems};
use bevy::camera::visibility::{RenderLayers, VisibilitySystems};
use bevy::core_pipeline::tonemapping::tonemapping;
use bevy::core_pipeline::{Core3d, Core3dSystems};
use bevy::mesh::MeshVertexAttribute;
use bevy::pbr::{MeshInputUniform, MeshUniform};
use bevy::prelude::*;
use bevy::render::batching::gpu_preprocessing::{self, GpuPreprocessingSupport};
use bevy::render::batching::no_gpu_preprocessing::{
clear_batched_cpu_instance_buffers, write_batched_instance_buffer, BatchedInstanceBuffer,
};
use bevy::render::camera::DirtySpecializationSystems;
use bevy::render::extract_component::{ExtractComponentPlugin, UniformComponentPlugin};
use bevy::render::render_phase::{
sort_phase_system, AddRenderCommand, BinnedRenderPhasePlugin, DrawFunctions, PhaseItem,
SortedRenderPhasePlugin,
};
use bevy::render::render_resource::{SpecializedMeshPipelines, VertexFormat};
use bevy::render::renderer::RenderDevice;
use bevy::render::texture::FallbackImage;
use bevy::render::view::Msaa;
use bevy::render::{
init_gpu_resource, Render, RenderApp, RenderDebugFlags, RenderStartup, RenderSystems,
};
use crate::culling::{
check_outline_view_visibility, collect_outline_cpu_culled_entities,
extract_outline_visible_entities, OutlineVisibleEntities, RenderExtractedOutlineEntities,
RenderOutlineEntities,
};
use crate::msaa::{
msaa_extra_writeback_pass, prepare_msaa_extra_writeback_pipelines,
prepare_outline_view_textures, ResolvedOutlineMsaa,
};
use crate::node::{outline_render_pass, OpaqueOutline, StencilOutline, TransparentOutline};
use crate::pipeline::{
init_outline_pipeline, OutlinePipeline, COMMON_SHADER_HANDLE, FRAGMENT_SHADER_HANDLE,
OUTLINE_SHADER_HANDLE,
};
use crate::pipeline_key::compute_outline_key;
use crate::queue::{
check_outline_entities_needing_specialisation, clear_dirty_outline_specialisations,
expire_outline_specialisations_for_views, extract_outline_entities_needing_specialisation,
extract_outline_entities_needing_specialisation_removed, queue_outline_mesh,
specialise_outlines, DirtyOutlineSpecialisations, OutlineCache,
OutlineEntitiesNeedingSpecialisation, PendingOutlineQueues,
};
use crate::render::DrawOutline;
use crate::uniforms::extract_outlines;
use crate::uniforms::RenderOutlineInstances;
use crate::uniforms::{
init_alpha_mask_bind_groups, prepare_alpha_mask_bind_groups,
prepare_outline_instance_bind_group, OutlineInstanceUniform,
};
use crate::view_uniforms::{
extract_outline_view_uniforms, prepare_outline_view_bind_group, OutlineViewUniform,
};
mod computed;
mod culling;
mod generate;
mod msaa;
mod node;
mod pipeline;
mod pipeline_key;
mod propagate;
mod queue;
mod render;
mod uniforms;
mod view_uniforms;
pub use computed::*;
pub use generate::*;
#[cfg(feature = "flood")]
mod flood;
#[cfg(feature = "world_serialisation")]
mod world_serialisation;
#[cfg(feature = "world_serialisation")]
pub use world_serialisation::*;
pub const ATTRIBUTE_OUTLINE_NORMAL: MeshVertexAttribute =
MeshVertexAttribute::new("Outline_Normal", 1585570526, VertexFormat::Float32x3);
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[cfg_attr(feature = "reflect", reflect(Default))]
pub enum OutlineStencilEnabled {
#[default]
Always,
IfVolume,
Never,
}
impl OutlineStencilEnabled {
pub(crate) fn is_enabled(&self, volume_enabled: bool) -> bool {
match self {
OutlineStencilEnabled::Always => true,
OutlineStencilEnabled::IfVolume => volume_enabled,
OutlineStencilEnabled::Never => false,
}
}
}
#[derive(Clone, Component, Default)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[cfg_attr(feature = "reflect", reflect(Component, Default))]
pub struct OutlineStencil {
pub enabled: OutlineStencilEnabled,
pub offset: f32,
}
impl OutlineStencil {
pub(crate) const INHERIT_DEFAULT: OutlineStencil = OutlineStencil {
enabled: OutlineStencilEnabled::IfVolume,
offset: 0.0,
};
}
fn lerp_stencil_enabled(
this: OutlineStencilEnabled,
other: OutlineStencilEnabled,
scalar: f32,
) -> OutlineStencilEnabled {
if scalar <= 0.0 {
this
} else if scalar >= 1.0 {
other
} else {
match (this, other) {
(OutlineStencilEnabled::Always, _) => OutlineStencilEnabled::Always,
(_, OutlineStencilEnabled::Always) => OutlineStencilEnabled::Always,
(OutlineStencilEnabled::IfVolume, _) => OutlineStencilEnabled::IfVolume,
(_, OutlineStencilEnabled::IfVolume) => OutlineStencilEnabled::IfVolume,
_ => OutlineStencilEnabled::Never,
}
}
}
macro_rules! impl_lerp {
($t:ty, $e:expr) => {
impl Ease for $t {
fn interpolating_curve_unbounded(start: Self, end: Self) -> impl Curve<Self> {
FunctionCurve::new(Interval::UNIT, move |t| $e(&start, &end, t))
}
}
#[cfg(feature = "interpolation")]
impl interpolation::Lerp for $t {
type Scalar = f32;
fn lerp(&self, other: &Self, scalar: &Self::Scalar) -> Self {
$e(self, other, *scalar)
}
}
};
}
fn lerp_stencil(start: &OutlineStencil, end: &OutlineStencil, t: f32) -> OutlineStencil {
OutlineStencil {
enabled: lerp_stencil_enabled(start.enabled, end.enabled, t),
offset: start.offset.lerp(end.offset, t),
}
}
impl_lerp!(OutlineStencil, lerp_stencil);
#[derive(Clone, Component, Default)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[cfg_attr(feature = "reflect", reflect(Component, Default))]
pub struct OutlineVolume {
pub visible: bool,
pub width: f32,
pub colour: Color,
}
fn lerp_bool(this: bool, other: bool, t: f32) -> bool {
if t <= 0.0 {
this
} else if t >= 1.0 {
other
} else {
this || other
}
}
fn lerp_volume(start: &OutlineVolume, end: &OutlineVolume, t: f32) -> OutlineVolume {
OutlineVolume {
visible: lerp_bool(start.visible, end.visible, t),
width: start.width.lerp(end.width, t),
colour: start.colour.mix(&end.colour, t),
}
}
impl_lerp!(OutlineVolume, lerp_volume);
#[derive(Component, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut, Default)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[cfg_attr(feature = "reflect", reflect(Component, Default))]
pub struct OutlineRenderLayers(pub RenderLayers);
impl From<RenderLayers> for OutlineRenderLayers {
fn from(value: RenderLayers) -> Self {
OutlineRenderLayers(value)
}
}
#[derive(Clone, Component, Default)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[cfg_attr(feature = "reflect", reflect(Component, Default))]
#[non_exhaustive]
pub enum OutlineMode {
#[default]
ExtrudeFlat,
ExtrudeReal,
#[cfg(feature = "flood")]
FloodFlat,
}
#[derive(Clone, Default, Resource, Deref)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[cfg_attr(feature = "reflect", reflect(Resource, Default))]
pub struct GlobalOutlineMode(pub OutlineMode);
#[derive(Clone, Component, Default)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[cfg_attr(feature = "reflect", reflect(Component, Default))]
#[non_exhaustive]
pub enum OutlineFace {
#[default]
Front,
DoubleSided,
}
#[derive(Clone, Component, Default)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[cfg_attr(feature = "reflect", reflect(Component, Default))]
pub struct OutlinePlaneDepth {
pub model_plane_origin: Vec3,
pub model_plane_offset: Vec3,
}
#[derive(Clone, Component, Default)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[cfg_attr(feature = "reflect", reflect(Component, Default))]
pub struct InheritOutline;
#[derive(Clone, Component, Default)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[cfg_attr(feature = "reflect", reflect(Component, Default))]
pub struct PropagateOutline;
#[derive(Clone, Component, Default)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[cfg_attr(feature = "reflect", reflect(Component, Default))]
pub struct StopPropagateOutline;
#[derive(Copy, Clone, Default)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[cfg_attr(feature = "reflect", reflect(Default))]
pub enum TextureChannel {
R,
G,
B,
#[default]
A,
}
#[derive(Clone, Component, Default)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[cfg_attr(feature = "reflect", reflect(Component, Default))]
pub struct OutlineAlphaMask {
pub texture: Option<Handle<Image>>,
pub channel: TextureChannel,
pub threshold: f32,
}
#[derive(Clone, Component, Default)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[cfg_attr(feature = "reflect", reflect(Component, Default))]
pub struct OutlineWarmUp {
transparency: bool,
vertex_offsets: bool,
}
impl OutlineWarmUp {
pub fn with_transparency(self, transparency: bool) -> Self {
let mut s = self.clone();
s.transparency = transparency;
s
}
pub fn with_vertex_offsets(self, vertex_offset_zero: bool) -> Self {
let mut s = self.clone();
s.vertex_offsets = vertex_offset_zero;
s
}
}
#[derive(Component, Clone, Default)]
#[cfg_attr(feature = "reflect", derive(Reflect))]
#[cfg_attr(feature = "reflect", reflect(Component, Default))]
pub enum OutlineMsaa {
#[default]
Auto,
Msaa(Msaa),
}
pub(crate) fn add_dummy_phase_buffer<P: PhaseItem + 'static>(
bibs: &mut gpu_preprocessing::BatchedInstanceBuffers<MeshUniform, MeshInputUniform>,
) {
let phase_buffer = bibs
.phase_instance_buffers
.entry(TypeId::of::<P>())
.or_default();
if phase_buffer.data_buffer.is_empty() {
phase_buffer.data_buffer.add();
}
}
fn add_dummy_phase_buffers(
mut bibs: ResMut<gpu_preprocessing::BatchedInstanceBuffers<MeshUniform, MeshInputUniform>>,
) {
add_dummy_phase_buffer::<StencilOutline>(&mut bibs);
add_dummy_phase_buffer::<OpaqueOutline>(&mut bibs);
add_dummy_phase_buffer::<TransparentOutline>(&mut bibs);
}
pub struct OutlinePlugin {
mode: OutlineMode,
}
impl OutlinePlugin {
pub const EXTRUDE_VERTEX: Self = Self {
mode: OutlineMode::ExtrudeFlat,
};
#[cfg(feature = "flood")]
pub const JUMP_FLOOD: Self = Self {
mode: OutlineMode::FloodFlat,
};
}
impl Plugin for OutlinePlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(app, COMMON_SHADER_HANDLE, "common.wgsl", Shader::from_wgsl);
load_internal_asset!(
app,
OUTLINE_SHADER_HANDLE,
"outline.wgsl",
Shader::from_wgsl
);
load_internal_asset!(
app,
FRAGMENT_SHADER_HANDLE,
"fragment.wgsl",
Shader::from_wgsl
);
app.add_plugins((
ExtractComponentPlugin::<ResolvedOutlineMsaa>::default(),
UniformComponentPlugin::<OutlineViewUniform>::default(),
BinnedRenderPhasePlugin::<StencilOutline, OutlinePipeline>::new(
RenderDebugFlags::empty(),
),
BinnedRenderPhasePlugin::<OpaqueOutline, OutlinePipeline>::new(
RenderDebugFlags::empty(),
),
SortedRenderPhasePlugin::<TransparentOutline, OutlinePipeline>::new(
RenderDebugFlags::empty(),
),
))
.register_required_components::<OutlineStencil, ComputedOutline>()
.register_required_components::<OutlineVolume, ComputedOutline>()
.register_required_components::<InheritOutline, ComputedOutline>()
.register_required_components::<PropagateOutline, ComputedOutline>()
.insert_resource(GlobalOutlineMode(self.mode.clone()))
.init_resource::<OutlineEntitiesNeedingSpecialisation>()
.init_resource::<OutlineVisibleEntities>()
.add_systems(
PostUpdate,
(
clean_up_computed_outline,
compute_outline
.after(TransformSystems::Propagate)
.after(VisibilitySystems::VisibilityPropagate),
compute_outline_key
.after(compute_outline)
.after(clean_up_computed_outline)
.after(AssetEventSystems),
check_outline_entities_needing_specialisation.after(compute_outline_key),
check_outline_view_visibility
.after(VisibilitySystems::CheckVisibility)
.after(compute_outline),
),
)
.sub_app_mut(RenderApp)
.init_resource::<DrawFunctions<StencilOutline>>()
.init_resource::<DrawFunctions<OpaqueOutline>>()
.init_resource::<DrawFunctions<TransparentOutline>>()
.init_resource::<SpecializedMeshPipelines<OutlinePipeline>>()
.add_render_command::<StencilOutline, DrawOutline>()
.add_render_command::<OpaqueOutline, DrawOutline>()
.add_render_command::<TransparentOutline, DrawOutline>()
.add_systems(
ExtractSchedule,
(
clear_dirty_outline_specialisations.in_set(DirtySpecializationSystems::Clear),
extract_outline_view_uniforms,
extract_outlines,
extract_outline_visible_entities,
extract_outline_entities_needing_specialisation
.in_set(DirtySpecializationSystems::CheckForChanges),
extract_outline_entities_needing_specialisation_removed
.in_set(DirtySpecializationSystems::CheckForRemovals),
expire_outline_specialisations_for_views.in_set(RenderSystems::Cleanup),
),
)
.add_systems(
Render,
(
prepare_outline_view_textures,
prepare_msaa_extra_writeback_pipelines,
)
.in_set(RenderSystems::Prepare),
)
.add_systems(
Render,
(
prepare_outline_view_bind_group,
prepare_outline_instance_bind_group,
prepare_alpha_mask_bind_groups,
)
.in_set(RenderSystems::PrepareBindGroups),
)
.add_systems(
Render,
collect_outline_cpu_culled_entities.in_set(RenderSystems::PrepareAssets),
)
.add_systems(
Render,
specialise_outlines.in_set(RenderSystems::PrepareMeshes),
)
.add_systems(
Render,
queue_outline_mesh.in_set(RenderSystems::QueueMeshes),
)
.add_systems(
Render,
sort_phase_system::<TransparentOutline>.in_set(RenderSystems::PhaseSort),
)
.add_systems(
Render,
clear_batched_cpu_instance_buffers::<OutlinePipeline>
.in_set(RenderSystems::Cleanup)
.after(RenderSystems::Render),
)
.add_systems(
Core3d,
(msaa_extra_writeback_pass, outline_render_pass)
.chain()
.in_set(Core3dSystems::PostProcess)
.after(tonemapping)
.before(fxaa)
.before(smaa),
);
propagate::add_propagate_observers(app);
#[cfg(feature = "reflect")]
app.register_type::<OutlineStencil>()
.register_type::<OutlineVolume>()
.register_type::<OutlineRenderLayers>()
.register_type::<OutlineMode>()
.register_type::<GlobalOutlineMode>()
.register_type::<OutlineFace>()
.register_type::<OutlineAlphaMask>()
.register_type::<OutlineMsaa>()
.register_type::<InheritOutline>()
.register_type::<PropagateOutline>()
.register_type::<StopPropagateOutline>();
#[cfg(feature = "world_serialisation")]
app.init_resource::<AsyncWorldInheritOutlineSystems>();
#[cfg(feature = "flood")]
app.add_plugins(flood::FloodPlugin);
}
fn finish(&self, app: &mut App) {
let render_app = app.sub_app_mut(RenderApp);
render_app
.init_resource::<RenderOutlineInstances>()
.init_resource::<RenderExtractedOutlineEntities>()
.init_resource::<RenderOutlineEntities>()
.init_resource::<PendingOutlineQueues>()
.init_resource::<DirtyOutlineSpecialisations>()
.init_resource::<OutlineCache>()
.add_systems(
RenderStartup,
(
(init_outline_pipeline, init_outline_instance_buffer)
.after(bevy::pbr::MeshPipelineSystems),
init_alpha_mask_bind_groups
.after(init_outline_pipeline)
.after(init_gpu_resource::<FallbackImage>),
),
)
.add_systems(
Render,
write_batched_instance_buffer::<OutlinePipeline>
.in_set(RenderSystems::PrepareResourcesFlush),
);
let gpu_preprocessing_support = render_app.world().resource::<GpuPreprocessingSupport>();
if gpu_preprocessing_support.is_available() {
render_app.add_systems(
Render,
add_dummy_phase_buffers.in_set(RenderSystems::PrepareResourcesCollectPhaseBuffers),
);
}
}
}
fn init_outline_instance_buffer(mut commands: Commands, render_device: Res<RenderDevice>) {
commands.insert_resource(BatchedInstanceBuffer::<OutlineInstanceUniform>::new(
&render_device.limits(),
));
}