mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 07:04:33 +00:00
Replace old renderer with new renderer (#3312)
This makes the [New Bevy Renderer](#2535) the default (and only) renderer. The new renderer isn't _quite_ ready for the final release yet, but I want as many people as possible to start testing it so we can identify bugs and address feedback prior to release. The examples are all ported over and operational with a few exceptions: * I removed a good portion of the examples in the `shader` folder. We still have some work to do in order to make these examples possible / ergonomic / worthwhile: #3120 and "high level shader material plugins" are the big ones. This is a temporary measure. * Temporarily removed the multiple_windows example: doing this properly in the new renderer will require the upcoming "render targets" changes. Same goes for the render_to_texture example. * Removed z_sort_debug: entity visibility sort info is no longer available in app logic. we could do this on the "render app" side, but i dont consider it a priority.
This commit is contained in:
parent
de8edd3165
commit
ffecb05a0a
329 changed files with 4560 additions and 30649 deletions
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -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
|
||||
|
||||
|
|
142
Cargo.toml
142
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"
|
||||
name = "shadow_biases"
|
||||
path = "examples/3d/shadow_biases.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"
|
||||
|
||||
[[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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" }
|
||||
|
|
@ -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::{
|
|
@ -1,5 +1,5 @@
|
|||
use bevy_ecs::world::World;
|
||||
use bevy_render2::{
|
||||
use bevy_render::{
|
||||
render_graph::{Node, NodeRunError, RenderGraphContext},
|
||||
renderer::RenderContext,
|
||||
};
|
|
@ -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},
|
|
@ -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},
|
|
@ -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},
|
|
@ -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,
|
|
@ -76,7 +76,7 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
|
|||
meta_item.path(),
|
||||
format!(
|
||||
"unknown component attribute `{}`",
|
||||
meta_item.path().into_token_stream().to_string()
|
||||
meta_item.path().into_token_stream()
|
||||
),
|
||||
));
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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<Handle<Scene>>,
|
||||
}
|
||||
|
||||
/// 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<GltfPrimitive>,
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
|
|
|
@ -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::<Vec<bevy_asset::Handle<GltfNode>>>();
|
||||
|
@ -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<u8>],
|
||||
linear_textures: &HashSet<usize>,
|
||||
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<StandardMaterial> {
|
||||
let material_label = material_label(material);
|
||||
|
||||
|
@ -381,7 +383,8 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle<
|
|||
None
|
||||
};
|
||||
|
||||
let normal_map = if let Some(normal_texture) = material.normal_texture() {
|
||||
let normal_map_texture: Option<Handle<Image>> =
|
||||
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());
|
||||
|
@ -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 {
|
||||
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 {
|
||||
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<PrimitiveTopology, GltfError> {
|
||||
match mode {
|
||||
Mode::Points => Ok(PrimitiveTopology::PointList),
|
||||
|
@ -640,6 +663,15 @@ fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
|
|||
}
|
||||
}
|
||||
|
||||
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<usize>)>,
|
||||
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::<Vec<_>>();
|
||||
.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);
|
||||
}
|
||||
}
|
||||
let children = children.into_iter().collect::<HashSet<_>>();
|
||||
if children.is_empty() {
|
||||
empty_children.push_back(i);
|
||||
}
|
||||
(i, (label, node, children))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
let mut nodes = std::collections::HashMap::<usize, (String, GltfNode)>::new();
|
||||
while max_steps > 0 && !nodes_step.is_empty() {
|
||||
if let Some((index, label, node, _)) = nodes_step
|
||||
.iter()
|
||||
.find(|(_, _, _, children)| children.is_empty())
|
||||
.cloned()
|
||||
{
|
||||
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));
|
||||
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(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) {
|
||||
node.children.push(child_node.clone())
|
||||
parent_node.children.push(child_node.clone())
|
||||
}
|
||||
if parent_children.is_empty() {
|
||||
empty_children.push_back(parent_index);
|
||||
}
|
||||
}
|
||||
}
|
||||
nodes_step = nodes_step
|
||||
.into_iter()
|
||||
.filter(|(i, _, _, _)| *i != index)
|
||||
.collect()
|
||||
if !unprocessed_nodes.is_empty() {
|
||||
warn!("GLTF model must be a tree: {:?}", asset_path);
|
||||
}
|
||||
max_steps -= 1;
|
||||
}
|
||||
|
||||
let mut nodes_to_sort = nodes.into_iter().collect::<Vec<_>>();
|
||||
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![
|
||||
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![
|
||||
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,7 +870,8 @@ mod test {
|
|||
|
||||
#[test]
|
||||
fn node_hierarchy_hierarchy() {
|
||||
let result = resolve_node_hierarchy(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]),
|
||||
|
@ -826,7 +879,9 @@ mod test {
|
|||
("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![
|
||||
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![
|
||||
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");
|
||||
|
|
|
@ -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" }
|
||||
|
||||
|
|
|
@ -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,71 +18,11 @@ 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());
|
||||
|
||||
#[cfg(feature = "bevy_render")]
|
||||
group.add(RenderPlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_sprite")]
|
||||
group.add(SpritePlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
group.add(PbrPlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_ui")]
|
||||
group.add(UiPlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_text")]
|
||||
group.add(TextPlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_audio")]
|
||||
group.add(AudioPlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_gilrs")]
|
||||
group.add(GilrsPlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_gltf")]
|
||||
group.add(GltfPlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_winit")]
|
||||
group.add(WinitPlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_wgpu")]
|
||||
group.add(WgpuPlugin::default());
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimal plugin group that will add the following plugins:
|
||||
/// * [`CorePlugin`]
|
||||
/// * [`ScheduleRunnerPlugin`]
|
||||
///
|
||||
/// See also [`DefaultPlugins`] for a more complete set of plugins
|
||||
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());
|
||||
|
@ -126,29 +36,42 @@ impl PluginGroup for PipelinedDefaultPlugins {
|
|||
#[cfg(feature = "bevy_winit")]
|
||||
group.add(bevy_winit::WinitPlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_render2")]
|
||||
{
|
||||
group.add(bevy_render2::RenderPlugin::default());
|
||||
}
|
||||
#[cfg(feature = "bevy_render")]
|
||||
group.add(bevy_render::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_sprite")]
|
||||
group.add(bevy_sprite::SpritePlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_text2")]
|
||||
group.add(bevy_text2::TextPlugin::default());
|
||||
#[cfg(feature = "bevy_text")]
|
||||
group.add(bevy_text::TextPlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_ui2")]
|
||||
group.add(bevy_ui2::UiPlugin::default());
|
||||
#[cfg(feature = "bevy_ui")]
|
||||
group.add(bevy_ui::UiPlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_pbr2")]
|
||||
group.add(bevy_pbr2::PbrPlugin::default());
|
||||
#[cfg(feature = "bevy_pbr")]
|
||||
group.add(bevy_pbr::PbrPlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_gltf2")]
|
||||
group.add(bevy_gltf2::GltfPlugin::default());
|
||||
}
|
||||
#[cfg(feature = "bevy_gltf")]
|
||||
group.add(bevy_gltf::GltfPlugin::default());
|
||||
|
||||
#[cfg(feature = "bevy_audio")]
|
||||
group.add(bevy_audio::AudioPlugin::default());
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimal plugin group that will add the following plugins:
|
||||
/// * [`CorePlugin`]
|
||||
/// * [`ScheduleRunnerPlugin`]
|
||||
///
|
||||
/// See also [`DefaultPlugins`] for a more complete set of plugins
|
||||
pub struct MinimalPlugins;
|
||||
|
||||
impl PluginGroup for MinimalPlugins {
|
||||
fn build(&mut self, group: &mut PluginGroupBuilder) {
|
||||
group.add(bevy_core::CorePlugin::default());
|
||||
group.add(bevy_app::ScheduleRunnerPlugin::default());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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"] }
|
||||
|
|
|
@ -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},
|
|
@ -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<Mesh>,
|
||||
pub material: Handle<StandardMaterial>,
|
||||
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,
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
pub mod draw_3d_graph {
|
||||
pub mod node {
|
||||
/// Label for the shadow pass node.
|
||||
pub const SHADOW_PASS: &str = "shadow_pass";
|
||||
}
|
||||
}
|
||||
|
||||
/// NOTE: this isn't PBR yet. consider this name "aspirational" :)
|
||||
use bevy_app::prelude::*;
|
||||
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;
|
||||
|
||||
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::<StandardMaterial>()
|
||||
.register_type::<PointLight>()
|
||||
let mut shaders = app.world.get_resource_mut::<Assets<Shader>>().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::<Handle<StandardMaterial>>::default())
|
||||
.init_resource::<AmbientLight>()
|
||||
.init_resource::<DirectionalLightShadowMap>()
|
||||
.init_resource::<PointLightShadowMap>()
|
||||
.init_resource::<AmbientLight>()
|
||||
.init_resource::<VisiblePointLights>()
|
||||
.add_system_to_stage(
|
||||
CoreStage::PostUpdate,
|
||||
shader::asset_shader_defs_system::<StandardMaterial>,
|
||||
// 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),
|
||||
)
|
||||
.init_resource::<AmbientLight>();
|
||||
add_pbr_graph(&mut app.world);
|
||||
|
||||
// add default StandardMaterial
|
||||
let mut materials = app
|
||||
.world
|
||||
.get_resource_mut::<Assets<StandardMaterial>>()
|
||||
.unwrap();
|
||||
materials.set_untracked(
|
||||
Handle::<StandardMaterial>::default(),
|
||||
StandardMaterial {
|
||||
base_color: Color::PINK,
|
||||
unlit: true,
|
||||
..Default::default()
|
||||
},
|
||||
.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,
|
||||
update_point_light_frusta
|
||||
.label(SimulationLightSystems::UpdatePointLightFrusta)
|
||||
.after(TransformSystem::TransformPropagate)
|
||||
.after(SimulationLightSystems::AssignLightsToClusters),
|
||||
)
|
||||
.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::<Shadow>)
|
||||
.init_resource::<PbrPipeline>()
|
||||
.init_resource::<ShadowPipeline>()
|
||||
.init_resource::<DrawFunctions<Shadow>>()
|
||||
.init_resource::<LightMeta>()
|
||||
.init_resource::<GlobalLightMeta>()
|
||||
.init_resource::<SpecializedPipelines<PbrPipeline>>()
|
||||
.init_resource::<SpecializedPipelines<ShadowPipeline>>();
|
||||
|
||||
let shadow_pass_node = ShadowPassNode::new(&mut render_app.world);
|
||||
render_app.add_render_command::<Opaque3d, DrawPbr>();
|
||||
render_app.add_render_command::<AlphaMask3d, DrawPbr>();
|
||||
render_app.add_render_command::<Transparent3d, DrawPbr>();
|
||||
render_app.add_render_command::<Shadow, DrawShadowMesh>();
|
||||
let mut graph = render_app.world.get_resource_mut::<RenderGraph>().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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 bevy_transform::components::GlobalTransform;
|
||||
use std::collections::HashSet;
|
||||
|
||||
/// A point light
|
||||
#[derive(Component, Debug, Clone, Copy, Reflect)]
|
||||
#[reflect(Component)]
|
||||
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 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();
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct PointLightShadowMap {
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
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,
|
||||
];
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DirectionalLightShadowMap {
|
||||
pub size: usize,
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
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<Aabb>,
|
||||
pub(crate) lights: Vec<VisiblePointLights>,
|
||||
}
|
||||
|
||||
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<Windows>,
|
||||
cameras: Query<(Entity, &Camera), Without<Clusters>>,
|
||||
) {
|
||||
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<Windows>, 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<Entity>,
|
||||
}
|
||||
|
||||
impl VisiblePointLights {
|
||||
pub fn from_light_count(count: usize) -> Self {
|
||||
Self {
|
||||
entities: Vec::with_capacity(count),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &Entity> {
|
||||
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<VisiblePointLights>,
|
||||
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<VisiblePointLights>,
|
||||
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::<Vec<_>>();
|
||||
|
||||
let global_lights_set = global_lights
|
||||
.entities
|
||||
.iter()
|
||||
.copied()
|
||||
.collect::<HashSet<_>>();
|
||||
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<DirectionalLight>
|
||||
// is needed to avoid an unnecessary QuerySet
|
||||
visible_point_lights: Query<&VisiblePointLights, Without<DirectionalLight>>,
|
||||
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<NotShadowCaster>,
|
||||
>,
|
||||
) {
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 <https://google.github.io/filament/Material%20Properties.pdf>
|
||||
#[derive(Debug, RenderResources, ShaderDefs, TypeUuid)]
|
||||
#[uuid = "dace545e-4bc6-4595-a79d-c224fc694975"]
|
||||
/// Standard property values with pictures here
|
||||
/// <https://google.github.io/filament/Material%20Properties.pdf>.
|
||||
///
|
||||
/// 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<Handle<Texture>>,
|
||||
pub base_color_texture: Option<Handle<Image>>,
|
||||
// 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<Handle<Image>>,
|
||||
/// 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<Handle<Image>>,
|
||||
/// 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<Handle<Texture>>,
|
||||
pub reflectance: f32,
|
||||
#[shader_def]
|
||||
pub normal_map: Option<Handle<Texture>>,
|
||||
#[render_resources(ignore)]
|
||||
#[shader_def]
|
||||
pub normal_map_texture: Option<Handle<Image>>,
|
||||
pub occlusion_texture: Option<Handle<Image>>,
|
||||
pub double_sided: bool,
|
||||
#[shader_def]
|
||||
pub occlusion_texture: Option<Handle<Texture>>,
|
||||
// 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<Handle<Texture>>,
|
||||
#[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 <https://google.github.io/filament/Filament.html#materialsystem/parameterization/>
|
||||
// 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,
|
||||
// <https://google.github.io/filament/Material%20Properties.pdf>
|
||||
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<Color> for StandardMaterial {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<Handle<Texture>> for StandardMaterial {
|
||||
fn from(texture: Handle<Texture>) -> Self {
|
||||
impl From<Handle<Image>> for StandardMaterial {
|
||||
fn from(texture: Handle<Image>) -> 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::<StandardMaterial>::default())
|
||||
.add_asset::<StandardMaterial>();
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<Handle<Image>>,
|
||||
pub alpha_mode: AlphaMode,
|
||||
}
|
||||
|
||||
impl RenderAsset for StandardMaterial {
|
||||
type ExtractedAsset = StandardMaterial;
|
||||
type PreparedAsset = GpuStandardMaterial;
|
||||
type Param = (
|
||||
SRes<RenderDevice>,
|
||||
SRes<PbrPipeline>,
|
||||
SRes<RenderAssets<Image>>,
|
||||
);
|
||||
|
||||
fn extract_asset(&self) -> Self::ExtractedAsset {
|
||||
self.clone()
|
||||
}
|
||||
|
||||
fn prepare_asset(
|
||||
material: Self::ExtractedAsset,
|
||||
(render_device, pbr_pipeline, gpu_images): &mut SystemParamItem<Self::Param>,
|
||||
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
|
@ -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,7 +693,7 @@ 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();
|
||||
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);
|
||||
|
@ -703,6 +703,9 @@ impl EntityRenderCommand for DrawMesh {
|
|||
}
|
||||
|
||||
RenderCommandResult::Success
|
||||
} else {
|
||||
RenderCommandResult::Failure
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
|
@ -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<BufferId>,
|
||||
staging_buffer: Option<BufferId>,
|
||||
command_queue: CommandQueue,
|
||||
max_point_lights: usize,
|
||||
max_dir_lights: usize,
|
||||
}
|
||||
|
||||
pub fn lights_node_system(
|
||||
mut state: Local<LightsNodeSystemState>,
|
||||
render_resource_context: Res<Box<dyn RenderResourceContext>>,
|
||||
ambient_light_resource: Res<AmbientLight>,
|
||||
// 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<RenderResourceBindings>,
|
||||
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::<PointLightUniform>();
|
||||
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::<DirectionalLightUniform>();
|
||||
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::<LightCount>();
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
|
@ -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::<RenderGraph>().unwrap();
|
||||
graph.add_system_node(
|
||||
node::TRANSFORM,
|
||||
RenderResourcesNode::<GlobalTransform>::new(true),
|
||||
);
|
||||
graph.add_system_node(
|
||||
node::STANDARD_MATERIAL,
|
||||
AssetRenderResourcesNode::<StandardMaterial>::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::<Assets<Shader>>().unwrap());
|
||||
let mut pipelines = world
|
||||
.get_resource_mut::<Assets<PipelineDescriptor>>()
|
||||
.unwrap();
|
||||
pipelines.set_untracked(PBR_PIPELINE_HANDLE, pipeline);
|
||||
}
|
|
@ -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<Shader>) -> 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"),
|
||||
))),
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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();
|
|
@ -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"] }
|
||||
|
|
|
@ -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<Entity>,
|
||||
pub bindings: RenderResourceBindings,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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::<Camera>()
|
||||
.register_type::<Visibility>()
|
||||
.register_type::<ComputedVisibility>()
|
||||
.register_type::<OrthographicProjection>()
|
||||
.register_type::<PerspectiveProjection>()
|
||||
.register_type::<VisibleEntities>()
|
||||
.register_type::<WindowOrigin>()
|
||||
.register_type::<ScalingMode>()
|
||||
.register_type::<DepthCalculation>()
|
||||
.register_type::<Aabb>()
|
||||
.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::<OrthographicProjection>,
|
||||
)
|
||||
.add_system_to_stage(
|
||||
CoreStage::PostUpdate,
|
||||
crate::camera::camera_system::<PerspectiveProjection>,
|
||||
);
|
||||
app.sub_app(RenderApp)
|
||||
.init_resource::<ExtractedCameraNames>()
|
||||
.add_system_to_stage(RenderStage::Extract, extract_cameras);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExtractedCameraNames {
|
||||
pub entities: HashMap<String, Entity>,
|
||||
}
|
||||
|
||||
#[derive(Component, Debug)]
|
||||
pub struct ExtractedCamera {
|
||||
pub window_id: WindowId,
|
||||
pub name: Option<String>,
|
||||
}
|
||||
|
||||
fn extract_cameras(
|
||||
mut commands: Commands,
|
||||
active_cameras: Res<ActiveCameras>,
|
||||
windows: Res<Windows>,
|
||||
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 })
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<VisibleEntity>,
|
||||
}
|
||||
|
||||
impl VisibleEntities {
|
||||
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &VisibleEntity> {
|
||||
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::<Vec<_>>())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::iter::FromIterator<Layer> for RenderLayers {
|
||||
fn from_iter<T: IntoIterator<Item = Layer>>(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::<LayerMask>() * 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<Item = Layer> {
|
||||
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<_>>(),
|
||||
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]),
|
||||
<RenderLayers as std::iter::FromIterator<Layer>>::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<OutsideFrustum>>,
|
||||
visible_transform_query: Query<&GlobalTransform, Without<OutsideFrustum>>,
|
||||
) {
|
||||
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
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<PipelineDescriptor>,
|
||||
},
|
||||
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<Arc<[u32]>>,
|
||||
},
|
||||
DrawIndexed {
|
||||
indices: Range<u32>,
|
||||
base_vertex: i32,
|
||||
instances: Range<u32>,
|
||||
},
|
||||
Draw {
|
||||
vertices: Range<u32>,
|
||||
instances: Range<u32>,
|
||||
},
|
||||
}
|
||||
|
||||
#[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<RenderCommand>,
|
||||
}
|
||||
|
||||
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<PipelineDescriptor>) {
|
||||
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<u32>, base_vertex: i32, instances: Range<u32>) {
|
||||
self.render_command(RenderCommand::DrawIndexed {
|
||||
base_vertex,
|
||||
indices,
|
||||
instances,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn draw(&mut self, vertices: Range<u32>, instances: Range<u32>) {
|
||||
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<PipelineDescriptor>>,
|
||||
pub shaders: ResMut<'w, Assets<Shader>>,
|
||||
pub asset_render_resource_bindings: ResMut<'w, AssetRenderResourceBindings>,
|
||||
pub pipeline_compiler: ResMut<'w, PipelineCompiler>,
|
||||
pub render_resource_context: Res<'w, Box<dyn RenderResourceContext>>,
|
||||
pub shared_buffers: ResMut<'w, SharedBuffers>,
|
||||
#[system_param(ignore)]
|
||||
pub current_pipeline: Option<Handle<PipelineDescriptor>>,
|
||||
#[system_param(ignore)]
|
||||
marker: PhantomData<&'s usize>,
|
||||
}
|
||||
|
||||
impl<'w, 's> DrawContext<'w, 's> {
|
||||
pub fn get_uniform_buffer<T: RenderResource>(
|
||||
&mut self,
|
||||
render_resource: &T,
|
||||
) -> Result<RenderResourceBinding, DrawError> {
|
||||
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<PipelineDescriptor>,
|
||||
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<T: Asset>(
|
||||
&mut self,
|
||||
draw: &mut Draw,
|
||||
asset_handle: &Handle<T>,
|
||||
) -> 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<Handle<PipelineDescriptor>>,
|
||||
pipelines: &Assets<PipelineDescriptor>,
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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<Mesh>,
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
};
|
||||
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;
|
||||
pub use once_cell;
|
||||
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
|
||||
pub enum RenderSystem {
|
||||
VisibleEntities,
|
||||
}
|
||||
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 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<BaseRenderGraphConfig>,
|
||||
/// 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::<ImageTextureLoader>();
|
||||
}
|
||||
#[cfg(feature = "hdr")]
|
||||
{
|
||||
app.init_asset_loader::<HdrTextureLoader>();
|
||||
}
|
||||
|
||||
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::<ShaderLoader>()
|
||||
.add_asset::<Mesh>()
|
||||
.add_asset::<Texture>()
|
||||
let options = app
|
||||
.world
|
||||
.get_resource::<options::WgpuOptions>()
|
||||
.cloned()
|
||||
.unwrap_or_default();
|
||||
let instance = wgpu::Instance::new(options.backends);
|
||||
let surface = {
|
||||
let world = app.world.cell();
|
||||
let windows = world.get_resource_mut::<bevy_window::Windows>().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::<Shader>()
|
||||
.add_asset::<PipelineDescriptor>()
|
||||
.register_type::<Camera>()
|
||||
.register_type::<DepthCalculation>()
|
||||
.register_type::<Draw>()
|
||||
.register_type::<Visible>()
|
||||
.register_type::<OutsideFrustum>()
|
||||
.register_type::<RenderPipelines>()
|
||||
.register_type::<OrthographicProjection>()
|
||||
.register_type::<PerspectiveProjection>()
|
||||
.register_type::<MainPass>()
|
||||
.register_type::<VisibleEntities>()
|
||||
.init_asset_loader::<ShaderLoader>()
|
||||
.init_resource::<ScratchRenderWorld>()
|
||||
.register_type::<Color>()
|
||||
.register_type::<ShaderSpecialization>()
|
||||
.register_type::<PrimitiveTopology>()
|
||||
.register_type::<IndexFormat>()
|
||||
.register_type::<PipelineSpecialization>()
|
||||
.register_type::<RenderLayers>()
|
||||
.register_type::<ScalingMode>()
|
||||
.register_type::<VertexBufferLayout>()
|
||||
.register_type::<WindowOrigin>()
|
||||
.init_resource::<ClearColor>()
|
||||
.init_resource::<RenderGraph>()
|
||||
.init_resource::<PipelineCompiler>()
|
||||
.init_resource::<Msaa>()
|
||||
.init_resource::<RenderResourceBindings>()
|
||||
.init_resource::<AssetRenderResourceBindings>()
|
||||
.init_resource::<ActiveCameras>()
|
||||
.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::<OrthographicProjection>.before(RenderSystem::VisibleEntities),
|
||||
)
|
||||
.add_system_to_stage(
|
||||
CoreStage::PostUpdate,
|
||||
camera::camera_system::<PerspectiveProjection>.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);
|
||||
.register_type::<Frustum>();
|
||||
let render_pipeline_cache = RenderPipelineCache::new(device.clone());
|
||||
let asset_server = app.world.get_resource::<AssetServer>().unwrap().clone();
|
||||
|
||||
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::<ActiveCameras>().unwrap();
|
||||
if config.add_3d_camera {
|
||||
active_cameras.add(base::camera::CAMERA_3D);
|
||||
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::<RenderGraph>();
|
||||
|
||||
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::<SystemStage>(&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::<SystemStage>(&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::<SystemStage>(&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::<SystemStage>(&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::<SystemStage>(&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<Res<Box<dyn RenderResourceContext>>>) {
|
||||
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::<SystemStage>(&RenderStage::Extract)
|
||||
.unwrap();
|
||||
|
||||
// temporarily add the render world to the app world as a resource
|
||||
let scratch_world = app_world.remove_resource::<ScratchRenderWorld>().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::<RenderWorld>().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);
|
||||
}
|
||||
|
|
|
@ -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<f32>),
|
||||
Sint32(Vec<i32>),
|
||||
Uint32(Vec<u32>),
|
||||
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<u16>),
|
||||
U32(Vec<u32>),
|
||||
}
|
||||
|
||||
impl Indices {
|
||||
fn iter(&self) -> impl Iterator<Item = usize> + '_ {
|
||||
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<Self::Item> {
|
||||
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<Cow<'static, str>, VertexAttributeValues>,
|
||||
indices: Option<Indices>,
|
||||
}
|
||||
|
||||
/// 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<Cow<'static, str>>,
|
||||
values: impl Into<VertexAttributeValues>,
|
||||
) {
|
||||
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<Cow<'static, str>>) -> Option<&VertexAttributeValues> {
|
||||
self.attributes.get(&name.into())
|
||||
}
|
||||
|
||||
pub fn attribute_mut(
|
||||
&mut self,
|
||||
name: impl Into<Cow<'static, str>>,
|
||||
) -> 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<Indices>) {
|
||||
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<usize> = 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<u8> {
|
||||
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<T: Copy>(values: &[T], indices: impl Iterator<Item = usize>) -> Vec<T> {
|
||||
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<Mesh>,
|
||||
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<Mesh>,
|
||||
) {
|
||||
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<Entity>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MeshResourceProviderState {
|
||||
mesh_entities: HashMap<Handle<Mesh>, MeshEntities>,
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn mesh_resource_provider_system(
|
||||
mut state: Local<MeshResourceProviderState>,
|
||||
render_resource_context: Res<Box<dyn RenderResourceContext>>,
|
||||
meshes: Res<Assets<Mesh>>,
|
||||
mut mesh_events: EventReader<AssetEvent<Mesh>>,
|
||||
mut queries: QuerySet<(
|
||||
QueryState<&mut RenderPipelines, With<Handle<Mesh>>>,
|
||||
QueryState<(Entity, &Handle<Mesh>, &mut RenderPipelines), Changed<Handle<Mesh>>>,
|
||||
)>,
|
||||
) {
|
||||
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<Mesh>,
|
||||
mut render_pipelines: Mut<RenderPipelines>,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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]]);
|
|
@ -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::<Mesh>()
|
||||
.add_plugin(RenderAssetPlugin::<Mesh>::default());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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<Cube> 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<Box> 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<Quad> 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;
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<Cow<'static, str>>,
|
||||
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 {
|
|
@ -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::*;
|
|
@ -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<V> {
|
||||
/// 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<V> {
|
||||
/// How data should be read through this attachment.
|
||||
pub load: LoadOp<V>,
|
||||
/// Whether data will be written to through this attachment.
|
||||
pub store: bool,
|
||||
}
|
|
@ -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<TextureId> {
|
||||
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<TextureAttachment>,
|
||||
|
||||
/// What operations will be performed on this color attachment.
|
||||
pub ops: Operations<Color>,
|
||||
}
|
||||
|
||||
#[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<Operations<f32>>,
|
||||
/// What operations will be performed on the stencil part of the attachment.
|
||||
pub stencil_ops: Option<Operations<u32>>,
|
||||
}
|
||||
|
||||
// A set of pipeline bindings and draw calls with color and depth outputs
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PassDescriptor {
|
||||
pub color_attachments: Vec<RenderPassColorAttachment>,
|
||||
pub depth_stencil_attachment: Option<RenderPassDepthStencilAttachment>,
|
||||
pub sample_count: u32,
|
||||
}
|
|
@ -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<PipelineDescriptor>);
|
||||
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<u32>, instances: Range<u32>);
|
||||
fn draw_indexed(&mut self, indices: Range<u32>, base_vertex: i32, instances: Range<u32>);
|
||||
fn set_bind_group(
|
||||
&mut self,
|
||||
index: u32,
|
||||
bind_group_descriptor_id: BindGroupDescriptorId,
|
||||
bind_group: BindGroupId,
|
||||
dynamic_uniform_indices: Option<&[u32]>,
|
||||
);
|
||||
}
|
|
@ -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<BindingDescriptor>,
|
||||
pub id: BindGroupDescriptorId,
|
||||
}
|
||||
|
||||
#[derive(Hash, Copy, Clone, Eq, PartialEq, Debug)]
|
||||
pub struct BindGroupDescriptorId(u64);
|
||||
|
||||
impl BindGroupDescriptor {
|
||||
pub fn new(index: u32, bindings: Vec<BindingDescriptor>) -> 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<H: Hasher>(&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
|
||||
}
|
||||
}
|
|
@ -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: <https://www.khronos.org/opengl/wiki/Sampler_Object#Comparison_mode>.
|
||||
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<u64> {
|
||||
match self {
|
||||
BindType::Uniform { property, .. } => Some(property.get_size()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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::*;
|
|
@ -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<String>,
|
||||
pub layout: Option<PipelineLayout>,
|
||||
pub shader_stages: ShaderStages,
|
||||
pub primitive: PrimitiveState,
|
||||
pub depth_stencil: Option<DepthStencilState>,
|
||||
pub multisample: MultisampleState,
|
||||
|
||||
/// The effect of draw calls on the color aspect of the output target.
|
||||
pub color_target_states: Vec<ColorTargetState>,
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
|
@ -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<String>,
|
||||
pub strip_index_format: Option<IndexFormat>,
|
||||
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<PipelineSpecialization> = 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<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SpecializedShader {
|
||||
shader: Handle<Shader>,
|
||||
specialization: ShaderSpecialization,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SpecializedPipeline {
|
||||
pipeline: Handle<PipelineDescriptor>,
|
||||
specialization: PipelineSpecialization,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PipelineCompiler {
|
||||
specialized_shaders: HashMap<Handle<Shader>, Vec<SpecializedShader>>,
|
||||
specialized_shader_pipelines: HashMap<Handle<Shader>, Vec<Handle<PipelineDescriptor>>>,
|
||||
specialized_pipelines: HashMap<Handle<PipelineDescriptor>, Vec<SpecializedPipeline>>,
|
||||
}
|
||||
|
||||
impl PipelineCompiler {
|
||||
fn compile_shader(
|
||||
&mut self,
|
||||
render_resource_context: &dyn RenderResourceContext,
|
||||
shaders: &mut Assets<Shader>,
|
||||
shader_handle: &Handle<Shader>,
|
||||
shader_specialization: &ShaderSpecialization,
|
||||
) -> Result<Handle<Shader>, 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::<Vec<String>>();
|
||||
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<PipelineDescriptor>,
|
||||
specialization: &PipelineSpecialization,
|
||||
) -> Option<Handle<PipelineDescriptor>> {
|
||||
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<PipelineDescriptor>,
|
||||
shaders: &mut Assets<Shader>,
|
||||
source_pipeline: &Handle<PipelineDescriptor>,
|
||||
pipeline_specialization: &PipelineSpecialization,
|
||||
) -> Handle<PipelineDescriptor> {
|
||||
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::<VertexBufferLayout>::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<PipelineDescriptor>,
|
||||
) -> Option<impl Iterator<Item = &Handle<PipelineDescriptor>>> {
|
||||
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<Item = &Handle<PipelineDescriptor>> {
|
||||
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<Shader>,
|
||||
pipelines: &mut Assets<PipelineDescriptor>,
|
||||
shaders: &mut Assets<Shader>,
|
||||
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::<Vec<String>>();
|
||||
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);
|
||||
}
|
|
@ -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<BindGroupDescriptor>,
|
||||
pub vertex_buffer_descriptors: Vec<VertexBufferLayout>,
|
||||
}
|
||||
|
||||
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::<u32, BindGroupDescriptor>::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::<Vec<BindGroupDescriptor>>();
|
||||
|
||||
// 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<UniformProperty>),
|
||||
Array(Box<UniformProperty>, 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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<PipelineDescriptor>,
|
||||
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<PipelineDescriptor>) -> Self {
|
||||
RenderPipeline {
|
||||
specialization: Default::default(),
|
||||
pipeline,
|
||||
dynamic_bindings_generation: std::usize::MAX,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn specialized(
|
||||
pipeline: Handle<PipelineDescriptor>,
|
||||
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<RenderPipeline>,
|
||||
#[reflect(ignore)]
|
||||
pub bindings: RenderResourceBindings,
|
||||
}
|
||||
|
||||
impl RenderPipelines {
|
||||
pub fn from_pipelines(pipelines: Vec<RenderPipeline>) -> Self {
|
||||
Self {
|
||||
pipelines,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_handles<'a, T: IntoIterator<Item = &'a Handle<PipelineDescriptor>>>(
|
||||
handles: T,
|
||||
) -> Self {
|
||||
RenderPipelines {
|
||||
pipelines: handles
|
||||
.into_iter()
|
||||
.map(|pipeline| RenderPipeline::new(pipeline.clone_weak()))
|
||||
.collect::<Vec<RenderPipeline>>(),
|
||||
..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<RenderResourceBindings>,
|
||||
msaa: Res<Msaa>,
|
||||
meshes: Res<Assets<Mesh>>,
|
||||
mut query: Query<
|
||||
(&mut Draw, &mut RenderPipelines, &Handle<Mesh>, &Visible),
|
||||
Without<OutsideFrustum>,
|
||||
>,
|
||||
) {
|
||||
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::<HashSet<String>>();
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<IndexFormat>,
|
||||
pub front_face: FrontFace,
|
||||
pub cull_mode: Option<Face>,
|
||||
pub polygon_mode: PolygonMode,
|
||||
pub clamp_depth: bool,
|
||||
pub conservative: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ColorTargetState {
|
||||
pub format: TextureFormat,
|
||||
pub blend: Option<BlendState>,
|
||||
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
|
||||
}
|
||||
}
|
|
@ -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<VertexAttribute>,
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
|
@ -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]
|
||||
}
|
||||
}
|
|
@ -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],
|
||||
}
|
||||
|
|
@ -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<Color>,
|
||||
) -> 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::<RenderGraph>().unwrap();
|
||||
let msaa = world.get_resource::<Msaa>().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();
|
||||
}
|
||||
}
|
|
@ -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<Mutex<Vec<Command>>>,
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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<NodeId, NodeState>,
|
||||
node_names: HashMap<Cow<'static, str>, NodeId>,
|
||||
system_node_schedule: Option<Schedule>,
|
||||
}
|
||||
|
||||
#[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),
|
||||
}
|
||||
}
|
||||
sub_graphs: HashMap<Cow<'static, str>, RenderGraph>,
|
||||
input_node: Option<NodeId>,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an [`GraphInputNode`] with the specified slots if not already present.
|
||||
pub fn set_input(&mut self, inputs: Vec<SlotInfo>) -> 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<T>(&mut self, name: impl Into<Cow<'static, str>>, node: T) -> NodeId
|
||||
where
|
||||
T: Node,
|
||||
|
@ -40,18 +101,7 @@ impl RenderGraph {
|
|||
id
|
||||
}
|
||||
|
||||
pub fn add_system_node<T>(&mut self, name: impl Into<Cow<'static, str>>, node: T) -> NodeId
|
||||
where
|
||||
T: SystemNode + 'static,
|
||||
{
|
||||
let schedule = self.system_node_schedule.as_mut().unwrap();
|
||||
let stage = schedule
|
||||
.get_stage_mut::<SystemStage>(&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<NodeLabel>,
|
||||
|
@ -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<NodeLabel>,
|
||||
|
@ -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<NodeLabel>) -> Result<NodeId, RenderGraphError> {
|
||||
let label = label.into();
|
||||
match label {
|
||||
|
@ -86,6 +138,7 @@ impl RenderGraph {
|
|||
}
|
||||
}
|
||||
|
||||
/// Retrieves the [`Node`] referenced by the `label`.
|
||||
pub fn get_node<T>(&self, label: impl Into<NodeLabel>) -> 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<T>(
|
||||
&mut self,
|
||||
label: impl Into<NodeLabel>,
|
||||
|
@ -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<NodeLabel>,
|
||||
|
@ -110,17 +166,21 @@ impl RenderGraph {
|
|||
input_node: impl Into<NodeLabel>,
|
||||
input_slot: impl Into<SlotLabel>,
|
||||
) -> 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<NodeLabel>,
|
||||
|
@ -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<Schedule> {
|
||||
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<Item = &NodeState> {
|
||||
self.nodes.values()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the [`NodeStates`](NodeState), that allows modifying each value.
|
||||
pub fn iter_nodes_mut(&mut self) -> impl Iterator<Item = &mut NodeState> {
|
||||
self.nodes.values_mut()
|
||||
}
|
||||
|
||||
/// Returns an iterator over the sub graphs.
|
||||
pub fn iter_sub_graphs(&self) -> impl Iterator<Item = (&str, &RenderGraph)> {
|
||||
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<Item = (&str, &mut RenderGraph)> {
|
||||
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<NodeLabel>,
|
||||
|
@ -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<NodeLabel>,
|
||||
|
@ -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<Cow<'static, str>>, 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<str>) -> 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<str>) -> 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<SlotInfo>,
|
||||
}
|
||||
|
||||
impl Node for GraphInputNode {
|
||||
fn input(&self) -> Vec<SlotInfo> {
|
||||
self.inputs.clone()
|
||||
}
|
||||
|
||||
fn output(&self) -> Vec<SlotInfo> {
|
||||
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<ResourceSlotInfo>,
|
||||
outputs: Vec<ResourceSlotInfo>,
|
||||
inputs: Vec<SlotInfo>,
|
||||
outputs: Vec<SlotInfo>,
|
||||
}
|
||||
|
||||
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<SlotInfo> {
|
||||
self.inputs.clone()
|
||||
}
|
||||
|
||||
fn output(&self) -> &[ResourceSlotInfo] {
|
||||
&self.outputs
|
||||
fn output(&self) -> Vec<SlotInfo> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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")]
|
||||
|
|
|
@ -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<SlotInfo> {
|
||||
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<SlotInfo> {
|
||||
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<Cow<'static, str>>,
|
||||
/// The name of the type that implements [`Node`].
|
||||
pub type_name: &'static str,
|
||||
pub node: Box<dyn Node>,
|
||||
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<T>(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::<T>(),
|
||||
edges: Edges {
|
||||
|
@ -150,6 +197,7 @@ impl NodeState {
|
|||
}
|
||||
}
|
||||
|
||||
/// Retrieves the [`Node`].
|
||||
pub fn node<T>(&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<T>(&mut self) -> Result<&mut T, RenderGraphError>
|
||||
where
|
||||
T: Node,
|
||||
|
@ -168,14 +217,7 @@ 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)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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)?;
|
||||
|
@ -183,8 +225,19 @@ impl NodeState {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 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<NodeId> 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(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<RenderResourceId>,
|
||||
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),
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
pub struct ResourceSlots {
|
||||
slots: Vec<ResourceSlot>,
|
||||
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<Buffer> for SlotValue {
|
||||
fn from(value: Buffer) -> Self {
|
||||
SlotValue::Buffer(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TextureView> for SlotValue {
|
||||
fn from(value: TextureView) -> Self {
|
||||
SlotValue::TextureView(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Sampler> for SlotValue {
|
||||
fn from(value: Sampler) -> Self {
|
||||
SlotValue::Sampler(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Entity> for SlotValue {
|
||||
fn from(value: Entity) -> Self {
|
||||
SlotValue::Entity(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<Cow<'static, str>> for SlotLabel {
|
||||
fn from(value: Cow<'static, str>) -> Self {
|
||||
SlotLabel::Name(value.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for SlotLabel {
|
||||
fn from(value: usize) -> Self {
|
||||
SlotLabel::Index(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceSlots {
|
||||
pub fn set(&mut self, label: impl Into<SlotLabel>, 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,
|
||||
}
|
||||
|
||||
pub fn get(&self, label: impl Into<SlotLabel>) -> Option<RenderResourceId> {
|
||||
let slot = self.get_slot(label).unwrap();
|
||||
slot.resource.clone()
|
||||
impl SlotInfo {
|
||||
pub fn new(name: impl Into<Cow<'static, str>>, slot_type: SlotType) -> Self {
|
||||
SlotInfo {
|
||||
name: name.into(),
|
||||
slot_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_slot(&self, label: impl Into<SlotLabel>) -> Result<&ResourceSlot, RenderGraphError> {
|
||||
let label = label.into();
|
||||
let index = self.get_slot_index(&label)?;
|
||||
self.slots
|
||||
.get(index)
|
||||
.ok_or(RenderGraphError::InvalidNodeSlot(label))
|
||||
}
|
||||
/// A collection of input or output [`SlotInfos`](SlotInfo) for
|
||||
/// a [`NodeState`](super::NodeState).
|
||||
#[derive(Default, Debug)]
|
||||
pub struct SlotInfos {
|
||||
slots: Vec<SlotInfo>,
|
||||
}
|
||||
|
||||
pub fn get_slot_mut(
|
||||
&mut self,
|
||||
label: impl Into<SlotLabel>,
|
||||
) -> Result<&mut ResourceSlot, RenderGraphError> {
|
||||
let label = label.into();
|
||||
let index = self.get_slot_index(&label)?;
|
||||
self.slots
|
||||
.get_mut(index)
|
||||
.ok_or(RenderGraphError::InvalidNodeSlot(label))
|
||||
}
|
||||
|
||||
pub fn get_slot_index(&self, label: impl Into<SlotLabel>) -> Result<usize, RenderGraphError> {
|
||||
let label = label.into();
|
||||
match label {
|
||||
SlotLabel::Index(index) => Ok(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)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter(&self) -> impl Iterator<Item = &ResourceSlot> {
|
||||
self.slots.iter()
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut ResourceSlot> {
|
||||
self.slots.iter_mut()
|
||||
impl<T: IntoIterator<Item = SlotInfo>> From<T> for SlotInfos {
|
||||
fn from(slots: T) -> Self {
|
||||
SlotInfos {
|
||||
slots: slots.into_iter().collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ResourceSlotInfo> for ResourceSlot {
|
||||
fn from(slot: &ResourceSlotInfo) -> Self {
|
||||
ResourceSlot {
|
||||
resource: None,
|
||||
info: slot.clone(),
|
||||
/// Retrieves the [`SlotInfo`] for the provided label.
|
||||
pub fn get_slot(&self, label: impl Into<SlotLabel>) -> Option<&SlotInfo> {
|
||||
let label = label.into();
|
||||
let index = self.get_slot_index(&label)?;
|
||||
self.slots.get(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[ResourceSlotInfo]> for ResourceSlots {
|
||||
fn from(slots: &[ResourceSlotInfo]) -> Self {
|
||||
ResourceSlots {
|
||||
slots: slots
|
||||
/// Retrieves the [`SlotInfo`] for the provided label mutably.
|
||||
pub fn get_slot_mut(&mut self, label: impl Into<SlotLabel>) -> Option<&mut SlotInfo> {
|
||||
let label = label.into();
|
||||
let index = self.get_slot_index(&label)?;
|
||||
self.slots.get_mut(index)
|
||||
}
|
||||
|
||||
/// Retrieves the index (inside input or output slots) of the slot for the provided label.
|
||||
pub fn get_slot_index(&self, label: impl Into<SlotLabel>) -> Option<usize> {
|
||||
let label = label.into();
|
||||
match label {
|
||||
SlotLabel::Index(index) => Some(index),
|
||||
SlotLabel::Name(ref name) => self
|
||||
.slots
|
||||
.iter()
|
||||
.map(|s| s.into())
|
||||
.collect::<Vec<ResourceSlot>>(),
|
||||
.enumerate()
|
||||
.find(|(_i, s)| s.name == *name)
|
||||
.map(|(i, _s)| i),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ResourceSlotInfo {
|
||||
pub name: Cow<'static, str>,
|
||||
pub resource_type: RenderResourceType,
|
||||
}
|
||||
|
||||
impl ResourceSlotInfo {
|
||||
pub fn new(name: impl Into<Cow<'static, str>>, resource_type: RenderResourceType) -> Self {
|
||||
ResourceSlotInfo {
|
||||
name: name.into(),
|
||||
resource_type,
|
||||
}
|
||||
/// Returns an iterator over the slot infos.
|
||||
pub fn iter(&self) -> impl Iterator<Item = &SlotInfo> {
|
||||
self.slots.iter()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<T>(camera_name: T) -> Self
|
||||
where
|
||||
T: Into<Cow<'static, str>>,
|
||||
{
|
||||
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<BufferId>,
|
||||
}
|
||||
|
||||
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<CameraNodeState>,
|
||||
mut active_cameras: ResMut<ActiveCameras>,
|
||||
render_resource_context: Res<Box<dyn RenderResourceContext>>,
|
||||
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);
|
||||
}
|
|
@ -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::*;
|
|
@ -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<Q: WorldQuery> {
|
||||
descriptor: PassDescriptor,
|
||||
inputs: Vec<ResourceSlotInfo>,
|
||||
cameras: Vec<String>,
|
||||
color_attachment_input_indices: Vec<Option<usize>>,
|
||||
color_resolve_target_indices: Vec<Option<usize>>,
|
||||
depth_stencil_attachment_input_index: Option<usize>,
|
||||
default_clear_color_inputs: Vec<usize>,
|
||||
query_state: Option<QueryState<Q>>,
|
||||
commands: Vec<RenderCommand>,
|
||||
}
|
||||
|
||||
impl<Q: WorldQuery> fmt::Debug for PassNode<Q> {
|
||||
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<Q: WorldQuery> PassNode<Q> {
|
||||
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<Q: WorldQuery + Send + Sync + 'static> Node for PassNode<Q>
|
||||
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<ActiveCameras>| {
|
||||
let mut pipeline_camera_commands = HashMap::default();
|
||||
let pipelines = world.get_resource::<Assets<PipelineDescriptor>>().unwrap();
|
||||
let render_resource_context = &**world
|
||||
.get_resource::<Box<dyn RenderResourceContext>>()
|
||||
.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::<VisibleEntities>(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::<Draw>(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::<ClearColor>() {
|
||||
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::<RenderResourceBindings>().unwrap();
|
||||
let pipelines = world.get_resource::<Assets<PipelineDescriptor>>().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<Handle<PipelineDescriptor>>,
|
||||
bind_groups: Vec<Option<BindGroupId>>,
|
||||
vertex_buffers: Vec<Option<(BufferId, u64)>>,
|
||||
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<PipelineDescriptor>) -> bool {
|
||||
self.pipeline == Some(pipeline)
|
||||
}
|
||||
|
||||
pub fn set_pipeline(
|
||||
&mut self,
|
||||
handle: &Handle<PipelineDescriptor>,
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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<I> {
|
||||
item_size: usize,
|
||||
buffer_capacity: usize,
|
||||
min_capacity: usize,
|
||||
len: usize,
|
||||
buffer: Option<BufferId>,
|
||||
free_indices: Vec<usize>,
|
||||
indices: HashMap<I, usize>,
|
||||
}
|
||||
|
||||
impl<I: Hash + Eq> BufferArray<I> {
|
||||
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<RenderResourceBinding> {
|
||||
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<I, T>
|
||||
where
|
||||
T: renderer::RenderResources,
|
||||
{
|
||||
buffer_arrays: Vec<Option<BufferArray<I>>>,
|
||||
staging_buffer: Option<BufferId>,
|
||||
staging_buffer_size: usize,
|
||||
required_staging_buffer_size: usize,
|
||||
current_staging_buffer_offset: usize,
|
||||
queued_buffer_writes: Vec<QueuedBufferWrite>,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<I, T> Default for UniformBufferArrays<I, T>
|
||||
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<I, T> UniformBufferArrays<I, T>
|
||||
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<T>
|
||||
where
|
||||
T: renderer::RenderResources,
|
||||
{
|
||||
command_queue: CommandQueue,
|
||||
dynamic_uniforms: bool,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> RenderResourcesNode<T>
|
||||
where
|
||||
T: renderer::RenderResources,
|
||||
{
|
||||
pub fn new(dynamic_uniforms: bool) -> Self {
|
||||
RenderResourcesNode {
|
||||
command_queue: CommandQueue::default(),
|
||||
dynamic_uniforms,
|
||||
_marker: PhantomData::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Node for RenderResourcesNode<T>
|
||||
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<T> SystemNode for RenderResourcesNode<T>
|
||||
where
|
||||
T: renderer::RenderResources + Component,
|
||||
{
|
||||
fn get_system(&self) -> BoxedSystem {
|
||||
let system = render_resources_node_system::<T>.config(|config| {
|
||||
config.0 = Some(RenderResourcesNodeState {
|
||||
command_queue: self.command_queue.clone(),
|
||||
uniform_buffer_arrays: UniformBufferArrays::<Entity, T>::default(),
|
||||
dynamic_uniforms: self.dynamic_uniforms,
|
||||
})
|
||||
});
|
||||
|
||||
Box::new(system)
|
||||
}
|
||||
}
|
||||
|
||||
struct RenderResourcesNodeState<I, T: RenderResources> {
|
||||
command_queue: CommandQueue,
|
||||
uniform_buffer_arrays: UniformBufferArrays<I, T>,
|
||||
dynamic_uniforms: bool,
|
||||
}
|
||||
|
||||
impl<I, T: RenderResources> Default for RenderResourcesNodeState<I, T> {
|
||||
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<T: RenderResources + Component>(
|
||||
mut state: Local<RenderResourcesNodeState<Entity, T>>,
|
||||
mut entities_waiting_for_textures: Local<Vec<Entity>>,
|
||||
render_resource_context: Res<Box<dyn RenderResourceContext>>,
|
||||
removed: RemovedComponents<T>,
|
||||
mut queries: QuerySet<(
|
||||
QueryState<
|
||||
(Entity, &T, &Visible, &mut RenderPipelines),
|
||||
Or<(Changed<T>, Changed<Visible>)>,
|
||||
>,
|
||||
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::<T>(
|
||||
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::<T>(
|
||||
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<T>
|
||||
where
|
||||
T: renderer::RenderResources,
|
||||
{
|
||||
command_queue: CommandQueue,
|
||||
dynamic_uniforms: bool,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> AssetRenderResourcesNode<T>
|
||||
where
|
||||
T: renderer::RenderResources,
|
||||
{
|
||||
pub fn new(dynamic_uniforms: bool) -> Self {
|
||||
AssetRenderResourcesNode {
|
||||
dynamic_uniforms,
|
||||
command_queue: Default::default(),
|
||||
_marker: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Node for AssetRenderResourcesNode<T>
|
||||
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<T> SystemNode for AssetRenderResourcesNode<T>
|
||||
where
|
||||
T: renderer::RenderResources + Asset,
|
||||
{
|
||||
fn get_system(&self) -> BoxedSystem {
|
||||
let system = asset_render_resources_node_system::<T>.config(|config| {
|
||||
config.0 = Some(RenderResourcesNodeState {
|
||||
command_queue: self.command_queue.clone(),
|
||||
uniform_buffer_arrays: UniformBufferArrays::<HandleId, T>::default(),
|
||||
dynamic_uniforms: self.dynamic_uniforms,
|
||||
})
|
||||
});
|
||||
|
||||
Box::new(system)
|
||||
}
|
||||
}
|
||||
|
||||
struct AssetRenderNodeState<T: Asset> {
|
||||
assets_waiting_for_textures: Vec<HandleId>,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: Asset> Default for AssetRenderNodeState<T> {
|
||||
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<T: RenderResources + Asset>(
|
||||
mut state: Local<RenderResourcesNodeState<HandleId, T>>,
|
||||
mut asset_state: Local<AssetRenderNodeState<T>>,
|
||||
assets: Res<Assets<T>>,
|
||||
mut asset_events: EventReader<AssetEvent<T>>,
|
||||
mut asset_render_resource_bindings: ResMut<AssetRenderResourceBindings>,
|
||||
render_resource_context: Res<Box<dyn RenderResourceContext>>,
|
||||
removed_handles: RemovedComponents<Handle<T>>,
|
||||
mut queries: QuerySet<(
|
||||
QueryState<(&Handle<T>, &mut RenderPipelines), Changed<Handle<T>>>,
|
||||
QueryState<&mut RenderPipelines, With<Handle<T>>>,
|
||||
)>,
|
||||
) {
|
||||
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::<T>::weak(asset_handle));
|
||||
if !setup_uniform_texture_resources::<T>(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::<T>::weak(*asset_handle));
|
||||
if !setup_uniform_texture_resources::<T>(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::<T>::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::<T>::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::<T>())
|
||||
}
|
||||
}
|
||||
|
||||
// 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::<T>());
|
||||
render_pipelines
|
||||
.bindings
|
||||
.add_asset(asset_handle.clone_weak_untyped(), TypeId::of::<T>());
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_uniform_texture_resources<T>(
|
||||
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
|
||||
}
|
|
@ -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::<SharedBuffers>().unwrap();
|
||||
shared_buffers.apply(render_context);
|
||||
}
|
||||
}
|
|
@ -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<AssetEvent<Texture>>,
|
||||
}
|
||||
|
||||
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::<Events<AssetEvent<Texture>>>().unwrap();
|
||||
let textures = world.get_resource::<Assets<Texture>>().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 { .. } => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<SamplerDescriptor>,
|
||||
pub handle: Option<HandleUntyped>,
|
||||
}
|
||||
|
||||
impl TextureNode {
|
||||
pub const TEXTURE: &'static str = "texture";
|
||||
|
||||
pub fn new(
|
||||
texture_descriptor: TextureDescriptor,
|
||||
sampler_descriptor: Option<SamplerDescriptor>,
|
||||
handle: Option<HandleUntyped>,
|
||||
) -> 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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<WindowCreated>,
|
||||
window_resized_event_reader: ManualEventReader<WindowResized>,
|
||||
}
|
||||
|
||||
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::<Events<WindowCreated>>().unwrap();
|
||||
let window_resized_events = world.get_resource::<Events<WindowResized>>().unwrap();
|
||||
let windows = world.get_resource::<Windows>().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),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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<WindowCreated>,
|
||||
window_resized_event_reader: ManualEventReader<WindowResized>,
|
||||
}
|
||||
|
||||
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::<Events<WindowCreated>>().unwrap();
|
||||
let window_resized_events = world.get_resource::<Events<WindowResized>>().unwrap();
|
||||
let windows = world.get_resource::<Windows>().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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<OrderedJob>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Eq, PartialEq)]
|
||||
pub struct OrderedJob {
|
||||
pub nodes: Vec<NodeId>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct StageBorrow<'a> {
|
||||
pub jobs: Vec<OrderedJobBorrow<'a>>,
|
||||
}
|
||||
|
||||
#[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<Stage>,
|
||||
/// a collection of node indices that are used to efficiently borrow render graph nodes
|
||||
node_indices: HashMap<NodeId, NodeIndices>,
|
||||
}
|
||||
|
||||
impl Stages {
|
||||
pub fn new(stages: Vec<Stage>) -> 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<StageBorrow<'a>> {
|
||||
// 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<Stages, RenderGraphError>;
|
||||
}
|
||||
|
||||
// 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<Stages, RenderGraphError> {
|
||||
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<Stages, RenderGraphError> {
|
||||
// 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<Stage>,
|
||||
node_stages_and_jobs: &mut HashMap<NodeId, (usize, usize)>,
|
||||
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<ResourceSlotInfo>,
|
||||
outputs: Vec<ResourceSlotInfo>,
|
||||
}
|
||||
|
||||
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]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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::<RenderGraph>().unwrap();
|
||||
render_graph.take_schedule()
|
||||
};
|
||||
|
||||
if let Some(schedule) = system_schedule.as_mut() {
|
||||
schedule.run(world);
|
||||
}
|
||||
let mut render_graph = world.get_resource_mut::<RenderGraph>().unwrap();
|
||||
if let Some(schedule) = system_schedule.take() {
|
||||
render_graph.set_schedule(schedule);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue