diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bdea131a9445e..7287c243e1908 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -242,13 +242,13 @@ jobs: sed -i "s|PATH_TO_CHANGE|$curr|" vk_swiftshader_icd.json; - name: Build bevy run: | - cargo build --no-default-features --features "bevy_dynamic_plugin,bevy_gilrs,bevy_gltf,bevy_wgpu,bevy_winit,render,png,hdr,x11,bevy_ci_testing" + cargo build --no-default-features --features "bevy_dynamic_plugin,bevy_gilrs,bevy_gltf,bevy_winit,render,png,hdr,x11,bevy_ci_testing" - name: Run examples run: | for example in .github/example-run/*.ron; do example_name=`basename $example .ron` echo "running $example_name - "`date` - time CI_TESTING_CONFIG=$example VK_ICD_FILENAMES=$(pwd)/vk_swiftshader_icd.json DRI_PRIME=0 xvfb-run cargo run --example $example_name --no-default-features --features "bevy_dynamic_plugin,bevy_gilrs,bevy_gltf,bevy_wgpu,bevy_winit,render,png,hdr,x11,bevy_ci_testing" + time CI_TESTING_CONFIG=$example VK_ICD_FILENAMES=$(pwd)/vk_swiftshader_icd.json DRI_PRIME=0 xvfb-run cargo run --example $example_name --no-default-features --features "bevy_dynamic_plugin,bevy_gilrs,bevy_gltf,bevy_winit,render,png,hdr,x11,bevy_ci_testing" sleep 10 done diff --git a/Cargo.toml b/Cargo.toml index 5a45103cc56c6..57e3f75ce3d4f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,20 +13,12 @@ repository = "https://github.com/bevyengine/bevy" [workspace] exclude = ["benches", "crates/bevy_ecs_compile_fail_tests"] -members = ["crates/*", "pipelined/*", "examples/ios", "tools/ci", "errors"] +members = ["crates/*", "examples/ios", "tools/ci", "errors"] [features] default = [ "bevy_audio", - "bevy_core_pipeline", "bevy_gilrs", - "bevy_gltf2", - "bevy_wgpu", - "bevy_sprite2", - "bevy_render2", - "bevy_pbr2", - "bevy_ui2", - "bevy_text2", "bevy_winit", "render", "png", @@ -39,9 +31,11 @@ default = [ # Force dynamic linking, which improves iterative compile times dynamic = ["bevy_dylib"] -# Rendering support (Also needs the bevy_wgpu feature or a third-party rendering backend) +# Rendering support render = [ + "bevy_internal/bevy_core_pipeline", "bevy_internal/bevy_pbr", + "bevy_internal/bevy_gltf", "bevy_internal/bevy_render", "bevy_internal/bevy_sprite", "bevy_internal/bevy_text", @@ -50,20 +44,16 @@ render = [ # Optional bevy crates bevy_audio = ["bevy_internal/bevy_audio"] +bevy_core_pipeline = ["bevy_internal/bevy_core_pipeline"] +bevy_render = ["bevy_internal/bevy_render"] +bevy_text = ["bevy_internal/bevy_text"] +bevy_pbr = ["bevy_internal/bevy_pbr"] +bevy_sprite = ["bevy_internal/bevy_sprite"] bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"] bevy_gilrs = ["bevy_internal/bevy_gilrs"] bevy_gltf = ["bevy_internal/bevy_gltf"] -bevy_wgpu = ["bevy_internal/bevy_wgpu"] bevy_winit = ["bevy_internal/bevy_winit"] -bevy_core_pipeline = ["bevy_internal/bevy_core_pipeline"] -bevy_render2 = ["bevy_internal/bevy_render2"] -bevy_sprite2 = ["bevy_internal/bevy_sprite2"] -bevy_pbr2 = ["bevy_internal/bevy_pbr2"] -bevy_gltf2 = ["bevy_internal/bevy_gltf2"] -bevy_ui2 = ["bevy_internal/bevy_ui2"] -bevy_text2 = ["bevy_internal/bevy_text2"] - trace_chrome = ["bevy_internal/trace_chrome"] trace_tracy = ["bevy_internal/trace_tracy"] trace = ["bevy_internal/trace"] @@ -120,10 +110,6 @@ path = "examples/hello_world.rs" name = "contributors" path = "examples/2d/contributors.rs" -[[example]] -name = "mesh" -path = "examples/2d/mesh.rs" - [[example]] name = "many_sprites" path = "examples/2d/many_sprites.rs" @@ -144,60 +130,35 @@ path = "examples/2d/sprite_sheet.rs" name = "text2d" path = "examples/2d/text2d.rs" -[[example]] -name = "text2d_pipelined" -path = "examples/2d/text2d_pipelined.rs" - [[example]] name = "texture_atlas" path = "examples/2d/texture_atlas.rs" -[[example]] -name = "pipelined_texture_atlas" -path = "examples/2d/pipelined_texture_atlas.rs" - # 3D Rendering [[example]] name = "3d_scene" path = "examples/3d/3d_scene.rs" [[example]] -name = "3d_scene_pipelined" -path = "examples/3d/3d_scene_pipelined.rs" - -[[example]] -name = "many_cubes_pipelined" -path = "examples/3d/many_cubes_pipelined.rs" - -[[example]] -name = "cornell_box_pipelined" -path = "examples/3d/cornell_box_pipelined.rs" +name = "lighting" +path = "examples/3d/lighting.rs" [[example]] name = "load_gltf" path = "examples/3d/load_gltf.rs" -required-features = ["bevy_gltf"] [[example]] -name = "load_gltf_pipelined" -path = "examples/3d/load_gltf_pipelined.rs" +name = "many_cubes" +path = "examples/3d/many_cubes.rs" [[example]] name = "msaa" path = "examples/3d/msaa.rs" -[[example]] -name = "msaa_pipelined" -path = "examples/3d/msaa_pipelined.rs" - [[example]] name = "orthographic" path = "examples/3d/orthographic.rs" -[[example]] -name = "orthographic_pipelined" -path = "examples/3d/orthographic_pipelined.rs" - [[example]] name = "parenting" path = "examples/3d/parenting.rs" @@ -207,46 +168,25 @@ name = "pbr" path = "examples/3d/pbr.rs" [[example]] -name = "pbr_pipelined" -path = "examples/3d/pbr_pipelined.rs" - -[[example]] -name = "render_to_texture" -path = "examples/3d/render_to_texture.rs" - -[[example]] -name = "shadow_biases_pipelined" -path = "examples/3d/shadow_biases_pipelined.rs" +name = "shadow_biases" +path = "examples/3d/shadow_biases.rs" [[example]] -name = "shadow_caster_receiver_pipelined" -path = "examples/3d/shadow_caster_receiver_pipelined.rs" - -[[example]] -name = "spawner" -path = "examples/3d/spawner.rs" +name = "shadow_caster_receiver" +path = "examples/3d/shadow_caster_receiver.rs" [[example]] name = "texture" path = "examples/3d/texture.rs" -[[example]] -name = "texture_pipelined" -path = "examples/3d/texture_pipelined.rs" - [[example]] name = "update_gltf_scene" path = "examples/3d/update_gltf_scene.rs" -required-features = ["bevy_gltf"] [[example]] name = "wireframe" path = "examples/3d/wireframe.rs" -[[example]] -name = "z_sort_debug" -path = "examples/3d/z_sort_debug.rs" - # Application [[example]] name = "custom_loop" @@ -292,7 +232,6 @@ path = "examples/app/thread_pool_resources.rs" [[example]] name = "asset_loading" path = "examples/asset/asset_loading.rs" -required-features = ["bevy_gltf"] [[example]] name = "custom_asset" @@ -305,7 +244,6 @@ path = "examples/asset/custom_asset_io.rs" [[example]] name = "hot_asset_reloading" path = "examples/asset/hot_asset_reloading.rs" -required-features = ["bevy_gltf"] # Async Tasks [[example]] @@ -387,7 +325,6 @@ path = "examples/ecs/timers.rs" [[example]] name = "alien_cake_addict" path = "examples/game/alien_cake_addict.rs" -required-features = ["bevy_gltf"] [[example]] name = "breakout" @@ -457,47 +394,19 @@ name = "scene" path = "examples/scene/scene.rs" # Shaders -[[example]] -name = "animate_shader" -path = "examples/shader/animate_shader.rs" - -[[example]] -name = "array_texture" -path = "examples/shader/array_texture.rs" - -[[example]] -name = "hot_shader_reloading" -path = "examples/shader/hot_shader_reloading.rs" - -[[example]] -name = "mesh_custom_attribute" -path = "examples/shader/mesh_custom_attribute.rs" - -[[example]] -name = "shader_custom_material" -path = "examples/shader/shader_custom_material.rs" - [[example]] name = "shader_defs" path = "examples/shader/shader_defs.rs" [[example]] -name = "custom_shader_pipelined" -path = "examples/shader/custom_shader_pipelined.rs" - -[[example]] -name = "shader_defs_pipelined" -path = "examples/shader/shader_defs_pipelined.rs" +name = "shader_material" +path = "examples/shader/shader_material.rs" # Tools [[example]] name = "bevymark" path = "examples/tools/bevymark.rs" -[[example]] -name = "bevymark_pipelined" -path = "examples/tools/bevymark_pipelined.rs" - # UI (User Interface) [[example]] name = "button" @@ -519,24 +428,11 @@ path = "examples/ui/text_debug.rs" name = "ui" path = "examples/ui/ui.rs" -[[example]] -name = "ui_pipelined" -path = "examples/ui/ui_pipelined.rs" - # Window [[example]] name = "clear_color" path = "examples/window/clear_color.rs" -[[example]] -name = "clear_color_pipelined" -path = "examples/window/clear_color_pipelined.rs" - -[[example]] -name = "multiple_windows" -path = "examples/window/multiple_windows.rs" -required-features = ["bevy_gltf"] - [[example]] name = "scale_factor_override" path = "examples/window/scale_factor_override.rs" diff --git a/crates/bevy_asset/src/io/file_asset_io.rs b/crates/bevy_asset/src/io/file_asset_io.rs index efebb81e49ff3..a7124c5982d43 100644 --- a/crates/bevy_asset/src/io/file_asset_io.rs +++ b/crates/bevy_asset/src/io/file_asset_io.rs @@ -84,10 +84,10 @@ impl AssetIo for FileAssetIo { ))) } - fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError> { + fn watch_path_for_changes(&self, _path: &Path) -> Result<(), AssetIoError> { #[cfg(feature = "filesystem_watcher")] { - let path = self.root_path.join(path); + let path = self.root_path.join(_path); let mut watcher = self.filesystem_watcher.write(); if let Some(ref mut watcher) = *watcher { watcher diff --git a/pipelined/bevy_core_pipeline/Cargo.toml b/crates/bevy_core_pipeline/Cargo.toml similarity index 55% rename from pipelined/bevy_core_pipeline/Cargo.toml rename to crates/bevy_core_pipeline/Cargo.toml index 35dec3902346f..e944e2c53cbd5 100644 --- a/pipelined/bevy_core_pipeline/Cargo.toml +++ b/crates/bevy_core_pipeline/Cargo.toml @@ -14,9 +14,9 @@ keywords = ["bevy"] [dependencies] # bevy -bevy_app = { path = "../../crates/bevy_app", version = "0.5.0" } -bevy_asset = { path = "../../crates/bevy_asset", version = "0.5.0" } -bevy_core = { path = "../../crates/bevy_core", version = "0.5.0" } -bevy_ecs = { path = "../../crates/bevy_ecs", version = "0.5.0" } -bevy_render2 = { path = "../bevy_render2", version = "0.5.0" } +bevy_app = { path = "../bevy_app", version = "0.5.0" } +bevy_asset = { path = "../bevy_asset", version = "0.5.0" } +bevy_core = { path = "../bevy_core", version = "0.5.0" } +bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" } +bevy_render = { path = "../bevy_render", version = "0.5.0" } diff --git a/pipelined/bevy_core_pipeline/src/clear_pass.rs b/crates/bevy_core_pipeline/src/clear_pass.rs similarity index 99% rename from pipelined/bevy_core_pipeline/src/clear_pass.rs rename to crates/bevy_core_pipeline/src/clear_pass.rs index 547900f7357d9..89c283aef8b10 100644 --- a/pipelined/bevy_core_pipeline/src/clear_pass.rs +++ b/crates/bevy_core_pipeline/src/clear_pass.rs @@ -2,7 +2,7 @@ use std::collections::HashSet; use crate::ClearColor; use bevy_ecs::prelude::*; -use bevy_render2::{ +use bevy_render::{ camera::ExtractedCamera, render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo}, render_resource::{ diff --git a/pipelined/bevy_core_pipeline/src/clear_pass_driver.rs b/crates/bevy_core_pipeline/src/clear_pass_driver.rs similarity index 95% rename from pipelined/bevy_core_pipeline/src/clear_pass_driver.rs rename to crates/bevy_core_pipeline/src/clear_pass_driver.rs index d70b36ec50f39..405c544f11077 100644 --- a/pipelined/bevy_core_pipeline/src/clear_pass_driver.rs +++ b/crates/bevy_core_pipeline/src/clear_pass_driver.rs @@ -1,5 +1,5 @@ use bevy_ecs::world::World; -use bevy_render2::{ +use bevy_render::{ render_graph::{Node, NodeRunError, RenderGraphContext}, renderer::RenderContext, }; diff --git a/pipelined/bevy_core_pipeline/src/lib.rs b/crates/bevy_core_pipeline/src/lib.rs similarity index 99% rename from pipelined/bevy_core_pipeline/src/lib.rs rename to crates/bevy_core_pipeline/src/lib.rs index bf586eebd7783..24d50dd4c00b3 100644 --- a/pipelined/bevy_core_pipeline/src/lib.rs +++ b/crates/bevy_core_pipeline/src/lib.rs @@ -4,6 +4,11 @@ mod main_pass_2d; mod main_pass_3d; mod main_pass_driver; +pub mod prelude { + #[doc(hidden)] + pub use crate::ClearColor; +} + pub use clear_pass::*; pub use clear_pass_driver::*; pub use main_pass_2d::*; @@ -13,7 +18,7 @@ pub use main_pass_driver::*; use bevy_app::{App, Plugin}; use bevy_core::FloatOrd; use bevy_ecs::prelude::*; -use bevy_render2::{ +use bevy_render::{ camera::{ActiveCameras, CameraPlugin}, color::Color, render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType}, diff --git a/pipelined/bevy_core_pipeline/src/main_pass_2d.rs b/crates/bevy_core_pipeline/src/main_pass_2d.rs similarity index 99% rename from pipelined/bevy_core_pipeline/src/main_pass_2d.rs rename to crates/bevy_core_pipeline/src/main_pass_2d.rs index 9eec2e5ea4246..1ca7a21b79bd6 100644 --- a/pipelined/bevy_core_pipeline/src/main_pass_2d.rs +++ b/crates/bevy_core_pipeline/src/main_pass_2d.rs @@ -1,6 +1,6 @@ use crate::Transparent2d; use bevy_ecs::prelude::*; -use bevy_render2::{ +use bevy_render::{ render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass}, render_resource::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor}, diff --git a/pipelined/bevy_core_pipeline/src/main_pass_3d.rs b/crates/bevy_core_pipeline/src/main_pass_3d.rs similarity index 99% rename from pipelined/bevy_core_pipeline/src/main_pass_3d.rs rename to crates/bevy_core_pipeline/src/main_pass_3d.rs index 617b89f512652..61a571b794d21 100644 --- a/pipelined/bevy_core_pipeline/src/main_pass_3d.rs +++ b/crates/bevy_core_pipeline/src/main_pass_3d.rs @@ -1,6 +1,6 @@ use crate::{AlphaMask3d, Opaque3d, Transparent3d}; use bevy_ecs::prelude::*; -use bevy_render2::{ +use bevy_render::{ render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType}, render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass}, render_resource::{LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor}, diff --git a/pipelined/bevy_core_pipeline/src/main_pass_driver.rs b/crates/bevy_core_pipeline/src/main_pass_driver.rs similarity index 98% rename from pipelined/bevy_core_pipeline/src/main_pass_driver.rs rename to crates/bevy_core_pipeline/src/main_pass_driver.rs index 07382d499598f..c165c636b5624 100644 --- a/pipelined/bevy_core_pipeline/src/main_pass_driver.rs +++ b/crates/bevy_core_pipeline/src/main_pass_driver.rs @@ -1,5 +1,5 @@ use bevy_ecs::world::World; -use bevy_render2::{ +use bevy_render::{ camera::{CameraPlugin, ExtractedCameraNames}, render_graph::{Node, NodeRunError, RenderGraphContext, SlotValue}, renderer::RenderContext, diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 446210d0667ea..d1d58615964aa 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -76,7 +76,7 @@ fn parse_component_attr(ast: &DeriveInput) -> Result { meta_item.path(), format!( "unknown component attribute `{}`", - meta_item.path().into_token_stream().to_string() + meta_item.path().into_token_stream() ), )); } diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index 850c031633ada..a314412102a6d 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -18,6 +18,7 @@ bevy_pbr = { path = "../bevy_pbr", version = "0.5.0" } bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] } bevy_render = { path = "../bevy_render", version = "0.5.0" } bevy_transform = { path = "../bevy_transform", version = "0.5.0" } +bevy_utils = { path = "../bevy_utils", version = "0.5.0" } bevy_math = { path = "../bevy_math", version = "0.5.0" } bevy_scene = { path = "../bevy_scene", version = "0.5.0" } bevy_log = { path = "../bevy_log", version = "0.5.0" } @@ -28,3 +29,4 @@ thiserror = "1.0" anyhow = "1.0.4" base64 = "0.13.0" percent-encoding = "2.1" +wgpu = "0.11.0" diff --git a/crates/bevy_gltf/src/lib.rs b/crates/bevy_gltf/src/lib.rs index 10669fd79ed51..24901f0250383 100644 --- a/crates/bevy_gltf/src/lib.rs +++ b/crates/bevy_gltf/src/lib.rs @@ -1,16 +1,16 @@ -use std::collections::HashMap; +use bevy_utils::HashMap; mod loader; pub use loader::*; use bevy_app::prelude::*; use bevy_asset::{AddAsset, Handle}; -use bevy_pbr::prelude::StandardMaterial; +use bevy_pbr::StandardMaterial; use bevy_reflect::TypeUuid; use bevy_render::mesh::Mesh; use bevy_scene::Scene; -/// Adds support for GLTF file loading to Apps +/// Adds support for glTF file loading to the app. #[derive(Default)] pub struct GltfPlugin; @@ -24,6 +24,7 @@ impl Plugin for GltfPlugin { } } +/// Representation of a loaded glTF file. #[derive(Debug, TypeUuid)] #[uuid = "5c7d5f8a-f7b0-4e45-a09e-406c0372fea2"] pub struct Gltf { @@ -38,6 +39,8 @@ pub struct Gltf { pub default_scene: Option>, } +/// A glTF node with all of its child nodes, its [`GltfMesh`] and +/// [`Transform`](bevy_transform::prelude::Transform). #[derive(Debug, Clone, TypeUuid)] #[uuid = "dad74750-1fd6-460f-ac51-0a7937563865"] pub struct GltfNode { @@ -46,12 +49,14 @@ pub struct GltfNode { pub transform: bevy_transform::prelude::Transform, } +/// A glTF mesh, which may consists of multiple [`GtlfPrimitives`](GltfPrimitive). #[derive(Debug, Clone, TypeUuid)] #[uuid = "8ceaec9a-926a-4f29-8ee3-578a69f42315"] pub struct GltfMesh { pub primitives: Vec, } +/// Part of a [`GltfMesh`] that consists of a [`Mesh`] and an optional [`StandardMaterial`]. #[derive(Debug, Clone, TypeUuid)] #[uuid = "cbfca302-82fd-41cb-af77-cab6b3d50af1"] pub struct GltfPrimitive { diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 74127a3d51af1..e8395df5009e9 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -5,42 +5,41 @@ use bevy_asset::{ use bevy_core::Name; use bevy_ecs::world::World; use bevy_log::warn; -use bevy_math::Mat4; -use bevy_pbr::prelude::{PbrBundle, StandardMaterial}; +use bevy_math::{Mat4, Vec3}; +use bevy_pbr::{AlphaMode, PbrBundle, StandardMaterial}; use bevy_render::{ camera::{ - Camera, CameraProjection, OrthographicProjection, PerspectiveProjection, VisibleEntities, + Camera, CameraPlugin, CameraProjection, OrthographicProjection, PerspectiveProjection, }, + color::Color, mesh::{Indices, Mesh, VertexAttributeValues}, - pipeline::PrimitiveTopology, - prelude::{Color, Texture}, - render_graph::base, - texture::{AddressMode, FilterMode, ImageType, SamplerDescriptor, TextureError, TextureFormat}, + primitives::{Aabb, Frustum}, + texture::{Image, ImageType, TextureError}, + view::VisibleEntities, }; use bevy_scene::Scene; use bevy_transform::{ hierarchy::{BuildWorldChildren, WorldChildBuilder}, prelude::{GlobalTransform, Transform}, }; +use bevy_utils::{HashMap, HashSet}; use gltf::{ mesh::Mode, texture::{MagFilter, MinFilter, WrappingMode}, Material, Primitive, }; -use std::{ - collections::{HashMap, HashSet}, - path::Path, -}; +use std::{collections::VecDeque, path::Path}; use thiserror::Error; +use wgpu::{AddressMode, FilterMode, PrimitiveTopology, SamplerDescriptor, TextureFormat}; use crate::{Gltf, GltfNode}; -/// An error that occurs when loading a GLTF file +/// An error that occurs when loading a glTF file. #[derive(Error, Debug)] pub enum GltfError { #[error("unsupported primitive mode")] UnsupportedPrimitive { mode: Mode }, - #[error("invalid GLTF file: {0}")] + #[error("invalid glTF file: {0}")] Gltf(#[from] gltf::Error), #[error("binary blob is missing")] MissingBlob, @@ -56,7 +55,7 @@ pub enum GltfError { AssetIoError(#[from] AssetIoError), } -/// Loads meshes from GLTF files into Mesh assets +/// Loads glTF files with all of their data as their corresponding bevy representations. #[derive(Default)] pub struct GltfLoader; @@ -74,6 +73,7 @@ impl AssetLoader for GltfLoader { } } +/// Loads an entire glTF file. async fn load_gltf<'a, 'b>( bytes: &'a [u8], load_context: &'a mut LoadContext<'b>, @@ -82,8 +82,8 @@ async fn load_gltf<'a, 'b>( let buffer_data = load_buffers(&gltf, load_context, load_context.path()).await?; let mut materials = vec![]; - let mut named_materials = HashMap::new(); - let mut linear_textures = HashSet::new(); + let mut named_materials = HashMap::default(); + let mut linear_textures = HashSet::default(); for material in gltf.materials() { let handle = load_material(&material, load_context); if let Some(name) = material.name() { @@ -105,7 +105,7 @@ async fn load_gltf<'a, 'b>( } let mut meshes = vec![]; - let mut named_meshes = HashMap::new(); + let mut named_meshes = HashMap::default(); for mesh in gltf.meshes() { let mut primitives = vec![]; for primitive in mesh.primitives() { @@ -148,12 +148,12 @@ async fn load_gltf<'a, 'b>( mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs); } - if let Some(vertex_attribute) = reader - .read_colors(0) - .map(|v| VertexAttributeValues::Float32x4(v.into_rgba_f32().collect())) - { - mesh.set_attribute(Mesh::ATTRIBUTE_COLOR, vertex_attribute); - } + // if let Some(vertex_attribute) = reader + // .read_colors(0) + // .map(|v| VertexAttributeValues::Float32x4(v.into_rgba_f32().collect())) + // { + // mesh.set_attribute(Mesh::ATTRIBUTE_COLOR, vertex_attribute); + // } if let Some(indices) = reader.read_indices() { mesh.set_indices(Some(Indices::U32(indices.into_u32().collect()))); @@ -194,7 +194,7 @@ async fn load_gltf<'a, 'b>( } let mut nodes_intermediate = vec![]; - let mut named_nodes_intermediate = HashMap::new(); + let mut named_nodes_intermediate = HashMap::default(); for node in gltf.nodes() { let node_label = node_label(&node); nodes_intermediate.push(( @@ -228,7 +228,7 @@ async fn load_gltf<'a, 'b>( named_nodes_intermediate.insert(name, node.index()); } } - let nodes = resolve_node_hierarchy(nodes_intermediate) + let nodes = resolve_node_hierarchy(nodes_intermediate, load_context.path()) .into_iter() .map(|(label, node)| load_context.set_labeled_asset(&label, LoadedAsset::new(node))) .collect::>>(); @@ -265,7 +265,7 @@ async fn load_gltf<'a, 'b>( .into_iter() .filter_map(|res| { if let Err(err) = res.as_ref() { - warn!("Error loading GLTF texture: {}", err); + warn!("Error loading glTF texture: {}", err); } res.ok() }) @@ -274,7 +274,7 @@ async fn load_gltf<'a, 'b>( }); let mut scenes = vec![]; - let mut named_scenes = HashMap::new(); + let mut named_scenes = HashMap::default(); for scene in gltf.scenes() { let mut err = None; let mut world = World::default(); @@ -320,18 +320,19 @@ async fn load_gltf<'a, 'b>( Ok(()) } +/// Loads a glTF texture as a bevy [`Image`] and returns it together with its label. async fn load_texture<'a>( gltf_texture: gltf::Texture<'a>, buffer_data: &[Vec], linear_textures: &HashSet, load_context: &LoadContext<'a>, -) -> Result<(Texture, String), GltfError> { +) -> Result<(Image, String), GltfError> { let mut texture = match gltf_texture.source().source() { gltf::image::Source::View { view, mime_type } => { let start = view.offset() as usize; let end = (view.offset() + view.length()) as usize; let buffer = &buffer_data[view.buffer().index()][start..end]; - Texture::from_buffer(buffer, ImageType::MimeType(mime_type))? + Image::from_buffer(buffer, ImageType::MimeType(mime_type))? } gltf::image::Source::Uri { uri, mime_type } => { let uri = percent_encoding::percent_decode_str(uri) @@ -352,20 +353,21 @@ async fn load_texture<'a>( } }; - Texture::from_buffer( + Image::from_buffer( &bytes, mime_type.map(ImageType::MimeType).unwrap_or(image_type), )? } }; - texture.sampler = texture_sampler(&gltf_texture); + texture.sampler_descriptor = texture_sampler(&gltf_texture); if (linear_textures).contains(&gltf_texture.index()) { - texture.format = TextureFormat::Rgba8Unorm; + texture.texture_descriptor.format = TextureFormat::Rgba8Unorm; } Ok((texture, texture_label(&gltf_texture))) } +/// Loads a glTF material as a bevy [`StandardMaterial`] and returns it. fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle { let material_label = material_label(material); @@ -381,15 +383,16 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle< None }; - let normal_map = if let Some(normal_texture) = material.normal_texture() { - // TODO: handle normal_texture.scale - // TODO: handle normal_texture.tex_coord() (the *set* index for the right texcoords) - let label = texture_label(&normal_texture.texture()); - let path = AssetPath::new_ref(load_context.path(), Some(&label)); - Some(load_context.get_handle(path)) - } else { - None - }; + let normal_map_texture: Option> = + if let Some(normal_texture) = material.normal_texture() { + // TODO: handle normal_texture.scale + // TODO: handle normal_texture.tex_coord() (the *set* index for the right texcoords) + let label = texture_label(&normal_texture.texture()); + let path = AssetPath::new_ref(load_context.path(), Some(&label)); + Some(load_context.get_handle(path)) + } else { + None + }; let metallic_roughness_texture = if let Some(info) = pbr.metallic_roughness_texture() { // TODO: handle info.tex_coord() (the *set* index for the right texcoords) @@ -426,20 +429,22 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle< LoadedAsset::new(StandardMaterial { base_color: Color::rgba(color[0], color[1], color[2], color[3]), base_color_texture, - roughness: pbr.roughness_factor(), + perceptual_roughness: pbr.roughness_factor(), metallic: pbr.metallic_factor(), metallic_roughness_texture, - normal_map, + normal_map_texture, double_sided: material.double_sided(), occlusion_texture, emissive: Color::rgba(emissive[0], emissive[1], emissive[2], 1.0), emissive_texture, unlit: material.unlit(), + alpha_mode: alpha_mode(material), ..Default::default() }), ) } +/// Loads a glTF node. fn load_node( gltf_node: &gltf::Node, world_builder: &mut WorldChildBuilder, @@ -459,9 +464,12 @@ fn load_node( // create camera node if let Some(camera) = gltf_node.camera() { - node.insert(VisibleEntities { - ..Default::default() - }); + node.insert_bundle(( + VisibleEntities { + ..Default::default() + }, + Frustum::default(), + )); match camera.projection() { gltf::camera::Projection::Orthographic(orthographic) => { @@ -478,7 +486,7 @@ fn load_node( }; node.insert(Camera { - name: Some(base::camera::CAMERA_2D.to_owned()), + name: Some(CameraPlugin::CAMERA_2D.to_owned()), projection_matrix: orthographic_projection.get_projection_matrix(), ..Default::default() }); @@ -497,7 +505,7 @@ fn load_node( perspective_projection.aspect_ratio = aspect_ratio; } node.insert(Camera { - name: Some(base::camera::CAMERA_3D.to_owned()), + name: Some(CameraPlugin::CAMERA_3D.to_owned()), projection_matrix: perspective_projection.get_projection_matrix(), ..Default::default() }); @@ -526,11 +534,17 @@ fn load_node( let material_asset_path = AssetPath::new_ref(load_context.path(), Some(&material_label)); - parent.spawn_bundle(PbrBundle { - mesh: load_context.get_handle(mesh_asset_path), - material: load_context.get_handle(material_asset_path), - ..Default::default() - }); + let bounds = primitive.bounding_box(); + parent + .spawn_bundle(PbrBundle { + mesh: load_context.get_handle(mesh_asset_path), + material: load_context.get_handle(material_asset_path), + ..Default::default() + }) + .insert(Aabb::from_min_max( + Vec3::from_slice(&bounds.min), + Vec3::from_slice(&bounds.max), + )); } } @@ -549,14 +563,17 @@ fn load_node( } } +/// Returns the label for the `mesh`. fn mesh_label(mesh: &gltf::Mesh) -> String { format!("Mesh{}", mesh.index()) } +/// Returns the label for the `mesh` and `primitive`. fn primitive_label(mesh: &gltf::Mesh, primitive: &Primitive) -> String { format!("Mesh{}/Primitive{}", mesh.index(), primitive.index()) } +/// Returns the label for the `material`. fn material_label(material: &gltf::Material) -> String { if let Some(index) = material.index() { format!("Material{}", index) @@ -565,19 +582,23 @@ fn material_label(material: &gltf::Material) -> String { } } +/// Returns the label for the `texture`. fn texture_label(texture: &gltf::Texture) -> String { format!("Texture{}", texture.index()) } +/// Returns the label for the `node`. fn node_label(node: &gltf::Node) -> String { format!("Node{}", node.index()) } +/// Returns the label for the `scene`. fn scene_label(scene: &gltf::Scene) -> String { format!("Scene{}", scene.index()) } -fn texture_sampler(texture: &gltf::Texture) -> SamplerDescriptor { +/// Extracts the texture sampler data from the glTF texture. +fn texture_sampler<'a>(texture: &gltf::Texture) -> SamplerDescriptor<'a> { let gltf_sampler = texture.sampler(); SamplerDescriptor { @@ -621,6 +642,7 @@ fn texture_sampler(texture: &gltf::Texture) -> SamplerDescriptor { } } +/// Maps the texture address mode form glTF to wgpu. fn texture_address_mode(gltf_address_mode: &gltf::texture::WrappingMode) -> AddressMode { match gltf_address_mode { WrappingMode::ClampToEdge => AddressMode::ClampToEdge, @@ -629,6 +651,7 @@ fn texture_address_mode(gltf_address_mode: &gltf::texture::WrappingMode) -> Addr } } +/// Maps the primitive_topology form glTF to wgpu. fn get_primitive_topology(mode: Mode) -> Result { match mode { Mode::Points => Ok(PrimitiveTopology::PointList), @@ -640,6 +663,15 @@ fn get_primitive_topology(mode: Mode) -> Result { } } +fn alpha_mode(material: &Material) -> AlphaMode { + match material.alpha_mode() { + gltf::material::AlphaMode::Opaque => AlphaMode::Opaque, + gltf::material::AlphaMode::Mask => AlphaMode::Mask(material.alpha_cutoff().unwrap_or(0.5)), + gltf::material::AlphaMode::Blend => AlphaMode::Blend, + } +} + +/// Loads the raw glTF buffer data for a specific glTF file. async fn load_buffers( gltf: &gltf::Gltf, load_context: &LoadContext<'_>, @@ -684,42 +716,51 @@ async fn load_buffers( fn resolve_node_hierarchy( nodes_intermediate: Vec<(String, GltfNode, Vec)>, + asset_path: &Path, ) -> Vec<(String, GltfNode)> { - let mut max_steps = nodes_intermediate.len(); - let mut nodes_step = nodes_intermediate + let mut has_errored = false; + let mut empty_children = VecDeque::new(); + let mut parents = vec![None; nodes_intermediate.len()]; + let mut unprocessed_nodes = nodes_intermediate .into_iter() .enumerate() - .map(|(i, (label, node, children))| (i, label, node, children)) - .collect::>(); - let mut nodes = std::collections::HashMap::::new(); - while max_steps > 0 && !nodes_step.is_empty() { - if let Some((index, label, node, _)) = nodes_step - .iter() - .find(|(_, _, _, children)| children.is_empty()) - .cloned() - { - nodes.insert(index, (label, node)); - for (_, _, node, children) in nodes_step.iter_mut() { - if let Some((i, _)) = children - .iter() - .enumerate() - .find(|(_, child_index)| **child_index == index) - { - children.remove(i); - - if let Some((_, child_node)) = nodes.get(&index) { - node.children.push(child_node.clone()) - } + .map(|(i, (label, node, children))| { + for child in children.iter() { + if let Some(parent) = parents.get_mut(*child) { + *parent = Some(i); + } else if !has_errored { + has_errored = true; + warn!("Unexpected child in GLTF Mesh {}", child); } } - nodes_step = nodes_step - .into_iter() - .filter(|(i, _, _, _)| *i != index) - .collect() + let children = children.into_iter().collect::>(); + if children.is_empty() { + empty_children.push_back(i); + } + (i, (label, node, children)) + }) + .collect::>(); + let mut nodes = std::collections::HashMap::::new(); + while let Some(index) = empty_children.pop_front() { + let (label, node, children) = unprocessed_nodes.remove(&index).unwrap(); + assert!(children.is_empty()); + nodes.insert(index, (label, node)); + if let Some(parent_index) = parents[index] { + let (_, parent_node, parent_children) = + unprocessed_nodes.get_mut(&parent_index).unwrap(); + + assert!(parent_children.remove(&index)); + if let Some((_, child_node)) = nodes.get(&index) { + parent_node.children.push(child_node.clone()) + } + if parent_children.is_empty() { + empty_children.push_back(parent_index); + } } - max_steps -= 1; } - + if !unprocessed_nodes.is_empty() { + warn!("GLTF model must be a tree: {:?}", asset_path); + } let mut nodes_to_sort = nodes.into_iter().collect::>(); nodes_to_sort.sort_by_key(|(i, _)| *i); nodes_to_sort @@ -767,6 +808,8 @@ impl<'a> DataUri<'a> { #[cfg(test)] mod test { + use std::path::PathBuf; + use super::resolve_node_hierarchy; use crate::GltfNode; @@ -781,7 +824,10 @@ mod test { } #[test] fn node_hierarchy_single_node() { - let result = resolve_node_hierarchy(vec![("l1".to_string(), GltfNode::empty(), vec![])]); + let result = resolve_node_hierarchy( + vec![("l1".to_string(), GltfNode::empty(), vec![])], + PathBuf::new().as_path(), + ); assert_eq!(result.len(), 1); assert_eq!(result[0].0, "l1"); @@ -790,10 +836,13 @@ mod test { #[test] fn node_hierarchy_no_hierarchy() { - let result = resolve_node_hierarchy(vec![ - ("l1".to_string(), GltfNode::empty(), vec![]), - ("l2".to_string(), GltfNode::empty(), vec![]), - ]); + let result = resolve_node_hierarchy( + vec![ + ("l1".to_string(), GltfNode::empty(), vec![]), + ("l2".to_string(), GltfNode::empty(), vec![]), + ], + PathBuf::new().as_path(), + ); assert_eq!(result.len(), 2); assert_eq!(result[0].0, "l1"); @@ -804,10 +853,13 @@ mod test { #[test] fn node_hierarchy_simple_hierarchy() { - let result = resolve_node_hierarchy(vec![ - ("l1".to_string(), GltfNode::empty(), vec![1]), - ("l2".to_string(), GltfNode::empty(), vec![]), - ]); + let result = resolve_node_hierarchy( + vec![ + ("l1".to_string(), GltfNode::empty(), vec![1]), + ("l2".to_string(), GltfNode::empty(), vec![]), + ], + PathBuf::new().as_path(), + ); assert_eq!(result.len(), 2); assert_eq!(result[0].0, "l1"); @@ -818,15 +870,18 @@ mod test { #[test] fn node_hierarchy_hierarchy() { - let result = resolve_node_hierarchy(vec![ - ("l1".to_string(), GltfNode::empty(), vec![1]), - ("l2".to_string(), GltfNode::empty(), vec![2]), - ("l3".to_string(), GltfNode::empty(), vec![3, 4, 5]), - ("l4".to_string(), GltfNode::empty(), vec![6]), - ("l5".to_string(), GltfNode::empty(), vec![]), - ("l6".to_string(), GltfNode::empty(), vec![]), - ("l7".to_string(), GltfNode::empty(), vec![]), - ]); + let result = resolve_node_hierarchy( + vec![ + ("l1".to_string(), GltfNode::empty(), vec![1]), + ("l2".to_string(), GltfNode::empty(), vec![2]), + ("l3".to_string(), GltfNode::empty(), vec![3, 4, 5]), + ("l4".to_string(), GltfNode::empty(), vec![6]), + ("l5".to_string(), GltfNode::empty(), vec![]), + ("l6".to_string(), GltfNode::empty(), vec![]), + ("l7".to_string(), GltfNode::empty(), vec![]), + ], + PathBuf::new().as_path(), + ); assert_eq!(result.len(), 7); assert_eq!(result[0].0, "l1"); @@ -847,20 +902,26 @@ mod test { #[test] fn node_hierarchy_cyclic() { - let result = resolve_node_hierarchy(vec![ - ("l1".to_string(), GltfNode::empty(), vec![1]), - ("l2".to_string(), GltfNode::empty(), vec![0]), - ]); + let result = resolve_node_hierarchy( + vec![ + ("l1".to_string(), GltfNode::empty(), vec![1]), + ("l2".to_string(), GltfNode::empty(), vec![0]), + ], + PathBuf::new().as_path(), + ); assert_eq!(result.len(), 0); } #[test] fn node_hierarchy_missing_node() { - let result = resolve_node_hierarchy(vec![ - ("l1".to_string(), GltfNode::empty(), vec![2]), - ("l2".to_string(), GltfNode::empty(), vec![]), - ]); + let result = resolve_node_hierarchy( + vec![ + ("l1".to_string(), GltfNode::empty(), vec![2]), + ("l2".to_string(), GltfNode::empty(), vec![]), + ], + PathBuf::new().as_path(), + ); assert_eq!(result.len(), 1); assert_eq!(result[0].0, "l2"); diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index 0e682aa357009..51a402c10fbf9 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -10,18 +10,18 @@ keywords = ["game", "engine", "gamedev", "graphics", "bevy"] categories = ["game-engines", "graphics", "gui", "rendering"] [features] -wgpu_trace = ["bevy_wgpu/trace"] -trace = [ "bevy_app/trace", "bevy_ecs/trace", "bevy_render2/trace" ] +trace = [ "bevy_app/trace", "bevy_ecs/trace", "bevy_render/trace" ] trace_chrome = [ "bevy_log/tracing-chrome" ] trace_tracy = [ "bevy_log/tracing-tracy" ] +wgpu_trace = ["bevy_render/wgpu_trace"] # Image format support for texture loading (PNG and HDR are enabled by default) -hdr = ["bevy_render/hdr", "bevy_render2/hdr" ] -png = ["bevy_render/png", "bevy_render2/png" ] -dds = ["bevy_render/dds", "bevy_render2/dds" ] -tga = ["bevy_render/tga", "bevy_render2/tga" ] -jpeg = ["bevy_render/jpeg", "bevy_render2/jpeg" ] -bmp = ["bevy_render/bmp", "bevy_render2/bmp" ] +hdr = ["bevy_render/hdr"] +png = ["bevy_render/png"] +dds = ["bevy_render/dds"] +tga = ["bevy_render/tga"] +jpeg = ["bevy_render/jpeg"] +bmp = ["bevy_render/bmp"] # Audio format support (MP3 is enabled by default) flac = ["bevy_audio/flac"] @@ -39,10 +39,10 @@ wayland = ["bevy_winit/wayland"] x11 = ["bevy_winit/x11"] # enable rendering of font glyphs using subpixel accuracy -subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas", "bevy_text2/subpixel_glyph_atlas"] +subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"] # enable systems that allow for automated testing on CI -bevy_ci_testing = ["bevy_app/bevy_ci_testing"] +bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_render/ci_limits"] [dependencies] # bevy @@ -63,21 +63,14 @@ bevy_window = { path = "../bevy_window", version = "0.5.0" } bevy_tasks = { path = "../bevy_tasks", version = "0.5.0" } # bevy (optional) bevy_audio = { path = "../bevy_audio", optional = true, version = "0.5.0" } -bevy_core_pipeline = { path = "../../pipelined/bevy_core_pipeline", optional = true, version = "0.5.0" } +bevy_core_pipeline = { path = "../bevy_core_pipeline", optional = true, version = "0.5.0" } bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.5.0" } -bevy_gltf2 = { path = "../../pipelined/bevy_gltf2", optional = true, version = "0.5.0" } bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.5.0" } -bevy_pbr2 = { path = "../../pipelined/bevy_pbr2", optional = true, version = "0.5.0" } bevy_render = { path = "../bevy_render", optional = true, version = "0.5.0" } -bevy_render2 = { path = "../../pipelined/bevy_render2", optional = true, version = "0.5.0" } bevy_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.5.0" } bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.5.0" } -bevy_sprite2 = { path = "../../pipelined/bevy_sprite2", optional = true, version = "0.5.0" } bevy_text = { path = "../bevy_text", optional = true, version = "0.5.0" } -bevy_text2 = { path = "../../pipelined/bevy_text2", optional = true, version = "0.5.0" } bevy_ui = { path = "../bevy_ui", optional = true, version = "0.5.0" } -bevy_ui2 = { path = "../../pipelined/bevy_ui2", optional = true, version = "0.5.0" } -bevy_wgpu = { path = "../bevy_wgpu", optional = true, version = "0.5.0" } bevy_winit = { path = "../bevy_winit", optional = true, version = "0.5.0" } bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.5.0" } diff --git a/crates/bevy_internal/src/default_plugins.rs b/crates/bevy_internal/src/default_plugins.rs index 26ea57f63300b..db0080ca05b88 100644 --- a/crates/bevy_internal/src/default_plugins.rs +++ b/crates/bevy_internal/src/default_plugins.rs @@ -1,35 +1,5 @@ use bevy_app::{PluginGroup, PluginGroupBuilder}; -use bevy_app::ScheduleRunnerPlugin; -use bevy_asset::AssetPlugin; -#[cfg(feature = "bevy_audio")] -use bevy_audio::AudioPlugin; -use bevy_core::CorePlugin; -use bevy_diagnostic::DiagnosticsPlugin; -#[cfg(feature = "bevy_gilrs")] -use bevy_gilrs::GilrsPlugin; -#[cfg(feature = "bevy_gltf")] -use bevy_gltf::GltfPlugin; -use bevy_input::InputPlugin; -use bevy_log::LogPlugin; -#[cfg(feature = "bevy_pbr")] -use bevy_pbr::PbrPlugin; -#[cfg(feature = "bevy_render")] -use bevy_render::RenderPlugin; -use bevy_scene::ScenePlugin; -#[cfg(feature = "bevy_sprite")] -use bevy_sprite::SpritePlugin; -#[cfg(feature = "bevy_text")] -use bevy_text::TextPlugin; -use bevy_transform::TransformPlugin; -#[cfg(feature = "bevy_ui")] -use bevy_ui::UiPlugin; -#[cfg(feature = "bevy_wgpu")] -use bevy_wgpu::WgpuPlugin; -use bevy_window::WindowPlugin; -#[cfg(feature = "bevy_winit")] -use bevy_winit::WinitPlugin; - /// This plugin group will add all the default plugins: /// * [`LogPlugin`] /// * [`CorePlugin`] @@ -48,51 +18,47 @@ use bevy_winit::WinitPlugin; /// * [`GilrsPlugin`] - with feature `bevy_gilrs` /// * [`GltfPlugin`] - with feature `bevy_gltf` /// * [`WinitPlugin`] - with feature `bevy_winit` -/// * [`WgpuPlugin`] - with feature `bevy_wgpu` /// /// See also [`MinimalPlugins`] for a slimmed down option pub struct DefaultPlugins; impl PluginGroup for DefaultPlugins { fn build(&mut self, group: &mut PluginGroupBuilder) { - group.add(LogPlugin::default()); - group.add(CorePlugin::default()); - group.add(TransformPlugin::default()); - group.add(DiagnosticsPlugin::default()); - group.add(InputPlugin::default()); - group.add(WindowPlugin::default()); - group.add(AssetPlugin::default()); - group.add(ScenePlugin::default()); + group.add(bevy_log::LogPlugin::default()); + group.add(bevy_core::CorePlugin::default()); + group.add(bevy_transform::TransformPlugin::default()); + group.add(bevy_diagnostic::DiagnosticsPlugin::default()); + group.add(bevy_input::InputPlugin::default()); + group.add(bevy_window::WindowPlugin::default()); + group.add(bevy_asset::AssetPlugin::default()); + group.add(bevy_scene::ScenePlugin::default()); - #[cfg(feature = "bevy_render")] - group.add(RenderPlugin::default()); + #[cfg(feature = "bevy_winit")] + group.add(bevy_winit::WinitPlugin::default()); - #[cfg(feature = "bevy_sprite")] - group.add(SpritePlugin::default()); + #[cfg(feature = "bevy_render")] + group.add(bevy_render::RenderPlugin::default()); - #[cfg(feature = "bevy_pbr")] - group.add(PbrPlugin::default()); + #[cfg(feature = "bevy_core_pipeline")] + group.add(bevy_core_pipeline::CorePipelinePlugin::default()); - #[cfg(feature = "bevy_ui")] - group.add(UiPlugin::default()); + #[cfg(feature = "bevy_sprite")] + group.add(bevy_sprite::SpritePlugin::default()); #[cfg(feature = "bevy_text")] - group.add(TextPlugin::default()); + group.add(bevy_text::TextPlugin::default()); - #[cfg(feature = "bevy_audio")] - group.add(AudioPlugin::default()); + #[cfg(feature = "bevy_ui")] + group.add(bevy_ui::UiPlugin::default()); - #[cfg(feature = "bevy_gilrs")] - group.add(GilrsPlugin::default()); + #[cfg(feature = "bevy_pbr")] + group.add(bevy_pbr::PbrPlugin::default()); #[cfg(feature = "bevy_gltf")] - group.add(GltfPlugin::default()); + group.add(bevy_gltf::GltfPlugin::default()); - #[cfg(feature = "bevy_winit")] - group.add(WinitPlugin::default()); - - #[cfg(feature = "bevy_wgpu")] - group.add(WgpuPlugin::default()); + #[cfg(feature = "bevy_audio")] + group.add(bevy_audio::AudioPlugin::default()); } } @@ -105,50 +71,7 @@ pub struct MinimalPlugins; impl PluginGroup for MinimalPlugins { fn build(&mut self, group: &mut PluginGroupBuilder) { - group.add(CorePlugin::default()); - group.add(ScheduleRunnerPlugin::default()); - } -} - -pub struct PipelinedDefaultPlugins; - -impl PluginGroup for PipelinedDefaultPlugins { - fn build(&mut self, group: &mut PluginGroupBuilder) { - group.add(bevy_log::LogPlugin::default()); group.add(bevy_core::CorePlugin::default()); - group.add(bevy_transform::TransformPlugin::default()); - group.add(bevy_diagnostic::DiagnosticsPlugin::default()); - group.add(bevy_input::InputPlugin::default()); - group.add(bevy_window::WindowPlugin::default()); - group.add(bevy_asset::AssetPlugin::default()); - group.add(bevy_scene::ScenePlugin::default()); - - #[cfg(feature = "bevy_winit")] - group.add(bevy_winit::WinitPlugin::default()); - - #[cfg(feature = "bevy_render2")] - { - group.add(bevy_render2::RenderPlugin::default()); - } - - #[cfg(feature = "bevy_core_pipeline")] - { - group.add(bevy_core_pipeline::CorePipelinePlugin::default()); - - #[cfg(feature = "bevy_sprite2")] - group.add(bevy_sprite2::SpritePlugin::default()); - - #[cfg(feature = "bevy_text2")] - group.add(bevy_text2::TextPlugin::default()); - - #[cfg(feature = "bevy_ui2")] - group.add(bevy_ui2::UiPlugin::default()); - - #[cfg(feature = "bevy_pbr2")] - group.add(bevy_pbr2::PbrPlugin::default()); - - #[cfg(feature = "bevy_gltf2")] - group.add(bevy_gltf2::GltfPlugin::default()); - } + group.add(bevy_app::ScheduleRunnerPlugin::default()); } } diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index bd973b12c0060..a0548274abcca 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -99,83 +99,41 @@ pub mod gltf { pub use bevy_gltf::*; } -#[cfg(feature = "bevy_gltf2")] -pub mod gltf2 { - //! Support for GLTF file loading. - pub use bevy_gltf2::*; -} - #[cfg(feature = "bevy_pbr")] pub mod pbr { //! Physically based rendering. pub use bevy_pbr::*; } -#[cfg(feature = "bevy_pbr2")] -pub mod pbr2 { - //! Physically based rendering. - pub use bevy_pbr2::*; -} - #[cfg(feature = "bevy_render")] pub mod render { //! Cameras, meshes, textures, shaders, and pipelines. pub use bevy_render::*; } -#[cfg(feature = "bevy_render2")] -pub mod render2 { - //! Cameras, meshes, textures, shaders, and pipelines. - pub use bevy_render2::*; -} - #[cfg(feature = "bevy_sprite")] pub mod sprite { //! Items for sprites, rects, texture atlases, etc. pub use bevy_sprite::*; } -#[cfg(feature = "bevy_sprite2")] -pub mod sprite2 { - //! Items for sprites, rects, texture atlases, etc. - pub use bevy_sprite2::*; -} - #[cfg(feature = "bevy_text")] pub mod text { //! Text drawing, styling, and font assets. pub use bevy_text::*; } -#[cfg(feature = "bevy_text2")] -pub mod text2 { - //! Text drawing, styling, and font assets. - pub use bevy_text2::*; -} - #[cfg(feature = "bevy_ui")] pub mod ui { //! User interface components and widgets. pub use bevy_ui::*; } -#[cfg(feature = "bevy_ui2")] -pub mod ui2 { - //! User interface components and widgets. - pub use bevy_ui2::*; -} - #[cfg(feature = "bevy_winit")] pub mod winit { pub use bevy_winit::*; } -#[cfg(feature = "bevy_wgpu")] -pub mod wgpu { - //! A render backend utilizing [wgpu](https://wgpu.rs/). - pub use bevy_wgpu::*; -} - #[cfg(feature = "bevy_dynamic_plugin")] pub mod dynamic_plugin { pub use bevy_dynamic_plugin::*; diff --git a/crates/bevy_internal/src/prelude.rs b/crates/bevy_internal/src/prelude.rs index 9362aea02f108..bddc7dee7dc71 100644 --- a/crates/bevy_internal/src/prelude.rs +++ b/crates/bevy_internal/src/prelude.rs @@ -11,6 +11,10 @@ pub use bevy_derive::bevy_main; #[cfg(feature = "bevy_audio")] pub use crate::audio::prelude::*; +#[doc(hidden)] +#[cfg(feature = "bevy_core_pipeline")] +pub use crate::core_pipeline::prelude::*; + #[doc(hidden)] #[cfg(feature = "bevy_pbr")] pub use crate::pbr::prelude::*; diff --git a/crates/bevy_math/src/lib.rs b/crates/bevy_math/src/lib.rs index f209224d9f396..9a8ae797ed04b 100644 --- a/crates/bevy_math/src/lib.rs +++ b/crates/bevy_math/src/lib.rs @@ -8,7 +8,7 @@ pub use glam::*; pub mod prelude { #[doc(hidden)] pub use crate::{ - BVec2, BVec3, BVec4, FaceToward, IVec2, IVec3, IVec4, Mat3, Mat4, Quat, Rect, Size, UVec2, - UVec3, UVec4, Vec2, Vec3, Vec4, + BVec2, BVec3, BVec4, EulerRot, FaceToward, IVec2, IVec3, IVec4, Mat3, Mat4, Quat, Rect, + Size, UVec2, UVec3, UVec4, Vec2, Vec3, Vec4, }; } diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index b978fcf2fa4b6..e943223f99e83 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -13,12 +13,18 @@ keywords = ["bevy"] bevy_app = { path = "../bevy_app", version = "0.5.0" } bevy_asset = { path = "../bevy_asset", version = "0.5.0" } bevy_core = { path = "../bevy_core", version = "0.5.0" } +bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.5.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" } bevy_math = { path = "../bevy_math", version = "0.5.0" } bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] } bevy_render = { path = "../bevy_render", version = "0.5.0" } bevy_transform = { path = "../bevy_transform", version = "0.5.0" } +bevy_utils = { path = "../bevy_utils", version = "0.5.0" } +bevy_window = { path = "../bevy_window", version = "0.5.0" } # other +bitflags = "1.2" # direct dependency required for derive macro bytemuck = { version = "1", features = ["derive"] } +crevice = { path = "../crevice", version = "0.8.0", features = ["glam"] } +wgpu = { version = "0.11.0", features = ["spirv"] } diff --git a/pipelined/bevy_pbr2/src/alpha.rs b/crates/bevy_pbr/src/alpha.rs similarity index 100% rename from pipelined/bevy_pbr2/src/alpha.rs rename to crates/bevy_pbr/src/alpha.rs diff --git a/pipelined/bevy_pbr2/src/bundle.rs b/crates/bevy_pbr/src/bundle.rs similarity index 99% rename from pipelined/bevy_pbr2/src/bundle.rs rename to crates/bevy_pbr/src/bundle.rs index 8a2d3418c257b..cb4db6e96290d 100644 --- a/pipelined/bevy_pbr2/src/bundle.rs +++ b/crates/bevy_pbr/src/bundle.rs @@ -1,7 +1,7 @@ use crate::{DirectionalLight, PointLight, StandardMaterial}; use bevy_asset::Handle; use bevy_ecs::{bundle::Bundle, component::Component}; -use bevy_render2::{ +use bevy_render::{ mesh::Mesh, primitives::{CubemapFrusta, Frustum}, view::{ComputedVisibility, Visibility, VisibleEntities}, diff --git a/crates/bevy_pbr/src/entity.rs b/crates/bevy_pbr/src/entity.rs deleted file mode 100644 index 9b1d3c6314743..0000000000000 --- a/crates/bevy_pbr/src/entity.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::{light::PointLight, material::StandardMaterial, render_graph::PBR_PIPELINE_HANDLE}; -use bevy_asset::Handle; -use bevy_ecs::bundle::Bundle; -use bevy_render::{ - draw::Draw, - mesh::Mesh, - pipeline::{RenderPipeline, RenderPipelines}, - prelude::Visible, - render_graph::base::MainPass, -}; -use bevy_transform::prelude::{GlobalTransform, Transform}; - -/// A component bundle for "pbr mesh" entities -#[derive(Bundle)] -pub struct PbrBundle { - pub mesh: Handle, - pub material: Handle, - pub main_pass: MainPass, - pub draw: Draw, - pub visible: Visible, - pub render_pipelines: RenderPipelines, - pub transform: Transform, - pub global_transform: GlobalTransform, -} - -impl Default for PbrBundle { - fn default() -> Self { - Self { - render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::new( - PBR_PIPELINE_HANDLE.typed(), - )]), - mesh: Default::default(), - visible: Default::default(), - material: Default::default(), - main_pass: Default::default(), - draw: Default::default(), - transform: Default::default(), - global_transform: Default::default(), - } - } -} - -/// A component bundle for "light" entities -#[derive(Debug, Bundle, Default)] -pub struct PointLightBundle { - pub point_light: PointLight, - pub transform: Transform, - pub global_transform: GlobalTransform, -} diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index a99bb80513284..1b979c32eb40c 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -1,55 +1,192 @@ -pub mod render_graph; +pub mod wireframe; -mod entity; +mod alpha; +mod bundle; mod light; mod material; +mod render; -pub use entity::*; +pub use alpha::*; +pub use bundle::*; pub use light::*; pub use material::*; +pub use render::*; pub mod prelude { #[doc(hidden)] pub use crate::{ - entity::*, - light::{DirectionalLight, PointLight}, + alpha::AlphaMode, + bundle::{DirectionalLightBundle, PbrBundle, PointLightBundle}, + light::{AmbientLight, DirectionalLight, PointLight}, material::StandardMaterial, }; } +pub mod draw_3d_graph { + pub mod node { + /// Label for the shadow pass node. + pub const SHADOW_PASS: &str = "shadow_pass"; + } +} + use bevy_app::prelude::*; -use bevy_asset::{AddAsset, Assets, Handle}; -use bevy_render::{prelude::Color, shader}; -use material::StandardMaterial; -use render_graph::add_pbr_graph; +use bevy_asset::{Assets, Handle, HandleUntyped}; +use bevy_core_pipeline::{AlphaMask3d, Opaque3d, Transparent3d}; +use bevy_ecs::prelude::*; +use bevy_reflect::TypeUuid; +use bevy_render::{ + render_component::ExtractComponentPlugin, + render_graph::RenderGraph, + render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions}, + render_resource::{Shader, SpecializedPipelines}, + view::VisibilitySystems, + RenderApp, RenderStage, +}; +use bevy_transform::TransformSystem; -/// NOTE: this isn't PBR yet. consider this name "aspirational" :) +pub const PBR_SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 4805239651767701046); +pub const SHADOW_SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 1836745567947005696); + +/// Sets up the entire PBR infrastructure of bevy. #[derive(Default)] pub struct PbrPlugin; impl Plugin for PbrPlugin { fn build(&self, app: &mut App) { - app.add_asset::() - .register_type::() + let mut shaders = app.world.get_resource_mut::>().unwrap(); + shaders.set_untracked( + PBR_SHADER_HANDLE, + Shader::from_wgsl(include_str!("render/pbr.wgsl")), + ); + shaders.set_untracked( + SHADOW_SHADER_HANDLE, + Shader::from_wgsl(include_str!("render/depth.wgsl")), + ); + + app.add_plugin(StandardMaterialPlugin) + .add_plugin(MeshRenderPlugin) + .add_plugin(ExtractComponentPlugin::>::default()) + .init_resource::() + .init_resource::() + .init_resource::() + .init_resource::() + .init_resource::() + .add_system_to_stage( + CoreStage::PostUpdate, + // NOTE: Clusters need to have been added before update_clusters is run so + // add as an exclusive system + add_clusters + .exclusive_system() + .label(SimulationLightSystems::AddClusters), + ) + .add_system_to_stage( + CoreStage::PostUpdate, + // NOTE: Must come after add_clusters! + update_clusters + .label(SimulationLightSystems::UpdateClusters) + .after(TransformSystem::TransformPropagate), + ) + .add_system_to_stage( + CoreStage::PostUpdate, + assign_lights_to_clusters + .label(SimulationLightSystems::AssignLightsToClusters) + .after(TransformSystem::TransformPropagate) + .after(SimulationLightSystems::UpdateClusters), + ) + .add_system_to_stage( + CoreStage::PostUpdate, + update_directional_light_frusta + .label(SimulationLightSystems::UpdateDirectionalLightFrusta) + .after(TransformSystem::TransformPropagate), + ) .add_system_to_stage( CoreStage::PostUpdate, - shader::asset_shader_defs_system::, + update_point_light_frusta + .label(SimulationLightSystems::UpdatePointLightFrusta) + .after(TransformSystem::TransformPropagate) + .after(SimulationLightSystems::AssignLightsToClusters), ) - .init_resource::(); - add_pbr_graph(&mut app.world); + .add_system_to_stage( + CoreStage::PostUpdate, + check_light_mesh_visibility + .label(SimulationLightSystems::CheckLightVisibility) + .after(TransformSystem::TransformPropagate) + .after(VisibilitySystems::CalculateBounds) + .after(SimulationLightSystems::UpdateDirectionalLightFrusta) + .after(SimulationLightSystems::UpdatePointLightFrusta) + // NOTE: This MUST be scheduled AFTER the core renderer visibility check + // because that resets entity ComputedVisibility for the first view + // which would override any results from this otherwise + .after(VisibilitySystems::CheckVisibility), + ); + + let render_app = app.sub_app(RenderApp); + render_app + .add_system_to_stage( + RenderStage::Extract, + render::extract_clusters.label(RenderLightSystems::ExtractClusters), + ) + .add_system_to_stage( + RenderStage::Extract, + render::extract_lights.label(RenderLightSystems::ExtractLights), + ) + .add_system_to_stage( + RenderStage::Prepare, + // this is added as an exclusive system because it contributes new views. it must run (and have Commands applied) + // _before_ the `prepare_views()` system is run. ideally this becomes a normal system when "stageless" features come out + render::prepare_lights + .exclusive_system() + .label(RenderLightSystems::PrepareLights), + ) + .add_system_to_stage( + RenderStage::Prepare, + // this is added as an exclusive system because it contributes new views. it must run (and have Commands applied) + // _before_ the `prepare_views()` system is run. ideally this becomes a normal system when "stageless" features come out + render::prepare_clusters + .exclusive_system() + .label(RenderLightSystems::PrepareClusters) + .after(RenderLightSystems::PrepareLights), + ) + .add_system_to_stage( + RenderStage::Queue, + render::queue_shadows.label(RenderLightSystems::QueueShadows), + ) + .add_system_to_stage(RenderStage::Queue, queue_meshes) + .add_system_to_stage(RenderStage::Queue, render::queue_shadow_view_bind_group) + .add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::) + .init_resource::() + .init_resource::() + .init_resource::>() + .init_resource::() + .init_resource::() + .init_resource::>() + .init_resource::>(); - // add default StandardMaterial - let mut materials = app - .world - .get_resource_mut::>() + let shadow_pass_node = ShadowPassNode::new(&mut render_app.world); + render_app.add_render_command::(); + render_app.add_render_command::(); + render_app.add_render_command::(); + render_app.add_render_command::(); + let mut graph = render_app.world.get_resource_mut::().unwrap(); + let draw_3d_graph = graph + .get_sub_graph_mut(bevy_core_pipeline::draw_3d_graph::NAME) + .unwrap(); + draw_3d_graph.add_node(draw_3d_graph::node::SHADOW_PASS, shadow_pass_node); + draw_3d_graph + .add_node_edge( + draw_3d_graph::node::SHADOW_PASS, + bevy_core_pipeline::draw_3d_graph::node::MAIN_PASS, + ) + .unwrap(); + draw_3d_graph + .add_slot_edge( + draw_3d_graph.input_node().unwrap().id, + bevy_core_pipeline::draw_3d_graph::input::VIEW_ENTITY, + draw_3d_graph::node::SHADOW_PASS, + ShadowPassNode::IN_VIEW, + ) .unwrap(); - materials.set_untracked( - Handle::::default(), - StandardMaterial { - base_color: Color::PINK, - unlit: true, - ..Default::default() - }, - ); } } diff --git a/crates/bevy_pbr/src/light.rs b/crates/bevy_pbr/src/light.rs index 7ef672045775f..12fcc2dd8694d 100644 --- a/crates/bevy_pbr/src/light.rs +++ b/crates/bevy_pbr/src/light.rs @@ -1,53 +1,79 @@ -use bevy_core::{Pod, Zeroable}; -use bevy_ecs::{component::Component, reflect::ReflectComponent}; -use bevy_math::Vec3; -use bevy_reflect::Reflect; -use bevy_render::color::Color; +use std::collections::HashSet; + +use bevy_ecs::prelude::*; +use bevy_math::{Mat4, UVec2, UVec3, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; +use bevy_render::{ + camera::{Camera, CameraProjection, OrthographicProjection}, + color::Color, + primitives::{Aabb, CubemapFrusta, Frustum, Sphere}, + view::{ComputedVisibility, RenderLayers, Visibility, VisibleEntities}, +}; use bevy_transform::components::GlobalTransform; +use bevy_window::Windows; + +use crate::{ + CubeMapFace, CubemapVisibleEntities, ViewClusterBindings, CUBE_MAP_FACES, POINT_LIGHT_NEAR_Z, +}; -/// A point light -#[derive(Component, Debug, Clone, Copy, Reflect)] -#[reflect(Component)] +/// A light that emits light in all directions from a central point. +/// +/// Real-world values for `intensity` (luminous power in lumens) based on the electrical power +/// consumption of the type of real-world light are: +/// +/// | Luminous Power (lumen) (i.e. the intensity member) | Incandescent non-halogen (Watts) | Incandescent halogen (Watts) | Compact fluorescent (Watts) | LED (Watts | +/// |------|-----|----|--------|-------| +/// | 200 | 25 | | 3-5 | 3 | +/// | 450 | 40 | 29 | 9-11 | 5-8 | +/// | 800 | 60 | | 13-15 | 8-12 | +/// | 1100 | 75 | 53 | 18-20 | 10-16 | +/// | 1600 | 100 | 72 | 24-28 | 14-17 | +/// | 2400 | 150 | | 30-52 | 24-30 | +/// | 3100 | 200 | | 49-75 | 32 | +/// | 4000 | 300 | | 75-100 | 40.5 | +/// +/// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lumen_(unit)#Lighting) +#[derive(Component, Debug, Clone, Copy)] pub struct PointLight { pub color: Color, pub intensity: f32, pub range: f32, pub radius: f32, + pub shadows_enabled: bool, + pub shadow_depth_bias: f32, + /// A bias applied along the direction of the fragment's surface normal. It is scaled to the + /// shadow map's texel size so that it can be small close to the camera and gets larger further + /// away. + pub shadow_normal_bias: f32, } impl Default for PointLight { fn default() -> Self { PointLight { color: Color::rgb(1.0, 1.0, 1.0), - intensity: 200.0, + /// Luminous power in lumens + intensity: 800.0, // Roughly a 60W non-halogen incandescent bulb range: 20.0, radius: 0.0, + shadows_enabled: false, + shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS, + shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS, } } } -#[repr(C)] -#[derive(Debug, Clone, Copy, Pod, Zeroable)] -pub(crate) struct PointLightUniform { - pub pos: [f32; 4], - pub color: [f32; 4], - // storing as a `[f32; 4]` for memory alignement - pub light_params: [f32; 4], +impl PointLight { + pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02; + pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.6; } -impl PointLightUniform { - pub fn new(light: &PointLight, global_transform: &GlobalTransform) -> PointLightUniform { - let (x, y, z) = global_transform.translation.into(); - - // premultiply color by intensity - // we don't use the alpha at all, so no reason to multiply only [0..3] - let color: [f32; 4] = (light.color * light.intensity).into(); +#[derive(Clone, Debug)] +pub struct PointLightShadowMap { + pub size: usize, +} - PointLightUniform { - pos: [x, y, z, 1.0], - color, - light_params: [1.0 / (light.range * light.range), light.radius, 0.0, 0.0], - } +impl Default for PointLightShadowMap { + fn default() -> Self { + Self { size: 1024 } } } @@ -77,86 +103,62 @@ impl PointLightUniform { /// | 32,000–100,000 | Direct sunlight | /// /// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lux) -#[derive(Component, Debug, Clone, Copy, Reflect)] -#[reflect(Component)] +#[derive(Component, Debug, Clone)] pub struct DirectionalLight { pub color: Color, + /// Illuminance in lux pub illuminance: f32, - direction: Vec3, -} - -impl DirectionalLight { - /// Create a new directional light component. - pub fn new(color: Color, illuminance: f32, direction: Vec3) -> Self { - DirectionalLight { - color, - illuminance, - direction: direction.normalize(), - } - } - - /// Set direction of light. - pub fn set_direction(&mut self, direction: Vec3) { - self.direction = direction.normalize(); - } - - pub fn get_direction(&self) -> Vec3 { - self.direction - } + pub shadows_enabled: bool, + pub shadow_projection: OrthographicProjection, + pub shadow_depth_bias: f32, + /// A bias applied along the direction of the fragment's surface normal. It is scaled to the + /// shadow map's texel size so that it is automatically adjusted to the orthographic projection. + pub shadow_normal_bias: f32, } impl Default for DirectionalLight { fn default() -> Self { + let size = 100.0; DirectionalLight { color: Color::rgb(1.0, 1.0, 1.0), illuminance: 100000.0, - direction: Vec3::new(0.0, -1.0, 0.0), + shadows_enabled: false, + shadow_projection: OrthographicProjection { + left: -size, + right: size, + bottom: -size, + top: size, + near: -size, + far: size, + ..Default::default() + }, + shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS, + shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS, } } } -#[repr(C)] -#[derive(Debug, Clone, Copy, Pod, Zeroable)] -pub(crate) struct DirectionalLightUniform { - pub dir: [f32; 4], - pub color: [f32; 4], +impl DirectionalLight { + pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02; + pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.6; } -impl DirectionalLightUniform { - pub fn new(light: &DirectionalLight) -> DirectionalLightUniform { - // direction is negated to be ready for N.L - let dir: [f32; 4] = [ - -light.direction.x, - -light.direction.y, - -light.direction.z, - 0.0, - ]; - - // convert from illuminance (lux) to candelas - // - // exposure is hard coded at the moment but should be replaced - // by values coming from the camera - // see: https://google.github.io/filament/Filament.html#imagingpipeline/physicallybasedcamera/exposuresettings - const APERTURE: f32 = 4.0; - const SHUTTER_SPEED: f32 = 1.0 / 250.0; - const SENSITIVITY: f32 = 100.0; - let ev100 = f32::log2(APERTURE * APERTURE / SHUTTER_SPEED) - f32::log2(SENSITIVITY / 100.0); - let exposure = 1.0 / (f32::powf(2.0, ev100) * 1.2); - let intensity = light.illuminance * exposure; - - // premultiply color by intensity - // we don't use the alpha at all, so no reason to multiply only [0..3] - let color: [f32; 4] = (light.color * intensity).into(); +#[derive(Clone, Debug)] +pub struct DirectionalLightShadowMap { + pub size: usize, +} - DirectionalLightUniform { dir, color } +impl Default for DirectionalLightShadowMap { + fn default() -> Self { + Self { size: 4096 } } } -// Ambient light color. +/// An ambient light, which lights the entire scene equally. #[derive(Debug)] pub struct AmbientLight { pub color: Color, - /// Color is premultiplied by brightness before being passed to the shader + /// A direct scale factor multiplied with `color` before being passed to the shader. pub brightness: f32, } @@ -168,3 +170,591 @@ impl Default for AmbientLight { } } } + +/// Add this component to make a [`Mesh`](bevy_render::mesh::Mesh) not cast shadows. +#[derive(Component)] +pub struct NotShadowCaster; +/// Add this component to make a [`Mesh`](bevy_render::mesh::Mesh) not receive shadows. +#[derive(Component)] +pub struct NotShadowReceiver; + +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +pub enum SimulationLightSystems { + AddClusters, + UpdateClusters, + AssignLightsToClusters, + UpdateDirectionalLightFrusta, + UpdatePointLightFrusta, + CheckLightVisibility, +} + +// Clustered-forward rendering notes +// The main initial reference material used was this rather accessible article: +// http://www.aortiz.me/2018/12/21/CG.html +// Some inspiration was taken from “Practical Clustered Shading” which is part 2 of: +// https://efficientshading.com/2015/01/01/real-time-many-light-management-and-shadows-with-clustered-shading/ +// (Also note that Part 3 of the above shows how we could support the shadow mapping for many lights.) +// The z-slicing method mentioned in the aortiz article is originally from Tiago Sousa’s Siggraph 2016 talk about Doom 2016: +// http://advances.realtimerendering.com/s2016/Siggraph2016_idTech6.pdf + +#[derive(Component, Debug)] +pub struct Clusters { + /// Tile size + pub(crate) tile_size: UVec2, + /// Number of clusters in x / y / z in the view frustum + pub(crate) axis_slices: UVec3, + aabbs: Vec, + pub(crate) lights: Vec, +} + +impl Clusters { + fn new(tile_size: UVec2, screen_size: UVec2, z_slices: u32) -> Self { + let mut clusters = Self { + tile_size, + axis_slices: Default::default(), + aabbs: Default::default(), + lights: Default::default(), + }; + clusters.update(tile_size, screen_size, z_slices); + clusters + } + + fn from_screen_size_and_z_slices(screen_size: UVec2, z_slices: u32) -> Self { + let aspect_ratio = screen_size.x as f32 / screen_size.y as f32; + let n_tiles_y = + ((ViewClusterBindings::MAX_OFFSETS as u32 / z_slices) as f32 / aspect_ratio).sqrt(); + // NOTE: Round down the number of tiles in order to avoid overflowing the maximum number of + // clusters. + let n_tiles = UVec2::new( + (aspect_ratio * n_tiles_y).floor() as u32, + n_tiles_y.floor() as u32, + ); + Clusters::new((screen_size + UVec2::ONE) / n_tiles, screen_size, Z_SLICES) + } + + fn update(&mut self, tile_size: UVec2, screen_size: UVec2, z_slices: u32) { + self.tile_size = tile_size; + self.axis_slices = UVec3::new( + (screen_size.x + 1) / tile_size.x, + (screen_size.y + 1) / tile_size.y, + z_slices, + ); + } +} + +fn clip_to_view(inverse_projection: Mat4, clip: Vec4) -> Vec4 { + let view = inverse_projection * clip; + view / view.w +} + +fn screen_to_view(screen_size: Vec2, inverse_projection: Mat4, screen: Vec2, ndc_z: f32) -> Vec4 { + let tex_coord = screen / screen_size; + let clip = Vec4::new( + tex_coord.x * 2.0 - 1.0, + (1.0 - tex_coord.y) * 2.0 - 1.0, + ndc_z, + 1.0, + ); + clip_to_view(inverse_projection, clip) +} + +// Calculate the intersection of a ray from the eye through the view space position to a z plane +fn line_intersection_to_z_plane(origin: Vec3, p: Vec3, z: f32) -> Vec3 { + let v = p - origin; + let t = (z - Vec3::Z.dot(origin)) / Vec3::Z.dot(v); + origin + t * v +} + +fn compute_aabb_for_cluster( + z_near: f32, + z_far: f32, + tile_size: Vec2, + screen_size: Vec2, + inverse_projection: Mat4, + cluster_dimensions: UVec3, + ijk: UVec3, +) -> Aabb { + let ijk = ijk.as_vec3(); + + // Calculate the minimum and maximum points in screen space + let p_min = ijk.xy() * tile_size; + let p_max = p_min + tile_size; + + // Convert to view space at the near plane + // NOTE: 1.0 is the near plane due to using reverse z projections + let p_min = screen_to_view(screen_size, inverse_projection, p_min, 1.0); + let p_max = screen_to_view(screen_size, inverse_projection, p_max, 1.0); + + let z_far_over_z_near = -z_far / -z_near; + let cluster_near = -z_near * z_far_over_z_near.powf(ijk.z / cluster_dimensions.z as f32); + // NOTE: This could be simplified to: + // let cluster_far = cluster_near * z_far_over_z_near; + let cluster_far = -z_near * z_far_over_z_near.powf((ijk.z + 1.0) / cluster_dimensions.z as f32); + + // Calculate the four intersection points of the min and max points with the cluster near and far planes + let p_min_near = line_intersection_to_z_plane(Vec3::ZERO, p_min.xyz(), cluster_near); + let p_min_far = line_intersection_to_z_plane(Vec3::ZERO, p_min.xyz(), cluster_far); + let p_max_near = line_intersection_to_z_plane(Vec3::ZERO, p_max.xyz(), cluster_near); + let p_max_far = line_intersection_to_z_plane(Vec3::ZERO, p_max.xyz(), cluster_far); + + let cluster_min = p_min_near.min(p_min_far).min(p_max_near.min(p_max_far)); + let cluster_max = p_min_near.max(p_min_far).max(p_max_near.max(p_max_far)); + + Aabb::from_min_max(cluster_min, cluster_max) +} + +const Z_SLICES: u32 = 24; + +pub fn add_clusters( + mut commands: Commands, + windows: Res, + cameras: Query<(Entity, &Camera), Without>, +) { + for (entity, camera) in cameras.iter() { + let window = windows.get(camera.window).unwrap(); + let clusters = Clusters::from_screen_size_and_z_slices( + UVec2::new(window.physical_width(), window.physical_height()), + Z_SLICES, + ); + commands.entity(entity).insert(clusters); + } +} + +pub fn update_clusters(windows: Res, mut views: Query<(&Camera, &mut Clusters)>) { + for (camera, mut clusters) in views.iter_mut() { + let inverse_projection = camera.projection_matrix.inverse(); + let window = windows.get(camera.window).unwrap(); + let screen_size_u32 = UVec2::new(window.physical_width(), window.physical_height()); + *clusters = + Clusters::from_screen_size_and_z_slices(screen_size_u32, clusters.axis_slices.z); + let screen_size = screen_size_u32.as_vec2(); + let tile_size_u32 = clusters.tile_size; + let tile_size = tile_size_u32.as_vec2(); + + // Calculate view space AABBs + // NOTE: It is important that these are iterated in a specific order + // so that we can calculate the cluster index in the fragment shader! + // I (Rob Swain) choose to scan along rows of tiles in x,y, and for each tile then scan + // along z + let mut aabbs = Vec::with_capacity( + (clusters.axis_slices.y * clusters.axis_slices.x * clusters.axis_slices.z) as usize, + ); + for y in 0..clusters.axis_slices.y { + for x in 0..clusters.axis_slices.x { + for z in 0..clusters.axis_slices.z { + aabbs.push(compute_aabb_for_cluster( + camera.near, + camera.far, + tile_size, + screen_size, + inverse_projection, + clusters.axis_slices, + UVec3::new(x, y, z), + )); + } + } + } + clusters.aabbs = aabbs; + } +} + +#[derive(Clone, Component, Debug, Default)] +pub struct VisiblePointLights { + pub entities: Vec, +} + +impl VisiblePointLights { + pub fn from_light_count(count: usize) -> Self { + Self { + entities: Vec::with_capacity(count), + } + } + + pub fn iter(&self) -> impl DoubleEndedIterator { + self.entities.iter() + } + + pub fn len(&self) -> usize { + self.entities.len() + } + + pub fn is_empty(&self) -> bool { + self.entities.is_empty() + } +} + +fn view_z_to_z_slice(cluster_factors: Vec2, view_z: f32) -> u32 { + // NOTE: had to use -view_z to make it positive else log(negative) is nan + ((-view_z).ln() * cluster_factors.x - cluster_factors.y).floor() as u32 +} + +fn ndc_position_to_cluster( + cluster_dimensions: UVec3, + cluster_factors: Vec2, + ndc_p: Vec3, + view_z: f32, +) -> UVec3 { + let cluster_dimensions_f32 = cluster_dimensions.as_vec3(); + let frag_coord = + (ndc_p.xy() * Vec2::new(0.5, -0.5) + Vec2::splat(0.5)).clamp(Vec2::ZERO, Vec2::ONE); + let xy = (frag_coord * cluster_dimensions_f32.xy()).floor(); + let z_slice = view_z_to_z_slice(cluster_factors, view_z); + xy.as_uvec2() + .extend(z_slice) + .clamp(UVec3::ZERO, cluster_dimensions - UVec3::ONE) +} + +fn cluster_to_index(cluster_dimensions: UVec3, cluster: UVec3) -> usize { + ((cluster.y * cluster_dimensions.x + cluster.x) * cluster_dimensions.z + cluster.z) as usize +} + +// NOTE: Run this before update_point_light_frusta! +pub fn assign_lights_to_clusters( + mut commands: Commands, + mut global_lights: ResMut, + mut views: Query<(Entity, &GlobalTransform, &Camera, &Frustum, &mut Clusters)>, + lights: Query<(Entity, &GlobalTransform, &PointLight)>, +) { + let light_count = lights.iter().count(); + let mut global_lights_set = HashSet::with_capacity(light_count); + for (view_entity, view_transform, camera, frustum, mut clusters) in views.iter_mut() { + let view_transform = view_transform.compute_matrix(); + let inverse_view_transform = view_transform.inverse(); + let cluster_count = clusters.aabbs.len(); + let z_slices_of_ln_zfar_over_znear = + clusters.axis_slices.z as f32 / (camera.far / camera.near).ln(); + let cluster_factors = Vec2::new( + z_slices_of_ln_zfar_over_znear, + camera.near.ln() * z_slices_of_ln_zfar_over_znear, + ); + + let mut clusters_lights = + vec![VisiblePointLights::from_light_count(light_count); cluster_count]; + let mut visible_lights_set = HashSet::with_capacity(light_count); + + for (light_entity, light_transform, light) in lights.iter() { + let light_sphere = Sphere { + center: light_transform.translation, + radius: light.range, + }; + + // Check if the light is within the view frustum + if !frustum.intersects_sphere(&light_sphere) { + continue; + } + + // Calculate an AABB for the light in view space, find the corresponding clusters for the min and max + // points of the AABB, then iterate over just those clusters for this light + let light_aabb_view = Aabb { + center: (inverse_view_transform * light_sphere.center.extend(1.0)).xyz(), + half_extents: Vec3::splat(light_sphere.radius), + }; + let (light_aabb_view_min, light_aabb_view_max) = + (light_aabb_view.min(), light_aabb_view.max()); + // Is there a cheaper way to do this? The problem is that because of perspective + // the point at max z but min xy may be less xy in screenspace, and similar. As + // such, projecting the min and max xy at both the closer and further z and taking + // the min and max of those projected points addresses this. + let ( + light_aabb_view_xymin_near, + light_aabb_view_xymin_far, + light_aabb_view_xymax_near, + light_aabb_view_xymax_far, + ) = ( + light_aabb_view_min, + light_aabb_view_min.xy().extend(light_aabb_view_max.z), + light_aabb_view_max.xy().extend(light_aabb_view_min.z), + light_aabb_view_max, + ); + let ( + light_aabb_clip_xymin_near, + light_aabb_clip_xymin_far, + light_aabb_clip_xymax_near, + light_aabb_clip_xymax_far, + ) = ( + camera.projection_matrix * light_aabb_view_xymin_near.extend(1.0), + camera.projection_matrix * light_aabb_view_xymin_far.extend(1.0), + camera.projection_matrix * light_aabb_view_xymax_near.extend(1.0), + camera.projection_matrix * light_aabb_view_xymax_far.extend(1.0), + ); + let ( + light_aabb_ndc_xymin_near, + light_aabb_ndc_xymin_far, + light_aabb_ndc_xymax_near, + light_aabb_ndc_xymax_far, + ) = ( + light_aabb_clip_xymin_near.xyz() / light_aabb_clip_xymin_near.w, + light_aabb_clip_xymin_far.xyz() / light_aabb_clip_xymin_far.w, + light_aabb_clip_xymax_near.xyz() / light_aabb_clip_xymax_near.w, + light_aabb_clip_xymax_far.xyz() / light_aabb_clip_xymax_far.w, + ); + let (light_aabb_ndc_min, light_aabb_ndc_max) = ( + light_aabb_ndc_xymin_near + .min(light_aabb_ndc_xymin_far) + .min(light_aabb_ndc_xymax_near) + .min(light_aabb_ndc_xymax_far), + light_aabb_ndc_xymin_near + .max(light_aabb_ndc_xymin_far) + .max(light_aabb_ndc_xymax_near) + .max(light_aabb_ndc_xymax_far), + ); + let min_cluster = ndc_position_to_cluster( + clusters.axis_slices, + cluster_factors, + light_aabb_ndc_min, + light_aabb_view_min.z, + ); + let max_cluster = ndc_position_to_cluster( + clusters.axis_slices, + cluster_factors, + light_aabb_ndc_max, + light_aabb_view_max.z, + ); + let (min_cluster, max_cluster) = + (min_cluster.min(max_cluster), min_cluster.max(max_cluster)); + for y in min_cluster.y..=max_cluster.y { + for x in min_cluster.x..=max_cluster.x { + for z in min_cluster.z..=max_cluster.z { + let cluster_index = + cluster_to_index(clusters.axis_slices, UVec3::new(x, y, z)); + let cluster_aabb = &clusters.aabbs[cluster_index]; + if light_sphere.intersects_obb(cluster_aabb, &view_transform) { + global_lights_set.insert(light_entity); + visible_lights_set.insert(light_entity); + clusters_lights[cluster_index].entities.push(light_entity); + } + } + } + } + } + + for cluster_lights in clusters_lights.iter_mut() { + cluster_lights.entities.shrink_to_fit(); + } + + clusters.lights = clusters_lights; + commands.entity(view_entity).insert(VisiblePointLights { + entities: visible_lights_set.into_iter().collect(), + }); + } + global_lights.entities = global_lights_set.into_iter().collect(); +} + +pub fn update_directional_light_frusta( + mut views: Query<(&GlobalTransform, &DirectionalLight, &mut Frustum)>, +) { + for (transform, directional_light, mut frustum) in views.iter_mut() { + // The frustum is used for culling meshes to the light for shadow mapping + // so if shadow mapping is disabled for this light, then the frustum is + // not needed. + if !directional_light.shadows_enabled { + continue; + } + + let view_projection = directional_light.shadow_projection.get_projection_matrix() + * transform.compute_matrix().inverse(); + *frustum = Frustum::from_view_projection( + &view_projection, + &transform.translation, + &transform.back(), + directional_light.shadow_projection.far(), + ); + } +} + +// NOTE: Run this after assign_lights_to_clusters! +pub fn update_point_light_frusta( + global_lights: Res, + mut views: Query<(Entity, &GlobalTransform, &PointLight, &mut CubemapFrusta)>, +) { + let projection = + Mat4::perspective_infinite_reverse_rh(std::f32::consts::FRAC_PI_2, 1.0, POINT_LIGHT_NEAR_Z); + let view_rotations = CUBE_MAP_FACES + .iter() + .map(|CubeMapFace { target, up }| GlobalTransform::identity().looking_at(*target, *up)) + .collect::>(); + + let global_lights_set = global_lights + .entities + .iter() + .copied() + .collect::>(); + for (entity, transform, point_light, mut cubemap_frusta) in views.iter_mut() { + // The frusta are used for culling meshes to the light for shadow mapping + // so if shadow mapping is disabled for this light, then the frusta are + // not needed. + // Also, if the light is not relevant for any cluster, it will not be in the + // global lights set and so there is no need to update its frusta. + if !point_light.shadows_enabled || !global_lights_set.contains(&entity) { + continue; + } + + // ignore scale because we don't want to effectively scale light radius and range + // by applying those as a view transform to shadow map rendering of objects + // and ignore rotation because we want the shadow map projections to align with the axes + let view_translation = GlobalTransform::from_translation(transform.translation); + let view_backward = transform.back(); + + for (view_rotation, frustum) in view_rotations.iter().zip(cubemap_frusta.iter_mut()) { + let view = view_translation * *view_rotation; + let view_projection = projection * view.compute_matrix().inverse(); + + *frustum = Frustum::from_view_projection( + &view_projection, + &transform.translation, + &view_backward, + point_light.range, + ); + } + } +} + +pub fn check_light_mesh_visibility( + // NOTE: VisiblePointLights is an alias for VisibleEntities so the Without + // is needed to avoid an unnecessary QuerySet + visible_point_lights: Query<&VisiblePointLights, Without>, + mut point_lights: Query<( + &PointLight, + &GlobalTransform, + &CubemapFrusta, + &mut CubemapVisibleEntities, + Option<&RenderLayers>, + )>, + mut directional_lights: Query<( + &DirectionalLight, + &Frustum, + &mut VisibleEntities, + Option<&RenderLayers>, + )>, + mut visible_entity_query: Query< + ( + Entity, + &Visibility, + &mut ComputedVisibility, + Option<&RenderLayers>, + Option<&Aabb>, + Option<&GlobalTransform>, + ), + Without, + >, +) { + // Directonal lights + for (directional_light, frustum, mut visible_entities, maybe_view_mask) in + directional_lights.iter_mut() + { + visible_entities.entities.clear(); + + // NOTE: If shadow mapping is disabled for the light then it must have no visible entities + if !directional_light.shadows_enabled { + continue; + } + + let view_mask = maybe_view_mask.copied().unwrap_or_default(); + + for ( + entity, + visibility, + mut computed_visibility, + maybe_entity_mask, + maybe_aabb, + maybe_transform, + ) in visible_entity_query.iter_mut() + { + if !visibility.is_visible { + continue; + } + + let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); + if !view_mask.intersects(&entity_mask) { + continue; + } + + // If we have an aabb and transform, do frustum culling + if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) { + if !frustum.intersects_obb(aabb, &transform.compute_matrix()) { + continue; + } + } + + computed_visibility.is_visible = true; + visible_entities.entities.push(entity); + } + + // TODO: check for big changes in visible entities len() vs capacity() (ex: 2x) and resize + // to prevent holding unneeded memory + } + + // Point lights + for visible_lights in visible_point_lights.iter() { + for light_entity in visible_lights.entities.iter().copied() { + if let Ok(( + point_light, + transform, + cubemap_frusta, + mut cubemap_visible_entities, + maybe_view_mask, + )) = point_lights.get_mut(light_entity) + { + for visible_entities in cubemap_visible_entities.iter_mut() { + visible_entities.entities.clear(); + } + + // NOTE: If shadow mapping is disabled for the light then it must have no visible entities + if !point_light.shadows_enabled { + continue; + } + + let view_mask = maybe_view_mask.copied().unwrap_or_default(); + let light_sphere = Sphere { + center: transform.translation, + radius: point_light.range, + }; + + for ( + entity, + visibility, + mut computed_visibility, + maybe_entity_mask, + maybe_aabb, + maybe_transform, + ) in visible_entity_query.iter_mut() + { + if !visibility.is_visible { + continue; + } + + let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); + if !view_mask.intersects(&entity_mask) { + continue; + } + + // If we have an aabb and transform, do frustum culling + if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) { + let model_to_world = transform.compute_matrix(); + // Do a cheap sphere vs obb test to prune out most meshes outside the sphere of the light + if !light_sphere.intersects_obb(aabb, &model_to_world) { + continue; + } + for (frustum, visible_entities) in cubemap_frusta + .iter() + .zip(cubemap_visible_entities.iter_mut()) + { + if frustum.intersects_obb(aabb, &model_to_world) { + computed_visibility.is_visible = true; + visible_entities.entities.push(entity); + } + } + } else { + computed_visibility.is_visible = true; + for visible_entities in cubemap_visible_entities.iter_mut() { + visible_entities.entities.push(entity) + } + } + } + + // TODO: check for big changes in visible entities len() vs capacity() (ex: 2x) and resize + // to prevent holding unneeded memory + } + } + } +} diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 0627c0d4c0a2a..7bf96bebd8701 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -1,47 +1,54 @@ -use bevy_asset::{self, Handle}; +use crate::{AlphaMode, PbrPipeline, StandardMaterialFlags}; +use bevy_app::{App, Plugin}; +use bevy_asset::{AddAsset, Handle}; +use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem}; +use bevy_math::Vec4; use bevy_reflect::TypeUuid; -use bevy_render::{color::Color, renderer::RenderResources, shader::ShaderDefs, texture::Texture}; +use bevy_render::{ + color::Color, + render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets}, + render_resource::{BindGroup, Buffer, BufferInitDescriptor, BufferUsages}, + renderer::RenderDevice, + texture::Image, +}; +use crevice::std140::{AsStd140, Std140}; +use wgpu::{BindGroupDescriptor, BindGroupEntry, BindingResource}; /// A material with "standard" properties used in PBR lighting -/// Standard property values with pictures here -#[derive(Debug, RenderResources, ShaderDefs, TypeUuid)] -#[uuid = "dace545e-4bc6-4595-a79d-c224fc694975"] +/// Standard property values with pictures here +/// . +/// +/// May be created directly from a [`Color`] or an [`Image`]. +#[derive(Debug, Clone, TypeUuid)] +#[uuid = "7494888b-c082-457b-aacf-517228cc0c22"] pub struct StandardMaterial { /// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything - /// in between If used together with a base_color_texture, this is factored into the final + /// in between. If used together with a base_color_texture, this is factored into the final /// base color as `base_color * base_color_texture_value` pub base_color: Color, - #[shader_def] - pub base_color_texture: Option>, + pub base_color_texture: Option>, + // Use a color for user friendliness even though we technically don't use the alpha channel + // Might be used in the future for exposure correction in HDR + pub emissive: Color, + pub emissive_texture: Option>, /// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader /// Defaults to minimum of 0.089 /// If used together with a roughness/metallic texture, this is factored into the final base /// color as `roughness * roughness_texture_value` - pub roughness: f32, + pub perceptual_roughness: f32, /// From [0.0, 1.0], dielectric to pure metallic /// If used together with a roughness/metallic texture, this is factored into the final base /// color as `metallic * metallic_texture_value` pub metallic: f32, + pub metallic_roughness_texture: Option>, /// Specular intensity for non-metals on a linear scale of [0.0, 1.0] /// defaults to 0.5 which is mapped to 4% reflectance in the shader - #[shader_def] - pub metallic_roughness_texture: Option>, pub reflectance: f32, - #[shader_def] - pub normal_map: Option>, - #[render_resources(ignore)] - #[shader_def] + pub normal_map_texture: Option>, + pub occlusion_texture: Option>, pub double_sided: bool, - #[shader_def] - pub occlusion_texture: Option>, - // Use a color for user friendliness even though we technically don't use the alpha channel - // Might be used in the future for exposure correction in HDR - pub emissive: Color, - #[shader_def] - pub emissive_texture: Option>, - #[render_resources(ignore)] - #[shader_def] pub unlit: bool, + pub alpha_mode: AlphaMode, } impl Default for StandardMaterial { @@ -49,26 +56,27 @@ impl Default for StandardMaterial { StandardMaterial { base_color: Color::rgb(1.0, 1.0, 1.0), base_color_texture: None, + emissive: Color::BLACK, + emissive_texture: None, // This is the minimum the roughness is clamped to in shader code - // See https://google.github.io/filament/Filament.html#materialsystem/parameterization/ + // See // It's the minimum floating point value that won't be rounded down to 0 in the // calculations used. Although technically for 32-bit floats, 0.045 could be // used. - roughness: 0.089, + perceptual_roughness: 0.089, // Few materials are purely dielectric or metallic // This is just a default for mostly-dielectric metallic: 0.01, + metallic_roughness_texture: None, // Minimum real-world reflectance is 2%, most materials between 2-5% // Expressed in a linear scale and equivalent to 4% reflectance see - // https://google.github.io/filament/Material%20Properties.pdf - metallic_roughness_texture: None, + // reflectance: 0.5, - normal_map: None, - double_sided: false, occlusion_texture: None, - emissive: Color::BLACK, - emissive_texture: None, + normal_map_texture: None, + double_sided: false, unlit: false, + alpha_mode: AlphaMode::Opaque, } } } @@ -82,11 +90,227 @@ impl From for StandardMaterial { } } -impl From> for StandardMaterial { - fn from(texture: Handle) -> Self { +impl From> for StandardMaterial { + fn from(texture: Handle) -> Self { StandardMaterial { base_color_texture: Some(texture), ..Default::default() } } } + +/// The GPU representation of the uniform data of a [`StandardMaterial`]. +#[derive(Clone, Default, AsStd140)] +pub struct StandardMaterialUniformData { + /// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything + /// in between. + pub base_color: Vec4, + // Use a color for user friendliness even though we technically don't use the alpha channel + // Might be used in the future for exposure correction in HDR + pub emissive: Vec4, + /// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader + /// Defaults to minimum of 0.089 + pub roughness: f32, + /// From [0.0, 1.0], dielectric to pure metallic + pub metallic: f32, + /// Specular intensity for non-metals on a linear scale of [0.0, 1.0] + /// defaults to 0.5 which is mapped to 4% reflectance in the shader + pub reflectance: f32, + pub flags: u32, + /// When the alpha mode mask flag is set, any base color alpha above this cutoff means fully opaque, + /// and any below means fully transparent. + pub alpha_cutoff: f32, +} + +/// This plugin adds the [`StandardMaterial`] asset to the app. +pub struct StandardMaterialPlugin; + +impl Plugin for StandardMaterialPlugin { + fn build(&self, app: &mut App) { + app.add_plugin(RenderAssetPlugin::::default()) + .add_asset::(); + } +} + +/// The GPU representation of a [`StandardMaterial`]. +#[derive(Debug, Clone)] +pub struct GpuStandardMaterial { + /// A buffer containing the [`StandardMaterialUniformData`] of the material. + pub buffer: Buffer, + /// The bind group specifying how the [`StandardMaterialUniformData`] and + /// all the textures of the material are bound. + pub bind_group: BindGroup, + pub has_normal_map: bool, + pub flags: StandardMaterialFlags, + pub base_color_texture: Option>, + pub alpha_mode: AlphaMode, +} + +impl RenderAsset for StandardMaterial { + type ExtractedAsset = StandardMaterial; + type PreparedAsset = GpuStandardMaterial; + type Param = ( + SRes, + SRes, + SRes>, + ); + + fn extract_asset(&self) -> Self::ExtractedAsset { + self.clone() + } + + fn prepare_asset( + material: Self::ExtractedAsset, + (render_device, pbr_pipeline, gpu_images): &mut SystemParamItem, + ) -> Result> { + let (base_color_texture_view, base_color_sampler) = if let Some(result) = pbr_pipeline + .mesh_pipeline + .get_image_texture(gpu_images, &material.base_color_texture) + { + result + } else { + return Err(PrepareAssetError::RetryNextUpdate(material)); + }; + + let (emissive_texture_view, emissive_sampler) = if let Some(result) = pbr_pipeline + .mesh_pipeline + .get_image_texture(gpu_images, &material.emissive_texture) + { + result + } else { + return Err(PrepareAssetError::RetryNextUpdate(material)); + }; + + let (metallic_roughness_texture_view, metallic_roughness_sampler) = if let Some(result) = + pbr_pipeline + .mesh_pipeline + .get_image_texture(gpu_images, &material.metallic_roughness_texture) + { + result + } else { + return Err(PrepareAssetError::RetryNextUpdate(material)); + }; + let (normal_map_texture_view, normal_map_sampler) = if let Some(result) = pbr_pipeline + .mesh_pipeline + .get_image_texture(gpu_images, &material.normal_map_texture) + { + result + } else { + return Err(PrepareAssetError::RetryNextUpdate(material)); + }; + let (occlusion_texture_view, occlusion_sampler) = if let Some(result) = pbr_pipeline + .mesh_pipeline + .get_image_texture(gpu_images, &material.occlusion_texture) + { + result + } else { + return Err(PrepareAssetError::RetryNextUpdate(material)); + }; + let mut flags = StandardMaterialFlags::NONE; + if material.base_color_texture.is_some() { + flags |= StandardMaterialFlags::BASE_COLOR_TEXTURE; + } + if material.emissive_texture.is_some() { + flags |= StandardMaterialFlags::EMISSIVE_TEXTURE; + } + if material.metallic_roughness_texture.is_some() { + flags |= StandardMaterialFlags::METALLIC_ROUGHNESS_TEXTURE; + } + if material.occlusion_texture.is_some() { + flags |= StandardMaterialFlags::OCCLUSION_TEXTURE; + } + if material.double_sided { + flags |= StandardMaterialFlags::DOUBLE_SIDED; + } + if material.unlit { + flags |= StandardMaterialFlags::UNLIT; + } + let has_normal_map = material.normal_map_texture.is_some(); + // NOTE: 0.5 is from the glTF default - do we want this? + let mut alpha_cutoff = 0.5; + match material.alpha_mode { + AlphaMode::Opaque => flags |= StandardMaterialFlags::ALPHA_MODE_OPAQUE, + AlphaMode::Mask(c) => { + alpha_cutoff = c; + flags |= StandardMaterialFlags::ALPHA_MODE_MASK + } + AlphaMode::Blend => flags |= StandardMaterialFlags::ALPHA_MODE_BLEND, + }; + + let value = StandardMaterialUniformData { + base_color: material.base_color.as_linear_rgba_f32().into(), + emissive: material.emissive.into(), + roughness: material.perceptual_roughness, + metallic: material.metallic, + reflectance: material.reflectance, + flags: flags.bits(), + alpha_cutoff, + }; + let value_std140 = value.as_std140(); + + let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor { + label: Some("pbr_standard_material_uniform_buffer"), + usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST, + contents: value_std140.as_bytes(), + }); + let bind_group = render_device.create_bind_group(&BindGroupDescriptor { + entries: &[ + BindGroupEntry { + binding: 0, + resource: buffer.as_entire_binding(), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::TextureView(base_color_texture_view), + }, + BindGroupEntry { + binding: 2, + resource: BindingResource::Sampler(base_color_sampler), + }, + BindGroupEntry { + binding: 3, + resource: BindingResource::TextureView(emissive_texture_view), + }, + BindGroupEntry { + binding: 4, + resource: BindingResource::Sampler(emissive_sampler), + }, + BindGroupEntry { + binding: 5, + resource: BindingResource::TextureView(metallic_roughness_texture_view), + }, + BindGroupEntry { + binding: 6, + resource: BindingResource::Sampler(metallic_roughness_sampler), + }, + BindGroupEntry { + binding: 7, + resource: BindingResource::TextureView(occlusion_texture_view), + }, + BindGroupEntry { + binding: 8, + resource: BindingResource::Sampler(occlusion_sampler), + }, + BindGroupEntry { + binding: 9, + resource: BindingResource::TextureView(normal_map_texture_view), + }, + BindGroupEntry { + binding: 10, + resource: BindingResource::Sampler(normal_map_sampler), + }, + ], + label: Some("pbr_standard_material_bind_group"), + layout: &pbr_pipeline.material_layout, + }); + + Ok(GpuStandardMaterial { + buffer, + bind_group, + flags, + has_normal_map, + base_color_texture: material.base_color_texture, + alpha_mode: material.alpha_mode, + }) + } +} diff --git a/pipelined/bevy_pbr2/src/render/depth.wgsl b/crates/bevy_pbr/src/render/depth.wgsl similarity index 100% rename from pipelined/bevy_pbr2/src/render/depth.wgsl rename to crates/bevy_pbr/src/render/depth.wgsl diff --git a/pipelined/bevy_pbr2/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs similarity index 99% rename from pipelined/bevy_pbr2/src/render/light.rs rename to crates/bevy_pbr/src/render/light.rs index 9b705473a3ea8..e1121e7b6d8d6 100644 --- a/pipelined/bevy_pbr2/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -11,7 +11,7 @@ use bevy_ecs::{ system::{lifetimeless::*, SystemParamItem}, }; use bevy_math::{const_vec3, Mat4, UVec3, UVec4, Vec3, Vec4, Vec4Swizzles}; -use bevy_render2::{ +use bevy_render::{ camera::{Camera, CameraProjection}, color::Color, mesh::Mesh, diff --git a/pipelined/bevy_pbr2/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs similarity index 97% rename from pipelined/bevy_pbr2/src/render/mesh.rs rename to crates/bevy_pbr/src/render/mesh.rs index 115b12fa5f530..dbc94e5fcb10f 100644 --- a/pipelined/bevy_pbr2/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -10,7 +10,7 @@ use bevy_ecs::{ }; use bevy_math::Mat4; use bevy_reflect::TypeUuid; -use bevy_render2::{ +use bevy_render::{ mesh::Mesh, render_asset::RenderAssets, render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin}, @@ -693,16 +693,19 @@ impl EntityRenderCommand for DrawMesh { pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let mesh_handle = mesh_query.get(item).unwrap(); - let gpu_mesh = meshes.into_inner().get(mesh_handle).unwrap(); - pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); - if let Some(index_info) = &gpu_mesh.index_info { - pass.set_index_buffer(index_info.buffer.slice(..), 0, index_info.index_format); - pass.draw_indexed(0..index_info.count, 0, 0..1); + if let Some(gpu_mesh) = meshes.into_inner().get(mesh_handle) { + pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..)); + if let Some(index_info) = &gpu_mesh.index_info { + pass.set_index_buffer(index_info.buffer.slice(..), 0, index_info.index_format); + pass.draw_indexed(0..index_info.count, 0, 0..1); + } else { + panic!("non-indexed drawing not supported yet") + } + + RenderCommandResult::Success } else { - panic!("non-indexed drawing not supported yet") + RenderCommandResult::Failure } - - RenderCommandResult::Success } } diff --git a/pipelined/bevy_pbr2/src/render/mesh.wgsl b/crates/bevy_pbr/src/render/mesh.wgsl similarity index 100% rename from pipelined/bevy_pbr2/src/render/mesh.wgsl rename to crates/bevy_pbr/src/render/mesh.wgsl diff --git a/pipelined/bevy_pbr2/src/render/mesh_struct.wgsl b/crates/bevy_pbr/src/render/mesh_struct.wgsl similarity index 100% rename from pipelined/bevy_pbr2/src/render/mesh_struct.wgsl rename to crates/bevy_pbr/src/render/mesh_struct.wgsl diff --git a/pipelined/bevy_pbr2/src/render/mesh_view_bind_group.wgsl b/crates/bevy_pbr/src/render/mesh_view_bind_group.wgsl similarity index 100% rename from pipelined/bevy_pbr2/src/render/mesh_view_bind_group.wgsl rename to crates/bevy_pbr/src/render/mesh_view_bind_group.wgsl diff --git a/pipelined/bevy_pbr2/src/render/mod.rs b/crates/bevy_pbr/src/render/mod.rs similarity index 98% rename from pipelined/bevy_pbr2/src/render/mod.rs rename to crates/bevy_pbr/src/render/mod.rs index 0b72317a57813..090a2a978435c 100644 --- a/pipelined/bevy_pbr2/src/render/mod.rs +++ b/crates/bevy_pbr/src/render/mod.rs @@ -11,7 +11,7 @@ use bevy_ecs::{ prelude::*, system::{lifetimeless::*, SystemParamItem}, }; -use bevy_render2::{ +use bevy_render::{ mesh::Mesh, render_asset::RenderAssets, render_phase::{ @@ -199,6 +199,15 @@ impl SpecializedPipeline for PbrPipeline { self.material_layout.clone(), self.mesh_pipeline.mesh_layout.clone(), ]); + + if key.normal_map { + descriptor + .fragment + .as_mut() + .unwrap() + .shader_defs + .push(String::from("STANDARDMATERIAL_NORMAL_MAP")); + } if let Some(label) = &mut descriptor.label { *label = format!("pbr_{}", *label).into(); } diff --git a/pipelined/bevy_pbr2/src/render/pbr.wgsl b/crates/bevy_pbr/src/render/pbr.wgsl similarity index 100% rename from pipelined/bevy_pbr2/src/render/pbr.wgsl rename to crates/bevy_pbr/src/render/pbr.wgsl diff --git a/pipelined/bevy_pbr2/src/render/wireframe.wgsl b/crates/bevy_pbr/src/render/wireframe.wgsl similarity index 100% rename from pipelined/bevy_pbr2/src/render/wireframe.wgsl rename to crates/bevy_pbr/src/render/wireframe.wgsl diff --git a/crates/bevy_pbr/src/render_graph/lights_node.rs b/crates/bevy_pbr/src/render_graph/lights_node.rs deleted file mode 100644 index 61ceb80827770..0000000000000 --- a/crates/bevy_pbr/src/render_graph/lights_node.rs +++ /dev/null @@ -1,200 +0,0 @@ -use crate::{ - light::{ - AmbientLight, DirectionalLight, DirectionalLightUniform, PointLight, PointLightUniform, - }, - render_graph::uniform, -}; -use bevy_core::{bytes_of, Pod, Zeroable}; -use bevy_ecs::{ - system::{BoxedSystem, ConfigurableSystem, Local, Query, Res, ResMut}, - world::World, -}; -use bevy_render::{ - render_graph::{CommandQueue, Node, ResourceSlots, SystemNode}, - renderer::{ - BufferId, BufferInfo, BufferMapMode, BufferUsage, RenderContext, RenderResourceBinding, - RenderResourceBindings, RenderResourceContext, - }, -}; -use bevy_transform::prelude::*; - -/// A Render Graph [Node] that write light data from the ECS to GPU buffers -#[derive(Debug, Default)] -pub struct LightsNode { - command_queue: CommandQueue, - max_point_lights: usize, - max_dir_lights: usize, -} - -impl LightsNode { - pub fn new(max_point_lights: usize, max_dir_lights: usize) -> Self { - LightsNode { - max_point_lights, - max_dir_lights, - command_queue: CommandQueue::default(), - } - } -} - -impl Node for LightsNode { - fn update( - &mut self, - _world: &World, - render_context: &mut dyn RenderContext, - _input: &ResourceSlots, - _output: &mut ResourceSlots, - ) { - self.command_queue.execute(render_context); - } -} - -#[repr(C)] -#[derive(Debug, Clone, Copy, Pod, Zeroable)] -struct LightCount { - // storing as a `[u32; 4]` for memory alignement - // Index 0 is for point lights, - // Index 1 is for directional lights - pub num_lights: [u32; 4], -} - -impl SystemNode for LightsNode { - fn get_system(&self) -> BoxedSystem { - let system = lights_node_system.config(|config| { - config.0 = Some(LightsNodeSystemState { - command_queue: self.command_queue.clone(), - max_point_lights: self.max_point_lights, - max_dir_lights: self.max_dir_lights, - light_buffer: None, - staging_buffer: None, - }) - }); - Box::new(system) - } -} - -/// Local "lights node system" state -#[derive(Debug, Default)] -pub struct LightsNodeSystemState { - light_buffer: Option, - staging_buffer: Option, - command_queue: CommandQueue, - max_point_lights: usize, - max_dir_lights: usize, -} - -pub fn lights_node_system( - mut state: Local, - render_resource_context: Res>, - ambient_light_resource: Res, - // TODO: this write on RenderResourceBindings will prevent this system from running in parallel - // with other systems that do the same - mut render_resource_bindings: ResMut, - point_lights: Query<(&PointLight, &GlobalTransform)>, - dir_lights: Query<&DirectionalLight>, -) { - let state = &mut state; - let render_resource_context = &**render_resource_context; - - // premultiply ambient brightness - let ambient_light: [f32; 4] = - (ambient_light_resource.color * ambient_light_resource.brightness).into(); - let ambient_light_size = std::mem::size_of::<[f32; 4]>(); - - let point_light_count = point_lights.iter().len().min(state.max_point_lights); - let point_light_size = std::mem::size_of::(); - let point_light_array_size = point_light_size * point_light_count; - let point_light_array_max_size = point_light_size * state.max_point_lights; - - let dir_light_count = dir_lights.iter().len().min(state.max_dir_lights); - let dir_light_size = std::mem::size_of::(); - let dir_light_array_size = dir_light_size * dir_light_count; - let dir_light_array_max_size = dir_light_size * state.max_dir_lights; - - let light_count_size = ambient_light_size + std::mem::size_of::(); - - let point_light_uniform_start = light_count_size; - let point_light_uniform_end = light_count_size + point_light_array_size; - - let dir_light_uniform_start = light_count_size + point_light_array_max_size; - let dir_light_uniform_end = - light_count_size + point_light_array_max_size + dir_light_array_size; - - let max_light_uniform_size = - light_count_size + point_light_array_max_size + dir_light_array_max_size; - - if let Some(staging_buffer) = state.staging_buffer { - if point_light_count == 0 && dir_light_count == 0 { - return; - } - - render_resource_context.map_buffer(staging_buffer, BufferMapMode::Write); - } else { - let buffer = render_resource_context.create_buffer(BufferInfo { - size: max_light_uniform_size, - buffer_usage: BufferUsage::UNIFORM | BufferUsage::COPY_SRC | BufferUsage::COPY_DST, - ..Default::default() - }); - render_resource_bindings.set( - uniform::LIGHTS, - RenderResourceBinding::Buffer { - buffer, - range: 0..max_light_uniform_size as u64, - dynamic_index: None, - }, - ); - state.light_buffer = Some(buffer); - - let staging_buffer = render_resource_context.create_buffer(BufferInfo { - size: max_light_uniform_size, - buffer_usage: BufferUsage::COPY_SRC | BufferUsage::MAP_WRITE, - mapped_at_creation: true, - }); - state.staging_buffer = Some(staging_buffer); - } - - let staging_buffer = state.staging_buffer.unwrap(); - render_resource_context.write_mapped_buffer( - staging_buffer, - 0..max_light_uniform_size as u64, - &mut |data, _renderer| { - // ambient light - data[0..ambient_light_size].copy_from_slice(bytes_of(&ambient_light)); - - // light count - data[ambient_light_size..light_count_size].copy_from_slice(bytes_of(&[ - point_light_count as u32, - dir_light_count as u32, - 0, - 0, - ])); - - // point light array - for ((point_light, global_transform), slot) in point_lights.iter().zip( - data[point_light_uniform_start..point_light_uniform_end] - .chunks_exact_mut(point_light_size), - ) { - slot.copy_from_slice(bytes_of(&PointLightUniform::new( - point_light, - global_transform, - ))); - } - - // directional light array - for (dir_light, slot) in dir_lights.iter().zip( - data[dir_light_uniform_start..dir_light_uniform_end] - .chunks_exact_mut(dir_light_size), - ) { - slot.copy_from_slice(bytes_of(&DirectionalLightUniform::new(dir_light))); - } - }, - ); - render_resource_context.unmap_buffer(staging_buffer); - let light_buffer = state.light_buffer.unwrap(); - state.command_queue.copy_buffer_to_buffer( - staging_buffer, - 0, - light_buffer, - 0, - max_light_uniform_size as u64, - ); -} diff --git a/crates/bevy_pbr/src/render_graph/mod.rs b/crates/bevy_pbr/src/render_graph/mod.rs deleted file mode 100644 index bc0fcb4732599..0000000000000 --- a/crates/bevy_pbr/src/render_graph/mod.rs +++ /dev/null @@ -1,64 +0,0 @@ -mod lights_node; -mod pbr_pipeline; - -use bevy_ecs::world::World; -pub use lights_node::*; -pub use pbr_pipeline::*; - -/// the names of pbr graph nodes -pub mod node { - pub const TRANSFORM: &str = "transform"; - pub const STANDARD_MATERIAL: &str = "standard_material"; - pub const LIGHTS: &str = "lights"; -} - -/// the names of pbr uniforms -pub mod uniform { - pub const LIGHTS: &str = "Lights"; -} - -use crate::prelude::StandardMaterial; -use bevy_asset::Assets; -use bevy_render::{ - pipeline::PipelineDescriptor, - render_graph::{base, AssetRenderResourcesNode, RenderGraph, RenderResourcesNode}, - shader::Shader, -}; -use bevy_transform::prelude::GlobalTransform; - -pub const MAX_POINT_LIGHTS: usize = 10; -pub const MAX_DIRECTIONAL_LIGHTS: usize = 1; -pub(crate) fn add_pbr_graph(world: &mut World) { - { - let mut graph = world.get_resource_mut::().unwrap(); - graph.add_system_node( - node::TRANSFORM, - RenderResourcesNode::::new(true), - ); - graph.add_system_node( - node::STANDARD_MATERIAL, - AssetRenderResourcesNode::::new(true), - ); - - graph.add_system_node( - node::LIGHTS, - LightsNode::new(MAX_POINT_LIGHTS, MAX_DIRECTIONAL_LIGHTS), - ); - - // TODO: replace these with "autowire" groups - graph - .add_node_edge(node::STANDARD_MATERIAL, base::node::MAIN_PASS) - .unwrap(); - graph - .add_node_edge(node::TRANSFORM, base::node::MAIN_PASS) - .unwrap(); - graph - .add_node_edge(node::LIGHTS, base::node::MAIN_PASS) - .unwrap(); - } - let pipeline = build_pbr_pipeline(&mut world.get_resource_mut::>().unwrap()); - let mut pipelines = world - .get_resource_mut::>() - .unwrap(); - pipelines.set_untracked(PBR_PIPELINE_HANDLE, pipeline); -} diff --git a/crates/bevy_pbr/src/render_graph/pbr_pipeline/mod.rs b/crates/bevy_pbr/src/render_graph/pbr_pipeline/mod.rs deleted file mode 100644 index 2653d7f535107..0000000000000 --- a/crates/bevy_pbr/src/render_graph/pbr_pipeline/mod.rs +++ /dev/null @@ -1,61 +0,0 @@ -use bevy_asset::{Assets, HandleUntyped}; -use bevy_reflect::TypeUuid; -use bevy_render::{ - pipeline::{ - BlendComponent, BlendFactor, BlendOperation, BlendState, ColorTargetState, ColorWrite, - CompareFunction, DepthBiasState, DepthStencilState, PipelineDescriptor, StencilFaceState, - StencilState, - }, - shader::{Shader, ShaderStage, ShaderStages}, - texture::TextureFormat, -}; - -pub const PBR_PIPELINE_HANDLE: HandleUntyped = - HandleUntyped::weak_from_u64(PipelineDescriptor::TYPE_UUID, 13148362314012771389); - -pub(crate) fn build_pbr_pipeline(shaders: &mut Assets) -> PipelineDescriptor { - PipelineDescriptor { - depth_stencil: Some(DepthStencilState { - format: TextureFormat::Depth32Float, - depth_write_enabled: true, - depth_compare: CompareFunction::Less, - stencil: StencilState { - front: StencilFaceState::IGNORE, - back: StencilFaceState::IGNORE, - read_mask: 0, - write_mask: 0, - }, - bias: DepthBiasState { - constant: 0, - slope_scale: 0.0, - clamp: 0.0, - }, - }), - color_target_states: vec![ColorTargetState { - format: TextureFormat::default(), - blend: Some(BlendState { - color: BlendComponent { - src_factor: BlendFactor::SrcAlpha, - dst_factor: BlendFactor::OneMinusSrcAlpha, - operation: BlendOperation::Add, - }, - alpha: BlendComponent { - src_factor: BlendFactor::One, - dst_factor: BlendFactor::One, - operation: BlendOperation::Add, - }, - }), - write_mask: ColorWrite::ALL, - }], - ..PipelineDescriptor::new(ShaderStages { - vertex: shaders.add(Shader::from_glsl( - ShaderStage::Vertex, - include_str!("pbr.vert"), - )), - fragment: Some(shaders.add(Shader::from_glsl( - ShaderStage::Fragment, - include_str!("pbr.frag"), - ))), - }) - } -} diff --git a/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag b/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag deleted file mode 100644 index 20745f7622aa4..0000000000000 --- a/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.frag +++ /dev/null @@ -1,445 +0,0 @@ -#version 450 - -// From the Filament design doc -// https://google.github.io/filament/Filament.html#table_symbols -// Symbol Definition -// v View unit vector -// l Incident light unit vector -// n Surface normal unit vector -// h Half unit vector between l and v -// f BRDF -// f_d Diffuse component of a BRDF -// f_r Specular component of a BRDF -// α Roughness, remapped from using input perceptualRoughness -// σ Diffuse reflectance -// Ω Spherical domain -// f0 Reflectance at normal incidence -// f90 Reflectance at grazing angle -// χ+(a) Heaviside function (1 if a>0 and 0 otherwise) -// nior Index of refraction (IOR) of an interface -// ⟨n⋅l⟩ Dot product clamped to [0..1] -// ⟨a⟩ Saturated value (clamped to [0..1]) - -// The Bidirectional Reflectance Distribution Function (BRDF) describes the surface response of a standard material -// and consists of two components, the diffuse component (f_d) and the specular component (f_r): -// f(v,l) = f_d(v,l) + f_r(v,l) -// -// The form of the microfacet model is the same for diffuse and specular -// f_r(v,l) = f_d(v,l) = 1 / { |n⋅v||n⋅l| } ∫_Ω D(m,α) G(v,l,m) f_m(v,l,m) (v⋅m) (l⋅m) dm -// -// In which: -// D, also called the Normal Distribution Function (NDF) models the distribution of the microfacets -// G models the visibility (or occlusion or shadow-masking) of the microfacets -// f_m is the microfacet BRDF and differs between specular and diffuse components -// -// The above integration needs to be approximated. - -// reflects the constants defined bevy_pbr/src/render_graph/mod.rs -const int MAX_POINT_LIGHTS = 10; -const int MAX_DIRECTIONAL_LIGHTS = 1; - -struct PointLight { - vec4 pos; - vec4 color; - vec4 lightParams; -}; - -struct DirectionalLight { - vec4 direction; - vec4 color; -}; - -layout(location = 0) in vec3 v_WorldPosition; -layout(location = 1) in vec3 v_WorldNormal; -layout(location = 2) in vec2 v_Uv; - -#ifdef STANDARDMATERIAL_NORMAL_MAP -layout(location = 3) in vec4 v_WorldTangent; -#endif - -layout(location = 0) out vec4 o_Target; - -layout(set = 0, binding = 0) uniform CameraViewProj { - mat4 ViewProj; -}; -layout(std140, set = 0, binding = 1) uniform CameraPosition { - vec4 CameraPos; -}; - -layout(std140, set = 1, binding = 0) uniform Lights { - vec4 AmbientColor; - uvec4 NumLights; // x = point lights, y = directional lights - PointLight PointLights[MAX_POINT_LIGHTS]; - DirectionalLight DirectionalLights[MAX_DIRECTIONAL_LIGHTS]; -}; - -layout(set = 3, binding = 0) uniform StandardMaterial_base_color { - vec4 base_color; -}; - -#ifdef STANDARDMATERIAL_BASE_COLOR_TEXTURE -layout(set = 3, binding = 1) uniform texture2D StandardMaterial_base_color_texture; -layout(set = 3, - binding = 2) uniform sampler StandardMaterial_base_color_texture_sampler; -#endif - -#ifndef STANDARDMATERIAL_UNLIT - -layout(set = 3, binding = 3) uniform StandardMaterial_roughness { - float perceptual_roughness; -}; - -layout(set = 3, binding = 4) uniform StandardMaterial_metallic { - float metallic; -}; - -# ifdef STANDARDMATERIAL_METALLIC_ROUGHNESS_TEXTURE -layout(set = 3, binding = 5) uniform texture2D StandardMaterial_metallic_roughness_texture; -layout(set = 3, - binding = 6) uniform sampler StandardMaterial_metallic_roughness_texture_sampler; -# endif - -layout(set = 3, binding = 7) uniform StandardMaterial_reflectance { - float reflectance; -}; - -# ifdef STANDARDMATERIAL_NORMAL_MAP -layout(set = 3, binding = 8) uniform texture2D StandardMaterial_normal_map; -layout(set = 3, - binding = 9) uniform sampler StandardMaterial_normal_map_sampler; -# endif - -# if defined(STANDARDMATERIAL_OCCLUSION_TEXTURE) -layout(set = 3, binding = 10) uniform texture2D StandardMaterial_occlusion_texture; -layout(set = 3, - binding = 11) uniform sampler StandardMaterial_occlusion_texture_sampler; -# endif - -layout(set = 3, binding = 12) uniform StandardMaterial_emissive { - vec4 emissive; -}; - -# if defined(STANDARDMATERIAL_EMISSIVE_TEXTURE) -layout(set = 3, binding = 13) uniform texture2D StandardMaterial_emissive_texture; -layout(set = 3, - binding = 14) uniform sampler StandardMaterial_emissive_texture_sampler; -# endif - -# define saturate(x) clamp(x, 0.0, 1.0) -const float PI = 3.141592653589793; - -float pow5(float x) { - float x2 = x * x; - return x2 * x2 * x; -} - -// distanceAttenuation is simply the square falloff of light intensity -// combined with a smooth attenuation at the edge of the light radius -// -// light radius is a non-physical construct for efficiency purposes, -// because otherwise every light affects every fragment in the scene -float getDistanceAttenuation(float distanceSquare, float inverseRangeSquared) { - float factor = distanceSquare * inverseRangeSquared; - float smoothFactor = saturate(1.0 - factor * factor); - float attenuation = smoothFactor * smoothFactor; - return attenuation * 1.0 / max(distanceSquare, 1e-4); -} - -// Normal distribution function (specular D) -// Based on https://google.github.io/filament/Filament.html#citation-walter07 - -// D_GGX(h,α) = α^2 / { π ((n⋅h)^2 (α2−1) + 1)^2 } - -// Simple implementation, has precision problems when using fp16 instead of fp32 -// see https://google.github.io/filament/Filament.html#listing_speculardfp16 -float D_GGX(float roughness, float NoH, const vec3 h) { - float oneMinusNoHSquared = 1.0 - NoH * NoH; - float a = NoH * roughness; - float k = roughness / (oneMinusNoHSquared + a * a); - float d = k * k * (1.0 / PI); - return d; -} - -// Visibility function (Specular G) -// V(v,l,a) = G(v,l,α) / { 4 (n⋅v) (n⋅l) } -// such that f_r becomes -// f_r(v,l) = D(h,α) V(v,l,α) F(v,h,f0) -// where -// V(v,l,α) = 0.5 / { n⋅l sqrt((n⋅v)^2 (1−α2) + α2) + n⋅v sqrt((n⋅l)^2 (1−α2) + α2) } -// Note the two sqrt's, that may be slow on mobile, see https://google.github.io/filament/Filament.html#listing_approximatedspecularv -float V_SmithGGXCorrelated(float roughness, float NoV, float NoL) { - float a2 = roughness * roughness; - float lambdaV = NoL * sqrt((NoV - a2 * NoV) * NoV + a2); - float lambdaL = NoV * sqrt((NoL - a2 * NoL) * NoL + a2); - float v = 0.5 / (lambdaV + lambdaL); - return v; -} - -// Fresnel function -// see https://google.github.io/filament/Filament.html#citation-schlick94 -// F_Schlick(v,h,f_0,f_90) = f_0 + (f_90 − f_0) (1 − v⋅h)^5 -vec3 F_Schlick(const vec3 f0, float f90, float VoH) { - // not using mix to keep the vec3 and float versions identical - return f0 + (f90 - f0) * pow5(1.0 - VoH); -} - -float F_Schlick(float f0, float f90, float VoH) { - // not using mix to keep the vec3 and float versions identical - return f0 + (f90 - f0) * pow5(1.0 - VoH); -} - -vec3 fresnel(vec3 f0, float LoH) { - // f_90 suitable for ambient occlusion - // see https://google.github.io/filament/Filament.html#lighting/occlusion - float f90 = saturate(dot(f0, vec3(50.0 * 0.33))); - return F_Schlick(f0, f90, LoH); -} - -// Specular BRDF -// https://google.github.io/filament/Filament.html#materialsystem/specularbrdf - -// Cook-Torrance approximation of the microfacet model integration using Fresnel law F to model f_m -// f_r(v,l) = { D(h,α) G(v,l,α) F(v,h,f0) } / { 4 (n⋅v) (n⋅l) } -vec3 specular(vec3 f0, float roughness, const vec3 h, float NoV, float NoL, - float NoH, float LoH, float specularIntensity) { - float D = D_GGX(roughness, NoH, h); - float V = V_SmithGGXCorrelated(roughness, NoV, NoL); - vec3 F = fresnel(f0, LoH); - - return (specularIntensity * D * V) * F; -} - -// Diffuse BRDF -// https://google.github.io/filament/Filament.html#materialsystem/diffusebrdf -// fd(v,l) = σ/π * 1 / { |n⋅v||n⋅l| } ∫Ω D(m,α) G(v,l,m) (v⋅m) (l⋅m) dm - -// simplest approximation -// float Fd_Lambert() { -// return 1.0 / PI; -// } -// -// vec3 Fd = diffuseColor * Fd_Lambert(); - -// Disney approximation -// See https://google.github.io/filament/Filament.html#citation-burley12 -// minimal quality difference -float Fd_Burley(float roughness, float NoV, float NoL, float LoH) { - float f90 = 0.5 + 2.0 * roughness * LoH * LoH; - float lightScatter = F_Schlick(1.0, f90, NoL); - float viewScatter = F_Schlick(1.0, f90, NoV); - return lightScatter * viewScatter * (1.0 / PI); -} - -// From https://www.unrealengine.com/en-US/blog/physically-based-shading-on-mobile -vec3 EnvBRDFApprox(vec3 f0, float perceptual_roughness, float NoV) { - const vec4 c0 = { -1, -0.0275, -0.572, 0.022 }; - const vec4 c1 = { 1, 0.0425, 1.04, -0.04 }; - vec4 r = perceptual_roughness * c0 + c1; - float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y; - vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw; - return f0 * AB.x + AB.y; -} - -float perceptualRoughnessToRoughness(float perceptualRoughness) { - // clamp perceptual roughness to prevent precision problems - // According to Filament design 0.089 is recommended for mobile - // Filament uses 0.045 for non-mobile - float clampedPerceptualRoughness = clamp(perceptualRoughness, 0.089, 1.0); - return clampedPerceptualRoughness * clampedPerceptualRoughness; -} - -// from https://64.github.io/tonemapping/ -// reinhard on RGB oversaturates colors -vec3 reinhard(vec3 color) { - return color / (1.0 + color); -} - -vec3 reinhard_extended(vec3 color, float max_white) { - vec3 numerator = color * (1.0f + (color / vec3(max_white * max_white))); - return numerator / (1.0 + color); -} - -// luminance coefficients from Rec. 709. -// https://en.wikipedia.org/wiki/Rec._709 -float luminance(vec3 v) { - return dot(v, vec3(0.2126, 0.7152, 0.0722)); -} - -vec3 change_luminance(vec3 c_in, float l_out) { - float l_in = luminance(c_in); - return c_in * (l_out / l_in); -} - -vec3 reinhard_luminance(vec3 color) { - float l_old = luminance(color); - float l_new = l_old / (1.0f + l_old); - return change_luminance(color, l_new); -} - -vec3 reinhard_extended_luminance(vec3 color, float max_white_l) { - float l_old = luminance(color); - float numerator = l_old * (1.0f + (l_old / (max_white_l * max_white_l))); - float l_new = numerator / (1.0f + l_old); - return change_luminance(color, l_new); -} - -vec3 point_light(PointLight light, float roughness, float NdotV, vec3 N, vec3 V, vec3 R, vec3 F0, vec3 diffuseColor) { - vec3 light_to_frag = light.pos.xyz - v_WorldPosition.xyz; - float distance_square = dot(light_to_frag, light_to_frag); - float rangeAttenuation = - getDistanceAttenuation(distance_square, light.lightParams.r); - - // Specular. - // Representative Point Area Lights. - // see http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf p14-16 - float a = roughness; - float radius = light.lightParams.g; - vec3 centerToRay = dot(light_to_frag, R) * R - light_to_frag; - vec3 closestPoint = light_to_frag + centerToRay * saturate(radius * inversesqrt(dot(centerToRay, centerToRay))); - float LspecLengthInverse = inversesqrt(dot(closestPoint, closestPoint)); - float normalizationFactor = a / saturate(a + (radius * 0.5 * LspecLengthInverse)); - float specularIntensity = normalizationFactor * normalizationFactor; - - vec3 L = closestPoint * LspecLengthInverse; // normalize() equivalent? - vec3 H = normalize(L + V); - float NoL = saturate(dot(N, L)); - float NoH = saturate(dot(N, H)); - float LoH = saturate(dot(L, H)); - - vec3 specular = specular(F0, roughness, H, NdotV, NoL, NoH, LoH, specularIntensity); - - // Diffuse. - // Comes after specular since its NoL is used in the lighting equation. - L = normalize(light_to_frag); - H = normalize(L + V); - NoL = saturate(dot(N, L)); - NoH = saturate(dot(N, H)); - LoH = saturate(dot(L, H)); - - vec3 diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH); - - // Lout = f(v,l) Φ / { 4 π d^2 }⟨n⋅l⟩ - // where - // f(v,l) = (f_d(v,l) + f_r(v,l)) * light_color - // Φ is light intensity - - // our rangeAttentuation = 1 / d^2 multiplied with an attenuation factor for smoothing at the edge of the non-physical maximum light radius - // It's not 100% clear where the 1/4π goes in the derivation, but we follow the filament shader and leave it out - - // See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminanceEquation - // TODO compensate for energy loss https://google.github.io/filament/Filament.html#materialsystem/improvingthebrdfs/energylossinspecularreflectance - // light.color.rgb is premultiplied with light.intensity on the CPU - return ((diffuse + specular) * light.color.rgb) * (rangeAttenuation * NoL); -} - -vec3 dir_light(DirectionalLight light, float roughness, float NdotV, vec3 normal, vec3 view, vec3 R, vec3 F0, vec3 diffuseColor) { - vec3 incident_light = light.direction.xyz; - - vec3 half_vector = normalize(incident_light + view); - float NoL = saturate(dot(normal, incident_light)); - float NoH = saturate(dot(normal, half_vector)); - float LoH = saturate(dot(incident_light, half_vector)); - - vec3 diffuse = diffuseColor * Fd_Burley(roughness, NdotV, NoL, LoH); - float specularIntensity = 1.0; - vec3 specular = specular(F0, roughness, half_vector, NdotV, NoL, NoH, LoH, specularIntensity); - - return (specular + diffuse) * light.color.rgb * NoL; -} - -#endif - -void main() { - vec4 output_color = base_color; -#ifdef STANDARDMATERIAL_BASE_COLOR_TEXTURE - output_color *= texture(sampler2D(StandardMaterial_base_color_texture, - StandardMaterial_base_color_texture_sampler), - v_Uv); -#endif - -#ifndef STANDARDMATERIAL_UNLIT - // calculate non-linear roughness from linear perceptualRoughness -# ifdef STANDARDMATERIAL_METALLIC_ROUGHNESS_TEXTURE - vec4 metallic_roughness = texture(sampler2D(StandardMaterial_metallic_roughness_texture, StandardMaterial_metallic_roughness_texture_sampler), v_Uv); - // Sampling from GLTF standard channels for now - float metallic = metallic * metallic_roughness.b; - float perceptual_roughness = perceptual_roughness * metallic_roughness.g; -# endif - - float roughness = perceptualRoughnessToRoughness(perceptual_roughness); - - vec3 N = normalize(v_WorldNormal); - -# ifdef STANDARDMATERIAL_NORMAL_MAP - vec3 T = normalize(v_WorldTangent.xyz); - vec3 B = cross(N, T) * v_WorldTangent.w; -# endif - -# ifdef STANDARDMATERIAL_DOUBLE_SIDED - N = gl_FrontFacing ? N : -N; -# ifdef STANDARDMATERIAL_NORMAL_MAP - T = gl_FrontFacing ? T : -T; - B = gl_FrontFacing ? B : -B; -# endif -# endif - -# ifdef STANDARDMATERIAL_NORMAL_MAP - mat3 TBN = mat3(T, B, N); - N = TBN * normalize(texture(sampler2D(StandardMaterial_normal_map, StandardMaterial_normal_map_sampler), v_Uv).rgb * 2.0 - 1.0); -# endif - -# ifdef STANDARDMATERIAL_OCCLUSION_TEXTURE - float occlusion = texture(sampler2D(StandardMaterial_occlusion_texture, StandardMaterial_occlusion_texture_sampler), v_Uv).r; -# else - float occlusion = 1.0; -# endif - -# ifdef STANDARDMATERIAL_EMISSIVE_TEXTURE - vec4 emissive = emissive; - // TODO use .a for exposure compensation in HDR - emissive.rgb *= texture(sampler2D(StandardMaterial_emissive_texture, StandardMaterial_emissive_texture_sampler), v_Uv).rgb; -# endif - - vec3 V; - if (ViewProj[3][3] != 1.0) { // If the projection is not orthographic - V = normalize(CameraPos.xyz - v_WorldPosition.xyz); // Only valid for a perpective projection - } else { - V = normalize(vec3(-ViewProj[0][2],-ViewProj[1][2],-ViewProj[2][2])); // Ortho view vec - } - // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886" - float NdotV = max(dot(N, V), 1e-4); - - // Remapping [0,1] reflectance to F0 - // See https://google.github.io/filament/Filament.html#materialsystem/parameterization/remapping - vec3 F0 = 0.16 * reflectance * reflectance * (1.0 - metallic) + output_color.rgb * metallic; - - // Diffuse strength inversely related to metallicity - vec3 diffuseColor = output_color.rgb * (1.0 - metallic); - - vec3 R = reflect(-V, N); - - // accumulate color - vec3 light_accum = vec3(0.0); - for (int i = 0; i < int(NumLights.x) && i < MAX_POINT_LIGHTS; ++i) { - light_accum += point_light(PointLights[i], roughness, NdotV, N, V, R, F0, diffuseColor); - } - for (int i = 0; i < int(NumLights.y) && i < MAX_DIRECTIONAL_LIGHTS; ++i) { - light_accum += dir_light(DirectionalLights[i], roughness, NdotV, N, V, R, F0, diffuseColor); - } - - vec3 diffuse_ambient = EnvBRDFApprox(diffuseColor, 1.0, NdotV); - vec3 specular_ambient = EnvBRDFApprox(F0, perceptual_roughness, NdotV); - - output_color.rgb = light_accum; - output_color.rgb += (diffuse_ambient + specular_ambient) * AmbientColor.xyz * occlusion; - output_color.rgb += emissive.rgb * output_color.a; - - // tone_mapping - output_color.rgb = reinhard_luminance(output_color.rgb); - // Gamma correction. - // Not needed with sRGB buffer - // output_color.rgb = pow(output_color.rgb, vec3(1.0 / 2.2)); -#endif - - o_Target = output_color; -} diff --git a/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.vert b/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.vert deleted file mode 100644 index 533a163a1c903..0000000000000 --- a/crates/bevy_pbr/src/render_graph/pbr_pipeline/pbr.vert +++ /dev/null @@ -1,36 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 Vertex_Position; -layout(location = 1) in vec3 Vertex_Normal; -layout(location = 2) in vec2 Vertex_Uv; - -#ifdef STANDARDMATERIAL_NORMAL_MAP -layout(location = 3) in vec4 Vertex_Tangent; -#endif - -layout(location = 0) out vec3 v_WorldPosition; -layout(location = 1) out vec3 v_WorldNormal; -layout(location = 2) out vec2 v_Uv; - -layout(set = 0, binding = 0) uniform CameraViewProj { - mat4 ViewProj; -}; - -#ifdef STANDARDMATERIAL_NORMAL_MAP -layout(location = 3) out vec4 v_WorldTangent; -#endif - -layout(set = 2, binding = 0) uniform Transform { - mat4 Model; -}; - -void main() { - vec4 world_position = Model * vec4(Vertex_Position, 1.0); - v_WorldPosition = world_position.xyz; - v_WorldNormal = mat3(Model) * Vertex_Normal; - v_Uv = Vertex_Uv; -#ifdef STANDARDMATERIAL_NORMAL_MAP - v_WorldTangent = vec4(mat3(Model) * Vertex_Tangent.xyz, Vertex_Tangent.w); -#endif - gl_Position = ViewProj * world_position; -} diff --git a/pipelined/bevy_pbr2/src/wireframe.rs b/crates/bevy_pbr/src/wireframe.rs similarity index 97% rename from pipelined/bevy_pbr2/src/wireframe.rs rename to crates/bevy_pbr/src/wireframe.rs index ee90cd21a18c7..1241393a41e44 100644 --- a/pipelined/bevy_pbr2/src/wireframe.rs +++ b/crates/bevy_pbr/src/wireframe.rs @@ -6,7 +6,7 @@ use bevy_core_pipeline::Opaque3d; use bevy_ecs::{prelude::*, reflect::ReflectComponent}; use bevy_reflect::Reflect; use bevy_reflect::TypeUuid; -use bevy_render2::{ +use bevy_render::{ mesh::Mesh, render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline}, render_resource::{RenderPipelineCache, Shader, SpecializedPipeline, SpecializedPipelines}, @@ -80,10 +80,7 @@ impl FromWorld for WireframePipeline { impl SpecializedPipeline for WireframePipeline { type Key = MeshPipelineKey; - fn specialize( - &self, - key: Self::Key, - ) -> bevy_render2::render_resource::RenderPipelineDescriptor { + fn specialize(&self, key: Self::Key) -> bevy_render::render_resource::RenderPipelineDescriptor { let mut descriptor = self.mesh_pipeline.specialize(key); descriptor.vertex.shader = self.shader.clone_weak(); descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak(); diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index c3ac5c6167dc0..ebcd6fd708594 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -8,6 +8,17 @@ repository = "https://github.com/bevyengine/bevy" license = "MIT OR Apache-2.0" keywords = ["bevy"] +[features] +png = ["image/png"] +hdr = ["image/hdr"] +dds = ["image/dds"] +tga = ["image/tga"] +jpeg = ["image/jpeg"] +bmp = ["image/bmp"] +trace = [] +wgpu_trace = ["wgpu/trace"] +ci_limits = [] + [dependencies] # bevy bevy_app = { path = "../bevy_app", version = "0.5.0" } @@ -25,29 +36,21 @@ bevy_utils = { path = "../bevy_utils", version = "0.5.0" } image = { version = "0.23.12", default-features = false } # misc +wgpu = { version = "0.11.0", features = ["spirv"] } +naga = { version = "0.7.0", features = ["glsl-in", "spv-in", "spv-out", "wgsl-in", "wgsl-out"] } serde = { version = "1", features = ["derive"] } bitflags = "1.2.1" +smallvec = { version = "1.6", features = ["union", "const_generics"] } once_cell = "1.4.1" # TODO: replace once_cell with std equivalent if/when this lands: https://github.com/rust-lang/rfcs/pull/2788 downcast-rs = "1.2.0" thiserror = "1.0" -anyhow = "1.0.4" +futures-lite = "1.4.0" +anyhow = "1.0" hex = "0.4.2" hexasphere = "6.0.0" parking_lot = "0.11.0" +regex = "1.5" +crevice = { path = "../crevice", version = "0.8.0", features = ["glam"] } -[target.'cfg(not(target_arch = "wasm32"))'.dependencies] -spirv-reflect = "0.2.3" - -[target.'cfg(any(all(target_arch="x86_64", target_os="linux", target_env="gnu"), all(target_arch="x86_64", target_os="macos"), all(target_arch="aarch64", target_os="android"), all(target_arch="armv7", target_os="androidabi"), all(target_arch="x86_64", target_os="windows", target_env="msvc")))'.dependencies] -bevy-glsl-to-spirv = "0.2.0" - -[target.'cfg(not(any(target_arch = "wasm32", all(target_arch="x86_64", target_os="linux", target_env="gnu"), all(target_arch="x86_64", target_os="macos"), all(target_arch="aarch64", target_os="android"), all(target_arch="armv7", target_os="androidabi"), all(target_arch="x86_64", target_os="windows", target_env="msvc"))))'.dependencies] -shaderc = "0.7.0" - -[features] -png = ["image/png"] -hdr = ["image/hdr"] -dds = ["image/dds"] -tga = ["image/tga"] -jpeg = ["image/jpeg"] -bmp = ["image/bmp"] +[target.'cfg(target_arch = "wasm32")'.dependencies] +wgpu = { version = "0.11.0", features = ["spirv", "webgl"] } diff --git a/crates/bevy_render/src/camera/active_cameras.rs b/crates/bevy_render/src/camera/active_cameras.rs index 37543dc8f34f7..8f7af81f1279a 100644 --- a/crates/bevy_render/src/camera/active_cameras.rs +++ b/crates/bevy_render/src/camera/active_cameras.rs @@ -1,18 +1,14 @@ -use crate::renderer::RenderResourceBindings; - use super::Camera; use bevy_ecs::{ - component::Component, entity::Entity, system::{Query, ResMut}, }; use bevy_utils::HashMap; -#[derive(Component, Debug, Default)] +#[derive(Debug, Default)] pub struct ActiveCamera { pub name: String, pub entity: Option, - pub bindings: RenderResourceBindings, } #[derive(Debug, Default)] diff --git a/pipelined/bevy_render2/src/camera/bundle.rs b/crates/bevy_render/src/camera/bundle.rs similarity index 100% rename from pipelined/bevy_render2/src/camera/bundle.rs rename to crates/bevy_render/src/camera/bundle.rs diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index f93581af7703e..4f73ebaf90230 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -1,10 +1,9 @@ -use super::CameraProjection; +use crate::camera::CameraProjection; use bevy_ecs::{ - change_detection::DetectChanges, component::Component, entity::Entity, event::EventReader, - prelude::QueryState, + prelude::{DetectChanges, QueryState}, query::Added, reflect::ReflectComponent, system::{QuerySet, Res}, @@ -24,6 +23,8 @@ pub struct Camera { pub window: WindowId, #[reflect(ignore)] pub depth_calculation: DepthCalculation, + pub near: f32, + pub far: f32, } #[derive(Debug, Clone, Copy, Reflect, Serialize, Deserialize)] diff --git a/crates/bevy_render/src/camera/mod.rs b/crates/bevy_render/src/camera/mod.rs index 28bdfe5ec1aab..8629d8022d64f 100644 --- a/crates/bevy_render/src/camera/mod.rs +++ b/crates/bevy_render/src/camera/mod.rs @@ -1,10 +1,107 @@ mod active_cameras; +mod bundle; #[allow(clippy::module_inception)] mod camera; mod projection; -mod visible_entities; pub use active_cameras::*; +use bevy_transform::components::GlobalTransform; +use bevy_utils::HashMap; +use bevy_window::{WindowId, Windows}; +pub use bundle::*; pub use camera::*; pub use projection::*; -pub use visible_entities::*; + +use crate::{ + primitives::Aabb, + view::{ComputedVisibility, ExtractedView, Visibility, VisibleEntities}, + RenderApp, RenderStage, +}; +use bevy_app::{App, CoreStage, Plugin}; +use bevy_ecs::prelude::*; + +#[derive(Default)] +pub struct CameraPlugin; + +impl CameraPlugin { + pub const CAMERA_2D: &'static str = "camera_2d"; + pub const CAMERA_3D: &'static str = "camera_3d"; +} + +impl Plugin for CameraPlugin { + fn build(&self, app: &mut App) { + let mut active_cameras = ActiveCameras::default(); + active_cameras.add(Self::CAMERA_2D); + active_cameras.add(Self::CAMERA_3D); + app.register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .insert_resource(active_cameras) + .add_system_to_stage(CoreStage::PostUpdate, crate::camera::active_cameras_system) + .add_system_to_stage( + CoreStage::PostUpdate, + crate::camera::camera_system::, + ) + .add_system_to_stage( + CoreStage::PostUpdate, + crate::camera::camera_system::, + ); + app.sub_app(RenderApp) + .init_resource::() + .add_system_to_stage(RenderStage::Extract, extract_cameras); + } +} + +#[derive(Default)] +pub struct ExtractedCameraNames { + pub entities: HashMap, +} + +#[derive(Component, Debug)] +pub struct ExtractedCamera { + pub window_id: WindowId, + pub name: Option, +} + +fn extract_cameras( + mut commands: Commands, + active_cameras: Res, + windows: Res, + query: Query<(Entity, &Camera, &GlobalTransform, &VisibleEntities)>, +) { + let mut entities = HashMap::default(); + for camera in active_cameras.iter() { + let name = &camera.name; + if let Some((entity, camera, transform, visible_entities)) = + camera.entity.and_then(|e| query.get(e).ok()) + { + entities.insert(name.clone(), entity); + if let Some(window) = windows.get(camera.window) { + commands.get_or_spawn(entity).insert_bundle(( + ExtractedCamera { + window_id: camera.window, + name: camera.name.clone(), + }, + ExtractedView { + projection: camera.projection_matrix, + transform: *transform, + width: window.physical_width().max(1), + height: window.physical_height().max(1), + near: camera.near, + far: camera.far, + }, + visible_entities.clone(), + )); + } + } + } + + commands.insert_resource(ExtractedCameraNames { entities }) +} diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index 62d66418f9cbc..b0ea170c75f6e 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -8,6 +8,7 @@ pub trait CameraProjection { fn get_projection_matrix(&self) -> Mat4; fn update(&mut self, width: f32, height: f32); fn depth_calculation(&self) -> DepthCalculation; + fn far(&self) -> f32; } #[derive(Component, Debug, Clone, Reflect)] @@ -21,7 +22,7 @@ pub struct PerspectiveProjection { impl CameraProjection for PerspectiveProjection { fn get_projection_matrix(&self) -> Mat4 { - Mat4::perspective_rh(self.fov, self.aspect_ratio, self.near, self.far) + Mat4::perspective_infinite_reverse_rh(self.fov, self.aspect_ratio, self.near) } fn update(&mut self, width: f32, height: f32) { @@ -31,13 +32,17 @@ impl CameraProjection for PerspectiveProjection { fn depth_calculation(&self) -> DepthCalculation { DepthCalculation::Distance } + + fn far(&self) -> f32 { + self.far + } } impl Default for PerspectiveProjection { fn default() -> Self { PerspectiveProjection { fov: std::f32::consts::PI / 4.0, - near: 1.0, + near: 0.1, far: 1000.0, aspect_ratio: 1.0, } @@ -88,8 +93,10 @@ impl CameraProjection for OrthographicProjection { self.right * self.scale, self.bottom * self.scale, self.top * self.scale, - self.near, + // NOTE: near and far are swapped to invert the depth range from [0,1] to [1,0] + // This is for interoperability with pipelines using infinite reverse perspective projections. self.far, + self.near, ) } @@ -144,6 +151,10 @@ impl CameraProjection for OrthographicProjection { fn depth_calculation(&self) -> DepthCalculation { self.depth_calculation } + + fn far(&self) -> f32 { + self.far + } } impl Default for OrthographicProjection { diff --git a/crates/bevy_render/src/camera/visible_entities.rs b/crates/bevy_render/src/camera/visible_entities.rs deleted file mode 100644 index af316da075c40..0000000000000 --- a/crates/bevy_render/src/camera/visible_entities.rs +++ /dev/null @@ -1,265 +0,0 @@ -use super::{Camera, DepthCalculation}; -use crate::{draw::OutsideFrustum, prelude::Visible}; -use bevy_core::FloatOrd; -use bevy_ecs::{ - component::Component, entity::Entity, query::Without, reflect::ReflectComponent, system::Query, -}; -use bevy_reflect::Reflect; -use bevy_transform::prelude::GlobalTransform; - -#[derive(Debug)] -pub struct VisibleEntity { - pub entity: Entity, - pub order: FloatOrd, -} - -#[derive(Component, Default, Debug, Reflect)] -#[reflect(Component)] -pub struct VisibleEntities { - #[reflect(ignore)] - pub value: Vec, -} - -impl VisibleEntities { - pub fn iter(&self) -> impl DoubleEndedIterator { - self.value.iter() - } -} - -type LayerMask = u32; - -/// An identifier for a rendering layer. -pub type Layer = u8; - -/// Describes which rendering layers an entity belongs to. -/// -/// Cameras with this component will only render entities with intersecting -/// layers. -/// -/// There are 32 layers numbered `0` - [`TOTAL_LAYERS`](RenderLayers::TOTAL_LAYERS). Entities may -/// belong to one or more layers, or no layer at all. -/// -/// The [`Default`] instance of `RenderLayers` contains layer `0`, the first layer. -/// -/// An entity with this component without any layers is invisible. -/// -/// Entities without this component belong to layer `0`. -#[derive(Component, Copy, Clone, Reflect, PartialEq, Eq, PartialOrd, Ord)] -#[reflect(Component, PartialEq)] -pub struct RenderLayers(LayerMask); - -impl std::fmt::Debug for RenderLayers { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_tuple("RenderLayers") - .field(&self.iter().collect::>()) - .finish() - } -} - -impl std::iter::FromIterator for RenderLayers { - fn from_iter>(i: T) -> Self { - i.into_iter().fold(Self::none(), |mask, g| mask.with(g)) - } -} - -/// Defaults to containing to layer `0`, the first layer. -impl Default for RenderLayers { - fn default() -> Self { - RenderLayers::layer(0) - } -} - -impl RenderLayers { - /// The total number of layers supported. - pub const TOTAL_LAYERS: usize = std::mem::size_of::() * 8; - - /// Create a new `RenderLayers` belonging to the given layer. - pub fn layer(n: Layer) -> Self { - RenderLayers(0).with(n) - } - - /// Create a new `RenderLayers` that belongs to all layers. - pub fn all() -> Self { - RenderLayers(u32::MAX) - } - - /// Create a new `RenderLayers` that belongs to no layers. - pub fn none() -> Self { - RenderLayers(0) - } - - /// Create a `RenderLayers` from a list of layers. - pub fn from_layers(layers: &[Layer]) -> Self { - layers.iter().copied().collect() - } - - /// Add the given layer. - /// - /// This may be called multiple times to allow an entity to belong - /// to multiple rendering layers. The maximum layer is `TOTAL_LAYERS - 1`. - /// - /// # Panics - /// Panics when called with a layer greater than `TOTAL_LAYERS - 1`. - pub fn with(mut self, layer: Layer) -> Self { - assert!(usize::from(layer) < Self::TOTAL_LAYERS); - self.0 |= 1 << layer; - self - } - - /// Removes the given rendering layer. - /// - /// # Panics - /// Panics when called with a layer greater than `TOTAL_LAYERS - 1`. - pub fn without(mut self, layer: Layer) -> Self { - assert!(usize::from(layer) < Self::TOTAL_LAYERS); - self.0 &= !(1 << layer); - self - } - - /// Get an iterator of the layers. - pub fn iter(&self) -> impl Iterator { - let total: Layer = std::convert::TryInto::try_into(Self::TOTAL_LAYERS).unwrap(); - let mask = *self; - (0..total).filter(move |g| RenderLayers::layer(*g).intersects(&mask)) - } - - /// Determine if a `RenderLayers` intersects another. - /// - /// `RenderLayers`s intersect if they share any common layers. - /// - /// A `RenderLayers` with no layers will not match any other - /// `RenderLayers`, even another with no layers. - pub fn intersects(&self, other: &RenderLayers) -> bool { - (self.0 & other.0) > 0 - } -} - -#[cfg(test)] -mod rendering_mask_tests { - use super::{Layer, RenderLayers}; - - #[test] - fn rendering_mask_sanity() { - assert_eq!( - RenderLayers::TOTAL_LAYERS, - 32, - "total layers is what we think it is" - ); - assert_eq!(RenderLayers::layer(0).0, 1, "layer 0 is mask 1"); - assert_eq!(RenderLayers::layer(1).0, 2, "layer 1 is mask 2"); - assert_eq!(RenderLayers::layer(0).with(1).0, 3, "layer 0 + 1 is mask 3"); - assert_eq!( - RenderLayers::layer(0).with(1).without(0).0, - 2, - "layer 0 + 1 - 0 is mask 2" - ); - assert!( - RenderLayers::layer(1).intersects(&RenderLayers::layer(1)), - "layers match like layers" - ); - assert!( - RenderLayers::layer(0).intersects(&RenderLayers(1)), - "a layer of 0 means the mask is just 1 bit" - ); - - assert!( - RenderLayers::layer(0) - .with(3) - .intersects(&RenderLayers::layer(3)), - "a mask will match another mask containing any similar layers" - ); - - assert!( - RenderLayers::default().intersects(&RenderLayers::default()), - "default masks match each other" - ); - - assert!( - !RenderLayers::layer(0).intersects(&RenderLayers::layer(1)), - "masks with differing layers do not match" - ); - assert!( - !RenderLayers(0).intersects(&RenderLayers(0)), - "empty masks don't match" - ); - assert_eq!( - RenderLayers::from_layers(&[0, 2, 16, 30]) - .iter() - .collect::>(), - vec![0, 2, 16, 30], - "from_layers and get_layers should roundtrip" - ); - assert_eq!( - format!("{:?}", RenderLayers::from_layers(&[0, 1, 2, 3])).as_str(), - "RenderLayers([0, 1, 2, 3])", - "Debug instance shows layers" - ); - assert_eq!( - RenderLayers::from_layers(&[0, 1, 2]), - >::from_iter(vec![0, 1, 2]), - "from_layers and from_iter are equivalent" - ) - } -} - -pub fn visible_entities_system( - mut camera_query: Query<( - &Camera, - &GlobalTransform, - &mut VisibleEntities, - Option<&RenderLayers>, - )>, - visible_query: Query<(Entity, &Visible, Option<&RenderLayers>), Without>, - visible_transform_query: Query<&GlobalTransform, Without>, -) { - for (camera, camera_global_transform, mut visible_entities, maybe_camera_mask) in - camera_query.iter_mut() - { - visible_entities.value.clear(); - let camera_position = camera_global_transform.translation; - let camera_mask = maybe_camera_mask.copied().unwrap_or_default(); - - let mut no_transform_order = 0.0; - let mut transparent_entities = Vec::new(); - for (entity, visible, maybe_entity_mask) in visible_query.iter() { - if !visible.is_visible { - continue; - } - - let entity_mask = maybe_entity_mask.copied().unwrap_or_default(); - if !camera_mask.intersects(&entity_mask) { - continue; - } - - let order = if let Ok(global_transform) = visible_transform_query.get(entity) { - let position = global_transform.translation; - // smaller distances are sorted to lower indices by using the distance from the - // camera - FloatOrd(match camera.depth_calculation { - DepthCalculation::ZDifference => camera_position.z - position.z, - DepthCalculation::Distance => (camera_position - position).length_squared(), - }) - } else { - let order = FloatOrd(no_transform_order); - no_transform_order += 0.1; - order - }; - - if visible.is_transparent { - transparent_entities.push(VisibleEntity { entity, order }) - } else { - visible_entities.value.push(VisibleEntity { entity, order }) - } - } - - // sort opaque entities front-to-back - visible_entities.value.sort_by_key(|e| e.order); - - // sort transparent entities front-to-back - transparent_entities.sort_by_key(|e| -e.order); - visible_entities.value.extend(transparent_entities); - - // TODO: check for big changes in visible entities len() vs capacity() (ex: 2x) and resize - // to prevent holding unneeded memory - } -} diff --git a/crates/bevy_render/src/color.rs b/crates/bevy_render/src/color.rs deleted file mode 100644 index 32ed8d2818596..0000000000000 --- a/crates/bevy_render/src/color.rs +++ /dev/null @@ -1,1234 +0,0 @@ -use super::texture::Texture; -use crate::{ - colorspace::*, - impl_render_resource_bytes, - renderer::{RenderResource, RenderResourceType}, -}; -use bevy_asset::Handle; -use bevy_core::Bytes; -use bevy_math::{Vec3, Vec4}; -use bevy_reflect::{Reflect, ReflectDeserialize}; -use serde::{Deserialize, Serialize}; -use std::ops::{Add, AddAssign, Mul, MulAssign}; -use thiserror::Error; - -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] -pub enum Color { - /// sRGBA color - Rgba { - /// Red component. [0.0, 1.0] - red: f32, - /// Green component. [0.0, 1.0] - green: f32, - /// Blue component. [0.0, 1.0] - blue: f32, - /// Alpha component. [0.0, 1.0] - alpha: f32, - }, - /// RGBA color in the Linear sRGB colorspace (often colloquially referred to as "linear", - /// "RGB", or "linear RGB"). - RgbaLinear { - /// Red component. [0.0, 1.0] - red: f32, - /// Green component. [0.0, 1.0] - green: f32, - /// Blue component. [0.0, 1.0] - blue: f32, - /// Alpha component. [0.0, 1.0] - alpha: f32, - }, - /// HSL (hue, saturation, lightness) color with an alpha channel - Hsla { - /// Hue component. [0.0, 360.0] - hue: f32, - /// Saturation component. [0.0, 1.0] - saturation: f32, - /// Lightness component. [0.0, 1.0] - lightness: f32, - /// Alpha component. [0.0, 1.0] - alpha: f32, - }, -} - -impl Color { - pub const ALICE_BLUE: Color = Color::rgb(0.94, 0.97, 1.0); - pub const ANTIQUE_WHITE: Color = Color::rgb(0.98, 0.92, 0.84); - pub const AQUAMARINE: Color = Color::rgb(0.49, 1.0, 0.83); - pub const AZURE: Color = Color::rgb(0.94, 1.0, 1.0); - pub const BEIGE: Color = Color::rgb(0.96, 0.96, 0.86); - pub const BISQUE: Color = Color::rgb(1.0, 0.89, 0.77); - pub const BLACK: Color = Color::rgb(0.0, 0.0, 0.0); - pub const BLUE: Color = Color::rgb(0.0, 0.0, 1.0); - pub const CRIMSON: Color = Color::rgb(0.86, 0.08, 0.24); - pub const CYAN: Color = Color::rgb(0.0, 1.0, 1.0); - pub const DARK_GRAY: Color = Color::rgb(0.25, 0.25, 0.25); - pub const DARK_GREEN: Color = Color::rgb(0.0, 0.5, 0.0); - pub const FUCHSIA: Color = Color::rgb(1.0, 0.0, 1.0); - pub const GOLD: Color = Color::rgb(1.0, 0.84, 0.0); - pub const GRAY: Color = Color::rgb(0.5, 0.5, 0.5); - pub const GREEN: Color = Color::rgb(0.0, 1.0, 0.0); - pub const INDIGO: Color = Color::rgb(0.29, 0.0, 0.51); - pub const LIME_GREEN: Color = Color::rgb(0.2, 0.8, 0.2); - pub const MAROON: Color = Color::rgb(0.5, 0.0, 0.0); - pub const MIDNIGHT_BLUE: Color = Color::rgb(0.1, 0.1, 0.44); - pub const NAVY: Color = Color::rgb(0.0, 0.0, 0.5); - pub const NONE: Color = Color::rgba(0.0, 0.0, 0.0, 0.0); - pub const OLIVE: Color = Color::rgb(0.5, 0.5, 0.0); - pub const ORANGE: Color = Color::rgb(1.0, 0.65, 0.0); - pub const ORANGE_RED: Color = Color::rgb(1.0, 0.27, 0.0); - pub const PINK: Color = Color::rgb(1.0, 0.08, 0.58); - pub const PURPLE: Color = Color::rgb(0.5, 0.0, 0.5); - pub const RED: Color = Color::rgb(1.0, 0.0, 0.0); - pub const SALMON: Color = Color::rgb(0.98, 0.5, 0.45); - pub const SEA_GREEN: Color = Color::rgb(0.18, 0.55, 0.34); - pub const SILVER: Color = Color::rgb(0.75, 0.75, 0.75); - pub const TEAL: Color = Color::rgb(0.0, 0.5, 0.5); - pub const TOMATO: Color = Color::rgb(1.0, 0.39, 0.28); - pub const TURQUOISE: Color = Color::rgb(0.25, 0.88, 0.82); - pub const VIOLET: Color = Color::rgb(0.93, 0.51, 0.93); - pub const WHITE: Color = Color::rgb(1.0, 1.0, 1.0); - pub const YELLOW: Color = Color::rgb(1.0, 1.0, 0.0); - pub const YELLOW_GREEN: Color = Color::rgb(0.6, 0.8, 0.2); - - /// New `Color` from sRGB colorspace. - pub const fn rgb(r: f32, g: f32, b: f32) -> Color { - Color::Rgba { - red: r, - green: g, - blue: b, - alpha: 1.0, - } - } - - /// New `Color` from sRGB colorspace. - pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Color { - Color::Rgba { - red: r, - green: g, - blue: b, - alpha: a, - } - } - - /// New `Color` from linear RGB colorspace. - pub const fn rgb_linear(r: f32, g: f32, b: f32) -> Color { - Color::RgbaLinear { - red: r, - green: g, - blue: b, - alpha: 1.0, - } - } - - /// New `Color` from linear RGB colorspace. - pub const fn rgba_linear(r: f32, g: f32, b: f32, a: f32) -> Color { - Color::RgbaLinear { - red: r, - green: g, - blue: b, - alpha: a, - } - } - - /// New `Color` with HSL representation in sRGB colorspace. - pub const fn hsl(hue: f32, saturation: f32, lightness: f32) -> Color { - Color::Hsla { - hue, - saturation, - lightness, - alpha: 1.0, - } - } - - /// New `Color` with HSL representation in sRGB colorspace. - pub const fn hsla(hue: f32, saturation: f32, lightness: f32, alpha: f32) -> Color { - Color::Hsla { - hue, - saturation, - lightness, - alpha, - } - } - - /// New `Color` from sRGB colorspace. - pub fn hex>(hex: T) -> Result { - let hex = hex.as_ref(); - - // RGB - if hex.len() == 3 { - let mut data = [0; 6]; - for (i, ch) in hex.chars().enumerate() { - data[i * 2] = ch as u8; - data[i * 2 + 1] = ch as u8; - } - return decode_rgb(&data); - } - - // RGBA - if hex.len() == 4 { - let mut data = [0; 8]; - for (i, ch) in hex.chars().enumerate() { - data[i * 2] = ch as u8; - data[i * 2 + 1] = ch as u8; - } - return decode_rgba(&data); - } - - // RRGGBB - if hex.len() == 6 { - return decode_rgb(hex.as_bytes()); - } - - // RRGGBBAA - if hex.len() == 8 { - return decode_rgba(hex.as_bytes()); - } - - Err(HexColorError::Length) - } - - /// New `Color` from sRGB colorspace. - pub fn rgb_u8(r: u8, g: u8, b: u8) -> Color { - Color::rgba_u8(r, g, b, u8::MAX) - } - - // Float operations in const fn are not stable yet - // see https://github.com/rust-lang/rust/issues/57241 - /// New `Color` from sRGB colorspace. - pub fn rgba_u8(r: u8, g: u8, b: u8, a: u8) -> Color { - Color::rgba( - r as f32 / u8::MAX as f32, - g as f32 / u8::MAX as f32, - b as f32 / u8::MAX as f32, - a as f32 / u8::MAX as f32, - ) - } - - /// Get red in sRGB colorspace. - pub fn r(&self) -> f32 { - match self.as_rgba() { - Color::Rgba { red, .. } => red, - _ => unreachable!(), - } - } - - /// Get green in sRGB colorspace. - pub fn g(&self) -> f32 { - match self.as_rgba() { - Color::Rgba { green, .. } => green, - _ => unreachable!(), - } - } - - /// Get blue in sRGB colorspace. - pub fn b(&self) -> f32 { - match self.as_rgba() { - Color::Rgba { blue, .. } => blue, - _ => unreachable!(), - } - } - - /// Set red in sRGB colorspace. - pub fn set_r(&mut self, r: f32) -> &mut Self { - *self = self.as_rgba(); - match self { - Color::Rgba { red, .. } => *red = r, - _ => unreachable!(), - } - self - } - - /// Set green in sRGB colorspace. - pub fn set_g(&mut self, g: f32) -> &mut Self { - *self = self.as_rgba(); - match self { - Color::Rgba { green, .. } => *green = g, - _ => unreachable!(), - } - self - } - - /// Set blue in sRGB colorspace. - pub fn set_b(&mut self, b: f32) -> &mut Self { - *self = self.as_rgba(); - match self { - Color::Rgba { blue, .. } => *blue = b, - _ => unreachable!(), - } - self - } - - /// Get alpha. - pub fn a(&self) -> f32 { - match self { - Color::Rgba { alpha, .. } - | Color::RgbaLinear { alpha, .. } - | Color::Hsla { alpha, .. } => *alpha, - } - } - - /// Set alpha. - pub fn set_a(&mut self, a: f32) -> &mut Self { - match self { - Color::Rgba { alpha, .. } - | Color::RgbaLinear { alpha, .. } - | Color::Hsla { alpha, .. } => { - *alpha = a; - } - } - self - } - - /// Converts a `Color` to variant `Color::Rgba` - pub fn as_rgba(self: &Color) -> Color { - match self { - Color::Rgba { .. } => *self, - Color::RgbaLinear { - red, - green, - blue, - alpha, - } => Color::Rgba { - red: red.linear_to_nonlinear_srgb(), - green: green.linear_to_nonlinear_srgb(), - blue: blue.linear_to_nonlinear_srgb(), - alpha: *alpha, - }, - Color::Hsla { - hue, - saturation, - lightness, - alpha, - } => { - let [red, green, blue] = - HslRepresentation::hsl_to_nonlinear_srgb(*hue, *saturation, *lightness); - Color::Rgba { - red, - green, - blue, - alpha: *alpha, - } - } - } - } - - /// Converts a `Color` to variant `Color::RgbaLinear` - pub fn as_rgba_linear(self: &Color) -> Color { - match self { - Color::Rgba { - red, - green, - blue, - alpha, - } => Color::RgbaLinear { - red: red.nonlinear_to_linear_srgb(), - green: green.nonlinear_to_linear_srgb(), - blue: blue.nonlinear_to_linear_srgb(), - alpha: *alpha, - }, - Color::RgbaLinear { .. } => *self, - Color::Hsla { - hue, - saturation, - lightness, - alpha, - } => { - let [red, green, blue] = - HslRepresentation::hsl_to_nonlinear_srgb(*hue, *saturation, *lightness); - Color::RgbaLinear { - red: red.nonlinear_to_linear_srgb(), - green: green.nonlinear_to_linear_srgb(), - blue: blue.nonlinear_to_linear_srgb(), - alpha: *alpha, - } - } - } - } - - /// Converts a `Color` to variant `Color::Hsla` - pub fn as_hsla(self: &Color) -> Color { - match self { - Color::Rgba { - red, - green, - blue, - alpha, - } => { - let (hue, saturation, lightness) = - HslRepresentation::nonlinear_srgb_to_hsl([*red, *green, *blue]); - Color::Hsla { - hue, - saturation, - lightness, - alpha: *alpha, - } - } - Color::RgbaLinear { - red, - green, - blue, - alpha, - } => { - let (hue, saturation, lightness) = HslRepresentation::nonlinear_srgb_to_hsl([ - red.linear_to_nonlinear_srgb(), - green.linear_to_nonlinear_srgb(), - blue.linear_to_nonlinear_srgb(), - ]); - Color::Hsla { - hue, - saturation, - lightness, - alpha: *alpha, - } - } - Color::Hsla { .. } => *self, - } - } - - /// Converts a `Color` to a `[f32; 4]` from sRBG colorspace - pub fn as_rgba_f32(self: Color) -> [f32; 4] { - match self { - Color::Rgba { - red, - green, - blue, - alpha, - } => [red, green, blue, alpha], - Color::RgbaLinear { - red, - green, - blue, - alpha, - } => [ - red.linear_to_nonlinear_srgb(), - green.linear_to_nonlinear_srgb(), - blue.linear_to_nonlinear_srgb(), - alpha, - ], - Color::Hsla { - hue, - saturation, - lightness, - alpha, - } => { - let [red, green, blue] = - HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); - [red, green, blue, alpha] - } - } - } - - /// Converts a `Color` to a `[f32; 4]` from linear RBG colorspace - pub fn as_linear_rgba_f32(self: Color) -> [f32; 4] { - match self { - Color::Rgba { - red, - green, - blue, - alpha, - } => [ - red.nonlinear_to_linear_srgb(), - green.nonlinear_to_linear_srgb(), - blue.nonlinear_to_linear_srgb(), - alpha, - ], - Color::RgbaLinear { - red, - green, - blue, - alpha, - } => [red, green, blue, alpha], - Color::Hsla { - hue, - saturation, - lightness, - alpha, - } => { - let [red, green, blue] = - HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); - [ - red.nonlinear_to_linear_srgb(), - green.nonlinear_to_linear_srgb(), - blue.nonlinear_to_linear_srgb(), - alpha, - ] - } - } - } - - /// Converts a `Color` to a `[f32; 4]` from HLS colorspace - pub fn as_hlsa_f32(self: Color) -> [f32; 4] { - match self { - Color::Rgba { - red, - green, - blue, - alpha, - } => { - let (hue, saturation, lightness) = - HslRepresentation::nonlinear_srgb_to_hsl([red, green, blue]); - [hue, saturation, lightness, alpha] - } - Color::RgbaLinear { - red, - green, - blue, - alpha, - } => { - let (hue, saturation, lightness) = HslRepresentation::nonlinear_srgb_to_hsl([ - red.linear_to_nonlinear_srgb(), - green.linear_to_nonlinear_srgb(), - blue.linear_to_nonlinear_srgb(), - ]); - [hue, saturation, lightness, alpha] - } - Color::Hsla { - hue, - saturation, - lightness, - alpha, - } => [hue, saturation, lightness, alpha], - } - } -} - -impl Default for Color { - fn default() -> Self { - Color::WHITE - } -} - -impl AddAssign for Color { - fn add_assign(&mut self, rhs: Color) { - match self { - Color::Rgba { - red, - green, - blue, - alpha, - } => { - let rhs = rhs.as_rgba_f32(); - *red += rhs[0]; - *green += rhs[1]; - *blue += rhs[2]; - *alpha += rhs[3]; - } - Color::RgbaLinear { - red, - green, - blue, - alpha, - } => { - let rhs = rhs.as_linear_rgba_f32(); - *red += rhs[0]; - *green += rhs[1]; - *blue += rhs[2]; - *alpha += rhs[3]; - } - Color::Hsla { - hue, - saturation, - lightness, - alpha, - } => { - let rhs = rhs.as_linear_rgba_f32(); - *hue += rhs[0]; - *saturation += rhs[1]; - *lightness += rhs[2]; - *alpha += rhs[3]; - } - } - } -} - -impl Add for Color { - type Output = Color; - - fn add(self, rhs: Color) -> Self::Output { - match self { - Color::Rgba { - red, - green, - blue, - alpha, - } => { - let rhs = rhs.as_rgba_f32(); - Color::Rgba { - red: red + rhs[0], - green: green + rhs[1], - blue: blue + rhs[2], - alpha: alpha + rhs[3], - } - } - Color::RgbaLinear { - red, - green, - blue, - alpha, - } => { - let rhs = rhs.as_linear_rgba_f32(); - Color::RgbaLinear { - red: red + rhs[0], - green: green + rhs[1], - blue: blue + rhs[2], - alpha: alpha + rhs[3], - } - } - Color::Hsla { - hue, - saturation, - lightness, - alpha, - } => { - let rhs = rhs.as_linear_rgba_f32(); - Color::Hsla { - hue: hue + rhs[0], - saturation: saturation + rhs[1], - lightness: lightness + rhs[2], - alpha: alpha + rhs[3], - } - } - } - } -} - -impl AddAssign for Color { - fn add_assign(&mut self, rhs: Vec4) { - let rhs: Color = rhs.into(); - *self += rhs - } -} - -impl Add for Color { - type Output = Color; - - fn add(self, rhs: Vec4) -> Self::Output { - let rhs: Color = rhs.into(); - self + rhs - } -} - -impl From for [f32; 4] { - fn from(color: Color) -> Self { - color.as_rgba_f32() - } -} - -impl From<[f32; 4]> for Color { - fn from([r, g, b, a]: [f32; 4]) -> Self { - Color::rgba(r, g, b, a) - } -} - -impl From for Vec4 { - fn from(color: Color) -> Self { - let color: [f32; 4] = color.into(); - Vec4::new(color[0], color[1], color[2], color[3]) - } -} - -impl From for Color { - fn from(vec4: Vec4) -> Self { - Color::rgba(vec4.x, vec4.y, vec4.z, vec4.w) - } -} - -impl Mul for Color { - type Output = Color; - - fn mul(self, rhs: f32) -> Self::Output { - match self { - Color::Rgba { - red, - green, - blue, - alpha, - } => Color::Rgba { - red: red * rhs, - green: green * rhs, - blue: blue * rhs, - alpha, - }, - Color::RgbaLinear { - red, - green, - blue, - alpha, - } => Color::RgbaLinear { - red: red * rhs, - green: green * rhs, - blue: blue * rhs, - alpha, - }, - Color::Hsla { - hue, - saturation, - lightness, - alpha, - } => Color::Hsla { - hue: hue * rhs, - saturation: saturation * rhs, - lightness: lightness * rhs, - alpha, - }, - } - } -} - -impl MulAssign for Color { - fn mul_assign(&mut self, rhs: f32) { - match self { - Color::Rgba { - red, green, blue, .. - } => { - *red *= rhs; - *green *= rhs; - *blue *= rhs; - } - Color::RgbaLinear { - red, green, blue, .. - } => { - *red *= rhs; - *green *= rhs; - *blue *= rhs; - } - Color::Hsla { - hue, - saturation, - lightness, - .. - } => { - *hue *= rhs; - *saturation *= rhs; - *lightness *= rhs; - } - } - } -} - -impl Mul for Color { - type Output = Color; - - fn mul(self, rhs: Vec4) -> Self::Output { - match self { - Color::Rgba { - red, - green, - blue, - alpha, - } => Color::Rgba { - red: red * rhs.x, - green: green * rhs.y, - blue: blue * rhs.z, - alpha: alpha * rhs.w, - }, - Color::RgbaLinear { - red, - green, - blue, - alpha, - } => Color::RgbaLinear { - red: red * rhs.x, - green: green * rhs.y, - blue: blue * rhs.z, - alpha: alpha * rhs.w, - }, - Color::Hsla { - hue, - saturation, - lightness, - alpha, - } => Color::Hsla { - hue: hue * rhs.x, - saturation: saturation * rhs.y, - lightness: lightness * rhs.z, - alpha: alpha * rhs.w, - }, - } - } -} - -impl MulAssign for Color { - fn mul_assign(&mut self, rhs: Vec4) { - match self { - Color::Rgba { - red, - green, - blue, - alpha, - } => { - *red *= rhs.x; - *green *= rhs.y; - *blue *= rhs.z; - *alpha *= rhs.w; - } - Color::RgbaLinear { - red, - green, - blue, - alpha, - } => { - *red *= rhs.x; - *green *= rhs.y; - *blue *= rhs.z; - *alpha *= rhs.w; - } - Color::Hsla { - hue, - saturation, - lightness, - alpha, - } => { - *hue *= rhs.x; - *saturation *= rhs.y; - *lightness *= rhs.z; - *alpha *= rhs.w; - } - } - } -} - -impl Mul for Color { - type Output = Color; - - fn mul(self, rhs: Vec3) -> Self::Output { - match self { - Color::Rgba { - red, - green, - blue, - alpha, - } => Color::Rgba { - red: red * rhs.x, - green: green * rhs.y, - blue: blue * rhs.z, - alpha, - }, - Color::RgbaLinear { - red, - green, - blue, - alpha, - } => Color::RgbaLinear { - red: red * rhs.x, - green: green * rhs.y, - blue: blue * rhs.z, - alpha, - }, - Color::Hsla { - hue, - saturation, - lightness, - alpha, - } => Color::Hsla { - hue: hue * rhs.x, - saturation: saturation * rhs.y, - lightness: lightness * rhs.z, - alpha, - }, - } - } -} - -impl MulAssign for Color { - fn mul_assign(&mut self, rhs: Vec3) { - match self { - Color::Rgba { - red, green, blue, .. - } => { - *red *= rhs.x; - *green *= rhs.y; - *blue *= rhs.z; - } - Color::RgbaLinear { - red, green, blue, .. - } => { - *red *= rhs.x; - *green *= rhs.y; - *blue *= rhs.z; - } - Color::Hsla { - hue, - saturation, - lightness, - .. - } => { - *hue *= rhs.x; - *saturation *= rhs.y; - *lightness *= rhs.z; - } - } - } -} - -impl Mul<[f32; 4]> for Color { - type Output = Color; - - fn mul(self, rhs: [f32; 4]) -> Self::Output { - match self { - Color::Rgba { - red, - green, - blue, - alpha, - } => Color::Rgba { - red: red * rhs[0], - green: green * rhs[1], - blue: blue * rhs[2], - alpha: alpha * rhs[3], - }, - Color::RgbaLinear { - red, - green, - blue, - alpha, - } => Color::RgbaLinear { - red: red * rhs[0], - green: green * rhs[1], - blue: blue * rhs[2], - alpha: alpha * rhs[3], - }, - Color::Hsla { - hue, - saturation, - lightness, - alpha, - } => Color::Hsla { - hue: hue * rhs[0], - saturation: saturation * rhs[1], - lightness: lightness * rhs[2], - alpha: alpha * rhs[3], - }, - } - } -} - -impl MulAssign<[f32; 4]> for Color { - fn mul_assign(&mut self, rhs: [f32; 4]) { - match self { - Color::Rgba { - red, - green, - blue, - alpha, - } => { - *red *= rhs[0]; - *green *= rhs[1]; - *blue *= rhs[2]; - *alpha *= rhs[3]; - } - Color::RgbaLinear { - red, - green, - blue, - alpha, - } => { - *red *= rhs[0]; - *green *= rhs[1]; - *blue *= rhs[2]; - *alpha *= rhs[3]; - } - Color::Hsla { - hue, - saturation, - lightness, - alpha, - } => { - *hue *= rhs[0]; - *saturation *= rhs[1]; - *lightness *= rhs[2]; - *alpha *= rhs[3]; - } - } - } -} - -impl Mul<[f32; 3]> for Color { - type Output = Color; - - fn mul(self, rhs: [f32; 3]) -> Self::Output { - match self { - Color::Rgba { - red, - green, - blue, - alpha, - } => Color::Rgba { - red: red * rhs[0], - green: green * rhs[1], - blue: blue * rhs[2], - alpha, - }, - Color::RgbaLinear { - red, - green, - blue, - alpha, - } => Color::RgbaLinear { - red: red * rhs[0], - green: green * rhs[1], - blue: blue * rhs[2], - alpha, - }, - Color::Hsla { - hue, - saturation, - lightness, - alpha, - } => Color::Hsla { - hue: hue * rhs[0], - saturation: saturation * rhs[1], - lightness: lightness * rhs[2], - alpha, - }, - } - } -} - -impl MulAssign<[f32; 3]> for Color { - fn mul_assign(&mut self, rhs: [f32; 3]) { - match self { - Color::Rgba { - red, green, blue, .. - } => { - *red *= rhs[0]; - *green *= rhs[1]; - *blue *= rhs[2]; - } - Color::RgbaLinear { - red, green, blue, .. - } => { - *red *= rhs[0]; - *green *= rhs[1]; - *blue *= rhs[2]; - } - Color::Hsla { - hue, - saturation, - lightness, - .. - } => { - *hue *= rhs[0]; - *saturation *= rhs[1]; - *lightness *= rhs[2]; - } - } - } -} - -impl Bytes for Color { - fn write_bytes(&self, buffer: &mut [u8]) { - match *self { - Color::Rgba { - red, - green, - blue, - alpha, - } => { - red.nonlinear_to_linear_srgb().write_bytes(buffer); - green - .nonlinear_to_linear_srgb() - .write_bytes(&mut buffer[std::mem::size_of::()..]); - blue.nonlinear_to_linear_srgb() - .write_bytes(&mut buffer[2 * std::mem::size_of::()..]); - alpha.write_bytes(&mut buffer[3 * std::mem::size_of::()..]); - } - Color::RgbaLinear { - red, - green, - blue, - alpha, - } => { - red.write_bytes(buffer); - green.write_bytes(&mut buffer[std::mem::size_of::()..]); - blue.write_bytes(&mut buffer[2 * std::mem::size_of::()..]); - alpha.write_bytes(&mut buffer[3 * std::mem::size_of::()..]); - } - Color::Hsla { - hue, - saturation, - lightness, - alpha, - } => { - let [red, green, blue] = - HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); - red.nonlinear_to_linear_srgb().write_bytes(buffer); - green - .nonlinear_to_linear_srgb() - .write_bytes(&mut buffer[std::mem::size_of::()..]); - blue.nonlinear_to_linear_srgb() - .write_bytes(&mut buffer[std::mem::size_of::() * 2..]); - alpha.write_bytes(&mut buffer[std::mem::size_of::() * 3..]); - } - } - } - - fn byte_len(&self) -> usize { - std::mem::size_of::() * 4 - } -} - -impl_render_resource_bytes!(Color); - -#[derive(Debug, Error)] -pub enum HexColorError { - #[error("Unexpected length of hex string")] - Length, - #[error("Error parsing hex value")] - Hex(#[from] hex::FromHexError), -} - -fn decode_rgb(data: &[u8]) -> Result { - let mut buf = [0; 3]; - match hex::decode_to_slice(data, &mut buf) { - Ok(_) => { - let r = buf[0] as f32 / 255.0; - let g = buf[1] as f32 / 255.0; - let b = buf[2] as f32 / 255.0; - Ok(Color::rgb(r, g, b)) - } - Err(err) => Err(HexColorError::Hex(err)), - } -} - -fn decode_rgba(data: &[u8]) -> Result { - let mut buf = [0; 4]; - match hex::decode_to_slice(data, &mut buf) { - Ok(_) => { - let r = buf[0] as f32 / 255.0; - let g = buf[1] as f32 / 255.0; - let b = buf[2] as f32 / 255.0; - let a = buf[3] as f32 / 255.0; - Ok(Color::rgba(r, g, b, a)) - } - Err(err) => Err(HexColorError::Hex(err)), - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn hex_color() { - assert_eq!(Color::hex("FFF").unwrap(), Color::rgb(1.0, 1.0, 1.0)); - assert_eq!(Color::hex("000").unwrap(), Color::rgb(0.0, 0.0, 0.0)); - assert!(Color::hex("---").is_err()); - - assert_eq!(Color::hex("FFFF").unwrap(), Color::rgba(1.0, 1.0, 1.0, 1.0)); - assert_eq!(Color::hex("0000").unwrap(), Color::rgba(0.0, 0.0, 0.0, 0.0)); - assert!(Color::hex("----").is_err()); - - assert_eq!(Color::hex("FFFFFF").unwrap(), Color::rgb(1.0, 1.0, 1.0)); - assert_eq!(Color::hex("000000").unwrap(), Color::rgb(0.0, 0.0, 0.0)); - assert!(Color::hex("------").is_err()); - - assert_eq!( - Color::hex("FFFFFFFF").unwrap(), - Color::rgba(1.0, 1.0, 1.0, 1.0) - ); - assert_eq!( - Color::hex("00000000").unwrap(), - Color::rgba(0.0, 0.0, 0.0, 0.0) - ); - assert!(Color::hex("--------").is_err()); - - assert!(Color::hex("1234567890").is_err()); - } - - #[test] - fn conversions_vec4() { - let starting_vec4 = Vec4::new(0.4, 0.5, 0.6, 1.0); - let starting_color = Color::from(starting_vec4); - - assert_eq!(starting_vec4, Vec4::from(starting_color),); - - let transformation = Vec4::new(0.5, 0.5, 0.5, 1.0); - - assert_eq!( - starting_color * transformation, - Color::from(starting_vec4 * transformation), - ); - } - - #[test] - fn mul_and_mulassign_f32() { - let transformation = 0.5; - let starting_color = Color::rgba(0.4, 0.5, 0.6, 1.0); - - assert_eq!( - starting_color * transformation, - Color::rgba(0.4 * 0.5, 0.5 * 0.5, 0.6 * 0.5, 1.0), - ); - - let mut mutated_color = starting_color; - mutated_color *= transformation; - - assert_eq!(starting_color * transformation, mutated_color,); - } - - #[test] - fn mul_and_mulassign_f32by3() { - let transformation = [0.4, 0.5, 0.6]; - let starting_color = Color::rgba(0.4, 0.5, 0.6, 1.0); - - assert_eq!( - starting_color * transformation, - Color::rgba(0.4 * 0.4, 0.5 * 0.5, 0.6 * 0.6, 1.0), - ); - - let mut mutated_color = starting_color; - mutated_color *= transformation; - - assert_eq!(starting_color * transformation, mutated_color,); - } - - #[test] - fn mul_and_mulassign_f32by4() { - let transformation = [0.4, 0.5, 0.6, 0.9]; - let starting_color = Color::rgba(0.4, 0.5, 0.6, 1.0); - - assert_eq!( - starting_color * transformation, - Color::rgba(0.4 * 0.4, 0.5 * 0.5, 0.6 * 0.6, 1.0 * 0.9), - ); - - let mut mutated_color = starting_color; - mutated_color *= transformation; - - assert_eq!(starting_color * transformation, mutated_color,); - } - - #[test] - fn mul_and_mulassign_vec3() { - let transformation = Vec3::new(0.2, 0.3, 0.4); - let starting_color = Color::rgba(0.4, 0.5, 0.6, 1.0); - - assert_eq!( - starting_color * transformation, - Color::rgba(0.4 * 0.2, 0.5 * 0.3, 0.6 * 0.4, 1.0), - ); - - let mut mutated_color = starting_color; - mutated_color *= transformation; - - assert_eq!(starting_color * transformation, mutated_color,); - } - - #[test] - fn mul_and_mulassign_vec4() { - let transformation = Vec4::new(0.2, 0.3, 0.4, 0.5); - let starting_color = Color::rgba(0.4, 0.5, 0.6, 1.0); - - assert_eq!( - starting_color * transformation, - Color::rgba(0.4 * 0.2, 0.5 * 0.3, 0.6 * 0.4, 1.0 * 0.5), - ); - - let mut mutated_color = starting_color; - mutated_color *= transformation; - - assert_eq!(starting_color * transformation, mutated_color,); - } -} diff --git a/pipelined/bevy_render2/src/color/colorspace.rs b/crates/bevy_render/src/color/colorspace.rs similarity index 100% rename from pipelined/bevy_render2/src/color/colorspace.rs rename to crates/bevy_render/src/color/colorspace.rs diff --git a/pipelined/bevy_render2/src/color/mod.rs b/crates/bevy_render/src/color/mod.rs similarity index 100% rename from pipelined/bevy_render2/src/color/mod.rs rename to crates/bevy_render/src/color/mod.rs diff --git a/crates/bevy_render/src/colorspace.rs b/crates/bevy_render/src/colorspace.rs deleted file mode 100644 index 677b115276476..0000000000000 --- a/crates/bevy_render/src/colorspace.rs +++ /dev/null @@ -1,201 +0,0 @@ -pub trait SrgbColorSpace { - fn linear_to_nonlinear_srgb(self) -> Self; - fn nonlinear_to_linear_srgb(self) -> Self; -} - -// source: https://entropymine.com/imageworsener/srgbformula/ -impl SrgbColorSpace for f32 { - fn linear_to_nonlinear_srgb(self) -> f32 { - if self <= 0.0 { - return self; - } - - if self <= 0.0031308 { - self * 12.92 // linear falloff in dark values - } else { - (1.055 * self.powf(1.0 / 2.4)) - 0.055 // gamma curve in other area - } - } - - fn nonlinear_to_linear_srgb(self) -> f32 { - if self <= 0.0 { - return self; - } - if self <= 0.04045 { - self / 12.92 // linear falloff in dark values - } else { - ((self + 0.055) / 1.055).powf(2.4) // gamma curve in other area - } - } -} - -pub struct HslRepresentation; -impl HslRepresentation { - /// converts a color in HLS space to sRGB space - pub fn hsl_to_nonlinear_srgb(hue: f32, saturation: f32, lightness: f32) -> [f32; 3] { - // https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB - let chroma = (1.0 - (2.0 * lightness - 1.0).abs()) * saturation; - let hue_prime = hue / 60.0; - let largest_component = chroma * (1.0 - (hue_prime % 2.0 - 1.0).abs()); - let (r_temp, g_temp, b_temp) = if hue_prime < 1.0 { - (chroma, largest_component, 0.0) - } else if hue_prime < 2.0 { - (largest_component, chroma, 0.0) - } else if hue_prime < 3.0 { - (0.0, chroma, largest_component) - } else if hue_prime < 4.0 { - (0.0, largest_component, chroma) - } else if hue_prime < 5.0 { - (largest_component, 0.0, chroma) - } else { - (chroma, 0.0, largest_component) - }; - let lightness_match = lightness - chroma / 2.0; - - [ - r_temp + lightness_match, - g_temp + lightness_match, - b_temp + lightness_match, - ] - } - - /// converts a color in sRGB space to HLS space - pub fn nonlinear_srgb_to_hsl([red, green, blue]: [f32; 3]) -> (f32, f32, f32) { - // https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB - let x_max = red.max(green.max(blue)); - let x_min = red.min(green.min(blue)); - let chroma = x_max - x_min; - let lightness = (x_max + x_min) / 2.0; - let hue = if chroma == 0.0 { - 0.0 - } else if red > green && red > blue { - 60.0 * (green - blue) / chroma - } else if green > red && green > blue { - 60.0 * (2.0 + (blue - red) / chroma) - } else { - 60.0 * (4.0 + (red - green) / chroma) - }; - let hue = if hue < 0.0 { 360.0 + hue } else { hue }; - let saturation = if lightness <= 0.0 || lightness >= 1.0 { - 0.0 - } else { - (x_max - lightness) / lightness.min(1.0 - lightness) - }; - - (hue, saturation, lightness) - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn srgb_linear_full_roundtrip() { - let u8max: f32 = u8::max_value() as f32; - for color in 0..u8::max_value() { - let color01 = color as f32 / u8max; - let color_roundtrip = color01 - .linear_to_nonlinear_srgb() - .nonlinear_to_linear_srgb(); - // roundtrip is not perfect due to numeric precision, even with f64 - // so ensure the error is at least ready for u8 (where sRGB is used) - assert_eq!( - (color01 * u8max).round() as u8, - (color_roundtrip * u8max).round() as u8 - ); - } - } - - #[test] - fn hsl_to_srgb() { - // "truth" from https://en.wikipedia.org/wiki/HSL_and_HSV#Examples - - // black - let (hue, saturation, lightness) = (0.0, 0.0, 0.0); - let [r, g, b] = HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); - assert_eq!((r * 100.0).round() as u32, 0); - assert_eq!((g * 100.0).round() as u32, 0); - assert_eq!((b * 100.0).round() as u32, 0); - - // white - let (hue, saturation, lightness) = (0.0, 0.0, 1.0); - let [r, g, b] = HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); - assert_eq!((r * 100.0).round() as u32, 100); - assert_eq!((g * 100.0).round() as u32, 100); - assert_eq!((b * 100.0).round() as u32, 100); - - let (hue, saturation, lightness) = (300.0, 0.5, 0.5); - let [r, g, b] = HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); - assert_eq!((r * 100.0).round() as u32, 75); - assert_eq!((g * 100.0).round() as u32, 25); - assert_eq!((b * 100.0).round() as u32, 75); - - // a red - let (hue, saturation, lightness) = (283.7, 0.775, 0.543); - let [r, g, b] = HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); - assert_eq!((r * 100.0).round() as u32, 70); - assert_eq!((g * 100.0).round() as u32, 19); - assert_eq!((b * 100.0).round() as u32, 90); - - // a green - let (hue, saturation, lightness) = (162.4, 0.779, 0.447); - let [r, g, b] = HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); - assert_eq!((r * 100.0).round() as u32, 10); - assert_eq!((g * 100.0).round() as u32, 80); - assert_eq!((b * 100.0).round() as u32, 59); - - // a blue - let (hue, saturation, lightness) = (251.1, 0.832, 0.511); - let [r, g, b] = HslRepresentation::hsl_to_nonlinear_srgb(hue, saturation, lightness); - assert_eq!((r * 100.0).round() as u32, 25); - assert_eq!((g * 100.0).round() as u32, 10); - assert_eq!((b * 100.0).round() as u32, 92); - } - - #[test] - fn srgb_to_hsl() { - // "truth" from https://en.wikipedia.org/wiki/HSL_and_HSV#Examples - - // black - let (hue, saturation, lightness) = - HslRepresentation::nonlinear_srgb_to_hsl([0.0, 0.0, 0.0]); - assert_eq!(hue.round() as u32, 0); - assert_eq!((saturation * 100.0).round() as u32, 0); - assert_eq!((lightness * 100.0).round() as u32, 0); - - // white - let (hue, saturation, lightness) = - HslRepresentation::nonlinear_srgb_to_hsl([1.0, 1.0, 1.0]); - assert_eq!(hue.round() as u32, 0); - assert_eq!((saturation * 100.0).round() as u32, 0); - assert_eq!((lightness * 100.0).round() as u32, 100); - - let (hue, saturation, lightness) = - HslRepresentation::nonlinear_srgb_to_hsl([0.75, 0.25, 0.75]); - assert_eq!(hue.round() as u32, 300); - assert_eq!((saturation * 100.0).round() as u32, 50); - assert_eq!((lightness * 100.0).round() as u32, 50); - - // a red - let (hue, saturation, lightness) = - HslRepresentation::nonlinear_srgb_to_hsl([0.704, 0.187, 0.897]); - assert_eq!(hue.round() as u32, 284); - assert_eq!((saturation * 100.0).round() as u32, 78); - assert_eq!((lightness * 100.0).round() as u32, 54); - - // a green - let (hue, saturation, lightness) = - HslRepresentation::nonlinear_srgb_to_hsl([0.099, 0.795, 0.591]); - assert_eq!(hue.round() as u32, 162); - assert_eq!((saturation * 100.0).round() as u32, 78); - assert_eq!((lightness * 100.0).round() as u32, 45); - - // a blue - let (hue, saturation, lightness) = - HslRepresentation::nonlinear_srgb_to_hsl([0.255, 0.104, 0.918]); - assert_eq!(hue.round() as u32, 251); - assert_eq!((saturation * 100.0).round() as u32, 83); - assert_eq!((lightness * 100.0).round() as u32, 51); - } -} diff --git a/crates/bevy_render/src/draw.rs b/crates/bevy_render/src/draw.rs deleted file mode 100644 index cc465b86bdc74..0000000000000 --- a/crates/bevy_render/src/draw.rs +++ /dev/null @@ -1,373 +0,0 @@ -use crate::{ - pipeline::{ - IndexFormat, PipelineCompiler, PipelineDescriptor, PipelineLayout, PipelineSpecialization, - }, - renderer::{ - AssetRenderResourceBindings, BindGroup, BindGroupId, BufferId, RenderResource, - RenderResourceBinding, RenderResourceBindings, RenderResourceContext, SharedBuffers, - }, - shader::Shader, -}; -use bevy_asset::{Asset, Assets, Handle}; -use bevy_ecs::{ - component::Component, - reflect::ReflectComponent, - system::{Query, Res, ResMut, SystemParam}, -}; -use bevy_reflect::Reflect; -use std::{marker::PhantomData, ops::Range, sync::Arc}; -use thiserror::Error; - -/// A queued command for the renderer -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum RenderCommand { - SetPipeline { - pipeline: Handle, - }, - SetVertexBuffer { - slot: u32, - buffer: BufferId, - offset: u64, - }, - SetIndexBuffer { - buffer: BufferId, - offset: u64, - index_format: IndexFormat, - }, - SetBindGroup { - index: u32, - bind_group: BindGroupId, - dynamic_uniform_indices: Option>, - }, - DrawIndexed { - indices: Range, - base_vertex: i32, - instances: Range, - }, - Draw { - vertices: Range, - instances: Range, - }, -} - -#[derive(Component, Debug, Clone, Reflect)] -#[reflect(Component)] -pub struct Visible { - pub is_visible: bool, - // TODO: consider moving this to materials - pub is_transparent: bool, -} - -impl Default for Visible { - fn default() -> Self { - Visible { - is_visible: true, - is_transparent: false, - } - } -} - -/// A component that indicates that an entity is outside the view frustum. -/// Any entity with this component will be ignored during rendering. -/// -/// # Note -/// This does not handle multiple "views" properly as it is a "global" filter. -/// This will be resolved in the future. For now, disable frustum culling if you -/// need to support multiple views (ex: set the `SpriteSettings::frustum_culling_enabled` resource). -#[derive(Component, Debug, Default, Clone, Reflect)] -#[reflect(Component)] -#[component(storage = "SparseSet")] -pub struct OutsideFrustum; - -/// A component that indicates how to draw an entity. -#[derive(Component, Debug, Clone, Reflect)] -#[reflect(Component)] -pub struct Draw { - #[reflect(ignore)] - pub render_commands: Vec, -} - -impl Default for Draw { - fn default() -> Self { - Self { - render_commands: Default::default(), - } - } -} - -impl Draw { - pub fn clear_render_commands(&mut self) { - self.render_commands.clear(); - } - - pub fn set_pipeline(&mut self, pipeline: &Handle) { - self.render_command(RenderCommand::SetPipeline { - pipeline: pipeline.clone_weak(), - }); - } - - pub fn set_vertex_buffer(&mut self, slot: u32, buffer: BufferId, offset: u64) { - self.render_command(RenderCommand::SetVertexBuffer { - slot, - buffer, - offset, - }); - } - - pub fn set_index_buffer(&mut self, buffer: BufferId, offset: u64, index_format: IndexFormat) { - self.render_command(RenderCommand::SetIndexBuffer { - buffer, - offset, - index_format, - }); - } - - pub fn set_bind_group(&mut self, index: u32, bind_group: &BindGroup) { - self.render_command(RenderCommand::SetBindGroup { - index, - bind_group: bind_group.id, - dynamic_uniform_indices: bind_group.dynamic_uniform_indices.clone(), - }); - } - - pub fn draw_indexed(&mut self, indices: Range, base_vertex: i32, instances: Range) { - self.render_command(RenderCommand::DrawIndexed { - base_vertex, - indices, - instances, - }); - } - - pub fn draw(&mut self, vertices: Range, instances: Range) { - self.render_command(RenderCommand::Draw { - vertices, - instances, - }); - } - - #[inline] - pub fn render_command(&mut self, render_command: RenderCommand) { - self.render_commands.push(render_command); - } -} - -#[derive(Debug, Error)] -pub enum DrawError { - #[error("pipeline does not exist")] - NonExistentPipeline, - #[error("no pipeline set")] - NoPipelineSet, - #[error("pipeline has no layout")] - PipelineHasNoLayout, - #[error("failed to get a buffer for the given `RenderResource`")] - BufferAllocationFailure, - #[error("the given asset does not have any render resources")] - MissingAssetRenderResources, -} - -#[derive(SystemParam)] -pub struct DrawContext<'w, 's> { - pub pipelines: ResMut<'w, Assets>, - pub shaders: ResMut<'w, Assets>, - pub asset_render_resource_bindings: ResMut<'w, AssetRenderResourceBindings>, - pub pipeline_compiler: ResMut<'w, PipelineCompiler>, - pub render_resource_context: Res<'w, Box>, - pub shared_buffers: ResMut<'w, SharedBuffers>, - #[system_param(ignore)] - pub current_pipeline: Option>, - #[system_param(ignore)] - marker: PhantomData<&'s usize>, -} - -impl<'w, 's> DrawContext<'w, 's> { - pub fn get_uniform_buffer( - &mut self, - render_resource: &T, - ) -> Result { - self.shared_buffers - .get_uniform_buffer(&**self.render_resource_context, render_resource) - .ok_or(DrawError::BufferAllocationFailure) - } - - pub fn set_pipeline( - &mut self, - draw: &mut Draw, - pipeline_handle: &Handle, - specialization: &PipelineSpecialization, - ) -> Result<(), DrawError> { - let specialized_pipeline = if let Some(specialized_pipeline) = self - .pipeline_compiler - .get_specialized_pipeline(pipeline_handle, specialization) - { - specialized_pipeline - } else { - self.pipeline_compiler.compile_pipeline( - &**self.render_resource_context, - &mut self.pipelines, - &mut self.shaders, - pipeline_handle, - specialization, - ) - }; - - draw.set_pipeline(&specialized_pipeline); - self.current_pipeline = Some(specialized_pipeline.clone_weak()); - Ok(()) - } - - pub fn get_pipeline_descriptor(&self) -> Result<&PipelineDescriptor, DrawError> { - self.current_pipeline - .as_ref() - .and_then(|handle| self.pipelines.get(handle)) - .ok_or(DrawError::NoPipelineSet) - } - - pub fn get_pipeline_layout(&self) -> Result<&PipelineLayout, DrawError> { - self.get_pipeline_descriptor().and_then(|descriptor| { - descriptor - .get_layout() - .ok_or(DrawError::PipelineHasNoLayout) - }) - } - - pub fn set_asset_bind_groups( - &mut self, - draw: &mut Draw, - asset_handle: &Handle, - ) -> Result<(), DrawError> { - if let Some(asset_bindings) = self - .asset_render_resource_bindings - .get_mut_untyped(&asset_handle.clone_weak_untyped()) - { - Self::set_bind_groups_from_bindings_internal( - &self.current_pipeline, - &self.pipelines, - &**self.render_resource_context, - None, - draw, - &mut [asset_bindings], - ) - } else { - Err(DrawError::MissingAssetRenderResources) - } - } - - pub fn set_bind_groups_from_bindings( - &mut self, - draw: &mut Draw, - render_resource_bindings: &mut [&mut RenderResourceBindings], - ) -> Result<(), DrawError> { - Self::set_bind_groups_from_bindings_internal( - &self.current_pipeline, - &self.pipelines, - &**self.render_resource_context, - Some(&mut self.asset_render_resource_bindings), - draw, - render_resource_bindings, - ) - } - - fn set_bind_groups_from_bindings_internal( - current_pipeline: &Option>, - pipelines: &Assets, - render_resource_context: &dyn RenderResourceContext, - mut asset_render_resource_bindings: Option<&mut AssetRenderResourceBindings>, - draw: &mut Draw, - render_resource_bindings: &mut [&mut RenderResourceBindings], - ) -> Result<(), DrawError> { - let pipeline = current_pipeline.as_ref().ok_or(DrawError::NoPipelineSet)?; - let pipeline_descriptor = pipelines - .get(pipeline) - .ok_or(DrawError::NonExistentPipeline)?; - let layout = pipeline_descriptor - .get_layout() - .ok_or(DrawError::PipelineHasNoLayout)?; - 'bind_group_descriptors: for bind_group_descriptor in layout.bind_groups.iter() { - for bindings in render_resource_bindings.iter_mut() { - if let Some(bind_group) = - bindings.update_bind_group(bind_group_descriptor, render_resource_context) - { - draw.set_bind_group(bind_group_descriptor.index, bind_group); - continue 'bind_group_descriptors; - } - } - - // if none of the given RenderResourceBindings have the current bind group, try their - // assets - let asset_render_resource_bindings = - if let Some(value) = asset_render_resource_bindings.as_mut() { - value - } else { - continue 'bind_group_descriptors; - }; - for bindings in render_resource_bindings.iter_mut() { - for (asset_handle, _) in bindings.iter_assets() { - let asset_bindings = if let Some(asset_bindings) = - asset_render_resource_bindings.get_mut_untyped(asset_handle) - { - asset_bindings - } else { - continue; - }; - - if let Some(bind_group) = asset_bindings - .update_bind_group(bind_group_descriptor, render_resource_context) - { - draw.set_bind_group(bind_group_descriptor.index, bind_group); - continue 'bind_group_descriptors; - } - } - } - } - - Ok(()) - } - - pub fn create_bind_group_resource( - &self, - index: u32, - bind_group: &BindGroup, - ) -> Result<(), DrawError> { - let pipeline = self - .current_pipeline - .as_ref() - .ok_or(DrawError::NoPipelineSet)?; - let pipeline_descriptor = self - .pipelines - .get(pipeline) - .ok_or(DrawError::NonExistentPipeline)?; - let layout = pipeline_descriptor - .get_layout() - .ok_or(DrawError::PipelineHasNoLayout)?; - let bind_group_descriptor = &layout.bind_groups[index as usize]; - self.render_resource_context - .create_bind_group(bind_group_descriptor.id, bind_group); - Ok(()) - } - - pub fn set_vertex_buffers_from_bindings( - &self, - draw: &mut Draw, - render_resource_bindings: &[&RenderResourceBindings], - ) -> Result<(), DrawError> { - for bindings in render_resource_bindings.iter() { - if let Some((index_buffer, index_format)) = bindings.index_buffer { - draw.set_index_buffer(index_buffer, 0, index_format); - } - if let Some(main_vertex_buffer) = bindings.vertex_attribute_buffer { - draw.set_vertex_buffer(0, main_vertex_buffer, 0); - } - } - Ok(()) - } -} - -pub trait Drawable { - fn draw(&mut self, draw: &mut Draw, context: &mut DrawContext) -> Result<(), DrawError>; -} - -pub fn clear_draw_system(mut query: Query<&mut Draw>) { - for mut draw in query.iter_mut() { - draw.clear_render_commands(); - } -} diff --git a/crates/bevy_render/src/entity.rs b/crates/bevy_render/src/entity.rs deleted file mode 100644 index 7c6d4df1a93f0..0000000000000 --- a/crates/bevy_render/src/entity.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::{ - camera::{ - Camera, DepthCalculation, OrthographicProjection, PerspectiveProjection, ScalingMode, - VisibleEntities, - }, - pipeline::RenderPipelines, - prelude::Visible, - render_graph::base, - Draw, Mesh, -}; -use base::MainPass; -use bevy_asset::Handle; -use bevy_ecs::bundle::Bundle; -use bevy_transform::components::{GlobalTransform, Transform}; - -/// A component bundle for "mesh" entities -#[derive(Bundle, Default)] -pub struct MeshBundle { - pub mesh: Handle, - pub draw: Draw, - pub visible: Visible, - pub render_pipelines: RenderPipelines, - pub main_pass: MainPass, - pub transform: Transform, - pub global_transform: GlobalTransform, -} - -/// Component bundle for camera entities with perspective projection -/// -/// Use this for 3D rendering. -#[derive(Bundle)] -pub struct PerspectiveCameraBundle { - pub camera: Camera, - pub perspective_projection: PerspectiveProjection, - pub visible_entities: VisibleEntities, - pub transform: Transform, - pub global_transform: GlobalTransform, -} - -impl PerspectiveCameraBundle { - pub fn new_3d() -> Self { - Default::default() - } - - pub fn with_name(name: &str) -> Self { - PerspectiveCameraBundle { - camera: Camera { - name: Some(name.to_string()), - ..Default::default() - }, - perspective_projection: Default::default(), - visible_entities: Default::default(), - transform: Default::default(), - global_transform: Default::default(), - } - } -} - -impl Default for PerspectiveCameraBundle { - fn default() -> Self { - PerspectiveCameraBundle { - camera: Camera { - name: Some(base::camera::CAMERA_3D.to_string()), - ..Default::default() - }, - perspective_projection: Default::default(), - visible_entities: Default::default(), - transform: Default::default(), - global_transform: Default::default(), - } - } -} - -/// Component bundle for camera entities with orthographic projection -/// -/// Use this for 2D games, isometric games, CAD-like 3D views. -#[derive(Bundle)] -pub struct OrthographicCameraBundle { - pub camera: Camera, - pub orthographic_projection: OrthographicProjection, - pub visible_entities: VisibleEntities, - pub transform: Transform, - pub global_transform: GlobalTransform, -} - -impl OrthographicCameraBundle { - pub fn new_2d() -> Self { - // we want 0 to be "closest" and +far to be "farthest" in 2d, so we offset - // the camera's translation by far and use a right handed coordinate system - let far = 1000.0; - OrthographicCameraBundle { - camera: Camera { - name: Some(base::camera::CAMERA_2D.to_string()), - ..Default::default() - }, - orthographic_projection: OrthographicProjection { - far, - depth_calculation: DepthCalculation::ZDifference, - ..Default::default() - }, - visible_entities: Default::default(), - transform: Transform::from_xyz(0.0, 0.0, far - 0.1), - global_transform: Default::default(), - } - } - - pub fn new_3d() -> Self { - OrthographicCameraBundle { - camera: Camera { - name: Some(base::camera::CAMERA_3D.to_string()), - ..Default::default() - }, - orthographic_projection: OrthographicProjection { - scaling_mode: ScalingMode::FixedVertical, - depth_calculation: DepthCalculation::Distance, - ..Default::default() - }, - visible_entities: Default::default(), - transform: Default::default(), - global_transform: Default::default(), - } - } - - pub fn with_name(name: &str) -> Self { - OrthographicCameraBundle { - camera: Camera { - name: Some(name.to_string()), - ..Default::default() - }, - orthographic_projection: Default::default(), - visible_entities: Default::default(), - transform: Default::default(), - global_transform: Default::default(), - } - } -} diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index b870e2b366e86..74194ee9f393d 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -1,231 +1,311 @@ -#![allow(clippy::all)] pub mod camera; pub mod color; -pub mod colorspace; -pub mod draw; -pub mod entity; pub mod mesh; -pub mod pass; -pub mod pipeline; +pub mod options; +pub mod primitives; +pub mod render_asset; +pub mod render_component; pub mod render_graph; +pub mod render_phase; +pub mod render_resource; pub mod renderer; -pub mod shader; pub mod texture; -pub mod wireframe; - -use bevy_ecs::{ - schedule::{ParallelSystemDescriptorCoercion, SystemStage}, - system::{IntoExclusiveSystem, Res}, -}; -use bevy_transform::TransformSystem; -use bevy_utils::tracing::warn; -use draw::{OutsideFrustum, Visible}; - -pub use once_cell; +pub mod view; pub mod prelude { #[doc(hidden)] pub use crate::{ - base::Msaa, + camera::{ + Camera, OrthographicCameraBundle, OrthographicProjection, PerspectiveCameraBundle, + PerspectiveProjection, + }, color::Color, - draw::{Draw, Visible}, - entity::*, mesh::{shape, Mesh}, - pass::ClearColor, - pipeline::RenderPipelines, - shader::Shader, - texture::Texture, + render_resource::Shader, + texture::Image, + view::{ComputedVisibility, Msaa, Visibility}, }; } -use crate::prelude::*; -use base::Msaa; -use bevy_app::prelude::*; -use bevy_asset::{AddAsset, AssetStage}; -use bevy_ecs::schedule::{StageLabel, SystemLabel}; -use camera::{ - ActiveCameras, Camera, DepthCalculation, OrthographicProjection, PerspectiveProjection, - RenderLayers, ScalingMode, VisibleEntities, WindowOrigin, -}; -use pipeline::{ - IndexFormat, PipelineCompiler, PipelineDescriptor, PipelineSpecialization, PrimitiveTopology, - ShaderSpecialization, VertexBufferLayout, -}; -use render_graph::{ - base::{self, BaseRenderGraphConfig, MainPass}, - RenderGraph, +pub use once_cell; + +use crate::{ + camera::CameraPlugin, + color::Color, + mesh::MeshPlugin, + primitives::Frustum, + render_graph::RenderGraph, + render_resource::{RenderPipelineCache, Shader, ShaderLoader}, + renderer::render_system, + texture::ImagePlugin, + view::{ViewPlugin, WindowRenderPlugin}, }; -use renderer::{AssetRenderResourceBindings, RenderResourceBindings, RenderResourceContext}; -use shader::ShaderLoader; -#[cfg(feature = "hdr")] -use texture::HdrTextureLoader; -#[cfg(any( - feature = "png", - feature = "dds", - feature = "tga", - feature = "jpeg", - feature = "bmp" -))] -use texture::ImageTextureLoader; - -#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] -pub enum RenderSystem { - VisibleEntities, -} +use bevy_app::{App, AppLabel, Plugin}; +use bevy_asset::{AddAsset, AssetServer}; +use bevy_ecs::prelude::*; +use std::ops::{Deref, DerefMut}; -/// The names of "render" App stages +/// Contains the default Bevy rendering backend based on wgpu. +#[derive(Default)] +pub struct RenderPlugin; + +/// The labels of the default App rendering stages. #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] pub enum RenderStage { - /// Stage where render resources are set up - RenderResource, - /// Stage where Render Graph systems are run. In general you shouldn't add systems to this - /// stage manually. - RenderGraphSystems, - // Stage where draw systems are executed. This is generally where Draw components are setup - Draw, + /// Extract data from the "app world" and insert it into the "render world". + /// This step should be kept as short as possible to increase the "pipelining potential" for + /// running the next frame while rendering the current frame. + Extract, + + /// Prepare render resources from the extracted data for the GPU. + Prepare, + + /// Create [`BindGroups`](crate::render_resource::BindGroup) that depend on + /// [`Prepare`](RenderStage::Prepare) data and queue up draw calls to run during the + /// [`Render`](RenderStage::Render) stage. + Queue, + + // TODO: This could probably be moved in favor of a system ordering abstraction in Render or Queue + /// Sort the [`RenderPhases`](crate::render_phase::RenderPhase) here. + PhaseSort, + + /// Actual rendering happens here. + /// In most cases, only the render backend should insert resources here. Render, - PostRender, + + /// Cleanup render resources here. + Cleanup, } -/// Adds core render types and systems to an App -pub struct RenderPlugin { - /// configures the "base render graph". If this is not `None`, the "base render graph" will be - /// added - pub base_render_graph_config: Option, +/// The Render App World. This is only available as a resource during the Extract step. +#[derive(Default)] +pub struct RenderWorld(World); + +impl Deref for RenderWorld { + type Target = World; + + fn deref(&self) -> &Self::Target { + &self.0 + } } -impl Default for RenderPlugin { - fn default() -> Self { - RenderPlugin { - base_render_graph_config: Some(BaseRenderGraphConfig::default()), - } +impl DerefMut for RenderWorld { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 } } +/// A Label for the rendering sub-app. +#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] +pub struct RenderApp; + +/// A "scratch" world used to avoid allocating new worlds every frame when +/// swapping out the [`RenderWorld`]. +#[derive(Default)] +struct ScratchRenderWorld(World); + impl Plugin for RenderPlugin { + /// Initializes the renderer, sets up the [`RenderStage`](RenderStage) and creates the rendering sub-app. fn build(&self, app: &mut App) { - #[cfg(any( - feature = "png", - feature = "dds", - feature = "tga", - feature = "jpeg", - feature = "bmp" - ))] - { - app.init_asset_loader::(); - } - #[cfg(feature = "hdr")] - { - app.init_asset_loader::(); - } - - app.add_stage_after( - AssetStage::AssetEvents, - RenderStage::RenderResource, - SystemStage::parallel(), - ) - .add_stage_after( - RenderStage::RenderResource, - RenderStage::RenderGraphSystems, - SystemStage::parallel(), - ) - .add_stage_after( - RenderStage::RenderGraphSystems, - RenderStage::Draw, - SystemStage::parallel(), - ) - .add_stage_after( - RenderStage::Draw, - RenderStage::Render, - SystemStage::parallel(), - ) - .add_stage_after( - RenderStage::Render, - RenderStage::PostRender, - SystemStage::parallel(), - ) - .init_asset_loader::() - .add_asset::() - .add_asset::() - .add_asset::() - .add_asset::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .register_type::() - .init_resource::() - .init_resource::() - .init_resource::() - .init_resource::() - .init_resource::() - .init_resource::() - .init_resource::() - .add_startup_system_to_stage(StartupStage::PreStartup, check_for_render_resource_context) - .add_system_to_stage(CoreStage::PreUpdate, draw::clear_draw_system) - .add_system_to_stage(CoreStage::PostUpdate, camera::active_cameras_system) - .add_system_to_stage( - CoreStage::PostUpdate, - camera::camera_system::.before(RenderSystem::VisibleEntities), - ) - .add_system_to_stage( - CoreStage::PostUpdate, - camera::camera_system::.before(RenderSystem::VisibleEntities), - ) - .add_system_to_stage( - CoreStage::PostUpdate, - camera::visible_entities_system - .label(RenderSystem::VisibleEntities) - .after(TransformSystem::TransformPropagate), - ) - .add_system_to_stage(RenderStage::RenderResource, shader::shader_update_system) - .add_system_to_stage( - RenderStage::RenderResource, - mesh::mesh_resource_provider_system, - ) - .add_system_to_stage( - RenderStage::RenderResource, - Texture::texture_resource_system, - ) - .add_system_to_stage( - RenderStage::RenderGraphSystems, - render_graph::render_graph_schedule_executor_system.exclusive_system(), - ) - .add_system_to_stage(RenderStage::Draw, pipeline::draw_render_pipelines_system) - .add_system_to_stage(RenderStage::PostRender, shader::clear_shader_defs_system); - - if let Some(ref config) = self.base_render_graph_config { - crate::base::add_base_graph(config, &mut app.world); - let mut active_cameras = app.world.get_resource_mut::().unwrap(); - if config.add_3d_camera { - active_cameras.add(base::camera::CAMERA_3D); + let options = app + .world + .get_resource::() + .cloned() + .unwrap_or_default(); + let instance = wgpu::Instance::new(options.backends); + let surface = { + let world = app.world.cell(); + let windows = world.get_resource_mut::().unwrap(); + let raw_handle = windows.get_primary().map(|window| unsafe { + let handle = window.raw_window_handle().get_handle(); + instance.create_surface(&handle) + }); + raw_handle + }; + let (device, queue) = futures_lite::future::block_on(renderer::initialize_renderer( + &instance, + &wgpu::RequestAdapterOptions { + power_preference: options.power_preference, + compatible_surface: surface.as_ref(), + ..Default::default() + }, + &wgpu::DeviceDescriptor { + label: options.device_label.as_ref().map(|a| a.as_ref()), + features: options.features, + limits: options.limits, + }, + )); + app.insert_resource(device.clone()) + .insert_resource(queue.clone()) + .add_asset::() + .init_asset_loader::() + .init_resource::() + .register_type::() + .register_type::(); + let render_pipeline_cache = RenderPipelineCache::new(device.clone()); + let asset_server = app.world.get_resource::().unwrap().clone(); + + let mut render_app = App::empty(); + let mut extract_stage = + SystemStage::parallel().with_system(RenderPipelineCache::extract_shaders); + // don't apply buffers when the stage finishes running + // extract stage runs on the app world, but the buffers are applied to the render world + extract_stage.set_apply_buffers(false); + render_app + .add_stage(RenderStage::Extract, extract_stage) + .add_stage(RenderStage::Prepare, SystemStage::parallel()) + .add_stage(RenderStage::Queue, SystemStage::parallel()) + .add_stage(RenderStage::PhaseSort, SystemStage::parallel()) + .add_stage( + RenderStage::Render, + SystemStage::parallel() + .with_system(RenderPipelineCache::process_pipeline_queue_system) + .with_system(render_system.exclusive_system().at_end()), + ) + .add_stage(RenderStage::Cleanup, SystemStage::parallel()) + .insert_resource(instance) + .insert_resource(device) + .insert_resource(queue) + .insert_resource(render_pipeline_cache) + .insert_resource(asset_server) + .init_resource::(); + + app.add_sub_app(RenderApp, render_app, move |app_world, render_app| { + #[cfg(feature = "trace")] + let render_span = bevy_utils::tracing::info_span!("renderer subapp"); + #[cfg(feature = "trace")] + let _render_guard = render_span.enter(); + { + #[cfg(feature = "trace")] + let stage_span = + bevy_utils::tracing::info_span!("stage", name = "reserve_and_flush"); + #[cfg(feature = "trace")] + let _stage_guard = stage_span.enter(); + + // reserve all existing app entities for use in render_app + // they can only be spawned using `get_or_spawn()` + let meta_len = app_world.entities().meta.len(); + render_app + .world + .entities() + .reserve_entities(meta_len as u32); + + // flushing as "invalid" ensures that app world entities aren't added as "empty archetype" entities by default + // these entities cannot be accessed without spawning directly onto them + // this _only_ works as expected because clear_entities() is called at the end of every frame. + render_app.world.entities_mut().flush_as_invalid(); } - if config.add_2d_camera { - active_cameras.add(base::camera::CAMERA_2D); + { + #[cfg(feature = "trace")] + let stage_span = bevy_utils::tracing::info_span!("stage", name = "extract"); + #[cfg(feature = "trace")] + let _stage_guard = stage_span.enter(); + + // extract + extract(app_world, render_app); + } + + { + #[cfg(feature = "trace")] + let stage_span = bevy_utils::tracing::info_span!("stage", name = "prepare"); + #[cfg(feature = "trace")] + let _stage_guard = stage_span.enter(); + + // prepare + let prepare = render_app + .schedule + .get_stage_mut::(&RenderStage::Prepare) + .unwrap(); + prepare.run(&mut render_app.world); + } + + { + #[cfg(feature = "trace")] + let stage_span = bevy_utils::tracing::info_span!("stage", name = "queue"); + #[cfg(feature = "trace")] + let _stage_guard = stage_span.enter(); + + // queue + let queue = render_app + .schedule + .get_stage_mut::(&RenderStage::Queue) + .unwrap(); + queue.run(&mut render_app.world); + } + + { + #[cfg(feature = "trace")] + let stage_span = bevy_utils::tracing::info_span!("stage", name = "sort"); + #[cfg(feature = "trace")] + let _stage_guard = stage_span.enter(); + + // phase sort + let phase_sort = render_app + .schedule + .get_stage_mut::(&RenderStage::PhaseSort) + .unwrap(); + phase_sort.run(&mut render_app.world); } - } + + { + #[cfg(feature = "trace")] + let stage_span = bevy_utils::tracing::info_span!("stage", name = "render"); + #[cfg(feature = "trace")] + let _stage_guard = stage_span.enter(); + + // render + let render = render_app + .schedule + .get_stage_mut::(&RenderStage::Render) + .unwrap(); + render.run(&mut render_app.world); + } + + { + #[cfg(feature = "trace")] + let stage_span = bevy_utils::tracing::info_span!("stage", name = "cleanup"); + #[cfg(feature = "trace")] + let _stage_guard = stage_span.enter(); + + // cleanup + let cleanup = render_app + .schedule + .get_stage_mut::(&RenderStage::Cleanup) + .unwrap(); + cleanup.run(&mut render_app.world); + + render_app.world.clear_entities(); + } + }); + + app.add_plugin(WindowRenderPlugin) + .add_plugin(CameraPlugin) + .add_plugin(ViewPlugin) + .add_plugin(MeshPlugin) + .add_plugin(ImagePlugin); } } -fn check_for_render_resource_context(context: Option>>) { - if context.is_none() { - warn!( - "bevy_render couldn't find a render backend. Perhaps try adding the bevy_wgpu feature/plugin!" - ); - } +/// Executes the [`Extract`](RenderStage::Extract) stage of the renderer. +/// This updates the render world with the extracted ECS data of the current frame. +fn extract(app_world: &mut World, render_app: &mut App) { + let extract = render_app + .schedule + .get_stage_mut::(&RenderStage::Extract) + .unwrap(); + + // temporarily add the render world to the app world as a resource + let scratch_world = app_world.remove_resource::().unwrap(); + let render_world = std::mem::replace(&mut render_app.world, scratch_world.0); + app_world.insert_resource(RenderWorld(render_world)); + + extract.run(app_world); + + // add the render world back to the render app + let render_world = app_world.remove_resource::().unwrap(); + let scratch_world = std::mem::replace(&mut render_app.world, render_world.0); + app_world.insert_resource(ScratchRenderWorld(scratch_world)); + + extract.apply_buffers(&mut render_app.world); } diff --git a/crates/bevy_render/src/mesh/mesh.rs b/crates/bevy_render/src/mesh/mesh.rs deleted file mode 100644 index 2648d42df11fc..0000000000000 --- a/crates/bevy_render/src/mesh/mesh.rs +++ /dev/null @@ -1,636 +0,0 @@ -mod conversions; - -use crate::{ - pipeline::{IndexFormat, PrimitiveTopology, RenderPipelines, VertexFormat}, - renderer::{BufferInfo, BufferUsage, RenderResourceContext, RenderResourceId}, -}; -use bevy_asset::{AssetEvent, Assets, Handle}; -use bevy_core::cast_slice; -use bevy_ecs::{ - entity::Entity, - event::EventReader, - prelude::QueryState, - query::{Changed, With}, - system::{Local, QuerySet, Res}, - world::Mut, -}; -use bevy_math::*; -use bevy_reflect::TypeUuid; -use bevy_utils::EnumVariantMeta; -use std::{borrow::Cow, collections::BTreeMap}; - -use crate::pipeline::{InputStepMode, VertexAttribute, VertexBufferLayout}; -use bevy_utils::{HashMap, HashSet}; - -pub const INDEX_BUFFER_ASSET_INDEX: u64 = 0; -pub const VERTEX_ATTRIBUTE_BUFFER_ID: u64 = 10; - -/// An array where each entry describes a property of a single vertex. -#[derive(Clone, Debug, EnumVariantMeta)] -pub enum VertexAttributeValues { - Float32(Vec), - Sint32(Vec), - Uint32(Vec), - Float32x2(Vec<[f32; 2]>), - Sint32x2(Vec<[i32; 2]>), - Uint32x2(Vec<[u32; 2]>), - Float32x3(Vec<[f32; 3]>), - Sint32x3(Vec<[i32; 3]>), - Uint32x3(Vec<[u32; 3]>), - Float32x4(Vec<[f32; 4]>), - Sint32x4(Vec<[i32; 4]>), - Uint32x4(Vec<[u32; 4]>), - Sint16x2(Vec<[i16; 2]>), - Snorm16x2(Vec<[i16; 2]>), - Uint16x2(Vec<[u16; 2]>), - Unorm16x2(Vec<[u16; 2]>), - Sint16x4(Vec<[i16; 4]>), - Snorm16x4(Vec<[i16; 4]>), - Uint16x4(Vec<[u16; 4]>), - Unorm16x4(Vec<[u16; 4]>), - Sint8x2(Vec<[i8; 2]>), - Snorm8x2(Vec<[i8; 2]>), - Uint8x2(Vec<[u8; 2]>), - Unorm8x2(Vec<[u8; 2]>), - Sint8x4(Vec<[i8; 4]>), - Snorm8x4(Vec<[i8; 4]>), - Uint8x4(Vec<[u8; 4]>), - Unorm8x4(Vec<[u8; 4]>), -} - -impl VertexAttributeValues { - /// Returns the number of vertices in this VertexAttribute. For a single - /// mesh, all of the VertexAttributeValues must have the same length. - pub fn len(&self) -> usize { - match *self { - VertexAttributeValues::Float32(ref values) => values.len(), - VertexAttributeValues::Sint32(ref values) => values.len(), - VertexAttributeValues::Uint32(ref values) => values.len(), - VertexAttributeValues::Float32x2(ref values) => values.len(), - VertexAttributeValues::Sint32x2(ref values) => values.len(), - VertexAttributeValues::Uint32x2(ref values) => values.len(), - VertexAttributeValues::Float32x3(ref values) => values.len(), - VertexAttributeValues::Sint32x3(ref values) => values.len(), - VertexAttributeValues::Uint32x3(ref values) => values.len(), - VertexAttributeValues::Float32x4(ref values) => values.len(), - VertexAttributeValues::Sint32x4(ref values) => values.len(), - VertexAttributeValues::Uint32x4(ref values) => values.len(), - VertexAttributeValues::Sint16x2(ref values) => values.len(), - VertexAttributeValues::Snorm16x2(ref values) => values.len(), - VertexAttributeValues::Uint16x2(ref values) => values.len(), - VertexAttributeValues::Unorm16x2(ref values) => values.len(), - VertexAttributeValues::Sint16x4(ref values) => values.len(), - VertexAttributeValues::Snorm16x4(ref values) => values.len(), - VertexAttributeValues::Uint16x4(ref values) => values.len(), - VertexAttributeValues::Unorm16x4(ref values) => values.len(), - VertexAttributeValues::Sint8x2(ref values) => values.len(), - VertexAttributeValues::Snorm8x2(ref values) => values.len(), - VertexAttributeValues::Uint8x2(ref values) => values.len(), - VertexAttributeValues::Unorm8x2(ref values) => values.len(), - VertexAttributeValues::Sint8x4(ref values) => values.len(), - VertexAttributeValues::Snorm8x4(ref values) => values.len(), - VertexAttributeValues::Uint8x4(ref values) => values.len(), - VertexAttributeValues::Unorm8x4(ref values) => values.len(), - } - } - - /// Returns `true` if there are no vertices in this VertexAttributeValue - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - fn as_float3(&self) -> Option<&[[f32; 3]]> { - match self { - VertexAttributeValues::Float32x3(values) => Some(values), - _ => None, - } - } - - // TODO: add vertex format as parameter here and perform type conversions - /// Flattens the VertexAttributeArray into a sequence of bytes. This is - /// useful for serialization and sending to the GPU. - pub fn get_bytes(&self) -> &[u8] { - match self { - VertexAttributeValues::Float32(values) => cast_slice(&values[..]), - VertexAttributeValues::Sint32(values) => cast_slice(&values[..]), - VertexAttributeValues::Uint32(values) => cast_slice(&values[..]), - VertexAttributeValues::Float32x2(values) => cast_slice(&values[..]), - VertexAttributeValues::Sint32x2(values) => cast_slice(&values[..]), - VertexAttributeValues::Uint32x2(values) => cast_slice(&values[..]), - VertexAttributeValues::Float32x3(values) => cast_slice(&values[..]), - VertexAttributeValues::Sint32x3(values) => cast_slice(&values[..]), - VertexAttributeValues::Uint32x3(values) => cast_slice(&values[..]), - VertexAttributeValues::Float32x4(values) => cast_slice(&values[..]), - VertexAttributeValues::Sint32x4(values) => cast_slice(&values[..]), - VertexAttributeValues::Uint32x4(values) => cast_slice(&values[..]), - VertexAttributeValues::Sint16x2(values) => cast_slice(&values[..]), - VertexAttributeValues::Snorm16x2(values) => cast_slice(&values[..]), - VertexAttributeValues::Uint16x2(values) => cast_slice(&values[..]), - VertexAttributeValues::Unorm16x2(values) => cast_slice(&values[..]), - VertexAttributeValues::Sint16x4(values) => cast_slice(&values[..]), - VertexAttributeValues::Snorm16x4(values) => cast_slice(&values[..]), - VertexAttributeValues::Uint16x4(values) => cast_slice(&values[..]), - VertexAttributeValues::Unorm16x4(values) => cast_slice(&values[..]), - VertexAttributeValues::Sint8x2(values) => cast_slice(&values[..]), - VertexAttributeValues::Snorm8x2(values) => cast_slice(&values[..]), - VertexAttributeValues::Uint8x2(values) => cast_slice(&values[..]), - VertexAttributeValues::Unorm8x2(values) => cast_slice(&values[..]), - VertexAttributeValues::Sint8x4(values) => cast_slice(&values[..]), - VertexAttributeValues::Snorm8x4(values) => cast_slice(&values[..]), - VertexAttributeValues::Uint8x4(values) => cast_slice(&values[..]), - VertexAttributeValues::Unorm8x4(values) => cast_slice(&values[..]), - } - } -} - -impl From<&VertexAttributeValues> for VertexFormat { - fn from(values: &VertexAttributeValues) -> Self { - match values { - VertexAttributeValues::Float32(_) => VertexFormat::Float32, - VertexAttributeValues::Sint32(_) => VertexFormat::Sint32, - VertexAttributeValues::Uint32(_) => VertexFormat::Uint32, - VertexAttributeValues::Float32x2(_) => VertexFormat::Float32x2, - VertexAttributeValues::Sint32x2(_) => VertexFormat::Sint32x2, - VertexAttributeValues::Uint32x2(_) => VertexFormat::Uint32x2, - VertexAttributeValues::Float32x3(_) => VertexFormat::Float32x3, - VertexAttributeValues::Sint32x3(_) => VertexFormat::Sint32x3, - VertexAttributeValues::Uint32x3(_) => VertexFormat::Uint32x3, - VertexAttributeValues::Float32x4(_) => VertexFormat::Float32x4, - VertexAttributeValues::Sint32x4(_) => VertexFormat::Sint32x4, - VertexAttributeValues::Uint32x4(_) => VertexFormat::Uint32x4, - VertexAttributeValues::Sint16x2(_) => VertexFormat::Sint16x2, - VertexAttributeValues::Snorm16x2(_) => VertexFormat::Snorm16x2, - VertexAttributeValues::Uint16x2(_) => VertexFormat::Uint16x2, - VertexAttributeValues::Unorm16x2(_) => VertexFormat::Unorm16x2, - VertexAttributeValues::Sint16x4(_) => VertexFormat::Sint16x4, - VertexAttributeValues::Snorm16x4(_) => VertexFormat::Snorm16x4, - VertexAttributeValues::Uint16x4(_) => VertexFormat::Uint16x4, - VertexAttributeValues::Unorm16x4(_) => VertexFormat::Unorm16x4, - VertexAttributeValues::Sint8x2(_) => VertexFormat::Sint8x2, - VertexAttributeValues::Snorm8x2(_) => VertexFormat::Snorm8x2, - VertexAttributeValues::Uint8x2(_) => VertexFormat::Uint8x2, - VertexAttributeValues::Unorm8x2(_) => VertexFormat::Unorm8x2, - VertexAttributeValues::Sint8x4(_) => VertexFormat::Sint8x4, - VertexAttributeValues::Snorm8x4(_) => VertexFormat::Snorm8x4, - VertexAttributeValues::Uint8x4(_) => VertexFormat::Uint8x4, - VertexAttributeValues::Unorm8x4(_) => VertexFormat::Unorm8x4, - } - } -} - -/// An array of indices into the VertexAttributeValues for a mesh. -/// -/// It describes the order in which the vertex attributes should be joined into faces. -#[derive(Debug, Clone)] -pub enum Indices { - U16(Vec), - U32(Vec), -} - -impl Indices { - fn iter(&self) -> impl Iterator + '_ { - match self { - Indices::U16(vec) => IndicesIter::U16(vec.iter()), - Indices::U32(vec) => IndicesIter::U32(vec.iter()), - } - } -} -enum IndicesIter<'a> { - U16(std::slice::Iter<'a, u16>), - U32(std::slice::Iter<'a, u32>), -} -impl Iterator for IndicesIter<'_> { - type Item = usize; - - fn next(&mut self) -> Option { - match self { - IndicesIter::U16(iter) => iter.next().map(|val| *val as usize), - IndicesIter::U32(iter) => iter.next().map(|val| *val as usize), - } - } -} - -impl From<&Indices> for IndexFormat { - fn from(indices: &Indices) -> Self { - match indices { - Indices::U16(_) => IndexFormat::Uint16, - Indices::U32(_) => IndexFormat::Uint32, - } - } -} - -// TODO: allow values to be unloaded after been submitting to the GPU to conserve memory -#[derive(Debug, TypeUuid, Clone)] -#[uuid = "8ecbac0f-f545-4473-ad43-e1f4243af51e"] -pub struct Mesh { - primitive_topology: PrimitiveTopology, - /// `std::collections::BTreeMap` with all defined vertex attributes (Positions, Normals, ...) - /// for this mesh. Attribute name maps to attribute values. - /// Uses a BTreeMap because, unlike HashMap, it has a defined iteration order, - /// which allows easy stable VertexBuffers (i.e. same buffer order) - attributes: BTreeMap, VertexAttributeValues>, - indices: Option, -} - -/// Contains geometry in the form of a mesh. -/// -/// Often meshes are automatically generated by bevy's asset loaders or primitives, such as -/// [`crate::shape::Cube`] or [`crate::shape::Box`], but you can also construct -/// one yourself. -/// -/// Example of constructing a mesh: -/// ``` -/// # use bevy_render::mesh::{Mesh, Indices}; -/// # use bevy_render::pipeline::PrimitiveTopology; -/// fn create_triangle() -> Mesh { -/// let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); -/// mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0]]); -/// mesh.set_indices(Some(Indices::U32(vec![0,1,2]))); -/// mesh -/// } -/// ``` -impl Mesh { - /// Per vertex coloring. Use in conjunction with [`Mesh::set_attribute`] - pub const ATTRIBUTE_COLOR: &'static str = "Vertex_Color"; - /// The direction the vertex normal is facing in. - /// Use in conjunction with [`Mesh::set_attribute`] - pub const ATTRIBUTE_NORMAL: &'static str = "Vertex_Normal"; - /// The direction of the vertex tangent. Used for normal mapping - pub const ATTRIBUTE_TANGENT: &'static str = "Vertex_Tangent"; - - /// Where the vertex is located in space. Use in conjunction with [`Mesh::set_attribute`] - pub const ATTRIBUTE_POSITION: &'static str = "Vertex_Position"; - /// Texture coordinates for the vertex. Use in conjunction with [`Mesh::set_attribute`] - pub const ATTRIBUTE_UV_0: &'static str = "Vertex_Uv"; - - /// Per vertex joint transform matrix weight. Use in conjunction with [`Mesh::set_attribute`] - pub const ATTRIBUTE_JOINT_WEIGHT: &'static str = "Vertex_JointWeight"; - /// Per vertex joint transform matrix index. Use in conjunction with [`Mesh::set_attribute`] - pub const ATTRIBUTE_JOINT_INDEX: &'static str = "Vertex_JointIndex"; - - /// Construct a new mesh. You need to provide a PrimitiveTopology so that the - /// renderer knows how to treat the vertex data. Most of the time this will be - /// `PrimitiveTopology::TriangleList`. - pub fn new(primitive_topology: PrimitiveTopology) -> Self { - Mesh { - primitive_topology, - attributes: Default::default(), - indices: None, - } - } - - pub fn primitive_topology(&self) -> PrimitiveTopology { - self.primitive_topology - } - - /// Sets the data for a vertex attribute (position, normal etc.). The name will - /// often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`] - pub fn set_attribute( - &mut self, - name: impl Into>, - values: impl Into, - ) { - let values: VertexAttributeValues = values.into(); - self.attributes.insert(name.into(), values); - } - - /// Retrieve the data currently set behind a vertex attribute. - pub fn attribute(&self, name: impl Into>) -> Option<&VertexAttributeValues> { - self.attributes.get(&name.into()) - } - - pub fn attribute_mut( - &mut self, - name: impl Into>, - ) -> Option<&mut VertexAttributeValues> { - self.attributes.get_mut(&name.into()) - } - - /// Indices describe how triangles are constructed out of the vertex attributes. - /// They are only useful for the [`crate::pipeline::PrimitiveTopology`] variants that use - /// triangles - pub fn set_indices(&mut self, indices: Option) { - self.indices = indices; - } - - pub fn indices(&self) -> Option<&Indices> { - self.indices.as_ref() - } - - pub fn indices_mut(&mut self) -> Option<&mut Indices> { - self.indices.as_mut() - } - - pub fn get_index_buffer_bytes(&self) -> Option<&[u8]> { - self.indices.as_ref().map(|indices| match &indices { - Indices::U16(indices) => cast_slice(&indices[..]), - Indices::U32(indices) => cast_slice(&indices[..]), - }) - } - - pub fn get_vertex_buffer_layout(&self) -> VertexBufferLayout { - let mut attributes = Vec::new(); - let mut accumulated_offset = 0; - for (attribute_name, attribute_values) in self.attributes.iter() { - let vertex_format = VertexFormat::from(attribute_values); - attributes.push(VertexAttribute { - name: attribute_name.clone(), - offset: accumulated_offset, - format: vertex_format, - shader_location: 0, - }); - accumulated_offset += vertex_format.get_size(); - } - - VertexBufferLayout { - name: Default::default(), - stride: accumulated_offset, - step_mode: InputStepMode::Vertex, - attributes, - } - } - - pub fn count_vertices(&self) -> usize { - let mut vertex_count: Option = None; - for (attribute_name, attribute_data) in self.attributes.iter() { - let attribute_len = attribute_data.len(); - if let Some(previous_vertex_count) = vertex_count { - assert_eq!(previous_vertex_count, attribute_len, - "Attribute {} has a different vertex count ({}) than other attributes ({}) in this mesh.", attribute_name, attribute_len, previous_vertex_count); - } - vertex_count = Some(attribute_len); - } - - vertex_count.unwrap_or(0) - } - - pub fn get_vertex_buffer_data(&self) -> Vec { - let mut vertex_size = 0; - for attribute_values in self.attributes.values() { - let vertex_format = VertexFormat::from(attribute_values); - vertex_size += vertex_format.get_size() as usize; - } - - let vertex_count = self.count_vertices(); - let mut attributes_interleaved_buffer = vec![0; vertex_count * vertex_size]; - // bundle into interleaved buffers - let mut attribute_offset = 0; - for attribute_values in self.attributes.values() { - let vertex_format = VertexFormat::from(attribute_values); - let attribute_size = vertex_format.get_size() as usize; - let attributes_bytes = attribute_values.get_bytes(); - for (vertex_index, attribute_bytes) in - attributes_bytes.chunks_exact(attribute_size).enumerate() - { - let offset = vertex_index * vertex_size + attribute_offset; - attributes_interleaved_buffer[offset..offset + attribute_size] - .copy_from_slice(attribute_bytes); - } - - attribute_offset += attribute_size; - } - - attributes_interleaved_buffer - } - - /// Duplicates the vertex attributes so that no vertices are shared. - /// - /// This can dramatically increase the vertex count, so make sure this is what you want. - /// Does nothing if no [Indices] are set. - pub fn duplicate_vertices(&mut self) { - fn duplicate(values: &[T], indices: impl Iterator) -> Vec { - indices.map(|i| values[i]).collect() - } - - assert!( - matches!(self.primitive_topology, PrimitiveTopology::TriangleList), - "can only duplicate vertices for `TriangleList`s" - ); - - let indices = match self.indices.take() { - Some(indices) => indices, - None => return, - }; - for (_, attributes) in self.attributes.iter_mut() { - let indices = indices.iter(); - match attributes { - VertexAttributeValues::Float32(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Sint32(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Uint32(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Float32x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Sint32x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Uint32x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Float32x3(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Sint32x3(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Uint32x3(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Sint32x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Uint32x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Float32x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Sint16x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Snorm16x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Uint16x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Unorm16x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Sint16x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Snorm16x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Uint16x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Unorm16x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Sint8x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Snorm8x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Uint8x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Unorm8x2(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Sint8x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Snorm8x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Uint8x4(vec) => *vec = duplicate(vec, indices), - VertexAttributeValues::Unorm8x4(vec) => *vec = duplicate(vec, indices), - } - } - } - - /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh. - /// - /// Panics if [`Indices`] are set. - /// Consider calling [Mesh::duplicate_vertices] or export your mesh with normal attributes. - pub fn compute_flat_normals(&mut self) { - if self.indices().is_some() { - panic!("`compute_flat_normals` can't work on indexed geometry. Consider calling `Mesh::duplicate_vertices`."); - } - - let positions = self - .attribute(Mesh::ATTRIBUTE_POSITION) - .unwrap() - .as_float3() - .expect("`Mesh::ATTRIBUTE_POSITION` vertex attributes should be of type `float3`"); - - let normals: Vec<_> = positions - .chunks_exact(3) - .map(|p| face_normal(p[0], p[1], p[2])) - .flat_map(|normal| [normal; 3]) - .collect(); - - self.set_attribute(Mesh::ATTRIBUTE_NORMAL, normals); - } -} - -fn face_normal(a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> [f32; 3] { - let (a, b, c) = (Vec3::from(a), Vec3::from(b), Vec3::from(c)); - (b - a).cross(c - a).normalize().into() -} - -fn remove_resource_save( - render_resource_context: &dyn RenderResourceContext, - handle: &Handle, - index: u64, -) { - if let Some(RenderResourceId::Buffer(buffer)) = - render_resource_context.get_asset_resource(handle, index) - { - render_resource_context.remove_buffer(buffer); - render_resource_context.remove_asset_resource(handle, index); - } -} -fn remove_current_mesh_resources( - render_resource_context: &dyn RenderResourceContext, - handle: &Handle, -) { - remove_resource_save(render_resource_context, handle, VERTEX_ATTRIBUTE_BUFFER_ID); - remove_resource_save(render_resource_context, handle, INDEX_BUFFER_ASSET_INDEX); -} - -#[derive(Default)] -pub struct MeshEntities { - entities: HashSet, -} - -#[derive(Default)] -pub struct MeshResourceProviderState { - mesh_entities: HashMap, MeshEntities>, -} - -#[allow(clippy::type_complexity)] -pub fn mesh_resource_provider_system( - mut state: Local, - render_resource_context: Res>, - meshes: Res>, - mut mesh_events: EventReader>, - mut queries: QuerySet<( - QueryState<&mut RenderPipelines, With>>, - QueryState<(Entity, &Handle, &mut RenderPipelines), Changed>>, - )>, -) { - let mut changed_meshes = HashSet::default(); - let render_resource_context = &**render_resource_context; - for event in mesh_events.iter() { - match event { - AssetEvent::Created { ref handle } => { - changed_meshes.insert(handle.clone_weak()); - } - AssetEvent::Modified { ref handle } => { - changed_meshes.insert(handle.clone_weak()); - remove_current_mesh_resources(render_resource_context, handle); - } - AssetEvent::Removed { ref handle } => { - remove_current_mesh_resources(render_resource_context, handle); - // if mesh was modified and removed in the same update, ignore the modification - // events are ordered so future modification events are ok - changed_meshes.remove(handle); - } - } - } - - // update changed mesh data - for changed_mesh_handle in changed_meshes.iter() { - if let Some(mesh) = meshes.get(changed_mesh_handle) { - // TODO: check for individual buffer changes in non-interleaved mode - if let Some(data) = mesh.get_index_buffer_bytes() { - let index_buffer = render_resource_context.create_buffer_with_data( - BufferInfo { - buffer_usage: BufferUsage::INDEX, - ..Default::default() - }, - data, - ); - - render_resource_context.set_asset_resource( - changed_mesh_handle, - RenderResourceId::Buffer(index_buffer), - INDEX_BUFFER_ASSET_INDEX, - ); - } - - let interleaved_buffer = mesh.get_vertex_buffer_data(); - if !interleaved_buffer.is_empty() { - render_resource_context.set_asset_resource( - changed_mesh_handle, - RenderResourceId::Buffer(render_resource_context.create_buffer_with_data( - BufferInfo { - buffer_usage: BufferUsage::VERTEX, - ..Default::default() - }, - &interleaved_buffer, - )), - VERTEX_ATTRIBUTE_BUFFER_ID, - ); - } - - if let Some(mesh_entities) = state.mesh_entities.get_mut(changed_mesh_handle) { - for entity in mesh_entities.entities.iter() { - if let Ok(render_pipelines) = queries.q0().get_mut(*entity) { - update_entity_mesh( - render_resource_context, - mesh, - changed_mesh_handle, - render_pipelines, - ); - } - } - } - } - } - - // handover buffers to pipeline - for (entity, handle, render_pipelines) in queries.q1().iter_mut() { - let mesh_entities = state - .mesh_entities - .entry(handle.clone_weak()) - .or_insert_with(MeshEntities::default); - mesh_entities.entities.insert(entity); - if let Some(mesh) = meshes.get(handle) { - update_entity_mesh(render_resource_context, mesh, handle, render_pipelines); - } - } -} - -fn update_entity_mesh( - render_resource_context: &dyn RenderResourceContext, - mesh: &Mesh, - handle: &Handle, - mut render_pipelines: Mut, -) { - for render_pipeline in render_pipelines.pipelines.iter_mut() { - render_pipeline.specialization.primitive_topology = mesh.primitive_topology; - // TODO: don't allocate a new vertex buffer descriptor for every entity - render_pipeline.specialization.vertex_buffer_layout = mesh.get_vertex_buffer_layout(); - if let PrimitiveTopology::LineStrip | PrimitiveTopology::TriangleStrip = - mesh.primitive_topology - { - render_pipeline.specialization.strip_index_format = - mesh.indices().map(|indices| indices.into()); - } - } - if let Some(RenderResourceId::Buffer(index_buffer_resource)) = - render_resource_context.get_asset_resource(handle, INDEX_BUFFER_ASSET_INDEX) - { - let index_format: IndexFormat = mesh.indices().unwrap().into(); - // set index buffer into binding - render_pipelines - .bindings - .set_index_buffer(index_buffer_resource, index_format); - } - - if let Some(RenderResourceId::Buffer(vertex_attribute_buffer_resource)) = - render_resource_context.get_asset_resource(handle, VERTEX_ATTRIBUTE_BUFFER_ID) - { - // set index buffer into binding - render_pipelines.bindings.vertex_attribute_buffer = Some(vertex_attribute_buffer_resource); - } -} diff --git a/pipelined/bevy_render2/src/mesh/mesh/mod.rs b/crates/bevy_render/src/mesh/mesh/mod.rs similarity index 99% rename from pipelined/bevy_render2/src/mesh/mesh/mod.rs rename to crates/bevy_render/src/mesh/mesh/mod.rs index 0818bd88bc0e9..1c82fdf1eed53 100644 --- a/pipelined/bevy_render2/src/mesh/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mesh/mod.rs @@ -40,8 +40,8 @@ pub struct Mesh { /// /// Example of constructing a mesh: /// ``` -/// # use bevy_render2::mesh::{Mesh, Indices}; -/// # use bevy_render2::render_resource::PrimitiveTopology; +/// # use bevy_render::mesh::{Mesh, Indices}; +/// # use bevy_render::render_resource::PrimitiveTopology; /// fn create_triangle() -> Mesh { /// let mut mesh = Mesh::new(PrimitiveTopology::TriangleList); /// mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0]]); diff --git a/crates/bevy_render/src/mesh/mod.rs b/crates/bevy_render/src/mesh/mod.rs index 4ed2fc54ef70b..6740bd02ef44d 100644 --- a/crates/bevy_render/src/mesh/mod.rs +++ b/crates/bevy_render/src/mesh/mod.rs @@ -4,3 +4,17 @@ mod mesh; pub mod shape; pub use mesh::*; + +use crate::render_asset::RenderAssetPlugin; +use bevy_app::{App, Plugin}; +use bevy_asset::AddAsset; + +/// Adds the [`Mesh`] as an asset and makes sure that they are extracted and prepared for the GPU. +pub struct MeshPlugin; + +impl Plugin for MeshPlugin { + fn build(&self, app: &mut App) { + app.add_asset::() + .add_plugin(RenderAssetPlugin::::default()); + } +} diff --git a/crates/bevy_render/src/mesh/shape/capsule.rs b/crates/bevy_render/src/mesh/shape/capsule.rs index aaba621cc70ab..7b6d268a9a9a6 100644 --- a/crates/bevy_render/src/mesh/shape/capsule.rs +++ b/crates/bevy_render/src/mesh/shape/capsule.rs @@ -1,8 +1,6 @@ -use crate::{ - mesh::{Indices, Mesh}, - pipeline::PrimitiveTopology, -}; +use crate::mesh::{Indices, Mesh}; use bevy_math::{Vec2, Vec3}; +use wgpu::PrimitiveTopology; /// A cylinder with hemispheres at the top and bottom #[derive(Debug, Copy, Clone)] diff --git a/crates/bevy_render/src/mesh/shape/icosphere.rs b/crates/bevy_render/src/mesh/shape/icosphere.rs index f1e07a0ecf92e..ff2068469ec51 100644 --- a/crates/bevy_render/src/mesh/shape/icosphere.rs +++ b/crates/bevy_render/src/mesh/shape/icosphere.rs @@ -1,9 +1,6 @@ +use crate::mesh::{Indices, Mesh}; use hexasphere::shapes::IcoSphere; - -use crate::{ - mesh::{Indices, Mesh}, - pipeline::PrimitiveTopology, -}; +use wgpu::PrimitiveTopology; /// A sphere made from a subdivided Icosahedron. #[derive(Debug, Clone, Copy)] diff --git a/crates/bevy_render/src/mesh/shape/mod.rs b/crates/bevy_render/src/mesh/shape/mod.rs index 85c61e9ab92f0..e2aa285d2085a 100644 --- a/crates/bevy_render/src/mesh/shape/mod.rs +++ b/crates/bevy_render/src/mesh/shape/mod.rs @@ -1,5 +1,4 @@ use super::{Indices, Mesh}; -use crate::pipeline::PrimitiveTopology; use bevy_math::*; #[derive(Debug, Copy, Clone)] @@ -25,6 +24,7 @@ impl From for Mesh { } } +/// An axis-aligned box defined by its minimum and maximum point. #[derive(Debug, Copy, Clone)] pub struct Box { pub min_x: f32, @@ -38,6 +38,7 @@ pub struct Box { } impl Box { + /// Creates a new box centered at the origin with the supplied side lengths. pub fn new(x_length: f32, y_length: f32, z_length: f32) -> Box { Box { max_x: x_length / 2.0, @@ -119,7 +120,7 @@ impl From for Mesh { } } -/// A rectangle on the XY plane. +/// A rectangle on the XY plane centered at the origin. #[derive(Debug, Copy, Clone)] pub struct Quad { /// Full width and height of the rectangle. @@ -221,7 +222,7 @@ impl From for Mesh { } } -/// A square on the XZ plane. +/// A square on the XZ plane centered at the origin. #[derive(Debug, Copy, Clone)] pub struct Plane { /// The total side length of the square. @@ -274,3 +275,4 @@ pub use capsule::{Capsule, CapsuleUvProfile}; pub use icosphere::Icosphere; pub use torus::Torus; pub use uvsphere::UVSphere; +use wgpu::PrimitiveTopology; diff --git a/crates/bevy_render/src/mesh/shape/torus.rs b/crates/bevy_render/src/mesh/shape/torus.rs index 56039f3d1884d..987cb3b230b19 100644 --- a/crates/bevy_render/src/mesh/shape/torus.rs +++ b/crates/bevy_render/src/mesh/shape/torus.rs @@ -1,8 +1,6 @@ -use crate::{ - mesh::{Indices, Mesh}, - pipeline::PrimitiveTopology, -}; +use crate::mesh::{Indices, Mesh}; use bevy_math::Vec3; +use wgpu::PrimitiveTopology; /// A torus (donut) shape. #[derive(Debug, Clone, Copy)] diff --git a/crates/bevy_render/src/mesh/shape/uvsphere.rs b/crates/bevy_render/src/mesh/shape/uvsphere.rs index 5addc973ff953..571c3123d9aa1 100644 --- a/crates/bevy_render/src/mesh/shape/uvsphere.rs +++ b/crates/bevy_render/src/mesh/shape/uvsphere.rs @@ -1,10 +1,9 @@ -use crate::{ - mesh::{Indices, Mesh}, - pipeline::PrimitiveTopology, -}; +use wgpu::PrimitiveTopology; + +use crate::mesh::{Indices, Mesh}; use std::f32::consts::PI; -/// A sphere made of sectors and stacks +/// A sphere made of sectors and stacks. #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Clone, Copy)] pub struct UVSphere { diff --git a/pipelined/bevy_render2/src/options.rs b/crates/bevy_render/src/options.rs similarity index 66% rename from pipelined/bevy_render2/src/options.rs rename to crates/bevy_render/src/options.rs index 7f2e47364e0e8..379abbe708c62 100644 --- a/pipelined/bevy_render2/src/options.rs +++ b/crates/bevy_render/src/options.rs @@ -1,14 +1,14 @@ use std::borrow::Cow; -pub use wgpu::{Backends, Features, Limits, PowerPreference}; +pub use wgpu::{Backends, Features as WgpuFeatures, Limits as WgpuLimits, PowerPreference}; #[derive(Clone)] pub struct WgpuOptions { pub device_label: Option>, pub backends: Backends, pub power_preference: PowerPreference, - pub features: Features, - pub limits: Limits, + pub features: WgpuFeatures, + pub limits: WgpuLimits, } impl Default for WgpuOptions { @@ -24,7 +24,14 @@ impl Default for WgpuOptions { let limits = if cfg!(target_arch = "wasm32") { wgpu::Limits::downlevel_webgl2_defaults() } else { - wgpu::Limits::default() + #[allow(unused_mut)] + let mut limits = wgpu::Limits::default(); + #[cfg(feature = "ci_limits")] + { + limits.max_storage_textures_per_shader_stage = 4; + limits.max_texture_dimension_3d = 1024; + } + limits }; Self { diff --git a/crates/bevy_render/src/pass/mod.rs b/crates/bevy_render/src/pass/mod.rs deleted file mode 100644 index 26342576ec483..0000000000000 --- a/crates/bevy_render/src/pass/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod ops; -#[allow(clippy::module_inception)] -mod pass; -mod render_pass; - -pub use ops::*; -pub use pass::*; -pub use render_pass::*; diff --git a/crates/bevy_render/src/pass/ops.rs b/crates/bevy_render/src/pass/ops.rs deleted file mode 100644 index 68a698541d074..0000000000000 --- a/crates/bevy_render/src/pass/ops.rs +++ /dev/null @@ -1,17 +0,0 @@ -/// Operation to perform to the output attachment at the start of a renderpass. -#[derive(Clone, Copy, Debug, Hash, PartialEq)] -pub enum LoadOp { - /// Clear with a specified value. - Clear(V), - /// Load from memory. - Load, -} - -/// Pair of load and store operations for an attachment aspect. -#[derive(Clone, Debug, Hash, PartialEq)] -pub struct Operations { - /// How data should be read through this attachment. - pub load: LoadOp, - /// Whether data will be written to through this attachment. - pub store: bool, -} diff --git a/crates/bevy_render/src/pass/pass.rs b/crates/bevy_render/src/pass/pass.rs deleted file mode 100644 index 9e799e1d706f0..0000000000000 --- a/crates/bevy_render/src/pass/pass.rs +++ /dev/null @@ -1,57 +0,0 @@ -use super::Operations; -use crate::{renderer::TextureId, Color}; - -#[derive(Debug, Clone)] -pub enum TextureAttachment { - Id(TextureId), - Name(String), - Input(String), -} - -impl TextureAttachment { - pub fn get_texture_id(&self) -> Option { - if let TextureAttachment::Id(texture_id) = self { - Some(*texture_id) - } else { - None - } - } -} - -#[derive(Clone, Debug)] -pub struct ClearColor(pub Color); - -impl Default for ClearColor { - fn default() -> Self { - Self(Color::rgb(0.4, 0.4, 0.4)) - } -} - -#[derive(Debug, Clone)] -pub struct RenderPassColorAttachment { - /// The actual color attachment. - pub attachment: TextureAttachment, - - /// The resolve target for this color attachment, if any. - pub resolve_target: Option, - - /// What operations will be performed on this color attachment. - pub ops: Operations, -} - -#[derive(Debug, Clone)] -pub struct RenderPassDepthStencilAttachment { - pub attachment: TextureAttachment, - /// What operations will be performed on the depth part of the attachment. - pub depth_ops: Option>, - /// What operations will be performed on the stencil part of the attachment. - pub stencil_ops: Option>, -} - -// A set of pipeline bindings and draw calls with color and depth outputs -#[derive(Debug, Clone)] -pub struct PassDescriptor { - pub color_attachments: Vec, - pub depth_stencil_attachment: Option, - pub sample_count: u32, -} diff --git a/crates/bevy_render/src/pass/render_pass.rs b/crates/bevy_render/src/pass/render_pass.rs deleted file mode 100644 index 40f3ba36bfb8f..0000000000000 --- a/crates/bevy_render/src/pass/render_pass.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::{ - pipeline::{BindGroupDescriptorId, IndexFormat, PipelineDescriptor}, - renderer::{BindGroupId, BufferId, RenderContext}, -}; -use bevy_asset::Handle; -use std::ops::Range; - -pub trait RenderPass { - fn get_render_context(&self) -> &dyn RenderContext; - fn set_index_buffer(&mut self, buffer: BufferId, offset: u64, index_format: IndexFormat); - fn set_vertex_buffer(&mut self, start_slot: u32, buffer: BufferId, offset: u64); - fn set_pipeline(&mut self, pipeline_handle: &Handle); - fn set_viewport(&mut self, x: f32, y: f32, w: f32, h: f32, min_depth: f32, max_depth: f32); - fn set_scissor_rect(&mut self, x: u32, y: u32, w: u32, h: u32); - fn set_stencil_reference(&mut self, reference: u32); - fn draw(&mut self, vertices: Range, instances: Range); - fn draw_indexed(&mut self, indices: Range, base_vertex: i32, instances: Range); - fn set_bind_group( - &mut self, - index: u32, - bind_group_descriptor_id: BindGroupDescriptorId, - bind_group: BindGroupId, - dynamic_uniform_indices: Option<&[u32]>, - ); -} diff --git a/crates/bevy_render/src/pipeline/bind_group.rs b/crates/bevy_render/src/pipeline/bind_group.rs deleted file mode 100644 index 8f244d9322780..0000000000000 --- a/crates/bevy_render/src/pipeline/bind_group.rs +++ /dev/null @@ -1,49 +0,0 @@ -use super::BindingDescriptor; -use bevy_utils::AHasher; -use std::hash::{Hash, Hasher}; - -#[derive(Clone, Debug, Eq)] -pub struct BindGroupDescriptor { - pub index: u32, - pub bindings: Vec, - pub id: BindGroupDescriptorId, -} - -#[derive(Hash, Copy, Clone, Eq, PartialEq, Debug)] -pub struct BindGroupDescriptorId(u64); - -impl BindGroupDescriptor { - pub fn new(index: u32, bindings: Vec) -> Self { - let mut descriptor = BindGroupDescriptor { - index, - bindings, - id: BindGroupDescriptorId(0), - }; - - descriptor.update_id(); - descriptor - } - - pub fn update_id(&mut self) { - let mut hasher = AHasher::default(); - self.hash(&mut hasher); - self.id = BindGroupDescriptorId(hasher.finish()); - } -} - -impl Hash for BindGroupDescriptor { - fn hash(&self, state: &mut H) { - // TODO: remove index from hash state (or at least id), and update the PartialEq implem. - // index is not considered a part of a bind group on the gpu. - // bind groups are bound to indices in pipelines. - self.index.hash(state); - self.bindings.hash(state); - } -} - -impl PartialEq for BindGroupDescriptor { - fn eq(&self, other: &Self) -> bool { - // This MUST be kept in sync with the hash implementation above - self.index == other.index && self.bindings == other.bindings - } -} diff --git a/crates/bevy_render/src/pipeline/binding.rs b/crates/bevy_render/src/pipeline/binding.rs deleted file mode 100644 index 55fbf9bda802e..0000000000000 --- a/crates/bevy_render/src/pipeline/binding.rs +++ /dev/null @@ -1,64 +0,0 @@ -use super::UniformProperty; -use crate::texture::{ - StorageTextureAccess, TextureFormat, TextureSampleType, TextureViewDimension, -}; - -bitflags::bitflags! { - pub struct BindingShaderStage: u32 { - const VERTEX = 1; - const FRAGMENT = 2; - const COMPUTE = 4; - } -} - -#[derive(Hash, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] -pub struct BindingDescriptor { - pub name: String, - pub index: u32, - pub bind_type: BindType, - pub shader_stage: BindingShaderStage, -} - -#[derive(Hash, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] -pub enum BindType { - Uniform { - has_dynamic_offset: bool, - property: UniformProperty, - }, - StorageBuffer { - has_dynamic_offset: bool, - readonly: bool, - }, - Sampler { - /// The sampling result is produced based on more than a single color sample from a - /// texture, e.g. when bilinear interpolation is enabled. - /// - /// A filtering sampler can only be used with a filterable texture. - filtering: bool, - /// Use as a comparison sampler instead of a normal sampler. - /// For more info take a look at the analogous functionality in OpenGL: . - comparison: bool, - }, - Texture { - multisampled: bool, - view_dimension: TextureViewDimension, - sample_type: TextureSampleType, - }, - StorageTexture { - /// Allowed access to this texture. - access: StorageTextureAccess, - /// Format of the texture. - format: TextureFormat, - /// Dimension of the texture view that is going to be sampled. - view_dimension: TextureViewDimension, - }, -} - -impl BindType { - pub fn get_uniform_size(&self) -> Option { - match self { - BindType::Uniform { property, .. } => Some(property.get_size()), - _ => None, - } - } -} diff --git a/crates/bevy_render/src/pipeline/mod.rs b/crates/bevy_render/src/pipeline/mod.rs deleted file mode 100644 index d25bbfea64f51..0000000000000 --- a/crates/bevy_render/src/pipeline/mod.rs +++ /dev/null @@ -1,20 +0,0 @@ -mod bind_group; -mod binding; -#[allow(clippy::module_inception)] -mod pipeline; -mod pipeline_compiler; -mod pipeline_layout; -mod render_pipelines; -mod state_descriptors; -mod vertex_buffer_descriptor; -mod vertex_format; - -pub use bind_group::*; -pub use binding::*; -pub use pipeline::*; -pub use pipeline_compiler::*; -pub use pipeline_layout::*; -pub use render_pipelines::*; -pub use state_descriptors::*; -pub use vertex_buffer_descriptor::*; -pub use vertex_format::*; diff --git a/crates/bevy_render/src/pipeline/pipeline.rs b/crates/bevy_render/src/pipeline/pipeline.rs deleted file mode 100644 index 88c71750e41fe..0000000000000 --- a/crates/bevy_render/src/pipeline/pipeline.rs +++ /dev/null @@ -1,118 +0,0 @@ -use super::{ - state_descriptors::{ - BlendFactor, BlendOperation, ColorWrite, CompareFunction, Face, FrontFace, - PrimitiveTopology, - }, - PipelineLayout, -}; -use crate::{ - pipeline::{ - BlendComponent, BlendState, ColorTargetState, DepthBiasState, DepthStencilState, - MultisampleState, PolygonMode, PrimitiveState, StencilFaceState, StencilState, - }, - shader::ShaderStages, - texture::TextureFormat, -}; -use bevy_reflect::TypeUuid; - -#[derive(Clone, Debug, TypeUuid)] -#[uuid = "ebfc1d11-a2a4-44cb-8f12-c49cc631146c"] -pub struct PipelineDescriptor { - pub name: Option, - pub layout: Option, - pub shader_stages: ShaderStages, - pub primitive: PrimitiveState, - pub depth_stencil: Option, - pub multisample: MultisampleState, - - /// The effect of draw calls on the color aspect of the output target. - pub color_target_states: Vec, -} - -impl PipelineDescriptor { - pub fn new(shader_stages: ShaderStages) -> Self { - PipelineDescriptor { - name: None, - layout: None, - color_target_states: Vec::new(), - depth_stencil: None, - shader_stages, - primitive: PrimitiveState { - topology: PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: FrontFace::Ccw, - cull_mode: Some(Face::Back), - polygon_mode: PolygonMode::Fill, - clamp_depth: false, - conservative: false, - }, - multisample: MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - } - } - - pub fn default_config(shader_stages: ShaderStages) -> Self { - PipelineDescriptor { - name: None, - primitive: PrimitiveState { - topology: PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: FrontFace::Ccw, - cull_mode: Some(Face::Back), - polygon_mode: PolygonMode::Fill, - clamp_depth: false, - conservative: false, - }, - layout: None, - depth_stencil: Some(DepthStencilState { - format: TextureFormat::Depth32Float, - depth_write_enabled: true, - depth_compare: CompareFunction::Less, - stencil: StencilState { - front: StencilFaceState::IGNORE, - back: StencilFaceState::IGNORE, - read_mask: 0, - write_mask: 0, - }, - bias: DepthBiasState { - constant: 0, - slope_scale: 0.0, - clamp: 0.0, - }, - }), - color_target_states: vec![ColorTargetState { - format: TextureFormat::default(), - blend: Some(BlendState { - color: BlendComponent { - src_factor: BlendFactor::SrcAlpha, - dst_factor: BlendFactor::OneMinusSrcAlpha, - operation: BlendOperation::Add, - }, - alpha: BlendComponent { - src_factor: BlendFactor::One, - dst_factor: BlendFactor::One, - operation: BlendOperation::Add, - }, - }), - write_mask: ColorWrite::ALL, - }], - multisample: MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - shader_stages, - } - } - - pub fn get_layout(&self) -> Option<&PipelineLayout> { - self.layout.as_ref() - } - - pub fn get_layout_mut(&mut self) -> Option<&mut PipelineLayout> { - self.layout.as_mut() - } -} diff --git a/crates/bevy_render/src/pipeline/pipeline_compiler.rs b/crates/bevy_render/src/pipeline/pipeline_compiler.rs deleted file mode 100644 index 1272551773403..0000000000000 --- a/crates/bevy_render/src/pipeline/pipeline_compiler.rs +++ /dev/null @@ -1,367 +0,0 @@ -use super::{state_descriptors::PrimitiveTopology, IndexFormat, PipelineDescriptor}; -use crate::{ - pipeline::{BindType, VertexBufferLayout}, - renderer::RenderResourceContext, - shader::{Shader, ShaderError}, -}; -use bevy_asset::{Assets, Handle}; -use bevy_reflect::{Reflect, ReflectDeserialize}; -use bevy_utils::{HashMap, HashSet}; -use once_cell::sync::Lazy; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Eq, PartialEq, Debug, Reflect)] -#[reflect(PartialEq)] -pub struct PipelineSpecialization { - pub shader_specialization: ShaderSpecialization, - pub primitive_topology: PrimitiveTopology, - pub dynamic_bindings: HashSet, - pub strip_index_format: Option, - pub vertex_buffer_layout: VertexBufferLayout, - pub sample_count: u32, -} - -impl Default for PipelineSpecialization { - fn default() -> Self { - Self { - sample_count: 1, - strip_index_format: None, - shader_specialization: Default::default(), - primitive_topology: Default::default(), - dynamic_bindings: Default::default(), - vertex_buffer_layout: Default::default(), - } - } -} - -impl PipelineSpecialization { - pub fn empty() -> &'static PipelineSpecialization { - pub static EMPTY: Lazy = Lazy::new(PipelineSpecialization::default); - &EMPTY - } -} - -#[derive(Clone, Eq, PartialEq, Debug, Default, Reflect, Serialize, Deserialize)] -#[reflect(PartialEq, Serialize, Deserialize)] -pub struct ShaderSpecialization { - pub shader_defs: HashSet, -} - -#[derive(Debug)] -struct SpecializedShader { - shader: Handle, - specialization: ShaderSpecialization, -} - -#[derive(Debug)] -struct SpecializedPipeline { - pipeline: Handle, - specialization: PipelineSpecialization, -} - -#[derive(Debug, Default)] -pub struct PipelineCompiler { - specialized_shaders: HashMap, Vec>, - specialized_shader_pipelines: HashMap, Vec>>, - specialized_pipelines: HashMap, Vec>, -} - -impl PipelineCompiler { - fn compile_shader( - &mut self, - render_resource_context: &dyn RenderResourceContext, - shaders: &mut Assets, - shader_handle: &Handle, - shader_specialization: &ShaderSpecialization, - ) -> Result, ShaderError> { - let specialized_shaders = self - .specialized_shaders - .entry(shader_handle.clone_weak()) - .or_insert_with(Vec::new); - - let shader = shaders.get(shader_handle).unwrap(); - - if let Some(specialized_shader) = - specialized_shaders - .iter() - .find(|current_specialized_shader| { - current_specialized_shader.specialization == *shader_specialization - }) - { - // if shader has already been compiled with current configuration, use existing shader - Ok(specialized_shader.shader.clone_weak()) - } else { - // if no shader exists with the current configuration, create new shader and compile - let shader_def_vec = shader_specialization - .shader_defs - .iter() - .cloned() - .collect::>(); - let compiled_shader = - render_resource_context.get_specialized_shader(shader, Some(&shader_def_vec))?; - let specialized_handle = shaders.add(compiled_shader); - let weak_specialized_handle = specialized_handle.clone_weak(); - specialized_shaders.push(SpecializedShader { - shader: specialized_handle, - specialization: shader_specialization.clone(), - }); - Ok(weak_specialized_handle) - } - } - - pub fn get_specialized_pipeline( - &self, - pipeline: &Handle, - specialization: &PipelineSpecialization, - ) -> Option> { - self.specialized_pipelines - .get(pipeline) - .and_then(|specialized_pipelines| { - specialized_pipelines - .iter() - .find(|current_specialized_pipeline| { - ¤t_specialized_pipeline.specialization == specialization - }) - }) - .map(|specialized_pipeline| specialized_pipeline.pipeline.clone_weak()) - } - - pub fn compile_pipeline( - &mut self, - render_resource_context: &dyn RenderResourceContext, - pipelines: &mut Assets, - shaders: &mut Assets, - source_pipeline: &Handle, - pipeline_specialization: &PipelineSpecialization, - ) -> Handle { - let source_descriptor = pipelines.get(source_pipeline).unwrap(); - let mut specialized_descriptor = source_descriptor.clone(); - let specialized_vertex_shader = self - .compile_shader( - render_resource_context, - shaders, - &specialized_descriptor.shader_stages.vertex, - &pipeline_specialization.shader_specialization, - ) - .unwrap_or_else(|e| panic_shader_error(e)); - specialized_descriptor.shader_stages.vertex = specialized_vertex_shader.clone_weak(); - let mut specialized_fragment_shader = None; - specialized_descriptor.shader_stages.fragment = specialized_descriptor - .shader_stages - .fragment - .as_ref() - .map(|fragment| { - let shader = self - .compile_shader( - render_resource_context, - shaders, - fragment, - &pipeline_specialization.shader_specialization, - ) - .unwrap_or_else(|e| panic_shader_error(e)); - specialized_fragment_shader = Some(shader.clone_weak()); - shader - }); - - let mut layout = render_resource_context.reflect_pipeline_layout( - shaders, - &specialized_descriptor.shader_stages, - true, - ); - - if !pipeline_specialization.dynamic_bindings.is_empty() { - // set binding uniforms to dynamic if render resource bindings use dynamic - for bind_group in layout.bind_groups.iter_mut() { - let mut binding_changed = false; - for binding in bind_group.bindings.iter_mut() { - if pipeline_specialization - .dynamic_bindings - .iter() - .any(|b| b == &binding.name) - { - if let BindType::Uniform { - ref mut has_dynamic_offset, - .. - } = binding.bind_type - { - *has_dynamic_offset = true; - binding_changed = true; - } - } - } - - if binding_changed { - bind_group.update_id(); - } - } - } - specialized_descriptor.layout = Some(layout); - - // create a vertex layout that provides all attributes from either the specialized vertex - // buffers or a zero buffer - let mut pipeline_layout = specialized_descriptor.layout.as_mut().unwrap(); - // the vertex buffer descriptor of the mesh - let mesh_vertex_buffer_layout = &pipeline_specialization.vertex_buffer_layout; - - // the vertex buffer descriptor that will be used for this pipeline - let mut compiled_vertex_buffer_descriptor = VertexBufferLayout { - step_mode: mesh_vertex_buffer_layout.step_mode, - stride: mesh_vertex_buffer_layout.stride, - ..Default::default() - }; - - for shader_vertex_attribute in pipeline_layout.vertex_buffer_descriptors.iter() { - let shader_vertex_attribute = shader_vertex_attribute - .attributes - .get(0) - .expect("Reflected layout has no attributes."); - - if let Some(target_vertex_attribute) = mesh_vertex_buffer_layout - .attributes - .iter() - .find(|x| x.name == shader_vertex_attribute.name) - { - // copy shader location from reflected layout - let mut compiled_vertex_attribute = target_vertex_attribute.clone(); - compiled_vertex_attribute.shader_location = shader_vertex_attribute.shader_location; - compiled_vertex_buffer_descriptor - .attributes - .push(compiled_vertex_attribute); - } else { - panic!( - "Attribute {} is required by shader, but not supplied by mesh. Either remove the attribute from the shader or supply the attribute ({}) to the mesh.", - shader_vertex_attribute.name, - shader_vertex_attribute.name, - ); - } - } - - // TODO: add other buffers (like instancing) here - let mut vertex_buffer_descriptors = Vec::::default(); - if !pipeline_layout.vertex_buffer_descriptors.is_empty() { - vertex_buffer_descriptors.push(compiled_vertex_buffer_descriptor); - } - - pipeline_layout.vertex_buffer_descriptors = vertex_buffer_descriptors; - specialized_descriptor.multisample.count = pipeline_specialization.sample_count; - specialized_descriptor.primitive.topology = pipeline_specialization.primitive_topology; - specialized_descriptor.primitive.strip_index_format = - pipeline_specialization.strip_index_format; - - let specialized_pipeline_handle = pipelines.add(specialized_descriptor); - render_resource_context.create_render_pipeline( - specialized_pipeline_handle.clone_weak(), - pipelines.get(&specialized_pipeline_handle).unwrap(), - shaders, - ); - - // track specialized shader pipelines - self.specialized_shader_pipelines - .entry(specialized_vertex_shader) - .or_insert_with(Default::default) - .push(source_pipeline.clone_weak()); - if let Some(specialized_fragment_shader) = specialized_fragment_shader { - self.specialized_shader_pipelines - .entry(specialized_fragment_shader) - .or_insert_with(Default::default) - .push(source_pipeline.clone_weak()); - } - - let specialized_pipelines = self - .specialized_pipelines - .entry(source_pipeline.clone_weak()) - .or_insert_with(Vec::new); - let weak_specialized_pipeline_handle = specialized_pipeline_handle.clone_weak(); - specialized_pipelines.push(SpecializedPipeline { - pipeline: specialized_pipeline_handle, - specialization: pipeline_specialization.clone(), - }); - - weak_specialized_pipeline_handle - } - - pub fn iter_compiled_pipelines( - &self, - pipeline_handle: Handle, - ) -> Option>> { - self.specialized_pipelines - .get(&pipeline_handle) - .map(|compiled_pipelines| { - compiled_pipelines - .iter() - .map(|specialized_pipeline| &specialized_pipeline.pipeline) - }) - } - - pub fn iter_all_compiled_pipelines(&self) -> impl Iterator> { - self.specialized_pipelines - .values() - .map(|compiled_pipelines| { - compiled_pipelines - .iter() - .map(|specialized_pipeline| &specialized_pipeline.pipeline) - }) - .flatten() - } - - /// Update specialized shaders and remove any related specialized - /// pipelines and assets. - pub fn update_shader( - &mut self, - shader: &Handle, - pipelines: &mut Assets, - shaders: &mut Assets, - render_resource_context: &dyn RenderResourceContext, - ) -> Result<(), ShaderError> { - if let Some(specialized_shaders) = self.specialized_shaders.get_mut(shader) { - for specialized_shader in specialized_shaders { - // Recompile specialized shader. If it fails, we bail immediately. - let shader_def_vec = specialized_shader - .specialization - .shader_defs - .iter() - .cloned() - .collect::>(); - let new_handle = - shaders.add(render_resource_context.get_specialized_shader( - shaders.get(shader).unwrap(), - Some(&shader_def_vec), - )?); - - // Replace handle and remove old from assets. - let old_handle = std::mem::replace(&mut specialized_shader.shader, new_handle); - shaders.remove(&old_handle); - - // Find source pipelines that use the old specialized - // shader, and remove from tracking. - if let Some(source_pipelines) = - self.specialized_shader_pipelines.remove(&old_handle) - { - // Remove all specialized pipelines from tracking - // and asset storage. They will be rebuilt on next - // draw. - for source_pipeline in source_pipelines { - if let Some(specialized_pipelines) = - self.specialized_pipelines.remove(&source_pipeline) - { - for p in specialized_pipelines { - pipelines.remove(p.pipeline); - } - } - } - } - } - } - - Ok(()) - } -} - -fn panic_shader_error(error: ShaderError) -> ! { - let msg = error.to_string(); - let msg = msg - .trim_end() - .trim_end_matches("Debug log:") // if this matches, then there wasn't a debug log anyways - .trim_end(); - panic!("{}\n", msg); -} diff --git a/crates/bevy_render/src/pipeline/pipeline_layout.rs b/crates/bevy_render/src/pipeline/pipeline_layout.rs deleted file mode 100644 index 5a1816ca1de9a..0000000000000 --- a/crates/bevy_render/src/pipeline/pipeline_layout.rs +++ /dev/null @@ -1,105 +0,0 @@ -use super::{BindGroupDescriptor, VertexBufferLayout}; -use crate::shader::ShaderLayout; -use bevy_utils::HashMap; -use std::hash::Hash; - -#[derive(Clone, Debug, Default)] -pub struct PipelineLayout { - pub bind_groups: Vec, - pub vertex_buffer_descriptors: Vec, -} - -impl PipelineLayout { - pub fn get_bind_group(&self, index: u32) -> Option<&BindGroupDescriptor> { - self.bind_groups - .iter() - .find(|bind_group| bind_group.index == index) - } - - pub fn from_shader_layouts(shader_layouts: &mut [ShaderLayout]) -> Self { - let mut bind_groups = HashMap::::default(); - let mut vertex_buffer_descriptors = Vec::new(); - for shader_layout in shader_layouts.iter_mut() { - for shader_bind_group in shader_layout.bind_groups.iter_mut() { - match bind_groups.get_mut(&shader_bind_group.index) { - Some(bind_group) => { - for shader_binding in shader_bind_group.bindings.iter() { - if let Some(binding) = bind_group - .bindings - .iter_mut() - .find(|binding| binding.index == shader_binding.index) - { - binding.shader_stage |= shader_binding.shader_stage; - if binding.bind_type != shader_binding.bind_type - || binding.name != shader_binding.name - || binding.index != shader_binding.index - { - panic!("Binding {} in BindGroup {} does not match across all shader types: {:?} {:?}.", binding.index, bind_group.index, binding, shader_binding); - } - } else { - bind_group.bindings.push(shader_binding.clone()); - } - } - bind_group.update_id(); - } - None => { - bind_groups.insert(shader_bind_group.index, shader_bind_group.clone()); - } - } - } - } - - for vertex_buffer_descriptor in shader_layouts[0].vertex_buffer_layout.iter() { - vertex_buffer_descriptors.push(vertex_buffer_descriptor.clone()); - } - - let mut bind_groups_result = bind_groups - .drain() - .map(|(_, value)| value) - .collect::>(); - - // NOTE: for some reason bind groups need to be sorted by index. this is likely an issue - // with bevy and not with wgpu TODO: try removing this - bind_groups_result.sort_by(|a, b| a.index.partial_cmp(&b.index).unwrap()); - - PipelineLayout { - bind_groups: bind_groups_result, - vertex_buffer_descriptors, - } - } -} - -#[derive(Hash, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] -pub enum UniformProperty { - UInt, - Int, - IVec2, - Float, - UVec4, - Vec2, - Vec3, - Vec4, - Mat3, - Mat4, - Struct(Vec), - Array(Box, usize), -} - -impl UniformProperty { - pub fn get_size(&self) -> u64 { - match self { - UniformProperty::UInt => 4, - UniformProperty::Int => 4, - UniformProperty::IVec2 => 4 * 2, - UniformProperty::Float => 4, - UniformProperty::UVec4 => 4 * 4, - UniformProperty::Vec2 => 4 * 2, - UniformProperty::Vec3 => 4 * 3, - UniformProperty::Vec4 => 4 * 4, - UniformProperty::Mat3 => 4 * 4 * 3, - UniformProperty::Mat4 => 4 * 4 * 4, - UniformProperty::Struct(properties) => properties.iter().map(|p| p.get_size()).sum(), - UniformProperty::Array(property, length) => property.get_size() * *length as u64, - } - } -} diff --git a/crates/bevy_render/src/pipeline/render_pipelines.rs b/crates/bevy_render/src/pipeline/render_pipelines.rs deleted file mode 100644 index 13ac09e429abf..0000000000000 --- a/crates/bevy_render/src/pipeline/render_pipelines.rs +++ /dev/null @@ -1,169 +0,0 @@ -use super::{PipelineDescriptor, PipelineSpecialization}; -use crate::{ - draw::{Draw, DrawContext, OutsideFrustum}, - mesh::{Indices, Mesh}, - prelude::{Msaa, Visible}, - renderer::RenderResourceBindings, -}; -use bevy_asset::{Assets, Handle}; -use bevy_ecs::{ - component::Component, - query::Without, - reflect::ReflectComponent, - system::{Query, Res, ResMut}, -}; -use bevy_reflect::Reflect; -use bevy_utils::HashSet; - -#[derive(Debug, Default, Clone, Reflect)] -pub struct RenderPipeline { - pub pipeline: Handle, - pub specialization: PipelineSpecialization, - /// used to track if PipelineSpecialization::dynamic_bindings is in sync with - /// RenderResourceBindings - pub dynamic_bindings_generation: usize, -} - -impl RenderPipeline { - pub fn new(pipeline: Handle) -> Self { - RenderPipeline { - specialization: Default::default(), - pipeline, - dynamic_bindings_generation: std::usize::MAX, - } - } - - pub fn specialized( - pipeline: Handle, - specialization: PipelineSpecialization, - ) -> Self { - RenderPipeline { - pipeline, - specialization, - dynamic_bindings_generation: std::usize::MAX, - } - } -} - -#[derive(Component, Debug, Clone, Reflect)] -#[reflect(Component)] -pub struct RenderPipelines { - pub pipelines: Vec, - #[reflect(ignore)] - pub bindings: RenderResourceBindings, -} - -impl RenderPipelines { - pub fn from_pipelines(pipelines: Vec) -> Self { - Self { - pipelines, - ..Default::default() - } - } - - pub fn from_handles<'a, T: IntoIterator>>( - handles: T, - ) -> Self { - RenderPipelines { - pipelines: handles - .into_iter() - .map(|pipeline| RenderPipeline::new(pipeline.clone_weak())) - .collect::>(), - ..Default::default() - } - } -} - -impl Default for RenderPipelines { - fn default() -> Self { - Self { - bindings: Default::default(), - pipelines: vec![RenderPipeline::default()], - } - } -} - -pub fn draw_render_pipelines_system( - mut draw_context: DrawContext, - mut render_resource_bindings: ResMut, - msaa: Res, - meshes: Res>, - mut query: Query< - (&mut Draw, &mut RenderPipelines, &Handle, &Visible), - Without, - >, -) { - for (mut draw, mut render_pipelines, mesh_handle, visible) in query.iter_mut() { - if !visible.is_visible { - continue; - } - - // don't render if the mesh isn't loaded yet - let mesh = if let Some(mesh) = meshes.get(mesh_handle) { - mesh - } else { - continue; - }; - - let index_range = match mesh.indices() { - Some(Indices::U32(indices)) => Some(0..indices.len() as u32), - Some(Indices::U16(indices)) => Some(0..indices.len() as u32), - None => None, - }; - - let render_pipelines = &mut *render_pipelines; - for pipeline in render_pipelines.pipelines.iter_mut() { - pipeline.specialization.sample_count = msaa.samples; - if pipeline.dynamic_bindings_generation - != render_pipelines.bindings.dynamic_bindings_generation() - { - pipeline.specialization.dynamic_bindings = render_pipelines - .bindings - .iter_dynamic_bindings() - .map(|name| name.to_string()) - .collect::>(); - pipeline.dynamic_bindings_generation = - render_pipelines.bindings.dynamic_bindings_generation(); - for (handle, _) in render_pipelines.bindings.iter_assets() { - if let Some(bindings) = draw_context - .asset_render_resource_bindings - .get_untyped(handle) - { - for binding in bindings.iter_dynamic_bindings() { - pipeline - .specialization - .dynamic_bindings - .insert(binding.to_string()); - } - } - } - } - } - - for render_pipeline in render_pipelines.pipelines.iter_mut() { - let render_resource_bindings = &mut [ - &mut render_pipelines.bindings, - &mut render_resource_bindings, - ]; - draw_context - .set_pipeline( - &mut draw, - &render_pipeline.pipeline, - &render_pipeline.specialization, - ) - .unwrap(); - draw_context - .set_bind_groups_from_bindings(&mut draw, render_resource_bindings) - .unwrap(); - draw_context - .set_vertex_buffers_from_bindings(&mut draw, &[&render_pipelines.bindings]) - .unwrap(); - - if let Some(indices) = index_range.clone() { - draw.draw_indexed(indices, 0, 0..1); - } else { - draw.draw(0..mesh.count_vertices() as u32, 0..1) - } - } - } -} diff --git a/crates/bevy_render/src/pipeline/state_descriptors.rs b/crates/bevy_render/src/pipeline/state_descriptors.rs deleted file mode 100644 index 5196d50df115f..0000000000000 --- a/crates/bevy_render/src/pipeline/state_descriptors.rs +++ /dev/null @@ -1,232 +0,0 @@ -use crate::texture::TextureFormat; -use bevy_reflect::{Reflect, ReflectDeserialize}; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, Debug)] -pub struct DepthStencilState { - pub format: TextureFormat, - pub depth_write_enabled: bool, - pub depth_compare: CompareFunction, - pub stencil: StencilState, - pub bias: DepthBiasState, -} - -#[derive(Clone, Debug)] -pub struct StencilState { - pub front: StencilFaceState, - pub back: StencilFaceState, - pub read_mask: u32, - pub write_mask: u32, -} -#[derive(Clone, Debug)] -pub struct MultisampleState { - /// The number of samples calculated per pixel (for MSAA). For non-multisampled textures, - /// this should be `1` - pub count: u32, - /// Bitmask that restricts the samples of a pixel modified by this pipeline. All samples - /// can be enabled using the value `!0` - pub mask: u64, - /// When enabled, produces another sample mask per pixel based on the alpha output value, that - /// is ANDed with the sample_mask and the primitive coverage to restrict the set of samples - /// affected by a primitive. - /// - /// The implicit mask produced for alpha of zero is guaranteed to be zero, and for alpha of one - /// is guaranteed to be all 1-s. - pub alpha_to_coverage_enabled: bool, -} - -#[derive(Clone, Debug)] -pub struct DepthBiasState { - /// Constant depth biasing factor, in basic units of the depth format. - pub constant: i32, - /// Slope depth biasing factor. - pub slope_scale: f32, - /// Depth bias clamp value (absolute). - pub clamp: f32, -} - -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -pub enum StencilOperation { - Keep = 0, - Zero = 1, - Replace = 2, - Invert = 3, - IncrementClamp = 4, - DecrementClamp = 5, - IncrementWrap = 6, - DecrementWrap = 7, -} - -#[derive(Clone, Debug, PartialEq)] -pub struct StencilFaceState { - pub compare: CompareFunction, - pub fail_op: StencilOperation, - pub depth_fail_op: StencilOperation, - pub pass_op: StencilOperation, -} - -impl StencilFaceState { - pub const IGNORE: Self = StencilFaceState { - compare: CompareFunction::Always, - fail_op: StencilOperation::Keep, - depth_fail_op: StencilOperation::Keep, - pass_op: StencilOperation::Keep, - }; -} - -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -pub enum CompareFunction { - Never = 0, - Less = 1, - Equal = 2, - LessEqual = 3, - Greater = 4, - NotEqual = 5, - GreaterEqual = 6, - Always = 7, -} - -/// Describes how the VertexAttributes should be interpreted while rendering -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, Reflect)] -#[reflect_value(Serialize, Deserialize, PartialEq, Hash)] -pub enum PrimitiveTopology { - PointList = 0, - LineList = 1, - LineStrip = 2, - TriangleList = 3, - TriangleStrip = 4, -} - -impl Default for PrimitiveTopology { - fn default() -> Self { - PrimitiveTopology::TriangleList - } -} - -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -pub enum FrontFace { - Ccw = 0, - Cw = 1, -} - -impl Default for FrontFace { - fn default() -> Self { - FrontFace::Ccw - } -} - -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -pub enum Face { - Front = 0, - Back = 1, -} - -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -pub enum PolygonMode { - /// Polygons are filled - Fill = 0, - /// Polygons are draw as line segments - Line = 1, - /// Polygons are draw as points - Point = 2, -} - -impl Default for PolygonMode { - fn default() -> Self { - PolygonMode::Fill - } -} - -#[derive(Clone, Debug, Default)] -pub struct PrimitiveState { - pub topology: PrimitiveTopology, - pub strip_index_format: Option, - pub front_face: FrontFace, - pub cull_mode: Option, - pub polygon_mode: PolygonMode, - pub clamp_depth: bool, - pub conservative: bool, -} - -#[derive(Clone, Debug)] -pub struct ColorTargetState { - pub format: TextureFormat, - pub blend: Option, - pub write_mask: ColorWrite, -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct BlendComponent { - pub src_factor: BlendFactor, - pub dst_factor: BlendFactor, - pub operation: BlendOperation, -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct BlendState { - pub alpha: BlendComponent, - pub color: BlendComponent, -} - -bitflags::bitflags! { - #[repr(transparent)] - pub struct ColorWrite: u32 { - const RED = 1; - const GREEN = 2; - const BLUE = 4; - const ALPHA = 8; - const COLOR = 7; - const ALL = 15; - } -} - -impl Default for ColorWrite { - fn default() -> Self { - ColorWrite::ALL - } -} - -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -pub enum BlendFactor { - Zero = 0, - One = 1, - Src = 2, - OneMinusSrc = 3, - SrcAlpha = 4, - OneMinusSrcAlpha = 5, - Dst = 6, - OneMinusDst = 7, - DstAlpha = 8, - OneMinusDstAlpha = 9, - SrcAlphaSaturated = 10, - Constant = 11, - OneMinusConstant = 12, -} - -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -pub enum BlendOperation { - Add = 0, - Subtract = 1, - ReverseSubtract = 2, - Min = 3, - Max = 4, -} - -impl Default for BlendOperation { - fn default() -> Self { - BlendOperation::Add - } -} - -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize, Reflect)] -#[reflect_value(Hash, PartialEq, Serialize, Deserialize)] -pub enum IndexFormat { - Uint16 = 0, - Uint32 = 1, -} - -impl Default for IndexFormat { - fn default() -> Self { - IndexFormat::Uint32 - } -} diff --git a/crates/bevy_render/src/pipeline/vertex_buffer_descriptor.rs b/crates/bevy_render/src/pipeline/vertex_buffer_descriptor.rs deleted file mode 100644 index 14ad026720346..0000000000000 --- a/crates/bevy_render/src/pipeline/vertex_buffer_descriptor.rs +++ /dev/null @@ -1,56 +0,0 @@ -use super::VertexFormat; -use bevy_reflect::{Reflect, ReflectDeserialize}; -use serde::{Deserialize, Serialize}; -use std::{ - borrow::Cow, - hash::{Hash, Hasher}, -}; - -#[derive(Clone, Debug, Eq, PartialEq, Default, Reflect, Serialize, Deserialize)] -#[reflect_value(Serialize, Deserialize, PartialEq)] -pub struct VertexBufferLayout { - pub name: Cow<'static, str>, - pub stride: u64, - pub step_mode: InputStepMode, - pub attributes: Vec, -} - -impl VertexBufferLayout { - pub fn new_from_attribute( - attribute: VertexAttribute, - step_mode: InputStepMode, - ) -> VertexBufferLayout { - VertexBufferLayout { - name: attribute.name.clone(), - stride: attribute.format.get_size(), - step_mode, - attributes: vec![attribute], - } - } -} -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -pub enum InputStepMode { - Vertex = 0, - Instance = 1, -} - -impl Default for InputStepMode { - fn default() -> Self { - InputStepMode::Vertex - } -} - -#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -pub struct VertexAttribute { - pub name: Cow<'static, str>, - pub format: VertexFormat, - pub offset: u64, - pub shader_location: u32, -} - -/// Internally, `bevy_render` uses hashes to identify vertex attribute names. -pub fn get_vertex_attribute_name_id(name: &str) -> u64 { - let mut hasher = bevy_utils::AHasher::default(); - hasher.write(name.as_bytes()); - hasher.finish() -} diff --git a/crates/bevy_render/src/pipeline/vertex_format.rs b/crates/bevy_render/src/pipeline/vertex_format.rs deleted file mode 100644 index cf1159b2a5967..0000000000000 --- a/crates/bevy_render/src/pipeline/vertex_format.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::Color; -use bevy_math::{Mat4, Vec2, Vec3, Vec4}; -use serde::{Deserialize, Serialize}; -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)] -pub enum VertexFormat { - Uint8x2 = 1, - Uint8x4 = 3, - Sint8x2 = 5, - Sint8x4 = 7, - Unorm8x2 = 9, - Unorm8x4 = 11, - Snorm8x2 = 14, - Snorm8x4 = 16, - Uint16x2 = 18, - Uint16x4 = 20, - Sint16x2 = 22, - Sint16x4 = 24, - Unorm16x2 = 26, - Unorm16x4 = 28, - Snorm16x2 = 30, - Snorm16x4 = 32, - Float16x2 = 34, - Float16x4 = 36, - Float32 = 37, - Float32x2 = 38, - Float32x3 = 39, - Float32x4 = 40, - Uint32 = 41, - Uint32x2 = 42, - Uint32x3 = 43, - Uint32x4 = 44, - Sint32 = 45, - Sint32x2 = 46, - Sint32x3 = 47, - Sint32x4 = 48, -} - -impl VertexFormat { - pub fn get_size(&self) -> u64 { - match *self { - VertexFormat::Uint8x2 => 2, - VertexFormat::Uint8x4 => 4, - VertexFormat::Sint8x2 => 2, - VertexFormat::Sint8x4 => 4, - VertexFormat::Unorm8x2 => 2, - VertexFormat::Unorm8x4 => 4, - VertexFormat::Snorm8x2 => 2, - VertexFormat::Snorm8x4 => 4, - VertexFormat::Uint16x2 => 2 * 2, - VertexFormat::Uint16x4 => 2 * 4, - VertexFormat::Sint16x2 => 2 * 2, - VertexFormat::Sint16x4 => 2 * 4, - VertexFormat::Unorm16x2 => 2 * 2, - VertexFormat::Unorm16x4 => 2 * 4, - VertexFormat::Snorm16x2 => 2 * 2, - VertexFormat::Snorm16x4 => 2 * 4, - VertexFormat::Float16x2 => 2 * 2, - VertexFormat::Float16x4 => 2 * 4, - VertexFormat::Float32 => 4, - VertexFormat::Float32x2 => 4 * 2, - VertexFormat::Float32x3 => 4 * 3, - VertexFormat::Float32x4 => 4 * 4, - VertexFormat::Uint32 => 4, - VertexFormat::Uint32x2 => 4 * 2, - VertexFormat::Uint32x3 => 4 * 3, - VertexFormat::Uint32x4 => 4 * 4, - VertexFormat::Sint32 => 4, - VertexFormat::Sint32x2 => 4 * 2, - VertexFormat::Sint32x3 => 4 * 3, - VertexFormat::Sint32x4 => 4 * 4, - } - } -} - -pub trait AsVertexFormats { - fn as_vertex_formats() -> &'static [VertexFormat]; -} - -impl AsVertexFormats for f32 { - fn as_vertex_formats() -> &'static [VertexFormat] { - &[VertexFormat::Float32] - } -} - -impl AsVertexFormats for Vec2 { - fn as_vertex_formats() -> &'static [VertexFormat] { - &[VertexFormat::Float32x2] - } -} - -impl AsVertexFormats for Vec3 { - fn as_vertex_formats() -> &'static [VertexFormat] { - &[VertexFormat::Float32x3] - } -} - -impl AsVertexFormats for Vec4 { - fn as_vertex_formats() -> &'static [VertexFormat] { - &[VertexFormat::Float32x4] - } -} - -impl AsVertexFormats for Mat4 { - fn as_vertex_formats() -> &'static [VertexFormat] { - &[ - VertexFormat::Float32x4, - VertexFormat::Float32x4, - VertexFormat::Float32x4, - VertexFormat::Float32x4, - ] - } -} - -impl AsVertexFormats for Color { - fn as_vertex_formats() -> &'static [VertexFormat] { - &[VertexFormat::Float32x4] - } -} - -impl AsVertexFormats for [f32; 2] { - fn as_vertex_formats() -> &'static [VertexFormat] { - &[VertexFormat::Float32x2] - } -} - -impl AsVertexFormats for [f32; 3] { - fn as_vertex_formats() -> &'static [VertexFormat] { - &[VertexFormat::Float32x3] - } -} - -impl AsVertexFormats for [f32; 4] { - fn as_vertex_formats() -> &'static [VertexFormat] { - &[VertexFormat::Float32x4] - } -} diff --git a/pipelined/bevy_render2/src/primitives/mod.rs b/crates/bevy_render/src/primitives/mod.rs similarity index 97% rename from pipelined/bevy_render2/src/primitives/mod.rs rename to crates/bevy_render/src/primitives/mod.rs index a99f0f2435dfc..deb38d3278670 100644 --- a/pipelined/bevy_render2/src/primitives/mod.rs +++ b/crates/bevy_render/src/primitives/mod.rs @@ -80,8 +80,10 @@ pub struct Plane { pub normal_d: Vec4, } -#[derive(Component, Clone, Copy, Debug, Default)] +#[derive(Component, Clone, Copy, Debug, Default, Reflect)] +#[reflect(Component)] pub struct Frustum { + #[reflect(ignore)] pub planes: [Plane; 6], } diff --git a/pipelined/bevy_render2/src/render_asset.rs b/crates/bevy_render/src/render_asset.rs similarity index 100% rename from pipelined/bevy_render2/src/render_asset.rs rename to crates/bevy_render/src/render_asset.rs diff --git a/pipelined/bevy_render2/src/render_component.rs b/crates/bevy_render/src/render_component.rs similarity index 100% rename from pipelined/bevy_render2/src/render_component.rs rename to crates/bevy_render/src/render_component.rs diff --git a/crates/bevy_render/src/render_graph/base.rs b/crates/bevy_render/src/render_graph/base.rs deleted file mode 100644 index 1e526ba8daa44..0000000000000 --- a/crates/bevy_render/src/render_graph/base.rs +++ /dev/null @@ -1,248 +0,0 @@ -use super::{ - CameraNode, PassNode, RenderGraph, SharedBuffersNode, TextureCopyNode, WindowSwapChainNode, - WindowTextureNode, -}; -use crate::{ - pass::{ - LoadOp, Operations, PassDescriptor, RenderPassColorAttachment, - RenderPassDepthStencilAttachment, TextureAttachment, - }, - texture::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages}, - Color, -}; -use bevy_ecs::{component::Component, reflect::ReflectComponent, world::World}; -use bevy_reflect::Reflect; -use bevy_window::WindowId; - -/// A component that indicates that an entity should be drawn in the "main pass" -#[derive(Component, Clone, Debug, Default, Reflect)] -#[reflect(Component)] -pub struct MainPass; - -#[derive(Debug)] -pub struct Msaa { - pub samples: u32, -} - -impl Default for Msaa { - fn default() -> Self { - Self { samples: 1 } - } -} - -impl Msaa { - pub fn color_attachment( - &self, - attachment: TextureAttachment, - resolve_target: TextureAttachment, - ops: Operations, - ) -> RenderPassColorAttachment { - if self.samples > 1 { - RenderPassColorAttachment { - attachment, - resolve_target: Some(resolve_target), - ops, - } - } else { - RenderPassColorAttachment { - attachment, - resolve_target: None, - ops, - } - } - } -} - -#[derive(Debug)] -pub struct BaseRenderGraphConfig { - pub add_2d_camera: bool, - pub add_3d_camera: bool, - pub add_main_depth_texture: bool, - pub add_main_pass: bool, - pub connect_main_pass_to_swapchain: bool, - pub connect_main_pass_to_main_depth_texture: bool, -} - -pub mod node { - pub const PRIMARY_SWAP_CHAIN: &str = "swapchain"; - pub const CAMERA_3D: &str = "camera_3d"; - pub const CAMERA_2D: &str = "camera_2d"; - pub const TEXTURE_COPY: &str = "texture_copy"; - pub const MAIN_DEPTH_TEXTURE: &str = "main_pass_depth_texture"; - pub const MAIN_SAMPLED_COLOR_ATTACHMENT: &str = "main_pass_sampled_color_attachment"; - pub const MAIN_PASS: &str = "main_pass"; - pub const SHARED_BUFFERS: &str = "shared_buffers"; -} - -pub mod camera { - pub const CAMERA_3D: &str = "Camera3d"; - pub const CAMERA_2D: &str = "Camera2d"; -} - -impl Default for BaseRenderGraphConfig { - fn default() -> Self { - BaseRenderGraphConfig { - add_2d_camera: true, - add_3d_camera: true, - add_main_pass: true, - add_main_depth_texture: true, - connect_main_pass_to_swapchain: true, - connect_main_pass_to_main_depth_texture: true, - } - } -} - -/// The "base render graph" provides a core set of render graph nodes which can be used to build any -/// graph. By itself this graph doesn't do much, but it allows Render plugins to interop with each -/// other by having a common set of nodes. It can be customized using `BaseRenderGraphConfig`. -pub(crate) fn add_base_graph(config: &BaseRenderGraphConfig, world: &mut World) { - let world = world.cell(); - let mut graph = world.get_resource_mut::().unwrap(); - let msaa = world.get_resource::().unwrap(); - - graph.add_node(node::TEXTURE_COPY, TextureCopyNode::default()); - if config.add_3d_camera { - graph.add_system_node(node::CAMERA_3D, CameraNode::new(camera::CAMERA_3D)); - } - - if config.add_2d_camera { - graph.add_system_node(node::CAMERA_2D, CameraNode::new(camera::CAMERA_2D)); - } - - graph.add_node(node::SHARED_BUFFERS, SharedBuffersNode::default()); - if config.add_main_depth_texture { - graph.add_node( - node::MAIN_DEPTH_TEXTURE, - WindowTextureNode::new( - WindowId::primary(), - TextureDescriptor { - size: Extent3d { - depth_or_array_layers: 1, - width: 1, - height: 1, - }, - mip_level_count: 1, - sample_count: msaa.samples, - dimension: TextureDimension::D2, - format: TextureFormat::Depth32Float, /* PERF: vulkan docs recommend using 24 - * bit depth for better performance */ - usage: TextureUsages::OUTPUT_ATTACHMENT, - }, - ), - ); - } - - if config.add_main_pass { - let mut main_pass_node = PassNode::<&MainPass>::new(PassDescriptor { - color_attachments: vec![msaa.color_attachment( - TextureAttachment::Input("color_attachment".to_string()), - TextureAttachment::Input("color_resolve_target".to_string()), - Operations { - load: LoadOp::Clear(Color::rgb(0.1, 0.1, 0.1)), - store: true, - }, - )], - depth_stencil_attachment: Some(RenderPassDepthStencilAttachment { - attachment: TextureAttachment::Input("depth".to_string()), - depth_ops: Some(Operations { - load: LoadOp::Clear(1.0), - store: true, - }), - stencil_ops: None, - }), - sample_count: msaa.samples, - }); - - main_pass_node.use_default_clear_color(0); - - if config.add_3d_camera { - main_pass_node.add_camera(camera::CAMERA_3D); - } - - if config.add_2d_camera { - main_pass_node.add_camera(camera::CAMERA_2D); - } - - graph.add_node(node::MAIN_PASS, main_pass_node); - - graph - .add_node_edge(node::TEXTURE_COPY, node::MAIN_PASS) - .unwrap(); - graph - .add_node_edge(node::SHARED_BUFFERS, node::MAIN_PASS) - .unwrap(); - - if config.add_3d_camera { - graph - .add_node_edge(node::CAMERA_3D, node::MAIN_PASS) - .unwrap(); - } - - if config.add_2d_camera { - graph - .add_node_edge(node::CAMERA_2D, node::MAIN_PASS) - .unwrap(); - } - } - - graph.add_node( - node::PRIMARY_SWAP_CHAIN, - WindowSwapChainNode::new(WindowId::primary()), - ); - - if config.connect_main_pass_to_swapchain { - graph - .add_slot_edge( - node::PRIMARY_SWAP_CHAIN, - WindowSwapChainNode::OUT_TEXTURE, - node::MAIN_PASS, - if msaa.samples > 1 { - "color_resolve_target" - } else { - "color_attachment" - }, - ) - .unwrap(); - } - - if msaa.samples > 1 { - graph.add_node( - node::MAIN_SAMPLED_COLOR_ATTACHMENT, - WindowTextureNode::new( - WindowId::primary(), - TextureDescriptor { - size: Extent3d { - depth_or_array_layers: 1, - width: 1, - height: 1, - }, - mip_level_count: 1, - sample_count: msaa.samples, - dimension: TextureDimension::D2, - format: TextureFormat::default(), - usage: TextureUsages::OUTPUT_ATTACHMENT, - }, - ), - ); - - graph - .add_slot_edge( - node::MAIN_SAMPLED_COLOR_ATTACHMENT, - WindowSwapChainNode::OUT_TEXTURE, - node::MAIN_PASS, - "color_attachment", - ) - .unwrap(); - } - - if config.connect_main_pass_to_main_depth_texture { - graph - .add_slot_edge( - node::MAIN_DEPTH_TEXTURE, - WindowTextureNode::OUT_TEXTURE, - node::MAIN_PASS, - "depth", - ) - .unwrap(); - } -} diff --git a/crates/bevy_render/src/render_graph/command.rs b/crates/bevy_render/src/render_graph/command.rs deleted file mode 100644 index 480cbbf3ba385..0000000000000 --- a/crates/bevy_render/src/render_graph/command.rs +++ /dev/null @@ -1,222 +0,0 @@ -use crate::{ - renderer::{BufferId, RenderContext, TextureId}, - texture::Extent3d, -}; -use parking_lot::Mutex; -use std::sync::Arc; - -#[derive(Clone, Debug)] -pub enum Command { - CopyBufferToBuffer { - source_buffer: BufferId, - source_offset: u64, - destination_buffer: BufferId, - destination_offset: u64, - size: u64, - }, - CopyBufferToTexture { - source_buffer: BufferId, - source_offset: u64, - source_bytes_per_row: u32, - destination_texture: TextureId, - destination_origin: [u32; 3], - destination_mip_level: u32, - size: Extent3d, - }, - CopyTextureToTexture { - source_texture: TextureId, - source_origin: [u32; 3], - source_mip_level: u32, - destination_texture: TextureId, - destination_origin: [u32; 3], - destination_mip_level: u32, - size: Extent3d, - }, - CopyTextureToBuffer { - source_texture: TextureId, - source_origin: [u32; 3], - source_mip_level: u32, - destination_buffer: BufferId, - destination_offset: u64, - destination_bytes_per_row: u32, - size: Extent3d, - }, - // TODO: Frees probably don't need to be queued? - FreeBuffer(BufferId), -} - -#[derive(Debug, Default, Clone)] -pub struct CommandQueue { - // TODO: this shouldn't really need a mutex. it just needs to be shared on whatever thread it's - // scheduled on - queue: Arc>>, -} - -impl CommandQueue { - fn push(&mut self, command: Command) { - self.queue.lock().push(command); - } - - pub fn copy_buffer_to_buffer( - &mut self, - source_buffer: BufferId, - source_offset: u64, - destination_buffer: BufferId, - destination_offset: u64, - size: u64, - ) { - self.push(Command::CopyBufferToBuffer { - source_buffer, - source_offset, - destination_buffer, - destination_offset, - size, - }); - } - - #[allow(clippy::too_many_arguments)] - pub fn copy_buffer_to_texture( - &mut self, - source_buffer: BufferId, - source_offset: u64, - source_bytes_per_row: u32, - destination_texture: TextureId, - destination_origin: [u32; 3], - destination_mip_level: u32, - size: Extent3d, - ) { - self.push(Command::CopyBufferToTexture { - source_buffer, - source_offset, - source_bytes_per_row, - destination_texture, - destination_origin, - destination_mip_level, - size, - }); - } - - #[allow(clippy::too_many_arguments)] - pub fn copy_texture_to_buffer( - &mut self, - source_texture: TextureId, - source_origin: [u32; 3], - source_mip_level: u32, - destination_buffer: BufferId, - destination_offset: u64, - destination_bytes_per_row: u32, - size: Extent3d, - ) { - self.push(Command::CopyTextureToBuffer { - source_texture, - source_origin, - source_mip_level, - destination_buffer, - destination_offset, - destination_bytes_per_row, - size, - }) - } - - #[allow(clippy::too_many_arguments)] - pub fn copy_texture_to_texture( - &mut self, - source_texture: TextureId, - source_origin: [u32; 3], - source_mip_level: u32, - destination_texture: TextureId, - destination_origin: [u32; 3], - destination_mip_level: u32, - size: Extent3d, - ) { - self.push(Command::CopyTextureToTexture { - source_texture, - source_origin, - source_mip_level, - destination_texture, - destination_origin, - destination_mip_level, - size, - }) - } - - pub fn free_buffer(&mut self, buffer: BufferId) { - self.push(Command::FreeBuffer(buffer)); - } - - pub fn clear(&mut self) { - self.queue.lock().clear(); - } - - pub fn execute(&self, render_context: &mut dyn RenderContext) { - for command in self.queue.lock().drain(..) { - match command { - Command::CopyBufferToBuffer { - source_buffer, - source_offset, - destination_buffer, - destination_offset, - size, - } => render_context.copy_buffer_to_buffer( - source_buffer, - source_offset, - destination_buffer, - destination_offset, - size, - ), - Command::CopyBufferToTexture { - source_buffer, - source_offset, - source_bytes_per_row, - destination_texture, - destination_origin, - destination_mip_level, - size, - } => render_context.copy_buffer_to_texture( - source_buffer, - source_offset, - source_bytes_per_row, - destination_texture, - destination_origin, - destination_mip_level, - size, - ), - Command::CopyTextureToTexture { - source_texture, - source_origin, - source_mip_level, - destination_texture, - destination_origin, - destination_mip_level, - size, - } => render_context.copy_texture_to_texture( - source_texture, - source_origin, - source_mip_level, - destination_texture, - destination_origin, - destination_mip_level, - size, - ), - Command::CopyTextureToBuffer { - source_texture, - source_origin, - source_mip_level, - destination_buffer, - destination_offset, - destination_bytes_per_row, - size, - } => render_context.copy_texture_to_buffer( - source_texture, - source_origin, - source_mip_level, - destination_buffer, - destination_offset, - destination_bytes_per_row, - size, - ), - Command::FreeBuffer(buffer) => render_context.resources().remove_buffer(buffer), - } - } - } -} diff --git a/pipelined/bevy_render2/src/render_graph/context.rs b/crates/bevy_render/src/render_graph/context.rs similarity index 100% rename from pipelined/bevy_render2/src/render_graph/context.rs rename to crates/bevy_render/src/render_graph/context.rs diff --git a/crates/bevy_render/src/render_graph/edge.rs b/crates/bevy_render/src/render_graph/edge.rs index e73679759871c..fce406f53d214 100644 --- a/crates/bevy_render/src/render_graph/edge.rs +++ b/crates/bevy_render/src/render_graph/edge.rs @@ -1,13 +1,30 @@ use super::NodeId; +/// An edge, which connects two [`Nodes`](super::Node) in +/// a [`RenderGraph`](crate::render_graph::RenderGraph). +/// +/// They are used to describe the ordering (which node has to run first) +/// and may be of two kinds: [`NodeEdge`](Self::NodeEdge) and [`SlotEdge`](Self::SlotEdge). +/// +/// Edges are added via the render_graph::add_node_edge(output_node, input_node) and the +/// render_graph::add_slot_edge(output_node, output_slot, input_node, input_slot) methode. +/// +/// The former simply states that the `output_node` has to be run before the `input_node`, +/// while the later connects an output slot of the `output_node` +/// with an input slot of the `input_node` to pass additional data along. +/// For more information see [`SlotType`](super::SlotType). #[derive(Clone, Debug, Eq, PartialEq)] pub enum Edge { + /// An edge describing to ordering of both nodes (`output_node` before `input_node`) + /// and connecting the output slot at the `output_index` of the output_node + /// with the slot at the `input_index` of the `input_node`. SlotEdge { input_node: NodeId, input_index: usize, output_node: NodeId, output_index: usize, }, + /// An edge describing to ordering of both nodes (`output_node` before `input_node`). NodeEdge { input_node: NodeId, output_node: NodeId, @@ -15,6 +32,7 @@ pub enum Edge { } impl Edge { + /// Returns the id of the 'input_node'. pub fn get_input_node(&self) -> NodeId { match self { Edge::SlotEdge { input_node, .. } => *input_node, @@ -22,6 +40,7 @@ impl Edge { } } + /// Returns the id of the 'output_node'. pub fn get_output_node(&self) -> NodeId { match self { Edge::SlotEdge { output_node, .. } => *output_node, diff --git a/crates/bevy_render/src/render_graph/graph.rs b/crates/bevy_render/src/render_graph/graph.rs index b820877818110..8fe398f190cb2 100644 --- a/crates/bevy_render/src/render_graph/graph.rs +++ b/crates/bevy_render/src/render_graph/graph.rs @@ -1,32 +1,93 @@ -use super::{Edge, Node, NodeId, NodeLabel, NodeState, RenderGraphError, SlotLabel, SystemNode}; -use bevy_ecs::{ - schedule::{Schedule, StageLabel, SystemStage}, - world::World, +use crate::{ + render_graph::{ + Edge, Node, NodeId, NodeLabel, NodeRunError, NodeState, RenderGraphContext, + RenderGraphError, SlotInfo, SlotLabel, + }, + renderer::RenderContext, }; +use bevy_ecs::prelude::World; use bevy_utils::HashMap; use std::{borrow::Cow, fmt::Debug}; + +/// The render graph configures the modular, parallel and re-usable render logic. +/// It is a retained and stateless (nodes itself my have their internal state) structure, +/// which can not be modified while it is executed by the graph runner. +/// +/// The `RenderGraphRunner` is responsible for executing the entire graph each frame. +/// +/// It consists of three main components: [`Nodes`](Node), [`Edges`](Edge) +/// and [`Slots`](super::SlotType). +/// +/// Nodes are responsible for generating draw calls and operating on input and output slots. +/// Edges specify the order of execution for nodes and connect input and output slots together. +/// Slots describe the render resources created or used by the nodes. +/// +/// Additionally a render graph can contain multiple sub graphs, which are run by the +/// corresponding nodes. Every render graph can have it’s own optional input node. +/// +/// ## Example +/// Here is a simple render graph example with two nodes connected by a node edge. +/// ``` +/// # use bevy_app::prelude::*; +/// # use bevy_ecs::prelude::World; +/// # use bevy_render::render_graph::{RenderGraph, Node, RenderGraphContext, NodeRunError}; +/// # use bevy_render::renderer::RenderContext; +/// # +/// # struct MyNode; +/// # +/// # impl Node for MyNode { +/// # fn run(&self, graph: &mut RenderGraphContext, render_context: &mut RenderContext, world: &World) -> Result<(), NodeRunError> { +/// # unimplemented!() +/// # } +/// # } +/// # +/// let mut graph = RenderGraph::default(); +/// graph.add_node("input_node", MyNode); +/// graph.add_node("output_node", MyNode); +/// graph.add_node_edge("output_node", "input_node").unwrap(); +/// ``` +#[derive(Default)] pub struct RenderGraph { nodes: HashMap, node_names: HashMap, NodeId>, - system_node_schedule: Option, + sub_graphs: HashMap, RenderGraph>, + input_node: Option, } -#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] -struct RenderGraphUpdate; - -impl Default for RenderGraph { - fn default() -> Self { - let mut schedule = Schedule::default(); - schedule.add_stage(RenderGraphUpdate, SystemStage::parallel()); - Self { - nodes: Default::default(), - node_names: Default::default(), - system_node_schedule: Some(schedule), +impl RenderGraph { + /// The name of the [`GraphInputNode`] of this graph. Used to connect other nodes to it. + pub const INPUT_NODE_NAME: &'static str = "GraphInputNode"; + + /// Updates all nodes and sub graphs of the render graph. Should be called before executing it. + pub fn update(&mut self, world: &mut World) { + for node in self.nodes.values_mut() { + node.node.update(world); + } + + for sub_graph in self.sub_graphs.values_mut() { + sub_graph.update(world); } } -} -impl RenderGraph { + /// Creates an [`GraphInputNode`] with the specified slots if not already present. + pub fn set_input(&mut self, inputs: Vec) -> NodeId { + if self.input_node.is_some() { + panic!("Graph already has an input node"); + } + + let id = self.add_node("GraphInputNode", GraphInputNode { inputs }); + self.input_node = Some(id); + id + } + + /// Returns the [`NodeState`] of the input node of this graph.. + #[inline] + pub fn input_node(&self) -> Option<&NodeState> { + self.input_node.and_then(|id| self.get_node_state(id).ok()) + } + + /// Adds the `node` with the `name` to the graph. + /// If the name is already present replaces it instead. pub fn add_node(&mut self, name: impl Into>, node: T) -> NodeId where T: Node, @@ -40,18 +101,7 @@ impl RenderGraph { id } - pub fn add_system_node(&mut self, name: impl Into>, node: T) -> NodeId - where - T: SystemNode + 'static, - { - let schedule = self.system_node_schedule.as_mut().unwrap(); - let stage = schedule - .get_stage_mut::(&RenderGraphUpdate) - .unwrap(); - stage.add_system(node.get_system()); - self.add_node(name, node) - } - + /// Retrieves the [`NodeState`] referenced by the `label`. pub fn get_node_state( &self, label: impl Into, @@ -63,6 +113,7 @@ impl RenderGraph { .ok_or(RenderGraphError::InvalidNode(label)) } + /// Retrieves the [`NodeState`] referenced by the `label` mutably. pub fn get_node_state_mut( &mut self, label: impl Into, @@ -74,6 +125,7 @@ impl RenderGraph { .ok_or(RenderGraphError::InvalidNode(label)) } + /// Retrieves the [`NodeId`] referenced by the `label`. pub fn get_node_id(&self, label: impl Into) -> Result { let label = label.into(); match label { @@ -86,6 +138,7 @@ impl RenderGraph { } } + /// Retrieves the [`Node`] referenced by the `label`. pub fn get_node(&self, label: impl Into) -> Result<&T, RenderGraphError> where T: Node, @@ -93,6 +146,7 @@ impl RenderGraph { self.get_node_state(label).and_then(|n| n.node()) } + /// Retrieves the [`Node`] referenced by the `label` mutably. pub fn get_node_mut( &mut self, label: impl Into, @@ -103,6 +157,8 @@ impl RenderGraph { self.get_node_state_mut(label).and_then(|n| n.node_mut()) } + /// Adds the [`Edge::SlotEdge`] to the graph. This guarantees that the `output_node` + /// is run before the `input_node` and also connects the `output_slot` to the `input_slot`. pub fn add_slot_edge( &mut self, output_node: impl Into, @@ -110,17 +166,21 @@ impl RenderGraph { input_node: impl Into, input_slot: impl Into, ) -> Result<(), RenderGraphError> { + let output_slot = output_slot.into(); + let input_slot = input_slot.into(); let output_node_id = self.get_node_id(output_node)?; let input_node_id = self.get_node_id(input_node)?; let output_index = self .get_node_state(output_node_id)? .output_slots - .get_slot_index(output_slot)?; + .get_slot_index(output_slot.clone()) + .ok_or(RenderGraphError::InvalidOutputNodeSlot(output_slot))?; let input_index = self .get_node_state(input_node_id)? .input_slots - .get_slot_index(input_slot)?; + .get_slot_index(input_slot.clone()) + .ok_or(RenderGraphError::InvalidInputNodeSlot(input_slot))?; let edge = Edge::SlotEdge { output_node: output_node_id, @@ -141,6 +201,8 @@ impl RenderGraph { Ok(()) } + /// Adds the [`Edge::NodeEdge`] to the graph. This guarantees that the `output_node` + /// is run before the `input_node`. pub fn add_node_edge( &mut self, output_node: impl Into, @@ -166,6 +228,8 @@ impl RenderGraph { Ok(()) } + /// Verifies that the edge is not already existing and + /// checks that slot edges are connected correctly. pub fn validate_edge(&mut self, edge: &Edge) -> Result<(), RenderGraphError> { if self.has_edge(edge) { return Err(RenderGraphError::EdgeAlreadyExists(edge.clone())); @@ -181,8 +245,15 @@ impl RenderGraph { let output_node_state = self.get_node_state(output_node)?; let input_node_state = self.get_node_state(input_node)?; - let output_slot = output_node_state.output_slots.get_slot(output_index)?; - let input_slot = input_node_state.input_slots.get_slot(input_index)?; + let output_slot = output_node_state + .output_slots + .get_slot(output_index) + .ok_or(RenderGraphError::InvalidOutputNodeSlot(SlotLabel::Index( + output_index, + )))?; + let input_slot = input_node_state.input_slots.get_slot(input_index).ok_or( + RenderGraphError::InvalidInputNodeSlot(SlotLabel::Index(input_index)), + )?; if let Some(Edge::SlotEdge { output_node: current_output_node, @@ -205,7 +276,7 @@ impl RenderGraph { }); } - if output_slot.info.resource_type != input_slot.info.resource_type { + if output_slot.slot_type != input_slot.slot_type { return Err(RenderGraphError::MismatchedNodeSlots { output_node, output_slot: output_index, @@ -220,6 +291,7 @@ impl RenderGraph { Ok(()) } + /// Checks whether the `edge` already exists in the graph. pub fn has_edge(&self, edge: &Edge) -> bool { let output_node_state = self.get_node_state(edge.get_output_node()); let input_node_state = self.get_node_state(edge.get_input_node()); @@ -236,22 +308,32 @@ impl RenderGraph { false } - pub fn take_schedule(&mut self) -> Option { - self.system_node_schedule.take() - } - - pub fn set_schedule(&mut self, schedule: Schedule) { - self.system_node_schedule = Some(schedule); - } - + /// Returns an iterator over the [`NodeStates`](NodeState). pub fn iter_nodes(&self) -> impl Iterator { self.nodes.values() } + /// Returns an iterator over the [`NodeStates`](NodeState), that allows modifying each value. pub fn iter_nodes_mut(&mut self) -> impl Iterator { self.nodes.values_mut() } + /// Returns an iterator over the sub graphs. + pub fn iter_sub_graphs(&self) -> impl Iterator { + self.sub_graphs + .iter() + .map(|(name, graph)| (name.as_ref(), graph)) + } + + /// Returns an iterator over the sub graphs, that allows modifying each value. + pub fn iter_sub_graphs_mut(&mut self) -> impl Iterator { + self.sub_graphs + .iter_mut() + .map(|(name, graph)| (name.as_ref(), graph)) + } + + /// Returns an iterator over a tuple of the input edges and the corresponding output nodes + /// for the node referenced by the label. pub fn iter_node_inputs( &self, label: impl Into, @@ -267,6 +349,8 @@ impl RenderGraph { })) } + /// Returns an iterator over a tuple of the ouput edges and the corresponding input nodes + /// for the node referenced by the label. pub fn iter_node_outputs( &self, label: impl Into, @@ -280,10 +364,20 @@ impl RenderGraph { .map(move |(edge, input_node_id)| (edge, self.get_node_state(input_node_id).unwrap()))) } - pub fn prepare(&mut self, world: &mut World) { - for node in self.nodes.values_mut() { - node.node.prepare(world); - } + /// Adds the `sub_graph` with the `name` to the graph. + /// If the name is already present replaces it instead. + pub fn add_sub_graph(&mut self, name: impl Into>, sub_graph: RenderGraph) { + self.sub_graphs.insert(name.into(), sub_graph); + } + + /// Retrieves the sub graph corresponding to the `name`. + pub fn get_sub_graph(&self, name: impl AsRef) -> Option<&RenderGraph> { + self.sub_graphs.get(name.as_ref()) + } + + /// Retrieves the sub graph corresponding to the `name` mutably. + pub fn get_sub_graph_mut(&mut self, name: impl AsRef) -> Option<&mut RenderGraph> { + self.sub_graphs.get_mut(name.as_ref()) } } @@ -299,57 +393,82 @@ impl Debug for RenderGraph { } } +/// A [`Node`] which acts as an entry point for a [`RenderGraph`] with custom inputs. +/// It has the same input and output slots and simply copies them over when run. +pub struct GraphInputNode { + inputs: Vec, +} + +impl Node for GraphInputNode { + fn input(&self) -> Vec { + self.inputs.clone() + } + + fn output(&self) -> Vec { + self.inputs.clone() + } + + fn run( + &self, + graph: &mut RenderGraphContext, + _render_context: &mut RenderContext, + _world: &World, + ) -> Result<(), NodeRunError> { + for i in 0..graph.inputs().len() { + let input = graph.inputs()[i].clone(); + graph.set_output(i, input)?; + } + Ok(()) + } +} + #[cfg(test)] mod tests { - use super::RenderGraph; use crate::{ - render_graph::{Edge, Node, NodeId, RenderGraphError, ResourceSlotInfo, ResourceSlots}, - renderer::{RenderContext, RenderResourceType}, + render_graph::{ + Edge, Node, NodeId, NodeRunError, RenderGraph, RenderGraphContext, RenderGraphError, + SlotInfo, SlotType, + }, + renderer::RenderContext, }; use bevy_ecs::world::World; use bevy_utils::HashSet; #[derive(Debug)] struct TestNode { - inputs: Vec, - outputs: Vec, + inputs: Vec, + outputs: Vec, } impl TestNode { pub fn new(inputs: usize, outputs: usize) -> Self { TestNode { inputs: (0..inputs) - .map(|i| ResourceSlotInfo { - name: format!("in_{}", i).into(), - resource_type: RenderResourceType::Texture, - }) + .map(|i| SlotInfo::new(format!("in_{}", i), SlotType::TextureView)) .collect(), outputs: (0..outputs) - .map(|i| ResourceSlotInfo { - name: format!("out_{}", i).into(), - resource_type: RenderResourceType::Texture, - }) + .map(|i| SlotInfo::new(format!("out_{}", i), SlotType::TextureView)) .collect(), } } } impl Node for TestNode { - fn input(&self) -> &[ResourceSlotInfo] { - &self.inputs + fn input(&self) -> Vec { + self.inputs.clone() } - fn output(&self) -> &[ResourceSlotInfo] { - &self.outputs + fn output(&self) -> Vec { + self.outputs.clone() } - fn update( - &mut self, + fn run( + &self, + _: &mut RenderGraphContext, + _: &mut RenderContext, _: &World, - _: &mut dyn RenderContext, - _: &ResourceSlots, - _: &mut ResourceSlots, - ) { + ) -> Result<(), NodeRunError> { + Ok(()) } } @@ -416,13 +535,13 @@ mod tests { } impl Node for MyNode { - fn update( - &mut self, + fn run( + &self, + _: &mut RenderGraphContext, + _: &mut RenderContext, _: &World, - _: &mut dyn RenderContext, - _: &ResourceSlots, - _: &mut ResourceSlots, - ) { + ) -> Result<(), NodeRunError> { + Ok(()) } } diff --git a/crates/bevy_render/src/render_graph/mod.rs b/crates/bevy_render/src/render_graph/mod.rs index a8c249e697c23..0d204a5162dc1 100644 --- a/crates/bevy_render/src/render_graph/mod.rs +++ b/crates/bevy_render/src/render_graph/mod.rs @@ -1,21 +1,14 @@ -pub mod base; -mod command; +mod context; mod edge; mod graph; mod node; mod node_slot; -mod nodes; -mod schedule; -mod system; -pub use command::*; +pub use context::*; pub use edge::*; pub use graph::*; pub use node::*; pub use node_slot::*; -pub use nodes::*; -pub use schedule::*; -pub use system::*; use thiserror::Error; @@ -23,8 +16,10 @@ use thiserror::Error; pub enum RenderGraphError { #[error("node does not exist")] InvalidNode(NodeLabel), - #[error("node slot does not exist")] - InvalidNodeSlot(SlotLabel), + #[error("output node slot does not exist")] + InvalidOutputNodeSlot(SlotLabel), + #[error("input node slot does not exist")] + InvalidInputNodeSlot(SlotLabel), #[error("node does not match the given type")] WrongNodeType, #[error("attempted to connect a node output slot to an incompatible input node slot")] diff --git a/crates/bevy_render/src/render_graph/node.rs b/crates/bevy_render/src/render_graph/node.rs index c7d84079fa36d..0562cef87bb0a 100644 --- a/crates/bevy_render/src/render_graph/node.rs +++ b/crates/bevy_render/src/render_graph/node.rs @@ -1,10 +1,20 @@ -use super::{Edge, RenderGraphError, ResourceSlotInfo, ResourceSlots}; -use crate::renderer::RenderContext; -use bevy_ecs::{system::BoxedSystem, world::World}; +use crate::{ + render_graph::{ + Edge, InputSlotError, OutputSlotError, RenderGraphContext, RenderGraphError, + RunSubGraphError, SlotInfo, SlotInfos, + }, + renderer::RenderContext, +}; +use bevy_ecs::world::World; use bevy_utils::Uuid; use downcast_rs::{impl_downcast, Downcast}; use std::{borrow::Cow, fmt::Debug}; +use thiserror::Error; +/// A [`Node`] identifier. +/// It automatically generates its own random uuid. +/// +/// This id is used to reference the node internally (edges, etc). #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub struct NodeId(Uuid); @@ -19,36 +29,58 @@ impl NodeId { } } +/// A render node that can be added to a [`RenderGraph`](super::RenderGraph). +/// +/// Nodes are the fundamental part of the graph and used to extend its functionality, by +/// generating draw calls and/or running subgraphs. +/// They are added via the render_graph::add_node(my_node) methode. +/// +/// To determine their position in the graph and ensure that all required dependencies (inputs) +/// are already executed, [`Edges`](Edge) are used. +/// +/// A node can produce outputs used as dependencies by other nodes. +/// Those inputs and outputs are called slots and are the default way of passing render data +/// inside the graph. For more information see [`SlotType`](super::SlotType). pub trait Node: Downcast + Send + Sync + 'static { - fn input(&self) -> &[ResourceSlotInfo] { - &[] + /// Specifies the required input slots for this node. + /// They will then be available during the run method inside the [`RenderContext`]. + fn input(&self) -> Vec { + Vec::new() } - fn output(&self) -> &[ResourceSlotInfo] { - &[] + /// Specifies the produced output slots for this node. + /// They can then be passed one inside [`RenderContext`] during the run method. + fn output(&self) -> Vec { + Vec::new() } - /// Prepare the graph node with unique world access. This runs once per graph run before - /// [Node::update] is called. - fn prepare(&mut self, _world: &mut World) {} + /// Updates internal node state using the current render [`World`] prior to the run method. + fn update(&mut self, _world: &mut World) {} - /// Run the graph node logic. This runs once per graph run after [Node::prepare] has been called - /// on all nodes. - fn update( - &mut self, + /// Runs the graph node logic, issues draw calls, updates the output slots and + /// optionally queues up subgraphs for execution. The graph data, input and output values are + /// passed via the [`RenderGraphContext`]. + fn run( + &self, + graph: &mut RenderGraphContext, + render_context: &mut RenderContext, world: &World, - render_context: &mut dyn RenderContext, - input: &ResourceSlots, - output: &mut ResourceSlots, - ); + ) -> Result<(), NodeRunError>; } impl_downcast!(Node); -pub trait SystemNode: Node { - fn get_system(&self) -> BoxedSystem; +#[derive(Error, Debug, Eq, PartialEq)] +pub enum NodeRunError { + #[error("encountered an input slot error")] + InputSlotError(#[from] InputSlotError), + #[error("encountered an output slot error")] + OutputSlotError(#[from] OutputSlotError), + #[error("encountered an error when running a sub-graph")] + RunSubGraphError(#[from] RunSubGraphError), } +/// A collection of input and output [`Edges`](Edge) for a [`Node`]. #[derive(Debug)] pub struct Edges { pub id: NodeId, @@ -57,6 +89,7 @@ pub struct Edges { } impl Edges { + /// Adds an edge to the `input_edges` if it does not already exist. pub(crate) fn add_input_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> { if self.has_input_edge(&edge) { return Err(RenderGraphError::EdgeAlreadyExists(edge)); @@ -65,6 +98,7 @@ impl Edges { Ok(()) } + /// Adds an edge to the `output_edges` if it does not already exist. pub(crate) fn add_output_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> { if self.has_output_edge(&edge) { return Err(RenderGraphError::EdgeAlreadyExists(edge)); @@ -73,14 +107,18 @@ impl Edges { Ok(()) } + /// Checks whether the input edge already exists. pub fn has_input_edge(&self, edge: &Edge) -> bool { self.input_edges.contains(edge) } + /// Checks whether the output edge already exists. pub fn has_output_edge(&self, edge: &Edge) -> bool { self.output_edges.contains(edge) } + /// Searches the `input_edges` for a [`Edge::SlotEdge`], + /// which `input_index` matches the `index`; pub fn get_input_slot_edge(&self, index: usize) -> Result<&Edge, RenderGraphError> { self.input_edges .iter() @@ -97,6 +135,8 @@ impl Edges { }) } + /// Searches the `output_edges` for a [`Edge::SlotEdge`], + /// which `output_index` matches the `index`; pub fn get_output_slot_edge(&self, index: usize) -> Result<&Edge, RenderGraphError> { self.output_edges .iter() @@ -114,13 +154,18 @@ impl Edges { } } +/// The internal representation of a [`Node`], with all data required +/// by the [`RenderGraph`](super::RenderGraph). +/// +/// The `input_slots` and `output_slots` are provided by the `node`. pub struct NodeState { pub id: NodeId, pub name: Option>, + /// The name of the type that implements [`Node`]. pub type_name: &'static str, pub node: Box, - pub input_slots: ResourceSlots, - pub output_slots: ResourceSlots, + pub input_slots: SlotInfos, + pub output_slots: SlotInfos, pub edges: Edges, } @@ -131,6 +176,8 @@ impl Debug for NodeState { } impl NodeState { + /// Creates an [`NodeState`] without edges, but the `input_slots` and `output_slots` + /// are provided by the `node`. pub fn new(id: NodeId, node: T) -> Self where T: Node, @@ -138,8 +185,8 @@ impl NodeState { NodeState { id, name: None, - input_slots: ResourceSlots::from(node.input()), - output_slots: ResourceSlots::from(node.output()), + input_slots: node.input().into(), + output_slots: node.output().into(), node: Box::new(node), type_name: std::any::type_name::(), edges: Edges { @@ -150,6 +197,7 @@ impl NodeState { } } + /// Retrieves the [`Node`]. pub fn node(&self) -> Result<&T, RenderGraphError> where T: Node, @@ -159,6 +207,7 @@ impl NodeState { .ok_or(RenderGraphError::WrongNodeType) } + /// Retrieves the [`Node`] mutably. pub fn node_mut(&mut self) -> Result<&mut T, RenderGraphError> where T: Node, @@ -168,23 +217,27 @@ impl NodeState { .ok_or(RenderGraphError::WrongNodeType) } - pub fn validate_output_slots(&self) -> Result<(), RenderGraphError> { - for i in 0..self.output_slots.len() { - self.edges.get_output_slot_edge(i)?; + /// Validates that each input slot corresponds to an input edge. + pub fn validate_input_slots(&self) -> Result<(), RenderGraphError> { + for i in 0..self.input_slots.len() { + self.edges.get_input_slot_edge(i)?; } Ok(()) } - pub fn validate_input_slots(&self) -> Result<(), RenderGraphError> { - for i in 0..self.input_slots.len() { - self.edges.get_input_slot_edge(i)?; + /// Validates that each output slot corresponds to an output edge. + pub fn validate_output_slots(&self) -> Result<(), RenderGraphError> { + for i in 0..self.output_slots.len() { + self.edges.get_output_slot_edge(i)?; } Ok(()) } } +/// A [`NodeLabel`] is used to reference a [`NodeState`] by either its name or [`NodeId`] +/// inside the [`RenderGraph`](super::RenderGraph). #[derive(Debug, Clone, Eq, PartialEq)] pub enum NodeLabel { Id(NodeId), @@ -214,3 +267,19 @@ impl From for NodeLabel { NodeLabel::Id(value) } } + +/// A [`Node`] without any inputs, outputs and subgraphs, which does nothing when run. +/// Used (as a label) to bundle multiple dependencies into one inside +/// the [`RenderGraph`](super::RenderGraph). +pub struct EmptyNode; + +impl Node for EmptyNode { + fn run( + &self, + _graph: &mut RenderGraphContext, + _render_context: &mut RenderContext, + _world: &World, + ) -> Result<(), NodeRunError> { + Ok(()) + } +} diff --git a/crates/bevy_render/src/render_graph/node_slot.rs b/crates/bevy_render/src/render_graph/node_slot.rs index e25fc95e6b525..0789d62df0b82 100644 --- a/crates/bevy_render/src/render_graph/node_slot.rs +++ b/crates/bevy_render/src/render_graph/node_slot.rs @@ -1,18 +1,81 @@ -use super::RenderGraphError; -use crate::renderer::{RenderResourceId, RenderResourceType}; +use bevy_ecs::entity::Entity; use std::borrow::Cow; +use crate::render_resource::{Buffer, Sampler, TextureView}; + +/// A value passed between render [`Nodes`](super::Node). +/// Corresponds to the [SlotType] specified in the [`RenderGraph`](super::RenderGraph). +/// +/// Slots can have four different types of values: +/// [`Buffer`], [`TextureView`], [`Sampler`] and [`Entity`]. +/// +/// These values do not contain the actual render data, but only the ids to retrieve them. #[derive(Debug, Clone)] -pub struct ResourceSlot { - pub resource: Option, - pub info: ResourceSlotInfo, +pub enum SlotValue { + /// A GPU-accessible [`Buffer`]. + Buffer(Buffer), + /// A [`TextureView`] describes a texture used in a pipeline. + TextureView(TextureView), + /// A texture [`Sampler`] defines how a pipeline will sample from a [`TextureView`]. + Sampler(Sampler), + /// An entity from the ECS. + Entity(Entity), +} + +impl SlotValue { + /// Returns the [`SlotType`] of this value. + pub fn slot_type(&self) -> SlotType { + match self { + SlotValue::Buffer(_) => SlotType::Buffer, + SlotValue::TextureView(_) => SlotType::TextureView, + SlotValue::Sampler(_) => SlotType::Sampler, + SlotValue::Entity(_) => SlotType::Entity, + } + } +} + +impl From for SlotValue { + fn from(value: Buffer) -> Self { + SlotValue::Buffer(value) + } +} + +impl From for SlotValue { + fn from(value: TextureView) -> Self { + SlotValue::TextureView(value) + } +} + +impl From for SlotValue { + fn from(value: Sampler) -> Self { + SlotValue::Sampler(value) + } +} + +impl From for SlotValue { + fn from(value: Entity) -> Self { + SlotValue::Entity(value) + } } -#[derive(Default, Debug, Clone)] -pub struct ResourceSlots { - slots: Vec, +/// Describes the render resources created (output) or used (input) by +/// the render [`Nodes`](super::Node). +/// +/// This should not be confused with [`SlotValue`], which actually contains the passed data. +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum SlotType { + /// A GPU-accessible [`Buffer`]. + Buffer, + /// A [`TextureView`] describes a texture used in a pipeline. + TextureView, + /// A texture [`Sampler`] defines how a pipeline will sample from a [`TextureView`]. + Sampler, + /// An entity from the ECS. + Entity, } +/// A [`SlotLabel`] is used to reference a slot by either its name or index +/// inside the [`RenderGraph`](super::RenderGraph). #[derive(Debug, Clone, Eq, PartialEq)] pub enum SlotLabel { Index(usize), @@ -37,104 +100,92 @@ impl From<&'static str> for SlotLabel { } } +impl From> for SlotLabel { + fn from(value: Cow<'static, str>) -> Self { + SlotLabel::Name(value.clone()) + } +} + impl From for SlotLabel { fn from(value: usize) -> Self { SlotLabel::Index(value) } } -impl ResourceSlots { - pub fn set(&mut self, label: impl Into, resource: RenderResourceId) { - let mut slot = self.get_slot_mut(label).unwrap(); - slot.resource = Some(resource); +/// The internal representation of a slot, which specifies its [`SlotType`] and name. +#[derive(Clone, Debug)] +pub struct SlotInfo { + pub name: Cow<'static, str>, + pub slot_type: SlotType, +} + +impl SlotInfo { + pub fn new(name: impl Into>, slot_type: SlotType) -> Self { + SlotInfo { + name: name.into(), + slot_type, + } } +} + +/// A collection of input or output [`SlotInfos`](SlotInfo) for +/// a [`NodeState`](super::NodeState). +#[derive(Default, Debug)] +pub struct SlotInfos { + slots: Vec, +} - pub fn get(&self, label: impl Into) -> Option { - let slot = self.get_slot(label).unwrap(); - slot.resource.clone() +impl> From for SlotInfos { + fn from(slots: T) -> Self { + SlotInfos { + slots: slots.into_iter().collect(), + } } +} - pub fn get_slot(&self, label: impl Into) -> Result<&ResourceSlot, RenderGraphError> { +impl SlotInfos { + /// Returns the count of slots. + #[inline] + pub fn len(&self) -> usize { + self.slots.len() + } + + /// Returns true if there are no slots. + #[inline] + pub fn is_empty(&self) -> bool { + self.slots.is_empty() + } + + /// Retrieves the [`SlotInfo`] for the provided label. + pub fn get_slot(&self, label: impl Into) -> Option<&SlotInfo> { let label = label.into(); let index = self.get_slot_index(&label)?; - self.slots - .get(index) - .ok_or(RenderGraphError::InvalidNodeSlot(label)) + self.slots.get(index) } - pub fn get_slot_mut( - &mut self, - label: impl Into, - ) -> Result<&mut ResourceSlot, RenderGraphError> { + /// Retrieves the [`SlotInfo`] for the provided label mutably. + pub fn get_slot_mut(&mut self, label: impl Into) -> Option<&mut SlotInfo> { let label = label.into(); let index = self.get_slot_index(&label)?; - self.slots - .get_mut(index) - .ok_or(RenderGraphError::InvalidNodeSlot(label)) + self.slots.get_mut(index) } - pub fn get_slot_index(&self, label: impl Into) -> Result { + /// Retrieves the index (inside input or output slots) of the slot for the provided label. + pub fn get_slot_index(&self, label: impl Into) -> Option { let label = label.into(); match label { - SlotLabel::Index(index) => Ok(index), + SlotLabel::Index(index) => Some(index), SlotLabel::Name(ref name) => self .slots .iter() .enumerate() - .find(|(_i, s)| s.info.name == *name) - .map(|(i, _s)| i) - .ok_or(RenderGraphError::InvalidNodeSlot(label)), + .find(|(_i, s)| s.name == *name) + .map(|(i, _s)| i), } } - pub fn iter(&self) -> impl Iterator { + /// Returns an iterator over the slot infos. + pub fn iter(&self) -> impl Iterator { self.slots.iter() } - - pub fn iter_mut(&mut self) -> impl Iterator { - self.slots.iter_mut() - } - - pub fn len(&self) -> usize { - self.slots.len() - } - - pub fn is_empty(&self) -> bool { - self.slots.is_empty() - } -} - -impl From<&ResourceSlotInfo> for ResourceSlot { - fn from(slot: &ResourceSlotInfo) -> Self { - ResourceSlot { - resource: None, - info: slot.clone(), - } - } -} - -impl From<&[ResourceSlotInfo]> for ResourceSlots { - fn from(slots: &[ResourceSlotInfo]) -> Self { - ResourceSlots { - slots: slots - .iter() - .map(|s| s.into()) - .collect::>(), - } - } -} - -#[derive(Clone, Debug)] -pub struct ResourceSlotInfo { - pub name: Cow<'static, str>, - pub resource_type: RenderResourceType, -} - -impl ResourceSlotInfo { - pub fn new(name: impl Into>, resource_type: RenderResourceType) -> Self { - ResourceSlotInfo { - name: name.into(), - resource_type, - } - } } diff --git a/crates/bevy_render/src/render_graph/nodes/camera_node.rs b/crates/bevy_render/src/render_graph/nodes/camera_node.rs deleted file mode 100644 index 3d6f72e452efe..0000000000000 --- a/crates/bevy_render/src/render_graph/nodes/camera_node.rs +++ /dev/null @@ -1,221 +0,0 @@ -use crate::{ - camera::{ActiveCameras, Camera}, - render_graph::{CommandQueue, Node, ResourceSlots, SystemNode}, - renderer::{ - BufferId, BufferInfo, BufferMapMode, BufferUsage, RenderContext, RenderResourceBinding, - RenderResourceContext, - }, -}; -use bevy_core::bytes_of; -use bevy_ecs::{ - system::{BoxedSystem, ConfigurableSystem, Local, Query, Res, ResMut}, - world::World, -}; -use bevy_transform::prelude::*; -use std::borrow::Cow; - -#[derive(Debug)] -pub struct CameraNode { - command_queue: CommandQueue, - camera_name: Cow<'static, str>, -} - -impl CameraNode { - pub fn new(camera_name: T) -> Self - where - T: Into>, - { - CameraNode { - command_queue: Default::default(), - camera_name: camera_name.into(), - } - } -} - -impl Node for CameraNode { - fn update( - &mut self, - _world: &World, - render_context: &mut dyn RenderContext, - _input: &ResourceSlots, - _output: &mut ResourceSlots, - ) { - self.command_queue.execute(render_context); - } -} - -impl SystemNode for CameraNode { - fn get_system(&self) -> BoxedSystem { - let system = camera_node_system.config(|config| { - config.0 = Some(CameraNodeState { - camera_name: self.camera_name.clone(), - command_queue: self.command_queue.clone(), - staging_buffer: None, - }) - }); - Box::new(system) - } -} - -const CAMERA_VIEW_PROJ: &str = "CameraViewProj"; -const CAMERA_VIEW: &str = "CameraView"; -const CAMERA_POSITION: &str = "CameraPosition"; - -#[derive(Debug, Default)] -pub struct CameraNodeState { - command_queue: CommandQueue, - camera_name: Cow<'static, str>, - staging_buffer: Option, -} - -const MATRIX_SIZE: usize = std::mem::size_of::<[[f32; 4]; 4]>(); -const VEC4_SIZE: usize = std::mem::size_of::<[f32; 4]>(); - -pub fn camera_node_system( - mut state: Local, - mut active_cameras: ResMut, - render_resource_context: Res>, - mut query: Query<(&Camera, &GlobalTransform)>, -) { - let render_resource_context = &**render_resource_context; - - let ((camera, global_transform), bindings) = - if let Some(active_camera) = active_cameras.get_mut(&state.camera_name) { - if let Some(entity) = active_camera.entity { - (query.get_mut(entity).unwrap(), &mut active_camera.bindings) - } else { - return; - } - } else { - return; - }; - - let staging_buffer = if let Some(staging_buffer) = state.staging_buffer { - render_resource_context.map_buffer(staging_buffer, BufferMapMode::Write); - staging_buffer - } else { - let staging_buffer = render_resource_context.create_buffer(BufferInfo { - size: - // ViewProj - MATRIX_SIZE + - // View - MATRIX_SIZE + - // Position - VEC4_SIZE, - buffer_usage: BufferUsage::COPY_SRC | BufferUsage::MAP_WRITE, - mapped_at_creation: true, - }); - - state.staging_buffer = Some(staging_buffer); - staging_buffer - }; - - if bindings.get(CAMERA_VIEW_PROJ).is_none() { - let buffer = render_resource_context.create_buffer(BufferInfo { - size: MATRIX_SIZE, - buffer_usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM, - ..Default::default() - }); - bindings.set( - CAMERA_VIEW_PROJ, - RenderResourceBinding::Buffer { - buffer, - range: 0..MATRIX_SIZE as u64, - dynamic_index: None, - }, - ); - } - - if bindings.get(CAMERA_VIEW).is_none() { - let buffer = render_resource_context.create_buffer(BufferInfo { - size: MATRIX_SIZE, - buffer_usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM, - ..Default::default() - }); - bindings.set( - CAMERA_VIEW, - RenderResourceBinding::Buffer { - buffer, - range: 0..MATRIX_SIZE as u64, - dynamic_index: None, - }, - ); - } - - if bindings.get(CAMERA_POSITION).is_none() { - let buffer = render_resource_context.create_buffer(BufferInfo { - size: VEC4_SIZE, - buffer_usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM, - ..Default::default() - }); - bindings.set( - CAMERA_POSITION, - RenderResourceBinding::Buffer { - buffer, - range: 0..VEC4_SIZE as u64, - dynamic_index: None, - }, - ); - } - - let view = global_transform.compute_matrix(); - let mut offset = 0; - - if let Some(RenderResourceBinding::Buffer { buffer, .. }) = bindings.get(CAMERA_VIEW) { - render_resource_context.write_mapped_buffer( - staging_buffer, - 0..MATRIX_SIZE as u64, - &mut |data, _renderer| { - data[0..MATRIX_SIZE].copy_from_slice(bytes_of(&view)); - }, - ); - state.command_queue.copy_buffer_to_buffer( - staging_buffer, - 0, - *buffer, - 0, - MATRIX_SIZE as u64, - ); - offset += MATRIX_SIZE as u64; - } - - if let Some(RenderResourceBinding::Buffer { buffer, .. }) = bindings.get(CAMERA_VIEW_PROJ) { - let view_proj = camera.projection_matrix * view.inverse(); - render_resource_context.write_mapped_buffer( - staging_buffer, - offset..(offset + MATRIX_SIZE as u64), - &mut |data, _renderer| { - data[0..MATRIX_SIZE].copy_from_slice(bytes_of(&view_proj)); - }, - ); - state.command_queue.copy_buffer_to_buffer( - staging_buffer, - offset, - *buffer, - 0, - MATRIX_SIZE as u64, - ); - offset += MATRIX_SIZE as u64; - } - - if let Some(RenderResourceBinding::Buffer { buffer, .. }) = bindings.get(CAMERA_POSITION) { - let position: [f32; 3] = global_transform.translation.into(); - let position: [f32; 4] = [position[0], position[1], position[2], 0.0]; - render_resource_context.write_mapped_buffer( - staging_buffer, - offset..(offset + VEC4_SIZE as u64), - &mut |data, _renderer| { - data[0..VEC4_SIZE].copy_from_slice(bytes_of(&position)); - }, - ); - state.command_queue.copy_buffer_to_buffer( - staging_buffer, - offset, - *buffer, - 0, - VEC4_SIZE as u64, - ); - } - - render_resource_context.unmap_buffer(staging_buffer); -} diff --git a/crates/bevy_render/src/render_graph/nodes/mod.rs b/crates/bevy_render/src/render_graph/nodes/mod.rs deleted file mode 100644 index d584b7bb7239d..0000000000000 --- a/crates/bevy_render/src/render_graph/nodes/mod.rs +++ /dev/null @@ -1,17 +0,0 @@ -mod camera_node; -mod pass_node; -mod render_resources_node; -mod shared_buffers_node; -mod texture_copy_node; -mod texture_node; -mod window_swapchain_node; -mod window_texture_node; - -pub use camera_node::*; -pub use pass_node::*; -pub use render_resources_node::*; -pub use shared_buffers_node::*; -pub use texture_copy_node::*; -pub use texture_node::*; -pub use window_swapchain_node::*; -pub use window_texture_node::*; diff --git a/crates/bevy_render/src/render_graph/nodes/pass_node.rs b/crates/bevy_render/src/render_graph/nodes/pass_node.rs deleted file mode 100644 index 73cf62e8f113a..0000000000000 --- a/crates/bevy_render/src/render_graph/nodes/pass_node.rs +++ /dev/null @@ -1,384 +0,0 @@ -use crate::{ - camera::{ActiveCameras, VisibleEntities}, - draw::{Draw, RenderCommand}, - pass::{ClearColor, LoadOp, PassDescriptor, TextureAttachment}, - pipeline::{IndexFormat, PipelineDescriptor}, - render_graph::{Node, ResourceSlotInfo, ResourceSlots}, - renderer::{ - BindGroupId, BufferId, RenderContext, RenderResourceBindings, RenderResourceContext, - RenderResourceType, - }, -}; -use bevy_asset::{Assets, Handle}; -use bevy_ecs::{ - query::{QueryState, ReadOnlyFetch, WorldQuery}, - world::{Mut, World}, -}; -use bevy_utils::{tracing::debug, HashMap}; -use std::fmt; - -pub struct PassNode { - descriptor: PassDescriptor, - inputs: Vec, - cameras: Vec, - color_attachment_input_indices: Vec>, - color_resolve_target_indices: Vec>, - depth_stencil_attachment_input_index: Option, - default_clear_color_inputs: Vec, - query_state: Option>, - commands: Vec, -} - -impl fmt::Debug for PassNode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("PassNode") - .field("descriptor", &self.descriptor) - .field("inputs", &self.inputs) - .field("cameras", &self.cameras) - .field( - "color_attachment_input_indices", - &self.color_attachment_input_indices, - ) - .field( - "color_resolve_target_indices", - &self.color_resolve_target_indices, - ) - .field( - "depth_stencil_attachment_input_index", - &self.depth_stencil_attachment_input_index, - ) - .field( - "default_clear_color_inputs", - &self.default_clear_color_inputs, - ) - .finish() - } -} - -impl PassNode { - pub fn new(descriptor: PassDescriptor) -> Self { - let mut inputs = Vec::new(); - let mut color_attachment_input_indices = Vec::new(); - let mut color_resolve_target_indices = Vec::new(); - for color_attachment in descriptor.color_attachments.iter() { - if let TextureAttachment::Input(ref name) = color_attachment.attachment { - color_attachment_input_indices.push(Some(inputs.len())); - inputs.push(ResourceSlotInfo::new( - name.to_string(), - RenderResourceType::Texture, - )); - } else { - color_attachment_input_indices.push(None); - } - - if let Some(TextureAttachment::Input(ref name)) = color_attachment.resolve_target { - color_resolve_target_indices.push(Some(inputs.len())); - inputs.push(ResourceSlotInfo::new( - name.to_string(), - RenderResourceType::Texture, - )); - } else { - color_resolve_target_indices.push(None); - } - } - - let mut depth_stencil_attachment_input_index = None; - if let Some(ref depth_stencil_attachment) = descriptor.depth_stencil_attachment { - if let TextureAttachment::Input(ref name) = depth_stencil_attachment.attachment { - depth_stencil_attachment_input_index = Some(inputs.len()); - inputs.push(ResourceSlotInfo::new( - name.to_string(), - RenderResourceType::Texture, - )); - } - } - - PassNode { - descriptor, - inputs, - cameras: Vec::new(), - color_attachment_input_indices, - color_resolve_target_indices, - depth_stencil_attachment_input_index, - default_clear_color_inputs: Vec::new(), - query_state: None, - commands: Vec::new(), - } - } - - pub fn add_camera(&mut self, camera_name: &str) { - self.cameras.push(camera_name.to_string()); - } - - pub fn use_default_clear_color(&mut self, color_attachment_index: usize) { - self.default_clear_color_inputs.push(color_attachment_index); - } -} - -impl Node for PassNode -where - Q::Fetch: ReadOnlyFetch, -{ - fn input(&self) -> &[ResourceSlotInfo] { - &self.inputs - } - - fn prepare(&mut self, world: &mut World) { - let query_state = self.query_state.get_or_insert_with(|| world.query()); - let cameras = &self.cameras; - let commands = &mut self.commands; - world.resource_scope(|world, mut active_cameras: Mut| { - let mut pipeline_camera_commands = HashMap::default(); - let pipelines = world.get_resource::>().unwrap(); - let render_resource_context = &**world - .get_resource::>() - .unwrap(); - - for camera_name in cameras.iter() { - let active_camera = if let Some(active_camera) = active_cameras.get_mut(camera_name) - { - active_camera - } else { - continue; - }; - - let visible_entities = if let Some(entity) = active_camera.entity { - world.get::(entity).unwrap() - } else { - continue; - }; - for visible_entity in visible_entities.iter() { - if query_state.get(world, visible_entity.entity).is_err() { - // visible entity does not match the Pass query - continue; - } - - let draw = if let Some(draw) = world.get::(visible_entity.entity) { - draw - } else { - continue; - }; - - for render_command in draw.render_commands.iter() { - commands.push(render_command.clone()); - // whenever a new pipeline is set, ensure the relevant camera bind groups - // are set - if let RenderCommand::SetPipeline { pipeline } = render_command { - let bind_groups = pipeline_camera_commands - .entry(pipeline.clone_weak()) - .or_insert_with(|| { - let descriptor = pipelines.get(pipeline).unwrap(); - let layout = descriptor.get_layout().unwrap(); - let mut commands = Vec::new(); - for bind_group_descriptor in layout.bind_groups.iter() { - if let Some(bind_group) = - active_camera.bindings.update_bind_group( - bind_group_descriptor, - render_resource_context, - ) - { - commands.push(RenderCommand::SetBindGroup { - index: bind_group_descriptor.index, - bind_group: bind_group.id, - dynamic_uniform_indices: bind_group - .dynamic_uniform_indices - .clone(), - }) - } - } - commands - }); - - commands.extend(bind_groups.iter().cloned()); - } - } - } - } - }); - } - - fn update( - &mut self, - world: &World, - render_context: &mut dyn RenderContext, - input: &ResourceSlots, - _output: &mut ResourceSlots, - ) { - for (i, color_attachment) in self.descriptor.color_attachments.iter_mut().enumerate() { - if self.default_clear_color_inputs.contains(&i) { - if let Some(default_clear_color) = world.get_resource::() { - color_attachment.ops.load = LoadOp::Clear(default_clear_color.0); - } - } - if let Some(input_index) = self.color_attachment_input_indices[i] { - color_attachment.attachment = - TextureAttachment::Id(input.get(input_index).unwrap().get_texture().unwrap()); - } - if let Some(input_index) = self.color_resolve_target_indices[i] { - color_attachment.resolve_target = Some(TextureAttachment::Id( - input.get(input_index).unwrap().get_texture().unwrap(), - )); - } - } - - if let Some(input_index) = self.depth_stencil_attachment_input_index { - self.descriptor - .depth_stencil_attachment - .as_mut() - .unwrap() - .attachment = - TextureAttachment::Id(input.get(input_index).unwrap().get_texture().unwrap()); - } - - let render_resource_bindings = world.get_resource::().unwrap(); - let pipelines = world.get_resource::>().unwrap(); - - let mut draw_state = DrawState::default(); - let commands = &mut self.commands; - render_context.begin_pass( - &self.descriptor, - render_resource_bindings, - &mut |render_pass| { - for render_command in commands.drain(..) { - match render_command { - RenderCommand::SetPipeline { pipeline } => { - if draw_state.is_pipeline_set(pipeline.clone_weak()) { - continue; - } - render_pass.set_pipeline(&pipeline); - let descriptor = pipelines.get(&pipeline).unwrap(); - draw_state.set_pipeline(&pipeline, descriptor); - } - RenderCommand::DrawIndexed { - base_vertex, - indices, - instances, - } => { - if draw_state.can_draw_indexed() { - render_pass.draw_indexed( - indices.clone(), - base_vertex, - instances.clone(), - ); - } else { - debug!("Could not draw indexed because the pipeline layout wasn't fully set for pipeline: {:?}", draw_state.pipeline); - } - } - RenderCommand::Draw { vertices, instances } => { - if draw_state.can_draw() { - render_pass.draw(vertices.clone(), instances.clone()); - } else { - debug!("Could not draw because the pipeline layout wasn't fully set for pipeline: {:?}", draw_state.pipeline); - } - } - RenderCommand::SetVertexBuffer { - buffer, - offset, - slot, - } => { - if draw_state.is_vertex_buffer_set(slot, buffer, offset) { - continue; - } - render_pass.set_vertex_buffer(slot, buffer, offset); - draw_state.set_vertex_buffer(slot, buffer, offset); - } - RenderCommand::SetIndexBuffer { buffer, offset, index_format } => { - if draw_state.is_index_buffer_set(buffer, offset, index_format) { - continue; - } - render_pass.set_index_buffer(buffer, offset, index_format); - draw_state.set_index_buffer(buffer, offset, index_format); - } - RenderCommand::SetBindGroup { - index, - bind_group, - dynamic_uniform_indices, - } => { - if dynamic_uniform_indices.is_none() && draw_state.is_bind_group_set(index, bind_group) { - continue; - } - let pipeline = pipelines.get(draw_state.pipeline.as_ref().unwrap()).unwrap(); - let layout = pipeline.get_layout().unwrap(); - let bind_group_descriptor = layout.get_bind_group(index).unwrap(); - render_pass.set_bind_group( - index, - bind_group_descriptor.id, - bind_group, - dynamic_uniform_indices.as_deref() - ); - draw_state.set_bind_group(index, bind_group); - } - } - } - }); - } -} - -/// Tracks the current pipeline state to ensure draw calls are valid. -#[derive(Debug, Default)] -struct DrawState { - pipeline: Option>, - bind_groups: Vec>, - vertex_buffers: Vec>, - index_buffer: Option<(BufferId, u64, IndexFormat)>, -} - -impl DrawState { - pub fn set_bind_group(&mut self, index: u32, bind_group: BindGroupId) { - self.bind_groups[index as usize] = Some(bind_group); - } - - pub fn is_bind_group_set(&self, index: u32, bind_group: BindGroupId) -> bool { - self.bind_groups[index as usize] == Some(bind_group) - } - - pub fn set_vertex_buffer(&mut self, index: u32, buffer: BufferId, offset: u64) { - self.vertex_buffers[index as usize] = Some((buffer, offset)); - } - - pub fn is_vertex_buffer_set(&self, index: u32, buffer: BufferId, offset: u64) -> bool { - self.vertex_buffers[index as usize] == Some((buffer, offset)) - } - - pub fn set_index_buffer(&mut self, buffer: BufferId, offset: u64, index_format: IndexFormat) { - self.index_buffer = Some((buffer, offset, index_format)); - } - - pub fn is_index_buffer_set( - &self, - buffer: BufferId, - offset: u64, - index_format: IndexFormat, - ) -> bool { - self.index_buffer == Some((buffer, offset, index_format)) - } - - pub fn can_draw(&self) -> bool { - self.bind_groups.iter().all(|b| b.is_some()) - && self.vertex_buffers.iter().all(|v| v.is_some()) - } - - pub fn can_draw_indexed(&self) -> bool { - self.can_draw() && self.index_buffer.is_some() - } - - pub fn is_pipeline_set(&self, pipeline: Handle) -> bool { - self.pipeline == Some(pipeline) - } - - pub fn set_pipeline( - &mut self, - handle: &Handle, - descriptor: &PipelineDescriptor, - ) { - self.bind_groups.clear(); - self.vertex_buffers.clear(); - self.index_buffer = None; - - self.pipeline = Some(handle.clone_weak()); - let layout = descriptor.get_layout().unwrap(); - self.bind_groups.resize(layout.bind_groups.len(), None); - self.vertex_buffers - .resize(layout.vertex_buffer_descriptors.len(), None); - } -} diff --git a/crates/bevy_render/src/render_graph/nodes/render_resources_node.rs b/crates/bevy_render/src/render_graph/nodes/render_resources_node.rs deleted file mode 100644 index 7844f122c925d..0000000000000 --- a/crates/bevy_render/src/render_graph/nodes/render_resources_node.rs +++ /dev/null @@ -1,811 +0,0 @@ -use crate::{ - pipeline::RenderPipelines, - prelude::Visible, - render_graph::{CommandQueue, Node, ResourceSlots, SystemNode}, - renderer::{ - self, BufferInfo, BufferMapMode, BufferUsage, RenderContext, RenderResourceBinding, - RenderResourceBindings, RenderResourceContext, RenderResourceHints, - }, - texture, -}; - -use bevy_app::EventReader; -use bevy_asset::{Asset, AssetEvent, Assets, Handle, HandleId}; -use bevy_ecs::{ - component::Component, - entity::Entity, - prelude::QueryState, - query::{Changed, Or, With}, - system::{BoxedSystem, ConfigurableSystem, Local, QuerySet, RemovedComponents, Res, ResMut}, - world::World, -}; -use bevy_utils::HashMap; -use renderer::{AssetRenderResourceBindings, BufferId, RenderResourceType, RenderResources}; -use std::{any::TypeId, hash::Hash, marker::PhantomData, ops::DerefMut}; - -#[derive(Debug)] -struct QueuedBufferWrite { - buffer: BufferId, - target_offset: usize, - source_offset: usize, - size: usize, -} - -/// Used to track items in a gpu buffer in an "array" style -#[derive(Debug)] -struct BufferArray { - item_size: usize, - buffer_capacity: usize, - min_capacity: usize, - len: usize, - buffer: Option, - free_indices: Vec, - indices: HashMap, -} - -impl BufferArray { - pub fn new(item_size: usize, min_capacity: usize) -> Self { - BufferArray { - item_size, - len: 0, - buffer_capacity: 0, - min_capacity, - buffer: None, - free_indices: Vec::new(), - indices: HashMap::default(), - } - } - - fn get_or_assign_index(&mut self, id: I) -> usize { - if let Some(index) = self.indices.get(&id) { - *index - } else if let Some(index) = self.free_indices.pop() { - self.indices.insert(id, index); - self.len += 1; - index - } else { - let index = self.len; - self.indices.insert(id, index); - self.len += 1; - index - } - } - - pub fn get_binding(&self, id: I) -> Option { - self.indices - .get(&id) - .map(|index| RenderResourceBinding::Buffer { - buffer: self.buffer.unwrap(), - dynamic_index: Some((index * self.item_size) as u32), - range: 0..self.item_size as u64, - }) - } - - pub fn remove_binding(&mut self, id: I) { - if let Some(index) = self.indices.remove(&id) { - self.free_indices.push(index); - self.len -= 1; - } - } - - pub fn resize(&mut self, render_resource_context: &dyn RenderResourceContext) -> bool { - if self.len <= self.buffer_capacity { - return false; - } - - self.allocate_buffer(render_resource_context); - // TODO: allow shrinking - true - } - - pub fn allocate_buffer(&mut self, render_resource_context: &dyn RenderResourceContext) { - if let Some(old_buffer) = self.buffer.take() { - render_resource_context.remove_buffer(old_buffer); - } - - let new_len = if self.buffer_capacity == 0 { - self.min_capacity.max(self.len) - } else { - self.min_capacity.max(self.len * 2) - }; - - let size = new_len * self.item_size; - let buffer = render_resource_context.create_buffer(BufferInfo { - size, - buffer_usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM, - ..Default::default() - }); - - self.buffer = Some(buffer); - self.buffer_capacity = new_len; - } -} - -struct UniformBufferArrays -where - T: renderer::RenderResources, -{ - buffer_arrays: Vec>>, - staging_buffer: Option, - staging_buffer_size: usize, - required_staging_buffer_size: usize, - current_staging_buffer_offset: usize, - queued_buffer_writes: Vec, - _marker: PhantomData, -} - -impl Default for UniformBufferArrays -where - T: renderer::RenderResources, -{ - fn default() -> Self { - Self { - buffer_arrays: Default::default(), - staging_buffer: Default::default(), - staging_buffer_size: 0, - current_staging_buffer_offset: 0, - queued_buffer_writes: Vec::new(), - required_staging_buffer_size: 0, - _marker: Default::default(), - } - } -} - -impl UniformBufferArrays -where - I: Hash + Eq + Copy, - T: renderer::RenderResources, -{ - /// Initialize this UniformBufferArrays using information from a RenderResources value. - fn initialize( - &mut self, - render_resources: &T, - render_resource_context: &dyn RenderResourceContext, - ) { - if self.buffer_arrays.len() != render_resources.render_resources_len() { - let mut buffer_arrays = Vec::with_capacity(render_resources.render_resources_len()); - for render_resource in render_resources.iter() { - if let Some(RenderResourceType::Buffer) = render_resource.resource_type() { - let size = render_resource.buffer_byte_len().unwrap(); - let aligned_size = render_resource_context.get_aligned_uniform_size(size, true); - buffer_arrays.push(Some(BufferArray::new(aligned_size, 10))); - } else { - buffer_arrays.push(None); - } - } - - self.buffer_arrays = buffer_arrays; - } - } - - /// Resets staging buffer tracking information - fn begin_update(&mut self) { - self.required_staging_buffer_size = 0; - self.current_staging_buffer_offset = 0; - } - - /// Find a spot for the given RenderResources in each uniform's BufferArray and prepare space in - /// the staging buffer - fn prepare_uniform_buffers(&mut self, id: I, render_resources: &T) { - for (i, render_resource) in render_resources.iter().enumerate() { - if let Some(RenderResourceType::Buffer) = render_resource.resource_type() { - let size = render_resource.buffer_byte_len().unwrap(); - if let Some(buffer_array) = &mut self.buffer_arrays[i] { - buffer_array.get_or_assign_index(id); - self.required_staging_buffer_size += size; - } - } - } - } - - /// Resize BufferArray buffers if they aren't large enough - fn resize_buffer_arrays( - &mut self, - render_resource_context: &dyn RenderResourceContext, - ) -> bool { - let mut resized = false; - for buffer_array in self.buffer_arrays.iter_mut().flatten() { - resized |= buffer_array.resize(render_resource_context); - } - - resized - } - - fn set_required_staging_buffer_size_to_max(&mut self) { - let mut new_size = 0; - for buffer_array in self.buffer_arrays.iter().flatten() { - new_size += buffer_array.item_size * buffer_array.len; - } - - if new_size > self.required_staging_buffer_size { - self.required_staging_buffer_size = new_size; - } - } - - /// Update the staging buffer to provide enough space to copy data to target buffers. - fn resize_staging_buffer(&mut self, render_resource_context: &dyn RenderResourceContext) { - // TODO: allow staging buffer to scale down - if self.required_staging_buffer_size > self.staging_buffer_size { - if let Some(staging_buffer) = self.staging_buffer { - render_resource_context.remove_buffer(staging_buffer); - } - - if self.required_staging_buffer_size > 0 { - let staging_buffer = render_resource_context.create_buffer(BufferInfo { - buffer_usage: BufferUsage::COPY_SRC | BufferUsage::MAP_WRITE, - size: self.required_staging_buffer_size, - ..Default::default() - }); - self.staging_buffer = Some(staging_buffer); - } else { - self.staging_buffer = None; - } - - self.staging_buffer_size = self.required_staging_buffer_size; - } - } - - fn remove_bindings(&mut self, id: I) { - for buffer_array in self.buffer_arrays.iter_mut().flatten() { - buffer_array.remove_binding(id); - } - } - - fn write_uniform_buffers( - &mut self, - id: I, - uniforms: &T, - dynamic_uniforms: bool, - render_resource_context: &dyn RenderResourceContext, - render_resource_bindings: &mut RenderResourceBindings, - staging_buffer: &mut [u8], - ) { - for (i, render_resource) in uniforms.iter().enumerate() { - if let Some(RenderResourceType::Buffer) = render_resource.resource_type() { - let size = render_resource.buffer_byte_len().unwrap(); - let render_resource_name = uniforms.get_render_resource_name(i).unwrap(); - let aligned_size = render_resource_context.get_aligned_uniform_size(size, false); - let buffer_array = self.buffer_arrays[i].as_mut().unwrap(); - let range = 0..aligned_size as u64; - let (target_buffer, target_offset) = if dynamic_uniforms { - let binding = buffer_array.get_binding(id).unwrap(); - let dynamic_index = if let RenderResourceBinding::Buffer { - dynamic_index: Some(dynamic_index), - .. - } = binding - { - dynamic_index - } else { - panic!("Dynamic index should always be set."); - }; - render_resource_bindings.set(render_resource_name, binding); - (buffer_array.buffer.unwrap(), dynamic_index) - } else { - let mut matching_buffer = None; - if let Some(binding) = render_resource_bindings.get(render_resource_name) { - let buffer_id = binding.get_buffer().unwrap(); - if let Some(BufferInfo { - size: current_size, .. - }) = render_resource_context.get_buffer_info(buffer_id) - { - if aligned_size == current_size { - matching_buffer = Some(buffer_id); - } else { - render_resource_context.remove_buffer(buffer_id); - } - } - } - - let resource = if let Some(matching_buffer) = matching_buffer { - matching_buffer - } else { - let mut usage = BufferUsage::UNIFORM; - if let Some(render_resource_hints) = uniforms.get_render_resource_hints(i) { - if render_resource_hints.contains(RenderResourceHints::BUFFER) { - usage = BufferUsage::STORAGE - } - } - - let buffer = render_resource_context.create_buffer(BufferInfo { - size: aligned_size, - buffer_usage: BufferUsage::COPY_DST | usage, - ..Default::default() - }); - - render_resource_bindings.set( - render_resource_name, - RenderResourceBinding::Buffer { - buffer, - range, - dynamic_index: None, - }, - ); - buffer - }; - - (resource, 0) - }; - - render_resource.write_buffer_bytes( - &mut staging_buffer[self.current_staging_buffer_offset - ..(self.current_staging_buffer_offset + size)], - ); - - self.queued_buffer_writes.push(QueuedBufferWrite { - buffer: target_buffer, - target_offset: target_offset as usize, - source_offset: self.current_staging_buffer_offset, - size, - }); - self.current_staging_buffer_offset += size; - } - } - } - - fn copy_staging_buffer_to_final_buffers( - &mut self, - command_queue: &mut CommandQueue, - staging_buffer: BufferId, - ) { - for queued_buffer_write in self.queued_buffer_writes.drain(..) { - command_queue.copy_buffer_to_buffer( - staging_buffer, - queued_buffer_write.source_offset as u64, - queued_buffer_write.buffer, - queued_buffer_write.target_offset as u64, - queued_buffer_write.size as u64, - ) - } - } -} - -#[derive(Default)] -pub struct RenderResourcesNode -where - T: renderer::RenderResources, -{ - command_queue: CommandQueue, - dynamic_uniforms: bool, - _marker: PhantomData, -} - -impl RenderResourcesNode -where - T: renderer::RenderResources, -{ - pub fn new(dynamic_uniforms: bool) -> Self { - RenderResourcesNode { - command_queue: CommandQueue::default(), - dynamic_uniforms, - _marker: PhantomData::default(), - } - } -} - -impl Node for RenderResourcesNode -where - T: renderer::RenderResources, -{ - fn update( - &mut self, - _world: &World, - render_context: &mut dyn RenderContext, - _input: &ResourceSlots, - _output: &mut ResourceSlots, - ) { - self.command_queue.execute(render_context); - } -} - -impl SystemNode for RenderResourcesNode -where - T: renderer::RenderResources + Component, -{ - fn get_system(&self) -> BoxedSystem { - let system = render_resources_node_system::.config(|config| { - config.0 = Some(RenderResourcesNodeState { - command_queue: self.command_queue.clone(), - uniform_buffer_arrays: UniformBufferArrays::::default(), - dynamic_uniforms: self.dynamic_uniforms, - }) - }); - - Box::new(system) - } -} - -struct RenderResourcesNodeState { - command_queue: CommandQueue, - uniform_buffer_arrays: UniformBufferArrays, - dynamic_uniforms: bool, -} - -impl Default for RenderResourcesNodeState { - fn default() -> Self { - Self { - command_queue: Default::default(), - uniform_buffer_arrays: Default::default(), - dynamic_uniforms: Default::default(), - } - } -} - -#[allow(clippy::type_complexity)] -fn render_resources_node_system( - mut state: Local>, - mut entities_waiting_for_textures: Local>, - render_resource_context: Res>, - removed: RemovedComponents, - mut queries: QuerySet<( - QueryState< - (Entity, &T, &Visible, &mut RenderPipelines), - Or<(Changed, Changed)>, - >, - QueryState<(Entity, &T, &Visible, &mut RenderPipelines)>, - )>, -) { - let state = state.deref_mut(); - let uniform_buffer_arrays = &mut state.uniform_buffer_arrays; - let render_resource_context = &**render_resource_context; - uniform_buffer_arrays.begin_update(); - // initialize uniform buffer arrays using the first RenderResources - if let Some((_, first, _, _)) = queries.q0().iter_mut().next() { - uniform_buffer_arrays.initialize(first, render_resource_context); - } - - for entity in removed.iter() { - uniform_buffer_arrays.remove_bindings(entity); - } - - // handle entities that were waiting for texture loads on the last update - for entity in std::mem::take(&mut *entities_waiting_for_textures) { - if let Ok((entity, uniforms, _visible, mut render_pipelines)) = queries.q1().get_mut(entity) - { - if !setup_uniform_texture_resources::( - uniforms, - render_resource_context, - &mut render_pipelines.bindings, - ) { - entities_waiting_for_textures.push(entity); - } - } - } - - for (entity, uniforms, visible, mut render_pipelines) in queries.q0().iter_mut() { - if !visible.is_visible { - continue; - } - uniform_buffer_arrays.prepare_uniform_buffers(entity, uniforms); - if !setup_uniform_texture_resources::( - uniforms, - render_resource_context, - &mut render_pipelines.bindings, - ) { - entities_waiting_for_textures.push(entity); - } - } - - let resized = uniform_buffer_arrays.resize_buffer_arrays(render_resource_context); - if resized { - uniform_buffer_arrays.set_required_staging_buffer_size_to_max() - } - uniform_buffer_arrays.resize_staging_buffer(render_resource_context); - - if let Some(staging_buffer) = state.uniform_buffer_arrays.staging_buffer { - render_resource_context.map_buffer(staging_buffer, BufferMapMode::Write); - render_resource_context.write_mapped_buffer( - staging_buffer, - 0..state.uniform_buffer_arrays.staging_buffer_size as u64, - &mut |mut staging_buffer, _render_resource_context| { - // if the buffer array was resized, write all entities to the new buffer, otherwise - // only write changes - if resized { - for (entity, uniforms, visible, mut render_pipelines) in queries.q1().iter_mut() - { - if !visible.is_visible { - continue; - } - - state.uniform_buffer_arrays.write_uniform_buffers( - entity, - uniforms, - state.dynamic_uniforms, - render_resource_context, - &mut render_pipelines.bindings, - &mut staging_buffer, - ); - } - } else { - for (entity, uniforms, visible, mut render_pipelines) in queries.q0().iter_mut() - { - if !visible.is_visible { - continue; - } - - state.uniform_buffer_arrays.write_uniform_buffers( - entity, - uniforms, - state.dynamic_uniforms, - render_resource_context, - &mut render_pipelines.bindings, - &mut staging_buffer, - ); - } - } - }, - ); - render_resource_context.unmap_buffer(staging_buffer); - - state - .uniform_buffer_arrays - .copy_staging_buffer_to_final_buffers(&mut state.command_queue, staging_buffer); - } -} - -#[derive(Default)] -pub struct AssetRenderResourcesNode -where - T: renderer::RenderResources, -{ - command_queue: CommandQueue, - dynamic_uniforms: bool, - _marker: PhantomData, -} - -impl AssetRenderResourcesNode -where - T: renderer::RenderResources, -{ - pub fn new(dynamic_uniforms: bool) -> Self { - AssetRenderResourcesNode { - dynamic_uniforms, - command_queue: Default::default(), - _marker: Default::default(), - } - } -} - -impl Node for AssetRenderResourcesNode -where - T: renderer::RenderResources, -{ - fn update( - &mut self, - _world: &World, - render_context: &mut dyn RenderContext, - _input: &ResourceSlots, - _output: &mut ResourceSlots, - ) { - self.command_queue.execute(render_context); - } -} - -impl SystemNode for AssetRenderResourcesNode -where - T: renderer::RenderResources + Asset, -{ - fn get_system(&self) -> BoxedSystem { - let system = asset_render_resources_node_system::.config(|config| { - config.0 = Some(RenderResourcesNodeState { - command_queue: self.command_queue.clone(), - uniform_buffer_arrays: UniformBufferArrays::::default(), - dynamic_uniforms: self.dynamic_uniforms, - }) - }); - - Box::new(system) - } -} - -struct AssetRenderNodeState { - assets_waiting_for_textures: Vec, - _marker: PhantomData, -} - -impl Default for AssetRenderNodeState { - fn default() -> Self { - Self { - _marker: Default::default(), - assets_waiting_for_textures: Default::default(), - } - } -} - -#[allow(clippy::too_many_arguments, clippy::type_complexity)] -fn asset_render_resources_node_system( - mut state: Local>, - mut asset_state: Local>, - assets: Res>, - mut asset_events: EventReader>, - mut asset_render_resource_bindings: ResMut, - render_resource_context: Res>, - removed_handles: RemovedComponents>, - mut queries: QuerySet<( - QueryState<(&Handle, &mut RenderPipelines), Changed>>, - QueryState<&mut RenderPipelines, With>>, - )>, -) { - let state = state.deref_mut(); - let uniform_buffer_arrays = &mut state.uniform_buffer_arrays; - let render_resource_context = &**render_resource_context; - - let mut changed_assets = HashMap::default(); - for event in asset_events.iter() { - match event { - AssetEvent::Created { ref handle } => { - if let Some(asset) = assets.get(handle) { - changed_assets.insert(handle.id, asset); - } - } - AssetEvent::Modified { ref handle } => { - if let Some(asset) = assets.get(handle) { - changed_assets.insert(handle.id, asset); - } - } - AssetEvent::Removed { ref handle } => { - uniform_buffer_arrays.remove_bindings(handle.id); - // if asset was modified and removed in the same update, ignore the modification - // events are ordered so future modification events are ok - changed_assets.remove(&handle.id); - } - } - } - - // handle assets that were waiting for texture loads on the last update - for asset_handle in std::mem::take(&mut asset_state.assets_waiting_for_textures) { - if let Some(asset) = assets.get(asset_handle) { - let mut bindings = - asset_render_resource_bindings.get_or_insert_mut(&Handle::::weak(asset_handle)); - if !setup_uniform_texture_resources::(asset, render_resource_context, &mut bindings) - { - asset_state.assets_waiting_for_textures.push(asset_handle); - } - } - } - - uniform_buffer_arrays.begin_update(); - // initialize uniform buffer arrays using the largest RenderResources - if let Some((asset, _)) = changed_assets - .values() - .map(|asset| { - let size: usize = asset - .iter() - .filter_map(|render_resource| { - if let Some(RenderResourceType::Buffer) = render_resource.resource_type() { - render_resource.buffer_byte_len() - } else { - None - } - }) - .sum(); - (asset, size) - }) - .max_by_key(|(_, size)| *size) - { - uniform_buffer_arrays.initialize(asset, render_resource_context); - } - - for (asset_handle, asset) in changed_assets.iter() { - uniform_buffer_arrays.prepare_uniform_buffers(*asset_handle, asset); - let mut bindings = - asset_render_resource_bindings.get_or_insert_mut(&Handle::::weak(*asset_handle)); - if !setup_uniform_texture_resources::(asset, render_resource_context, &mut bindings) { - asset_state.assets_waiting_for_textures.push(*asset_handle); - } - } - - let resized = uniform_buffer_arrays.resize_buffer_arrays(render_resource_context); - if resized { - // full asset copy needed, make sure there is also space for unchanged assets - for (asset_handle, asset) in assets.iter() { - if !changed_assets.contains_key(&asset_handle) { - uniform_buffer_arrays.prepare_uniform_buffers(asset_handle, asset); - } - } - uniform_buffer_arrays.set_required_staging_buffer_size_to_max() - } - uniform_buffer_arrays.resize_staging_buffer(render_resource_context); - - if let Some(staging_buffer) = state.uniform_buffer_arrays.staging_buffer { - render_resource_context.map_buffer(staging_buffer, BufferMapMode::Write); - render_resource_context.write_mapped_buffer( - staging_buffer, - 0..state.uniform_buffer_arrays.staging_buffer_size as u64, - &mut |mut staging_buffer, _render_resource_context| { - if resized { - for (asset_handle, asset) in assets.iter() { - let mut render_resource_bindings = asset_render_resource_bindings - .get_or_insert_mut(&Handle::::weak(asset_handle)); - // TODO: only setup buffer if we haven't seen this handle before - state.uniform_buffer_arrays.write_uniform_buffers( - asset_handle, - asset, - state.dynamic_uniforms, - render_resource_context, - &mut render_resource_bindings, - &mut staging_buffer, - ); - } - } else { - for (asset_handle, asset) in changed_assets.iter() { - let mut render_resource_bindings = asset_render_resource_bindings - .get_or_insert_mut(&Handle::::weak(*asset_handle)); - // TODO: only setup buffer if we haven't seen this handle before - state.uniform_buffer_arrays.write_uniform_buffers( - *asset_handle, - asset, - state.dynamic_uniforms, - render_resource_context, - &mut render_resource_bindings, - &mut staging_buffer, - ); - } - } - }, - ); - render_resource_context.unmap_buffer(staging_buffer); - - state - .uniform_buffer_arrays - .copy_staging_buffer_to_final_buffers(&mut state.command_queue, staging_buffer); - } - - // update removed entity asset mapping - for entity in removed_handles.iter() { - if let Ok(mut render_pipelines) = queries.q1().get_mut(entity) { - render_pipelines - .bindings - .remove_asset_with_type(TypeId::of::()) - } - } - - // update changed entity asset mapping - for (asset_handle, mut render_pipelines) in queries.q0().iter_mut() { - render_pipelines - .bindings - .remove_asset_with_type(TypeId::of::()); - render_pipelines - .bindings - .add_asset(asset_handle.clone_weak_untyped(), TypeId::of::()); - } -} - -fn setup_uniform_texture_resources( - uniforms: &T, - render_resource_context: &dyn RenderResourceContext, - render_resource_bindings: &mut RenderResourceBindings, -) -> bool -where - T: renderer::RenderResources, -{ - let mut success = true; - for (i, render_resource) in uniforms.iter().enumerate() { - if let Some(RenderResourceType::Texture) = render_resource.resource_type() { - let render_resource_name = uniforms.get_render_resource_name(i).unwrap(); - let sampler_name = format!("{}_sampler", render_resource_name); - if let Some(texture_handle) = render_resource.texture() { - if let Some(texture_resource) = render_resource_context - .get_asset_resource(texture_handle, texture::TEXTURE_ASSET_INDEX) - { - let sampler_resource = render_resource_context - .get_asset_resource(texture_handle, texture::SAMPLER_ASSET_INDEX) - .unwrap(); - - render_resource_bindings.set( - render_resource_name, - RenderResourceBinding::Texture(texture_resource.get_texture().unwrap()), - ); - render_resource_bindings.set( - &sampler_name, - RenderResourceBinding::Sampler(sampler_resource.get_sampler().unwrap()), - ); - continue; - } else { - success = false; - } - } - } - } - - success -} diff --git a/crates/bevy_render/src/render_graph/nodes/shared_buffers_node.rs b/crates/bevy_render/src/render_graph/nodes/shared_buffers_node.rs deleted file mode 100644 index 6106ef7261c89..0000000000000 --- a/crates/bevy_render/src/render_graph/nodes/shared_buffers_node.rs +++ /dev/null @@ -1,21 +0,0 @@ -use crate::{ - render_graph::{Node, ResourceSlots}, - renderer::{RenderContext, SharedBuffers}, -}; -use bevy_ecs::world::World; - -#[derive(Default)] -pub struct SharedBuffersNode; - -impl Node for SharedBuffersNode { - fn update( - &mut self, - world: &World, - render_context: &mut dyn RenderContext, - _input: &ResourceSlots, - _output: &mut ResourceSlots, - ) { - let shared_buffers = world.get_resource::().unwrap(); - shared_buffers.apply(render_context); - } -} diff --git a/crates/bevy_render/src/render_graph/nodes/texture_copy_node.rs b/crates/bevy_render/src/render_graph/nodes/texture_copy_node.rs deleted file mode 100644 index ff37812800cb9..0000000000000 --- a/crates/bevy_render/src/render_graph/nodes/texture_copy_node.rs +++ /dev/null @@ -1,88 +0,0 @@ -use crate::{ - render_graph::{Node, ResourceSlots}, - renderer::{BufferInfo, BufferUsage, RenderContext}, - texture::{Texture, TextureDescriptor, TEXTURE_ASSET_INDEX}, -}; -use bevy_app::{Events, ManualEventReader}; -use bevy_asset::{AssetEvent, Assets}; -use bevy_ecs::world::World; -use bevy_utils::HashSet; - -#[derive(Default)] -pub struct TextureCopyNode { - pub texture_event_reader: ManualEventReader>, -} - -impl Node for TextureCopyNode { - fn update( - &mut self, - world: &World, - render_context: &mut dyn RenderContext, - _input: &ResourceSlots, - _output: &mut ResourceSlots, - ) { - let texture_events = world.get_resource::>>().unwrap(); - let textures = world.get_resource::>().unwrap(); - let mut copied_textures = HashSet::default(); - for event in self.texture_event_reader.iter(texture_events) { - match event { - AssetEvent::Created { handle } | AssetEvent::Modified { handle } => { - if let Some(texture) = textures.get(handle) { - if copied_textures.contains(&handle.id) { - continue; - } - - let texture_descriptor: TextureDescriptor = texture.into(); - let width = texture.size.width as usize; - let aligned_width = - render_context.resources().get_aligned_texture_size(width); - let format_size = texture.format.pixel_size(); - let mut aligned_data = vec![ - 0; - format_size - * aligned_width - * texture.size.height as usize - * texture.size.depth_or_array_layers - as usize - ]; - texture - .data - .chunks_exact(format_size * width) - .enumerate() - .for_each(|(index, row)| { - let offset = index * aligned_width * format_size; - aligned_data[offset..(offset + width * format_size)] - .copy_from_slice(row); - }); - let texture_buffer = render_context.resources().create_buffer_with_data( - BufferInfo { - buffer_usage: BufferUsage::COPY_SRC, - ..Default::default() - }, - &aligned_data, - ); - - let texture_resource = render_context - .resources() - .get_asset_resource(handle, TEXTURE_ASSET_INDEX) - .unwrap(); - - render_context.copy_buffer_to_texture( - texture_buffer, - 0, - (format_size * aligned_width) as u32, - texture_resource.get_texture().unwrap(), - [0, 0, 0], - 0, - texture_descriptor.size, - ); - render_context.resources().remove_buffer(texture_buffer); - - copied_textures.insert(&handle.id); - } - } - AssetEvent::Removed { .. } => {} - } - } - } -} diff --git a/crates/bevy_render/src/render_graph/nodes/texture_node.rs b/crates/bevy_render/src/render_graph/nodes/texture_node.rs deleted file mode 100644 index 5e2f129e98978..0000000000000 --- a/crates/bevy_render/src/render_graph/nodes/texture_node.rs +++ /dev/null @@ -1,69 +0,0 @@ -use bevy_asset::HandleUntyped; -use bevy_ecs::world::World; -use std::borrow::Cow; - -use crate::{ - render_graph::{Node, ResourceSlotInfo, ResourceSlots}, - renderer::{RenderContext, RenderResourceId, RenderResourceType}, - texture::{SamplerDescriptor, TextureDescriptor, SAMPLER_ASSET_INDEX, TEXTURE_ASSET_INDEX}, -}; -pub struct TextureNode { - pub texture_descriptor: TextureDescriptor, - pub sampler_descriptor: Option, - pub handle: Option, -} - -impl TextureNode { - pub const TEXTURE: &'static str = "texture"; - - pub fn new( - texture_descriptor: TextureDescriptor, - sampler_descriptor: Option, - handle: Option, - ) -> Self { - Self { - texture_descriptor, - sampler_descriptor, - handle, - } - } -} - -impl Node for TextureNode { - fn output(&self) -> &[ResourceSlotInfo] { - static OUTPUT: &[ResourceSlotInfo] = &[ResourceSlotInfo { - name: Cow::Borrowed(TextureNode::TEXTURE), - resource_type: RenderResourceType::Texture, - }]; - OUTPUT - } - - fn update( - &mut self, - _world: &World, - render_context: &mut dyn RenderContext, - _input: &ResourceSlots, - output: &mut ResourceSlots, - ) { - if output.get(0).is_none() { - let render_resource_context = render_context.resources_mut(); - let texture_id = render_resource_context.create_texture(self.texture_descriptor); - if let Some(handle) = &self.handle { - render_resource_context.set_asset_resource_untyped( - handle.clone(), - RenderResourceId::Texture(texture_id), - TEXTURE_ASSET_INDEX, - ); - if let Some(sampler_descriptor) = self.sampler_descriptor { - let sampler_id = render_resource_context.create_sampler(&sampler_descriptor); - render_resource_context.set_asset_resource_untyped( - handle.clone(), - RenderResourceId::Sampler(sampler_id), - SAMPLER_ASSET_INDEX, - ); - } - } - output.set(0, RenderResourceId::Texture(texture_id)); - } - } -} diff --git a/crates/bevy_render/src/render_graph/nodes/window_swapchain_node.rs b/crates/bevy_render/src/render_graph/nodes/window_swapchain_node.rs deleted file mode 100644 index 52c7904e42194..0000000000000 --- a/crates/bevy_render/src/render_graph/nodes/window_swapchain_node.rs +++ /dev/null @@ -1,74 +0,0 @@ -use crate::{ - render_graph::{Node, ResourceSlotInfo, ResourceSlots}, - renderer::{RenderContext, RenderResourceId, RenderResourceType}, -}; -use bevy_app::{Events, ManualEventReader}; -use bevy_ecs::world::World; -use bevy_window::{WindowCreated, WindowId, WindowResized, Windows}; -use std::borrow::Cow; - -pub struct WindowSwapChainNode { - window_id: WindowId, - window_created_event_reader: ManualEventReader, - window_resized_event_reader: ManualEventReader, -} - -impl WindowSwapChainNode { - pub const OUT_TEXTURE: &'static str = "texture"; - - pub fn new(window_id: WindowId) -> Self { - WindowSwapChainNode { - window_id, - window_created_event_reader: Default::default(), - window_resized_event_reader: Default::default(), - } - } -} - -impl Node for WindowSwapChainNode { - fn output(&self) -> &[ResourceSlotInfo] { - static OUTPUT: &[ResourceSlotInfo] = &[ResourceSlotInfo { - name: Cow::Borrowed(WindowSwapChainNode::OUT_TEXTURE), - resource_type: RenderResourceType::Texture, - }]; - OUTPUT - } - - fn update( - &mut self, - world: &World, - render_context: &mut dyn RenderContext, - _input: &ResourceSlots, - output: &mut ResourceSlots, - ) { - const WINDOW_TEXTURE: usize = 0; - let window_created_events = world.get_resource::>().unwrap(); - let window_resized_events = world.get_resource::>().unwrap(); - let windows = world.get_resource::().unwrap(); - - let window = windows - .get(self.window_id) - .expect("Window swapchain node refers to a non-existent window."); - - let render_resource_context = render_context.resources_mut(); - - // reconfigure surface window is resized or created - if self - .window_created_event_reader - .iter(window_created_events) - .any(|e| e.id == window.id()) - || self - .window_resized_event_reader - .iter(window_resized_events) - .any(|e| e.id == window.id()) - { - render_resource_context.configure_surface(window); - } - - let swap_chain_texture = render_resource_context.next_surface_frame(window); - output.set( - WINDOW_TEXTURE, - RenderResourceId::Texture(swap_chain_texture), - ); - } -} diff --git a/crates/bevy_render/src/render_graph/nodes/window_texture_node.rs b/crates/bevy_render/src/render_graph/nodes/window_texture_node.rs deleted file mode 100644 index 92fc94da769cf..0000000000000 --- a/crates/bevy_render/src/render_graph/nodes/window_texture_node.rs +++ /dev/null @@ -1,76 +0,0 @@ -use crate::{ - render_graph::{Node, ResourceSlotInfo, ResourceSlots}, - renderer::{RenderContext, RenderResourceId, RenderResourceType}, - texture::TextureDescriptor, -}; -use bevy_app::{Events, ManualEventReader}; -use bevy_ecs::world::World; -use bevy_window::{WindowCreated, WindowId, WindowResized, Windows}; -use std::borrow::Cow; - -pub struct WindowTextureNode { - window_id: WindowId, - descriptor: TextureDescriptor, - window_created_event_reader: ManualEventReader, - window_resized_event_reader: ManualEventReader, -} - -impl WindowTextureNode { - pub const OUT_TEXTURE: &'static str = "texture"; - - pub fn new(window_id: WindowId, descriptor: TextureDescriptor) -> Self { - WindowTextureNode { - window_id, - descriptor, - window_created_event_reader: Default::default(), - window_resized_event_reader: Default::default(), - } - } -} - -impl Node for WindowTextureNode { - fn output(&self) -> &[ResourceSlotInfo] { - static OUTPUT: &[ResourceSlotInfo] = &[ResourceSlotInfo { - name: Cow::Borrowed(WindowTextureNode::OUT_TEXTURE), - resource_type: RenderResourceType::Texture, - }]; - OUTPUT - } - - fn update( - &mut self, - world: &World, - render_context: &mut dyn RenderContext, - _input: &ResourceSlots, - output: &mut ResourceSlots, - ) { - const WINDOW_TEXTURE: usize = 0; - let window_created_events = world.get_resource::>().unwrap(); - let window_resized_events = world.get_resource::>().unwrap(); - let windows = world.get_resource::().unwrap(); - - let window = windows - .get(self.window_id) - .expect("Window texture node refers to a non-existent window."); - - if self - .window_created_event_reader - .iter(window_created_events) - .any(|e| e.id == window.id()) - || self - .window_resized_event_reader - .iter(window_resized_events) - .any(|e| e.id == window.id()) - { - let render_resource_context = render_context.resources_mut(); - if let Some(RenderResourceId::Texture(old_texture)) = output.get(WINDOW_TEXTURE) { - render_resource_context.remove_texture(old_texture); - } - - self.descriptor.size.width = window.physical_width().max(1); - self.descriptor.size.height = window.physical_height().max(1); - let texture_resource = render_resource_context.create_texture(self.descriptor); - output.set(WINDOW_TEXTURE, RenderResourceId::Texture(texture_resource)); - } - } -} diff --git a/crates/bevy_render/src/render_graph/schedule.rs b/crates/bevy_render/src/render_graph/schedule.rs deleted file mode 100644 index eb0c98077c5b1..0000000000000 --- a/crates/bevy_render/src/render_graph/schedule.rs +++ /dev/null @@ -1,549 +0,0 @@ -use super::{NodeId, NodeState, RenderGraph, RenderGraphError}; -use bevy_utils::HashMap; -use thiserror::Error; - -#[derive(Error, Debug)] -pub enum StagerError { - // This might have to be `:` tagged at the end. - #[error("encountered a `RenderGraphError`")] - RenderGraphError(#[from] RenderGraphError), -} - -#[derive(Default, Debug, Eq, PartialEq)] -pub struct Stage { - pub jobs: Vec, -} - -#[derive(Default, Debug, Eq, PartialEq)] -pub struct OrderedJob { - pub nodes: Vec, -} - -#[derive(Default, Debug)] -pub struct StageBorrow<'a> { - pub jobs: Vec>, -} - -#[derive(Default, Debug)] -pub struct OrderedJobBorrow<'a> { - pub node_states: Vec<&'a mut NodeState>, -} - -#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] -struct NodeIndices { - stage: usize, - job: usize, - node: usize, -} - -#[derive(Default, Debug)] -pub struct Stages { - stages: Vec, - /// a collection of node indices that are used to efficiently borrow render graph nodes - node_indices: HashMap, -} - -impl Stages { - pub fn new(stages: Vec) -> Self { - let mut node_indices = HashMap::default(); - for (stage_index, stage) in stages.iter().enumerate() { - for (job_index, job) in stage.jobs.iter().enumerate() { - for (node_index, node) in job.nodes.iter().enumerate() { - node_indices.insert( - *node, - NodeIndices { - stage: stage_index, - job: job_index, - node: node_index, - }, - ); - } - } - } - Stages { - stages, - node_indices, - } - } - - pub fn borrow<'a>(&self, render_graph: &'a mut RenderGraph) -> Vec> { - // unfortunately borrowing render graph nodes in a specific order takes a little bit of - // gymnastics - let mut stage_borrows = Vec::with_capacity(self.stages.len()); - - let mut node_borrows = Vec::new(); - for node in render_graph.iter_nodes_mut() { - let indices = self.node_indices.get(&node.id).unwrap(); - node_borrows.push((node, indices)); - } - - node_borrows.sort_by_key(|(_node, indices)| <&NodeIndices>::clone(indices)); - let mut last_stage = usize::MAX; - let mut last_job = usize::MAX; - for (node, indices) in node_borrows.drain(..) { - if last_stage != indices.stage { - stage_borrows.push(StageBorrow::default()); - last_job = usize::MAX; - } - - let stage = &mut stage_borrows[indices.stage]; - if last_job != indices.job { - stage.jobs.push(OrderedJobBorrow::default()); - } - - let job = &mut stage.jobs[indices.job]; - job.node_states.push(node); - - last_stage = indices.stage; - last_job = indices.job; - } - - stage_borrows - } -} - -/// Produces a collection of `Stages`, which are sets of OrderedJobs that must be run before moving -/// on to the next stage -pub trait RenderGraphStager { - fn get_stages(&mut self, render_graph: &RenderGraph) -> Result; -} - -// TODO: remove this -/// This scheduler ignores dependencies and puts everything in one stage. It shouldn't be used for -/// anything :) -#[derive(Debug, Default)] -pub struct LinearStager; - -impl RenderGraphStager for LinearStager { - fn get_stages(&mut self, render_graph: &RenderGraph) -> Result { - let mut stage = Stage::default(); - let mut job = OrderedJob::default(); - for node in render_graph.iter_nodes() { - job.nodes.push(node.id); - } - - stage.jobs.push(job); - - Ok(Stages::new(vec![stage])) - } -} - -#[derive(Debug, Copy, Clone)] -/// Determines the grouping strategy used when constructing graph stages -pub enum JobGrouping { - /// Default to adding the current node to a new job in its assigned stage. This results - /// in a "loose" pack that is easier to parallelize but has more jobs - Loose, - /// Default to adding the current node into the first job in its assigned stage. This results - /// in a "tight" pack that is harder to parallelize but results in fewer jobs - Tight, -} - -#[derive(Debug)] -/// Produces Render Graph stages and jobs in a way that ensures node dependencies are respected. -pub struct DependentNodeStager { - job_grouping: JobGrouping, -} - -impl DependentNodeStager { - pub fn loose_grouping() -> Self { - DependentNodeStager { - job_grouping: JobGrouping::Loose, - } - } - - pub fn tight_grouping() -> Self { - DependentNodeStager { - job_grouping: JobGrouping::Tight, - } - } -} - -impl RenderGraphStager for DependentNodeStager { - fn get_stages<'a>(&mut self, render_graph: &RenderGraph) -> Result { - // get all nodes without input. this intentionally includes nodes with no outputs - let output_only_nodes = render_graph - .iter_nodes() - .filter(|node| node.input_slots.is_empty()); - let mut stages = vec![Stage::default()]; - let mut node_stages = HashMap::default(); - for output_only_node in output_only_nodes { - // each "output only" node should start a new job on the first stage - stage_node( - render_graph, - &mut stages, - &mut node_stages, - output_only_node, - self.job_grouping, - ); - } - - Ok(Stages::new(stages)) - } -} - -fn stage_node( - graph: &RenderGraph, - stages: &mut Vec, - node_stages_and_jobs: &mut HashMap, - node: &NodeState, - job_grouping: JobGrouping, -) { - // don't re-visit nodes or visit them before all of their parents have been visited - if node_stages_and_jobs.contains_key(&node.id) - || node - .edges - .input_edges - .iter() - .any(|e| !node_stages_and_jobs.contains_key(&e.get_output_node())) - { - return; - } - - // by default assume we are creating a new job on a new stage - let mut stage_index = 0; - let mut job_index = match job_grouping { - JobGrouping::Tight => Some(0), - JobGrouping::Loose => None, - }; - - // check to see if the current node has a parent. if so, grab the parent with the highest stage - if let Some((max_parent_stage, max_parent_job)) = node - .edges - .input_edges - .iter() - .map(|e| { - node_stages_and_jobs - .get(&e.get_output_node()) - .expect("Already checked that parents were visited.") - }) - .max() - { - // count the number of parents that are in the highest stage - let max_stage_parent_count = node - .edges - .input_edges - .iter() - .filter(|e| { - let (max_stage, _) = node_stages_and_jobs - .get(&e.get_output_node()) - .expect("Already checked that parents were visited."); - max_stage == max_parent_stage - }) - .count(); - - // if the current node has more than one parent on the highest stage (aka requires - // synchronization), then move it to the next stage and start a new job on that - // stage - if max_stage_parent_count > 1 { - stage_index = max_parent_stage + 1; - } else { - stage_index = *max_parent_stage; - job_index = Some(*max_parent_job); - } - } - - if stage_index == stages.len() { - stages.push(Stage::default()); - } - - let stage = &mut stages[stage_index]; - - let job_index = job_index.unwrap_or_else(|| stage.jobs.len()); - if job_index == stage.jobs.len() { - stage.jobs.push(OrderedJob::default()); - } - - let job = &mut stage.jobs[job_index]; - job.nodes.push(node.id); - - node_stages_and_jobs.insert(node.id, (stage_index, job_index)); - - for (_edge, node) in graph.iter_node_outputs(node.id).unwrap() { - stage_node(graph, stages, node_stages_and_jobs, node, job_grouping); - } -} - -#[cfg(test)] -mod tests { - use super::{DependentNodeStager, OrderedJob, RenderGraphStager, Stage}; - use crate::{ - render_graph::{Node, NodeId, RenderGraph, ResourceSlotInfo, ResourceSlots}, - renderer::{RenderContext, RenderResourceType}, - }; - use bevy_ecs::world::World; - - struct TestNode { - inputs: Vec, - outputs: Vec, - } - - impl TestNode { - pub fn new(inputs: usize, outputs: usize) -> Self { - TestNode { - inputs: (0..inputs) - .map(|i| ResourceSlotInfo { - name: format!("in_{}", i).into(), - resource_type: RenderResourceType::Texture, - }) - .collect(), - outputs: (0..outputs) - .map(|i| ResourceSlotInfo { - name: format!("out_{}", i).into(), - resource_type: RenderResourceType::Texture, - }) - .collect(), - } - } - } - - impl Node for TestNode { - fn input(&self) -> &[ResourceSlotInfo] { - &self.inputs - } - - fn output(&self) -> &[ResourceSlotInfo] { - &self.outputs - } - - fn update( - &mut self, - _: &World, - _: &mut dyn RenderContext, - _: &ResourceSlots, - _: &mut ResourceSlots, - ) { - } - } - - #[test] - fn test_render_graph_dependency_stager_loose() { - let mut graph = RenderGraph::default(); - - // Setup graph to look like this: - // - // A -> B -> C -> D - // / / - // E F -> G - // - // H -> I -> J - - let a_id = graph.add_node("A", TestNode::new(0, 1)); - let b_id = graph.add_node("B", TestNode::new(2, 1)); - let c_id = graph.add_node("C", TestNode::new(2, 1)); - let d_id = graph.add_node("D", TestNode::new(1, 0)); - let e_id = graph.add_node("E", TestNode::new(0, 1)); - let f_id = graph.add_node("F", TestNode::new(0, 2)); - let g_id = graph.add_node("G", TestNode::new(1, 0)); - let h_id = graph.add_node("H", TestNode::new(0, 1)); - let i_id = graph.add_node("I", TestNode::new(1, 1)); - let j_id = graph.add_node("J", TestNode::new(1, 0)); - - graph.add_node_edge("A", "B").unwrap(); - graph.add_node_edge("B", "C").unwrap(); - graph.add_node_edge("C", "D").unwrap(); - graph.add_node_edge("E", "B").unwrap(); - graph.add_node_edge("F", "C").unwrap(); - graph.add_node_edge("F", "G").unwrap(); - graph.add_node_edge("H", "I").unwrap(); - graph.add_node_edge("I", "J").unwrap(); - - let mut stager = DependentNodeStager::loose_grouping(); - let mut stages = stager.get_stages(&graph).unwrap(); - - // Expected Stages: - // (X indicates nodes that are not part of that stage) - - // Stage 1 - // A -> X -> X -> X - // / / - // E F -> G - // - // H -> I -> J - - // Stage 2 - // X -> B -> C -> D - // / / - // X X -> X - // - // X -> X -> X - - let mut expected_stages = vec![ - Stage { - jobs: vec![ - OrderedJob { - nodes: vec![f_id, g_id], - }, - OrderedJob { nodes: vec![a_id] }, - OrderedJob { nodes: vec![e_id] }, - OrderedJob { - nodes: vec![h_id, i_id, j_id], - }, - ], - }, - Stage { - jobs: vec![OrderedJob { - nodes: vec![b_id, c_id, d_id], - }], - }, - ]; - - // ensure job order lines up within stages (this can vary due to hash maps) - // jobs within a stage are unordered conceptually so this is ok - expected_stages - .iter_mut() - .for_each(|stage| stage.jobs.sort_by_key(|job| job.nodes[0])); - - stages - .stages - .iter_mut() - .for_each(|stage| stage.jobs.sort_by_key(|job| job.nodes[0])); - - assert_eq!( - stages.stages, expected_stages, - "stages should be loosely grouped" - ); - - let mut borrowed = stages.borrow(&mut graph); - // ensure job order lines up within stages (this can vary due to hash maps) - // jobs within a stage are unordered conceptually so this is ok - borrowed - .iter_mut() - .for_each(|stage| stage.jobs.sort_by_key(|job| job.node_states[0].id)); - - assert_eq!( - borrowed.len(), - expected_stages.len(), - "same number of stages" - ); - for (stage_index, borrowed_stage) in borrowed.iter().enumerate() { - assert_eq!( - borrowed_stage.jobs.len(), - stages.stages[stage_index].jobs.len(), - "job length matches" - ); - for (job_index, borrowed_job) in borrowed_stage.jobs.iter().enumerate() { - assert_eq!( - borrowed_job.node_states.len(), - stages.stages[stage_index].jobs[job_index].nodes.len(), - "node length matches" - ); - for (node_index, borrowed_node) in borrowed_job.node_states.iter().enumerate() { - assert_eq!( - borrowed_node.id, - stages.stages[stage_index].jobs[job_index].nodes[node_index] - ); - } - } - } - } - - #[test] - fn test_render_graph_dependency_stager_tight() { - let mut graph = RenderGraph::default(); - - // Setup graph to look like this: - // - // A -> B -> C -> D - // / / - // E F -> G - // - // H -> I -> J - - let _a_id = graph.add_node("A", TestNode::new(0, 1)); - let b_id = graph.add_node("B", TestNode::new(2, 1)); - let c_id = graph.add_node("C", TestNode::new(2, 1)); - let d_id = graph.add_node("D", TestNode::new(1, 0)); - let _e_id = graph.add_node("E", TestNode::new(0, 1)); - let f_id = graph.add_node("F", TestNode::new(0, 2)); - let g_id = graph.add_node("G", TestNode::new(1, 0)); - let h_id = graph.add_node("H", TestNode::new(0, 1)); - let i_id = graph.add_node("I", TestNode::new(1, 1)); - let j_id = graph.add_node("J", TestNode::new(1, 0)); - - graph.add_node_edge("A", "B").unwrap(); - graph.add_node_edge("B", "C").unwrap(); - graph.add_node_edge("C", "D").unwrap(); - graph.add_node_edge("E", "B").unwrap(); - graph.add_node_edge("F", "C").unwrap(); - graph.add_node_edge("F", "G").unwrap(); - graph.add_node_edge("H", "I").unwrap(); - graph.add_node_edge("I", "J").unwrap(); - - let mut stager = DependentNodeStager::tight_grouping(); - let mut stages = stager.get_stages(&graph).unwrap(); - - // Expected Stages: - // (X indicates nodes that are not part of that stage) - - // Stage 1 - // A -> X -> X -> X - // / / - // E F -> G - // - // H -> I -> J - - // Stage 2 - // X -> B -> C -> D - // / / - // X X -> X - // - // X -> X -> X - - assert_eq!(stages.stages[0].jobs.len(), 1, "expect exactly 1 job"); - - let job = &stages.stages[0].jobs[0]; - - assert_eq!(job.nodes.len(), 7, "expect exactly 7 nodes in the job"); - - // its hard to test the exact order of this job's nodes because of hashing, so instead we'll - // test the constraints that must hold true - let index = - |node_id: NodeId| -> usize { job.nodes.iter().position(|id| *id == node_id).unwrap() }; - - assert!(index(f_id) < index(g_id)); - assert!(index(h_id) < index(i_id)); - assert!(index(i_id) < index(j_id)); - - let expected_stage_1 = Stage { - jobs: vec![OrderedJob { - nodes: vec![b_id, c_id, d_id], - }], - }; - - assert_eq!(stages.stages[1], expected_stage_1,); - - let mut borrowed = stages.borrow(&mut graph); - // ensure job order lines up within stages (this can vary due to hash maps) - // jobs within a stage are unordered conceptually so this is ok - stages - .stages - .iter_mut() - .for_each(|stage| stage.jobs.sort_by_key(|job| job.nodes[0])); - borrowed - .iter_mut() - .for_each(|stage| stage.jobs.sort_by_key(|job| job.node_states[0].id)); - - assert_eq!(borrowed.len(), 2, "same number of stages"); - for (stage_index, borrowed_stage) in borrowed.iter().enumerate() { - assert_eq!( - borrowed_stage.jobs.len(), - stages.stages[stage_index].jobs.len(), - "job length matches" - ); - for (job_index, borrowed_job) in borrowed_stage.jobs.iter().enumerate() { - assert_eq!( - borrowed_job.node_states.len(), - stages.stages[stage_index].jobs[job_index].nodes.len(), - "node length matches" - ); - for (node_index, borrowed_node) in borrowed_job.node_states.iter().enumerate() { - assert_eq!( - borrowed_node.id, - stages.stages[stage_index].jobs[job_index].nodes[node_index] - ); - } - } - } - } -} diff --git a/crates/bevy_render/src/render_graph/system.rs b/crates/bevy_render/src/render_graph/system.rs deleted file mode 100644 index 650e318c4ab59..0000000000000 --- a/crates/bevy_render/src/render_graph/system.rs +++ /dev/null @@ -1,18 +0,0 @@ -use super::RenderGraph; -use bevy_ecs::{schedule::Stage, world::World}; - -pub fn render_graph_schedule_executor_system(world: &mut World) { - // run render graph systems - let mut system_schedule = { - let mut render_graph = world.get_resource_mut::().unwrap(); - render_graph.take_schedule() - }; - - if let Some(schedule) = system_schedule.as_mut() { - schedule.run(world); - } - let mut render_graph = world.get_resource_mut::().unwrap(); - if let Some(schedule) = system_schedule.take() { - render_graph.set_schedule(schedule); - } -} diff --git a/pipelined/bevy_render2/src/render_phase/draw.rs b/crates/bevy_render/src/render_phase/draw.rs similarity index 100% rename from pipelined/bevy_render2/src/render_phase/draw.rs rename to crates/bevy_render/src/render_phase/draw.rs diff --git a/pipelined/bevy_render2/src/render_phase/draw_state.rs b/crates/bevy_render/src/render_phase/draw_state.rs similarity index 100% rename from pipelined/bevy_render2/src/render_phase/draw_state.rs rename to crates/bevy_render/src/render_phase/draw_state.rs diff --git a/pipelined/bevy_render2/src/render_phase/mod.rs b/crates/bevy_render/src/render_phase/mod.rs similarity index 100% rename from pipelined/bevy_render2/src/render_phase/mod.rs rename to crates/bevy_render/src/render_phase/mod.rs diff --git a/pipelined/bevy_render2/src/render_resource/bind_group.rs b/crates/bevy_render/src/render_resource/bind_group.rs similarity index 100% rename from pipelined/bevy_render2/src/render_resource/bind_group.rs rename to crates/bevy_render/src/render_resource/bind_group.rs diff --git a/pipelined/bevy_render2/src/render_resource/bind_group_layout.rs b/crates/bevy_render/src/render_resource/bind_group_layout.rs similarity index 100% rename from pipelined/bevy_render2/src/render_resource/bind_group_layout.rs rename to crates/bevy_render/src/render_resource/bind_group_layout.rs diff --git a/pipelined/bevy_render2/src/render_resource/buffer.rs b/crates/bevy_render/src/render_resource/buffer.rs similarity index 100% rename from pipelined/bevy_render2/src/render_resource/buffer.rs rename to crates/bevy_render/src/render_resource/buffer.rs diff --git a/pipelined/bevy_render2/src/render_resource/buffer_vec.rs b/crates/bevy_render/src/render_resource/buffer_vec.rs similarity index 100% rename from pipelined/bevy_render2/src/render_resource/buffer_vec.rs rename to crates/bevy_render/src/render_resource/buffer_vec.rs diff --git a/pipelined/bevy_render2/src/render_resource/mod.rs b/crates/bevy_render/src/render_resource/mod.rs similarity index 77% rename from pipelined/bevy_render2/src/render_resource/mod.rs rename to crates/bevy_render/src/render_resource/mod.rs index 8bf500c7d75eb..22232f4a2695c 100644 --- a/pipelined/bevy_render2/src/render_resource/mod.rs +++ b/crates/bevy_render/src/render_resource/mod.rs @@ -27,10 +27,11 @@ pub use wgpu::{ BlendFactor, BlendOperation, BlendState, BufferAddress, BufferBindingType, BufferSize, BufferUsages, ColorTargetState, ColorWrites, CommandEncoder, CommandEncoderDescriptor, CompareFunction, ComputePassDescriptor, ComputePipelineDescriptor, DepthBiasState, - DepthStencilState, Extent3d, Face, FilterMode, FragmentState as RawFragmentState, FrontFace, - ImageCopyBuffer, ImageCopyBufferBase, ImageCopyTexture, ImageCopyTextureBase, ImageDataLayout, - ImageSubresourceRange, IndexFormat, LoadOp, MultisampleState, Operations, Origin3d, - PipelineLayout, PipelineLayoutDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology, + DepthStencilState, Extent3d, Face, Features as WgpuFeatures, FilterMode, + FragmentState as RawFragmentState, FrontFace, ImageCopyBuffer, ImageCopyBufferBase, + ImageCopyTexture, ImageCopyTextureBase, ImageDataLayout, ImageSubresourceRange, IndexFormat, + Limits as WgpuLimits, LoadOp, MultisampleState, Operations, Origin3d, PipelineLayout, + PipelineLayoutDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology, RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor, RenderPipelineDescriptor as RawRenderPipelineDescriptor, SamplerDescriptor, ShaderModule, ShaderModuleDescriptor, ShaderSource, ShaderStages, StencilFaceState, StencilOperation, diff --git a/pipelined/bevy_render2/src/render_resource/pipeline.rs b/crates/bevy_render/src/render_resource/pipeline.rs similarity index 100% rename from pipelined/bevy_render2/src/render_resource/pipeline.rs rename to crates/bevy_render/src/render_resource/pipeline.rs diff --git a/pipelined/bevy_render2/src/render_resource/pipeline_cache.rs b/crates/bevy_render/src/render_resource/pipeline_cache.rs similarity index 100% rename from pipelined/bevy_render2/src/render_resource/pipeline_cache.rs rename to crates/bevy_render/src/render_resource/pipeline_cache.rs diff --git a/pipelined/bevy_render2/src/render_resource/pipeline_specializer.rs b/crates/bevy_render/src/render_resource/pipeline_specializer.rs similarity index 100% rename from pipelined/bevy_render2/src/render_resource/pipeline_specializer.rs rename to crates/bevy_render/src/render_resource/pipeline_specializer.rs diff --git a/pipelined/bevy_render2/src/render_resource/shader.rs b/crates/bevy_render/src/render_resource/shader.rs similarity index 100% rename from pipelined/bevy_render2/src/render_resource/shader.rs rename to crates/bevy_render/src/render_resource/shader.rs diff --git a/pipelined/bevy_render2/src/render_resource/texture.rs b/crates/bevy_render/src/render_resource/texture.rs similarity index 100% rename from pipelined/bevy_render2/src/render_resource/texture.rs rename to crates/bevy_render/src/render_resource/texture.rs diff --git a/pipelined/bevy_render2/src/render_resource/uniform_vec.rs b/crates/bevy_render/src/render_resource/uniform_vec.rs similarity index 100% rename from pipelined/bevy_render2/src/render_resource/uniform_vec.rs rename to crates/bevy_render/src/render_resource/uniform_vec.rs diff --git a/pipelined/bevy_render2/src/renderer/graph_runner.rs b/crates/bevy_render/src/renderer/graph_runner.rs similarity index 100% rename from pipelined/bevy_render2/src/renderer/graph_runner.rs rename to crates/bevy_render/src/renderer/graph_runner.rs diff --git a/crates/bevy_render/src/renderer/headless_render_resource_context.rs b/crates/bevy_render/src/renderer/headless_render_resource_context.rs deleted file mode 100644 index 89f6c2e726dda..0000000000000 --- a/crates/bevy_render/src/renderer/headless_render_resource_context.rs +++ /dev/null @@ -1,174 +0,0 @@ -use super::RenderResourceContext; -use crate::{ - pipeline::{BindGroupDescriptorId, PipelineDescriptor}, - renderer::{ - BindGroup, BufferId, BufferInfo, BufferMapMode, RenderResourceId, SamplerId, TextureId, - }, - shader::{Shader, ShaderError}, - texture::{SamplerDescriptor, TextureDescriptor}, -}; -use bevy_asset::{Assets, Handle, HandleUntyped}; -use bevy_utils::HashMap; -use bevy_window::Window; -use parking_lot::RwLock; -use std::{ops::Range, sync::Arc}; - -#[derive(Debug, Default)] -pub struct HeadlessRenderResourceContext { - buffer_info: Arc>>, - texture_descriptors: Arc>>, - pub asset_resources: Arc>>, -} - -impl HeadlessRenderResourceContext { - pub fn add_buffer_info(&self, buffer: BufferId, info: BufferInfo) { - self.buffer_info.write().insert(buffer, info); - } - - pub fn add_texture_descriptor(&self, texture: TextureId, descriptor: TextureDescriptor) { - self.texture_descriptors.write().insert(texture, descriptor); - } -} - -impl RenderResourceContext for HeadlessRenderResourceContext { - fn configure_surface(&self, _window: &Window) {} - - fn next_surface_frame(&self, _window: &Window) -> TextureId { - TextureId::new() - } - - fn drop_surface_frame(&self, _render_resource: TextureId) {} - - fn drop_all_surface_frames(&self) {} - - fn create_sampler(&self, _sampler_descriptor: &SamplerDescriptor) -> SamplerId { - SamplerId::new() - } - - fn create_texture(&self, texture_descriptor: TextureDescriptor) -> TextureId { - let texture = TextureId::new(); - self.add_texture_descriptor(texture, texture_descriptor); - texture - } - - fn create_buffer(&self, buffer_info: BufferInfo) -> BufferId { - let buffer = BufferId::new(); - self.add_buffer_info(buffer, buffer_info); - buffer - } - - fn write_mapped_buffer( - &self, - id: BufferId, - _range: Range, - write: &mut dyn FnMut(&mut [u8], &dyn RenderResourceContext), - ) { - let size = self.buffer_info.read().get(&id).unwrap().size; - let mut buffer = vec![0; size]; - write(&mut buffer, self); - } - - fn read_mapped_buffer( - &self, - id: BufferId, - _range: Range, - read: &dyn Fn(&[u8], &dyn RenderResourceContext), - ) { - let size = self.buffer_info.read().get(&id).unwrap().size; - let buffer = vec![0; size]; - read(&buffer, self); - } - - fn map_buffer(&self, _id: BufferId, _mode: BufferMapMode) {} - - fn unmap_buffer(&self, _id: BufferId) {} - - fn create_buffer_with_data(&self, buffer_info: BufferInfo, _data: &[u8]) -> BufferId { - let buffer = BufferId::new(); - self.add_buffer_info(buffer, buffer_info); - buffer - } - - fn create_shader_module(&self, _shader_handle: &Handle, _shaders: &Assets) {} - - fn remove_buffer(&self, buffer: BufferId) { - self.buffer_info.write().remove(&buffer); - } - - fn remove_texture(&self, texture: TextureId) { - self.texture_descriptors.write().remove(&texture); - } - - fn remove_sampler(&self, _sampler: SamplerId) {} - - fn set_asset_resource_untyped( - &self, - handle: HandleUntyped, - render_resource: RenderResourceId, - index: u64, - ) { - self.asset_resources - .write() - .insert((handle, index), render_resource); - } - - fn get_asset_resource_untyped( - &self, - handle: HandleUntyped, - index: u64, - ) -> Option { - self.asset_resources.write().get(&(handle, index)).cloned() - } - - fn create_render_pipeline( - &self, - _pipeline_handle: Handle, - _pipeline_descriptor: &PipelineDescriptor, - _shaders: &Assets, - ) { - } - - fn create_bind_group( - &self, - _bind_group_descriptor_id: BindGroupDescriptorId, - _bind_group: &BindGroup, - ) { - } - - fn create_shader_module_from_source(&self, _shader_handle: &Handle, _shader: &Shader) {} - - fn remove_asset_resource_untyped(&self, handle: HandleUntyped, index: u64) { - self.asset_resources.write().remove(&(handle, index)); - } - - fn clear_bind_groups(&self) {} - - fn get_buffer_info(&self, buffer: BufferId) -> Option { - self.buffer_info.read().get(&buffer).cloned() - } - - fn bind_group_descriptor_exists( - &self, - _bind_group_descriptor_id: BindGroupDescriptorId, - ) -> bool { - false - } - - fn get_aligned_uniform_size(&self, size: usize, _dynamic: bool) -> usize { - size - } - - fn get_aligned_texture_size(&self, size: usize) -> usize { - size - } - - fn get_specialized_shader( - &self, - shader: &Shader, - _macros: Option<&[String]>, - ) -> Result { - Ok(shader.clone()) - } - - fn remove_stale_bind_groups(&self) {} -} diff --git a/crates/bevy_render/src/renderer/mod.rs b/crates/bevy_render/src/renderer/mod.rs index fe3bf2347c77b..6538b1f604817 100644 --- a/crates/bevy_render/src/renderer/mod.rs +++ b/crates/bevy_render/src/renderer/mod.rs @@ -1,9 +1,104 @@ -mod headless_render_resource_context; -mod render_context; -mod render_resource; -mod render_resource_context; - -pub use headless_render_resource_context::*; -pub use render_context::*; -pub use render_resource::*; -pub use render_resource_context::*; +mod graph_runner; +mod render_device; + +use bevy_utils::tracing::{info, info_span}; +pub use graph_runner::*; +pub use render_device::*; + +use crate::{ + render_graph::RenderGraph, + view::{ExtractedWindows, ViewTarget}, +}; +use bevy_ecs::prelude::*; +use std::sync::Arc; +use wgpu::{CommandEncoder, DeviceDescriptor, Instance, Queue, RequestAdapterOptions}; + +/// Updates the [`RenderGraph`] with all of its nodes and then runs it to render the entire frame. +pub fn render_system(world: &mut World) { + world.resource_scope(|world, mut graph: Mut| { + graph.update(world); + }); + let graph = world.get_resource::().unwrap(); + let render_device = world.get_resource::().unwrap(); + let render_queue = world.get_resource::().unwrap(); + RenderGraphRunner::run( + graph, + render_device.clone(), // TODO: is this clone really necessary? + render_queue, + world, + ) + .unwrap(); + { + let span = info_span!("present_frames"); + let _guard = span.enter(); + + // Remove ViewTarget components to ensure swap chain TextureViews are dropped. + // If all TextureViews aren't dropped before present, acquiring the next swap chain texture will fail. + let view_entities = world + .query_filtered::>() + .iter(world) + .collect::>(); + for view_entity in view_entities { + world.entity_mut(view_entity).remove::(); + } + + let mut windows = world.get_resource_mut::().unwrap(); + for window in windows.values_mut() { + if let Some(texture_view) = window.swap_chain_texture.take() { + if let Some(surface_texture) = texture_view.take_surface_texture() { + surface_texture.present(); + } + } + } + } +} + +/// This queue is used to enqueue tasks for the GPU to execute asynchronously. +pub type RenderQueue = Arc; + +/// The GPU instance is used to initialize the [`RenderQueue`] and [`RenderDevice`], +/// aswell as to create [`WindowSurfaces`](crate::view::window::WindowSurfaces). +pub type RenderInstance = Instance; + +/// Initializes the renderer by retrieving and preparing the GPU instance, device and queue +/// for the specified backend. +pub async fn initialize_renderer( + instance: &Instance, + request_adapter_options: &RequestAdapterOptions<'_>, + device_descriptor: &DeviceDescriptor<'_>, +) -> (RenderDevice, RenderQueue) { + let adapter = instance + .request_adapter(request_adapter_options) + .await + .expect("Unable to find a GPU! Make sure you have installed required drivers!"); + + #[cfg(not(target_arch = "wasm32"))] + info!("{:?}", adapter.get_info()); + + #[cfg(feature = "wgpu_trace")] + let trace_path = { + let path = std::path::Path::new("wgpu_trace"); + // ignore potential error, wgpu will log it + let _ = std::fs::create_dir(path); + Some(path) + }; + #[cfg(not(feature = "wgpu_trace"))] + let trace_path = None; + + let (device, queue) = adapter + .request_device(device_descriptor, trace_path) + .await + .unwrap(); + let device = Arc::new(device); + let queue = Arc::new(queue); + (RenderDevice::from(device), queue) +} + +/// The context with all information required to interact with the GPU. +/// +/// The [`RenderDevice`] is used to create render resources and the +/// the [`CommandEncoder`] is used to record a series of GPU operations. +pub struct RenderContext { + pub render_device: RenderDevice, + pub command_encoder: CommandEncoder, +} diff --git a/crates/bevy_render/src/renderer/render_context.rs b/crates/bevy_render/src/renderer/render_context.rs deleted file mode 100644 index 5caaf892ab9eb..0000000000000 --- a/crates/bevy_render/src/renderer/render_context.rs +++ /dev/null @@ -1,62 +0,0 @@ -use downcast_rs::{impl_downcast, Downcast}; - -use super::RenderResourceContext; -use crate::{ - pass::{PassDescriptor, RenderPass}, - renderer::{BufferId, RenderResourceBindings, TextureId}, - texture::Extent3d, -}; - -pub trait RenderContext: Downcast { - fn resources(&self) -> &dyn RenderResourceContext; - fn resources_mut(&mut self) -> &mut dyn RenderResourceContext; - fn copy_buffer_to_buffer( - &mut self, - source_buffer: BufferId, - source_offset: u64, - destination_buffer: BufferId, - destination_offset: u64, - size: u64, - ); - #[allow(clippy::too_many_arguments)] - fn copy_buffer_to_texture( - &mut self, - source_buffer: BufferId, - source_offset: u64, - source_bytes_per_row: u32, - destination_texture: TextureId, - destination_origin: [u32; 3], - destination_mip_level: u32, - size: Extent3d, - ); - #[allow(clippy::too_many_arguments)] - fn copy_texture_to_buffer( - &mut self, - source_texture: TextureId, - source_origin: [u32; 3], - source_mip_level: u32, - destination_buffer: BufferId, - destination_offset: u64, - destination_bytes_per_row: u32, - size: Extent3d, - ); - #[allow(clippy::too_many_arguments)] - fn copy_texture_to_texture( - &mut self, - source_texture: TextureId, - source_origin: [u32; 3], - source_mip_level: u32, - destination_texture: TextureId, - destination_origin: [u32; 3], - destination_mip_level: u32, - size: Extent3d, - ); - fn begin_pass( - &mut self, - pass_descriptor: &PassDescriptor, - render_resource_bindings: &RenderResourceBindings, - run_pass: &mut dyn FnMut(&mut dyn RenderPass), - ); -} - -impl_downcast!(RenderContext); diff --git a/pipelined/bevy_render2/src/renderer/render_device.rs b/crates/bevy_render/src/renderer/render_device.rs similarity index 100% rename from pipelined/bevy_render2/src/renderer/render_device.rs rename to crates/bevy_render/src/renderer/render_device.rs diff --git a/crates/bevy_render/src/renderer/render_resource/bind_group.rs b/crates/bevy_render/src/renderer/render_resource/bind_group.rs deleted file mode 100644 index 70f5974c81047..0000000000000 --- a/crates/bevy_render/src/renderer/render_resource/bind_group.rs +++ /dev/null @@ -1,124 +0,0 @@ -use super::{BufferId, RenderResourceBinding, RenderResourceId, SamplerId, TextureId}; -use bevy_utils::AHasher; -use std::{ - hash::{Hash, Hasher}, - ops::Range, - sync::Arc, -}; - -#[derive(Hash, Eq, PartialEq, Debug, Copy, Clone)] -pub struct BindGroupId(pub u64); - -#[derive(Eq, PartialEq, Debug)] -pub struct IndexedBindGroupEntry { - pub index: u32, - pub entry: RenderResourceBinding, -} - -#[derive(Clone, Eq, PartialEq, Debug)] -pub struct BindGroup { - pub id: BindGroupId, - pub indexed_bindings: Arc<[IndexedBindGroupEntry]>, - pub dynamic_uniform_indices: Option>, -} - -impl BindGroup { - pub fn build() -> BindGroupBuilder { - BindGroupBuilder::default() - } -} - -#[derive(Debug, Default)] -pub struct BindGroupBuilder { - pub indexed_bindings: Vec, - pub dynamic_uniform_indices: Vec, - pub hasher: AHasher, -} - -impl BindGroupBuilder { - pub fn add_binding(mut self, index: u32, binding: RenderResourceBinding) -> Self { - if let RenderResourceBinding::Buffer { - dynamic_index: Some(dynamic_index), - .. - } = binding - { - self.dynamic_uniform_indices.push(dynamic_index); - } - - self.hash_binding(&binding); - self.indexed_bindings.push(IndexedBindGroupEntry { - index, - entry: binding, - }); - self - } - - pub fn add_texture(self, index: u32, texture: TextureId) -> Self { - self.add_binding(index, RenderResourceBinding::Texture(texture)) - } - - pub fn add_sampler(self, index: u32, sampler: SamplerId) -> Self { - self.add_binding(index, RenderResourceBinding::Sampler(sampler)) - } - - pub fn add_buffer(self, index: u32, buffer: BufferId, range: Range) -> Self { - self.add_binding( - index, - RenderResourceBinding::Buffer { - buffer, - range, - dynamic_index: None, - }, - ) - } - - pub fn add_dynamic_buffer( - self, - index: u32, - buffer: BufferId, - range: Range, - dynamic_index: u32, - ) -> Self { - self.add_binding( - index, - RenderResourceBinding::Buffer { - buffer, - range, - dynamic_index: Some(dynamic_index), - }, - ) - } - - pub fn finish(mut self) -> BindGroup { - // this sort ensures that RenderResourceSets are insertion-order independent - self.indexed_bindings.sort_by_key(|i| i.index); - BindGroup { - id: BindGroupId(self.hasher.finish()), - indexed_bindings: self.indexed_bindings.into(), - dynamic_uniform_indices: if self.dynamic_uniform_indices.is_empty() { - None - } else { - Some(self.dynamic_uniform_indices.into()) - }, - } - } - - fn hash_binding(&mut self, binding: &RenderResourceBinding) { - match binding { - RenderResourceBinding::Buffer { - buffer, - range, - dynamic_index: _, // dynamic_index is not a part of the binding - } => { - RenderResourceId::from(*buffer).hash(&mut self.hasher); - range.hash(&mut self.hasher); - } - RenderResourceBinding::Texture(texture) => { - RenderResourceId::from(*texture).hash(&mut self.hasher); - } - RenderResourceBinding::Sampler(sampler) => { - RenderResourceId::from(*sampler).hash(&mut self.hasher); - } - } - } -} diff --git a/crates/bevy_render/src/renderer/render_resource/buffer.rs b/crates/bevy_render/src/renderer/render_resource/buffer.rs deleted file mode 100644 index 00252ffa9e2d6..0000000000000 --- a/crates/bevy_render/src/renderer/render_resource/buffer.rs +++ /dev/null @@ -1,51 +0,0 @@ -use bevy_utils::Uuid; - -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] -pub struct BufferId(Uuid); - -impl BufferId { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - BufferId(Uuid::new_v4()) - } -} - -#[derive(Debug, Clone, Eq, PartialEq)] -pub struct BufferInfo { - pub size: usize, - pub buffer_usage: BufferUsage, - pub mapped_at_creation: bool, -} - -impl Default for BufferInfo { - fn default() -> Self { - BufferInfo { - size: 0, - buffer_usage: BufferUsage::empty(), - mapped_at_creation: false, - } - } -} - -bitflags::bitflags! { - #[repr(transparent)] - #[cfg_attr(feature = "trace", derive(Serialize))] - #[cfg_attr(feature = "replay", derive(Deserialize))] - pub struct BufferUsage: u32 { - const MAP_READ = 1; - const MAP_WRITE = 2; - const COPY_SRC = 4; - const COPY_DST = 8; - const INDEX = 16; - const VERTEX = 32; - const UNIFORM = 64; - const STORAGE = 128; - const INDIRECT = 256; - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum BufferMapMode { - Read, - Write, -} diff --git a/crates/bevy_render/src/renderer/render_resource/mod.rs b/crates/bevy_render/src/renderer/render_resource/mod.rs deleted file mode 100644 index aa81167e636b2..0000000000000 --- a/crates/bevy_render/src/renderer/render_resource/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -mod bind_group; -mod buffer; -#[allow(clippy::module_inception)] -mod render_resource; -mod render_resource_bindings; -mod shared_buffers; -mod texture; - -pub use bind_group::*; -pub use buffer::*; -pub use render_resource::*; -pub use render_resource_bindings::*; -pub use shared_buffers::*; -pub use texture::*; diff --git a/crates/bevy_render/src/renderer/render_resource/render_resource.rs b/crates/bevy_render/src/renderer/render_resource/render_resource.rs deleted file mode 100644 index 40f4d7113bf17..0000000000000 --- a/crates/bevy_render/src/renderer/render_resource/render_resource.rs +++ /dev/null @@ -1,322 +0,0 @@ -use super::{BufferId, SamplerId, TextureId}; -use crate::texture::Texture; -use bevy_asset::Handle; - -use bevy_core::{cast_slice, Bytes, Pod}; -pub use bevy_derive::{RenderResource, RenderResources}; -use bevy_math::{Mat4, Vec2, Vec3, Vec4}; -use bevy_transform::components::GlobalTransform; - -#[derive(Debug, Clone, Eq, PartialEq)] -pub enum RenderResourceType { - Buffer, - Texture, - Sampler, -} - -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub enum RenderResourceId { - Buffer(BufferId), - Texture(TextureId), - Sampler(SamplerId), -} - -impl From for RenderResourceId { - fn from(value: BufferId) -> Self { - RenderResourceId::Buffer(value) - } -} - -impl From for RenderResourceId { - fn from(value: TextureId) -> Self { - RenderResourceId::Texture(value) - } -} - -impl From for RenderResourceId { - fn from(value: SamplerId) -> Self { - RenderResourceId::Sampler(value) - } -} - -impl RenderResourceId { - pub fn get_texture(&self) -> Option { - if let RenderResourceId::Texture(id) = self { - Some(*id) - } else { - None - } - } - - pub fn get_buffer(&self) -> Option { - if let RenderResourceId::Buffer(id) = self { - Some(*id) - } else { - None - } - } - - pub fn get_sampler(&self) -> Option { - if let RenderResourceId::Sampler(id) = self { - Some(*id) - } else { - None - } - } -} - -bitflags::bitflags! { - #[repr(transparent)] - pub struct RenderResourceHints: u32 { - const BUFFER = 1; - } -} - -pub trait RenderResource { - fn resource_type(&self) -> Option; - fn write_buffer_bytes(&self, buffer: &mut [u8]); - fn buffer_byte_len(&self) -> Option; - // TODO: consider making these panic by default, but return non-options - fn texture(&self) -> Option<&Handle>; -} - -pub trait RenderResources: Send + Sync + 'static { - fn render_resources_len(&self) -> usize; - fn get_render_resource(&self, index: usize) -> Option<&dyn RenderResource>; - fn get_render_resource_name(&self, index: usize) -> Option<&str>; - fn get_render_resource_hints(&self, _index: usize) -> Option { - None - } - fn iter(&self) -> RenderResourceIterator; -} - -pub struct RenderResourceIterator<'a> { - render_resources: &'a dyn RenderResources, - index: usize, -} - -impl<'a> RenderResourceIterator<'a> { - pub fn new(render_resources: &'a dyn RenderResources) -> Self { - Self { - render_resources, - index: 0, - } - } -} -impl<'a> Iterator for RenderResourceIterator<'a> { - type Item = &'a dyn RenderResource; - - fn next(&mut self) -> Option { - if self.index == self.render_resources.render_resources_len() { - None - } else { - let render_resource = self - .render_resources - .get_render_resource(self.index) - .unwrap(); - self.index += 1; - Some(render_resource) - } - } - - fn size_hint(&self) -> (usize, Option) { - let size = self.render_resources.render_resources_len(); - (size, Some(size)) - } -} - -impl<'a> ExactSizeIterator for RenderResourceIterator<'a> {} - -#[macro_export] -macro_rules! impl_render_resource_bytes { - ($ty:ident) => { - impl RenderResource for $ty { - fn resource_type(&self) -> Option { - Some(RenderResourceType::Buffer) - } - - fn write_buffer_bytes(&self, buffer: &mut [u8]) { - self.write_bytes(buffer); - } - - fn buffer_byte_len(&self) -> Option { - Some(self.byte_len()) - } - - fn texture(&self) -> Option<&Handle> { - None - } - } - }; -} - -// TODO: when specialization lands, replace these with impl RenderResource for T where T: Bytes -impl_render_resource_bytes!(Vec2); -impl_render_resource_bytes!(Vec3); -impl_render_resource_bytes!(Vec4); -impl_render_resource_bytes!(Mat4); -impl_render_resource_bytes!(u8); -impl_render_resource_bytes!(u16); -impl_render_resource_bytes!(u32); -impl_render_resource_bytes!(u64); -impl_render_resource_bytes!(i8); -impl_render_resource_bytes!(i16); -impl_render_resource_bytes!(i32); -impl_render_resource_bytes!(i64); -impl_render_resource_bytes!(f32); -impl_render_resource_bytes!(f64); - -impl RenderResource for Box -where - T: RenderResource, -{ - fn resource_type(&self) -> Option { - self.as_ref().resource_type() - } - - fn write_buffer_bytes(&self, buffer: &mut [u8]) { - self.as_ref().write_buffer_bytes(buffer); - } - - fn buffer_byte_len(&self) -> Option { - self.as_ref().buffer_byte_len() - } - - fn texture(&self) -> Option<&Handle> { - self.as_ref().texture() - } -} - -impl RenderResource for Vec -where - T: Sized + Pod, -{ - fn resource_type(&self) -> Option { - Some(RenderResourceType::Buffer) - } - - fn write_buffer_bytes(&self, buffer: &mut [u8]) { - buffer.copy_from_slice(cast_slice(self)); - } - - fn buffer_byte_len(&self) -> Option { - Some(std::mem::size_of_val(&self[..])) - } - - fn texture(&self) -> Option<&Handle> { - None - } -} - -impl RenderResource for [T; N] -where - T: Sized + Pod, -{ - fn resource_type(&self) -> Option { - Some(RenderResourceType::Buffer) - } - - fn write_buffer_bytes(&self, buffer: &mut [u8]) { - buffer.copy_from_slice(cast_slice(self)); - } - - fn buffer_byte_len(&self) -> Option { - Some(std::mem::size_of_val(self)) - } - - fn texture(&self) -> Option<&Handle> { - None - } -} - -impl RenderResource for GlobalTransform { - fn resource_type(&self) -> Option { - Some(RenderResourceType::Buffer) - } - - fn write_buffer_bytes(&self, buffer: &mut [u8]) { - let mat4 = self.compute_matrix(); - mat4.write_bytes(buffer); - } - - fn buffer_byte_len(&self) -> Option { - Some(std::mem::size_of::<[f32; 16]>()) - } - - fn texture(&self) -> Option<&Handle> { - None - } -} - -impl RenderResources for bevy_transform::prelude::GlobalTransform { - fn render_resources_len(&self) -> usize { - 1 - } - - fn get_render_resource(&self, index: usize) -> Option<&dyn RenderResource> { - if index == 0 { - Some(self) - } else { - None - } - } - - fn get_render_resource_name(&self, index: usize) -> Option<&str> { - if index == 0 { - Some("Transform") - } else { - None - } - } - - fn iter(&self) -> RenderResourceIterator { - RenderResourceIterator::new(self) - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate as bevy_render; - - #[derive(RenderResource, Bytes)] - struct GenericRenderResource - where - T: Bytes + Send + Sync + 'static, - { - value: T, - } - - #[derive(RenderResources)] - struct GenericRenderResources - where - T: RenderResource + Send + Sync + 'static, - { - resource: T, - } - - #[derive(Bytes, RenderResource, RenderResources)] - #[render_resources(from_self)] - struct FromSelfGenericRenderResources - where - T: Bytes + Send + Sync + 'static, - { - value: T, - } - - fn test_impl_render_resource(_: &impl RenderResource) {} - fn test_impl_render_resources(_: &impl RenderResources) {} - - #[test] - fn test_generic_render_resource_derive() { - let resource = GenericRenderResource { value: 42 }; - test_impl_render_resource(&resource); - - let resources = GenericRenderResources { resource }; - test_impl_render_resources(&resources); - - let from_self_resources = FromSelfGenericRenderResources { value: 42 }; - test_impl_render_resource(&from_self_resources); - test_impl_render_resources(&from_self_resources); - } -} diff --git a/crates/bevy_render/src/renderer/render_resource/render_resource_bindings.rs b/crates/bevy_render/src/renderer/render_resource/render_resource_bindings.rs deleted file mode 100644 index f44982fd4cba7..0000000000000 --- a/crates/bevy_render/src/renderer/render_resource/render_resource_bindings.rs +++ /dev/null @@ -1,380 +0,0 @@ -use super::{BindGroup, BindGroupId, BufferId, SamplerId, TextureId}; -use crate::{ - pipeline::{BindGroupDescriptor, BindGroupDescriptorId, IndexFormat, PipelineDescriptor}, - renderer::RenderResourceContext, -}; -use bevy_asset::{Asset, Handle, HandleUntyped}; -use bevy_utils::{HashMap, HashSet}; -use std::{any::TypeId, ops::Range}; - -#[derive(Clone, PartialEq, Eq, Debug)] -pub enum RenderResourceBinding { - Buffer { - buffer: BufferId, - range: Range, - dynamic_index: Option, - }, - Texture(TextureId), - Sampler(SamplerId), -} - -impl RenderResourceBinding { - pub fn get_texture(&self) -> Option { - if let RenderResourceBinding::Texture(texture) = self { - Some(*texture) - } else { - None - } - } - - pub fn get_buffer(&self) -> Option { - if let RenderResourceBinding::Buffer { buffer, .. } = self { - Some(*buffer) - } else { - None - } - } - - pub fn is_dynamic_buffer(&self) -> bool { - matches!( - self, - RenderResourceBinding::Buffer { - dynamic_index: Some(_), - .. - } - ) - } - - pub fn get_sampler(&self) -> Option { - if let RenderResourceBinding::Sampler(sampler) = self { - Some(*sampler) - } else { - None - } - } -} - -#[derive(Eq, PartialEq, Debug)] -pub enum BindGroupStatus { - Changed(BindGroupId), - Unchanged(BindGroupId), - NoMatch, -} - -// PERF: if the bindings are scoped to a specific pipeline layout, then names could be replaced with -// indices here for a perf boost -#[derive(Eq, PartialEq, Debug, Default, Clone)] -pub struct RenderResourceBindings { - pub bindings: HashMap, - /// A Buffer that contains all attributes a mesh has defined - pub vertex_attribute_buffer: Option, - /// A Buffer that is filled with zeros that will be used for attributes required by the shader, - /// but undefined by the mesh. - pub vertex_fallback_buffer: Option, - pub index_buffer: Option<(BufferId, IndexFormat)>, - assets: HashSet<(HandleUntyped, TypeId)>, - bind_groups: HashMap, - bind_group_descriptors: HashMap>, - dirty_bind_groups: HashSet, - dynamic_bindings_generation: usize, -} - -impl RenderResourceBindings { - pub fn get(&self, name: &str) -> Option<&RenderResourceBinding> { - self.bindings.get(name) - } - - pub fn set(&mut self, name: &str, binding: RenderResourceBinding) { - self.try_set_dirty(name, &binding); - self.bindings.insert(name.to_string(), binding); - } - - /// The current "generation" of dynamic bindings. This number increments every time a dynamic - /// binding changes - pub fn dynamic_bindings_generation(&self) -> usize { - self.dynamic_bindings_generation - } - - fn try_set_dirty(&mut self, name: &str, binding: &RenderResourceBinding) { - if let Some(current_binding) = self.bindings.get(name) { - if current_binding != binding { - if current_binding.is_dynamic_buffer() { - self.dynamic_bindings_generation += 1; - } - // TODO: this is crude. we shouldn't need to invalidate all bind groups - for id in self.bind_groups.keys() { - self.dirty_bind_groups.insert(*id); - } - } - } else { - // unmatched bind group descriptors might now match - self.bind_group_descriptors - .retain(|_, value| value.is_some()); - } - } - - pub fn extend(&mut self, render_resource_bindings: &RenderResourceBindings) { - for (name, binding) in render_resource_bindings.bindings.iter() { - self.set(name, binding.clone()); - } - } - - pub fn set_index_buffer(&mut self, index_buffer: BufferId, index_format: IndexFormat) { - self.index_buffer = Some((index_buffer, index_format)); - } - - fn create_bind_group(&mut self, descriptor: &BindGroupDescriptor) -> BindGroupStatus { - let bind_group = self.build_bind_group(descriptor); - if let Some(bind_group) = bind_group { - let id = bind_group.id; - self.bind_groups.insert(id, bind_group); - self.bind_group_descriptors.insert(descriptor.id, Some(id)); - BindGroupStatus::Changed(id) - } else { - self.bind_group_descriptors.insert(descriptor.id, None); - BindGroupStatus::NoMatch - } - } - - fn update_bind_group_status( - &mut self, - bind_group_descriptor: &BindGroupDescriptor, - ) -> BindGroupStatus { - if let Some(id) = self.bind_group_descriptors.get(&bind_group_descriptor.id) { - if let Some(id) = id { - if self.dirty_bind_groups.contains(id) { - self.dirty_bind_groups.remove(id); - self.create_bind_group(bind_group_descriptor) - } else { - BindGroupStatus::Unchanged(*id) - } - } else { - BindGroupStatus::NoMatch - } - } else { - self.create_bind_group(bind_group_descriptor) - } - } - - pub fn add_asset(&mut self, handle: HandleUntyped, type_id: TypeId) { - self.dynamic_bindings_generation += 1; - self.assets.insert((handle, type_id)); - } - - pub fn remove_asset_with_type(&mut self, type_id: TypeId) { - self.dynamic_bindings_generation += 1; - self.assets.retain(|(_, current_id)| *current_id != type_id); - } - - pub fn iter_assets(&self) -> impl Iterator { - self.assets.iter() - } - - pub fn update_bind_group( - &mut self, - bind_group_descriptor: &BindGroupDescriptor, - render_resource_context: &dyn RenderResourceContext, - ) -> Option<&BindGroup> { - let status = self.update_bind_group_status(bind_group_descriptor); - match status { - BindGroupStatus::Changed(id) => { - let bind_group = self - .get_bind_group(id) - .expect("`RenderResourceSet` was just changed, so it should exist."); - render_resource_context.create_bind_group(bind_group_descriptor.id, bind_group); - Some(bind_group) - } - BindGroupStatus::Unchanged(id) => { - // PERF: this is only required because - // RenderResourceContext::remove_stale_bind_groups doesn't inform - // RenderResourceBindings when a stale bind group has been removed - let bind_group = self - .get_bind_group(id) - .expect("`RenderResourceSet` was just changed, so it should exist."); - render_resource_context.create_bind_group(bind_group_descriptor.id, bind_group); - Some(bind_group) - } - BindGroupStatus::NoMatch => { - // ignore unchanged / unmatched render resource sets - None - } - } - } - - pub fn update_bind_groups( - &mut self, - pipeline: &PipelineDescriptor, - render_resource_context: &dyn RenderResourceContext, - ) { - let layout = pipeline.get_layout().unwrap(); - for bind_group_descriptor in layout.bind_groups.iter() { - self.update_bind_group(bind_group_descriptor, render_resource_context); - } - } - - pub fn get_bind_group(&self, id: BindGroupId) -> Option<&BindGroup> { - self.bind_groups.get(&id) - } - - pub fn get_descriptor_bind_group(&self, id: BindGroupDescriptorId) -> Option<&BindGroup> { - self.bind_group_descriptors - .get(&id) - .and_then(|bind_group_id| { - if let Some(bind_group_id) = bind_group_id { - self.get_bind_group(*bind_group_id) - } else { - None - } - }) - } - - fn build_bind_group(&self, bind_group_descriptor: &BindGroupDescriptor) -> Option { - let mut bind_group_builder = BindGroup::build(); - for binding_descriptor in bind_group_descriptor.bindings.iter() { - if let Some(binding) = self.get(&binding_descriptor.name) { - bind_group_builder = - bind_group_builder.add_binding(binding_descriptor.index, binding.clone()); - } else { - return None; - } - } - - Some(bind_group_builder.finish()) - } - - pub fn iter_dynamic_bindings(&self) -> impl Iterator { - self.bindings - .iter() - .filter(|(_, binding)| { - matches!( - binding, - RenderResourceBinding::Buffer { - dynamic_index: Some(_), - .. - } - ) - }) - .map(|(name, _)| name.as_str()) - } -} - -#[derive(Debug, Default)] -pub struct AssetRenderResourceBindings { - pub bindings: HashMap, -} - -impl AssetRenderResourceBindings { - pub fn get(&self, handle: &Handle) -> Option<&RenderResourceBindings> { - self.get_untyped(&handle.clone_weak_untyped()) - } - - pub fn get_untyped(&self, handle: &HandleUntyped) -> Option<&RenderResourceBindings> { - self.bindings.get(handle) - } - - pub fn get_or_insert_mut( - &mut self, - handle: &Handle, - ) -> &mut RenderResourceBindings { - self.bindings - .entry(handle.clone_weak_untyped()) - .or_insert_with(RenderResourceBindings::default) - } - - pub fn get_mut(&mut self, handle: &Handle) -> Option<&mut RenderResourceBindings> { - self.get_mut_untyped(&handle.clone_weak_untyped()) - } - - pub fn get_mut_untyped( - &mut self, - handle: &HandleUntyped, - ) -> Option<&mut RenderResourceBindings> { - self.bindings.get_mut(handle) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::pipeline::{BindType, BindingDescriptor, BindingShaderStage, UniformProperty}; - - #[test] - fn test_bind_groups() { - let bind_group_descriptor = BindGroupDescriptor::new( - 0, - vec![ - BindingDescriptor { - index: 0, - name: "a".to_string(), - bind_type: BindType::Uniform { - has_dynamic_offset: false, - property: UniformProperty::Struct(vec![UniformProperty::Mat4]), - }, - shader_stage: BindingShaderStage::VERTEX | BindingShaderStage::FRAGMENT, - }, - BindingDescriptor { - index: 1, - name: "b".to_string(), - bind_type: BindType::Uniform { - has_dynamic_offset: false, - property: UniformProperty::Float, - }, - shader_stage: BindingShaderStage::VERTEX | BindingShaderStage::FRAGMENT, - }, - ], - ); - - let resource1 = RenderResourceBinding::Texture(TextureId::new()); - let resource2 = RenderResourceBinding::Texture(TextureId::new()); - let resource3 = RenderResourceBinding::Texture(TextureId::new()); - let resource4 = RenderResourceBinding::Texture(TextureId::new()); - - let mut bindings = RenderResourceBindings::default(); - bindings.set("a", resource1.clone()); - bindings.set("b", resource2.clone()); - - let mut different_bindings = RenderResourceBindings::default(); - different_bindings.set("a", resource3); - different_bindings.set("b", resource4); - - let mut equal_bindings = RenderResourceBindings::default(); - equal_bindings.set("a", resource1.clone()); - equal_bindings.set("b", resource2); - - let status = bindings.update_bind_group_status(&bind_group_descriptor); - let id = if let BindGroupStatus::Changed(id) = status { - id - } else { - panic!("Expected a changed bind group."); - }; - - let different_bind_group_status = - different_bindings.update_bind_group_status(&bind_group_descriptor); - if let BindGroupStatus::Changed(different_bind_group_id) = different_bind_group_status { - assert_ne!( - id, different_bind_group_id, - "different bind group shouldn't have the same id" - ); - different_bind_group_id - } else { - panic!("Expected a changed bind group."); - }; - - let equal_bind_group_status = - equal_bindings.update_bind_group_status(&bind_group_descriptor); - if let BindGroupStatus::Changed(equal_bind_group_id) = equal_bind_group_status { - assert_eq!( - id, equal_bind_group_id, - "equal bind group should have the same id" - ); - } else { - panic!("Expected a changed bind group."); - }; - - let mut unmatched_bindings = RenderResourceBindings::default(); - unmatched_bindings.set("a", resource1); - let unmatched_bind_group_status = - unmatched_bindings.update_bind_group_status(&bind_group_descriptor); - assert_eq!(unmatched_bind_group_status, BindGroupStatus::NoMatch); - } -} diff --git a/crates/bevy_render/src/renderer/render_resource/shared_buffers.rs b/crates/bevy_render/src/renderer/render_resource/shared_buffers.rs deleted file mode 100644 index 1bfe06ff25f67..0000000000000 --- a/crates/bevy_render/src/renderer/render_resource/shared_buffers.rs +++ /dev/null @@ -1,139 +0,0 @@ -use super::{BufferId, BufferInfo, RenderResource, RenderResourceBinding}; -use crate::{ - render_graph::CommandQueue, - renderer::{BufferMapMode, BufferUsage, RenderContext, RenderResourceContext}, -}; -use bevy_ecs::system::{Res, ResMut}; - -pub struct SharedBuffers { - staging_buffer: Option, - uniform_buffer: Option, - buffers_to_free: Vec, - buffer_size: usize, - initial_size: usize, - current_offset: usize, - command_queue: CommandQueue, -} - -impl SharedBuffers { - pub fn new(initial_size: usize) -> Self { - Self { - staging_buffer: None, - uniform_buffer: None, - buffer_size: 0, - current_offset: 0, - initial_size, - buffers_to_free: Default::default(), - command_queue: Default::default(), - } - } - - pub fn grow( - &mut self, - render_resource_context: &dyn RenderResourceContext, - required_space: usize, - ) { - while self.buffer_size < self.current_offset + required_space { - self.buffer_size = if self.buffer_size == 0 { - self.initial_size - } else { - self.buffer_size * 2 - }; - } - - self.current_offset = 0; - - if let Some(staging_buffer) = self.staging_buffer.take() { - render_resource_context.unmap_buffer(staging_buffer); - self.buffers_to_free.push(staging_buffer); - } - - if let Some(uniform_buffer) = self.uniform_buffer.take() { - self.buffers_to_free.push(uniform_buffer); - } - - self.staging_buffer = Some(render_resource_context.create_buffer(BufferInfo { - size: self.buffer_size, - buffer_usage: BufferUsage::MAP_WRITE | BufferUsage::COPY_SRC, - mapped_at_creation: true, - })); - self.uniform_buffer = Some(render_resource_context.create_buffer(BufferInfo { - size: self.buffer_size, - buffer_usage: BufferUsage::COPY_DST | BufferUsage::UNIFORM, - mapped_at_creation: false, - })); - } - - pub fn get_uniform_buffer( - &mut self, - render_resource_context: &dyn RenderResourceContext, - render_resource: &T, - ) -> Option { - if let Some(size) = render_resource.buffer_byte_len() { - // TODO: overlap alignment if/when possible - let aligned_size = render_resource_context.get_aligned_uniform_size(size, true); - let mut new_offset = self.current_offset + aligned_size; - if new_offset > self.buffer_size { - self.grow(render_resource_context, aligned_size); - new_offset = aligned_size; - } - - let range = self.current_offset as u64..new_offset as u64; - let staging_buffer = self.staging_buffer.unwrap(); - let uniform_buffer = self.uniform_buffer.unwrap(); - render_resource_context.write_mapped_buffer( - staging_buffer, - range.clone(), - &mut |data, _renderer| { - render_resource.write_buffer_bytes(data); - }, - ); - - self.command_queue.copy_buffer_to_buffer( - staging_buffer, - self.current_offset as u64, - uniform_buffer, - self.current_offset as u64, - aligned_size as u64, - ); - - self.current_offset = new_offset; - Some(RenderResourceBinding::Buffer { - buffer: uniform_buffer, - range, - dynamic_index: None, - }) - } else { - None - } - } - - pub fn update(&mut self, render_resource_context: &dyn RenderResourceContext) { - self.current_offset = 0; - for buffer in self.buffers_to_free.drain(..) { - render_resource_context.remove_buffer(buffer) - } - - if let Some(staging_buffer) = self.staging_buffer { - render_resource_context.map_buffer(staging_buffer, BufferMapMode::Write); - } - } - - pub fn apply(&self, render_context: &mut dyn RenderContext) { - if let Some(staging_buffer) = self.staging_buffer { - render_context.resources().unmap_buffer(staging_buffer); - } - self.command_queue.execute(render_context); - } - - pub fn command_queue_mut(&mut self) -> &mut CommandQueue { - &mut self.command_queue - } -} - -pub fn shared_buffers_update_system( - mut shared_buffers: ResMut, - render_resource_context: Res>, -) { - shared_buffers.update(&**render_resource_context); -} diff --git a/crates/bevy_render/src/renderer/render_resource/texture.rs b/crates/bevy_render/src/renderer/render_resource/texture.rs deleted file mode 100644 index 46e7e51753eee..0000000000000 --- a/crates/bevy_render/src/renderer/render_resource/texture.rs +++ /dev/null @@ -1,21 +0,0 @@ -use bevy_utils::Uuid; - -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] -pub struct TextureId(Uuid); - -impl TextureId { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - TextureId(Uuid::new_v4()) - } -} - -#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] -pub struct SamplerId(Uuid); - -impl SamplerId { - #[allow(clippy::new_without_default)] - pub fn new() -> Self { - SamplerId(Uuid::new_v4()) - } -} diff --git a/crates/bevy_render/src/renderer/render_resource_context.rs b/crates/bevy_render/src/renderer/render_resource_context.rs deleted file mode 100644 index 9d16197e76348..0000000000000 --- a/crates/bevy_render/src/renderer/render_resource_context.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::{ - pipeline::{BindGroupDescriptorId, PipelineDescriptor, PipelineLayout}, - renderer::{ - BindGroup, BufferId, BufferInfo, BufferMapMode, RenderResourceId, SamplerId, TextureId, - }, - shader::{Shader, ShaderError, ShaderLayout, ShaderStages}, - texture::{SamplerDescriptor, TextureDescriptor}, -}; -use bevy_asset::{Asset, Assets, Handle, HandleUntyped}; -use bevy_window::Window; -use downcast_rs::{impl_downcast, Downcast}; -use std::ops::Range; - -pub trait RenderResourceContext: Downcast + Send + Sync + 'static { - fn configure_surface(&self, window: &Window); - fn next_surface_frame(&self, window: &Window) -> TextureId; - fn drop_surface_frame(&self, resource: TextureId); - fn drop_all_surface_frames(&self); - fn create_sampler(&self, sampler_descriptor: &SamplerDescriptor) -> SamplerId; - fn create_texture(&self, texture_descriptor: TextureDescriptor) -> TextureId; - fn create_buffer(&self, buffer_info: BufferInfo) -> BufferId; - // TODO: remove RenderResourceContext here - fn write_mapped_buffer( - &self, - id: BufferId, - range: Range, - write: &mut dyn FnMut(&mut [u8], &dyn RenderResourceContext), - ); - fn read_mapped_buffer( - &self, - id: BufferId, - range: Range, - read: &dyn Fn(&[u8], &dyn RenderResourceContext), - ); - fn map_buffer(&self, id: BufferId, mode: BufferMapMode); - fn unmap_buffer(&self, id: BufferId); - fn create_buffer_with_data(&self, buffer_info: BufferInfo, data: &[u8]) -> BufferId; - fn create_shader_module(&self, shader_handle: &Handle, shaders: &Assets); - fn create_shader_module_from_source(&self, shader_handle: &Handle, shader: &Shader); - fn get_specialized_shader( - &self, - shader: &Shader, - macros: Option<&[String]>, - ) -> Result; - fn remove_buffer(&self, buffer: BufferId); - fn remove_texture(&self, texture: TextureId); - fn remove_sampler(&self, sampler: SamplerId); - fn get_buffer_info(&self, buffer: BufferId) -> Option; - fn get_aligned_uniform_size(&self, size: usize, dynamic: bool) -> usize; - fn get_aligned_texture_size(&self, data_size: usize) -> usize; - fn set_asset_resource_untyped( - &self, - handle: HandleUntyped, - resource: RenderResourceId, - index: u64, - ); - fn get_asset_resource_untyped( - &self, - handle: HandleUntyped, - index: u64, - ) -> Option; - fn remove_asset_resource_untyped(&self, handle: HandleUntyped, index: u64); - fn create_render_pipeline( - &self, - pipeline_handle: Handle, - pipeline_descriptor: &PipelineDescriptor, - shaders: &Assets, - ); - fn bind_group_descriptor_exists(&self, bind_group_descriptor_id: BindGroupDescriptorId) - -> bool; - fn create_bind_group( - &self, - bind_group_descriptor_id: BindGroupDescriptorId, - bind_group: &BindGroup, - ); - fn clear_bind_groups(&self); - fn remove_stale_bind_groups(&self); - /// Reflects the pipeline layout from its shaders. - /// - /// If `bevy_conventions` is true, it will be assumed that the shader follows "bevy shader - /// conventions". These allow richer reflection, such as inferred Vertex Buffer names and - /// inferred instancing. - /// - /// If `dynamic_bindings` has values, shader uniforms will be set to "dynamic" if there is a - /// matching binding in the list - /// - /// If `vertex_buffer_descriptors` is set, the pipeline's vertex buffers - /// will inherit their layouts from global descriptors, otherwise the layout will be assumed to - /// be complete / local. - fn reflect_pipeline_layout( - &self, - shaders: &Assets, - shader_stages: &ShaderStages, - enforce_bevy_conventions: bool, - ) -> PipelineLayout { - // TODO: maybe move this default implementation to PipelineLayout? - let mut shader_layouts: Vec = shader_stages - .iter() - .map(|handle| { - shaders - .get(&handle) - .unwrap() - .reflect_layout(enforce_bevy_conventions) - .unwrap() - }) - .collect(); - PipelineLayout::from_shader_layouts(&mut shader_layouts) - } -} - -impl dyn RenderResourceContext { - pub fn set_asset_resource(&self, handle: &Handle, resource: RenderResourceId, index: u64) - where - T: Asset, - { - self.set_asset_resource_untyped(handle.clone_weak_untyped(), resource, index); - } - - pub fn get_asset_resource(&self, handle: &Handle, index: u64) -> Option - where - T: Asset, - { - self.get_asset_resource_untyped(handle.clone_weak_untyped(), index) - } - - pub fn remove_asset_resource(&self, handle: &Handle, index: u64) - where - T: Asset, - { - self.remove_asset_resource_untyped(handle.clone_weak_untyped(), index); - } -} - -impl_downcast!(RenderResourceContext); diff --git a/crates/bevy_render/src/shader/mod.rs b/crates/bevy_render/src/shader/mod.rs deleted file mode 100644 index f9d727e147bab..0000000000000 --- a/crates/bevy_render/src/shader/mod.rs +++ /dev/null @@ -1,26 +0,0 @@ -#[allow(clippy::module_inception)] -mod shader; -mod shader_defs; - -#[cfg(not(target_arch = "wasm32"))] -mod shader_reflect; - -pub use shader::*; -pub use shader_defs::*; - -#[cfg(not(target_arch = "wasm32"))] -pub use shader_reflect::*; - -use crate::pipeline::{BindGroupDescriptor, VertexBufferLayout}; - -/// Defines the memory layout of a shader -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct ShaderLayout { - pub bind_groups: Vec, - pub vertex_buffer_layout: Vec, - pub entry_point: String, -} - -pub const GL_VERTEX_INDEX: &str = "gl_VertexIndex"; -pub const GL_INSTANCE_INDEX: &str = "gl_InstanceIndex"; -pub const GL_FRONT_FACING: &str = "gl_FrontFacing"; diff --git a/crates/bevy_render/src/shader/shader.rs b/crates/bevy_render/src/shader/shader.rs deleted file mode 100644 index b8ea268546fb8..0000000000000 --- a/crates/bevy_render/src/shader/shader.rs +++ /dev/null @@ -1,355 +0,0 @@ -use crate::{ - pipeline::{PipelineCompiler, PipelineDescriptor}, - renderer::RenderResourceContext, -}; - -use super::ShaderLayout; -use bevy_app::EventReader; -use bevy_asset::{AssetEvent, AssetLoader, Assets, Handle, LoadContext, LoadedAsset}; -use bevy_ecs::system::{Res, ResMut}; -use bevy_reflect::TypeUuid; -use bevy_utils::{tracing::error, BoxedFuture}; -use std::marker::Copy; -use thiserror::Error; - -/// The stage of a shader -#[derive(Hash, Eq, PartialEq, Copy, Clone, Debug)] -pub enum ShaderStage { - Vertex, - Fragment, - Compute, -} - -/// An error that occurs during shader handling. -#[derive(Error, Debug)] -pub enum ShaderError { - /// Shader compilation error. - #[error("Shader compilation error:\n{0}")] - Compilation(String), - - #[cfg(not(any( - target_arch = "wasm32", - all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"), - all(target_arch = "x86_64", target_os = "macos"), - all(target_arch = "aarch64", target_os = "android"), - all(target_arch = "armv7", target_os = "androidabi"), - all(target_arch = "x86_64", target_os = "windows", target_env = "msvc"), - )))] - /// shaderc error. - #[error("shaderc error: {0}")] - ShaderC(#[from] shaderc::Error), - - #[cfg(not(any( - target_arch = "wasm32", - all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"), - all(target_arch = "x86_64", target_os = "macos"), - all(target_arch = "aarch64", target_os = "android"), - all(target_arch = "armv7", target_os = "androidabi"), - all(target_arch = "x86_64", target_os = "windows", target_env = "msvc"), - )))] - #[error("Error initializing shaderc Compiler")] - ErrorInitializingShadercCompiler, - - #[cfg(not(any( - target_arch = "wasm32", - all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"), - all(target_arch = "x86_64", target_os = "macos"), - all(target_arch = "aarch64", target_os = "android"), - all(target_arch = "armv7", target_os = "androidabi"), - all(target_arch = "x86_64", target_os = "windows", target_env = "msvc"), - )))] - #[error("Error initializing shaderc CompileOptions")] - ErrorInitializingShadercCompileOptions, -} - -#[cfg(any( - all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"), - all(target_arch = "x86_64", target_os = "macos"), - all(target_arch = "aarch64", target_os = "android"), - all(target_arch = "armv7", target_os = "androidabi"), - all(target_arch = "x86_64", target_os = "windows", target_env = "msvc"), -))] -impl From for bevy_glsl_to_spirv::ShaderType { - fn from(s: ShaderStage) -> bevy_glsl_to_spirv::ShaderType { - match s { - ShaderStage::Vertex => bevy_glsl_to_spirv::ShaderType::Vertex, - ShaderStage::Fragment => bevy_glsl_to_spirv::ShaderType::Fragment, - ShaderStage::Compute => bevy_glsl_to_spirv::ShaderType::Compute, - } - } -} - -#[cfg(any( - all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"), - all(target_arch = "x86_64", target_os = "macos"), - all(target_arch = "aarch64", target_os = "android"), - all(target_arch = "armv7", target_os = "androidabi"), - all(target_arch = "x86_64", target_os = "windows", target_env = "msvc"), -))] -pub fn glsl_to_spirv( - glsl_source: &str, - stage: ShaderStage, - shader_defs: Option<&[String]>, -) -> Result, ShaderError> { - bevy_glsl_to_spirv::compile(glsl_source, stage.into(), shader_defs) - .map_err(ShaderError::Compilation) -} - -#[cfg(not(any( - target_arch = "wasm32", - all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"), - all(target_arch = "x86_64", target_os = "macos"), - all(target_arch = "aarch64", target_os = "android"), - all(target_arch = "armv7", target_os = "androidabi"), - all(target_arch = "x86_64", target_os = "windows", target_env = "msvc"), -)))] -impl Into for ShaderStage { - fn into(self) -> shaderc::ShaderKind { - match self { - ShaderStage::Vertex => shaderc::ShaderKind::Vertex, - ShaderStage::Fragment => shaderc::ShaderKind::Fragment, - ShaderStage::Compute => shaderc::ShaderKind::Compute, - } - } -} - -#[cfg(not(any( - target_arch = "wasm32", - all(target_arch = "x86_64", target_os = "linux", target_env = "gnu"), - all(target_arch = "x86_64", target_os = "macos"), - all(target_arch = "aarch64", target_os = "android"), - all(target_arch = "armv7", target_os = "androidabi"), - all(target_arch = "x86_64", target_os = "windows", target_env = "msvc"), -)))] -pub fn glsl_to_spirv( - glsl_source: &str, - stage: ShaderStage, - shader_defs: Option<&[String]>, -) -> Result, ShaderError> { - let mut compiler = - shaderc::Compiler::new().ok_or(ShaderError::ErrorInitializingShadercCompiler)?; - let mut options = shaderc::CompileOptions::new() - .ok_or(ShaderError::ErrorInitializingShadercCompileOptions)?; - if let Some(shader_defs) = shader_defs { - for def in shader_defs.iter() { - options.add_macro_definition(def, None); - } - } - - let binary_result = compiler.compile_into_spirv( - glsl_source, - stage.into(), - "shader.glsl", - "main", - Some(&options), - )?; - - Ok(binary_result.as_binary().to_vec()) -} - -fn bytes_to_words(bytes: &[u8]) -> Vec { - let mut words = Vec::new(); - for bytes4 in bytes.chunks(4) { - words.push(u32::from_le_bytes([ - bytes4[0], bytes4[1], bytes4[2], bytes4[3], - ])); - } - - words -} - -/// The full "source" of a shader -#[derive(Clone, Debug, Hash, Eq, PartialEq)] -pub enum ShaderSource { - Spirv(Vec), - Glsl(String), -} - -impl ShaderSource { - pub fn spirv_from_bytes(bytes: &[u8]) -> ShaderSource { - ShaderSource::Spirv(bytes_to_words(bytes)) - } -} - -/// A shader, as defined by its [ShaderSource] and [ShaderStage] -#[derive(Clone, Debug, TypeUuid)] -#[uuid = "d95bc916-6c55-4de3-9622-37e7b6969fda"] -pub struct Shader { - pub source: ShaderSource, - pub stage: ShaderStage, -} - -impl Shader { - pub fn new(stage: ShaderStage, source: ShaderSource) -> Shader { - Shader { source, stage } - } - - #[cfg(not(target_arch = "wasm32"))] - pub fn from_spirv(spirv: &[u8]) -> Result { - use spirv_reflect::{types::ReflectShaderStageFlags, ShaderModule}; - - let module = ShaderModule::load_u8_data(spirv) - .map_err(|msg| ShaderError::Compilation(msg.to_string()))?; - let stage = match module.get_shader_stage() { - ReflectShaderStageFlags::VERTEX => ShaderStage::Vertex, - ReflectShaderStageFlags::FRAGMENT => ShaderStage::Fragment, - other => panic!("cannot load {:?} shader", other), - }; - - Ok(Shader { - source: ShaderSource::spirv_from_bytes(spirv), - stage, - }) - } - - pub fn from_glsl(stage: ShaderStage, glsl: &str) -> Shader { - Shader { - source: ShaderSource::Glsl(glsl.to_string()), - stage, - } - } - - #[cfg(not(target_arch = "wasm32"))] - pub fn get_spirv(&self, macros: Option<&[String]>) -> Result, ShaderError> { - match self.source { - ShaderSource::Spirv(ref bytes) => Ok(bytes.clone()), - ShaderSource::Glsl(ref source) => glsl_to_spirv(source, self.stage, macros), - } - } - - #[cfg(not(target_arch = "wasm32"))] - pub fn get_spirv_shader(&self, macros: Option<&[String]>) -> Result { - Ok(Shader { - source: ShaderSource::Spirv(self.get_spirv(macros)?), - stage: self.stage, - }) - } - - #[cfg(not(target_arch = "wasm32"))] - pub fn reflect_layout(&self, enforce_bevy_conventions: bool) -> Option { - if let ShaderSource::Spirv(ref spirv) = self.source { - Some(ShaderLayout::from_spirv( - spirv.as_slice(), - enforce_bevy_conventions, - )) - } else { - panic!("Cannot reflect layout of non-SpirV shader. Try compiling this shader to SpirV first using self.get_spirv_shader()."); - } - } - - #[cfg(target_arch = "wasm32")] - pub fn reflect_layout(&self, _enforce_bevy_conventions: bool) -> Option { - panic!("Cannot reflect layout on wasm32."); - } -} - -/// All stages in a shader program -#[derive(Clone, Debug, Eq, PartialEq, Hash)] -pub struct ShaderStages { - pub vertex: Handle, - pub fragment: Option>, -} - -pub struct ShaderStagesIterator<'a> { - shader_stages: &'a ShaderStages, - state: u32, -} - -impl<'a> Iterator for ShaderStagesIterator<'a> { - type Item = Handle; - - fn next(&mut self) -> Option { - let ret = match self.state { - 0 => Some(self.shader_stages.vertex.clone_weak()), - 1 => self.shader_stages.fragment.as_ref().map(|h| h.clone_weak()), - _ => None, - }; - self.state += 1; - ret - } - - fn size_hint(&self) -> (usize, Option) { - if self.shader_stages.fragment.is_some() { - return (2, Some(2)); - } - (1, Some(1)) - } -} - -impl<'a> ExactSizeIterator for ShaderStagesIterator<'a> {} - -impl ShaderStages { - pub fn new(vertex_shader: Handle) -> Self { - ShaderStages { - vertex: vertex_shader, - fragment: None, - } - } - - pub fn iter(&self) -> ShaderStagesIterator { - ShaderStagesIterator { - shader_stages: self, - state: 0, - } - } -} - -#[derive(Default)] -pub struct ShaderLoader; - -impl AssetLoader for ShaderLoader { - fn load<'a>( - &'a self, - bytes: &'a [u8], - load_context: &'a mut LoadContext, - ) -> BoxedFuture<'a, Result<(), anyhow::Error>> { - Box::pin(async move { - let ext = load_context.path().extension().unwrap().to_str().unwrap(); - - let shader = match ext { - "vert" => Shader::from_glsl(ShaderStage::Vertex, std::str::from_utf8(bytes)?), - "frag" => Shader::from_glsl(ShaderStage::Fragment, std::str::from_utf8(bytes)?), - #[cfg(not(target_arch = "wasm32"))] - "spv" => Shader::from_spirv(bytes)?, - #[cfg(target_arch = "wasm32")] - "spv" => panic!("cannot load .spv file on wasm"), - _ => panic!("unhandled extension: {}", ext), - }; - - load_context.set_default_asset(LoadedAsset::new(shader)); - Ok(()) - }) - } - - fn extensions(&self) -> &[&str] { - &["vert", "frag", "spv"] - } -} - -pub fn shader_update_system( - mut shaders: ResMut>, - mut pipelines: ResMut>, - mut shader_events: EventReader>, - mut pipeline_compiler: ResMut, - render_resource_context: Res>, -) { - for event in shader_events.iter() { - match event { - AssetEvent::Modified { handle } => { - if let Err(e) = pipeline_compiler.update_shader( - handle, - &mut pipelines, - &mut shaders, - &**render_resource_context, - ) { - error!("Failed to update shader: {}", e); - } - } - // Creating shaders on the fly is unhandled since they - // have to exist already when assigned to a pipeline. If a - // shader is removed the pipeline keeps using its - // specialized version. Maybe this should be a warning? - AssetEvent::Created { .. } | AssetEvent::Removed { .. } => (), - } - } -} diff --git a/crates/bevy_render/src/shader/shader_defs.rs b/crates/bevy_render/src/shader/shader_defs.rs deleted file mode 100644 index bf579be454318..0000000000000 --- a/crates/bevy_render/src/shader/shader_defs.rs +++ /dev/null @@ -1,123 +0,0 @@ -use bevy_asset::{Asset, Assets, Handle}; - -use crate::{draw::OutsideFrustum, pipeline::RenderPipelines, Texture}; -pub use bevy_derive::ShaderDefs; -use bevy_ecs::{ - prelude::Component, - query::Without, - system::{Query, Res}, -}; - -/// Something that can either be "defined" or "not defined". This is used to determine if a "shader -/// def" should be considered "defined" -pub trait ShaderDef { - fn is_defined(&self) -> bool; -} - -/// A collection of "shader defs", which define compile time definitions for shaders. -pub trait ShaderDefs { - fn shader_defs_len(&self) -> usize; - fn get_shader_def(&self, index: usize) -> Option<&str>; - fn iter_shader_defs(&self) -> ShaderDefIterator; -} - -/// Iterates over all [ShaderDef] items in [ShaderDefs] -pub struct ShaderDefIterator<'a> { - shader_defs: &'a dyn ShaderDefs, - index: usize, -} - -impl<'a> ShaderDefIterator<'a> { - pub fn new(shader_defs: &'a dyn ShaderDefs) -> Self { - Self { - shader_defs, - index: 0, - } - } -} -impl<'a> Iterator for ShaderDefIterator<'a> { - type Item = &'a str; - - fn next(&mut self) -> Option { - loop { - if self.index == self.shader_defs.shader_defs_len() { - return None; - } - let shader_def = self.shader_defs.get_shader_def(self.index); - self.index += 1; - if shader_def.is_some() { - return shader_def; - } - } - } - - fn size_hint(&self) -> (usize, Option) { - (0, Some(self.shader_defs.shader_defs_len())) - } -} - -impl ShaderDef for bool { - fn is_defined(&self) -> bool { - *self - } -} - -impl ShaderDef for Option> { - fn is_defined(&self) -> bool { - self.is_some() - } -} - -/// Updates [RenderPipelines] with the latest [ShaderDefs] -pub fn shader_defs_system(mut query: Query<(&T, &mut RenderPipelines), Without>) -where - T: ShaderDefs + Component, -{ - for (shader_defs, mut render_pipelines) in query.iter_mut() { - for shader_def in shader_defs.iter_shader_defs() { - for render_pipeline in render_pipelines.pipelines.iter_mut() { - render_pipeline - .specialization - .shader_specialization - .shader_defs - .insert(shader_def.to_string()); - } - } - } -} - -/// Clears each [RenderPipelines]' shader defs collection -pub fn clear_shader_defs_system(mut query: Query<&mut RenderPipelines>) { - for mut render_pipelines in query.iter_mut() { - for render_pipeline in render_pipelines.pipelines.iter_mut() { - render_pipeline - .specialization - .shader_specialization - .shader_defs - .clear(); - } - } -} - -/// Updates [RenderPipelines] with the latest [ShaderDefs] from a given asset type -pub fn asset_shader_defs_system( - assets: Res>, - mut query: Query<(&Handle, &mut RenderPipelines), Without>, -) where - T: ShaderDefs + Send + Sync + 'static, -{ - for (asset_handle, mut render_pipelines) in query.iter_mut() { - if let Some(asset_handle) = assets.get(asset_handle) { - let shader_defs = asset_handle; - for shader_def in shader_defs.iter_shader_defs() { - for render_pipeline in render_pipelines.pipelines.iter_mut() { - render_pipeline - .specialization - .shader_specialization - .shader_defs - .insert(shader_def.to_string()); - } - } - } - } -} diff --git a/crates/bevy_render/src/shader/shader_reflect.rs b/crates/bevy_render/src/shader/shader_reflect.rs deleted file mode 100644 index 9210c41ef8325..0000000000000 --- a/crates/bevy_render/src/shader/shader_reflect.rs +++ /dev/null @@ -1,416 +0,0 @@ -use crate::{ - pipeline::{ - BindGroupDescriptor, BindType, BindingDescriptor, BindingShaderStage, InputStepMode, - UniformProperty, VertexAttribute, VertexBufferLayout, VertexFormat, - }, - shader::{ShaderLayout, GL_FRONT_FACING, GL_INSTANCE_INDEX, GL_VERTEX_INDEX}, - texture::{TextureSampleType, TextureViewDimension}, -}; -use bevy_core::cast_slice; -use spirv_reflect::{ - types::{ - ReflectDescriptorBinding, ReflectDescriptorSet, ReflectDescriptorType, ReflectDimension, - ReflectShaderStageFlags, ReflectTypeDescription, ReflectTypeFlags, - }, - ShaderModule, -}; - -impl ShaderLayout { - pub fn from_spirv(spirv_data: &[u32], bevy_conventions: bool) -> ShaderLayout { - match ShaderModule::load_u8_data(cast_slice(spirv_data)) { - Ok(ref mut module) => { - // init - let entry_point_name = module.get_entry_point_name(); - let shader_stage = module.get_shader_stage(); - let mut bind_groups = Vec::new(); - for descriptor_set in module.enumerate_descriptor_sets(None).unwrap() { - let bind_group = reflect_bind_group(&descriptor_set, shader_stage); - bind_groups.push(bind_group); - } - - // obtain attribute descriptors from reflection - let mut vertex_attributes = Vec::new(); - for input_variable in module.enumerate_input_variables(None).unwrap() { - if input_variable.name == GL_VERTEX_INDEX - || input_variable.name == GL_INSTANCE_INDEX - || input_variable.name == GL_FRONT_FACING - { - continue; - } - // reflect vertex attribute descriptor and record it - vertex_attributes.push(VertexAttribute { - name: input_variable.name.clone().into(), - format: reflect_vertex_format( - input_variable.type_description.as_ref().unwrap(), - ), - offset: 0, - shader_location: input_variable.location, - }); - } - - vertex_attributes.sort_by(|a, b| a.shader_location.cmp(&b.shader_location)); - - let mut vertex_buffer_layout = Vec::new(); - for vertex_attribute in vertex_attributes.drain(..) { - let mut instance = false; - // obtain buffer name and instancing flag - let current_buffer_name = { - if bevy_conventions { - if vertex_attribute.name == GL_VERTEX_INDEX { - GL_VERTEX_INDEX.to_string() - } else { - instance = vertex_attribute.name.starts_with("I_"); - vertex_attribute.name.to_string() - } - } else { - "DefaultVertex".to_string() - } - }; - - // create a new buffer descriptor, per attribute! - vertex_buffer_layout.push(VertexBufferLayout { - attributes: vec![vertex_attribute], - name: current_buffer_name.into(), - step_mode: if instance { - InputStepMode::Instance - } else { - InputStepMode::Vertex - }, - stride: 0, - }); - } - - ShaderLayout { - bind_groups, - vertex_buffer_layout, - entry_point: entry_point_name, - } - } - Err(err) => panic!("Failed to reflect shader layout: {:?}.", err), - } - } -} - -fn reflect_bind_group( - descriptor_set: &ReflectDescriptorSet, - shader_stage: ReflectShaderStageFlags, -) -> BindGroupDescriptor { - let mut bindings = Vec::new(); - for descriptor_binding in descriptor_set.bindings.iter() { - let binding = reflect_binding(descriptor_binding, shader_stage); - bindings.push(binding); - } - - BindGroupDescriptor::new(descriptor_set.set, bindings) -} - -fn reflect_dimension(type_description: &ReflectTypeDescription) -> TextureViewDimension { - match type_description.traits.image.dim { - ReflectDimension::Type1d => TextureViewDimension::D1, - ReflectDimension::Type2d => TextureViewDimension::D2, - ReflectDimension::Type3d => TextureViewDimension::D3, - ReflectDimension::Cube => TextureViewDimension::Cube, - dimension => panic!("Unsupported image dimension: {:?}.", dimension), - } -} - -fn reflect_binding( - binding: &ReflectDescriptorBinding, - shader_stage: ReflectShaderStageFlags, -) -> BindingDescriptor { - let type_description = binding.type_description.as_ref().unwrap(); - let (name, bind_type) = match binding.descriptor_type { - ReflectDescriptorType::UniformBuffer => ( - &type_description.type_name, - BindType::Uniform { - has_dynamic_offset: false, - property: reflect_uniform(type_description), - }, - ), - ReflectDescriptorType::SampledImage => ( - &binding.name, - BindType::Texture { - view_dimension: reflect_dimension(type_description), - sample_type: TextureSampleType::Float { filterable: true }, - multisampled: false, - }, - ), - ReflectDescriptorType::StorageBuffer => ( - &type_description.type_name, - BindType::StorageBuffer { - has_dynamic_offset: false, - readonly: true, - }, - ), - // TODO: detect comparison "true" case: https://github.com/gpuweb/gpuweb/issues/552 - // TODO: detect filtering "true" case - ReflectDescriptorType::Sampler => ( - &binding.name, - BindType::Sampler { - comparison: false, - filtering: true, - }, - ), - _ => { - let ReflectDescriptorBinding { - descriptor_type, - name, - set, - binding, - .. - } = binding; - panic!( - "Unsupported shader bind type {:?} (name '{}', set {}, binding {})", - descriptor_type, name, set, binding - ); - } - }; - - let shader_stage = match shader_stage { - ReflectShaderStageFlags::COMPUTE => BindingShaderStage::COMPUTE, - ReflectShaderStageFlags::VERTEX => BindingShaderStage::VERTEX, - ReflectShaderStageFlags::FRAGMENT => BindingShaderStage::FRAGMENT, - _ => panic!("Only one specified shader stage is supported."), - }; - - BindingDescriptor { - index: binding.binding, - bind_type, - name: name.to_string(), - shader_stage, - } -} - -#[derive(Debug)] -enum NumberType { - Int, - UInt, - Float, -} - -fn reflect_uniform(type_description: &ReflectTypeDescription) -> UniformProperty { - if type_description - .type_flags - .contains(ReflectTypeFlags::STRUCT) - { - reflect_uniform_struct(type_description) - } else { - reflect_uniform_numeric(type_description) - } -} - -fn reflect_uniform_struct(type_description: &ReflectTypeDescription) -> UniformProperty { - let mut properties = Vec::new(); - for member in type_description.members.iter() { - properties.push(reflect_uniform(member)); - } - - UniformProperty::Struct(properties) -} - -fn reflect_uniform_numeric(type_description: &ReflectTypeDescription) -> UniformProperty { - let traits = &type_description.traits; - let number_type = if type_description.type_flags.contains(ReflectTypeFlags::INT) { - match traits.numeric.scalar.signedness { - 0 => NumberType::UInt, - 1 => NumberType::Int, - signedness => panic!("Unexpected signedness {}.", signedness), - } - } else if type_description - .type_flags - .contains(ReflectTypeFlags::FLOAT) - { - NumberType::Float - } else { - panic!("Unexpected type flag {:?}.", type_description.type_flags); - }; - - // TODO: handle scalar width here - - if type_description - .type_flags - .contains(ReflectTypeFlags::MATRIX) - { - match ( - number_type, - traits.numeric.matrix.column_count, - traits.numeric.matrix.row_count, - ) { - (NumberType::Float, 3, 3) => UniformProperty::Mat3, - (NumberType::Float, 4, 4) => UniformProperty::Mat4, - (number_type, column_count, row_count) => panic!( - "unexpected uniform property matrix format {:?} {}x{}", - number_type, column_count, row_count - ), - } - } else { - match (number_type, traits.numeric.vector.component_count) { - (NumberType::UInt, 0) => UniformProperty::UInt, - (NumberType::Int, 0) => UniformProperty::Int, - (NumberType::Int, 2) => UniformProperty::IVec2, - (NumberType::Float, 0) => UniformProperty::Float, - (NumberType::Float, 2) => UniformProperty::Vec2, - (NumberType::Float, 3) => UniformProperty::Vec3, - (NumberType::Float, 4) => UniformProperty::Vec4, - (NumberType::UInt, 4) => UniformProperty::UVec4, - (number_type, component_count) => panic!( - "unexpected uniform property format {:?} {}", - number_type, component_count - ), - } - } -} - -fn reflect_vertex_format(type_description: &ReflectTypeDescription) -> VertexFormat { - let traits = &type_description.traits; - let number_type = if type_description.type_flags.contains(ReflectTypeFlags::INT) { - match traits.numeric.scalar.signedness { - 0 => NumberType::UInt, - 1 => NumberType::Int, - signedness => panic!("Unexpected signedness {}.", signedness), - } - } else if type_description - .type_flags - .contains(ReflectTypeFlags::FLOAT) - { - NumberType::Float - } else { - panic!("Unexpected type flag {:?}.", type_description.type_flags); - }; - - let width = traits.numeric.scalar.width; - - match (number_type, traits.numeric.vector.component_count, width) { - (NumberType::UInt, 2, 8) => VertexFormat::Uint8x2, - (NumberType::UInt, 4, 8) => VertexFormat::Uint8x4, - (NumberType::Int, 2, 8) => VertexFormat::Sint8x2, - (NumberType::Int, 4, 8) => VertexFormat::Sint8x4, - (NumberType::UInt, 2, 16) => VertexFormat::Uint16x2, - (NumberType::UInt, 4, 16) => VertexFormat::Uint16x4, - (NumberType::Int, 2, 16) => VertexFormat::Sint16x2, - (NumberType::Int, 8, 16) => VertexFormat::Sint16x4, - (NumberType::Float, 2, 16) => VertexFormat::Float16x2, - (NumberType::Float, 4, 16) => VertexFormat::Float16x4, - (NumberType::Float, 0, 32) => VertexFormat::Float32, - (NumberType::Float, 2, 32) => VertexFormat::Float32x2, - (NumberType::Float, 3, 32) => VertexFormat::Float32x3, - (NumberType::Float, 4, 32) => VertexFormat::Float32x4, - (NumberType::UInt, 0, 32) => VertexFormat::Uint32, - (NumberType::UInt, 2, 32) => VertexFormat::Uint32x2, - (NumberType::UInt, 3, 32) => VertexFormat::Uint32x3, - (NumberType::UInt, 4, 32) => VertexFormat::Uint32x4, - (NumberType::Int, 0, 32) => VertexFormat::Sint32, - (NumberType::Int, 2, 32) => VertexFormat::Sint32x2, - (NumberType::Int, 3, 32) => VertexFormat::Sint32x3, - (NumberType::Int, 4, 32) => VertexFormat::Sint32x4, - (number_type, component_count, width) => panic!( - "unexpected uniform property format {:?} {} {}", - number_type, component_count, width - ), - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::shader::{Shader, ShaderStage}; - - impl VertexBufferLayout { - pub fn test_zero_stride(mut self) -> VertexBufferLayout { - self.stride = 0; - self - } - } - #[test] - fn test_reflection() { - let vertex_shader = Shader::from_glsl( - ShaderStage::Vertex, - r#" - #version 450 - layout(location = 0) in vec4 Vertex_Position; - layout(location = 1) in uvec4 Vertex_Normal; - layout(location = 2) in uvec4 I_TestInstancing_Property; - - layout(location = 0) out vec4 v_Position; - layout(set = 0, binding = 0) uniform CameraViewProj { - mat4 ViewProj; - }; - layout(set = 1, binding = 0) uniform texture2D Texture; - - void main() { - v_Position = Vertex_Position; - gl_Position = ViewProj * v_Position; - } - "#, - ) - .get_spirv_shader(None) - .unwrap(); - - let layout = vertex_shader.reflect_layout(true).unwrap(); - assert_eq!( - layout, - ShaderLayout { - entry_point: "main".into(), - vertex_buffer_layout: vec![ - VertexBufferLayout::new_from_attribute( - VertexAttribute { - name: "Vertex_Position".into(), - format: VertexFormat::Float32x4, - offset: 0, - shader_location: 0, - }, - InputStepMode::Vertex - ) - .test_zero_stride(), - VertexBufferLayout::new_from_attribute( - VertexAttribute { - name: "Vertex_Normal".into(), - format: VertexFormat::Uint32x4, - offset: 0, - shader_location: 1, - }, - InputStepMode::Vertex - ) - .test_zero_stride(), - VertexBufferLayout::new_from_attribute( - VertexAttribute { - name: "I_TestInstancing_Property".into(), - format: VertexFormat::Uint32x4, - offset: 0, - shader_location: 2, - }, - InputStepMode::Instance - ) - .test_zero_stride(), - ], - bind_groups: vec![ - BindGroupDescriptor::new( - 0, - vec![BindingDescriptor { - index: 0, - name: "CameraViewProj".into(), - bind_type: BindType::Uniform { - has_dynamic_offset: false, - property: UniformProperty::Struct(vec![UniformProperty::Mat4]), - }, - shader_stage: BindingShaderStage::VERTEX, - }] - ), - BindGroupDescriptor::new( - 1, - vec![BindingDescriptor { - index: 0, - name: "Texture".into(), - bind_type: BindType::Texture { - multisampled: false, - view_dimension: TextureViewDimension::D2, - sample_type: TextureSampleType::Float { filterable: true } - }, - shader_stage: BindingShaderStage::VERTEX, - }] - ), - ] - } - ); - } -} diff --git a/crates/bevy_render/src/texture/hdr_texture_loader.rs b/crates/bevy_render/src/texture/hdr_texture_loader.rs index da03340ef67ea..09890c0033a2a 100644 --- a/crates/bevy_render/src/texture/hdr_texture_loader.rs +++ b/crates/bevy_render/src/texture/hdr_texture_loader.rs @@ -1,7 +1,8 @@ -use super::{Extent3d, Texture, TextureDimension, TextureFormat}; +use crate::texture::{Image, TextureFormatPixelInfo}; use anyhow::Result; use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; use bevy_utils::BoxedFuture; +use wgpu::{Extent3d, TextureDimension, TextureFormat}; /// Loads HDR textures as Texture assets #[derive(Clone, Default)] @@ -35,8 +36,12 @@ impl AssetLoader for HdrTextureLoader { rgba_data.extend_from_slice(&alpha.to_ne_bytes()); } - let texture = Texture::new( - Extent3d::new(info.width, info.height, 1), + let texture = Image::new( + Extent3d { + width: info.width, + height: info.height, + depth_or_array_layers: 1, + }, TextureDimension::D2, rgba_data, format, diff --git a/pipelined/bevy_render2/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs similarity index 100% rename from pipelined/bevy_render2/src/texture/image.rs rename to crates/bevy_render/src/texture/image.rs diff --git a/crates/bevy_render/src/texture/image_texture_conversion.rs b/crates/bevy_render/src/texture/image_texture_conversion.rs index d355860f26dc8..d3c243bad4659 100644 --- a/crates/bevy_render/src/texture/image_texture_conversion.rs +++ b/crates/bevy_render/src/texture/image_texture_conversion.rs @@ -1,160 +1,158 @@ -use thiserror::Error; - -use super::{Extent3d, Texture, TextureDimension, TextureFormat}; - -impl From for Texture { - fn from(dyn_img: image::DynamicImage) -> Self { - use bevy_core::cast_slice; - let width; - let height; +use crate::texture::{Image, TextureFormatPixelInfo}; +use wgpu::{Extent3d, TextureDimension, TextureFormat}; + +// TODO: fix name? +/// Converts a [`DynamicImage`] to an [`Image`]. +pub(crate) fn image_to_texture(dyn_img: image::DynamicImage) -> Image { + use bevy_core::cast_slice; + let width; + let height; + + let data: Vec; + let format: TextureFormat; + + match dyn_img { + image::DynamicImage::ImageLuma8(i) => { + let i = image::DynamicImage::ImageLuma8(i).into_rgba8(); + width = i.width(); + height = i.height(); + format = TextureFormat::Rgba8UnormSrgb; + + data = i.into_raw(); + } + image::DynamicImage::ImageLumaA8(i) => { + let i = image::DynamicImage::ImageLumaA8(i).into_rgba8(); + width = i.width(); + height = i.height(); + format = TextureFormat::Rgba8UnormSrgb; - let data: Vec; - let format: TextureFormat; + data = i.into_raw(); + } + image::DynamicImage::ImageRgb8(i) => { + let i = image::DynamicImage::ImageRgb8(i).into_rgba8(); + width = i.width(); + height = i.height(); + format = TextureFormat::Rgba8UnormSrgb; - match dyn_img { - image::DynamicImage::ImageLuma8(i) => { - let i = image::DynamicImage::ImageLuma8(i).into_rgba8(); - width = i.width(); - height = i.height(); - format = TextureFormat::Rgba8UnormSrgb; + data = i.into_raw(); + } + image::DynamicImage::ImageRgba8(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Rgba8UnormSrgb; - data = i.into_raw(); - } - image::DynamicImage::ImageLumaA8(i) => { - let i = image::DynamicImage::ImageLumaA8(i).into_rgba8(); - width = i.width(); - height = i.height(); - format = TextureFormat::Rgba8UnormSrgb; + data = i.into_raw(); + } + image::DynamicImage::ImageBgr8(i) => { + let i = image::DynamicImage::ImageBgr8(i).into_bgra8(); - data = i.into_raw(); - } - image::DynamicImage::ImageRgb8(i) => { - let i = image::DynamicImage::ImageRgb8(i).into_rgba8(); - width = i.width(); - height = i.height(); - format = TextureFormat::Rgba8UnormSrgb; + width = i.width(); + height = i.height(); + format = TextureFormat::Bgra8UnormSrgb; - data = i.into_raw(); - } - image::DynamicImage::ImageRgba8(i) => { - width = i.width(); - height = i.height(); - format = TextureFormat::Rgba8UnormSrgb; - - data = i.into_raw(); - } - image::DynamicImage::ImageBgr8(i) => { - let i = image::DynamicImage::ImageBgr8(i).into_bgra8(); + data = i.into_raw(); + } + image::DynamicImage::ImageBgra8(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Bgra8UnormSrgb; - width = i.width(); - height = i.height(); - format = TextureFormat::Bgra8UnormSrgb; + data = i.into_raw(); + } + image::DynamicImage::ImageLuma16(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::R16Uint; - data = i.into_raw(); - } - image::DynamicImage::ImageBgra8(i) => { - width = i.width(); - height = i.height(); - format = TextureFormat::Bgra8UnormSrgb; + let raw_data = i.into_raw(); - data = i.into_raw(); - } - image::DynamicImage::ImageLuma16(i) => { - width = i.width(); - height = i.height(); - format = TextureFormat::R16Uint; - let raw_data = i.into_raw(); + data = cast_slice(&raw_data).to_owned(); + } + image::DynamicImage::ImageLumaA16(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Rg16Uint; - data = cast_slice(&raw_data).to_owned(); - } - image::DynamicImage::ImageLumaA16(i) => { - width = i.width(); - height = i.height(); - format = TextureFormat::Rg16Uint; + let raw_data = i.into_raw(); - let raw_data = i.into_raw(); + data = cast_slice(&raw_data).to_owned(); + } - data = cast_slice(&raw_data).to_owned(); + image::DynamicImage::ImageRgb16(image) => { + width = image.width(); + height = image.height(); + format = TextureFormat::Rgba16Uint; + + let mut local_data = + Vec::with_capacity(width as usize * height as usize * format.pixel_size()); + + for pixel in image.into_raw().chunks_exact(3) { + // TODO use the array_chunks method once stabilised + // https://github.com/rust-lang/rust/issues/74985 + let r = pixel[0]; + let g = pixel[1]; + let b = pixel[2]; + let a = u16::max_value(); + + local_data.extend_from_slice(&r.to_ne_bytes()); + local_data.extend_from_slice(&g.to_ne_bytes()); + local_data.extend_from_slice(&b.to_ne_bytes()); + local_data.extend_from_slice(&a.to_ne_bytes()); } - image::DynamicImage::ImageRgb16(image) => { - width = image.width(); - height = image.height(); - format = TextureFormat::Rgba16Uint; - - let mut local_data = - Vec::with_capacity(width as usize * height as usize * format.pixel_size()); - - for pixel in image.into_raw().chunks_exact(3) { - // TODO use the array_chunks method once stabilised - // https://github.com/rust-lang/rust/issues/74985 - let r = pixel[0]; - let g = pixel[1]; - let b = pixel[2]; - let a = u16::MAX; - - local_data.extend_from_slice(&r.to_ne_bytes()); - local_data.extend_from_slice(&g.to_ne_bytes()); - local_data.extend_from_slice(&b.to_ne_bytes()); - local_data.extend_from_slice(&a.to_ne_bytes()); - } - - data = local_data; - } - image::DynamicImage::ImageRgba16(i) => { - width = i.width(); - height = i.height(); - format = TextureFormat::Rgba16Uint; + data = local_data; + } + image::DynamicImage::ImageRgba16(i) => { + width = i.width(); + height = i.height(); + format = TextureFormat::Rgba16Uint; - let raw_data = i.into_raw(); + let raw_data = i.into_raw(); - data = cast_slice(&raw_data).to_owned(); - } + data = cast_slice(&raw_data).to_owned(); } - - Texture::new( - Extent3d::new(width, height, 1), - TextureDimension::D2, - data, - format, - ) } -} -#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)] -pub enum TextureConversionError { - #[error("Unsupported texture format")] - UnsupportedFormat, - #[error("Invalid texture size")] - InvalidSize, + Image::new( + Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + TextureDimension::D2, + data, + format, + ) } -impl TryFrom for image::DynamicImage { - type Error = TextureConversionError; - - fn try_from(texture: Texture) -> Result { - match texture.format { - TextureFormat::R8Unorm => { - image::ImageBuffer::from_raw(texture.size.width, texture.size.height, texture.data) - .map(image::DynamicImage::ImageLuma8) - .ok_or(TextureConversionError::InvalidSize) - } - TextureFormat::Rg8Unorm => { - image::ImageBuffer::from_raw(texture.size.width, texture.size.height, texture.data) - .map(image::DynamicImage::ImageLumaA8) - .ok_or(TextureConversionError::InvalidSize) - } - TextureFormat::Rgba8UnormSrgb => { - image::ImageBuffer::from_raw(texture.size.width, texture.size.height, texture.data) - .map(image::DynamicImage::ImageRgba8) - .ok_or(TextureConversionError::InvalidSize) - } - TextureFormat::Bgra8UnormSrgb => { - image::ImageBuffer::from_raw(texture.size.width, texture.size.height, texture.data) - .map(image::DynamicImage::ImageBgra8) - .ok_or(TextureConversionError::InvalidSize) - } - _ => Err(TextureConversionError::UnsupportedFormat), - } +/// Converts an [`Image`] to a [`DynamicImage`]. Not all [`TextureFormat`] are +/// covered, therefore it will return `None` if the format is unsupported. +pub(crate) fn texture_to_image(texture: &Image) -> Option { + match texture.texture_descriptor.format { + TextureFormat::R8Unorm => image::ImageBuffer::from_raw( + texture.texture_descriptor.size.width, + texture.texture_descriptor.size.height, + texture.data.clone(), + ) + .map(image::DynamicImage::ImageLuma8), + TextureFormat::Rg8Unorm => image::ImageBuffer::from_raw( + texture.texture_descriptor.size.width, + texture.texture_descriptor.size.height, + texture.data.clone(), + ) + .map(image::DynamicImage::ImageLumaA8), + TextureFormat::Rgba8UnormSrgb => image::ImageBuffer::from_raw( + texture.texture_descriptor.size.width, + texture.texture_descriptor.size.height, + texture.data.clone(), + ) + .map(image::DynamicImage::ImageRgba8), + TextureFormat::Bgra8UnormSrgb => image::ImageBuffer::from_raw( + texture.texture_descriptor.size.width, + texture.texture_descriptor.size.height, + texture.data.clone(), + ) + .map(image::DynamicImage::ImageBgra8), + _ => None, } } diff --git a/crates/bevy_render/src/texture/image_texture_loader.rs b/crates/bevy_render/src/texture/image_texture_loader.rs index a0527020de559..20e6c774ccbd0 100644 --- a/crates/bevy_render/src/texture/image_texture_loader.rs +++ b/crates/bevy_render/src/texture/image_texture_loader.rs @@ -1,27 +1,15 @@ -use super::texture::{ImageType, Texture, TextureError}; use anyhow::Result; use bevy_asset::{AssetLoader, LoadContext, LoadedAsset}; use bevy_utils::BoxedFuture; use thiserror::Error; +use crate::texture::{Image, ImageType, TextureError}; + /// Loader for images that can be read by the `image` crate. #[derive(Clone, Default)] pub struct ImageTextureLoader; -const FILE_EXTENSIONS: &[&str] = &[ - #[cfg(feature = "png")] - "png", - #[cfg(feature = "dds")] - "dds", - #[cfg(feature = "tga")] - "tga", - #[cfg(feature = "jpeg")] - "jpg", - #[cfg(feature = "jpeg")] - "jpeg", - #[cfg(feature = "bmp")] - "bmp", -]; +const FILE_EXTENSIONS: &[&str] = &["png", "dds", "tga", "jpg", "jpeg", "bmp"]; impl AssetLoader for ImageTextureLoader { fn load<'a>( @@ -33,13 +21,12 @@ impl AssetLoader for ImageTextureLoader { // use the file extension for the image type let ext = load_context.path().extension().unwrap().to_str().unwrap(); - let dyn_img = - Texture::from_buffer(bytes, ImageType::Extension(ext)).map_err(|err| { - FileTextureError { - error: err, - path: format!("{}", load_context.path().display()), - } - })?; + let dyn_img = Image::from_buffer(bytes, ImageType::Extension(ext)).map_err(|err| { + FileTextureError { + error: err, + path: format!("{}", load_context.path().display()), + } + })?; load_context.set_default_asset(LoadedAsset::new(dyn_img)); Ok(()) @@ -51,7 +38,7 @@ impl AssetLoader for ImageTextureLoader { } } -/// An error that occurs when loading a texture from a file +/// An error that occurs when loading a texture from a file. #[derive(Error, Debug)] pub struct FileTextureError { error: TextureError, diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 0116754b7fda3..82ca2f4128288 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -1,18 +1,57 @@ #[cfg(feature = "hdr")] mod hdr_texture_loader; -mod image_texture_loader; -mod sampler_descriptor; #[allow(clippy::module_inception)] -mod texture; -mod texture_descriptor; -mod texture_dimension; +mod image; +mod image_texture_loader; +mod texture_cache; pub(crate) mod image_texture_conversion; +pub use self::image::*; #[cfg(feature = "hdr")] pub use hdr_texture_loader::*; pub use image_texture_loader::*; -pub use sampler_descriptor::*; -pub use texture::*; -pub use texture_descriptor::*; -pub use texture_dimension::*; +pub use texture_cache::*; + +use crate::{render_asset::RenderAssetPlugin, RenderApp, RenderStage}; +use bevy_app::{App, Plugin}; +use bevy_asset::{AddAsset, Assets}; + +// TODO: replace Texture names with Image names? +/// Adds the [`Image`] as an asset and makes sure that they are extracted and prepared for the GPU. +pub struct ImagePlugin; + +impl Plugin for ImagePlugin { + fn build(&self, app: &mut App) { + #[cfg(feature = "png")] + { + app.init_asset_loader::(); + } + + app.add_plugin(RenderAssetPlugin::::default()) + .add_asset::(); + app.world + .get_resource_mut::>() + .unwrap() + .set_untracked(DEFAULT_IMAGE_HANDLE, Image::default()); + + app.sub_app(RenderApp) + .init_resource::() + .add_system_to_stage(RenderStage::Cleanup, update_texture_cache_system); + } +} + +pub trait BevyDefault { + fn bevy_default() -> Self; +} + +impl BevyDefault for wgpu::TextureFormat { + fn bevy_default() -> Self { + if cfg!(target_os = "android") || cfg!(target_arch = "wasm32") { + // Bgra8UnormSrgb texture missing on some Android devices + wgpu::TextureFormat::Rgba8UnormSrgb + } else { + wgpu::TextureFormat::Bgra8UnormSrgb + } + } +} diff --git a/crates/bevy_render/src/texture/sampler_descriptor.rs b/crates/bevy_render/src/texture/sampler_descriptor.rs deleted file mode 100644 index 453bf70d988a6..0000000000000 --- a/crates/bevy_render/src/texture/sampler_descriptor.rs +++ /dev/null @@ -1,79 +0,0 @@ -use crate::pipeline::CompareFunction; -use std::num::NonZeroU8; - -/// Describes a sampler -#[derive(Debug, Copy, Clone)] -pub struct SamplerDescriptor { - pub address_mode_u: AddressMode, - pub address_mode_v: AddressMode, - pub address_mode_w: AddressMode, - pub mag_filter: FilterMode, - pub min_filter: FilterMode, - pub mipmap_filter: FilterMode, - pub lod_min_clamp: f32, - pub lod_max_clamp: f32, - pub compare_function: Option, - pub anisotropy_clamp: Option, - pub border_color: Option, -} - -impl SamplerDescriptor { - /// Sets the address mode for all dimensions of the sampler descriptor. - pub fn set_address_mode(&mut self, address_mode: AddressMode) { - self.address_mode_u = address_mode; - self.address_mode_v = address_mode; - self.address_mode_w = address_mode; - } -} - -impl Default for SamplerDescriptor { - fn default() -> Self { - SamplerDescriptor { - address_mode_u: AddressMode::ClampToEdge, - address_mode_v: AddressMode::ClampToEdge, - address_mode_w: AddressMode::ClampToEdge, - mag_filter: FilterMode::Nearest, - min_filter: FilterMode::Linear, - mipmap_filter: FilterMode::Nearest, - lod_min_clamp: 0.0, - lod_max_clamp: std::f32::MAX, - compare_function: None, - anisotropy_clamp: None, - border_color: None, - } - } -} - -/// How edges should be handled in texture addressing. -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -pub enum AddressMode { - ClampToEdge = 0, - Repeat = 1, - MirrorRepeat = 2, -} - -impl Default for AddressMode { - fn default() -> Self { - AddressMode::ClampToEdge - } -} - -/// Texel mixing mode when sampling between texels. -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -pub enum FilterMode { - Nearest = 0, - Linear = 1, -} - -impl Default for FilterMode { - fn default() -> Self { - FilterMode::Nearest - } -} - -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -pub enum SamplerBorderColor { - TransparentBlack, - OpaqueBlack, - OpaqueWhite, -} diff --git a/crates/bevy_render/src/texture/texture.rs b/crates/bevy_render/src/texture/texture.rs deleted file mode 100644 index 6ef80ed4cf4d2..0000000000000 --- a/crates/bevy_render/src/texture/texture.rs +++ /dev/null @@ -1,297 +0,0 @@ -use super::{Extent3d, SamplerDescriptor, TextureDescriptor, TextureDimension, TextureFormat}; -use crate::renderer::{ - RenderResource, RenderResourceContext, RenderResourceId, RenderResourceType, -}; -use bevy_asset::{AssetEvent, Assets, Handle}; -use bevy_ecs::{event::EventReader, system::Res}; -use bevy_reflect::TypeUuid; -use bevy_utils::HashSet; -use thiserror::Error; - -pub const TEXTURE_ASSET_INDEX: u64 = 0; -pub const SAMPLER_ASSET_INDEX: u64 = 1; - -#[derive(Debug, Clone, TypeUuid)] -#[uuid = "6ea26da6-6cf8-4ea2-9986-1d7bf6c17d6f"] -pub struct Texture { - pub data: Vec, - pub size: Extent3d, - pub format: TextureFormat, - pub dimension: TextureDimension, - pub sampler: SamplerDescriptor, -} - -impl Default for Texture { - fn default() -> Self { - Texture { - data: Default::default(), - size: Extent3d { - width: 1, - height: 1, - depth_or_array_layers: 1, - }, - format: TextureFormat::Rgba8UnormSrgb, - dimension: TextureDimension::D2, - sampler: Default::default(), - } - } -} - -impl Texture { - pub fn new( - size: Extent3d, - dimension: TextureDimension, - data: Vec, - format: TextureFormat, - ) -> Self { - debug_assert_eq!( - size.volume() * format.pixel_size(), - data.len(), - "Pixel data, size and format have to match", - ); - Self { - data, - size, - format, - dimension, - ..Default::default() - } - } - - pub fn new_fill( - size: Extent3d, - dimension: TextureDimension, - pixel: &[u8], - format: TextureFormat, - ) -> Self { - let mut value = Texture { - format, - dimension, - ..Default::default() - }; - value.resize(size); - - debug_assert_eq!( - pixel.len() % format.pixel_size(), - 0, - "Must not have incomplete pixel data." - ); - debug_assert!( - pixel.len() <= value.data.len(), - "Fill data must fit within pixel buffer." - ); - - for current_pixel in value.data.chunks_exact_mut(pixel.len()) { - current_pixel.copy_from_slice(pixel); - } - value - } - - pub fn aspect_2d(&self) -> f32 { - self.size.height as f32 / self.size.width as f32 - } - - pub fn resize(&mut self, size: Extent3d) { - self.size = size; - self.data - .resize(size.volume() * self.format.pixel_size(), 0); - } - - /// Changes the `size`, asserting that the total number of data elements (pixels) remains the - /// same. - pub fn reinterpret_size(&mut self, new_size: Extent3d) { - assert!( - new_size.volume() == self.size.volume(), - "Incompatible sizes: old = {:?} new = {:?}", - self.size, - new_size - ); - - self.size = new_size; - } - - /// Takes a 2D texture containing vertically stacked images of the same size, and reinterprets - /// it as a 2D array texture, where each of the stacked images becomes one layer of the - /// array. This is primarily for use with the `texture2DArray` shader uniform type. - pub fn reinterpret_stacked_2d_as_array(&mut self, layers: u32) { - // Must be a stacked image, and the height must be divisible by layers. - assert!(self.dimension == TextureDimension::D2); - assert!(self.size.depth_or_array_layers == 1); - assert_eq!(self.size.height % layers, 0); - - self.reinterpret_size(Extent3d { - width: self.size.width, - height: self.size.height / layers, - depth_or_array_layers: layers, - }); - } - - /// Convert a texture from a format to another - /// Only a few formats are supported as input and output: - /// - `TextureFormat::R8Unorm` - /// - `TextureFormat::Rg8Unorm` - /// - `TextureFormat::Rgba8UnormSrgb` - /// - `TextureFormat::Bgra8UnormSrgb` - pub fn convert(self, new_format: TextureFormat) -> Option { - self.try_into() - .ok() - .and_then(|img: image::DynamicImage| match new_format { - TextureFormat::R8Unorm => Some(image::DynamicImage::ImageLuma8(img.into_luma8())), - TextureFormat::Rg8Unorm => { - Some(image::DynamicImage::ImageLumaA8(img.into_luma_alpha8())) - } - TextureFormat::Rgba8UnormSrgb => { - Some(image::DynamicImage::ImageRgba8(img.into_rgba8())) - } - TextureFormat::Bgra8UnormSrgb => { - Some(image::DynamicImage::ImageBgra8(img.into_bgra8())) - } - _ => None, - }) - .map(|image| image.into()) - } - - pub fn texture_resource_system( - render_resource_context: Res>, - textures: Res>, - mut texture_events: EventReader>, - ) { - let render_resource_context = &**render_resource_context; - let mut changed_textures = HashSet::default(); - for event in texture_events.iter() { - match event { - AssetEvent::Created { handle } => { - changed_textures.insert(handle); - } - AssetEvent::Modified { handle } => { - changed_textures.insert(handle); - Self::remove_current_texture_resources(render_resource_context, handle); - } - AssetEvent::Removed { handle } => { - Self::remove_current_texture_resources(render_resource_context, handle); - // if texture was modified and removed in the same update, ignore the - // modification events are ordered so future modification - // events are ok - changed_textures.remove(handle); - } - } - } - - for texture_handle in changed_textures.iter() { - if let Some(texture) = textures.get(*texture_handle) { - let texture_descriptor: TextureDescriptor = texture.into(); - let texture_resource = render_resource_context.create_texture(texture_descriptor); - - let sampler_resource = render_resource_context.create_sampler(&texture.sampler); - - render_resource_context.set_asset_resource( - texture_handle, - RenderResourceId::Texture(texture_resource), - TEXTURE_ASSET_INDEX, - ); - render_resource_context.set_asset_resource( - texture_handle, - RenderResourceId::Sampler(sampler_resource), - SAMPLER_ASSET_INDEX, - ); - } - } - } - - fn remove_current_texture_resources( - render_resource_context: &dyn RenderResourceContext, - handle: &Handle, - ) { - if let Some(RenderResourceId::Texture(resource)) = - render_resource_context.get_asset_resource(handle, TEXTURE_ASSET_INDEX) - { - render_resource_context.remove_texture(resource); - render_resource_context.remove_asset_resource(handle, TEXTURE_ASSET_INDEX); - } - if let Some(RenderResourceId::Sampler(resource)) = - render_resource_context.get_asset_resource(handle, SAMPLER_ASSET_INDEX) - { - render_resource_context.remove_sampler(resource); - render_resource_context.remove_asset_resource(handle, SAMPLER_ASSET_INDEX); - } - } - - /// Load a bytes buffer in a [`Texture`], according to type `image_type`, using the `image` - /// crate` - pub fn from_buffer(buffer: &[u8], image_type: ImageType) -> Result { - let format = match image_type { - ImageType::MimeType(mime_type) => match mime_type { - "image/png" => Ok(image::ImageFormat::Png), - "image/vnd-ms.dds" => Ok(image::ImageFormat::Dds), - "image/x-targa" => Ok(image::ImageFormat::Tga), - "image/x-tga" => Ok(image::ImageFormat::Tga), - "image/jpeg" => Ok(image::ImageFormat::Jpeg), - "image/bmp" => Ok(image::ImageFormat::Bmp), - "image/x-bmp" => Ok(image::ImageFormat::Bmp), - _ => Err(TextureError::InvalidImageMimeType(mime_type.to_string())), - }, - ImageType::Extension(extension) => image::ImageFormat::from_extension(extension) - .ok_or_else(|| TextureError::InvalidImageMimeType(extension.to_string())), - }?; - - // Load the image in the expected format. - // Some formats like PNG allow for R or RG textures too, so the texture - // format needs to be determined. For RGB textures an alpha channel - // needs to be added, so the image data needs to be converted in those - // cases. - - let dyn_img = image::load_from_memory_with_format(buffer, format)?; - Ok(dyn_img.into()) - } -} - -impl RenderResource for Option> { - fn resource_type(&self) -> Option { - self.as_ref().map(|_texture| RenderResourceType::Texture) - } - - fn write_buffer_bytes(&self, _buffer: &mut [u8]) {} - - fn buffer_byte_len(&self) -> Option { - None - } - - fn texture(&self) -> Option<&Handle> { - self.as_ref() - } -} - -impl RenderResource for Handle { - fn resource_type(&self) -> Option { - Some(RenderResourceType::Texture) - } - - fn write_buffer_bytes(&self, _buffer: &mut [u8]) {} - - fn buffer_byte_len(&self) -> Option { - None - } - - fn texture(&self) -> Option<&Handle> { - Some(self) - } -} - -/// An error that occurs when loading a texture -#[derive(Error, Debug)] -pub enum TextureError { - #[error("invalid image mime type")] - InvalidImageMimeType(String), - #[error("invalid image extension")] - InvalidImageExtension(String), - #[error("failed to load an image: {0}")] - ImageError(#[from] image::ImageError), -} - -/// Type of a raw image buffer -pub enum ImageType<'a> { - /// Mime type of an image, for example `"image/png"` - MimeType(&'a str), - /// Extension of an image file, for example `"png"` - Extension(&'a str), -} diff --git a/pipelined/bevy_render2/src/texture/texture_cache.rs b/crates/bevy_render/src/texture/texture_cache.rs similarity index 100% rename from pipelined/bevy_render2/src/texture/texture_cache.rs rename to crates/bevy_render/src/texture/texture_cache.rs diff --git a/crates/bevy_render/src/texture/texture_descriptor.rs b/crates/bevy_render/src/texture/texture_descriptor.rs deleted file mode 100644 index 9f83c86457d5f..0000000000000 --- a/crates/bevy_render/src/texture/texture_descriptor.rs +++ /dev/null @@ -1,69 +0,0 @@ -use super::{Extent3d, Texture, TextureDimension, TextureFormat, TextureUsages}; - -/// Describes a texture -#[derive(Debug, Copy, Clone, Eq, PartialEq)] -pub struct TextureDescriptor { - pub size: Extent3d, - pub mip_level_count: u32, - pub sample_count: u32, - pub dimension: TextureDimension, - pub format: TextureFormat, - pub usage: TextureUsages, -} - -impl From<&Texture> for TextureDescriptor { - fn from(texture: &Texture) -> Self { - TextureDescriptor { - size: texture.size, - mip_level_count: 1, - sample_count: 1, - dimension: texture.dimension, - format: texture.format, - usage: TextureUsages::SAMPLED | TextureUsages::COPY_DST, - } - } -} - -impl Default for TextureDescriptor { - fn default() -> Self { - TextureDescriptor { - size: Extent3d { - width: 1, - height: 1, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: TextureDimension::D2, - format: TextureFormat::Rgba8UnormSrgb, - usage: TextureUsages::SAMPLED | TextureUsages::COPY_DST, - } - } -} - -#[derive(Hash, Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)] -pub enum StorageTextureAccess { - /// The texture can only be read in the shader and it must be annotated with `readonly`. - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(set=0, binding=0, r32f) readonly uniform image2D myStorageImage; - /// ``` - ReadOnly, - /// The texture can only be written in the shader and it must be annotated with `writeonly`. - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(set=0, binding=0, r32f) writeonly uniform image2D myStorageImage; - /// ``` - WriteOnly, - /// The texture can be both read and written in the shader. - /// `wgpu::Features::STORAGE_TEXTURE_ACCESS_READ_WRITE` must be enabled to use this access - /// mode. - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(set=0, binding=0, r32f) uniform image2D myStorageImage; - /// ``` - ReadWrite, -} diff --git a/crates/bevy_render/src/texture/texture_dimension.rs b/crates/bevy_render/src/texture/texture_dimension.rs deleted file mode 100644 index e1cdda77eb7c7..0000000000000 --- a/crates/bevy_render/src/texture/texture_dimension.rs +++ /dev/null @@ -1,296 +0,0 @@ -// NOTE: These are currently just copies of the wgpu types, but they might change in the future - -use bevy_math::Vec3; - -/// Dimensions of a particular texture view. -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub enum TextureViewDimension { - D1, - D2, - D2Array, - Cube, - CubeArray, - D3, -} - -/// Dimensionality of a texture. -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)] -pub enum TextureDimension { - D1, - D2, - D3, -} - -// TODO: use math type here -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct Extent3d { - pub width: u32, - pub height: u32, - pub depth_or_array_layers: u32, -} - -impl Extent3d { - pub fn new(width: u32, height: u32, depth_or_array_layers: u32) -> Self { - Self { - width, - height, - depth_or_array_layers, - } - } - - pub fn volume(&self) -> usize { - (self.width * self.height * self.depth_or_array_layers) as usize - } - - pub fn as_vec3(&self) -> Vec3 { - Vec3::new( - self.width as f32, - self.height as f32, - self.depth_or_array_layers as f32, - ) - } -} - -/// Type of data shaders will read from a texture. -#[derive(Copy, Hash, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] -pub enum TextureSampleType { - /// Sampling returns floats. - /// - /// If `filterable` is false, the texture can't be sampled with - /// a filtering sampler. - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(binding = 0) - /// uniform texture2D t; - /// ``` - Float { filterable: bool }, - /// Sampling does the depth reference comparison. - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(binding = 0) - /// uniform texture2DShadow t; - /// ``` - Depth, - /// Sampling returns signed integers. - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(binding = 0) - /// uniform itexture2D t; - /// ``` - Sint, - /// Sampling returns unsigned integers. - /// - /// Example GLSL syntax: - /// ```cpp,ignore - /// layout(binding = 0) - /// uniform utexture2D t; - /// ``` - Uint, -} - -pub struct PixelInfo { - pub type_size: usize, - pub num_components: usize, -} - -/// Underlying texture data format. -/// -/// If there is a conversion in the format (such as srgb -> linear), The conversion listed is for -/// loading from texture in a shader. When writing to the texture, the opposite conversion takes -/// place. -#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] -pub enum TextureFormat { - // Normal 8 bit formats - R8Unorm = 0, - R8Snorm = 1, - R8Uint = 2, - R8Sint = 3, - - // Normal 16 bit formats - R16Uint = 4, - R16Sint = 5, - R16Float = 6, - Rg8Unorm = 7, - Rg8Snorm = 8, - Rg8Uint = 9, - Rg8Sint = 10, - - // Normal 32 bit formats - R32Uint = 11, - R32Sint = 12, - R32Float = 13, - Rg16Uint = 14, - Rg16Sint = 15, - Rg16Float = 16, - Rgba8Unorm = 17, - Rgba8UnormSrgb = 18, - Rgba8Snorm = 19, - Rgba8Uint = 20, - Rgba8Sint = 21, - Bgra8Unorm = 22, - Bgra8UnormSrgb = 23, - - // Packed 32 bit formats - Rgb10a2Unorm = 24, - Rg11b10Float = 25, - - // Normal 64 bit formats - Rg32Uint = 26, - Rg32Sint = 27, - Rg32Float = 28, - Rgba16Uint = 29, - Rgba16Sint = 30, - Rgba16Float = 31, - - // Normal 128 bit formats - Rgba32Uint = 32, - Rgba32Sint = 33, - Rgba32Float = 34, - - // Depth and stencil formats - Depth32Float = 35, - Depth24Plus = 36, - Depth24PlusStencil8 = 37, -} - -impl TextureFormat { - pub fn pixel_info(&self) -> PixelInfo { - let type_size = match self { - // 8bit - TextureFormat::R8Unorm - | TextureFormat::R8Snorm - | TextureFormat::R8Uint - | TextureFormat::R8Sint - | TextureFormat::Rg8Unorm - | TextureFormat::Rg8Snorm - | TextureFormat::Rg8Uint - | TextureFormat::Rg8Sint - | TextureFormat::Rgba8Unorm - | TextureFormat::Rgba8UnormSrgb - | TextureFormat::Rgba8Snorm - | TextureFormat::Rgba8Uint - | TextureFormat::Rgba8Sint - | TextureFormat::Bgra8Unorm - | TextureFormat::Bgra8UnormSrgb => 1, - - // 16bit - TextureFormat::R16Uint - | TextureFormat::R16Sint - | TextureFormat::R16Float - | TextureFormat::Rg16Uint - | TextureFormat::Rg16Sint - | TextureFormat::Rg16Float - | TextureFormat::Rgba16Uint - | TextureFormat::Rgba16Sint - | TextureFormat::Rgba16Float => 2, - - // 32bit - TextureFormat::R32Uint - | TextureFormat::R32Sint - | TextureFormat::R32Float - | TextureFormat::Rg32Uint - | TextureFormat::Rg32Sint - | TextureFormat::Rg32Float - | TextureFormat::Rgba32Uint - | TextureFormat::Rgba32Sint - | TextureFormat::Rgba32Float - | TextureFormat::Depth32Float => 4, - - // special cases - TextureFormat::Rgb10a2Unorm => 4, - TextureFormat::Rg11b10Float => 4, - TextureFormat::Depth24Plus => 3, // FIXME is this correct? - TextureFormat::Depth24PlusStencil8 => 4, - }; - - let components = match self { - TextureFormat::R8Unorm - | TextureFormat::R8Snorm - | TextureFormat::R8Uint - | TextureFormat::R8Sint - | TextureFormat::R16Uint - | TextureFormat::R16Sint - | TextureFormat::R16Float - | TextureFormat::R32Uint - | TextureFormat::R32Sint - | TextureFormat::R32Float => 1, - - TextureFormat::Rg8Unorm - | TextureFormat::Rg8Snorm - | TextureFormat::Rg8Uint - | TextureFormat::Rg8Sint - | TextureFormat::Rg16Uint - | TextureFormat::Rg16Sint - | TextureFormat::Rg16Float - | TextureFormat::Rg32Uint - | TextureFormat::Rg32Sint - | TextureFormat::Rg32Float => 2, - - TextureFormat::Rgba8Unorm - | TextureFormat::Rgba8UnormSrgb - | TextureFormat::Rgba8Snorm - | TextureFormat::Rgba8Uint - | TextureFormat::Rgba8Sint - | TextureFormat::Bgra8Unorm - | TextureFormat::Bgra8UnormSrgb - | TextureFormat::Rgba16Uint - | TextureFormat::Rgba16Sint - | TextureFormat::Rgba16Float - | TextureFormat::Rgba32Uint - | TextureFormat::Rgba32Sint - | TextureFormat::Rgba32Float => 4, - - // special cases - TextureFormat::Rgb10a2Unorm - | TextureFormat::Rg11b10Float - | TextureFormat::Depth32Float - | TextureFormat::Depth24Plus - | TextureFormat::Depth24PlusStencil8 => 1, - }; - - PixelInfo { - type_size, - num_components: components, - } - } - - pub fn pixel_size(&self) -> usize { - let info = self.pixel_info(); - info.type_size * info.num_components - } -} - -impl Default for TextureFormat { - fn default() -> Self { - if cfg!(target_os = "android") { - // Bgra8UnormSrgb texture missing on some Android devices - TextureFormat::Rgba8UnormSrgb - } else { - TextureFormat::Bgra8UnormSrgb - } - } -} - -bitflags::bitflags! { - #[repr(transparent)] - pub struct TextureUsages: u32 { - const COPY_SRC = 1; - const COPY_DST = 2; - const SAMPLED = 4; - const STORAGE = 8; - const OUTPUT_ATTACHMENT = 16; - const NONE = 0; - /// The combination of all read-only usages. - const READ_ALL = Self::COPY_SRC.bits | Self::SAMPLED.bits; - /// The combination of all write-only and read-write usages. - const WRITE_ALL = Self::COPY_DST.bits | Self::STORAGE.bits | Self::OUTPUT_ATTACHMENT.bits; - /// The combination of all usages that the are guaranteed to be be ordered by the hardware. - /// If a usage is not ordered, then even if it doesn't change between draw calls, there - /// still need to be pipeline barriers inserted for synchronization. - const ORDERED = Self::READ_ALL.bits | Self::OUTPUT_ATTACHMENT.bits; - const UNINITIALIZED = 0xFFFF; - } -} diff --git a/pipelined/bevy_render2/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs similarity index 100% rename from pipelined/bevy_render2/src/view/mod.rs rename to crates/bevy_render/src/view/mod.rs diff --git a/pipelined/bevy_render2/src/view/visibility/mod.rs b/crates/bevy_render/src/view/visibility/mod.rs similarity index 100% rename from pipelined/bevy_render2/src/view/visibility/mod.rs rename to crates/bevy_render/src/view/visibility/mod.rs diff --git a/pipelined/bevy_render2/src/view/visibility/render_layers.rs b/crates/bevy_render/src/view/visibility/render_layers.rs similarity index 100% rename from pipelined/bevy_render2/src/view/visibility/render_layers.rs rename to crates/bevy_render/src/view/visibility/render_layers.rs diff --git a/pipelined/bevy_render2/src/view/window.rs b/crates/bevy_render/src/view/window.rs similarity index 100% rename from pipelined/bevy_render2/src/view/window.rs rename to crates/bevy_render/src/view/window.rs diff --git a/crates/bevy_render/src/wireframe/mod.rs b/crates/bevy_render/src/wireframe/mod.rs deleted file mode 100644 index d9ba4d39588f9..0000000000000 --- a/crates/bevy_render/src/wireframe/mod.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crate::{ - draw::DrawContext, - mesh::Indices, - pipeline::{PipelineDescriptor, PipelineSpecialization, RenderPipeline}, - prelude::*, - shader::Shader, -}; -use bevy_app::prelude::*; -use bevy_asset::{Assets, Handle, HandleUntyped}; -use bevy_ecs::{ - component::Component, - query::{QueryState, With}, - reflect::ReflectComponent, - system::{QuerySet, Res}, - world::Mut, -}; -use bevy_reflect::{Reflect, TypeUuid}; -use bevy_utils::HashSet; - -mod pipeline; - -pub const WIREFRAME_PIPELINE_HANDLE: HandleUntyped = - HandleUntyped::weak_from_u64(PipelineDescriptor::TYPE_UUID, 0x137c75ab7e9ad7f5); - -#[derive(Debug, Default)] -pub struct WireframePlugin; - -impl Plugin for WireframePlugin { - fn build(&self, app: &mut App) { - app.init_resource::() - .add_system_to_stage(crate::RenderStage::Draw, draw_wireframes_system); - let world = app.world.cell(); - let mut shaders = world.get_resource_mut::>().unwrap(); - let mut pipelines = world - .get_resource_mut::>() - .unwrap(); - pipelines.set_untracked( - WIREFRAME_PIPELINE_HANDLE, - pipeline::build_wireframe_pipeline(&mut shaders), - ); - } -} - -#[derive(Component, Debug, Clone, Reflect, Default)] -#[reflect(Component)] -pub struct Wireframe; - -#[derive(Debug, Clone)] -pub struct WireframeConfig { - pub global: bool, -} - -impl Default for WireframeConfig { - fn default() -> Self { - WireframeConfig { global: false } - } -} - -#[allow(clippy::type_complexity)] -pub fn draw_wireframes_system( - mut draw_context: DrawContext, - msaa: Res, - meshes: Res>, - wireframe_config: Res, - mut query: QuerySet<( - QueryState<(&mut Draw, &mut RenderPipelines, &Handle, &Visible)>, - QueryState<(&mut Draw, &mut RenderPipelines, &Handle, &Visible), With>, - )>, -) { - let iterator = |(mut draw, mut render_pipelines, mesh_handle, visible): ( - Mut, - Mut, - &Handle, - &Visible, - )| { - if !visible.is_visible { - return; - } - - // don't render if the mesh isn't loaded yet - let mesh = if let Some(mesh) = meshes.get(mesh_handle) { - mesh - } else { - return; - }; - - let mut render_pipeline = RenderPipeline::specialized( - WIREFRAME_PIPELINE_HANDLE.typed(), - PipelineSpecialization { - sample_count: msaa.samples, - strip_index_format: None, - shader_specialization: Default::default(), - primitive_topology: mesh.primitive_topology(), - dynamic_bindings: render_pipelines - .bindings - .iter_dynamic_bindings() - .map(|name| name.to_string()) - .collect::>(), - vertex_buffer_layout: mesh.get_vertex_buffer_layout(), - }, - ); - render_pipeline.dynamic_bindings_generation = - render_pipelines.bindings.dynamic_bindings_generation(); - - draw_context - .set_pipeline( - &mut draw, - &render_pipeline.pipeline, - &render_pipeline.specialization, - ) - .unwrap(); - draw_context - .set_bind_groups_from_bindings(&mut draw, &mut [&mut render_pipelines.bindings]) - .unwrap(); - draw_context - .set_vertex_buffers_from_bindings(&mut draw, &[&render_pipelines.bindings]) - .unwrap(); - - match mesh.indices() { - Some(Indices::U32(indices)) => draw.draw_indexed(0..indices.len() as u32, 0, 0..1), - Some(Indices::U16(indices)) => draw.draw_indexed(0..indices.len() as u32, 0, 0..1), - None => draw.draw(0..mesh.count_vertices() as u32, 0..1), - }; - }; - - if wireframe_config.global { - query.q0().iter_mut().for_each(iterator); - } else { - query.q1().iter_mut().for_each(iterator); - } -} diff --git a/crates/bevy_render/src/wireframe/pipeline.rs b/crates/bevy_render/src/wireframe/pipeline.rs deleted file mode 100644 index 5c4b3cc0a75de..0000000000000 --- a/crates/bevy_render/src/wireframe/pipeline.rs +++ /dev/null @@ -1,30 +0,0 @@ -use crate::{ - pipeline::{FrontFace, PipelineDescriptor, PolygonMode, PrimitiveState, PrimitiveTopology}, - shader::{Shader, ShaderStage, ShaderStages}, -}; -use bevy_asset::Assets; - -pub(crate) fn build_wireframe_pipeline(shaders: &mut Assets) -> PipelineDescriptor { - PipelineDescriptor { - name: Some("wireframe".into()), - primitive: PrimitiveState { - topology: PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: FrontFace::Ccw, - cull_mode: None, - polygon_mode: PolygonMode::Line, - clamp_depth: false, - conservative: false, - }, - ..PipelineDescriptor::default_config(ShaderStages { - vertex: shaders.add(Shader::from_glsl( - ShaderStage::Vertex, - include_str!("wireframe.vert"), - )), - fragment: Some(shaders.add(Shader::from_glsl( - ShaderStage::Fragment, - include_str!("wireframe.frag"), - ))), - }) - } -} diff --git a/crates/bevy_render/src/wireframe/wireframe.frag b/crates/bevy_render/src/wireframe/wireframe.frag deleted file mode 100644 index 6d3971d8518ed..0000000000000 --- a/crates/bevy_render/src/wireframe/wireframe.frag +++ /dev/null @@ -1,8 +0,0 @@ -#version 450 - -layout(location = 0) out vec4 o_Target; - - -void main() { - o_Target = vec4(1.0, 1.0, 1.0, 1.0); -} diff --git a/crates/bevy_render/src/wireframe/wireframe.vert b/crates/bevy_render/src/wireframe/wireframe.vert deleted file mode 100644 index 87b32a667a1cc..0000000000000 --- a/crates/bevy_render/src/wireframe/wireframe.vert +++ /dev/null @@ -1,16 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 Vertex_Position; - -layout(set = 0, binding = 0) uniform CameraViewProj { - mat4 ViewProj; -}; - -layout(set = 1, binding = 0) uniform Transform { - mat4 Model; -}; - -void main() { - vec3 v_Position = (Model * vec4(Vertex_Position, 1.0)).xyz; - gl_Position = ViewProj * vec4(v_Position, 1.0); -} diff --git a/crates/bevy_sprite/Cargo.toml b/crates/bevy_sprite/Cargo.toml index 4b1097c8ee153..64163006280a0 100644 --- a/crates/bevy_sprite/Cargo.toml +++ b/crates/bevy_sprite/Cargo.toml @@ -13,19 +13,21 @@ keywords = ["bevy"] bevy_app = { path = "../bevy_app", version = "0.5.0" } bevy_asset = { path = "../bevy_asset", version = "0.5.0" } bevy_core = { path = "../bevy_core", version = "0.5.0" } +bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.5.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" } bevy_log = { path = "../bevy_log", version = "0.5.0" } bevy_math = { path = "../bevy_math", version = "0.5.0" } -bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] } +bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = [ + "bevy", +] } bevy_render = { path = "../bevy_render", version = "0.5.0" } bevy_transform = { path = "../bevy_transform", version = "0.5.0" } bevy_utils = { path = "../bevy_utils", version = "0.5.0" } -bevy_window = { path = "../bevy_window", version = "0.5.0" } # other -# direct dependency required for derive macro -bytemuck = { version = "1", features = ["derive"] } -rectangle-pack = "0.4" -thiserror = "1.0" +bytemuck = { version = "1.5", features = ["derive"] } +crevice = { path = "../crevice", version = "0.8.0", features = ["glam"] } guillotiere = "0.6.0" +thiserror = "1.0" +rectangle-pack = "0.4" serde = { version = "1", features = ["derive"] } diff --git a/pipelined/bevy_sprite2/src/bundle.rs b/crates/bevy_sprite/src/bundle.rs similarity index 92% rename from pipelined/bevy_sprite2/src/bundle.rs rename to crates/bevy_sprite/src/bundle.rs index 67d84449780a7..d97e1857ded21 100644 --- a/pipelined/bevy_sprite2/src/bundle.rs +++ b/crates/bevy_sprite/src/bundle.rs @@ -4,14 +4,14 @@ use crate::{ }; use bevy_asset::Handle; use bevy_ecs::bundle::Bundle; -use bevy_render2::{ +use bevy_render::{ texture::{Image, DEFAULT_IMAGE_HANDLE}, view::{ComputedVisibility, Visibility}, }; use bevy_transform::components::{GlobalTransform, Transform}; #[derive(Bundle, Clone)] -pub struct PipelinedSpriteBundle { +pub struct SpriteBundle { pub sprite: Sprite, pub transform: Transform, pub global_transform: GlobalTransform, @@ -22,7 +22,7 @@ pub struct PipelinedSpriteBundle { pub computed_visibility: ComputedVisibility, } -impl Default for PipelinedSpriteBundle { +impl Default for SpriteBundle { fn default() -> Self { Self { sprite: Default::default(), @@ -37,7 +37,7 @@ impl Default for PipelinedSpriteBundle { /// A Bundle of components for drawing a single sprite from a sprite sheet (also referred /// to as a `TextureAtlas`) #[derive(Bundle, Clone, Default)] -pub struct PipelinedSpriteSheetBundle { +pub struct SpriteSheetBundle { /// The specific sprite from the texture atlas to be drawn pub sprite: TextureAtlasSprite, /// A handle to the texture atlas that holds the sprite images diff --git a/crates/bevy_sprite/src/color_material.rs b/crates/bevy_sprite/src/color_material.rs deleted file mode 100644 index d4070ab9b0c5e..0000000000000 --- a/crates/bevy_sprite/src/color_material.rs +++ /dev/null @@ -1,153 +0,0 @@ -use bevy_app::{EventReader, Events, ManualEventReader}; -use bevy_asset::{self, AssetEvent, Assets, Handle}; -use bevy_ecs::system::{Local, Res, ResMut}; -use bevy_reflect::TypeUuid; -use bevy_render::{color::Color, renderer::RenderResources, shader::ShaderDefs, texture::Texture}; -use bevy_utils::{HashMap, HashSet}; - -#[derive(Debug, RenderResources, ShaderDefs, TypeUuid)] -#[uuid = "506cff92-a9f3-4543-862d-6851c7fdfc99"] -pub struct ColorMaterial { - pub color: Color, - #[shader_def] - pub texture: Option>, -} - -impl ColorMaterial { - pub fn color(color: Color) -> Self { - ColorMaterial { - color, - texture: None, - } - } - - pub fn texture(texture: Handle) -> Self { - ColorMaterial { - color: Color::WHITE, - texture: Some(texture), - } - } - - pub fn modulated_texture(texture: Handle, color: Color) -> Self { - ColorMaterial { - color, - texture: Some(texture), - } - } -} - -impl Default for ColorMaterial { - fn default() -> Self { - ColorMaterial { - color: Color::rgb(1.0, 1.0, 1.0), - texture: None, - } - } -} - -impl From for ColorMaterial { - fn from(color: Color) -> Self { - ColorMaterial::color(color) - } -} - -impl From> for ColorMaterial { - fn from(texture: Handle) -> Self { - ColorMaterial::texture(texture) - } -} - -// Temporary solution for sub-assets change handling, see https://github.com/bevyengine/bevy/issues/1161#issuecomment-780467768 -// TODO: should be removed when pipelined rendering is done -#[allow(clippy::type_complexity)] -pub(crate) fn material_texture_detection_system( - mut texture_to_material: Local, HashSet>>>, - mut material_to_texture: Local, Handle>>, - materials: Res>, - mut texture_events: EventReader>, - (mut material_events_reader, mut material_events): ( - Local>>, - ResMut>>, - ), -) { - for event in material_events_reader.iter(&material_events) { - match event { - AssetEvent::Created { handle } => { - if let Some(texture) = materials.get(handle).and_then(|mat| mat.texture.as_ref()) { - material_to_texture.insert(handle.clone_weak(), texture.clone_weak()); - texture_to_material - .entry(texture.clone_weak()) - .or_default() - .insert(handle.clone_weak()); - } - } - AssetEvent::Modified { handle } => { - let old_texture = material_to_texture.get(handle).cloned(); - match ( - materials.get(handle).and_then(|mat| mat.texture.as_ref()), - old_texture, - ) { - (None, None) => (), - (Some(texture), None) => { - material_to_texture.insert(handle.clone_weak(), texture.clone_weak()); - texture_to_material - .entry(texture.clone_weak()) - .or_default() - .insert(handle.clone_weak()); - } - (None, Some(texture)) => { - material_to_texture.remove(handle); - texture_to_material - .entry(texture.clone_weak()) - .or_default() - .remove(handle); - } - (Some(new_texture), Some(old_texture)) => { - if &old_texture == new_texture { - continue; - } - material_to_texture.insert(handle.clone_weak(), new_texture.clone_weak()); - texture_to_material - .entry(new_texture.clone_weak()) - .or_default() - .insert(handle.clone_weak()); - texture_to_material - .entry(old_texture.clone_weak()) - .or_default() - .remove(handle); - } - } - } - AssetEvent::Removed { handle } => { - if let Some(texture) = materials.get(handle).and_then(|mat| mat.texture.as_ref()) { - material_to_texture.remove(handle); - texture_to_material - .entry(texture.clone_weak()) - .or_default() - .remove(handle); - } - } - } - } - - let mut changed_textures = HashSet::default(); - for event in texture_events.iter() { - match event { - AssetEvent::Created { handle } - | AssetEvent::Modified { handle } - | AssetEvent::Removed { handle } => { - changed_textures.insert(handle); - } - } - } - - for texture_handle in changed_textures.iter() { - if let Some(materials) = texture_to_material.get(texture_handle) { - for material in materials.iter() { - material_events.send(AssetEvent::Modified { - handle: material.clone_weak(), - }); - } - } - } -} diff --git a/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs b/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs index 0353bb1ea9f4d..2cdf99a1710f2 100644 --- a/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/dynamic_texture_atlas_builder.rs @@ -1,7 +1,7 @@ use crate::{Rect, TextureAtlas}; use bevy_asset::Assets; use bevy_math::Vec2; -use bevy_render::texture::Texture; +use bevy_render::texture::{Image, TextureFormatPixelInfo}; use guillotiere::{size2, Allocation, AtlasAllocator}; pub struct DynamicTextureAtlasBuilder { @@ -20,12 +20,12 @@ impl DynamicTextureAtlasBuilder { pub fn add_texture( &mut self, texture_atlas: &mut TextureAtlas, - textures: &mut Assets, - texture: &Texture, - ) -> Option { + textures: &mut Assets, + texture: &Image, + ) -> Option { let allocation = self.atlas_allocator.allocate(size2( - texture.size.width as i32 + self.padding, - texture.size.height as i32 + self.padding, + texture.texture_descriptor.size.width as i32 + self.padding, + texture.texture_descriptor.size.height as i32 + self.padding, )); if let Some(allocation) = allocation { let atlas_texture = textures.get_mut(&texture_atlas.texture).unwrap(); @@ -33,8 +33,7 @@ impl DynamicTextureAtlasBuilder { let mut rect: Rect = allocation.rectangle.into(); rect.max.x -= self.padding as f32; rect.max.y -= self.padding as f32; - texture_atlas.add_texture(rect); - Some((texture_atlas.len() - 1) as u32) + Some(texture_atlas.add_texture(rect)) } else { None } @@ -65,16 +64,16 @@ impl DynamicTextureAtlasBuilder { fn place_texture( &mut self, - atlas_texture: &mut Texture, + atlas_texture: &mut Image, allocation: Allocation, - texture: &Texture, + texture: &Image, ) { let mut rect = allocation.rectangle; rect.max.x -= self.padding; rect.max.y -= self.padding; - let atlas_width = atlas_texture.size.width as usize; + let atlas_width = atlas_texture.texture_descriptor.size.width as usize; let rect_width = rect.width() as usize; - let format_size = atlas_texture.format.pixel_size(); + let format_size = atlas_texture.texture_descriptor.format.pixel_size(); for (texture_y, bound_y) in (rect.min.y..rect.max.y).map(|i| i as usize).enumerate() { let begin = (bound_y * atlas_width + rect.min.x as usize) * format_size; diff --git a/crates/bevy_sprite/src/entity.rs b/crates/bevy_sprite/src/entity.rs deleted file mode 100644 index 70fd8d5699640..0000000000000 --- a/crates/bevy_sprite/src/entity.rs +++ /dev/null @@ -1,86 +0,0 @@ -use crate::{ - render::SPRITE_PIPELINE_HANDLE, sprite::Sprite, ColorMaterial, TextureAtlas, - TextureAtlasSprite, QUAD_HANDLE, SPRITE_SHEET_PIPELINE_HANDLE, -}; -use bevy_asset::Handle; -use bevy_ecs::bundle::Bundle; -use bevy_render::{ - mesh::Mesh, - pipeline::{RenderPipeline, RenderPipelines}, - prelude::{Draw, Visible}, - render_graph::base::MainPass, -}; -use bevy_transform::prelude::{GlobalTransform, Transform}; - -#[derive(Bundle, Clone)] -pub struct SpriteBundle { - pub sprite: Sprite, - pub mesh: Handle, // TODO: maybe abstract this out - pub material: Handle, - pub main_pass: MainPass, - pub draw: Draw, - pub visible: Visible, - pub render_pipelines: RenderPipelines, - pub transform: Transform, - pub global_transform: GlobalTransform, -} - -impl Default for SpriteBundle { - fn default() -> Self { - Self { - mesh: QUAD_HANDLE.typed(), - render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::new( - SPRITE_PIPELINE_HANDLE.typed(), - )]), - visible: Visible { - is_transparent: true, - ..Default::default() - }, - main_pass: MainPass, - draw: Default::default(), - sprite: Default::default(), - material: Default::default(), - transform: Default::default(), - global_transform: Default::default(), - } - } -} - -/// A Bundle of components for drawing a single sprite from a sprite sheet (also referred -/// to as a `TextureAtlas`) -#[derive(Bundle, Clone)] -pub struct SpriteSheetBundle { - /// The specific sprite from the texture atlas to be drawn - pub sprite: TextureAtlasSprite, - /// A handle to the texture atlas that holds the sprite images - pub texture_atlas: Handle, - /// Data pertaining to how the sprite is drawn on the screen - pub draw: Draw, - pub visible: Visible, - pub render_pipelines: RenderPipelines, - pub main_pass: MainPass, - pub mesh: Handle, // TODO: maybe abstract this out - pub transform: Transform, - pub global_transform: GlobalTransform, -} - -impl Default for SpriteSheetBundle { - fn default() -> Self { - Self { - render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::new( - SPRITE_SHEET_PIPELINE_HANDLE.typed(), - )]), - visible: Visible { - is_transparent: true, - ..Default::default() - }, - main_pass: MainPass, - mesh: QUAD_HANDLE.typed(), - draw: Default::default(), - sprite: Default::default(), - texture_atlas: Default::default(), - transform: Default::default(), - global_transform: Default::default(), - } - } -} diff --git a/crates/bevy_sprite/src/frustum_culling.rs b/crates/bevy_sprite/src/frustum_culling.rs deleted file mode 100644 index 0c75722c6fefb..0000000000000 --- a/crates/bevy_sprite/src/frustum_culling.rs +++ /dev/null @@ -1,120 +0,0 @@ -use bevy_asset::{Assets, Handle}; -use bevy_ecs::prelude::{Commands, Entity, Query, Res, With}; -use bevy_math::Vec2; -use bevy_render::{ - camera::{ActiveCameras, Camera}, - draw::OutsideFrustum, -}; -use bevy_transform::components::Transform; -use bevy_window::Windows; - -use crate::{Sprite, TextureAtlas, TextureAtlasSprite}; - -struct Rect { - position: Vec2, - size: Vec2, -} - -impl Rect { - #[inline] - pub fn is_intersecting(&self, other: Rect) -> bool { - self.position.distance(other.position) < (self.get_radius() + other.get_radius()) - } - - #[inline] - pub fn get_radius(&self) -> f32 { - let half_size = self.size / Vec2::splat(2.0); - (half_size.x.powf(2.0) + half_size.y.powf(2.0)).sqrt() - } -} - -pub fn sprite_frustum_culling_system( - mut commands: Commands, - windows: Res, - active_cameras: Res, - camera_transforms: Query<&Transform, With>, - culled_sprites: Query<&OutsideFrustum, With>, - sprites: Query<(Entity, &Transform, &Sprite)>, -) { - let window_size = if let Some(window) = windows.get_primary() { - Vec2::new(window.width(), window.height()) - } else { - return; - }; - - for active_camera_entity in active_cameras.iter().filter_map(|a| a.entity) { - if let Ok(camera_transform) = camera_transforms.get(active_camera_entity) { - let camera_size = window_size * camera_transform.scale.truncate(); - - let rect = Rect { - position: camera_transform.translation.truncate(), - size: camera_size, - }; - - for (entity, drawable_transform, sprite) in sprites.iter() { - let sprite_rect = Rect { - position: drawable_transform.translation.truncate(), - size: sprite.size, - }; - - if rect.is_intersecting(sprite_rect) { - if culled_sprites.get(entity).is_ok() { - commands.entity(entity).remove::(); - } - } else if culled_sprites.get(entity).is_err() { - commands.entity(entity).insert(OutsideFrustum); - } - } - } - } -} - -pub fn atlas_frustum_culling_system( - mut commands: Commands, - windows: Res, - active_cameras: Res, - textures: Res>, - camera_transforms: Query<&Transform, With>, - culled_sprites: Query<&OutsideFrustum, With>, - sprites: Query<( - Entity, - &Transform, - &TextureAtlasSprite, - &Handle, - )>, -) { - let window = windows.get_primary().unwrap(); - let window_size = Vec2::new(window.width(), window.height()); - - for active_camera_entity in active_cameras.iter().filter_map(|a| a.entity) { - if let Ok(camera_transform) = camera_transforms.get(active_camera_entity) { - let camera_size = window_size * camera_transform.scale.truncate(); - - let rect = Rect { - position: camera_transform.translation.truncate(), - size: camera_size, - }; - - for (entity, drawable_transform, sprite, atlas_handle) in sprites.iter() { - if let Some(atlas) = textures.get(atlas_handle) { - if let Some(sprite) = atlas.textures.get(sprite.index as usize) { - let size = Vec2::new(sprite.width(), sprite.height()); - - let sprite_rect = Rect { - position: drawable_transform.translation.truncate(), - size, - }; - - if rect.is_intersecting(sprite_rect) { - if culled_sprites.get(entity).is_ok() { - commands.entity(entity).remove::(); - } - } else if culled_sprites.get(entity).is_err() { - commands.entity(entity).insert(OutsideFrustum); - } - } - } - } - } - } -} diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 2ec3f918813b1..c9ef2cf8f54fb 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -1,24 +1,24 @@ -pub mod collide_aabb; -pub mod entity; - -mod color_material; +mod bundle; mod dynamic_texture_atlas_builder; -mod frustum_culling; mod rect; mod render; mod sprite; mod texture_atlas; mod texture_atlas_builder; +pub mod collide_aabb; + pub mod prelude { #[doc(hidden)] pub use crate::{ - entity::{SpriteBundle, SpriteSheetBundle}, - ColorMaterial, Sprite, SpriteResizeMode, TextureAtlas, TextureAtlasSprite, + bundle::{SpriteBundle, SpriteSheetBundle}, + sprite::Sprite, + texture_atlas::{TextureAtlas, TextureAtlasSprite}, + TextureAtlasBuilder, }; } -pub use color_material::*; +pub use bundle::*; pub use dynamic_texture_atlas_builder::*; pub use rect::*; pub use render::*; @@ -27,77 +27,55 @@ pub use texture_atlas::*; pub use texture_atlas_builder::*; use bevy_app::prelude::*; -use bevy_asset::{AddAsset, Assets, Handle, HandleUntyped}; -use bevy_math::Vec2; +use bevy_asset::{AddAsset, Assets, HandleUntyped}; +use bevy_core_pipeline::Transparent2d; +use bevy_ecs::schedule::{ParallelSystemDescriptorCoercion, SystemLabel}; use bevy_reflect::TypeUuid; use bevy_render::{ - mesh::{shape, Mesh}, - pipeline::PipelineDescriptor, - render_graph::RenderGraph, - shader::{asset_shader_defs_system, Shader}, + render_phase::DrawFunctions, + render_resource::{Shader, SpecializedPipelines}, + RenderApp, RenderStage, }; -use sprite::sprite_system; - -#[derive(Debug, Clone, Default)] -pub struct SpriteSettings { - /// Enable sprite frustum culling. - /// - /// # Warning - /// This is currently experimental. It does not work correctly in all cases. - pub frustum_culling_enabled: bool, -} #[derive(Default)] pub struct SpritePlugin; -pub const QUAD_HANDLE: HandleUntyped = - HandleUntyped::weak_from_u64(Mesh::TYPE_UUID, 14240461981130137526); +pub const SPRITE_SHADER_HANDLE: HandleUntyped = + HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 2763343953151597127); + +#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] +pub enum SpriteSystem { + ExtractSprite, +} impl Plugin for SpritePlugin { fn build(&self, app: &mut App) { - app.add_asset::() - .add_asset::() - .register_type::() - .register_type::() - .add_system_to_stage(CoreStage::PostUpdate, sprite_system) - .add_system_to_stage(CoreStage::PostUpdate, material_texture_detection_system) + let mut shaders = app.world.get_resource_mut::>().unwrap(); + let sprite_shader = Shader::from_wgsl(include_str!("render/sprite.wgsl")); + shaders.set_untracked(SPRITE_SHADER_HANDLE, sprite_shader); + app.add_asset::().register_type::(); + let render_app = app.sub_app(RenderApp); + render_app + .init_resource::() + .init_resource::() + .init_resource::>() + .init_resource::() + .init_resource::() + .init_resource::() .add_system_to_stage( - CoreStage::PostUpdate, - asset_shader_defs_system::, - ); - - let sprite_settings = app - .world - .get_resource_or_insert_with(SpriteSettings::default) - .clone(); - if sprite_settings.frustum_culling_enabled { - app.add_system_to_stage( - CoreStage::PostUpdate, - frustum_culling::sprite_frustum_culling_system, + RenderStage::Extract, + render::extract_sprites.label(SpriteSystem::ExtractSprite), ) - .add_system_to_stage( - CoreStage::PostUpdate, - frustum_culling::atlas_frustum_culling_system, - ); - } - let world_cell = app.world.cell(); - let mut render_graph = world_cell.get_resource_mut::().unwrap(); - let mut pipelines = world_cell - .get_resource_mut::>() - .unwrap(); - let mut shaders = world_cell.get_resource_mut::>().unwrap(); - crate::render::add_sprite_graph(&mut render_graph, &mut pipelines, &mut shaders); + .add_system_to_stage(RenderStage::Extract, render::extract_sprite_events) + .add_system_to_stage(RenderStage::Prepare, render::prepare_sprites) + .add_system_to_stage(RenderStage::Queue, queue_sprites); - let mut meshes = world_cell.get_resource_mut::>().unwrap(); - let mut color_materials = world_cell - .get_resource_mut::>() - .unwrap(); - color_materials.set_untracked(Handle::::default(), ColorMaterial::default()); - meshes.set_untracked( - QUAD_HANDLE, - // Use a flipped quad because the camera is facing "forward" but quads should face - // backward - Mesh::from(shape::Quad::new(Vec2::new(1.0, 1.0))), - ) + let draw_sprite = DrawSprite::new(&mut render_app.world); + render_app + .world + .get_resource::>() + .unwrap() + .write() + .add(draw_sprite); } } diff --git a/crates/bevy_sprite/src/rect.rs b/crates/bevy_sprite/src/rect.rs index 888aa91661f45..a90a59d71d68b 100644 --- a/crates/bevy_sprite/src/rect.rs +++ b/crates/bevy_sprite/src/rect.rs @@ -1,10 +1,9 @@ -use bevy_core::{Pod, Zeroable}; use bevy_math::Vec2; /// A rectangle defined by two points. There is no defined origin, so 0,0 could be anywhere /// (top-left, bottom-left, etc) #[repr(C)] -#[derive(Default, Clone, Copy, Debug, Pod, Zeroable)] +#[derive(Default, Clone, Copy, Debug)] pub struct Rect { /// The beginning point of the rect pub min: Vec2, @@ -20,4 +19,8 @@ impl Rect { pub fn height(&self) -> f32 { self.max.y - self.min.y } + + pub fn size(&self) -> Vec2 { + Vec2::new(self.width(), self.height()) + } } diff --git a/crates/bevy_sprite/src/render/mod.rs b/crates/bevy_sprite/src/render/mod.rs index a166e5ebe791f..f50bfa484611b 100644 --- a/crates/bevy_sprite/src/render/mod.rs +++ b/crates/bevy_sprite/src/render/mod.rs @@ -1,173 +1,609 @@ -use crate::{ColorMaterial, Sprite, TextureAtlas, TextureAtlasSprite}; -use bevy_asset::{Assets, HandleUntyped}; -use bevy_reflect::TypeUuid; +use std::{cmp::Ordering, ops::Range}; + +use crate::{ + texture_atlas::{TextureAtlas, TextureAtlasSprite}, + Rect, Sprite, SPRITE_SHADER_HANDLE, +}; +use bevy_asset::{AssetEvent, Assets, Handle}; +use bevy_core::FloatOrd; +use bevy_core_pipeline::Transparent2d; +use bevy_ecs::{ + prelude::*, + system::{lifetimeless::*, SystemState}, +}; +use bevy_math::{const_vec3, Mat4, Vec2, Vec3, Vec4Swizzles}; use bevy_render::{ - pipeline::{ - BlendComponent, BlendFactor, BlendOperation, BlendState, ColorTargetState, ColorWrite, - CompareFunction, DepthBiasState, DepthStencilState, FrontFace, PipelineDescriptor, - PolygonMode, PrimitiveState, PrimitiveTopology, StencilFaceState, StencilState, - }, - render_graph::{base, AssetRenderResourcesNode, RenderGraph, RenderResourcesNode}, - shader::{Shader, ShaderStage, ShaderStages}, - texture::TextureFormat, + color::Color, + render_asset::RenderAssets, + render_phase::{Draw, DrawFunctions, RenderPhase, TrackedRenderPass}, + render_resource::*, + renderer::{RenderDevice, RenderQueue}, + texture::{BevyDefault, Image}, + view::{ComputedVisibility, ViewUniform, ViewUniformOffset, ViewUniforms}, + RenderWorld, }; +use bevy_transform::components::GlobalTransform; +use bevy_utils::HashMap; +use bytemuck::{Pod, Zeroable}; +use crevice::std140::AsStd140; -pub const SPRITE_PIPELINE_HANDLE: HandleUntyped = - HandleUntyped::weak_from_u64(PipelineDescriptor::TYPE_UUID, 2785347840338765446); - -pub const SPRITE_SHEET_PIPELINE_HANDLE: HandleUntyped = - HandleUntyped::weak_from_u64(PipelineDescriptor::TYPE_UUID, 9016885805180281612); - -pub fn build_sprite_sheet_pipeline(shaders: &mut Assets) -> PipelineDescriptor { - PipelineDescriptor { - depth_stencil: Some(DepthStencilState { - format: TextureFormat::Depth32Float, - depth_write_enabled: true, - depth_compare: CompareFunction::LessEqual, - stencil: StencilState { - front: StencilFaceState::IGNORE, - back: StencilFaceState::IGNORE, - read_mask: 0, - write_mask: 0, - }, - bias: DepthBiasState { - constant: 0, - slope_scale: 0.0, - clamp: 0.0, - }, - }), - color_target_states: vec![ColorTargetState { - format: TextureFormat::default(), - blend: Some(BlendState { - color: BlendComponent { - src_factor: BlendFactor::SrcAlpha, - dst_factor: BlendFactor::OneMinusSrcAlpha, - operation: BlendOperation::Add, +pub struct SpritePipeline { + view_layout: BindGroupLayout, + material_layout: BindGroupLayout, +} + +impl FromWorld for SpritePipeline { + fn from_world(world: &mut World) -> Self { + let world = world.cell(); + let render_device = world.get_resource::().unwrap(); + + let view_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::VERTEX | ShaderStages::FRAGMENT, + ty: BindingType::Buffer { + ty: BufferBindingType::Uniform, + has_dynamic_offset: true, + min_binding_size: BufferSize::new(ViewUniform::std140_size_static() as u64), }, - alpha: BlendComponent { - src_factor: BlendFactor::One, - dst_factor: BlendFactor::One, - operation: BlendOperation::Add, + count: None, + }], + label: Some("sprite_view_layout"), + }); + + let material_layout = render_device.create_bind_group_layout(&BindGroupLayoutDescriptor { + entries: &[ + BindGroupLayoutEntry { + binding: 0, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Texture { + multisampled: false, + sample_type: TextureSampleType::Float { filterable: true }, + view_dimension: TextureViewDimension::D2, + }, + count: None, }, - }), - write_mask: ColorWrite::ALL, - }], - primitive: PrimitiveState { - topology: PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: FrontFace::Ccw, - cull_mode: None, - polygon_mode: PolygonMode::Fill, - clamp_depth: false, - conservative: false, - }, - ..PipelineDescriptor::new(ShaderStages { - vertex: shaders.add(Shader::from_glsl( - ShaderStage::Vertex, - include_str!("sprite_sheet.vert"), - )), - fragment: Some(shaders.add(Shader::from_glsl( - ShaderStage::Fragment, - include_str!("sprite_sheet.frag"), - ))), - }) + BindGroupLayoutEntry { + binding: 1, + visibility: ShaderStages::FRAGMENT, + ty: BindingType::Sampler { + comparison: false, + filtering: true, + }, + count: None, + }, + ], + label: Some("sprite_material_layout"), + }); + + SpritePipeline { + view_layout, + material_layout, + } } } -pub fn build_sprite_pipeline(shaders: &mut Assets) -> PipelineDescriptor { - PipelineDescriptor { - depth_stencil: Some(DepthStencilState { - format: TextureFormat::Depth32Float, - depth_write_enabled: true, - depth_compare: CompareFunction::LessEqual, - stencil: StencilState { - front: StencilFaceState::IGNORE, - back: StencilFaceState::IGNORE, - read_mask: 0, - write_mask: 0, - }, - bias: DepthBiasState { - constant: 0, - slope_scale: 0.0, - clamp: 0.0, - }, - }), - color_target_states: vec![ColorTargetState { - format: TextureFormat::default(), - blend: Some(BlendState { - color: BlendComponent { - src_factor: BlendFactor::SrcAlpha, - dst_factor: BlendFactor::OneMinusSrcAlpha, - operation: BlendOperation::Add, +#[derive(Clone, Copy, Hash, PartialEq, Eq)] +pub struct SpritePipelineKey { + colored: bool, +} + +impl SpecializedPipeline for SpritePipeline { + type Key = SpritePipelineKey; + + fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor { + let mut vertex_buffer_layout = VertexBufferLayout { + array_stride: 20, + step_mode: VertexStepMode::Vertex, + attributes: vec![ + VertexAttribute { + format: VertexFormat::Float32x3, + offset: 0, + shader_location: 0, }, - alpha: BlendComponent { - src_factor: BlendFactor::One, - dst_factor: BlendFactor::One, - operation: BlendOperation::Add, + VertexAttribute { + format: VertexFormat::Float32x2, + offset: 12, + shader_location: 1, }, + ], + }; + let mut shader_defs = Vec::new(); + if key.colored { + shader_defs.push("COLORED".to_string()); + vertex_buffer_layout.attributes.push(VertexAttribute { + format: VertexFormat::Uint32, + offset: 20, + shader_location: 2, + }); + vertex_buffer_layout.array_stride += 4; + } + + RenderPipelineDescriptor { + vertex: VertexState { + shader: SPRITE_SHADER_HANDLE.typed::(), + entry_point: "vertex".into(), + shader_defs: shader_defs.clone(), + buffers: vec![vertex_buffer_layout], + }, + fragment: Some(FragmentState { + shader: SPRITE_SHADER_HANDLE.typed::(), + shader_defs, + entry_point: "fragment".into(), + targets: vec![ColorTargetState { + format: TextureFormat::bevy_default(), + blend: Some(BlendState::ALPHA_BLENDING), + write_mask: ColorWrites::ALL, + }], }), - write_mask: ColorWrite::ALL, - }], - primitive: PrimitiveState { - topology: PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: FrontFace::Ccw, - cull_mode: None, - polygon_mode: PolygonMode::Fill, - clamp_depth: false, - conservative: false, - }, - ..PipelineDescriptor::new(ShaderStages { - vertex: shaders.add(Shader::from_glsl( - ShaderStage::Vertex, - include_str!("sprite.vert"), - )), - fragment: Some(shaders.add(Shader::from_glsl( - ShaderStage::Fragment, - include_str!("sprite.frag"), - ))), - }) + layout: Some(vec![self.view_layout.clone(), self.material_layout.clone()]), + primitive: PrimitiveState { + front_face: FrontFace::Ccw, + cull_mode: None, + polygon_mode: PolygonMode::Fill, + clamp_depth: false, + conservative: false, + topology: PrimitiveTopology::TriangleList, + strip_index_format: None, + }, + depth_stencil: None, + multisample: MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + label: Some("sprite_pipeline".into()), + } } } -pub mod node { - pub const COLOR_MATERIAL: &str = "color_material"; - pub const SPRITE: &str = "sprite"; - pub const SPRITE_SHEET: &str = "sprite_sheet"; - pub const SPRITE_SHEET_SPRITE: &str = "sprite_sheet_sprite"; +pub struct ExtractedSprite { + pub transform: Mat4, + pub color: Color, + pub rect: Rect, + pub handle: Handle, + pub atlas_size: Option, + pub flip_x: bool, + pub flip_y: bool, } -pub(crate) fn add_sprite_graph( - graph: &mut RenderGraph, - pipelines: &mut Assets, - shaders: &mut Assets, +#[derive(Default)] +pub struct ExtractedSprites { + pub sprites: Vec, +} + +#[derive(Default)] +pub struct SpriteAssetEvents { + pub images: Vec>, +} + +pub fn extract_sprite_events( + mut render_world: ResMut, + mut image_events: EventReader>, ) { - graph.add_system_node( - node::COLOR_MATERIAL, - AssetRenderResourcesNode::::new(false), - ); - graph - .add_node_edge(node::COLOR_MATERIAL, base::node::MAIN_PASS) + let mut events = render_world + .get_resource_mut::() .unwrap(); + let SpriteAssetEvents { ref mut images } = *events; + images.clear(); - graph.add_system_node(node::SPRITE, RenderResourcesNode::::new(true)); - graph - .add_node_edge(node::SPRITE, base::node::MAIN_PASS) - .unwrap(); + for image in image_events.iter() { + // AssetEvent: !Clone + images.push(match image { + AssetEvent::Created { handle } => AssetEvent::Created { + handle: handle.clone_weak(), + }, + AssetEvent::Modified { handle } => AssetEvent::Modified { + handle: handle.clone_weak(), + }, + AssetEvent::Removed { handle } => AssetEvent::Removed { + handle: handle.clone_weak(), + }, + }); + } +} - graph.add_system_node( - node::SPRITE_SHEET, - AssetRenderResourcesNode::::new(false), - ); - - graph.add_system_node( - node::SPRITE_SHEET_SPRITE, - RenderResourcesNode::::new(true), - ); - - pipelines.set_untracked(SPRITE_PIPELINE_HANDLE, build_sprite_pipeline(shaders)); - pipelines.set_untracked( - SPRITE_SHEET_PIPELINE_HANDLE, - build_sprite_sheet_pipeline(shaders), - ); +pub fn extract_sprites( + mut render_world: ResMut, + images: Res>, + texture_atlases: Res>, + sprite_query: Query<( + &ComputedVisibility, + &Sprite, + &GlobalTransform, + &Handle, + )>, + atlas_query: Query<( + &ComputedVisibility, + &TextureAtlasSprite, + &GlobalTransform, + &Handle, + )>, +) { + let mut extracted_sprites = render_world.get_resource_mut::().unwrap(); + extracted_sprites.sprites.clear(); + for (computed_visibility, sprite, transform, handle) in sprite_query.iter() { + if !computed_visibility.is_visible { + continue; + } + if let Some(image) = images.get(handle) { + let size = image.texture_descriptor.size; + + extracted_sprites.sprites.push(ExtractedSprite { + atlas_size: None, + color: sprite.color, + transform: transform.compute_matrix(), + rect: Rect { + min: Vec2::ZERO, + max: sprite + .custom_size + .unwrap_or_else(|| Vec2::new(size.width as f32, size.height as f32)), + }, + flip_x: sprite.flip_x, + flip_y: sprite.flip_y, + handle: handle.clone_weak(), + }); + }; + } + for (computed_visibility, atlas_sprite, transform, texture_atlas_handle) in atlas_query.iter() { + if !computed_visibility.is_visible { + continue; + } + if let Some(texture_atlas) = texture_atlases.get(texture_atlas_handle) { + if images.contains(&texture_atlas.texture) { + let rect = texture_atlas.textures[atlas_sprite.index as usize]; + extracted_sprites.sprites.push(ExtractedSprite { + atlas_size: Some(texture_atlas.size), + color: atlas_sprite.color, + transform: transform.compute_matrix(), + rect, + flip_x: atlas_sprite.flip_x, + flip_y: atlas_sprite.flip_y, + handle: texture_atlas.texture.clone_weak(), + }); + } + } + } +} + +#[repr(C)] +#[derive(Copy, Clone, Pod, Zeroable)] +struct SpriteVertex { + pub position: [f32; 3], + pub uv: [f32; 2], +} + +#[repr(C)] +#[derive(Copy, Clone, Pod, Zeroable)] +struct ColoredSpriteVertex { + pub position: [f32; 3], + pub uv: [f32; 2], + pub color: u32, +} + +pub struct SpriteMeta { + vertices: BufferVec, + colored_vertices: BufferVec, + view_bind_group: Option, +} + +impl Default for SpriteMeta { + fn default() -> Self { + Self { + vertices: BufferVec::new(BufferUsages::VERTEX), + colored_vertices: BufferVec::new(BufferUsages::VERTEX), + view_bind_group: None, + } + } +} + +const QUAD_VERTEX_POSITIONS: &[Vec3] = &[ + const_vec3!([-0.5, -0.5, 0.0]), + const_vec3!([0.5, 0.5, 0.0]), + const_vec3!([-0.5, 0.5, 0.0]), + const_vec3!([-0.5, -0.5, 0.0]), + const_vec3!([0.5, -0.5, 0.0]), + const_vec3!([0.5, 0.5, 0.0]), +]; + +#[derive(Component)] +pub struct SpriteBatch { + range: Range, + handle: Handle, + z: f32, + colored: bool, +} + +pub fn prepare_sprites( + mut commands: Commands, + render_device: Res, + render_queue: Res, + mut sprite_meta: ResMut, + mut extracted_sprites: ResMut, +) { + sprite_meta.vertices.clear(); + sprite_meta.colored_vertices.clear(); + + // sort first by z and then by handle. this ensures that, when possible, batches span multiple z layers + // batches won't span z-layers if there is another batch between them + extracted_sprites.sprites.sort_by(|a, b| { + match FloatOrd(a.transform.w_axis[2]).cmp(&FloatOrd(b.transform.w_axis[2])) { + Ordering::Equal => a.handle.cmp(&b.handle), + other => other, + } + }); + + let mut start = 0; + let mut end = 0; + let mut colored_start = 0; + let mut colored_end = 0; + let mut current_batch_handle: Option> = None; + let mut current_batch_colored = false; + let mut last_z = 0.0; + for extracted_sprite in extracted_sprites.sprites.iter() { + let colored = extracted_sprite.color != Color::WHITE; + if let Some(current_batch_handle) = ¤t_batch_handle { + if *current_batch_handle != extracted_sprite.handle || current_batch_colored != colored + { + if current_batch_colored { + commands.spawn_bundle((SpriteBatch { + range: colored_start..colored_end, + handle: current_batch_handle.clone_weak(), + z: last_z, + colored: true, + },)); + colored_start = colored_end; + } else { + commands.spawn_bundle((SpriteBatch { + range: start..end, + handle: current_batch_handle.clone_weak(), + z: last_z, + colored: false, + },)); + start = end; + } + } + } + current_batch_handle = Some(extracted_sprite.handle.clone_weak()); + current_batch_colored = colored; + let sprite_rect = extracted_sprite.rect; + + // Specify the corners of the sprite + let mut bottom_left = Vec2::new(sprite_rect.min.x, sprite_rect.max.y); + let mut top_left = sprite_rect.min; + let mut top_right = Vec2::new(sprite_rect.max.x, sprite_rect.min.y); + let mut bottom_right = sprite_rect.max; + + if extracted_sprite.flip_x { + bottom_left.x = sprite_rect.max.x; + top_left.x = sprite_rect.max.x; + bottom_right.x = sprite_rect.min.x; + top_right.x = sprite_rect.min.x; + } + + if extracted_sprite.flip_y { + bottom_left.y = sprite_rect.min.y; + bottom_right.y = sprite_rect.min.y; + top_left.y = sprite_rect.max.y; + top_right.y = sprite_rect.max.y; + } + + let atlas_extent = extracted_sprite.atlas_size.unwrap_or(sprite_rect.max); + bottom_left /= atlas_extent; + bottom_right /= atlas_extent; + top_left /= atlas_extent; + top_right /= atlas_extent; + + let uvs: [[f32; 2]; 6] = [ + bottom_left.into(), + top_right.into(), + top_left.into(), + bottom_left.into(), + bottom_right.into(), + top_right.into(), + ]; + + let rect_size = extracted_sprite.rect.size().extend(1.0); + if current_batch_colored { + let color = extracted_sprite.color.as_linear_rgba_f32(); + // encode color as a single u32 to save space + let color = (color[0] * 255.0) as u32 + | ((color[1] * 255.0) as u32) << 8 + | ((color[2] * 255.0) as u32) << 16 + | ((color[3] * 255.0) as u32) << 24; + for (index, vertex_position) in QUAD_VERTEX_POSITIONS.iter().enumerate() { + let mut final_position = *vertex_position * rect_size; + final_position = (extracted_sprite.transform * final_position.extend(1.0)).xyz(); + sprite_meta.colored_vertices.push(ColoredSpriteVertex { + position: final_position.into(), + uv: uvs[index], + color, + }); + } + } else { + for (index, vertex_position) in QUAD_VERTEX_POSITIONS.iter().enumerate() { + let mut final_position = *vertex_position * rect_size; + final_position = (extracted_sprite.transform * final_position.extend(1.0)).xyz(); + sprite_meta.vertices.push(SpriteVertex { + position: final_position.into(), + uv: uvs[index], + }); + } + } + + last_z = extracted_sprite.transform.w_axis[2]; + if current_batch_colored { + colored_end += QUAD_VERTEX_POSITIONS.len() as u32; + } else { + end += QUAD_VERTEX_POSITIONS.len() as u32; + } + } + + // if start != end, there is one last batch to process + if start != end { + if let Some(current_batch_handle) = current_batch_handle { + commands.spawn_bundle((SpriteBatch { + range: start..end, + handle: current_batch_handle, + colored: false, + z: last_z, + },)); + } + } else if colored_start != colored_end { + if let Some(current_batch_handle) = current_batch_handle { + commands.spawn_bundle((SpriteBatch { + range: colored_start..colored_end, + handle: current_batch_handle, + colored: true, + z: last_z, + },)); + } + } + + sprite_meta + .vertices + .write_buffer(&render_device, &render_queue); + sprite_meta + .colored_vertices + .write_buffer(&render_device, &render_queue); +} + +#[derive(Default)] +pub struct ImageBindGroups { + values: HashMap, BindGroup>, +} + +#[allow(clippy::too_many_arguments)] +pub fn queue_sprites( + draw_functions: Res>, + render_device: Res, + mut sprite_meta: ResMut, + view_uniforms: Res, + sprite_pipeline: Res, + mut pipelines: ResMut>, + mut pipeline_cache: ResMut, + mut image_bind_groups: ResMut, + gpu_images: Res>, + mut sprite_batches: Query<(Entity, &SpriteBatch)>, + mut views: Query<&mut RenderPhase>, + events: Res, +) { + // If an image has changed, the GpuImage has (probably) changed + for event in &events.images { + match event { + AssetEvent::Created { .. } => None, + AssetEvent::Modified { handle } => image_bind_groups.values.remove(handle), + AssetEvent::Removed { handle } => image_bind_groups.values.remove(handle), + }; + } + + if let Some(view_binding) = view_uniforms.uniforms.binding() { + sprite_meta.view_bind_group = Some(render_device.create_bind_group(&BindGroupDescriptor { + entries: &[BindGroupEntry { + binding: 0, + resource: view_binding, + }], + label: Some("sprite_view_bind_group"), + layout: &sprite_pipeline.view_layout, + })); + let draw_sprite_function = draw_functions.read().get_id::().unwrap(); + let pipeline = pipelines.specialize( + &mut pipeline_cache, + &sprite_pipeline, + SpritePipelineKey { colored: false }, + ); + let colored_pipeline = pipelines.specialize( + &mut pipeline_cache, + &sprite_pipeline, + SpritePipelineKey { colored: true }, + ); + for mut transparent_phase in views.iter_mut() { + for (entity, batch) in sprite_batches.iter_mut() { + image_bind_groups + .values + .entry(batch.handle.clone_weak()) + .or_insert_with(|| { + let gpu_image = gpu_images.get(&batch.handle).unwrap(); + render_device.create_bind_group(&BindGroupDescriptor { + entries: &[ + BindGroupEntry { + binding: 0, + resource: BindingResource::TextureView(&gpu_image.texture_view), + }, + BindGroupEntry { + binding: 1, + resource: BindingResource::Sampler(&gpu_image.sampler), + }, + ], + label: Some("sprite_material_bind_group"), + layout: &sprite_pipeline.material_layout, + }) + }); + transparent_phase.add(Transparent2d { + draw_function: draw_sprite_function, + pipeline: if batch.colored { + colored_pipeline + } else { + pipeline + }, + entity, + sort_key: FloatOrd(batch.z), + }); + } + } + } +} + +pub struct DrawSprite { + params: SystemState<( + SRes, + SRes, + SRes, + SQuery>, + SQuery>, + )>, +} + +impl DrawSprite { + pub fn new(world: &mut World) -> Self { + Self { + params: SystemState::new(world), + } + } +} + +impl Draw for DrawSprite { + fn draw<'w>( + &mut self, + world: &'w World, + pass: &mut TrackedRenderPass<'w>, + view: Entity, + item: &Transparent2d, + ) { + let (sprite_meta, image_bind_groups, pipelines, views, sprites) = self.params.get(world); + let view_uniform = views.get(view).unwrap(); + let sprite_meta = sprite_meta.into_inner(); + let image_bind_groups = image_bind_groups.into_inner(); + let sprite_batch = sprites.get(item.entity).unwrap(); + if let Some(pipeline) = pipelines.into_inner().get(item.pipeline) { + pass.set_render_pipeline(pipeline); + if sprite_batch.colored { + pass.set_vertex_buffer(0, sprite_meta.colored_vertices.buffer().unwrap().slice(..)); + } else { + pass.set_vertex_buffer(0, sprite_meta.vertices.buffer().unwrap().slice(..)); + } + pass.set_bind_group( + 0, + sprite_meta.view_bind_group.as_ref().unwrap(), + &[view_uniform.offset], + ); + pass.set_bind_group( + 1, + image_bind_groups.values.get(&sprite_batch.handle).unwrap(), + &[], + ); + + pass.draw(sprite_batch.range.clone(), 0..1); + } + } } diff --git a/crates/bevy_sprite/src/render/sprite.frag b/crates/bevy_sprite/src/render/sprite.frag deleted file mode 100644 index 9683598e2ed8c..0000000000000 --- a/crates/bevy_sprite/src/render/sprite.frag +++ /dev/null @@ -1,24 +0,0 @@ -#version 450 - -layout(location = 0) in vec2 v_Uv; - -layout(location = 0) out vec4 o_Target; - -layout(set = 1, binding = 0) uniform ColorMaterial_color { - vec4 Color; -}; - -# ifdef COLORMATERIAL_TEXTURE -layout(set = 1, binding = 1) uniform texture2D ColorMaterial_texture; -layout(set = 1, binding = 2) uniform sampler ColorMaterial_texture_sampler; -# endif - -void main() { - vec4 color = Color; -# ifdef COLORMATERIAL_TEXTURE - color *= texture( - sampler2D(ColorMaterial_texture, ColorMaterial_texture_sampler), - v_Uv); -# endif - o_Target = color; -} diff --git a/crates/bevy_sprite/src/render/sprite.vert b/crates/bevy_sprite/src/render/sprite.vert deleted file mode 100644 index 8d84140495d43..0000000000000 --- a/crates/bevy_sprite/src/render/sprite.vert +++ /dev/null @@ -1,44 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 Vertex_Position; -layout(location = 1) in vec3 Vertex_Normal; -layout(location = 2) in vec2 Vertex_Uv; - -layout(location = 0) out vec2 v_Uv; - -layout(set = 0, binding = 0) uniform CameraViewProj { - mat4 ViewProj; -}; - -layout(set = 2, binding = 0) uniform Transform { - mat4 Model; -}; -layout(set = 2, binding = 1) uniform Sprite { - vec2 size; - uint flip; -}; - -void main() { - vec2 uv = Vertex_Uv; - - // Flip the sprite if necessary by flipping the UVs - - uint x_flip_bit = 1; // The X flip bit - uint y_flip_bit = 2; // The Y flip bit - - // Note: Here we subtract f32::EPSILON from the flipped UV coord. This is due to reasons unknown - // to me (@zicklag ) that causes the uv's to be slightly offset and causes over/under running of - // the sprite UV sampling which is visible when resizing the screen. - float epsilon = 0.00000011920929; - if ((flip & x_flip_bit) == x_flip_bit) { - uv = vec2(1.0 - uv.x - epsilon, uv.y); - } - if ((flip & y_flip_bit) == y_flip_bit) { - uv = vec2(uv.x, 1.0 - uv.y - epsilon); - } - - v_Uv = uv; - - vec3 position = Vertex_Position * vec3(size, 1.0); - gl_Position = ViewProj * Model * vec4(position, 1.0); -} diff --git a/pipelined/bevy_sprite2/src/render/sprite.wgsl b/crates/bevy_sprite/src/render/sprite.wgsl similarity index 100% rename from pipelined/bevy_sprite2/src/render/sprite.wgsl rename to crates/bevy_sprite/src/render/sprite.wgsl diff --git a/crates/bevy_sprite/src/render/sprite_sheet.frag b/crates/bevy_sprite/src/render/sprite_sheet.frag deleted file mode 100644 index 8c8d9217f08d1..0000000000000 --- a/crates/bevy_sprite/src/render/sprite_sheet.frag +++ /dev/null @@ -1,15 +0,0 @@ -#version 450 - -layout(location = 0) in vec2 v_Uv; -layout(location = 1) in vec4 v_Color; - -layout(location = 0) out vec4 o_Target; - -layout(set = 1, binding = 2) uniform texture2D TextureAtlas_texture; -layout(set = 1, binding = 3) uniform sampler TextureAtlas_texture_sampler; - -void main() { - o_Target = v_Color * texture( - sampler2D(TextureAtlas_texture, TextureAtlas_texture_sampler), - v_Uv); -} diff --git a/crates/bevy_sprite/src/render/sprite_sheet.vert b/crates/bevy_sprite/src/render/sprite_sheet.vert deleted file mode 100644 index 8380a12455159..0000000000000 --- a/crates/bevy_sprite/src/render/sprite_sheet.vert +++ /dev/null @@ -1,85 +0,0 @@ -#version 450 - -layout(location = 0) in vec3 Vertex_Position; -layout(location = 1) in vec3 Vertex_Normal; -layout(location = 2) in vec2 Vertex_Uv; - -layout(location = 0) out vec2 v_Uv; -layout(location = 1) out vec4 v_Color; - -layout(set = 0, binding = 0) uniform CameraViewProj { - mat4 ViewProj; -}; - -// TODO: merge dimensions into "sprites" buffer when that is supported in the Uniforms derive abstraction -layout(set = 1, binding = 0) uniform TextureAtlas_size { - vec2 AtlasSize; -}; - -struct Rect { - vec2 begin; - vec2 end; -}; - -layout(set = 1, binding = 1) readonly buffer TextureAtlas_textures { - Rect[] Textures; -}; - - -layout(set = 2, binding = 0) uniform Transform { - mat4 SpriteTransform; -}; - -layout(set = 2, binding = 1) uniform TextureAtlasSprite { - vec4 color; - uint index; - uint flip; -}; - -void main() { - Rect sprite_rect = Textures[index]; - vec2 sprite_dimensions = sprite_rect.end - sprite_rect.begin; - vec3 vertex_position = vec3(Vertex_Position.xy * sprite_dimensions, 0.0); - - // Specify the corners of the sprite - vec2 bottom_left = vec2(sprite_rect.begin.x, sprite_rect.end.y); - vec2 top_left = sprite_rect.begin; - vec2 top_right = vec2(sprite_rect.end.x, sprite_rect.begin.y); - vec2 bottom_right = sprite_rect.end; - - // Flip the sprite if necessary - uint x_flip_bit = 1; - uint y_flip_bit = 2; - - vec2 tmp; - if ((flip & x_flip_bit) == x_flip_bit) { - // Shuffle the corners to flip around x - tmp = bottom_left; - bottom_left = bottom_right; - bottom_right = tmp; - tmp = top_left; - top_left = top_right; - top_right = tmp; - } - if ((flip & y_flip_bit) == y_flip_bit) { - // Shuffle the corners to flip around y - tmp = bottom_left; - bottom_left = top_left; - top_left = tmp; - tmp = bottom_right; - bottom_right = top_right; - top_right = tmp; - } - - vec2 atlas_positions[4] = vec2[]( - bottom_left, - top_left, - top_right, - bottom_right - ); - - v_Uv = (atlas_positions[gl_VertexIndex]) / AtlasSize; - - v_Color = color; - gl_Position = ViewProj * SpriteTransform * vec4(vertex_position, 1.0); -} diff --git a/crates/bevy_sprite/src/sprite.rs b/crates/bevy_sprite/src/sprite.rs index b5ab716975e5d..d46b12b11c096 100644 --- a/crates/bevy_sprite/src/sprite.rs +++ b/crates/bevy_sprite/src/sprite.rs @@ -1,107 +1,19 @@ -use crate::ColorMaterial; -use bevy_asset::{Assets, Handle}; -use bevy_core::Bytes; -use bevy_ecs::{ - component::Component, - query::Without, - system::{Query, Res}, -}; +use bevy_ecs::component::Component; use bevy_math::Vec2; -use bevy_reflect::{Reflect, ReflectDeserialize, TypeUuid}; -use bevy_render::{ - draw::OutsideFrustum, - renderer::{RenderResource, RenderResourceType, RenderResources}, - texture::Texture, -}; -use serde::{Deserialize, Serialize}; +use bevy_reflect::{Reflect, TypeUuid}; +use bevy_render::color::Color; -/// General Sprite Examples: [Link](https://github.com/bevyengine/bevy/tree/latest/examples/2d) -#[derive(Component, Debug, Default, Clone, TypeUuid, Reflect, RenderResources)] -#[render_resources(from_self)] +#[derive(Component, Debug, Default, Clone, TypeUuid, Reflect)] #[uuid = "7233c597-ccfa-411f-bd59-9af349432ada"] #[repr(C)] pub struct Sprite { - pub size: Vec2, - /// When true flips sprite to left. [Example](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_flipping.rs) + /// The sprite's color tint + pub color: Color, + /// Flip the sprite along the X axis pub flip_x: bool, - /// When true flips sprite upside down. [Example](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_flipping.rs) + /// Flip the sprite along the Y axis pub flip_y: bool, - pub resize_mode: SpriteResizeMode, -} - -impl RenderResource for Sprite { - fn resource_type(&self) -> Option { - Some(RenderResourceType::Buffer) - } - - fn buffer_byte_len(&self) -> Option { - Some(12) - } - - fn write_buffer_bytes(&self, buffer: &mut [u8]) { - // Write the size buffer - let (size_buf, flip_buf) = buffer.split_at_mut(8); - self.size.write_bytes(size_buf); - - // First bit means flip x, second bit means flip y - flip_buf[0] = if self.flip_x { 0b01 } else { 0 } | if self.flip_y { 0b10 } else { 0 }; - flip_buf[1] = 0; - flip_buf[2] = 0; - flip_buf[3] = 0; - } - - fn texture(&self) -> Option<&Handle> { - None - } -} - -/// Determines how `Sprite` resize should be handled -#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)] -#[reflect_value(PartialEq, Serialize, Deserialize)] -pub enum SpriteResizeMode { - Manual, - Automatic, -} - -impl Default for SpriteResizeMode { - fn default() -> Self { - SpriteResizeMode::Automatic - } -} - -impl Sprite { - /// Creates new `Sprite` with `SpriteResizeMode::Manual` value for `resize_mode` - pub fn new(size: Vec2) -> Self { - Self { - size, - resize_mode: SpriteResizeMode::Manual, - flip_x: false, - flip_y: false, - } - } -} - -pub fn sprite_system( - materials: Res>, - textures: Res>, - mut query: Query<(&mut Sprite, &Handle), Without>, -) { - for (mut sprite, handle) in query.iter_mut() { - match sprite.resize_mode { - SpriteResizeMode::Manual => continue, - SpriteResizeMode::Automatic => { - let material = materials.get(handle).unwrap(); - if let Some(ref texture_handle) = material.texture { - if let Some(texture) = textures.get(texture_handle) { - let texture_size = texture.size.as_vec3().truncate(); - // only set sprite size if it has changed (this check prevents change - // detection from triggering) - if sprite.size != texture_size { - sprite.size = texture_size; - } - } - } - } - } - } + /// An optional custom size for the sprite that will be used when rendering, instead of the size + /// of the sprite's image + pub custom_size: Option, } diff --git a/crates/bevy_sprite/src/texture_atlas.rs b/crates/bevy_sprite/src/texture_atlas.rs index 41dcc89dd452d..84d2e3872a363 100644 --- a/crates/bevy_sprite/src/texture_atlas.rs +++ b/crates/bevy_sprite/src/texture_atlas.rs @@ -1,73 +1,35 @@ use crate::Rect; use bevy_asset::Handle; -use bevy_core::Bytes; use bevy_ecs::component::Component; use bevy_math::Vec2; -use bevy_reflect::TypeUuid; -use bevy_render::{ - color::Color, - renderer::{RenderResource, RenderResourceType, RenderResources}, - texture::Texture, -}; +use bevy_reflect::{Reflect, TypeUuid}; +use bevy_render::{color::Color, texture::Image}; use bevy_utils::HashMap; /// An atlas containing multiple textures (like a spritesheet or a tilemap). /// [Example usage animating sprite.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs) /// [Example usage loading sprite sheet.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs) -#[derive(Debug, RenderResources, TypeUuid)] -#[uuid = "946dacc5-c2b2-4b30-b81d-af77d79d1db7"] +#[derive(Debug, Clone, TypeUuid)] +#[uuid = "7233c597-ccfa-411f-bd59-9af349432ada"] pub struct TextureAtlas { /// The handle to the texture in which the sprites are stored - pub texture: Handle, + pub texture: Handle, // TODO: add support to Uniforms derive to write dimensions and sprites to the same buffer pub size: Vec2, /// The specific areas of the atlas where each texture can be found - #[render_resources(buffer)] pub textures: Vec, - #[render_resources(ignore)] - pub texture_handles: Option, usize>>, + pub texture_handles: Option, usize>>, } -#[derive(Component, Debug, Clone, RenderResources)] -#[render_resources(from_self)] -#[repr(C)] +#[derive(Component, Debug, Clone, TypeUuid, Reflect)] +#[uuid = "7233c597-ccfa-411f-bd59-9af349432ada"] pub struct TextureAtlasSprite { pub color: Color, - pub index: u32, + pub index: usize, pub flip_x: bool, pub flip_y: bool, } -impl RenderResource for TextureAtlasSprite { - fn resource_type(&self) -> Option { - Some(RenderResourceType::Buffer) - } - - fn buffer_byte_len(&self) -> Option { - Some(24) - } - - fn write_buffer_bytes(&self, buffer: &mut [u8]) { - // Write the color buffer - let (color_buf, rest) = buffer.split_at_mut(16); - self.color.write_bytes(color_buf); - - // Write the index buffer - let (index_buf, flip_buf) = rest.split_at_mut(4); - self.index.write_bytes(index_buf); - - // First bit means flip x, second bit means flip y - flip_buf[0] = if self.flip_x { 0b01 } else { 0 } | if self.flip_y { 0b10 } else { 0 }; - flip_buf[1] = 0; - flip_buf[2] = 0; - flip_buf[3] = 0; - } - - fn texture(&self) -> Option<&Handle> { - None - } -} - impl Default for TextureAtlasSprite { fn default() -> Self { Self { @@ -80,7 +42,7 @@ impl Default for TextureAtlasSprite { } impl TextureAtlasSprite { - pub fn new(index: u32) -> TextureAtlasSprite { + pub fn new(index: usize) -> TextureAtlasSprite { Self { index, ..Default::default() @@ -91,7 +53,7 @@ impl TextureAtlasSprite { impl TextureAtlas { /// Create a new `TextureAtlas` that has a texture, but does not have /// any individual sprites specified - pub fn new_empty(texture: Handle, dimensions: Vec2) -> Self { + pub fn new_empty(texture: Handle, dimensions: Vec2) -> Self { Self { texture, size: dimensions, @@ -103,7 +65,7 @@ impl TextureAtlas { /// Generate a `TextureAtlas` by splitting a texture into a grid where each /// cell of the grid of `tile_size` is one of the textures in the atlas pub fn from_grid( - texture: Handle, + texture: Handle, tile_size: Vec2, columns: usize, rows: usize, @@ -113,10 +75,9 @@ impl TextureAtlas { /// Generate a `TextureAtlas` by splitting a texture into a grid where each /// cell of the grid of `tile_size` is one of the textures in the atlas and is separated by - /// some `padding` in the texture. The padding is assumed to be only between tiles - /// and not at the borders of the texture. + /// some `padding` in the texture pub fn from_grid_with_padding( - texture: Handle, + texture: Handle, tile_size: Vec2, columns: usize, rows: usize, @@ -165,9 +126,9 @@ impl TextureAtlas { /// /// * `rect` - The section of the atlas that contains the texture to be added, /// from the top-left corner of the texture to the bottom-right corner - pub fn add_texture(&mut self, rect: Rect) -> u32 { + pub fn add_texture(&mut self, rect: Rect) -> usize { self.textures.push(rect); - (self.textures.len() - 1) as u32 + self.textures.len() - 1 } /// How many textures are in the `TextureAtlas` @@ -179,7 +140,7 @@ impl TextureAtlas { self.textures.is_empty() } - pub fn get_texture_index(&self, texture: &Handle) -> Option { + pub fn get_texture_index(&self, texture: &Handle) -> Option { self.texture_handles .as_ref() .and_then(|texture_handles| texture_handles.get(texture).cloned()) diff --git a/crates/bevy_sprite/src/texture_atlas_builder.rs b/crates/bevy_sprite/src/texture_atlas_builder.rs index e26ac27c87688..bb5d6ca5afcdd 100644 --- a/crates/bevy_sprite/src/texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/texture_atlas_builder.rs @@ -1,8 +1,10 @@ -use crate::{Rect, TextureAtlas}; use bevy_asset::{Assets, Handle}; use bevy_log::{debug, error, warn}; use bevy_math::Vec2; -use bevy_render::texture::{Extent3d, Texture, TextureDimension, TextureFormat}; +use bevy_render::{ + render_resource::{Extent3d, TextureDimension, TextureFormat}, + texture::{Image, TextureFormatPixelInfo}, +}; use bevy_utils::HashMap; use rectangle_pack::{ contains_smallest_box, pack_rects, volume_heuristic, GroupedRectsToPlace, PackedLocation, @@ -10,6 +12,8 @@ use rectangle_pack::{ }; use thiserror::Error; +use crate::{texture_atlas::TextureAtlas, Rect}; + #[derive(Debug, Error)] pub enum TextureAtlasBuilderError { #[error("could not pack textures into an atlas within the given bounds")] @@ -24,7 +28,7 @@ pub enum TextureAtlasBuilderError { pub struct TextureAtlasBuilder { /// The grouped rects which must be placed with a key value pair of a /// texture handle to an index. - rects_to_place: GroupedRectsToPlace>, + rects_to_place: GroupedRectsToPlace>, /// The initial atlas size in pixels. initial_size: Vec2, /// The absolute maximum size of the texture atlas in pixels. @@ -75,25 +79,29 @@ impl TextureAtlasBuilder { } /// Adds a texture to be copied to the texture atlas. - pub fn add_texture(&mut self, texture_handle: Handle, texture: &Texture) { + pub fn add_texture(&mut self, texture_handle: Handle, texture: &Image) { self.rects_to_place.push_rect( texture_handle, None, - RectToInsert::new(texture.size.width, texture.size.height, 1), + RectToInsert::new( + texture.texture_descriptor.size.width, + texture.texture_descriptor.size.height, + 1, + ), ) } fn copy_texture_to_atlas( - atlas_texture: &mut Texture, - texture: &Texture, + atlas_texture: &mut Image, + texture: &Image, packed_location: &PackedLocation, ) { let rect_width = packed_location.width() as usize; let rect_height = packed_location.height() as usize; let rect_x = packed_location.x() as usize; let rect_y = packed_location.y() as usize; - let atlas_width = atlas_texture.size.width as usize; - let format_size = atlas_texture.format.pixel_size(); + let atlas_width = atlas_texture.texture_descriptor.size.width as usize; + let format_size = atlas_texture.texture_descriptor.format.pixel_size(); for (texture_y, bound_y) in (rect_y..rect_y + rect_height).enumerate() { let begin = (bound_y * atlas_width + rect_x) * format_size; @@ -107,22 +115,22 @@ impl TextureAtlasBuilder { fn copy_converted_texture( &self, - atlas_texture: &mut Texture, - texture: &Texture, + atlas_texture: &mut Image, + texture: &Image, packed_location: &PackedLocation, ) { - if self.format == texture.format { + if self.format == texture.texture_descriptor.format { Self::copy_texture_to_atlas(atlas_texture, texture, packed_location); - } else if let Some(converted_texture) = texture.clone().convert(self.format) { + } else if let Some(converted_texture) = texture.convert(self.format) { debug!( "Converting texture from '{:?}' to '{:?}'", - texture.format, self.format + texture.texture_descriptor.format, self.format ); Self::copy_texture_to_atlas(atlas_texture, &converted_texture, packed_location); } else { error!( "Error converting texture from '{:?}' to '{:?}', ignoring", - texture.format, self.format + texture.texture_descriptor.format, self.format ); } } @@ -140,7 +148,7 @@ impl TextureAtlasBuilder { /// be returned. It is then recommended to make a larger sprite sheet. pub fn finish( self, - textures: &mut Assets, + textures: &mut Assets, ) -> Result { let initial_width = self.initial_size.x as u32; let initial_height = self.initial_size.y as u32; @@ -150,7 +158,7 @@ impl TextureAtlasBuilder { let mut current_width = initial_width; let mut current_height = initial_height; let mut rect_placements = None; - let mut atlas_texture = Texture::default(); + let mut atlas_texture = Image::default(); while rect_placements.is_none() { if current_width > max_width || current_height > max_height { @@ -168,11 +176,17 @@ impl TextureAtlasBuilder { &contains_smallest_box, ) { Ok(rect_placements) => { - let size = Extent3d::new(current_width, current_height, 1); - atlas_texture = Texture::new( - size, + atlas_texture = Image::new( + Extent3d { + width: current_width, + height: current_height, + depth_or_array_layers: 1, + }, TextureDimension::D2, - vec![0; self.format.pixel_size() * size.volume()], + vec![ + 0; + self.format.pixel_size() * (current_width * current_height) as usize + ], self.format, ); Some(rect_placements) @@ -203,17 +217,20 @@ impl TextureAtlasBuilder { ); texture_handles.insert(texture_handle.clone_weak(), texture_rects.len()); texture_rects.push(Rect { min, max }); - if texture.format != self.format && !self.auto_format_conversion { + if texture.texture_descriptor.format != self.format && !self.auto_format_conversion { warn!( "Loading a texture of format '{:?}' in an atlas with format '{:?}'", - texture.format, self.format + texture.texture_descriptor.format, self.format ); return Err(TextureAtlasBuilderError::WrongFormat); } self.copy_converted_texture(&mut atlas_texture, texture, packed_location); } Ok(TextureAtlas { - size: atlas_texture.size.as_vec3().truncate(), + size: Vec2::new( + atlas_texture.texture_descriptor.size.width as f32, + atlas_texture.texture_descriptor.size.height as f32, + ), texture: textures.add(atlas_texture), textures: texture_rects, texture_handles: Some(texture_handles), diff --git a/crates/bevy_text/Cargo.toml b/crates/bevy_text/Cargo.toml index 613e50f93be40..b28a6c72c953b 100644 --- a/crates/bevy_text/Cargo.toml +++ b/crates/bevy_text/Cargo.toml @@ -30,3 +30,4 @@ anyhow = "1.0.4" ab_glyph = "0.2.6" glyph_brush_layout = "0.2.1" thiserror = "1.0" +serde = {version = "1", features = ["derive"]} \ No newline at end of file diff --git a/crates/bevy_text/src/draw.rs b/crates/bevy_text/src/draw.rs deleted file mode 100644 index e4dd639d37598..0000000000000 --- a/crates/bevy_text/src/draw.rs +++ /dev/null @@ -1,101 +0,0 @@ -use crate::{PositionedGlyph, TextSection}; -use bevy_math::{Mat4, Vec3}; -use bevy_render::pipeline::IndexFormat; -use bevy_render::{ - draw::{Draw, DrawContext, DrawError, Drawable}, - mesh, - mesh::Mesh, - pipeline::{PipelineSpecialization, VertexBufferLayout}, - prelude::Msaa, - renderer::{BindGroup, RenderResourceBindings, RenderResourceId}, -}; -use bevy_sprite::TextureAtlasSprite; -use bevy_transform::prelude::GlobalTransform; -use bevy_utils::tracing::error; - -pub struct DrawableText<'a> { - pub render_resource_bindings: &'a mut RenderResourceBindings, - pub global_transform: GlobalTransform, - pub scale_factor: f32, - pub sections: &'a [TextSection], - pub text_glyphs: &'a Vec, - pub msaa: &'a Msaa, - pub font_quad_vertex_layout: &'a VertexBufferLayout, - pub alignment_offset: Vec3, -} - -impl<'a> Drawable for DrawableText<'a> { - fn draw(&mut self, draw: &mut Draw, context: &mut DrawContext) -> Result<(), DrawError> { - context.set_pipeline( - draw, - &bevy_sprite::SPRITE_SHEET_PIPELINE_HANDLE.typed(), - &PipelineSpecialization { - sample_count: self.msaa.samples, - vertex_buffer_layout: self.font_quad_vertex_layout.clone(), - ..Default::default() - }, - )?; - - let render_resource_context = &**context.render_resource_context; - - if let Some(RenderResourceId::Buffer(vertex_attribute_buffer_id)) = render_resource_context - .get_asset_resource( - &bevy_sprite::QUAD_HANDLE.typed::(), - mesh::VERTEX_ATTRIBUTE_BUFFER_ID, - ) - { - draw.set_vertex_buffer(0, vertex_attribute_buffer_id, 0); - } else { - error!("Could not find vertex buffer for `bevy_sprite::QUAD_HANDLE`.") - } - - let mut indices = 0..0; - if let Some(RenderResourceId::Buffer(quad_index_buffer)) = render_resource_context - .get_asset_resource( - &bevy_sprite::QUAD_HANDLE.typed::(), - mesh::INDEX_BUFFER_ASSET_INDEX, - ) - { - draw.set_index_buffer(quad_index_buffer, 0, IndexFormat::Uint32); - if let Some(buffer_info) = render_resource_context.get_buffer_info(quad_index_buffer) { - indices = 0..(buffer_info.size / 4) as u32; - } else { - panic!("Expected buffer type."); - } - } - - // set global bindings - context.set_bind_groups_from_bindings(draw, &mut [self.render_resource_bindings])?; - - for tv in self.text_glyphs { - context.set_asset_bind_groups(draw, &tv.atlas_info.texture_atlas)?; - - let sprite = TextureAtlasSprite { - index: tv.atlas_info.glyph_index, - color: self.sections[tv.section_index].style.color, - flip_x: false, - flip_y: false, - }; - - let transform = Mat4::from_rotation_translation( - self.global_transform.rotation, - self.global_transform.translation, - ) * Mat4::from_scale(self.global_transform.scale / self.scale_factor) - * Mat4::from_translation( - self.alignment_offset * self.scale_factor + tv.position.extend(0.), - ); - - let transform_buffer = context.get_uniform_buffer(&transform).unwrap(); - let sprite_buffer = context.get_uniform_buffer(&sprite).unwrap(); - let sprite_bind_group = BindGroup::build() - .add_binding(0, transform_buffer) - .add_binding(1, sprite_buffer) - .finish(); - context.create_bind_group_resource(2, &sprite_bind_group)?; - draw.set_bind_group(2, &sprite_bind_group); - draw.draw_indexed(indices.clone(), 0, 0..1); - } - - Ok(()) - } -} diff --git a/crates/bevy_text/src/font.rs b/crates/bevy_text/src/font.rs index 320751fc77b0a..fced2cbc1a046 100644 --- a/crates/bevy_text/src/font.rs +++ b/crates/bevy_text/src/font.rs @@ -1,6 +1,9 @@ use ab_glyph::{FontArc, FontVec, InvalidFont, OutlinedGlyph}; use bevy_reflect::TypeUuid; -use bevy_render::texture::{Extent3d, Texture, TextureDimension, TextureFormat}; +use bevy_render::{ + render_resource::{Extent3d, TextureDimension, TextureFormat}, + texture::Image, +}; #[derive(Debug, TypeUuid)] #[uuid = "97059ac6-c9ba-4da9-95b6-bed82c3ce198"] @@ -15,7 +18,7 @@ impl Font { Ok(Font { font }) } - pub fn get_outlined_glyph_texture(outlined_glyph: OutlinedGlyph) -> Texture { + pub fn get_outlined_glyph_texture(outlined_glyph: OutlinedGlyph) -> Image { let bounds = outlined_glyph.px_bounds(); let width = bounds.width() as usize; let height = bounds.height() as usize; @@ -25,8 +28,12 @@ impl Font { }); // TODO: make this texture grayscale - Texture::new( - Extent3d::new(width as u32, height as u32, 1), + Image::new( + Extent3d { + width: width as u32, + height: height as u32, + depth_or_array_layers: 1, + }, TextureDimension::D2, alpha .iter() diff --git a/crates/bevy_text/src/font_atlas.rs b/crates/bevy_text/src/font_atlas.rs index bcfa7936a47ab..be1903c121b36 100644 --- a/crates/bevy_text/src/font_atlas.rs +++ b/crates/bevy_text/src/font_atlas.rs @@ -1,7 +1,10 @@ use ab_glyph::{GlyphId, Point}; use bevy_asset::{Assets, Handle}; use bevy_math::Vec2; -use bevy_render::texture::{Extent3d, Texture, TextureDimension, TextureFormat}; +use bevy_render::{ + render_resource::{Extent3d, TextureDimension, TextureFormat}, + texture::Image, +}; use bevy_sprite::{DynamicTextureAtlasBuilder, TextureAtlas}; use bevy_utils::HashMap; @@ -38,18 +41,22 @@ impl From for SubpixelOffset { pub struct FontAtlas { pub dynamic_texture_atlas_builder: DynamicTextureAtlasBuilder, - pub glyph_to_atlas_index: HashMap<(GlyphId, SubpixelOffset), u32>, + pub glyph_to_atlas_index: HashMap<(GlyphId, SubpixelOffset), usize>, pub texture_atlas: Handle, } impl FontAtlas { pub fn new( - textures: &mut Assets, + textures: &mut Assets, texture_atlases: &mut Assets, size: Vec2, ) -> FontAtlas { - let atlas_texture = textures.add(Texture::new_fill( - Extent3d::new(size.x as u32, size.y as u32, 1), + let atlas_texture = textures.add(Image::new_fill( + Extent3d { + width: size.x as u32, + height: size.y as u32, + depth_or_array_layers: 1, + }, TextureDimension::D2, &[0, 0, 0, 0], TextureFormat::Rgba8UnormSrgb, @@ -66,7 +73,7 @@ impl FontAtlas { &self, glyph_id: GlyphId, subpixel_offset: SubpixelOffset, - ) -> Option { + ) -> Option { self.glyph_to_atlas_index .get(&(glyph_id, subpixel_offset)) .copied() @@ -79,11 +86,11 @@ impl FontAtlas { pub fn add_glyph( &mut self, - textures: &mut Assets, + textures: &mut Assets, texture_atlases: &mut Assets, glyph_id: GlyphId, subpixel_offset: SubpixelOffset, - texture: &Texture, + texture: &Image, ) -> bool { let texture_atlas = texture_atlases.get_mut(&self.texture_atlas).unwrap(); if let Some(index) = diff --git a/crates/bevy_text/src/font_atlas_set.rs b/crates/bevy_text/src/font_atlas_set.rs index c073ab31ca3c3..29ede6cb53490 100644 --- a/crates/bevy_text/src/font_atlas_set.rs +++ b/crates/bevy_text/src/font_atlas_set.rs @@ -4,7 +4,7 @@ use bevy_asset::{Assets, Handle}; use bevy_core::FloatOrd; use bevy_math::Vec2; use bevy_reflect::TypeUuid; -use bevy_render::texture::Texture; +use bevy_render::texture::Image; use bevy_sprite::TextureAtlas; use bevy_utils::HashMap; @@ -19,7 +19,7 @@ pub struct FontAtlasSet { #[derive(Debug, Clone)] pub struct GlyphAtlasInfo { pub texture_atlas: Handle, - pub glyph_index: u32, + pub glyph_index: usize, } impl Default for FontAtlasSet { @@ -48,7 +48,7 @@ impl FontAtlasSet { pub fn add_glyph_to_atlas( &mut self, texture_atlases: &mut Assets, - textures: &mut Assets, + textures: &mut Assets, outlined_glyph: OutlinedGlyph, ) -> Result { let glyph = outlined_glyph.glyph(); diff --git a/crates/bevy_text/src/glyph_brush.rs b/crates/bevy_text/src/glyph_brush.rs index 30557f20ab8b8..fc9d5fffc76c5 100644 --- a/crates/bevy_text/src/glyph_brush.rs +++ b/crates/bevy_text/src/glyph_brush.rs @@ -1,7 +1,7 @@ use ab_glyph::{Font as _, FontArc, Glyph, ScaleFont as _}; use bevy_asset::{Assets, Handle}; use bevy_math::{Size, Vec2}; -use bevy_render::prelude::Texture; +use bevy_render::texture::Image; use bevy_sprite::TextureAtlas; use glyph_brush_layout::{ FontId, GlyphPositioner, Layout, SectionGeometry, SectionGlyph, SectionText, ToSectionText, @@ -37,8 +37,8 @@ impl GlyphBrush { ..Default::default() }; let section_glyphs = Layout::default() - .h_align(text_alignment.horizontal) - .v_align(text_alignment.vertical) + .h_align(text_alignment.horizontal.into()) + .v_align(text_alignment.vertical.into()) .calculate_glyphs(&self.fonts, &geom, sections); Ok(section_glyphs) } @@ -50,7 +50,7 @@ impl GlyphBrush { font_atlas_set_storage: &mut Assets, fonts: &Assets, texture_atlases: &mut Assets, - textures: &mut Assets, + textures: &mut Assets, ) -> Result, TextError> { if glyphs.is_empty() { return Ok(Vec::new()); diff --git a/crates/bevy_text/src/lib.rs b/crates/bevy_text/src/lib.rs index ab87b21889f5d..0b81909b3cec1 100644 --- a/crates/bevy_text/src/lib.rs +++ b/crates/bevy_text/src/lib.rs @@ -1,4 +1,3 @@ -mod draw; mod error; mod font; mod font_atlas; @@ -9,7 +8,6 @@ mod pipeline; mod text; mod text2d; -pub use draw::*; pub use error::*; pub use font::*; pub use font_atlas::*; @@ -22,15 +20,17 @@ pub use text2d::*; pub mod prelude { #[doc(hidden)] - pub use crate::{Font, Text, Text2dBundle, TextAlignment, TextError, TextSection, TextStyle}; - #[doc(hidden)] - pub use glyph_brush_layout::{HorizontalAlign, VerticalAlign}; + pub use crate::{ + Font, HorizontalAlign, Text, Text2dBundle, TextAlignment, TextError, TextSection, + TextStyle, VerticalAlign, + }; } use bevy_app::prelude::*; use bevy_asset::AddAsset; -use bevy_ecs::entity::Entity; -use bevy_render::RenderStage; +use bevy_ecs::{entity::Entity, schedule::ParallelSystemDescriptorCoercion}; +use bevy_render::{RenderApp, RenderStage}; +use bevy_sprite::SpriteSystem; pub type DefaultTextPipeline = TextPipeline; @@ -41,9 +41,18 @@ impl Plugin for TextPlugin { fn build(&self, app: &mut App) { app.add_asset::() .add_asset::() + // TODO: uncomment when #2215 is fixed + // .register_type::() + .register_type::() + .register_type::() .init_asset_loader::() - .init_resource::() - .add_system_to_stage(CoreStage::PostUpdate, text2d_system) - .add_system_to_stage(RenderStage::Draw, text2d::draw_text2d_system); + .insert_resource(DefaultTextPipeline::default()) + .add_system_to_stage(CoreStage::PostUpdate, text2d_system); + + let render_app = app.sub_app(RenderApp); + render_app.add_system_to_stage( + RenderStage::Extract, + extract_text2d_sprite.after(SpriteSystem::ExtractSprite), + ); } } diff --git a/crates/bevy_text/src/pipeline.rs b/crates/bevy_text/src/pipeline.rs index 87cbb48816121..4a13ecffdf1b0 100644 --- a/crates/bevy_text/src/pipeline.rs +++ b/crates/bevy_text/src/pipeline.rs @@ -3,7 +3,7 @@ use std::hash::Hash; use ab_glyph::{PxScale, ScaleFont}; use bevy_asset::{Assets, Handle, HandleId}; use bevy_math::Size; -use bevy_render::prelude::Texture; +use bevy_render::texture::Image; use bevy_sprite::TextureAtlas; use bevy_utils::HashMap; @@ -59,7 +59,7 @@ impl TextPipeline { bounds: Size, font_atlas_set_storage: &mut Assets, texture_atlases: &mut Assets, - textures: &mut Assets, + textures: &mut Assets, ) -> Result<(), TextError> { let mut scaled_fonts = Vec::new(); let sections = sections diff --git a/crates/bevy_text/src/text.rs b/crates/bevy_text/src/text.rs index bcb2ed858246f..56d3631c40d26 100644 --- a/crates/bevy_text/src/text.rs +++ b/crates/bevy_text/src/text.rs @@ -1,12 +1,14 @@ use bevy_asset::Handle; -use bevy_ecs::component::Component; +use bevy_ecs::{prelude::Component, reflect::ReflectComponent}; use bevy_math::Size; +use bevy_reflect::{Reflect, ReflectDeserialize}; use bevy_render::color::Color; -use glyph_brush_layout::{HorizontalAlign, VerticalAlign}; +use serde::{Deserialize, Serialize}; use crate::Font; -#[derive(Component, Debug, Default, Clone)] +#[derive(Component, Debug, Default, Clone, Reflect)] +#[reflect(Component)] pub struct Text { pub sections: Vec, pub alignment: TextAlignment, @@ -18,8 +20,7 @@ impl Text { /// ``` /// # use bevy_asset::{AssetServer, Handle}; /// # use bevy_render::color::Color; - /// # use bevy_text::{Font, Text, TextAlignment, TextStyle}; - /// # use glyph_brush_layout::{HorizontalAlign, VerticalAlign}; + /// # use bevy_text::{Font, Text, TextAlignment, TextStyle, HorizontalAlign, VerticalAlign}; /// # /// # let font_handle: Handle = Default::default(); /// # @@ -64,13 +65,13 @@ impl Text { } } -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, Reflect)] pub struct TextSection { pub value: String, pub style: TextStyle, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Reflect)] pub struct TextAlignment { pub vertical: VerticalAlign, pub horizontal: HorizontalAlign, @@ -85,7 +86,55 @@ impl Default for TextAlignment { } } -#[derive(Clone, Debug)] +/// Describes horizontal alignment preference for positioning & bounds. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)] +#[reflect_value(Serialize, Deserialize)] +pub enum HorizontalAlign { + /// Leftmost character is immediately to the right of the render position.
+ /// Bounds start from the render position and advance rightwards. + Left, + /// Leftmost & rightmost characters are equidistant to the render position.
+ /// Bounds start from the render position and advance equally left & right. + Center, + /// Rightmost character is immetiately to the left of the render position.
+ /// Bounds start from the render position and advance leftwards. + Right, +} + +impl From for glyph_brush_layout::HorizontalAlign { + fn from(val: HorizontalAlign) -> Self { + match val { + HorizontalAlign::Left => glyph_brush_layout::HorizontalAlign::Left, + HorizontalAlign::Center => glyph_brush_layout::HorizontalAlign::Center, + HorizontalAlign::Right => glyph_brush_layout::HorizontalAlign::Right, + } + } +} + +/// Describes vertical alignment preference for positioning & bounds. Currently a placeholder +/// for future functionality. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Reflect, Serialize, Deserialize)] +#[reflect_value(Serialize, Deserialize)] +pub enum VerticalAlign { + /// Characters/bounds start underneath the render position and progress downwards. + Top, + /// Characters/bounds center at the render position and progress outward equally. + Center, + /// Characters/bounds start above the render position and progress upward. + Bottom, +} + +impl From for glyph_brush_layout::VerticalAlign { + fn from(val: VerticalAlign) -> Self { + match val { + VerticalAlign::Top => glyph_brush_layout::VerticalAlign::Top, + VerticalAlign::Center => glyph_brush_layout::VerticalAlign::Center, + VerticalAlign::Bottom => glyph_brush_layout::VerticalAlign::Bottom, + } + } +} + +#[derive(Clone, Debug, Reflect)] pub struct TextStyle { pub font: Handle, pub font_size: f32, @@ -102,7 +151,8 @@ impl Default for TextStyle { } } -#[derive(Component, Default, Copy, Clone, Debug)] +#[derive(Component, Default, Copy, Clone, Debug, Reflect)] +#[reflect(Component)] pub struct Text2dSize { pub size: Size, } diff --git a/crates/bevy_text/src/text2d.rs b/crates/bevy_text/src/text2d.rs index 88dc89575c381..f8cf94be449fa 100644 --- a/crates/bevy_text/src/text2d.rs +++ b/crates/bevy_text/src/text2d.rs @@ -2,51 +2,36 @@ use bevy_asset::Assets; use bevy_ecs::{ bundle::Bundle, entity::Entity, - query::{Changed, QueryState, With, Without}, + query::{Changed, QueryState, With}, system::{Local, Query, QuerySet, Res, ResMut}, }; -use bevy_math::{Size, Vec3}; -use bevy_render::{ - draw::{DrawContext, Drawable, OutsideFrustum}, - mesh::Mesh, - prelude::{Draw, Msaa, Texture, Visible}, - render_graph::base::MainPass, - renderer::RenderResourceBindings, -}; -use bevy_sprite::{TextureAtlas, QUAD_HANDLE}; +use bevy_math::{Mat4, Size, Vec3}; +use bevy_render::{texture::Image, RenderWorld}; +use bevy_sprite::{ExtractedSprite, ExtractedSprites, TextureAtlas}; use bevy_transform::prelude::{GlobalTransform, Transform}; use bevy_window::Windows; -use glyph_brush_layout::{HorizontalAlign, VerticalAlign}; -use crate::{DefaultTextPipeline, DrawableText, Font, FontAtlasSet, Text, Text2dSize, TextError}; +use crate::{ + DefaultTextPipeline, Font, FontAtlasSet, HorizontalAlign, Text, Text2dSize, TextError, + VerticalAlign, +}; /// The bundle of components needed to draw text in a 2D scene via a 2D `OrthographicCameraBundle`. /// [Example usage.](https://github.com/bevyengine/bevy/blob/latest/examples/2d/text2d.rs) #[derive(Bundle, Clone, Debug)] pub struct Text2dBundle { - pub draw: Draw, - pub visible: Visible, pub text: Text, pub transform: Transform, pub global_transform: GlobalTransform, - pub main_pass: MainPass, pub text_2d_size: Text2dSize, } impl Default for Text2dBundle { fn default() -> Self { Self { - draw: Draw { - ..Default::default() - }, - visible: Visible { - is_transparent: true, - ..Default::default() - }, text: Default::default(), transform: Default::default(), global_transform: Default::default(), - main_pass: MainPass {}, text_2d_size: Text2dSize { size: Size::default(), }, @@ -54,46 +39,25 @@ impl Default for Text2dBundle { } } -/// System for drawing text in a 2D scene via a 2D `OrthographicCameraBundle`. Included in the -/// default `TextPlugin`. Position is determined by the `Transform`'s translation, though scale and -/// rotation are ignored. -#[allow(clippy::type_complexity)] -pub fn draw_text2d_system( - mut context: DrawContext, - msaa: Res, - meshes: Res>, - windows: Res, - mut render_resource_bindings: ResMut, +pub fn extract_text2d_sprite( + mut render_world: ResMut, + texture_atlases: Res>, text_pipeline: Res, - mut query: Query< - ( - Entity, - &mut Draw, - &Visible, - &Text, - &GlobalTransform, - &Text2dSize, - ), - (With, Without), - >, + windows: Res, + mut text2d_query: Query<(Entity, &Text, &GlobalTransform, &Text2dSize)>, ) { - let font_quad = meshes.get(&QUAD_HANDLE).unwrap(); - let font_quad_vertex_layout = font_quad.get_vertex_buffer_layout(); - + let mut extracted_sprites = render_world.get_resource_mut::().unwrap(); let scale_factor = if let Some(window) = windows.get_primary() { window.scale_factor() as f32 } else { 1. }; - for (entity, mut draw, visible, text, global_transform, calculated_size) in query.iter_mut() { - if !visible.is_visible { - continue; - } - + for (entity, text, transform, calculated_size) in text2d_query.iter_mut() { let (width, height) = (calculated_size.size.width, calculated_size.size.height); - if let Some(text_glyphs) = text_pipeline.get_glyphs(&entity) { + if let Some(text_layout) = text_pipeline.get_glyphs(&entity) { + let text_glyphs = &text_layout.glyphs; let alignment_offset = match text.alignment.vertical { VerticalAlign::Top => Vec3::new(0.0, -height, 0.0), VerticalAlign::Center => Vec3::new(0.0, -height * 0.5, 0.0), @@ -104,18 +68,36 @@ pub fn draw_text2d_system( HorizontalAlign::Right => Vec3::new(-width, 0.0, 0.0), }; - let mut drawable_text = DrawableText { - render_resource_bindings: &mut render_resource_bindings, - global_transform: *global_transform, - scale_factor, - msaa: &msaa, - text_glyphs: &text_glyphs.glyphs, - font_quad_vertex_layout: &font_quad_vertex_layout, - sections: &text.sections, - alignment_offset, - }; - - drawable_text.draw(&mut draw, &mut context).unwrap(); + for text_glyph in text_glyphs { + let color = text.sections[text_glyph.section_index] + .style + .color + .as_rgba_linear(); + let atlas = texture_atlases + .get(text_glyph.atlas_info.texture_atlas.clone_weak()) + .unwrap(); + let handle = atlas.texture.clone_weak(); + let index = text_glyph.atlas_info.glyph_index as usize; + let rect = atlas.textures[index]; + let atlas_size = Some(atlas.size); + + let transform = + Mat4::from_rotation_translation(transform.rotation, transform.translation) + * Mat4::from_scale(transform.scale / scale_factor) + * Mat4::from_translation( + alignment_offset * scale_factor + text_glyph.position.extend(0.), + ); + + extracted_sprites.sprites.push(ExtractedSprite { + transform, + color, + rect, + handle, + atlas_size, + flip_x: false, + flip_y: false, + }); + } } } } @@ -129,15 +111,15 @@ pub struct QueuedText2d { #[allow(clippy::too_many_arguments, clippy::type_complexity)] pub fn text2d_system( mut queued_text: Local, - mut textures: ResMut>, + mut textures: ResMut>, fonts: Res>, windows: Res, mut texture_atlases: ResMut>, mut font_atlas_set_storage: ResMut>, mut text_pipeline: ResMut, mut text_queries: QuerySet<( - QueryState, Changed)>, - QueryState<(&Text, &mut Text2dSize), With>, + QueryState, Changed)>, + QueryState<(&Text, &mut Text2dSize), With>, )>, ) { // Adds all entities where the text or the style has changed to the local queue diff --git a/crates/bevy_ui/Cargo.toml b/crates/bevy_ui/Cargo.toml index ab2f886e814e9..1858f8d4471c5 100644 --- a/crates/bevy_ui/Cargo.toml +++ b/crates/bevy_ui/Cargo.toml @@ -13,6 +13,8 @@ keywords = ["bevy"] bevy_app = { path = "../bevy_app", version = "0.5.0" } bevy_asset = { path = "../bevy_asset", version = "0.5.0" } bevy_core = { path = "../bevy_core", version = "0.5.0" } +bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.5.0" } +bevy_derive = { path = "../bevy_derive", version = "0.5.0" } bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" } bevy_input = { path = "../bevy_input", version = "0.5.0" } bevy_log = { path = "../bevy_log", version = "0.5.0" } @@ -29,3 +31,5 @@ bevy_utils = { path = "../bevy_utils", version = "0.5.0" } stretch = "0.3.2" serde = {version = "1", features = ["derive"]} smallvec = { version = "1.6", features = ["union", "const_generics"] } +bytemuck = { version = "1.5", features = ["derive"] } +crevice = { path = "../crevice", version = "0.8.0", features = ["glam"] } \ No newline at end of file diff --git a/crates/bevy_ui/src/entity.rs b/crates/bevy_ui/src/entity.rs index 7957a4f0adea6..da8c307167b01 100644 --- a/crates/bevy_ui/src/entity.rs +++ b/crates/bevy_ui/src/entity.rs @@ -1,109 +1,41 @@ -use super::Node; use crate::{ - render::UI_PIPELINE_HANDLE, - widget::{Button, Image}, - CalculatedSize, ControlNode, FocusPolicy, Interaction, Style, + widget::{Button, ImageMode}, + CalculatedSize, FocusPolicy, Interaction, Node, Style, UiColor, UiImage, CAMERA_UI, }; -use bevy_asset::Handle; use bevy_ecs::bundle::Bundle; use bevy_render::{ - camera::{Camera, DepthCalculation, OrthographicProjection, VisibleEntities, WindowOrigin}, - draw::Draw, - mesh::Mesh, - pipeline::{RenderPipeline, RenderPipelines}, - prelude::Visible, + camera::{Camera, DepthCalculation, OrthographicProjection, WindowOrigin}, + view::VisibleEntities, }; -use bevy_sprite::{ColorMaterial, QUAD_HANDLE}; use bevy_text::Text; use bevy_transform::prelude::{GlobalTransform, Transform}; -/// If you add this to an entity, it should be the *only* bundle on it from bevy_ui. -/// This bundle will mark the entity as transparent to the UI layout system, meaning the -/// children of this entity will be treated as the children of this entity s parent by the layout system. -pub struct ControlBundle { - pub control_node: ControlNode, - pub transform: Transform, - pub global_transform: GlobalTransform, -} - -#[derive(Bundle, Clone, Debug)] +#[derive(Bundle, Clone, Debug, Default)] pub struct NodeBundle { pub node: Node, pub style: Style, - pub mesh: Handle, // TODO: maybe abstract this out - pub material: Handle, - pub draw: Draw, - pub visible: Visible, - pub render_pipelines: RenderPipelines, + pub color: UiColor, + pub image: UiImage, pub transform: Transform, pub global_transform: GlobalTransform, } -impl Default for NodeBundle { - fn default() -> Self { - NodeBundle { - mesh: QUAD_HANDLE.typed(), - render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::new( - UI_PIPELINE_HANDLE.typed(), - )]), - visible: Visible { - is_transparent: true, - ..Default::default() - }, - node: Default::default(), - style: Default::default(), - material: Default::default(), - draw: Default::default(), - transform: Default::default(), - global_transform: Default::default(), - } - } -} - -#[derive(Bundle, Clone, Debug)] +#[derive(Bundle, Clone, Debug, Default)] pub struct ImageBundle { pub node: Node, pub style: Style, - pub image: Image, + pub image_mode: ImageMode, pub calculated_size: CalculatedSize, - pub mesh: Handle, // TODO: maybe abstract this out - pub material: Handle, - pub draw: Draw, - pub visible: Visible, - pub render_pipelines: RenderPipelines, + pub color: UiColor, + pub image: UiImage, pub transform: Transform, pub global_transform: GlobalTransform, } -impl Default for ImageBundle { - fn default() -> Self { - ImageBundle { - mesh: QUAD_HANDLE.typed(), - render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::new( - UI_PIPELINE_HANDLE.typed(), - )]), - node: Default::default(), - image: Default::default(), - calculated_size: Default::default(), - style: Default::default(), - material: Default::default(), - draw: Default::default(), - visible: Visible { - is_transparent: true, - ..Default::default() - }, - transform: Default::default(), - global_transform: Default::default(), - } - } -} - #[derive(Bundle, Clone, Debug)] pub struct TextBundle { pub node: Node, pub style: Style, - pub draw: Draw, - pub visible: Visible, pub text: Text, pub calculated_size: CalculatedSize, pub focus_policy: FocusPolicy, @@ -115,13 +47,6 @@ impl Default for TextBundle { fn default() -> Self { TextBundle { focus_policy: FocusPolicy::Pass, - draw: Draw { - ..Default::default() - }, - visible: Visible { - is_transparent: true, - ..Default::default() - }, text: Default::default(), node: Default::default(), calculated_size: Default::default(), @@ -139,11 +64,8 @@ pub struct ButtonBundle { pub style: Style, pub interaction: Interaction, pub focus_policy: FocusPolicy, - pub mesh: Handle, // TODO: maybe abstract this out - pub material: Handle, - pub draw: Draw, - pub visible: Visible, - pub render_pipelines: RenderPipelines, + pub color: UiColor, + pub image: UiImage, pub transform: Transform, pub global_transform: GlobalTransform, } @@ -152,20 +74,12 @@ impl Default for ButtonBundle { fn default() -> Self { ButtonBundle { button: Button, - mesh: QUAD_HANDLE.typed(), - render_pipelines: RenderPipelines::from_pipelines(vec![RenderPipeline::new( - UI_PIPELINE_HANDLE.typed(), - )]), interaction: Default::default(), focus_policy: Default::default(), node: Default::default(), style: Default::default(), - material: Default::default(), - draw: Default::default(), - visible: Visible { - is_transparent: true, - ..Default::default() - }, + color: Default::default(), + image: Default::default(), transform: Default::default(), global_transform: Default::default(), } @@ -176,9 +90,10 @@ impl Default for ButtonBundle { pub struct UiCameraBundle { pub camera: Camera, pub orthographic_projection: OrthographicProjection, - pub visible_entities: VisibleEntities, pub transform: Transform, pub global_transform: GlobalTransform, + // FIXME there is no frustrum culling for UI + pub visible_entities: VisibleEntities, } impl Default for UiCameraBundle { @@ -188,7 +103,7 @@ impl Default for UiCameraBundle { let far = 1000.0; UiCameraBundle { camera: Camera { - name: Some(crate::camera::CAMERA_UI.to_string()), + name: Some(CAMERA_UI.to_string()), ..Default::default() }, orthographic_projection: OrthographicProjection { @@ -197,9 +112,9 @@ impl Default for UiCameraBundle { depth_calculation: DepthCalculation::ZDifference, ..Default::default() }, - visible_entities: Default::default(), transform: Transform::from_xyz(0.0, 0.0, far - 0.1), global_transform: Default::default(), + visible_entities: Default::default(), } } } diff --git a/crates/bevy_ui/src/flex/mod.rs b/crates/bevy_ui/src/flex/mod.rs index 1e6a2a7b92860..0bb7a573d302a 100644 --- a/crates/bevy_ui/src/flex/mod.rs +++ b/crates/bevy_ui/src/flex/mod.rs @@ -1,6 +1,6 @@ mod convert; -use crate::{CalculatedSize, ControlNode, Node, Style}; +use crate::{CalculatedSize, Node, Style}; use bevy_app::EventReader; use bevy_ecs::{ entity::Entity, @@ -111,62 +111,19 @@ impl FlexSurface { } } - pub fn update_children( - &mut self, - entity: Entity, - children: &Children, - control_node_query: &mut Query<&mut ControlNode>, - unfiltered_children_query: &Query<&Children>, - ) { + pub fn update_children(&mut self, entity: Entity, children: &Children) { let mut stretch_children = Vec::with_capacity(children.len()); - fn inner( - true_parent: Entity, - child: Entity, - control_node_query: &mut Query<&mut ControlNode>, - unfiltered_children_query: &Query<&Children>, - do_on_real: &mut impl FnMut(Entity), - ) { - if let Ok(mut control_node) = control_node_query.get_mut(child) { - control_node.true_parent = Some(true_parent); - for &child in unfiltered_children_query - .get(child) - .ok() - .into_iter() - .map(|c| &**c) - .flatten() - { - inner( - true_parent, - child, - control_node_query, - unfiltered_children_query, - do_on_real, - ); - } + for child in children.iter() { + if let Some(stretch_node) = self.entity_to_stretch.get(child) { + stretch_children.push(*stretch_node); } else { - do_on_real(child); + warn!( + "Unstyled child in a UI entity hierarchy. You are using an entity \ +without UI components as a child of an entity with UI components, results may be unexpected." + ); } } - for &child in children.iter() { - inner( - entity, - child, - control_node_query, - unfiltered_children_query, - &mut |e| { - if let Some(stretch_node) = self.entity_to_stretch.get(&e) { - stretch_children.push(*stretch_node); - } else { - warn!( - "Unstyled child in a UI entity hierarchy. You are using an entity \ - without UI components as a child of an entity with UI components, results may be unexpected." - ); - } - }, - ); - } - let stretch_node = self.entity_to_stretch.get(&entity).unwrap(); self.stretch .set_children(*stretch_node, stretch_children) @@ -250,10 +207,7 @@ pub fn flex_node_system( (Entity, &Style, &CalculatedSize), (With, Changed), >, - changed_children_query: Query<(Entity, &Children), (With, Changed)>, - unfiltered_children_query: Query<&Children>, - mut control_node_query: Query<&mut ControlNode>, - changed_cnc_query: Query, With)>, + children_query: Query<(Entity, &Children), (With, Changed)>, mut node_transform_query: Query<(Entity, &mut Node, &mut Transform, Option<&Parent>)>, ) { // update window root nodes @@ -308,28 +262,8 @@ pub fn flex_node_system( } // update children - for (entity, children) in changed_children_query.iter() { - flex_surface.update_children( - entity, - children, - &mut control_node_query, - &unfiltered_children_query, - ); - } - - for entity in changed_cnc_query.iter() { - let true_parent = if let Some(e) = control_node_query.get_mut(entity).unwrap().true_parent { - e - } else { - continue; - }; - let children = unfiltered_children_query.get(true_parent).unwrap(); - flex_surface.update_children( - true_parent, - children, - &mut control_node_query, - &unfiltered_children_query, - ); + for (entity, children) in children_query.iter() { + flex_surface.update_children(entity, children); } // compute layouts @@ -350,11 +284,7 @@ pub fn flex_node_system( position.x = to_logical(layout.location.x + layout.size.width / 2.0); position.y = to_logical(layout.location.y + layout.size.height / 2.0); if let Some(parent) = parent { - let parent = control_node_query - .get_mut(parent.0) - .map(|cn| cn.true_parent.unwrap()) - .unwrap_or(parent.0); - if let Ok(parent_layout) = flex_surface.get_layout(parent) { + if let Ok(parent_layout) = flex_surface.get_layout(parent.0) { position.x -= to_logical(parent_layout.size.width / 2.0); position.y -= to_logical(parent_layout.size.height / 2.0); } diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 2e5acd8516062..14340f5ed144d 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -1,16 +1,20 @@ use crate::Node; use bevy_core::FloatOrd; use bevy_ecs::{ - component::Component, entity::Entity, + prelude::Component, + reflect::ReflectComponent, system::{Local, Query, Res}, }; use bevy_input::{mouse::MouseButton, touch::Touches, Input}; +use bevy_reflect::{Reflect, ReflectDeserialize}; use bevy_transform::components::GlobalTransform; use bevy_window::Windows; +use serde::{Deserialize, Serialize}; use smallvec::SmallVec; -#[derive(Component, Copy, Clone, Eq, PartialEq, Debug)] +#[derive(Component, Copy, Clone, Eq, PartialEq, Debug, Reflect, Serialize, Deserialize)] +#[reflect_value(Component, Serialize, Deserialize, PartialEq)] pub enum Interaction { Clicked, Hovered, @@ -23,7 +27,8 @@ impl Default for Interaction { } } -#[derive(Component, Copy, Clone, Eq, PartialEq, Debug)] +#[derive(Component, Copy, Clone, Eq, PartialEq, Debug, Reflect, Serialize, Deserialize)] +#[reflect_value(Component, Serialize, Deserialize, PartialEq)] pub enum FocusPolicy { Block, Pass, diff --git a/crates/bevy_ui/src/lib.rs b/crates/bevy_ui/src/lib.rs index e9ca5b3b328f8..36a8f4fc83b49 100644 --- a/crates/bevy_ui/src/lib.rs +++ b/crates/bevy_ui/src/lib.rs @@ -25,7 +25,6 @@ use bevy_app::prelude::*; use bevy_ecs::schedule::{ParallelSystemDescriptorCoercion, SystemLabel}; use bevy_input::InputSystem; use bevy_math::{Rect, Size}; -use bevy_render::RenderStage; use bevy_transform::TransformSystem; use update::ui_z_system; @@ -45,18 +44,27 @@ impl Plugin for UiPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() .register_type::() + .register_type::() + .register_type::() .register_type::() .register_type::() + // NOTE: used by Style::aspect_ratio + .register_type::>() .register_type::() .register_type::>() .register_type::>() .register_type::>() .register_type::