Compare commits

..

3 Commits

Author SHA1 Message Date
SeanOMik ef68b2a4c5
render: create a RenderGraphLabel trait for graph labels instead of strings
ci/woodpecker/pr/debug Pipeline failed Details
ci/woodpecker/pr/release Pipeline failed Details
2024-06-02 21:35:59 -04:00
SeanOMik 41d77c5687
render: a tiny bit of code cleanup in mesh pass 2024-06-01 23:05:43 -04:00
SeanOMik bb21805278
render: add a debug_assert to ensure the developer doesn't reuse ids for slots 2024-06-01 22:55:50 -04:00
14 changed files with 341 additions and 316 deletions

10
Cargo.lock generated
View File

@ -1867,6 +1867,7 @@ dependencies = [
"instant",
"itertools 0.11.0",
"lyra-ecs",
"lyra-game-derive",
"lyra-math",
"lyra-reflect",
"lyra-resource",
@ -1887,6 +1888,15 @@ dependencies = [
"winit",
]
[[package]]
name = "lyra-game-derive"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.51",
]
[[package]]
name = "lyra-math"
version = "0.1.0"

View File

@ -4,6 +4,7 @@ version = "0.0.1"
edition = "2021"
[dependencies]
lyra-game-derive = { path = "./lyra-game-derive" }
lyra-resource = { path = "../lyra-resource" }
lyra-ecs = { path = "../lyra-ecs", features = [ "math" ] }
lyra-reflect = { path = "../lyra-reflect", features = [ "math" ] }

View File

@ -0,0 +1,14 @@
[package]
name = "lyra-game-derive"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0.70"
quote = "1.0.33"
syn = "2.0.41"

View File

@ -0,0 +1,35 @@
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
#[proc_macro_derive(RenderGraphLabel)]
pub fn derive_render_graph_label(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let type_ident = &input.ident;
proc_macro::TokenStream::from(quote! {
impl #impl_generics crate::render::graph::RenderGraphLabel for #type_ident #ty_generics #where_clause {
fn rc_clone(&self) -> std::rc::Rc<dyn crate::render::graph::RenderGraphLabel> {
std::rc::Rc::new(self.clone())
}
/* fn as_dyn(&self) -> &dyn crate::render::graph::RenderGraphLabel {
&self
}
fn as_partial_eq(&self) -> &dyn PartialEq<dyn crate::render::graph::RenderGraphLabel> {
self
} */
fn as_label_hash(&self) -> u64 {
let tyid = ::std::any::TypeId::of::<Self>();
let mut s = ::std::hash::DefaultHasher::new();
::std::hash::Hash::hash(&tyid, &mut s);
::std::hash::Hash::hash(self, &mut s);
::std::hash::Hasher::finish(&s)
}
}
})
}

View File

