Document the new pipelined renderer (#3094)

This is a squash-and-rebase of @Ku95's documentation of the new renderer onto the latest `pipelined-rendering` branch.

Original PR is #2884.

Co-authored-by: dataphract <dataphract@gmail.com>
Co-authored-by: Carter Anderson <mcanders1@gmail.com>
This commit is contained in:
dataphract 2021-11-16 03:37:48 +00:00
parent 14ce281904
commit 1076a8f2b5
33 changed files with 592 additions and 72 deletions

View file

@ -170,9 +170,18 @@ impl<Param: SystemParam> SystemState<Param> {
} }
} }
/// A trait for defining systems with a [`SystemParam`] associated type.
///
/// This facilitates the creation of systems that are generic over some trait
/// and that use that trait's associated types as `SystemParam`s.
pub trait RunSystem: Send + Sync + 'static { pub trait RunSystem: Send + Sync + 'static {
/// The `SystemParam` type passed to the system when it runs.
type Param: SystemParam; type Param: SystemParam;
/// Runs the system.
fn run(param: SystemParamItem<Self::Param>); fn run(param: SystemParamItem<Self::Param>);
/// Creates a concrete instance of the system for the specified `World`.
fn system(world: &mut World) -> ParamSystem<Self::Param> { fn system(world: &mut World) -> ParamSystem<Self::Param> {
ParamSystem { ParamSystem {
run: Self::run, run: Self::run,

View file

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

View file

@ -35,12 +35,12 @@ use wgpu::{AddressMode, FilterMode, PrimitiveTopology, SamplerDescriptor, Textur
use crate::{Gltf, GltfNode}; use crate::{Gltf, GltfNode};
/// An error that occurs when loading a GLTF file /// An error that occurs when loading a glTF file.
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub enum GltfError { pub enum GltfError {
#[error("unsupported primitive mode")] #[error("unsupported primitive mode")]
UnsupportedPrimitive { mode: Mode }, UnsupportedPrimitive { mode: Mode },
#[error("invalid GLTF file: {0}")] #[error("invalid glTF file: {0}")]
Gltf(#[from] gltf::Error), Gltf(#[from] gltf::Error),
#[error("binary blob is missing")] #[error("binary blob is missing")]
MissingBlob, MissingBlob,
@ -56,7 +56,7 @@ pub enum GltfError {
AssetIoError(#[from] AssetIoError), AssetIoError(#[from] AssetIoError),
} }
/// Loads meshes from GLTF files into Mesh assets /// Loads glTF files with all of their data as their corresponding bevy representations.
#[derive(Default)] #[derive(Default)]
pub struct GltfLoader; pub struct GltfLoader;
@ -74,6 +74,7 @@ impl AssetLoader for GltfLoader {
} }
} }
/// Loads an entire glTF file.
async fn load_gltf<'a, 'b>( async fn load_gltf<'a, 'b>(
bytes: &'a [u8], bytes: &'a [u8],
load_context: &'a mut LoadContext<'b>, load_context: &'a mut LoadContext<'b>,
@ -265,7 +266,7 @@ async fn load_gltf<'a, 'b>(
.into_iter() .into_iter()
.filter_map(|res| { .filter_map(|res| {
if let Err(err) = res.as_ref() { if let Err(err) = res.as_ref() {
warn!("Error loading GLTF texture: {}", err); warn!("Error loading glTF texture: {}", err);
} }
res.ok() res.ok()
}) })
@ -320,6 +321,7 @@ async fn load_gltf<'a, 'b>(
Ok(()) Ok(())
} }
/// Loads a glTF texture as a bevy [`Image`] and returns it together with its label.
async fn load_texture<'a>( async fn load_texture<'a>(
gltf_texture: gltf::Texture<'a>, gltf_texture: gltf::Texture<'a>,
buffer_data: &[Vec<u8>], buffer_data: &[Vec<u8>],
@ -368,6 +370,7 @@ async fn load_texture<'a>(
Ok((texture, texture_label(&gltf_texture))) Ok((texture, texture_label(&gltf_texture)))
} }
/// Loads a glTF material as a bevy [`StandardMaterial`] and returns it.
fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle<StandardMaterial> { fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle<StandardMaterial> {
let material_label = material_label(material); let material_label = material_label(material);
@ -444,6 +447,7 @@ fn load_material(material: &Material, load_context: &mut LoadContext) -> Handle<
) )
} }
/// Loads a glTF node.
fn load_node( fn load_node(
gltf_node: &gltf::Node, gltf_node: &gltf::Node,
world_builder: &mut WorldChildBuilder, world_builder: &mut WorldChildBuilder,
@ -559,14 +563,17 @@ fn load_node(
} }
} }
/// Returns the label for the `mesh`.
fn mesh_label(mesh: &gltf::Mesh) -> String { fn mesh_label(mesh: &gltf::Mesh) -> String {
format!("Mesh{}", mesh.index()) format!("Mesh{}", mesh.index())
} }
/// Returns the label for the `mesh` and `primitive`.
fn primitive_label(mesh: &gltf::Mesh, primitive: &Primitive) -> String { fn primitive_label(mesh: &gltf::Mesh, primitive: &Primitive) -> String {
format!("Mesh{}/Primitive{}", mesh.index(), primitive.index()) format!("Mesh{}/Primitive{}", mesh.index(), primitive.index())
} }
/// Returns the label for the `material`.
fn material_label(material: &gltf::Material) -> String { fn material_label(material: &gltf::Material) -> String {
if let Some(index) = material.index() { if let Some(index) = material.index() {
format!("Material{}", index) format!("Material{}", index)
@ -575,18 +582,22 @@ fn material_label(material: &gltf::Material) -> String {
} }
} }
/// Returns the label for the `texture`.
fn texture_label(texture: &gltf::Texture) -> String { fn texture_label(texture: &gltf::Texture) -> String {
format!("Texture{}", texture.index()) format!("Texture{}", texture.index())
} }
/// Returns the label for the `node`.
fn node_label(node: &gltf::Node) -> String { fn node_label(node: &gltf::Node) -> String {
format!("Node{}", node.index()) format!("Node{}", node.index())
} }
/// Returns the label for the `scene`.
fn scene_label(scene: &gltf::Scene) -> String { fn scene_label(scene: &gltf::Scene) -> String {
format!("Scene{}", scene.index()) format!("Scene{}", scene.index())
} }
/// Extracts the texture sampler data from the glTF texture.
fn texture_sampler<'a>(texture: &gltf::Texture) -> SamplerDescriptor<'a> { fn texture_sampler<'a>(texture: &gltf::Texture) -> SamplerDescriptor<'a> {
let gltf_sampler = texture.sampler(); let gltf_sampler = texture.sampler();
@ -631,6 +642,7 @@ fn texture_sampler<'a>(texture: &gltf::Texture) -> SamplerDescriptor<'a> {
} }
} }
/// Maps the texture address mode form glTF to wgpu.
fn texture_address_mode(gltf_address_mode: &gltf::texture::WrappingMode) -> AddressMode { fn texture_address_mode(gltf_address_mode: &gltf::texture::WrappingMode) -> AddressMode {
match gltf_address_mode { match gltf_address_mode {
WrappingMode::ClampToEdge => AddressMode::ClampToEdge, WrappingMode::ClampToEdge => AddressMode::ClampToEdge,
@ -639,6 +651,7 @@ fn texture_address_mode(gltf_address_mode: &gltf::texture::WrappingMode) -> Addr
} }
} }
/// Maps the primitive_topology form glTF to wgpu.
fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> { fn get_primitive_topology(mode: Mode) -> Result<PrimitiveTopology, GltfError> {
match mode { match mode {
Mode::Points => Ok(PrimitiveTopology::PointList), Mode::Points => Ok(PrimitiveTopology::PointList),
@ -658,6 +671,7 @@ fn alpha_mode(material: &Material) -> AlphaMode {
} }
} }
/// Loads the raw glTF buffer data for a specific glTF file.
async fn load_buffers( async fn load_buffers(
gltf: &gltf::Gltf, gltf: &gltf::Gltf,
load_context: &LoadContext<'_>, load_context: &LoadContext<'_>,

View file

@ -8,6 +8,7 @@ use bevy_render2::{
}; };
use bevy_transform::components::{GlobalTransform, Transform}; use bevy_transform::components::{GlobalTransform, Transform};
/// A component bundle for PBR entities with a [`Mesh`] and a [`StandardMaterial`].
#[derive(Bundle, Clone)] #[derive(Bundle, Clone)]
pub struct PbrBundle { pub struct PbrBundle {
pub mesh: Handle<Mesh>, pub mesh: Handle<Mesh>,
@ -56,7 +57,7 @@ impl CubemapVisibleEntities {
} }
} }
/// A component bundle for "point light" entities /// A component bundle for [`PointLight`] entities.
#[derive(Debug, Bundle, Default)] #[derive(Debug, Bundle, Default)]
pub struct PointLightBundle { pub struct PointLightBundle {
pub point_light: PointLight, pub point_light: PointLight,
@ -66,7 +67,7 @@ pub struct PointLightBundle {
pub global_transform: GlobalTransform, pub global_transform: GlobalTransform,
} }
/// A component bundle for "directional light" entities /// A component bundle for [`DirectionalLight`] entities.
#[derive(Debug, Bundle, Default)] #[derive(Debug, Bundle, Default)]
pub struct DirectionalLightBundle { pub struct DirectionalLightBundle {
pub directional_light: DirectionalLight, pub directional_light: DirectionalLight,

View file

@ -27,6 +27,7 @@ use bevy_transform::TransformSystem;
pub mod draw_3d_graph { pub mod draw_3d_graph {
pub mod node { pub mod node {
/// Label for the shadow pass node.
pub const SHADOW_PASS: &str = "shadow_pass"; pub const SHADOW_PASS: &str = "shadow_pass";
} }
} }
@ -36,6 +37,7 @@ pub const PBR_SHADER_HANDLE: HandleUntyped =
pub const SHADOW_SHADER_HANDLE: HandleUntyped = pub const SHADOW_SHADER_HANDLE: HandleUntyped =
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 1836745567947005696); HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 1836745567947005696);
/// Sets up the entire PBR infrastructure of bevy.
#[derive(Default)] #[derive(Default)]
pub struct PbrPlugin; pub struct PbrPlugin;

View file

@ -145,11 +145,11 @@ impl Default for DirectionalLightShadowMap {
} }
} }
/// Ambient light. /// An ambient light, which lights the entire scene equally.
#[derive(Debug)] #[derive(Debug)]
pub struct AmbientLight { pub struct AmbientLight {
pub color: Color, pub color: Color,
/// A direct scale factor multiplied with `color` before being passed to the shader /// A direct scale factor multiplied with `color` before being passed to the shader.
pub brightness: f32, pub brightness: f32,
} }
@ -162,9 +162,9 @@ impl Default for AmbientLight {
} }
} }
/// Add this component to make a `Mesh` not cast shadows /// Add this component to make a [`Mesh`](bevy_render2::mesh::Mesh) not cast shadows.
pub struct NotShadowCaster; pub struct NotShadowCaster;
/// Add this component to make a `Mesh` not receive shadows /// Add this component to make a [`Mesh`](bevy_render2::mesh::Mesh) not receive shadows.
pub struct NotShadowReceiver; pub struct NotShadowReceiver;
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)] #[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]

View file

