mirror of
https://github.com/bevyengine/bevy
synced 2024-11-10 15:14:50 +00:00
RenderGraph2: Stager, (semi-functional) Executor, PassNode, and tests.
Rendering doesn't quite work yet, but we're close!
This commit is contained in:
parent
b6711d8eae
commit
8326a1a3c2
27 changed files with 1889 additions and 562 deletions
|
@ -18,6 +18,7 @@ legion = { path = "../bevy_legion" }
|
|||
# rendering
|
||||
spirv-reflect = "0.2.3"
|
||||
shaderc = "0.6"
|
||||
# TODO: move this to its own crate
|
||||
png = "0.16.0"
|
||||
|
||||
# misc
|
||||
|
|
|
@ -3,7 +3,7 @@ use bevy_asset::Handle;
|
|||
use legion::prelude::{Resources, World};
|
||||
|
||||
// A set of draw calls. ex: get + draw meshes, get + draw instanced meshes, draw ui meshes, etc
|
||||
pub trait DrawTarget {
|
||||
pub trait DrawTarget: Send + Sync + 'static {
|
||||
fn draw(
|
||||
&self,
|
||||
world: &World,
|
||||
|
|
|
@ -28,14 +28,14 @@ pub mod texture;
|
|||
pub use once_cell;
|
||||
|
||||
use self::{
|
||||
draw_target::draw_targets::{
|
||||
AssignedBatchesDrawTarget, AssignedMeshesDrawTarget, MeshesDrawTarget, UiDrawTarget,
|
||||
},
|
||||
draw_target::draw_targets::AssignedMeshesDrawTarget,
|
||||
mesh::Mesh,
|
||||
pass::passes::ForwardPassBuilder,
|
||||
pass::{
|
||||
LoadOp, RenderPassColorAttachmentDescriptor, RenderPassDepthStencilAttachmentDescriptor,
|
||||
StoreOp, TextureAttachment,
|
||||
},
|
||||
pipeline::{
|
||||
pipelines::ForwardPipelineBuilder, PipelineAssignments, PipelineCompiler,
|
||||
PipelineDescriptor, VertexBufferDescriptors,
|
||||
PipelineAssignments, PipelineCompiler, PipelineDescriptor, VertexBufferDescriptors,
|
||||
},
|
||||
render_graph::RenderGraph,
|
||||
render_resource::{
|
||||
|
@ -51,8 +51,10 @@ use bevy_app::{stage, AppBuilder, AppPlugin, GetEventReader};
|
|||
use bevy_asset::AssetStorage;
|
||||
use bevy_transform::prelude::LocalToWorld;
|
||||
use bevy_window::{WindowCreated, WindowResized};
|
||||
use pass::PassDescriptor;
|
||||
use pipeline::pipelines::build_forward_pipeline;
|
||||
use render_graph_2::{
|
||||
nodes::{Camera2dNode, CameraNode, SwapChainWindowSource, WindowSwapChainNode},
|
||||
nodes::{Camera2dNode, CameraNode, PassNode, SwapChainWindowSource, WindowSwapChainNode},
|
||||
RenderGraph2,
|
||||
};
|
||||
use render_resource::resource_providers::mesh_resource_provider_system;
|
||||
|
@ -74,36 +76,20 @@ impl RenderPlugin {
|
|||
let mut render_graph = resources.get_mut::<RenderGraph>().unwrap();
|
||||
render_graph
|
||||
.build(&mut pipelines, &mut shaders)
|
||||
.add_draw_target(MeshesDrawTarget::default())
|
||||
.add_draw_target(AssignedBatchesDrawTarget::default())
|
||||
.add_draw_target(AssignedMeshesDrawTarget::default())
|
||||
.add_draw_target(UiDrawTarget::default())
|
||||
.add_resource_provider(LightResourceProvider::new(10))
|
||||
.add_resource_provider(UniformResourceProvider::<StandardMaterial>::new(true))
|
||||
.add_resource_provider(UniformResourceProvider::<LocalToWorld>::new(true))
|
||||
.add_forward_pass()
|
||||
.add_forward_pipeline();
|
||||
.add_resource_provider(UniformResourceProvider::<LocalToWorld>::new(true));
|
||||
}
|
||||
}
|
||||
|
||||
impl AppPlugin for RenderPlugin {
|
||||
fn build(&self, app: &mut AppBuilder) {
|
||||
let mut render_graph = RenderGraph2::default();
|
||||
let resources = app.resources_mut();
|
||||
render_graph.add_system_node(CameraNode::default(), resources);
|
||||
render_graph.add_system_node(Camera2dNode::default(), resources);
|
||||
render_graph.add_node(WindowSwapChainNode::new(
|
||||
SwapChainWindowSource::Primary,
|
||||
resources.get_event_reader::<WindowCreated>(),
|
||||
resources.get_event_reader::<WindowResized>(),
|
||||
));
|
||||
let mut asset_batchers = AssetBatchers::default();
|
||||
asset_batchers.batch_types2::<Mesh, StandardMaterial>();
|
||||
app.add_stage_after(stage::POST_UPDATE, RENDER_RESOURCE_STAGE)
|
||||
.add_stage_after(RENDER_RESOURCE_STAGE, RENDER_STAGE)
|
||||
// resources
|
||||
.add_resource(RenderGraph::default())
|
||||
.add_resource(render_graph)
|
||||
.add_resource(AssetStorage::<Mesh>::new())
|
||||
.add_resource(AssetStorage::<Texture>::new())
|
||||
.add_resource(AssetStorage::<Shader>::new())
|
||||
|
@ -131,5 +117,57 @@ impl AppPlugin for RenderPlugin {
|
|||
// render resource provider systems
|
||||
.add_system_to_stage_init(RENDER_RESOURCE_STAGE, mesh_resource_provider_system);
|
||||
RenderPlugin::setup_render_graph_defaults(app);
|
||||
let mut render_graph = RenderGraph2::default();
|
||||
// begin render graph 2
|
||||
{
|
||||
let resources = app.resources_mut();
|
||||
render_graph.add_system_node_named("camera", CameraNode::default(), resources);
|
||||
render_graph.add_system_node_named("camera2d", Camera2dNode::default(), resources);
|
||||
render_graph.add_node_named(
|
||||
"swapchain",
|
||||
WindowSwapChainNode::new(
|
||||
SwapChainWindowSource::Primary,
|
||||
resources.get_event_reader::<WindowCreated>(),
|
||||
resources.get_event_reader::<WindowResized>(),
|
||||
),
|
||||
);
|
||||
let mut shaders = resources.get_mut::<AssetStorage<Shader>>().unwrap();
|
||||
let mut pipelines = resources
|
||||
.get_mut::<AssetStorage<PipelineDescriptor>>()
|
||||
.unwrap();
|
||||
let mut main_pass = PassNode::new(PassDescriptor {
|
||||
color_attachments: vec![RenderPassColorAttachmentDescriptor {
|
||||
attachment: TextureAttachment::Input("color".to_string()),
|
||||
resolve_target: None,
|
||||
load_op: LoadOp::Clear,
|
||||
store_op: StoreOp::Store,
|
||||
clear_color: Color::rgb(0.1, 0.1, 0.1),
|
||||
}],
|
||||
depth_stencil_attachment: Some(RenderPassDepthStencilAttachmentDescriptor {
|
||||
attachment: TextureAttachment::Input("depth".to_string()),
|
||||
depth_load_op: LoadOp::Clear,
|
||||
depth_store_op: StoreOp::Store,
|
||||
stencil_load_op: LoadOp::Clear,
|
||||
stencil_store_op: StoreOp::Store,
|
||||
clear_depth: 1.0,
|
||||
clear_stencil: 0,
|
||||
}),
|
||||
sample_count: 1,
|
||||
});
|
||||
main_pass.add_pipeline(
|
||||
pipelines.add_default(build_forward_pipeline(&mut shaders)),
|
||||
vec![Box::new(AssignedMeshesDrawTarget)],
|
||||
);
|
||||
render_graph.add_node_named("main_pass", main_pass);
|
||||
|
||||
// TODO: replace these with "autowire" groups
|
||||
render_graph.add_node_edge("camera", "main_pass").unwrap();
|
||||
render_graph.add_node_edge("camera2d", "main_pass").unwrap();
|
||||
render_graph
|
||||
.add_slot_edge("swapchain", WindowSwapChainNode::OUT_TEXTURE, "main_pass", "color")
|
||||
.unwrap();
|
||||
}
|
||||
app.add_resource(render_graph);
|
||||
// end render graph 2
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
use super::{LoadOp, StoreOp};
|
||||
use crate::Color;
|
||||
use crate::{render_resource::RenderResource, Color};
|
||||
|
||||
pub enum TextureAttachment {
|
||||
RenderResource(RenderResource),
|
||||
Name(String),
|
||||
Input(String),
|
||||
}
|
||||
|
||||
pub struct RenderPassColorAttachmentDescriptor {
|
||||
/// The actual color attachment.
|
||||
pub attachment: String,
|
||||
pub attachment: TextureAttachment,
|
||||
|
||||
/// The resolve target for this color attachment, if any.
|
||||
pub resolve_target: Option<String>,
|
||||
pub resolve_target: Option<TextureAttachment>,
|
||||
|
||||
/// The beginning-of-pass load operation for this color attachment.
|
||||
pub load_op: LoadOp,
|
||||
|
@ -19,7 +25,7 @@ pub struct RenderPassColorAttachmentDescriptor {
|
|||
}
|
||||
|
||||
pub struct RenderPassDepthStencilAttachmentDescriptor {
|
||||
pub attachment: String,
|
||||
pub attachment: TextureAttachment,
|
||||
pub depth_load_op: LoadOp,
|
||||
pub depth_store_op: StoreOp,
|
||||
pub clear_depth: f32,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::{
|
||||
pass::{
|
||||
LoadOp, PassDescriptor, RenderPassColorAttachmentDescriptor,
|
||||
RenderPassDepthStencilAttachmentDescriptor, StoreOp,
|
||||
RenderPassDepthStencilAttachmentDescriptor, StoreOp, TextureAttachment
|
||||
},
|
||||
render_graph::RenderGraphBuilder,
|
||||
render_resource::{resource_name, resource_providers::FrameTextureResourceProvider},
|
||||
|
@ -35,14 +35,14 @@ impl<'a, 'b, 'c> ForwardPassBuilder for RenderGraphBuilder<'a, 'b, 'c> {
|
|||
resource_name::pass::MAIN,
|
||||
PassDescriptor {
|
||||
color_attachments: vec![RenderPassColorAttachmentDescriptor {
|
||||
attachment: resource_name::texture::SWAP_CHAIN.to_string(),
|
||||
attachment: TextureAttachment::Name(resource_name::texture::SWAP_CHAIN.to_string()),
|
||||
resolve_target: None,
|
||||
load_op: LoadOp::Clear,
|
||||
store_op: StoreOp::Store,
|
||||
clear_color: Color::rgb(0.1, 0.1, 0.1),
|
||||
}],
|
||||
depth_stencil_attachment: Some(RenderPassDepthStencilAttachmentDescriptor {
|
||||
attachment: resource_name::texture::DEPTH.to_string(),
|
||||
attachment: TextureAttachment::Name(resource_name::texture::DEPTH.to_string()),
|
||||
depth_load_op: LoadOp::Clear,
|
||||
depth_store_op: StoreOp::Store,
|
||||
stencil_load_op: LoadOp::Clear,
|
||||
|
|
|
@ -14,6 +14,7 @@ use crate::{
|
|||
|
||||
use bevy_asset::{AssetStorage, Handle};
|
||||
|
||||
// TODO: consider removing this in favor of Option<Layout>
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PipelineLayoutType {
|
||||
Manual(PipelineLayout),
|
||||
|
@ -61,6 +62,23 @@ pub struct PipelineDescriptor {
|
|||
}
|
||||
|
||||
impl PipelineDescriptor {
|
||||
pub fn new_new(shader_stages: ShaderStages) -> Self {
|
||||
PipelineDescriptor {
|
||||
name: None,
|
||||
layout: PipelineLayoutType::Reflected(None),
|
||||
color_states: Vec::new(),
|
||||
depth_stencil_state: None,
|
||||
draw_targets: Vec::new(),
|
||||
shader_stages,
|
||||
rasterization_state: None,
|
||||
primitive_topology: PrimitiveTopology::TriangleList,
|
||||
index_format: IndexFormat::Uint16,
|
||||
sample_count: 1,
|
||||
sample_mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn new(name: Option<&str>, vertex_shader: Handle<Shader>) -> Self {
|
||||
PipelineDescriptor {
|
||||
name: name.map(|name| name.to_string()),
|
||||
|
|
|
@ -1,61 +1,60 @@
|
|||
use crate::{
|
||||
pipeline::state_descriptors::{
|
||||
BlendDescriptor, BlendFactor, BlendOperation, ColorStateDescriptor, ColorWrite,
|
||||
CompareFunction, CullMode, DepthStencilStateDescriptor, FrontFace,
|
||||
RasterizationStateDescriptor, StencilStateFaceDescriptor,
|
||||
pipeline::{
|
||||
state_descriptors::{
|
||||
BlendDescriptor, BlendFactor, BlendOperation, ColorStateDescriptor, ColorWrite,
|
||||
CompareFunction, CullMode, DepthStencilStateDescriptor, FrontFace,
|
||||
RasterizationStateDescriptor, StencilStateFaceDescriptor,
|
||||
},
|
||||
PipelineDescriptor,
|
||||
},
|
||||
render_graph::RenderGraphBuilder,
|
||||
render_resource::resource_name,
|
||||
shader::{Shader, ShaderStage},
|
||||
shader::{Shader, ShaderStage, ShaderStages},
|
||||
texture::TextureFormat,
|
||||
};
|
||||
pub trait ForwardPipelineBuilder {
|
||||
fn add_forward_pipeline(&mut self) -> &mut Self;
|
||||
}
|
||||
use bevy_asset::AssetStorage;
|
||||
|
||||
impl<'a, 'b, 'c> ForwardPipelineBuilder for RenderGraphBuilder<'a, 'b, 'c> {
|
||||
fn add_forward_pipeline(&mut self) -> &mut Self {
|
||||
self.add_default_pipeline(resource_name::pipeline::FORWARD, |builder| {
|
||||
builder
|
||||
.with_vertex_shader(Shader::from_glsl(
|
||||
ShaderStage::Vertex,
|
||||
include_str!("forward.vert"),
|
||||
))
|
||||
.with_fragment_shader(Shader::from_glsl(
|
||||
ShaderStage::Fragment,
|
||||
include_str!("forward.frag"),
|
||||
))
|
||||
.with_rasterization_state(RasterizationStateDescriptor {
|
||||
front_face: FrontFace::Ccw,
|
||||
cull_mode: CullMode::Back,
|
||||
depth_bias: 0,
|
||||
depth_bias_slope_scale: 0.0,
|
||||
depth_bias_clamp: 0.0,
|
||||
})
|
||||
.with_depth_stencil_state(DepthStencilStateDescriptor {
|
||||
format: TextureFormat::Depth32Float,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: CompareFunction::Less,
|
||||
stencil_front: StencilStateFaceDescriptor::IGNORE,
|
||||
stencil_back: StencilStateFaceDescriptor::IGNORE,
|
||||
stencil_read_mask: 0,
|
||||
stencil_write_mask: 0,
|
||||
})
|
||||
.add_color_state(ColorStateDescriptor {
|
||||
format: TextureFormat::Bgra8UnormSrgb,
|
||||
color_blend: BlendDescriptor {
|
||||
src_factor: BlendFactor::SrcAlpha,
|
||||
dst_factor: BlendFactor::OneMinusSrcAlpha,
|
||||
operation: BlendOperation::Add,
|
||||
},
|
||||
alpha_blend: BlendDescriptor {
|
||||
src_factor: BlendFactor::One,
|
||||
dst_factor: BlendFactor::One,
|
||||
operation: BlendOperation::Add,
|
||||
},
|
||||
write_mask: ColorWrite::ALL,
|
||||
})
|
||||
.add_draw_target(resource_name::draw_target::ASSIGNED_MESHES);
|
||||
pub fn build_forward_pipeline(shaders: &mut AssetStorage<Shader>) -> PipelineDescriptor {
|
||||
PipelineDescriptor {
|
||||
rasterization_state: Some(RasterizationStateDescriptor {
|
||||
front_face: FrontFace::Ccw,
|
||||
cull_mode: CullMode::Back,
|
||||
depth_bias: 0,
|
||||
depth_bias_slope_scale: 0.0,
|
||||
depth_bias_clamp: 0.0,
|
||||
}),
|
||||
depth_stencil_state: Some(DepthStencilStateDescriptor {
|
||||
format: TextureFormat::Depth32Float,
|
||||
depth_write_enabled: true,
|
||||
depth_compare: CompareFunction::Less,
|
||||
stencil_front: StencilStateFaceDescriptor::IGNORE,
|
||||
stencil_back: StencilStateFaceDescriptor::IGNORE,
|
||||
stencil_read_mask: 0,
|
||||
stencil_write_mask: 0,
|
||||
}),
|
||||
color_states: vec![
|
||||
ColorStateDescriptor {
|
||||
format: TextureFormat::Bgra8UnormSrgb,
|
||||
color_blend: BlendDescriptor {
|
||||
src_factor: BlendFactor::SrcAlpha,
|
||||
dst_factor: BlendFactor::OneMinusSrcAlpha,
|
||||
operation: BlendOperation::Add,
|
||||
},
|
||||
alpha_blend: BlendDescriptor {
|
||||
src_factor: BlendFactor::One,
|
||||
dst_factor: BlendFactor::One,
|
||||
operation: BlendOperation::Add,
|
||||
},
|
||||
write_mask: ColorWrite::ALL,
|
||||
}
|
||||
],
|
||||
..PipelineDescriptor::new_new(ShaderStages {
|
||||
vertex: shaders.add(Shader::from_glsl(
|
||||
ShaderStage::Vertex,
|
||||
include_str!("forward.vert"),
|
||||
)),
|
||||
fragment: Some(shaders.add(Shader::from_glsl(
|
||||
ShaderStage::Fragment,
|
||||
include_str!("forward.frag"),
|
||||
))),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
61
bevy_render/src/render_graph_2/command.rs
Normal file
61
bevy_render/src/render_graph_2/command.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use crate::{renderer_2::RenderContext, render_resource::RenderResource};
|
||||
use std::{sync::{Arc, Mutex}, collections::VecDeque};
|
||||
|
||||
pub enum Command {
|
||||
CopyBufferToBuffer {
|
||||
source_buffer: RenderResource,
|
||||
source_offset: u64,
|
||||
destination_buffer: RenderResource,
|
||||
destination_offset: u64,
|
||||
size: u64,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct CommandQueue {
|
||||
// TODO: this shouldn't really need a mutex. its just needs to be shared on whatever thread its scheduled on
|
||||
queue: Arc<Mutex<VecDeque<Command>>>,
|
||||
}
|
||||
|
||||
impl CommandQueue {
|
||||
fn push(&mut self, command: Command) {
|
||||
self.queue.lock().unwrap().push_front(command);
|
||||
}
|
||||
|
||||
pub fn copy_buffer_to_buffer(
|
||||
&mut self,
|
||||
source_buffer: RenderResource,
|
||||
source_offset: u64,
|
||||
destination_buffer: RenderResource,
|
||||
destination_offset: u64,
|
||||
size: u64,
|
||||
) {
|
||||
self.push(Command::CopyBufferToBuffer {
|
||||
source_buffer,
|
||||
source_offset,
|
||||
destination_buffer,
|
||||
destination_offset,
|
||||
size,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn execute(&mut self, render_context: &mut dyn RenderContext) {
|
||||
for command in self.queue.lock().unwrap().drain(..) {
|
||||
match command {
|
||||
Command::CopyBufferToBuffer {
|
||||
source_buffer,
|
||||
source_offset,
|
||||
destination_buffer,
|
||||
destination_offset,
|
||||
size,
|
||||
} => render_context.copy_buffer_to_buffer(
|
||||
source_buffer,
|
||||
source_offset,
|
||||
destination_buffer,
|
||||
destination_offset,
|
||||
size,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
31
bevy_render/src/render_graph_2/edge.rs
Normal file
31
bevy_render/src/render_graph_2/edge.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use super::NodeId;
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum Edge {
|
||||
SlotEdge {
|
||||
input_node: NodeId,
|
||||
input_index: usize,
|
||||
output_node: NodeId,
|
||||
output_index: usize,
|
||||
},
|
||||
NodeEdge {
|
||||
input_node: NodeId,
|
||||
output_node: NodeId,
|
||||
},
|
||||
}
|
||||
|
||||
impl Edge {
|
||||
pub fn get_input_node(&self) -> NodeId {
|
||||
match self {
|
||||
Edge::SlotEdge { input_node, .. } => *input_node,
|
||||
Edge::NodeEdge { input_node, .. } => *input_node,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_output_node(&self) -> NodeId {
|
||||
match self {
|
||||
Edge::SlotEdge { output_node, .. } => *output_node,
|
||||
Edge::NodeEdge { output_node, .. } => *output_node,
|
||||
}
|
||||
}
|
||||
}
|
489
bevy_render/src/render_graph_2/graph.rs
Normal file
489
bevy_render/src/render_graph_2/graph.rs
Normal file
|
@ -0,0 +1,489 @@
|
|||
use super::{Node, NodeId, NodeLabel, NodeState, RenderGraphError, SlotLabel, SystemNode, Edge};
|
||||
use legion::prelude::{Executor, Resources, Schedulable};
|
||||
use std::{borrow::Cow, collections::HashMap, fmt::Debug};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RenderGraph2 {
|
||||
nodes: HashMap<NodeId, NodeState>,
|
||||
node_names: HashMap<Cow<'static, str>, NodeId>,
|
||||
new_node_systems: Vec<Box<dyn Schedulable>>,
|
||||
node_system_executor: Option<Executor>,
|
||||
}
|
||||
|
||||
impl RenderGraph2 {
|
||||
pub fn add_node<T>(&mut self, node: T) -> NodeId
|
||||
where
|
||||
T: Node,
|
||||
{
|
||||
let id = NodeId::new();
|
||||
self.nodes.insert(id, NodeState::new(id, node));
|
||||
id
|
||||
}
|
||||
|
||||
pub fn add_node_named<T>(&mut self, name: impl Into<Cow<'static, str>>, node: T) -> NodeId
|
||||
where
|
||||
T: Node,
|
||||
{
|
||||
let id = NodeId::new();
|
||||
let name = name.into();
|
||||
let mut node_state = NodeState::new(id, node);
|
||||
node_state.name = Some(name.clone());
|
||||
self.nodes.insert(id, node_state);
|
||||
self.node_names.insert(name, id);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn add_system_node<T>(&mut self, node: T, resources: &mut Resources) -> NodeId
|
||||
where
|
||||
T: SystemNode + 'static,
|
||||
{
|
||||
self.new_node_systems.push(node.get_system(resources));
|
||||
self.add_node(node)
|
||||
}
|
||||
|
||||
pub fn add_system_node_named<T>(&mut self, name: impl Into<Cow<'static, str>>, node: T, resources: &mut Resources) -> NodeId
|
||||
where
|
||||
T: SystemNode + 'static,
|
||||
{
|
||||
self.new_node_systems.push(node.get_system(resources));
|
||||
self.add_node_named(name, node)
|
||||
}
|
||||
|
||||
|
||||
pub fn get_node_state(
|
||||
&self,
|
||||
label: impl Into<NodeLabel>,
|
||||
) -> Result<&NodeState, RenderGraphError> {
|
||||
let label = label.into();
|
||||
let node_id = self.get_node_id(&label)?;
|
||||
self.nodes
|
||||
.get(&node_id)
|
||||
.ok_or_else(|| RenderGraphError::InvalidNode(label))
|
||||
}
|
||||
|
||||
pub fn get_node_state_mut(
|
||||
&mut self,
|
||||
label: impl Into<NodeLabel>,
|
||||
) -> Result<&mut NodeState, RenderGraphError> {
|
||||
let label = label.into();
|
||||
let node_id = self.get_node_id(&label)?;
|
||||
self.nodes
|
||||
.get_mut(&node_id)
|
||||
.ok_or_else(|| RenderGraphError::InvalidNode(label))
|
||||
}
|
||||
|
||||
pub fn get_node_id(&self, label: impl Into<NodeLabel>) -> Result<NodeId, RenderGraphError> {
|
||||
let label = label.into();
|
||||
match label {
|
||||
NodeLabel::Id(id) => Ok(id),
|
||||
NodeLabel::Name(ref name) => self
|
||||
.node_names
|
||||
.get(name)
|
||||
.cloned()
|
||||
.ok_or_else(|| RenderGraphError::InvalidNode(label)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_node<T>(&self, label: impl Into<NodeLabel>) -> Result<&T, RenderGraphError>
|
||||
where
|
||||
T: Node,
|
||||
{
|
||||
self.get_node_state(label).and_then(|n| n.node())
|
||||
}
|
||||
|
||||
pub fn get_node_mut<T>(
|
||||
&mut self,
|
||||
label: impl Into<NodeLabel>,
|
||||
) -> Result<&mut T, RenderGraphError>
|
||||
where
|
||||
T: Node,
|
||||
{
|
||||
self.get_node_state_mut(label).and_then(|n| n.node_mut())
|
||||
}
|
||||
|
||||
pub fn add_slot_edge(
|
||||
&mut self,
|
||||
output_node: impl Into<NodeLabel>,
|
||||
output_slot: impl Into<SlotLabel>,
|
||||
input_node: impl Into<NodeLabel>,
|
||||
input_slot: impl Into<SlotLabel>,
|
||||
) -> Result<(), RenderGraphError> {
|
||||
let output_node_id = self.get_node_id(output_node)?;
|
||||
let input_node_id = self.get_node_id(input_node)?;
|
||||
|
||||
let output_index = self
|
||||
.get_node_state(output_node_id)?
|
||||
.output_slots
|
||||
.get_slot_index(output_slot)?;
|
||||
let input_index = self
|
||||
.get_node_state(input_node_id)?
|
||||
.input_slots
|
||||
.get_slot_index(input_slot)?;
|
||||
|
||||
let edge = Edge::SlotEdge {
|
||||
output_node: output_node_id,
|
||||
output_index,
|
||||
input_node: input_node_id,
|
||||
input_index,
|
||||
};
|
||||
|
||||
self.validate_edge(&edge)?;
|
||||
|
||||
{
|
||||
let output_node = self.get_node_state_mut(output_node_id)?;
|
||||
output_node.add_output_edge(edge.clone())?;
|
||||
}
|
||||
let input_node = self.get_node_state_mut(input_node_id)?;
|
||||
input_node.add_input_edge(edge)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn add_node_edge(
|
||||
&mut self,
|
||||
output_node: impl Into<NodeLabel>,
|
||||
input_node: impl Into<NodeLabel>,
|
||||
) -> Result<(), RenderGraphError> {
|
||||
let output_node_id = self.get_node_id(output_node)?;
|
||||
let input_node_id = self.get_node_id(input_node)?;
|
||||
|
||||
let edge = Edge::NodeEdge {
|
||||
output_node: output_node_id,
|
||||
input_node: input_node_id,
|
||||
};
|
||||
|
||||
self.validate_edge(&edge)?;
|
||||
|
||||
{
|
||||
let output_node = self.get_node_state_mut(output_node_id)?;
|
||||
output_node.add_output_edge(edge.clone())?;
|
||||
}
|
||||
let input_node = self.get_node_state_mut(input_node_id)?;
|
||||
input_node.add_input_edge(edge)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_edge(&mut self, edge: &Edge) -> Result<(), RenderGraphError> {
|
||||
if self.has_edge(edge) {
|
||||
return Err(RenderGraphError::EdgeAlreadyExists(edge.clone()));
|
||||
}
|
||||
|
||||
match *edge {
|
||||
Edge::SlotEdge {
|
||||
output_node,
|
||||
output_index,
|
||||
input_node,
|
||||
input_index,
|
||||
} => {
|
||||
let output_node_state = self.get_node_state(output_node)?;
|
||||
let input_node_state = self.get_node_state(input_node)?;
|
||||
|
||||
let output_slot = output_node_state.output_slots.get_slot(output_index)?;
|
||||
let input_slot = input_node_state.input_slots.get_slot(input_index)?;
|
||||
|
||||
if let Some(Edge::SlotEdge {
|
||||
output_node: current_output_node,
|
||||
..
|
||||
}) = input_node_state.input_edges.iter().find(|e| {
|
||||
if let Edge::SlotEdge {
|
||||
input_index: current_input_index,
|
||||
..
|
||||
} = e
|
||||
{
|
||||
input_index == *current_input_index
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}) {
|
||||
return Err(RenderGraphError::NodeInputSlotAlreadyOccupied {
|
||||
node: input_node,
|
||||
input_slot: input_index,
|
||||
occupied_by_node: *current_output_node,
|
||||
});
|
||||
}
|
||||
|
||||
if output_slot.info.resource_type != input_slot.info.resource_type {
|
||||
return Err(RenderGraphError::MismatchedNodeSlots {
|
||||
output_node,
|
||||
output_slot: output_index,
|
||||
input_node,
|
||||
input_slot: input_index,
|
||||
});
|
||||
}
|
||||
}
|
||||
Edge::NodeEdge { .. } => { /* nothing to validate here */ }
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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());
|
||||
if let Ok(output_node_state) = output_node_state {
|
||||
if output_node_state.output_edges.contains(edge) {
|
||||
if let Ok(input_node_state) = input_node_state {
|
||||
if input_node_state.input_edges.contains(edge) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
pub fn take_executor(&mut self) -> Option<Executor> {
|
||||
// rebuild executor if there are new systems
|
||||
if self.new_node_systems.len() > 0 {
|
||||
let mut systems = self
|
||||
.node_system_executor
|
||||
.take()
|
||||
.map(|executor| executor.into_vec())
|
||||
.unwrap_or_else(|| Vec::new());
|
||||
for system in self.new_node_systems.drain(..) {
|
||||
systems.push(system);
|
||||
}
|
||||
|
||||
self.node_system_executor = Some(Executor::new(systems));
|
||||
}
|
||||
|
||||
self.node_system_executor.take()
|
||||
}
|
||||
|
||||
pub fn set_executor(&mut self, executor: Executor) {
|
||||
self.node_system_executor = Some(executor);
|
||||
}
|
||||
|
||||
pub fn iter_nodes(&self) -> impl Iterator<Item = &NodeState> {
|
||||
self.nodes.values()
|
||||
}
|
||||
|
||||
pub fn iter_nodes_mut(&mut self) -> impl Iterator<Item = &mut NodeState> {
|
||||
self.nodes.values_mut()
|
||||
}
|
||||
|
||||
pub fn iter_node_inputs(
|
||||
&self,
|
||||
label: impl Into<NodeLabel>,
|
||||
) -> Result<impl Iterator<Item = (&Edge, &NodeState)>, RenderGraphError> {
|
||||
let node = self.get_node_state(label)?;
|
||||
Ok(node
|
||||
.input_edges
|
||||
.iter()
|
||||
.map(|edge| (edge, edge.get_output_node()))
|
||||
.map(move |(edge, output_node_id)| {
|
||||
(edge, self.get_node_state(output_node_id).unwrap())
|
||||
}))
|
||||
}
|
||||
|
||||
pub fn iter_node_outputs(
|
||||
&self,
|
||||
label: impl Into<NodeLabel>,
|
||||
) -> Result<impl Iterator<Item = (&Edge, &NodeState)>, RenderGraphError> {
|
||||
let node = self.get_node_state(label)?;
|
||||
Ok(node
|
||||
.output_edges
|
||||
.iter()
|
||||
.map(|edge| (edge, edge.get_input_node()))
|
||||
.map(move |(edge, input_node_id)| (edge, self.get_node_state(input_node_id).unwrap())))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for RenderGraph2 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for node in self.iter_nodes() {
|
||||
writeln!(f, "{:?}", node.id)?;
|
||||
writeln!(f, " in: {:?}", node.input_slots)?;
|
||||
writeln!(f, " out: {:?}", node.output_slots)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::RenderGraph2;
|
||||
use crate::{
|
||||
render_graph_2::{Edge, Node, NodeId, RenderGraphError, ResourceSlotInfo, ResourceSlots},
|
||||
render_resource::ResourceInfo,
|
||||
renderer_2::RenderContext,
|
||||
};
|
||||
use legion::prelude::{Resources, World};
|
||||
use std::{collections::HashSet, iter::FromIterator};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestNode {
|
||||
inputs: Vec<ResourceSlotInfo>,
|
||||
outputs: Vec<ResourceSlotInfo>,
|
||||
}
|
||||
|
||||
impl TestNode {
|
||||
pub fn new(inputs: usize, outputs: usize) -> Self {
|
||||
TestNode {
|
||||
inputs: (0..inputs)
|
||||
.map(|i| ResourceSlotInfo {
|
||||
name: format!("in_{}", i).into(),
|
||||
resource_type: ResourceInfo::Texture,
|
||||
})
|
||||
.collect(),
|
||||
outputs: (0..outputs)
|
||||
.map(|i| ResourceSlotInfo {
|
||||
name: format!("out_{}", i).into(),
|
||||
resource_type: ResourceInfo::Texture,
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for TestNode {
|
||||
fn input(&self) -> &[ResourceSlotInfo] {
|
||||
&self.inputs
|
||||
}
|
||||
|
||||
fn output(&self) -> &[ResourceSlotInfo] {
|
||||
&self.outputs
|
||||
}
|
||||
fn update(
|
||||
&mut self,
|
||||
_: &World,
|
||||
_: &Resources,
|
||||
_: &mut dyn RenderContext,
|
||||
_: &ResourceSlots,
|
||||
_: &mut ResourceSlots,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_graph_edges() {
|
||||
let mut graph = RenderGraph2::default();
|
||||
let a_id = graph.add_node_named("A", TestNode::new(0, 1));
|
||||
let b_id = graph.add_node_named("B", TestNode::new(0, 1));
|
||||
let c_id = graph.add_node_named("C", TestNode::new(1, 1));
|
||||
let d_id = graph.add_node_named("D", TestNode::new(1, 0));
|
||||
|
||||
graph.add_slot_edge("A", "out_0", "C", "in_0").unwrap();
|
||||
graph.add_node_edge("B", "C").unwrap();
|
||||
graph.add_slot_edge("C", 0, "D", 0).unwrap();
|
||||
|
||||
fn input_nodes(name: &'static str, graph: &RenderGraph2) -> HashSet<NodeId> {
|
||||
graph
|
||||
.iter_node_inputs(name)
|
||||
.unwrap()
|
||||
.map(|(_edge, node)| node.id)
|
||||
.collect::<HashSet<NodeId>>()
|
||||
}
|
||||
|
||||
fn output_nodes(name: &'static str, graph: &RenderGraph2) -> HashSet<NodeId> {
|
||||
graph
|
||||
.iter_node_outputs(name)
|
||||
.unwrap()
|
||||
.map(|(_edge, node)| node.id)
|
||||
.collect::<HashSet<NodeId>>()
|
||||
}
|
||||
|
||||
assert!(input_nodes("A", &graph).is_empty(), "A has no inputs");
|
||||
assert!(
|
||||
output_nodes("A", &graph) == HashSet::from_iter(vec![c_id]),
|
||||
"A outputs to C"
|
||||
);
|
||||
|
||||
assert!(input_nodes("B", &graph).is_empty(), "B has no inputs");
|
||||
assert!(
|
||||
output_nodes("B", &graph) == HashSet::from_iter(vec![c_id]),
|
||||
"B outputs to C"
|
||||
);
|
||||
|
||||
assert!(
|
||||
input_nodes("C", &graph) == HashSet::from_iter(vec![a_id, b_id]),
|
||||
"A and B input to C"
|
||||
);
|
||||
assert!(
|
||||
output_nodes("C", &graph) == HashSet::from_iter(vec![d_id]),
|
||||
"C outputs to D"
|
||||
);
|
||||
|
||||
assert!(
|
||||
input_nodes("D", &graph) == HashSet::from_iter(vec![c_id]),
|
||||
"C inputs to D"
|
||||
);
|
||||
assert!(output_nodes("D", &graph).is_empty(), "D has no outputs");
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_get_node_typed() {
|
||||
struct MyNode {
|
||||
value: usize,
|
||||
}
|
||||
|
||||
impl Node for MyNode {
|
||||
fn update(
|
||||
&mut self,
|
||||
_: &World,
|
||||
_: &Resources,
|
||||
_: &mut dyn RenderContext,
|
||||
_: &ResourceSlots,
|
||||
_: &mut ResourceSlots,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
let mut graph = RenderGraph2::default();
|
||||
|
||||
graph.add_node_named("A", MyNode { value: 42 });
|
||||
|
||||
let node: &MyNode = graph.get_node("A").unwrap();
|
||||
assert_eq!(node.value, 42, "node value matches");
|
||||
|
||||
let result: Result<&TestNode, RenderGraphError> = graph.get_node("A");
|
||||
assert_eq!(
|
||||
result.unwrap_err(),
|
||||
RenderGraphError::WrongNodeType,
|
||||
"expect a wrong node type error"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_slot_already_occupied() {
|
||||
let mut graph = RenderGraph2::default();
|
||||
|
||||
graph.add_node_named("A", TestNode::new(0, 1));
|
||||
graph.add_node_named("B", TestNode::new(0, 1));
|
||||
graph.add_node_named("C", TestNode::new(1, 1));
|
||||
|
||||
graph.add_slot_edge("A", 0, "C", 0).unwrap();
|
||||
assert_eq!(
|
||||
graph.add_slot_edge("B", 0, "C", 0),
|
||||
Err(RenderGraphError::NodeInputSlotAlreadyOccupied {
|
||||
node: graph.get_node_id("C").unwrap(),
|
||||
input_slot: 0,
|
||||
occupied_by_node: graph.get_node_id("A").unwrap(),
|
||||
}),
|
||||
"Adding to a slot that is already occupied should return an error"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_edge_already_exists() {
|
||||
let mut graph = RenderGraph2::default();
|
||||
|
||||
graph.add_node_named("A", TestNode::new(0, 1));
|
||||
graph.add_node_named("B", TestNode::new(1, 0));
|
||||
|
||||
graph.add_slot_edge("A", 0, "B", 0).unwrap();
|
||||
assert_eq!(
|
||||
graph.add_slot_edge("A", 0, "B", 0),
|
||||
Err(RenderGraphError::EdgeAlreadyExists(Edge::SlotEdge {
|
||||
output_node: graph.get_node_id("A").unwrap(),
|
||||
output_index: 0,
|
||||
input_node: graph.get_node_id("B").unwrap(),
|
||||
input_index: 0,
|
||||
})),
|
||||
"Adding to a duplicate edge should return an error"
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,295 +1,40 @@
|
|||
pub mod nodes;
|
||||
mod command;
|
||||
mod graph;
|
||||
mod node;
|
||||
mod edge;
|
||||
mod node_slot;
|
||||
mod schedule;
|
||||
pub use command::*;
|
||||
pub use graph::*;
|
||||
pub use node::*;
|
||||
pub use edge::*;
|
||||
pub use node_slot::*;
|
||||
pub use schedule::*;
|
||||
|
||||
use crate::{
|
||||
render_resource::{RenderResource, ResourceInfo},
|
||||
renderer_2::RenderContext,
|
||||
};
|
||||
use legion::prelude::{Executor, Resources, Schedulable, World};
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
use thiserror::Error;
|
||||
|
||||
pub enum Command {
|
||||
CopyBufferToBuffer {
|
||||
source_buffer: RenderResource,
|
||||
source_offset: u64,
|
||||
destination_buffer: RenderResource,
|
||||
destination_offset: u64,
|
||||
size: u64,
|
||||
#[derive(Error, Debug, Eq, PartialEq)]
|
||||
pub enum RenderGraphError {
|
||||
#[error("Node does not exist")]
|
||||
InvalidNode(NodeLabel),
|
||||
#[error("Node slot does not exist")]
|
||||
InvalidNodeSlot(SlotLabel),
|
||||
#[error("Node does not match the given type")]
|
||||
WrongNodeType,
|
||||
#[error("Attempted to connect a node output slot to an incompatible input node slot")]
|
||||
MismatchedNodeSlots {
|
||||
output_node: NodeId,
|
||||
output_slot: usize,
|
||||
input_node: NodeId,
|
||||
input_slot: usize,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct CommandQueue {
|
||||
queue: Arc<Mutex<VecDeque<Command>>>,
|
||||
}
|
||||
|
||||
impl CommandQueue {
|
||||
fn push(&mut self, command: Command) {
|
||||
self.queue.lock().unwrap().push_front(command);
|
||||
}
|
||||
|
||||
pub fn copy_buffer_to_buffer(
|
||||
&mut self,
|
||||
source_buffer: RenderResource,
|
||||
source_offset: u64,
|
||||
destination_buffer: RenderResource,
|
||||
destination_offset: u64,
|
||||
size: u64,
|
||||
) {
|
||||
self.push(Command::CopyBufferToBuffer {
|
||||
source_buffer,
|
||||
source_offset,
|
||||
destination_buffer,
|
||||
destination_offset,
|
||||
size,
|
||||
});
|
||||
}
|
||||
|
||||
pub fn execute(&mut self, render_context: &mut dyn RenderContext) {
|
||||
for command in self.queue.lock().unwrap().drain(..) {
|
||||
match command {
|
||||
Command::CopyBufferToBuffer {
|
||||
source_buffer,
|
||||
source_offset,
|
||||
destination_buffer,
|
||||
destination_offset,
|
||||
size,
|
||||
} => render_context.copy_buffer_to_buffer(
|
||||
source_buffer,
|
||||
source_offset,
|
||||
destination_buffer,
|
||||
destination_offset,
|
||||
size,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct NodeId(Uuid);
|
||||
|
||||
impl NodeId {
|
||||
fn new() -> Self {
|
||||
NodeId(Uuid::new_v4())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ResourceBinding {
|
||||
pub resource: Option<RenderResource>,
|
||||
pub slot: ResourceSlot,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ResourceBindings {
|
||||
bindings: Vec<ResourceBinding>,
|
||||
}
|
||||
|
||||
impl ResourceBindings {
|
||||
pub fn set(&mut self, index: usize, resource: RenderResource) {
|
||||
self.bindings[index].resource = Some(resource);
|
||||
}
|
||||
|
||||
pub fn set_named(&mut self, name: &str, resource: RenderResource) {
|
||||
let binding = self
|
||||
.bindings
|
||||
.iter_mut()
|
||||
.find(|b| b.slot.name == name)
|
||||
.expect("Name not found");
|
||||
binding.resource = Some(resource);
|
||||
}
|
||||
|
||||
pub fn get(&self, index: usize) -> Option<RenderResource> {
|
||||
self.bindings
|
||||
.get(index)
|
||||
.and_then(|binding| binding.resource)
|
||||
}
|
||||
|
||||
pub fn get_named(&self, name: &str) -> Option<RenderResource> {
|
||||
self.bindings
|
||||
.iter()
|
||||
.find(|b| b.slot.name == name)
|
||||
.and_then(|binding| binding.resource)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ResourceSlot> for ResourceBinding {
|
||||
fn from(slot: &ResourceSlot) -> Self {
|
||||
ResourceBinding {
|
||||
resource: None,
|
||||
slot: slot.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[ResourceSlot]> for ResourceBindings {
|
||||
fn from(slots: &[ResourceSlot]) -> Self {
|
||||
ResourceBindings {
|
||||
bindings: slots
|
||||
.iter()
|
||||
.map(|s| s.into())
|
||||
.collect::<Vec<ResourceBinding>>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ResourceSlot {
|
||||
name: &'static str,
|
||||
resource_type: ResourceInfo,
|
||||
}
|
||||
|
||||
impl ResourceSlot {
|
||||
pub const fn new(name: &'static str, resource_type: ResourceInfo) -> Self {
|
||||
ResourceSlot {
|
||||
name,
|
||||
resource_type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Node: Send + Sync + 'static {
|
||||
fn input(&self) -> &[ResourceSlot] {
|
||||
&[]
|
||||
}
|
||||
|
||||
fn output(&self) -> &[ResourceSlot] {
|
||||
&[]
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
world: &World,
|
||||
resources: &Resources,
|
||||
render_context: &mut dyn RenderContext,
|
||||
input: &ResourceBindings,
|
||||
output: &mut ResourceBindings,
|
||||
);
|
||||
}
|
||||
|
||||
pub struct NodeState {
|
||||
pub node: Box<dyn Node>,
|
||||
pub input: ResourceBindings,
|
||||
pub output: ResourceBindings,
|
||||
}
|
||||
|
||||
impl NodeState {
|
||||
pub fn new<T>(node: T) -> Self
|
||||
where
|
||||
T: Node,
|
||||
{
|
||||
NodeState {
|
||||
input: ResourceBindings::from(node.input()),
|
||||
output: ResourceBindings::from(node.output()),
|
||||
node: Box::new(node),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait SystemNode: Node {
|
||||
fn get_system(&self, resources: &mut Resources) -> Box<dyn Schedulable>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RenderGraph2 {
|
||||
nodes: HashMap<NodeId, NodeState>,
|
||||
new_systems: Vec<Box<dyn Schedulable>>,
|
||||
system_executor: Option<Executor>,
|
||||
}
|
||||
|
||||
impl RenderGraph2 {
|
||||
pub fn add_node<T>(&mut self, node: T) -> NodeId
|
||||
where
|
||||
T: Node + 'static,
|
||||
{
|
||||
let id = NodeId::new();
|
||||
self.nodes.insert(id, NodeState::new(node));
|
||||
id
|
||||
}
|
||||
|
||||
pub fn add_system_node<T>(&mut self, node: T, resources: &mut Resources) -> NodeId
|
||||
where
|
||||
T: SystemNode + 'static,
|
||||
{
|
||||
let id = NodeId::new();
|
||||
self.new_systems.push(node.get_system(resources));
|
||||
self.nodes.insert(id, NodeState::new(node));
|
||||
id
|
||||
}
|
||||
|
||||
pub fn take_executor(&mut self) -> Option<Executor> {
|
||||
// rebuild executor if there are new systems
|
||||
if self.new_systems.len() > 0 {
|
||||
let mut systems = self
|
||||
.system_executor
|
||||
.take()
|
||||
.map(|executor| executor.into_vec())
|
||||
.unwrap_or_else(|| Vec::new());
|
||||
for system in self.new_systems.drain(..) {
|
||||
systems.push(system);
|
||||
}
|
||||
|
||||
self.system_executor = Some(Executor::new(systems));
|
||||
}
|
||||
|
||||
self.system_executor.take()
|
||||
}
|
||||
|
||||
pub fn set_executor(&mut self, executor: Executor) {
|
||||
self.system_executor = Some(executor);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct Stage<'a> {
|
||||
ordered_jobs: Vec<OrderedJob<'a>>,
|
||||
}
|
||||
|
||||
impl<'a> Stage<'a> {
|
||||
pub fn add(&mut self, job: OrderedJob<'a>) {
|
||||
self.ordered_jobs.push(job);
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item=&mut OrderedJob<'a>> {
|
||||
self.ordered_jobs.iter_mut()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct OrderedJob<'a> {
|
||||
node_states: Vec<&'a mut NodeState>,
|
||||
}
|
||||
|
||||
impl<'a> OrderedJob<'a> {
|
||||
pub fn add(&mut self, node_state: &'a mut NodeState) {
|
||||
self.node_states.push(node_state);
|
||||
}
|
||||
|
||||
pub fn iter_mut(&mut self) -> impl Iterator<Item=&mut &'a mut NodeState> {
|
||||
self.node_states.iter_mut()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait RenderGraphScheduler {
|
||||
fn get_stages<'a>(&mut self, render_graph: &'a mut RenderGraph2) -> Vec<Stage<'a>>;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LinearScheduler;
|
||||
|
||||
impl RenderGraphScheduler for LinearScheduler {
|
||||
fn get_stages<'a>(&mut self, render_graph: &'a mut RenderGraph2) -> Vec<Stage<'a>> {
|
||||
let mut stage = Stage::default();
|
||||
let mut job = OrderedJob::default();
|
||||
for node_state in render_graph.nodes.values_mut() {
|
||||
job.add(node_state);
|
||||
}
|
||||
|
||||
stage.ordered_jobs.push(job);
|
||||
|
||||
vec![stage]
|
||||
}
|
||||
#[error("Attempted to add an edge that already exists")]
|
||||
EdgeAlreadyExists(Edge),
|
||||
#[error("Node has an unconnected input slot.")]
|
||||
UnconnectedNodeInputSlot { node: NodeId, input_slot: usize },
|
||||
#[error("Node has an unconnected output slot.")]
|
||||
UnconnectedNodeOutputSlot { node: NodeId, output_slot: usize },
|
||||
#[error("Node input slot already occupied")]
|
||||
NodeInputSlotAlreadyOccupied { node: NodeId, input_slot: usize, occupied_by_node: NodeId },
|
||||
}
|
193
bevy_render/src/render_graph_2/node.rs
Normal file
193
bevy_render/src/render_graph_2/node.rs
Normal file
|
@ -0,0 +1,193 @@
|
|||
use super::{Edge, RenderGraphError, ResourceSlotInfo, ResourceSlots};
|
||||
use crate::renderer_2::RenderContext;
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
use legion::prelude::{Resources, Schedulable, World};
|
||||
use std::{borrow::Cow, fmt::Debug};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct NodeId(Uuid);
|
||||
|
||||
impl NodeId {
|
||||
pub fn new() -> Self {
|
||||
NodeId(Uuid::new_v4())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait Node: Downcast + Send + Sync + 'static {
|
||||
fn input(&self) -> &[ResourceSlotInfo] {
|
||||
&[]
|
||||
}
|
||||
|
||||
fn output(&self) -> &[ResourceSlotInfo] {
|
||||
&[]
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
world: &World,
|
||||
resources: &Resources,
|
||||
render_context: &mut dyn RenderContext,
|
||||
input: &ResourceSlots,
|
||||
output: &mut ResourceSlots,
|
||||
);
|
||||
}
|
||||
|
||||
impl_downcast!(Node);
|
||||
|
||||
pub trait SystemNode: Node {
|
||||
fn get_system(&self, resources: &mut Resources) -> Box<dyn Schedulable>;
|
||||
}
|
||||
|
||||
pub struct NodeState {
|
||||
pub id: NodeId,
|
||||
pub name: Option<Cow<'static, str>>,
|
||||
pub node: Box<dyn Node>,
|
||||
pub input_slots: ResourceSlots,
|
||||
pub output_slots: ResourceSlots,
|
||||
pub input_edges: Vec<Edge>,
|
||||
pub output_edges: Vec<Edge>,
|
||||
}
|
||||
|
||||
impl Debug for NodeState {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
writeln!(f, "{:?} ({:?})", self.id, self.name)
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeState {
|
||||
pub fn new<T>(id: NodeId, node: T) -> Self
|
||||
where
|
||||
T: Node,
|
||||
{
|
||||
NodeState {
|
||||
id,
|
||||
name: None,
|
||||
input_slots: ResourceSlots::from(node.input()),
|
||||
output_slots: ResourceSlots::from(node.output()),
|
||||
node: Box::new(node),
|
||||
input_edges: Vec::new(),
|
||||
output_edges: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn node<T>(&self) -> Result<&T, RenderGraphError>
|
||||
where
|
||||
T: Node,
|
||||
{
|
||||
self.node
|
||||
.downcast_ref::<T>()
|
||||
.ok_or_else(|| RenderGraphError::WrongNodeType)
|
||||
}
|
||||
|
||||
pub fn node_mut<T>(&mut self) -> Result<&mut T, RenderGraphError>
|
||||
where
|
||||
T: Node,
|
||||
{
|
||||
self.node
|
||||
.downcast_mut::<T>()
|
||||
.ok_or_else(|| RenderGraphError::WrongNodeType)
|
||||
}
|
||||
|
||||
pub(crate) fn add_input_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> {
|
||||
if self.has_input_edge(&edge) {
|
||||
return Err(RenderGraphError::EdgeAlreadyExists(edge.clone()));
|
||||
}
|
||||
self.input_edges.push(edge);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn add_output_edge(&mut self, edge: Edge) -> Result<(), RenderGraphError> {
|
||||
if self.has_output_edge(&edge) {
|
||||
return Err(RenderGraphError::EdgeAlreadyExists(edge.clone()));
|
||||
}
|
||||
self.output_edges.push(edge);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn has_input_edge(&self, edge: &Edge) -> bool {
|
||||
self.input_edges.contains(edge)
|
||||
}
|
||||
|
||||
pub fn has_output_edge(&self, edge: &Edge) -> bool {
|
||||
self.output_edges.contains(edge)
|
||||
}
|
||||
|
||||
pub fn get_input_slot_edge(&self, index: usize) -> Result<&Edge, RenderGraphError> {
|
||||
self.input_edges
|
||||
.iter()
|
||||
.find(|e| {
|
||||
if let Edge::SlotEdge { input_index, .. } = e {
|
||||
*input_index == index
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| RenderGraphError::UnconnectedNodeInputSlot {
|
||||
input_slot: index,
|
||||
node: self.id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_output_slot_edge(&self, index: usize) -> Result<&Edge, RenderGraphError> {
|
||||
self.output_edges
|
||||
.iter()
|
||||
.find(|e| {
|
||||
if let Edge::SlotEdge { output_index, .. } = e {
|
||||
*output_index == index
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
.ok_or_else(|| RenderGraphError::UnconnectedNodeOutputSlot {
|
||||
output_slot: index,
|
||||
node: self.id,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn validate_output_slots(&self) -> Result<(), RenderGraphError> {
|
||||
for i in 0..self.output_slots.len() {
|
||||
self.get_output_slot_edge(i)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validate_input_slots(&self) -> Result<(), RenderGraphError> {
|
||||
for i in 0..self.input_slots.len() {
|
||||
self.get_input_slot_edge(i)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum NodeLabel {
|
||||
Id(NodeId),
|
||||
Name(Cow<'static, str>),
|
||||
}
|
||||
|
||||
impl From<&NodeLabel> for NodeLabel {
|
||||
fn from(value: &NodeLabel) -> Self {
|
||||
value.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for NodeLabel {
|
||||
fn from(value: String) -> Self {
|
||||
NodeLabel::Name(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for NodeLabel {
|
||||
fn from(value: &'static str) -> Self {
|
||||
NodeLabel::Name(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NodeId> for NodeLabel {
|
||||
fn from(value: NodeId) -> Self {
|
||||
NodeLabel::Id(value)
|
||||
}
|
||||
}
|
134
bevy_render/src/render_graph_2/node_slot.rs
Normal file
134
bevy_render/src/render_graph_2/node_slot.rs
Normal file
|
@ -0,0 +1,134 @@
|
|||
use super::RenderGraphError;
|
||||
use crate::render_resource::{RenderResource, ResourceInfo};
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ResourceSlot {
|
||||
pub resource: Option<RenderResource>,
|
||||
pub info: ResourceSlotInfo,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct ResourceSlots {
|
||||
slots: Vec<ResourceSlot>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum SlotLabel {
|
||||
Index(usize),
|
||||
Name(Cow<'static, str>),
|
||||
}
|
||||
|
||||
impl From<&SlotLabel> for SlotLabel {
|
||||
fn from(value: &SlotLabel) -> Self {
|
||||
value.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<String> for SlotLabel {
|
||||
fn from(value: String) -> Self {
|
||||
SlotLabel::Name(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&'static str> for SlotLabel {
|
||||
fn from(value: &'static str) -> Self {
|
||||
SlotLabel::Name(value.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for SlotLabel {
|
||||
fn from(value: usize) -> Self {
|
||||
SlotLabel::Index(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceSlots {
|
||||
pub fn set(&mut self, label: impl Into<SlotLabel>, resource: RenderResource) {
|
||||
let mut slot = self.get_slot_mut(label).unwrap();
|
||||
slot.resource = Some(resource);
|
||||
}
|
||||
|
||||
pub fn get(&self, label: impl Into<SlotLabel>) -> Option<RenderResource> {
|
||||
let slot = self.get_slot(label).unwrap();
|
||||
slot.resource.clone()
|
||||
}
|
||||
|
||||
pub fn get_slot(
|
||||
&self,
|
||||
label: impl Into<SlotLabel>,
|
||||
) -> Result<&ResourceSlot, RenderGraphError> {
|
||||
let label = label.into();
|
||||
let index = self.get_slot_index(&label)?;
|
||||
self.slots
|
||||
.get(index)
|
||||
.ok_or_else(|| RenderGraphError::InvalidNodeSlot(label))
|
||||
}
|
||||
|
||||
pub fn get_slot_mut(
|
||||
&mut self,
|
||||
label: impl Into<SlotLabel>,
|
||||
) -> Result<&mut ResourceSlot, RenderGraphError> {
|
||||
let label = label.into();
|
||||
let index = self.get_slot_index(&label)?;
|
||||
self.slots
|
||||
.get_mut(index)
|
||||
.ok_or_else(|| RenderGraphError::InvalidNodeSlot(label))
|
||||
}
|
||||
|
||||
pub fn get_slot_index(&self, label: impl Into<SlotLabel>) -> Result<usize, RenderGraphError> {
|
||||
let label = label.into();
|
||||
match label {
|
||||
SlotLabel::Index(index) => Ok(index),
|
||||
SlotLabel::Name(ref name) => self
|
||||
.slots
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_i, s)| s.info.name == *name)
|
||||
.map(|(i, _s)| i)
|
||||
.ok_or_else(|| RenderGraphError::InvalidNodeSlot(label)),
|
||||
}
|
||||
}
|
||||
pub fn iter(&self) -> impl Iterator<Item = &ResourceSlot> {
|
||||
self.slots.iter()
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.slots.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&ResourceSlotInfo> for ResourceSlot {
|
||||
fn from(slot: &ResourceSlotInfo) -> Self {
|
||||
ResourceSlot {
|
||||
resource: None,
|
||||
info: slot.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&[ResourceSlotInfo]> for ResourceSlots {
|
||||
fn from(slots: &[ResourceSlotInfo]) -> Self {
|
||||
ResourceSlots {
|
||||
slots: slots
|
||||
.iter()
|
||||
.map(|s| s.into())
|
||||
.collect::<Vec<ResourceSlot>>(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ResourceSlotInfo {
|
||||
pub name: Cow<'static, str>,
|
||||
pub resource_type: ResourceInfo,
|
||||
}
|
||||
|
||||
impl ResourceSlotInfo {
|
||||
pub fn new(name: impl Into<Cow<'static, str>>, resource_type: ResourceInfo) -> Self {
|
||||
ResourceSlotInfo {
|
||||
name: name.into(),
|
||||
resource_type,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,8 +2,10 @@ mod camera_node;
|
|||
mod camera2d_node;
|
||||
mod window_texture_node;
|
||||
mod window_swapchain_node;
|
||||
mod pass_node;
|
||||
|
||||
pub use camera_node::*;
|
||||
pub use camera2d_node::*;
|
||||
pub use window_texture_node::*;
|
||||
pub use window_swapchain_node::*;
|
||||
pub use window_swapchain_node::*;
|
||||
pub use pass_node::*;
|
|
@ -3,7 +3,7 @@ use bevy_window::WindowResized;
|
|||
|
||||
use crate::{
|
||||
camera::{ActiveCamera2d, Camera},
|
||||
render_graph_2::{CommandQueue, Node, SystemNode, ResourceBindings},
|
||||
render_graph_2::{CommandQueue, Node, SystemNode, ResourceSlots},
|
||||
render_resource::{resource_name, BufferInfo, BufferUsage, RenderResourceAssignments},
|
||||
renderer_2::{GlobalRenderResourceContext, RenderContext},
|
||||
};
|
||||
|
@ -23,8 +23,8 @@ impl Node for Camera2dNode {
|
|||
_world: &World,
|
||||
_resources: &Resources,
|
||||
render_context: &mut dyn RenderContext,
|
||||
_input: &ResourceBindings,
|
||||
_output: &mut ResourceBindings,
|
||||
_input: &ResourceSlots,
|
||||
_output: &mut ResourceSlots,
|
||||
) {
|
||||
self.command_queue.execute(render_context);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
render_graph_2::{CommandQueue, Node, SystemNode, ResourceBindings},
|
||||
render_graph_2::{CommandQueue, Node, SystemNode, ResourceSlots},
|
||||
render_resource::{resource_name, BufferInfo, BufferUsage, RenderResourceAssignments},
|
||||
renderer_2::{GlobalRenderResourceContext, RenderContext},
|
||||
ActiveCamera, Camera,
|
||||
|
@ -22,8 +22,8 @@ impl Node for CameraNode {
|
|||
_world: &World,
|
||||
_resources: &Resources,
|
||||
render_context: &mut dyn RenderContext,
|
||||
_input: &ResourceBindings,
|
||||
_output: &mut ResourceBindings,
|
||||
_input: &ResourceSlots,
|
||||
_output: &mut ResourceSlots,
|
||||
) {
|
||||
self.command_queue.execute(render_context);
|
||||
}
|
||||
|
|
109
bevy_render/src/render_graph_2/nodes/pass_node.rs
Normal file
109
bevy_render/src/render_graph_2/nodes/pass_node.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
use crate::{
|
||||
pass::{TextureAttachment, PassDescriptor},
|
||||
pipeline::{PipelineCompiler, PipelineDescriptor},
|
||||
render_graph_2::{Node, ResourceSlots, ResourceSlotInfo},
|
||||
render_resource::{RenderResourceAssignments, ResourceInfo},
|
||||
renderer_2::RenderContext, draw_target::DrawTarget,
|
||||
};
|
||||
use bevy_asset::{AssetStorage, Handle};
|
||||
use legion::prelude::*;
|
||||
|
||||
pub struct PassNode {
|
||||
descriptor: PassDescriptor,
|
||||
pipelines: Vec<(Handle<PipelineDescriptor>, Vec<Box<dyn DrawTarget>>)>,
|
||||
inputs: Vec<ResourceSlotInfo>,
|
||||
color_attachment_input_indices: Vec<Option<usize>>,
|
||||
depth_stencil_attachment_input_index: Option<usize>,
|
||||
}
|
||||
|
||||
impl PassNode {
|
||||
pub fn new(descriptor: PassDescriptor) -> Self {
|
||||
let mut inputs = Vec::new();
|
||||
let mut color_attachment_input_indices = Vec::new();
|
||||
for color_attachment in descriptor.color_attachments.iter() {
|
||||
if let TextureAttachment::Input(ref name) = color_attachment.attachment {
|
||||
inputs.push(ResourceSlotInfo::new(name.to_string(), ResourceInfo::Texture));
|
||||
color_attachment_input_indices.push(Some(inputs.len()));
|
||||
} else {
|
||||
color_attachment_input_indices.push(None);
|
||||
}
|
||||
}
|
||||
|
||||
let mut depth_stencil_attachment_input_index = None;
|
||||
if let Some(ref depth_stencil_attachment)= descriptor.depth_stencil_attachment {
|
||||
if let TextureAttachment::Input(ref name) = depth_stencil_attachment.attachment {
|
||||
inputs.push(ResourceSlotInfo::new(name.to_string(), ResourceInfo::Texture));
|
||||
depth_stencil_attachment_input_index = Some(inputs.len());
|
||||
}
|
||||
}
|
||||
|
||||
PassNode {
|
||||
descriptor,
|
||||
pipelines: Vec::new(),
|
||||
inputs,
|
||||
color_attachment_input_indices,
|
||||
depth_stencil_attachment_input_index,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_pipeline(&mut self, pipeline_handle: Handle<PipelineDescriptor>, draw_targets: Vec<Box<dyn DrawTarget>>) {
|
||||
self.pipelines.push((pipeline_handle, draw_targets));
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for PassNode {
|
||||
fn input(&self) -> &[ResourceSlotInfo] {
|
||||
&self.inputs
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
world: &World,
|
||||
resources: &Resources,
|
||||
render_context: &mut dyn RenderContext,
|
||||
input: &ResourceSlots,
|
||||
_output: &mut ResourceSlots,
|
||||
) {
|
||||
let render_resource_assignments = resources.get::<RenderResourceAssignments>().unwrap();
|
||||
let pipeline_compiler = resources.get::<PipelineCompiler>().unwrap();
|
||||
let pipeline_storage = resources.get::<AssetStorage<PipelineDescriptor>>().unwrap();
|
||||
|
||||
for (i, color_attachment) in self.descriptor.color_attachments.iter_mut().enumerate() {
|
||||
if let Some(input_index) = self.color_attachment_input_indices[i] {
|
||||
color_attachment.attachment = TextureAttachment::RenderResource(input.get(input_index).unwrap());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if let Some(input_index) = self.depth_stencil_attachment_input_index {
|
||||
self.descriptor.depth_stencil_attachment.as_mut().unwrap().attachment = TextureAttachment::RenderResource(input.get(input_index).unwrap());
|
||||
}
|
||||
|
||||
render_context.begin_pass(
|
||||
&self.descriptor,
|
||||
&render_resource_assignments,
|
||||
&mut |render_pass| {
|
||||
for (pipeline_handle, draw_targets) in self.pipelines.iter() {
|
||||
if let Some(compiled_pipelines_iter) =
|
||||
pipeline_compiler.iter_compiled_pipelines(*pipeline_handle)
|
||||
{
|
||||
for compiled_pipeline_handle in compiled_pipelines_iter {
|
||||
let pipeline_descriptor =
|
||||
pipeline_storage.get(compiled_pipeline_handle).unwrap();
|
||||
render_pass.set_pipeline(*compiled_pipeline_handle);
|
||||
for draw_target in draw_targets.iter() {
|
||||
draw_target.draw(
|
||||
world,
|
||||
resources,
|
||||
render_pass,
|
||||
*compiled_pipeline_handle,
|
||||
pipeline_descriptor,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
use crate::{
|
||||
render_graph_2::{Node, ResourceBindings, ResourceSlot},
|
||||
render_graph_2::{Node, ResourceSlots, ResourceSlotInfo},
|
||||
render_resource::ResourceInfo,
|
||||
renderer_2::RenderContext,
|
||||
};
|
||||
use bevy_app::{EventReader, Events};
|
||||
use bevy_window::{WindowCreated, WindowId, WindowResized, Windows};
|
||||
use legion::prelude::*;
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub enum SwapChainWindowSource {
|
||||
Primary,
|
||||
|
@ -25,6 +26,7 @@ pub struct WindowSwapChainNode {
|
|||
}
|
||||
|
||||
impl WindowSwapChainNode {
|
||||
pub const OUT_TEXTURE: &'static str = "texture";
|
||||
pub fn new(
|
||||
source_window: SwapChainWindowSource,
|
||||
window_created_event_reader: EventReader<WindowCreated>,
|
||||
|
@ -39,11 +41,11 @@ impl WindowSwapChainNode {
|
|||
}
|
||||
|
||||
impl Node for WindowSwapChainNode {
|
||||
fn output(&self) -> &[ResourceSlot] {
|
||||
static OUTPUT: &[ResourceSlot] = &[ResourceSlot::new(
|
||||
"swapchain_texture",
|
||||
ResourceInfo::Texture,
|
||||
)];
|
||||
fn output(&self) -> &[ResourceSlotInfo] {
|
||||
static OUTPUT: &[ResourceSlotInfo] = &[ResourceSlotInfo {
|
||||
name: Cow::Borrowed(WindowSwapChainNode::OUT_TEXTURE),
|
||||
resource_type: ResourceInfo::Texture,
|
||||
}];
|
||||
OUTPUT
|
||||
}
|
||||
|
||||
|
@ -52,8 +54,8 @@ impl Node for WindowSwapChainNode {
|
|||
_world: &World,
|
||||
resources: &Resources,
|
||||
render_context: &mut dyn RenderContext,
|
||||
_input: &ResourceBindings,
|
||||
output: &mut ResourceBindings,
|
||||
_input: &ResourceSlots,
|
||||
output: &mut ResourceSlots,
|
||||
) {
|
||||
const WINDOW_TEXTURE: usize = 0;
|
||||
let window_created_events = resources.get::<Events<WindowCreated>>().unwrap();
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::{
|
||||
render_graph_2::{Node, ResourceBindings, ResourceSlot},
|
||||
render_graph_2::{Node, ResourceSlots, ResourceSlotInfo},
|
||||
render_resource::ResourceInfo,
|
||||
renderer_2::RenderContext,
|
||||
texture::TextureDescriptor,
|
||||
|
@ -7,6 +7,7 @@ use crate::{
|
|||
use bevy_app::{EventReader, Events};
|
||||
use bevy_window::WindowResized;
|
||||
use legion::prelude::*;
|
||||
use std::borrow::Cow;
|
||||
|
||||
pub struct WindowTextureNode {
|
||||
pub descriptor: TextureDescriptor,
|
||||
|
@ -14,9 +15,11 @@ pub struct WindowTextureNode {
|
|||
}
|
||||
|
||||
impl Node for WindowTextureNode {
|
||||
fn output(&self) -> &[ResourceSlot] {
|
||||
static OUTPUT: &[ResourceSlot] =
|
||||
&[ResourceSlot::new("window_texture", ResourceInfo::Texture)];
|
||||
fn output(&self) -> &[ResourceSlotInfo] {
|
||||
static OUTPUT: &[ResourceSlotInfo] = &[ResourceSlotInfo {
|
||||
name: Cow::Borrowed("texture"),
|
||||
resource_type: ResourceInfo::Texture,
|
||||
}];
|
||||
OUTPUT
|
||||
}
|
||||
|
||||
|
@ -25,8 +28,8 @@ impl Node for WindowTextureNode {
|
|||
_world: &World,
|
||||
resources: &Resources,
|
||||
render_context: &mut dyn RenderContext,
|
||||
_input: &ResourceBindings,
|
||||
output: &mut ResourceBindings,
|
||||
_input: &ResourceSlots,
|
||||
output: &mut ResourceSlots,
|
||||
) {
|
||||
const WINDOW_TEXTURE: usize = 0;
|
||||
let window_resized_events = resources.get::<Events<WindowResized>>().unwrap();
|
||||
|
|
541
bevy_render/src/render_graph_2/schedule.rs
Normal file
541
bevy_render/src/render_graph_2/schedule.rs
Normal file
|
@ -0,0 +1,541 @@
|
|||
use super::{NodeId, NodeState, RenderGraph2, RenderGraphError};
|
||||
use std::collections::HashMap;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum StagerError {
|
||||
#[error("Encountered a RenderGraphError")]
|
||||
RenderGraphError(#[from] RenderGraphError),
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Eq, PartialEq)]
|
||||
pub struct Stage {
|
||||
pub jobs: Vec<OrderedJob>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Eq, PartialEq)]
|
||||
pub struct OrderedJob {
|
||||
pub nodes: Vec<NodeId>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct StageBorrow<'a> {
|
||||
pub jobs: Vec<OrderedJobBorrow<'a>>,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct OrderedJobBorrow<'a> {
|
||||
pub node_states: Vec<&'a mut NodeState>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||
struct NodeIndices {
|
||||
stage: usize,
|
||||
job: usize,
|
||||
node: usize,
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Stages {
|
||||
stages: Vec<Stage>,
|
||||
/// a collection of node indices that are used to efficiently borrow render graph nodes
|
||||
node_indices: HashMap<NodeId, NodeIndices>,
|
||||
}
|
||||
|
||||
impl Stages {
|
||||
pub fn new(stages: Vec<Stage>) -> Self {
|
||||
let mut node_indices = HashMap::default();
|
||||
for (stage_index, stage) in stages.iter().enumerate() {
|
||||
for (job_index, job) in stage.jobs.iter().enumerate() {
|
||||
for (node_index, node) in job.nodes.iter().enumerate() {
|
||||
node_indices.insert(
|
||||
*node,
|
||||
NodeIndices {
|
||||
stage: stage_index,
|
||||
job: job_index,
|
||||
node: node_index,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
Stages {
|
||||
stages,
|
||||
node_indices,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn borrow<'a>(&self, render_graph: &'a mut RenderGraph2) -> Vec<StageBorrow<'a>> {
|
||||
// unfortunately borrowing render graph nodes in a specific order takes a little bit of gymnastics
|
||||
let mut stage_borrows = Vec::with_capacity(self.stages.len());
|
||||
|
||||
let mut node_borrows = Vec::new();
|
||||
for node in render_graph.iter_nodes_mut() {
|
||||
let indices = self.node_indices.get(&node.id).unwrap();
|
||||
node_borrows.push((node, indices));
|
||||
}
|
||||
|
||||
node_borrows.sort_by_key(|(_node, indices)| indices.clone());
|
||||
let mut last_stage = usize::MAX;
|
||||
let mut last_job = usize::MAX;
|
||||
for (node, indices) in node_borrows.drain(..) {
|
||||
if last_stage != indices.stage {
|
||||
stage_borrows.push(StageBorrow::default());
|
||||
last_job = usize::MAX;
|
||||
}
|
||||
|
||||
let stage = &mut stage_borrows[indices.stage];
|
||||
if last_job != indices.job {
|
||||
stage.jobs.push(OrderedJobBorrow::default());
|
||||
}
|
||||
|
||||
let job = &mut stage.jobs[indices.job];
|
||||
job.node_states.push(node);
|
||||
|
||||
last_stage = indices.stage;
|
||||
last_job = indices.job;
|
||||
}
|
||||
|
||||
stage_borrows
|
||||
}
|
||||
}
|
||||
|
||||
/// Produces a collection of `Stages`, which are sets of OrderedJobs that must be run before moving on to the next stage
|
||||
pub trait RenderGraphStager {
|
||||
fn get_stages(&mut self, render_graph: &RenderGraph2) -> Result<Stages, RenderGraphError>;
|
||||
}
|
||||
|
||||
// TODO: remove this
|
||||
/// This scheduler ignores dependencies and puts everything in one stage. It shouldn't be used for anything :)
|
||||
#[derive(Default)]
|
||||
pub struct LinearStager;
|
||||
|
||||
impl RenderGraphStager for LinearStager {
|
||||
fn get_stages(&mut self, render_graph: &RenderGraph2) -> Result<Stages, RenderGraphError> {
|
||||
let mut stage = Stage::default();
|
||||
let mut job = OrderedJob::default();
|
||||
for node in render_graph.iter_nodes() {
|
||||
job.nodes.push(node.id);
|
||||
}
|
||||
|
||||
stage.jobs.push(job);
|
||||
|
||||
Ok(Stages::new(vec![stage]))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
/// Determines the grouping strategy used when constructing graph stages
|
||||
pub enum JobGrouping {
|
||||
/// Default to adding the current node to a new job in its assigned stage. This results
|
||||
/// in a "loose" pack that is easier to parallelize but has more jobs
|
||||
Loose,
|
||||
/// Default to adding the current node into the first job in its assigned stage. This results
|
||||
/// in a "tight" pack that is harder to parallelize but results in fewer jobs
|
||||
Tight,
|
||||
}
|
||||
|
||||
/// Produces Render Graph stages and jobs in a way that ensures node dependencies are respected.
|
||||
pub struct DependentNodeStager {
|
||||
job_grouping: JobGrouping,
|
||||
}
|
||||
|
||||
impl DependentNodeStager {
|
||||
pub fn loose_grouping() -> Self {
|
||||
DependentNodeStager {
|
||||
job_grouping: JobGrouping::Loose,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tight_grouping() -> Self {
|
||||
DependentNodeStager {
|
||||
job_grouping: JobGrouping::Tight,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderGraphStager for DependentNodeStager {
|
||||
fn get_stages<'a>(&mut self, render_graph: &RenderGraph2) -> Result<Stages, RenderGraphError> {
|
||||
// get all nodes without input. this intentionally includes nodes with no outputs
|
||||
let output_only_nodes = render_graph
|
||||
.iter_nodes()
|
||||
.filter(|node| node.input_slots.len() == 0);
|
||||
let mut stages = vec![Stage::default()];
|
||||
let mut node_stages = HashMap::new();
|
||||
for output_only_node in output_only_nodes {
|
||||
// each "output only" node should start a new job on the first stage
|
||||
stage_node(
|
||||
render_graph,
|
||||
&mut stages,
|
||||
&mut node_stages,
|
||||
output_only_node,
|
||||
self.job_grouping,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(Stages::new(stages))
|
||||
}
|
||||
}
|
||||
|
||||
fn stage_node(
|
||||
graph: &RenderGraph2,
|
||||
stages: &mut Vec<Stage>,
|
||||
node_stages_and_jobs: &mut HashMap<NodeId, (usize, usize)>,
|
||||
node: &NodeState,
|
||||
job_grouping: JobGrouping,
|
||||
) {
|
||||
// don't re-visit nodes or visit them before all of their parents have been visited
|
||||
if node_stages_and_jobs.contains_key(&node.id)
|
||||
|| node
|
||||
.input_edges
|
||||
.iter()
|
||||
.find(|e| !node_stages_and_jobs.contains_key(&e.get_output_node()))
|
||||
.is_some()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// by default assume we are creating a new job on a new stage
|
||||
let mut stage_index = 0;
|
||||
let mut job_index = match job_grouping {
|
||||
JobGrouping::Tight => Some(0),
|
||||
JobGrouping::Loose => None,
|
||||
};
|
||||
|
||||
// check to see if the current node has a parent. if so, grab the parent with the highest stage
|
||||
if let Some((max_parent_stage, max_parent_job)) = node
|
||||
.input_edges
|
||||
.iter()
|
||||
.map(|e| {
|
||||
node_stages_and_jobs
|
||||
.get(&e.get_output_node())
|
||||
.expect("already checked that parents were visited")
|
||||
})
|
||||
.max()
|
||||
{
|
||||
// count the number of parents that are in the highest stage
|
||||
let max_stage_parent_count = node
|
||||
.input_edges
|
||||
.iter()
|
||||
.filter(|e| {
|
||||
let (max_stage, _) = node_stages_and_jobs
|
||||
.get(&e.get_output_node())
|
||||
.expect("already checked that parents were visited");
|
||||
max_stage == max_parent_stage
|
||||
})
|
||||
.count();
|
||||
|
||||
// if the current node has more than one parent on the highest stage (aka requires synchronization), then move it to the next
|
||||
// stage and start a new job on that stage
|
||||
if max_stage_parent_count > 1 {
|
||||
stage_index = max_parent_stage + 1;
|
||||
} else {
|
||||
stage_index = *max_parent_stage;
|
||||
job_index = Some(*max_parent_job);
|
||||
}
|
||||
}
|
||||
|
||||
if stage_index == stages.len() {
|
||||
stages.push(Stage::default());
|
||||
}
|
||||
|
||||
let stage = &mut stages[stage_index];
|
||||
|
||||
let job_index = job_index.unwrap_or_else(|| stage.jobs.len());
|
||||
if job_index == stage.jobs.len() {
|
||||
stage.jobs.push(OrderedJob::default());
|
||||
}
|
||||
|
||||
let job = &mut stage.jobs[job_index];
|
||||
job.nodes.push(node.id);
|
||||
|
||||
node_stages_and_jobs.insert(node.id, (stage_index, job_index));
|
||||
|
||||
for (_edge, node) in graph.iter_node_outputs(node.id).unwrap() {
|
||||
stage_node(graph, stages, node_stages_and_jobs, node, job_grouping);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{DependentNodeStager, OrderedJob, RenderGraphStager, Stage};
|
||||
use crate::{
|
||||
render_graph_2::{Node, NodeId, RenderGraph2, ResourceSlotInfo, ResourceSlots},
|
||||
render_resource::ResourceInfo,
|
||||
renderer_2::RenderContext,
|
||||
};
|
||||
use legion::prelude::{Resources, World};
|
||||
|
||||
struct TestNode {
|
||||
inputs: Vec<ResourceSlotInfo>,
|
||||
outputs: Vec<ResourceSlotInfo>,
|
||||
}
|
||||
|
||||
impl TestNode {
|
||||
pub fn new(inputs: usize, outputs: usize) -> Self {
|
||||
TestNode {
|
||||
inputs: (0..inputs)
|
||||
.map(|i| ResourceSlotInfo {
|
||||
name: format!("in_{}", i).into(),
|
||||
resource_type: ResourceInfo::Texture,
|
||||
})
|
||||
.collect(),
|
||||
outputs: (0..outputs)
|
||||
.map(|i| ResourceSlotInfo {
|
||||
name: format!("out_{}", i).into(),
|
||||
resource_type: ResourceInfo::Texture,
|
||||
})
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node for TestNode {
|
||||
fn input(&self) -> &[ResourceSlotInfo] {
|
||||
&self.inputs
|
||||
}
|
||||
|
||||
fn output(&self) -> &[ResourceSlotInfo] {
|
||||
&self.outputs
|
||||
}
|
||||
fn update(
|
||||
&mut self,
|
||||
_: &World,
|
||||
_: &Resources,
|
||||
_: &mut dyn RenderContext,
|
||||
_: &ResourceSlots,
|
||||
_: &mut ResourceSlots,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_graph_dependency_stager_loose() {
|
||||
let mut graph = RenderGraph2::default();
|
||||
|
||||
// Setup graph to look like this:
|
||||
//
|
||||
// A -> B -> C -> D
|
||||
// / /
|
||||
// E F -> G
|
||||
//
|
||||
// H -> I -> J
|
||||
|
||||
let a_id = graph.add_node_named("A", TestNode::new(0, 1));
|
||||
let b_id = graph.add_node_named("B", TestNode::new(2, 1));
|
||||
let c_id = graph.add_node_named("C", TestNode::new(2, 1));
|
||||
let d_id = graph.add_node_named("D", TestNode::new(1, 0));
|
||||
let e_id = graph.add_node_named("E", TestNode::new(0, 1));
|
||||
let f_id = graph.add_node_named("F", TestNode::new(0, 2));
|
||||
let g_id = graph.add_node_named("G", TestNode::new(1, 0));
|
||||
let h_id = graph.add_node_named("H", TestNode::new(0, 1));
|
||||
let i_id = graph.add_node_named("I", TestNode::new(1, 1));
|
||||
let j_id = graph.add_node_named("J", TestNode::new(1, 0));
|
||||
|
||||
graph.add_node_edge("A", "B").unwrap();
|
||||
graph.add_node_edge("B", "C").unwrap();
|
||||
graph.add_node_edge("C", "D").unwrap();
|
||||
graph.add_node_edge("E", "B").unwrap();
|
||||
graph.add_node_edge("F", "C").unwrap();
|
||||
graph.add_node_edge("F", "G").unwrap();
|
||||
graph.add_node_edge("H", "I").unwrap();
|
||||
graph.add_node_edge("I", "J").unwrap();
|
||||
|
||||
let mut stager = DependentNodeStager::loose_grouping();
|
||||
let mut stages = stager.get_stages(&graph).unwrap();
|
||||
|
||||
// Expected Stages:
|
||||
// (X indicates nodes that are not part of that stage)
|
||||
|
||||
// Stage 1
|
||||
// A -> X -> X -> X
|
||||
// / /
|
||||
// E F -> G
|
||||
//
|
||||
// H -> I -> J
|
||||
|
||||
// Stage 2
|
||||
// X -> B -> C -> D
|
||||
// / /
|
||||
// X X -> X
|
||||
//
|
||||
// X -> X -> X
|
||||
|
||||
let mut expected_stages = vec![
|
||||
Stage {
|
||||
jobs: vec![
|
||||
OrderedJob {
|
||||
nodes: vec![f_id, g_id],
|
||||
},
|
||||
OrderedJob { nodes: vec![a_id] },
|
||||
OrderedJob { nodes: vec![e_id] },
|
||||
OrderedJob {
|
||||
nodes: vec![h_id, i_id, j_id],
|
||||
},
|
||||
],
|
||||
},
|
||||
Stage {
|
||||
jobs: vec![OrderedJob {
|
||||
nodes: vec![b_id, c_id, d_id],
|
||||
}],
|
||||
},
|
||||
];
|
||||
|
||||
// ensure job order lines up within stages (this can vary due to hash maps)
|
||||
// jobs within a stage are unordered conceptually so this is ok
|
||||
expected_stages
|
||||
.iter_mut()
|
||||
.for_each(|stage| stage.jobs.sort_by_key(|job| job.nodes[0]));
|
||||
|
||||
stages
|
||||
.stages
|
||||
.iter_mut()
|
||||
.for_each(|stage| stage.jobs.sort_by_key(|job| job.nodes[0]));
|
||||
|
||||
assert_eq!(
|
||||
stages.stages, expected_stages,
|
||||
"stages should be loosely grouped"
|
||||
);
|
||||
|
||||
let mut borrowed = stages.borrow(&mut graph);
|
||||
// ensure job order lines up within stages (this can vary due to hash maps)
|
||||
// jobs within a stage are unordered conceptually so this is ok
|
||||
borrowed
|
||||
.iter_mut()
|
||||
.for_each(|stage| stage.jobs.sort_by_key(|job| job.node_states[0].id));
|
||||
|
||||
assert_eq!(
|
||||
borrowed.len(),
|
||||
expected_stages.len(),
|
||||
"same number of stages"
|
||||
);
|
||||
for (stage_index, borrowed_stage) in borrowed.iter().enumerate() {
|
||||
assert_eq!(
|
||||
borrowed_stage.jobs.len(),
|
||||
stages.stages[stage_index].jobs.len(),
|
||||
"job length matches"
|
||||
);
|
||||
for (job_index, borrowed_job) in borrowed_stage.jobs.iter().enumerate() {
|
||||
assert_eq!(
|
||||
borrowed_job.node_states.len(),
|
||||
stages.stages[stage_index].jobs[job_index].nodes.len(),
|
||||
"node length matches"
|
||||
);
|
||||
for (node_index, borrowed_node) in borrowed_job.node_states.iter().enumerate() {
|
||||
assert_eq!(
|
||||
borrowed_node.id,
|
||||
stages.stages[stage_index].jobs[job_index].nodes[node_index]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_render_graph_dependency_stager_tight() {
|
||||
let mut graph = RenderGraph2::default();
|
||||
|
||||
// Setup graph to look like this:
|
||||
//
|
||||
// A -> B -> C -> D
|
||||
// / /
|
||||
// E F -> G
|
||||
//
|
||||
// H -> I -> J
|
||||
|
||||
let _a_id = graph.add_node_named("A", TestNode::new(0, 1));
|
||||
let b_id = graph.add_node_named("B", TestNode::new(2, 1));
|
||||
let c_id = graph.add_node_named("C", TestNode::new(2, 1));
|
||||
let d_id = graph.add_node_named("D", TestNode::new(1, 0));
|
||||
let _e_id = graph.add_node_named("E", TestNode::new(0, 1));
|
||||
let f_id = graph.add_node_named("F", TestNode::new(0, 2));
|
||||
let g_id = graph.add_node_named("G", TestNode::new(1, 0));
|
||||
let h_id = graph.add_node_named("H", TestNode::new(0, 1));
|
||||
let i_id = graph.add_node_named("I", TestNode::new(1, 1));
|
||||
let j_id = graph.add_node_named("J", TestNode::new(1, 0));
|
||||
|
||||
graph.add_node_edge("A", "B").unwrap();
|
||||
graph.add_node_edge("B", "C").unwrap();
|
||||
graph.add_node_edge("C", "D").unwrap();
|
||||
graph.add_node_edge("E", "B").unwrap();
|
||||
graph.add_node_edge("F", "C").unwrap();
|
||||
graph.add_node_edge("F", "G").unwrap();
|
||||
graph.add_node_edge("H", "I").unwrap();
|
||||
graph.add_node_edge("I", "J").unwrap();
|
||||
|
||||
let mut stager = DependentNodeStager::tight_grouping();
|
||||
let mut stages = stager.get_stages(&graph).unwrap();
|
||||
|
||||
// Expected Stages:
|
||||
// (X indicates nodes that are not part of that stage)
|
||||
|
||||
// Stage 1
|
||||
// A -> X -> X -> X
|
||||
// / /
|
||||
// E F -> G
|
||||
//
|
||||
// H -> I -> J
|
||||
|
||||
// Stage 2
|
||||
// X -> B -> C -> D
|
||||
// / /
|
||||
// X X -> X
|
||||
//
|
||||
// X -> X -> X
|
||||
|
||||
assert_eq!(stages.stages[0].jobs.len(), 1, "expect exactly 1 job");
|
||||
|
||||
let job = &stages.stages[0].jobs[0];
|
||||
|
||||
assert_eq!(job.nodes.len(), 7, "expect exactly 7 nodes in the job");
|
||||
|
||||
// its hard to test the exact order of this job's nodes because of hashing, so instead we'll test the constraints that must hold true
|
||||
let index =
|
||||
|node_id: NodeId| -> usize { job.nodes.iter().position(|id| *id == node_id).unwrap() };
|
||||
|
||||
assert!(index(f_id) < index(g_id));
|
||||
assert!(index(h_id) < index(i_id));
|
||||
assert!(index(i_id) < index(j_id));
|
||||
|
||||
let expected_stage_1 = Stage {
|
||||
jobs: vec![OrderedJob {
|
||||
nodes: vec![b_id, c_id, d_id],
|
||||
}],
|
||||
};
|
||||
|
||||
assert_eq!(stages.stages[1], expected_stage_1,);
|
||||
|
||||
let mut borrowed = stages.borrow(&mut graph);
|
||||
// ensure job order lines up within stages (this can vary due to hash maps)
|
||||
// jobs within a stage are unordered conceptually so this is ok
|
||||
stages
|
||||
.stages
|
||||
.iter_mut()
|
||||
.for_each(|stage| stage.jobs.sort_by_key(|job| job.nodes[0]));
|
||||
borrowed
|
||||
.iter_mut()
|
||||
.for_each(|stage| stage.jobs.sort_by_key(|job| job.node_states[0].id));
|
||||
|
||||
assert_eq!(borrowed.len(), 2, "same number of stages");
|
||||
for (stage_index, borrowed_stage) in borrowed.iter().enumerate() {
|
||||
assert_eq!(
|
||||
borrowed_stage.jobs.len(),
|
||||
stages.stages[stage_index].jobs.len(),
|
||||
"job length matches"
|
||||
);
|
||||
for (job_index, borrowed_job) in borrowed_stage.jobs.iter().enumerate() {
|
||||
assert_eq!(
|
||||
borrowed_job.node_states.len(),
|
||||
stages.stages[stage_index].jobs[job_index].nodes.len(),
|
||||
"node length matches"
|
||||
);
|
||||
for (node_index, borrowed_node) in borrowed_job.node_states.iter().enumerate() {
|
||||
assert_eq!(
|
||||
borrowed_node.id,
|
||||
stages.stages[stage_index].jobs[job_index].nodes[node_index]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
use uuid::Uuid;
|
||||
|
||||
// TODO: Rename to RenderResourceId
|
||||
#[derive(Copy, Clone, Hash, Debug, Eq, PartialEq)]
|
||||
#[derive(Copy, Clone, Hash, Eq, PartialEq, Debug)]
|
||||
pub struct RenderResource(Uuid);
|
||||
|
||||
impl RenderResource {
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
use crate::render_resource::BufferUsage;
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
#[derive(Default, Debug, Clone, Eq, PartialEq)]
|
||||
pub struct BufferArrayInfo {
|
||||
pub item_size: usize,
|
||||
pub item_capacity: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct BufferInfo {
|
||||
pub size: usize,
|
||||
pub buffer_usage: BufferUsage,
|
||||
|
@ -25,7 +25,7 @@ impl Default for BufferInfo {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub enum ResourceInfo {
|
||||
Buffer(BufferInfo),
|
||||
Texture,
|
||||
|
|
|
@ -1,50 +1,12 @@
|
|||
use crate::{
|
||||
render_graph_2::{Node, ResourceBindings, ResourceSlot},
|
||||
render_resource::{RenderResourceAssignments, ResourceInfo, ResourceProvider},
|
||||
render_resource::{RenderResourceAssignments, ResourceProvider},
|
||||
renderer_2::RenderContext,
|
||||
texture::TextureDescriptor,
|
||||
};
|
||||
use bevy_app::{EventReader, Events};
|
||||
use bevy_window::{WindowResized, Windows};
|
||||
|
||||
use bevy_window::Windows;
|
||||
use legion::prelude::*;
|
||||
|
||||
pub struct WindowTextureNode {
|
||||
pub name: String,
|
||||
pub descriptor: TextureDescriptor,
|
||||
pub window_resized_event_reader: EventReader<WindowResized>,
|
||||
}
|
||||
|
||||
impl Node for WindowTextureNode {
|
||||
fn output(&self) -> &[ResourceSlot] {
|
||||
static OUTPUT: &[ResourceSlot] =
|
||||
&[ResourceSlot::new("window_texture", ResourceInfo::Texture)];
|
||||
OUTPUT
|
||||
}
|
||||
|
||||
fn update(
|
||||
&mut self,
|
||||
_world: &World,
|
||||
resources: &Resources,
|
||||
render_context: &mut dyn RenderContext,
|
||||
_input: &ResourceBindings,
|
||||
output: &mut ResourceBindings,
|
||||
) {
|
||||
const WINDOW_TEXTURE: usize = 0;
|
||||
let window_resized_events = resources.get::<Events<WindowResized>>().unwrap();
|
||||
if let Some(event) = window_resized_events.latest(&mut self.window_resized_event_reader) {
|
||||
let render_resources = render_context.resources_mut();
|
||||
if let Some(old_texture) = output.get(WINDOW_TEXTURE) {
|
||||
render_resources.remove_texture(old_texture);
|
||||
}
|
||||
|
||||
self.descriptor.size.width = event.width;
|
||||
self.descriptor.size.height = event.height;
|
||||
let texture_resource = render_resources.create_texture(&self.descriptor);
|
||||
output.set(WINDOW_TEXTURE, texture_resource);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct FrameTextureResourceProvider {
|
||||
pub name: String,
|
||||
pub descriptor: TextureDescriptor,
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
mod systems;
|
||||
mod wgpu_render_context;
|
||||
mod wgpu_render_resource_context;
|
||||
mod wgpu_render_graph_executor;
|
||||
|
||||
pub use systems::*;
|
||||
pub use wgpu_render_context::*;
|
||||
pub use wgpu_render_resource_context::*;
|
||||
pub use wgpu_render_graph_executor::*;
|
|
@ -7,7 +7,7 @@ use bevy_asset::{AssetStorage, Handle};
|
|||
use bevy_render::{
|
||||
pass::{
|
||||
PassDescriptor, RenderPass, RenderPassColorAttachmentDescriptor,
|
||||
RenderPassDepthStencilAttachmentDescriptor,
|
||||
RenderPassDepthStencilAttachmentDescriptor, TextureAttachment,
|
||||
},
|
||||
pipeline::{BindGroupDescriptor, BindType, PipelineDescriptor},
|
||||
render_resource::{
|
||||
|
@ -456,11 +456,7 @@ pub fn create_render_pass<'a, 'b>(
|
|||
.color_attachments
|
||||
.iter()
|
||||
.map(|c| {
|
||||
create_wgpu_color_attachment_descriptor(
|
||||
global_render_resource_assignments,
|
||||
refs,
|
||||
c,
|
||||
)
|
||||
create_wgpu_color_attachment_descriptor(global_render_resource_assignments, refs, c)
|
||||
})
|
||||
.collect::<Vec<wgpu::RenderPassColorAttachmentDescriptor>>(),
|
||||
depth_stencil_attachment: pass_descriptor.depth_stencil_attachment.as_ref().map(|d| {
|
||||
|
@ -476,30 +472,30 @@ pub fn create_render_pass<'a, 'b>(
|
|||
fn get_texture_view<'a>(
|
||||
global_render_resource_assignments: &RenderResourceAssignments,
|
||||
refs: &WgpuResourceRefs<'a>,
|
||||
name: &str,
|
||||
attachment: &TextureAttachment,
|
||||
) -> &'a wgpu::TextureView {
|
||||
match name {
|
||||
resource_name::texture::SWAP_CHAIN => {
|
||||
if let Some(primary_swap_chain) = refs
|
||||
.swap_chain_outputs
|
||||
.values()
|
||||
.next()
|
||||
{
|
||||
&primary_swap_chain.view
|
||||
} else {
|
||||
panic!("No primary swap chain found for color attachment");
|
||||
}
|
||||
}
|
||||
_ => match global_render_resource_assignments.get(name) {
|
||||
Some(resource) => refs.textures.get(&resource).unwrap(),
|
||||
None => {
|
||||
// if let Some(swap_chain_output) = swap_chain_outputs.get(name) {
|
||||
// &swap_chain_output.view
|
||||
// } else {
|
||||
panic!("Color attachment {} does not exist", name);
|
||||
// }
|
||||
match attachment {
|
||||
TextureAttachment::Name(name) => match name.as_str() {
|
||||
resource_name::texture::SWAP_CHAIN => {
|
||||
if let Some(primary_swap_chain) = refs.swap_chain_outputs.values().next() {
|
||||
&primary_swap_chain.view
|
||||
} else {
|
||||
panic!("No primary swap chain found for color attachment");
|
||||
}
|
||||
}
|
||||
_ => match global_render_resource_assignments.get(&name) {
|
||||
Some(resource) => refs.textures.get(&resource).unwrap(),
|
||||
None => {
|
||||
// if let Some(swap_chain_output) = swap_chain_outputs.get(name) {
|
||||
// &swap_chain_output.view
|
||||
// } else {
|
||||
panic!("Color attachment {} does not exist", name);
|
||||
// }
|
||||
}
|
||||
},
|
||||
},
|
||||
TextureAttachment::RenderResource(render_resource) => refs.textures.get(&render_resource).unwrap(),
|
||||
TextureAttachment::Input(_) => panic!("Encountered unset TextureAttachment::Input. The RenderGraph executor should always set TextureAttachment::Inputs to TextureAttachment::RenderResource before running. This is a bug"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -511,19 +507,13 @@ fn create_wgpu_color_attachment_descriptor<'a>(
|
|||
let attachment = get_texture_view(
|
||||
global_render_resource_assignments,
|
||||
refs,
|
||||
color_attachment_descriptor.attachment.as_str(),
|
||||
&color_attachment_descriptor.attachment,
|
||||
);
|
||||
|
||||
let resolve_target = color_attachment_descriptor
|
||||
.resolve_target
|
||||
.as_ref()
|
||||
.map(|target| {
|
||||
get_texture_view(
|
||||
global_render_resource_assignments,
|
||||
refs,
|
||||
target.as_str(),
|
||||
)
|
||||
});
|
||||
.map(|target| get_texture_view(global_render_resource_assignments, refs, &target));
|
||||
|
||||
wgpu::RenderPassColorAttachmentDescriptor {
|
||||
store_op: color_attachment_descriptor.store_op.wgpu_into(),
|
||||
|
@ -542,7 +532,7 @@ fn create_wgpu_depth_stencil_attachment_descriptor<'a>(
|
|||
let attachment = get_texture_view(
|
||||
global_render_resource_assignments,
|
||||
refs,
|
||||
depth_stencil_attachment_descriptor.attachment.as_str(),
|
||||
&depth_stencil_attachment_descriptor.attachment,
|
||||
);
|
||||
|
||||
wgpu::RenderPassDepthStencilAttachmentDescriptor {
|
||||
|
|
69
bevy_wgpu/src/renderer_2/wgpu_render_graph_executor.rs
Normal file
69
bevy_wgpu/src/renderer_2/wgpu_render_graph_executor.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use super::{WgpuRenderContext, WgpuRenderResourceContext};
|
||||
use bevy_render::{render_graph_2::StageBorrow, renderer_2::GlobalRenderResourceContext};
|
||||
use legion::prelude::{Resources, World};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct WgpuRenderGraphExecutor {
|
||||
pub max_thread_count: usize,
|
||||
}
|
||||
|
||||
impl WgpuRenderGraphExecutor {
|
||||
pub fn execute(
|
||||
&self,
|
||||
world: &World,
|
||||
resources: &Resources,
|
||||
device: Arc<wgpu::Device>,
|
||||
queue: &mut wgpu::Queue,
|
||||
stages: &mut [StageBorrow],
|
||||
) {
|
||||
let mut global_context = resources.get_mut::<GlobalRenderResourceContext>().unwrap();
|
||||
let render_resource_context = global_context
|
||||
.context
|
||||
.downcast_mut::<WgpuRenderResourceContext>()
|
||||
.unwrap();
|
||||
for stage in stages.iter_mut() {
|
||||
// TODO: sort jobs and slice by "amount of work" / weights
|
||||
// stage.jobs.sort_by_key(|j| j.node_states.len());
|
||||
|
||||
let (sender, receiver) = crossbeam_channel::bounded(self.max_thread_count);
|
||||
let chunk_size = (stage.jobs.len() + self.max_thread_count - 1) / self.max_thread_count; // divide ints rounding remainder up
|
||||
let mut actual_thread_count = 0;
|
||||
crossbeam_utils::thread::scope(|s| {
|
||||
for jobs_chunk in stage.jobs.chunks_mut(chunk_size) {
|
||||
let sender = sender.clone();
|
||||
let world = &*world;
|
||||
actual_thread_count += 1;
|
||||
let device = device.clone();
|
||||
let render_resource_context = render_resource_context.clone();
|
||||
s.spawn(move |_| {
|
||||
let mut render_context =
|
||||
WgpuRenderContext::new(device, render_resource_context);
|
||||
for job in jobs_chunk.iter_mut() {
|
||||
for node_state in job.node_states.iter_mut() {
|
||||
node_state.node.update(
|
||||
world,
|
||||
resources,
|
||||
&mut render_context,
|
||||
&node_state.input_slots,
|
||||
&mut node_state.output_slots,
|
||||
);
|
||||
}
|
||||
}
|
||||
sender.send(render_context.finish()).unwrap();
|
||||
});
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let mut command_buffers = Vec::new();
|
||||
for _i in 0..actual_thread_count {
|
||||
let command_buffer = receiver.recv().unwrap();
|
||||
if let Some(command_buffer) = command_buffer {
|
||||
command_buffers.push(command_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
queue.submit(&command_buffers);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,13 @@
|
|||
use crate::renderer_2::{
|
||||
render_resource_sets_system, WgpuRenderContext, WgpuRenderResourceContext,
|
||||
render_resource_sets_system, WgpuRenderContext, WgpuRenderResourceContext, WgpuRenderGraphExecutor,
|
||||
};
|
||||
use bevy_app::{EventReader, Events};
|
||||
use bevy_asset::AssetStorage;
|
||||
use bevy_render::{
|
||||
pipeline::{update_shader_assignments, PipelineCompiler, PipelineDescriptor},
|
||||
pipeline::{update_shader_assignments},
|
||||
render_graph::RenderGraph,
|
||||
render_graph_2::{LinearScheduler, RenderGraph2, RenderGraphScheduler},
|
||||
render_graph_2::{RenderGraph2, RenderGraphStager, DependentNodeStager},
|
||||
render_resource::RenderResourceAssignments,
|
||||
renderer_2::{GlobalRenderResourceContext, RenderContext, RenderResourceContext},
|
||||
renderer_2::{GlobalRenderResourceContext, RenderResourceContext},
|
||||
};
|
||||
use bevy_window::{WindowCreated, WindowResized, Windows};
|
||||
use legion::prelude::*;
|
||||
|
@ -197,6 +196,7 @@ impl WgpuRenderer {
|
|||
}
|
||||
|
||||
pub fn run_graph(&mut self, world: &mut World, resources: &mut Resources) {
|
||||
// run systems
|
||||
let mut executor = {
|
||||
let mut render_graph = resources.get_mut::<RenderGraph2>().unwrap();
|
||||
render_graph.take_executor()
|
||||
|
@ -210,32 +210,17 @@ impl WgpuRenderer {
|
|||
if let Some(executor) = executor.take() {
|
||||
render_graph.set_executor(executor);
|
||||
}
|
||||
let mut global_context = resources.get_mut::<GlobalRenderResourceContext>().unwrap();
|
||||
let render_resource_context = global_context
|
||||
.context
|
||||
.downcast_mut::<WgpuRenderResourceContext>()
|
||||
.unwrap();
|
||||
let mut linear_scheduler = LinearScheduler::default();
|
||||
let mut render_context =
|
||||
WgpuRenderContext::new(self.device.clone(), render_resource_context.clone());
|
||||
for mut stage in linear_scheduler.get_stages(&mut render_graph) {
|
||||
for job in stage.iter_mut() {
|
||||
for node_state in job.iter_mut() {
|
||||
node_state.node.update(
|
||||
world,
|
||||
resources,
|
||||
&mut render_context,
|
||||
&node_state.input,
|
||||
&mut node_state.output,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let command_buffer = render_context.finish();
|
||||
if let Some(command_buffer) = command_buffer {
|
||||
self.queue.submit(&[command_buffer]);
|
||||
}
|
||||
// stage nodes
|
||||
let mut stager = DependentNodeStager::loose_grouping();
|
||||
let stages = stager.get_stages(&render_graph).unwrap();
|
||||
let mut borrowed = stages.borrow(&mut render_graph);
|
||||
|
||||
// execute stages
|
||||
let executor = WgpuRenderGraphExecutor {
|
||||
max_thread_count: 2,
|
||||
};
|
||||
executor.execute(world, resources, self.device.clone(), &mut self.queue, &mut borrowed);
|
||||
}
|
||||
|
||||
pub fn update(&mut self, world: &mut World, resources: &mut Resources) {
|
||||
|
@ -267,8 +252,8 @@ impl WgpuRenderer {
|
|||
};
|
||||
|
||||
self.run_graph(world, resources);
|
||||
// TODO: add to POST_UPDATE and remove redundant global_context
|
||||
render_resource_sets_system().run(world, resources);
|
||||
// TODO: add to POST_UPDATE and remove redundant global_context
|
||||
let mut global_context = resources.get_mut::<GlobalRenderResourceContext>().unwrap();
|
||||
let render_resource_context = global_context
|
||||
.context
|
||||
|
@ -285,59 +270,6 @@ impl WgpuRenderer {
|
|||
let mut render_graph = resources.get_mut::<RenderGraph>().unwrap();
|
||||
render_graph.setup_pipeline_draw_targets(world, resources, &mut render_context);
|
||||
|
||||
// get next swap chain texture on primary window
|
||||
let primary_window_id = resources
|
||||
.get::<Windows>()
|
||||
.unwrap()
|
||||
.get_primary()
|
||||
.map(|window| window.id);
|
||||
if let Some(primary_window_id) = primary_window_id {
|
||||
render_context.primary_window = Some(primary_window_id);
|
||||
}
|
||||
|
||||
// begin render passes
|
||||
let pipeline_storage = resources.get::<AssetStorage<PipelineDescriptor>>().unwrap();
|
||||
let pipeline_compiler = resources.get::<PipelineCompiler>().unwrap();
|
||||
|
||||
for (pass_name, pass_descriptor) in render_graph.pass_descriptors.iter() {
|
||||
let global_render_resource_assignments =
|
||||
resources.get::<RenderResourceAssignments>().unwrap();
|
||||
render_context.begin_pass(
|
||||
pass_descriptor,
|
||||
&global_render_resource_assignments,
|
||||
&mut |render_pass| {
|
||||
if let Some(pass_pipelines) = render_graph.pass_pipelines.get(pass_name) {
|
||||
for pass_pipeline in pass_pipelines.iter() {
|
||||
if let Some(compiled_pipelines_iter) =
|
||||
pipeline_compiler.iter_compiled_pipelines(*pass_pipeline)
|
||||
{
|
||||
for compiled_pipeline_handle in compiled_pipelines_iter {
|
||||
let pipeline_descriptor =
|
||||
pipeline_storage.get(compiled_pipeline_handle).unwrap();
|
||||
render_pass.set_pipeline(*compiled_pipeline_handle);
|
||||
|
||||
for draw_target_name in pipeline_descriptor.draw_targets.iter()
|
||||
{
|
||||
let draw_target = render_graph
|
||||
.draw_targets
|
||||
.get(draw_target_name)
|
||||
.unwrap();
|
||||
draw_target.draw(
|
||||
world,
|
||||
resources,
|
||||
render_pass,
|
||||
*compiled_pipeline_handle,
|
||||
pipeline_descriptor,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
let command_buffer = render_context.finish();
|
||||
if let Some(command_buffer) = command_buffer {
|
||||
self.queue.submit(&[command_buffer]);
|
||||
|
|
Loading…
Reference in a new issue