Implement a Render Graph #16

Merged
SeanOMik merged 20 commits from feature/render-graph into main 2024-06-15 22:54:47 +00:00
16 changed files with 736 additions and 134 deletions
Showing only changes of commit bccf6287c0 - Show all commits

29
.lapce/run.toml Normal file
View File

@ -0,0 +1,29 @@
# The run config is used for both run mode and debug mode
[[configs]]
# the name of this task
name = "Example 'simple_scene'"
# the type of the debugger. If not set, it can't be debugged but can still be run
type = "lldb"
# the program to run
program = "../../target/debug/simple_scene"
# the program arguments, e.g. args = ["arg1", "arg2"], optional
# args = []
# current working directory, optional
cwd = "${workspace}/examples/simple_scene"
# environment variables, optional
# [configs.env]
# VAR1 = "VAL1"
# VAR2 = "VAL2"
# task to run before the run/debug session is started, optional
[configs.prelaunch]
program = "cargo"
args = [
"build",
]

10
Cargo.lock generated
View File

@ -3065,6 +3065,16 @@ version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe"
[[package]]
name = "simple_scene"
version = "0.1.0"
dependencies = [
"anyhow",
"async-std",
"lyra-engine",
"tracing",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.9" version = "0.4.9"

View File

@ -0,0 +1,10 @@
[package]
name = "simple_scene"
version = "0.1.0"
edition = "2021"
[dependencies]
lyra-engine = { path = "../../", features = ["tracy"] }
anyhow = "1.0.75"
async-std = "1.12.0"
tracing = "0.1.37"

View File

@ -0,0 +1,59 @@
---Return the userdata's name from its metatable
---@param val userdata
---@return string
function udname(val)
return getmetatable(val).__name
end
function on_init()
local cube = world:request_res("../assets/cube-texture-embedded.gltf")
print("Loaded textured cube (" .. udname(cube) .. ")")
cube:wait_until_loaded()
local scenes = cube:scenes()
local cube_scene = scenes[1]
local pos = Transform.from_translation(Vec3.new(0, 0, -8.0))
local e = world:spawn(pos, cube_scene)
print("spawned entity " .. tostring(e))
end
--[[ function on_first()
print("Lua's first function was called")
end
function on_pre_update()
print("Lua's pre-update function was called")
end ]]
function on_update()
--[[ ---@type number
local dt = world:resource(DeltaTime)
local act = world:resource(ActionHandler)
---@type number
local move_objs = act:get_axis("ObjectsMoveUpDown")
world:view(function (t)
if move_objs ~= nil then
t:translate(0, move_objs * 0.35 * dt, 0)
return t
end
end, Transform) ]]
---@type number
local dt = world:resource(DeltaTime)
world:view(function (t)
t:translate(0, 0.15 * dt, 0)
return t
end, Transform)
end
--[[ function on_post_update()
print("Lua's post-update function was called")
end
function on_last()
print("Lua's last function was called")
end ]]

View File

@ -0,0 +1,149 @@
use lyra_engine::{
assets::{gltf::Gltf, ResourceManager},
game::Game,
input::{
Action, ActionHandler, ActionKind, ActionMapping, ActionMappingId, ActionSource,
InputActionPlugin, KeyCode, LayoutId, MouseAxis, MouseInput,
},
math::{self, Transform, Vec3},
render::light::directional::DirectionalLight,
scene::{
CameraComponent, FreeFlyCamera, FreeFlyCameraPlugin, WorldTransform,
ACTLBL_LOOK_LEFT_RIGHT, ACTLBL_LOOK_ROLL, ACTLBL_LOOK_UP_DOWN,
ACTLBL_MOVE_FORWARD_BACKWARD, ACTLBL_MOVE_LEFT_RIGHT, ACTLBL_MOVE_UP_DOWN,
},
};
#[async_std::main]
async fn main() {
let action_handler_plugin = |game: &mut Game| {
let action_handler = ActionHandler::builder()
.add_layout(LayoutId::from(0))
.add_action(ACTLBL_MOVE_FORWARD_BACKWARD, Action::new(ActionKind::Axis))
.add_action(ACTLBL_MOVE_LEFT_RIGHT, Action::new(ActionKind::Axis))
.add_action(ACTLBL_MOVE_UP_DOWN, Action::new(ActionKind::Axis))
.add_action(ACTLBL_LOOK_LEFT_RIGHT, Action::new(ActionKind::Axis))
.add_action(ACTLBL_LOOK_UP_DOWN, Action::new(ActionKind::Axis))
.add_action(ACTLBL_LOOK_ROLL, Action::new(ActionKind::Axis))
.add_action("Debug", Action::new(ActionKind::Button))
.add_mapping(
ActionMapping::builder(LayoutId::from(0), ActionMappingId::from(0))
.bind(
ACTLBL_MOVE_FORWARD_BACKWARD,
&[
ActionSource::Keyboard(KeyCode::W).into_binding_modifier(1.0),
ActionSource::Keyboard(KeyCode::S).into_binding_modifier(-1.0),
],
)
.bind(
ACTLBL_MOVE_LEFT_RIGHT,
&[
ActionSource::Keyboard(KeyCode::A).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::D).into_binding_modifier(1.0),
],
)
.bind(
ACTLBL_MOVE_UP_DOWN,
&[
ActionSource::Keyboard(KeyCode::C).into_binding_modifier(1.0),
ActionSource::Keyboard(KeyCode::Z).into_binding_modifier(-1.0),
],
)
.bind(
ACTLBL_LOOK_LEFT_RIGHT,
&[
ActionSource::Mouse(MouseInput::Axis(MouseAxis::X)).into_binding(),
ActionSource::Keyboard(KeyCode::Left).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::Right).into_binding_modifier(1.0),
],
)
.bind(
ACTLBL_LOOK_UP_DOWN,
&[
ActionSource::Mouse(MouseInput::Axis(MouseAxis::Y)).into_binding(),
ActionSource::Keyboard(KeyCode::Up).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::Down).into_binding_modifier(1.0),
],
)
.bind(
ACTLBL_LOOK_ROLL,
&[
ActionSource::Keyboard(KeyCode::E).into_binding_modifier(-1.0),
ActionSource::Keyboard(KeyCode::Q).into_binding_modifier(1.0),
],
)
.bind(
"Debug",
&[ActionSource::Keyboard(KeyCode::B).into_binding()],
)
.finish(),
)
.finish();
let world = game.world_mut();
world.add_resource(action_handler);
game.with_plugin(InputActionPlugin);
};
Game::initialize()
.await
.with_plugin(lyra_engine::DefaultPlugins)
.with_plugin(setup_scene_plugin)
.with_plugin(action_handler_plugin)
//.with_plugin(camera_debug_plugin)
.with_plugin(FreeFlyCameraPlugin)
.run()
.await;
}
fn setup_scene_plugin(game: &mut Game) {
let world = game.world_mut();
let resman = world.get_resource_mut::<ResourceManager>();
/* let camera_gltf = resman
.request::<Gltf>("../assets/AntiqueCamera.glb")
.unwrap();
camera_gltf.wait_recurse_dependencies_load();
let camera_mesh = &camera_gltf.data_ref().unwrap().scenes[0];
drop(resman);
world.spawn((
camera_mesh.clone(),
WorldTransform::default(),
Transform::from_xyz(0.0, -5.0, -2.0),
)); */
let cube_gltf = resman
.request::<Gltf>("../assets/cube-texture-embedded.gltf")
.unwrap();
cube_gltf.wait_recurse_dependencies_load();
let cube_mesh = &cube_gltf.data_ref().unwrap().scenes[0];
drop(resman);
world.spawn((
cube_mesh.clone(),
WorldTransform::default(),
Transform::from_xyz(0.0, -5.0, -2.0),
));
{
let mut light_tran = Transform::from_xyz(1.5, 2.5, 0.0);
light_tran.scale = Vec3::new(0.5, 0.5, 0.5);
light_tran.rotate_x(math::Angle::Degrees(-45.0));
light_tran.rotate_y(math::Angle::Degrees(25.0));
world.spawn((
DirectionalLight {
enabled: true,
color: Vec3::ONE,
intensity: 0.15, //..Default::default()
},
light_tran,
));
}
let mut camera = CameraComponent::new_3d();
camera.transform.translation += math::Vec3::new(0.0, 0.0, 5.5);
world.spawn((camera, FreeFlyCamera::default()));
}

