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:
Carter Anderson 2021-12-14 03:58:23 +00:00
parent de8edd3165
commit ffecb05a0a
329 changed files with 4560 additions and 30649 deletions

View file

@ -242,13 +242,13 @@ jobs:
sed -i "s|PATH_TO_CHANGE|$curr|" vk_swiftshader_icd.json;
- name: Build bevy
run: |
cargo build --no-default-features --features "bevy_dynamic_plugin,bevy_gilrs,bevy_gltf,bevy_wgpu,bevy_winit,render,png,hdr,x11,bevy_ci_testing"
cargo build --no-default-features --features "bevy_dynamic_plugin,bevy_gilrs,bevy_gltf,bevy_winit,render,png,hdr,x11,bevy_ci_testing"
- name: Run examples
run: |
for example in .github/example-run/*.ron; do
example_name=`basename $example .ron`
echo "running $example_name - "`date`
time CI_TESTING_CONFIG=$example VK_ICD_FILENAMES=$(pwd)/vk_swiftshader_icd.json DRI_PRIME=0 xvfb-run cargo run --example $example_name --no-default-features --features "bevy_dynamic_plugin,bevy_gilrs,bevy_gltf,bevy_wgpu,bevy_winit,render,png,hdr,x11,bevy_ci_testing"
time CI_TESTING_CONFIG=$example VK_ICD_FILENAMES=$(pwd)/vk_swiftshader_icd.json DRI_PRIME=0 xvfb-run cargo run --example $example_name --no-default-features --features "bevy_dynamic_plugin,bevy_gilrs,bevy_gltf,bevy_winit,render,png,hdr,x11,bevy_ci_testing"
sleep 10
done

View file

@ -13,20 +13,12 @@ repository = "https://github.com/bevyengine/bevy"
[workspace]
exclude = ["benches", "crates/bevy_ecs_compile_fail_tests"]
members = ["crates/*", "pipelined/*", "examples/ios", "tools/ci", "errors"]
members = ["crates/*", "examples/ios", "tools/ci", "errors"]
[features]
default = [
"bevy_audio",
"bevy_core_pipeline",
"bevy_gilrs",
"bevy_gltf2",
"bevy_wgpu",
"bevy_sprite2",
"bevy_render2",
"bevy_pbr2",
"bevy_ui2",
"bevy_text2",
"bevy_winit",
"render",
"png",
@ -39,9 +31,11 @@ default = [
# Force dynamic linking, which improves iterative compile times
dynamic = ["bevy_dylib"]
# Rendering support (Also needs the bevy_wgpu feature or a third-party rendering backend)
# Rendering support
render = [
"bevy_internal/bevy_core_pipeline",
"bevy_internal/bevy_pbr",
"bevy_internal/bevy_gltf",
"bevy_internal/bevy_render",
"bevy_internal/bevy_sprite",
"bevy_internal/bevy_text",
@ -50,20 +44,16 @@ render = [
# Optional bevy crates
bevy_audio = ["bevy_internal/bevy_audio"]
bevy_core_pipeline = ["bevy_internal/bevy_core_pipeline"]
bevy_render = ["bevy_internal/bevy_render"]
bevy_text = ["bevy_internal/bevy_text"]
bevy_pbr = ["bevy_internal/bevy_pbr"]
bevy_sprite = ["bevy_internal/bevy_sprite"]
bevy_dynamic_plugin = ["bevy_internal/bevy_dynamic_plugin"]
bevy_gilrs = ["bevy_internal/bevy_gilrs"]
bevy_gltf = ["bevy_internal/bevy_gltf"]
bevy_wgpu = ["bevy_internal/bevy_wgpu"]
bevy_winit = ["bevy_internal/bevy_winit"]
bevy_core_pipeline = ["bevy_internal/bevy_core_pipeline"]
bevy_render2 = ["bevy_internal/bevy_render2"]
bevy_sprite2 = ["bevy_internal/bevy_sprite2"]
bevy_pbr2 = ["bevy_internal/bevy_pbr2"]
bevy_gltf2 = ["bevy_internal/bevy_gltf2"]
bevy_ui2 = ["bevy_internal/bevy_ui2"]
bevy_text2 = ["bevy_internal/bevy_text2"]
trace_chrome = ["bevy_internal/trace_chrome"]
trace_tracy = ["bevy_internal/trace_tracy"]
trace = ["bevy_internal/trace"]
@ -120,10 +110,6 @@ path = "examples/hello_world.rs"
name = "contributors"
path = "examples/2d/contributors.rs"
[[example]]
name = "mesh"
path = "examples/2d/mesh.rs"
[[example]]
name = "many_sprites"
path = "examples/2d/many_sprites.rs"
@ -144,60 +130,35 @@ path = "examples/2d/sprite_sheet.rs"
name = "text2d"
path = "examples/2d/text2d.rs"
[[example]]
name = "text2d_pipelined"
path = "examples/2d/text2d_pipelined.rs"
[[example]]
name = "texture_atlas"
path = "examples/2d/texture_atlas.rs"
[[example]]
name = "pipelined_texture_atlas"
path = "examples/2d/pipelined_texture_atlas.rs"
# 3D Rendering
[[example]]
name = "3d_scene"
path = "examples/3d/3d_scene.rs"
[[example]]
name = "3d_scene_pipelined"
path = "examples/3d/3d_scene_pipelined.rs"
[[example]]
name = "many_cubes_pipelined"
path = "examples/3d/many_cubes_pipelined.rs"
[[example]]
name = "cornell_box_pipelined"
path = "examples/3d/cornell_box_pipelined.rs"
name = "lighting"
path = "examples/3d/lighting.rs"
[[example]]
name = "load_gltf"
path = "examples/3d/load_gltf.rs"
required-features = ["bevy_gltf"]
[[example]]
name = "load_gltf_pipelined"
path = "examples/3d/load_gltf_pipelined.rs"
name = "many_cubes"
path = "examples/3d/many_cubes.rs"
[[example]]
name = "msaa"
path = "examples/3d/msaa.rs"
[[example]]
name = "msaa_pipelined"
path = "examples/3d/msaa_pipelined.rs"
[[example]]
name = "orthographic"
path = "examples/3d/orthographic.rs"
[[example]]
name = "orthographic_pipelined"
path = "examples/3d/orthographic_pipelined.rs"
[[example]]
name = "parenting"
path = "examples/3d/parenting.rs"
@ -207,46 +168,25 @@ name = "pbr"
path = "examples/3d/pbr.rs"
[[example]]
name = "pbr_pipelined"
path = "examples/3d/pbr_pipelined.rs"
name = "shadow_biases"
path = "examples/3d/shadow_biases.rs"
[[example]]
name = "render_to_texture"
path = "examples/3d/render_to_texture.rs"
[[example]]
name = "shadow_biases_pipelined"
path = "examples/3d/shadow_biases_pipelined.rs"
[[example]]
name = "shadow_caster_receiver_pipelined"
path = "examples/3d/shadow_caster_receiver_pipelined.rs"
[[example]]
name = "spawner"
path = "examples/3d/spawner.rs"
name = "shadow_caster_receiver"
path = "examples/3d/shadow_caster_receiver.rs"
[[example]]
name = "texture"
path = "examples/3d/texture.rs"
[[example]]
name = "texture_pipelined"
path = "examples/3d/texture_pipelined.rs"
[[example]]
name = "update_gltf_scene"
path = "examples/3d/update_gltf_scene.rs"
required-features = ["bevy_gltf"]
[[example]]
name = "wireframe"
path = "examples/3d/wireframe.rs"
[[example]]
name = "z_sort_debug"
path = "examples/3d/z_sort_debug.rs"
# Application
[[example]]
name = "custom_loop"
@ -292,7 +232,6 @@ path = "examples/app/thread_pool_resources.rs"
[[example]]
name = "asset_loading"
path = "examples/asset/asset_loading.rs"
required-features = ["bevy_gltf"]
[[example]]
name = "custom_asset"
@ -305,7 +244,6 @@ path = "examples/asset/custom_asset_io.rs"
[[example]]
name = "hot_asset_reloading"
path = "examples/asset/hot_asset_reloading.rs"
required-features = ["bevy_gltf"]
# Async Tasks
[[example]]
@ -387,7 +325,6 @@ path = "examples/ecs/timers.rs"
[[example]]
name = "alien_cake_addict"
path = "examples/game/alien_cake_addict.rs"
required-features = ["bevy_gltf"]
[[example]]
name = "breakout"
@ -457,47 +394,19 @@ name = "scene"
path = "examples/scene/scene.rs"
# Shaders
[[example]]
name = "animate_shader"
path = "examples/shader/animate_shader.rs"
[[example]]
name = "array_texture"
path = "examples/shader/array_texture.rs"
[[example]]
name = "hot_shader_reloading"
path = "examples/shader/hot_shader_reloading.rs"
[[example]]
name = "mesh_custom_attribute"
path = "examples/shader/mesh_custom_attribute.rs"
[[example]]
name = "shader_custom_material"
path = "examples/shader/shader_custom_material.rs"
[[example]]
name = "shader_defs"
path = "examples/shader/shader_defs.rs"
[[example]]
name = "custom_shader_pipelined"
path = "examples/shader/custom_shader_pipelined.rs"
[[example]]
name = "shader_defs_pipelined"
path = "examples/shader/shader_defs_pipelined.rs"
name = "shader_material"
path = "examples/shader/shader_material.rs"
# Tools
[[example]]
name = "bevymark"
path = "examples/tools/bevymark.rs"
[[example]]
name = "bevymark_pipelined"
path = "examples/tools/bevymark_pipelined.rs"
# UI (User Interface)
[[example]]
name = "button"
@ -519,24 +428,11 @@ path = "examples/ui/text_debug.rs"
name = "ui"
path = "examples/ui/ui.rs"
[[example]]
name = "ui_pipelined"
path = "examples/ui/ui_pipelined.rs"
# Window
[[example]]
name = "clear_color"
path = "examples/window/clear_color.rs"
[[example]]
name = "clear_color_pipelined"
path = "examples/window/clear_color_pipelined.rs"
[[example]]
name = "multiple_windows"
path = "examples/window/multiple_windows.rs"
required-features = ["bevy_gltf"]
[[example]]
name = "scale_factor_override"
path = "examples/window/scale_factor_override.rs"

View file

@ -84,10 +84,10 @@ impl AssetIo for FileAssetIo {
)))
}
fn watch_path_for_changes(&self, path: &Path) -> Result<(), AssetIoError> {
fn watch_path_for_changes(&self, _path: &Path) -> Result<(), AssetIoError> {
#[cfg(feature = "filesystem_watcher")]
{
let path = self.root_path.join(path);
let path = self.root_path.join(_path);
let mut watcher = self.filesystem_watcher.write();
if let Some(ref mut watcher) = *watcher {
watcher

View file

@ -14,9 +14,9 @@ keywords = ["bevy"]
[dependencies]
# bevy
bevy_app = { path = "../../crates/bevy_app", version = "0.5.0" }
bevy_asset = { path = "../../crates/bevy_asset", version = "0.5.0" }
bevy_core = { path = "../../crates/bevy_core", version = "0.5.0" }
bevy_ecs = { path = "../../crates/bevy_ecs", version = "0.5.0" }
bevy_render2 = { path = "../bevy_render2", version = "0.5.0" }
bevy_app = { path = "../bevy_app", version = "0.5.0" }
bevy_asset = { path = "../bevy_asset", version = "0.5.0" }
bevy_core = { path = "../bevy_core", version = "0.5.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" }
bevy_render = { path = "../bevy_render", version = "0.5.0" }

View file

@ -2,7 +2,7 @@ use std::collections::HashSet;
use crate::ClearColor;
use bevy_ecs::prelude::*;
use bevy_render2::{
use bevy_render::{
camera::ExtractedCamera,
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo},
render_resource::{

View file

@ -1,5 +1,5 @@
use bevy_ecs::world::World;
use bevy_render2::{
use bevy_render::{
render_graph::{Node, NodeRunError, RenderGraphContext},
renderer::RenderContext,
};

View file

@ -4,6 +4,11 @@ mod main_pass_2d;
mod main_pass_3d;
mod main_pass_driver;
pub mod prelude {
#[doc(hidden)]
pub use crate::ClearColor;
}
pub use clear_pass::*;
pub use clear_pass_driver::*;
pub use main_pass_2d::*;
@ -13,7 +18,7 @@ pub use main_pass_driver::*;
use bevy_app::{App, Plugin};
use bevy_core::FloatOrd;
use bevy_ecs::prelude::*;
use bevy_render2::{
use bevy_render::{
camera::{ActiveCameras, CameraPlugin},
color::Color,
render_graph::{EmptyNode, RenderGraph, SlotInfo, SlotType},

View file

@ -1,6 +1,6 @@
use crate::Transparent2d;
use bevy_ecs::prelude::*;
use bevy_render2::{
use bevy_render::{
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass},
render_resource::{LoadOp, Operations, RenderPassColorAttachment, RenderPassDescriptor},

View file

@ -1,6 +1,6 @@
use crate::{AlphaMask3d, Opaque3d, Transparent3d};
use bevy_ecs::prelude::*;
use bevy_render2::{
use bevy_render::{
render_graph::{Node, NodeRunError, RenderGraphContext, SlotInfo, SlotType},
render_phase::{DrawFunctions, RenderPhase, TrackedRenderPass},
render_resource::{LoadOp, Operations, RenderPassDepthStencilAttachment, RenderPassDescriptor},

View file

@ -1,5 +1,5 @@
use bevy_ecs::world::World;
use bevy_render2::{
use bevy_render::{
camera::{CameraPlugin, ExtractedCameraNames},
render_graph::{Node, NodeRunError, RenderGraphContext, SlotValue},
renderer::RenderContext,

View file

@ -76,7 +76,7 @@ fn parse_component_attr(ast: &DeriveInput) -> Result<Attrs> {
meta_item.path(),
format!(
"unknown component attribute `{}`",
meta_item.path().into_token_stream().to_string()
meta_item.path().into_token_stream()
),
));
}

View file

@ -18,6 +18,7 @@ bevy_pbr = { path = "../bevy_pbr", version = "0.5.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] }
bevy_render = { path = "../bevy_render", version = "0.5.0" }
bevy_transform = { path = "../bevy_transform", version = "0.5.0" }
bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
bevy_math = { path = "../bevy_math", version = "0.5.0" }
bevy_scene = { path = "../bevy_scene", version = "0.5.0" }
bevy_log = { path = "../bevy_log", version = "0.5.0" }
@ -28,3 +29,4 @@ thiserror = "1.0"
anyhow = "1.0.4"
base64 = "0.13.0"
percent-encoding = "2.1"
wgpu = "0.11.0"

View file

@ -1,16 +1,16 @@
use std::collections::HashMap;
use bevy_utils::HashMap;
mod loader;
pub use loader::*;
use bevy_app::prelude::*;
use bevy_asset::{AddAsset, Handle};
use bevy_pbr::prelude::StandardMaterial;
use bevy_pbr::StandardMaterial;
use bevy_reflect::TypeUuid;
use bevy_render::mesh::Mesh;
use bevy_scene::Scene;
/// Adds support for GLTF file loading to Apps
/// Adds support for glTF file loading to the app.
#[derive(Default)]
pub struct GltfPlugin;
@ -24,6 +24,7 @@ impl Plugin for GltfPlugin {
}
}
/// Representation of a loaded glTF file.
#[derive(Debug, TypeUuid)]
#[uuid = "5c7d5f8a-f7b0-4e45-a09e-406c0372fea2"]
pub struct Gltf {
@ -38,6 +39,8 @@ pub struct Gltf {
pub default_scene: Option<Handle<Scene>>,
}
/// A glTF node with all of its child nodes, its [`GltfMesh`] and
/// [`Transform`](bevy_transform::prelude::Transform).
#[derive(Debug, Clone, TypeUuid)]
#[uuid = "dad74750-1fd6-460f-ac51-0a7937563865"]
pub struct GltfNode {
@ -46,12 +49,14 @@ pub struct GltfNode {
pub transform: bevy_transform::prelude::Transform,
}
/// A glTF mesh, which may consists of multiple [`GtlfPrimitives`](GltfPrimitive).
#[derive(Debug, Clone, TypeUuid)]
#[uuid = "8ceaec9a-926a-4f29-8ee3-578a69f42315"]
pub struct GltfMesh {
pub primitives: Vec<GltfPrimitive>,
}
/// Part of a [`GltfMesh`] that consists of a [`Mesh`] and an optional [`StandardMaterial`].
#[derive(Debug, Clone, TypeUuid)]
#[uuid = "cbfca302-82fd-41cb-af77-cab6b3d50af1"]
pub struct GltfPrimitive {

View file

@ -5,42 +5,41 @@ use bevy_asset::{
use bevy_core::Name;
use bevy_ecs::world::World;
use bevy_log::warn;
use bevy_math::Mat4;
use bevy_pbr::prelude::{PbrBundle, StandardMaterial};
use bevy_math::{Mat4, Vec3};
use bevy_pbr::{AlphaMode, PbrBundle, StandardMaterial};
use bevy_render::{
camera::{
Camera, CameraProjection, OrthographicProjection, PerspectiveProjection, VisibleEntities,
Camera, CameraPlugin, CameraProjection, OrthographicProjection, PerspectiveProjection,
},
color::Color,
mesh::{Indices, Mesh, VertexAttributeValues},
pipeline::PrimitiveTopology,
prelude::{Color, Texture},
render_graph::base,
texture::{AddressMode, FilterMode, ImageType, SamplerDescriptor, TextureError, TextureFormat},
primitives::{Aabb, Frustum},
texture::{Image, ImageType, TextureError},
view::VisibleEntities,
};
use bevy_scene::Scene;
use bevy_transform::{
hierarchy::{BuildWorldChildren, WorldChildBuilder},
prelude::{GlobalTransform, Transform},
};
use bevy_utils::{HashMap, HashSet};
use gltf::{
mesh::Mode,
texture::{MagFilter, MinFilter, WrappingMode},
Material, Primitive,
};
use std::{
collections::{HashMap, HashSet},
path::Path,
};
use std::{collections::VecDeque, path::Path};
use thiserror::Error;
use wgpu::{AddressMode, FilterMode, PrimitiveTopology, SamplerDescriptor, TextureFormat};
use crate::{Gltf, GltfNode};
/// An error that occurs when loading a GLTF file
/// An error that occurs when loading a glTF file.
#[derive(Error, Debug)]
pub enum GltfError {
#[error("unsupported primitive mode")]
UnsupportedPrimitive { mode: Mode },
#[error("invalid GLTF file: {0}")]
#[error("invalid glTF file: {0}")]
Gltf(#[from] gltf::Error),
#[error("binary blob is missing")]
MissingBlob,
@ -56,7 +55,7 @@ pub enum GltfError {
AssetIoError(#[from] AssetIoError),
}
/// Loads meshes from GLTF files into Mesh assets
/// Loads glTF files with all of their data as their corresponding bevy representations.
#[derive(Default)]
pub struct GltfLoader;
@ -74,6 +73,7 @@ impl AssetLoader for GltfLoader {
}
}
/// Loads an entire glTF file.
async fn load_gltf<'a, 'b>(
bytes: &'a [u8],
load_context: &'a mut LoadContext<'b>,
@ -82,8 +82,8 @@ async fn load_gltf<'a, 'b>(
let buffer_data = load_buffers(&gltf, load_context, load_context.path()).await?;
let mut materials = vec![];
let mut named_materials = HashMap::new();
let mut linear_textures = HashSet::new();
let mut named_materials = HashMap::default();
let mut linear_textures = HashSet::default();
for material in gltf.materials() {
let handle = load_material(&material, load_context);
if let Some(name) = material.name() {
@ -105,7 +105,7 @@ async fn load_gltf<'a, 'b>(
}
let mut meshes = vec![];
let mut named_meshes = HashMap::new();
let mut named_meshes = HashMap::default();
for mesh in gltf.meshes() {
let mut primitives = vec![];
for primitive in mesh.primitives() {
@ -148,12 +148,12 @@ async fn load_gltf<'a, 'b>(
mesh.set_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
}
if let Some(vertex_attribute) = reader
.read_colors(0)
.map(|v| VertexAttributeValues::Float32x4(v.into_rgba_f32().collect()))
{
mesh.set_attribute(Mesh::ATTRIBUTE_COLOR, vertex_attribute);
}
// if let Some(vertex_attribute) = reader
// .read_colors(0)
// .map(|v| VertexAttributeValues::Float32x4(v.into_rgba_f32().collect()))
// {
// mesh.set_attribute(Mesh::ATTRIBUTE_COLOR, vertex_attribute);
// }
if let Some(indices) = reader.read_indices() {
mesh.set_indices(Some(Indices::U32(indices.into_u32().collect())));
@ -194,7 +194,7 @@ async fn load_gltf<'a, 'b>(
}
let mut nodes_intermediate = vec![];
let mut named_nodes_intermediate = HashMap::new();
let mut named_nodes_intermediate = HashMap::default();
for node in gltf.nodes() {
let node_label = node_label(&node);
nodes_intermediate.push((
@ -228,7 +228,7 @@ async fn load_gltf<'a, 'b>(
named_nodes_intermediate.insert(name, node.index());
}
}
let nodes = resolve_node_hierarchy(nodes_intermediate)
let nodes = resolve_node_hierarchy(nodes_intermediate, load_context.path())
.into_iter()
.map(|(label, node)| load_context.set_labeled_asset(&label, LoadedAsset::new(node)))
.collect::<Vec<bevy_asset::Handle<GltfNode>>>();
@ -265,7 +265,7 @@ async fn load_gltf<'a, 'b>(
.into_iter()
.filter_map(|res| {
if let Err(err) = res.as_ref() {
warn!("Error loading GLTF texture: {}", err);
warn!("Error loading glTF texture: {}", err);
}
res.ok()
})
@ -274,7 +274,7 @@ async fn load_gltf<'a, 'b>(
});
let mut scenes = vec![];
let mut named_scenes = HashMap::new();
let mut named_scenes = HashMap::default();
for scene in gltf.scenes() {
let mut err = None;
let mut world = World::default();
@ -320,18 +320,19 @@ async fn load_gltf<'a, 'b>(
Ok(())
}
/// Loads a glTF texture as a bevy [`Image`] and returns it together with its label.
async fn load_texture<'a>(
gltf_texture: gltf::Texture<'a>,
buffer_data: &[Vec<u8>],
linear_textures: &HashSet<usize>,
load_context: &LoadContext<'a>,
) -> Result<(Texture, String), GltfError> {
) -> Result<(Image, String), GltfError> {
let mut texture = match gltf_texture.source().source() {
gltf::image::Source::View { view, mime_type } => {
let start = view.offset() as usize;
let end = (view.offset() + view.length()) as usize;
let buffer = &buffer_data[view.buffer().index()][start..end];
Texture::from_buffer(buffer, ImageType::MimeType(mime_type))?
Image::from_buffer(buffer, ImageType::MimeType(mime_type))?
}
gltf::image::Source::Uri { uri, mime_type } => {
let uri = percent_encoding::percent_decode_str(uri)
@ -352,20 +353,21 @@ async fn load_texture<'a>(
}
};
Texture::from_buffer(
Image::from_buffer(
&bytes,
mime_type.map(ImageType::MimeType).unwrap_or(image_type),
)?
}
};
texture.sampler = texture_sampler(&gltf_texture);
texture.sampler_descriptor = texture_sampler(&gltf_texture);
if (linear_textures).contains(&gltf_texture.index()) {
texture.format = TextureFormat::Rgba8Unorm;
texture.texture_descriptor.format = TextureFormat::Rgba8Unorm;
}
Ok((texture, texture_label(&gltf_texture)))
}
/// Loads a glTF material as a bevy [`StandardMaterial`] and returns it.
fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle<StandardMaterial> {
let material_label = material_label(material);
@ -381,15 +383,16 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle<
None
};
let normal_map = if let Some(normal_texture) = material.normal_texture() {
// TODO: handle normal_texture.scale
// TODO: handle normal_texture.tex_coord() (the *set* index for the right texcoords)
let label = texture_label(&normal_texture.texture());
let path = AssetPath::new_ref(load_context.path(), Some(&label));
Some(load_context.get_handle(path))
} else {
None
};
let normal_map_texture: Option<Handle<Image>> =
if let Some(normal_texture) = material.normal_texture() {
// TODO: handle normal_texture.scale
// TODO: handle normal_texture.tex_coord() (the *set* index for the right texcoords)
let label = texture_label(&normal_texture.texture());
let path = AssetPath::new_ref(load_context.path(), Some(&label));
Some(load_context.get_handle(path))
} else {
None
};
let metallic_roughness_texture = if let Some(info) = pbr.metallic_roughness_texture() {
// TODO: handle info.tex_coord() (the *set* index for the right texcoords)
@ -426,20 +429,22 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle<
LoadedAsset::new(StandardMaterial {
base_color: Color::rgba(color[0], color[1], color[2], color[3]),
base_color_texture,
roughness: pbr.roughness_factor(),
perceptual_roughness: pbr.roughness_factor(),
metallic: pbr.metallic_factor(),
metallic_roughness_texture,
normal_map,
normal_map_texture,
double_sided: material.double_sided(),
occlusion_texture,
emissive: Color::rgba(emissive[0], emissive[1], emissive[2], 1.0),
emissive_texture,
unlit: material.unlit(),
alpha_mode: alpha_mode(material),
..Default::default()
}),
)
}
/// Loads a glTF node.
fn load_node(
gltf_node: &gltf::Node,
world_builder: &mut WorldChildBuilder,
@ -459,9 +464,12 @@ fn load_node(
// create camera node
if let Some(camera) = gltf_node.camera() {
node.insert(VisibleEntities {
..Default::default()
});
node.insert_bundle((
VisibleEntities {
..Default::default()
},
Frustum::default(),
));
match camera.projection() {
gltf::camera::Projection::Orthographic(orthographic) => {
@ -478,7 +486,7 @@ fn load_node(
};
node.insert(Camera {
name: Some(base::camera::CAMERA_2D.to_owned()),
name: Some(CameraPlugin::CAMERA_2D.to_owned()),
projection_matrix: orthographic_projection.get_projection_matrix(),
..Default::default()
});
@ -497,7 +505,7 @@ fn load_node(
perspective_projection.aspect_ratio = aspect_ratio;
}
node.insert(Camera {
name: Some(base::camera::CAMERA_3D.to_owned()),
name: Some(CameraPlugin::CAMERA_3D.to_owned()),
projection_matrix: perspective_projection.get_projection_matrix(),
..Default::default()
});
@ -526,11 +534,17 @@ fn load_node(
let material_asset_path =
AssetPath::new_ref(load_context.path(), Some(&material_label));
parent.spawn_bundle(PbrBundle {
mesh: load_context.get_handle(mesh_asset_path),
material: load_context.get_handle(material_asset_path),
..Default::default()
});
let bounds = primitive.bounding_box();
parent
.spawn_bundle(PbrBundle {
mesh: load_context.get_handle(mesh_asset_path),
material: load_context.get_handle(material_asset_path),
..Default::default()
})
.insert(Aabb::from_min_max(
Vec3::from_slice(&bounds.min),
Vec3::from_slice(&bounds.max),
));
}
}
@ -549,14 +563,17 @@ fn load_node(
}
}
/// Returns the label for the `mesh`.
fn mesh_label(mesh: &gltf::Mesh) -> String {
format!("Mesh{}", mesh.index())
}
/// Returns the label for the `mesh` and `primitive`.
fn primitive_label(mesh: &gltf::Mesh, primitive: &Primitive) -> String {
format!("Mesh{}/Primitive{}", mesh.index(), primitive.index())
}
/// Returns the label for the `material`.
fn material_label(material: &gltf::Material) -> String {
if let Some(index) = material.index() {
format!("Material{}", index)
@ -565,19 +582,23 @@ fn material_label(material: &gltf::Material) -> String {
}
}
/// Returns the label for the `texture`.
fn texture_label(texture: &gltf::Texture) -> String {
format!("Texture{}", texture.index())
}
/// Returns the label for the `node`.
fn node_label(node: &gltf::Node) -> String {
format!("Node{}", node.index())
}
/// Returns the label for the `scene`.
fn scene_label(scene: &gltf::Scene) -> String {
format!("Scene{}", scene.index())
}
fn texture_sampler(texture: &gltf::Texture) -> SamplerDescriptor {
/// Extracts the texture sampler data from the glTF texture.
fn texture_sampler<'a>(texture: &gltf::Texture) -> SamplerDescriptor<'a> {
let gltf_sampler = texture.sampler();
SamplerDescriptor {
@ -621,6 +642,7 @@ fn texture_sampler(texture: &gltf::Texture) -> SamplerDescriptor {
}
}
/// Maps the texture address mode form glTF to wgpu.
fn texture_address_mode(gltf_address_mode: &gltf::texture::WrappingMode) -> AddressMode {
match gltf_address_mode {
WrappingMode::ClampToEdge => AddressMode::ClampToEdge,
@ -629,6 +651,7 @@ fn texture_address_mode(gltf_address_mode: &gltf::texture::WrappingMode) -> Addr
}
}
/// Maps the primitive_topology form glTF to wgpu.
fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
match mode {
Mode::Points => Ok(PrimitiveTopology::PointList),
@ -640,6 +663,15 @@ fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
}
}
fn alpha_mode(material: &Material) -> AlphaMode {
match material.alpha_mode() {
gltf::material::AlphaMode::Opaque => AlphaMode::Opaque,
gltf::material::AlphaMode::Mask => AlphaMode::Mask(material.alpha_cutoff().unwrap_or(0.5)),
gltf::material::AlphaMode::Blend => AlphaMode::Blend,
}
}
/// Loads the raw glTF buffer data for a specific glTF file.
async fn load_buffers(
gltf: &gltf::Gltf,
load_context: &LoadContext<'_>,
@ -684,42 +716,51 @@ async fn load_buffers(
fn resolve_node_hierarchy(
nodes_intermediate: Vec<(String, GltfNode, Vec<usize>)>,
asset_path: &Path,
) -> Vec<(String, GltfNode)> {
let mut max_steps = nodes_intermediate.len();
let mut nodes_step = nodes_intermediate
let mut has_errored = false;
let mut empty_children = VecDeque::new();
let mut parents = vec![None; nodes_intermediate.len()];
let mut unprocessed_nodes = nodes_intermediate
.into_iter()
.enumerate()
.map(|(i, (label, node, children))| (i, label, node, children))
.collect::<Vec<_>>();
let mut nodes = std::collections::HashMap::<usize, (String, GltfNode)>::new();
while max_steps > 0 && !nodes_step.is_empty() {
if let Some((index, label, node, _)) = nodes_step
.iter()
.find(|(_, _, _, children)| children.is_empty())
.cloned()
{
nodes.insert(index, (label, node));
for (_, _, node, children) in nodes_step.iter_mut() {
if let Some((i, _)) = children
.iter()
.enumerate()
.find(|(_, child_index)| **child_index == index)
{
children.remove(i);
if let Some((_, child_node)) = nodes.get(&index) {
node.children.push(child_node.clone())
}
.map(|(i, (label, node, children))| {
for child in children.iter() {
if let Some(parent) = parents.get_mut(*child) {
*parent = Some(i);
} else if !has_errored {
has_errored = true;
warn!("Unexpected child in GLTF Mesh {}", child);
}
}
nodes_step = nodes_step
.into_iter()
.filter(|(i, _, _, _)| *i != index)
.collect()
}
max_steps -= 1;
}
let children = children.into_iter().collect::<HashSet<_>>();
if children.is_empty() {
empty_children.push_back(i);
}
(i, (label, node, children))
})
.collect::<HashMap<_, _>>();
let mut nodes = std::collections::HashMap::<usize, (String, GltfNode)>::new();
while 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<_>>();
nodes_to_sort.sort_by_key(|(i, _)| *i);
nodes_to_sort
@ -767,6 +808,8 @@ impl<'a> DataUri<'a> {
#[cfg(test)]
mod test {
use std::path::PathBuf;
use super::resolve_node_hierarchy;
use crate::GltfNode;
@ -781,7 +824,10 @@ mod test {
}
#[test]
fn node_hierarchy_single_node() {
let result = resolve_node_hierarchy(vec![("l1".to_string(), GltfNode::empty(), vec![])]);
let result = resolve_node_hierarchy(
vec![("l1".to_string(), GltfNode::empty(), vec![])],
PathBuf::new().as_path(),
);
assert_eq!(result.len(), 1);
assert_eq!(result[0].0, "l1");
@ -790,10 +836,13 @@ mod test {
#[test]
fn node_hierarchy_no_hierarchy() {
let result = resolve_node_hierarchy(vec![
("l1".to_string(), GltfNode::empty(), vec![]),
("l2".to_string(), GltfNode::empty(), vec![]),
]);
let result = resolve_node_hierarchy(
vec![
("l1".to_string(), GltfNode::empty(), vec![]),
("l2".to_string(), GltfNode::empty(), vec![]),
],
PathBuf::new().as_path(),
);
assert_eq!(result.len(), 2);
assert_eq!(result[0].0, "l1");
@ -804,10 +853,13 @@ mod test {
#[test]
fn node_hierarchy_simple_hierarchy() {
let result = resolve_node_hierarchy(vec![
("l1".to_string(), GltfNode::empty(), vec![1]),
("l2".to_string(), GltfNode::empty(), vec![]),
]);
let result = resolve_node_hierarchy(
vec![
("l1".to_string(), GltfNode::empty(), vec![1]),
("l2".to_string(), GltfNode::empty(), vec![]),
],
PathBuf::new().as_path(),
);
assert_eq!(result.len(), 2);
assert_eq!(result[0].0, "l1");
@ -818,15 +870,18 @@ mod test {
#[test]
fn node_hierarchy_hierarchy() {
let result = resolve_node_hierarchy(vec![
("l1".to_string(), GltfNode::empty(), vec![1]),
("l2".to_string(), GltfNode::empty(), vec![2]),
("l3".to_string(), GltfNode::empty(), vec![3, 4, 5]),
("l4".to_string(), GltfNode::empty(), vec![6]),
("l5".to_string(), GltfNode::empty(), vec![]),
("l6".to_string(), GltfNode::empty(), vec![]),
("l7".to_string(), GltfNode::empty(), vec![]),
]);
let result = resolve_node_hierarchy(
vec![
("l1".to_string(), GltfNode::empty(), vec![1]),
("l2".to_string(), GltfNode::empty(), vec![2]),
("l3".to_string(), GltfNode::empty(), vec![3, 4, 5]),
("l4".to_string(), GltfNode::empty(), vec![6]),
("l5".to_string(), GltfNode::empty(), vec![]),
("l6".to_string(), GltfNode::empty(), vec![]),
("l7".to_string(), GltfNode::empty(), vec![]),
],
PathBuf::new().as_path(),
);
assert_eq!(result.len(), 7);
assert_eq!(result[0].0, "l1");
@ -847,20 +902,26 @@ mod test {
#[test]
fn node_hierarchy_cyclic() {
let result = resolve_node_hierarchy(vec![
("l1".to_string(), GltfNode::empty(), vec![1]),
("l2".to_string(), GltfNode::empty(), vec![0]),
]);
let result = resolve_node_hierarchy(
vec![
("l1".to_string(), GltfNode::empty(), vec![1]),
("l2".to_string(), GltfNode::empty(), vec![0]),
],
PathBuf::new().as_path(),
);
assert_eq!(result.len(), 0);
}
#[test]
fn node_hierarchy_missing_node() {
let result = resolve_node_hierarchy(vec![
("l1".to_string(), GltfNode::empty(), vec![2]),
("l2".to_string(), GltfNode::empty(), vec![]),
]);
let result = resolve_node_hierarchy(
vec![
("l1".to_string(), GltfNode::empty(), vec![2]),
("l2".to_string(), GltfNode::empty(), vec![]),
],
PathBuf::new().as_path(),
);
assert_eq!(result.len(), 1);
assert_eq!(result[0].0, "l2");

View file

@ -10,18 +10,18 @@ keywords = ["game", "engine", "gamedev", "graphics", "bevy"]
categories = ["game-engines", "graphics", "gui", "rendering"]
[features]
wgpu_trace = ["bevy_wgpu/trace"]
trace = [ "bevy_app/trace", "bevy_ecs/trace", "bevy_render2/trace" ]
trace = [ "bevy_app/trace", "bevy_ecs/trace", "bevy_render/trace" ]
trace_chrome = [ "bevy_log/tracing-chrome" ]
trace_tracy = [ "bevy_log/tracing-tracy" ]
wgpu_trace = ["bevy_render/wgpu_trace"]
# Image format support for texture loading (PNG and HDR are enabled by default)
hdr = ["bevy_render/hdr", "bevy_render2/hdr" ]
png = ["bevy_render/png", "bevy_render2/png" ]
dds = ["bevy_render/dds", "bevy_render2/dds" ]
tga = ["bevy_render/tga", "bevy_render2/tga" ]
jpeg = ["bevy_render/jpeg", "bevy_render2/jpeg" ]
bmp = ["bevy_render/bmp", "bevy_render2/bmp" ]
hdr = ["bevy_render/hdr"]
png = ["bevy_render/png"]
dds = ["bevy_render/dds"]
tga = ["bevy_render/tga"]
jpeg = ["bevy_render/jpeg"]
bmp = ["bevy_render/bmp"]
# Audio format support (MP3 is enabled by default)
flac = ["bevy_audio/flac"]
@ -39,10 +39,10 @@ wayland = ["bevy_winit/wayland"]
x11 = ["bevy_winit/x11"]
# enable rendering of font glyphs using subpixel accuracy
subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas", "bevy_text2/subpixel_glyph_atlas"]
subpixel_glyph_atlas = ["bevy_text/subpixel_glyph_atlas"]
# enable systems that allow for automated testing on CI
bevy_ci_testing = ["bevy_app/bevy_ci_testing"]
bevy_ci_testing = ["bevy_app/bevy_ci_testing", "bevy_render/ci_limits"]
[dependencies]
# bevy
@ -63,21 +63,14 @@ bevy_window = { path = "../bevy_window", version = "0.5.0" }
bevy_tasks = { path = "../bevy_tasks", version = "0.5.0" }
# bevy (optional)
bevy_audio = { path = "../bevy_audio", optional = true, version = "0.5.0" }
bevy_core_pipeline = { path = "../../pipelined/bevy_core_pipeline", optional = true, version = "0.5.0" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", optional = true, version = "0.5.0" }
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.5.0" }
bevy_gltf2 = { path = "../../pipelined/bevy_gltf2", optional = true, version = "0.5.0" }
bevy_pbr = { path = "../bevy_pbr", optional = true, version = "0.5.0" }
bevy_pbr2 = { path = "../../pipelined/bevy_pbr2", optional = true, version = "0.5.0" }
bevy_render = { path = "../bevy_render", optional = true, version = "0.5.0" }
bevy_render2 = { path = "../../pipelined/bevy_render2", optional = true, version = "0.5.0" }
bevy_dynamic_plugin = { path = "../bevy_dynamic_plugin", optional = true, version = "0.5.0" }
bevy_sprite = { path = "../bevy_sprite", optional = true, version = "0.5.0" }
bevy_sprite2 = { path = "../../pipelined/bevy_sprite2", optional = true, version = "0.5.0" }
bevy_text = { path = "../bevy_text", optional = true, version = "0.5.0" }
bevy_text2 = { path = "../../pipelined/bevy_text2", optional = true, version = "0.5.0" }
bevy_ui = { path = "../bevy_ui", optional = true, version = "0.5.0" }
bevy_ui2 = { path = "../../pipelined/bevy_ui2", optional = true, version = "0.5.0" }
bevy_wgpu = { path = "../bevy_wgpu", optional = true, version = "0.5.0" }
bevy_winit = { path = "../bevy_winit", optional = true, version = "0.5.0" }
bevy_gilrs = { path = "../bevy_gilrs", optional = true, version = "0.5.0" }

View file

@ -1,35 +1,5 @@
use bevy_app::{PluginGroup, PluginGroupBuilder};
use bevy_app::ScheduleRunnerPlugin;
use bevy_asset::AssetPlugin;
#[cfg(feature = "bevy_audio")]
use bevy_audio::AudioPlugin;
use bevy_core::CorePlugin;
use bevy_diagnostic::DiagnosticsPlugin;
#[cfg(feature = "bevy_gilrs")]
use bevy_gilrs::GilrsPlugin;
#[cfg(feature = "bevy_gltf")]
use bevy_gltf::GltfPlugin;
use bevy_input::InputPlugin;
use bevy_log::LogPlugin;
#[cfg(feature = "bevy_pbr")]
use bevy_pbr::PbrPlugin;
#[cfg(feature = "bevy_render")]
use bevy_render::RenderPlugin;
use bevy_scene::ScenePlugin;
#[cfg(feature = "bevy_sprite")]
use bevy_sprite::SpritePlugin;
#[cfg(feature = "bevy_text")]
use bevy_text::TextPlugin;
use bevy_transform::TransformPlugin;
#[cfg(feature = "bevy_ui")]
use bevy_ui::UiPlugin;
#[cfg(feature = "bevy_wgpu")]
use bevy_wgpu::WgpuPlugin;
use bevy_window::WindowPlugin;
#[cfg(feature = "bevy_winit")]
use bevy_winit::WinitPlugin;
/// This plugin group will add all the default plugins:
/// * [`LogPlugin`]
/// * [`CorePlugin`]
@ -48,71 +18,11 @@ use bevy_winit::WinitPlugin;
/// * [`GilrsPlugin`] - with feature `bevy_gilrs`
/// * [`GltfPlugin`] - with feature `bevy_gltf`
/// * [`WinitPlugin`] - with feature `bevy_winit`
/// * [`WgpuPlugin`] - with feature `bevy_wgpu`
///
/// See also [`MinimalPlugins`] for a slimmed down option
pub struct DefaultPlugins;
impl PluginGroup for DefaultPlugins {
fn build(&mut self, group: &mut PluginGroupBuilder) {
group.add(LogPlugin::default());
group.add(CorePlugin::default());
group.add(TransformPlugin::default());
group.add(DiagnosticsPlugin::default());
group.add(InputPlugin::default());
group.add(WindowPlugin::default());
group.add(AssetPlugin::default());
group.add(ScenePlugin::default());
#[cfg(feature = "bevy_render")]
group.add(RenderPlugin::default());
#[cfg(feature = "bevy_sprite")]
group.add(SpritePlugin::default());
#[cfg(feature = "bevy_pbr")]
group.add(PbrPlugin::default());
#[cfg(feature = "bevy_ui")]
group.add(UiPlugin::default());
#[cfg(feature = "bevy_text")]
group.add(TextPlugin::default());
#[cfg(feature = "bevy_audio")]
group.add(AudioPlugin::default());
#[cfg(feature = "bevy_gilrs")]
group.add(GilrsPlugin::default());
#[cfg(feature = "bevy_gltf")]
group.add(GltfPlugin::default());
#[cfg(feature = "bevy_winit")]
group.add(WinitPlugin::default());
#[cfg(feature = "bevy_wgpu")]
group.add(WgpuPlugin::default());
}
}
/// Minimal plugin group that will add the following plugins:
/// * [`CorePlugin`]
/// * [`ScheduleRunnerPlugin`]
///
/// See also [`DefaultPlugins`] for a more complete set of plugins
pub struct MinimalPlugins;
impl PluginGroup for MinimalPlugins {
fn build(&mut self, group: &mut PluginGroupBuilder) {
group.add(CorePlugin::default());
group.add(ScheduleRunnerPlugin::default());
}
}
pub struct PipelinedDefaultPlugins;
impl PluginGroup for PipelinedDefaultPlugins {
fn build(&mut self, group: &mut PluginGroupBuilder) {
group.add(bevy_log::LogPlugin::default());
group.add(bevy_core::CorePlugin::default());
@ -126,29 +36,42 @@ impl PluginGroup for PipelinedDefaultPlugins {
#[cfg(feature = "bevy_winit")]
group.add(bevy_winit::WinitPlugin::default());
#[cfg(feature = "bevy_render2")]
{
group.add(bevy_render2::RenderPlugin::default());
}
#[cfg(feature = "bevy_render")]
group.add(bevy_render::RenderPlugin::default());
#[cfg(feature = "bevy_core_pipeline")]
{
group.add(bevy_core_pipeline::CorePipelinePlugin::default());
group.add(bevy_core_pipeline::CorePipelinePlugin::default());
#[cfg(feature = "bevy_sprite2")]
group.add(bevy_sprite2::SpritePlugin::default());
#[cfg(feature = "bevy_sprite")]
group.add(bevy_sprite::SpritePlugin::default());
#[cfg(feature = "bevy_text2")]
group.add(bevy_text2::TextPlugin::default());
#[cfg(feature = "bevy_text")]
group.add(bevy_text::TextPlugin::default());
#[cfg(feature = "bevy_ui2")]
group.add(bevy_ui2::UiPlugin::default());
#[cfg(feature = "bevy_ui")]
group.add(bevy_ui::UiPlugin::default());
#[cfg(feature = "bevy_pbr2")]
group.add(bevy_pbr2::PbrPlugin::default());
#[cfg(feature = "bevy_pbr")]
group.add(bevy_pbr::PbrPlugin::default());
#[cfg(feature = "bevy_gltf2")]
group.add(bevy_gltf2::GltfPlugin::default());
}
#[cfg(feature = "bevy_gltf")]
group.add(bevy_gltf::GltfPlugin::default());
#[cfg(feature = "bevy_audio")]
group.add(bevy_audio::AudioPlugin::default());
}
}
/// Minimal plugin group that will add the following plugins:
/// * [`CorePlugin`]
/// * [`ScheduleRunnerPlugin`]
///
/// See also [`DefaultPlugins`] for a more complete set of plugins
pub struct MinimalPlugins;
impl PluginGroup for MinimalPlugins {
fn build(&mut self, group: &mut PluginGroupBuilder) {
group.add(bevy_core::CorePlugin::default());
group.add(bevy_app::ScheduleRunnerPlugin::default());
}
}

View file

@ -99,83 +99,41 @@ pub mod gltf {
pub use bevy_gltf::*;
}
#[cfg(feature = "bevy_gltf2")]
pub mod gltf2 {
//! Support for GLTF file loading.
pub use bevy_gltf2::*;
}
#[cfg(feature = "bevy_pbr")]
pub mod pbr {
//! Physically based rendering.
pub use bevy_pbr::*;
}
#[cfg(feature = "bevy_pbr2")]
pub mod pbr2 {
//! Physically based rendering.
pub use bevy_pbr2::*;
}
#[cfg(feature = "bevy_render")]
pub mod render {
//! Cameras, meshes, textures, shaders, and pipelines.
pub use bevy_render::*;
}
#[cfg(feature = "bevy_render2")]
pub mod render2 {
//! Cameras, meshes, textures, shaders, and pipelines.
pub use bevy_render2::*;
}
#[cfg(feature = "bevy_sprite")]
pub mod sprite {
//! Items for sprites, rects, texture atlases, etc.
pub use bevy_sprite::*;
}
#[cfg(feature = "bevy_sprite2")]
pub mod sprite2 {
//! Items for sprites, rects, texture atlases, etc.
pub use bevy_sprite2::*;
}
#[cfg(feature = "bevy_text")]
pub mod text {
//! Text drawing, styling, and font assets.
pub use bevy_text::*;
}
#[cfg(feature = "bevy_text2")]
pub mod text2 {
//! Text drawing, styling, and font assets.
pub use bevy_text2::*;
}
#[cfg(feature = "bevy_ui")]
pub mod ui {
//! User interface components and widgets.
pub use bevy_ui::*;
}
#[cfg(feature = "bevy_ui2")]
pub mod ui2 {
//! User interface components and widgets.
pub use bevy_ui2::*;
}
#[cfg(feature = "bevy_winit")]
pub mod winit {
pub use bevy_winit::*;
}
#[cfg(feature = "bevy_wgpu")]
pub mod wgpu {
//! A render backend utilizing [wgpu](https://wgpu.rs/).
pub use bevy_wgpu::*;
}
#[cfg(feature = "bevy_dynamic_plugin")]
pub mod dynamic_plugin {
pub use bevy_dynamic_plugin::*;

View file

@ -11,6 +11,10 @@ pub use bevy_derive::bevy_main;
#[cfg(feature = "bevy_audio")]
pub use crate::audio::prelude::*;
#[doc(hidden)]
#[cfg(feature = "bevy_core_pipeline")]
pub use crate::core_pipeline::prelude::*;
#[doc(hidden)]
#[cfg(feature = "bevy_pbr")]
pub use crate::pbr::prelude::*;

View file

@ -8,7 +8,7 @@ pub use glam::*;
pub mod prelude {
#[doc(hidden)]
pub use crate::{
BVec2, BVec3, BVec4, FaceToward, IVec2, IVec3, IVec4, Mat3, Mat4, Quat, Rect, Size, UVec2,
UVec3, UVec4, Vec2, Vec3, Vec4,
BVec2, BVec3, BVec4, EulerRot, FaceToward, IVec2, IVec3, IVec4, Mat3, Mat4, Quat, Rect,
Size, UVec2, UVec3, UVec4, Vec2, Vec3, Vec4,
};
}

View file

@ -13,12 +13,18 @@ keywords = ["bevy"]
bevy_app = { path = "../bevy_app", version = "0.5.0" }
bevy_asset = { path = "../bevy_asset", version = "0.5.0" }
bevy_core = { path = "../bevy_core", version = "0.5.0" }
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.5.0" }
bevy_ecs = { path = "../bevy_ecs", version = "0.5.0" }
bevy_math = { path = "../bevy_math", version = "0.5.0" }
bevy_reflect = { path = "../bevy_reflect", version = "0.5.0", features = ["bevy"] }
bevy_render = { path = "../bevy_render", version = "0.5.0" }
bevy_transform = { path = "../bevy_transform", version = "0.5.0" }
bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
bevy_window = { path = "../bevy_window", version = "0.5.0" }
# other
bitflags = "1.2"
# direct dependency required for derive macro
bytemuck = { version = "1", features = ["derive"] }
crevice = { path = "../crevice", version = "0.8.0", features = ["glam"] }
wgpu = { version = "0.11.0", features = ["spirv"] }

View file

@ -1,7 +1,7 @@
use crate::{DirectionalLight, PointLight, StandardMaterial};
use bevy_asset::Handle;
use bevy_ecs::{bundle::Bundle, component::Component};
use bevy_render2::{
use bevy_render::{
mesh::Mesh,
primitives::{CubemapFrusta, Frustum},
view::{ComputedVisibility, Visibility, VisibleEntities},

View file

@ -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,
}

View file

@ -1,55 +1,192 @@
pub mod render_graph;
pub mod wireframe;
mod entity;
mod alpha;
mod bundle;
mod light;
mod material;
mod render;
pub use entity::*;
pub use alpha::*;
pub use bundle::*;
pub use light::*;
pub use material::*;
pub use render::*;
pub mod prelude {
#[doc(hidden)]
pub use crate::{
entity::*,
light::{DirectionalLight, PointLight},
alpha::AlphaMode,
bundle::{DirectionalLightBundle, PbrBundle, PointLightBundle},
light::{AmbientLight, DirectionalLight, PointLight},
material::StandardMaterial,
};
}
use bevy_app::prelude::*;
use bevy_asset::{AddAsset, Assets, Handle};
use bevy_render::{prelude::Color, shader};
use material::StandardMaterial;
use render_graph::add_pbr_graph;
pub mod draw_3d_graph {
pub mod node {
/// Label for the shadow pass node.
pub const SHADOW_PASS: &str = "shadow_pass";
}
}
/// NOTE: this isn't PBR yet. consider this name "aspirational" :)
use bevy_app::prelude::*;
use bevy_asset::{Assets, Handle, HandleUntyped};
use bevy_core_pipeline::{AlphaMask3d, Opaque3d, Transparent3d};
use bevy_ecs::prelude::*;
use bevy_reflect::TypeUuid;
use bevy_render::{
render_component::ExtractComponentPlugin,
render_graph::RenderGraph,
render_phase::{sort_phase_system, AddRenderCommand, DrawFunctions},
render_resource::{Shader, SpecializedPipelines},
view::VisibilitySystems,
RenderApp, RenderStage,
};
use bevy_transform::TransformSystem;
pub const PBR_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 4805239651767701046);
pub const SHADOW_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 1836745567947005696);
/// Sets up the entire PBR infrastructure of bevy.
#[derive(Default)]
pub struct PbrPlugin;
impl Plugin for PbrPlugin {
fn build(&self, app: &mut App) {
app.add_asset::<StandardMaterial>()
.register_type::<PointLight>()
let mut shaders = app.world.get_resource_mut::<Assets<Shader>>().unwrap();
shaders.set_untracked(
PBR_SHADER_HANDLE,
Shader::from_wgsl(include_str!("render/pbr.wgsl")),
);
shaders.set_untracked(
SHADOW_SHADER_HANDLE,
Shader::from_wgsl(include_str!("render/depth.wgsl")),
);
app.add_plugin(StandardMaterialPlugin)
.add_plugin(MeshRenderPlugin)
.add_plugin(ExtractComponentPlugin::<Handle<StandardMaterial>>::default())
.init_resource::<AmbientLight>()
.init_resource::<DirectionalLightShadowMap>()
.init_resource::<PointLightShadowMap>()
.init_resource::<AmbientLight>()
.init_resource::<VisiblePointLights>()
.add_system_to_stage(
CoreStage::PostUpdate,
shader::asset_shader_defs_system::<StandardMaterial>,
// NOTE: Clusters need to have been added before update_clusters is run so
// add as an exclusive system
add_clusters
.exclusive_system()
.label(SimulationLightSystems::AddClusters),
)
.init_resource::<AmbientLight>();
add_pbr_graph(&mut app.world);
.add_system_to_stage(
CoreStage::PostUpdate,
// NOTE: Must come after add_clusters!
update_clusters
.label(SimulationLightSystems::UpdateClusters)
.after(TransformSystem::TransformPropagate),
)
.add_system_to_stage(
CoreStage::PostUpdate,
assign_lights_to_clusters
.label(SimulationLightSystems::AssignLightsToClusters)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::UpdateClusters),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_directional_light_frusta
.label(SimulationLightSystems::UpdateDirectionalLightFrusta)
.after(TransformSystem::TransformPropagate),
)
.add_system_to_stage(
CoreStage::PostUpdate,
update_point_light_frusta
.label(SimulationLightSystems::UpdatePointLightFrusta)
.after(TransformSystem::TransformPropagate)
.after(SimulationLightSystems::AssignLightsToClusters),
)
.add_system_to_stage(
CoreStage::PostUpdate,
check_light_mesh_visibility
.label(SimulationLightSystems::CheckLightVisibility)
.after(TransformSystem::TransformPropagate)
.after(VisibilitySystems::CalculateBounds)
.after(SimulationLightSystems::UpdateDirectionalLightFrusta)
.after(SimulationLightSystems::UpdatePointLightFrusta)
// NOTE: This MUST be scheduled AFTER the core renderer visibility check
// because that resets entity ComputedVisibility for the first view
// which would override any results from this otherwise
.after(VisibilitySystems::CheckVisibility),
);
// add default StandardMaterial
let mut materials = app
.world
.get_resource_mut::<Assets<StandardMaterial>>()
let render_app = app.sub_app(RenderApp);
render_app
.add_system_to_stage(
RenderStage::Extract,
render::extract_clusters.label(RenderLightSystems::ExtractClusters),
)
.add_system_to_stage(
RenderStage::Extract,
render::extract_lights.label(RenderLightSystems::ExtractLights),
)
.add_system_to_stage(
RenderStage::Prepare,
// this is added as an exclusive system because it contributes new views. it must run (and have Commands applied)
// _before_ the `prepare_views()` system is run. ideally this becomes a normal system when "stageless" features come out
render::prepare_lights
.exclusive_system()
.label(RenderLightSystems::PrepareLights),
)
.add_system_to_stage(
RenderStage::Prepare,
// this is added as an exclusive system because it contributes new views. it must run (and have Commands applied)
// _before_ the `prepare_views()` system is run. ideally this becomes a normal system when "stageless" features come out
render::prepare_clusters
.exclusive_system()
.label(RenderLightSystems::PrepareClusters)
.after(RenderLightSystems::PrepareLights),
)
.add_system_to_stage(
RenderStage::Queue,
render::queue_shadows.label(RenderLightSystems::QueueShadows),
)
.add_system_to_stage(RenderStage::Queue, queue_meshes)
.add_system_to_stage(RenderStage::Queue, render::queue_shadow_view_bind_group)
.add_system_to_stage(RenderStage::PhaseSort, sort_phase_system::<Shadow>)
.init_resource::<PbrPipeline>()
.init_resource::<ShadowPipeline>()
.init_resource::<DrawFunctions<Shadow>>()
.init_resource::<LightMeta>()
.init_resource::<GlobalLightMeta>()
.init_resource::<SpecializedPipelines<PbrPipeline>>()
.init_resource::<SpecializedPipelines<ShadowPipeline>>();
let shadow_pass_node = ShadowPassNode::new(&mut render_app.world);
render_app.add_render_command::<Opaque3d, DrawPbr>();
render_app.add_render_command::<AlphaMask3d, DrawPbr>();
render_app.add_render_command::<Transparent3d, DrawPbr>();
render_app.add_render_command::<Shadow, DrawShadowMesh>();
let mut graph = render_app.world.get_resource_mut::<RenderGraph>().unwrap();
let draw_3d_graph = graph
.get_sub_graph_mut(bevy_core_pipeline::draw_3d_graph::NAME)
.unwrap();
draw_3d_graph.add_node(draw_3d_graph::node::SHADOW_PASS, shadow_pass_node);
draw_3d_graph
.add_node_edge(
draw_3d_graph::node::SHADOW_PASS,
bevy_core_pipeline::draw_3d_graph::node::MAIN_PASS,
)
.unwrap();
draw_3d_graph
.add_slot_edge(
draw_3d_graph.input_node().unwrap().id,
bevy_core_pipeline::draw_3d_graph::input::VIEW_ENTITY,
draw_3d_graph::node::SHADOW_PASS,
ShadowPassNode::IN_VIEW,
)
.unwrap();
materials.set_untracked(
Handle::<StandardMaterial>::default(),
StandardMaterial {
base_color: Color::PINK,
unlit: true,
..Default::default()
},
);
}
}

View file

@ -1,53 +1,79 @@
use bevy_core::{Pod, Zeroable};
use bevy_ecs::{component::Component, reflect::ReflectComponent};
use bevy_math::Vec3;
use bevy_reflect::Reflect;
use bevy_render::color::Color;
use bevy_transform::components::GlobalTransform;
use std::collections::HashSet;
/// A point light
#[derive(Component, Debug, Clone, Copy, Reflect)]
#[reflect(Component)]
use bevy_ecs::prelude::*;
use bevy_math::{Mat4, UVec2, UVec3, Vec2, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
use bevy_render::{
camera::{Camera, CameraProjection, OrthographicProjection},
color::Color,
primitives::{Aabb, CubemapFrusta, Frustum, Sphere},
view::{ComputedVisibility, RenderLayers, Visibility, VisibleEntities},
};
use bevy_transform::components::GlobalTransform;
use bevy_window::Windows;
use crate::{
CubeMapFace, CubemapVisibleEntities, ViewClusterBindings, CUBE_MAP_FACES, POINT_LIGHT_NEAR_Z,
};
/// A light that emits light in all directions from a central point.
///
/// Real-world values for `intensity` (luminous power in lumens) based on the electrical power
/// consumption of the type of real-world light are:
///
/// | Luminous Power (lumen) (i.e. the intensity member) | Incandescent non-halogen (Watts) | Incandescent halogen (Watts) | Compact fluorescent (Watts) | LED (Watts |
/// |------|-----|----|--------|-------|
/// | 200 | 25 | | 3-5 | 3 |
/// | 450 | 40 | 29 | 9-11 | 5-8 |
/// | 800 | 60 | | 13-15 | 8-12 |
/// | 1100 | 75 | 53 | 18-20 | 10-16 |
/// | 1600 | 100 | 72 | 24-28 | 14-17 |
/// | 2400 | 150 | | 30-52 | 24-30 |
/// | 3100 | 200 | | 49-75 | 32 |
/// | 4000 | 300 | | 75-100 | 40.5 |
///
/// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lumen_(unit)#Lighting)
#[derive(Component, Debug, Clone, Copy)]
pub struct PointLight {
pub color: Color,
pub intensity: f32,
pub range: f32,
pub radius: f32,
pub shadows_enabled: bool,
pub shadow_depth_bias: f32,
/// A bias applied along the direction of the fragment's surface normal. It is scaled to the
/// shadow map's texel size so that it can be small close to the camera and gets larger further
/// away.
pub shadow_normal_bias: f32,
}
impl Default for PointLight {
fn default() -> Self {
PointLight {
color: Color::rgb(1.0, 1.0, 1.0),
intensity: 200.0,
/// Luminous power in lumens
intensity: 800.0, // Roughly a 60W non-halogen incandescent bulb
range: 20.0,
radius: 0.0,
shadows_enabled: false,
shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
pub(crate) struct PointLightUniform {
pub pos: [f32; 4],
pub color: [f32; 4],
// storing as a `[f32; 4]` for memory alignement
pub light_params: [f32; 4],
impl PointLight {
pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02;
pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.6;
}
impl PointLightUniform {
pub fn new(light: &PointLight, global_transform: &GlobalTransform) -> PointLightUniform {
let (x, y, z) = global_transform.translation.into();
#[derive(Clone, Debug)]
pub struct PointLightShadowMap {
pub size: usize,
}
// premultiply color by intensity
// we don't use the alpha at all, so no reason to multiply only [0..3]
let color: [f32; 4] = (light.color * light.intensity).into();
PointLightUniform {
pos: [x, y, z, 1.0],
color,
light_params: [1.0 / (light.range * light.range), light.radius, 0.0, 0.0],
}
impl Default for PointLightShadowMap {
fn default() -> Self {
Self { size: 1024 }
}
}
@ -77,86 +103,62 @@ impl PointLightUniform {
/// | 32,000100,000 | Direct sunlight |
///
/// Source: [Wikipedia](https://en.wikipedia.org/wiki/Lux)
#[derive(Component, Debug, Clone, Copy, Reflect)]
#[reflect(Component)]
#[derive(Component, Debug, Clone)]
pub struct DirectionalLight {
pub color: Color,
/// Illuminance in lux
pub illuminance: f32,
direction: Vec3,
}
impl DirectionalLight {
/// Create a new directional light component.
pub fn new(color: Color, illuminance: f32, direction: Vec3) -> Self {
DirectionalLight {
color,
illuminance,
direction: direction.normalize(),
}
}
/// Set direction of light.
pub fn set_direction(&mut self, direction: Vec3) {
self.direction = direction.normalize();
}
pub fn get_direction(&self) -> Vec3 {
self.direction
}
pub shadows_enabled: bool,
pub shadow_projection: OrthographicProjection,
pub shadow_depth_bias: f32,
/// A bias applied along the direction of the fragment's surface normal. It is scaled to the
/// shadow map's texel size so that it is automatically adjusted to the orthographic projection.
pub shadow_normal_bias: f32,
}
impl Default for DirectionalLight {
fn default() -> Self {
let size = 100.0;
DirectionalLight {
color: Color::rgb(1.0, 1.0, 1.0),
illuminance: 100000.0,
direction: Vec3::new(0.0, -1.0, 0.0),
shadows_enabled: false,
shadow_projection: OrthographicProjection {
left: -size,
right: size,
bottom: -size,
top: size,
near: -size,
far: size,
..Default::default()
},
shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
pub(crate) struct DirectionalLightUniform {
pub dir: [f32; 4],
pub color: [f32; 4],
impl DirectionalLight {
pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02;
pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.6;
}
impl DirectionalLightUniform {
pub fn new(light: &DirectionalLight) -> DirectionalLightUniform {
// direction is negated to be ready for N.L
let dir: [f32; 4] = [
-light.direction.x,
-light.direction.y,
-light.direction.z,
0.0,
];
#[derive(Clone, Debug)]
pub struct DirectionalLightShadowMap {
pub size: usize,
}
// convert from illuminance (lux) to candelas
//
// exposure is hard coded at the moment but should be replaced
// by values coming from the camera
// see: https://google.github.io/filament/Filament.html#imagingpipeline/physicallybasedcamera/exposuresettings
const APERTURE: f32 = 4.0;
const SHUTTER_SPEED: f32 = 1.0 / 250.0;
const SENSITIVITY: f32 = 100.0;
let ev100 = f32::log2(APERTURE * APERTURE / SHUTTER_SPEED) - f32::log2(SENSITIVITY / 100.0);
let exposure = 1.0 / (f32::powf(2.0, ev100) * 1.2);
let intensity = light.illuminance * exposure;
// premultiply color by intensity
// we don't use the alpha at all, so no reason to multiply only [0..3]
let color: [f32; 4] = (light.color * intensity).into();
DirectionalLightUniform { dir, color }
impl Default for DirectionalLightShadowMap {
fn default() -> Self {
Self { size: 4096 }
}
}
// Ambient light color.
/// An ambient light, which lights the entire scene equally.
#[derive(Debug)]
pub struct AmbientLight {
pub color: Color,
/// Color is premultiplied by brightness before being passed to the shader
/// A direct scale factor multiplied with `color` before being passed to the shader.
pub brightness: f32,
}
@ -168,3 +170,591 @@ impl Default for AmbientLight {
}
}
}
/// Add this component to make a [`Mesh`](bevy_render::mesh::Mesh) not cast shadows.
#[derive(Component)]
pub struct NotShadowCaster;
/// Add this component to make a [`Mesh`](bevy_render::mesh::Mesh) not receive shadows.
#[derive(Component)]
pub struct NotShadowReceiver;
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
pub enum SimulationLightSystems {
AddClusters,
UpdateClusters,
AssignLightsToClusters,
UpdateDirectionalLightFrusta,
UpdatePointLightFrusta,
CheckLightVisibility,
}
// Clustered-forward rendering notes
// The main initial reference material used was this rather accessible article:
// http://www.aortiz.me/2018/12/21/CG.html
// Some inspiration was taken from “Practical Clustered Shading” which is part 2 of:
// https://efficientshading.com/2015/01/01/real-time-many-light-management-and-shadows-with-clustered-shading/
// (Also note that Part 3 of the above shows how we could support the shadow mapping for many lights.)
// The z-slicing method mentioned in the aortiz article is originally from Tiago Sousas 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
}
}
}
}

View file

@ -1,47 +1,54 @@
use bevy_asset::{self, Handle};
use crate::{AlphaMode, PbrPipeline, StandardMaterialFlags};
use bevy_app::{App, Plugin};
use bevy_asset::{AddAsset, Handle};
use bevy_ecs::system::{lifetimeless::SRes, SystemParamItem};
use bevy_math::Vec4;
use bevy_reflect::TypeUuid;
use bevy_render::{color::Color, renderer::RenderResources, shader::ShaderDefs, texture::Texture};
use bevy_render::{
color::Color,
render_asset::{PrepareAssetError, RenderAsset, RenderAssetPlugin, RenderAssets},
render_resource::{BindGroup, Buffer, BufferInitDescriptor, BufferUsages},
renderer::RenderDevice,
texture::Image,
};
use crevice::std140::{AsStd140, Std140};
use wgpu::{BindGroupDescriptor, BindGroupEntry, BindingResource};
/// A material with "standard" properties used in PBR lighting
/// Standard property values with pictures here <https://google.github.io/filament/Material%20Properties.pdf>
#[derive(Debug, RenderResources, ShaderDefs, TypeUuid)]
#[uuid = "dace545e-4bc6-4595-a79d-c224fc694975"]
/// Standard property values with pictures here
/// <https://google.github.io/filament/Material%20Properties.pdf>.
///
/// May be created directly from a [`Color`] or an [`Image`].
#[derive(Debug, Clone, TypeUuid)]
#[uuid = "7494888b-c082-457b-aacf-517228cc0c22"]
pub struct StandardMaterial {
/// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything
/// in between If used together with a base_color_texture, this is factored into the final
/// in between. If used together with a base_color_texture, this is factored into the final
/// base color as `base_color * base_color_texture_value`
pub base_color: Color,
#[shader_def]
pub base_color_texture: Option<Handle<Texture>>,
pub base_color_texture: Option<Handle<Image>>,
// Use a color for user friendliness even though we technically don't use the alpha channel
// Might be used in the future for exposure correction in HDR
pub emissive: Color,
pub emissive_texture: Option<Handle<Image>>,
/// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader
/// Defaults to minimum of 0.089
/// If used together with a roughness/metallic texture, this is factored into the final base
/// color as `roughness * roughness_texture_value`
pub roughness: f32,
pub perceptual_roughness: f32,
/// From [0.0, 1.0], dielectric to pure metallic
/// If used together with a roughness/metallic texture, this is factored into the final base
/// color as `metallic * metallic_texture_value`
pub metallic: f32,
pub metallic_roughness_texture: Option<Handle<Image>>,
/// Specular intensity for non-metals on a linear scale of [0.0, 1.0]
/// defaults to 0.5 which is mapped to 4% reflectance in the shader
#[shader_def]
pub metallic_roughness_texture: Option<Handle<Texture>>,
pub reflectance: f32,
#[shader_def]
pub normal_map: Option<Handle<Texture>>,
#[render_resources(ignore)]
#[shader_def]
pub normal_map_texture: Option<Handle<Image>>,
pub occlusion_texture: Option<Handle<Image>>,
pub double_sided: bool,
#[shader_def]
pub occlusion_texture: Option<Handle<Texture>>,
// Use a color for user friendliness even though we technically don't use the alpha channel
// Might be used in the future for exposure correction in HDR
pub emissive: Color,
#[shader_def]
pub emissive_texture: Option<Handle<Texture>>,
#[render_resources(ignore)]
#[shader_def]
pub unlit: bool,
pub alpha_mode: AlphaMode,
}
impl Default for StandardMaterial {
@ -49,26 +56,27 @@ impl Default for StandardMaterial {
StandardMaterial {
base_color: Color::rgb(1.0, 1.0, 1.0),
base_color_texture: None,
emissive: Color::BLACK,
emissive_texture: None,
// This is the minimum the roughness is clamped to in shader code
// See https://google.github.io/filament/Filament.html#materialsystem/parameterization/
// See <https://google.github.io/filament/Filament.html#materialsystem/parameterization/>
// It's the minimum floating point value that won't be rounded down to 0 in the
// calculations used. Although technically for 32-bit floats, 0.045 could be
// used.
roughness: 0.089,
perceptual_roughness: 0.089,
// Few materials are purely dielectric or metallic
// This is just a default for mostly-dielectric
metallic: 0.01,
metallic_roughness_texture: None,
// Minimum real-world reflectance is 2%, most materials between 2-5%
// Expressed in a linear scale and equivalent to 4% reflectance see
// https://google.github.io/filament/Material%20Properties.pdf
metallic_roughness_texture: None,
// <https://google.github.io/filament/Material%20Properties.pdf>
reflectance: 0.5,
normal_map: None,
double_sided: false,
occlusion_texture: None,
emissive: Color::BLACK,
emissive_texture: None,
normal_map_texture: None,
double_sided: false,
unlit: false,
alpha_mode: AlphaMode::Opaque,
}
}
}
@ -82,11 +90,227 @@ impl From<Color> for StandardMaterial {
}
}
impl From<Handle<Texture>> for StandardMaterial {
fn from(texture: Handle<Texture>) -> Self {
impl From<Handle<Image>> for StandardMaterial {
fn from(texture: Handle<Image>) -> Self {
StandardMaterial {
base_color_texture: Some(texture),
..Default::default()
}
}
}
/// The GPU representation of the uniform data of a [`StandardMaterial`].
#[derive(Clone, Default, AsStd140)]
pub struct StandardMaterialUniformData {
/// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything
/// in between.
pub base_color: Vec4,
// Use a color for user friendliness even though we technically don't use the alpha channel
// Might be used in the future for exposure correction in HDR
pub emissive: Vec4,
/// Linear perceptual roughness, clamped to [0.089, 1.0] in the shader
/// Defaults to minimum of 0.089
pub roughness: f32,
/// From [0.0, 1.0], dielectric to pure metallic
pub metallic: f32,
/// Specular intensity for non-metals on a linear scale of [0.0, 1.0]
/// defaults to 0.5 which is mapped to 4% reflectance in the shader
pub reflectance: f32,
pub flags: u32,
/// When the alpha mode mask flag is set, any base color alpha above this cutoff means fully opaque,
/// and any below means fully transparent.
pub alpha_cutoff: f32,
}
/// This plugin adds the [`StandardMaterial`] asset to the app.
pub struct StandardMaterialPlugin;
impl Plugin for StandardMaterialPlugin {
fn build(&self, app: &mut App) {
app.add_plugin(RenderAssetPlugin::<StandardMaterial>::default())
.add_asset::<StandardMaterial>();
}
}
/// The GPU representation of a [`StandardMaterial`].
#[derive(Debug, Clone)]
pub struct GpuStandardMaterial {
/// A buffer containing the [`StandardMaterialUniformData`] of the material.
pub buffer: Buffer,
/// The bind group specifying how the [`StandardMaterialUniformData`] and
/// all the textures of the material are bound.
pub bind_group: BindGroup,
pub has_normal_map: bool,
pub flags: StandardMaterialFlags,
pub base_color_texture: Option<Handle<Image>>,
pub alpha_mode: AlphaMode,
}
impl RenderAsset for StandardMaterial {
type ExtractedAsset = StandardMaterial;
type PreparedAsset = GpuStandardMaterial;
type Param = (
SRes<RenderDevice>,
SRes<PbrPipeline>,
SRes<RenderAssets<Image>>,
);
fn extract_asset(&self) -> Self::ExtractedAsset {
self.clone()
}
fn prepare_asset(
material: Self::ExtractedAsset,
(render_device, pbr_pipeline, gpu_images): &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>> {
let (base_color_texture_view, base_color_sampler) = if let Some(result) = pbr_pipeline
.mesh_pipeline
.get_image_texture(gpu_images, &material.base_color_texture)
{
result
} else {
return Err(PrepareAssetError::RetryNextUpdate(material));
};
let (emissive_texture_view, emissive_sampler) = if let Some(result) = pbr_pipeline
.mesh_pipeline
.get_image_texture(gpu_images, &material.emissive_texture)
{
result
} else {
return Err(PrepareAssetError::RetryNextUpdate(material));
};
let (metallic_roughness_texture_view, metallic_roughness_sampler) = if let Some(result) =
pbr_pipeline
.mesh_pipeline
.get_image_texture(gpu_images, &material.metallic_roughness_texture)
{
result
} else {
return Err(PrepareAssetError::RetryNextUpdate(material));
};
let (normal_map_texture_view, normal_map_sampler) = if let Some(result) = pbr_pipeline
.mesh_pipeline
.get_image_texture(gpu_images, &material.normal_map_texture)
{
result
} else {
return Err(PrepareAssetError::RetryNextUpdate(material));
};
let (occlusion_texture_view, occlusion_sampler) = if let Some(result) = pbr_pipeline
.mesh_pipeline
.get_image_texture(gpu_images, &material.occlusion_texture)
{
result
} else {
return Err(PrepareAssetError::RetryNextUpdate(material));
};
let mut flags = StandardMaterialFlags::NONE;
if material.base_color_texture.is_some() {
flags |= StandardMaterialFlags::BASE_COLOR_TEXTURE;
}
if material.emissive_texture.is_some() {
flags |= StandardMaterialFlags::EMISSIVE_TEXTURE;
}
if material.metallic_roughness_texture.is_some() {
flags |= StandardMaterialFlags::METALLIC_ROUGHNESS_TEXTURE;
}
if material.occlusion_texture.is_some() {
flags |= StandardMaterialFlags::OCCLUSION_TEXTURE;
}
if material.double_sided {
flags |= StandardMaterialFlags::DOUBLE_SIDED;
}
if material.unlit {
flags |= StandardMaterialFlags::UNLIT;
}
let has_normal_map = material.normal_map_texture.is_some();
// NOTE: 0.5 is from the glTF default - do we want this?
let mut alpha_cutoff = 0.5;
match material.alpha_mode {
AlphaMode::Opaque => flags |= StandardMaterialFlags::ALPHA_MODE_OPAQUE,
AlphaMode::Mask(c) => {
alpha_cutoff = c;
flags |= StandardMaterialFlags::ALPHA_MODE_MASK
}
AlphaMode::Blend => flags |= StandardMaterialFlags::ALPHA_MODE_BLEND,
};
let value = StandardMaterialUniformData {
base_color: material.base_color.as_linear_rgba_f32().into(),
emissive: material.emissive.into(),
roughness: material.perceptual_roughness,
metallic: material.metallic,
reflectance: material.reflectance,
flags: flags.bits(),
alpha_cutoff,
};
let value_std140 = value.as_std140();
let buffer = render_device.create_buffer_with_data(&BufferInitDescriptor {
label: Some("pbr_standard_material_uniform_buffer"),
usage: BufferUsages::UNIFORM | BufferUsages::COPY_DST,
contents: value_std140.as_bytes(),
});
let bind_group = render_device.create_bind_group(&BindGroupDescriptor {
entries: &[
BindGroupEntry {
binding: 0,
resource: buffer.as_entire_binding(),
},
BindGroupEntry {
binding: 1,
resource: BindingResource::TextureView(base_color_texture_view),
},
BindGroupEntry {
binding: 2,
resource: BindingResource::Sampler(base_color_sampler),
},
BindGroupEntry {
binding: 3,
resource: BindingResource::TextureView(emissive_texture_view),
},
BindGroupEntry {
binding: 4,
resource: BindingResource::Sampler(emissive_sampler),
},
BindGroupEntry {
binding: 5,
resource: BindingResource::TextureView(metallic_roughness_texture_view),
},
BindGroupEntry {
binding: 6,
resource: BindingResource::Sampler(metallic_roughness_sampler),
},
BindGroupEntry {
binding: 7,
resource: BindingResource::TextureView(occlusion_texture_view),
},
BindGroupEntry {
binding: 8,
resource: BindingResource::Sampler(occlusion_sampler),
},
BindGroupEntry {
binding: 9,
resource: BindingResource::TextureView(normal_map_texture_view),
},
BindGroupEntry {
binding: 10,
resource: BindingResource::Sampler(normal_map_sampler),
},
],
label: Some("pbr_standard_material_bind_group"),
layout: &pbr_pipeline.material_layout,
});
Ok(GpuStandardMaterial {
buffer,
bind_group,
flags,
has_normal_map,
base_color_texture: material.base_color_texture,
alpha_mode: material.alpha_mode,
})
}
}

View file

@ -11,7 +11,7 @@ use bevy_ecs::{
system::{lifetimeless::*, SystemParamItem},
};
use bevy_math::{const_vec3, Mat4, UVec3, UVec4, Vec3, Vec4, Vec4Swizzles};
use bevy_render2::{
use bevy_render::{
camera::{Camera, CameraProjection},
color::Color,
mesh::Mesh,

View file

@ -10,7 +10,7 @@ use bevy_ecs::{
};
use bevy_math::Mat4;
use bevy_reflect::TypeUuid;
use bevy_render2::{
use bevy_render::{
mesh::Mesh,
render_asset::RenderAssets,
render_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
@ -693,16 +693,19 @@ impl EntityRenderCommand for DrawMesh {
pass: &mut TrackedRenderPass<'w>,
) -> RenderCommandResult {
let mesh_handle = mesh_query.get(item).unwrap();
let gpu_mesh = meshes.into_inner().get(mesh_handle).unwrap();
pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..));
if let Some(index_info) = &gpu_mesh.index_info {
pass.set_index_buffer(index_info.buffer.slice(..), 0, index_info.index_format);
pass.draw_indexed(0..index_info.count, 0, 0..1);
} else {
panic!("non-indexed drawing not supported yet")
}
if let Some(gpu_mesh) = meshes.into_inner().get(mesh_handle) {
pass.set_vertex_buffer(0, gpu_mesh.vertex_buffer.slice(..));
if let Some(index_info) = &gpu_mesh.index_info {
pass.set_index_buffer(index_info.buffer.slice(..), 0, index_info.index_format);
pass.draw_indexed(0..index_info.count, 0, 0..1);
} else {
panic!("non-indexed drawing not supported yet")
}
RenderCommandResult::Success
RenderCommandResult::Success
} else {
RenderCommandResult::Failure
}
}
}

View file

@ -11,7 +11,7 @@ use bevy_ecs::{
prelude::*,
system::{lifetimeless::*, SystemParamItem},
};
use bevy_render2::{
use bevy_render::{
mesh::Mesh,
render_asset::RenderAssets,
render_phase::{
@ -199,6 +199,15 @@ impl SpecializedPipeline for PbrPipeline {
self.material_layout.clone(),
self.mesh_pipeline.mesh_layout.clone(),
]);
if key.normal_map {
descriptor
.fragment
.as_mut()
.unwrap()
.shader_defs
.push(String::from("STANDARDMATERIAL_NORMAL_MAP"));
}
if let Some(label) = &mut descriptor.label {
*label = format!("pbr_{}", *label).into();
}

View file

@ -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,
);
}

View file

@ -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);
}

View file

@ -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"),
))),
})
}
}

View file

@ -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 (α21) + 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;
}

View file

@ -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;
}

View file

@ -6,7 +6,7 @@ use bevy_core_pipeline::Opaque3d;
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
use bevy_reflect::Reflect;
use bevy_reflect::TypeUuid;
use bevy_render2::{
use bevy_render::{
mesh::Mesh,
render_phase::{AddRenderCommand, DrawFunctions, RenderPhase, SetItemPipeline},
render_resource::{RenderPipelineCache, Shader, SpecializedPipeline, SpecializedPipelines},
@ -80,10 +80,7 @@ impl FromWorld for WireframePipeline {
impl SpecializedPipeline for WireframePipeline {
type Key = MeshPipelineKey;
fn specialize(
&self,
key: Self::Key,
) -> bevy_render2::render_resource::RenderPipelineDescriptor {
fn specialize(&self, key: Self::Key) -> bevy_render::render_resource::RenderPipelineDescriptor {
let mut descriptor = self.mesh_pipeline.specialize(key);
descriptor.vertex.shader = self.shader.clone_weak();
descriptor.fragment.as_mut().unwrap().shader = self.shader.clone_weak();

View file

@ -8,6 +8,17 @@ repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]
[features]
png = ["image/png"]
hdr = ["image/hdr"]
dds = ["image/dds"]
tga = ["image/tga"]
jpeg = ["image/jpeg"]
bmp = ["image/bmp"]
trace = []
wgpu_trace = ["wgpu/trace"]
ci_limits = []
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.5.0" }
@ -25,29 +36,21 @@ bevy_utils = { path = "../bevy_utils", version = "0.5.0" }
image = { version = "0.23.12", default-features = false }
# misc
wgpu = { version = "0.11.0", features = ["spirv"] }
naga = { version = "0.7.0", features = ["glsl-in", "spv-in", "spv-out", "wgsl-in", "wgsl-out"] }
serde = { version = "1", features = ["derive"] }
bitflags = "1.2.1"
smallvec = { version = "1.6", features = ["union", "const_generics"] }
once_cell = "1.4.1" # TODO: replace once_cell with std equivalent if/when this lands: https://github.com/rust-lang/rfcs/pull/2788
downcast-rs = "1.2.0"
thiserror = "1.0"
anyhow = "1.0.4"
futures-lite = "1.4.0"
anyhow = "1.0"
hex = "0.4.2"
hexasphere = "6.0.0"
parking_lot = "0.11.0"
regex = "1.5"
crevice = { path = "../crevice", version = "0.8.0", features = ["glam"] }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
spirv-reflect = "0.2.3"
[target.'cfg(any(all(target_arch="x86_64", target_os="linux", target_env="gnu"), all(target_arch="x86_64", target_os="macos"), all(target_arch="aarch64", target_os="android"), all(target_arch="armv7", target_os="androidabi"), all(target_arch="x86_64", target_os="windows", target_env="msvc")))'.dependencies]
bevy-glsl-to-spirv = "0.2.0"
[target.'cfg(not(any(target_arch = "wasm32", all(target_arch="x86_64", target_os="linux", target_env="gnu"), all(target_arch="x86_64", target_os="macos"), all(target_arch="aarch64", target_os="android"), all(target_arch="armv7", target_os="androidabi"), all(target_arch="x86_64", target_os="windows", target_env="msvc"))))'.dependencies]
shaderc = "0.7.0"
[features]
png = ["image/png"]
hdr = ["image/hdr"]
dds = ["image/dds"]
tga = ["image/tga"]
jpeg = ["image/jpeg"]
bmp = ["image/bmp"]
[target.'cfg(target_arch = "wasm32")'.dependencies]
wgpu = { version = "0.11.0", features = ["spirv", "webgl"] }

View file

@ -1,18 +1,14 @@
use crate::renderer::RenderResourceBindings;
use super::Camera;
use bevy_ecs::{
component::Component,
entity::Entity,
system::{Query, ResMut},
};
use bevy_utils::HashMap;
#[derive(Component, Debug, Default)]
#[derive(Debug, Default)]
pub struct ActiveCamera {
pub name: String,
pub entity: Option<Entity>,
pub bindings: RenderResourceBindings,
}
#[derive(Debug, Default)]

View file

@ -1,10 +1,9 @@
use super::CameraProjection;
use crate::camera::CameraProjection;
use bevy_ecs::{
change_detection::DetectChanges,
component::Component,
entity::Entity,
event::EventReader,
prelude::QueryState,
prelude::{DetectChanges, QueryState},
query::Added,
reflect::ReflectComponent,
system::{QuerySet, Res},
@ -24,6 +23,8 @@ pub struct Camera {
pub window: WindowId,
#[reflect(ignore)]
pub depth_calculation: DepthCalculation,
pub near: f32,
pub far: f32,
}
#[derive(Debug, Clone, Copy, Reflect, Serialize, Deserialize)]

View file

@ -1,10 +1,107 @@
mod active_cameras;
mod bundle;
#[allow(clippy::module_inception)]
mod camera;
mod projection;
mod visible_entities;
pub use active_cameras::*;
use bevy_transform::components::GlobalTransform;
use bevy_utils::HashMap;
use bevy_window::{WindowId, Windows};
pub use bundle::*;
pub use camera::*;
pub use projection::*;
pub use visible_entities::*;
use crate::{
primitives::Aabb,
view::{ComputedVisibility, ExtractedView, Visibility, VisibleEntities},
RenderApp, RenderStage,
};
use bevy_app::{App, CoreStage, Plugin};
use bevy_ecs::prelude::*;
#[derive(Default)]
pub struct CameraPlugin;
impl CameraPlugin {
pub const CAMERA_2D: &'static str = "camera_2d";
pub const CAMERA_3D: &'static str = "camera_3d";
}
impl Plugin for CameraPlugin {
fn build(&self, app: &mut App) {
let mut active_cameras = ActiveCameras::default();
active_cameras.add(Self::CAMERA_2D);
active_cameras.add(Self::CAMERA_3D);
app.register_type::<Camera>()
.register_type::<Visibility>()
.register_type::<ComputedVisibility>()
.register_type::<OrthographicProjection>()
.register_type::<PerspectiveProjection>()
.register_type::<VisibleEntities>()
.register_type::<WindowOrigin>()
.register_type::<ScalingMode>()
.register_type::<DepthCalculation>()
.register_type::<Aabb>()
.insert_resource(active_cameras)
.add_system_to_stage(CoreStage::PostUpdate, crate::camera::active_cameras_system)
.add_system_to_stage(
CoreStage::PostUpdate,
crate::camera::camera_system::<OrthographicProjection>,
)
.add_system_to_stage(
CoreStage::PostUpdate,
crate::camera::camera_system::<PerspectiveProjection>,
);
app.sub_app(RenderApp)
.init_resource::<ExtractedCameraNames>()
.add_system_to_stage(RenderStage::Extract, extract_cameras);
}
}
#[derive(Default)]
pub struct ExtractedCameraNames {
pub entities: HashMap<String, Entity>,
}
#[derive(Component, Debug)]
pub struct ExtractedCamera {
pub window_id: WindowId,
pub name: Option<String>,
}
fn extract_cameras(
mut commands: Commands,
active_cameras: Res<ActiveCameras>,
windows: Res<Windows>,
query: Query<(Entity, &Camera, &GlobalTransform, &VisibleEntities)>,
) {
let mut entities = HashMap::default();
for camera in active_cameras.iter() {
let name = &camera.name;
if let Some((entity, camera, transform, visible_entities)) =
camera.entity.and_then(|e| query.get(e).ok())
{
entities.insert(name.clone(), entity);
if let Some(window) = windows.get(camera.window) {
commands.get_or_spawn(entity).insert_bundle((
ExtractedCamera {
window_id: camera.window,
name: camera.name.clone(),
},
ExtractedView {
projection: camera.projection_matrix,
transform: *transform,
width: window.physical_width().max(1),
height: window.physical_height().max(1),
near: camera.near,
far: camera.far,
},
visible_entities.clone(),
));
}
}
}
commands.insert_resource(ExtractedCameraNames { entities })
}

View file

@ -8,6 +8,7 @@ pub trait CameraProjection {
fn get_projection_matrix(&self) -> Mat4;
fn update(&mut self, width: f32, height: f32);
fn depth_calculation(&self) -> DepthCalculation;
fn far(&self) -> f32;
}
#[derive(Component, Debug, Clone, Reflect)]
@ -21,7 +22,7 @@ pub struct PerspectiveProjection {
impl CameraProjection for PerspectiveProjection {
fn get_projection_matrix(&self) -> Mat4 {
Mat4::perspective_rh(self.fov, self.aspect_ratio, self.near, self.far)
Mat4::perspective_infinite_reverse_rh(self.fov, self.aspect_ratio, self.near)
}
fn update(&mut self, width: f32, height: f32) {
@ -31,13 +32,17 @@ impl CameraProjection for PerspectiveProjection {
fn depth_calculation(&self) -> DepthCalculation {
DepthCalculation::Distance
}
fn far(&self) -> f32 {
self.far
}
}
impl Default for PerspectiveProjection {
fn default() -> Self {
PerspectiveProjection {
fov: std::f32::consts::PI / 4.0,
near: 1.0,
near: 0.1,
far: 1000.0,
aspect_ratio: 1.0,
}
@ -88,8 +93,10 @@ impl CameraProjection for OrthographicProjection {
self.right * self.scale,
self.bottom * self.scale,
self.top * self.scale,
self.near,
// NOTE: near and far are swapped to invert the depth range from [0,1] to [1,0]
// This is for interoperability with pipelines using infinite reverse perspective projections.
self.far,
self.near,
)
}
@ -144,6 +151,10 @@ impl CameraProjection for OrthographicProjection {
fn depth_calculation(&self) -> DepthCalculation {
self.depth_calculation
}
fn far(&self) -> f32 {
self.far
}
}
impl Default for OrthographicProjection {

View file

@ -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

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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(),
}
}
}

View file

@ -1,231 +1,311 @@
#![allow(clippy::all)]
pub mod camera;
pub mod color;
pub mod colorspace;
pub mod draw;
pub mod entity;
pub mod mesh;
pub mod pass;
pub mod pipeline;
pub mod options;
pub mod primitives;
pub mod render_asset;
pub mod render_component;
pub mod render_graph;
pub mod render_phase;
pub mod render_resource;
pub mod renderer;
pub mod shader;
pub mod texture;
pub mod wireframe;
use bevy_ecs::{
schedule::{ParallelSystemDescriptorCoercion, SystemStage},
system::{IntoExclusiveSystem, Res},
};
use bevy_transform::TransformSystem;
use bevy_utils::tracing::warn;
use draw::{OutsideFrustum, Visible};
pub use once_cell;
pub mod view;
pub mod prelude {
#[doc(hidden)]
pub use crate::{
base::Msaa,
camera::{
Camera, OrthographicCameraBundle, OrthographicProjection, PerspectiveCameraBundle,
PerspectiveProjection,
},
color::Color,
draw::{Draw, Visible},
entity::*,
mesh::{shape, Mesh},
pass::ClearColor,
pipeline::RenderPipelines,
shader::Shader,
texture::Texture,
render_resource::Shader,
texture::Image,
view::{ComputedVisibility, Msaa, Visibility},
};
}
use crate::prelude::*;
use base::Msaa;
use bevy_app::prelude::*;
use bevy_asset::{AddAsset, AssetStage};
use bevy_ecs::schedule::{StageLabel, SystemLabel};
use camera::{
ActiveCameras, Camera, DepthCalculation, OrthographicProjection, PerspectiveProjection,
RenderLayers, ScalingMode, VisibleEntities, WindowOrigin,
};
use pipeline::{
IndexFormat, PipelineCompiler, PipelineDescriptor, PipelineSpecialization, PrimitiveTopology,
ShaderSpecialization, VertexBufferLayout,
};
use render_graph::{
base::{self, BaseRenderGraphConfig, MainPass},
RenderGraph,
};
use renderer::{AssetRenderResourceBindings, RenderResourceBindings, RenderResourceContext};
use shader::ShaderLoader;
#[cfg(feature = "hdr")]
use texture::HdrTextureLoader;
#[cfg(any(
feature = "png",
feature = "dds",
feature = "tga",
feature = "jpeg",
feature = "bmp"
))]
use texture::ImageTextureLoader;
pub use once_cell;
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
pub enum RenderSystem {
VisibleEntities,
}
use crate::{
camera::CameraPlugin,
color::Color,
mesh::MeshPlugin,
primitives::Frustum,
render_graph::RenderGraph,
render_resource::{RenderPipelineCache, Shader, ShaderLoader},
renderer::render_system,
texture::ImagePlugin,
view::{ViewPlugin, WindowRenderPlugin},
};
use bevy_app::{App, AppLabel, Plugin};
use bevy_asset::{AddAsset, AssetServer};
use bevy_ecs::prelude::*;
use std::ops::{Deref, DerefMut};
/// The names of "render" App stages
/// Contains the default Bevy rendering backend based on wgpu.
#[derive(Default)]
pub struct RenderPlugin;
/// The labels of the default App rendering stages.
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
pub enum RenderStage {
/// Stage where render resources are set up
RenderResource,
/// Stage where Render Graph systems are run. In general you shouldn't add systems to this
/// stage manually.
RenderGraphSystems,
// Stage where draw systems are executed. This is generally where Draw components are setup
Draw,
/// Extract data from the "app world" and insert it into the "render world".
/// This step should be kept as short as possible to increase the "pipelining potential" for
/// running the next frame while rendering the current frame.
Extract,
/// Prepare render resources from the extracted data for the GPU.
Prepare,
/// Create [`BindGroups`](crate::render_resource::BindGroup) that depend on
/// [`Prepare`](RenderStage::Prepare) data and queue up draw calls to run during the
/// [`Render`](RenderStage::Render) stage.
Queue,
// TODO: This could probably be moved in favor of a system ordering abstraction in Render or Queue
/// Sort the [`RenderPhases`](crate::render_phase::RenderPhase) here.
PhaseSort,
/// Actual rendering happens here.
/// In most cases, only the render backend should insert resources here.
Render,
PostRender,
/// Cleanup render resources here.
Cleanup,
}
/// Adds core render types and systems to an App
pub struct RenderPlugin {
/// configures the "base render graph". If this is not `None`, the "base render graph" will be
/// added
pub base_render_graph_config: Option<BaseRenderGraphConfig>,
}
/// The Render App World. This is only available as a resource during the Extract step.
#[derive(Default)]
pub struct RenderWorld(World);
impl Default for RenderPlugin {
fn default() -> Self {
RenderPlugin {
base_render_graph_config: Some(BaseRenderGraphConfig::default()),
}
impl Deref for RenderWorld {
type Target = World;
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 {
/// Initializes the renderer, sets up the [`RenderStage`](RenderStage) and creates the rendering sub-app.
fn build(&self, app: &mut App) {
#[cfg(any(
feature = "png",
feature = "dds",
feature = "tga",
feature = "jpeg",
feature = "bmp"
))]
{
app.init_asset_loader::<ImageTextureLoader>();
}
#[cfg(feature = "hdr")]
{
app.init_asset_loader::<HdrTextureLoader>();
}
let options = app
.world
.get_resource::<options::WgpuOptions>()
.cloned()
.unwrap_or_default();
let instance = wgpu::Instance::new(options.backends);
let surface = {
let world = app.world.cell();
let windows = world.get_resource_mut::<bevy_window::Windows>().unwrap();
let raw_handle = windows.get_primary().map(|window| unsafe {
let handle = window.raw_window_handle().get_handle();
instance.create_surface(&handle)
});
raw_handle
};
let (device, queue) = futures_lite::future::block_on(renderer::initialize_renderer(
&instance,
&wgpu::RequestAdapterOptions {
power_preference: options.power_preference,
compatible_surface: surface.as_ref(),
..Default::default()
},
&wgpu::DeviceDescriptor {
label: options.device_label.as_ref().map(|a| a.as_ref()),
features: options.features,
limits: options.limits,
},
));
app.insert_resource(device.clone())
.insert_resource(queue.clone())
.add_asset::<Shader>()
.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(
AssetStage::AssetEvents,
RenderStage::RenderResource,
SystemStage::parallel(),
)
.add_stage_after(
RenderStage::RenderResource,
RenderStage::RenderGraphSystems,
SystemStage::parallel(),
)
.add_stage_after(
RenderStage::RenderGraphSystems,
RenderStage::Draw,
SystemStage::parallel(),
)
.add_stage_after(
RenderStage::Draw,
RenderStage::Render,
SystemStage::parallel(),
)
.add_stage_after(
RenderStage::Render,
RenderStage::PostRender,
SystemStage::parallel(),
)
.init_asset_loader::<ShaderLoader>()
.add_asset::<Mesh>()
.add_asset::<Texture>()
.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);
let mut render_app = App::empty();
let mut extract_stage =
SystemStage::parallel().with_system(RenderPipelineCache::extract_shaders);
// don't apply buffers when the stage finishes running
// extract stage runs on the app world, but the buffers are applied to the render world
extract_stage.set_apply_buffers(false);
render_app
.add_stage(RenderStage::Extract, extract_stage)
.add_stage(RenderStage::Prepare, SystemStage::parallel())
.add_stage(RenderStage::Queue, SystemStage::parallel())
.add_stage(RenderStage::PhaseSort, SystemStage::parallel())
.add_stage(
RenderStage::Render,
SystemStage::parallel()
.with_system(RenderPipelineCache::process_pipeline_queue_system)
.with_system(render_system.exclusive_system().at_end()),
)
.add_stage(RenderStage::Cleanup, SystemStage::parallel())
.insert_resource(instance)
.insert_resource(device)
.insert_resource(queue)
.insert_resource(render_pipeline_cache)
.insert_resource(asset_server)
.init_resource::<RenderGraph>();
if let Some(ref config) = self.base_render_graph_config {
crate::base::add_base_graph(config, &mut app.world);
let mut active_cameras = app.world.get_resource_mut::<ActiveCameras>().unwrap();
if config.add_3d_camera {
active_cameras.add(base::camera::CAMERA_3D);
app.add_sub_app(RenderApp, render_app, move |app_world, render_app| {
#[cfg(feature = "trace")]
let render_span = bevy_utils::tracing::info_span!("renderer subapp");
#[cfg(feature = "trace")]
let _render_guard = render_span.enter();
{
#[cfg(feature = "trace")]
let stage_span =
bevy_utils::tracing::info_span!("stage", name = "reserve_and_flush");
#[cfg(feature = "trace")]
let _stage_guard = stage_span.enter();
// reserve all existing app entities for use in render_app
// they can only be spawned using `get_or_spawn()`
let meta_len = app_world.entities().meta.len();
render_app
.world
.entities()
.reserve_entities(meta_len as u32);
// flushing as "invalid" ensures that app world entities aren't added as "empty archetype" entities by default
// these entities cannot be accessed without spawning directly onto them
// this _only_ works as expected because clear_entities() is called at the end of every frame.
render_app.world.entities_mut().flush_as_invalid();
}
if config.add_2d_camera {
active_cameras.add(base::camera::CAMERA_2D);
{
#[cfg(feature = "trace")]
let stage_span = bevy_utils::tracing::info_span!("stage", name = "extract");
#[cfg(feature = "trace")]
let _stage_guard = stage_span.enter();
// extract
extract(app_world, render_app);
}
}
{
#[cfg(feature = "trace")]
let stage_span = bevy_utils::tracing::info_span!("stage", name = "prepare");
#[cfg(feature = "trace")]
let _stage_guard = stage_span.enter();
// prepare
let prepare = render_app
.schedule
.get_stage_mut::<SystemStage>(&RenderStage::Prepare)
.unwrap();
prepare.run(&mut render_app.world);
}
{
#[cfg(feature = "trace")]
let stage_span = bevy_utils::tracing::info_span!("stage", name = "queue");
#[cfg(feature = "trace")]
let _stage_guard = stage_span.enter();
// queue
let queue = render_app
.schedule
.get_stage_mut::<SystemStage>(&RenderStage::Queue)
.unwrap();
queue.run(&mut render_app.world);
}
{
#[cfg(feature = "trace")]
let stage_span = bevy_utils::tracing::info_span!("stage", name = "sort");
#[cfg(feature = "trace")]
let _stage_guard = stage_span.enter();
// phase sort
let phase_sort = render_app
.schedule
.get_stage_mut::<SystemStage>(&RenderStage::PhaseSort)
.unwrap();
phase_sort.run(&mut render_app.world);
}
{
#[cfg(feature = "trace")]
let stage_span = bevy_utils::tracing::info_span!("stage", name = "render");
#[cfg(feature = "trace")]
let _stage_guard = stage_span.enter();
// render
let render = render_app
.schedule
.get_stage_mut::<SystemStage>(&RenderStage::Render)
.unwrap();
render.run(&mut render_app.world);
}
{
#[cfg(feature = "trace")]
let stage_span = bevy_utils::tracing::info_span!("stage", name = "cleanup");
#[cfg(feature = "trace")]
let _stage_guard = stage_span.enter();
// cleanup
let cleanup = render_app
.schedule
.get_stage_mut::<SystemStage>(&RenderStage::Cleanup)
.unwrap();
cleanup.run(&mut render_app.world);
render_app.world.clear_entities();
}
});
app.add_plugin(WindowRenderPlugin)
.add_plugin(CameraPlugin)
.add_plugin(ViewPlugin)
.add_plugin(MeshPlugin)
.add_plugin(ImagePlugin);
}
}
fn check_for_render_resource_context(context: Option<Res<Box<dyn RenderResourceContext>>>) {
if context.is_none() {
warn!(
"bevy_render couldn't find a render backend. Perhaps try adding the bevy_wgpu feature/plugin!"
);
}
/// Executes the [`Extract`](RenderStage::Extract) stage of the renderer.
/// This updates the render world with the extracted ECS data of the current frame.
fn extract(app_world: &mut World, render_app: &mut App) {
let extract = render_app
.schedule
.get_stage_mut::<SystemStage>(&RenderStage::Extract)
.unwrap();
// temporarily add the render world to the app world as a resource
let scratch_world = app_world.remove_resource::<ScratchRenderWorld>().unwrap();
let render_world = std::mem::replace(&mut render_app.world, scratch_world.0);
app_world.insert_resource(RenderWorld(render_world));
extract.run(app_world);
// add the render world back to the render app
let render_world = app_world.remove_resource::<RenderWorld>().unwrap();
let scratch_world = std::mem::replace(&mut render_app.world, render_world.0);
app_world.insert_resource(ScratchRenderWorld(scratch_world));
extract.apply_buffers(&mut render_app.world);
}

View file

@ -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);
}
}

View file

@ -40,8 +40,8 @@ pub struct Mesh {
///
/// Example of constructing a mesh:
/// ```
/// # use bevy_render2::mesh::{Mesh, Indices};
/// # use bevy_render2::render_resource::PrimitiveTopology;
/// # use bevy_render::mesh::{Mesh, Indices};
/// # use bevy_render::render_resource::PrimitiveTopology;
/// fn create_triangle() -> Mesh {
/// let mut mesh = Mesh::new(PrimitiveTopology::TriangleList);
/// mesh.set_attribute(Mesh::ATTRIBUTE_POSITION, vec![[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [1.0, 1.0, 0.0]]);

View file

@ -4,3 +4,17 @@ mod mesh;
pub mod shape;
pub use mesh::*;
use crate::render_asset::RenderAssetPlugin;
use bevy_app::{App, Plugin};
use bevy_asset::AddAsset;
/// Adds the [`Mesh`] as an asset and makes sure that they are extracted and prepared for the GPU.
pub struct MeshPlugin;
impl Plugin for MeshPlugin {
fn build(&self, app: &mut App) {
app.add_asset::<Mesh>()
.add_plugin(RenderAssetPlugin::<Mesh>::default());
}
}

View file

@ -1,8 +1,6 @@
use crate::{
mesh::{Indices, Mesh},
pipeline::PrimitiveTopology,
};
use crate::mesh::{Indices, Mesh};
use bevy_math::{Vec2, Vec3};
use wgpu::PrimitiveTopology;
/// A cylinder with hemispheres at the top and bottom
#[derive(Debug, Copy, Clone)]

View file

@ -1,9 +1,6 @@
use crate::mesh::{Indices, Mesh};
use hexasphere::shapes::IcoSphere;
use crate::{
mesh::{Indices, Mesh},
pipeline::PrimitiveTopology,
};
use wgpu::PrimitiveTopology;
/// A sphere made from a subdivided Icosahedron.
#[derive(Debug, Clone, Copy)]

View file

@ -1,5 +1,4 @@
use super::{Indices, Mesh};
use crate::pipeline::PrimitiveTopology;
use bevy_math::*;
#[derive(Debug, Copy, Clone)]
@ -25,6 +24,7 @@ impl From<Cube> for Mesh {
}
}
/// An axis-aligned box defined by its minimum and maximum point.
#[derive(Debug, Copy, Clone)]
pub struct Box {
pub min_x: f32,
@ -38,6 +38,7 @@ pub struct Box {
}
impl Box {
/// Creates a new box centered at the origin with the supplied side lengths.
pub fn new(x_length: f32, y_length: f32, z_length: f32) -> Box {
Box {
max_x: x_length / 2.0,
@ -119,7 +120,7 @@ impl From<Box> for Mesh {
}
}
/// A rectangle on the XY plane.
/// A rectangle on the XY plane centered at the origin.
#[derive(Debug, Copy, Clone)]
pub struct Quad {
/// Full width and height of the rectangle.
@ -221,7 +222,7 @@ impl From<Quad> for Mesh {
}
}
/// A square on the XZ plane.
/// A square on the XZ plane centered at the origin.
#[derive(Debug, Copy, Clone)]
pub struct Plane {
/// The total side length of the square.
@ -274,3 +275,4 @@ pub use capsule::{Capsule, CapsuleUvProfile};
pub use icosphere::Icosphere;
pub use torus::Torus;
pub use uvsphere::UVSphere;
use wgpu::PrimitiveTopology;

View file

@ -1,8 +1,6 @@
use crate::{
mesh::{Indices, Mesh},
pipeline::PrimitiveTopology,
};
use crate::mesh::{Indices, Mesh};
use bevy_math::Vec3;
use wgpu::PrimitiveTopology;
/// A torus (donut) shape.
#[derive(Debug, Clone, Copy)]

View file

@ -1,10 +1,9 @@
use crate::{
mesh::{Indices, Mesh},
pipeline::PrimitiveTopology,
};
use wgpu::PrimitiveTopology;
use crate::mesh::{Indices, Mesh};
use std::f32::consts::PI;
/// A sphere made of sectors and stacks
/// A sphere made of sectors and stacks.
#[allow(clippy::upper_case_acronyms)]
#[derive(Debug, Clone, Copy)]
pub struct UVSphere {

View file

@ -1,14 +1,14 @@
use std::borrow::Cow;
pub use wgpu::{Backends, Features, Limits, PowerPreference};
pub use wgpu::{Backends, Features as WgpuFeatures, Limits as WgpuLimits, PowerPreference};
#[derive(Clone)]
pub struct WgpuOptions {
pub device_label: Option<Cow<'static, str>>,
pub backends: Backends,
pub power_preference: PowerPreference,
pub features: Features,
pub limits: Limits,
pub features: WgpuFeatures,
pub limits: WgpuLimits,
}
impl Default for WgpuOptions {
@ -24,7 +24,14 @@ impl Default for WgpuOptions {
let limits = if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults()
} else {
wgpu::Limits::default()
#[allow(unused_mut)]
let mut limits = wgpu::Limits::default();
#[cfg(feature = "ci_limits")]
{
limits.max_storage_textures_per_shader_stage = 4;
limits.max_texture_dimension_3d = 1024;
}
limits
};
Self {

View file

@ -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::*;

View file

@ -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,
}

View file

@ -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,
}

View file

@ -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]>,
);
}

View file

@ -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
}
}

View file

@ -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,
}
}
}

View file

@ -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::*;

View file

@ -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()
}
}

View file

@ -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| {
&current_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);
}

View file

@ -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,
}
}
}

View file

@ -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)
}
}
}
}

View file

@ -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
}
}

View file

@ -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()
}

View file

@ -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]
}
}

View file

@ -80,8 +80,10 @@ pub struct Plane {
pub normal_d: Vec4,
}
#[derive(Component, Clone, Copy, Debug, Default)]
#[derive(Component, Clone, Copy, Debug, Default, Reflect)]
#[reflect(Component)]
pub struct Frustum {
#[reflect(ignore)]
pub planes: [Plane; 6],
}

View file

@ -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();
}
}

View file

@ -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),
}
}
}
}

View file

@ -1,13 +1,30 @@
use super::NodeId;
/// An edge, which connects two [`Nodes`](super::Node) in
/// a [`RenderGraph`](crate::render_graph::RenderGraph).
///
/// They are used to describe the ordering (which node has to run first)
/// and may be of two kinds: [`NodeEdge`](Self::NodeEdge) and [`SlotEdge`](Self::SlotEdge).
///
/// Edges are added via the render_graph::add_node_edge(output_node, input_node) and the
/// render_graph::add_slot_edge(output_node, output_slot, input_node, input_slot) methode.
///
/// The former simply states that the `output_node` has to be run before the `input_node`,
/// while the later connects an output slot of the `output_node`
/// with an input slot of the `input_node` to pass additional data along.
/// For more information see [`SlotType`](super::SlotType).
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Edge {
/// An edge describing to ordering of both nodes (`output_node` before `input_node`)
/// and connecting the output slot at the `output_index` of the output_node
/// with the slot at the `input_index` of the `input_node`.
SlotEdge {
input_node: NodeId,
input_index: usize,
output_node: NodeId,
output_index: usize,
},
/// An edge describing to ordering of both nodes (`output_node` before `input_node`).
NodeEdge {
input_node: NodeId,
output_node: NodeId,
@ -15,6 +32,7 @@ pub enum Edge {
}
impl Edge {
/// Returns the id of the 'input_node'.
pub fn get_input_node(&self) -> NodeId {
match self {
Edge::SlotEdge { input_node, .. } => *input_node,
@ -22,6 +40,7 @@ impl Edge {
}
}
/// Returns the id of the 'output_node'.
pub fn get_output_node(&self) -> NodeId {
match self {
Edge::SlotEdge { output_node, .. } => *output_node,

View file

@ -1,32 +1,93 @@
use super::{Edge, Node, NodeId, NodeLabel, NodeState, RenderGraphError, SlotLabel, SystemNode};
use bevy_ecs::{
schedule::{Schedule, StageLabel, SystemStage},
world::World,
use crate::{
render_graph::{
Edge, Node, NodeId, NodeLabel, NodeRunError, NodeState, RenderGraphContext,
RenderGraphError, SlotInfo, SlotLabel,
},
renderer::RenderContext,
};
use bevy_ecs::prelude::World;
use bevy_utils::HashMap;
use std::{borrow::Cow, fmt::Debug};
/// The render graph configures the modular, parallel and re-usable render logic.
/// It is a retained and stateless (nodes itself my have their internal state) structure,
/// which can not be modified while it is executed by the graph runner.
///
/// The `RenderGraphRunner` is responsible for executing the entire graph each frame.
///
/// It consists of three main components: [`Nodes`](Node), [`Edges`](Edge)
/// and [`Slots`](super::SlotType).
///
/// Nodes are responsible for generating draw calls and operating on input and output slots.
/// Edges specify the order of execution for nodes and connect input and output slots together.
/// Slots describe the render resources created or used by the nodes.
///
/// Additionally a render graph can contain multiple sub graphs, which are run by the
/// corresponding nodes. Every render graph can have its own optional input node.
///
/// ## Example
/// Here is a simple render graph example with two nodes connected by a node edge.
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_ecs::prelude::World;
/// # use bevy_render::render_graph::{RenderGraph, Node, RenderGraphContext, NodeRunError};
/// # use bevy_render::renderer::RenderContext;
/// #
/// # struct MyNode;
/// #
/// # impl Node for MyNode {
/// # fn run(&self, graph: &mut RenderGraphContext, render_context: &mut RenderContext, world: &World) -> Result<(), NodeRunError> {
/// # unimplemented!()
/// # }
/// # }
/// #
/// let mut graph = RenderGraph::default();
/// graph.add_node("input_node", MyNode);
/// graph.add_node("output_node", MyNode);
/// graph.add_node_edge("output_node", "input_node").unwrap();
/// ```
#[derive(Default)]
pub struct RenderGraph {
nodes: HashMap<NodeId, NodeState>,
node_names: HashMap<Cow<'static, str>, NodeId>,
system_node_schedule: Option<Schedule>,
}
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
struct RenderGraphUpdate;
impl Default for RenderGraph {
fn default() -> Self {
let mut schedule = Schedule::default();
schedule.add_stage(RenderGraphUpdate, SystemStage::parallel());
Self {
nodes: Default::default(),
node_names: Default::default(),
system_node_schedule: Some(schedule),
}
}
sub_graphs: HashMap<Cow<'static, str>, RenderGraph>,
input_node: Option<NodeId>,
}
impl RenderGraph {
/// The name of the [`GraphInputNode`] of this graph. Used to connect other nodes to it.
pub const INPUT_NODE_NAME: &'static str = "GraphInputNode";
/// Updates all nodes and sub graphs of the render graph. Should be called before executing it.
pub fn update(&mut self, world: &mut World) {
for node in self.nodes.values_mut() {
node.node.update(world);
}
for sub_graph in self.sub_graphs.values_mut() {
sub_graph.update(world);
}
}
/// Creates an [`GraphInputNode`] with the specified slots if not already present.
pub fn set_input(&mut self, inputs: Vec<SlotInfo>) -> NodeId {
if self.input_node.is_some() {
panic!("Graph already has an input node");
}
let id = self.add_node("GraphInputNode", GraphInputNode { inputs });
self.input_node = Some(id);
id
}
/// Returns the [`NodeState`] of the input node of this graph..
#[inline]
pub fn input_node(&self) -> Option<&NodeState> {
self.input_node.and_then(|id| self.get_node_state(id).ok())
}
/// Adds the `node` with the `name` to the graph.
/// If the name is already present replaces it instead.
pub fn add_node<T>(&mut self, name: impl Into<Cow<'static, str>>, node: T) -> NodeId
where
T: Node,
@ -40,18 +101,7 @@ impl RenderGraph {
id
}
pub fn add_system_node<T>(&mut self, name: impl Into<Cow<'static, str>>, node: T) -> NodeId
where
T: SystemNode + 'static,
{
let schedule = self.system_node_schedule.as_mut().unwrap();
let stage = schedule
.get_stage_mut::<SystemStage>(&RenderGraphUpdate)
.unwrap();
stage.add_system(node.get_system());
self.add_node(name, node)
}
/// Retrieves the [`NodeState`] referenced by the `label`.
pub fn get_node_state(
&self,
label: impl Into<NodeLabel>,
@ -63,6 +113,7 @@ impl RenderGraph {
.ok_or(RenderGraphError::InvalidNode(label))
}
/// Retrieves the [`NodeState`] referenced by the `label` mutably.
pub fn get_node_state_mut(
&mut self,
label: impl Into<NodeLabel>,
@ -74,6 +125,7 @@ impl RenderGraph {
.ok_or(RenderGraphError::InvalidNode(label))
}
/// Retrieves the [`NodeId`] referenced by the `label`.
pub fn get_node_id(&self, label: impl Into<NodeLabel>) -> Result<NodeId, RenderGraphError> {
let label = label.into();
match label {
@ -86,6 +138,7 @@ impl RenderGraph {
}
}
/// Retrieves the [`Node`] referenced by the `label`.
pub fn get_node<T>(&self, label: impl Into<NodeLabel>) -> Result<&T, RenderGraphError>
where
T: Node,
@ -93,6 +146,7 @@ impl RenderGraph {
self.get_node_state(label).and_then(|n| n.node())
}
/// Retrieves the [`Node`] referenced by the `label` mutably.
pub fn get_node_mut<T>(
&mut self,
label: impl Into<NodeLabel>,
@ -103,6 +157,8 @@ impl RenderGraph {
self.get_node_state_mut(label).and_then(|n| n.node_mut())
}
/// Adds the [`Edge::SlotEdge`] to the graph. This guarantees that the `output_node`
/// is run before the `input_node` and also connects the `output_slot` to the `input_slot`.
pub fn add_slot_edge(
&mut self,
output_node: impl Into<NodeLabel>,
@ -110,17 +166,21 @@ impl RenderGraph {
input_node: impl Into<NodeLabel>,
input_slot: impl Into<SlotLabel>,
) -> Result<(), RenderGraphError> {
let output_slot = output_slot.into();
let input_slot = input_slot.into();
let output_node_id = self.get_node_id(output_node)?;
let input_node_id = self.get_node_id(input_node)?;
let output_index = self
.get_node_state(output_node_id)?
.output_slots
.get_slot_index(output_slot)?;
.get_slot_index(output_slot.clone())
.ok_or(RenderGraphError::InvalidOutputNodeSlot(output_slot))?;
let input_index = self
.get_node_state(input_node_id)?
.input_slots
.get_slot_index(input_slot)?;
.get_slot_index(input_slot.clone())
.ok_or(RenderGraphError::InvalidInputNodeSlot(input_slot))?;
let edge = Edge::SlotEdge {
output_node: output_node_id,
@ -141,6 +201,8 @@ impl RenderGraph {
Ok(())
}
/// Adds the [`Edge::NodeEdge`] to the graph. This guarantees that the `output_node`
/// is run before the `input_node`.
pub fn add_node_edge(
&mut self,
output_node: impl Into<NodeLabel>,
@ -166,6 +228,8 @@ impl RenderGraph {
Ok(())
}
/// Verifies that the edge is not already existing and
/// checks that slot edges are connected correctly.
pub fn validate_edge(&mut self, edge: &Edge) -> Result<(), RenderGraphError> {
if self.has_edge(edge) {
return Err(RenderGraphError::EdgeAlreadyExists(edge.clone()));
@ -181,8 +245,15 @@ impl RenderGraph {
let output_node_state = self.get_node_state(output_node)?;
let input_node_state = self.get_node_state(input_node)?;
let output_slot = output_node_state.output_slots.get_slot(output_index)?;
let input_slot = input_node_state.input_slots.get_slot(input_index)?;
let output_slot = output_node_state
.output_slots
.get_slot(output_index)
.ok_or(RenderGraphError::InvalidOutputNodeSlot(SlotLabel::Index(
output_index,
)))?;
let input_slot = input_node_state.input_slots.get_slot(input_index).ok_or(
RenderGraphError::InvalidInputNodeSlot(SlotLabel::Index(input_index)),
)?;
if let Some(Edge::SlotEdge {
output_node: current_output_node,
@ -205,7 +276,7 @@ impl RenderGraph {
});
}
if output_slot.info.resource_type != input_slot.info.resource_type {
if output_slot.slot_type != input_slot.slot_type {
return Err(RenderGraphError::MismatchedNodeSlots {
output_node,
output_slot: output_index,
@ -220,6 +291,7 @@ impl RenderGraph {
Ok(())
}
/// Checks whether the `edge` already exists in the graph.
pub fn has_edge(&self, edge: &Edge) -> bool {
let output_node_state = self.get_node_state(edge.get_output_node());
let input_node_state = self.get_node_state(edge.get_input_node());
@ -236,22 +308,32 @@ impl RenderGraph {
false
}
pub fn take_schedule(&mut self) -> Option<Schedule> {
self.system_node_schedule.take()
}
pub fn set_schedule(&mut self, schedule: Schedule) {
self.system_node_schedule = Some(schedule);
}
/// Returns an iterator over the [`NodeStates`](NodeState).
pub fn iter_nodes(&self) -> impl Iterator<Item = &NodeState> {
self.nodes.values()
}
/// Returns an iterator over the [`NodeStates`](NodeState), that allows modifying each value.
pub fn iter_nodes_mut(&mut self) -> impl Iterator<Item = &mut NodeState> {
self.nodes.values_mut()
}
/// Returns an iterator over the sub graphs.
pub fn iter_sub_graphs(&self) -> impl Iterator<Item = (&str, &RenderGraph)> {
self.sub_graphs
.iter()
.map(|(name, graph)| (name.as_ref(), graph))
}
/// Returns an iterator over the sub graphs, that allows modifying each value.
pub fn iter_sub_graphs_mut(&mut self) -> impl Iterator<Item = (&str, &mut RenderGraph)> {
self.sub_graphs
.iter_mut()
.map(|(name, graph)| (name.as_ref(), graph))
}
/// Returns an iterator over a tuple of the input edges and the corresponding output nodes
/// for the node referenced by the label.
pub fn iter_node_inputs(
&self,
label: impl Into<NodeLabel>,
@ -267,6 +349,8 @@ impl RenderGraph {
}))
}
/// Returns an iterator over a tuple of the ouput edges and the corresponding input nodes
/// for the node referenced by the label.
pub fn iter_node_outputs(
&self,
label: impl Into<NodeLabel>,
@ -280,10 +364,20 @@ impl RenderGraph {
.map(move |(edge, input_node_id)| (edge, self.get_node_state(input_node_id).unwrap())))
}
pub fn prepare(&mut self, world: &mut World) {
for node in self.nodes.values_mut() {
node.node.prepare(world);
}
/// Adds the `sub_graph` with the `name` to the graph.
/// If the name is already present replaces it instead.
pub fn add_sub_graph(&mut self, name: impl Into<Cow<'static, str>>, sub_graph: RenderGraph) {
self.sub_graphs.insert(name.into(), sub_graph);
}
/// Retrieves the sub graph corresponding to the `name`.
pub fn get_sub_graph(&self, name: impl AsRef<str>) -> Option<&RenderGraph> {
self.sub_graphs.get(name.as_ref())
}
/// Retrieves the sub graph corresponding to the `name` mutably.
pub fn get_sub_graph_mut(&mut self, name: impl AsRef<str>) -> Option<&mut RenderGraph> {
self.sub_graphs.get_mut(name.as_ref())
}
}
@ -299,57 +393,82 @@ impl Debug for RenderGraph {
}
}
/// A [`Node`] which acts as an entry point for a [`RenderGraph`] with custom inputs.
/// It has the same input and output slots and simply copies them over when run.
pub struct GraphInputNode {
inputs: Vec<SlotInfo>,
}
impl Node for GraphInputNode {
fn input(&self) -> Vec<SlotInfo> {
self.inputs.clone()
}
fn output(&self) -> Vec<SlotInfo> {
self.inputs.clone()
}
fn run(
&self,
graph: &mut RenderGraphContext,
_render_context: &mut RenderContext,
_world: &World,
) -> Result<(), NodeRunError> {
for i in 0..graph.inputs().len() {
let input = graph.inputs()[i].clone();
graph.set_output(i, input)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::RenderGraph;
use crate::{
render_graph::{Edge, Node, NodeId, RenderGraphError, ResourceSlotInfo, ResourceSlots},
renderer::{RenderContext, RenderResourceType},
render_graph::{
Edge, Node, NodeId, NodeRunError, RenderGraph, RenderGraphContext, RenderGraphError,
SlotInfo, SlotType,
},
renderer::RenderContext,
};
use bevy_ecs::world::World;
use bevy_utils::HashSet;
#[derive(Debug)]
struct TestNode {
inputs: Vec<ResourceSlotInfo>,
outputs: Vec<ResourceSlotInfo>,
inputs: Vec<SlotInfo>,
outputs: Vec<SlotInfo>,
}
impl TestNode {
pub fn new(inputs: usize, outputs: usize) -> Self {
TestNode {
inputs: (0..inputs)
.map(|i| ResourceSlotInfo {
name: format!("in_{}", i).into(),
resource_type: RenderResourceType::Texture,
})
.map(|i| SlotInfo::new(format!("in_{}", i), SlotType::TextureView))
.collect(),
outputs: (0..outputs)
.map(|i| ResourceSlotInfo {
name: format!("out_{}", i).into(),
resource_type: RenderResourceType::Texture,
})
.map(|i| SlotInfo::new(format!("out_{}", i), SlotType::TextureView))
.collect(),
}
}
}
impl Node for TestNode {
fn input(&self) -> &[ResourceSlotInfo] {
&self.inputs
fn input(&self) -> Vec<SlotInfo> {
self.inputs.clone()
}
fn output(&self) -> &[ResourceSlotInfo] {
&self.outputs
fn output(&self) -> Vec<SlotInfo> {
self.outputs.clone()
}
fn update(
&mut self,
fn run(
&self,
_: &mut RenderGraphContext,
_: &mut RenderContext,
_: &World,
_: &mut dyn RenderContext,
_: &ResourceSlots,
_: &mut ResourceSlots,
) {
) -> Result<(), NodeRunError> {
Ok(())
}
}
@ -416,13 +535,13 @@ mod tests {
}
impl Node for MyNode {
fn update(
&mut self,
fn run(
&self,
_: &mut RenderGraphContext,
_: &mut RenderContext,
_: &World,
_: &mut dyn RenderContext,
_: &ResourceSlots,
_: &mut ResourceSlots,
) {
) -> Result<(), NodeRunError> {
Ok(())
}
}

View file

@ -1,21 +1,14 @@
pub mod base;
mod command;
mod context;
mod edge;
mod graph;
mod node;
mod node_slot;
mod nodes;
mod schedule;
mod system;
pub use command::*;
pub use context::*;
pub use edge::*;
pub use graph::*;
pub use node::*;
pub use node_slot::*;
pub use nodes::*;
pub use schedule::*;
pub use system::*;
use thiserror::Error;
@ -23,8 +16,10 @@ use thiserror::Error;
pub enum RenderGraphError {
#[error("node does not exist")]
InvalidNode(NodeLabel),
#[error("node slot does not exist")]
InvalidNodeSlot(SlotLabel),
#[error("output node slot does not exist")]
InvalidOutputNodeSlot(SlotLabel),
#[error("input node slot does not exist")]
InvalidInputNodeSlot(SlotLabel),
#[error("node does not match the given type")]
WrongNodeType,
#[error("attempted to connect a node output slot to an incompatible input node slot")]

View file

@ -1,10 +1,20 @@
use super::{Edge, RenderGraphError, ResourceSlotInfo, ResourceSlots};
use crate::renderer::RenderContext;
use bevy_ecs::{system::BoxedSystem, world::World};
use crate::{
render_graph::{
Edge, InputSlotError, OutputSlotError, RenderGraphContext, RenderGraphError,
RunSubGraphError, SlotInfo, SlotInfos,
},
renderer::RenderContext,
};
use bevy_ecs::world::World;
use bevy_utils::Uuid;
use downcast_rs::{impl_downcast, Downcast};
use std::{borrow::Cow, fmt::Debug};
use thiserror::Error;
/// A [`Node`] identifier.
/// It automatically generates its own random uuid.
///
/// This id is used to reference the node internally (edges, etc).
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct NodeId(Uuid);
@ -19,36 +29,58 @@ impl NodeId {
}
}
/// A render node that can be added to a [`RenderGraph`](super::RenderGraph).
///
/// Nodes are the fundamental part of the graph and used to extend its functionality, by
/// generating draw calls and/or running subgraphs.
/// They are added via the render_graph::add_node(my_node) methode.
///
/// To determine their position in the graph and ensure that all required dependencies (inputs)
/// are already executed, [`Edges`](Edge) are used.
///
/// A node can produce outputs used as dependencies by other nodes.
/// Those inputs and outputs are called slots and are the default way of passing render data
/// inside the graph. For more information see [`SlotType`](super::SlotType).
pub trait Node: Downcast + Send + Sync + 'static {
fn input(&self) -> &[ResourceSlotInfo] {
&[]
/// Specifies the required input slots for this node.
/// They will then be available during the run method inside the [`RenderContext`].
fn input(&self) -> Vec<SlotInfo> {
Vec::new()
}
fn output(&self) -> &[ResourceSlotInfo] {
&[]
/// Specifies the produced output slots for this node.
/// They can then be passed one inside [`RenderContext`] during the run method.
fn output(&self) -> Vec<SlotInfo> {
Vec::new()
}
/// Prepare the graph node with unique world access. This runs once per graph run before
/// [Node::update] is called.
fn prepare(&mut self, _world: &mut World) {}
/// Updates internal node state using the current render [`World`] prior to the run method.
fn update(&mut self, _world: &mut World) {}
/// Run the graph node logic. This runs once per graph run after [Node::prepare] has been called
/// on all nodes.
fn update(
&mut self,
/// Runs the graph node logic, issues draw calls, updates the output slots and
/// optionally queues up subgraphs for execution. The graph data, input and output values are
/// passed via the [`RenderGraphContext`].
fn run(
&self,
graph: &mut RenderGraphContext,
render_context: &mut RenderContext,
world: &World,
render_context: &mut dyn RenderContext,
input: &ResourceSlots,
output: &mut ResourceSlots,
);
) -> Result<(), NodeRunError>;
}
impl_downcast!(Node);
pub trait SystemNode: Node {
fn get_system(&self) -> BoxedSystem;
#[derive(Error, Debug, Eq, PartialEq)]
pub enum NodeRunError {
#[error("encountered an input slot error")]
InputSlotError(#[from] InputSlotError),
#[error("encountered an output slot error")]
OutputSlotError(#[from] OutputSlotError),
#[error("encountered an error when running a sub-graph")]
RunSubGraphError(#[from] RunSubGraphError),
}
/// A collection of input and output [`Edges`](Edge) for a [`Node`].
#[derive(Debug)]
pub struct Edges {
pub id: NodeId,
@ -57,6 +89,7 @@ pub struct Edges {
}
impl Edges {
/// Adds an edge to the `input_edges` if it does not already exist.
pub(crate) fn add_input_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> {
if self.has_input_edge(&edge) {
return Err(RenderGraphError::EdgeAlreadyExists(edge));
@ -65,6 +98,7 @@ impl Edges {
Ok(())
}
/// Adds an edge to the `output_edges` if it does not already exist.
pub(crate) fn add_output_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> {
if self.has_output_edge(&edge) {
return Err(RenderGraphError::EdgeAlreadyExists(edge));
@ -73,14 +107,18 @@ impl Edges {
Ok(())
}
/// Checks whether the input edge already exists.
pub fn has_input_edge(&self, edge: &Edge) -> bool {
self.input_edges.contains(edge)
}
/// Checks whether the output edge already exists.
pub fn has_output_edge(&self, edge: &Edge) -> bool {
self.output_edges.contains(edge)
}
/// Searches the `input_edges` for a [`Edge::SlotEdge`],
/// which `input_index` matches the `index`;
pub fn get_input_slot_edge(&self, index: usize) -> Result<&Edge, RenderGraphError> {
self.input_edges
.iter()
@ -97,6 +135,8 @@ impl Edges {
})
}
/// Searches the `output_edges` for a [`Edge::SlotEdge`],
/// which `output_index` matches the `index`;
pub fn get_output_slot_edge(&self, index: usize) -> Result<&Edge, RenderGraphError> {
self.output_edges
.iter()
@ -114,13 +154,18 @@ impl Edges {
}
}
/// The internal representation of a [`Node`], with all data required
/// by the [`RenderGraph`](super::RenderGraph).
///
/// The `input_slots` and `output_slots` are provided by the `node`.
pub struct NodeState {
pub id: NodeId,
pub name: Option<Cow<'static, str>>,
/// The name of the type that implements [`Node`].
pub type_name: &'static str,
pub node: Box<dyn Node>,
pub input_slots: ResourceSlots,
pub output_slots: ResourceSlots,
pub input_slots: SlotInfos,
pub output_slots: SlotInfos,
pub edges: Edges,
}
@ -131,6 +176,8 @@ impl Debug for NodeState {
}
impl NodeState {
/// Creates an [`NodeState`] without edges, but the `input_slots` and `output_slots`
/// are provided by the `node`.
pub fn new<T>(id: NodeId, node: T) -> Self
where
T: Node,
@ -138,8 +185,8 @@ impl NodeState {
NodeState {
id,
name: None,
input_slots: ResourceSlots::from(node.input()),
output_slots: ResourceSlots::from(node.output()),
input_slots: node.input().into(),
output_slots: node.output().into(),
node: Box::new(node),
type_name: std::any::type_name::<T>(),
edges: Edges {
@ -150,6 +197,7 @@ impl NodeState {
}
}
/// Retrieves the [`Node`].
pub fn node<T>(&self) -> Result<&T, RenderGraphError>
where
T: Node,
@ -159,6 +207,7 @@ impl NodeState {
.ok_or(RenderGraphError::WrongNodeType)
}
/// Retrieves the [`Node`] mutably.
pub fn node_mut<T>(&mut self) -> Result<&mut T, RenderGraphError>
where
T: Node,
@ -168,14 +217,7 @@ impl NodeState {
.ok_or(RenderGraphError::WrongNodeType)
}
pub fn validate_output_slots(&self) -> Result<(), RenderGraphError> {
for i in 0..self.output_slots.len() {
self.edges.get_output_slot_edge(i)?;
}
Ok(())
}
/// Validates that each input slot corresponds to an input edge.
pub fn validate_input_slots(&self) -> Result<(), RenderGraphError> {
for i in 0..self.input_slots.len() {
self.edges.get_input_slot_edge(i)?;
@ -183,8 +225,19 @@ impl NodeState {
Ok(())
}
/// Validates that each output slot corresponds to an output edge.
pub fn validate_output_slots(&self) -> Result<(), RenderGraphError> {
for i in 0..self.output_slots.len() {
self.edges.get_output_slot_edge(i)?;
}
Ok(())
}
}
/// A [`NodeLabel`] is used to reference a [`NodeState`] by either its name or [`NodeId`]
/// inside the [`RenderGraph`](super::RenderGraph).
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum NodeLabel {
Id(NodeId),
@ -214,3 +267,19 @@ impl From<NodeId> for NodeLabel {
NodeLabel::Id(value)
}
}
/// A [`Node`] without any inputs, outputs and subgraphs, which does nothing when run.
/// Used (as a label) to bundle multiple dependencies into one inside
/// the [`RenderGraph`](super::RenderGraph).
pub struct EmptyNode;
impl Node for EmptyNode {
fn run(
&self,
_graph: &mut RenderGraphContext,
_render_context: &mut RenderContext,
_world: &World,
) -> Result<(), NodeRunError> {
Ok(())
}
}

View file

@ -1,18 +1,81 @@
use super::RenderGraphError;
use crate::renderer::{RenderResourceId, RenderResourceType};
use bevy_ecs::entity::Entity;
use std::borrow::Cow;
use crate::render_resource::{Buffer, Sampler, TextureView};
/// A value passed between render [`Nodes`](super::Node).
/// Corresponds to the [SlotType] specified in the [`RenderGraph`](super::RenderGraph).
///
/// Slots can have four different types of values:
/// [`Buffer`], [`TextureView`], [`Sampler`] and [`Entity`].
///
/// These values do not contain the actual render data, but only the ids to retrieve them.
#[derive(Debug, Clone)]
pub struct ResourceSlot {
pub resource: Option<RenderResourceId>,
pub info: ResourceSlotInfo,
pub enum SlotValue {
/// A GPU-accessible [`Buffer`].
Buffer(Buffer),
/// A [`TextureView`] describes a texture used in a pipeline.
TextureView(TextureView),
/// A texture [`Sampler`] defines how a pipeline will sample from a [`TextureView`].
Sampler(Sampler),
/// An entity from the ECS.
Entity(Entity),
}
#[derive(Default, Debug, Clone)]
pub struct ResourceSlots {
slots: Vec<ResourceSlot>,
impl SlotValue {
/// Returns the [`SlotType`] of this value.
pub fn slot_type(&self) -> SlotType {
match self {
SlotValue::Buffer(_) => SlotType::Buffer,
SlotValue::TextureView(_) => SlotType::TextureView,
SlotValue::Sampler(_) => SlotType::Sampler,
SlotValue::Entity(_) => SlotType::Entity,
}
}
}
impl From<Buffer> for SlotValue {
fn from(value: Buffer) -> Self {
SlotValue::Buffer(value)
}
}
impl From<TextureView> for SlotValue {
fn from(value: TextureView) -> Self {
SlotValue::TextureView(value)
}
}
impl From<Sampler> for SlotValue {
fn from(value: Sampler) -> Self {
SlotValue::Sampler(value)
}
}
impl From<Entity> for SlotValue {
fn from(value: Entity) -> Self {
SlotValue::Entity(value)
}
}
/// Describes the render resources created (output) or used (input) by
/// the render [`Nodes`](super::Node).
///
/// This should not be confused with [`SlotValue`], which actually contains the passed data.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum SlotType {
/// A GPU-accessible [`Buffer`].
Buffer,
/// A [`TextureView`] describes a texture used in a pipeline.
TextureView,
/// A texture [`Sampler`] defines how a pipeline will sample from a [`TextureView`].
Sampler,
/// An entity from the ECS.
Entity,
}
/// A [`SlotLabel`] is used to reference a slot by either its name or index
/// inside the [`RenderGraph`](super::RenderGraph).
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum SlotLabel {
Index(usize),
@ -37,104 +100,92 @@ impl From<&'static str> for SlotLabel {
}
}
impl From<Cow<'static, str>> for SlotLabel {
fn from(value: Cow<'static, str>) -> Self {
SlotLabel::Name(value.clone())
}
}
impl From<usize> for SlotLabel {
fn from(value: usize) -> Self {
SlotLabel::Index(value)
}
}
impl ResourceSlots {
pub fn set(&mut self, label: impl Into<SlotLabel>, resource: RenderResourceId) {
let mut slot = self.get_slot_mut(label).unwrap();
slot.resource = Some(resource);
}
/// The internal representation of a slot, which specifies its [`SlotType`] and name.
#[derive(Clone, Debug)]
pub struct SlotInfo {
pub name: Cow<'static, str>,
pub slot_type: SlotType,
}
pub fn get(&self, label: impl Into<SlotLabel>) -> Option<RenderResourceId> {
let slot = self.get_slot(label).unwrap();
slot.resource.clone()
}
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)),
impl SlotInfo {
pub fn new(name: impl Into<Cow<'static, str>>, slot_type: SlotType) -> Self {
SlotInfo {
name: name.into(),
slot_type,
}
}
}
pub fn iter(&self) -> impl Iterator<Item = &ResourceSlot> {
self.slots.iter()
}
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut ResourceSlot> {
self.slots.iter_mut()
/// A collection of input or output [`SlotInfos`](SlotInfo) for
/// a [`NodeState`](super::NodeState).
#[derive(Default, Debug)]
pub struct SlotInfos {
slots: Vec<SlotInfo>,
}
impl<T: IntoIterator<Item = SlotInfo>> From<T> for SlotInfos {
fn from(slots: T) -> Self {
SlotInfos {
slots: slots.into_iter().collect(),
}
}
}
impl SlotInfos {
/// Returns the count of slots.
#[inline]
pub fn len(&self) -> usize {
self.slots.len()
}
/// Returns true if there are no slots.
#[inline]
pub fn is_empty(&self) -> bool {
self.slots.is_empty()
}
}
impl From<&ResourceSlotInfo> for ResourceSlot {
fn from(slot: &ResourceSlotInfo) -> Self {
ResourceSlot {
resource: None,
info: slot.clone(),
}
/// Retrieves the [`SlotInfo`] for the provided label.
pub fn get_slot(&self, label: impl Into<SlotLabel>) -> Option<&SlotInfo> {
let label = label.into();
let index = self.get_slot_index(&label)?;
self.slots.get(index)
}
}
impl From<&[ResourceSlotInfo]> for ResourceSlots {
fn from(slots: &[ResourceSlotInfo]) -> Self {
ResourceSlots {
slots: slots
/// Retrieves the [`SlotInfo`] for the provided label mutably.
pub fn get_slot_mut(&mut self, label: impl Into<SlotLabel>) -> Option<&mut SlotInfo> {
let label = label.into();
let index = self.get_slot_index(&label)?;
self.slots.get_mut(index)
}
/// Retrieves the index (inside input or output slots) of the slot for the provided label.
pub fn get_slot_index(&self, label: impl Into<SlotLabel>) -> Option<usize> {
let label = label.into();
match label {
SlotLabel::Index(index) => Some(index),
SlotLabel::Name(ref name) => self
.slots
.iter()
.map(|s| s.into())
.collect::<Vec<ResourceSlot>>(),
.enumerate()
.find(|(_i, s)| s.name == *name)
.map(|(i, _s)| i),
}
}
}
#[derive(Clone, Debug)]
pub struct ResourceSlotInfo {
pub name: Cow<'static, str>,
pub resource_type: RenderResourceType,
}
impl ResourceSlotInfo {
pub fn new(name: impl Into<Cow<'static, str>>, resource_type: RenderResourceType) -> Self {
ResourceSlotInfo {
name: name.into(),
resource_type,
}
/// Returns an iterator over the slot infos.
pub fn iter(&self) -> impl Iterator<Item = &SlotInfo> {
self.slots.iter()
}
}

View file

@ -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);
}

View file

@ -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::*;

View file

@ -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);
}
}

View file

@ -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
}

View file

@ -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);
}
}

View file

@ -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 { .. } => {}
}
}
}
}

View file

@ -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));
}
}
}

View file

@ -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),
);
}
}

View file

@ -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));
}
}
}

View file

@ -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]
);
}
}
}
}
}

View file

@ -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