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;
|
sed -i "s|PATH_TO_CHANGE|$curr|" vk_swiftshader_icd.json;
|
||||||
- name: Build bevy
|
- name: Build bevy
|
||||||
run: |
|
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
|
- name: Run examples
|
||||||
run: |
|
run: |
|
||||||
for example in .github/example-run/*.ron; do
|
for example in .github/example-run/*.ron; do
|
||||||
example_name=`basename $example .ron`
|
example_name=`basename $example .ron`
|
||||||
echo "running $example_name - "`date`
|
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
|
sleep 10
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
142
Cargo.toml
142
Cargo.toml
|
@ -13,20 +13,12 @@ repository = "https://github.com/bevyengine/bevy"
|
||||||
|
|
||||||
[workspace]
|
[workspace]
|
||||||
exclude = ["benches", "crates/bevy_ecs_compile_fail_tests"]
|
exclude = ["benches", "crates/bevy_ecs_compile_fail_tests"]
|
||||||
members = ["crates/*", "pipelined/*", "examples/ios", "tools/ci", "errors"]
|
members = ["crates/*", "examples/ios", "tools/ci", "errors"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [
|
default = [
|
||||||
"bevy_audio",
|
"bevy_audio",
|
||||||
"bevy_core_pipeline",
|
|
||||||
"bevy_gilrs",
|
"bevy_gilrs",
|
||||||
"bevy_gltf2",
|
|
||||||
"bevy_wgpu",
|
|
||||||
"bevy_sprite2",
|
|
||||||
"bevy_render2",
|
|
||||||
"bevy_pbr2",
|
|
||||||
"bevy_ui2",
|
|
||||||
"bevy_text2",
|
|
||||||
"bevy_winit",
|
"bevy_winit",
|
||||||
"render",
|
"render",
|
||||||
"png",
|
"png",
|
||||||
|
@ -39,9 +31,11 @@ default = [
|
||||||
# Force dynamic linking, which improves iterative compile times
|
# Force dynamic linking, which improves iterative compile times
|
||||||
dynamic = ["bevy_dylib"]
|
dynamic = ["bevy_dylib"]
|
||||||
|
|
||||||
# Rendering support (Also needs the bevy_wgpu feature or a third-party rendering backend)
|
# Rendering support
|
||||||
render = [
|
render = [
|
||||||
|
"bevy_internal/bevy_core_pipeline",
|
||||||
"bevy_internal/bevy_pbr",
|
"bevy_internal/bevy_pbr",
|
||||||
|
"bevy_internal/bevy_gltf",
|
||||||
"bevy_internal/bevy_render",
|
"bevy_internal/bevy_render",
|
||||||
"bevy_internal/bevy_sprite",
|
"bevy_internal/bevy_sprite",
|
||||||
"bevy_internal/bevy_text",
|
"bevy_internal/bevy_text",
|
||||||
|
@ -50,20 +44,16 @@ render = [
|
||||||
|
|
||||||
# Optional bevy crates
|
# Optional bevy crates
|
||||||
bevy_audio = ["bevy_internal/bevy_audio"]
|
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_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"]
|
||||||
bevy_gilrs = ["bevy_internal/bevy_gilrs"]
|
bevy_gilrs = ["bevy_internal/bevy_gilrs"]
|
||||||
bevy_gltf = ["bevy_internal/bevy_gltf"]
|
bevy_gltf = ["bevy_internal/bevy_gltf"]
|
||||||
bevy_wgpu = ["bevy_internal/bevy_wgpu"]
|
|
||||||
bevy_winit = ["bevy_internal/bevy_winit"]
|
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_chrome = ["bevy_internal/trace_chrome"]
|
||||||
trace_tracy = ["bevy_internal/trace_tracy"]
|
trace_tracy = ["bevy_internal/trace_tracy"]
|
||||||
trace = ["bevy_internal/trace"]
|
trace = ["bevy_internal/trace"]
|
||||||
|
@ -120,10 +110,6 @@ path = "examples/hello_world.rs"
|
||||||
name = "contributors"
|
name = "contributors"
|
||||||
path = "examples/2d/contributors.rs"
|
path = "examples/2d/contributors.rs"
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "mesh"
|
|
||||||
path = "examples/2d/mesh.rs"
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "many_sprites"
|
name = "many_sprites"
|
||||||
path = "examples/2d/many_sprites.rs"
|
path = "examples/2d/many_sprites.rs"
|
||||||
|
@ -144,60 +130,35 @@ path = "examples/2d/sprite_sheet.rs"
|
||||||
name = "text2d"
|
name = "text2d"
|
||||||
path = "examples/2d/text2d.rs"
|
path = "examples/2d/text2d.rs"
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "text2d_pipelined"
|
|
||||||
path = "examples/2d/text2d_pipelined.rs"
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "texture_atlas"
|
name = "texture_atlas"
|
||||||
path = "examples/2d/texture_atlas.rs"
|
path = "examples/2d/texture_atlas.rs"
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "pipelined_texture_atlas"
|
|
||||||
path = "examples/2d/pipelined_texture_atlas.rs"
|
|
||||||
|
|
||||||
# 3D Rendering
|
# 3D Rendering
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "3d_scene"
|
name = "3d_scene"
|
||||||
path = "examples/3d/3d_scene.rs"
|
path = "examples/3d/3d_scene.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "3d_scene_pipelined"
|
name = "lighting"
|
||||||
path = "examples/3d/3d_scene_pipelined.rs"
|
path = "examples/3d/lighting.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"
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "load_gltf"
|
name = "load_gltf"
|
||||||
path = "examples/3d/load_gltf.rs"
|
path = "examples/3d/load_gltf.rs"
|
||||||
required-features = ["bevy_gltf"]
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "load_gltf_pipelined"
|
name = "many_cubes"
|
||||||
path = "examples/3d/load_gltf_pipelined.rs"
|
path = "examples/3d/many_cubes.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "msaa"
|
name = "msaa"
|
||||||
path = "examples/3d/msaa.rs"
|
path = "examples/3d/msaa.rs"
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "msaa_pipelined"
|
|
||||||
path = "examples/3d/msaa_pipelined.rs"
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "orthographic"
|
name = "orthographic"
|
||||||
path = "examples/3d/orthographic.rs"
|
path = "examples/3d/orthographic.rs"
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "orthographic_pipelined"
|
|
||||||
path = "examples/3d/orthographic_pipelined.rs"
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "parenting"
|
name = "parenting"
|
||||||
path = "examples/3d/parenting.rs"
|
path = "examples/3d/parenting.rs"
|
||||||
|
@ -207,46 +168,25 @@ name = "pbr"
|
||||||
path = "examples/3d/pbr.rs"
|
path = "examples/3d/pbr.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "pbr_pipelined"
|
name = "shadow_biases"
|
||||||
path = "examples/3d/pbr_pipelined.rs"
|
path = "examples/3d/shadow_biases.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "render_to_texture"
|
name = "shadow_caster_receiver"
|
||||||
path = "examples/3d/render_to_texture.rs"
|
path = "examples/3d/shadow_caster_receiver.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"
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "texture"
|
name = "texture"
|
||||||
path = "examples/3d/texture.rs"
|
path = "examples/3d/texture.rs"
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "texture_pipelined"
|
|
||||||
path = "examples/3d/texture_pipelined.rs"
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "update_gltf_scene"
|
name = "update_gltf_scene"
|
||||||
path = "examples/3d/update_gltf_scene.rs"
|
path = "examples/3d/update_gltf_scene.rs"
|
||||||
required-features = ["bevy_gltf"]
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "wireframe"
|
name = "wireframe"
|
||||||
path = "examples/3d/wireframe.rs"
|
path = "examples/3d/wireframe.rs"
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "z_sort_debug"
|
|
||||||
path = "examples/3d/z_sort_debug.rs"
|
|
||||||
|
|
||||||
# Application
|
# Application
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "custom_loop"
|
name = "custom_loop"
|
||||||
|
@ -292,7 +232,6 @@ path = "examples/app/thread_pool_resources.rs"
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "asset_loading"
|
name = "asset_loading"
|
||||||
path = "examples/asset/asset_loading.rs"
|
path = "examples/asset/asset_loading.rs"
|
||||||
required-features = ["bevy_gltf"]
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "custom_asset"
|
name = "custom_asset"
|
||||||
|
@ -305,7 +244,6 @@ path = "examples/asset/custom_asset_io.rs"
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "hot_asset_reloading"
|
name = "hot_asset_reloading"
|
||||||
path = "examples/asset/hot_asset_reloading.rs"
|
path = "examples/asset/hot_asset_reloading.rs"
|
||||||
required-features = ["bevy_gltf"]
|
|
||||||
|
|
||||||
# Async Tasks
|
# Async Tasks
|
||||||
[[example]]
|
[[example]]
|
||||||
|
@ -387,7 +325,6 @@ path = "examples/ecs/timers.rs"
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "alien_cake_addict"
|
name = "alien_cake_addict"
|
||||||
path = "examples/game/alien_cake_addict.rs"
|
path = "examples/game/alien_cake_addict.rs"
|
||||||
required-features = ["bevy_gltf"]
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "breakout"
|
name = "breakout"
|
||||||
|
@ -457,47 +394,19 @@ name = "scene"
|
||||||
path = "examples/scene/scene.rs"
|
path = "examples/scene/scene.rs"
|
||||||
|
|
||||||
# Shaders
|
# 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]]
|
[[example]]
|
||||||
name = "shader_defs"
|
name = "shader_defs"
|
||||||
path = "examples/shader/shader_defs.rs"
|
path = "examples/shader/shader_defs.rs"
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "custom_shader_pipelined"
|
name = "shader_material"
|
||||||
path = "examples/shader/custom_shader_pipelined.rs"
|
path = "examples/shader/shader_material.rs"
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "shader_defs_pipelined"
|
|
||||||
path = "examples/shader/shader_defs_pipelined.rs"
|
|
||||||
|
|
||||||
# Tools
|
# Tools
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "bevymark"
|
name = "bevymark"
|
||||||
path = "examples/tools/bevymark.rs"
|
path = "examples/tools/bevymark.rs"
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "bevymark_pipelined"
|
|
||||||
path = "examples/tools/bevymark_pipelined.rs"
|
|
||||||
|
|
||||||
# UI (User Interface)
|
# UI (User Interface)
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "button"
|
name = "button"
|
||||||
|
@ -519,24 +428,11 @@ path = "examples/ui/text_debug.rs"
|
||||||
name = "ui"
|
name = "ui"
|
||||||
path = "examples/ui/ui.rs"
|
path = "examples/ui/ui.rs"
|
||||||
|
|
||||||
[[example]]
|
|
||||||
name = "ui_pipelined"
|
|
||||||
path = "examples/ui/ui_pipelined.rs"
|
|
||||||
|
|
||||||
# Window
|
# Window
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "clear_color"
|
name = "clear_color"
|
||||||
path = "examples/window/clear_color.rs"
|
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]]
|
[[example]]
|
||||||
name = "scale_factor_override"
|
name = "scale_factor_override"
|
||||||
path = "examples/window/scale_factor_override.rs"
|
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")]
|
#[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();
|
let mut watcher = self.filesystem_watcher.write();
|
||||||
if let Some(ref mut watcher) = *watcher {
|
if let Some(ref mut watcher) = *watcher {
|
||||||
watcher
|
watcher
|
||||||
|
|
|
@ -14,9 +14,9 @@ keywords = ["bevy"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_app = { path = "../../crates/bevy_app", version = "0.5.0" }
|
bevy_app = { path = "../bevy_app", version = "0.5.0" }
|
||||||
bevy_asset = { path = "../../crates/bevy_asset", version = "0.5.0" }
|
bevy_asset = { path = "../bevy_asset", version = "0.5.0" }
|
||||||
bevy_core = { path = "../../crates/bevy_core", version = "0.5.0" }
|
bevy_core = { path = "../bevy_core", version = "0.5.0" }
|
||||||
bevy_ecs = { path = "../../crates/bevy_ecs", version = "0.5.0" }
|
bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" }
|
||||||
bevy_render2 = { path = "../bevy_render2", 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 crate::ClearColor;
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_render2::{
|
use bevy_render::{
|
||||||
camera::ExtractedCamera,
|
camera::ExtractedCamera,
|
||||||
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo},
|
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo},
|
||||||
render_resource::{
|
render_resource::{
|
|
@ -1,5 +1,5 @@
|
||||||
use bevy_ecs::world::World;
|
use bevy_ecs::world::World;
|
||||||
use bevy_render2::{
|
use bevy_render::{
|
||||||
render_graph::{Node, NodeRunError, RenderGraphContext},
|
render_graph::{Node, NodeRunError, RenderGraphContext},
|
||||||
renderer::RenderContext,
|
renderer::RenderContext,
|
||||||
};
|
};
|
|
@ -4,6 +4,11 @@ mod main_pass_2d;
|
||||||
mod main_pass_3d;
|
mod main_pass_3d;
|
||||||
mod main_pass_driver;
|
mod main_pass_driver;
|
||||||
|
|
||||||
|
pub mod prelude {
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub use crate::ClearColor;
|
||||||
|
}
|
||||||
|
|
||||||
pub use clear_pass::*;
|
pub use clear_pass::*;
|
||||||
pub use clear_pass_driver::*;
|
pub use clear_pass_driver::*;
|
||||||
pub use main_pass_2d::*;
|
pub use main_pass_2d::*;
|
||||||
|
@ -13,7 +18,7 @@ pub use main_pass_driver::*;
|
||||||
use bevy_app::{App, Plugin};
|
use bevy_app::{App, Plugin};
|
||||||
use bevy_core::FloatOrd;
|
use bevy_core::FloatOrd;
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_render2::{
|
use bevy_render::{
|
||||||
camera::{ActiveCameras, CameraPlugin},
|
camera::{ActiveCameras, CameraPlugin},
|
||||||
color::Color,
|
color::Color,
|
||||||
render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType},
|
render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType},
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::Transparent2d;
|
use crate::Transparent2d;
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_render2::{
|
use bevy_render::{
|
||||||
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
|
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
|
||||||
render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass},
|
render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass},
|
||||||
render_resource::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor},
|
render_resource::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor},
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{AlphaMask3d, Opaque3d, Transparent3d};
|
use crate::{AlphaMask3d, Opaque3d, Transparent3d};
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_render2::{
|
use bevy_render::{
|
||||||
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
|
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
|
||||||
render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass},
|
render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass},
|
||||||
render_resource::{LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor},
|
render_resource::{LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor},
|
|
@ -1,5 +1,5 @@
|
||||||
use bevy_ecs::world::World;
|
use bevy_ecs::world::World;
|
||||||
use bevy_render2::{
|
use bevy_render::{
|
||||||
camera::{CameraPlugin, ExtractedCameraNames},
|
camera::{CameraPlugin, ExtractedCameraNames},
|
||||||
render_graph::{Node, NodeRunError, RenderGraphContext, SlotValue},
|
render_graph::{Node, NodeRunError, RenderGraphContext, SlotValue},
|
||||||
renderer::RenderContext,
|
renderer::RenderContext,
|
|
@ -76,7 +76,7 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
|
||||||
meta_item.path(),
|
meta_item.path(),
|
||||||
format!(
|
format!(
|
||||||
"unknown component attribute `{}`",
|
"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_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] }
|
||||||
bevy_render = { path = "../bevy_render", version = "0.5.0" }
|
bevy_render = { path = "../bevy_render", version = "0.5.0" }
|
||||||
bevy_transform = { path = "../bevy_transform", 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_math = { path = "../bevy_math", version = "0.5.0" }
|
||||||
bevy_scene = { path = "../bevy_scene", version = "0.5.0" }
|
bevy_scene = { path = "../bevy_scene", version = "0.5.0" }
|
||||||
bevy_log = { path = "../bevy_log", version = "0.5.0" }
|
bevy_log = { path = "../bevy_log", version = "0.5.0" }
|
||||||
|
@ -28,3 +29,4 @@ thiserror = "1.0"
|
||||||
anyhow = "1.0.4"
|
anyhow = "1.0.4"
|
||||||
base64 = "0.13.0"
|
base64 = "0.13.0"
|
||||||
percent-encoding = "2.1"
|
percent-encoding = "2.1"
|
||||||
|
wgpu = "0.11.0"
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
use std::collections::HashMap;
|
use bevy_utils::HashMap;
|
||||||
|
|
||||||
mod loader;
|
mod loader;
|
||||||
pub use loader::*;
|
pub use loader::*;
|
||||||
|
|
||||||
use bevy_app::prelude::*;
|
use bevy_app::prelude::*;
|
||||||
use bevy_asset::{AddAsset, Handle};
|
use bevy_asset::{AddAsset, Handle};
|
||||||
use bevy_pbr::prelude::StandardMaterial;
|
use bevy_pbr::StandardMaterial;
|
||||||
use bevy_reflect::TypeUuid;
|
use bevy_reflect::TypeUuid;
|
||||||
use bevy_render::mesh::Mesh;
|
use bevy_render::mesh::Mesh;
|
||||||
use bevy_scene::Scene;
|
use bevy_scene::Scene;
|
||||||
|
|
||||||
/// Adds support for GLTF file loading to Apps
|
/// Adds support for glTF file loading to the app.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct GltfPlugin;
|
pub struct GltfPlugin;
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ impl Plugin for GltfPlugin {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Representation of a loaded glTF file.
|
||||||
#[derive(Debug, TypeUuid)]
|
#[derive(Debug, TypeUuid)]
|
||||||
#[uuid = "5c7d5f8a-f7b0-4e45-a09e-406c0372fea2"]
|
#[uuid = "5c7d5f8a-f7b0-4e45-a09e-406c0372fea2"]
|
||||||
pub struct Gltf {
|
pub struct Gltf {
|
||||||
|
@ -38,6 +39,8 @@ pub struct Gltf {
|
||||||
pub default_scene: Option<Handle<Scene>>,
|
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)]
|
#[derive(Debug, Clone, TypeUuid)]
|
||||||
#[uuid = "dad74750-1fd6-460f-ac51-0a7937563865"]
|
#[uuid = "dad74750-1fd6-460f-ac51-0a7937563865"]
|
||||||
pub struct GltfNode {
|
pub struct GltfNode {
|
||||||
|
@ -46,12 +49,14 @@ pub struct GltfNode {
|
||||||
pub transform: bevy_transform::prelude::Transform,
|
pub transform: bevy_transform::prelude::Transform,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A glTF mesh, which may consists of multiple [`GtlfPrimitives`](GltfPrimitive).
|
||||||
#[derive(Debug, Clone, TypeUuid)]
|
#[derive(Debug, Clone, TypeUuid)]
|
||||||
#[uuid = "8ceaec9a-926a-4f29-8ee3-578a69f42315"]
|
#[uuid = "8ceaec9a-926a-4f29-8ee3-578a69f42315"]
|
||||||
pub struct GltfMesh {
|
pub struct GltfMesh {
|
||||||
pub primitives: Vec<GltfPrimitive>,
|
pub primitives: Vec<GltfPrimitive>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Part of a [`GltfMesh`] that consists of a [`Mesh`] and an optional [`StandardMaterial`].
|
||||||
#[derive(Debug, Clone, TypeUuid)]
|
#[derive(Debug, Clone, TypeUuid)]
|
||||||
#[uuid = "cbfca302-82fd-41cb-af77-cab6b3d50af1"]
|
#[uuid = "cbfca302-82fd-41cb-af77-cab6b3d50af1"]
|
||||||
pub struct GltfPrimitive {
|
pub struct GltfPrimitive {
|
||||||
|
|
|
@ -5,42 +5,41 @@ use bevy_asset::{
|
||||||
use bevy_core::Name;
|
use bevy_core::Name;
|
||||||
use bevy_ecs::world::World;
|
use bevy_ecs::world::World;
|
||||||
use bevy_log::warn;
|
use bevy_log::warn;
|
||||||
use bevy_math::Mat4;
|
use bevy_math::{Mat4, Vec3};
|
||||||
use bevy_pbr::prelude::{PbrBundle, StandardMaterial};
|
use bevy_pbr::{AlphaMode, PbrBundle, StandardMaterial};
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
camera::{
|
camera::{
|
||||||
Camera, CameraProjection, OrthographicProjection, PerspectiveProjection, VisibleEntities,
|
Camera, CameraPlugin, CameraProjection, OrthographicProjection, PerspectiveProjection,
|
||||||
},
|
},
|
||||||
|
color::Color,
|
||||||
mesh::{Indices, Mesh, VertexAttributeValues},
|
mesh::{Indices, Mesh, VertexAttributeValues},
|
||||||
pipeline::PrimitiveTopology,
|
primitives::{Aabb, Frustum},
|
||||||
prelude::{Color, Texture},
|
texture::{Image, ImageType, TextureError},
|
||||||
render_graph::base,
|
view::VisibleEntities,
|
||||||
texture::{AddressMode, FilterMode, ImageType, SamplerDescriptor, TextureError, TextureFormat},
|
|
||||||
};
|
};
|
||||||
use bevy_scene::Scene;
|
use bevy_scene::Scene;
|
||||||
use bevy_transform::{
|
use bevy_transform::{
|
||||||
hierarchy::{BuildWorldChildren, WorldChildBuilder},
|
hierarchy::{BuildWorldChildren, WorldChildBuilder},
|
||||||
prelude::{GlobalTransform, Transform},
|
prelude::{GlobalTransform, Transform},
|
||||||
};
|
};
|
||||||
|
use bevy_utils::{HashMap, HashSet};
|
||||||
use gltf::{
|
use gltf::{
|
||||||
mesh::Mode,
|
mesh::Mode,
|
||||||
texture::{MagFilter, MinFilter, WrappingMode},
|
texture::{MagFilter, MinFilter, WrappingMode},
|
||||||
Material, Primitive,
|
Material, Primitive,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{collections::VecDeque, path::Path};
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
path::Path,
|
|
||||||
};
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
use wgpu::{AddressMode, FilterMode, PrimitiveTopology, SamplerDescriptor, TextureFormat};
|
||||||
|
|
||||||
use crate::{Gltf, GltfNode};
|
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)]
|
#[derive(Error, Debug)]
|
||||||
pub enum GltfError {
|
pub enum GltfError {
|
||||||
#[error("unsupported primitive mode")]
|
#[error("unsupported primitive mode")]
|
||||||
UnsupportedPrimitive { mode: Mode },
|
UnsupportedPrimitive { mode: Mode },
|
||||||
#[error("invalid GLTF file: {0}")]
|
#[error("invalid glTF file: {0}")]
|
||||||
Gltf(#[from] gltf::Error),
|
Gltf(#[from] gltf::Error),
|
||||||
#[error("binary blob is missing")]
|
#[error("binary blob is missing")]
|
||||||
MissingBlob,
|
MissingBlob,
|
||||||
|
@ -56,7 +55,7 @@ pub enum GltfError {
|
||||||
AssetIoError(#[from] AssetIoError),
|
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)]
|
#[derive(Default)]
|
||||||
pub struct GltfLoader;
|
pub struct GltfLoader;
|
||||||
|
|
||||||
|
@ -74,6 +73,7 @@ impl AssetLoader for GltfLoader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads an entire glTF file.
|
||||||
async fn load_gltf<'a, 'b>(
|
async fn load_gltf<'a, 'b>(
|
||||||
bytes: &'a [u8],
|
bytes: &'a [u8],
|
||||||
load_context: &'a mut LoadContext<'b>,
|
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 buffer_data = load_buffers(&gltf, load_context, load_context.path()).await?;
|
||||||
|
|
||||||
let mut materials = vec![];
|
let mut materials = vec![];
|
||||||
let mut named_materials = HashMap::new();
|
let mut named_materials = HashMap::default();
|
||||||
let mut linear_textures = HashSet::new();
|
let mut linear_textures = HashSet::default();
|
||||||
for material in gltf.materials() {
|
for material in gltf.materials() {
|
||||||
let handle = load_material(&material, load_context);
|
let handle = load_material(&material, load_context);
|
||||||
if let Some(name) = material.name() {
|
if let Some(name) = material.name() {
|
||||||
|
@ -105,7 +105,7 @@ async fn load_gltf<'a, 'b>(
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut meshes = vec![];
|
let mut meshes = vec![];
|
||||||
let mut named_meshes = HashMap::new();
|
let mut named_meshes = HashMap::default();
|
||||||
for mesh in gltf.meshes() {
|
for mesh in gltf.meshes() {
|
||||||
let mut primitives = vec![];
|
let mut primitives = vec![];
|
||||||
for primitive in mesh.primitives() {
|
for primitive in mesh.primitives() {
|
||||||
|
@ -148,12 +148,12 @@ async fn load_gltf<'a, 'b>(
|
||||||
mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
|
mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(vertex_attribute) = reader
|
// if let Some(vertex_attribute) = reader
|
||||||
.read_colors(0)
|
// .read_colors(0)
|
||||||
.map(|v| VertexAttributeValues::Float32x4(v.into_rgba_f32().collect()))
|
// .map(|v| VertexAttributeValues::Float32x4(v.into_rgba_f32().collect()))
|
||||||
{
|
// {
|
||||||
mesh.set_attribute(Mesh::ATTRIBUTE_COLOR, vertex_attribute);
|
// mesh.set_attribute(Mesh::ATTRIBUTE_COLOR, vertex_attribute);
|
||||||
}
|
// }
|
||||||
|
|
||||||
if let Some(indices) = reader.read_indices() {
|
if let Some(indices) = reader.read_indices() {
|
||||||
mesh.set_indices(Some(Indices::U32(indices.into_u32().collect())));
|
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 nodes_intermediate = vec![];
|
||||||
let mut named_nodes_intermediate = HashMap::new();
|
let mut named_nodes_intermediate = HashMap::default();
|
||||||
for node in gltf.nodes() {
|
for node in gltf.nodes() {
|
||||||
let node_label = node_label(&node);
|
let node_label = node_label(&node);
|
||||||
nodes_intermediate.push((
|
nodes_intermediate.push((
|
||||||
|
@ -228,7 +228,7 @@ async fn load_gltf<'a, 'b>(
|
||||||
named_nodes_intermediate.insert(name, node.index());
|
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()
|
.into_iter()
|
||||||
.map(|(label, node)| load_context.set_labeled_asset(&label, LoadedAsset::new(node)))
|
.map(|(label, node)| load_context.set_labeled_asset(&label, LoadedAsset::new(node)))
|
||||||
.collect::<Vec<bevy_asset::Handle<GltfNode>>>();
|
.collect::<Vec<bevy_asset::Handle<GltfNode>>>();
|
||||||
|
@ -265,7 +265,7 @@ async fn load_gltf<'a, 'b>(
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter_map(|res| {
|
.filter_map(|res| {
|
||||||
if let Err(err) = res.as_ref() {
|
if let Err(err) = res.as_ref() {
|
||||||
warn!("Error loading GLTF texture: {}", err);
|
warn!("Error loading glTF texture: {}", err);
|
||||||
}
|
}
|
||||||
res.ok()
|
res.ok()
|
||||||
})
|
})
|
||||||
|
@ -274,7 +274,7 @@ async fn load_gltf<'a, 'b>(
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut scenes = vec![];
|
let mut scenes = vec![];
|
||||||
let mut named_scenes = HashMap::new();
|
let mut named_scenes = HashMap::default();
|
||||||
for scene in gltf.scenes() {
|
for scene in gltf.scenes() {
|
||||||
let mut err = None;
|
let mut err = None;
|
||||||
let mut world = World::default();
|
let mut world = World::default();
|
||||||
|
@ -320,18 +320,19 @@ async fn load_gltf<'a, 'b>(
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads a glTF texture as a bevy [`Image`] and returns it together with its label.
|
||||||
async fn load_texture<'a>(
|
async fn load_texture<'a>(
|
||||||
gltf_texture: gltf::Texture<'a>,
|
gltf_texture: gltf::Texture<'a>,
|
||||||
buffer_data: &[Vec<u8>],
|
buffer_data: &[Vec<u8>],
|
||||||
linear_textures: &HashSet<usize>,
|
linear_textures: &HashSet<usize>,
|
||||||
load_context: &LoadContext<'a>,
|
load_context: &LoadContext<'a>,
|
||||||
) -> Result<(Texture, String), GltfError> {
|
) -> Result<(Image, String), GltfError> {
|
||||||
let mut texture = match gltf_texture.source().source() {
|
let mut texture = match gltf_texture.source().source() {
|
||||||
gltf::image::Source::View { view, mime_type } => {
|
gltf::image::Source::View { view, mime_type } => {
|
||||||
let start = view.offset() as usize;
|
let start = view.offset() as usize;
|
||||||
let end = (view.offset() + view.length()) as usize;
|
let end = (view.offset() + view.length()) as usize;
|
||||||
let buffer = &buffer_data[view.buffer().index()][start..end];
|
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 } => {
|
gltf::image::Source::Uri { uri, mime_type } => {
|
||||||
let uri = percent_encoding::percent_decode_str(uri)
|
let uri = percent_encoding::percent_decode_str(uri)
|
||||||
|
@ -352,20 +353,21 @@ async fn load_texture<'a>(
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Texture::from_buffer(
|
Image::from_buffer(
|
||||||
&bytes,
|
&bytes,
|
||||||
mime_type.map(ImageType::MimeType).unwrap_or(image_type),
|
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()) {
|
if (linear_textures).contains(&gltf_texture.index()) {
|
||||||
texture.format = TextureFormat::Rgba8Unorm;
|
texture.texture_descriptor.format = TextureFormat::Rgba8Unorm;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((texture, texture_label(&gltf_texture)))
|
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> {
|
fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle<StandardMaterial> {
|
||||||
let material_label = material_label(material);
|
let material_label = material_label(material);
|
||||||
|
|
||||||
|
@ -381,15 +383,16 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle<
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
|
|
||||||
let normal_map = if let Some(normal_texture) = material.normal_texture() {
|
let normal_map_texture: Option<Handle<Image>> =
|
||||||
// TODO: handle normal_texture.scale
|
if let Some(normal_texture) = material.normal_texture() {
|
||||||
// TODO: handle normal_texture.tex_coord() (the *set* index for the right texcoords)
|
// TODO: handle normal_texture.scale
|
||||||
let label = texture_label(&normal_texture.texture());
|
// TODO: handle normal_texture.tex_coord() (the *set* index for the right texcoords)
|
||||||
let path = AssetPath::new_ref(load_context.path(), Some(&label));
|
let label = texture_label(&normal_texture.texture());
|
||||||
Some(load_context.get_handle(path))
|
let path = AssetPath::new_ref(load_context.path(), Some(&label));
|
||||||
} else {
|
Some(load_context.get_handle(path))
|
||||||
None
|
} else {
|
||||||
};
|
None
|
||||||
|
};
|
||||||
|
|
||||||
let metallic_roughness_texture = if let Some(info) = pbr.metallic_roughness_texture() {
|
let metallic_roughness_texture = if let Some(info) = pbr.metallic_roughness_texture() {
|
||||||
// TODO: handle info.tex_coord() (the *set* index for the right texcoords)
|
// TODO: handle info.tex_coord() (the *set* index for the right texcoords)
|
||||||
|
@ -426,20 +429,22 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle<
|
||||||
LoadedAsset::new(StandardMaterial {
|
LoadedAsset::new(StandardMaterial {
|
||||||
base_color: Color::rgba(color[0], color[1], color[2], color[3]),
|
base_color: Color::rgba(color[0], color[1], color[2], color[3]),
|
||||||
base_color_texture,
|
base_color_texture,
|
||||||
roughness: pbr.roughness_factor(),
|
perceptual_roughness: pbr.roughness_factor(),
|
||||||
metallic: pbr.metallic_factor(),
|
metallic: pbr.metallic_factor(),
|
||||||
metallic_roughness_texture,
|
metallic_roughness_texture,
|
||||||
normal_map,
|
normal_map_texture,
|
||||||
double_sided: material.double_sided(),
|
double_sided: material.double_sided(),
|
||||||
occlusion_texture,
|
occlusion_texture,
|
||||||
emissive: Color::rgba(emissive[0], emissive[1], emissive[2], 1.0),
|
emissive: Color::rgba(emissive[0], emissive[1], emissive[2], 1.0),
|
||||||
emissive_texture,
|
emissive_texture,
|
||||||
unlit: material.unlit(),
|
unlit: material.unlit(),
|
||||||
|
alpha_mode: alpha_mode(material),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads a glTF node.
|
||||||
fn load_node(
|
fn load_node(
|
||||||
gltf_node: &gltf::Node,
|
gltf_node: &gltf::Node,
|
||||||
world_builder: &mut WorldChildBuilder,
|
world_builder: &mut WorldChildBuilder,
|
||||||
|
@ -459,9 +464,12 @@ fn load_node(
|
||||||
|
|
||||||
// create camera node
|
// create camera node
|
||||||
if let Some(camera) = gltf_node.camera() {
|
if let Some(camera) = gltf_node.camera() {
|
||||||
node.insert(VisibleEntities {
|
node.insert_bundle((
|
||||||
..Default::default()
|
VisibleEntities {
|
||||||
});
|
..Default::default()
|
||||||
|
},
|
||||||
|
Frustum::default(),
|
||||||
|
));
|
||||||
|
|
||||||
match camera.projection() {
|
match camera.projection() {
|
||||||
gltf::camera::Projection::Orthographic(orthographic) => {
|
gltf::camera::Projection::Orthographic(orthographic) => {
|
||||||
|
@ -478,7 +486,7 @@ fn load_node(
|
||||||
};
|
};
|
||||||
|
|
||||||
node.insert(Camera {
|
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(),
|
projection_matrix: orthographic_projection.get_projection_matrix(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
@ -497,7 +505,7 @@ fn load_node(
|
||||||
perspective_projection.aspect_ratio = aspect_ratio;
|
perspective_projection.aspect_ratio = aspect_ratio;
|
||||||
}
|
}
|
||||||
node.insert(Camera {
|
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(),
|
projection_matrix: perspective_projection.get_projection_matrix(),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
@ -526,11 +534,17 @@ fn load_node(
|
||||||
let material_asset_path =
|
let material_asset_path =
|
||||||
AssetPath::new_ref(load_context.path(), Some(&material_label));
|
AssetPath::new_ref(load_context.path(), Some(&material_label));
|
||||||
|
|
||||||
parent.spawn_bundle(PbrBundle {
|
let bounds = primitive.bounding_box();
|
||||||
mesh: load_context.get_handle(mesh_asset_path),
|
parent
|
||||||
material: load_context.get_handle(material_asset_path),
|
.spawn_bundle(PbrBundle {
|
||||||
..Default::default()
|
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 {
|
fn mesh_label(mesh: &gltf::Mesh) -> String {
|
||||||
format!("Mesh{}", mesh.index())
|
format!("Mesh{}", mesh.index())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the label for the `mesh` and `primitive`.
|
||||||
fn primitive_label(mesh: &gltf::Mesh, primitive: &Primitive) -> String {
|
fn primitive_label(mesh: &gltf::Mesh, primitive: &Primitive) -> String {
|
||||||
format!("Mesh{}/Primitive{}", mesh.index(), primitive.index())
|
format!("Mesh{}/Primitive{}", mesh.index(), primitive.index())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the label for the `material`.
|
||||||
fn material_label(material: &gltf::Material) -> String {
|
fn material_label(material: &gltf::Material) -> String {
|
||||||
if let Some(index) = material.index() {
|
if let Some(index) = material.index() {
|
||||||
format!("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 {
|
fn texture_label(texture: &gltf::Texture) -> String {
|
||||||
format!("Texture{}", texture.index())
|
format!("Texture{}", texture.index())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the label for the `node`.
|
||||||
fn node_label(node: &gltf::Node) -> String {
|
fn node_label(node: &gltf::Node) -> String {
|
||||||
format!("Node{}", node.index())
|
format!("Node{}", node.index())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the label for the `scene`.
|
||||||
fn scene_label(scene: &gltf::Scene) -> String {
|
fn scene_label(scene: &gltf::Scene) -> String {
|
||||||
format!("Scene{}", scene.index())
|
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();
|
let gltf_sampler = texture.sampler();
|
||||||
|
|
||||||
SamplerDescriptor {
|
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 {
|
fn texture_address_mode(gltf_address_mode: &gltf::texture::WrappingMode) -> AddressMode {
|
||||||
match gltf_address_mode {
|
match gltf_address_mode {
|
||||||
WrappingMode::ClampToEdge => AddressMode::ClampToEdge,
|
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> {
|
fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
|
||||||
match mode {
|
match mode {
|
||||||
Mode::Points => Ok(PrimitiveTopology::PointList),
|
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(
|
async fn load_buffers(
|
||||||
gltf: &gltf::Gltf,
|
gltf: &gltf::Gltf,
|
||||||
load_context: &LoadContext<'_>,
|
load_context: &LoadContext<'_>,
|
||||||
|
@ -684,42 +716,51 @@ async fn load_buffers(
|
||||||
|
|
||||||
fn resolve_node_hierarchy(
|
fn resolve_node_hierarchy(
|
||||||
nodes_intermediate: Vec<(String, GltfNode, Vec<usize>)>,
|
nodes_intermediate: Vec<(String, GltfNode, Vec<usize>)>,
|
||||||
|
asset_path: &Path,
|
||||||
) -> Vec<(String, GltfNode)> {
|
) -> Vec<(String, GltfNode)> {
|
||||||
let mut max_steps = nodes_intermediate.len();
|
let mut has_errored = false;
|
||||||
let mut nodes_step = nodes_intermediate
|
let mut empty_children = VecDeque::new();
|
||||||
|
let mut parents = vec![None; nodes_intermediate.len()];
|
||||||
|
let mut unprocessed_nodes = nodes_intermediate
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, (label, node, children))| (i, label, node, children))
|
.map(|(i, (label, node, children))| {
|
||||||
.collect::<Vec<_>>();
|
for child in children.iter() {
|
||||||
let mut nodes = std::collections::HashMap::<usize, (String, GltfNode)>::new();
|
if let Some(parent) = parents.get_mut(*child) {
|
||||||
while max_steps > 0 && !nodes_step.is_empty() {
|
*parent = Some(i);
|
||||||
if let Some((index, label, node, _)) = nodes_step
|
} else if !has_errored {
|
||||||
.iter()
|
has_errored = true;
|
||||||
.find(|(_, _, _, children)| children.is_empty())
|
warn!("Unexpected child in GLTF Mesh {}", child);
|
||||||
.cloned()
|
|
||||||
{
|
|
||||||
nodes.insert(index, (label, node));
|
|
||||||
for (_, _, node, children) in nodes_step.iter_mut() {
|
|
||||||
if let Some((i, _)) = children
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|(_, child_index)| **child_index == index)
|
|
||||||
{
|
|
||||||
children.remove(i);
|
|
||||||
|
|
||||||
if let Some((_, child_node)) = nodes.get(&index) {
|
|
||||||
node.children.push(child_node.clone())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nodes_step = nodes_step
|
let children = children.into_iter().collect::<HashSet<_>>();
|
||||||
.into_iter()
|
if children.is_empty() {
|
||||||
.filter(|(i, _, _, _)| *i != index)
|
empty_children.push_back(i);
|
||||||
.collect()
|
}
|
||||||
}
|
(i, (label, node, children))
|
||||||
max_steps -= 1;
|
})
|
||||||
}
|
.collect::<HashMap<_, _>>();
|
||||||
|
let mut nodes = std::collections::HashMap::<usize, (String, GltfNode)>::new();
|
||||||
|
while let Some(index) = empty_children.pop_front() {
|
||||||
|
let (label, node, children) = unprocessed_nodes.remove(&index).unwrap();
|
||||||
|
assert!(children.is_empty());
|
||||||
|
nodes.insert(index, (label, node));
|
||||||
|
if let Some(parent_index) = parents[index] {
|
||||||
|
let (_, parent_node, parent_children) =
|
||||||
|
unprocessed_nodes.get_mut(&parent_index).unwrap();
|
||||||
|
|
||||||
|
assert!(parent_children.remove(&index));
|
||||||
|
if let Some((_, child_node)) = nodes.get(&index) {
|
||||||
|
parent_node.children.push(child_node.clone())
|
||||||
|
}
|
||||||
|
if parent_children.is_empty() {
|
||||||
|
empty_children.push_back(parent_index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !unprocessed_nodes.is_empty() {
|
||||||
|
warn!("GLTF model must be a tree: {:?}", asset_path);
|
||||||
|
}
|
||||||
let mut nodes_to_sort = nodes.into_iter().collect::<Vec<_>>();
|
let mut nodes_to_sort = nodes.into_iter().collect::<Vec<_>>();
|
||||||
nodes_to_sort.sort_by_key(|(i, _)| *i);
|
nodes_to_sort.sort_by_key(|(i, _)| *i);
|
||||||
nodes_to_sort
|
nodes_to_sort
|
||||||
|
@ -767,6 +808,8 @@ impl<'a> DataUri<'a> {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use super::resolve_node_hierarchy;
|
use super::resolve_node_hierarchy;
|
||||||
use crate::GltfNode;
|
use crate::GltfNode;
|
||||||
|
|
||||||
|
@ -781,7 +824,10 @@ mod test {
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn node_hierarchy_single_node() {
|
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.len(), 1);
|
||||||
assert_eq!(result[0].0, "l1");
|
assert_eq!(result[0].0, "l1");
|
||||||
|
@ -790,10 +836,13 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn node_hierarchy_no_hierarchy() {
|
fn node_hierarchy_no_hierarchy() {
|
||||||
let result = resolve_node_hierarchy(vec![
|
let result = resolve_node_hierarchy(
|
||||||
("l1".to_string(), GltfNode::empty(), vec![]),
|
vec![
|
||||||
("l2".to_string(), GltfNode::empty(), 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.len(), 2);
|
||||||
assert_eq!(result[0].0, "l1");
|
assert_eq!(result[0].0, "l1");
|
||||||
|
@ -804,10 +853,13 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn node_hierarchy_simple_hierarchy() {
|
fn node_hierarchy_simple_hierarchy() {
|
||||||
let result = resolve_node_hierarchy(vec![
|
let result = resolve_node_hierarchy(
|
||||||
("l1".to_string(), GltfNode::empty(), vec![1]),
|
vec![
|
||||||
("l2".to_string(), GltfNode::empty(), 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.len(), 2);
|
||||||
assert_eq!(result[0].0, "l1");
|
assert_eq!(result[0].0, "l1");
|
||||||
|
@ -818,15 +870,18 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn node_hierarchy_hierarchy() {
|
fn node_hierarchy_hierarchy() {
|
||||||
let result = resolve_node_hierarchy(vec![
|
let result = resolve_node_hierarchy(
|
||||||
("l1".to_string(), GltfNode::empty(), vec![1]),
|
vec![
|
||||||
("l2".to_string(), GltfNode::empty(), vec![2]),
|
("l1".to_string(), GltfNode::empty(), vec![1]),
|
||||||
("l3".to_string(), GltfNode::empty(), vec![3, 4, 5]),
|
("l2".to_string(), GltfNode::empty(), vec![2]),
|
||||||
("l4".to_string(), GltfNode::empty(), vec![6]),
|
("l3".to_string(), GltfNode::empty(), vec![3, 4, 5]),
|
||||||
("l5".to_string(), GltfNode::empty(), vec![]),
|
("l4".to_string(), GltfNode::empty(), vec![6]),
|
||||||
("l6".to_string(), GltfNode::empty(), vec![]),
|
("l5".to_string(), GltfNode::empty(), vec![]),
|
||||||
("l7".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.len(), 7);
|
||||||
assert_eq!(result[0].0, "l1");
|
assert_eq!(result[0].0, "l1");
|
||||||
|
@ -847,20 +902,26 @@ mod test {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn node_hierarchy_cyclic() {
|
fn node_hierarchy_cyclic() {
|
||||||
let result = resolve_node_hierarchy(vec![
|
let result = resolve_node_hierarchy(
|
||||||
("l1".to_string(), GltfNode::empty(), vec![1]),
|
vec![
|
||||||
("l2".to_string(), GltfNode::empty(), vec![0]),
|
("l1".to_string(), GltfNode::empty(), vec![1]),
|
||||||
]);
|
("l2".to_string(), GltfNode::empty(), vec![0]),
|
||||||
|
],
|
||||||
|
PathBuf::new().as_path(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(result.len(), 0);
|
assert_eq!(result.len(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn node_hierarchy_missing_node() {
|
fn node_hierarchy_missing_node() {
|
||||||
let result = resolve_node_hierarchy(vec![
|
let result = resolve_node_hierarchy(
|
||||||
("l1".to_string(), GltfNode::empty(), vec![2]),
|
vec![
|
||||||
("l2".to_string(), GltfNode::empty(), 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.len(), 1);
|
||||||
assert_eq!(result[0].0, "l2");
|
assert_eq!(result[0].0, "l2");
|
||||||
|
|
|
@ -10,18 +10,18 @@ keywords = ["game", "engine", "gamedev", "graphics", "bevy"]
|
||||||
categories = ["game-engines", "graphics", "gui", "rendering"]
|
categories = ["game-engines", "graphics", "gui", "rendering"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
wgpu_trace = ["bevy_wgpu/trace"]
|
trace = [ "bevy_app/trace", "bevy_ecs/trace", "bevy_render/trace" ]
|
||||||
trace = [ "bevy_app/trace", "bevy_ecs/trace", "bevy_render2/trace" ]
|
|
||||||
trace_chrome = [ "bevy_log/tracing-chrome" ]
|
trace_chrome = [ "bevy_log/tracing-chrome" ]
|
||||||
trace_tracy = [ "bevy_log/tracing-tracy" ]
|
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)
|
# Image format support for texture loading (PNG and HDR are enabled by default)
|
||||||
hdr = ["bevy_render/hdr", "bevy_render2/hdr" ]
|
hdr = ["bevy_render/hdr"]
|
||||||
png = ["bevy_render/png", "bevy_render2/png" ]
|
png = ["bevy_render/png"]
|
||||||
dds = ["bevy_render/dds", "bevy_render2/dds" ]
|
dds = ["bevy_render/dds"]
|
||||||
tga = ["bevy_render/tga", "bevy_render2/tga" ]
|
tga = ["bevy_render/tga"]
|
||||||
jpeg = ["bevy_render/jpeg", "bevy_render2/jpeg" ]
|
jpeg = ["bevy_render/jpeg"]
|
||||||
bmp = ["bevy_render/bmp", "bevy_render2/bmp" ]
|
bmp = ["bevy_render/bmp"]
|
||||||
|
|
||||||
# Audio format support (MP3 is enabled by default)
|
# Audio format support (MP3 is enabled by default)
|
||||||
flac = ["bevy_audio/flac"]
|
flac = ["bevy_audio/flac"]
|
||||||
|
@ -39,10 +39,10 @@ wayland = ["bevy_winit/wayland"]
|
||||||
x11 = ["bevy_winit/x11"]
|
x11 = ["bevy_winit/x11"]
|
||||||
|
|
||||||
# enable rendering of font glyphs using subpixel accuracy
|
# 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
|
# 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]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
|
@ -63,21 +63,14 @@ bevy_window = { path = "../bevy_window", version = "0.5.0" }
|
||||||
bevy_tasks = { path = "../bevy_tasks", version = "0.5.0" }
|
bevy_tasks = { path = "../bevy_tasks", version = "0.5.0" }
|
||||||
# bevy (optional)
|
# bevy (optional)
|
||||||
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.5.0" }
|
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_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_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_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_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.5.0" }
|
||||||
bevy_sprite = { path = "../bevy_sprite", 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_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_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_winit = { path = "../bevy_winit", optional = true, version = "0.5.0" }
|
||||||
bevy_gilrs = { path = "../bevy_gilrs", 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::{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:
|
/// This plugin group will add all the default plugins:
|
||||||
/// * [`LogPlugin`]
|
/// * [`LogPlugin`]
|
||||||
/// * [`CorePlugin`]
|
/// * [`CorePlugin`]
|
||||||
|
@ -48,71 +18,11 @@ use bevy_winit::WinitPlugin;
|
||||||
/// * [`GilrsPlugin`] - with feature `bevy_gilrs`
|
/// * [`GilrsPlugin`] - with feature `bevy_gilrs`
|
||||||
/// * [`GltfPlugin`] - with feature `bevy_gltf`
|
/// * [`GltfPlugin`] - with feature `bevy_gltf`
|
||||||
/// * [`WinitPlugin`] - with feature `bevy_winit`
|
/// * [`WinitPlugin`] - with feature `bevy_winit`
|
||||||
/// * [`WgpuPlugin`] - with feature `bevy_wgpu`
|
|
||||||
///
|
///
|
||||||
/// See also [`MinimalPlugins`] for a slimmed down option
|
/// See also [`MinimalPlugins`] for a slimmed down option
|
||||||
pub struct DefaultPlugins;
|
pub struct DefaultPlugins;
|
||||||
|
|
||||||
impl PluginGroup for 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) {
|
fn build(&mut self, group: &mut PluginGroupBuilder) {
|
||||||
group.add(bevy_log::LogPlugin::default());
|
group.add(bevy_log::LogPlugin::default());
|
||||||
group.add(bevy_core::CorePlugin::default());
|
group.add(bevy_core::CorePlugin::default());
|
||||||
|
@ -126,29 +36,42 @@ impl PluginGroup for PipelinedDefaultPlugins {
|
||||||
#[cfg(feature = "bevy_winit")]
|
#[cfg(feature = "bevy_winit")]
|
||||||
group.add(bevy_winit::WinitPlugin::default());
|
group.add(bevy_winit::WinitPlugin::default());
|
||||||
|
|
||||||
#[cfg(feature = "bevy_render2")]
|
#[cfg(feature = "bevy_render")]
|
||||||
{
|
group.add(bevy_render::RenderPlugin::default());
|
||||||
group.add(bevy_render2::RenderPlugin::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "bevy_core_pipeline")]
|
#[cfg(feature = "bevy_core_pipeline")]
|
||||||
{
|
group.add(bevy_core_pipeline::CorePipelinePlugin::default());
|
||||||
group.add(bevy_core_pipeline::CorePipelinePlugin::default());
|
|
||||||
|
|
||||||
#[cfg(feature = "bevy_sprite2")]
|
#[cfg(feature = "bevy_sprite")]
|
||||||
group.add(bevy_sprite2::SpritePlugin::default());
|
group.add(bevy_sprite::SpritePlugin::default());
|
||||||
|
|
||||||
#[cfg(feature = "bevy_text2")]
|
#[cfg(feature = "bevy_text")]
|
||||||
group.add(bevy_text2::TextPlugin::default());
|
group.add(bevy_text::TextPlugin::default());
|
||||||
|
|
||||||
#[cfg(feature = "bevy_ui2")]
|
#[cfg(feature = "bevy_ui")]
|
||||||
group.add(bevy_ui2::UiPlugin::default());
|
group.add(bevy_ui::UiPlugin::default());
|
||||||
|
|
||||||
#[cfg(feature = "bevy_pbr2")]
|
#[cfg(feature = "bevy_pbr")]
|
||||||
group.add(bevy_pbr2::PbrPlugin::default());
|
group.add(bevy_pbr::PbrPlugin::default());
|
||||||
|
|
||||||
#[cfg(feature = "bevy_gltf2")]
|
#[cfg(feature = "bevy_gltf")]
|
||||||
group.add(bevy_gltf2::GltfPlugin::default());
|
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::*;
|
pub use bevy_gltf::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bevy_gltf2")]
|
|
||||||
pub mod gltf2 {
|
|
||||||
//! Support for GLTF file loading.
|
|
||||||
pub use bevy_gltf2::*;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "bevy_pbr")]
|
#[cfg(feature = "bevy_pbr")]
|
||||||
pub mod pbr {
|
pub mod pbr {
|
||||||
//! Physically based rendering.
|
//! Physically based rendering.
|
||||||
pub use bevy_pbr::*;
|
pub use bevy_pbr::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bevy_pbr2")]
|
|
||||||
pub mod pbr2 {
|
|
||||||
//! Physically based rendering.
|
|
||||||
pub use bevy_pbr2::*;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "bevy_render")]
|
#[cfg(feature = "bevy_render")]
|
||||||
pub mod render {
|
pub mod render {
|
||||||
//! Cameras, meshes, textures, shaders, and pipelines.
|
//! Cameras, meshes, textures, shaders, and pipelines.
|
||||||
pub use bevy_render::*;
|
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")]
|
#[cfg(feature = "bevy_sprite")]
|
||||||
pub mod sprite {
|
pub mod sprite {
|
||||||
//! Items for sprites, rects, texture atlases, etc.
|
//! Items for sprites, rects, texture atlases, etc.
|
||||||
pub use bevy_sprite::*;
|
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")]
|
#[cfg(feature = "bevy_text")]
|
||||||
pub mod text {
|
pub mod text {
|
||||||
//! Text drawing, styling, and font assets.
|
//! Text drawing, styling, and font assets.
|
||||||
pub use bevy_text::*;
|
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")]
|
#[cfg(feature = "bevy_ui")]
|
||||||
pub mod ui {
|
pub mod ui {
|
||||||
//! User interface components and widgets.
|
//! User interface components and widgets.
|
||||||
pub use bevy_ui::*;
|
pub use bevy_ui::*;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "bevy_ui2")]
|
|
||||||
pub mod ui2 {
|
|
||||||
//! User interface components and widgets.
|
|
||||||
pub use bevy_ui2::*;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "bevy_winit")]
|
#[cfg(feature = "bevy_winit")]
|
||||||
pub mod winit {
|
pub mod winit {
|
||||||
pub use bevy_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")]
|
#[cfg(feature = "bevy_dynamic_plugin")]
|
||||||
pub mod dynamic_plugin {
|
pub mod dynamic_plugin {
|
||||||
pub use bevy_dynamic_plugin::*;
|
pub use bevy_dynamic_plugin::*;
|
||||||
|
|
|
@ -11,6 +11,10 @@ pub use bevy_derive::bevy_main;
|
||||||
#[cfg(feature = "bevy_audio")]
|
#[cfg(feature = "bevy_audio")]
|
||||||
pub use crate::audio::prelude::*;
|
pub use crate::audio::prelude::*;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[cfg(feature = "bevy_core_pipeline")]
|
||||||
|
pub use crate::core_pipeline::prelude::*;
|
||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
#[cfg(feature = "bevy_pbr")]
|
#[cfg(feature = "bevy_pbr")]
|
||||||
pub use crate::pbr::prelude::*;
|
pub use crate::pbr::prelude::*;
|
||||||
|
|
|
@ -8,7 +8,7 @@ pub use glam::*;
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
BVec2, BVec3, BVec4, FaceToward, IVec2, IVec3, IVec4, Mat3, Mat4, Quat, Rect, Size, UVec2,
|
BVec2, BVec3, BVec4, EulerRot, FaceToward, IVec2, IVec3, IVec4, Mat3, Mat4, Quat, Rect,
|
||||||
UVec3, UVec4, Vec2, Vec3, Vec4,
|
Size, UVec2, UVec3, UVec4, Vec2, Vec3, Vec4,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,12 +13,18 @@ keywords = ["bevy"]
|
||||||
bevy_app = { path = "../bevy_app", version = "0.5.0" }
|
bevy_app = { path = "../bevy_app", version = "0.5.0" }
|
||||||
bevy_asset = { path = "../bevy_asset", version = "0.5.0" }
|
bevy_asset = { path = "../bevy_asset", version = "0.5.0" }
|
||||||
bevy_core = { path = "../bevy_core", 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_ecs = { path = "../bevy_ecs", version = "0.5.0" }
|
||||||
bevy_math = { path = "../bevy_math", version = "0.5.0" }
|
bevy_math = { path = "../bevy_math", version = "0.5.0" }
|
||||||
bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] }
|
bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] }
|
||||||
bevy_render = { path = "../bevy_render", version = "0.5.0" }
|
bevy_render = { path = "../bevy_render", version = "0.5.0" }
|
||||||
bevy_transform = { path = "../bevy_transform", 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
|
# other
|
||||||
|
bitflags = "1.2"
|
||||||
# direct dependency required for derive macro
|
# direct dependency required for derive macro
|
||||||
bytemuck = { version = "1", features = ["derive"] }
|
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 crate::{DirectionalLight, PointLight, StandardMaterial};
|
||||||
use bevy_asset::Handle;
|
use bevy_asset::Handle;
|
||||||
use bevy_ecs::{bundle::Bundle, component::Component};
|
use bevy_ecs::{bundle::Bundle, component::Component};
|
||||||
use bevy_render2::{
|
use bevy_render::{
|
||||||
mesh::Mesh,
|
mesh::Mesh,
|
||||||
primitives::{CubemapFrusta, Frustum},
|
primitives::{CubemapFrusta, Frustum},
|
||||||
view::{ComputedVisibility, Visibility, VisibleEntities},
|
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 light;
|
||||||
mod material;
|
mod material;
|
||||||
|
mod render;
|
||||||
|
|
||||||
pub use entity::*;
|
pub use alpha::*;
|
||||||
|
pub use bundle::*;
|
||||||
pub use light::*;
|
pub use light::*;
|
||||||
pub use material::*;
|
pub use material::*;
|
||||||
|
pub use render::*;
|
||||||
|
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
entity::*,
|
alpha::AlphaMode,
|
||||||
light::{DirectionalLight, PointLight},
|
bundle::{DirectionalLightBundle, PbrBundle, PointLightBundle},
|
||||||
|
light::{AmbientLight, DirectionalLight, PointLight},
|
||||||
material::StandardMaterial,
|
material::StandardMaterial,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
use bevy_app::prelude::*;
|
pub mod draw_3d_graph {
|
||||||
use bevy_asset::{AddAsset, Assets, Handle};
|
pub mod node {
|
||||||
use bevy_render::{prelude::Color, shader};
|
/// Label for the shadow pass node.
|
||||||
use material::StandardMaterial;
|
pub const SHADOW_PASS: &str = "shadow_pass";
|
||||||
use render_graph::add_pbr_graph;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 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)]
|
#[derive(Default)]
|
||||||
pub struct PbrPlugin;
|
pub struct PbrPlugin;
|
||||||
|
|
||||||
impl Plugin for PbrPlugin {
|
impl Plugin for PbrPlugin {
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
app.add_asset::<StandardMaterial>()
|
let mut shaders = app.world.get_resource_mut::<Assets<Shader>>().unwrap();
|
||||||
.register_type::<PointLight>()
|
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(
|
.add_system_to_stage(
|
||||||
CoreStage::PostUpdate,
|
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_system_to_stage(
|
||||||
add_pbr_graph(&mut app.world);
|
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),
|
||||||
|
);
|
||||||
|
|
||||||
// add default StandardMaterial
|
let render_app = app.sub_app(RenderApp);
|
||||||
let mut materials = app
|
render_app
|
||||||
.world
|
.add_system_to_stage(
|
||||||
.get_resource_mut::<Assets<StandardMaterial>>()
|
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();
|
.unwrap();
|
||||||
materials.set_untracked(
|
|
||||||
Handle::<StandardMaterial>::default(),
|
|
||||||
StandardMaterial {
|
|
||||||
base_color: Color::PINK,
|
|
||||||
unlit: true,
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,53 +1,79 @@
|
||||||
use bevy_core::{Pod, Zeroable};
|
use std::collections::HashSet;
|
||||||
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;
|
|
||||||
|
|
||||||
/// A point light
|
use bevy_ecs::prelude::*;
|
||||||
#[derive(Component, Debug, Clone, Copy, Reflect)]
|
use bevy_math::{Mat4, UVec2, UVec3, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
|
||||||
#[reflect(Component)]
|
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 struct PointLight {
|
||||||
pub color: Color,
|
pub color: Color,
|
||||||
pub intensity: f32,
|
pub intensity: f32,
|
||||||
pub range: f32,
|
pub range: f32,
|
||||||
pub radius: 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 {
|
impl Default for PointLight {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
PointLight {
|
PointLight {
|
||||||
color: Color::rgb(1.0, 1.0, 1.0),
|
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,
|
range: 20.0,
|
||||||
radius: 0.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)]
|
impl PointLight {
|
||||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02;
|
||||||
pub(crate) struct PointLightUniform {
|
pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.6;
|
||||||
pub pos: [f32; 4],
|
|
||||||
pub color: [f32; 4],
|
|
||||||
// storing as a `[f32; 4]` for memory alignement
|
|
||||||
pub light_params: [f32; 4],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PointLightUniform {
|
#[derive(Clone, Debug)]
|
||||||
pub fn new(light: &PointLight, global_transform: &GlobalTransform) -> PointLightUniform {
|
pub struct PointLightShadowMap {
|
||||||
let (x, y, z) = global_transform.translation.into();
|
pub size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
// premultiply color by intensity
|
impl Default for PointLightShadowMap {
|
||||||
// we don't use the alpha at all, so no reason to multiply only [0..3]
|
fn default() -> Self {
|
||||||
let color: [f32; 4] = (light.color * light.intensity).into();
|
Self { size: 1024 }
|
||||||
|
|
||||||
PointLightUniform {
|
|
||||||
pos: [x, y, z, 1.0],
|
|
||||||
color,
|
|
||||||
light_params: [1.0 / (light.range * light.range), light.radius, 0.0, 0.0],
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,86 +103,62 @@ impl PointLightUniform {
|
||||||
/// | 32,000–100,000 | Direct sunlight |
|
/// | 32,000–100,000 | Direct sunlight |
|
||||||
///
|
///
|
||||||
/// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lux)
|
/// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lux)
|
||||||
#[derive(Component, Debug, Clone, Copy, Reflect)]
|
#[derive(Component, Debug, Clone)]
|
||||||
#[reflect(Component)]
|
|
||||||
pub struct DirectionalLight {
|
pub struct DirectionalLight {
|
||||||
pub color: Color,
|
pub color: Color,
|
||||||
|
/// Illuminance in lux
|
||||||
pub illuminance: f32,
|
pub illuminance: f32,
|
||||||
direction: Vec3,
|
pub shadows_enabled: bool,
|
||||||
}
|
pub shadow_projection: OrthographicProjection,
|
||||||
|
pub shadow_depth_bias: f32,
|
||||||
impl DirectionalLight {
|
/// A bias applied along the direction of the fragment's surface normal. It is scaled to the
|
||||||
/// Create a new directional light component.
|
/// shadow map's texel size so that it is automatically adjusted to the orthographic projection.
|
||||||
pub fn new(color: Color, illuminance: f32, direction: Vec3) -> Self {
|
pub shadow_normal_bias: f32,
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DirectionalLight {
|
impl Default for DirectionalLight {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
|
let size = 100.0;
|
||||||
DirectionalLight {
|
DirectionalLight {
|
||||||
color: Color::rgb(1.0, 1.0, 1.0),
|
color: Color::rgb(1.0, 1.0, 1.0),
|
||||||
illuminance: 100000.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)]
|
impl DirectionalLight {
|
||||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02;
|
||||||
pub(crate) struct DirectionalLightUniform {
|
pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.6;
|
||||||
pub dir: [f32; 4],
|
|
||||||
pub color: [f32; 4],
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DirectionalLightUniform {
|
#[derive(Clone, Debug)]
|
||||||
pub fn new(light: &DirectionalLight) -> DirectionalLightUniform {
|
pub struct DirectionalLightShadowMap {
|
||||||
// direction is negated to be ready for N.L
|
pub size: usize,
|
||||||
let dir: [f32; 4] = [
|
}
|
||||||
-light.direction.x,
|
|
||||||
-light.direction.y,
|
|
||||||
-light.direction.z,
|
|
||||||
0.0,
|
|
||||||
];
|
|
||||||
|
|
||||||
// convert from illuminance (lux) to candelas
|
impl Default for DirectionalLightShadowMap {
|
||||||
//
|
fn default() -> Self {
|
||||||
// exposure is hard coded at the moment but should be replaced
|
Self { size: 4096 }
|
||||||
// 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 }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ambient light color.
|
/// An ambient light, which lights the entire scene equally.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct AmbientLight {
|
pub struct AmbientLight {
|
||||||
pub color: Color,
|
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,
|
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_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
|
/// A material with "standard" properties used in PBR lighting
|
||||||
/// Standard property values with pictures here <https://google.github.io/filament/Material%20Properties.pdf>
|
/// Standard property values with pictures here
|
||||||
#[derive(Debug, RenderResources, ShaderDefs, TypeUuid)]
|
/// <https://google.github.io/filament/Material%20Properties.pdf>.
|
||||||
#[uuid = "dace545e-4bc6-4595-a79d-c224fc694975"]
|
///
|
||||||
|
/// May be created directly from a [`Color`] or an [`Image`].
|
||||||
|
#[derive(Debug, Clone, TypeUuid)]
|
||||||
|
#[uuid = "7494888b-c082-457b-aacf-517228cc0c22"]
|
||||||
pub struct StandardMaterial {
|
pub struct StandardMaterial {
|
||||||
/// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything
|
/// 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`
|
/// base color as `base_color * base_color_texture_value`
|
||||||
pub base_color: Color,
|
pub base_color: Color,
|
||||||
#[shader_def]
|
pub base_color_texture: Option<Handle<Image>>,
|
||||||
pub base_color_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,
|
||||||
|
pub emissive_texture: Option<Handle<Image>>,
|
||||||
/// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader
|
/// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader
|
||||||
/// Defaults to minimum of 0.089
|
/// Defaults to minimum of 0.089
|
||||||
/// If used together with a roughness/metallic texture, this is factored into the final base
|
/// If used together with a roughness/metallic texture, this is factored into the final base
|
||||||
/// color as `roughness * roughness_texture_value`
|
/// color as `roughness * roughness_texture_value`
|
||||||
pub roughness: f32,
|
pub perceptual_roughness: f32,
|
||||||
/// From [0.0, 1.0], dielectric to pure metallic
|
/// From [0.0, 1.0], dielectric to pure metallic
|
||||||
/// If used together with a roughness/metallic texture, this is factored into the final base
|
/// If used together with a roughness/metallic texture, this is factored into the final base
|
||||||
/// color as `metallic * metallic_texture_value`
|
/// color as `metallic * metallic_texture_value`
|
||||||
pub metallic: f32,
|
pub metallic: f32,
|
||||||
|
pub metallic_roughness_texture: Option<Handle<Image>>,
|
||||||
/// Specular intensity for non-metals on a linear scale of [0.0, 1.0]
|
/// 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
|
/// 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,
|
pub reflectance: f32,
|
||||||
#[shader_def]
|
pub normal_map_texture: Option<Handle<Image>>,
|
||||||
pub normal_map: Option<Handle<Texture>>,
|
pub occlusion_texture: Option<Handle<Image>>,
|
||||||
#[render_resources(ignore)]
|
|
||||||
#[shader_def]
|
|
||||||
pub double_sided: bool,
|
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 unlit: bool,
|
||||||
|
pub alpha_mode: AlphaMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for StandardMaterial {
|
impl Default for StandardMaterial {
|
||||||
|
@ -49,26 +56,27 @@ impl Default for StandardMaterial {
|
||||||
StandardMaterial {
|
StandardMaterial {
|
||||||
base_color: Color::rgb(1.0, 1.0, 1.0),
|
base_color: Color::rgb(1.0, 1.0, 1.0),
|
||||||
base_color_texture: None,
|
base_color_texture: None,
|
||||||
|
emissive: Color::BLACK,
|
||||||
|
emissive_texture: None,
|
||||||
// This is the minimum the roughness is clamped to in shader code
|
// 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
|
// 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
|
// calculations used. Although technically for 32-bit floats, 0.045 could be
|
||||||
// used.
|
// used.
|
||||||
roughness: 0.089,
|
perceptual_roughness: 0.089,
|
||||||
// Few materials are purely dielectric or metallic
|
// Few materials are purely dielectric or metallic
|
||||||
// This is just a default for mostly-dielectric
|
// This is just a default for mostly-dielectric
|
||||||
metallic: 0.01,
|
metallic: 0.01,
|
||||||
|
metallic_roughness_texture: None,
|
||||||
// Minimum real-world reflectance is 2%, most materials between 2-5%
|
// Minimum real-world reflectance is 2%, most materials between 2-5%
|
||||||
// Expressed in a linear scale and equivalent to 4% reflectance see
|
// Expressed in a linear scale and equivalent to 4% reflectance see
|
||||||
// https://google.github.io/filament/Material%20Properties.pdf
|
// <https://google.github.io/filament/Material%20Properties.pdf>
|
||||||
metallic_roughness_texture: None,
|
|
||||||
reflectance: 0.5,
|
reflectance: 0.5,
|
||||||
normal_map: None,
|
|
||||||
double_sided: false,
|
|
||||||
occlusion_texture: None,
|
occlusion_texture: None,
|
||||||
emissive: Color::BLACK,
|
normal_map_texture: None,
|
||||||
emissive_texture: None,
|
double_sided: false,
|
||||||
unlit: false,
|
unlit: false,
|
||||||
|
alpha_mode: AlphaMode::Opaque,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,11 +90,227 @@ impl From<Color> for StandardMaterial {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Handle<Texture>> for StandardMaterial {
|
impl From<Handle<Image>> for StandardMaterial {
|
||||||
fn from(texture: Handle<Texture>) -> Self {
|
fn from(texture: Handle<Image>) -> Self {
|
||||||
StandardMaterial {
|
StandardMaterial {
|
||||||
base_color_texture: Some(texture),
|
base_color_texture: Some(texture),
|
||||||
..Default::default()
|
..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},
|
system::{lifetimeless::*, SystemParamItem},
|
||||||
};
|
};
|
||||||
use bevy_math::{const_vec3, Mat4, UVec3, UVec4, Vec3, Vec4, Vec4Swizzles};
|
use bevy_math::{const_vec3, Mat4, UVec3, UVec4, Vec3, Vec4, Vec4Swizzles};
|
||||||
use bevy_render2::{
|
use bevy_render::{
|
||||||
camera::{Camera, CameraProjection},
|
camera::{Camera, CameraProjection},
|
||||||
color::Color,
|
color::Color,
|
||||||
mesh::Mesh,
|
mesh::Mesh,
|
|
@ -10,7 +10,7 @@ use bevy_ecs::{
|
||||||
};
|
};
|
||||||
use bevy_math::Mat4;
|
use bevy_math::Mat4;
|
||||||
use bevy_reflect::TypeUuid;
|
use bevy_reflect::TypeUuid;
|
||||||
use bevy_render2::{
|
use bevy_render::{
|
||||||
mesh::Mesh,
|
mesh::Mesh,
|
||||||
render_asset::RenderAssets,
|
render_asset::RenderAssets,
|
||||||
render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
|
render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
|
||||||
|
@ -693,16 +693,19 @@ impl EntityRenderCommand for DrawMesh {
|
||||||
pass: &mut TrackedRenderPass<'w>,
|
pass: &mut TrackedRenderPass<'w>,
|
||||||
) -> RenderCommandResult {
|
) -> RenderCommandResult {
|
||||||
let mesh_handle = mesh_query.get(item).unwrap();
|
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(..));
|
pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..));
|
||||||
if let Some(index_info) = &gpu_mesh.index_info {
|
if let Some(index_info) = &gpu_mesh.index_info {
|
||||||
pass.set_index_buffer(index_info.buffer.slice(..), 0, index_info.index_format);
|
pass.set_index_buffer(index_info.buffer.slice(..), 0, index_info.index_format);
|
||||||
pass.draw_indexed(0..index_info.count, 0, 0..1);
|
pass.draw_indexed(0..index_info.count, 0, 0..1);
|
||||||
} else {
|
} else {
|
||||||
panic!("non-indexed drawing not supported yet")
|
panic!("non-indexed drawing not supported yet")
|
||||||
}
|
}
|
||||||
|
|
||||||
RenderCommandResult::Success
|
RenderCommandResult::Success
|
||||||
|
} else {
|
||||||
|
RenderCommandResult::Failure
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use bevy_ecs::{
|
||||||
prelude::*,
|
prelude::*,
|
||||||
system::{lifetimeless::*, SystemParamItem},
|
system::{lifetimeless::*, SystemParamItem},
|
||||||
};
|
};
|
||||||
use bevy_render2::{
|
use bevy_render::{
|
||||||
mesh::Mesh,
|
mesh::Mesh,
|
||||||
render_asset::RenderAssets,
|
render_asset::RenderAssets,
|
||||||
render_phase::{
|
render_phase::{
|
||||||
|
@ -199,6 +199,15 @@ impl SpecializedPipeline for PbrPipeline {
|
||||||
self.material_layout.clone(),
|
self.material_layout.clone(),
|
||||||
self.mesh_pipeline.mesh_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 {
|
if let Some(label) = &mut descriptor.label {
|
||||||
*label = format!("pbr_{}", *label).into();
|
*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_ecs::{prelude::*, reflect::ReflectComponent};
|
||||||
use bevy_reflect::Reflect;
|
use bevy_reflect::Reflect;
|
||||||
use bevy_reflect::TypeUuid;
|
use bevy_reflect::TypeUuid;
|
||||||
use bevy_render2::{
|
use bevy_render::{
|
||||||
mesh::Mesh,
|
mesh::Mesh,
|
||||||
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
|
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
|
||||||
render_resource::{RenderPipelineCache, Shader, SpecializedPipeline, SpecializedPipelines},
|
render_resource::{RenderPipelineCache, Shader, SpecializedPipeline, SpecializedPipelines},
|
||||||
|
@ -80,10 +80,7 @@ impl FromWorld for WireframePipeline {
|
||||||
impl SpecializedPipeline for WireframePipeline {
|
impl SpecializedPipeline for WireframePipeline {
|
||||||
type Key = MeshPipelineKey;
|
type Key = MeshPipelineKey;
|
||||||
|
|
||||||
fn specialize(
|
fn specialize(&self, key: Self::Key) -> bevy_render::render_resource::RenderPipelineDescriptor {
|
||||||
&self,
|
|
||||||
key: Self::Key,
|
|
||||||
) -> bevy_render2::render_resource::RenderPipelineDescriptor {
|
|
||||||
let mut descriptor = self.mesh_pipeline.specialize(key);
|
let mut descriptor = self.mesh_pipeline.specialize(key);
|
||||||
descriptor.vertex.shader = self.shader.clone_weak();
|
descriptor.vertex.shader = self.shader.clone_weak();
|
||||||
descriptor.fragment.as_mut().unwrap().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"
|
license = "MIT OR Apache-2.0"
|
||||||
keywords = ["bevy"]
|
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]
|
[dependencies]
|
||||||
# bevy
|
# bevy
|
||||||
bevy_app = { path = "../bevy_app", version = "0.5.0" }
|
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 }
|
image = { version = "0.23.12", default-features = false }
|
||||||
|
|
||||||
# misc
|
# 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"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
bitflags = "1.2.1"
|
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
|
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"
|
downcast-rs = "1.2.0"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
anyhow = "1.0.4"
|
futures-lite = "1.4.0"
|
||||||
|
anyhow = "1.0"
|
||||||
hex = "0.4.2"
|
hex = "0.4.2"
|
||||||
hexasphere = "6.0.0"
|
hexasphere = "6.0.0"
|
||||||
parking_lot = "0.11.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]
|
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||||
spirv-reflect = "0.2.3"
|
wgpu = { version = "0.11.0", features = ["spirv", "webgl"] }
|
||||||
|
|
||||||
[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"]
|
|
||||||
|
|
|
@ -1,18 +1,14 @@
|
||||||
use crate::renderer::RenderResourceBindings;
|
|
||||||
|
|
||||||
use super::Camera;
|
use super::Camera;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
component::Component,
|
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
system::{Query, ResMut},
|
system::{Query, ResMut},
|
||||||
};
|
};
|
||||||
use bevy_utils::HashMap;
|
use bevy_utils::HashMap;
|
||||||
|
|
||||||
#[derive(Component, Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct ActiveCamera {
|
pub struct ActiveCamera {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub entity: Option<Entity>,
|
pub entity: Option<Entity>,
|
||||||
pub bindings: RenderResourceBindings,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use super::CameraProjection;
|
use crate::camera::CameraProjection;
|
||||||
use bevy_ecs::{
|
use bevy_ecs::{
|
||||||
change_detection::DetectChanges,
|
|
||||||
component::Component,
|
component::Component,
|
||||||
entity::Entity,
|
entity::Entity,
|
||||||
event::EventReader,
|
event::EventReader,
|
||||||
prelude::QueryState,
|
prelude::{DetectChanges, QueryState},
|
||||||
query::Added,
|
query::Added,
|
||||||
reflect::ReflectComponent,
|
reflect::ReflectComponent,
|
||||||
system::{QuerySet, Res},
|
system::{QuerySet, Res},
|
||||||
|
@ -24,6 +23,8 @@ pub struct Camera {
|
||||||
pub window: WindowId,
|
pub window: WindowId,
|
||||||
#[reflect(ignore)]
|
#[reflect(ignore)]
|
||||||
pub depth_calculation: DepthCalculation,
|
pub depth_calculation: DepthCalculation,
|
||||||
|
pub near: f32,
|
||||||
|
pub far: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Reflect, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Copy, Reflect, Serialize, Deserialize)]
|
||||||
|
|
|
@ -1,10 +1,107 @@
|
||||||
mod active_cameras;
|
mod active_cameras;
|
||||||
|
mod bundle;
|
||||||
#[allow(clippy::module_inception)]
|
#[allow(clippy::module_inception)]
|
||||||
mod camera;
|
mod camera;
|
||||||
mod projection;
|
mod projection;
|
||||||
mod visible_entities;
|
|
||||||
|
|
||||||
pub use active_cameras::*;
|
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 camera::*;
|
||||||
pub use projection::*;
|
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 get_projection_matrix(&self) -> Mat4;
|
||||||
fn update(&mut self, width: f32, height: f32);
|
fn update(&mut self, width: f32, height: f32);
|
||||||
fn depth_calculation(&self) -> DepthCalculation;
|
fn depth_calculation(&self) -> DepthCalculation;
|
||||||
|
fn far(&self) -> f32;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Debug, Clone, Reflect)]
|
#[derive(Component, Debug, Clone, Reflect)]
|
||||||
|
@ -21,7 +22,7 @@ pub struct PerspectiveProjection {
|
||||||
|
|
||||||
impl CameraProjection for PerspectiveProjection {
|
impl CameraProjection for PerspectiveProjection {
|
||||||
fn get_projection_matrix(&self) -> Mat4 {
|
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) {
|
fn update(&mut self, width: f32, height: f32) {
|
||||||
|
@ -31,13 +32,17 @@ impl CameraProjection for PerspectiveProjection {
|
||||||
fn depth_calculation(&self) -> DepthCalculation {
|
fn depth_calculation(&self) -> DepthCalculation {
|
||||||
DepthCalculation::Distance
|
DepthCalculation::Distance
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn far(&self) -> f32 {
|
||||||
|
self.far
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PerspectiveProjection {
|
impl Default for PerspectiveProjection {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
PerspectiveProjection {
|
PerspectiveProjection {
|
||||||
fov: std::f32::consts::PI / 4.0,
|
fov: std::f32::consts::PI / 4.0,
|
||||||
near: 1.0,
|
near: 0.1,
|
||||||
far: 1000.0,
|
far: 1000.0,
|
||||||
aspect_ratio: 1.0,
|
aspect_ratio: 1.0,
|
||||||
}
|
}
|
||||||
|
@ -88,8 +93,10 @@ impl CameraProjection for OrthographicProjection {
|
||||||
self.right * self.scale,
|
self.right * self.scale,
|
||||||
self.bottom * self.scale,
|
self.bottom * self.scale,
|
||||||
self.top * 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.far,
|
||||||
|
self.near,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,6 +151,10 @@ impl CameraProjection for OrthographicProjection {
|
||||||
fn depth_calculation(&self) -> DepthCalculation {
|
fn depth_calculation(&self) -> DepthCalculation {
|
||||||
self.depth_calculation
|
self.depth_calculation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn far(&self) -> f32 {
|
||||||
|
self.far
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for OrthographicProjection {
|
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 camera;
|
||||||
pub mod color;
|
pub mod color;
|
||||||
pub mod colorspace;
|
|
||||||
pub mod draw;
|
|
||||||
pub mod entity;
|
|
||||||
pub mod mesh;
|
pub mod mesh;
|
||||||
pub mod pass;
|
pub mod options;
|
||||||
pub mod pipeline;
|
pub mod primitives;
|
||||||
|
pub mod render_asset;
|
||||||
|
pub mod render_component;
|
||||||
pub mod render_graph;
|
pub mod render_graph;
|
||||||
|
pub mod render_phase;
|
||||||
|
pub mod render_resource;
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
pub mod shader;
|
|
||||||
pub mod texture;
|
pub mod texture;
|
||||||
pub mod wireframe;
|
pub mod view;
|
||||||
|
|
||||||
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 prelude {
|
pub mod prelude {
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
base::Msaa,
|
camera::{
|
||||||
|
Camera, OrthographicCameraBundle, OrthographicProjection, PerspectiveCameraBundle,
|
||||||
|
PerspectiveProjection,
|
||||||
|
},
|
||||||
color::Color,
|
color::Color,
|
||||||
draw::{Draw, Visible},
|
|
||||||
entity::*,
|
|
||||||
mesh::{shape, Mesh},
|
mesh::{shape, Mesh},
|
||||||
pass::ClearColor,
|
render_resource::Shader,
|
||||||
pipeline::RenderPipelines,
|
texture::Image,
|
||||||
shader::Shader,
|
view::{ComputedVisibility, Msaa, Visibility},
|
||||||
texture::Texture,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
use crate::prelude::*;
|
pub use once_cell;
|
||||||
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;
|
|
||||||
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
|
use crate::{
|
||||||
pub enum RenderSystem {
|
camera::CameraPlugin,
|
||||||
VisibleEntities,
|
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)]
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
|
||||||
pub enum RenderStage {
|
pub enum RenderStage {
|
||||||
/// Stage where render resources are set up
|
/// Extract data from the "app world" and insert it into the "render world".
|
||||||
RenderResource,
|
/// This step should be kept as short as possible to increase the "pipelining potential" for
|
||||||
/// Stage where Render Graph systems are run. In general you shouldn't add systems to this
|
/// running the next frame while rendering the current frame.
|
||||||
/// stage manually.
|
Extract,
|
||||||
RenderGraphSystems,
|
|
||||||
// Stage where draw systems are executed. This is generally where Draw components are setup
|
/// Prepare render resources from the extracted data for the GPU.
|
||||||
Draw,
|
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,
|
Render,
|
||||||
PostRender,
|
|
||||||
|
/// Cleanup render resources here.
|
||||||
|
Cleanup,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds core render types and systems to an App
|
/// The Render App World. This is only available as a resource during the Extract step.
|
||||||
pub struct RenderPlugin {
|
#[derive(Default)]
|
||||||
/// configures the "base render graph". If this is not `None`, the "base render graph" will be
|
pub struct RenderWorld(World);
|
||||||
/// added
|
|
||||||
pub base_render_graph_config: Option<BaseRenderGraphConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for RenderPlugin {
|
impl Deref for RenderWorld {
|
||||||
fn default() -> Self {
|
type Target = World;
|
||||||
RenderPlugin {
|
|
||||||
base_render_graph_config: Some(BaseRenderGraphConfig::default()),
|
fn deref(&self) -> &Self::Target {
|
||||||
}
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
impl Plugin for RenderPlugin {
|
||||||
|
/// Initializes the renderer, sets up the [`RenderStage`](RenderStage) and creates the rendering sub-app.
|
||||||
fn build(&self, app: &mut App) {
|
fn build(&self, app: &mut App) {
|
||||||
#[cfg(any(
|
let options = app
|
||||||
feature = "png",
|
.world
|
||||||
feature = "dds",
|
.get_resource::<options::WgpuOptions>()
|
||||||
feature = "tga",
|
.cloned()
|
||||||
feature = "jpeg",
|
.unwrap_or_default();
|
||||||
feature = "bmp"
|
let instance = wgpu::Instance::new(options.backends);
|
||||||
))]
|
let surface = {
|
||||||
{
|
let world = app.world.cell();
|
||||||
app.init_asset_loader::<ImageTextureLoader>();
|
let windows = world.get_resource_mut::<bevy_window::Windows>().unwrap();
|
||||||
}
|
let raw_handle = windows.get_primary().map(|window| unsafe {
|
||||||
#[cfg(feature = "hdr")]
|
let handle = window.raw_window_handle().get_handle();
|
||||||
{
|
instance.create_surface(&handle)
|
||||||
app.init_asset_loader::<HdrTextureLoader>();
|
});
|
||||||
}
|
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>()
|
||||||
|
.init_asset_loader::<ShaderLoader>()
|
||||||
|
.init_resource::<ScratchRenderWorld>()
|
||||||
|
.register_type::<Color>()
|
||||||
|
.register_type::<Frustum>();
|
||||||
|
let render_pipeline_cache = RenderPipelineCache::new(device.clone());
|
||||||
|
let asset_server = app.world.get_resource::<AssetServer>().unwrap().clone();
|
||||||
|
|
||||||
app.add_stage_after(
|
let mut render_app = App::empty();
|
||||||
AssetStage::AssetEvents,
|
let mut extract_stage =
|
||||||
RenderStage::RenderResource,
|
SystemStage::parallel().with_system(RenderPipelineCache::extract_shaders);
|
||||||
SystemStage::parallel(),
|
// 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
|
||||||
.add_stage_after(
|
extract_stage.set_apply_buffers(false);
|
||||||
RenderStage::RenderResource,
|
render_app
|
||||||
RenderStage::RenderGraphSystems,
|
.add_stage(RenderStage::Extract, extract_stage)
|
||||||
SystemStage::parallel(),
|
.add_stage(RenderStage::Prepare, SystemStage::parallel())
|
||||||
)
|
.add_stage(RenderStage::Queue, SystemStage::parallel())
|
||||||
.add_stage_after(
|
.add_stage(RenderStage::PhaseSort, SystemStage::parallel())
|
||||||
RenderStage::RenderGraphSystems,
|
.add_stage(
|
||||||
RenderStage::Draw,
|
RenderStage::Render,
|
||||||
SystemStage::parallel(),
|
SystemStage::parallel()
|
||||||
)
|
.with_system(RenderPipelineCache::process_pipeline_queue_system)
|
||||||
.add_stage_after(
|
.with_system(render_system.exclusive_system().at_end()),
|
||||||
RenderStage::Draw,
|
)
|
||||||
RenderStage::Render,
|
.add_stage(RenderStage::Cleanup, SystemStage::parallel())
|
||||||
SystemStage::parallel(),
|
.insert_resource(instance)
|
||||||
)
|
.insert_resource(device)
|
||||||
.add_stage_after(
|
.insert_resource(queue)
|
||||||
RenderStage::Render,
|
.insert_resource(render_pipeline_cache)
|
||||||
RenderStage::PostRender,
|
.insert_resource(asset_server)
|
||||||
SystemStage::parallel(),
|
.init_resource::<RenderGraph>();
|
||||||
)
|
|
||||||
.init_asset_loader::<ShaderLoader>()
|
|
||||||
.add_asset::<Mesh>()
|
|
||||||
.add_asset::<Texture>()
|
|
||||||
.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>()
|
|
||||||
.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);
|
|
||||||
|
|
||||||
if let Some(ref config) = self.base_render_graph_config {
|
app.add_sub_app(RenderApp, render_app, move |app_world, render_app| {
|
||||||
crate::base::add_base_graph(config, &mut app.world);
|
#[cfg(feature = "trace")]
|
||||||
let mut active_cameras = app.world.get_resource_mut::<ActiveCameras>().unwrap();
|
let render_span = bevy_utils::tracing::info_span!("renderer subapp");
|
||||||
if config.add_3d_camera {
|
#[cfg(feature = "trace")]
|
||||||
active_cameras.add(base::camera::CAMERA_3D);
|
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>>>) {
|
/// Executes the [`Extract`](RenderStage::Extract) stage of the renderer.
|
||||||
if context.is_none() {
|
/// This updates the render world with the extracted ECS data of the current frame.
|
||||||
warn!(
|
fn extract(app_world: &mut World, render_app: &mut App) {
|
||||||
"bevy_render couldn't find a render backend. Perhaps try adding the bevy_wgpu feature/plugin!"
|
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:
|
/// Example of constructing a mesh:
|
||||||
/// ```
|
/// ```
|
||||||
/// # use bevy_render2::mesh::{Mesh, Indices};
|
/// # use bevy_render::mesh::{Mesh, Indices};
|
||||||
/// # use bevy_render2::render_resource::PrimitiveTopology;
|
/// # use bevy_render::render_resource::PrimitiveTopology;
|
||||||
/// fn create_triangle() -> Mesh {
|
/// fn create_triangle() -> Mesh {
|
||||||
/// let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
|
/// 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_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 mod shape;
|
||||||
|
|
||||||
pub use mesh::*;
|
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::{
|
use crate::mesh::{Indices, Mesh};
|
||||||
mesh::{Indices, Mesh},
|
|
||||||
pipeline::PrimitiveTopology,
|
|
||||||
};
|
|
||||||
use bevy_math::{Vec2, Vec3};
|
use bevy_math::{Vec2, Vec3};
|
||||||
|
use wgpu::PrimitiveTopology;
|
||||||
|
|
||||||
/// A cylinder with hemispheres at the top and bottom
|
/// A cylinder with hemispheres at the top and bottom
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
|
use crate::mesh::{Indices, Mesh};
|
||||||
use hexasphere::shapes::IcoSphere;
|
use hexasphere::shapes::IcoSphere;
|
||||||
|
use wgpu::PrimitiveTopology;
|
||||||
use crate::{
|
|
||||||
mesh::{Indices, Mesh},
|
|
||||||
pipeline::PrimitiveTopology,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A sphere made from a subdivided Icosahedron.
|
/// A sphere made from a subdivided Icosahedron.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use super::{Indices, Mesh};
|
use super::{Indices, Mesh};
|
||||||
use crate::pipeline::PrimitiveTopology;
|
|
||||||
use bevy_math::*;
|
use bevy_math::*;
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[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)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct Box {
|
pub struct Box {
|
||||||
pub min_x: f32,
|
pub min_x: f32,
|
||||||
|
@ -38,6 +38,7 @@ pub struct Box {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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 {
|
pub fn new(x_length: f32, y_length: f32, z_length: f32) -> Box {
|
||||||
Box {
|
Box {
|
||||||
max_x: x_length / 2.0,
|
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)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct Quad {
|
pub struct Quad {
|
||||||
/// Full width and height of the rectangle.
|
/// 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)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub struct Plane {
|
pub struct Plane {
|
||||||
/// The total side length of the square.
|
/// The total side length of the square.
|
||||||
|
@ -274,3 +275,4 @@ pub use capsule::{Capsule, CapsuleUvProfile};
|
||||||
pub use icosphere::Icosphere;
|
pub use icosphere::Icosphere;
|
||||||
pub use torus::Torus;
|
pub use torus::Torus;
|
||||||
pub use uvsphere::UVSphere;
|
pub use uvsphere::UVSphere;
|
||||||
|
use wgpu::PrimitiveTopology;
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
use crate::{
|
use crate::mesh::{Indices, Mesh};
|
||||||
mesh::{Indices, Mesh},
|
|
||||||
pipeline::PrimitiveTopology,
|
|
||||||
};
|
|
||||||
use bevy_math::Vec3;
|
use bevy_math::Vec3;
|
||||||
|
use wgpu::PrimitiveTopology;
|
||||||
|
|
||||||
/// A torus (donut) shape.
|
/// A torus (donut) shape.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
use crate::{
|
use wgpu::PrimitiveTopology;
|
||||||
mesh::{Indices, Mesh},
|
|
||||||
pipeline::PrimitiveTopology,
|
use crate::mesh::{Indices, Mesh};
|
||||||
};
|
|
||||||
use std::f32::consts::PI;
|
use std::f32::consts::PI;
|
||||||
|
|
||||||
/// A sphere made of sectors and stacks
|
/// A sphere made of sectors and stacks.
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct UVSphere {
|
pub struct UVSphere {
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
|
||||||
pub use wgpu::{Backends, Features, Limits, PowerPreference};
|
pub use wgpu::{Backends, Features as WgpuFeatures, Limits as WgpuLimits, PowerPreference};
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct WgpuOptions {
|
pub struct WgpuOptions {
|
||||||
pub device_label: Option<Cow<'static, str>>,
|
pub device_label: Option<Cow<'static, str>>,
|
||||||
pub backends: Backends,
|
pub backends: Backends,
|
||||||
pub power_preference: PowerPreference,
|
pub power_preference: PowerPreference,
|
||||||
pub features: Features,
|
pub features: WgpuFeatures,
|
||||||
pub limits: Limits,
|
pub limits: WgpuLimits,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WgpuOptions {
|
impl Default for WgpuOptions {
|
||||||
|
@ -24,7 +24,14 @@ impl Default for WgpuOptions {
|
||||||
let limits = if cfg!(target_arch = "wasm32") {
|
let limits = if cfg!(target_arch = "wasm32") {
|
||||||
wgpu::Limits::downlevel_webgl2_defaults()
|
wgpu::Limits::downlevel_webgl2_defaults()
|
||||||
} else {
|
} 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 {
|
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,
|
pub normal_d: Vec4,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Component, Clone, Copy, Debug, Default)]
|
#[derive(Component, Clone, Copy, Debug, Default, Reflect)]
|
||||||
|
#[reflect(Component)]
|
||||||
pub struct Frustum {
|
pub struct Frustum {
|
||||||
|
#[reflect(ignore)]
|
||||||
pub planes: [Plane; 6],
|
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;
|
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)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub enum Edge {
|
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 {
|
SlotEdge {
|
||||||
input_node: NodeId,
|
input_node: NodeId,
|
||||||
input_index: usize,
|
input_index: usize,
|
||||||
output_node: NodeId,
|
output_node: NodeId,
|
||||||
output_index: usize,
|
output_index: usize,
|
||||||
},
|
},
|
||||||
|
/// An edge describing to ordering of both nodes (`output_node` before `input_node`).
|
||||||
NodeEdge {
|
NodeEdge {
|
||||||
input_node: NodeId,
|
input_node: NodeId,
|
||||||
output_node: NodeId,
|
output_node: NodeId,
|
||||||
|
@ -15,6 +32,7 @@ pub enum Edge {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Edge {
|
impl Edge {
|
||||||
|
/// Returns the id of the 'input_node'.
|
||||||
pub fn get_input_node(&self) -> NodeId {
|
pub fn get_input_node(&self) -> NodeId {
|
||||||
match self {
|
match self {
|
||||||
Edge::SlotEdge { input_node, .. } => *input_node,
|
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 {
|
pub fn get_output_node(&self) -> NodeId {
|
||||||
match self {
|
match self {
|
||||||
Edge::SlotEdge { output_node, .. } => *output_node,
|
Edge::SlotEdge { output_node, .. } => *output_node,
|
||||||
|
|
|
@ -1,32 +1,93 @@
|
||||||
use super::{Edge, Node, NodeId, NodeLabel, NodeState, RenderGraphError, SlotLabel, SystemNode};
|
use crate::{
|
||||||
use bevy_ecs::{
|
render_graph::{
|
||||||
schedule::{Schedule, StageLabel, SystemStage},
|
Edge, Node, NodeId, NodeLabel, NodeRunError, NodeState, RenderGraphContext,
|
||||||
world::World,
|
RenderGraphError, SlotInfo, SlotLabel,
|
||||||
|
},
|
||||||
|
renderer::RenderContext,
|
||||||
};
|
};
|
||||||
|
use bevy_ecs::prelude::World;
|
||||||
use bevy_utils::HashMap;
|
use bevy_utils::HashMap;
|
||||||
use std::{borrow::Cow, fmt::Debug};
|
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 {
|
pub struct RenderGraph {
|
||||||
nodes: HashMap<NodeId, NodeState>,
|
nodes: HashMap<NodeId, NodeState>,
|
||||||
node_names: HashMap<Cow<'static, str>, NodeId>,
|
node_names: HashMap<Cow<'static, str>, NodeId>,
|
||||||
system_node_schedule: Option<Schedule>,
|
sub_graphs: HashMap<Cow<'static, str>, RenderGraph>,
|
||||||
}
|
input_node: Option<NodeId>,
|
||||||
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
|
|
||||||
struct RenderGraphUpdate;
|
|
||||||
|
|
||||||
impl Default for RenderGraph {
|
|
||||||
fn default() -> Self {
|
|
||||||
let mut schedule = Schedule::default();
|
|
||||||
schedule.add_stage(RenderGraphUpdate, SystemStage::parallel());
|
|
||||||
Self {
|
|
||||||
nodes: Default::default(),
|
|
||||||
node_names: Default::default(),
|
|
||||||
system_node_schedule: Some(schedule),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderGraph {
|
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
|
pub fn add_node<T>(&mut self, name: impl Into<Cow<'static, str>>, node: T) -> NodeId
|
||||||
where
|
where
|
||||||
T: Node,
|
T: Node,
|
||||||
|
@ -40,18 +101,7 @@ impl RenderGraph {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_system_node<T>(&mut self, name: impl Into<Cow<'static, str>>, node: T) -> NodeId
|
/// Retrieves the [`NodeState`] referenced by the `label`.
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_node_state(
|
pub fn get_node_state(
|
||||||
&self,
|
&self,
|
||||||
label: impl Into<NodeLabel>,
|
label: impl Into<NodeLabel>,
|
||||||
|
@ -63,6 +113,7 @@ impl RenderGraph {
|
||||||
.ok_or(RenderGraphError::InvalidNode(label))
|
.ok_or(RenderGraphError::InvalidNode(label))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves the [`NodeState`] referenced by the `label` mutably.
|
||||||
pub fn get_node_state_mut(
|
pub fn get_node_state_mut(
|
||||||
&mut self,
|
&mut self,
|
||||||
label: impl Into<NodeLabel>,
|
label: impl Into<NodeLabel>,
|
||||||
|
@ -74,6 +125,7 @@ impl RenderGraph {
|
||||||
.ok_or(RenderGraphError::InvalidNode(label))
|
.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> {
|
pub fn get_node_id(&self, label: impl Into<NodeLabel>) -> Result<NodeId, RenderGraphError> {
|
||||||
let label = label.into();
|
let label = label.into();
|
||||||
match label {
|
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>
|
pub fn get_node<T>(&self, label: impl Into<NodeLabel>) -> Result<&T, RenderGraphError>
|
||||||
where
|
where
|
||||||
T: Node,
|
T: Node,
|
||||||
|
@ -93,6 +146,7 @@ impl RenderGraph {
|
||||||
self.get_node_state(label).and_then(|n| n.node())
|
self.get_node_state(label).and_then(|n| n.node())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves the [`Node`] referenced by the `label` mutably.
|
||||||
pub fn get_node_mut<T>(
|
pub fn get_node_mut<T>(
|
||||||
&mut self,
|
&mut self,
|
||||||
label: impl Into<NodeLabel>,
|
label: impl Into<NodeLabel>,
|
||||||
|
@ -103,6 +157,8 @@ impl RenderGraph {
|
||||||
self.get_node_state_mut(label).and_then(|n| n.node_mut())
|
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(
|
pub fn add_slot_edge(
|
||||||
&mut self,
|
&mut self,
|
||||||
output_node: impl Into<NodeLabel>,
|
output_node: impl Into<NodeLabel>,
|
||||||
|
@ -110,17 +166,21 @@ impl RenderGraph {
|
||||||
input_node: impl Into<NodeLabel>,
|
input_node: impl Into<NodeLabel>,
|
||||||
input_slot: impl Into<SlotLabel>,
|
input_slot: impl Into<SlotLabel>,
|
||||||
) -> Result<(), RenderGraphError> {
|
) -> 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 output_node_id = self.get_node_id(output_node)?;
|
||||||
let input_node_id = self.get_node_id(input_node)?;
|
let input_node_id = self.get_node_id(input_node)?;
|
||||||
|
|
||||||
let output_index = self
|
let output_index = self
|
||||||
.get_node_state(output_node_id)?
|
.get_node_state(output_node_id)?
|
||||||
.output_slots
|
.output_slots
|
||||||
.get_slot_index(output_slot)?;
|
.get_slot_index(output_slot.clone())
|
||||||
|
.ok_or(RenderGraphError::InvalidOutputNodeSlot(output_slot))?;
|
||||||
let input_index = self
|
let input_index = self
|
||||||
.get_node_state(input_node_id)?
|
.get_node_state(input_node_id)?
|
||||||
.input_slots
|
.input_slots
|
||||||
.get_slot_index(input_slot)?;
|
.get_slot_index(input_slot.clone())
|
||||||
|
.ok_or(RenderGraphError::InvalidInputNodeSlot(input_slot))?;
|
||||||
|
|
||||||
let edge = Edge::SlotEdge {
|
let edge = Edge::SlotEdge {
|
||||||
output_node: output_node_id,
|
output_node: output_node_id,
|
||||||
|
@ -141,6 +201,8 @@ impl RenderGraph {
|
||||||
Ok(())
|
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(
|
pub fn add_node_edge(
|
||||||
&mut self,
|
&mut self,
|
||||||
output_node: impl Into<NodeLabel>,
|
output_node: impl Into<NodeLabel>,
|
||||||
|
@ -166,6 +228,8 @@ impl RenderGraph {
|
||||||
Ok(())
|
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> {
|
pub fn validate_edge(&mut self, edge: &Edge) -> Result<(), RenderGraphError> {
|
||||||
if self.has_edge(edge) {
|
if self.has_edge(edge) {
|
||||||
return Err(RenderGraphError::EdgeAlreadyExists(edge.clone()));
|
return Err(RenderGraphError::EdgeAlreadyExists(edge.clone()));
|
||||||
|
@ -181,8 +245,15 @@ impl RenderGraph {
|
||||||
let output_node_state = self.get_node_state(output_node)?;
|
let output_node_state = self.get_node_state(output_node)?;
|
||||||
let input_node_state = self.get_node_state(input_node)?;
|
let input_node_state = self.get_node_state(input_node)?;
|
||||||
|
|
||||||
let output_slot = output_node_state.output_slots.get_slot(output_index)?;
|
let output_slot = output_node_state
|
||||||
let input_slot = input_node_state.input_slots.get_slot(input_index)?;
|
.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 {
|
if let Some(Edge::SlotEdge {
|
||||||
output_node: current_output_node,
|
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 {
|
return Err(RenderGraphError::MismatchedNodeSlots {
|
||||||
output_node,
|
output_node,
|
||||||
output_slot: output_index,
|
output_slot: output_index,
|
||||||
|
@ -220,6 +291,7 @@ impl RenderGraph {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether the `edge` already exists in the graph.
|
||||||
pub fn has_edge(&self, edge: &Edge) -> bool {
|
pub fn has_edge(&self, edge: &Edge) -> bool {
|
||||||
let output_node_state = self.get_node_state(edge.get_output_node());
|
let output_node_state = self.get_node_state(edge.get_output_node());
|
||||||
let input_node_state = self.get_node_state(edge.get_input_node());
|
let input_node_state = self.get_node_state(edge.get_input_node());
|
||||||
|
@ -236,22 +308,32 @@ impl RenderGraph {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn take_schedule(&mut self) -> Option<Schedule> {
|
/// Returns an iterator over the [`NodeStates`](NodeState).
|
||||||
self.system_node_schedule.take()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_schedule(&mut self, schedule: Schedule) {
|
|
||||||
self.system_node_schedule = Some(schedule);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn iter_nodes(&self) -> impl Iterator<Item = &NodeState> {
|
pub fn iter_nodes(&self) -> impl Iterator<Item = &NodeState> {
|
||||||
self.nodes.values()
|
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> {
|
pub fn iter_nodes_mut(&mut self) -> impl Iterator<Item = &mut NodeState> {
|
||||||
self.nodes.values_mut()
|
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(
|
pub fn iter_node_inputs(
|
||||||
&self,
|
&self,
|
||||||
label: impl Into<NodeLabel>,
|
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(
|
pub fn iter_node_outputs(
|
||||||
&self,
|
&self,
|
||||||
label: impl Into<NodeLabel>,
|
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())))
|
.map(move |(edge, input_node_id)| (edge, self.get_node_state(input_node_id).unwrap())))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prepare(&mut self, world: &mut World) {
|
/// Adds the `sub_graph` with the `name` to the graph.
|
||||||
for node in self.nodes.values_mut() {
|
/// If the name is already present replaces it instead.
|
||||||
node.node.prepare(world);
|
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::RenderGraph;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
render_graph::{Edge, Node, NodeId, RenderGraphError, ResourceSlotInfo, ResourceSlots},
|
render_graph::{
|
||||||
renderer::{RenderContext, RenderResourceType},
|
Edge, Node, NodeId, NodeRunError, RenderGraph, RenderGraphContext, RenderGraphError,
|
||||||
|
SlotInfo, SlotType,
|
||||||
|
},
|
||||||
|
renderer::RenderContext,
|
||||||
};
|
};
|
||||||
use bevy_ecs::world::World;
|
use bevy_ecs::world::World;
|
||||||
use bevy_utils::HashSet;
|
use bevy_utils::HashSet;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct TestNode {
|
struct TestNode {
|
||||||
inputs: Vec<ResourceSlotInfo>,
|
inputs: Vec<SlotInfo>,
|
||||||
outputs: Vec<ResourceSlotInfo>,
|
outputs: Vec<SlotInfo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestNode {
|
impl TestNode {
|
||||||
pub fn new(inputs: usize, outputs: usize) -> Self {
|
pub fn new(inputs: usize, outputs: usize) -> Self {
|
||||||
TestNode {
|
TestNode {
|
||||||
inputs: (0..inputs)
|
inputs: (0..inputs)
|
||||||
.map(|i| ResourceSlotInfo {
|
.map(|i| SlotInfo::new(format!("in_{}", i), SlotType::TextureView))
|
||||||
name: format!("in_{}", i).into(),
|
|
||||||
resource_type: RenderResourceType::Texture,
|
|
||||||
})
|
|
||||||
.collect(),
|
.collect(),
|
||||||
outputs: (0..outputs)
|
outputs: (0..outputs)
|
||||||
.map(|i| ResourceSlotInfo {
|
.map(|i| SlotInfo::new(format!("out_{}", i), SlotType::TextureView))
|
||||||
name: format!("out_{}", i).into(),
|
|
||||||
resource_type: RenderResourceType::Texture,
|
|
||||||
})
|
|
||||||
.collect(),
|
.collect(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node for TestNode {
|
impl Node for TestNode {
|
||||||
fn input(&self) -> &[ResourceSlotInfo] {
|
fn input(&self) -> Vec<SlotInfo> {
|
||||||
&self.inputs
|
self.inputs.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn output(&self) -> &[ResourceSlotInfo] {
|
fn output(&self) -> Vec<SlotInfo> {
|
||||||
&self.outputs
|
self.outputs.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update(
|
fn run(
|
||||||
&mut self,
|
&self,
|
||||||
|
_: &mut RenderGraphContext,
|
||||||
|
_: &mut RenderContext,
|
||||||
_: &World,
|
_: &World,
|
||||||
_: &mut dyn RenderContext,
|
) -> Result<(), NodeRunError> {
|
||||||
_: &ResourceSlots,
|
Ok(())
|
||||||
_: &mut ResourceSlots,
|
|
||||||
) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,13 +535,13 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Node for MyNode {
|
impl Node for MyNode {
|
||||||
fn update(
|
fn run(
|
||||||
&mut self,
|
&self,
|
||||||
|
_: &mut RenderGraphContext,
|
||||||
|
_: &mut RenderContext,
|
||||||
_: &World,
|
_: &World,
|
||||||
_: &mut dyn RenderContext,
|
) -> Result<(), NodeRunError> {
|
||||||
_: &ResourceSlots,
|
Ok(())
|
||||||
_: &mut ResourceSlots,
|
|
||||||
) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,21 +1,14 @@
|
||||||
pub mod base;
|
mod context;
|
||||||
mod command;
|
|
||||||
mod edge;
|
mod edge;
|
||||||
mod graph;
|
mod graph;
|
||||||
mod node;
|
mod node;
|
||||||
mod node_slot;
|
mod node_slot;
|
||||||
mod nodes;
|
|
||||||
mod schedule;
|
|
||||||
mod system;
|
|
||||||
|
|
||||||
pub use command::*;
|
pub use context::*;
|
||||||
pub use edge::*;
|
pub use edge::*;
|
||||||
pub use graph::*;
|
pub use graph::*;
|
||||||
pub use node::*;
|
pub use node::*;
|
||||||
pub use node_slot::*;
|
pub use node_slot::*;
|
||||||
pub use nodes::*;
|
|
||||||
pub use schedule::*;
|
|
||||||
pub use system::*;
|
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
@ -23,8 +16,10 @@ use thiserror::Error;
|
||||||
pub enum RenderGraphError {
|
pub enum RenderGraphError {
|
||||||
#[error("node does not exist")]
|
#[error("node does not exist")]
|
||||||
InvalidNode(NodeLabel),
|
InvalidNode(NodeLabel),
|
||||||
#[error("node slot does not exist")]
|
#[error("output node slot does not exist")]
|
||||||
InvalidNodeSlot(SlotLabel),
|
InvalidOutputNodeSlot(SlotLabel),
|
||||||
|
#[error("input node slot does not exist")]
|
||||||
|
InvalidInputNodeSlot(SlotLabel),
|
||||||
#[error("node does not match the given type")]
|
#[error("node does not match the given type")]
|
||||||
WrongNodeType,
|
WrongNodeType,
|
||||||
#[error("attempted to connect a node output slot to an incompatible input node slot")]
|
#[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::{
|
||||||
use crate::renderer::RenderContext;
|
render_graph::{
|
||||||
use bevy_ecs::{system::BoxedSystem, world::World};
|
Edge, InputSlotError, OutputSlotError, RenderGraphContext, RenderGraphError,
|
||||||
|
RunSubGraphError, SlotInfo, SlotInfos,
|
||||||
|
},
|
||||||
|
renderer::RenderContext,
|
||||||
|
};
|
||||||
|
use bevy_ecs::world::World;
|
||||||
use bevy_utils::Uuid;
|
use bevy_utils::Uuid;
|
||||||
use downcast_rs::{impl_downcast, Downcast};
|
use downcast_rs::{impl_downcast, Downcast};
|
||||||
use std::{borrow::Cow, fmt::Debug};
|
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)]
|
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub struct NodeId(Uuid);
|
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 {
|
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
|
/// Updates internal node state using the current render [`World`] prior to the run method.
|
||||||
/// [Node::update] is called.
|
fn update(&mut self, _world: &mut World) {}
|
||||||
fn prepare(&mut self, _world: &mut World) {}
|
|
||||||
|
|
||||||
/// Run the graph node logic. This runs once per graph run after [Node::prepare] has been called
|
/// Runs the graph node logic, issues draw calls, updates the output slots and
|
||||||
/// on all nodes.
|
/// optionally queues up subgraphs for execution. The graph data, input and output values are
|
||||||
fn update(
|
/// passed via the [`RenderGraphContext`].
|
||||||
&mut self,
|
fn run(
|
||||||
|
&self,
|
||||||
|
graph: &mut RenderGraphContext,
|
||||||
|
render_context: &mut RenderContext,
|
||||||
world: &World,
|
world: &World,
|
||||||
render_context: &mut dyn RenderContext,
|
) -> Result<(), NodeRunError>;
|
||||||
input: &ResourceSlots,
|
|
||||||
output: &mut ResourceSlots,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_downcast!(Node);
|
impl_downcast!(Node);
|
||||||
|
|
||||||
pub trait SystemNode: Node {
|
#[derive(Error, Debug, Eq, PartialEq)]
|
||||||
fn get_system(&self) -> BoxedSystem;
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct Edges {
|
pub struct Edges {
|
||||||
pub id: NodeId,
|
pub id: NodeId,
|
||||||
|
@ -57,6 +89,7 @@ pub struct Edges {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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> {
|
pub(crate) fn add_input_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> {
|
||||||
if self.has_input_edge(&edge) {
|
if self.has_input_edge(&edge) {
|
||||||
return Err(RenderGraphError::EdgeAlreadyExists(edge));
|
return Err(RenderGraphError::EdgeAlreadyExists(edge));
|
||||||
|
@ -65,6 +98,7 @@ impl Edges {
|
||||||
Ok(())
|
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> {
|
pub(crate) fn add_output_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> {
|
||||||
if self.has_output_edge(&edge) {
|
if self.has_output_edge(&edge) {
|
||||||
return Err(RenderGraphError::EdgeAlreadyExists(edge));
|
return Err(RenderGraphError::EdgeAlreadyExists(edge));
|
||||||
|
@ -73,14 +107,18 @@ impl Edges {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether the input edge already exists.
|
||||||
pub fn has_input_edge(&self, edge: &Edge) -> bool {
|
pub fn has_input_edge(&self, edge: &Edge) -> bool {
|
||||||
self.input_edges.contains(edge)
|
self.input_edges.contains(edge)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Checks whether the output edge already exists.
|
||||||
pub fn has_output_edge(&self, edge: &Edge) -> bool {
|
pub fn has_output_edge(&self, edge: &Edge) -> bool {
|
||||||
self.output_edges.contains(edge)
|
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> {
|
pub fn get_input_slot_edge(&self, index: usize) -> Result<&Edge, RenderGraphError> {
|
||||||
self.input_edges
|
self.input_edges
|
||||||
.iter()
|
.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> {
|
pub fn get_output_slot_edge(&self, index: usize) -> Result<&Edge, RenderGraphError> {
|
||||||
self.output_edges
|
self.output_edges
|
||||||
.iter()
|
.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 struct NodeState {
|
||||||
pub id: NodeId,
|
pub id: NodeId,
|
||||||
pub name: Option<Cow<'static, str>>,
|
pub name: Option<Cow<'static, str>>,
|
||||||
|
/// The name of the type that implements [`Node`].
|
||||||
pub type_name: &'static str,
|
pub type_name: &'static str,
|
||||||
pub node: Box<dyn Node>,
|
pub node: Box<dyn Node>,
|
||||||
pub input_slots: ResourceSlots,
|
pub input_slots: SlotInfos,
|
||||||
pub output_slots: ResourceSlots,
|
pub output_slots: SlotInfos,
|
||||||
pub edges: Edges,
|
pub edges: Edges,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,6 +176,8 @@ impl Debug for NodeState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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
|
pub fn new<T>(id: NodeId, node: T) -> Self
|
||||||
where
|
where
|
||||||
T: Node,
|
T: Node,
|
||||||
|
@ -138,8 +185,8 @@ impl NodeState {
|
||||||
NodeState {
|
NodeState {
|
||||||
id,
|
id,
|
||||||
name: None,
|
name: None,
|
||||||
input_slots: ResourceSlots::from(node.input()),
|
input_slots: node.input().into(),
|
||||||
output_slots: ResourceSlots::from(node.output()),
|
output_slots: node.output().into(),
|
||||||
node: Box::new(node),
|
node: Box::new(node),
|
||||||
type_name: std::any::type_name::<T>(),
|
type_name: std::any::type_name::<T>(),
|
||||||
edges: Edges {
|
edges: Edges {
|
||||||
|
@ -150,6 +197,7 @@ impl NodeState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves the [`Node`].
|
||||||
pub fn node<T>(&self) -> Result<&T, RenderGraphError>
|
pub fn node<T>(&self) -> Result<&T, RenderGraphError>
|
||||||
where
|
where
|
||||||
T: Node,
|
T: Node,
|
||||||
|
@ -159,6 +207,7 @@ impl NodeState {
|
||||||
.ok_or(RenderGraphError::WrongNodeType)
|
.ok_or(RenderGraphError::WrongNodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves the [`Node`] mutably.
|
||||||
pub fn node_mut<T>(&mut self) -> Result<&mut T, RenderGraphError>
|
pub fn node_mut<T>(&mut self) -> Result<&mut T, RenderGraphError>
|
||||||
where
|
where
|
||||||
T: Node,
|
T: Node,
|
||||||
|
@ -168,14 +217,7 @@ impl NodeState {
|
||||||
.ok_or(RenderGraphError::WrongNodeType)
|
.ok_or(RenderGraphError::WrongNodeType)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_output_slots(&self) -> Result<(), RenderGraphError> {
|
/// Validates that each input slot corresponds to an input edge.
|
||||||
for i in 0..self.output_slots.len() {
|
|
||||||
self.edges.get_output_slot_edge(i)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn validate_input_slots(&self) -> Result<(), RenderGraphError> {
|
pub fn validate_input_slots(&self) -> Result<(), RenderGraphError> {
|
||||||
for i in 0..self.input_slots.len() {
|
for i in 0..self.input_slots.len() {
|
||||||
self.edges.get_input_slot_edge(i)?;
|
self.edges.get_input_slot_edge(i)?;
|
||||||
|
@ -183,8 +225,19 @@ impl NodeState {
|
||||||
|
|
||||||
Ok(())
|
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)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum NodeLabel {
|
pub enum NodeLabel {
|
||||||
Id(NodeId),
|
Id(NodeId),
|
||||||
|
@ -214,3 +267,19 @@ impl From<NodeId> for NodeLabel {
|
||||||
NodeLabel::Id(value)
|
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 bevy_ecs::entity::Entity;
|
||||||
use crate::renderer::{RenderResourceId, RenderResourceType};
|
|
||||||
use std::borrow::Cow;
|
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)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ResourceSlot {
|
pub enum SlotValue {
|
||||||
pub resource: Option<RenderResourceId>,
|
/// A GPU-accessible [`Buffer`].
|
||||||
pub info: ResourceSlotInfo,
|
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)]
|
impl SlotValue {
|
||||||
pub struct ResourceSlots {
|
/// Returns the [`SlotType`] of this value.
|
||||||
slots: Vec<ResourceSlot>,
|
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)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum SlotLabel {
|
pub enum SlotLabel {
|
||||||
Index(usize),
|
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 {
|
impl From<usize> for SlotLabel {
|
||||||
fn from(value: usize) -> Self {
|
fn from(value: usize) -> Self {
|
||||||
SlotLabel::Index(value)
|
SlotLabel::Index(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ResourceSlots {
|
/// The internal representation of a slot, which specifies its [`SlotType`] and name.
|
||||||
pub fn set(&mut self, label: impl Into<SlotLabel>, resource: RenderResourceId) {
|
#[derive(Clone, Debug)]
|
||||||
let mut slot = self.get_slot_mut(label).unwrap();
|
pub struct SlotInfo {
|
||||||
slot.resource = Some(resource);
|
pub name: Cow<'static, str>,
|
||||||
}
|
pub slot_type: SlotType,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get(&self, label: impl Into<SlotLabel>) -> Option<RenderResourceId> {
|
impl SlotInfo {
|
||||||
let slot = self.get_slot(label).unwrap();
|
pub fn new(name: impl Into<Cow<'static, str>>, slot_type: SlotType) -> Self {
|
||||||
slot.resource.clone()
|
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))
|
|
||||||
}
|
|
||||||
|
|
||||||
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> {
|
/// A collection of input or output [`SlotInfos`](SlotInfo) for
|
||||||
self.slots.iter()
|
/// a [`NodeState`](super::NodeState).
|
||||||
}
|
#[derive(Default, Debug)]
|
||||||
|
pub struct SlotInfos {
|
||||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut ResourceSlot> {
|
slots: Vec<SlotInfo>,
|
||||||
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 {
|
pub fn len(&self) -> usize {
|
||||||
self.slots.len()
|
self.slots.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns true if there are no slots.
|
||||||
|
#[inline]
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.slots.is_empty()
|
self.slots.is_empty()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&ResourceSlotInfo> for ResourceSlot {
|
/// Retrieves the [`SlotInfo`] for the provided label.
|
||||||
fn from(slot: &ResourceSlotInfo) -> Self {
|
pub fn get_slot(&self, label: impl Into<SlotLabel>) -> Option<&SlotInfo> {
|
||||||
ResourceSlot {
|
let label = label.into();
|
||||||
resource: None,
|
let index = self.get_slot_index(&label)?;
|
||||||
info: slot.clone(),
|
self.slots.get(index)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&[ResourceSlotInfo]> for ResourceSlots {
|
/// Retrieves the [`SlotInfo`] for the provided label mutably.
|
||||||
fn from(slots: &[ResourceSlotInfo]) -> Self {
|
pub fn get_slot_mut(&mut self, label: impl Into<SlotLabel>) -> Option<&mut SlotInfo> {
|
||||||
ResourceSlots {
|
let label = label.into();
|
||||||
slots: slots
|
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()
|
.iter()
|
||||||
.map(|s| s.into())
|
.enumerate()
|
||||||
.collect::<Vec<ResourceSlot>>(),
|
.find(|(_i, s)| s.name == *name)
|
||||||
|
.map(|(i, _s)| i),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
/// Returns an iterator over the slot infos.
|
||||||
pub struct ResourceSlotInfo {
|
pub fn iter(&self) -> impl Iterator<Item = &SlotInfo> {
|
||||||
pub name: Cow<'static, str>,
|
self.slots.iter()
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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