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 {
/// The `SystemParam` type passed to the system when it runs.
type Param: SystemParam;
/// Runs the system.
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> {
ParamSystem {
run: Self::run,

View file

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

View file

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

View file

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

View file

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

View file

@ -145,11 +145,11 @@ impl Default for DirectionalLightShadowMap {
}
}
/// Ambient light.
/// An ambient light, which lights the entire scene equally.
#[derive(Debug)]
pub struct AmbientLight {
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,
}
@ -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;
/// 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;
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]

View file

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

View file

@ -29,28 +29,32 @@ use bevy_ecs::prelude::*;
use std::ops::{Deref, DerefMut};
use wgpu::Backends;
/// Contains the default Bevy rendering backend based on wgpu.
#[derive(Default)]
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)]
pub enum RenderStage {
/// Extract data from "app world" and insert it into "render world". This step should be kept
/// as short as possible to increase the "pipelining potential" for running the next frame
/// while rendering the current frame.
/// Extract data from the "app world" and insert it into the "render world".
/// This step should be kept as short as possible to increase the "pipelining potential" for
/// running the next frame while rendering the current frame.
Extract,
/// Prepare render resources from extracted data.
/// Prepare render resources from the extracted data for the GPU.
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,
// 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,
/// 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,
/// 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)]
pub struct RenderApp;
/// A "scratch" world used to avoid allocating new worlds every frame when
// swapping out the Render World.
/// swapping out the [`RenderWorld`].
#[derive(Default)]
struct ScratchRenderWorld(World);
impl Plugin for RenderPlugin {
/// Initializes the renderer, sets up the [`RenderStage`](RenderStage) and creates the rendering sub-app.
fn build(&self, app: &mut App) {
let default_backend = if cfg!(not(target_arch = "wasm32")) {
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) {
let extract = render_app
.schedule

View file

@ -79,12 +79,13 @@ impl Mesh {
}
}
/// Returns the topology of the mesh.
pub fn primitive_topology(&self) -> PrimitiveTopology {
self.primitive_topology
}
/// Sets the data for a vertex attribute (position, normal etc.). The name will
/// often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`]
/// often be one of the associated constants such as [`Mesh::ATTRIBUTE_POSITION`].
pub fn set_attribute(
&mut self,
name: impl Into<Cow<'static, str>>,
@ -94,11 +95,12 @@ impl Mesh {
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> {
self.attributes.get(&name.into())
}
/// Retrieves the data currently set to the vertex attribute with the specified `name` mutably.
pub fn attribute_mut(
&mut self,
name: impl Into<Cow<'static, str>>,
@ -106,21 +108,25 @@ impl Mesh {
self.attributes.get_mut(&name.into())
}
/// Indices describe how triangles are constructed out of the vertex attributes.
/// They are only useful for the [`crate::pipeline::PrimitiveTopology`] variants that use
/// triangles
/// Sets the vertex indices of the mesh. They describe how triangles are constructed out of the
/// vertex attributes and are therefore only useful for the [`PrimitiveTopology`] variants
/// that use triangles.
pub fn set_indices(&mut self, indices: Option<Indices>) {
self.indices = indices;
}
/// Retrieves the vertex `indices` of the mesh.
pub fn indices(&self) -> Option<&Indices> {
self.indices.as_ref()
}
/// Retrieves the vertex `indices` of the mesh mutably.
pub fn indices_mut(&mut self) -> Option<&mut Indices> {
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]> {
self.indices.as_ref().map(|indices| match &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 {
let mut vertex_count: Option<usize> = None;
for (attribute_name, attribute_data) in self.attributes.iter() {
@ -164,6 +174,12 @@ impl Mesh {
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> {
let mut vertex_size = 0;
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.
/// Does nothing if no [Indices] are set.
///
/// # Panics
/// If the mesh has any other topology than [`PrimitiveTopology::TriangleList`].
pub fn duplicate_vertices(&mut self) {
fn duplicate<T: Copy>(values: &[T], indices: impl Iterator<Item = usize>) -> Vec<T> {
indices.map(|i| values[i]).collect()
@ -248,7 +267,8 @@ impl 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.
pub fn compute_flat_normals(&mut self) {
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)]
pub enum VertexAttributeValues {
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 {
self.len() == 0
}
/// Returns the values as float triples if possible.
fn as_float3(&self) -> Option<&[[f32; 3]]> {
match self {
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.
#[derive(Debug, Clone)]
@ -512,6 +534,7 @@ pub enum Indices {
}
impl Indices {
/// Returns an iterator over the indices.
fn iter(&self) -> impl Iterator<Item = usize> + '_ {
match self {
Indices::U16(vec) => IndicesIter::U16(vec.iter()),
@ -519,6 +542,7 @@ impl Indices {
}
}
/// Returns the number of indices.
pub fn len(&self) -> usize {
match self {
Indices::U16(vec) => vec.len(),
@ -526,6 +550,7 @@ impl Indices {
}
}
/// Returns `true` if there are no indices.
pub fn is_empty(&self) -> bool {
match self {
Indices::U16(vec) => vec.is_empty(),
@ -533,10 +558,13 @@ impl Indices {
}
}
}
/// An Iterator for the [`Indices`].
enum IndicesIter<'a> {
U16(std::slice::Iter<'a, u16>),
U32(std::slice::Iter<'a, u32>),
}
impl Iterator for IndicesIter<'_> {
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)]
pub struct GpuMesh {
/// Contains all attribute data for each vertex.
pub vertex_buffer: Buffer,
pub index_info: Option<GpuIndexInfo>,
pub has_tangents: bool,
}
/// The index info of a [`GpuMesh`].
#[derive(Debug, Clone)]
pub struct GpuIndexInfo {
/// Contains all index data of a mesh.
pub buffer: Buffer,
pub count: u32,
pub index_format: IndexFormat,
@ -576,10 +609,12 @@ impl RenderAsset for Mesh {
type PreparedAsset = GpuMesh;
type Param = SRes<RenderDevice>;
/// Clones the mesh.
fn extract_asset(&self) -> Self::ExtractedAsset {
self.clone()
}
/// Converts the extracted mesh a into [`GpuMesh`].
fn prepare_asset(
mesh: Self::ExtractedAsset,
render_device: &mut SystemParamItem<Self::Param>,

View file

@ -9,6 +9,7 @@ use crate::render_asset::RenderAssetPlugin;
use bevy_app::{App, Plugin};
use bevy_asset::AddAsset;
/// Adds the [`Mesh`] as an asset and makes sure that they are extracted and prepared for the GPU.
pub struct MeshPlugin;
impl Plugin for MeshPlugin {

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)]
pub struct Box {
pub min_x: f32,
@ -37,6 +38,7 @@ pub struct Box {
}
impl Box {
/// Creates a new box centered at the origin with the supplied side lengths.
pub fn new(x_length: f32, y_length: f32, z_length: f32) -> Box {
Box {
max_x: x_length / 2.0,
@ -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)]
pub struct Quad {
/// 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)]
pub struct Plane {
/// The total side length of the square.

View file

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

View file

@ -12,18 +12,38 @@ pub enum PrepareAssetError<E: Send + Sync + 'static> {
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 {
/// The representation of the the asset in the "render world".
type ExtractedAsset: Send + Sync + 'static;
/// The GPU-representation of the the asset.
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;
/// Converts the asset into a [`RenderAsset::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(
extracted_asset: Self::ExtractedAsset,
param: &mut SystemParamItem<Self::Param>,
) -> 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>);
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> {
extracted: Vec<(Handle<A>, A::ExtractedAsset)>,
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>;
/// This system extracts all crated or modified assets of the corresponding [`RenderAsset`] type
/// into the "render world".
fn extract_render_asset<A: RenderAsset>(
mut commands: Commands,
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> = (
SResMut<ExtractedAssets<R>>,
SResMut<RenderAssets<R>>,
@ -105,6 +131,7 @@ pub type RenderAssetParams<R> = (
);
// TODO: consider storing inside system?
/// All assets that should be prepared next frame.
pub struct PrepareNextFrameAssets<A: RenderAsset> {
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>);
impl<R: RenderAsset> RunSystem for PrepareAssetSystem<R> {
type Param = RenderAssetParams<R>;
fn run(
(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 std::{marker::PhantomData, ops::Deref};
/// Stores the index of a uniform inside of [`ComponentUniforms`].
pub struct DynamicUniformIndex<C: Component> {
index: u32,
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 {
/// ECS [`WorldQuery`] to fetch the components to extract.
type Query: WorldQuery;
/// Filters the entities with additional constraints.
type Filter: WorldQuery;
/// Defines how the component is transferred into the "render world".
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>);
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> {
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>(
mut commands: Commands,
render_device: Res<RenderDevice>,
@ -107,6 +126,10 @@ fn prepare_uniform_components<C: Component>(
.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)>);
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>);
impl<C: ExtractComponent> RunSystem for ExtractComponentSystem<C>
@ -146,6 +170,7 @@ where
{
type Param = (
SCommands,
// the previous amount of extracted components
Local<'static, usize>,
SQuery<(Entity, C::Query), C::Filter>,
);

View file

@ -6,11 +6,21 @@ use bevy_ecs::entity::Entity;
use std::borrow::Cow;
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 name: Cow<'static, str>,
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> {
graph: &'a RenderGraph,
node: &'a NodeState,
@ -20,6 +30,7 @@ pub struct RenderGraphContext<'a> {
}
impl<'a> RenderGraphContext<'a> {
/// Creates a new render graph context for the `node`.
pub fn new(
graph: &'a RenderGraph,
node: &'a NodeState,
@ -35,19 +46,23 @@ impl<'a> RenderGraphContext<'a> {
}
}
/// Returns the input slot values for the node.
#[inline]
pub fn inputs(&self) -> &[SlotValue] {
self.inputs
}
/// Returns the [`SlotInfos`] of the inputs.
pub fn input_info(&self) -> &SlotInfos {
&self.node.input_slots
}
/// Returns the [`SlotInfos`] of the outputs.
pub fn output_info(&self) -> &SlotInfos {
&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> {
let label = label.into();
let index = self
@ -58,6 +73,7 @@ impl<'a> RenderGraphContext<'a> {
}
// 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(
&self,
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(
&self,
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> {
let label = label.into();
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> {
let label = label.into();
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(
&mut self,
label: impl Into<SlotLabel>,
@ -138,6 +158,7 @@ impl<'a> RenderGraphContext<'a> {
Ok(())
}
/// Queues up a sub graph for execution after the node has finished running.
pub fn run_sub_graph(
&mut self,
name: impl Into<Cow<'static, str>>,
@ -177,6 +198,8 @@ impl<'a> RenderGraphContext<'a> {
Ok(())
}
/// Finishes the context for this [`Node`](super::Node) by
/// returning the sub graphs to run next.
pub fn finish(self) -> Vec<RunSubGraph> {
self.run_sub_graphs
}

View file

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

View file

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

View file

@ -11,6 +11,10 @@ use downcast_rs::{impl_downcast, Downcast};
use std::{borrow::Cow, fmt::Debug};
use thiserror::Error;
/// A [`Node`] identifier.
/// It automatically generates its own random uuid.
///
/// This id is used to reference the node internally (edges, etc).
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct NodeId(Uuid);
@ -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 {
/// Specifies the required input slots for this node.
/// They will then be available during the run method inside the [`RenderContext`].
fn input(&self) -> Vec<SlotInfo> {
Vec::new()
}
/// Specifies the produced output slots for this node.
/// They can then be passed one inside [`RenderContext`] during the run method.
fn output(&self) -> Vec<SlotInfo> {
Vec::new()
}
/// 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) {}
/// 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(
&self,
graph: &mut RenderGraphContext,
@ -58,6 +80,7 @@ pub enum NodeRunError {
RunSubGraphError(#[from] RunSubGraphError),
}
/// A collection of input and output [`Edges`](Edge) for a [`Node`].
#[derive(Debug)]
pub struct Edges {
pub id: NodeId,
@ -66,6 +89,7 @@ pub struct Edges {
}
impl Edges {
/// Adds an edge to the `input_edges` if it does not already exist.
pub(crate) fn add_input_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> {
if self.has_input_edge(&edge) {
return Err(RenderGraphError::EdgeAlreadyExists(edge));
@ -74,6 +98,7 @@ impl Edges {
Ok(())
}
/// Adds an edge to the `output_edges` if it does not already exist.
pub(crate) fn add_output_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> {
if self.has_output_edge(&edge) {
return Err(RenderGraphError::EdgeAlreadyExists(edge));
@ -82,14 +107,18 @@ impl Edges {
Ok(())
}
/// Checks whether the input edge already exists.
pub fn has_input_edge(&self, edge: &Edge) -> bool {
self.input_edges.contains(edge)
}
/// Checks whether the output edge already exists.
pub fn has_output_edge(&self, edge: &Edge) -> bool {
self.output_edges.contains(edge)
}
/// Searches the `input_edges` for a [`Edge::SlotEdge`],
/// which `input_index` matches the `index`;
pub fn get_input_slot_edge(&self, index: usize) -> Result<&Edge, RenderGraphError> {
self.input_edges
.iter()
@ -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> {
self.output_edges
.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 id: NodeId,
pub name: Option<Cow<'static, str>>,
/// The name of the type that implements [`Node`].
pub type_name: &'static str,
pub node: Box<dyn Node>,
pub input_slots: SlotInfos,
@ -140,6 +176,8 @@ impl Debug for NodeState {
}
impl NodeState {
/// Creates an [`NodeState`] without edges, but the `input_slots` and `output_slots`
/// are provided by the `node`.
pub fn new<T>(id: NodeId, node: T) -> Self
where
T: Node,
@ -159,6 +197,7 @@ impl NodeState {
}
}
/// Retrieves the [`Node`].
pub fn node<T>(&self) -> Result<&T, RenderGraphError>
where
T: Node,
@ -168,6 +207,7 @@ impl NodeState {
.ok_or(RenderGraphError::WrongNodeType)
}
/// Retrieves the [`Node`] mutably.
pub fn node_mut<T>(&mut self) -> Result<&mut T, RenderGraphError>
where
T: Node,
@ -177,14 +217,7 @@ impl NodeState {
.ok_or(RenderGraphError::WrongNodeType)
}
pub fn validate_output_slots(&self) -> Result<(), RenderGraphError> {
for i in 0..self.output_slots.len() {
self.edges.get_output_slot_edge(i)?;
}
Ok(())
}
/// Validates that each input slot corresponds to an input edge.
pub fn validate_input_slots(&self) -> Result<(), RenderGraphError> {
for i in 0..self.input_slots.len() {
self.edges.get_input_slot_edge(i)?;
@ -192,8 +225,19 @@ impl NodeState {
Ok(())
}
/// Validates that each output slot corresponds to an output edge.
pub fn validate_output_slots(&self) -> Result<(), RenderGraphError> {
for i in 0..self.output_slots.len() {
self.edges.get_output_slot_edge(i)?;
}
Ok(())
}
}
/// A [`NodeLabel`] is used to reference a [`NodeState`] by either its name or [`NodeId`]
/// inside the [`RenderGraph`](super::RenderGraph).
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum NodeLabel {
Id(NodeId),
@ -223,6 +267,10 @@ impl From<NodeId> for NodeLabel {
NodeLabel::Id(value)
}
}
/// A [`Node`] without any inputs, outputs and subgraphs, which does nothing when run.
/// Used (as a label) to bundle multiple dependencies into one inside
/// the [`RenderGraph`](super::RenderGraph).
pub struct EmptyNode;
impl Node for EmptyNode {

View file

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

View file

@ -15,7 +15,12 @@ use bevy_utils::HashMap;
use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard};
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 {
/// Draws the [`PhaseItem`] by issuing draw calls via the [`TrackedRenderPass`].
fn draw<'w>(
&mut self,
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 {
/// The type used for ordering the items. The smallest values are drawn first.
type SortKey: Ord;
/// Determines the order in which the items are drawn during the corresponding [`RenderPhase`].
fn sort_key(&self) -> Self::SortKey;
/// Specifies the [`Draw`] function used to render the item.
fn draw_function(&self) -> DrawFunctionId;
}
// TODO: make this generic?
/// /// A [`Draw`] function identifier.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
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 draw_functions: Vec<Box<dyn Draw<P>>>,
pub indices: HashMap<TypeId, DrawFunctionId>,
}
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 {
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 {
self.draw_functions.push(Box::new(draw_function));
let id = DrawFunctionId(self.draw_functions.len() - 1);
@ -52,15 +70,19 @@ impl<P: PhaseItem> DrawFunctionsInternal<P> {
id
}
/// Retrieves the [`Draw`] function corresponding to the `id` mutably.
pub fn get_mut(&mut self, id: DrawFunctionId) -> Option<&mut dyn Draw<P>> {
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> {
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> {
internal: RwLock<DrawFunctionsInternal<P>>,
}
@ -77,16 +99,42 @@ impl<P: PhaseItem> Default for DrawFunctions<P> {
}
impl<P: PhaseItem> DrawFunctions<P> {
/// Accesses the draw functions in read mode.
pub fn read(&self) -> RwLockReadGuard<'_, DrawFunctionsInternal<P>> {
self.internal.read()
}
/// Accesses the draw functions in write mode.
pub fn write(&self) -> RwLockWriteGuard<'_, DrawFunctionsInternal<P>> {
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> {
/// Specifies all ECS data required by [`RenderCommand::render`].
/// All parameters have to be read only.
type Param: SystemParam;
/// Renders the [`PhaseItem`] by issuing draw calls via the [`TrackedRenderPass`].
fn render<'w>(
view: Entity,
item: &P,
@ -165,6 +213,8 @@ macro_rules! render_command_tuple_impl {
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>> {
state: SystemState<C::Param>,
}
@ -181,6 +231,7 @@ impl<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static> Draw<P> for Rend
where
<C::Param as SystemParam>::Fetch: ReadOnlySystemParamFetch,
{
/// Prepares the ECS parameters for the wrapped [`RenderCommand`] and then renders it.
fn draw<'w>(
&mut self,
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 {
/// Adds the [`RenderCommand`] for the specified [`RenderPhase`](super::RenderPhase) to the app.
fn add_render_command<P: PhaseItem, C: RenderCommand<P> + Send + Sync + 'static>(
&mut self,
) -> &mut Self

View file

@ -5,7 +5,7 @@ use bevy_utils::tracing::debug;
use std::ops::Range;
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)]
pub struct DrawState {
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> {
pass: RenderPass<'a>,
state: DrawState,
}
impl<'a> TrackedRenderPass<'a> {
/// Tracks the supplied render pass.
pub fn new(pass: RenderPass<'a>) -> Self {
Self {
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) {
debug!("set pipeline: {:?}", pipeline);
if self.state.is_pipeline_set(pipeline.id()) {
@ -105,6 +112,9 @@ impl<'a> TrackedRenderPass<'a> {
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(
&mut self,
index: usize,
@ -132,6 +142,13 @@ impl<'a> TrackedRenderPass<'a> {
.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>) {
let offset = buffer_slice.offset();
if self
@ -158,6 +175,10 @@ impl<'a> TrackedRenderPass<'a> {
.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(
&mut self,
buffer_slice: BufferSlice<'a>,
@ -182,11 +203,18 @@ impl<'a> TrackedRenderPass<'a> {
.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>) {
debug!("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>) {
debug!(
"draw indexed: {:?} {} {:?}",

View file

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

View file

@ -1,9 +1,16 @@
use bevy_reflect::Uuid;
use std::{ops::Deref, sync::Arc};
/// A [`BindGroup`] identifier.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
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)]
pub struct BindGroup {
id: BindGroupId,
@ -11,6 +18,7 @@ pub struct BindGroup {
}
impl BindGroup {
/// Returns the [`BindGroupId`].
#[inline]
pub fn id(&self) -> BindGroupId {
self.id

View file

@ -7,9 +7,14 @@ use wgpu::{
VertexAttribute, VertexStepMode,
};
/// A [`RenderPipeline`] identifier.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
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)]
pub struct RenderPipeline {
id: RenderPipelineId,
@ -41,9 +46,14 @@ impl Deref for RenderPipeline {
}
}
/// A [`ComputePipeline`] identifier.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
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)]
pub struct ComputePipeline {
id: ComputePipelineId,
@ -51,6 +61,7 @@ pub struct ComputePipeline {
}
impl ComputePipeline {
/// Returns the [`ComputePipelineId`].
#[inline]
pub fn id(&self) -> ComputePipelineId {
self.id

View file

@ -1,9 +1,14 @@
use bevy_utils::Uuid;
use std::{ops::Deref, sync::Arc};
/// A [`Texture`] identifier.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
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)]
pub struct Texture {
id: TextureId,
@ -11,11 +16,13 @@ pub struct Texture {
}
impl Texture {
/// Returns the [`TextureId`].
#[inline]
pub fn id(&self) -> TextureId {
self.id
}
/// Creates a view of this texture.
pub fn create_view(&self, desc: &wgpu::TextureViewDescriptor) -> TextureView {
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)]
pub struct TextureViewId(Uuid);
/// This type combines wgpu's [`TextureView`](wgpu::TextureView) and
/// [SurfaceTexture`](wgpu::SurfaceTexture) into the same interface.
#[derive(Clone, Debug)]
pub enum TextureViewValue {
/// The value is an actual wgpu [`TextureView`](wgpu::TextureView).
TextureView(Arc<wgpu::TextureView>),
/// The value is a wgpu [`SurfaceTexture`](wgpu::SurfaceTexture), but dereferences to
/// a [`TextureView`](wgpu::TextureView).
SurfaceTexture {
// NOTE: The order of these fields is important because the view must be dropped before the
// 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)]
pub struct TextureView {
id: TextureViewId,
@ -60,11 +78,13 @@ pub struct TextureView {
}
impl TextureView {
/// Returns the [`TextureViewId`].
#[inline]
pub fn id(&self) -> TextureViewId {
self.id
}
/// Returns the [`SurfaceTexture`](wgpu::SurfaceTexture) of the texture view if it is of that type.
#[inline]
pub fn take_surface_texture(self) -> Option<wgpu::SurfaceTexture> {
match self.value {
@ -107,9 +127,15 @@ impl Deref for TextureView {
}
}
/// A [`Sampler`] identifier.
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
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)]
pub struct Sampler {
id: SamplerId,
@ -117,6 +143,7 @@ pub struct Sampler {
}
impl Sampler {
/// Returns the [`SamplerId`].
#[inline]
pub fn id(&self) -> SamplerId {
self.id

View file

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

View file

@ -1,12 +1,12 @@
use futures_lite::future;
use wgpu::util::DeviceExt;
use crate::render_resource::{
BindGroup, BindGroupLayout, Buffer, ComputePipeline, RawRenderPipelineDescriptor,
RenderPipeline, Sampler, Texture,
};
use futures_lite::future;
use std::sync::Arc;
use wgpu::util::DeviceExt;
/// This GPU device is responsible for the creation of most rendering and compute resources.
#[derive(Clone)]
pub struct RenderDevice {
device: Arc<wgpu::Device>,
@ -19,7 +19,7 @@ impl From<Arc<wgpu::Device>> for 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.
#[inline]
@ -27,7 +27,7 @@ impl RenderDevice {
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.
#[inline]
@ -35,7 +35,7 @@ impl RenderDevice {
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]
pub fn create_shader_module(&self, desc: &wgpu::ShaderModuleDescriptor) -> wgpu::ShaderModule {
self.device.create_shader_module(desc)
@ -49,7 +49,7 @@ impl RenderDevice {
self.device.poll(maintain)
}
/// Creates an empty [`CommandEncoder`].
/// Creates an empty [`CommandEncoder`](wgpu::CommandEncoder).
#[inline]
pub fn create_command_encoder(
&self,
@ -58,7 +58,7 @@ impl RenderDevice {
self.device.create_command_encoder(desc)
}
/// Creates an empty [`RenderBundleEncoder`].
/// Creates an empty [`RenderBundleEncoder`](wgpu::RenderBundleEncoder).
#[inline]
pub fn create_render_bundle_encoder(
&self,
@ -67,14 +67,14 @@ impl RenderDevice {
self.device.create_render_bundle_encoder(desc)
}
/// Creates a new [`BindGroup`].
/// Creates a new [`BindGroup`](wgpu::BindGroup).
#[inline]
pub fn create_bind_group(&self, desc: &wgpu::BindGroupDescriptor) -> BindGroup {
let wgpu_bind_group = self.device.create_bind_group(desc);
BindGroup::from(wgpu_bind_group)
}
/// Creates a [`BindGroupLayout`].
/// Creates a [`BindGroupLayout`](wgpu::BindGroupLayout).
#[inline]
pub fn create_bind_group_layout(
&self,
@ -83,7 +83,7 @@ impl RenderDevice {
BindGroupLayout::from(self.device.create_bind_group_layout(desc))
}
/// Creates a [`PipelineLayout`].
/// Creates a [`PipelineLayout`](wgpu::PipelineLayout).
#[inline]
pub fn create_pipeline_layout(
&self,
@ -115,7 +115,7 @@ impl RenderDevice {
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 {
let wgpu_buffer = self.device.create_buffer_init(desc);
Buffer::from(wgpu_buffer)
@ -137,16 +137,17 @@ impl RenderDevice {
Sampler::from(wgpu_sampler)
}
/// Create a new [`SwapChain`] which targets `surface`.
/// Create a new [`SwapChain`](wgpu::SwapChain) which targets `surface`.
///
/// # 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.
pub fn configure_surface(&self, surface: &wgpu::Surface, config: &wgpu::SurfaceConfiguration) {
surface.configure(&self.device, config)
}
/// Returns the wgpu [`Device`](wgpu::Device).
pub fn wgpu_device(&self) -> &wgpu::Device {
&self.device
}

View file

@ -50,6 +50,11 @@ impl Default for 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(
size: Extent3d,
dimension: TextureDimension,
@ -71,6 +76,12 @@ impl 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(
size: Extent3d,
dimension: TextureDimension,
@ -98,10 +109,13 @@ impl Image {
value
}
/// Returns the aspect ratio (height/width) of a 2D image.
pub fn aspect_2d(&self) -> 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) {
self.texture_descriptor.size = size;
self.data.resize(
@ -112,6 +126,9 @@ impl Image {
/// Changes the `size`, asserting that the total number of data elements (pixels) remains the
/// 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) {
assert!(
new_size.volume() == self.texture_descriptor.size.volume(),
@ -123,9 +140,13 @@ impl Image {
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
/// 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) {
// Must be a stacked image, and the height must be divisible by layers.
assert!(self.texture_descriptor.dimension == TextureDimension::D2);
@ -203,31 +224,39 @@ pub enum TextureError {
ImageError(#[from] image::ImageError),
}
/// Type of a raw image buffer
/// The type of a raw image buffer.
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),
/// Extension of an image file, for example `"png"`
/// The extension of an image file, for example `"png"`.
Extension(&'a str),
}
/// Used to calculate the volume of an item.
pub trait Volume {
fn volume(&self) -> usize;
}
impl Volume for Extent3d {
/// Calculates the volume of the [`Extent3D`].
fn volume(&self) -> 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 {
/// The size of a component of a pixel in bytes.
pub type_size: usize,
/// The amount of different components (color channels).
pub num_components: usize,
}
/// Extends the wgpu [`TextureFormat`] with information about the pixel.
pub trait TextureFormatPixelInfo {
/// Returns the pixel information of the format.
fn pixel_info(&self) -> PixelInfo;
/// Returns the size of a pixel of the format.
fn pixel_size(&self) -> usize {
let info = self.pixel_info();
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)]
pub struct GpuImage {
pub texture: Texture,
@ -352,10 +383,12 @@ impl RenderAsset for Image {
type PreparedAsset = GpuImage;
type Param = (SRes<RenderDevice>, SRes<RenderQueue>);
/// Clones the Image.
fn extract_asset(&self) -> Self::ExtractedAsset {
self.clone()
}
/// Converts the extracted image into a [`GpuImage`].
fn prepare_asset(
image: Self::ExtractedAsset,
(render_device, render_queue): &mut SystemParamItem<Self::Param>,

View file

@ -2,7 +2,7 @@ use crate::texture::{Image, TextureFormatPixelInfo};
use wgpu::{Extent3d, TextureDimension, TextureFormat};
// 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 {
use bevy_core::cast_slice;
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
/// covered, it will return `None` if the format is not supported
/// Converts an [`Image`] to a [`DynamicImage`]. Not all [`TextureFormat`] are
/// covered, therefore it will return `None` if the format is unsupported.
pub(crate) fn texture_to_image(texture: &Image) -> Option<image::DynamicImage> {
match texture.texture_descriptor.format {
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)]
pub struct FileTextureError {
error: TextureError,

View file

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

View file

@ -6,6 +6,8 @@ use bevy_ecs::prelude::ResMut;
use bevy_utils::HashMap;
use wgpu::{TextureDescriptor, TextureViewDescriptor};
/// The internal representation of a [`CachedTexture`] used to track whether it was recently used
/// and is currently taken.
struct CachedTextureMeta {
texture: Texture,
default_view: TextureView,
@ -13,17 +15,24 @@ struct CachedTextureMeta {
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 texture: Texture,
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)]
pub struct TextureCache {
textures: HashMap<wgpu::TextureDescriptor<'static>, Vec<CachedTextureMeta>>,
}
impl TextureCache {
/// Retrieves a texture that matches the `descriptor`. If no matching one is found a new
/// [`CachedTexture`] is created.
pub fn get(
&mut self,
render_device: &RenderDevice,
@ -72,6 +81,7 @@ impl TextureCache {
}
}
/// Updates the cache and only retains recently used textures.
pub fn update(&mut self) {
for textures in self.textures.values_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>) {
texture_cache.update();
}

View file

@ -11,7 +11,7 @@ use bevy_window::{RawWindowHandleWrapper, WindowId, Windows};
use std::ops::{Deref, DerefMut};
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)]
pub struct NonSendMarker;