diff --git a/examples/cube.rs b/examples/cube.rs index 8ec18fc..86e0ae6 100644 --- a/examples/cube.rs +++ b/examples/cube.rs @@ -9,6 +9,7 @@ use bevy_mod_rounded_box::*; fn main() { App::new() .insert_resource(Msaa { samples: 4 }) + .insert_resource(ClearColor(Color::BLACK)) .add_plugins(DefaultPlugins) .add_plugin(OutlinePlugin) .add_startup_system(setup) @@ -44,7 +45,7 @@ fn setup( }) .insert(outlines.add(Outline { colour: Color::rgba(0.0, 1.0, 0.0, 0.5), - width: 50.0, + width: 25.0, })) .insert(TheCube()); commands.spawn_bundle(PointLightBundle { diff --git a/src/lib.rs b/src/lib.rs index 8e902e4..a2df54a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ use bevy::pbr::{ }; use bevy::prelude::*; use bevy::reflect::TypeUuid; +use bevy::render::camera::{ActiveCamera, Camera3d}; use bevy::render::mesh::{MeshVertexBufferLayout, PrimitiveTopology}; use bevy::render::render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets}; use bevy::render::render_component::ExtractComponentPlugin; @@ -18,10 +19,10 @@ use bevy::render::render_resource::std140::{AsStd140, Std140}; use bevy::render::render_resource::{ BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, Buffer, BufferBindingType, BufferInitDescriptor, BufferSize, - BufferUsages, Face, PipelineCache, RenderPipelineDescriptor, ShaderStages, + BufferUsages, DynamicUniformVec, Face, PipelineCache, RenderPipelineDescriptor, ShaderStages, SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, }; -use bevy::render::renderer::RenderDevice; +use bevy::render::renderer::{RenderDevice, RenderQueue}; use bevy::render::view::ExtractedView; use bevy::render::{RenderApp, RenderStage}; use libm::nextafterf; @@ -37,7 +38,7 @@ const OUTLINE_SHADER_HANDLE: HandleUntyped = pub struct Outline { /// Colour of the outline pub colour: Color, - /// Width of the outline in pixels + /// Width of the outline in logical pixels pub width: f32, } @@ -76,7 +77,7 @@ impl RenderAsset for Outline { }); let bind_group = render_device.create_bind_group(&BindGroupDescriptor { label: Some("outline_bind_group"), - layout: &outline_pipeline.bind_group_layout, + layout: &outline_pipeline.outline_bind_group_layout, entries: &[ BindGroupEntry { binding: 0, @@ -97,6 +98,26 @@ impl RenderAsset for Outline { } } +#[derive(Clone, Component, AsStd140)] +struct ViewSizeUniform { + logical_size: Vec2, +} + +#[derive(Default)] +struct ViewSizeUniforms { + pub uniforms: DynamicUniformVec, +} + +#[derive(Component)] +struct ViewSizeUniformOffset { + pub offset: u32, +} + +#[derive(Component)] +struct GpuViewSize { + bind_group: BindGroup, +} + #[derive(Clone, AsStd140)] struct VertexStageData { width: f32, @@ -133,6 +154,9 @@ impl Plugin for OutlinePlugin { .add_render_command::() .init_resource::() .init_resource::>() + .init_resource::() + .add_system_to_stage(RenderStage::Extract, extract_view_size_uniforms) + .add_system_to_stage(RenderStage::Prepare, prepare_view_size_uniforms) .add_system_to_stage(RenderStage::Queue, queue_outline); } } @@ -141,10 +165,29 @@ type DrawOutline = ( SetItemPipeline, SetMeshViewBindGroup<0>, SetMeshBindGroup<1>, - SetOutlineBindGroup<2>, + SetOutlineViewBindGroup<2>, + SetOutlineBindGroup<3>, DrawMesh, ); +struct SetOutlineViewBindGroup(); + +impl EntityRenderCommand for SetOutlineViewBindGroup { + type Param = SQuery<(Read, Read)>; + #[inline] + fn render<'w>( + view: Entity, + _item: Entity, + view_query: SystemParamItem<'w, '_, Self::Param>, + pass: &mut TrackedRenderPass<'w>, + ) -> RenderCommandResult { + let (view_size_uniform, gpu_view_size) = view_query.get_inner(view).unwrap(); + pass.set_bind_group(I, &gpu_view_size.bind_group, &[view_size_uniform.offset]); + + RenderCommandResult::Success + } +} + struct SetOutlineBindGroup(); impl EntityRenderCommand for SetOutlineBindGroup { @@ -162,6 +205,62 @@ impl EntityRenderCommand for SetOutlineBindGroup { } } +fn extract_view_size_uniforms( + mut commands: Commands, + windows: Res, + images: Res>, + active_camera: Res>, + query: Query<&Camera, With>, +) { + if let Some(entity) = active_camera.get() { + if let Ok(camera) = query.get(entity) { + if let Some(size) = camera.target.get_logical_size(&windows, &images) { + commands + .get_or_spawn(entity) + .insert(ViewSizeUniform { logical_size: size }); + } + } + } +} + +fn prepare_view_size_uniforms( + mut commands: Commands, + render_device: Res, + render_queue: Res, + outline_pipeline: Res, + mut view_size_uniforms: ResMut, + views: Query<(Entity, &ViewSizeUniform)>, +) { + view_size_uniforms.uniforms.clear(); + for (entity, view_size_uniform) in views.iter() { + let view_size_uniforms = ViewSizeUniformOffset { + offset: view_size_uniforms.uniforms.push(view_size_uniform.clone()), + }; + + commands.entity(entity).insert(view_size_uniforms); + } + + view_size_uniforms + .uniforms + .write_buffer(&render_device, &render_queue); + + if let Some(view_binding) = view_size_uniforms.uniforms.binding() { + let bind_group = render_device.create_bind_group(&BindGroupDescriptor { + entries: &[BindGroupEntry { + binding: 0, + resource: view_binding.clone(), + }], + label: Some("outline_view_size_bind_group"), + layout: &outline_pipeline.view_size_bind_group_layout, + }); + for (entity, _) in views.iter() { + commands.entity(entity).insert(GpuViewSize { + bind_group: bind_group.clone(), + }); + } + } +} + #[allow(clippy::too_many_arguments)] fn queue_outline( opaque_3d_draw_functions: Res>, @@ -232,7 +331,8 @@ fn queue_outline( pub struct OutlinePipeline { mesh_pipeline: MeshPipeline, - bind_group_layout: BindGroupLayout, + view_size_bind_group_layout: BindGroupLayout, + outline_bind_group_layout: BindGroupLayout, } impl FromWorld for OutlinePipeline { @@ -240,9 +340,25 @@ impl FromWorld for OutlinePipeline { let world = world.cell(); let mesh_pipeline = world.get_resource::().unwrap().clone(); let render_device = world.get_resource::().unwrap(); - let bind_group_layout = + let view_size_bind_group_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - label: Some("outline_pipeline_bind_group_layout"), + label: Some("outline_view_size_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( + ViewSizeUniform::std140_size_static() as u64 + ), + }, + count: None, + }], + }); + let outline_bind_group_layout = + render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + label: Some("outline_bind_group_layout"), entries: &[ BindGroupLayoutEntry { binding: 0, @@ -272,7 +388,8 @@ impl FromWorld for OutlinePipeline { }); OutlinePipeline { mesh_pipeline, - bind_group_layout, + view_size_bind_group_layout, + outline_bind_group_layout, } } } @@ -292,7 +409,8 @@ impl SpecializedMeshPipeline for OutlinePipeline { descriptor.layout = Some(vec![ self.mesh_pipeline.view_layout.clone(), self.mesh_pipeline.mesh_layout.clone(), - self.bind_group_layout.clone(), + self.view_size_bind_group_layout.clone(), + self.outline_bind_group_layout.clone(), ]); Ok(descriptor) } diff --git a/src/outline.wgsl b/src/outline.wgsl index b2b2163..dc9cbec 100644 --- a/src/outline.wgsl +++ b/src/outline.wgsl @@ -10,6 +10,10 @@ struct VertexOutput { [[builtin(position)]] clip_position: vec4; }; +struct ViewSizeUniforms { + logical_size: vec2; +}; + struct VertexStageData { width: f32; }; @@ -22,9 +26,12 @@ struct FragmentStageData { var mesh: Mesh; [[group(2), binding(0)]] +var view_size: ViewSizeUniforms; + +[[group(3), binding(0)]] var vstage: VertexStageData; -[[group(2), binding(1)]] +[[group(3), binding(1)]] var fstage: FragmentStageData; fn mat4to3(m: mat4x4) -> mat3x3 { @@ -38,7 +45,7 @@ fn vertex(vertex: Vertex) -> VertexOutput { var out: VertexOutput; var clip_pos = view.view_proj * (mesh.model * vec4(vertex.position, 1.0)); var clip_norm = mat4to3(view.view_proj) * (mat4to3(mesh.model) * normalize(vertex.normal)); - var clip_delta = vec4(vstage.width * normalize(clip_norm.xy) * clip_pos.w / vec2(view.width, view.height), 0.0, 0.0); + var clip_delta = vec4(2.0 * vstage.width * normalize(clip_norm.xy) * clip_pos.w / view_size.logical_size, 0.0, 0.0); out.clip_position = clip_pos + clip_delta; return out; }