mirror of
https://github.com/bevyengine/bevy
synced 2024-11-22 04:33:37 +00:00
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:
parent
14ce281904
commit
1076a8f2b5
33 changed files with 592 additions and 72 deletions
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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<'_>,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>,
|
||||
) {
|
||||
|
|
|
@ -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>,
|
||||
);
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 it’s own optional input node.
|
||||
///
|
||||
/// ## Example
|
||||
/// Here is a simple render graph example with two nodes connected by a node edge.
|
||||
/// ```
|
||||
/// # use bevy_app::prelude::*;
|
||||
/// # use bevy_ecs::prelude::World;
|
||||
/// # use bevy_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>,
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: {:?} {} {:?}",
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
Loading…
Reference in a new issue