Implement a Render Graph #16

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

View File

@ -0,0 +1,35 @@
use std::{collections::HashMap, ops::Deref};
use wgpu::PipelineLayout;
pub struct ComputePipeline {
layout: PipelineLayout,
wgpu_pipeline: wgpu::ComputePipeline,
}
impl Deref for ComputePipeline {
type Target = wgpu::ComputePipeline;
fn deref(&self) -> &Self::Target {
&self.wgpu_pipeline
}
}
impl ComputePipeline {
pub fn new(layout: PipelineLayout, pipeline: wgpu::ComputePipeline) -> Self {
Self {
layout,
wgpu_pipeline: pipeline,
}
}
#[inline(always)]
pub fn layout(&self) -> &PipelineLayout {
&self.layout
}
#[inline(always)]
pub fn wgpu_pipeline(&self) -> &wgpu::ComputePipeline {
&self.wgpu_pipeline
}
}

View File

@ -6,12 +6,16 @@ pub use pass::*;
mod passes;
pub use passes::*;
mod slot_desc;
pub use slot_desc::*;
mod execution_path;
use rustc_hash::FxHashMap;
use wgpu::RenderPass;
use tracing::debug;
use wgpu::{util::DeviceExt, RenderPass};
use super::renderer::{BasicRenderer, Renderer};
use super::{compute_pipeline::ComputePipeline, pipeline::Pipeline, render_pipeline::RenderPipeline, renderer::{BasicRenderer, Renderer}};
struct PassEntry {
inner: Box<dyn RenderGraphPass>,
@ -19,22 +23,54 @@ struct PassEntry {
}
struct ResourcedSlot {
slot: RenderPassSlot,
name: String,
//slot: RenderPassSlot,
ty: SlotType,
value: SlotValue,
bindgroup: wgpu::BindGroup,
// will when the bind group is created
/// The id of the bind group for this slot. Becomes `Some` when the render graph creates the
/// bind group
bind_group_id: Option<u64>,
create_desc: Option<SlotDescriptor>,
}
/// Stores the pipeline and other resources it uses.
///
/// This stores the bind groups that have been created for it
pub struct PipelineResource {
pub pipeline: Pipeline,
/// Lookup map for bind groups using names
pub bg_layout_name_lookup: HashMap<String, u32>,
}
#[derive(Default)]
pub struct RenderGraph {
slots: FxHashMap<u64, ResourcedSlot>,
slot_names: HashMap<String, u64>,
// slots with same name
slot_mutlikey: FxHashMap<u64, u64>,
passes: FxHashMap<u64, PassEntry>,
// TODO: Use a SlotMap
bind_groups: FxHashMap<u64, wgpu::BindGroup>,
// TODO: make pipelines a `type` parameter in RenderPasses,
// then the pipelines can be retrieved via TypeId to the pass.
pipelines: HashMap<String, PipelineResource>,
current_id: u64,
pub(crate) surface_config: wgpu::SurfaceConfiguration,
}
impl RenderGraph {
pub fn new() -> Self {
Self::default()
pub fn new(surface_config: wgpu::SurfaceConfiguration) -> Self {
Self {
slots: Default::default(),
slot_names: Default::default(),
slot_mutlikey: Default::default(),
passes: Default::default(),
bind_groups: Default::default(),
pipelines: Default::default(),
current_id: 0,
surface_config,
}
}
pub fn slot_id(&self, name: &str) -> Option<u64> {
@ -46,13 +82,32 @@ impl RenderGraph {
.map(|s| &s.desc)
}
pub fn bindgroup(&self, id: u64) -> Option<&wgpu::BindGroup> {
self.slots.get(&id)
.map(|s| &s.bindgroup)
}
pub fn add_pass<P: RenderGraphPass>(&mut self, pass: P) {
let desc = pass.desc(&mut self.current_id);
let mut desc = pass.desc(self, &mut self.current_id);
for slot in &mut desc.slots {
if let Some((id, other)) = self.slot_names.get(&slot.name)
.and_then(|id| self.slots.get_mut(id).map(|s| (id, s)))
{
slot.id = *id;
if slot.desc.is_some() && other.create_desc.is_none() {
other.create_desc = slot.desc;
}
} else {
let res_slot = ResourcedSlot {
name: slot.name,
ty: slot.ty,
value: SlotValue::None,
bind_group_id: None,
create_desc: slot.desc,
};
self.slots.insert(slot.id, res_slot);
self.slot_names.insert(slot.name, slot.id);
}
}
self.passes.insert(desc.id, PassEntry {
inner: Box::new(pass),
desc,
@ -60,7 +115,39 @@ impl RenderGraph {
}
/// Creates all buffers required for the passes, also creates an internal execution path.
pub fn setup(&mut self) {
pub fn setup(&mut self, device: &wgpu::Device) {
for slot in self.slots.values_mut() {
if slot.bind_group_id.is_none() {
match slot.create_desc {
Some(SlotDescriptor::BufferInit(bi)) => {
let label = format!("B_{}", slot.name);
let wb = bi.as_wgpu(Some(&label));
let buf = device.create_buffer_init(&wb);
slot.value = SlotValue::Buffer(buf);
debug!(slot=slot.name, "Created and initialized buffer for slot");
},
Some(SlotDescriptor::Buffer(b)) => {
let label = format!("B_{}", slot.name);
let wb = b.as_wgpu(Some(&label));
let buf = device.create_buffer(&wb);
slot.value = SlotValue::Buffer(buf);
debug!(slot=slot.name, "Created buffer");
}
//Some(SlotDescriptor::Sampler(b)) => {},
//Some(SlotDescriptor::Texture(b)) => {},
//Some(SlotDescriptor::TextureView(b)) => {},
Some(SlotDescriptor::None) => {},
None => {},
_ => todo!(),
}
}
}
todo!()
}
@ -71,13 +158,102 @@ impl RenderGraph {
pub fn render(&mut self, renderer: &mut BasicRenderer) {
todo!()
}
/// Get a pipeline by name from the graph.
///
/// # Panics
/// Panics if the pipeline was not found by name.
#[inline(always)]
pub fn pipeline(&self, name: &str) -> &Pipeline {
&self.pipelines.get(name)
.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)]
pub fn try_bind_group(&self, id: u64) -> Option<&wgpu::BindGroup> {
self.bind_groups.get(&id)
}
#[inline(always)]
pub fn bind_group(&self, id: u64) -> &wgpu::BindGroup {
self.try_bind_group(id)
.expect("Unknown id for bind group")
}
#[inline(always)]
pub fn try_slot_bind_group(&self, id: u64) -> Option<&wgpu::BindGroup> {
self.slots.get(&id)
.and_then(|s| self.bind_groups.get(&s.bind_group_id.expect("Slot bind group has not been created yet")))
}
#[inline(always)]
pub fn slot_bind_group(&self, id: u64) -> &wgpu::BindGroup {
let bg_id = self.slots.get(&id)
.expect("unknown slot id")
.bind_group_id
.expect("Slot bind group has not been created yet");
self.bind_group(id)
}
}
pub struct RenderGraphContext {
pub struct RenderGraphContext<'a> {
encoder: wgpu::CommandEncoder,
queue: &'a wgpu::Queue,
}
impl RenderGraphContext {
impl<'a> RenderGraphContext<'a> {
pub fn begin_render_pass(&mut self, desc: &wgpu::RenderPassDescriptor) -> wgpu::RenderPass {
todo!()
}
@ -85,30 +261,4 @@ impl RenderGraphContext {
pub fn begin_compute_pass(&mut self, desc: &wgpu::ComputePassDescriptor) -> wgpu::ComputePass {
todo!()
}
}
#[cfg(test)]
mod tests {
use winit::dpi::PhysicalSize;
use super::{execution_path::GraphExecutionPath, BasePass, LightCullComputePass, RenderGraph};
#[test]
fn full_test() {
let mut graph = RenderGraph::new();
graph.add_pass(BasePass::new());
graph.add_pass(LightCullComputePass::new(PhysicalSize::new(800, 600)));
let descs = graph.passes.values().map(|pass| &pass.desc).collect();
let mut path = GraphExecutionPath::new(descs);
println!("Pass execution order:");
let mut num = 1;
while let Some(pass_id) = path.queue.pop_front() {
let pass = graph.pass(pass_id).unwrap();
println!(" {}: {}", num, pass.name);
num += 1;
}
}
}

View File

@ -2,7 +2,7 @@ use std::collections::HashMap;
use lyra_ecs::World;
use super::{RenderGraph, RenderGraphContext};
use super::{BufferDescriptor, BufferInitDescriptor, RenderGraph, RenderGraphContext, SamplerDescriptor, TextureDescriptor, TextureViewDescriptor};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum RenderPassType {
@ -15,16 +15,31 @@ pub enum RenderPassType {
pub enum SlotType {
TextureView,
Sampler,
Texture,
Buffer,
}
#[derive(Debug)]
pub enum SlotValue {
None,
TextureView(wgpu::TextureView),
Sampler(wgpu::Sampler),
Texture(wgpu::Texture),
Buffer(wgpu::Buffer),
}
#[derive(Clone, Debug)]
pub enum SlotDescriptor {
/// Most likely this slot is an input, so it doesn't need to specify the descriptor.
None,
TextureView(TextureViewDescriptor),
Sampler(SamplerDescriptor),
Texture(TextureDescriptor),
Buffer(BufferDescriptor),
BufferInit(BufferInitDescriptor),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum SlotAttribute {
Input,
@ -37,8 +52,9 @@ pub struct RenderPassSlot {
pub attribute: SlotAttribute,
pub id: u64,
pub name: String,
// buffer desc, texture desc
/// The descriptor of the slot value.
/// This will be `None` if this slot is an input.
pub desc: Option<SlotDescriptor>,
}
#[derive(Clone)]
@ -67,34 +83,61 @@ impl RenderGraphPassDesc {
}
#[inline(always)]
pub fn add_buffer_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute) {
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(_)) ),
"slot descriptor does not match the type of slot");
let slot = RenderPassSlot {
id,
name: name.to_string(),
ty: SlotType::Buffer,
attribute,
desc,
};
self.add_slot(slot);
}
#[inline(always)]
pub fn add_texture_view_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute) {
pub fn add_texture_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option<SlotDescriptor>) {
debug_assert!(matches!(desc, Some(SlotDescriptor::Texture(_))),
"slot descriptor does not match the type of slot");
let slot = RenderPassSlot {
id,
name: name.to_string(),
ty: SlotType::Texture,
attribute,
desc,
};
self.add_slot(slot);
}
#[inline(always)]
pub fn add_texture_view_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option<SlotDescriptor>) {
debug_assert!(matches!(desc, Some(SlotDescriptor::TextureView(_))),
"slot descriptor does not match the type of slot");
let slot = RenderPassSlot {
id,
name: name.to_string(),
ty: SlotType::TextureView,
attribute,
desc,
};
self.add_slot(slot);
}
#[inline(always)]
pub fn add_texture_sampler_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute) {
pub fn add_texture_sampler_slot(&mut self, id: u64, name: &str, attribute: SlotAttribute, desc: Option<SlotDescriptor>) {
debug_assert!(matches!(desc, Some(SlotDescriptor::Sampler(_))),
"slot descriptor does not match the type of slot");
let slot = RenderPassSlot {
id,
name: name.to_string(),
ty: SlotType::Sampler,
attribute,
desc,
};
self.add_slot(slot);
}
@ -118,7 +161,7 @@ pub trait RenderGraphPass: 'static {
/// Create a render pass describer.
///
/// The `id` argument is passed as mutable so you can increment it as you use it for new slots.
fn desc(&self, id: &mut u64) -> RenderGraphPassDesc;
fn desc(&self, graph: &mut RenderGraph, id: &mut u64) -> RenderGraphPassDesc;
fn prepare(&mut self, world: &mut World);
fn execute(&mut self, graph: &mut RenderGraph, desc: &RenderGraphPassDesc, context: &mut RenderGraphContext);

View File

@ -1,4 +1,6 @@
use crate::render::graph::{RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute};
use glam::UVec2;
use crate::render::{camera::CameraUniform, graph::{BufferInitDescriptor, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute, SlotDescriptor}};
/// Supplies some basic things other passes needs.
///
@ -13,13 +15,21 @@ impl BasePass {
}
impl RenderGraphPass for BasePass {
fn desc(&self, id: &mut u64) -> crate::render::graph::RenderGraphPassDesc {
fn desc(&self, graph: &mut crate::render::graph::RenderGraph, id: &mut u64) -> crate::render::graph::RenderGraphPassDesc {
let mut desc = RenderGraphPassDesc::new(*id, "BasePass", RenderPassType::Compute);
*id += 1;
desc.add_buffer_slot(*id, "screen_size_buffer", SlotAttribute::Output);
desc.add_buffer_slot(*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,
})));
*id += 1;
desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Output);
desc.add_buffer_slot(*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,
})));
*id += 1;
desc

View File

@ -0,0 +1,94 @@
use glam::UVec2;
use crate::render::{
camera::CameraUniform,
graph::{
BufferInitDescriptor, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SamplerDescriptor, SlotAttribute, SlotDescriptor, TextureDescriptor, TextureViewDescriptor
},
};
/// Supplies some basic things other passes needs.
///
/// screen size buffer, camera buffer,
#[derive(Default)]
pub struct DepthPrePass;
impl DepthPrePass {
pub fn new() -> Self {
Self::default()
}
}
impl RenderGraphPass for DepthPrePass {
fn desc(
&self,
graph: &mut crate::render::graph::RenderGraph,
id: &mut u64,
) -> crate::render::graph::RenderGraphPassDesc {
let mut desc = RenderGraphPassDesc::new(*id, "DepthPrePass", RenderPassType::Compute);
*id += 1;
let size = wgpu::Extent3d {
width: graph.surface_config.width,
height: graph.surface_config.height,
depth_or_array_layers: 1,
};
desc.add_texture_slot(
*id,
"depth_texture",
SlotAttribute::Output,
Some(SlotDescriptor::Texture(TextureDescriptor {
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT
| wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: vec![],
})),
);
*id += 1;
desc.add_texture_view_slot(
*id,
"depth_texture_view",
SlotAttribute::Output,
Some(SlotDescriptor::TextureView(TextureViewDescriptor::default_view("depth_texture"))),
);
*id += 1;
desc.add_texture_view_slot(
*id,
"depth_texture_sampler",
SlotAttribute::Output,
Some(SlotDescriptor::Sampler(SamplerDescriptor {
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
compare: Some(wgpu::CompareFunction::LessEqual),
lod_min_clamp: 0.0,
lod_max_clamp: 100.0,
..SamplerDescriptor::default_sampler("depth_texture")
})),
);
*id += 1;
desc
}
fn prepare(&mut self, world: &mut lyra_ecs::World) {
let _ = world;
todo!()
}
fn execute(
&mut self,
graph: &mut crate::render::graph::RenderGraph,
desc: &crate::render::graph::RenderGraphPassDesc,
context: &mut crate::render::graph::RenderGraphContext,
) {
let _ = (graph, desc, context);
todo!()
}
}

View File

@ -1,6 +1,11 @@
use std::mem;
use lyra_ecs::World;
use crate::render::graph::{RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType, SlotAttribute};
use crate::render::graph::{
BufferInitDescriptor, RenderGraphContext, RenderGraphPass, RenderGraphPassDesc, RenderPassType,
SlotAttribute, SlotDescriptor, TextureDescriptor, TextureViewDescriptor,
};
pub struct LightCullComputePass {
workgroup_size: glam::UVec2,
@ -9,28 +14,78 @@ pub struct LightCullComputePass {
impl LightCullComputePass {
pub fn new(screen_size: winit::dpi::PhysicalSize<u32>) -> Self {
Self {
workgroup_size: glam::UVec2::new(screen_size.width, screen_size.height)
workgroup_size: glam::UVec2::new(screen_size.width, screen_size.height),
}
}
}
impl RenderGraphPass for LightCullComputePass {
fn desc(&self, id: &mut u64) -> crate::render::graph::RenderGraphPassDesc {
fn desc(
&self,
graph: &mut crate::render::graph::RenderGraph,
id: &mut u64,
) -> crate::render::graph::RenderGraphPassDesc {
let mut desc = RenderGraphPassDesc::new(*id, "LightCullCompute", RenderPassType::Compute);
*id += 1;
desc.add_buffer_slot(*id, "screen_size_buffer", SlotAttribute::Input);
desc.add_buffer_slot(*id, "screen_size_buffer", SlotAttribute::Input, None);
*id += 1;
desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Input);
desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Input, None);
*id += 1;
desc.add_buffer_slot(*id, "light_indices", SlotAttribute::Output);
let mut contents = Vec::<u8>::new();
let contents_len =
self.workgroup_size.x * self.workgroup_size.y * 200 * mem::size_of::<u32>() as u32;
contents.resize(contents_len as _, 0);
desc.add_buffer_slot(
*id,
"light_indices",
SlotAttribute::Output,
Some(SlotDescriptor::BufferInit(BufferInitDescriptor {
label: Some("B_LightIndices".to_string()),
contents,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
})),
);
*id += 1;
/* desc.add_buffer_slot(*id, "indices_buffer", SlotAttribute::Output);
*id += 1; */
desc.add_texture_view_slot(*id, "grid_texture", SlotAttribute::Output);
let size = wgpu::Extent3d {
width: self.workgroup_size.x,
height: self.workgroup_size.y,
depth_or_array_layers: 1,
};
desc.add_texture_slot(
*id,
"lightgrid_texture",
SlotAttribute::Output,
Some(SlotDescriptor::Texture(TextureDescriptor {
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rg32Uint, // vec2<uint>
usage: wgpu::TextureUsages::STORAGE_BINDING,
view_formats: vec![],
})),
);
*id += 1;
desc.add_texture_view_slot(
*id,
"lightgrid_texture_view",
SlotAttribute::Output,
Some(SlotDescriptor::TextureView(TextureViewDescriptor {
texture_label: "lightgrid_texture".to_string(),
format: Some(wgpu::TextureFormat::Rg32Uint), // vec2<uint>
dimension: Some(wgpu::TextureViewDimension::D2),
aspect: wgpu::TextureAspect::All,
base_mip_level: 0,
mip_level_count: None,
base_array_layer: 0,
array_layer_count: None,
})),
);
*id += 1;
desc
}
@ -40,18 +95,44 @@ impl RenderGraphPass for LightCullComputePass {
todo!()
}
fn execute(&mut self, graph: &mut crate::render::graph::RenderGraph, _: &crate::render::graph::RenderGraphPassDesc, context: &mut RenderGraphContext) {
fn execute(
&mut self,
graph: &mut crate::render::graph::RenderGraph,
_: &crate::render::graph::RenderGraphPassDesc,
context: &mut RenderGraphContext,
) {
let mut pass = context.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Pass_lightCull")
label: Some("Pass_lightCull"),
});
let depth_tex = graph.bindgroup(graph.slot_id("depth_texture").expect("Could not find depth texture slot")).unwrap();
let camera_bg = graph.bindgroup(graph.slot_id("camera_buffer").expect("Could not find camera buffers")).unwrap();
let screen_size_bg = graph.bindgroup(graph.slot_id("screen_size_buffer").expect("Could not find screen size buffer slot")).unwrap();
let indices_bg = graph.bindgroup(graph.slot_id("light_indices").expect("Could not find light index buffer slot")).unwrap();
let light_grid_bg = graph.bindgroup(graph.slot_id("grid_texture").expect("Could not find light grid buffer slot")).unwrap();
let pipeline = graph.compute_pipeline("main");
pass.set_pipeline(pipeline);
//pass.set_pipeline(pipeline)
let depth_tex = graph.slot_bind_group(
graph
.slot_id("depth_texture")
.expect("Could not find depth texture slot"),
);
let camera_bg = graph.slot_bind_group(
graph
.slot_id("camera_buffer")
.expect("Could not find camera buffers"),
);
let screen_size_bg = graph.slot_bind_group(
graph
.slot_id("screen_size_buffer")
.expect("Could not find screen size buffer slot"),
);
let indices_bg = graph.slot_bind_group(
graph
.slot_id("light_indices")
.expect("Could not find light index buffer slot"),
);
let light_grid_bg = graph.slot_bind_group(
graph
.slot_id("grid_texture")
.expect("Could not find light grid buffer slot"),
);
pass.set_bind_group(0, depth_tex, &[]);
pass.set_bind_group(1, camera_bg, &[]);
@ -60,7 +141,5 @@ impl RenderGraphPass for LightCullComputePass {
pass.set_bind_group(4, screen_size_bg, &[]);
pass.dispatch_workgroups(self.workgroup_size.x, self.workgroup_size.y, 1);
}
}
}

View File

@ -2,4 +2,7 @@ mod light_cull_compute;
pub use light_cull_compute::*;
mod base;
pub use base::*;
pub use base::*;
mod depth_prepass;
pub use depth_prepass::*;

View File

@ -0,0 +1,166 @@
use std::num::{NonZeroU32, NonZeroU8};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TextureViewDescriptor {
/// The label of the texture that this view will be created from.
pub texture_label: String,
/// Format of the texture view. At this time, it must be the same as the underlying format of the texture.
pub format: Option<wgpu::TextureFormat>,
/// The dimension of the texture view. For 1D textures, this must be `D1`. For 2D textures it must be one of
/// `D2`, `D2Array`, `Cube`, and `CubeArray`. For 3D textures it must be `D3`
pub dimension: Option<wgpu::TextureViewDimension>,
/// Aspect of the texture. Color textures must be [`TextureAspect::All`].
pub aspect: wgpu::TextureAspect,
/// Base mip level.
pub base_mip_level: u32,
/// Mip level count.
/// If `Some(count)`, `base_mip_level + count` must be less or equal to underlying texture mip count.
/// If `None`, considered to include the rest of the mipmap levels, but at least 1 in total.
pub mip_level_count: Option<NonZeroU32>,
/// Base array layer.
pub base_array_layer: u32,
/// Layer count.
/// If `Some(count)`, `base_array_layer + count` must be less or equal to the underlying array count.
/// If `None`, considered to include the rest of the array layers, but at least 1 in total.
pub array_layer_count: Option<NonZeroU32>,
}
impl TextureViewDescriptor {
pub fn default_view(texture_label: &str) -> Self {
let d = wgpu::TextureViewDescriptor::default();
Self {
texture_label: texture_label.to_string(),
format: d.format,
dimension: d.dimension,
aspect: d.aspect,
base_array_layer: d.base_array_layer,
base_mip_level: d.base_mip_level,
mip_level_count: d.mip_level_count,
array_layer_count: d.array_layer_count,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct SamplerDescriptor {
/// The label of the texture that this view will be created from.
pub texture_label: String,
/// How to deal with out of bounds accesses in the u (i.e. x) direction
pub address_mode_u: wgpu::AddressMode,
/// How to deal with out of bounds accesses in the v (i.e. y) direction
pub address_mode_v: wgpu::AddressMode,
/// How to deal with out of bounds accesses in the w (i.e. z) direction
pub address_mode_w: wgpu::AddressMode,
/// How to filter the texture when it needs to be magnified (made larger)
pub mag_filter: wgpu::FilterMode,
/// How to filter the texture when it needs to be minified (made smaller)
pub min_filter: wgpu::FilterMode,
/// How to filter between mip map levels
pub mipmap_filter: wgpu::FilterMode,
/// Minimum level of detail (i.e. mip level) to use
pub lod_min_clamp: f32,
/// Maximum level of detail (i.e. mip level) to use
pub lod_max_clamp: f32,
/// If this is enabled, this is a comparison sampler using the given comparison function.
pub compare: Option<wgpu::CompareFunction>,
/// Valid values: 1, 2, 4, 8, and 16.
pub anisotropy_clamp: Option<NonZeroU8>,
/// Border color to use when address_mode is [`AddressMode::ClampToBorder`]
pub border_color: Option<wgpu::SamplerBorderColor>,
}
impl SamplerDescriptor {
pub fn default_sampler(texture_label: &str) -> Self {
let d = wgpu::SamplerDescriptor::default();
Self {
texture_label: texture_label.to_string(),
address_mode_u: d.address_mode_u,
address_mode_v: d.address_mode_v,
address_mode_w: d.address_mode_w,
mag_filter: d.mag_filter,
min_filter: d.min_filter,
mipmap_filter: d.mipmap_filter,
lod_min_clamp: d.lod_min_clamp,
lod_max_clamp: d.lod_max_clamp,
compare: d.compare,
anisotropy_clamp: d.anisotropy_clamp,
border_color: d.border_color,
}
}
}
#[repr(C)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct TextureDescriptor {
/// Size of the texture. All components must be greater than zero. For a
/// regular 1D/2D texture, the unused sizes will be 1. For 2DArray textures,
/// Z is the number of 2D textures in that array.
pub size: wgpu::Extent3d,
/// Mip count of texture. For a texture with no extra mips, this must be 1.
pub mip_level_count: u32,
/// Sample count of texture. If this is not 1, texture must have [`BindingType::Texture::multisampled`] set to true.
pub sample_count: u32,
/// Dimensions of the texture.
pub dimension: wgpu::TextureDimension,
/// Format of the texture.
pub format: wgpu::TextureFormat,
/// Allowed usages of the texture. If used in other ways, the operation will panic.
pub usage: wgpu::TextureUsages,
/// Specifies what view formats will be allowed when calling create_view() on this texture.
///
/// View formats of the same format as the texture are always allowed.
///
/// Note: currently, only the srgb-ness is allowed to change. (ex: Rgba8Unorm texture + Rgba8UnormSrgb view)
pub view_formats: Vec<wgpu::TextureFormat>,
}
#[repr(C)]
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct BufferDescriptor {
/// Size of a buffer.
pub size: wgpu::BufferAddress,
/// Usages of a buffer. If the buffer is used in any way that isn't specified here, the operation
/// will panic.
pub usage: wgpu::BufferUsages,
/// Allows a buffer to be mapped immediately after they are made. It does not have to be [`BufferUsages::MAP_READ`] or
/// [`BufferUsages::MAP_WRITE`], all buffers are allowed to be mapped at creation.
///
/// If this is `true`, [`size`](#structfield.size) must be a multiple of
/// [`COPY_BUFFER_ALIGNMENT`].
pub mapped_at_creation: bool,
}
impl BufferDescriptor {
pub fn as_wgpu(&self, label: Option<&str>) -> wgpu::BufferDescriptor {
wgpu::BufferDescriptor {
label,
size: self.size,
usage: self.usage,
mapped_at_creation: self.mapped_at_creation,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct BufferInitDescriptor {
/// Debug label of a buffer. This will show up in graphics debuggers for easy identification.
pub label: Option<String>,
/// Contents of a buffer on creation.
pub contents: Vec<u8>,
/// Usages of a buffer. If the buffer is used in any way that isn't specified here, the operation
/// will panic.
pub usage: wgpu::BufferUsages,
}
impl BufferInitDescriptor {
pub fn as_wgpu(&self, label: Option<&str>) -> wgpu::util::BufferInitDescriptor {
wgpu::util::BufferInitDescriptor {
label,
contents: &self.contents,
usage: self.usage,
}
}
}

View File

@ -1,5 +1,7 @@
pub mod renderer;
pub mod render_pipeline;
pub mod compute_pipeline;
pub mod pipeline;
pub mod vertex;
pub mod desc_buf_lay;
pub mod render_buffer;

View File

@ -0,0 +1,65 @@
use super::{compute_pipeline::ComputePipeline, render_pipeline::RenderPipeline};
pub enum Pipeline {
Render(RenderPipeline),
Compute(ComputePipeline),
}
impl Into<RenderPipeline> for Pipeline {
fn into(self) -> RenderPipeline {
match self {
Self::Render(r) => r,
_ => panic!("Pipeline is not a RenderPipeline"),
}
}
}
impl Into<ComputePipeline> for Pipeline {
fn into(self) -> ComputePipeline {
match self {
Self::Compute(c) => c,
_ => panic!("Pipeline is not a RenderPipeline"),
}
}
}
impl Pipeline {
pub fn bind_group_layout(&self, index: u32) -> wgpu::BindGroupLayout {
match self {
Pipeline::Render(r) => r.get_bind_group_layout(index),
Pipeline::Compute(c) => c.get_bind_group_layout(index),
}
}
/// Gets self as a render pipeline, panics if self is not a render pipeline.
pub fn as_render(&self) -> &RenderPipeline {
match self {
Self::Render(r) => r,
_ => panic!("Pipeline is not a RenderPipeline")
}
}
/// Gets self as a render pipeline, returns `None` if self is not a render pipeline.
pub fn try_as_render(&self) -> Option<&RenderPipeline> {
match self {
Self::Render(r) => Some(r),
_ => None,
}
}
/// Gets self as a compute pipeline, panics if self is not a compute pipeline.
pub fn as_compute(&self) -> &ComputePipeline {
match self {
Self::Compute(r) => r,
_ => panic!("Pipeline is not a ComputePipeline")
}
}
/// Gets self as a compute pipeline, returns `None` if self is not a compute pipeline.
pub fn try_as_compute(&self) -> Option<&ComputePipeline> {
match self {
Self::Compute(c) => Some(c),
_ => None,
}
}
}

View File

@ -1,26 +1,25 @@
use wgpu::{PipelineLayout, RenderPipeline, VertexBufferLayout, BindGroupLayout};
use std::{collections::HashMap, ops::Deref};
use wgpu::{PipelineLayout, VertexBufferLayout, BindGroupLayout};
use super::texture::RenderTexture;
pub struct FullRenderPipeline {
pub struct RenderPipeline {
layout: PipelineLayout,
wgpu_pipeline: RenderPipeline,
wgpu_pipeline: wgpu::RenderPipeline,
}
impl FullRenderPipeline {
pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, shader: &wgpu::ShaderModule, buffer_layouts: Vec<VertexBufferLayout>, bind_group_layouts: Vec<&BindGroupLayout>) -> FullRenderPipeline {
// Extract the layouts from all the jobs
/* let mut buffer_layouts = vec![];
let mut bind_group_layouts = vec![];
for job in jobs.iter() {
// Push layout for the vertex buffer, index buffer doesn't need one
buffer_layouts.push(Vertex::desc());
impl Deref for RenderPipeline {
type Target = wgpu::RenderPipeline;
if let Some(layout) = job.mesh().texture_layout.as_ref() {
bind_group_layouts.push(layout);
}
} */
fn deref(&self) -> &Self::Target {
&self.wgpu_pipeline
}
}
impl RenderPipeline {
/// Creates the default render pipeline
pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, shader: &wgpu::ShaderModule, buffer_layouts: Vec<VertexBufferLayout>, bind_group_layouts: Vec<&BindGroupLayout>) -> RenderPipeline {
let render_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
@ -78,13 +77,13 @@ impl FullRenderPipeline {
}
}
#[allow(dead_code)]
pub fn get_layout(&self) -> &PipelineLayout {
#[inline(always)]
pub fn layout(&self) -> &PipelineLayout {
&self.layout
}
#[allow(dead_code)]
pub fn get_wgpu_pipeline(&self) -> &RenderPipeline {
#[inline(always)]
pub fn wgpu_pipeline(&self) -> &wgpu::RenderPipeline {
&self.wgpu_pipeline
}
}

View File

@ -18,6 +18,7 @@ use wgpu::util::DeviceExt;
use winit::window::Window;
use crate::math::Transform;
use crate::render::graph::{BasePass, DepthPrePass, LightCullComputePass};
use crate::render::material::MaterialUniform;
use crate::render::render_buffer::BufferWrapperBuilder;
use crate::scene::CameraComponent;
@ -25,6 +26,7 @@ use crate::DeltaTime;
use super::camera::{RenderCamera, CameraUniform};
use super::desc_buf_lay::DescVertexBufferLayout;
use super::graph::RenderGraph;
use super::light::LightUniformBuffers;
use super::light_cull_compute::LightCullCompute;
use super::material::Material;
@ -32,20 +34,23 @@ use super::render_buffer::BufferWrapper;
use super::texture::RenderTexture;
use super::transform_buffer_storage::{TransformBuffers, TransformGroup};
use super::vertex::Vertex;
use super::{render_pipeline::FullRenderPipeline, render_buffer::BufferStorage, render_job::RenderJob};
use super::{render_pipeline::RenderPipeline, render_buffer::BufferStorage, render_job::RenderJob};
use lyra_resource::{gltf::Mesh, ResHandle};
type MeshHandle = ResHandle<Mesh>;
type SceneHandle = ResHandle<SceneGraph>;
#[derive(Clone, Copy, Debug)]
pub struct ScreenSize(glam::UVec2);
pub trait Renderer {
fn prepare(&mut self, main_world: &mut World);
fn render(&mut self) -> Result<(), wgpu::SurfaceError>;
fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>);
fn on_resize(&mut self, world: &mut World, new_size: winit::dpi::PhysicalSize<u32>);
fn surface_size(&self) -> winit::dpi::PhysicalSize<u32>;
fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc<FullRenderPipeline>);
fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc<RenderPipeline>);
}
pub trait RenderPass {
@ -83,36 +88,35 @@ pub struct BasicRenderer {
pub clear_color: wgpu::Color,
pub render_pipelines: rustc_hash::FxHashMap<u64, Arc<FullRenderPipeline>>,
pub render_pipelines: rustc_hash::FxHashMap<u64, Arc<RenderPipeline>>,
pub render_jobs: VecDeque<RenderJob>,
mesh_buffers: rustc_hash::FxHashMap<uuid::Uuid, MeshBufferStorage>, // TODO: clean up left over buffers from deleted entities/components
material_buffers: rustc_hash::FxHashMap<uuid::Uuid, Rc<Material>>,
entity_meshes: rustc_hash::FxHashMap<Entity, uuid::Uuid>,
//mesh_buffers: rustc_hash::FxHashMap<uuid::Uuid, MeshBufferStorage>, // TODO: clean up left over buffers from deleted entities/components
//material_buffers: rustc_hash::FxHashMap<uuid::Uuid, Rc<Material>>,
//entity_meshes: rustc_hash::FxHashMap<Entity, uuid::Uuid>,
transform_buffers: TransformBuffers,
//transform_buffers: TransformBuffers,
render_limits: Limits,
inuse_camera: RenderCamera,
camera_buffer: BufferWrapper,
//camera_bind_group: wgpu::BindGroup,
//inuse_camera: RenderCamera,
//camera_buffer: BufferWrapper,
bgl_texture: Rc<BindGroupLayout>,
default_texture: RenderTexture,
depth_buffer_texture: RenderTexture,
//bgl_texture: Rc<BindGroupLayout>,
//default_texture: RenderTexture,
//depth_buffer_texture: RenderTexture,
//material_buffer: BufferWrapper,
//light_buffers: LightUniformBuffers,
//light_cull_compute: LightCullCompute,
material_buffer: BufferWrapper,
light_buffers: LightUniformBuffers,
light_cull_compute: LightCullCompute,
graph: RenderGraph,
}
impl BasicRenderer {
#[instrument(skip(window))]
pub async fn create_with_window(window: Arc<Window>) -> BasicRenderer {
#[instrument(skip(world, window))]
pub async fn create_with_window(world: &mut World, window: Arc<Window>) -> BasicRenderer {
let size = window.inner_size();
world.add_resource(ScreenSize(glam::UVec2::new(size.width, size.height)));
// Get a GPU handle
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
@ -205,7 +209,13 @@ impl BasicRenderer {
let device = Rc::new(device);
let queue = Rc::new(queue);
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());
g.add_pass(BasePass::new());
g.add_pass(DepthPrePass::new());
g.add_pass(LightCullComputePass::new(size));
g.setup(&device);
let mut s = Self {
window,
@ -222,28 +232,14 @@ impl BasicRenderer {
},
render_pipelines: Default::default(),
render_jobs: Default::default(),
mesh_buffers: Default::default(),
material_buffers: Default::default(),
entity_meshes: Default::default(),
render_limits,
transform_buffers,
inuse_camera: RenderCamera::new(size),
camera_buffer,
bgl_texture,
default_texture,
depth_buffer_texture: depth_texture,
light_buffers: light_uniform_buffers,
material_buffer: mat_buffer,
light_cull_compute,
graph: g,
};
// create the default pipelines
let mut pipelines = rustc_hash::FxHashMap::default();
pipelines.insert(0, Arc::new(FullRenderPipeline::new(&s.device, &s.config, &shader,
/* let mut pipelines = rustc_hash::FxHashMap::default();
pipelines.insert(0, Arc::new(RenderPipeline::new(&s.device, &s.config, &shader,
vec![super::vertex::Vertex::desc(),],
vec![&s.bgl_texture, &s.transform_buffers.bindgroup_layout,
s.camera_buffer.bindgroup_layout().unwrap(),
@ -251,401 +247,27 @@ impl BasicRenderer {
&s.bgl_texture,
&s.light_cull_compute.light_indices_grid.bg_pair.layout,
])));
s.render_pipelines = pipelines;
s.render_pipelines = pipelines; */
s
}
/// Checks if the mesh buffers in the GPU need to be updated.
#[instrument(skip(self, _entity, meshh))]
fn check_mesh_buffers(&mut self, _entity: Entity, meshh: &ResHandle<Mesh>) {
let mesh_uuid = meshh.uuid();
if let (Some(mesh), Some(buffers)) = (meshh.data_ref(), self.mesh_buffers.get_mut(&mesh_uuid)) {
// check if the buffer sizes dont match. If they dont, completely remake the buffers
let vertices = mesh.position().unwrap();
if buffers.buffer_vertex.count() != vertices.len() {
debug!("Recreating buffers for mesh {}", mesh_uuid.to_string());
let (vert, idx) = self.create_vertex_index_buffers(&mesh);
// have to re-get buffers because of borrow checker
let buffers = self.mesh_buffers.get_mut(&mesh_uuid).unwrap();
buffers.buffer_indices = idx;
buffers.buffer_vertex = vert;
return;
}
// update vertices
let vertex_buffer = buffers.buffer_vertex.buffer();
let vertices = vertices.as_slice();
// align the vertices to 4 bytes (u32 is 4 bytes, which is wgpu::COPY_BUFFER_ALIGNMENT)
let (_, vertices, _) = bytemuck::pod_align_to::<Vec3, u32>(vertices);
self.queue.write_buffer(vertex_buffer, 0, bytemuck::cast_slice(vertices));
// update the indices if they're given
if let Some(index_buffer) = buffers.buffer_indices.as_ref() {
let aligned_indices = match mesh.indices.as_ref().unwrap() {
// U16 indices need to be aligned to u32, for wpgu, which are 4-bytes in size.
lyra_resource::gltf::MeshIndices::U16(v) => bytemuck::pod_align_to::<u16, u32>(v).1,
lyra_resource::gltf::MeshIndices::U32(v) => bytemuck::pod_align_to::<u32, u32>(v).1,
};
let index_buffer = index_buffer.1.buffer();
self.queue.write_buffer(index_buffer, 0, bytemuck::cast_slice(aligned_indices));
}
}
}
#[instrument(skip(self, mesh))]
fn create_vertex_index_buffers(&mut self, mesh: &Mesh) -> (BufferStorage, Option<(wgpu::IndexFormat, BufferStorage)>) {
let positions = mesh.position().unwrap();
let tex_coords: Vec<glam::Vec2> = mesh.tex_coords().cloned()
.unwrap_or_else(|| vec![glam::Vec2::new(0.0, 0.0); positions.len()]);
let normals = mesh.normals().unwrap();
assert!(positions.len() == tex_coords.len() && positions.len() == normals.len());
let mut vertex_inputs = vec![];
for (v, t, n) in izip!(positions.iter(), tex_coords.iter(), normals.iter()) {
vertex_inputs.push(Vertex::new(*v, *t, *n));
}
let vertex_buffer = self.device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(vertex_inputs.as_slice()),//vertex_combined.as_slice(),
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages:: COPY_DST,
}
);
let vertex_buffer = BufferStorage::new(vertex_buffer, 0, vertex_inputs.len());
let indices = match mesh.indices.as_ref() {
Some(indices) => {
let (idx_type, len, contents) = match indices {
lyra_resource::gltf::MeshIndices::U16(v) => (wgpu::IndexFormat::Uint16, v.len(), bytemuck::cast_slice(v)),
lyra_resource::gltf::MeshIndices::U32(v) => (wgpu::IndexFormat::Uint32, v.len(), bytemuck::cast_slice(v)),
};
let index_buffer = self.device.create_buffer_init(
&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"),
contents,
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages:: COPY_DST,
}
);
let buffer_indices = BufferStorage::new(index_buffer, 0, len);
Some((idx_type, buffer_indices))
},
None => {
None
}
};
( vertex_buffer, indices )
}
#[instrument(skip(self, mesh))]
fn create_mesh_buffers(&mut self, mesh: &Mesh) -> MeshBufferStorage {
let (vertex_buffer, buffer_indices) = self.create_vertex_index_buffers(mesh);
let material = mesh.material.as_ref()
.expect("Material resource not loaded yet");
let material_ref = material.data_ref()
.unwrap();
let material = self.material_buffers.entry(material.uuid())
.or_insert_with(|| {
debug!(uuid=material.uuid().to_string(), "Sending material to gpu");
Rc::new(Material::from_resource(&self.device, &self.queue, self.bgl_texture.clone(), &material_ref))
});
// TODO: support material uniforms from multiple uniforms
let uni = MaterialUniform::from(&**material);
self.queue.write_buffer(&self.material_buffer.inner_buf, 0, bytemuck::bytes_of(&uni));
MeshBufferStorage {
buffer_vertex: vertex_buffer,
buffer_indices,
material: Some(material.clone()),
}
}
/// Processes the mesh for the renderer, storing and creating buffers as needed. Returns true if a new mesh was processed.
#[instrument(skip(self, transform, mesh, entity))]
fn process_mesh(&mut self, 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) )); */
#[allow(clippy::map_entry)]
if !self.mesh_buffers.contains_key(&mesh_uuid) {
// create the mesh's buffers
let buffers = self.create_mesh_buffers(mesh);
self.mesh_buffers.insert(mesh_uuid, buffers);
self.entity_meshes.insert(entity, mesh_uuid);
true
} else { false }
}
}
impl Renderer for BasicRenderer {
#[instrument(skip(self, main_world))]
fn prepare(&mut self, main_world: &mut World) {
let last_epoch = main_world.current_tick();
let mut alive_entities = HashSet::new();
let view = main_world.view_iter::<(
Entities,
&Transform,
TickOf<Transform>,
Or<
(&MeshHandle, TickOf<MeshHandle>),
(&SceneHandle, TickOf<SceneHandle>)
>,
Option<&mut InterpTransform>,
Res<DeltaTime>,
)>();
// used to store InterpTransform components to add to entities later
let mut component_queue: Vec<(Entity, InterpTransform)> = vec![];
for (
entity,
transform,
_transform_epoch,
(
mesh_pair,
scene_pair
),
interp_tran,
delta_time,
) in view
{
alive_entities.insert(entity);
let interp_transform = match interp_tran {
Some(mut interp_transform) => {
// found in https://youtu.be/YJB1QnEmlTs?t=472
interp_transform.alpha = 1.0 - interp_transform.alpha.powf(**delta_time);
interp_transform.last_transform = interp_transform.last_transform.lerp(*transform, interp_transform.alpha);
interp_transform.last_transform
},
None => {
let interp = InterpTransform {
last_transform: *transform,
alpha: 0.5,
};
component_queue.push((entity, interp));
*transform
}
};
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(entity, interp_transform, &*mesh, mesh_han.uuid())
&& mesh_epoch == last_epoch {
self.check_mesh_buffers(entity, &mesh_han);
}
if self.transform_buffers.needs_expand() {
self.transform_buffers.expand_buffers(&self.device);
}
let group = TransformGroup::EntityRes(entity, mesh_han.uuid());
let transform_id = self.transform_buffers.update_or_push(&self.device, &self.queue, &self.render_limits,
group, interp_transform.calculate_mat4(), glam::Mat3::from_quat(interp_transform.rotation));
let material = mesh.material.as_ref().unwrap()
.data_ref().unwrap();
let shader = material.shader_uuid.unwrap_or(0);
let job = RenderJob::new(entity, shader, mesh_han.uuid(), transform_id);
self.render_jobs.push_back(job);
}
}
if let Some((scene_han, scene_epoch)) = scene_pair {
if let Some(scene) = scene_han.data_ref() {
if scene_epoch == last_epoch {
let view = scene.world().view::<(Entities, &mut WorldTransform, &Transform, Not<Has<RelationOriginComponent<ChildOf>>>)>();
lyra_scene::system_update_world_transforms(scene.world(), view).unwrap();
}
for (mesh_han, pos) in scene.world().view_iter::<(&MeshHandle, &WorldTransform)>() {
if let Some(mesh) = mesh_han.data_ref() {
let mesh_interpo = interp_transform + **pos;
// 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(entity, mesh_interpo, &*mesh, mesh_han.uuid())
&& scene_epoch == last_epoch {
self.check_mesh_buffers(entity, &mesh_han);
}
if self.transform_buffers.needs_expand() {
self.transform_buffers.expand_buffers(&self.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 = self.transform_buffers.update_or_push(&self.device, &self.queue, &self.render_limits,
group, mesh_interpo.calculate_mat4(), glam::Mat3::from_quat(mesh_interpo.rotation) );
let material = mesh.material.as_ref().unwrap()
.data_ref().unwrap();
let shader = material.shader_uuid.unwrap_or(0);
let job = RenderJob::new(entity, shader, mesh_han.uuid(), transform_id);
self.render_jobs.push_back(job);
}
}
}
}
}
for (en, interp) in component_queue {
main_world.insert(en, interp);
}
// collect dead entities
self.transform_buffers.send_to_gpu(&self.queue);
// when buffer storage length does not match the amount of iterated entities,
// remove all dead entities, and their buffers, if they weren't iterated over
if self.mesh_buffers.len() != alive_entities.len() {
let removed_entities: Vec<uuid::Uuid> = self.entity_meshes
.extract_if(|e, _| !alive_entities.contains(e))
.map(|(_, v)| v)
.collect();
self.mesh_buffers.retain(|u, _| !removed_entities.contains(u));
}
// update camera uniform
if let Some(camera) = main_world.view_iter::<&mut CameraComponent>().next() {
let uniform = self.inuse_camera.calc_view_projection(&camera);
self.camera_buffer.write_buffer(&self.queue, 0, &[uniform]);
} else {
warn!("Missing camera!");
}
self.light_buffers.update_lights(&self.queue, last_epoch, main_world);
self.graph.prepare();
}
#[instrument(skip(self))]
fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
let output = self.surface.get_current_texture()?;
let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
self.light_cull_compute.compute(&self.camera_buffer, &self.light_buffers, &self.depth_buffer_texture);
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Basic Renderer's Encoder")
});
// Create a new variable scope for the render pass
{
// There's only one render pass currently
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(self.clear_color),
store: true,
},
})],
// enable depth buffer
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &self.depth_buffer_texture.view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: true,
}),
stencil_ops: None,
}),
});
// Pop off jobs from the queue as they're being processed
while let Some(job) = self.render_jobs.pop_front() {
if let Some(pipeline) = self.render_pipelines.get(&job.shader_id) {
// specify to use this pipeline
render_pass.set_pipeline(pipeline.get_wgpu_pipeline());
// get the mesh (containing vertices) and the buffers from storage
let buffers = self.mesh_buffers.get(&job.mesh_uuid);
if buffers.is_none() {
warn!("Skipping job since its mesh is missing {:?}", job.mesh_uuid);
continue;
}
let buffers = buffers.unwrap();
/* let buffers = self.mesh_buffers.get(&job.mesh_uuid)
.expect("missing render job mesh"); */
// Bind the optional texture
if let Some(tex) = buffers.material.as_ref()
.and_then(|m| m.diffuse_texture.as_ref()) {
render_pass.set_bind_group(0, tex.bind_group(), &[]);
} else {
render_pass.set_bind_group(0, self.default_texture.bind_group(), &[]);
}
if let Some(tex) = buffers.material.as_ref()
.and_then(|m| m.specular.as_ref())
.and_then(|s| s.texture.as_ref().or(s.color_texture.as_ref())) {
render_pass.set_bind_group(5, tex.bind_group(), &[]);
} else {
render_pass.set_bind_group(5, self.default_texture.bind_group(), &[]);
}
// Get the bindgroup for job's transform and bind to it using an offset.
let bindgroup = self.transform_buffers.bind_group(job.transform_id);
let offset = self.transform_buffers.buffer_offset(job.transform_id);
render_pass.set_bind_group(1, bindgroup, &[ offset, ]);
render_pass.set_bind_group(2, &self.camera_buffer.bindgroup(), &[]);
render_pass.set_bind_group(3, &self.light_buffers.bind_group_pair.bindgroup, &[]);
render_pass.set_bind_group(4, &self.material_buffer.bindgroup_pair.as_ref().unwrap().bindgroup, &[]);
render_pass.set_bind_group(6, &self.light_cull_compute.light_indices_grid.bg_pair.bindgroup, &[]);
// if this mesh uses indices, use them to draw the mesh
if let Some((idx_type, indices)) = buffers.buffer_indices.as_ref() {
let indices_len = indices.count() as u32;
render_pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..));
render_pass.set_index_buffer(indices.buffer().slice(..), *idx_type);
render_pass.draw_indexed(0..indices_len, 0, 0..1);
} else {
let vertex_count = buffers.buffer_vertex.count();
render_pass.set_vertex_buffer(buffers.buffer_vertex.slot(), buffers.buffer_vertex.buffer().slice(..));
render_pass.draw(0..vertex_count as u32, 0..1);
}
}
}
}
self.queue.submit(std::iter::once(encoder.finish()));
output.present();
self.graph.render(self);
Ok(())
}
#[instrument(skip(self))]
fn on_resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
#[instrument(skip(world, self))]
fn on_resize(&mut self, world: &mut World, new_size: winit::dpi::PhysicalSize<u32>) {
if new_size.width > 0 && new_size.height > 0 {
self.size = new_size;
self.config.width = new_size.width;
@ -653,15 +275,10 @@ impl Renderer for BasicRenderer {
// tell other things of updated resize
self.surface.configure(&self.device, &self.config);
let create_bindgroup = self.depth_buffer_texture.bindgroup_pair.is_some();
self.depth_buffer_texture = RenderTexture::create_depth_texture(&self.device, &self.config, "Depth Buffer Texture");
if create_bindgroup {
self.depth_buffer_texture.create_bind_group(&self.device);
}
self.inuse_camera.update_aspect_ratio(self.size);
self.light_cull_compute.update_screen_size(new_size);
let mut world_ss = world.get_resource::<ScreenSize>();
world_ss.0 = glam::UVec2::new(new_size.width, new_size.height);
self.graph.surface_config = self.config.clone();
}
}
@ -669,7 +286,7 @@ impl Renderer for BasicRenderer {
self.size
}
fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc<FullRenderPipeline>) {
fn add_render_pipeline(&mut self, shader_id: u64, pipeline: Arc<RenderPipeline>) {
self.render_pipelines.insert(shader_id, pipeline);
}
}