Implement a Render Graph #16

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

View File

@ -3,9 +3,11 @@ use std::{
cell::RefCell,
collections::{HashMap, VecDeque},
ptr::NonNull,
rc::Rc,
sync::Arc,
};
use lyra_ecs::World;
pub use pass::*;
mod passes;
@ -17,13 +19,14 @@ pub use slot_desc::*;
mod execution_path;
use rustc_hash::{FxHashMap, FxHashSet};
use tracing::{debug, debug_span, instrument};
use tracing::{debug, debug_span, instrument, trace};
use wgpu::{util::DeviceExt, RenderPass};
use self::execution_path::GraphExecutionPath;
use super::{
renderer::{BasicRenderer, Renderer}, resource::{Pipeline, RenderPipeline},
renderer::{BasicRenderer, Renderer},
resource::{Pipeline, RenderPipeline},
};
//#[derive(Clone)]
@ -37,11 +40,6 @@ struct ResourcedSlot {
//slot: RenderPassSlot,
ty: SlotType,
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.
@ -54,6 +52,8 @@ pub struct PipelineResource {
}
pub struct RenderGraph {
device: Rc<wgpu::Device>,
queue: Rc<wgpu::Queue>,
slots: FxHashMap<u64, ResourcedSlot>,
slot_names: HashMap<String, u64>,
passes: FxHashMap<u64, PassEntry>,
@ -70,7 +70,7 @@ pub struct 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 slot_names = HashMap::default();
@ -79,14 +79,15 @@ impl RenderGraph {
ResourcedSlot {
name: "window_texture_view".to_string(),
ty: SlotType::TextureView,
// this will get set in prepare stage.
value: SlotValue::None,
bind_group_id: None,
create_desc: None,
},
);
slot_names.insert("window_texture_view".to_string(), 0u64);
Self {
device,
queue,
slots,
slot_names,
passes: Default::default(),
@ -98,6 +99,10 @@ impl RenderGraph {
}
}
pub fn device(&self) -> &wgpu::Device {
&*self.device
}
pub fn next_id(&mut self) -> u64 {
self.current_id += 1;
self.current_id
@ -115,7 +120,7 @@ impl RenderGraph {
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);
for slot in &mut desc.slots {
@ -124,18 +129,19 @@ impl RenderGraph {
.get(&slot.name)
.and_then(|id| self.slots.get_mut(id).map(|s| (id, s)))
{
// if there is a slot of the same name
slot.id = *id;
if slot.desc.is_some() && other.create_desc.is_none() {
other.create_desc = slot.desc.clone();
}
debug_assert_eq!(
slot.ty, other.ty,
"slot {} in pass {} does not match existing slot of same name",
slot.name, desc.name
);
} else {
let res_slot = ResourcedSlot {
name: slot.name.clone(),
ty: slot.ty,
value: SlotValue::None,
bind_group_id: None,
create_desc: slot.desc.clone(),
};
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.
#[instrument(skip(self, device))]
pub fn setup(&mut self, device: &wgpu::Device) {
let mut later_slots = VecDeque::new();
// For all passes, create their pipelines
for pass in self.passes.values() {
if let Some(pipei) = &pass.desc.pipeline_desc {
@ -174,101 +178,27 @@ impl RenderGraph {
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 mut h = FxHashSet::default();
h.insert(0u64);
h
};
let descs = self.passes.values().map(|p| &*p.desc).collect();
self.exec_path = Some(GraphExecutionPath::new(builtin, descs));
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) {
@ -298,12 +228,12 @@ impl RenderGraph {
let encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
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();
inner.execute(self, &*pass_desc, &mut context);
encoders.push(context.encoder.finish());
encoders.push(context.encoder.unwrap().finish());
}
queue.submit(encoders.into_iter());
@ -327,23 +257,12 @@ impl RenderGraph {
#[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"),
)
})
todo!()
}
#[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)
todo!()
}
}
@ -355,14 +274,14 @@ pub(crate) struct BufferWrite {
pub struct RenderGraphContext<'a> {
/// 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) buffer_writes: Vec<BufferWrite>,
renderpass_desc: Vec<wgpu::RenderPassDescriptor<'a, '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 {
encoder,
queue,
@ -375,11 +294,19 @@ impl<'a> RenderGraphContext<'a> {
&'a mut self,
desc: wgpu::RenderPassDescriptor<'a, 'a>,
) -> 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 {
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]) {

View File

@ -30,7 +30,7 @@ pub enum SlotValue {
TextureView(wgpu::TextureView),
Sampler(wgpu::Sampler),
Texture(wgpu::Texture),
Buffer(wgpu::Buffer),
Buffer(Rc<wgpu::Buffer>),
}
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)]
pub enum SlotAttribute {
Input,
@ -76,7 +63,7 @@ pub struct RenderPassSlot {
pub name: String,
/// The descriptor of the slot value.
/// This will be `None` if this slot is an input.
pub desc: Option<SlotDescriptor>,
pub value: Option<Rc<SlotValue>>,
}
#[derive(Clone)]
@ -171,14 +158,14 @@ impl RenderGraphPassDesc {
id: u64,
name: &str,
attribute: SlotAttribute,
desc: Option<SlotDescriptor>,
value: Option<SlotValue>,
) {
debug_assert!(
matches!(
desc,
None | Some(SlotDescriptor::Buffer(_)) | Some(SlotDescriptor::BufferInit(_))
value,
None | Some(SlotValue::Buffer(_))
),
"slot descriptor does not match the type of slot"
"slot value is not a buffer"
);
let slot = RenderPassSlot {
@ -186,7 +173,7 @@ impl RenderGraphPassDesc {
name: name.to_string(),
ty: SlotType::Buffer,
attribute,
desc,
value: value.map(|v| Rc::new(v)),
};
self.add_slot(slot);
}
@ -197,11 +184,14 @@ impl RenderGraphPassDesc {
id: u64,
name: &str,
attribute: SlotAttribute,
desc: Option<SlotDescriptor>,
value: Option<SlotValue>,
) {
debug_assert!(
matches!(desc, None | Some(SlotDescriptor::Texture(_))),
"slot descriptor does not match the type of slot"
matches!(
value,
None | Some(SlotValue::Texture(_))
),
"slot value is not a texture"
);
let slot = RenderPassSlot {
@ -209,7 +199,7 @@ impl RenderGraphPassDesc {
name: name.to_string(),
ty: SlotType::Texture,
attribute,
desc,
value: value.map(|v| Rc::new(v)),
};
self.add_slot(slot);
}
@ -220,11 +210,14 @@ impl RenderGraphPassDesc {
id: u64,
name: &str,
attribute: SlotAttribute,
desc: Option<SlotDescriptor>,
value: Option<SlotValue>,
) {
debug_assert!(
matches!(desc, None | Some(SlotDescriptor::TextureView(_))),
"slot descriptor does not match the type of slot"
matches!(
value,
None | Some(SlotValue::TextureView(_))
),
"slot value is not a texture view"
);
let slot = RenderPassSlot {
@ -232,7 +225,7 @@ impl RenderGraphPassDesc {
name: name.to_string(),
ty: SlotType::TextureView,
attribute,
desc,
value: value.map(|v| Rc::new(v)),
};
self.add_slot(slot);
}
@ -243,11 +236,14 @@ impl RenderGraphPassDesc {
id: u64,
name: &str,
attribute: SlotAttribute,
desc: Option<SlotDescriptor>,
value: Option<SlotValue>,
) {
debug_assert!(
matches!(desc, None | Some(SlotDescriptor::Sampler(_))),
"slot descriptor does not match the type of slot"
matches!(
value,
None | Some(SlotValue::Sampler(_))
),
"slot value is not a sampler"
);
let slot = RenderPassSlot {
@ -255,7 +251,7 @@ impl RenderGraphPassDesc {
name: name.to_string(),
ty: SlotType::Sampler,
attribute,
desc,
value: value.map(|v| Rc::new(v)),
};
self.add_slot(slot);
}
@ -279,7 +275,7 @@ pub trait RenderGraphPass: 'static {
/// Create a render pass describer.
///
/// The `id` argument is passed as mutable so you can increment it as you use it for new slots.
fn desc<'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 execute(

View File

@ -8,21 +8,26 @@ use crate::{
render::{
camera::{CameraUniform, RenderCamera},
graph::{
BufferInitDescriptor, PipelineShaderDesc, RenderGraphContext, RenderGraphPass,
RenderGraphPassDesc, RenderGraphPipelineInfo, RenderPassType, SlotAttribute,
SlotDescriptor,
BufferDescriptor, BufferInitDescriptor, PipelineShaderDesc, RenderGraphContext,
RenderGraphPass, RenderGraphPassDesc, RenderGraphPipelineInfo, RenderPassType,
SlotAttribute, SlotValue,
},
render_buffer::BufferWrapper,
renderer::ScreenSize,
resource::{FragmentState, RenderPipelineDescriptor, Shader, VertexState},
},
scene::CameraComponent,
scene::CameraComponent, DeltaTime,
};
/// Supplies some basic things other passes needs.
///
/// screen size buffer, camera buffer,
#[derive(Default)]
pub struct TrianglePass;
pub struct TrianglePass {
color_bg: Option<wgpu::BindGroup>,
color_buf: Option<Rc<wgpu::Buffer>>,
acc: f32,
}
impl TrianglePass {
pub fn new() -> Self {
@ -32,7 +37,7 @@ impl TrianglePass {
impl RenderGraphPass for TrianglePass {
fn desc(
&self,
&mut self,
graph: &mut crate::render::graph::RenderGraph,
) -> crate::render::graph::RenderGraphPassDesc {
let shader = Rc::new(Shader {
@ -40,13 +45,25 @@ impl RenderGraphPass for TrianglePass {
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(
graph.next_id(),
"TrianglePass",
RenderPassType::Render,
Some(RenderPipelineDescriptor {
label: Some("triangle_pipeline".into()),
layouts: vec![],
layouts: vec![color_bgl],
push_constant_ranges: vec![],
vertex: VertexState {
module: shader.clone(),
@ -76,21 +93,28 @@ impl RenderGraphPass for TrianglePass {
None,
);
/* desc.add_buffer_slot(graph.next_id(), "screen_size_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor {
label: Some("B_ScreenSize".to_string()),
contents: bytemuck::bytes_of(&UVec2::new(800, 600)).to_vec(),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
})));
desc.add_buffer_slot(graph.next_id(), "camera_buffer", SlotAttribute::Output, Some(SlotDescriptor::BufferInit(BufferInitDescriptor {
label: Some("B_Camera".to_string()),
contents: bytemuck::bytes_of(&CameraUniform::default()).to_vec(),
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
}))); */
desc.add_buffer_slot(
graph.next_id(),
"color_buffer",
SlotAttribute::Output,
Some(SlotValue::Buffer(color_buf)),
);
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(
&mut self,
@ -102,7 +126,12 @@ impl RenderGraphPass for TrianglePass {
.slot_value(graph.slot_id("window_texture_view").unwrap())
.unwrap()
.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 {
label: Some("TrianglePass"),
@ -127,6 +156,7 @@ impl RenderGraphPass for TrianglePass {
let pipeline = graph.pipeline(desc.id);
pass.set_pipeline(&pipeline.as_render());
pass.set_bind_group(0, color_bg, &[]);
pass.draw(0..3, 0..1);
}
}

View File

@ -1,4 +1,4 @@
use std::num::{NonZeroU32, NonZeroU8};
use std::{mem, num::{NonZeroU32, NonZeroU8}};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct TextureViewDescriptor {
@ -162,6 +162,14 @@ pub struct 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> {
wgpu::BufferDescriptor {
label,
@ -184,6 +192,14 @@ pub struct 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> {
wgpu::util::BufferInitDescriptor {
label,

View File

@ -134,6 +134,15 @@ impl BufferWrapper {
"BufferWrapper is missing bindgroup pair! Cannot set bind group on RenderPass!",
).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
@ -221,7 +230,7 @@ impl BufferWrapperBuilder {
/// * `contents` - The contents to initialize the buffer with.
///
/// 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 buffer = if let Some(contents) = self.contents.as_ref() {
device.create_buffer_init(
@ -293,10 +302,25 @@ impl BufferWrapperBuilder {
}
};
BufferWrapper {
/* BufferWrapper {
bindgroup_pair: Some(bg_pair),
inner_buf: buffer,
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),
}
}
}

View File

@ -214,7 +214,7 @@ impl BasicRenderer {
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 mut g = RenderGraph::new(config.clone());
let mut g = RenderGraph::new(device.clone(), queue.clone(), config.clone());
/* debug!("Adding base pass");
g.add_pass(TrianglePass::new());
debug!("Adding depth pre-pass");
@ -265,7 +265,7 @@ impl BasicRenderer {
impl Renderer for BasicRenderer {
#[instrument(skip(self, main_world))]
fn prepare(&mut self, main_world: &mut World) {
self.graph.prepare();
self.graph.prepare(main_world);
}
#[instrument(skip(self))]

View File

@ -63,9 +63,7 @@ impl RenderPipelineDescriptor {
label: None, //self.label.as_ref().map(|s| format!("{}Layout", s)),
bind_group_layouts: &bgs,
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> {

View File

@ -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;
}

View File

@ -2,6 +2,9 @@ struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
};
@group(0) @binding(0)
var<uniform> u_triangle_color: vec4<f32>;
@vertex
fn vs_main(
@builtin(vertex_index) in_vertex_index: u32,
@ -15,5 +18,5 @@ fn vs_main(
@fragment
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);
}