@ -1,9 +1,6 @@
mod pass;
use std::{
cell::RefCell,
collections::{HashMap, VecDeque},
rc::Rc,
sync::Arc,
cell::RefCell, collections::{HashMap, VecDeque}, fmt::Debug, hash::Hash, rc::Rc, sync::Arc
};
use itertools::Itertools;
@ -22,6 +19,62 @@ use wgpu::ComputePass;
use super::resource::{ComputePipeline, Pipeline, RenderPipeline};
pub trait RenderGraphLabel: Debug + 'static {
fn rc_clone(&self) -> Rc<dyn RenderGraphLabel>;
//fn as_dyn(&self) -> &dyn RenderGraphLabel;
//fn as_partial_eq(&self) -> &dyn PartialEq<dyn RenderGraphLabel>;
fn as_label_hash(&self) -> u64;
fn label_eq_rc(&self, other: &Rc<dyn RenderGraphLabel>) -> bool {
self.as_label_hash() == other.as_label_hash()
}
fn label_eq(&self, other: &dyn RenderGraphLabel) -> bool {
self.as_label_hash() == other.as_label_hash()
}
}
#[derive(Clone)]
pub struct RenderGraphLabelValue(Rc<dyn RenderGraphLabel>);
impl Debug for RenderGraphLabelValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
impl<L: RenderGraphLabel> From<L> for RenderGraphLabelValue {
fn from(value: L) -> Self {
Self(Rc::new(value))
}
}
impl From<Rc<dyn RenderGraphLabel>> for RenderGraphLabelValue {
fn from(value: Rc<dyn RenderGraphLabel>) -> Self {
Self(value)
}
}
impl From<&Rc<dyn RenderGraphLabel>> for RenderGraphLabelValue {
fn from(value: &Rc<dyn RenderGraphLabel>) -> Self {
Self(value.clone())
}
}
impl Hash for RenderGraphLabelValue {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
state.write_u64(self.0.as_label_hash());
}
}
impl PartialEq for RenderGraphLabelValue {
fn eq(&self, other: &Self) -> bool {
self.0.label_eq_rc(&other.0)
}
}
impl Eq for RenderGraphLabelValue {}
struct PassEntry {
inner: Arc<RefCell<dyn RenderGraphPass>>,
desc: Arc<RenderGraphPassDesc>,
@ -30,7 +83,7 @@ struct PassEntry {
}
pub struct BindGroupEntry {
pub name: String,
pub label: RenderGraphLabelValue,
/// BindGroup
pub bg: Rc<wgpu::BindGroup>,
/// BindGroupLayout
@ -39,7 +92,7 @@ pub struct BindGroupEntry {
#[allow(dead_code)]
struct ResourcedSlot {
name: String,
label: RenderGraphLabelValue,
ty: SlotType,
value: SlotValue,
}
@ -64,11 +117,13 @@ pub struct RenderGraph {
device: Rc<wgpu::Device>,
queue: Rc<wgpu::Queue>,
slots: FxHashMap<u64, ResourcedSlot>,
slot_names: HashMap<String, u64>,
/// HashMap used to lookup the slot id using the label's hash
slot_label_lookup: FxHashMap<RenderGraphLabelValue, u64>,
passes: FxHashMap<u64, PassEntry>,
// TODO: Use a SlotMap
bind_groups: FxHashMap<u64, BindGroupEntry>,
bind_group_names: HashMap<String, u64>,
/// HashMap used to lookup the bind group id using the label's hash
bind_group_names: FxHashMap<RenderGraphLabelValue, u64>,
// TODO: make pipelines a `type` parameter in RenderPasses,
// then the pipelines can be retrieved via TypeId to the pass.
pipelines: FxHashMap<u64, PipelineResource>,
@ -83,7 +138,7 @@ impl RenderGraph {
device,
queue,
slots: Default::default(),
slot_names: Default::default(),
slot_label_lookup: Default::default(),
passes: Default::default(),
bind_groups: Default::default(),
bind_group_names: Default::default(),
@ -102,8 +157,12 @@ impl RenderGraph {
self.current_id
}
pub fn slot_id(&self, name: &str) -> Option<u64> {
self.slot_names.get(name).cloned()
pub(crate) fn slot_id_rc(&self, label: &RenderGraphLabelValue) -> Option<u64> {
self.slot_label_lookup.get(&label.clone().into()).cloned()
}
pub fn slot_id(&self, label: &dyn RenderGraphLabel) -> Option<u64> {
self.slot_label_lookup.get(&label.rc_clone().into()).cloned()
}
#[instrument(skip(self, pass), level = "debug")]
@ -113,48 +172,53 @@ impl RenderGraph {
// collect all the slots of the pass
for slot in &mut desc.slots {
if let Some((id, other)) = self
.slot_names
.get(&slot.name)
.slot_label_lookup
.get(&slot.label)
.and_then(|id| self.slots.get_mut(id).map(|s| (id, s)))
{
debug_assert_eq!(
slot.ty, other.ty,
"slot {} in pass {} does not match existing slot of same name",
slot.name, desc.name
"slot {:?} in pass {:?} does not match existing slot of same name",
slot.label, desc.label
);
trace!(
"Found existing slot for {}, changing id to {}",
slot.name,
"Found existing slot for {:?}, changing id to {}",
slot.label,
id
);
// if there is a slot of the same name
slot.id = *id;
} else {
debug_assert!(!self.slots.contains_key(&slot.id),
"Reuse of id detected in render graph! Pass: {:?}, slot: {:?}",
desc.label, slot.label,
);
let res_slot = ResourcedSlot {
name: slot.name.clone(),
label: slot.label.clone(),
ty: slot.ty,
value: slot.value.clone().unwrap_or(SlotValue::None),
};
self.slots.insert(slot.id, res_slot);
self.slot_names.insert(slot.name.clone(), slot.id);
self.slot_label_lookup.insert(slot.label.clone(), slot.id);
}
}
// get clones of the bind groups and layouts
for (name, bg, bgl) in &desc.bind_groups {
for (label, bg, bgl) in &desc.bind_groups {
let bg_id = self.next_id();
self.bind_groups.insert(
bg_id,
BindGroupEntry {
name: name.clone(),
label: label.clone(),
bg: bg.clone(),
layout: bgl.clone(),
},
);
self.bind_group_names.insert(name.clone(), bg_id);
self.bind_group_names.insert(label.clone().into(), bg_id);
}
let index = self.execution_graph.add_node(desc.id);
@ -219,15 +283,15 @@ impl RenderGraph {
while let Some(bufwr) = context.buffer_writes.pop_front() {
let slot = self
.slots
.get(&self.slot_id(&bufwr.target_slot).unwrap())
.get(&self.slot_id_rc(&bufwr.target_slot).unwrap())
.expect(&format!(
"Failed to find slot '{}' for buffer write",
"Failed to find slot '{:?}' for buffer write",
bufwr.target_slot
));
let buf = slot
.value
.as_buffer()
.expect(&format!("Slot '{}' is not a buffer", bufwr.target_slot));
.expect(&format!("Slot '{:?}' is not a buffer", bufwr.target_slot));
self.queue.write_buffer(buf, bufwr.offset, &bufwr.bytes);
}
@ -243,7 +307,7 @@ impl RenderGraph {
.collect();
let path_names = sorted
.iter()
.map(|i| self.pass(*i).unwrap().name.clone())
.map(|i| self.pass(*i).unwrap().label.clone())
.collect_vec();
trace!("Render graph execution order: {:?}", path_names);
@ -252,7 +316,7 @@ impl RenderGraph {
let pass = self.passes.get(&pass_id).unwrap();
let pass_inn = pass.inner.clone();
let pass_desc = pass.desc.clone();
let label = format!("{} Encoder", pass_desc.name);
let label = format!("{:?} Encoder", pass_desc.label);
// encoders are not needed for presenter nodes.
let encoder = if pass_desc.pass_type.should_have_pipeline() {
@ -277,7 +341,7 @@ impl RenderGraph {
self.queue.submit(encoders.drain(..));
}
trace!("Executing {}", pass_desc.name);
trace!("Executing {:?}", pass_desc.label);
let mut inner = pass_inn.borrow_mut();
inner.execute(self, &*pass_desc, &mut context);
@ -336,24 +400,30 @@ impl RenderGraph {
}
#[inline(always)]
pub fn bind_group_id(&self, name: &str) -> Option<u64> {
self.bind_group_names.get(name).copied()
pub fn bind_group_id(&self, label: &dyn RenderGraphLabel) -> Option<u64> {
self.bind_group_names.get(&label.rc_clone().into()).copied()
}
pub fn add_edge(&mut self, from: &str, to: &str) {
pub fn add_edge(&mut self, from: impl RenderGraphLabel, to: impl RenderGraphLabel)
{
let from = RenderGraphLabelValue::from(from);
let to = RenderGraphLabelValue::from(to);
let from_idx = self
.passes
.iter()
.find(|p| p.1.desc.name == from)
.find(|p| p.1.desc.label == from)
.map(|p| p.1.graph_index)
.expect("Failed to find from pass");
let to_idx = self
.passes
.iter()
.find(|p| p.1.desc.name == to)
.find(|p| p.1.desc.label == to)
.map(|p| p.1.graph_index)
.expect("Failed to find to pass");
debug_assert_ne!(from_idx, to_idx, "cannot add edges between the same node");
self.execution_graph.add_edge(from_idx, to_idx, ());
}
@ -384,13 +454,13 @@ impl RenderGraph {
pub fn set_bind_groups<'a>(
&'a self,
pass: &mut ComputePass<'a>,
bind_groups: &[(&str, u32)],
bind_groups: &[(&dyn RenderGraphLabel, u32)],
) {
for (name, index) in bind_groups {
for (label, index) in bind_groups {
let bg = self
.bind_group_id(name)
.bind_group_id(*label)
.map(|bgi| self.bind_group(bgi))
.expect(&format!("Could not find bind group '{}'", name));
.expect(&format!("Could not find bind group '{:?}'", label));
pass.set_bind_group(*index, bg, &[]);
}
@ -400,7 +470,7 @@ impl RenderGraph {
/// A queued write to a GPU buffer targeting a graph slot.
pub(crate) struct GraphBufferWrite {
/// The name of the slot that has the resource that will be written
target_slot: String,
target_slot: RenderGraphLabelValue,
offset: u64,
bytes: Vec<u8>,
}
@ -455,9 +525,9 @@ impl<'a> RenderGraphContext<'a> {
/// data will be submitted to the GPU queue right after the prepare stage for all passes
/// is ran.
#[instrument(skip(self, bytes), level="trace", fields(size = bytes.len()))]
pub fn queue_buffer_write(&mut self, target_slot: &str, offset: u64, bytes: &[u8]) {
pub fn queue_buffer_write(&mut self, target_slot: impl RenderGraphLabel, offset: u64, bytes: &[u8]) {
self.buffer_writes.push_back(GraphBufferWrite {
target_slot: target_slot.to_string(),
target_slot: target_slot.into(),
offset,
bytes: bytes.to_vec(),
})
@ -467,7 +537,7 @@ impl<'a> RenderGraphContext<'a> {
#[instrument(skip(self, bytes), level="trace", fields(size = std::mem::size_of::<T>()))]
pub fn queue_buffer_write_with<T: bytemuck::NoUninit>(
&mut self,
target_slot: &str,
target_slot: impl RenderGraphLabel,
offset: u64,
bytes: T,
) {

View File

@ -4,7 +4,7 @@ use lyra_ecs::World;
use crate::render::resource::PipelineDescriptor;
use super::{RenderGraph, RenderGraphContext, RenderTarget};
use super::{RenderGraph, RenderGraphContext, RenderGraphLabel, RenderGraphLabelValue, RenderTarget};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum RenderPassType {
@ -96,7 +96,7 @@ pub struct RenderPassSlot {
pub ty: SlotType,
pub attribute: SlotAttribute,
pub id: u64,
pub name: String,
pub label: RenderGraphLabelValue,
/// The descriptor of the slot value.
/// This will be `None` if this slot is an input.
pub value: Option<SlotValue>,
@ -159,36 +159,36 @@ impl RenderGraphPipelineInfo {
pub struct RenderGraphPassDesc {
pub id: u64,
pub name: String,
pub label: RenderGraphLabelValue,
pub pass_type: RenderPassType,
pub slots: Vec<RenderPassSlot>,
slot_names: HashMap<String, u64>,
slot_label_lookup: HashMap<RenderGraphLabelValue, u64>,
pub pipeline_desc: Option<PipelineDescriptor>,
pub bind_groups: Vec<(
String,
RenderGraphLabelValue,
Rc<wgpu::BindGroup>,
Option<Rc<wgpu::BindGroupLayout>>,
)>,
}
impl RenderGraphPassDesc {
pub fn new(
pub fn new<L: RenderGraphLabel>(
id: u64,
name: &str,
label: L,
pass_type: RenderPassType,
pipeline_desc: Option<PipelineDescriptor>,
bind_groups: Vec<(&str, Rc<wgpu::BindGroup>, Option<Rc<wgpu::BindGroupLayout>>)>,
bind_groups: Vec<(&dyn RenderGraphLabel, Rc<wgpu::BindGroup>, Option<Rc<wgpu::BindGroupLayout>>)>,
) -> Self {
Self {
id,
name: name.to_string(),
label: label.into(),
pass_type,
slots: vec![],
slot_names: HashMap::default(),
slot_label_lookup: HashMap::default(),
pipeline_desc,
bind_groups: bind_groups
.into_iter()
.map(|bg| (bg.0.to_string(), bg.1, bg.2))
.map(|bg| (bg.0.rc_clone().into(), bg.1, bg.2))
.collect(),
}
}
@ -199,15 +199,15 @@ impl RenderGraphPassDesc {
"input slots should not have values"
);
self.slot_names.insert(slot.name.clone(), slot.id);
self.slot_label_lookup.insert(slot.label.clone().into(), slot.id);
self.slots.push(slot);
}
#[inline(always)]
pub fn add_buffer_slot(
pub fn add_buffer_slot<L: RenderGraphLabel>(
&mut self,
id: u64,
name: &str,
label: L,
attribute: SlotAttribute,
value: Option<SlotValue>,
) {
@ -218,7 +218,7 @@ impl RenderGraphPassDesc {
let slot = RenderPassSlot {
id,
name: name.to_string(),
label: label.into(),
ty: SlotType::Buffer,
attribute,
value,
@ -227,10 +227,10 @@ impl RenderGraphPassDesc {
}
#[inline(always)]
pub fn add_texture_slot(
pub fn add_texture_slot<L: RenderGraphLabel>(
&mut self,
id: u64,
name: &str,
label: L,
attribute: SlotAttribute,
value: Option<SlotValue>,
) {
@ -241,7 +241,7 @@ impl RenderGraphPassDesc {
let slot = RenderPassSlot {
id,
name: name.to_string(),
label: label.into(),
ty: SlotType::Texture,
attribute,
value,
@ -250,10 +250,10 @@ impl RenderGraphPassDesc {
}
#[inline(always)]
pub fn add_texture_view_slot(
pub fn add_texture_view_slot<L: RenderGraphLabel>(
&mut self,
id: u64,
name: &str,
label: L,
attribute: SlotAttribute,
value: Option<SlotValue>,
) {
@ -264,7 +264,7 @@ impl RenderGraphPassDesc {
let slot = RenderPassSlot {
id,
name: name.to_string(),
label: label.into(),
ty: SlotType::TextureView,
attribute,
value,
@ -273,10 +273,10 @@ impl RenderGraphPassDesc {
}
#[inline(always)]
pub fn add_sampler_slot(
pub fn add_sampler_slot<L: RenderGraphLabel>(
&mut self,
id: u64,
name: &str,
label: L,
attribute: SlotAttribute,
value: Option<SlotValue>,
) {
@ -287,7 +287,7 @@ impl RenderGraphPassDesc {
let slot = RenderPassSlot {
id,
name: name.to_string(),
label: label.into(),
ty: SlotType::Sampler,
attribute,
value,

View File

@ -1,6 +1,7 @@
use std::{cell::RefCell, rc::Rc};
use glam::UVec2;
use lyra_game_derive::RenderGraphLabel;
use tracing::warn;
use winit::dpi::PhysicalSize;
@ -16,6 +17,19 @@ use crate::{
scene::CameraComponent,
};
#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)]
pub struct BasePassLabel;
#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)]
pub enum BasePassSlots {
DepthTexture,
ScreenSize,
Camera,
MainRenderTarget,
WindowTextureView,
DepthTextureView,
}
/// Supplies some basic things other passes needs.
///
/// screen size buffer, camera buffer,
@ -89,13 +103,13 @@ impl RenderGraphPass for BasePass {
let mut desc = RenderGraphPassDesc::new(
graph.next_id(),
"base",
BasePassLabel,
RenderPassType::Node,
None,
vec![
("depth_texture", depth_texture_bg, Some(depth_texture_bgl)),
("screen_size", screen_size_bg, Some(screen_size_bgl)),
("camera", camera_bg, Some(camera_bgl)),
(&BasePassSlots::DepthTexture, depth_texture_bg, Some(depth_texture_bgl)),
(&BasePassSlots::ScreenSize, screen_size_bg, Some(screen_size_bgl)),
(&BasePassSlots::Camera, camera_bg, Some(camera_bgl)),
],
);
@ -104,7 +118,7 @@ impl RenderGraphPass for BasePass {
ty: SlotType::RenderTarget,
attribute: SlotAttribute::Output,
id: self.main_rt_id,
name: "main_render_target".into(),
label: BasePassSlots::MainRenderTarget.into(),
value: Some(SlotValue::RenderTarget(Rc::new(RefCell::new(
render_target,
)))),
@ -112,25 +126,25 @@ impl RenderGraphPass for BasePass {
self.window_tv_id = graph.next_id();
desc.add_texture_view_slot(
self.window_tv_id,
"window_texture_view",
BasePassSlots::WindowTextureView,
SlotAttribute::Output,
Some(SlotValue::Lazy),
);
desc.add_texture_view_slot(
graph.next_id(),
"depth_texture_view",
BasePassSlots::DepthTextureView,
SlotAttribute::Output,
Some(SlotValue::TextureView(depth_texture_view)),
);
desc.add_buffer_slot(
graph.next_id(),
"screen_size_buffer",
BasePassSlots::ScreenSize,
SlotAttribute::Output,
Some(SlotValue::Buffer(Rc::new(screen_size_buf))),
);
desc.add_buffer_slot(
graph.next_id(),
"camera_buffer",
BasePassSlots::Camera,
SlotAttribute::Output,
Some(SlotValue::Buffer(Rc::new(camera_buf))),
);
@ -144,7 +158,7 @@ impl RenderGraphPass for BasePass {
RenderCamera::new(PhysicalSize::new(self.screen_size.x, self.screen_size.y));
let uniform = render_cam.calc_view_projection(&camera);
context.queue_buffer_write_with("camera_buffer", 0, uniform)
context.queue_buffer_write_with(BasePassSlots::Camera, 0, uniform)
} else {
warn!("Missing camera!");
}
@ -170,7 +184,7 @@ impl RenderGraphPass for BasePass {
|| rt.surface_config.height != self.screen_size.y
{
self.screen_size = UVec2::new(rt.surface_config.width, rt.surface_config.height);
context.queue_buffer_write_with("screen_size_buffer", 0, self.screen_size)
context.queue_buffer_write_with(BasePassSlots::ScreenSize, 0, self.screen_size)
}
let surface_tex = rt.surface.get_current_texture().unwrap();

View File

@ -1,3 +1,5 @@
use lyra_game_derive::RenderGraphLabel;
use crate::render::{
graph::{
RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute,
@ -6,6 +8,14 @@ use crate::render::{
light::LightUniformBuffers,
};
#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)]
pub struct LightBasePassLabel;
#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)]
pub enum LightBasePassSlots {
Lights
}
/// Supplies some basic things other passes needs.
///
/// screen size buffer, camera buffer,
@ -31,11 +41,11 @@ impl RenderGraphPass for LightBasePass {
let mut desc = RenderGraphPassDesc::new(
graph.next_id(),
"light_base",
LightBasePassLabel,
RenderPassType::Node,
None,
vec![(
"light_buffers",
&LightBasePassSlots::Lights,
light_buffers.bind_group.clone(),
Some(light_buffers.bind_group_layout.clone()),
)],
@ -43,7 +53,7 @@ impl RenderGraphPass for LightBasePass {
desc.add_buffer_slot(
graph.next_id(),
"light_buffers",
LightBasePassSlots::Lights,
SlotAttribute::Output,
Some(SlotValue::Buffer(light_buffers.buffer.clone())),
);

View File

@ -1,6 +1,7 @@
use std::{mem, rc::Rc};
use lyra_ecs::World;
use lyra_game_derive::RenderGraphLabel;
use wgpu::util::DeviceExt;
use crate::render::{
@ -11,6 +12,19 @@ use crate::render::{
resource::{ComputePipelineDescriptor, PipelineDescriptor, Shader},
};
use super::{BasePassSlots, LightBasePassSlots};
#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)]
pub struct LightCullComputePassLabel;
#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)]
pub enum LightCullComputePassSlots {
LightGridTexture,
LightGridTextureView,
IndexCounterBuffer,
LightIndicesGridGroup,
}
pub struct LightCullComputePass {
workgroup_size: glam::UVec2,
}
@ -35,7 +49,7 @@ impl RenderGraphPass for LightCullComputePass {
// get the size of the work group for the grid
let main_rt = graph
.slot_id("main_render_target")
.slot_id(&BasePassSlots::MainRenderTarget)
.and_then(|s| graph.slot_value(s))
.and_then(|s| s.as_render_target())
.expect("missing main render target");
@ -157,14 +171,14 @@ impl RenderGraphPass for LightCullComputePass {
drop(main_rt);
let pass_id = graph.next_id();
let depth_tex_bgl = graph.bind_group_layout(graph.bind_group_id("depth_texture").unwrap());
let camera_bgl = graph.bind_group_layout(graph.bind_group_id("camera").unwrap());
let lights_bgl = graph.bind_group_layout(graph.bind_group_id("light_buffers").unwrap());
let screen_size_bgl = graph.bind_group_layout(graph.bind_group_id("screen_size").unwrap());
let depth_tex_bgl = graph.bind_group_layout(graph.bind_group_id(&BasePassSlots::DepthTexture).unwrap());
let camera_bgl = graph.bind_group_layout(graph.bind_group_id(&BasePassSlots::Camera).unwrap());
let lights_bgl = graph.bind_group_layout(graph.bind_group_id(&LightBasePassSlots::Lights).unwrap());
let screen_size_bgl = graph.bind_group_layout(graph.bind_group_id(&BasePassSlots::ScreenSize).unwrap());
let mut desc = RenderGraphPassDesc::new(
pass_id,
"light_cull_compute",
LightCullComputePassLabel,
RenderPassType::Compute,
Some(PipelineDescriptor::Compute(ComputePipelineDescriptor {
label: Some("light_cull_pipeline".into()),
@ -180,7 +194,7 @@ impl RenderGraphPass for LightCullComputePass {
shader_entry_point: "cs_main".into(),
})),
vec![(
"light_indices_grid",
&LightCullComputePassSlots::LightIndicesGridGroup,
light_indices_bg,
Some(light_indices_bg_layout),
)],
@ -188,20 +202,20 @@ impl RenderGraphPass for LightCullComputePass {
desc.add_texture_view_slot(
graph.next_id(),
"window_texture_view",
BasePassSlots::WindowTextureView,
SlotAttribute::Input,
None,
);
desc.add_buffer_slot(
graph.next_id(),
"screen_size_buffer",
BasePassSlots::ScreenSize,
SlotAttribute::Input,
None,
);
desc.add_buffer_slot(graph.next_id(), "camera_buffer", SlotAttribute::Input, None);
desc.add_buffer_slot(graph.next_id(), BasePassSlots::Camera, SlotAttribute::Input, None);
desc.add_buffer_slot(
graph.next_id(),
"index_counter_buffer",
LightCullComputePassSlots::IndexCounterBuffer,
SlotAttribute::Output,
Some(SlotValue::Buffer(Rc::new(light_index_counter_buffer))),
);
@ -239,11 +253,11 @@ impl RenderGraphPass for LightCullComputePass {
graph.set_bind_groups(
&mut pass,
&[
("depth_texture", 0),
("camera", 1),
("light_buffers", 2),
("light_indices_grid", 3),
("screen_size", 4),
(&BasePassSlots::DepthTexture, 0),
(&BasePassSlots::Camera, 1),
(&LightBasePassSlots::Lights, 2),
(&LightCullComputePassSlots::LightIndicesGridGroup, 3),
(&BasePassSlots::ScreenSize, 4),
],
);

View File

@ -3,6 +3,7 @@ use std::{collections::{HashSet, VecDeque}, rc::Rc};
use glam::Vec3;
use itertools::izip;
use lyra_ecs::{query::{filter::{Has, Not, Or}, Entities, Res, TickOf}, relation::{ChildOf, RelationOriginComponent}, Component, Entity};
use lyra_game_derive::RenderGraphLabel;
use lyra_math::Transform;
use lyra_resource::{gltf::Mesh, ResHandle};
use lyra_scene::{SceneGraph, WorldTransform};
@ -21,20 +22,26 @@ use crate::{
DeltaTime,
};
use super::{BasePassSlots, LightBasePassSlots, LightCullComputePassSlots};
type MeshHandle = ResHandle<Mesh>;
type SceneHandle = ResHandle<SceneGraph>;
#[derive(Debug, Hash, Clone, Default, PartialEq, RenderGraphLabel)]
pub struct MeshesPassLabel;
#[derive(Debug, Hash, Clone, PartialEq, RenderGraphLabel)]
pub enum MeshesPassSlots {
Material
}
struct MeshBufferStorage {
buffer_vertex: BufferStorage,
buffer_indices: Option<(wgpu::IndexFormat, BufferStorage)>,
//#[allow(dead_code)]
//render_texture: Option<RenderTexture>,
// maybe this should just be a Uuid and the material can be retrieved though
// MeshPass's `material_buffers` field?
material: Option<Rc<Material>>,
// The index of the transform for this entity.
// The tuple is structured like this: (transform index, index of transform inside the buffer)
//transform_index: TransformBufferIndices,
}
#[derive(Clone, Debug, Component)]
@ -180,16 +187,8 @@ impl MeshPass {
}
/// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed.
#[instrument(skip(self, device, queue, transform, mesh, entity))]
fn process_mesh(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, entity: Entity, transform: Transform, mesh: &Mesh, mesh_uuid: Uuid) -> bool {
let _ = transform;
/* if self.transform_buffers.should_expand() {
self.transform_buffers.expand_buffers(&self.device);
}
self.transform_buffers.update_or_insert(&self.queue, &self.render_limits,
entity, || ( transform.calculate_mat4(), glam::Mat3::from_quat(transform.rotation) )); */
#[instrument(skip(self, device, queue, mesh, entity))]
fn process_mesh(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, entity: Entity, mesh: &Mesh, mesh_uuid: Uuid) -> bool {
#[allow(clippy::map_entry)]
if !self.mesh_buffers.contains_key(&mesh_uuid) {
// create the mesh's buffers
@ -233,7 +232,7 @@ impl RenderGraphPass for MeshPass {
self.default_texture = Some(RenderTexture::from_bytes(&device, &graph.queue, texture_bind_group_layout.clone(), bytes, "default_texture").unwrap());
// get surface config format
let main_rt = graph.slot_id("main_render_target")
let main_rt = graph.slot_id(&BasePassSlots::MainRenderTarget)
.and_then(|s| graph.slot_value(s))
.and_then(|s| s.as_render_target())
.expect("missing main render target");
@ -243,10 +242,10 @@ impl RenderGraphPass for MeshPass {
// get the id here to make borrow checker happy
let pass_id = graph.next_id();
let camera_bgl = graph.bind_group_layout(graph.bind_group_id("camera").unwrap());
let lights_bgl = graph.bind_group_layout(graph.bind_group_id("light_buffers").unwrap());
let camera_bgl = graph.bind_group_layout(graph.bind_group_id(&BasePassSlots::Camera).unwrap());
let lights_bgl = graph.bind_group_layout(graph.bind_group_id(&LightBasePassSlots::Lights).unwrap());
let light_grid_bgl = graph
.bind_group_layout(graph.bind_group_id("light_indices_grid")
.bind_group_layout(graph.bind_group_id(&LightCullComputePassSlots::LightIndicesGridGroup)
.expect("Missing light grid bind group"));
let shader = Rc::new(Shader {
@ -256,7 +255,7 @@ impl RenderGraphPass for MeshPass {
let desc = RenderGraphPassDesc::new(
pass_id,
"meshes",
MeshesPassLabel,
RenderPassType::Render,
Some(PipelineDescriptor::Render(RenderPipelineDescriptor {
label: Some("meshes".into()),
@ -298,7 +297,7 @@ impl RenderGraphPass for MeshPass {
multiview: None,
})),
vec![
("material", material_bg, Some(material_bgl)),
(&MeshesPassSlots::Material, material_bg, Some(material_bgl)),
],
);
@ -343,6 +342,9 @@ impl RenderGraphPass for MeshPass {
{
alive_entities.insert(entity);
// Interpolate the transform for this entity using a component.
// If the entity does not have the component then it will be queued to be added
// to it after all the entities are prepared for rendering.
let interp_transform = match interp_tran {
Some(mut interp_transform) => {
// found in https://youtu.be/YJB1QnEmlTs?t=472
@ -362,22 +364,28 @@ impl RenderGraphPass for MeshPass {
}
};
{
// expand the transform buffers if they need to be.
// this is done in its own scope to avoid multiple mutable references to self at
// once; aka, make the borrow checker happy
let transforms = self.transforms.as_mut().unwrap();
if transforms.needs_expand() {
debug!("Expanding transform buffers");
transforms.expand_buffers(device);
}
}
if let Some((mesh_han, mesh_epoch)) = mesh_pair {
if let Some(mesh) = mesh_han.data_ref() {
// if process mesh did not just create a new mesh, and the epoch
// shows that the scene has changed, verify that the mesh buffers
// dont need to be resent to the gpu.
if !self.process_mesh(device, queue, entity, interp_transform, &*mesh, mesh_han.uuid())
if !self.process_mesh(device, queue, entity, &*mesh, mesh_han.uuid())
&& mesh_epoch == last_epoch {
self.check_mesh_buffers(device, queue, &mesh_han);
}
let transforms = self.transforms.as_mut().unwrap();
if transforms.needs_expand() {
debug!("Expanding transform buffers");
transforms.expand_buffers(device);
}
let group = TransformGroup::EntityRes(entity, mesh_han.uuid());
let transform_id = transforms.update_or_push(device, queue, &render_limits,
group, interp_transform.calculate_mat4(), glam::Mat3::from_quat(interp_transform.rotation));
@ -404,17 +412,12 @@ impl RenderGraphPass for MeshPass {
// if process mesh did not just create a new mesh, and the epoch
// shows that the scene has changed, verify that the mesh buffers
// dont need to be resent to the gpu.
if !self.process_mesh(device, queue, entity, mesh_interpo, &*mesh, mesh_han.uuid())
if !self.process_mesh(device, queue, entity, &*mesh, mesh_han.uuid())
&& scene_epoch == last_epoch {
self.check_mesh_buffers(device, queue, &mesh_han);
}
let transforms = self.transforms.as_mut().unwrap();
if transforms.needs_expand() {
debug!("Expanding transform buffers");
transforms.expand_buffers(device);
}
let scene_mesh_group = TransformGroup::Res(scene_han.uuid(), mesh_han.uuid());
let group = TransformGroup::OwnedGroup(entity, scene_mesh_group.into());
let transform_id = transforms.update_or_push(device, queue, &render_limits,
@ -448,29 +451,29 @@ impl RenderGraphPass for MeshPass {
let encoder = context.encoder.as_mut().unwrap();
let view = graph
.slot_value(graph.slot_id("window_texture_view").unwrap())
.slot_value(graph.slot_id(&BasePassSlots::WindowTextureView).unwrap())
.unwrap()
.as_texture_view();
let depth_view = graph
.slot_value(graph.slot_id("depth_texture_view").unwrap())
.slot_value(graph.slot_id(&BasePassSlots::DepthTextureView).unwrap())
.unwrap()
.as_texture_view();
let camera_bg = graph
.bind_group(graph.bind_group_id("camera")
.bind_group(graph.bind_group_id(&BasePassSlots::Camera)
.expect("Missing camera bind group"));
let lights_bg = graph
.bind_group(graph.bind_group_id("light_buffers")
.bind_group(graph.bind_group_id(&LightBasePassSlots::Lights)
.expect("Missing lights bind group"));
let light_grid_bg = graph
.bind_group(graph.bind_group_id("light_indices_grid")
.bind_group(graph.bind_group_id(&LightCullComputePassSlots::LightIndicesGridGroup)
.expect("Missing light grid bind group"));
let material_bg = graph
.bind_group(graph.bind_group_id("material")
.bind_group(graph.bind_group_id(&MeshesPassSlots::Material)
.expect("Missing material bind group"));
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {

View File

@ -1,12 +1,6 @@
mod light_cull_compute;
pub use light_cull_compute::*;
/*mod depth_prepass;
pub use depth_prepass::*; */
/* mod simple_phong;
pub use simple_phong::*; */
mod base;
pub use base::*;
@ -18,6 +12,3 @@ pub use light_base::*;
mod present_pass;
pub use present_pass::*;
mod triangle;
pub use triangle::*;

View File

@ -1,16 +1,32 @@
use crate::render::graph::{RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassSlot, RenderPassType, SlotAttribute, SlotType};
use std::hash::Hash;
use lyra_game_derive::RenderGraphLabel;
use crate::render::graph::{RenderGraphContext, RenderGraphLabel, RenderGraphLabelValue, RenderGraphPass, RenderGraphPassDesc, RenderPassSlot, RenderPassType, SlotAttribute, SlotType};
#[derive(Debug, Clone, Hash, PartialEq, RenderGraphLabel)]
pub struct PresentPassLabel(RenderGraphLabelValue);
impl PresentPassLabel {
pub fn new(label: impl RenderGraphLabel) -> Self {
Self(label.into())
}
}
/// Supplies some basic things other passes needs.
///
/// screen size buffer, camera buffer,
pub struct PresentPass {
render_target_slot: String,
/// Label of this pass
label: PresentPassLabel,
//render_target_slot: Rc<dyn RenderGraphLabel>,
}
impl PresentPass {
pub fn new(render_target_slot: &str) -> Self {
pub fn new(render_target_slot: impl RenderGraphLabel) -> Self {
Self {
render_target_slot: render_target_slot.into(),
//render_target_slot: render_target_slot.rc_clone(),
label: PresentPassLabel::new(render_target_slot),
}
}
}
@ -19,7 +35,7 @@ impl RenderGraphPass for PresentPass {
fn desc(&mut self, graph: &mut crate::render::graph::RenderGraph) -> crate::render::graph::RenderGraphPassDesc {
let mut desc = RenderGraphPassDesc::new(
graph.next_id(),
&format!("present_{}", self.render_target_slot),
self.label.clone(),
RenderPassType::Presenter,
None,
vec![],
@ -30,7 +46,7 @@ impl RenderGraphPass for PresentPass {
ty: SlotType::RenderTarget,
attribute: SlotAttribute::Input,
id: graph.next_id(),
name: self.render_target_slot.clone(),
label: self.label.0.clone(),
value: None,
}
);
@ -43,8 +59,8 @@ impl RenderGraphPass for PresentPass {
}
fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _desc: &crate::render::graph::RenderGraphPassDesc, _context: &mut crate::render::graph::RenderGraphContext) {
let id = graph.slot_id(&self.render_target_slot)
.expect(&format!("render target slot '{}' for PresentPass is missing", self.render_target_slot));
let id = graph.slot_id_rc(&self.label.0)
.expect(&format!("render target slot '{:?}' for PresentPass is missing", self.label.0));
let mut slot = graph.slot_value_mut(id).unwrap().as_render_target_mut().unwrap();
let surf_tex = slot.current_texture.take().unwrap();
surf_tex.present();

View File

@ -1,153 +0,0 @@
use std::rc::Rc;
use crate::{
render::{
graph::{
RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType,
SlotAttribute, SlotValue,
},
render_buffer::BufferWrapper,
resource::{FragmentState, PipelineDescriptor, RenderPipelineDescriptor, Shader, VertexState},
},
DeltaTime,
};
/// A demo pass that renders a triangle that changes colors every frame.
#[derive(Default)]
pub struct TrianglePass {
acc: f32,
}
impl TrianglePass {
pub fn new() -> Self {
Self::default()
}
}
impl RenderGraphPass for TrianglePass {
fn desc(
&mut self,
graph: &mut crate::render::graph::RenderGraph,
) -> crate::render::graph::RenderGraphPassDesc {
let shader = Rc::new(Shader {
label: Some("triangle_shader".into()),
source: include_str!("../../shaders/triangle.wgsl").to_string(),
});
let device = graph.device();
let (color_bgl, color_bg, color_buf, _) = BufferWrapper::builder()
.label_prefix("color")
.visibility(wgpu::ShaderStages::FRAGMENT)
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
.contents(&[glam::Vec4::new(0.902, 0.639, 0.451, 1.0)])
.finish_parts(device);
let color_bgl = Rc::new(color_bgl);
let color_bg = Rc::new(color_bg);
let main_rt = graph.slot_id("main_render_target")
.and_then(|s| graph.slot_value(s))
.and_then(|s| s.as_render_target())
.expect("missing main render target");
let surface_config_format = main_rt.surface_config.format;
drop(main_rt);
let mut desc = RenderGraphPassDesc::new(
graph.next_id(),
"triangle",
RenderPassType::Render,
Some(PipelineDescriptor::Render(RenderPipelineDescriptor {
label: Some("triangle_pipeline".into()),
layouts: vec![color_bgl.clone()],
push_constant_ranges: vec![],
vertex: VertexState {
module: shader.clone(),
entry_point: "vs_main".into(),
buffers: vec![],
},
fragment: Some(FragmentState {
module: shader,
entry_point: "fs_main".into(),
targets: vec![Some(wgpu::ColorTargetState {
format: surface_config_format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
depth_stencil: None,
primitive: wgpu::PrimitiveState::default(),
multisample: wgpu::MultisampleState::default(),
multiview: None,
})),
vec![("color_bg", color_bg, Some(color_bgl))],
);
desc.add_texture_view_slot(
graph.next_id(),
"window_texture_view",
SlotAttribute::Input,
None,
);
desc.add_buffer_slot(
graph.next_id(),
"color_buffer",
SlotAttribute::Output,
Some(SlotValue::Buffer(Rc::new(color_buf))),
);
desc
}
fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
const SPEED: f32 = 1.5;
let dt = **world.get_resource::<DeltaTime>();
self.acc += dt;
let x = (self.acc * SPEED).sin();
let y = ((self.acc + 2.15) * SPEED).sin();
let z = ((self.acc + 5.35) * SPEED).sin();
let color = glam::Vec4::new(x, y, z, 1.0);
context.queue_buffer_write_with("color_buffer", 0, color);
}
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 color_bg = graph.bind_group(graph.bind_group_id("color_bg").unwrap());
let encoder = context.encoder.as_mut().unwrap();
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("triangle_pass"),
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.set_bind_group(0, color_bg, &[]);
pass.draw(0..3, 0..1);
}
}

View File

@ -7,7 +7,7 @@ use lyra_ecs::World;
use tracing::{debug, instrument, warn};
use winit::window::Window;
use crate::render::graph::{BasePass, LightBasePass, LightCullComputePass, MeshPass, PresentPass};
use crate::render::graph::{BasePass, BasePassLabel, BasePassSlots, LightBasePass, LightBasePassLabel, LightCullComputePass, LightCullComputePassLabel, MeshPass, MeshesPassLabel, PresentPass, PresentPassLabel};
use super::graph::RenderGraph;
use super::{resource::RenderPipeline, render_job::RenderJob};
@ -137,16 +137,16 @@ impl BasicRenderer {
g.add_pass(MeshPass::new());
debug!("Adding present pass");
g.add_pass(PresentPass::new("main_render_target"));
g.add_pass(PresentPass::new(BasePassSlots::MainRenderTarget));
g.add_edge("base", "light_base");
g.add_edge("light_base", "light_cull_compute");
g.add_edge("base", "meshes");
g.add_edge(BasePassLabel, LightBasePassLabel);
g.add_edge(LightBasePassLabel, LightCullComputePassLabel);
g.add_edge(BasePassLabel, MeshesPassLabel);
// make sure that present runs last
g.add_edge("base", "present_main_render_target");
g.add_edge("light_cull_compute", "present_main_render_target");
g.add_edge("meshes", "present_main_render_target");
g.add_edge(BasePassLabel, PresentPassLabel::new(BasePassSlots::MainRenderTarget));
g.add_edge(LightCullComputePassLabel, PresentPassLabel::new(BasePassSlots::MainRenderTarget));
g.add_edge(MeshesPassLabel, PresentPassLabel::new(BasePassSlots::MainRenderTarget));
g.setup(&device);
@ -188,7 +188,7 @@ impl Renderer for BasicRenderer {
self.size = new_size;
// update surface config and the surface
let mut rt = self.graph.slot_value_mut(self.graph.slot_id("main_render_target").unwrap())
let mut rt = self.graph.slot_value_mut(self.graph.slot_id(&BasePassSlots::MainRenderTarget).unwrap())
.unwrap().as_render_target_mut().unwrap();
rt.surface_config.width = new_size.width;
rt.surface_config.height = new_size.height;