use std::cmp::Reverse;
use bevy::ecs::system::lifetimeless::Read;
use bevy::prelude::*;
use bevy::render::camera::ExtractedCamera;
use bevy::render::render_graph::{NodeRunError, SlotInfo, SlotType};
use bevy::render::render_phase::{
CachedRenderPipelinePhaseItem, DrawFunctionId, DrawFunctions, EntityPhaseItem, PhaseItem,
RenderPhase, TrackedRenderPass,
};
use bevy::render::render_resource::{
CachedRenderPipelineId, LoadOp, Operations, RenderPassDepthStencilAttachment,
RenderPassDescriptor,
};
use bevy::render::view::{ExtractedView, ViewDepthTexture, ViewTarget};
use bevy::render::{
render_graph::{Node, RenderGraphContext},
renderer::RenderContext,
};
use bevy::utils::FloatOrd;
pub(crate) struct StencilOutline {
pub distance: f32,
pub pipeline: CachedRenderPipelineId,
pub entity: Entity,
pub draw_function: DrawFunctionId,
}
impl PhaseItem for StencilOutline {
type SortKey = Reverse<FloatOrd>;
fn sort_key(&self) -> Self::SortKey {
Reverse(FloatOrd(self.distance))
}
fn draw_function(&self) -> bevy::render::render_phase::DrawFunctionId {
self.draw_function
}
}
impl EntityPhaseItem for StencilOutline {
#[inline]
fn entity(&self) -> Entity {
self.entity
}
}
impl CachedRenderPipelinePhaseItem for StencilOutline {
#[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId {
self.pipeline
}
}
pub(crate) struct OpaqueOutline {
pub distance: f32,
pub pipeline: CachedRenderPipelineId,
pub entity: Entity,
pub draw_function: DrawFunctionId,
}
impl PhaseItem for OpaqueOutline {
type SortKey = Reverse<FloatOrd>;
fn sort_key(&self) -> Self::SortKey {
Reverse(FloatOrd(self.distance))
}
fn draw_function(&self) -> bevy::render::render_phase::DrawFunctionId {
self.draw_function
}
}
impl EntityPhaseItem for OpaqueOutline {
#[inline]
fn entity(&self) -> Entity {
self.entity
}
}
impl CachedRenderPipelinePhaseItem for OpaqueOutline {
#[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId {
self.pipeline
}
}
pub(crate) struct TransparentOutline {
pub distance: f32,
pub pipeline: CachedRenderPipelineId,
pub entity: Entity,
pub draw_function: DrawFunctionId,
}
impl PhaseItem for TransparentOutline {
type SortKey = FloatOrd;
fn sort_key(&self) -> Self::SortKey {
FloatOrd(self.distance)
}
fn draw_function(&self) -> bevy::render::render_phase::DrawFunctionId {
self.draw_function
}
}
impl EntityPhaseItem for TransparentOutline {
#[inline]
fn entity(&self) -> Entity {
self.entity
}
}
impl CachedRenderPipelinePhaseItem for TransparentOutline {
#[inline]
fn cached_pipeline(&self) -> CachedRenderPipelineId {
self.pipeline
}
}
#[allow(clippy::type_complexity)]
pub(crate) struct OutlineNode {
query: QueryState<
(
Read<ExtractedCamera>,
Read<RenderPhase<StencilOutline>>,
Read<RenderPhase<OpaqueOutline>>,
Read<RenderPhase<TransparentOutline>>,
Read<Camera3d>,
Read<ViewTarget>,
Read<ViewDepthTexture>,
),
With<ExtractedView>,
>,
}
impl OutlineNode {
pub(crate) const IN_VIEW: &'static str = "view";
pub(crate) fn new(world: &mut World) -> Self {
Self {
query: world.query_filtered(),
}
}
}
impl Node for OutlineNode {
fn input(&self) -> Vec<SlotInfo> {
vec![SlotInfo::new(Self::IN_VIEW, SlotType::Entity)]
}
fn update(&mut self, world: &mut World) {
self.query.update_archetypes(world);
}
fn run(
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
world: &World,
) -> Result<(), NodeRunError> {
let view_entity = graph.get_input_entity(Self::IN_VIEW)?;
let (camera, stencil_phase, opaque_phase, transparent_phase, camera_3d, target, depth) =
match self.query.get_manual(world, view_entity) {
Ok(query) => query,
Err(_) => {
return Ok(());
} };
if !opaque_phase.items.is_empty() || !transparent_phase.items.is_empty() {
let pass_descriptor = RenderPassDescriptor {
label: Some("outline_stencil_pass"),
color_attachments: &[],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &depth.view,
depth_ops: Some(Operations {
load: camera_3d.depth_load_op.clone().into(),
store: true,
}),
stencil_ops: None,
}),
};
let draw_functions = world.resource::<DrawFunctions<StencilOutline>>();
let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
if let Some(viewport) = camera.viewport.as_ref() {
tracked_pass.set_camera_viewport(viewport);
}
for item in &stencil_phase.items {
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
draw_function.draw(world, &mut tracked_pass, view_entity, item);
}
}
if !opaque_phase.items.is_empty() {
let pass_descriptor = RenderPassDescriptor {
label: Some("outline_opaque_pass"),
color_attachments: &[Some(target.get_color_attachment(Operations {
load: LoadOp::Load,
store: true,
}))],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &depth.view,
depth_ops: Some(Operations {
load: LoadOp::Load,
store: true,
}),
stencil_ops: None,
}),
};
let draw_functions = world.resource::<DrawFunctions<OpaqueOutline>>();
let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
if let Some(viewport) = camera.viewport.as_ref() {
tracked_pass.set_camera_viewport(viewport);
}
for item in &opaque_phase.items {
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
draw_function.draw(world, &mut tracked_pass, view_entity, item);
}
}
if !transparent_phase.items.is_empty() {
let pass_descriptor = RenderPassDescriptor {
label: Some("outline_transparent_pass"),
color_attachments: &[Some(target.get_color_attachment(Operations {
load: LoadOp::Load,
store: true,
}))],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachment {
view: &depth.view,
depth_ops: Some(Operations {
load: LoadOp::Load,
store: true,
}),
stencil_ops: None,
}),
};
let draw_functions = world.resource::<DrawFunctions<TransparentOutline>>();
let render_pass = render_context
.command_encoder
.begin_render_pass(&pass_descriptor);
let mut draw_functions = draw_functions.write();
let mut tracked_pass = TrackedRenderPass::new(render_pass);
if let Some(viewport) = camera.viewport.as_ref() {
tracked_pass.set_camera_viewport(viewport);
}
for item in &transparent_phase.items {
let draw_function = draw_functions.get_mut(item.draw_function).unwrap();
draw_function.draw(world, &mut tracked_pass, view_entity, item);
}
}
Ok(())
}
}