diff --git a/Cargo.toml b/Cargo.toml index f9a6d67..34ec70f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,22 +10,19 @@ repository = "https://github.com/komadori/bevy_mod_outline/" keywords = ["gamedev", "bevy", "outline"] categories = ["game-engines", "rendering"] -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] -bevy = { version = "0.7", default-features = false, features = [ +bevy = { version = "0.8", default-features = false, features = [ + "bevy_asset", "render", ] } -bevy_asset = { version = "0.7", default-features = false } libm = "0.2" [dev-dependencies] -bevy = { version = "0.7", default-features = false, features = [ +bevy = { version = "0.8", default-features = false, features = [ "bevy_winit", "x11", ] } -bevy_mod_rounded_box = "0.1" [[example]] -name = "cube" -path = "examples/cube.rs" +name = "torus" +path = "examples/torus.rs" \ No newline at end of file diff --git a/examples/cube.rs b/examples/torus.rs similarity index 88% rename from examples/cube.rs rename to examples/torus.rs index 86e0ae6..3514773 100644 --- a/examples/cube.rs +++ b/examples/torus.rs @@ -1,9 +1,8 @@ use std::f32::consts::TAU; -use bevy::prelude::*; +use bevy::prelude::{shape::Torus, *}; use bevy_mod_outline::*; -use bevy_mod_rounded_box::*; #[bevy_main] fn main() { @@ -34,10 +33,11 @@ fn setup( }); commands .spawn_bundle(PbrBundle { - mesh: meshes.add(Mesh::from(RoundedBox { - size: Vec3::new(1., 1., 1.), - radius: 0.3, - subdivisions: 5, + mesh: meshes.add(Mesh::from(Torus { + radius: 0.6, + ring_radius: 0.2, + subdivisions_segments: 20, + subdivisions_sides: 10, })), material: materials.add(Color::rgb(0.1, 0.1, 0.9).into()), transform: Transform::from_xyz(0.0, 1.0, 0.0), @@ -57,7 +57,7 @@ fn setup( transform: Transform::from_xyz(4.0, 8.0, 4.0), ..default() }); - commands.spawn_bundle(PerspectiveCameraBundle { + commands.spawn_bundle(Camera3dBundle { transform: Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y), ..default() }); diff --git a/src/lib.rs b/src/lib.rs index 5f2a382..14c3791 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,5 @@ use bevy::asset::load_internal_asset; -use bevy::core_pipeline::{Opaque3d, Transparent3d}; +use bevy::core_pipeline::core_3d::{Opaque3d, Transparent3d}; use bevy::ecs::system::lifetimeless::{Read, SQuery, SRes}; use bevy::ecs::system::SystemParamItem; use bevy::pbr::{ @@ -7,24 +7,24 @@ use bevy::pbr::{ }; use bevy::prelude::*; use bevy::reflect::TypeUuid; -use bevy::render::camera::{ActiveCamera, Camera3d}; +use bevy::render::extract_component::ExtractComponentPlugin; use bevy::render::mesh::{MeshVertexBufferLayout, PrimitiveTopology}; -use bevy::render::render_asset::{RenderAsset, RenderAssetPlugin, RenderAssets}; -use bevy::render::render_component::ExtractComponentPlugin; +use bevy::render::render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}; use bevy::render::render_phase::{ AddRenderCommand, DrawFunctions, EntityRenderCommand, RenderCommandResult, RenderPhase, SetItemPipeline, TrackedRenderPass, }; -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, DynamicUniformVec, Face, PipelineCache, RenderPipelineDescriptor, ShaderStages, - SpecializedMeshPipeline, SpecializedMeshPipelineError, SpecializedMeshPipelines, + AsBindGroup, BindGroup, BindGroupDescriptor, BindGroupEntry, BindGroupLayout, + BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingType, BufferBindingType, BufferSize, + DynamicUniformBuffer, Face, PipelineCache, PreparedBindGroup, RenderPipelineDescriptor, + ShaderSize, ShaderStages, ShaderType, SpecializedMeshPipeline, SpecializedMeshPipelineError, + SpecializedMeshPipelines, }; use bevy::render::renderer::{RenderDevice, RenderQueue}; +use bevy::render::texture::FallbackImage; use bevy::render::view::ExtractedView; -use bevy::render::{RenderApp, RenderStage}; +use bevy::render::{Extract, RenderApp, RenderStage}; use libm::nextafterf; // See https://alexanderameye.github.io/notes/rendering-outlines/ @@ -33,12 +33,14 @@ const OUTLINE_SHADER_HANDLE: HandleUntyped = HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 2101625026478770097); /// An asset for rendering outlines around meshes. -#[derive(Clone, TypeUuid)] +#[derive(Clone, TypeUuid, AsBindGroup)] #[uuid = "552e416b-2766-4e6a-9ee5-9ebd0e8c0230"] pub struct Outline { /// Colour of the outline + #[uniform(1, visibility(fragment))] pub colour: Color, /// Width of the outline in logical pixels + #[uniform(0, visibility(vertex))] pub width: f32, } @@ -47,7 +49,12 @@ impl RenderAsset for Outline { type PreparedAsset = GpuOutline; - type Param = (SRes, SRes); + type Param = ( + SRes, + SRes, + SRes>, + SRes, + ); fn extract_asset(&self) -> Self::ExtractedAsset { self.clone() @@ -55,57 +62,35 @@ impl RenderAsset for Outline { fn prepare_asset( outline: Self::ExtractedAsset, - (render_device, outline_pipeline): &mut bevy::ecs::system::SystemParamItem, + (render_device, outline_pipeline, images, fallback_image): &mut bevy::ecs::system::SystemParamItem, ) -> Result< Self::PreparedAsset, bevy::render::render_asset::PrepareAssetError, > { - let colour = outline.colour.as_linear_rgba_f32().into(); - let vbuffer = render_device.create_buffer_with_data(&BufferInitDescriptor { - label: Some("outline_vertex_stage_uniform_buffer"), - usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, - contents: VertexStageData { - width: outline.width, - } - .as_std140() - .as_bytes(), - }); - let fbuffer = render_device.create_buffer_with_data(&BufferInitDescriptor { - label: Some("outline_fragment_stage_uniform_buffer"), - usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, - contents: FragmentStageData { colour }.as_std140().as_bytes(), - }); - let bind_group = render_device.create_bind_group(&BindGroupDescriptor { - label: Some("outline_bind_group"), - layout: &outline_pipeline.outline_bind_group_layout, - entries: &[ - BindGroupEntry { - binding: 0, - resource: vbuffer.as_entire_binding(), - }, - BindGroupEntry { - binding: 1, - resource: fbuffer.as_entire_binding(), - }, - ], - }); - Ok(GpuOutline { - _vertex_stage_buffer: vbuffer, - _fragment_stage_buffer: fbuffer, - bind_group, - transparent: colour.w < 1.0, - }) + if let Ok(pbg) = outline.as_bind_group( + &outline_pipeline.outline_bind_group_layout, + render_device, + images, + fallback_image, + ) { + Ok(GpuOutline { + bind_group: pbg, + transparent: outline.colour.a() < 1.0, + }) + } else { + Err(PrepareAssetError::RetryNextUpdate(outline)) + } } } -#[derive(Clone, Component, AsStd140)] +#[derive(Clone, Component, ShaderType)] struct ViewSizeUniform { logical_size: Vec2, } #[derive(Default)] struct ViewSizeUniforms { - pub uniforms: DynamicUniformVec, + pub uniforms: DynamicUniformBuffer, } #[derive(Component)] @@ -117,20 +102,8 @@ struct GpuViewSize { bind_group: BindGroup, } -#[derive(Clone, AsStd140)] -struct VertexStageData { - width: f32, -} - -#[derive(Clone, AsStd140)] -struct FragmentStageData { - colour: Vec4, -} - pub struct GpuOutline { - _vertex_stage_buffer: Buffer, - _fragment_stage_buffer: Buffer, - bind_group: BindGroup, + bind_group: PreparedBindGroup, transparent: bool, } @@ -203,25 +176,23 @@ impl EntityRenderCommand for SetOutlineBindGroup { ) -> RenderCommandResult { let outline_handle = query.get(item).unwrap(); let outline = outlines.into_inner().get(outline_handle).unwrap(); - pass.set_bind_group(I, &outline.bind_group, &[]); + pass.set_bind_group(I, &outline.bind_group.bind_group, &[]); RenderCommandResult::Success } } fn extract_view_size_uniforms( mut commands: Commands, - windows: Res, - images: Res>, - active_camera: Res>, - query: Query<&Camera, With>, + query: Extract>>, ) { - 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 }); - } + for (entity, camera) in query.iter() { + if !camera.is_active { + continue; + } + if let Some(size) = camera.logical_viewport_size() { + commands + .get_or_spawn(entity) + .insert(ViewSizeUniform { logical_size: size }); } } } @@ -348,43 +319,12 @@ impl FromWorld for OutlinePipeline { ty: BindingType::Buffer { ty: BufferBindingType::Uniform, has_dynamic_offset: true, - min_binding_size: BufferSize::new( - ViewSizeUniform::std140_size_static() as u64 - ), + min_binding_size: BufferSize::new(ViewSizeUniform::SHADER_SIZE.get()), }, count: None, }], }); - let outline_bind_group_layout = - render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { - label: Some("outline_bind_group_layout"), - entries: &[ - BindGroupLayoutEntry { - binding: 0, - visibility: ShaderStages::VERTEX, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: BufferSize::new( - VertexStageData::std140_size_static() as u64, - ), - }, - count: None, - }, - BindGroupLayoutEntry { - binding: 1, - visibility: ShaderStages::FRAGMENT, - ty: BindingType::Buffer { - ty: BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: BufferSize::new( - FragmentStageData::std140_size_static() as u64, - ), - }, - count: None, - }, - ], - }); + let outline_bind_group_layout = Outline::bind_group_layout(&render_device); OutlinePipeline { mesh_pipeline, view_size_bind_group_layout, diff --git a/src/outline.wgsl b/src/outline.wgsl index dc9cbec..ce3dcf8 100644 --- a/src/outline.wgsl +++ b/src/outline.wgsl @@ -1,37 +1,37 @@ -#import bevy_pbr::mesh_view_bind_group -#import bevy_pbr::mesh_struct +#import bevy_pbr::mesh_types +#import bevy_pbr::mesh_view_bindings struct Vertex { - [[location(0)]] position: vec3; - [[location(1)]] normal: vec3; + @location(0) position: vec3, + @location(1) normal: vec3, }; struct VertexOutput { - [[builtin(position)]] clip_position: vec4; + @builtin(position) clip_position: vec4, }; struct ViewSizeUniforms { - logical_size: vec2; + logical_size: vec2, }; struct VertexStageData { - width: f32; + width: f32, }; struct FragmentStageData { - colour: vec4; + colour: vec4, }; -[[group(1), binding(0)]] +@group(1) @binding(0) var mesh: Mesh; -[[group(2), binding(0)]] +@group(2) @binding(0) var view_size: ViewSizeUniforms; -[[group(3), binding(0)]] +@group(3) @binding(0) var vstage: VertexStageData; -[[group(3), binding(1)]] +@group(3) @binding(1) var fstage: FragmentStageData; fn mat4to3(m: mat4x4) -> mat3x3 { @@ -40,7 +40,7 @@ fn mat4to3(m: mat4x4) -> mat3x3 { ); } -[[stage(vertex)]] +@vertex fn vertex(vertex: Vertex) -> VertexOutput { var out: VertexOutput; var clip_pos = view.view_proj * (mesh.model * vec4(vertex.position, 1.0)); @@ -50,11 +50,7 @@ fn vertex(vertex: Vertex) -> VertexOutput { return out; } -struct FragmentInput { - [[builtin(front_facing)]] is_front: bool; -}; - -[[stage(fragment)]] -fn fragment(in: FragmentInput) -> [[location(0)]] vec4 { +@fragment +fn fragment() -> @location(0) vec4 { return fstage.colour; }