View File

@ -57,10 +57,12 @@ struct GameLoop {
} }
impl GameLoop { impl GameLoop {
pub async fn new(window: Arc<Window>, world: World, staged_exec: StagedExecutor) -> GameLoop { pub async fn new(window: Arc<Window>, mut world: World, staged_exec: StagedExecutor) -> Self {
let renderer = BasicRenderer::create_with_window(&mut world, window.clone()).await;
Self { Self {
window: Arc::clone(&window), window: Arc::clone(&window),
renderer: Box::new(BasicRenderer::create_with_window(window).await), renderer: Box::new(renderer),
world, world,
staged_exec, staged_exec,
@ -68,7 +70,7 @@ impl GameLoop {
} }
pub async fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) { pub async fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
self.renderer.on_resize(new_size); self.renderer.on_resize(&mut self.world, new_size);
} }
pub async fn on_init(&mut self) { pub async fn on_init(&mut self) {

View File

@ -1,6 +1,7 @@
#![feature(hash_extract_if)] #![feature(hash_extract_if)]
#![feature(lint_reasons)] #![feature(lint_reasons)]
#![feature(trait_alias)] #![feature(trait_alias)]
#![feature(map_many_mut)]
extern crate self as lyra_engine; extern crate self as lyra_engine;

View File

@ -11,7 +11,7 @@ pub struct GraphExecutionPath {
} }
impl GraphExecutionPath { impl GraphExecutionPath {
pub fn new(pass_descriptions: Vec<&RenderGraphPassDesc>) -> Self { pub fn new(built_in_slots: FxHashSet<u64>, pass_descriptions: Vec<&RenderGraphPassDesc>) -> Self {
// collect all the output slots // collect all the output slots
let mut total_outputs = HashMap::new(); let mut total_outputs = HashMap::new();
for desc in pass_descriptions.iter() { for desc in pass_descriptions.iter() {
@ -28,6 +28,9 @@ impl GraphExecutionPath {
// find the node inputs // find the node inputs
let mut inputs = vec![]; let mut inputs = vec![];
for slot in desc.input_slots() { for slot in desc.input_slots() {
// If the slot is built in to the graph, no need to care about the sorting.
if built_in_slots.contains(&slot.id) { continue; }
let inp = total_outputs.get(&slot.name) let inp = total_outputs.get(&slot.name)
.expect(&format!("failed to find slot: '{}', ensure that there is a pass outputting it", slot.name)); .expect(&format!("failed to find slot: '{}', ensure that there is a pass outputting it", slot.name));
inputs.push(*inp); inputs.push(*inp);

View File

@ -1,5 +1,10 @@
mod pass; mod pass;
use std::collections::HashMap; use std::{
cell::RefCell,
collections::{HashMap, VecDeque},
ptr::NonNull,
sync::Arc,
};
pub use pass::*; pub use pass::*;
@ -11,14 +16,22 @@ pub use slot_desc::*;
mod execution_path; mod execution_path;
use rustc_hash::FxHashMap; use rustc_hash::{FxHashMap, FxHashSet};
use tracing::debug; use tracing::{debug, debug_span, instrument};
use wgpu::{util::DeviceExt, RenderPass}; use wgpu::{util::DeviceExt, RenderPass};
use super::{compute_pipeline::ComputePipeline, pipeline::Pipeline, render_pipeline::RenderPipeline, renderer::{BasicRenderer, Renderer}}; use self::execution_path::GraphExecutionPath;
use super::{
compute_pipeline::ComputePipeline,
pipeline::Pipeline,
render_pipeline::RenderPipeline,
renderer::{BasicRenderer, Renderer},
};
#[derive(Clone)]
struct PassEntry { struct PassEntry {
inner: Box<dyn RenderGraphPass>, inner: Arc<RefCell<dyn RenderGraphPass>>,
desc: RenderGraphPassDesc, desc: RenderGraphPassDesc,
} }
@ -46,80 +59,137 @@ pub struct PipelineResource {
pub struct RenderGraph { pub struct RenderGraph {
slots: FxHashMap<u64, ResourcedSlot>, slots: FxHashMap<u64, ResourcedSlot>,
slot_names: HashMap<String, u64>, slot_names: HashMap<String, u64>,
// slots with same name
slot_mutlikey: FxHashMap<u64, u64>,
passes: FxHashMap<u64, PassEntry>, passes: FxHashMap<u64, PassEntry>,
// TODO: Use a SlotMap // TODO: Use a SlotMap
bind_groups: FxHashMap<u64, wgpu::BindGroup>, bind_groups: FxHashMap<u64, wgpu::BindGroup>,
// TODO: make pipelines a `type` parameter in RenderPasses, // TODO: make pipelines a `type` parameter in RenderPasses,
// then the pipelines can be retrieved via TypeId to the pass. // then the pipelines can be retrieved via TypeId to the pass.
pipelines: HashMap<String, PipelineResource>, ///
pipelines: FxHashMap<u64, PipelineResource>,
current_id: u64, current_id: u64,
exec_path: Option<GraphExecutionPath>,
pub(crate) surface_config: wgpu::SurfaceConfiguration, pub(crate) surface_config: wgpu::SurfaceConfiguration,
} }
impl RenderGraph { impl RenderGraph {
pub fn new(surface_config: wgpu::SurfaceConfiguration) -> Self { pub fn new(surface_config: wgpu::SurfaceConfiguration) -> Self {
let mut slots = FxHashMap::default();
let mut slot_names = HashMap::default();
slots.insert(
0,
ResourcedSlot {
name: "window_texture_view".to_string(),
ty: SlotType::TextureView,
value: SlotValue::None,
bind_group_id: None,
create_desc: None,
},
);
slot_names.insert("window_texture_view".to_string(), 0u64);
Self { Self {
slots: Default::default(), slots,
slot_names: Default::default(), slot_names,
slot_mutlikey: Default::default(),
passes: Default::default(), passes: Default::default(),
bind_groups: Default::default(), bind_groups: Default::default(),
pipelines: Default::default(), pipelines: Default::default(),
current_id: 0, current_id: 1,
exec_path: None,
surface_config, surface_config,
} }
} }
pub fn next_id(&mut self) -> u64 {
self.current_id += 1;
self.current_id
}
pub fn slot_id(&self, name: &str) -> Option<u64> { pub fn slot_id(&self, name: &str) -> Option<u64> {
self.slot_names.get(name).cloned() self.slot_names.get(name).cloned()
} }
pub fn slot_value(&self, id: u64) -> Option<&SlotValue> {
self.slots.get(&id).map(|s| &s.value)
}
pub fn pass(&self, id: u64) -> Option<&RenderGraphPassDesc> { pub fn pass(&self, id: u64) -> Option<&RenderGraphPassDesc> {
self.passes.get(&id) self.passes.get(&id).map(|s| &s.desc)
.map(|s| &s.desc)
} }
pub fn add_pass<P: RenderGraphPass>(&mut self, pass: P) { pub fn add_pass<P: RenderGraphPass>(&mut self, pass: P) {
let mut desc = pass.desc(self, &mut self.current_id); let mut desc = pass.desc(self);
for slot in &mut desc.slots { for slot in &mut desc.slots {
if let Some((id, other)) = self.slot_names.get(&slot.name) if let Some((id, other)) = self
.slot_names
.get(&slot.name)
.and_then(|id| self.slots.get_mut(id).map(|s| (id, s))) .and_then(|id| self.slots.get_mut(id).map(|s| (id, s)))
{ {
slot.id = *id; slot.id = *id;
if slot.desc.is_some() && other.create_desc.is_none() { if slot.desc.is_some() && other.create_desc.is_none() {
other.create_desc = slot.desc; other.create_desc = slot.desc.clone();
} }
} else { } else {
let res_slot = ResourcedSlot { let res_slot = ResourcedSlot {
name: slot.name, name: slot.name.clone(),
ty: slot.ty, ty: slot.ty,
value: SlotValue::None, value: SlotValue::None,
bind_group_id: None, bind_group_id: None,
create_desc: slot.desc, create_desc: slot.desc.clone(),
}; };
self.slots.insert(slot.id, res_slot); self.slots.insert(slot.id, res_slot);
self.slot_names.insert(slot.name, slot.id); self.slot_names.insert(slot.name.clone(), slot.id);
} }
} }
self.passes.insert(desc.id, PassEntry { self.passes.insert(
inner: Box::new(pass), desc.id,
PassEntry {
inner: Arc::new(RefCell::new(pass)),
desc, desc,
}); },
);
} }
/// Creates all buffers required for the passes, also creates an internal execution path. /// Creates all buffers required for the passes, also creates an internal execution path.
#[instrument(skip(self, device))]
pub fn setup(&mut self, device: &wgpu::Device) { pub fn setup(&mut self, device: &wgpu::Device) {
let mut later_slots = VecDeque::new();
for slot in self.slots.values_mut() { // For all passes, create their pipelines
if slot.bind_group_id.is_none() { for pass in self.passes.values() {
match slot.create_desc { if let Some(pipei) = &pass.desc.pipeline_info {
let pipeline = match pass.desc.pass_type {
RenderPassType::Render => {
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some(pass.desc.name.as_str()),
source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(&pipei.source)),
});
Pipeline::Render(RenderPipeline::new(device, &self.surface_config, &shader, vec![], vec![]))
}
_ => todo!(),
};
let res = PipelineResource {
pipeline,
bg_layout_name_lookup: Default::default(),
};
self.pipelines.insert(pass.desc.id, res);
}
}
for (slot_id, slot) in &mut self.slots {
if slot.bind_group_id.is_none() && slot.create_desc.is_some() {
let s = debug_span!("res_creation", slot = slot.name);
let _e = s.enter();
debug!("Creating bind group for slot");
match &slot.create_desc {
Some(SlotDescriptor::BufferInit(bi)) => { Some(SlotDescriptor::BufferInit(bi)) => {
let label = format!("B_{}", slot.name); let label = format!("B_{}", slot.name);
let wb = bi.as_wgpu(Some(&label)); let wb = bi.as_wgpu(Some(&label));
@ -127,8 +197,8 @@ impl RenderGraph {
let buf = device.create_buffer_init(&wb); let buf = device.create_buffer_init(&wb);
slot.value = SlotValue::Buffer(buf); slot.value = SlotValue::Buffer(buf);
debug!(slot=slot.name, "Created and initialized buffer for slot"); debug!("Created and initialized buffer for slot");
}, }
Some(SlotDescriptor::Buffer(b)) => { Some(SlotDescriptor::Buffer(b)) => {
let label = format!("B_{}", slot.name); let label = format!("B_{}", slot.name);
let wb = b.as_wgpu(Some(&label)); let wb = b.as_wgpu(Some(&label));
@ -136,89 +206,116 @@ impl RenderGraph {
let buf = device.create_buffer(&wb); let buf = device.create_buffer(&wb);
slot.value = SlotValue::Buffer(buf); slot.value = SlotValue::Buffer(buf);
debug!(slot=slot.name, "Created buffer"); debug!("Created buffer");
} }
Some(SlotDescriptor::Texture(t)) => {
let label = format!("Tex_{}", slot.name);
let wt = t.as_wgpu(Some(&label));
let tex = device.create_texture(&wt);
slot.value = SlotValue::Texture(tex);
debug!("Created texture");
}
Some(SlotDescriptor::TextureView(tv)) => {
// texture views cannot be done in this step. Not only would we run into a
// borrow checker error when trying to get the texture for the view, we
// can also not guarantee that the texture was actually created.
let tex_slot = self
.slot_names
.get(&tv.texture_label)
.expect("Failed to find texture for texture view slot");
later_slots.push_back((*slot_id, *tex_slot));
debug!("Queuing slot resources for later creation");
}
//Some(SlotDescriptor::Sampler(b)) => {}, //Some(SlotDescriptor::Sampler(b)) => {},
//Some(SlotDescriptor::Texture(b)) => {}, //Some(SlotDescriptor::Texture(b)) => {},
//Some(SlotDescriptor::TextureView(b)) => {}, Some(SlotDescriptor::None) => {}
Some(SlotDescriptor::None) => {}, None => {}
None => {},
_ => todo!(), _ => todo!(),
} }
} }
} }
todo!() // create buffers for the queued slots
while let Some((lower_id, upper_id)) = later_slots.pop_front() {
let many = self.slots.get_many_mut([&lower_id, &upper_id]).unwrap();
let (lower_slot, upper_slot) = match many {
[a, b] => (a, b),
};
let s = debug_span!("deferred_res_creation", slot = lower_slot.name);
let _e = s.enter();
match &lower_slot.create_desc {
Some(SlotDescriptor::TextureView(tv)) => {
let label = format!("TexView_{}", lower_slot.name);
let wt = tv.as_wgpu(Some(&label));
let tex = upper_slot.value.as_texture();
let texview = tex.create_view(&wt);
lower_slot.value = SlotValue::TextureView(texview);
debug!(slot = lower_slot.name, "Created texture view");
}
Some(SlotDescriptor::None) => {}
None => {}
_ => unreachable!("this slot should not have been put into the do later list"),
}
}
} }
pub fn prepare(&mut self) { pub fn prepare(&mut self) {
todo!() let builtin = {
let mut h = FxHashSet::default();
h.insert(0u64);
h
};
let descs = self.passes.values().map(|p| &p.desc).collect();
self.exec_path = Some(GraphExecutionPath::new(builtin, descs));
} }
pub fn render(&mut self, renderer: &mut BasicRenderer) { pub fn render(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, surface: &wgpu::Surface) {
todo!() let mut path = self.exec_path.take().unwrap();
let output = surface.get_current_texture().unwrap();
let view = output
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
// update the window texture view slot.
let window_tv_slot = self.slots.get_mut(&0).unwrap(); // 0 is window_texture_view
debug_assert_eq!(
window_tv_slot.name.as_str(),
"window_texture_view",
"unexpected slot where 'window_texture_view' should be"
);
window_tv_slot.value = SlotValue::TextureView(view);
while let Some(pass_id) = path.queue.pop_front() {
let pass = self.passes.get(&pass_id).unwrap().clone();
let label = format!("{} Encoder", pass.desc.name);
let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some(&label),
});
let mut context = RenderGraphContext::new(encoder, queue);
let mut inner = pass.inner.borrow_mut();
inner.execute(self, &pass.desc, &mut context);
queue.submit(std::iter::once(context.encoder.finish()));
}
output.present();
} }
/// Get a pipeline by name from the graph.
///
/// # Panics
/// Panics if the pipeline was not found by name.
#[inline(always)] #[inline(always)]
pub fn pipeline(&self, name: &str) -> &Pipeline { pub fn pipeline(&self, id: u64) -> &Pipeline {
&self.pipelines.get(name) &self.pipelines.get(&id).unwrap().pipeline
.unwrap()
.pipeline
}
/// Attempt to get a pipeline by name from the graph.
///
/// Returns `None` if the pipeline was not found by name,
#[inline(always)]
pub fn try_pipeline(&self, name: &str) -> Option<&Pipeline> {
self.pipelines.get(name)
.map(|p| &p.pipeline)
}
/// Get a [`RenderPipeline`] by name from the graph.
///
/// # Panics
/// Panics if the pipeline was not found by name, or if the pipeline is not a render pipeline.
#[inline(always)]
pub fn render_pipeline(&self, name: &str) -> &RenderPipeline {
self.pipelines.get(name)
.unwrap()
.pipeline
.as_render()
}
/// Attempt to get a [`RenderPipeline`] by name from the graph.
///
/// Returns `None` if the pipeline was not found by name, or if the pipeline is not a render pipeline.
#[inline(always)]
pub fn try_render_pipeline(&self, name: &str) -> Option<&RenderPipeline> {
self.pipelines.get(name)
.and_then(|p| p.pipeline.try_as_render())
}
/// Get a [`ComputePipeline`] by name from the graph.
///
/// # Panics
/// Panics if the pipeline was not found by name, or if the pipeline is not a compute pipeline.
#[inline(always)]
pub fn compute_pipeline(&self, name: &str) -> &ComputePipeline {
&self.pipelines.get(name)
.unwrap()
.pipeline
.as_compute()
}
/// Attempt to get a [`ComputePipeline`] by name from the graph.
///
/// Returns `None` if the pipeline was not found by name, or if the pipeline is not a render pipeline.
#[inline(always)]
pub fn try_compute_pipeline(&self, name: &str) -> Option<&ComputePipeline> {
self.pipelines.get(name)
.and_then(|p| p.pipeline.try_as_compute())
} }
#[inline(always)] #[inline(always)]
@ -228,19 +325,24 @@ impl RenderGraph {
#[inline(always)] #[inline(always)]
pub fn bind_group(&self, id: u64) -> &wgpu::BindGroup { pub fn bind_group(&self, id: u64) -> &wgpu::BindGroup {
self.try_bind_group(id) self.try_bind_group(id).expect("Unknown id for bind group")
.expect("Unknown id for bind group")
} }
#[inline(always)] #[inline(always)]
pub fn try_slot_bind_group(&self, id: u64) -> Option<&wgpu::BindGroup> { pub fn try_slot_bind_group(&self, id: u64) -> Option<&wgpu::BindGroup> {
self.slots.get(&id) self.slots.get(&id).and_then(|s| {
.and_then(|s| self.bind_groups.get(&s.bind_group_id.expect("Slot bind group has not been created yet"))) self.bind_groups.get(
&s.bind_group_id
.expect("Slot bind group has not been created yet"),
)
})
} }
#[inline(always)] #[inline(always)]
pub fn slot_bind_group(&self, id: u64) -> &wgpu::BindGroup { pub fn slot_bind_group(&self, id: u64) -> &wgpu::BindGroup {
let bg_id = self.slots.get(&id) let bg_id = self
.slots
.get(&id)
.expect("unknown slot id") .expect("unknown slot id")
.bind_group_id .bind_group_id
.expect("Slot bind group has not been created yet"); .expect("Slot bind group has not been created yet");
@ -248,17 +350,50 @@ impl RenderGraph {
} }
} }
pub(crate) struct BufferWrite {
/// The name of the slot that has the resource that will be written
target_slot: String,
bytes: Vec<u8>,
}
pub struct RenderGraphContext<'a> { pub struct RenderGraphContext<'a> {
encoder: wgpu::CommandEncoder, /// Becomes None when the encoder is submitted
queue: &'a wgpu::Queue, pub(crate) encoder: wgpu::CommandEncoder,
pub(crate) queue: &'a wgpu::Queue,
pub(crate) buffer_writes: Vec<BufferWrite>,
renderpass_desc: Vec<wgpu::RenderPassDescriptor<'a, 'a>>,
} }
impl<'a> RenderGraphContext<'a> { impl<'a> RenderGraphContext<'a> {
pub fn begin_render_pass(&mut self, desc: &wgpu::RenderPassDescriptor) -> wgpu::RenderPass { pub fn new(encoder: wgpu::CommandEncoder, queue: &'a wgpu::Queue) -> Self {
todo!() Self {
encoder,
queue,
buffer_writes: vec![],
renderpass_desc: vec![],
}
}
pub fn begin_render_pass(
&'a mut self,
desc: wgpu::RenderPassDescriptor<'a, 'a>,
) -> wgpu::RenderPass {
self.encoder.begin_render_pass(&desc)
} }
pub fn begin_compute_pass(&mut self, desc: &wgpu::ComputePassDescriptor) -> wgpu::ComputePass { pub fn begin_compute_pass(&mut self, desc: &wgpu::ComputePassDescriptor) -> wgpu::ComputePass {
todo!() self.encoder.begin_compute_pass(desc)
}
pub fn write_buffer(&mut self, target_slot: &str, bytes: &[u8]) {
//self.queue.write_buffer(buffer, offset, data)
self.buffer_writes.push(BufferWrite {
target_slot: target_slot.to_string(),
bytes: bytes.to_vec(),
})
}
pub fn write_buffer_muck<T: bytemuck::NoUninit>(&mut self, target_slot: &str, bytes: T) {
self.write_buffer(target_slot, bytemuck::bytes_of(&bytes));
} }
} }

View File

@ -28,6 +28,23 @@ pub enum SlotValue {
Buffer(wgpu::Buffer), Buffer(wgpu::Buffer),
} }
impl SlotValue {
/// Gets `self` as a texture, panics if self is not a instance of [`SlotValue::Texture`].
pub fn as_texture(&self) -> &wgpu::Texture {
match self {
Self::Texture(t) => t,
_ => panic!("self is not an instance of SlotValue::Texture"),
}
}
pub fn as_texture_view(&self) -> &wgpu::TextureView {
match self {
Self::TextureView(t) => t,
_ => panic!("self is not an instance of SlotValue::TextureView"),
}
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum SlotDescriptor { pub enum SlotDescriptor {
/// Most likely this slot is an input, so it doesn't need to specify the descriptor. /// Most likely this slot is an input, so it doesn't need to specify the descriptor.
@ -57,6 +74,21 @@ pub struct RenderPassSlot {
pub desc: Option<SlotDescriptor>, pub desc: Option<SlotDescriptor>,
} }
#[derive(Clone)]
pub struct RenderGraphPipelineInfo {
pub label: String,
pub source: String,
}
impl RenderGraphPipelineInfo {
pub fn new(label: &str, source: &str) -> Self {
Self {
label: label.to_string(),
source: source.to_string(),
}
}
}
#[derive(Clone)] #[derive(Clone)]
pub struct RenderGraphPassDesc { pub struct RenderGraphPassDesc {
pub id: u64, pub id: u64,
@ -64,16 +96,18 @@ pub struct RenderGraphPassDesc {
pub pass_type: RenderPassType, pub pass_type: RenderPassType,
pub slots: Vec<RenderPassSlot>, pub slots: Vec<RenderPassSlot>,
slot_names: HashMap<String, u64>, slot_names: HashMap<String, u64>,
pub pipeline_info: Option<RenderGraphPipelineInfo>,
} }
impl RenderGraphPassDesc { impl RenderGraphPassDesc {
pub fn new(id: u64, name: &str, pass_type: RenderPassType) -> Self { pub fn new(id: u64, name: &str, pass_type: RenderPassType, pipeline_info: Option<RenderGraphPipelineInfo>) -> Self {
Self { Self {
id, id,
name: name.to_string(), name: name.to_string(),
pass_type, pass_type,
slots: vec![], slots: vec![],
slot_names: HashMap::default(), slot_names: HashMap::default(),
pipeline_info
} }
} }
@ -84,7 +118,7 @@ impl RenderGraphPassDesc {
#[inline(always)] #[inline(always)]
pub fn add_buffer_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option<SlotDescriptor>) { pub fn add_buffer_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option<SlotDescriptor>) {
debug_assert!(matches!(desc, Some(SlotDescriptor::Buffer(_)) | Some(SlotDescriptor::BufferInit(_)) ), debug_assert!(matches!(desc, None | Some(SlotDescriptor::Buffer(_)) | Some(SlotDescriptor::BufferInit(_)) ),
"slot descriptor does not match the type of slot"); "slot descriptor does not match the type of slot");
let slot = RenderPassSlot { let slot = RenderPassSlot {
@ -99,7 +133,7 @@ impl RenderGraphPassDesc {
#[inline(always)] #[inline(always)]
pub fn add_texture_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option<SlotDescriptor>) { pub fn add_texture_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option<SlotDescriptor>) {
debug_assert!(matches!(desc, Some(SlotDescriptor::Texture(_))), debug_assert!(matches!(desc, None | Some(SlotDescriptor::Texture(_))),
"slot descriptor does not match the type of slot"); "slot descriptor does not match the type of slot");
let slot = RenderPassSlot { let slot = RenderPassSlot {
@ -114,7 +148,7 @@ impl RenderGraphPassDesc {
#[inline(always)] #[inline(always)]
pub fn add_texture_view_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option<SlotDescriptor>) { pub fn add_texture_view_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option<SlotDescriptor>) {
debug_assert!(matches!(desc, Some(SlotDescriptor::TextureView(_))), debug_assert!(matches!(desc, None | Some(SlotDescriptor::TextureView(_))),
"slot descriptor does not match the type of slot"); "slot descriptor does not match the type of slot");
let slot = RenderPassSlot { let slot = RenderPassSlot {
@ -128,8 +162,8 @@ impl RenderGraphPassDesc {
} }
#[inline(always)] #[inline(always)]
pub fn add_texture_sampler_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option<SlotDescriptor>) { pub fn add_sampler_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option<SlotDescriptor>) {
debug_assert!(matches!(desc, Some(SlotDescriptor::Sampler(_))), debug_assert!(matches!(desc, None | Some(SlotDescriptor::Sampler(_))),
"slot descriptor does not match the type of slot"); "slot descriptor does not match the type of slot");
let slot = RenderPassSlot { let slot = RenderPassSlot {
@ -161,8 +195,8 @@ pub trait RenderGraphPass: 'static {
/// Create a render pass describer. /// Create a render pass describer.
/// ///
/// The `id` argument is passed as mutable so you can increment it as you use it for new slots. /// The `id` argument is passed as mutable so you can increment it as you use it for new slots.
fn desc(&self, graph: &mut RenderGraph, id: &mut u64) -> RenderGraphPassDesc; fn desc<'a, 'b>(&'a self, graph: &'b mut RenderGraph) -> RenderGraphPassDesc;
fn prepare(&mut self, world: &mut World); fn prepare(&mut self, world: &mut World, context: &mut RenderGraphContext);
fn execute(&mut self, graph: &mut RenderGraph, desc: &RenderGraphPassDesc, context: &mut RenderGraphContext); fn execute(&mut self, graph: &mut RenderGraph, desc: &RenderGraphPassDesc, context: &mut RenderGraphContext);
} }

View File

@ -1,4 +1,4 @@
mod light_cull_compute; /* mod light_cull_compute;
pub use light_cull_compute::*; pub use light_cull_compute::*;
mod base; mod base;
@ -6,3 +6,9 @@ pub use base::*;
mod depth_prepass; mod depth_prepass;
pub use depth_prepass::*; pub use depth_prepass::*;
mod simple_phong;
pub use simple_phong::*; */
mod triangle;
pub use triangle::*;

View File

@ -0,0 +1,95 @@
use glam::UVec2;
use tracing::warn;
use crate::{
render::{
camera::{CameraUniform, RenderCamera},
graph::{
BufferInitDescriptor, RenderGraphContext, RenderGraphPass, RenderGraphPassDesc,
RenderGraphPipelineInfo, RenderPassType, SlotAttribute, SlotDescriptor,
},
renderer::ScreenSize,
},
scene::CameraComponent,
};
/// Supplies some basic things other passes needs.
///
/// screen size buffer, camera buffer,
#[derive(Default)]
pub struct TrianglePass;
impl TrianglePass {
pub fn new() -> Self {
Self::default()
}
}
impl RenderGraphPass for TrianglePass {
fn desc(
&self,
graph: &mut crate::render::graph::RenderGraph,
) -> crate::render::graph::RenderGraphPassDesc {
let mut desc = RenderGraphPassDesc::new(
graph.next_id(),
"TrianglePass",
RenderPassType::Render,
Some(RenderGraphPipelineInfo::new(
"TriangleShader",
include_str!("../../shaders/triangle.wgsl"),
)),
);
desc.add_texture_view_slot(graph.next_id(), "window_texture_view", SlotAttribute::Input, None);
/* desc.add_buffer_slot(graph.next_id(), "screen_size_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor {
label: Some("B_ScreenSize".to_string()),
contents: bytemuck::bytes_of(&UVec2::new(800, 600)).to_vec(),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
})));
desc.add_buffer_slot(graph.next_id(), "camera_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor {
label: Some("B_Camera".to_string()),
contents: bytemuck::bytes_of(&CameraUniform::default()).to_vec(),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
}))); */
desc
}
fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {}
fn execute(
&mut self,
graph: &mut crate::render::graph::RenderGraph,
desc: &crate::render::graph::RenderGraphPassDesc,
context: &mut crate::render::graph::RenderGraphContext,
) {
let view = graph.slot_value(graph.slot_id("window_texture_view").unwrap()).unwrap().as_texture_view();
let encoder = &mut context.encoder;
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("TrianglePass"),
color_attachments: &[
// This is what @location(0) in the fragment shader targets
Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.1,
g: 0.2,
b: 0.3,
a: 1.0,
}),
store: true,
},
}),
],
depth_stencil_attachment: None,
});
let pipeline = graph.pipeline(desc.id);
pass.set_pipeline(&pipeline.as_render());
pass.draw(0..3, 0..1);
}
}

View File

@ -40,6 +40,19 @@ impl TextureViewDescriptor {
array_layer_count: d.array_layer_count, array_layer_count: d.array_layer_count,
} }
} }
pub fn as_wgpu<'a>(&self, label: Option<&'a str>) -> wgpu::TextureViewDescriptor<'a> {
wgpu::TextureViewDescriptor {
label,
format: self.format,
dimension: self.dimension,
aspect: self.aspect,
base_mip_level: self.base_mip_level,
mip_level_count: self.mip_level_count,
base_array_layer: self.base_array_layer,
array_layer_count: self.array_layer_count,
}
}
} }
@ -117,6 +130,21 @@ pub struct TextureDescriptor {
pub view_formats: Vec<wgpu::TextureFormat>, pub view_formats: Vec<wgpu::TextureFormat>,
} }
impl TextureDescriptor {
pub fn as_wgpu<'a>(&'a self, label: Option<&'a str>) -> wgpu::TextureDescriptor<'a> {
wgpu::TextureDescriptor {
label,
size: self.size,
mip_level_count: self.mip_level_count,
sample_count: self.sample_count,
dimension: self.dimension,
format: self.format,
usage: self.usage,
view_formats: &self.view_formats,
}
}
}
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct BufferDescriptor { pub struct BufferDescriptor {
@ -134,7 +162,7 @@ pub struct BufferDescriptor {
} }
impl BufferDescriptor { impl BufferDescriptor {
pub fn as_wgpu(&self, label: Option<&str>) -> wgpu::BufferDescriptor { pub fn as_wgpu<'a>(&self, label: Option<&'a str>) -> wgpu::BufferDescriptor<'a> {
wgpu::BufferDescriptor { wgpu::BufferDescriptor {
label, label,
size: self.size, size: self.size,
@ -156,7 +184,7 @@ pub struct BufferInitDescriptor {
} }
impl BufferInitDescriptor { impl BufferInitDescriptor {
pub fn as_wgpu(&self, label: Option<&str>) -> wgpu::util::BufferInitDescriptor { pub fn as_wgpu<'a>(&'a self, label: Option<&'a str>) -> wgpu::util::BufferInitDescriptor<'a> {
wgpu::util::BufferInitDescriptor { wgpu::util::BufferInitDescriptor {
label, label,
contents: &self.contents, contents: &self.contents,

View File

@ -56,13 +56,14 @@ impl RenderPipeline {
// Requires Features::CONSERVATIVE_RASTERIZATION // Requires Features::CONSERVATIVE_RASTERIZATION
conservative: false, conservative: false,
}, },
depth_stencil: Some(wgpu::DepthStencilState { /*depth_stencil: Some(wgpu::DepthStencilState {
format: RenderTexture::DEPTH_FORMAT, format: RenderTexture::DEPTH_FORMAT,
depth_write_enabled: true, depth_write_enabled: true,
depth_compare: wgpu::CompareFunction::Less, depth_compare: wgpu::CompareFunction::Less,
stencil: wgpu::StencilState::default(), // TODO: stencil buffer stencil: wgpu::StencilState::default(), // TODO: stencil buffer
bias: wgpu::DepthBiasState::default(), bias: wgpu::DepthBiasState::default(),
}), }),*/
depth_stencil: None,
multisample: wgpu::MultisampleState { multisample: wgpu::MultisampleState {
count: 1, count: 1,
mask: !0, mask: !0,

View File

@ -1,4 +1,5 @@
use std::collections::{VecDeque, HashSet}; use std::collections::{VecDeque, HashSet};
use std::ops::{Deref, DerefMut};
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
use std::borrow::Cow; use std::borrow::Cow;
@ -18,7 +19,7 @@ use wgpu::util::DeviceExt;
use winit::window::Window; use winit::window::Window;
use crate::math::Transform; use crate::math::Transform;
use crate::render::graph::{BasePass, DepthPrePass, LightCullComputePass}; use crate::render::graph::TrianglePass;
use crate::render::material::MaterialUniform; use crate::render::material::MaterialUniform;
use crate::render::render_buffer::BufferWrapperBuilder; use crate::render::render_buffer::BufferWrapperBuilder;
use crate::scene::CameraComponent; use crate::scene::CameraComponent;
@ -44,6 +45,20 @@ type SceneHandle = ResHandle<SceneGraph>;
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct ScreenSize(glam::UVec2); pub struct ScreenSize(glam::UVec2);
impl Deref for ScreenSize {
type Target = glam::UVec2;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for ScreenSize {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
pub trait Renderer { pub trait Renderer {
fn prepare(&mut self, main_world: &mut World); fn prepare(&mut self, main_world: &mut World);
fn render(&mut self) -> Result<(), wgpu::SurfaceError>; fn render(&mut self) -> Result<(), wgpu::SurfaceError>;
@ -212,9 +227,15 @@ impl BasicRenderer {
//let light_cull_compute = LightCullCompute::new(device.clone(), queue.clone(), size, &light_uniform_buffers, &camera_buffer, &mut depth_texture); //let light_cull_compute = LightCullCompute::new(device.clone(), queue.clone(), size, &light_uniform_buffers, &camera_buffer, &mut depth_texture);
let mut g = RenderGraph::new(config.clone()); let mut g = RenderGraph::new(config.clone());
g.add_pass(BasePass::new()); /* debug!("Adding base pass");
g.add_pass(TrianglePass::new());
debug!("Adding depth pre-pass");
g.add_pass(DepthPrePass::new()); g.add_pass(DepthPrePass::new());
g.add_pass(LightCullComputePass::new(size)); debug!("Adding light cull compute pass");
g.add_pass(LightCullComputePass::new(size)); */
debug!("Adding triangle pass");
g.add_pass(TrianglePass::new());
g.setup(&device); g.setup(&device);
let mut s = Self { let mut s = Self {
@ -261,7 +282,7 @@ impl Renderer for BasicRenderer {
#[instrument(skip(self))] #[instrument(skip(self))]
fn render(&mut self) -> Result<(), wgpu::SurfaceError> { fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
self.graph.render(self); self.graph.render(&self.device, &self.queue, &self.surface);
Ok(()) Ok(())
} }
@ -276,7 +297,7 @@ impl Renderer for BasicRenderer {
// tell other things of updated resize // tell other things of updated resize
self.surface.configure(&self.device, &self.config); self.surface.configure(&self.device, &self.config);
let mut world_ss = world.get_resource::<ScreenSize>(); let mut world_ss = world.get_resource_mut::<ScreenSize>();
world_ss.0 = glam::UVec2::new(new_size.width, new_size.height); world_ss.0 = glam::UVec2::new(new_size.width, new_size.height);
self.graph.surface_config = self.config.clone(); self.graph.surface_config = self.config.clone();
} }

View File

@ -0,0 +1,19 @@
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
};
@vertex
fn vs_main(
@builtin(vertex_index) in_vertex_index: u32,
) -> VertexOutput {
var out: VertexOutput;
let x = f32(1 - i32(in_vertex_index)) * 0.5;
let y = f32(i32(in_vertex_index & 1u) * 2 - 1) * 0.5;
out.clip_position = vec4<f32>(x, y, 0.0, 1.0);
return out;
}
@fragment
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
return vec4<f32>(0.3, 0.2, 0.1, 1.0);
}