@ -15,7 +15,10 @@ use crevice::std140::{AsStd140, Std140};
use wgpu::{BindGroupDescriptor, BindGroupEntry, BindingResource}; use wgpu::{BindGroupDescriptor, BindGroupEntry, BindingResource};
/// A material with "standard" properties used in PBR lighting /// A material with "standard" properties used in PBR lighting
/// Standard property values with pictures here https://google.github.io/filament/Material%20Properties.pdf /// Standard property values with pictures here
/// <https://google.github.io/filament/Material%20Properties.pdf>.
///
/// May be created directly from a [`Color`] or an [`Image`].
#[derive(Debug, Clone, TypeUuid)] #[derive(Debug, Clone, TypeUuid)]
#[uuid = "7494888b-c082-457b-aacf-517228cc0c22"] #[uuid = "7494888b-c082-457b-aacf-517228cc0c22"]
pub struct StandardMaterial { pub struct StandardMaterial {
@ -56,7 +59,7 @@ impl Default for StandardMaterial {
emissive: Color::BLACK, emissive: Color::BLACK,
emissive_texture: None, emissive_texture: None,
// This is the minimum the roughness is clamped to in shader code // This is the minimum the roughness is clamped to in shader code
// See https://google.github.io/filament/Filament.html#materialsystem/parameterization/ // See <https://google.github.io/filament/Filament.html#materialsystem/parameterization/>
// It's the minimum floating point value that won't be rounded down to 0 in the // It's the minimum floating point value that won't be rounded down to 0 in the
// calculations used. Although technically for 32-bit floats, 0.045 could be // calculations used. Although technically for 32-bit floats, 0.045 could be
// used. // used.
@ -66,7 +69,8 @@ impl Default for StandardMaterial {
metallic: 0.01, metallic: 0.01,
metallic_roughness_texture: None, metallic_roughness_texture: None,
// Minimum real-world reflectance is 2%, most materials between 2-5% // Minimum real-world reflectance is 2%, most materials between 2-5%
// Expressed in a linear scale and equivalent to 4% reflectance see https://google.github.io/filament/Material%20Properties.pdf // Expressed in a linear scale and equivalent to 4% reflectance see
// <https://google.github.io/filament/Material%20Properties.pdf>
reflectance: 0.5, reflectance: 0.5,
occlusion_texture: None, occlusion_texture: None,
normal_map_texture: None, normal_map_texture: None,
@ -95,6 +99,7 @@ impl From<Handle<Image>> for StandardMaterial {
} }
} }
/// The GPU representation of the uniform data of a [`StandardMaterial`].
#[derive(Clone, Default, AsStd140)] #[derive(Clone, Default, AsStd140)]
pub struct StandardMaterialUniformData { pub struct StandardMaterialUniformData {
/// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything /// Doubles as diffuse albedo for non-metallic, specular for metallic and a mix for everything
@ -117,6 +122,7 @@ pub struct StandardMaterialUniformData {
pub alpha_cutoff: f32, pub alpha_cutoff: f32,
} }
/// This plugin adds the [`StandardMaterial`] asset to the app.
pub struct StandardMaterialPlugin; pub struct StandardMaterialPlugin;
impl Plugin for StandardMaterialPlugin { impl Plugin for StandardMaterialPlugin {
@ -126,9 +132,13 @@ impl Plugin for StandardMaterialPlugin {
} }
} }
/// The GPU representation of a [`StandardMaterial`].
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct GpuStandardMaterial { pub struct GpuStandardMaterial {
/// A buffer containing the [`StandardMaterialUniformData`] of the material.
pub buffer: Buffer, pub buffer: Buffer,
/// The bind group specifying how the [`StandardMaterialUniformData`] and
/// all the textures of the material are bound.
pub bind_group: BindGroup, pub bind_group: BindGroup,
pub has_normal_map: bool, pub has_normal_map: bool,
pub flags: StandardMaterialFlags, pub flags: StandardMaterialFlags,

View file

@ -29,28 +29,32 @@ use bevy_ecs::prelude::*;
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use wgpu::Backends; use wgpu::Backends;
/// Contains the default Bevy rendering backend based on wgpu.
#[derive(Default)] #[derive(Default)]
pub struct RenderPlugin; pub struct RenderPlugin;
/// The names of the default App stages /// The labels of the default App rendering stages.
#[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)] #[derive(Debug, Hash, PartialEq, Eq, Clone, StageLabel)]
pub enum RenderStage { pub enum RenderStage {
/// Extract data from "app world" and insert it into "render world". This step should be kept /// Extract data from the "app world" and insert it into the "render world".
/// as short as possible to increase the "pipelining potential" for running the next frame /// This step should be kept as short as possible to increase the "pipelining potential" for
/// while rendering the current frame. /// running the next frame while rendering the current frame.
Extract, Extract,
/// Prepare render resources from extracted data. /// Prepare render resources from the extracted data for the GPU.
Prepare, Prepare,
/// Create Bind Groups that depend on Prepare data and queue up draw calls to run during the Render stage. /// 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, Queue,
// TODO: This could probably be moved in favor of a system ordering abstraction in Render or Queue // TODO: This could probably be moved in favor of a system ordering abstraction in Render or Queue
/// Sort RenderPhases here /// Sort the [`RenderPhases`](crate::render_phase::RenderPhase) here.
PhaseSort, PhaseSort,
/// Actual rendering happens here. In most cases, only the render backend should insert resources here /// Actual rendering happens here.
/// In most cases, only the render backend should insert resources here.
Render, Render,
/// Cleanup render resources here. /// Cleanup render resources here.
@ -75,16 +79,17 @@ impl DerefMut for RenderWorld {
} }
} }
/// Label for the rendering sub-app /// A Label for the rendering sub-app.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)] #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
pub struct RenderApp; pub struct RenderApp;
/// A "scratch" world used to avoid allocating new worlds every frame when /// A "scratch" world used to avoid allocating new worlds every frame when
// swapping out the Render World. /// swapping out the [`RenderWorld`].
#[derive(Default)] #[derive(Default)]
struct ScratchRenderWorld(World); struct ScratchRenderWorld(World);
impl Plugin for RenderPlugin { impl Plugin for RenderPlugin {
/// Initializes the renderer, sets up the [`RenderStage`](RenderStage) and creates the rendering sub-app.
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
let default_backend = if cfg!(not(target_arch = "wasm32")) { let default_backend = if cfg!(not(target_arch = "wasm32")) {
Backends::PRIMARY Backends::PRIMARY
@ -271,6 +276,8 @@ impl Plugin for RenderPlugin {
} }
} }
/// 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) { fn extract(app_world: &mut World, render_app: &mut App) {
let extract = render_app let extract = render_app
.schedule .schedule

View file

@ -79,12 +79,13 @@ impl Mesh {
} }
} }
/// Returns the topology of the mesh.
pub fn primitive_topology(&self) -> PrimitiveTopology { pub fn primitive_topology(&self) -> PrimitiveTopology {
self.primitive_topology self.primitive_topology
} }
/// Sets the data for a vertex attribute (position, normal etc.). The name will /// 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`] /// often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`].
pub fn set_attribute( pub fn set_attribute(
&mut self, &mut self,
name: impl Into<Cow<'static, str>>, name: impl Into<Cow<'static, str>>,
@ -94,11 +95,12 @@ impl Mesh {
self.attributes.insert(name.into(), values); self.attributes.insert(name.into(), values);
} }
/// Retrieve the data currently set behind a vertex attribute. /// Retrieves the data currently set to the vertex attribute with the specified `name`.
pub fn attribute(&self, name: impl Into<Cow<'static, str>>) -> Option<&VertexAttributeValues> { pub fn attribute(&self, name: impl Into<Cow<'static, str>>) -> Option<&VertexAttributeValues> {
self.attributes.get(&name.into()) self.attributes.get(&name.into())
} }
/// Retrieves the data currently set to the vertex attribute with the specified `name` mutably.
pub fn attribute_mut( pub fn attribute_mut(
&mut self, &mut self,
name: impl Into<Cow<'static, str>>, name: impl Into<Cow<'static, str>>,
@ -106,21 +108,25 @@ impl Mesh {
self.attributes.get_mut(&name.into()) self.attributes.get_mut(&name.into())
} }
/// Indices describe how triangles are constructed out of the vertex attributes. /// Sets the vertex indices of the mesh. They describe how triangles are constructed out of the
/// They are only useful for the [`crate::pipeline::PrimitiveTopology`] variants that use /// vertex attributes and are therefore only useful for the [`PrimitiveTopology`] variants
/// triangles /// that use triangles.
pub fn set_indices(&mut self, indices: Option<Indices>) { pub fn set_indices(&mut self, indices: Option<Indices>) {
self.indices = indices; self.indices = indices;
} }
/// Retrieves the vertex `indices` of the mesh.
pub fn indices(&self) -> Option<&Indices> { pub fn indices(&self) -> Option<&Indices> {
self.indices.as_ref() self.indices.as_ref()
} }
/// Retrieves the vertex `indices` of the mesh mutably.
pub fn indices_mut(&mut self) -> Option<&mut Indices> { pub fn indices_mut(&mut self) -> Option<&mut Indices> {
self.indices.as_mut() self.indices.as_mut()
} }
/// Computes and returns the index data of the mesh as bytes.
/// This is used to transform the index data into a GPU friendly format.
pub fn get_index_buffer_bytes(&self) -> Option<&[u8]> { pub fn get_index_buffer_bytes(&self) -> Option<&[u8]> {
self.indices.as_ref().map(|indices| match &indices { self.indices.as_ref().map(|indices| match &indices {
Indices::U16(indices) => cast_slice(&indices[..]), Indices::U16(indices) => cast_slice(&indices[..]),
@ -150,6 +156,10 @@ impl Mesh {
// } // }
// } // }
/// Counts all vertices of the mesh.
///
/// # Panics
/// Panics if the attributes have different vertex counts.
pub fn count_vertices(&self) -> usize { pub fn count_vertices(&self) -> usize {
let mut vertex_count: Option<usize> = None; let mut vertex_count: Option<usize> = None;
for (attribute_name, attribute_data) in self.attributes.iter() { for (attribute_name, attribute_data) in self.attributes.iter() {
@ -164,6 +174,12 @@ impl Mesh {
vertex_count.unwrap_or(0) vertex_count.unwrap_or(0)
} }
/// Computes and returns the vertex data of the mesh as bytes.
/// Therefore the attributes are located in alphabetical order.
/// This is used to transform the vertex data into a GPU friendly format.
///
/// # Panics
/// Panics if the attributes have different vertex counts.
pub fn get_vertex_buffer_data(&self) -> Vec<u8> { pub fn get_vertex_buffer_data(&self) -> Vec<u8> {
let mut vertex_size = 0; let mut vertex_size = 0;
for attribute_values in self.attributes.values() { for attribute_values in self.attributes.values() {
@ -197,6 +213,9 @@ impl Mesh {
/// ///
/// This can dramatically increase the vertex count, so make sure this is what you want. /// This can dramatically increase the vertex count, so make sure this is what you want.
/// Does nothing if no [Indices] are set. /// Does nothing if no [Indices] are set.
///
/// # Panics
/// If the mesh has any other topology than [`PrimitiveTopology::TriangleList`].
pub fn duplicate_vertices(&mut self) { pub fn duplicate_vertices(&mut self) {
fn duplicate<T: Copy>(values: &[T], indices: impl Iterator<Item = usize>) -> Vec<T> { fn duplicate<T: Copy>(values: &[T], indices: impl Iterator<Item = usize>) -> Vec<T> {
indices.map(|i| values[i]).collect() indices.map(|i| values[i]).collect()
@ -248,7 +267,8 @@ impl Mesh {
/// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh. /// Calculates the [`Mesh::ATTRIBUTE_NORMAL`] of a mesh.
/// ///
/// Panics if [`Indices`] are set. /// # Panics
/// Panics if [`Indices`] are set or [`Mesh::ATTRIBUTE_POSITION`] is not of type `float3`.
/// Consider calling [Mesh::duplicate_vertices] or export your mesh with normal attributes. /// Consider calling [Mesh::duplicate_vertices] or export your mesh with normal attributes.
pub fn compute_flat_normals(&mut self) { pub fn compute_flat_normals(&mut self) {
if self.indices().is_some() { if self.indices().is_some() {
@ -349,7 +369,8 @@ impl VertexFormatSize for wgpu::VertexFormat {
} }
} }
/// An array where each entry describes a property of a single vertex. /// Contains an array where each entry describes a property of a single vertex.
/// Matches the [`VertexFormats`](VertexFormat).
#[derive(Clone, Debug, EnumVariantMeta)] #[derive(Clone, Debug, EnumVariantMeta)]
pub enum VertexAttributeValues { pub enum VertexAttributeValues {
Float32(Vec<f32>), Float32(Vec<f32>),
@ -418,11 +439,12 @@ impl VertexAttributeValues {
} }
} }
/// Returns `true` if there are no vertices in this VertexAttributeValue /// Returns `true` if there are no vertices in this VertexAttributeValue.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.len() == 0 self.len() == 0
} }
/// Returns the values as float triples if possible.
fn as_float3(&self) -> Option<&[[f32; 3]]> { fn as_float3(&self) -> Option<&[[f32; 3]]> {
match self { match self {
VertexAttributeValues::Float32x3(values) => Some(values), VertexAttributeValues::Float32x3(values) => Some(values),
@ -502,7 +524,7 @@ impl From<&VertexAttributeValues> for VertexFormat {
} }
} }
/// An array of indices into the VertexAttributeValues for a mesh. /// An array of indices into the [`VertexAttributeValues`] for a mesh.
/// ///
/// It describes the order in which the vertex attributes should be joined into faces. /// It describes the order in which the vertex attributes should be joined into faces.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -512,6 +534,7 @@ pub enum Indices {
} }
impl Indices { impl Indices {
/// Returns an iterator over the indices.
fn iter(&self) -> impl Iterator<Item = usize> + '_ { fn iter(&self) -> impl Iterator<Item = usize> + '_ {
match self { match self {
Indices::U16(vec) => IndicesIter::U16(vec.iter()), Indices::U16(vec) => IndicesIter::U16(vec.iter()),
@ -519,6 +542,7 @@ impl Indices {
} }
} }
/// Returns the number of indices.
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
match self { match self {
Indices::U16(vec) => vec.len(), Indices::U16(vec) => vec.len(),
@ -526,6 +550,7 @@ impl Indices {
} }
} }
/// Returns `true` if there are no indices.
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
match self { match self {
Indices::U16(vec) => vec.is_empty(), Indices::U16(vec) => vec.is_empty(),
@ -533,10 +558,13 @@ impl Indices {
} }
} }
} }
/// An Iterator for the [`Indices`].
enum IndicesIter<'a> { enum IndicesIter<'a> {
U16(std::slice::Iter<'a, u16>), U16(std::slice::Iter<'a, u16>),
U32(std::slice::Iter<'a, u32>), U32(std::slice::Iter<'a, u32>),
} }
impl Iterator for IndicesIter<'_> { impl Iterator for IndicesIter<'_> {
type Item = usize; type Item = usize;
@ -557,15 +585,20 @@ impl From<&Indices> for IndexFormat {
} }
} }
/// The GPU-representation of a [`Mesh`].
/// Consists of a vertex data buffer and an optional index data buffer.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct GpuMesh { pub struct GpuMesh {
/// Contains all attribute data for each vertex.
pub vertex_buffer: Buffer, pub vertex_buffer: Buffer,
pub index_info: Option<GpuIndexInfo>, pub index_info: Option<GpuIndexInfo>,
pub has_tangents: bool, pub has_tangents: bool,
} }
/// The index info of a [`GpuMesh`].
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct GpuIndexInfo { pub struct GpuIndexInfo {
/// Contains all index data of a mesh.
pub buffer: Buffer, pub buffer: Buffer,
pub count: u32, pub count: u32,
pub index_format: IndexFormat, pub index_format: IndexFormat,
@ -576,10 +609,12 @@ impl RenderAsset for Mesh {
type PreparedAsset = GpuMesh; type PreparedAsset = GpuMesh;
type Param = SRes<RenderDevice>; type Param = SRes<RenderDevice>;
/// Clones the mesh.
fn extract_asset(&self) -> Self::ExtractedAsset { fn extract_asset(&self) -> Self::ExtractedAsset {
self.clone() self.clone()
} }
/// Converts the extracted mesh a into [`GpuMesh`].
fn prepare_asset( fn prepare_asset(
mesh: Self::ExtractedAsset, mesh: Self::ExtractedAsset,
render_device: &mut SystemParamItem<Self::Param>, render_device: &mut SystemParamItem<Self::Param>,

View file

@ -9,6 +9,7 @@ use crate::render_asset::RenderAssetPlugin;
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_asset::AddAsset; 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; pub struct MeshPlugin;
impl Plugin for MeshPlugin { impl Plugin for MeshPlugin {

View file

@ -24,6 +24,7 @@ impl From<Cube> for Mesh {
} }
} }
/// An axis-aligned box defined by its minimum and maximum point.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Box { pub struct Box {
pub min_x: f32, pub min_x: f32,
@ -37,6 +38,7 @@ pub struct Box {
} }
impl Box { impl Box {
/// Creates a new box centered at the origin with the supplied side lengths.
pub fn new(x_length: f32, y_length: f32, z_length: f32) -> Box { pub fn new(x_length: f32, y_length: f32, z_length: f32) -> Box {
Box { Box {
max_x: x_length / 2.0, max_x: x_length / 2.0,
@ -118,7 +120,7 @@ impl From<Box> for Mesh {
} }
} }
/// A rectangle on the XY plane. /// A rectangle on the XY plane centered at the origin.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Quad { pub struct Quad {
/// Full width and height of the rectangle. /// Full width and height of the rectangle.
@ -220,7 +222,7 @@ impl From<Quad> for Mesh {
} }
} }
/// A square on the XZ plane. /// A square on the XZ plane centered at the origin.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Plane { pub struct Plane {
/// The total side length of the square. /// The total side length of the square.

View file

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

View file

@ -12,18 +12,38 @@ pub enum PrepareAssetError<E: Send + Sync + 'static> {
RetryNextUpdate(E), RetryNextUpdate(E),
} }
/// Describes how an asset gets extracted and prepared for rendering.
///
/// In the [`RenderStage::Extract`](crate::RenderStage::Extract) step the asset is transferred
/// from the "app world" into the "render world".
/// Therefore it is converted into a [`RenderAsset::ExtractedAsset`], which may be the same type
/// as the render asset itself.
///
/// After that in the [`RenderStage::Prepare`](crate::RenderStage::Prepare) step the extracted asset
/// is transformed into its GPU-representation of type [`RenderAsset::PreparedAsset`].
pub trait RenderAsset: Asset { pub trait RenderAsset: Asset {
/// The representation of the the asset in the "render world".
type ExtractedAsset: Send + Sync + 'static; type ExtractedAsset: Send + Sync + 'static;
/// The GPU-representation of the the asset.
type PreparedAsset: Send + Sync + 'static; type PreparedAsset: Send + Sync + 'static;
/// Specifies all ECS data required by [`RenderAsset::prepare_asset`].
/// For convenience use the [`lifetimeless`](bevy_ecs::system::lifetimeless) SystemParams.
type Param: SystemParam; type Param: SystemParam;
/// Converts the asset into a [`RenderAsset::ExtractedAsset`].
fn extract_asset(&self) -> Self::ExtractedAsset; fn extract_asset(&self) -> Self::ExtractedAsset;
/// Prepares the `extracted asset` for the GPU by transforming it into
/// a [`RenderAsset::PreparedAsset`]. Therefore ECS data may be accessed via the `param`.
fn prepare_asset( fn prepare_asset(
extracted_asset: Self::ExtractedAsset, extracted_asset: Self::ExtractedAsset,
param: &mut SystemParamItem<Self::Param>, param: &mut SystemParamItem<Self::Param>,
) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>>; ) -> Result<Self::PreparedAsset, PrepareAssetError<Self::ExtractedAsset>>;
} }
/// Extracts assets into gpu-usable data /// This plugin extracts the changed assets from the "app world" into the "render world"
/// and prepares them for the GPU. They can then be accessed from the [`RenderAssets`] resource.
///
/// Therefore it sets up the [`RenderStage::Extract`](crate::RenderStage::Extract) and
/// [`RenderStage::Prepare`](crate::RenderStage::Prepare) steps for the specified [`RenderAsset`].
pub struct RenderAssetPlugin<A: RenderAsset>(PhantomData<fn() -> A>); pub struct RenderAssetPlugin<A: RenderAsset>(PhantomData<fn() -> A>);
impl<A: RenderAsset> Default for RenderAssetPlugin<A> { impl<A: RenderAsset> Default for RenderAssetPlugin<A> {
@ -45,6 +65,7 @@ impl<A: RenderAsset> Plugin for RenderAssetPlugin<A> {
} }
} }
/// Temporarily stores the extracted and removed assets of the current frame.
pub struct ExtractedAssets<A: RenderAsset> { pub struct ExtractedAssets<A: RenderAsset> {
extracted: Vec<(Handle<A>, A::ExtractedAsset)>, extracted: Vec<(Handle<A>, A::ExtractedAsset)>,
removed: Vec<Handle<A>>, removed: Vec<Handle<A>>,
@ -59,8 +80,12 @@ impl<A: RenderAsset> Default for ExtractedAssets<A> {
} }
} }
/// Stores all GPU representations ([`RenderAsset::PreparedAssets`](RenderAsset::PreparedAsset))
/// of [`RenderAssets`](RenderAsset) as long as they exist.
pub type RenderAssets<A> = HashMap<Handle<A>, <A as RenderAsset>::PreparedAsset>; pub type RenderAssets<A> = HashMap<Handle<A>, <A as RenderAsset>::PreparedAsset>;
/// This system extracts all crated or modified assets of the corresponding [`RenderAsset`] type
/// into the "render world".
fn extract_render_asset<A: RenderAsset>( fn extract_render_asset<A: RenderAsset>(
mut commands: Commands, mut commands: Commands,
mut events: EventReader<AssetEvent<A>>, mut events: EventReader<AssetEvent<A>>,
@ -97,6 +122,7 @@ fn extract_render_asset<A: RenderAsset>(
}) })
} }
/// Specifies all ECS data required by [`PrepareAssetSystem`].
pub type RenderAssetParams<R> = ( pub type RenderAssetParams<R> = (
SResMut<ExtractedAssets<R>>, SResMut<ExtractedAssets<R>>,
SResMut<RenderAssets<R>>, SResMut<RenderAssets<R>>,
@ -105,6 +131,7 @@ pub type RenderAssetParams<R> = (
); );
// TODO: consider storing inside system? // TODO: consider storing inside system?
/// All assets that should be prepared next frame.
pub struct PrepareNextFrameAssets<A: RenderAsset> { pub struct PrepareNextFrameAssets<A: RenderAsset> {
assets: Vec<(Handle<A>, A::ExtractedAsset)>, assets: Vec<(Handle<A>, A::ExtractedAsset)>,
} }
@ -117,10 +144,13 @@ impl<A: RenderAsset> Default for PrepareNextFrameAssets<A> {
} }
} }
/// This system prepares all assets of the corresponding [`RenderAsset`] type
/// which where extracted this frame for the GPU.
pub struct PrepareAssetSystem<R: RenderAsset>(PhantomData<R>); pub struct PrepareAssetSystem<R: RenderAsset>(PhantomData<R>);
impl<R: RenderAsset> RunSystem for PrepareAssetSystem<R> { impl<R: RenderAsset> RunSystem for PrepareAssetSystem<R> {
type Param = RenderAssetParams<R>; type Param = RenderAssetParams<R>;
fn run( fn run(
(mut extracted_assets, mut render_assets, mut prepare_next_frame, mut param): SystemParamItem<Self::Param>, (mut extracted_assets, mut render_assets, mut prepare_next_frame, mut param): SystemParamItem<Self::Param>,
) { ) {

View file

@ -17,6 +17,7 @@ use bevy_ecs::{
use crevice::std140::AsStd140; use crevice::std140::AsStd140;
use std::{marker::PhantomData, ops::Deref}; use std::{marker::PhantomData, ops::Deref};
/// Stores the index of a uniform inside of [`ComponentUniforms`].
pub struct DynamicUniformIndex<C: Component> { pub struct DynamicUniformIndex<C: Component> {
index: u32, index: u32,
marker: PhantomData<C>, marker: PhantomData<C>,
@ -29,13 +30,28 @@ impl<C: Component> DynamicUniformIndex<C> {
} }
} }
/// Describes how a component gets extracted for rendering.
///
/// Therefore the component is transferred from the "app world" into the "render world"
/// in the [`RenderStage::Extract`](crate::RenderStage::Extract) step.
pub trait ExtractComponent: Component { pub trait ExtractComponent: Component {
/// ECS [`WorldQuery`] to fetch the components to extract.
type Query: WorldQuery; type Query: WorldQuery;
/// Filters the entities with additional constraints.
type Filter: WorldQuery; type Filter: WorldQuery;
/// Defines how the component is transferred into the "render world".
fn extract_component(item: QueryItem<Self::Query>) -> Self; fn extract_component(item: QueryItem<Self::Query>) -> Self;
} }
/// Extracts assets into gpu-usable data /// This plugin prepares the components of the corresponding type for the GPU
/// by transforming them into uniforms.
///
/// They can then be accessed from the [`ComponentUniforms`] resource.
/// For referencing the newly created uniforms a [`DynamicUniformIndex`] is inserted
/// for every processed entity.
///
/// Therefore it sets up the [`RenderStage::Prepare`](crate::RenderStage::Prepare) step
/// for the specified [`ExtractComponent`].
pub struct UniformComponentPlugin<C>(PhantomData<fn() -> C>); pub struct UniformComponentPlugin<C>(PhantomData<fn() -> C>);
impl<C> Default for UniformComponentPlugin<C> { impl<C> Default for UniformComponentPlugin<C> {
@ -55,6 +71,7 @@ impl<C: Component + AsStd140 + Clone> Plugin for UniformComponentPlugin<C> {
} }
} }
/// Stores all uniforms of the component type.
pub struct ComponentUniforms<C: Component + AsStd140> { pub struct ComponentUniforms<C: Component + AsStd140> {
uniforms: DynamicUniformVec<C>, uniforms: DynamicUniformVec<C>,
} }
@ -83,6 +100,8 @@ impl<C: Component + AsStd140> Default for ComponentUniforms<C> {
} }
} }
/// This system prepares all components of the corresponding component type.
/// They are transformed into uniforms and stored in the [`ComponentUniforms`] resource.
fn prepare_uniform_components<C: Component>( fn prepare_uniform_components<C: Component>(
mut commands: Commands, mut commands: Commands,
render_device: Res<RenderDevice>, render_device: Res<RenderDevice>,
@ -107,6 +126,10 @@ fn prepare_uniform_components<C: Component>(
.write_buffer(&render_device, &render_queue); .write_buffer(&render_device, &render_queue);
} }
/// This plugin extracts the components into the "render world".
///
/// Therefore it sets up the [`RenderStage::Extract`](crate::RenderStage::Extract) step
/// for the specified [`ExtractComponent`].
pub struct ExtractComponentPlugin<C, F = ()>(PhantomData<fn() -> (C, F)>); pub struct ExtractComponentPlugin<C, F = ()>(PhantomData<fn() -> (C, F)>);
impl<C, F> Default for ExtractComponentPlugin<C, F> { impl<C, F> Default for ExtractComponentPlugin<C, F> {
@ -137,6 +160,7 @@ impl<T: Asset> ExtractComponent for Handle<T> {
} }
} }
/// This system extracts all components of the corresponding [`ExtractComponent`] type.
pub struct ExtractComponentSystem<C: ExtractComponent>(PhantomData<C>); pub struct ExtractComponentSystem<C: ExtractComponent>(PhantomData<C>);
impl<C: ExtractComponent> RunSystem for ExtractComponentSystem<C> impl<C: ExtractComponent> RunSystem for ExtractComponentSystem<C>
@ -146,6 +170,7 @@ where
{ {
type Param = ( type Param = (
SCommands, SCommands,
// the previous amount of extracted components
Local<'static, usize>, Local<'static, usize>,
SQuery<(Entity, C::Query), C::Filter>, SQuery<(Entity, C::Query), C::Filter>,
); );

View file

@ -6,11 +6,21 @@ use bevy_ecs::entity::Entity;
use std::borrow::Cow; use std::borrow::Cow;
use thiserror::Error; use thiserror::Error;
/// A command that signals the graph runner to run the sub graph corresponding to the `name`
/// with the specified `inputs` next.
pub struct RunSubGraph { pub struct RunSubGraph {
pub name: Cow<'static, str>, pub name: Cow<'static, str>,
pub inputs: Vec<SlotValue>, pub inputs: Vec<SlotValue>,
} }
/// The context with all graph information required to run a [`Node`](super::Node).
/// This context is created for each node by the `RenderGraphRunner`.
///
/// The slot input can be read from here and the outputs must be written back to the context for
/// passing them onto the next node.
///
/// Sub graphs can be queued for running by adding a [`RunSubGraph`] command to the context.
/// After the node has finished running the graph runner is responsible for executing the sub graphs.
pub struct RenderGraphContext<'a> { pub struct RenderGraphContext<'a> {
graph: &'a RenderGraph, graph: &'a RenderGraph,
node: &'a NodeState, node: &'a NodeState,
@ -20,6 +30,7 @@ pub struct RenderGraphContext<'a> {
} }
impl<'a> RenderGraphContext<'a> { impl<'a> RenderGraphContext<'a> {
/// Creates a new render graph context for the `node`.
pub fn new( pub fn new(
graph: &'a RenderGraph, graph: &'a RenderGraph,
node: &'a NodeState, node: &'a NodeState,
@ -35,19 +46,23 @@ impl<'a> RenderGraphContext<'a> {
} }
} }
/// Returns the input slot values for the node.
#[inline] #[inline]
pub fn inputs(&self) -> &[SlotValue] { pub fn inputs(&self) -> &[SlotValue] {
self.inputs self.inputs
} }
/// Returns the [`SlotInfos`] of the inputs.
pub fn input_info(&self) -> &SlotInfos { pub fn input_info(&self) -> &SlotInfos {
&self.node.input_slots &self.node.input_slots
} }
/// Returns the [`SlotInfos`] of the outputs.
pub fn output_info(&self) -> &SlotInfos { pub fn output_info(&self) -> &SlotInfos {
&self.node.output_slots &self.node.output_slots
} }
/// Retrieves the input slot value referenced by the `label`.
pub fn get_input(&self, label: impl Into<SlotLabel>) -> Result<&SlotValue, InputSlotError> { pub fn get_input(&self, label: impl Into<SlotLabel>) -> Result<&SlotValue, InputSlotError> {
let label = label.into(); let label = label.into();
let index = self let index = self
@ -58,6 +73,7 @@ impl<'a> RenderGraphContext<'a> {
} }
// TODO: should this return an Arc or a reference? // TODO: should this return an Arc or a reference?
/// Retrieves the input slot value referenced by the `label` as a [`TextureView`].
pub fn get_input_texture( pub fn get_input_texture(
&self, &self,
label: impl Into<SlotLabel>, label: impl Into<SlotLabel>,
@ -73,6 +89,7 @@ impl<'a> RenderGraphContext<'a> {
} }
} }
/// Retrieves the input slot value referenced by the `label` as a [`Sampler`].
pub fn get_input_sampler( pub fn get_input_sampler(
&self, &self,
label: impl Into<SlotLabel>, label: impl Into<SlotLabel>,
@ -88,6 +105,7 @@ impl<'a> RenderGraphContext<'a> {
} }
} }
/// Retrieves the input slot value referenced by the `label` as a [`Buffer`].
pub fn get_input_buffer(&self, label: impl Into<SlotLabel>) -> Result<&Buffer, InputSlotError> { pub fn get_input_buffer(&self, label: impl Into<SlotLabel>) -> Result<&Buffer, InputSlotError> {
let label = label.into(); let label = label.into();
match self.get_input(label.clone())? { match self.get_input(label.clone())? {
@ -100,6 +118,7 @@ impl<'a> RenderGraphContext<'a> {
} }
} }
/// Retrieves the input slot value referenced by the `label` as an [`Entity`].
pub fn get_input_entity(&self, label: impl Into<SlotLabel>) -> Result<Entity, InputSlotError> { pub fn get_input_entity(&self, label: impl Into<SlotLabel>) -> Result<Entity, InputSlotError> {
let label = label.into(); let label = label.into();
match self.get_input(label.clone())? { match self.get_input(label.clone())? {
@ -112,6 +131,7 @@ impl<'a> RenderGraphContext<'a> {
} }
} }
/// Sets the output slot value referenced by the `label`.
pub fn set_output( pub fn set_output(
&mut self, &mut self,
label: impl Into<SlotLabel>, label: impl Into<SlotLabel>,
@ -138,6 +158,7 @@ impl<'a> RenderGraphContext<'a> {
Ok(()) Ok(())
} }
/// Queues up a sub graph for execution after the node has finished running.
pub fn run_sub_graph( pub fn run_sub_graph(
&mut self, &mut self,
name: impl Into<Cow<'static, str>>, name: impl Into<Cow<'static, str>>,
@ -177,6 +198,8 @@ impl<'a> RenderGraphContext<'a> {
Ok(()) Ok(())
} }
/// Finishes the context for this [`Node`](super::Node) by
/// returning the sub graphs to run next.
pub fn finish(self) -> Vec<RunSubGraph> { pub fn finish(self) -> Vec<RunSubGraph> {
self.run_sub_graphs self.run_sub_graphs
} }

View file

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

View file

@ -9,6 +9,43 @@ use bevy_ecs::prelude::World;
use bevy_utils::HashMap; use bevy_utils::HashMap;
use std::{borrow::Cow, fmt::Debug}; use std::{borrow::Cow, fmt::Debug};
/// The render graph configures the modular, parallel and re-usable render logic.
/// It is a retained and stateless (nodes itself my have their internal state) structure,
/// which can not be modified while it is executed by the graph runner.
///
/// The `RenderGraphRunner` is responsible for executing the entire graph each frame.
///
/// It consists of three main components: [`Nodes`](Node), [`Edges`](Edge)
/// and [`Slots`](super::SlotType).
///
/// Nodes are responsible for generating draw calls and operating on input and output slots.
/// Edges specify the order of execution for nodes and connect input and output slots together.
/// Slots describe the render resources created or used by the nodes.
///
/// Additionally a render graph can contain multiple sub graphs, which are run by the
/// corresponding nodes. Every render graph can have 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_render2::render_graph::{RenderGraph, Node, RenderGraphContext, NodeRunError};
/// # use bevy_render2::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)] #[derive(Default)]
pub struct RenderGraph { pub struct RenderGraph {
nodes: HashMap<NodeId, NodeState>, nodes: HashMap<NodeId, NodeState>,
@ -18,8 +55,10 @@ pub struct RenderGraph {
} }
impl RenderGraph { impl RenderGraph {
/// The name of the [`GraphInputNode`] of this graph. Used to connect other nodes to it.
pub const INPUT_NODE_NAME: &'static str = "GraphInputNode"; 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) { pub fn update(&mut self, world: &mut World) {
for node in self.nodes.values_mut() { for node in self.nodes.values_mut() {
node.node.update(world); node.node.update(world);
@ -30,6 +69,7 @@ impl RenderGraph {
} }
} }
/// Creates an [`GraphInputNode`] with the specified slots if not already present.
pub fn set_input(&mut self, inputs: Vec<SlotInfo>) -> NodeId { pub fn set_input(&mut self, inputs: Vec<SlotInfo>) -> NodeId {
if self.input_node.is_some() { if self.input_node.is_some() {
panic!("Graph already has an input node"); panic!("Graph already has an input node");
@ -40,11 +80,14 @@ impl RenderGraph {
id id
} }
/// Returns the [`NodeState`] of the input node of this graph..
#[inline] #[inline]
pub fn input_node(&self) -> Option<&NodeState> { pub fn input_node(&self) -> Option<&NodeState> {
self.input_node.and_then(|id| self.get_node_state(id).ok()) self.input_node.and_then(|id| self.get_node_state(id).ok())
} }
/// Adds the `node` with the `name` to the graph.
/// If the name is already present replaces it instead.
pub fn add_node<T>(&mut self, name: impl Into<Cow<'static, str>>, node: T) -> NodeId pub fn add_node<T>(&mut self, name: impl Into<Cow<'static, str>>, node: T) -> NodeId
where where
T: Node, T: Node,
@ -58,6 +101,7 @@ impl RenderGraph {
id id
} }
/// Retrieves the [`NodeState`] referenced by the `label`.
pub fn get_node_state( pub fn get_node_state(
&self, &self,
label: impl Into<NodeLabel>, label: impl Into<NodeLabel>,
@ -69,6 +113,7 @@ impl RenderGraph {
.ok_or(RenderGraphError::InvalidNode(label)) .ok_or(RenderGraphError::InvalidNode(label))
} }
/// Retrieves the [`NodeState`] referenced by the `label` mutably.
pub fn get_node_state_mut( pub fn get_node_state_mut(
&mut self, &mut self,
label: impl Into<NodeLabel>, label: impl Into<NodeLabel>,
@ -80,6 +125,7 @@ impl RenderGraph {
.ok_or(RenderGraphError::InvalidNode(label)) .ok_or(RenderGraphError::InvalidNode(label))
} }
/// Retrieves the [`NodeId`] referenced by the `label`.
pub fn get_node_id(&self, label: impl Into<NodeLabel>) -> Result<NodeId, RenderGraphError> { pub fn get_node_id(&self, label: impl Into<NodeLabel>) -> Result<NodeId, RenderGraphError> {
let label = label.into(); let label = label.into();
match label { match label {
@ -92,6 +138,7 @@ impl RenderGraph {
} }
} }
/// Retrieves the [`Node`] referenced by the `label`.
pub fn get_node<T>(&self, label: impl Into<NodeLabel>) -> Result<&T, RenderGraphError> pub fn get_node<T>(&self, label: impl Into<NodeLabel>) -> Result<&T, RenderGraphError>
where where
T: Node, T: Node,
@ -99,6 +146,7 @@ impl RenderGraph {
self.get_node_state(label).and_then(|n| n.node()) self.get_node_state(label).and_then(|n| n.node())
} }
/// Retrieves the [`Node`] referenced by the `label` mutably.
pub fn get_node_mut<T>( pub fn get_node_mut<T>(
&mut self, &mut self,
label: impl Into<NodeLabel>, label: impl Into<NodeLabel>,
@ -109,6 +157,8 @@ impl RenderGraph {
self.get_node_state_mut(label).and_then(|n| n.node_mut()) self.get_node_state_mut(label).and_then(|n| n.node_mut())
} }
/// Adds the [`Edge::SlotEdge`] to the graph. This guarantees that the `output_node`
/// is run before the `input_node` and also connects the `output_slot` to the `input_slot`.
pub fn add_slot_edge( pub fn add_slot_edge(
&mut self, &mut self,
output_node: impl Into<NodeLabel>, output_node: impl Into<NodeLabel>,
@ -151,6 +201,8 @@ impl RenderGraph {
Ok(()) Ok(())
} }
/// Adds the [`Edge::NodeEdge`] to the graph. This guarantees that the `output_node`
/// is run before the `input_node`.
pub fn add_node_edge( pub fn add_node_edge(
&mut self, &mut self,
output_node: impl Into<NodeLabel>, output_node: impl Into<NodeLabel>,
@ -176,6 +228,8 @@ impl RenderGraph {
Ok(()) Ok(())
} }
/// Verifies that the edge is not already existing and
/// checks that slot edges are connected correctly.
pub fn validate_edge(&mut self, edge: &Edge) -> Result<(), RenderGraphError> { pub fn validate_edge(&mut self, edge: &Edge) -> Result<(), RenderGraphError> {
if self.has_edge(edge) { if self.has_edge(edge) {
return Err(RenderGraphError::EdgeAlreadyExists(edge.clone())); return Err(RenderGraphError::EdgeAlreadyExists(edge.clone()));
@ -237,6 +291,7 @@ impl RenderGraph {
Ok(()) Ok(())
} }
/// Checks whether the `edge` already exists in the graph.
pub fn has_edge(&self, edge: &Edge) -> bool { pub fn has_edge(&self, edge: &Edge) -> bool {
let output_node_state = self.get_node_state(edge.get_output_node()); let output_node_state = self.get_node_state(edge.get_output_node());
let input_node_state = self.get_node_state(edge.get_input_node()); let input_node_state = self.get_node_state(edge.get_input_node());
@ -253,26 +308,32 @@ impl RenderGraph {
false false
} }
/// Returns an iterator over the [`NodeStates`](NodeState).
pub fn iter_nodes(&self) -> impl Iterator<Item = &NodeState> { pub fn iter_nodes(&self) -> impl Iterator<Item = &NodeState> {
self.nodes.values() self.nodes.values()
} }
/// Returns an iterator over the [`NodeStates`](NodeState), that allows modifying each value.
pub fn iter_nodes_mut(&mut self) -> impl Iterator<Item = &mut NodeState> { pub fn iter_nodes_mut(&mut self) -> impl Iterator<Item = &mut NodeState> {
self.nodes.values_mut() self.nodes.values_mut()
} }
/// Returns an iterator over the sub graphs.
pub fn iter_sub_graphs(&self) -> impl Iterator<Item = (&str, &RenderGraph)> { pub fn iter_sub_graphs(&self) -> impl Iterator<Item = (&str, &RenderGraph)> {
self.sub_graphs self.sub_graphs
.iter() .iter()
.map(|(name, graph)| (name.as_ref(), graph)) .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)> { pub fn iter_sub_graphs_mut(&mut self) -> impl Iterator<Item = (&str, &mut RenderGraph)> {
self.sub_graphs self.sub_graphs
.iter_mut() .iter_mut()
.map(|(name, graph)| (name.as_ref(), graph)) .map(|(name, graph)| (name.as_ref(), graph))
} }
/// Returns an iterator over a tuple of the input edges and the corresponding output nodes
/// for the node referenced by the label.
pub fn iter_node_inputs( pub fn iter_node_inputs(
&self, &self,
label: impl Into<NodeLabel>, label: impl Into<NodeLabel>,
@ -288,6 +349,8 @@ impl RenderGraph {
})) }))
} }
/// Returns an iterator over a tuple of the ouput edges and the corresponding input nodes
/// for the node referenced by the label.
pub fn iter_node_outputs( pub fn iter_node_outputs(
&self, &self,
label: impl Into<NodeLabel>, label: impl Into<NodeLabel>,
@ -301,14 +364,18 @@ impl RenderGraph {
.map(move |(edge, input_node_id)| (edge, self.get_node_state(input_node_id).unwrap()))) .map(move |(edge, input_node_id)| (edge, self.get_node_state(input_node_id).unwrap())))
} }
/// 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) { pub fn add_sub_graph(&mut self, name: impl Into<Cow<'static, str>>, sub_graph: RenderGraph) {
self.sub_graphs.insert(name.into(), sub_graph); 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> { pub fn get_sub_graph(&self, name: impl AsRef<str>) -> Option<&RenderGraph> {
self.sub_graphs.get(name.as_ref()) 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> { pub fn get_sub_graph_mut(&mut self, name: impl AsRef<str>) -> Option<&mut RenderGraph> {
self.sub_graphs.get_mut(name.as_ref()) self.sub_graphs.get_mut(name.as_ref())
} }
@ -326,6 +393,8 @@ 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 { pub struct GraphInputNode {
inputs: Vec<SlotInfo>, inputs: Vec<SlotInfo>,
} }

View file

@ -11,6 +11,10 @@ use downcast_rs::{impl_downcast, Downcast};
use std::{borrow::Cow, fmt::Debug}; use std::{borrow::Cow, fmt::Debug};
use thiserror::Error; use thiserror::Error;
/// A [`Node`] identifier.
/// It automatically generates its own random uuid.
///
/// This id is used to reference the node internally (edges, etc).
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct NodeId(Uuid); pub struct NodeId(Uuid);
@ -25,19 +29,37 @@ impl NodeId {
} }
} }
/// A render node that can be added to a [`RenderGraph`](super::RenderGraph).
///
/// Nodes are the fundamental part of the graph and used to extend its functionality, by
/// generating draw calls and/or running subgraphs.
/// They are added via the render_graph::add_node(my_node) methode.
///
/// To determine their position in the graph and ensure that all required dependencies (inputs)
/// are already executed, [`Edges`](Edge) are used.
///
/// A node can produce outputs used as dependencies by other nodes.
/// Those inputs and outputs are called slots and are the default way of passing render data
/// inside the graph. For more information see [`SlotType`](super::SlotType).
pub trait Node: Downcast + Send + Sync + 'static { pub trait Node: Downcast + Send + Sync + 'static {
/// 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> { fn input(&self) -> Vec<SlotInfo> {
Vec::new() Vec::new()
} }
/// 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> { fn output(&self) -> Vec<SlotInfo> {
Vec::new() Vec::new()
} }
/// Update internal node state using the current render [`World`]. /// Updates internal node state using the current render [`World`] prior to the run method.
fn update(&mut self, _world: &mut World) {} fn update(&mut self, _world: &mut World) {}
/// Run the graph node logic /// 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( fn run(
&self, &self,
graph: &mut RenderGraphContext, graph: &mut RenderGraphContext,
@ -58,6 +80,7 @@ pub enum NodeRunError {
RunSubGraphError(#[from] RunSubGraphError), RunSubGraphError(#[from] RunSubGraphError),
} }
/// A collection of input and output [`Edges`](Edge) for a [`Node`].
#[derive(Debug)] #[derive(Debug)]
pub struct Edges { pub struct Edges {
pub id: NodeId, pub id: NodeId,
@ -66,6 +89,7 @@ pub struct Edges {
} }
impl Edges { impl Edges {
/// Adds an edge to the `input_edges` if it does not already exist.
pub(crate) fn add_input_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> { pub(crate) fn add_input_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> {
if self.has_input_edge(&edge) { if self.has_input_edge(&edge) {
return Err(RenderGraphError::EdgeAlreadyExists(edge)); return Err(RenderGraphError::EdgeAlreadyExists(edge));
@ -74,6 +98,7 @@ impl Edges {
Ok(()) Ok(())
} }
/// Adds an edge to the `output_edges` if it does not already exist.
pub(crate) fn add_output_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> { pub(crate) fn add_output_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> {
if self.has_output_edge(&edge) { if self.has_output_edge(&edge) {
return Err(RenderGraphError::EdgeAlreadyExists(edge)); return Err(RenderGraphError::EdgeAlreadyExists(edge));
@ -82,14 +107,18 @@ impl Edges {
Ok(()) Ok(())
} }
/// Checks whether the input edge already exists.
pub fn has_input_edge(&self, edge: &Edge) -> bool { pub fn has_input_edge(&self, edge: &Edge) -> bool {
self.input_edges.contains(edge) self.input_edges.contains(edge)
} }
/// Checks whether the output edge already exists.
pub fn has_output_edge(&self, edge: &Edge) -> bool { pub fn has_output_edge(&self, edge: &Edge) -> bool {
self.output_edges.contains(edge) self.output_edges.contains(edge)
} }
/// Searches the `input_edges` for a [`Edge::SlotEdge`],
/// which `input_index` matches the `index`;
pub fn get_input_slot_edge(&self, index: usize) -> Result<&Edge, RenderGraphError> { pub fn get_input_slot_edge(&self, index: usize) -> Result<&Edge, RenderGraphError> {
self.input_edges self.input_edges
.iter() .iter()
@ -106,6 +135,8 @@ impl Edges {
}) })
} }
/// Searches the `output_edges` for a [`Edge::SlotEdge`],
/// which `output_index` matches the `index`;
pub fn get_output_slot_edge(&self, index: usize) -> Result<&Edge, RenderGraphError> { pub fn get_output_slot_edge(&self, index: usize) -> Result<&Edge, RenderGraphError> {
self.output_edges self.output_edges
.iter() .iter()
@ -123,9 +154,14 @@ impl Edges {
} }
} }
/// The internal representation of a [`Node`], with all data required
/// by the [`RenderGraph`](super::RenderGraph).
///
/// The `input_slots` and `output_slots` are provided by the `node`.
pub struct NodeState { pub struct NodeState {
pub id: NodeId, pub id: NodeId,
pub name: Option<Cow<'static, str>>, pub name: Option<Cow<'static, str>>,
/// The name of the type that implements [`Node`].
pub type_name: &'static str, pub type_name: &'static str,
pub node: Box<dyn Node>, pub node: Box<dyn Node>,
pub input_slots: SlotInfos, pub input_slots: SlotInfos,
@ -140,6 +176,8 @@ impl Debug for NodeState {
} }
impl NodeState { impl NodeState {
/// Creates an [`NodeState`] without edges, but the `input_slots` and `output_slots`
/// are provided by the `node`.
pub fn new<T>(id: NodeId, node: T) -> Self pub fn new<T>(id: NodeId, node: T) -> Self
where where
T: Node, T: Node,
@ -159,6 +197,7 @@ impl NodeState {
} }
} }
/// Retrieves the [`Node`].
pub fn node<T>(&self) -> Result<&T, RenderGraphError> pub fn node<T>(&self) -> Result<&T, RenderGraphError>
where where
T: Node, T: Node,
@ -168,6 +207,7 @@ impl NodeState {
.ok_or(RenderGraphError::WrongNodeType) .ok_or(RenderGraphError::WrongNodeType)
} }
/// Retrieves the [`Node`] mutably.
pub fn node_mut<T>(&mut self) -> Result<&mut T, RenderGraphError> pub fn node_mut<T>(&mut self) -> Result<&mut T, RenderGraphError>
where where
T: Node, T: Node,
@ -177,14 +217,7 @@ impl NodeState {
.ok_or(RenderGraphError::WrongNodeType) .ok_or(RenderGraphError::WrongNodeType)
} }
pub fn validate_output_slots(&self) -> Result<(), RenderGraphError> { /// Validates that each input slot corresponds to an input edge.
for i in 0..self.output_slots.len() {
self.edges.get_output_slot_edge(i)?;
}
Ok(())
}
pub fn validate_input_slots(&self) -> Result<(), RenderGraphError> { pub fn validate_input_slots(&self) -> Result<(), RenderGraphError> {
for i in 0..self.input_slots.len() { for i in 0..self.input_slots.len() {
self.edges.get_input_slot_edge(i)?; self.edges.get_input_slot_edge(i)?;
@ -192,8 +225,19 @@ impl NodeState {
Ok(()) Ok(())
} }
/// Validates that each output slot corresponds to an output edge.
pub fn validate_output_slots(&self) -> Result<(), RenderGraphError> {
for i in 0..self.output_slots.len() {
self.edges.get_output_slot_edge(i)?;
}
Ok(())
}
} }
/// A [`NodeLabel`] is used to reference a [`NodeState`] by either its name or [`NodeId`]
/// inside the [`RenderGraph`](super::RenderGraph).
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum NodeLabel { pub enum NodeLabel {
Id(NodeId), Id(NodeId),
@ -223,6 +267,10 @@ impl From<NodeId> for NodeLabel {
NodeLabel::Id(value) NodeLabel::Id(value)
} }
} }
/// A [`Node`] without any inputs, outputs and subgraphs, which does nothing when run.
/// Used (as a label) to bundle multiple dependencies into one inside
/// the [`RenderGraph`](super::RenderGraph).
pub struct EmptyNode; pub struct EmptyNode;
impl Node for EmptyNode { impl Node for EmptyNode {

View file

@ -3,15 +3,27 @@ use std::borrow::Cow;
use crate::render_resource::{Buffer, Sampler, TextureView}; use crate::render_resource::{Buffer, Sampler, TextureView};
/// A value passed between render [`Nodes`](super::Node).
/// Corresponds to the [SlotType] specified in the [`RenderGraph`](super::RenderGraph).
///
/// Slots can have four different types of values:
/// [`Buffer`], [`TextureView`], [`Sampler`] and [`Entity`].
///
/// These values do not contain the actual render data, but only the ids to retrieve them.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum SlotValue { pub enum SlotValue {
/// A GPU-accessible [`Buffer`].
Buffer(Buffer), Buffer(Buffer),
/// A [`TextureView`] describes a texture used in a pipeline.
TextureView(TextureView), TextureView(TextureView),
/// A texture [`Sampler`] defines how a pipeline will sample from a [`TextureView`].
Sampler(Sampler), Sampler(Sampler),
/// An entity from the ECS.
Entity(Entity), Entity(Entity),
} }
impl SlotValue { impl SlotValue {
/// Returns the [`SlotType`] of this value.
pub fn slot_type(&self) -> SlotType { pub fn slot_type(&self) -> SlotType {
match self { match self {
SlotValue::Buffer(_) => SlotType::Buffer, SlotValue::Buffer(_) => SlotType::Buffer,
@ -46,14 +58,24 @@ impl From<Entity> for SlotValue {
} }
} }
/// 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)] #[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum SlotType { pub enum SlotType {
/// 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,
} }
/// A [`SlotLabel`] is used to reference a slot by either its name or index
/// inside the [`RenderGraph`](super::RenderGraph).
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub enum SlotLabel { pub enum SlotLabel {
Index(usize), Index(usize),
@ -90,6 +112,7 @@ impl From<usize> for SlotLabel {
} }
} }
/// The internal representation of a slot, which specifies its [`SlotType`] and name.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SlotInfo { pub struct SlotInfo {
pub name: Cow<'static, str>, pub name: Cow<'static, str>,
@ -105,6 +128,8 @@ impl SlotInfo {
} }
} }
/// A collection of input or output [`SlotInfos`](SlotInfo) for
/// a [`NodeState`](super::NodeState).
#[derive(Default, Debug)] #[derive(Default, Debug)]
pub struct SlotInfos { pub struct SlotInfos {
slots: Vec<SlotInfo>, slots: Vec<SlotInfo>,
@ -119,28 +144,33 @@ impl<T: IntoIterator<Item = SlotInfo>> From<T> for SlotInfos {
} }
impl SlotInfos { impl SlotInfos {
/// Returns the count of slots.
#[inline] #[inline]
pub fn len(&self) -> usize { pub fn len(&self) -> usize {
self.slots.len() self.slots.len()
} }
/// Returns true if there are no slots.
#[inline] #[inline]
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.slots.is_empty() self.slots.is_empty()
} }
/// Retrieves the [`SlotInfo`] for the provided label.
pub fn get_slot(&self, label: impl Into<SlotLabel>) -> Option<&SlotInfo> { pub fn get_slot(&self, label: impl Into<SlotLabel>) -> Option<&SlotInfo> {
let label = label.into(); let label = label.into();
let index = self.get_slot_index(&label)?; let index = self.get_slot_index(&label)?;
self.slots.get(index) self.slots.get(index)
} }
/// Retrieves the [`SlotInfo`] for the provided label mutably.
pub fn get_slot_mut(&mut self, label: impl Into<SlotLabel>) -> Option<&mut SlotInfo> { pub fn get_slot_mut(&mut self, label: impl Into<SlotLabel>) -> Option<&mut SlotInfo> {
let label = label.into(); let label = label.into();
let index = self.get_slot_index(&label)?; let index = self.get_slot_index(&label)?;
self.slots.get_mut(index) 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> { pub fn get_slot_index(&self, label: impl Into<SlotLabel>) -> Option<usize> {
let label = label.into(); let label = label.into();
match label { match label {
@ -154,6 +184,7 @@ impl SlotInfos {
} }
} }
/// Returns an iterator over the slot infos.
pub fn iter(&self) -> impl Iterator<Item = &SlotInfo> { pub fn iter(&self) -> impl Iterator<Item = &SlotInfo> {
self.slots.iter() self.slots.iter()
} }

View file

@ -15,7 +15,12 @@ use bevy_utils::HashMap;
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use std::{any::TypeId, fmt::Debug, hash::Hash}; use std::{any::TypeId, fmt::Debug, hash::Hash};
/// A draw function which is used to draw a specific [`PhaseItem`].
///
/// They are the the general form of drawing items, whereas [`RenderCommands`](RenderCommand)
/// are more modular.
pub trait Draw<P: PhaseItem>: Send + Sync + 'static { pub trait Draw<P: PhaseItem>: Send + Sync + 'static {
/// Draws the [`PhaseItem`] by issuing draw calls via the [`TrackedRenderPass`].
fn draw<'w>( fn draw<'w>(
&mut self, &mut self,
world: &'w World, world: &'w World,
@ -25,26 +30,39 @@ pub trait Draw<P: PhaseItem>: Send + Sync + 'static {
); );
} }
/// An item which will be drawn to the screen. A phase item should be queued up for rendering
/// during the [`RenderStage::Queue`](crate::RenderStage::Queue) stage.
/// Afterwards it will be sorted and rendered automatically in the
/// [`RenderStage::PhaseSort`](crate::RenderStage::PhaseSort) stage and
/// [`RenderStage::Render`](crate::RenderStage::Render) stage, respectively.
pub trait PhaseItem: Send + Sync + 'static { pub trait PhaseItem: Send + Sync + 'static {
/// The type used for ordering the items. The smallest values are drawn first.
type SortKey: Ord; type SortKey: Ord;
/// Determines the order in which the items are drawn during the corresponding [`RenderPhase`].
fn sort_key(&self) -> Self::SortKey; fn sort_key(&self) -> Self::SortKey;
/// Specifies the [`Draw`] function used to render the item.
fn draw_function(&self) -> DrawFunctionId; fn draw_function(&self) -> DrawFunctionId;
} }
// TODO: make this generic? // TODO: make this generic?
/// /// A [`Draw`] function identifier.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
pub struct DrawFunctionId(usize); pub struct DrawFunctionId(usize);
/// Stores all draw functions for the [`PhaseItem`] type.
/// For retrieval they are associated with their [`TypeId`].
pub struct DrawFunctionsInternal<P: PhaseItem> { pub struct DrawFunctionsInternal<P: PhaseItem> {
pub draw_functions: Vec<Box<dyn Draw<P>>>, pub draw_functions: Vec<Box<dyn Draw<P>>>,
pub indices: HashMap<TypeId, DrawFunctionId>, pub indices: HashMap<TypeId, DrawFunctionId>,
} }
impl<P: PhaseItem> DrawFunctionsInternal<P> { impl<P: PhaseItem> DrawFunctionsInternal<P> {
/// Adds the [`Draw`] function and associates it to its own type.
pub fn add<T: Draw<P>>(&mut self, draw_function: T) -> DrawFunctionId { pub fn add<T: Draw<P>>(&mut self, draw_function: T) -> DrawFunctionId {
self.add_with::<T, T>(draw_function) self.add_with::<T, T>(draw_function)
} }
/// Adds the [`Draw`] function and associates it to the type `T`
pub fn add_with<T: 'static, D: Draw<P>>(&mut self, draw_function: D) -> DrawFunctionId { pub fn add_with<T: 'static, D: Draw<P>>(&mut self, draw_function: D) -> DrawFunctionId {
self.draw_functions.push(Box::new(draw_function)); self.draw_functions.push(Box::new(draw_function));
let id = DrawFunctionId(self.draw_functions.len() - 1); let id = DrawFunctionId(self.draw_functions.len() - 1);
@ -52,15 +70,19 @@ impl<P: PhaseItem> DrawFunctionsInternal<P> {
id id
} }
/// Retrieves the [`Draw`] function corresponding to the `id` mutably.
pub fn get_mut(&mut self, id: DrawFunctionId) -> Option<&mut dyn Draw<P>> { pub fn get_mut(&mut self, id: DrawFunctionId) -> Option<&mut dyn Draw<P>> {
self.draw_functions.get_mut(id.0).map(|f| &mut **f) self.draw_functions.get_mut(id.0).map(|f| &mut **f)
} }
/// Retrieves the id of the [`Draw`] function corresponding to their associated type `T`.
pub fn get_id<T: 'static>(&self) -> Option<DrawFunctionId> { pub fn get_id<T: 'static>(&self) -> Option<DrawFunctionId> {
self.indices.get(&TypeId::of::<T>()).copied() self.indices.get(&TypeId::of::<T>()).copied()
} }
} }
/// Stores all draw functions for the [`PhaseItem`] type hidden behind a reader-writer lock.
/// To access them the [`DrawFunctions::read`] and [`DrawFunctions::write`] methods are used.
pub struct DrawFunctions<P: PhaseItem> { pub struct DrawFunctions<P: PhaseItem> {
internal: RwLock<DrawFunctionsInternal<P>>, internal: RwLock<DrawFunctionsInternal<P>>,
} }
@ -77,16 +99,42 @@ impl<P: PhaseItem> Default for DrawFunctions<P> {
} }
impl<P: PhaseItem> DrawFunctions<P> { impl<P: PhaseItem> DrawFunctions<P> {
/// Accesses the draw functions in read mode.
pub fn read(&self) -> RwLockReadGuard<'_, DrawFunctionsInternal<P>> { pub fn read(&self) -> RwLockReadGuard<'_, DrawFunctionsInternal<P>> {
self.internal.read() self.internal.read()
} }
/// Accesses the draw functions in write mode.
pub fn write(&self) -> RwLockWriteGuard<'_, DrawFunctionsInternal<P>> { pub fn write(&self) -> RwLockWriteGuard<'_, DrawFunctionsInternal<P>> {
self.internal.write() self.internal.write()
} }
} }
/// RenderCommand is a trait that runs an ECS query and produces one or more
/// [`TrackedRenderPass`] calls. Types implementing this trait can be composed (as tuples).
///
/// They can be registered as a [`Draw`] function via the
/// [`AddRenderCommand::add_render_command`] method.
///
/// # Example
/// The `DrawPbr` draw function is created from the following render command
/// tuple. Const generics are used to set specific bind group locations:
///
/// ```ignore
/// pub type DrawPbr = (
/// SetItemPipeline,
/// SetMeshViewBindGroup<0>,
/// SetStandardMaterialBindGroup<1>,
/// SetTransformBindGroup<2>,
/// DrawMesh,
/// );
/// ```
pub trait RenderCommand<P: PhaseItem> { pub trait RenderCommand<P: PhaseItem> {
/// Specifies all ECS data required by [`RenderCommand::render`].
/// All parameters have to be read only.
type Param: SystemParam; type Param: SystemParam;
/// Renders the [`PhaseItem`] by issuing draw calls via the [`TrackedRenderPass`].
fn render<'w>( fn render<'w>(
view: Entity, view: Entity,
item: &P, item: &P,
@ -165,6 +213,8 @@ macro_rules! render_command_tuple_impl {
all_tuples!(render_command_tuple_impl, 0, 15, C); all_tuples!(render_command_tuple_impl, 0, 15, C);
/// Wraps a [`RenderCommand`] into a state so that it can be used as a [`Draw`] function.
/// Therefore the [`RenderCommand::Param`] is queried from the ECS and passed to the command.
pub struct RenderCommandState<P: PhaseItem, C: RenderCommand<P>> { pub struct RenderCommandState<P: PhaseItem, C: RenderCommand<P>> {
state: SystemState<C::Param>, state: SystemState<C::Param>,
} }
@ -181,6 +231,7 @@ impl<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static> Draw<P> for Rend
where where
<C::Param as SystemParam>::Fetch: ReadOnlySystemParamFetch, <C::Param as SystemParam>::Fetch: ReadOnlySystemParamFetch,
{ {
/// Prepares the ECS parameters for the wrapped [`RenderCommand`] and then renders it.
fn draw<'w>( fn draw<'w>(
&mut self, &mut self,
world: &'w World, world: &'w World,
@ -193,7 +244,10 @@ where
} }
} }
/// Registers a [`RenderCommand`] as a [`Draw`] function.
/// They are stored inside the [`DrawFunctions`] resource of the app.
pub trait AddRenderCommand { pub trait AddRenderCommand {
/// Adds the [`RenderCommand`] for the specified [`RenderPhase`](super::RenderPhase) to the app.
fn add_render_command<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static>( fn add_render_command<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static>(
&mut self, &mut self,
) -> &mut Self ) -> &mut Self

View file

@ -5,7 +5,7 @@ use bevy_utils::tracing::debug;
use std::ops::Range; use std::ops::Range;
use wgpu::{IndexFormat, RenderPass}; use wgpu::{IndexFormat, RenderPass};
/// Tracks the current pipeline state to ensure draw calls are valid. /// Tracks the current [`TrackedRenderPipeline`] state to ensure draw calls are valid.
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct DrawState { pub struct DrawState {
pipeline: Option<RenderPipelineId>, pipeline: Option<RenderPipelineId>,
@ -83,12 +83,16 @@ impl DrawState {
} }
} }
/// A [`RenderPass`], which tracks the current pipeline state to ensure all draw calls are valid.
/// It is used to set the current [`RenderPipeline`], [`BindGroups`](BindGroup) and buffers.
/// After all requirements are specified, draw calls can be issued.
pub struct TrackedRenderPass<'a> { pub struct TrackedRenderPass<'a> {
pass: RenderPass<'a>, pass: RenderPass<'a>,
state: DrawState, state: DrawState,
} }
impl<'a> TrackedRenderPass<'a> { impl<'a> TrackedRenderPass<'a> {
/// Tracks the supplied render pass.
pub fn new(pass: RenderPass<'a>) -> Self { pub fn new(pass: RenderPass<'a>) -> Self {
Self { Self {
state: DrawState::default(), state: DrawState::default(),
@ -96,6 +100,9 @@ impl<'a> TrackedRenderPass<'a> {
} }
} }
/// Sets the active [`RenderPipeline`].
///
/// Subsequent draw calls will exhibit the behavior defined by the `pipeline`.
pub fn set_render_pipeline(&mut self, pipeline: &'a RenderPipeline) { pub fn set_render_pipeline(&mut self, pipeline: &'a RenderPipeline) {
debug!("set pipeline: {:?}", pipeline); debug!("set pipeline: {:?}", pipeline);
if self.state.is_pipeline_set(pipeline.id()) { if self.state.is_pipeline_set(pipeline.id()) {
@ -105,6 +112,9 @@ impl<'a> TrackedRenderPass<'a> {
self.state.set_pipeline(pipeline.id()); self.state.set_pipeline(pipeline.id());
} }
/// Sets the active [`BindGroup`] for a given bind group index. The bind group layout in the
/// active pipeline when any `draw()` function is called must match the layout
/// of this `bind group`.
pub fn set_bind_group( pub fn set_bind_group(
&mut self, &mut self,
index: usize, index: usize,
@ -132,6 +142,13 @@ impl<'a> TrackedRenderPass<'a> {
.set_bind_group(index as usize, bind_group.id(), dynamic_uniform_indices); .set_bind_group(index as usize, bind_group.id(), dynamic_uniform_indices);
} }
/// Assign a vertex buffer to a slot.
///
/// Subsequent calls to [`TrackedRenderPass::draw`] and [`TrackedRenderPass::draw_indexed`]
/// will use the `buffer` as one of the source vertex buffers.
///
/// The `slot` refers to the index of the matching descriptor in
/// [`VertexState::buffers`](crate::render_resource::VertexState::buffers).
pub fn set_vertex_buffer(&mut self, index: usize, buffer_slice: BufferSlice<'a>) { pub fn set_vertex_buffer(&mut self, index: usize, buffer_slice: BufferSlice<'a>) {
let offset = buffer_slice.offset(); let offset = buffer_slice.offset();
if self if self
@ -158,6 +175,10 @@ impl<'a> TrackedRenderPass<'a> {
.set_vertex_buffer(index, buffer_slice.id(), offset); .set_vertex_buffer(index, buffer_slice.id(), offset);
} }
/// Sets the active index buffer.
///
/// Subsequent calls to [`TrackedRenderPass::draw_indexed`] will use the `buffer` as
/// the source index buffer.
pub fn set_index_buffer( pub fn set_index_buffer(
&mut self, &mut self,
buffer_slice: BufferSlice<'a>, buffer_slice: BufferSlice<'a>,
@ -182,11 +203,18 @@ impl<'a> TrackedRenderPass<'a> {
.set_index_buffer(buffer_slice.id(), offset, index_format); .set_index_buffer(buffer_slice.id(), offset, index_format);
} }
/// Draws primitives from the active vertex buffer(s).
///
/// The active vertex buffers can be set with [`TrackedRenderPass::set_vertex_buffer`].
pub fn draw(&mut self, vertices: Range<u32>, instances: Range<u32>) { pub fn draw(&mut self, vertices: Range<u32>, instances: Range<u32>) {
debug!("draw: {:?} {:?}", vertices, instances); debug!("draw: {:?} {:?}", vertices, instances);
self.pass.draw(vertices, instances); self.pass.draw(vertices, instances);
} }
/// Draws indexed primitives using the active index buffer and the active vertex buffer(s).
///
/// The active index buffer can be set with [`TrackedRenderPass::set_index_buffer`], while the
/// active vertex buffers can be set with [`TrackedRenderPass::set_vertex_buffer`].
pub fn draw_indexed(&mut self, indices: Range<u32>, base_vertex: i32, instances: Range<u32>) { pub fn draw_indexed(&mut self, indices: Range<u32>, base_vertex: i32, instances: Range<u32>) {
debug!( debug!(
"draw indexed: {:?} {} {:?}", "draw indexed: {:?} {} {:?}",

View file

@ -6,6 +6,7 @@ pub use draw_state::*;
use bevy_ecs::prelude::Query; use bevy_ecs::prelude::Query;
/// A resource to collect and sort draw requests for specific [`PhaseItems`](PhaseItem).
pub struct RenderPhase<I: PhaseItem> { pub struct RenderPhase<I: PhaseItem> {
pub items: Vec<I>, pub items: Vec<I>,
} }
@ -17,16 +18,19 @@ impl<I: PhaseItem> Default for RenderPhase<I> {
} }
impl<I: PhaseItem> RenderPhase<I> { impl<I: PhaseItem> RenderPhase<I> {
/// Adds a [`PhaseItem`] to this render phase.
#[inline] #[inline]
pub fn add(&mut self, item: I) { pub fn add(&mut self, item: I) {
self.items.push(item); self.items.push(item);
} }
/// Sorts all of its [`PhaseItems`](PhaseItem).
pub fn sort(&mut self) { pub fn sort(&mut self) {
self.items.sort_by_key(|d| d.sort_key()); self.items.sort_by_key(|d| d.sort_key());
} }
} }
/// This system sorts all [`RenderPhases`](RenderPhase) for the [`PhaseItem`] type.
pub fn sort_phase_system<I: PhaseItem>(mut render_phases: Query<&mut RenderPhase<I>>) { pub fn sort_phase_system<I: PhaseItem>(mut render_phases: Query<&mut RenderPhase<I>>) {
for mut phase in render_phases.iter_mut() { for mut phase in render_phases.iter_mut() {
phase.sort(); phase.sort();

View file

@ -1,9 +1,16 @@
use bevy_reflect::Uuid; use bevy_reflect::Uuid;
use std::{ops::Deref, sync::Arc}; use std::{ops::Deref, sync::Arc};
/// A [`BindGroup`] identifier.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
pub struct BindGroupId(Uuid); pub struct BindGroupId(Uuid);
/// Bind groups are responsible for binding render resources (e.g. buffers, textures, samplers)
/// to a [`TrackedRenderPass`](crate::render_phase::TrackedRenderPass).
/// This makes them accessible in the pipeline (shaders) as uniforms.
///
/// May be converted from and dereferences to a wgpu [`BindGroup`](wgpu::BindGroup).
/// Can be created via [`RenderDevice::create_bind_group`](crate::renderer::RenderDevice::create_bind_group).
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct BindGroup { pub struct BindGroup {
id: BindGroupId, id: BindGroupId,
@ -11,6 +18,7 @@ pub struct BindGroup {
} }
impl BindGroup { impl BindGroup {
/// Returns the [`BindGroupId`].
#[inline] #[inline]
pub fn id(&self) -> BindGroupId { pub fn id(&self) -> BindGroupId {
self.id self.id

View file

@ -7,9 +7,14 @@ use wgpu::{
VertexAttribute, VertexStepMode, VertexAttribute, VertexStepMode,
}; };
/// A [`RenderPipeline`] identifier.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
pub struct RenderPipelineId(Uuid); pub struct RenderPipelineId(Uuid);
/// A RenderPipeline represents a graphics pipeline and its stages (shaders), bindings and vertex buffers.
///
/// May be converted from and dereferences to a wgpu [`RenderPipeline`](wgpu::RenderPipeline).
/// Can be created via [`RenderDevice::create_render_pipeline`](crate::renderer::RenderDevice::create_render_pipeline).
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct RenderPipeline { pub struct RenderPipeline {
id: RenderPipelineId, id: RenderPipelineId,
@ -41,9 +46,14 @@ impl Deref for RenderPipeline {
} }
} }
/// A [`ComputePipeline`] identifier.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
pub struct ComputePipelineId(Uuid); pub struct ComputePipelineId(Uuid);
/// A ComputePipeline represents a compute pipeline and its single shader stage.
///
/// May be converted from and dereferences to a wgpu [`ComputePipeline`](wgpu::ComputePipeline).
/// Can be created via [`RenderDevice::create_compute_pipeline`](crate::renderer::RenderDevice::create_compute_pipeline).
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct ComputePipeline { pub struct ComputePipeline {
id: ComputePipelineId, id: ComputePipelineId,
@ -51,6 +61,7 @@ pub struct ComputePipeline {
} }
impl ComputePipeline { impl ComputePipeline {
/// Returns the [`ComputePipelineId`].
#[inline] #[inline]
pub fn id(&self) -> ComputePipelineId { pub fn id(&self) -> ComputePipelineId {
self.id self.id

View file

@ -1,9 +1,14 @@
use bevy_utils::Uuid; use bevy_utils::Uuid;
use std::{ops::Deref, sync::Arc}; use std::{ops::Deref, sync::Arc};
/// A [`Texture`] identifier.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
pub struct TextureId(Uuid); pub struct TextureId(Uuid);
/// A GPU-accessible texture.
///
/// May be converted from and dereferences to a wgpu [`Texture`](wgpu::Texture).
/// Can be created via [`RenderDevice::create_texture`](crate::renderer::RenderDevice::create_texture).
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Texture { pub struct Texture {
id: TextureId, id: TextureId,
@ -11,11 +16,13 @@ pub struct Texture {
} }
impl Texture { impl Texture {
/// Returns the [`TextureId`].
#[inline] #[inline]
pub fn id(&self) -> TextureId { pub fn id(&self) -> TextureId {
self.id self.id
} }
/// Creates a view of this texture.
pub fn create_view(&self, desc: &wgpu::TextureViewDescriptor) -> TextureView { pub fn create_view(&self, desc: &wgpu::TextureViewDescriptor) -> TextureView {
TextureView::from(self.value.create_view(desc)) TextureView::from(self.value.create_view(desc))
} }
@ -39,12 +46,19 @@ impl Deref for Texture {
} }
} }
/// A [`TextureView`] identifier.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
pub struct TextureViewId(Uuid); pub struct TextureViewId(Uuid);
/// This type combines wgpu's [`TextureView`](wgpu::TextureView) and
/// [SurfaceTexture`](wgpu::SurfaceTexture) into the same interface.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum TextureViewValue { pub enum TextureViewValue {
/// The value is an actual wgpu [`TextureView`](wgpu::TextureView).
TextureView(Arc<wgpu::TextureView>), TextureView(Arc<wgpu::TextureView>),
/// The value is a wgpu [`SurfaceTexture`](wgpu::SurfaceTexture), but dereferences to
/// a [`TextureView`](wgpu::TextureView).
SurfaceTexture { SurfaceTexture {
// NOTE: The order of these fields is important because the view must be dropped before the // NOTE: The order of these fields is important because the view must be dropped before the
// frame is dropped // frame is dropped
@ -53,6 +67,10 @@ pub enum TextureViewValue {
}, },
} }
/// Describes a [`Texture`] with its associated metadata required by a pipeline or [`BindGroup`](super::BindGroup).
///
/// May be converted from a [`TextureView`](wgpu::TextureView) or [`SurfaceTexture`](wgpu::SurfaceTexture)
/// or dereferences to a wgpu [`TextureView`](wgpu::TextureView).
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct TextureView { pub struct TextureView {
id: TextureViewId, id: TextureViewId,
@ -60,11 +78,13 @@ pub struct TextureView {
} }
impl TextureView { impl TextureView {
/// Returns the [`TextureViewId`].
#[inline] #[inline]
pub fn id(&self) -> TextureViewId { pub fn id(&self) -> TextureViewId {
self.id self.id
} }
/// Returns the [`SurfaceTexture`](wgpu::SurfaceTexture) of the texture view if it is of that type.
#[inline] #[inline]
pub fn take_surface_texture(self) -> Option<wgpu::SurfaceTexture> { pub fn take_surface_texture(self) -> Option<wgpu::SurfaceTexture> {
match self.value { match self.value {
@ -107,9 +127,15 @@ impl Deref for TextureView {
} }
} }
/// A [`Sampler`] identifier.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)] #[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
pub struct SamplerId(Uuid); pub struct SamplerId(Uuid);
/// A Sampler defines how a pipeline will sample from a [`TextureView`].
/// They define image filters (including anisotropy) and address (wrapping) modes, among other things.
///
/// May be converted from and dereferences to a wgpu [`Sampler`](wgpu::Sampler).
/// Can be created via [`RenderDevice::create_sampler`](crate::renderer::RenderDevice::create_sampler).
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Sampler { pub struct Sampler {
id: SamplerId, id: SamplerId,
@ -117,6 +143,7 @@ pub struct Sampler {
} }
impl Sampler { impl Sampler {
/// Returns the [`SamplerId`].
#[inline] #[inline]
pub fn id(&self) -> SamplerId { pub fn id(&self) -> SamplerId {
self.id self.id

View file

@ -13,6 +13,7 @@ use bevy_ecs::prelude::*;
use std::sync::Arc; use std::sync::Arc;
use wgpu::{CommandEncoder, DeviceDescriptor, Instance, Queue, RequestAdapterOptions}; use wgpu::{CommandEncoder, DeviceDescriptor, Instance, Queue, RequestAdapterOptions};
/// Updates the [`RenderGraph`] with all of its nodes and then runs it to render the entire frame.
pub fn render_system(world: &mut World) { pub fn render_system(world: &mut World) {
world.resource_scope(|world, mut graph: Mut<RenderGraph>| { world.resource_scope(|world, mut graph: Mut<RenderGraph>| {
graph.update(world); graph.update(world);
@ -52,9 +53,15 @@ pub fn render_system(world: &mut World) {
} }
} }
/// This queue is used to enqueue tasks for the GPU to execute asynchronously.
pub type RenderQueue = Arc<Queue>; pub type RenderQueue = Arc<Queue>;
/// The GPU instance is used to initialize the [`RenderQueue`] and [`RenderDevice`],
/// aswell as to create [`WindowSurfaces`](crate::view::window::WindowSurfaces).
pub type RenderInstance = Instance; pub type RenderInstance = Instance;
/// Initializes the renderer by retrieving and preparing the GPU instance, device and queue
/// for the specified backend.
pub async fn initialize_renderer( pub async fn initialize_renderer(
instance: &Instance, instance: &Instance,
request_adapter_options: &RequestAdapterOptions<'_>, request_adapter_options: &RequestAdapterOptions<'_>,
@ -87,6 +94,10 @@ pub async fn initialize_renderer(
(RenderDevice::from(device), queue) (RenderDevice::from(device), queue)
} }
/// The context with all information required to interact with the GPU.
///
/// The [`RenderDevice`] is used to create render resources and the
/// the [`CommandEncoder`] is used to record a series of GPU operations.
pub struct RenderContext { pub struct RenderContext {
pub render_device: RenderDevice, pub render_device: RenderDevice,
pub command_encoder: CommandEncoder, pub command_encoder: CommandEncoder,

View file

@ -1,12 +1,12 @@
use futures_lite::future;
use wgpu::util::DeviceExt;
use crate::render_resource::{ use crate::render_resource::{
BindGroup, BindGroupLayout, Buffer, ComputePipeline, RawRenderPipelineDescriptor, BindGroup, BindGroupLayout, Buffer, ComputePipeline, RawRenderPipelineDescriptor,
RenderPipeline, Sampler, Texture, RenderPipeline, Sampler, Texture,
}; };
use futures_lite::future;
use std::sync::Arc; use std::sync::Arc;
use wgpu::util::DeviceExt;
/// This GPU device is responsible for the creation of most rendering and compute resources.
#[derive(Clone)] #[derive(Clone)]
pub struct RenderDevice { pub struct RenderDevice {
device: Arc<wgpu::Device>, device: Arc<wgpu::Device>,
@ -19,7 +19,7 @@ impl From<Arc<wgpu::Device>> for RenderDevice {
} }
impl RenderDevice { impl RenderDevice {
/// List all features that may be used with this device. /// List all [`Features`](wgpu::Features) that may be used with this device.
/// ///
/// Functions may panic if you use unsupported features. /// Functions may panic if you use unsupported features.
#[inline] #[inline]
@ -27,7 +27,7 @@ impl RenderDevice {
self.device.features() self.device.features()
} }
/// List all limits that were requested of this device. /// List all [`Limits`](wgpu::Limits) that were requested of this device.
/// ///
/// If any of these limits are exceeded, functions may panic. /// If any of these limits are exceeded, functions may panic.
#[inline] #[inline]
@ -35,7 +35,7 @@ impl RenderDevice {
self.device.limits() self.device.limits()
} }
/// Creates a shader module from either SPIR-V or WGSL source code. /// Creates a [ShaderModule](wgpu::ShaderModule) from either SPIR-V or WGSL source code.
#[inline] #[inline]
pub fn create_shader_module(&self, desc: &wgpu::ShaderModuleDescriptor) -> wgpu::ShaderModule { pub fn create_shader_module(&self, desc: &wgpu::ShaderModuleDescriptor) -> wgpu::ShaderModule {
self.device.create_shader_module(desc) self.device.create_shader_module(desc)
@ -49,7 +49,7 @@ impl RenderDevice {
self.device.poll(maintain) self.device.poll(maintain)
} }
/// Creates an empty [`CommandEncoder`]. /// Creates an empty [`CommandEncoder`](wgpu::CommandEncoder).
#[inline] #[inline]
pub fn create_command_encoder( pub fn create_command_encoder(
&self, &self,
@ -58,7 +58,7 @@ impl RenderDevice {
self.device.create_command_encoder(desc) self.device.create_command_encoder(desc)
} }
/// Creates an empty [`RenderBundleEncoder`]. /// Creates an empty [`RenderBundleEncoder`](wgpu::RenderBundleEncoder).
#[inline] #[inline]
pub fn create_render_bundle_encoder( pub fn create_render_bundle_encoder(
&self, &self,
@ -67,14 +67,14 @@ impl RenderDevice {
self.device.create_render_bundle_encoder(desc) self.device.create_render_bundle_encoder(desc)
} }
/// Creates a new [`BindGroup`]. /// Creates a new [`BindGroup`](wgpu::BindGroup).
#[inline] #[inline]
pub fn create_bind_group(&self, desc: &wgpu::BindGroupDescriptor) -> BindGroup { pub fn create_bind_group(&self, desc: &wgpu::BindGroupDescriptor) -> BindGroup {
let wgpu_bind_group = self.device.create_bind_group(desc); let wgpu_bind_group = self.device.create_bind_group(desc);
BindGroup::from(wgpu_bind_group) BindGroup::from(wgpu_bind_group)
} }
/// Creates a [`BindGroupLayout`]. /// Creates a [`BindGroupLayout`](wgpu::BindGroupLayout).
#[inline] #[inline]
pub fn create_bind_group_layout( pub fn create_bind_group_layout(
&self, &self,
@ -83,7 +83,7 @@ impl RenderDevice {
BindGroupLayout::from(self.device.create_bind_group_layout(desc)) BindGroupLayout::from(self.device.create_bind_group_layout(desc))
} }
/// Creates a [`PipelineLayout`]. /// Creates a [`PipelineLayout`](wgpu::PipelineLayout).
#[inline] #[inline]
pub fn create_pipeline_layout( pub fn create_pipeline_layout(
&self, &self,
@ -115,7 +115,7 @@ impl RenderDevice {
Buffer::from(wgpu_buffer) Buffer::from(wgpu_buffer)
} }
/// Creates a [`Buffer`]. /// Creates a [`Buffer`] and initializes it with the specified data.
pub fn create_buffer_with_data(&self, desc: &wgpu::util::BufferInitDescriptor) -> Buffer { pub fn create_buffer_with_data(&self, desc: &wgpu::util::BufferInitDescriptor) -> Buffer {
let wgpu_buffer = self.device.create_buffer_init(desc); let wgpu_buffer = self.device.create_buffer_init(desc);
Buffer::from(wgpu_buffer) Buffer::from(wgpu_buffer)
@ -137,16 +137,17 @@ impl RenderDevice {
Sampler::from(wgpu_sampler) Sampler::from(wgpu_sampler)
} }
/// Create a new [`SwapChain`] which targets `surface`. /// Create a new [`SwapChain`](wgpu::SwapChain) which targets `surface`.
/// ///
/// # Panics /// # Panics
/// ///
/// - A old [`SwapChainFrame`] is still alive referencing an old swapchain. /// - A old [`SwapChainFrame`](wgpu::SwapChain) is still alive referencing an old swap chain.
/// - Texture format requested is unsupported on the swap chain. /// - Texture format requested is unsupported on the swap chain.
pub fn configure_surface(&self, surface: &wgpu::Surface, config: &wgpu::SurfaceConfiguration) { pub fn configure_surface(&self, surface: &wgpu::Surface, config: &wgpu::SurfaceConfiguration) {
surface.configure(&self.device, config) surface.configure(&self.device, config)
} }
/// Returns the wgpu [`Device`](wgpu::Device).
pub fn wgpu_device(&self) -> &wgpu::Device { pub fn wgpu_device(&self) -> &wgpu::Device {
&self.device &self.device
} }

View file

@ -50,6 +50,11 @@ impl Default for Image {
} }
impl Image { impl Image {
/// Creates a new image from raw binary data and the corresponding metadata.
///
/// # Panics
/// Panics if the length of the `data`, volume of the `size` and the size of the `format`
/// do not match.
pub fn new( pub fn new(
size: Extent3d, size: Extent3d,
dimension: TextureDimension, dimension: TextureDimension,
@ -71,6 +76,12 @@ impl Image {
image image
} }
/// Creates a new image from raw binary data and the corresponding metadata, by filling
/// the image data with the `pixel` data repeated multiple times.
///
/// # Panics
/// Panics if the size of the `format` is not a multiple of the length of the `pixel` data.
/// do not match.
pub fn new_fill( pub fn new_fill(
size: Extent3d, size: Extent3d,
dimension: TextureDimension, dimension: TextureDimension,
@ -98,10 +109,13 @@ impl Image {
value value
} }
/// Returns the aspect ratio (height/width) of a 2D image.
pub fn aspect_2d(&self) -> f32 { pub fn aspect_2d(&self) -> f32 {
self.texture_descriptor.size.height as f32 / self.texture_descriptor.size.width as f32 self.texture_descriptor.size.height as f32 / self.texture_descriptor.size.width as f32
} }
/// Resizes the image to the new size, by removing information or appending 0 to the `data`.
/// Does not properly resize the contents of the image, but only its internal `data` buffer.
pub fn resize(&mut self, size: Extent3d) { pub fn resize(&mut self, size: Extent3d) {
self.texture_descriptor.size = size; self.texture_descriptor.size = size;
self.data.resize( self.data.resize(
@ -112,6 +126,9 @@ impl Image {
/// Changes the `size`, asserting that the total number of data elements (pixels) remains the /// Changes the `size`, asserting that the total number of data elements (pixels) remains the
/// same. /// same.
///
/// # Panics
/// Panics if the `new_size` does not have the same volume as to old one.
pub fn reinterpret_size(&mut self, new_size: Extent3d) { pub fn reinterpret_size(&mut self, new_size: Extent3d) {
assert!( assert!(
new_size.volume() == self.texture_descriptor.size.volume(), new_size.volume() == self.texture_descriptor.size.volume(),
@ -123,9 +140,13 @@ impl Image {
self.texture_descriptor.size = new_size; self.texture_descriptor.size = new_size;
} }
/// Takes a 2D texture containing vertically stacked images of the same size, and reinterprets /// Takes a 2D image containing vertically stacked images of the same size, and reinterprets
/// it as a 2D array texture, where each of the stacked images becomes one layer of the /// it as a 2D array texture, where each of the stacked images becomes one layer of the
/// array. This is primarily for use with the `texture2DArray` shader uniform type. /// array. This is primarily for use with the `texture2DArray` shader uniform type.
///
/// # Panics
/// Panics if the texture is not 2D, has more than one layers or is not evenly dividable into
/// the `layers`.
pub fn reinterpret_stacked_2d_as_array(&mut self, layers: u32) { pub fn reinterpret_stacked_2d_as_array(&mut self, layers: u32) {
// Must be a stacked image, and the height must be divisible by layers. // Must be a stacked image, and the height must be divisible by layers.
assert!(self.texture_descriptor.dimension == TextureDimension::D2); assert!(self.texture_descriptor.dimension == TextureDimension::D2);
@ -203,31 +224,39 @@ pub enum TextureError {
ImageError(#[from] image::ImageError), ImageError(#[from] image::ImageError),
} }
/// Type of a raw image buffer /// The type of a raw image buffer.
pub enum ImageType<'a> { pub enum ImageType<'a> {
/// Mime type of an image, for example `"image/png"` /// The mime type of an image, for example `"image/png"`.
MimeType(&'a str), MimeType(&'a str),
/// Extension of an image file, for example `"png"` /// The extension of an image file, for example `"png"`.
Extension(&'a str), Extension(&'a str),
} }
/// Used to calculate the volume of an item.
pub trait Volume { pub trait Volume {
fn volume(&self) -> usize; fn volume(&self) -> usize;
} }
impl Volume for Extent3d { impl Volume for Extent3d {
/// Calculates the volume of the [`Extent3D`].
fn volume(&self) -> usize { fn volume(&self) -> usize {
(self.width * self.height * self.depth_or_array_layers) as usize (self.width * self.height * self.depth_or_array_layers) as usize
} }
} }
/// Information about the pixel size in bytes and the number of different components.
pub struct PixelInfo { pub struct PixelInfo {
/// The size of a component of a pixel in bytes.
pub type_size: usize, pub type_size: usize,
/// The amount of different components (color channels).
pub num_components: usize, pub num_components: usize,
} }
/// Extends the wgpu [`TextureFormat`] with information about the pixel.
pub trait TextureFormatPixelInfo { pub trait TextureFormatPixelInfo {
/// Returns the pixel information of the format.
fn pixel_info(&self) -> PixelInfo; fn pixel_info(&self) -> PixelInfo;
/// Returns the size of a pixel of the format.
fn pixel_size(&self) -> usize { fn pixel_size(&self) -> usize {
let info = self.pixel_info(); let info = self.pixel_info();
info.type_size * info.num_components info.type_size * info.num_components
@ -340,6 +369,8 @@ impl TextureFormatPixelInfo for TextureFormat {
} }
} }
/// The GPU-representation of an [`Image`].
/// Consists of the [`Texture`], its [`TextureView`] and the corresponding [`Sampler`].
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct GpuImage { pub struct GpuImage {
pub texture: Texture, pub texture: Texture,
@ -352,10 +383,12 @@ impl RenderAsset for Image {
type PreparedAsset = GpuImage; type PreparedAsset = GpuImage;
type Param = (SRes<RenderDevice>, SRes<RenderQueue>); type Param = (SRes<RenderDevice>, SRes<RenderQueue>);
/// Clones the Image.
fn extract_asset(&self) -> Self::ExtractedAsset { fn extract_asset(&self) -> Self::ExtractedAsset {
self.clone() self.clone()
} }
/// Converts the extracted image into a [`GpuImage`].
fn prepare_asset( fn prepare_asset(
image: Self::ExtractedAsset, image: Self::ExtractedAsset,
(render_device, render_queue): &mut SystemParamItem<Self::Param>, (render_device, render_queue): &mut SystemParamItem<Self::Param>,

View file

@ -2,7 +2,7 @@ use crate::texture::{Image, TextureFormatPixelInfo};
use wgpu::{Extent3d, TextureDimension, TextureFormat}; use wgpu::{Extent3d, TextureDimension, TextureFormat};
// TODO: fix name? // TODO: fix name?
/// Helper method to convert a `DynamicImage` to a `Texture` /// Converts a [`DynamicImage`] to an [`Image`].
pub(crate) fn image_to_texture(dyn_img: image::DynamicImage) -> Image { pub(crate) fn image_to_texture(dyn_img: image::DynamicImage) -> Image {
use bevy_core::cast_slice; use bevy_core::cast_slice;
let width; let width;
@ -125,8 +125,8 @@ pub(crate) fn image_to_texture(dyn_img: image::DynamicImage) -> Image {
) )
} }
/// Helper method to convert a `Texture` to a `DynamicImage`. Not all `Texture` formats are /// Converts an [`Image`] to a [`DynamicImage`]. Not all [`TextureFormat`] are
/// covered, it will return `None` if the format is not supported /// covered, therefore it will return `None` if the format is unsupported.
pub(crate) fn texture_to_image(texture: &Image) -> Option<image::DynamicImage> { pub(crate) fn texture_to_image(texture: &Image) -> Option<image::DynamicImage> {
match texture.texture_descriptor.format { match texture.texture_descriptor.format {
TextureFormat::R8Unorm => image::ImageBuffer::from_raw( TextureFormat::R8Unorm => image::ImageBuffer::from_raw(

View file

@ -38,7 +38,7 @@ impl AssetLoader for ImageTextureLoader {
} }
} }
/// An error that occurs when loading a texture from a file /// An error that occurs when loading a texture from a file.
#[derive(Error, Debug)] #[derive(Error, Debug)]
pub struct FileTextureError { pub struct FileTextureError {
error: TextureError, error: TextureError,

View file

@ -18,6 +18,7 @@ use bevy_app::{App, Plugin};
use bevy_asset::AddAsset; use bevy_asset::AddAsset;
// TODO: replace Texture names with Image names? // TODO: replace Texture names with Image names?
/// Adds the [`Image`] as an asset and makes sure that they are extracted and prepared for the GPU.
pub struct ImagePlugin; pub struct ImagePlugin;
impl Plugin for ImagePlugin { impl Plugin for ImagePlugin {

View file

@ -6,6 +6,8 @@ use bevy_ecs::prelude::ResMut;
use bevy_utils::HashMap; use bevy_utils::HashMap;
use wgpu::{TextureDescriptor, TextureViewDescriptor}; use wgpu::{TextureDescriptor, TextureViewDescriptor};
/// The internal representation of a [`CachedTexture`] used to track whether it was recently used
/// and is currently taken.
struct CachedTextureMeta { struct CachedTextureMeta {
texture: Texture, texture: Texture,
default_view: TextureView, default_view: TextureView,
@ -13,17 +15,24 @@ struct CachedTextureMeta {
frames_since_last_use: usize, frames_since_last_use: usize,
} }
/// A cached GPU [`Texture`] with corresponding [`TextureView`].
/// This is useful for textures that are created repeatedly (each frame) in the rendering process
/// to reduce the amount of GPU memory allocations.
pub struct CachedTexture { pub struct CachedTexture {
pub texture: Texture, pub texture: Texture,
pub default_view: TextureView, pub default_view: TextureView,
} }
/// This resource caches textures that are created repeatedly in the rendering process and
/// are only required for one frame.
#[derive(Default)] #[derive(Default)]
pub struct TextureCache { pub struct TextureCache {
textures: HashMap<wgpu::TextureDescriptor<'static>, Vec<CachedTextureMeta>>, textures: HashMap<wgpu::TextureDescriptor<'static>, Vec<CachedTextureMeta>>,
} }
impl TextureCache { impl TextureCache {
/// Retrieves a texture that matches the `descriptor`. If no matching one is found a new
/// [`CachedTexture`] is created.
pub fn get( pub fn get(
&mut self, &mut self,
render_device: &RenderDevice, render_device: &RenderDevice,
@ -72,6 +81,7 @@ impl TextureCache {
} }
} }
/// Updates the cache and only retains recently used textures.
pub fn update(&mut self) { pub fn update(&mut self) {
for textures in self.textures.values_mut() { for textures in self.textures.values_mut() {
for texture in textures.iter_mut() { for texture in textures.iter_mut() {
@ -84,6 +94,7 @@ impl TextureCache {
} }
} }
/// Updates the [`TextureCache`] to only retains recently used textures.
pub fn update_texture_cache_system(mut texture_cache: ResMut<TextureCache>) { pub fn update_texture_cache_system(mut texture_cache: ResMut<TextureCache>) {
texture_cache.update(); texture_cache.update();
} }

View file

@ -11,7 +11,7 @@ use bevy_window::{RawWindowHandleWrapper, WindowId, Windows};
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use wgpu::TextureFormat; use wgpu::TextureFormat;
// Token to ensure a system runs on the main thread. /// Token to ensure a system runs on the main thread.
#[derive(Default)] #[derive(Default)]
pub struct NonSendMarker; pub struct NonSendMarker;