Implement a Render Graph #16
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,12 +6,16 @@ pub use pass::*;
|
||||||
mod passes;
|
mod passes;
|
||||||
pub use passes::*;
|
pub use passes::*;
|
||||||
|
|
||||||
|
mod slot_desc;
|
||||||
|
pub use slot_desc::*;
|
||||||
|
|
||||||
mod execution_path;
|
mod execution_path;
|
||||||
|
|
||||||
use rustc_hash::FxHashMap;
|
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 {
|
struct PassEntry {
|
||||||
inner: Box<dyn RenderGraphPass>,
|
inner: Box<dyn RenderGraphPass>,
|
||||||
|
@ -19,22 +23,54 @@ struct PassEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ResourcedSlot {
|
struct ResourcedSlot {
|
||||||
slot: RenderPassSlot,
|
name: String,
|
||||||
|
//slot: RenderPassSlot,
|
||||||
|
ty: SlotType,
|
||||||
value: SlotValue,
|
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 {
|
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
|
||||||
|
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,
|
current_id: u64,
|
||||||
|
|
||||||
|
pub(crate) surface_config: wgpu::SurfaceConfiguration,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderGraph {
|
impl RenderGraph {
|
||||||
pub fn new() -> Self {
|
pub fn new(surface_config: wgpu::SurfaceConfiguration) -> Self {
|
||||||
Self::default()
|
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> {
|
pub fn slot_id(&self, name: &str) -> Option<u64> {
|
||||||
|
@ -46,13 +82,32 @@ impl RenderGraph {
|
||||||
.map(|s| &s.desc)
|
.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) {
|
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 {
|
self.passes.insert(desc.id, PassEntry {
|
||||||
inner: Box::new(pass),
|
inner: Box::new(pass),
|
||||||
desc,
|
desc,
|
||||||
|
@ -60,7 +115,39 @@ impl RenderGraph {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
||||||
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!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,13 +158,102 @@ impl RenderGraph {
|
||||||
pub fn render(&mut self, renderer: &mut BasicRenderer) {
|
pub fn render(&mut self, renderer: &mut BasicRenderer) {
|
||||||
todo!()
|
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 {
|
pub fn begin_render_pass(&mut self, desc: &wgpu::RenderPassDescriptor) -> wgpu::RenderPass {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
@ -86,29 +262,3 @@ impl RenderGraphContext {
|
||||||
todo!()
|
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -2,7 +2,7 @@ use std::collections::HashMap;
|
||||||
|
|
||||||
use lyra_ecs::World;
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
|
||||||
pub enum RenderPassType {
|
pub enum RenderPassType {
|
||||||
|
@ -15,16 +15,31 @@ pub enum RenderPassType {
|
||||||
pub enum SlotType {
|
pub enum SlotType {
|
||||||
TextureView,
|
TextureView,
|
||||||
Sampler,
|
Sampler,
|
||||||
|
Texture,
|
||||||
Buffer,
|
Buffer,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SlotValue {
|
pub enum SlotValue {
|
||||||
|
None,
|
||||||
TextureView(wgpu::TextureView),
|
TextureView(wgpu::TextureView),
|
||||||
Sampler(wgpu::Sampler),
|
Sampler(wgpu::Sampler),
|
||||||
|
Texture(wgpu::Texture),
|
||||||
Buffer(wgpu::Buffer),
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum SlotAttribute {
|
pub enum SlotAttribute {
|
||||||
Input,
|
Input,
|
||||||
|
@ -37,8 +52,9 @@ pub struct RenderPassSlot {
|
||||||
pub attribute: SlotAttribute,
|
pub attribute: SlotAttribute,
|
||||||
pub id: u64,
|
pub id: u64,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
/// The descriptor of the slot value.
|
||||||
// buffer desc, texture desc
|
/// This will be `None` if this slot is an input.
|
||||||
|
pub desc: Option<SlotDescriptor>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -67,34 +83,61 @@ impl RenderGraphPassDesc {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[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 {
|
let slot = RenderPassSlot {
|
||||||
id,
|
id,
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
ty: SlotType::Buffer,
|
ty: SlotType::Buffer,
|
||||||
attribute,
|
attribute,
|
||||||
|
desc,
|
||||||
};
|
};
|
||||||
self.add_slot(slot);
|
self.add_slot(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[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 {
|
let slot = RenderPassSlot {
|
||||||
id,
|
id,
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
ty: SlotType::TextureView,
|
ty: SlotType::TextureView,
|
||||||
attribute,
|
attribute,
|
||||||
|
desc,
|
||||||
};
|
};
|
||||||
self.add_slot(slot);
|
self.add_slot(slot);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[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 {
|
let slot = RenderPassSlot {
|
||||||
id,
|
id,
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
ty: SlotType::Sampler,
|
ty: SlotType::Sampler,
|
||||||
attribute,
|
attribute,
|
||||||
|
desc,
|
||||||
};
|
};
|
||||||
self.add_slot(slot);
|
self.add_slot(slot);
|
||||||
}
|
}
|
||||||
|
@ -118,7 +161,7 @@ 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, id: &mut u64) -> RenderGraphPassDesc;
|
fn desc(&self, graph: &mut RenderGraph, id: &mut u64) -> RenderGraphPassDesc;
|
||||||
|
|
||||||
fn prepare(&mut self, world: &mut World);
|
fn prepare(&mut self, world: &mut World);
|
||||||
fn execute(&mut self, graph: &mut RenderGraph, desc: &RenderGraphPassDesc, context: &mut RenderGraphContext);
|
fn execute(&mut self, graph: &mut RenderGraph, desc: &RenderGraphPassDesc, context: &mut RenderGraphContext);
|
||||||
|
|
|
@ -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.
|
/// Supplies some basic things other passes needs.
|
||||||
///
|
///
|
||||||
|
@ -13,13 +15,21 @@ impl BasePass {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderGraphPass for 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);
|
let mut desc = RenderGraphPassDesc::new(*id, "BasePass", RenderPassType::Compute);
|
||||||
*id += 1;
|
*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;
|
*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;
|
*id += 1;
|
||||||
|
|
||||||
desc
|
desc
|
||||||
|
|
|
@ -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!()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,11 @@
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
use lyra_ecs::World;
|
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 {
|
pub struct LightCullComputePass {
|
||||||
workgroup_size: glam::UVec2,
|
workgroup_size: glam::UVec2,
|
||||||
|
@ -9,28 +14,78 @@ pub struct LightCullComputePass {
|
||||||
impl LightCullComputePass {
|
impl LightCullComputePass {
|
||||||
pub fn new(screen_size: winit::dpi::PhysicalSize<u32>) -> Self {
|
pub fn new(screen_size: winit::dpi::PhysicalSize<u32>) -> Self {
|
||||||
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 {
|
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);
|
let mut desc = RenderGraphPassDesc::new(*id, "LightCullCompute", RenderPassType::Compute);
|
||||||
*id += 1;
|
*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;
|
*id += 1;
|
||||||
desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Input);
|
desc.add_buffer_slot(*id, "camera_buffer", SlotAttribute::Input, None);
|
||||||
*id += 1;
|
*id += 1;
|
||||||
|
|
||||||
desc.add_buffer_slot(*id, "light_indices", SlotAttribute::Output);
|
let mut contents = Vec::<u8>::new();
|
||||||
*id += 1;
|
let contents_len =
|
||||||
/* desc.add_buffer_slot(*id, "indices_buffer", SlotAttribute::Output);
|
self.workgroup_size.x * self.workgroup_size.y * 200 * mem::size_of::<u32>() as u32;
|
||||||
*id += 1; */
|
contents.resize(contents_len as _, 0);
|
||||||
desc.add_texture_view_slot(*id, "grid_texture", SlotAttribute::Output);
|
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;
|
*id += 1;
|
||||||
|
|
||||||
|
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
|
desc
|
||||||
}
|
}
|
||||||
|
@ -40,18 +95,44 @@ impl RenderGraphPass for LightCullComputePass {
|
||||||
todo!()
|
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 {
|
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 pipeline = graph.compute_pipeline("main");
|
||||||
let camera_bg = graph.bindgroup(graph.slot_id("camera_buffer").expect("Could not find camera buffers")).unwrap();
|
pass.set_pipeline(pipeline);
|
||||||
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();
|
|
||||||
|
|
||||||
//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(0, depth_tex, &[]);
|
||||||
pass.set_bind_group(1, camera_bg, &[]);
|
pass.set_bind_group(1, camera_bg, &[]);
|
||||||
|
@ -60,7 +141,5 @@ impl RenderGraphPass for LightCullComputePass {
|
||||||
pass.set_bind_group(4, screen_size_bg, &[]);
|
pass.set_bind_group(4, screen_size_bg, &[]);
|
||||||
|
|
||||||
pass.dispatch_workgroups(self.workgroup_size.x, self.workgroup_size.y, 1);
|
pass.dispatch_workgroups(self.workgroup_size.x, self.workgroup_size.y, 1);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,3 +3,6 @@ pub use light_cull_compute::*;
|
||||||
|
|
||||||
mod base;
|
mod base;
|
||||||
pub use base::*;
|
pub use base::*;
|
||||||
|
|
||||||
|
mod depth_prepass;
|
||||||
|
pub use depth_prepass::*;
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
pub mod renderer;
|
pub mod renderer;
|
||||||
pub mod render_pipeline;
|
pub mod render_pipeline;
|
||||||
|
pub mod compute_pipeline;
|
||||||
|
pub mod pipeline;
|
||||||
pub mod vertex;
|
pub mod vertex;
|
||||||
pub mod desc_buf_lay;
|
pub mod desc_buf_lay;
|
||||||
pub mod render_buffer;
|
pub mod render_buffer;
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
use super::texture::RenderTexture;
|
||||||
|
|
||||||
pub struct FullRenderPipeline {
|
pub struct RenderPipeline {
|
||||||
layout: PipelineLayout,
|
layout: PipelineLayout,
|
||||||
wgpu_pipeline: RenderPipeline,
|
wgpu_pipeline: wgpu::RenderPipeline,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FullRenderPipeline {
|
impl Deref for RenderPipeline {
|
||||||
pub fn new(device: &wgpu::Device, config: &wgpu::SurfaceConfiguration, shader: &wgpu::ShaderModule, buffer_layouts: Vec<VertexBufferLayout>, bind_group_layouts: Vec<&BindGroupLayout>) -> FullRenderPipeline {
|
type Target = wgpu::RenderPipeline;
|
||||||
// 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());
|
|
||||||
|
|
||||||
if let Some(layout) = job.mesh().texture_layout.as_ref() {
|
fn deref(&self) -> &Self::Target {
|
||||||
bind_group_layouts.push(layout);
|
&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 =
|
let render_pipeline_layout =
|
||||||
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
||||||
label: Some("Render Pipeline Layout"),
|
label: Some("Render Pipeline Layout"),
|
||||||
|
@ -78,13 +77,13 @@ impl FullRenderPipeline {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[inline(always)]
|
||||||
pub fn get_layout(&self) -> &PipelineLayout {
|
pub fn layout(&self) -> &PipelineLayout {
|
||||||
&self.layout
|
&self.layout
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[inline(always)]
|
||||||
pub fn get_wgpu_pipeline(&self) -> &RenderPipeline {
|
pub fn wgpu_pipeline(&self) -> &wgpu::RenderPipeline {
|
||||||
&self.wgpu_pipeline
|
&self.wgpu_pipeline
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -18,6 +18,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::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;
|
||||||
|
@ -25,6 +26,7 @@ use crate::DeltaTime;
|
||||||
|
|
||||||
use super::camera::{RenderCamera, CameraUniform};
|
use super::camera::{RenderCamera, CameraUniform};
|
||||||
use super::desc_buf_lay::DescVertexBufferLayout;
|
use super::desc_buf_lay::DescVertexBufferLayout;
|
||||||
|
use super::graph::RenderGraph;
|
||||||
use super::light::LightUniformBuffers;
|
use super::light::LightUniformBuffers;
|
||||||
use super::light_cull_compute::LightCullCompute;
|
use super::light_cull_compute::LightCullCompute;
|
||||||
use super::material::Material;
|
use super::material::Material;
|
||||||
|
@ -32,20 +34,23 @@ use super::render_buffer::BufferWrapper;
|
||||||
use super::texture::RenderTexture;
|
use super::texture::RenderTexture;
|
||||||
use super::transform_buffer_storage::{TransformBuffers, TransformGroup};
|
use super::transform_buffer_storage::{TransformBuffers, TransformGroup};
|
||||||
use super::vertex::Vertex;
|
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};
|
use lyra_resource::{gltf::Mesh, ResHandle};
|
||||||
|
|
||||||
type MeshHandle = ResHandle<Mesh>;
|
type MeshHandle = ResHandle<Mesh>;
|
||||||
type SceneHandle = ResHandle<SceneGraph>;
|
type SceneHandle = ResHandle<SceneGraph>;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
pub struct ScreenSize(glam::UVec2);
|
||||||
|
|
||||||
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>;
|
||||||
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 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 {
|
pub trait RenderPass {
|
||||||
|
@ -83,36 +88,35 @@ pub struct BasicRenderer {
|
||||||
|
|
||||||
pub clear_color: wgpu::Color,
|
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>,
|
pub render_jobs: VecDeque<RenderJob>,
|
||||||
|
|
||||||
mesh_buffers: rustc_hash::FxHashMap<uuid::Uuid, MeshBufferStorage>, // TODO: clean up left over buffers from deleted entities/components
|
//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>>,
|
//material_buffers: rustc_hash::FxHashMap<uuid::Uuid, Rc<Material>>,
|
||||||
entity_meshes: rustc_hash::FxHashMap<Entity, uuid::Uuid>,
|
//entity_meshes: rustc_hash::FxHashMap<Entity, uuid::Uuid>,
|
||||||
|
|
||||||
transform_buffers: TransformBuffers,
|
//transform_buffers: TransformBuffers,
|
||||||
|
|
||||||
render_limits: Limits,
|
render_limits: Limits,
|
||||||
|
|
||||||
inuse_camera: RenderCamera,
|
//inuse_camera: RenderCamera,
|
||||||
camera_buffer: BufferWrapper,
|
//camera_buffer: BufferWrapper,
|
||||||
//camera_bind_group: wgpu::BindGroup,
|
|
||||||
|
|
||||||
bgl_texture: Rc<BindGroupLayout>,
|
//bgl_texture: Rc<BindGroupLayout>,
|
||||||
default_texture: RenderTexture,
|
//default_texture: RenderTexture,
|
||||||
depth_buffer_texture: RenderTexture,
|
//depth_buffer_texture: RenderTexture,
|
||||||
|
//material_buffer: BufferWrapper,
|
||||||
|
//light_buffers: LightUniformBuffers,
|
||||||
|
//light_cull_compute: LightCullCompute,
|
||||||
|
|
||||||
material_buffer: BufferWrapper,
|
graph: RenderGraph,
|
||||||
|
|
||||||
light_buffers: LightUniformBuffers,
|
|
||||||
|
|
||||||
light_cull_compute: LightCullCompute,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BasicRenderer {
|
impl BasicRenderer {
|
||||||
#[instrument(skip(window))]
|
#[instrument(skip(world, window))]
|
||||||
pub async fn create_with_window(window: Arc<Window>) -> BasicRenderer {
|
pub async fn create_with_window(world: &mut World, window: Arc<Window>) -> BasicRenderer {
|
||||||
let size = window.inner_size();
|
let size = window.inner_size();
|
||||||
|
world.add_resource(ScreenSize(glam::UVec2::new(size.width, size.height)));
|
||||||
|
|
||||||
// Get a GPU handle
|
// Get a GPU handle
|
||||||
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
|
||||||
|
@ -205,7 +209,13 @@ impl BasicRenderer {
|
||||||
|
|
||||||
let device = Rc::new(device);
|
let device = Rc::new(device);
|
||||||
let queue = Rc::new(queue);
|
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 {
|
let mut s = Self {
|
||||||
window,
|
window,
|
||||||
|
@ -222,28 +232,14 @@ impl BasicRenderer {
|
||||||
},
|
},
|
||||||
render_pipelines: Default::default(),
|
render_pipelines: Default::default(),
|
||||||
render_jobs: Default::default(),
|
render_jobs: Default::default(),
|
||||||
mesh_buffers: Default::default(),
|
|
||||||
material_buffers: Default::default(),
|
|
||||||
entity_meshes: Default::default(),
|
|
||||||
|
|
||||||
render_limits,
|
render_limits,
|
||||||
transform_buffers,
|
graph: g,
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// create the default pipelines
|
// create the default pipelines
|
||||||
let mut pipelines = rustc_hash::FxHashMap::default();
|
/* let mut pipelines = rustc_hash::FxHashMap::default();
|
||||||
pipelines.insert(0, Arc::new(FullRenderPipeline::new(&s.device, &s.config, &shader,
|
pipelines.insert(0, Arc::new(RenderPipeline::new(&s.device, &s.config, &shader,
|
||||||
vec![super::vertex::Vertex::desc(),],
|
vec![super::vertex::Vertex::desc(),],
|
||||||
vec![&s.bgl_texture, &s.transform_buffers.bindgroup_layout,
|
vec![&s.bgl_texture, &s.transform_buffers.bindgroup_layout,
|
||||||
s.camera_buffer.bindgroup_layout().unwrap(),
|
s.camera_buffer.bindgroup_layout().unwrap(),
|
||||||
|
@ -251,401 +247,27 @@ impl BasicRenderer {
|
||||||
&s.bgl_texture,
|
&s.bgl_texture,
|
||||||
&s.light_cull_compute.light_indices_grid.bg_pair.layout,
|
&s.light_cull_compute.light_indices_grid.bg_pair.layout,
|
||||||
])));
|
])));
|
||||||
s.render_pipelines = pipelines;
|
s.render_pipelines = pipelines; */
|
||||||
|
|
||||||
s
|
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 {
|
impl Renderer for BasicRenderer {
|
||||||
#[instrument(skip(self, main_world))]
|
#[instrument(skip(self, main_world))]
|
||||||
fn prepare(&mut self, main_world: &mut World) {
|
fn prepare(&mut self, main_world: &mut World) {
|
||||||
let last_epoch = main_world.current_tick();
|
self.graph.prepare();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
|
fn render(&mut self) -> Result<(), wgpu::SurfaceError> {
|
||||||
let output = self.surface.get_current_texture()?;
|
self.graph.render(self);
|
||||||
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();
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(world, self))]
|
||||||
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>) {
|
||||||
if new_size.width > 0 && new_size.height > 0 {
|
if new_size.width > 0 && new_size.height > 0 {
|
||||||
self.size = new_size;
|
self.size = new_size;
|
||||||
self.config.width = new_size.width;
|
self.config.width = new_size.width;
|
||||||
|
@ -654,14 +276,9 @@ 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 create_bindgroup = self.depth_buffer_texture.bindgroup_pair.is_some();
|
let mut world_ss = world.get_resource::<ScreenSize>();
|
||||||
self.depth_buffer_texture = RenderTexture::create_depth_texture(&self.device, &self.config, "Depth Buffer Texture");
|
world_ss.0 = glam::UVec2::new(new_size.width, new_size.height);
|
||||||
if create_bindgroup {
|
self.graph.surface_config = self.config.clone();
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -669,7 +286,7 @@ impl Renderer for BasicRenderer {
|
||||||
self.size
|
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);
|
self.render_pipelines.insert(shader_id, pipeline);
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue