RenderGraph2: Stager, (semi-functional) Executor, PassNode, and tests.

Rendering doesn't quite work yet, but we're close!
This commit is contained in:
Carter Anderson 2020-04-23 17:24:41 -07:00
parent b6711d8eae
commit 8326a1a3c2
27 changed files with 1889 additions and 562 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

View 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"
);
}
}

View file

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

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

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

View file

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

View file

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

View file

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

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

View file

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

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