use std::borrow::Cow; use bevy::pbr::{setup_morph_and_skinning_defs, MeshPipelineKey}; use bevy::prelude::*; use bevy::reflect::TypeUuid; use bevy::render::render_resource::{ BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BlendState, BufferBindingType, BufferSize, ColorTargetState, ColorWrites, CompareFunction, DepthBiasState, DepthStencilState, Face, FragmentState, FrontFace, MultisampleState, PolygonMode, PrimitiveState, PrimitiveTopology, ShaderDefVal, ShaderSize, ShaderStages, StencilState, TextureFormat, VertexState, }; use bevy::render::renderer::RenderDevice; use bevy::render::texture::BevyDefault; use bevy::render::view::ViewTarget; use bevy::{ pbr::MeshPipeline, render::{ mesh::MeshVertexBufferLayout, render_resource::{ RenderPipelineDescriptor, SpecializedMeshPipeline, SpecializedMeshPipelineError, }, }, }; use bitfield::{bitfield_bitrange, bitfield_fields}; use crate::uniforms::{ DepthMode, OutlineFragmentUniform, OutlineStencilUniform, OutlineVolumeUniform, }; use crate::view_uniforms::OutlineViewUniform; use crate::ATTRIBUTE_OUTLINE_NORMAL; pub(crate) const OUTLINE_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 2101625026478770097); pub(crate) const FRAGMENT_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 12033806834125368121); #[derive(Clone, Copy, PartialEq, Eq)] pub(crate) enum PassType { Stencil = 1, Opaque = 2, Transparent = 3, } #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct PipelineKey(u32); bitfield_bitrange! {struct PipelineKey(u32)} impl PipelineKey { bitfield_fields! { u32; msaa_samples_minus_one, set_msaa_samples_minus_one: 5, 0; primitive_topology_int, set_primitive_topology_int: 8, 6; pass_type_int, set_pass_type_int: 10, 9; depth_mode_int, set_depth_mode_int: 12, 11; pub offset_zero, set_offset_zero: 13; pub hdr_format, set_hdr_format: 14; pub opengl_workaround, set_opengl_workaround: 15; pub morph_targets, set_morph_targets: 16; } pub(crate) fn new() -> Self { PipelineKey(0) } pub(crate) fn with_msaa(mut self, msaa: Msaa) -> Self { self.set_msaa_samples_minus_one(msaa as u32 - 1); self } pub(crate) fn msaa(&self) -> Msaa { match self.msaa_samples_minus_one() + 1 { x if x == Msaa::Off as u32 => Msaa::Off, x if x == Msaa::Sample2 as u32 => Msaa::Sample2, x if x == Msaa::Sample4 as u32 => Msaa::Sample4, x if x == Msaa::Sample8 as u32 => Msaa::Sample8, x => panic!("Invalid value for Msaa: {}", x), } } pub(crate) fn with_primitive_topology(mut self, primitive_topology: PrimitiveTopology) -> Self { self.set_primitive_topology_int(primitive_topology as u32); self } pub(crate) fn primitive_topology(&self) -> PrimitiveTopology { match self.primitive_topology_int() { x if x == PrimitiveTopology::PointList as u32 => PrimitiveTopology::PointList, x if x == PrimitiveTopology::LineList as u32 => PrimitiveTopology::LineList, x if x == PrimitiveTopology::LineStrip as u32 => PrimitiveTopology::LineStrip, x if x == PrimitiveTopology::TriangleList as u32 => PrimitiveTopology::TriangleList, x if x == PrimitiveTopology::TriangleStrip as u32 => PrimitiveTopology::TriangleStrip, x => panic!("Invalid value for PrimitiveTopology: {}", x), } } pub(crate) fn with_pass_type(mut self, pass_type: PassType) -> Self { self.set_pass_type_int(pass_type as u32); self } pub(crate) fn pass_type(&self) -> PassType { match self.pass_type_int() { x if x == PassType::Stencil as u32 => PassType::Stencil, x if x == PassType::Opaque as u32 => PassType::Opaque, x if x == PassType::Transparent as u32 => PassType::Transparent, x => panic!("Invalid value for PassType: {}", x), } } pub(crate) fn with_depth_mode(mut self, depth_mode: DepthMode) -> Self { self.set_depth_mode_int(depth_mode as u32); self } pub(crate) fn depth_mode(&self) -> DepthMode { match self.depth_mode_int() { x if x == DepthMode::Flat as u32 => DepthMode::Flat, x if x == DepthMode::Real as u32 => DepthMode::Real, x => panic!("Invalid value for DepthMode: {}", x), } } pub(crate) fn with_offset_zero(mut self, offset_zero: bool) -> Self { self.set_offset_zero(offset_zero); self } pub(crate) fn with_hdr_format(mut self, hdr_format: bool) -> Self { self.set_hdr_format(hdr_format); self } pub(crate) fn with_opengl_workaround(mut self, opengl_workaround: bool) -> Self { self.set_opengl_workaround(opengl_workaround); self } pub(crate) fn with_morph_targets(mut self, morph_targets: bool) -> Self { self.set_morph_targets(morph_targets); self } } impl From for MeshPipelineKey { fn from(key: PipelineKey) -> Self { if key.morph_targets() { MeshPipelineKey::empty() | MeshPipelineKey::MORPH_TARGETS } else { MeshPipelineKey::empty() } } } #[derive(Resource)] pub(crate) struct OutlinePipeline { mesh_pipeline: MeshPipeline, pub outline_view_bind_group_layout: BindGroupLayout, pub outline_stencil_bind_group_layout: BindGroupLayout, pub outline_volume_bind_group_layout: BindGroupLayout, } impl FromWorld for OutlinePipeline { fn from_world(world: &mut World) -> Self { let world = world.cell(); let mesh_pipeline = world.get_resource::().unwrap().clone(); let render_device = world.get_resource::().unwrap(); let outline_view_bind_group_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { label: Some("outline_view_bind_group_layout"), entries: &[BindGroupLayoutEntry { binding: 0, visibility: ShaderStages::VERTEX, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, min_binding_size: BufferSize::new(OutlineViewUniform::SHADER_SIZE.get()), }, count: None, }], }); let outline_volume_bind_group_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { label: Some("outline_volume_bind_group_layout"), entries: &[ BindGroupLayoutEntry { binding: 0, visibility: ShaderStages::VERTEX, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, min_binding_size: BufferSize::new( OutlineVolumeUniform::SHADER_SIZE.get(), ), }, count: None, }, BindGroupLayoutEntry { binding: 1, visibility: ShaderStages::FRAGMENT, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, min_binding_size: BufferSize::new( OutlineFragmentUniform::SHADER_SIZE.get(), ), }, count: None, }, ], }); let outline_stencil_bind_group_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { label: Some("outline_stencil_bind_group_layout"), entries: &[BindGroupLayoutEntry { binding: 0, visibility: ShaderStages::VERTEX, ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, min_binding_size: BufferSize::new(OutlineStencilUniform::SHADER_SIZE.get()), }, count: None, }], }); OutlinePipeline { mesh_pipeline, outline_view_bind_group_layout, outline_stencil_bind_group_layout, outline_volume_bind_group_layout, } } } impl SpecializedMeshPipeline for OutlinePipeline { type Key = PipelineKey; fn specialize( &self, key: Self::Key, layout: &MeshVertexBufferLayout, ) -> Result { let mut targets = vec![]; let mut vertex_defs = vec!["MESH_BINDGROUP_1".into()]; let mut fragment_defs = vec![]; let mut buffer_attrs = Vec::new(); if layout.contains(Mesh::ATTRIBUTE_POSITION) { vertex_defs.push("VERTEX_POSITIONS".into()); buffer_attrs.push(Mesh::ATTRIBUTE_POSITION.at_shader_location(0)); } if layout.contains(Mesh::ATTRIBUTE_NORMAL) { vertex_defs.push("VERTEX_NORMALS".into()); buffer_attrs.push(Mesh::ATTRIBUTE_NORMAL.at_shader_location(2)); } if layout.contains(Mesh::ATTRIBUTE_TANGENT) { vertex_defs.push("VERTEX_TANGENTS".into()); buffer_attrs.push(Mesh::ATTRIBUTE_TANGENT.at_shader_location(3)); } let mut bind_layouts = vec![if key.msaa() == Msaa::Off { self.mesh_pipeline.view_layout.clone() } else { self.mesh_pipeline.view_layout_multisampled.clone() }]; bind_layouts.push(setup_morph_and_skinning_defs( &self.mesh_pipeline.mesh_layouts, layout, 5, &key.into(), &mut vertex_defs, &mut buffer_attrs, )); bind_layouts.push(self.outline_view_bind_group_layout.clone()); let cull_mode; if key.depth_mode() == DepthMode::Flat { vertex_defs.push(ShaderDefVal::from("FLAT_DEPTH")); cull_mode = Some(Face::Back); } else if key.pass_type() == PassType::Stencil { cull_mode = Some(Face::Back); } else { cull_mode = Some(Face::Front); } if key.offset_zero() { vertex_defs.push(ShaderDefVal::from("OFFSET_ZERO")); } else { buffer_attrs.push( if layout.contains(ATTRIBUTE_OUTLINE_NORMAL) { ATTRIBUTE_OUTLINE_NORMAL } else { Mesh::ATTRIBUTE_NORMAL } .at_shader_location(1), ); } match key.pass_type() { PassType::Stencil => { bind_layouts.push(self.outline_stencil_bind_group_layout.clone()); } PassType::Opaque | PassType::Transparent => { fragment_defs.push(ShaderDefVal::from("VOLUME")); targets.push(Some(ColorTargetState { format: if key.hdr_format() { ViewTarget::TEXTURE_FORMAT_HDR } else { TextureFormat::bevy_default() }, blend: Some(if key.pass_type() == PassType::Transparent { BlendState::ALPHA_BLENDING } else { BlendState::REPLACE }), write_mask: ColorWrites::ALL, })); bind_layouts.push(self.outline_volume_bind_group_layout.clone()); } } if key.opengl_workaround() { let val = ShaderDefVal::from("OPENGL_WORKAROUND"); vertex_defs.push(val.clone()); fragment_defs.push(val); } let buffers = vec![layout.get_layout(&buffer_attrs)?]; Ok(RenderPipelineDescriptor { vertex: VertexState { shader: OUTLINE_SHADER_HANDLE.typed::(), entry_point: "vertex".into(), shader_defs: vertex_defs, buffers, }, fragment: Some(FragmentState { shader: FRAGMENT_SHADER_HANDLE.typed::(), shader_defs: fragment_defs, entry_point: "fragment".into(), targets, }), layout: bind_layouts, primitive: PrimitiveState { front_face: FrontFace::Ccw, cull_mode, unclipped_depth: false, polygon_mode: PolygonMode::Fill, conservative: false, topology: key.primitive_topology(), strip_index_format: None, }, depth_stencil: Some(DepthStencilState { format: TextureFormat::Depth32Float, depth_write_enabled: true, depth_compare: CompareFunction::Greater, stencil: StencilState::default(), bias: DepthBiasState { constant: if key.depth_mode() == DepthMode::Flat && key.pass_type() == PassType::Stencil { 2 // 1 is empirically not enough to prevent Z-fighting. } else { 0 }, ..default() }, }), multisample: MultisampleState { count: key.msaa().samples(), mask: !0, alpha_to_coverage_enabled: false, }, push_constant_ranges: default(), label: Some(Cow::Borrowed("outline_pipeline")), }) } }