Implement a Render Graph #16
|
@ -3,9 +3,11 @@ use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::{HashMap, VecDeque},
|
collections::{HashMap, VecDeque},
|
||||||
ptr::NonNull,
|
ptr::NonNull,
|
||||||
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use lyra_ecs::World;
|
||||||
pub use pass::*;
|
pub use pass::*;
|
||||||
|
|
||||||
mod passes;
|
mod passes;
|
||||||
|
@ -17,13 +19,14 @@ pub use slot_desc::*;
|
||||||
mod execution_path;
|
mod execution_path;
|
||||||
|
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use tracing::{debug, debug_span, instrument};
|
use tracing::{debug, debug_span, instrument, trace};
|
||||||
use wgpu::{util::DeviceExt, RenderPass};
|
use wgpu::{util::DeviceExt, RenderPass};
|
||||||
|
|
||||||
use self::execution_path::GraphExecutionPath;
|
use self::execution_path::GraphExecutionPath;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
renderer::{BasicRenderer, Renderer}, resource::{Pipeline, RenderPipeline},
|
renderer::{BasicRenderer, Renderer},
|
||||||
|
resource::{Pipeline, RenderPipeline},
|
||||||
};
|
};
|
||||||
|
|
||||||
//#[derive(Clone)]
|
//#[derive(Clone)]
|
||||||
|
@ -37,11 +40,6 @@ struct ResourcedSlot {
|
||||||
//slot: RenderPassSlot,
|
//slot: RenderPassSlot,
|
||||||
ty: SlotType,
|
ty: SlotType,
|
||||||
value: SlotValue,
|
value: SlotValue,
|
||||||
// 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.
|
/// Stores the pipeline and other resources it uses.
|
||||||
|
@ -54,6 +52,8 @@ pub struct PipelineResource {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct RenderGraph {
|
pub struct RenderGraph {
|
||||||
|
device: Rc<wgpu::Device>,
|
||||||
|
queue: Rc<wgpu::Queue>,
|
||||||
slots: FxHashMap<u64, ResourcedSlot>,
|
slots: FxHashMap<u64, ResourcedSlot>,
|
||||||
slot_names: HashMap<String, u64>,
|
slot_names: HashMap<String, u64>,
|
||||||
passes: FxHashMap<u64, PassEntry>,
|
passes: FxHashMap<u64, PassEntry>,
|
||||||
|
@ -70,7 +70,7 @@ pub struct RenderGraph {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RenderGraph {
|
impl RenderGraph {
|
||||||
pub fn new(surface_config: wgpu::SurfaceConfiguration) -> Self {
|
pub fn new(device: Rc<wgpu::Device>, queue: Rc<wgpu::Queue>, surface_config: wgpu::SurfaceConfiguration) -> Self {
|
||||||
let mut slots = FxHashMap::default();
|
let mut slots = FxHashMap::default();
|
||||||
let mut slot_names = HashMap::default();
|
let mut slot_names = HashMap::default();
|
||||||
|
|
||||||
|
@ -79,14 +79,15 @@ impl RenderGraph {
|
||||||
ResourcedSlot {
|
ResourcedSlot {
|
||||||
name: "window_texture_view".to_string(),
|
name: "window_texture_view".to_string(),
|
||||||
ty: SlotType::TextureView,
|
ty: SlotType::TextureView,
|
||||||
|
// this will get set in prepare stage.
|
||||||
value: SlotValue::None,
|
value: SlotValue::None,
|
||||||
bind_group_id: None,
|
|
||||||
create_desc: None,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
slot_names.insert("window_texture_view".to_string(), 0u64);
|
slot_names.insert("window_texture_view".to_string(), 0u64);
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
device,
|
||||||
|
queue,
|
||||||
slots,
|
slots,
|
||||||
slot_names,
|
slot_names,
|
||||||
passes: Default::default(),
|
passes: Default::default(),
|
||||||
|
@ -98,6 +99,10 @@ impl RenderGraph {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn device(&self) -> &wgpu::Device {
|
||||||
|
&*self.device
|
||||||
|
}
|
||||||
|
|
||||||
pub fn next_id(&mut self) -> u64 {
|
pub fn next_id(&mut self) -> u64 {
|
||||||
self.current_id += 1;
|
self.current_id += 1;
|
||||||
self.current_id
|
self.current_id
|
||||||
|
@ -115,7 +120,7 @@ impl RenderGraph {
|
||||||
self.passes.get(&id).map(|s| &*s.desc)
|
self.passes.get(&id).map(|s| &*s.desc)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_pass<P: RenderGraphPass>(&mut self, pass: P) {
|
pub fn add_pass<P: RenderGraphPass>(&mut self, mut pass: P) {
|
||||||
let mut desc = pass.desc(self);
|
let mut desc = pass.desc(self);
|
||||||
|
|
||||||
for slot in &mut desc.slots {
|
for slot in &mut desc.slots {
|
||||||
|
@ -124,18 +129,19 @@ impl RenderGraph {
|
||||||
.get(&slot.name)
|
.get(&slot.name)
|
||||||
.and_then(|id| self.slots.get_mut(id).map(|s| (id, s)))
|
.and_then(|id| self.slots.get_mut(id).map(|s| (id, s)))
|
||||||
{
|
{
|
||||||
|
// if there is a slot of the same name
|
||||||
slot.id = *id;
|
slot.id = *id;
|
||||||
|
|
||||||
if slot.desc.is_some() && other.create_desc.is_none() {
|
debug_assert_eq!(
|
||||||
other.create_desc = slot.desc.clone();
|
slot.ty, other.ty,
|
||||||
}
|
"slot {} in pass {} does not match existing slot of same name",
|
||||||
|
slot.name, desc.name
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
let res_slot = ResourcedSlot {
|
let res_slot = ResourcedSlot {
|
||||||
name: slot.name.clone(),
|
name: slot.name.clone(),
|
||||||
ty: slot.ty,
|
ty: slot.ty,
|
||||||
value: SlotValue::None,
|
value: SlotValue::None,
|
||||||
bind_group_id: None,
|
|
||||||
create_desc: slot.desc.clone(),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self.slots.insert(slot.id, res_slot);
|
self.slots.insert(slot.id, res_slot);
|
||||||
|
@ -155,8 +161,6 @@ 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.
|
||||||
#[instrument(skip(self, device))]
|
#[instrument(skip(self, device))]
|
||||||
pub fn setup(&mut self, device: &wgpu::Device) {
|
pub fn setup(&mut self, device: &wgpu::Device) {
|
||||||
let mut later_slots = VecDeque::new();
|
|
||||||
|
|
||||||
// For all passes, create their pipelines
|
// For all passes, create their pipelines
|
||||||
for pass in self.passes.values() {
|
for pass in self.passes.values() {
|
||||||
if let Some(pipei) = &pass.desc.pipeline_desc {
|
if let Some(pipei) = &pass.desc.pipeline_desc {
|
||||||
|
@ -174,101 +178,27 @@ impl RenderGraph {
|
||||||
self.pipelines.insert(pass.desc.id, res);
|
self.pipelines.insert(pass.desc.id, res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (slot_id, slot) in &mut self.slots {
|
|
||||||
if slot.bind_group_id.is_none() && slot.create_desc.is_some() {
|
|
||||||
let s = debug_span!("res_creation", slot = slot.name);
|
|
||||||
let _e = s.enter();
|
|
||||||
|
|
||||||
debug!("Creating bind group for slot");
|
|
||||||
|
|
||||||
match &slot.create_desc {
|
|
||||||
Some(SlotDescriptor::BufferInit(bi)) => {
|
|
||||||
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!("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!("Created buffer");
|
|
||||||
}
|
|
||||||
Some(SlotDescriptor::Texture(t)) => {
|
|
||||||
let label = format!("Tex_{}", slot.name);
|
|
||||||
let wt = t.as_wgpu(Some(&label));
|
|
||||||
|
|
||||||
let tex = device.create_texture(&wt);
|
|
||||||
slot.value = SlotValue::Texture(tex);
|
|
||||||
|
|
||||||
debug!("Created texture");
|
|
||||||
}
|
|
||||||
Some(SlotDescriptor::TextureView(tv)) => {
|
|
||||||
// texture views cannot be done in this step. Not only would we run into a
|
|
||||||
// borrow checker error when trying to get the texture for the view, we
|
|
||||||
// can also not guarantee that the texture was actually created.
|
|
||||||
let tex_slot = self
|
|
||||||
.slot_names
|
|
||||||
.get(&tv.texture_label)
|
|
||||||
.expect("Failed to find texture for texture view slot");
|
|
||||||
later_slots.push_back((*slot_id, *tex_slot));
|
|
||||||
|
|
||||||
debug!("Queuing slot resources for later creation");
|
|
||||||
}
|
|
||||||
|
|
||||||
//Some(SlotDescriptor::Sampler(b)) => {},
|
|
||||||
//Some(SlotDescriptor::Texture(b)) => {},
|
|
||||||
Some(SlotDescriptor::None) => {}
|
|
||||||
None => {}
|
|
||||||
_ => todo!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// create buffers for the queued slots
|
|
||||||
while let Some((lower_id, upper_id)) = later_slots.pop_front() {
|
|
||||||
let many = self.slots.get_many_mut([&lower_id, &upper_id]).unwrap();
|
|
||||||
let (lower_slot, upper_slot) = match many {
|
|
||||||
[a, b] => (a, b),
|
|
||||||
};
|
|
||||||
|
|
||||||
let s = debug_span!("deferred_res_creation", slot = lower_slot.name);
|
|
||||||
let _e = s.enter();
|
|
||||||
|
|
||||||
match &lower_slot.create_desc {
|
|
||||||
Some(SlotDescriptor::TextureView(tv)) => {
|
|
||||||
let label = format!("TexView_{}", lower_slot.name);
|
|
||||||
|
|
||||||
let wt = tv.as_wgpu(Some(&label));
|
|
||||||
let tex = upper_slot.value.as_texture();
|
|
||||||
|
|
||||||
let texview = tex.create_view(&wt);
|
|
||||||
lower_slot.value = SlotValue::TextureView(texview);
|
|
||||||
|
|
||||||
debug!(slot = lower_slot.name, "Created texture view");
|
|
||||||
}
|
|
||||||
Some(SlotDescriptor::None) => {}
|
|
||||||
None => {}
|
|
||||||
_ => unreachable!("this slot should not have been put into the do later list"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prepare(&mut self) {
|
#[instrument(skip(self, world))]
|
||||||
|
pub fn prepare(&mut self, world: &mut World) {
|
||||||
|
// prepare all passes
|
||||||
|
let mut context = RenderGraphContext::new(&self.queue, None);
|
||||||
|
for (_, pass) in &mut self.passes {
|
||||||
|
let mut inner = pass.inner.borrow_mut();
|
||||||
|
inner.prepare(world, &mut context);
|
||||||
|
}
|
||||||
|
|
||||||
|
// create the execution path for the graph. This will be executed in `RenderGraph::render`
|
||||||
let builtin = {
|
let builtin = {
|
||||||
let mut h = FxHashSet::default();
|
let mut h = FxHashSet::default();
|
||||||
h.insert(0u64);
|
h.insert(0u64);
|
||||||
h
|
h
|
||||||
};
|
};
|
||||||
let descs = self.passes.values().map(|p| &*p.desc).collect();
|
let descs = self.passes.values().map(|p| &*p.desc).collect();
|
||||||
self.exec_path = Some(GraphExecutionPath::new(builtin, descs));
|
let path = GraphExecutionPath::new(builtin, descs);
|
||||||
|
trace!("Found {} steps in the rendergraph to execute", path.queue.len());
|
||||||
|
self.exec_path = Some(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, surface: &wgpu::Surface) {
|
pub fn render(&mut self, device: &wgpu::Device, queue: &wgpu::Queue, surface: &wgpu::Surface) {
|
||||||
|
@ -298,12 +228,12 @@ impl RenderGraph {
|
||||||
let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
||||||
label: Some(&label),
|
label: Some(&label),
|
||||||
});
|
});
|
||||||
let mut context = RenderGraphContext::new(encoder, queue);
|
let mut context = RenderGraphContext::new(queue, Some(encoder));
|
||||||
|
|
||||||
let mut inner = pass_inn.borrow_mut();
|
let mut inner = pass_inn.borrow_mut();
|
||||||
inner.execute(self, &*pass_desc, &mut context);
|
inner.execute(self, &*pass_desc, &mut context);
|
||||||
|
|
||||||
encoders.push(context.encoder.finish());
|
encoders.push(context.encoder.unwrap().finish());
|
||||||
}
|
}
|
||||||
|
|
||||||
queue.submit(encoders.into_iter());
|
queue.submit(encoders.into_iter());
|
||||||
|
@ -327,23 +257,12 @@ impl RenderGraph {
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn try_slot_bind_group(&self, id: u64) -> Option<&wgpu::BindGroup> {
|
pub fn try_slot_bind_group(&self, id: u64) -> Option<&wgpu::BindGroup> {
|
||||||
self.slots.get(&id).and_then(|s| {
|
todo!()
|
||||||
self.bind_groups.get(
|
|
||||||
&s.bind_group_id
|
|
||||||
.expect("Slot bind group has not been created yet"),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn slot_bind_group(&self, id: u64) -> &wgpu::BindGroup {
|
pub fn slot_bind_group(&self, id: u64) -> &wgpu::BindGroup {
|
||||||
let bg_id = self
|
todo!()
|
||||||
.slots
|
|
||||||
.get(&id)
|
|
||||||
.expect("unknown slot id")
|
|
||||||
.bind_group_id
|
|
||||||
.expect("Slot bind group has not been created yet");
|
|
||||||
self.bind_group(id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -355,14 +274,14 @@ pub(crate) struct BufferWrite {
|
||||||
|
|
||||||
pub struct RenderGraphContext<'a> {
|
pub struct RenderGraphContext<'a> {
|
||||||
/// Becomes None when the encoder is submitted
|
/// Becomes None when the encoder is submitted
|
||||||
pub(crate) encoder: wgpu::CommandEncoder,
|
pub(crate) encoder: Option<wgpu::CommandEncoder>,
|
||||||
pub(crate) queue: &'a wgpu::Queue,
|
pub(crate) queue: &'a wgpu::Queue,
|
||||||
pub(crate) buffer_writes: Vec<BufferWrite>,
|
pub(crate) buffer_writes: Vec<BufferWrite>,
|
||||||
renderpass_desc: Vec<wgpu::RenderPassDescriptor<'a, 'a>>,
|
renderpass_desc: Vec<wgpu::RenderPassDescriptor<'a, 'a>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> RenderGraphContext<'a> {
|
impl<'a> RenderGraphContext<'a> {
|
||||||
pub fn new(encoder: wgpu::CommandEncoder, queue: &'a wgpu::Queue) -> Self {
|
pub fn new(queue: &'a wgpu::Queue, encoder: Option<wgpu::CommandEncoder>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
encoder,
|
encoder,
|
||||||
queue,
|
queue,
|
||||||
|
@ -375,11 +294,19 @@ impl<'a> RenderGraphContext<'a> {
|
||||||
&'a mut self,
|
&'a mut self,
|
||||||
desc: wgpu::RenderPassDescriptor<'a, 'a>,
|
desc: wgpu::RenderPassDescriptor<'a, 'a>,
|
||||||
) -> wgpu::RenderPass {
|
) -> wgpu::RenderPass {
|
||||||
self.encoder.begin_render_pass(&desc)
|
self.encoder
|
||||||
|
.as_mut()
|
||||||
|
.expect("RenderGraphContext is missing a command encoder. This is likely \
|
||||||
|
because you are trying to run render commands in the prepare stage.")
|
||||||
|
.begin_render_pass(&desc)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn begin_compute_pass(&mut self, desc: &wgpu::ComputePassDescriptor) -> wgpu::ComputePass {
|
pub fn begin_compute_pass(&mut self, desc: &wgpu::ComputePassDescriptor) -> wgpu::ComputePass {
|
||||||
self.encoder.begin_compute_pass(desc)
|
self.encoder
|
||||||
|
.as_mut()
|
||||||
|
.expect("RenderGraphContext is missing a command encoder. This is likely \
|
||||||
|
because you are trying to run render commands in the prepare stage.")
|
||||||
|
.begin_compute_pass(desc)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write_buffer(&mut self, target_slot: &str, bytes: &[u8]) {
|
pub fn write_buffer(&mut self, target_slot: &str, bytes: &[u8]) {
|
||||||
|
|
|
@ -30,7 +30,7 @@ pub enum SlotValue {
|
||||||
TextureView(wgpu::TextureView),
|
TextureView(wgpu::TextureView),
|
||||||
Sampler(wgpu::Sampler),
|
Sampler(wgpu::Sampler),
|
||||||
Texture(wgpu::Texture),
|
Texture(wgpu::Texture),
|
||||||
Buffer(wgpu::Buffer),
|
Buffer(Rc<wgpu::Buffer>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SlotValue {
|
impl SlotValue {
|
||||||
|
@ -49,19 +49,6 @@ impl SlotValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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,
|
||||||
|
@ -76,7 +63,7 @@ pub struct RenderPassSlot {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
/// The descriptor of the slot value.
|
/// The descriptor of the slot value.
|
||||||
/// This will be `None` if this slot is an input.
|
/// This will be `None` if this slot is an input.
|
||||||
pub desc: Option<SlotDescriptor>,
|
pub value: Option<Rc<SlotValue>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
@ -171,14 +158,14 @@ impl RenderGraphPassDesc {
|
||||||
id: u64,
|
id: u64,
|
||||||
name: &str,
|
name: &str,
|
||||||
attribute: SlotAttribute,
|
attribute: SlotAttribute,
|
||||||
desc: Option<SlotDescriptor>,
|
value: Option<SlotValue>,
|
||||||
) {
|
) {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
matches!(
|
matches!(
|
||||||
desc,
|
value,
|
||||||
None | Some(SlotDescriptor::Buffer(_)) | Some(SlotDescriptor::BufferInit(_))
|
None | Some(SlotValue::Buffer(_))
|
||||||
),
|
),
|
||||||
"slot descriptor does not match the type of slot"
|
"slot value is not a buffer"
|
||||||
);
|
);
|
||||||
|
|
||||||
let slot = RenderPassSlot {
|
let slot = RenderPassSlot {
|
||||||
|
@ -186,7 +173,7 @@ impl RenderGraphPassDesc {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
ty: SlotType::Buffer,
|
ty: SlotType::Buffer,
|
||||||
attribute,
|
attribute,
|
||||||
desc,
|
value: value.map(|v| Rc::new(v)),
|
||||||
};
|
};
|
||||||
self.add_slot(slot);
|
self.add_slot(slot);
|
||||||
}
|
}
|
||||||
|
@ -197,11 +184,14 @@ impl RenderGraphPassDesc {
|
||||||
id: u64,
|
id: u64,
|
||||||
name: &str,
|
name: &str,
|
||||||
attribute: SlotAttribute,
|
attribute: SlotAttribute,
|
||||||
desc: Option<SlotDescriptor>,
|
value: Option<SlotValue>,
|
||||||
) {
|
) {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
matches!(desc, None | Some(SlotDescriptor::Texture(_))),
|
matches!(
|
||||||
"slot descriptor does not match the type of slot"
|
value,
|
||||||
|
None | Some(SlotValue::Texture(_))
|
||||||
|
),
|
||||||
|
"slot value is not a texture"
|
||||||
);
|
);
|
||||||
|
|
||||||
let slot = RenderPassSlot {
|
let slot = RenderPassSlot {
|
||||||
|
@ -209,7 +199,7 @@ impl RenderGraphPassDesc {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
ty: SlotType::Texture,
|
ty: SlotType::Texture,
|
||||||
attribute,
|
attribute,
|
||||||
desc,
|
value: value.map(|v| Rc::new(v)),
|
||||||
};
|
};
|
||||||
self.add_slot(slot);
|
self.add_slot(slot);
|
||||||
}
|
}
|
||||||
|
@ -220,11 +210,14 @@ impl RenderGraphPassDesc {
|
||||||
id: u64,
|
id: u64,
|
||||||
name: &str,
|
name: &str,
|
||||||
attribute: SlotAttribute,
|
attribute: SlotAttribute,
|
||||||
desc: Option<SlotDescriptor>,
|
value: Option<SlotValue>,
|
||||||
) {
|
) {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
matches!(desc, None | Some(SlotDescriptor::TextureView(_))),
|
matches!(
|
||||||
"slot descriptor does not match the type of slot"
|
value,
|
||||||
|
None | Some(SlotValue::TextureView(_))
|
||||||
|
),
|
||||||
|
"slot value is not a texture view"
|
||||||
);
|
);
|
||||||
|
|
||||||
let slot = RenderPassSlot {
|
let slot = RenderPassSlot {
|
||||||
|
@ -232,7 +225,7 @@ impl RenderGraphPassDesc {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
ty: SlotType::TextureView,
|
ty: SlotType::TextureView,
|
||||||
attribute,
|
attribute,
|
||||||
desc,
|
value: value.map(|v| Rc::new(v)),
|
||||||
};
|
};
|
||||||
self.add_slot(slot);
|
self.add_slot(slot);
|
||||||
}
|
}
|
||||||
|
@ -243,11 +236,14 @@ impl RenderGraphPassDesc {
|
||||||
id: u64,
|
id: u64,
|
||||||
name: &str,
|
name: &str,
|
||||||
attribute: SlotAttribute,
|
attribute: SlotAttribute,
|
||||||
desc: Option<SlotDescriptor>,
|
value: Option<SlotValue>,
|
||||||
) {
|
) {
|
||||||
debug_assert!(
|
debug_assert!(
|
||||||
matches!(desc, None | Some(SlotDescriptor::Sampler(_))),
|
matches!(
|
||||||
"slot descriptor does not match the type of slot"
|
value,
|
||||||
|
None | Some(SlotValue::Sampler(_))
|
||||||
|
),
|
||||||
|
"slot value is not a sampler"
|
||||||
);
|
);
|
||||||
|
|
||||||
let slot = RenderPassSlot {
|
let slot = RenderPassSlot {
|
||||||
|
@ -255,7 +251,7 @@ impl RenderGraphPassDesc {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
ty: SlotType::Sampler,
|
ty: SlotType::Sampler,
|
||||||
attribute,
|
attribute,
|
||||||
desc,
|
value: value.map(|v| Rc::new(v)),
|
||||||
};
|
};
|
||||||
self.add_slot(slot);
|
self.add_slot(slot);
|
||||||
}
|
}
|
||||||
|
@ -279,7 +275,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<'a, 'b>(&'a self, graph: &'b mut RenderGraph) -> RenderGraphPassDesc;
|
fn desc<'a, 'b>(&'a mut self, graph: &'b mut RenderGraph) -> RenderGraphPassDesc;
|
||||||
|
|
||||||
fn prepare(&mut self, world: &mut World, context: &mut RenderGraphContext);
|
fn prepare(&mut self, world: &mut World, context: &mut RenderGraphContext);
|
||||||
fn execute(
|
fn execute(
|
||||||
|
|
|
@ -8,21 +8,26 @@ use crate::{
|
||||||
render::{
|
render::{
|
||||||
camera::{CameraUniform, RenderCamera},
|
camera::{CameraUniform, RenderCamera},
|
||||||
graph::{
|
graph::{
|
||||||
BufferInitDescriptor, PipelineShaderDesc, RenderGraphContext, RenderGraphPass,
|
BufferDescriptor, BufferInitDescriptor, PipelineShaderDesc, RenderGraphContext,
|
||||||
RenderGraphPassDesc, RenderGraphPipelineInfo, RenderPassType, SlotAttribute,
|
RenderGraphPass, RenderGraphPassDesc, RenderGraphPipelineInfo, RenderPassType,
|
||||||
SlotDescriptor,
|
SlotAttribute, SlotValue,
|
||||||
},
|
},
|
||||||
|
render_buffer::BufferWrapper,
|
||||||
renderer::ScreenSize,
|
renderer::ScreenSize,
|
||||||
resource::{FragmentState, RenderPipelineDescriptor, Shader, VertexState},
|
resource::{FragmentState, RenderPipelineDescriptor, Shader, VertexState},
|
||||||
},
|
},
|
||||||
scene::CameraComponent,
|
scene::CameraComponent, DeltaTime,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Supplies some basic things other passes needs.
|
/// Supplies some basic things other passes needs.
|
||||||
///
|
///
|
||||||
/// screen size buffer, camera buffer,
|
/// screen size buffer, camera buffer,
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct TrianglePass;
|
pub struct TrianglePass {
|
||||||
|
color_bg: Option<wgpu::BindGroup>,
|
||||||
|
color_buf: Option<Rc<wgpu::Buffer>>,
|
||||||
|
acc: f32,
|
||||||
|
}
|
||||||
|
|
||||||
impl TrianglePass {
|
impl TrianglePass {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
|
@ -32,7 +37,7 @@ impl TrianglePass {
|
||||||
|
|
||||||
impl RenderGraphPass for TrianglePass {
|
impl RenderGraphPass for TrianglePass {
|
||||||
fn desc(
|
fn desc(
|
||||||
&self,
|
&mut self,
|
||||||
graph: &mut crate::render::graph::RenderGraph,
|
graph: &mut crate::render::graph::RenderGraph,
|
||||||
) -> crate::render::graph::RenderGraphPassDesc {
|
) -> crate::render::graph::RenderGraphPassDesc {
|
||||||
let shader = Rc::new(Shader {
|
let shader = Rc::new(Shader {
|
||||||
|
@ -40,13 +45,25 @@ impl RenderGraphPass for TrianglePass {
|
||||||
source: include_str!("../../shaders/triangle.wgsl").to_string(),
|
source: include_str!("../../shaders/triangle.wgsl").to_string(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let device = graph.device();
|
||||||
|
|
||||||
|
let (color_bgl, color_bg, color_buf, _) = BufferWrapper::builder()
|
||||||
|
.label_prefix("color")
|
||||||
|
.visibility(wgpu::ShaderStages::FRAGMENT)
|
||||||
|
.buffer_usage(wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST)
|
||||||
|
.contents(&[glam::Vec4::new(0.902, 0.639, 0.451, 1.0)])
|
||||||
|
.finish_parts(device);
|
||||||
|
let color_buf = Rc::new(color_buf);
|
||||||
|
self.color_bg = Some(color_bg);
|
||||||
|
self.color_buf = Some(color_buf.clone());
|
||||||
|
|
||||||
let mut desc = RenderGraphPassDesc::new(
|
let mut desc = RenderGraphPassDesc::new(
|
||||||
graph.next_id(),
|
graph.next_id(),
|
||||||
"TrianglePass",
|
"TrianglePass",
|
||||||
RenderPassType::Render,
|
RenderPassType::Render,
|
||||||
Some(RenderPipelineDescriptor {
|
Some(RenderPipelineDescriptor {
|
||||||
label: Some("triangle_pipeline".into()),
|
label: Some("triangle_pipeline".into()),
|
||||||
layouts: vec![],
|
layouts: vec![color_bgl],
|
||||||
push_constant_ranges: vec![],
|
push_constant_ranges: vec![],
|
||||||
vertex: VertexState {
|
vertex: VertexState {
|
||||||
module: shader.clone(),
|
module: shader.clone(),
|
||||||
|
@ -76,21 +93,28 @@ impl RenderGraphPass for TrianglePass {
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
/* desc.add_buffer_slot(graph.next_id(), "screen_size_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor {
|
desc.add_buffer_slot(
|
||||||
label: Some("B_ScreenSize".to_string()),
|
graph.next_id(),
|
||||||
contents: bytemuck::bytes_of(&UVec2::new(800, 600)).to_vec(),
|
"color_buffer",
|
||||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
SlotAttribute::Output,
|
||||||
})));
|
Some(SlotValue::Buffer(color_buf)),
|
||||||
desc.add_buffer_slot(graph.next_id(), "camera_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor {
|
);
|
||||||
label: Some("B_Camera".to_string()),
|
|
||||||
contents: bytemuck::bytes_of(&CameraUniform::default()).to_vec(),
|
|
||||||
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
|
|
||||||
}))); */
|
|
||||||
|
|
||||||
desc
|
desc
|
||||||
}
|
}
|
||||||
|
|
||||||
fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {}
|
fn prepare(&mut self, world: &mut lyra_ecs::World, context: &mut RenderGraphContext) {
|
||||||
|
const SPEED: f32 = 1.5;
|
||||||
|
|
||||||
|
let dt = **world.get_resource::<DeltaTime>();
|
||||||
|
self.acc += dt;
|
||||||
|
let x = (self.acc * SPEED).sin();
|
||||||
|
let y = ((self.acc + 2.15) * SPEED).sin();
|
||||||
|
let z = ((self.acc + 5.35) * SPEED).sin();
|
||||||
|
|
||||||
|
let color = glam::Vec4::new(x, y, z, 1.0);
|
||||||
|
context.queue.write_buffer(self.color_buf.as_ref().unwrap(), 0, bytemuck::bytes_of(&color));
|
||||||
|
}
|
||||||
|
|
||||||
fn execute(
|
fn execute(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
@ -102,7 +126,12 @@ impl RenderGraphPass for TrianglePass {
|
||||||
.slot_value(graph.slot_id("window_texture_view").unwrap())
|
.slot_value(graph.slot_id("window_texture_view").unwrap())
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.as_texture_view();
|
.as_texture_view();
|
||||||
let encoder = &mut context.encoder;
|
let encoder = context.encoder.as_mut().unwrap();
|
||||||
|
|
||||||
|
//context.queue.write_buffer(buffer, offset, data)
|
||||||
|
|
||||||
|
//let color_bg = graph.bind_group(graph.slot_id("color_buffer").unwrap());
|
||||||
|
let color_bg = self.color_bg.as_ref().unwrap();
|
||||||
|
|
||||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: Some("TrianglePass"),
|
label: Some("TrianglePass"),
|
||||||
|
@ -127,6 +156,7 @@ impl RenderGraphPass for TrianglePass {
|
||||||
|
|
||||||
let pipeline = graph.pipeline(desc.id);
|
let pipeline = graph.pipeline(desc.id);
|
||||||
pass.set_pipeline(&pipeline.as_render());
|
pass.set_pipeline(&pipeline.as_render());
|
||||||
|
pass.set_bind_group(0, color_bg, &[]);
|
||||||
pass.draw(0..3, 0..1);
|
pass.draw(0..3, 0..1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::num::{NonZeroU32, NonZeroU8};
|
use std::{mem, num::{NonZeroU32, NonZeroU8}};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct TextureViewDescriptor {
|
pub struct TextureViewDescriptor {
|
||||||
|
@ -162,6 +162,14 @@ pub struct BufferDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferDescriptor {
|
impl BufferDescriptor {
|
||||||
|
pub fn new<T: Sized>(usage: wgpu::BufferUsages, mapped_at_creation: bool) -> Self {
|
||||||
|
Self {
|
||||||
|
size: mem::size_of::<T>() as _,
|
||||||
|
usage,
|
||||||
|
mapped_at_creation,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn as_wgpu<'a>(&self, label: Option<&'a str>) -> wgpu::BufferDescriptor<'a> {
|
pub fn as_wgpu<'a>(&self, label: Option<&'a str>) -> wgpu::BufferDescriptor<'a> {
|
||||||
wgpu::BufferDescriptor {
|
wgpu::BufferDescriptor {
|
||||||
label,
|
label,
|
||||||
|
@ -184,6 +192,14 @@ pub struct BufferInitDescriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BufferInitDescriptor {
|
impl BufferInitDescriptor {
|
||||||
|
pub fn new<T: bytemuck::Pod>(label: Option<&str>, data: &T, usage: wgpu::BufferUsages) -> Self {
|
||||||
|
Self {
|
||||||
|
label: label.map(|s| s.to_string()),
|
||||||
|
contents: bytemuck::bytes_of(data).to_vec(),
|
||||||
|
usage,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn as_wgpu<'a>(&'a self, label: Option<&'a str>) -> wgpu::util::BufferInitDescriptor<'a> {
|
pub fn as_wgpu<'a>(&'a self, label: Option<&'a str>) -> wgpu::util::BufferInitDescriptor<'a> {
|
||||||
wgpu::util::BufferInitDescriptor {
|
wgpu::util::BufferInitDescriptor {
|
||||||
label,
|
label,
|
||||||
|
|
|
@ -134,6 +134,15 @@ impl BufferWrapper {
|
||||||
"BufferWrapper is missing bindgroup pair! Cannot set bind group on RenderPass!",
|
"BufferWrapper is missing bindgroup pair! Cannot set bind group on RenderPass!",
|
||||||
).bindgroup
|
).bindgroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Take the bind group layout, the bind group, and the buffer out of the wrapper.
|
||||||
|
pub fn parts(self) -> (Option<Rc<wgpu::BindGroupLayout>>, Option<wgpu::BindGroup>, wgpu::Buffer) {
|
||||||
|
if let Some(pair) = self.bindgroup_pair {
|
||||||
|
(Some(pair.layout), Some(pair.bindgroup), self.inner_buf)
|
||||||
|
} else {
|
||||||
|
(None, None, self.inner_buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Struct used for building a BufferWrapper
|
/// Struct used for building a BufferWrapper
|
||||||
|
@ -221,7 +230,7 @@ impl BufferWrapperBuilder {
|
||||||
/// * `contents` - The contents to initialize the buffer with.
|
/// * `contents` - The contents to initialize the buffer with.
|
||||||
///
|
///
|
||||||
/// If a field is missing, a panic will occur.
|
/// If a field is missing, a panic will occur.
|
||||||
pub fn finish(self, device: &wgpu::Device) -> BufferWrapper {
|
pub fn finish_parts(self, device: &wgpu::Device) -> (wgpu::BindGroupLayout, wgpu::BindGroup, wgpu::Buffer, usize) {
|
||||||
let buf_usage = self.buffer_usage.expect("Buffer usage was not set");
|
let buf_usage = self.buffer_usage.expect("Buffer usage was not set");
|
||||||
let buffer = if let Some(contents) = self.contents.as_ref() {
|
let buffer = if let Some(contents) = self.contents.as_ref() {
|
||||||
device.create_buffer_init(
|
device.create_buffer_init(
|
||||||
|
@ -293,10 +302,25 @@ impl BufferWrapperBuilder {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
BufferWrapper {
|
/* BufferWrapper {
|
||||||
bindgroup_pair: Some(bg_pair),
|
bindgroup_pair: Some(bg_pair),
|
||||||
inner_buf: buffer,
|
inner_buf: buffer,
|
||||||
len: Some(self.count.unwrap_or_default() as usize),
|
len: Some(self.count.unwrap_or_default() as usize),
|
||||||
|
} */
|
||||||
|
|
||||||
|
(Rc::try_unwrap(bg_pair.layout).unwrap(), bg_pair.bindgroup, buffer, self.count.unwrap_or_default() as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finish(self, device: &wgpu::Device) -> BufferWrapper {
|
||||||
|
let (bgl, bg, buff, len) = self.finish_parts(device);
|
||||||
|
|
||||||
|
BufferWrapper {
|
||||||
|
bindgroup_pair: Some(BindGroupPair {
|
||||||
|
layout: Rc::new(bgl),
|
||||||
|
bindgroup: bg
|
||||||
|
}),
|
||||||
|
inner_buf: buff,
|
||||||
|
len: Some(len),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -214,7 +214,7 @@ impl BasicRenderer {
|
||||||
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());
|
let mut g = RenderGraph::new(device.clone(), queue.clone(), config.clone());
|
||||||
/* debug!("Adding base pass");
|
/* debug!("Adding base pass");
|
||||||
g.add_pass(TrianglePass::new());
|
g.add_pass(TrianglePass::new());
|
||||||
debug!("Adding depth pre-pass");
|
debug!("Adding depth pre-pass");
|
||||||
|
@ -265,7 +265,7 @@ impl BasicRenderer {
|
||||||
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) {
|
||||||
self.graph.prepare();
|
self.graph.prepare(main_world);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(self))]
|
#[instrument(skip(self))]
|
||||||
|
|
|
@ -63,9 +63,7 @@ impl RenderPipelineDescriptor {
|
||||||
label: None, //self.label.as_ref().map(|s| format!("{}Layout", s)),
|
label: None, //self.label.as_ref().map(|s| format!("{}Layout", s)),
|
||||||
bind_group_layouts: &bgs,
|
bind_group_layouts: &bgs,
|
||||||
push_constant_ranges: &self.push_constant_ranges,
|
push_constant_ranges: &self.push_constant_ranges,
|
||||||
});
|
})
|
||||||
|
|
||||||
todo!()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* fn as_wgpu<'a>(&'a self, device: &wgpu::Device, layout: Option<&'a wgpu::PipelineLayout>) -> wgpu::RenderPipelineDescriptor<'a> {
|
/* fn as_wgpu<'a>(&'a self, device: &wgpu::Device, layout: Option<&'a wgpu::PipelineLayout>) -> wgpu::RenderPipelineDescriptor<'a> {
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
// Vertex shader
|
||||||
|
|
||||||
|
const max_light_count: u32 = 16u;
|
||||||
|
|
||||||
|
const LIGHT_TY_DIRECTIONAL = 0u;
|
||||||
|
const LIGHT_TY_POINT = 1u;
|
||||||
|
const LIGHT_TY_SPOT = 2u;
|
||||||
|
|
||||||
|
const ALPHA_CUTOFF = 0.1;
|
||||||
|
|
||||||
|
struct VertexInput {
|
||||||
|
@location(0) position: vec3<f32>,
|
||||||
|
@location(1) tex_coords: vec2<f32>,
|
||||||
|
@location(2) normal: vec3<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VertexOutput {
|
||||||
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
|
@location(0) tex_coords: vec2<f32>,
|
||||||
|
@location(1) world_position: vec3<f32>,
|
||||||
|
@location(2) world_normal: vec3<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TransformData {
|
||||||
|
transform: mat4x4<f32>,
|
||||||
|
normal_matrix: mat4x4<f32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CameraUniform {
|
||||||
|
view: mat4x4<f32>,
|
||||||
|
inverse_projection: mat4x4<f32>,
|
||||||
|
view_projection: mat4x4<f32>,
|
||||||
|
projection: mat4x4<f32>,
|
||||||
|
position: vec3<f32>,
|
||||||
|
tile_debug: u32,
|
||||||
|
};
|
||||||
|
|
||||||
|
@group(1) @binding(0)
|
||||||
|
var<uniform> u_model_transform_data: TransformData;
|
||||||
|
|
||||||
|
@group(2) @binding(0)
|
||||||
|
var<uniform> u_camera: CameraUniform;
|
||||||
|
|
||||||
|
@vertex
|
||||||
|
fn vs_main(
|
||||||
|
model: VertexInput,
|
||||||
|
) -> VertexOutput {
|
||||||
|
var out: VertexOutput;
|
||||||
|
|
||||||
|
out.tex_coords = model.tex_coords;
|
||||||
|
out.clip_position = u_camera.view_projection * u_model_transform_data.transform * vec4<f32>(model.position, 1.0);
|
||||||
|
|
||||||
|
// the normal mat is actually only a mat3x3, but there's a bug in wgpu: https://github.com/gfx-rs/wgpu-rs/issues/36
|
||||||
|
let normal_mat4 = u_model_transform_data.normal_matrix;
|
||||||
|
let normal_mat = mat3x3(normal_mat4[0].xyz, normal_mat4[1].xyz, normal_mat4[2].xyz);
|
||||||
|
out.world_normal = normalize(normal_mat * model.normal, );
|
||||||
|
|
||||||
|
var world_position: vec4<f32> = u_model_transform_data.transform * vec4<f32>(model.position, 1.0);
|
||||||
|
out.world_position = world_position.xyz;
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fragment shader
|
||||||
|
|
||||||
|
struct Material {
|
||||||
|
ambient: vec4<f32>,
|
||||||
|
diffuse: vec4<f32>,
|
||||||
|
specular: vec4<f32>,
|
||||||
|
shininess: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
@group(0) @binding(0)
|
||||||
|
var t_diffuse: texture_2d<f32>;
|
||||||
|
@group(0) @binding(1)
|
||||||
|
var s_diffuse: sampler;
|
||||||
|
|
||||||
|
@fragment
|
||||||
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
|
let object_color: vec4<f32> = textureSample(t_diffuse, s_diffuse, in.tex_coords);
|
||||||
|
|
||||||
|
if (object_color.a < ALPHA_CUTOFF) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
|
||||||
|
return object_color;
|
||||||
|
}
|
|
@ -2,6 +2,9 @@ struct VertexOutput {
|
||||||
@builtin(position) clip_position: vec4<f32>,
|
@builtin(position) clip_position: vec4<f32>,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@group(0) @binding(0)
|
||||||
|
var<uniform> u_triangle_color: vec4<f32>;
|
||||||
|
|
||||||
@vertex
|
@vertex
|
||||||
fn vs_main(
|
fn vs_main(
|
||||||
@builtin(vertex_index) in_vertex_index: u32,
|
@builtin(vertex_index) in_vertex_index: u32,
|
||||||
|
@ -15,5 +18,5 @@ fn vs_main(
|
||||||
|
|
||||||
@fragment
|
@fragment
|
||||||
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||||
return vec4<f32>(0.3, 0.2, 0.1, 1.0);
|
return vec4<f32>(u_triangle_color.xyz, 1.0);
|
||||||
}
|
}
|
Loading…
Reference in New Issue