lots of hacking and gutting, but it compiles

master
mitchellhansen 4 years ago
parent 3585c053ae
commit f933fe8312

@ -27,7 +27,7 @@ noise = "0.6"
ddsfile = "0.4" ddsfile = "0.4"
wgpu-subscriber = "0.1.0" wgpu-subscriber = "0.1.0"
tobj = "2.0.3" tobj = "2.0.3"
legion = "0.3.1"

@ -4,13 +4,21 @@ extern crate winit;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use futures::task::LocalSpawn; use futures::task::LocalSpawn;
use wgpu_subscriber; use wgpu_subscriber;
use winit::{ use winit::{
event::{self, WindowEvent}, event::{self, WindowEvent},
event_loop::{ControlFlow, EventLoop}, event_loop::{ControlFlow, EventLoop},
}; };
use crate::render::Renderer; use crate::render::Renderer;
use bytemuck::__core::ops::Range;
use cgmath::Point3;
use std::rc::Rc;
use wgpu::Buffer;
use winit::platform::unix::x11::ffi::Time;
use legion::*;
mod framework; mod framework;
mod geometry; mod geometry;
@ -48,8 +56,6 @@ ECS
*/ */
#[cfg_attr(rustfmt, rustfmt_skip)] #[cfg_attr(rustfmt, rustfmt_skip)]
#[allow(unused)] #[allow(unused)]
pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new( pub const OPENGL_TO_WGPU_MATRIX: cgmath::Matrix4<f32> = cgmath::Matrix4::new(
@ -84,7 +90,40 @@ pub enum ShaderStage {
queue: wgpu::Queue, queue: wgpu::Queue,
*/ */
async fn main() { // a component is any type that is 'static, sized, send and sync
#[derive(Clone, Copy, Debug, PartialEq)]
struct Position {
x: f32,
y: f32,
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct Velocity {
dx: f32,
dy: f32,
}
#[derive(Clone, Default, PartialEq, Eq, Hash, Copy, Debug)]
pub struct RangeCopy<Idx> {
pub start: Idx,
pub end: Idx,
}
#[derive(Clone, Copy, Debug, PartialEq)]
struct DirectionalLight {
color: wgpu::Color,
fov: f32,
depth: RangeCopy<f32>
}
#[derive(Clone, Debug)]
struct Mesh {
index_buffer: Rc<Buffer>,
vertex_buffer: Rc<Buffer>,
}
fn main() {
// #[cfg(not(target_arch = "wasm32"))] // #[cfg(not(target_arch = "wasm32"))]
// { // {
@ -96,6 +135,66 @@ async fn main() {
// #[cfg(target_arch = "wasm32")] // #[cfg(target_arch = "wasm32")]
// console_log::init().expect("could not initialize logger"); // console_log::init().expect("could not initialize logger");
use legion::*;
let mut world = World::default();
// This could be used for relationships between entities...???
let entity: Entity = world.push((
cgmath::Point3 {
x: -5.0,
y: 7.0,
z: 10.0,
},
DirectionalLight {
color: wgpu::Color {
r: 1.0,
g: 0.5,
b: 0.5,
a: 1.0,
},
fov: 45.0,
depth: RangeCopy { start: 1.0, end: 20.0 },
}
));
let entities: &[Entity] = world.extend(vec![
(Position { x: 0.0, y: 0.0 }, Velocity { dx: 0.0, dy: 0.0 }),
(Position { x: 1.0, y: 1.0 }, Velocity { dx: 0.0, dy: 0.0 }),
(Position { x: 2.0, y: 2.0 }, Velocity { dx: 0.0, dy: 0.0 }),
]);
/*
Querying entities by their handle
// entries return `None` if the entity does not exist
if let Some(mut entry) = world.entry(entity) {
// access information about the entity's archetype
//println!("{:?} has {:?}", entity, entry.archetype().layout().component_types());
// add an extra component
//entry.add_component(12f32);
// access the entity's components, returns `None` if the entity does not have the component
//assert_eq!(entry.get_component::<f32>().unwrap(), &12f32);
}
*/
// construct a schedule (you should do this on init)
let mut schedule = Schedule::builder()
// .add_system(Renderer::render_test)
.build();
// run our schedule (you should do this each update)
//schedule.execute(&mut world, &mut resources);
// Querying entities by component is just defining the component type!
let mut query = Read::<Position>::query();
// you can then iterate through the components found in the world
for position in query.iter(&world) {
println!("{:?}", position);
}
let event_loop = EventLoop::new(); let event_loop = EventLoop::new();
let mut builder = winit::window::WindowBuilder::new(); let mut builder = winit::window::WindowBuilder::new();
@ -119,15 +218,14 @@ async fn main() {
let surface = instance.create_surface(&window); let surface = instance.create_surface(&window);
(size, surface) (size, surface)
}; };
let adapter = async { let adapter =
instance instance
.request_adapter(&wgpu::RequestAdapterOptions { .request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance, power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: Some(&surface), compatible_surface: Some(&surface),
}) });
.await
.unwrap(); let adapter = futures::executor::block_on(adapter).unwrap();
};
let optional_features = Renderer::optional_features(); let optional_features = Renderer::optional_features();
let required_features = Renderer::required_features(); let required_features = Renderer::required_features();
@ -144,7 +242,7 @@ async fn main() {
let trace_dir = std::env::var("WGPU_TRACE"); let trace_dir = std::env::var("WGPU_TRACE");
// And then get the device we want // And then get the device we want
let (device, queue) = adapter let device = adapter
.request_device( .request_device(
&wgpu::DeviceDescriptor { &wgpu::DeviceDescriptor {
features: (optional_features & adapter_features) | required_features, features: (optional_features & adapter_features) | required_features,
@ -152,10 +250,11 @@ async fn main() {
shader_validation: true, shader_validation: true,
}, },
trace_dir.ok().as_ref().map(std::path::Path::new), trace_dir.ok().as_ref().map(std::path::Path::new),
) );
.unwrap();
let (device, queue) = futures::executor::block_on(device).unwrap();
let device = Rc::new(device);
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
let (mut pool, spawner) = { let (mut pool, spawner) = {
let local_pool = futures::executor::LocalPool::new(); let local_pool = futures::executor::LocalPool::new();
@ -219,9 +318,9 @@ async fn main() {
log::info!("Entering render loop..."); log::info!("Entering render loop...");
// Load up the renderer (and the resources) // Load up the renderer (and the resources)
let mut renderer = render::Renderer::init(&device, &sc_desc); let mut renderer = render::Renderer::init(device.clone(), &sc_desc);
let (plane_vertex_buffer, plane_index_buffer) = Renderer::load_mesh_to_buffer(device, "plane.obj"); let (plane_vertex_buffer, plane_index_buffer) = Renderer::load_mesh_to_buffer(device.clone(), "plane.obj");
// Init, this wants the references to the buffers... // Init, this wants the references to the buffers...
let mut runtime = runtime::Runtime::init(&sc_desc, &device, &queue); let mut runtime = runtime::Runtime::init(&sc_desc, &device, &queue);
@ -291,7 +390,7 @@ async fn main() {
*control_flow = ControlFlow::Exit; *control_flow = ControlFlow::Exit;
} }
_ => { _ => {
renderer.update(event); //renderer.update(event);
} }
}, },
event::Event::RedrawRequested(_) => { event::Event::RedrawRequested(_) => {

@ -2,11 +2,13 @@ use bytemuck::{Pod, Zeroable};
use bytemuck::__core::mem; use bytemuck::__core::mem;
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use std::{iter, num::NonZeroU32, ops::Range, rc::Rc}; use std::{iter, num::NonZeroU32, ops::Range, rc::Rc};
use crate::OPENGL_TO_WGPU_MATRIX; use crate::{OPENGL_TO_WGPU_MATRIX, Velocity};
use crate::light::LightRaw; use crate::light::LightRaw;
use crate::geometry::{Vertex, import_mesh, create_plane}; use crate::geometry::{Vertex, import_mesh, create_plane};
use wgpu::Buffer; use wgpu::{Buffer, Device};
use winit::dpi::Position;
use winit::platform::unix::x11::ffi::Time;
use legion::*;
#[repr(C)] #[repr(C)]
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -42,16 +44,16 @@ pub struct Pass {
} }
pub struct Renderer { pub struct Renderer {
device: Device, device: Rc<Device>,
lights_are_dirty: bool, lights_are_dirty: bool,
shadow_pass: Pass, shadow_pass: Pass,
forward_pass: Pass, forward_pass: Pass,
forward_depth: wgpu::TextureView, forward_depth: wgpu::TextureView,
light_uniform_buf: wgpu::Buffer, light_uniform_buf: wgpu::Buffer,
plane_uniform_buf: wgpu::Buffer, // plane_uniform_buf: wgpu::Buffer,
plane_vertex_buf: wgpu::Buffer, // plane_vertex_buf: wgpu::Buffer,
plane_index_buf: wgpu::Buffer, // plane_index_buf: wgpu::Buffer,
} }
impl Renderer { impl Renderer {
@ -122,13 +124,13 @@ impl Renderer {
} }
pub fn load_mesh_to_buffer(device: &wgpu::Device, filepath: &str) -> (Rc<Buffer>, Rc<Buffer>) { pub fn load_mesh_to_buffer(device: Rc<wgpu::Device>, filepath: &str) -> (Rc<Buffer>, Rc<Buffer>) {
let (vertices, indices) = import_mesh(filepath); let (vertices, indices) = import_mesh(filepath);
Renderer::create_buffer(device, indices, vertices) Renderer::create_buffer(&device, indices, vertices)
} }
pub fn init(device: &wgpu::Device, sc_desc: &wgpu::SwapChainDescriptor) -> Renderer { pub fn init(device: Rc<wgpu::Device>, sc_desc: &wgpu::SwapChainDescriptor) -> Renderer {
let entity_uniform_size = mem::size_of::<EntityUniforms>() as wgpu::BufferAddress; let entity_uniform_size = mem::size_of::<EntityUniforms>() as wgpu::BufferAddress;
@ -469,17 +471,23 @@ impl Renderer {
}); });
Renderer { Renderer {
device, device: device,
lights_are_dirty: false, lights_are_dirty: false,
shadow_pass, shadow_pass,
forward_pass, forward_pass,
forward_depth: depth_texture.create_view(&wgpu::TextureViewDescriptor::default()), forward_depth: depth_texture.create_view(&wgpu::TextureViewDescriptor::default()),
light_uniform_buf, light_uniform_buf,
plane_uniform_buf, // plane_uniform_buf,
plane_vertex_buf: (), // plane_vertex_buf: (),
plane_index_buf: () // plane_index_buf: ()
} }
} }
//
// #[system(for_each)]
// pub fn render_test(pos: &mut Position, vel: &Velocity) {
// //pos.x += vel.dx * time.elapsed_seconds;
// //pos.y += vel.dy * time.elapsed_seconds;
// }
pub fn render( pub fn render(
&mut self, &mut self,
@ -490,39 +498,42 @@ impl Renderer {
) )
{ {
// update uniforms // update uniforms
for entity in self.entities.iter_mut() { // for entity in self.entities.iter_mut() {
if entity.rotation_speed != 0.0 { //
let rotation = cgmath::Matrix4::from_angle_x(cgmath::Deg(entity.rotation_speed)); // // Revolve the entity by the rotation speed, only if it is non-zero
entity.mx_world = entity.mx_world * rotation; // if entity.rotation_speed != 0.0 {
} // let rotation = cgmath::Matrix4::from_angle_x(cgmath::Deg(entity.rotation_speed));
let data = EntityUniforms { // entity.mx_world = entity.mx_world * rotation;
model: entity.mx_world.into(), // }
color: [ //
entity.color.r as f32, // let data = EntityUniforms {
entity.color.g as f32, // model: entity.mx_world.into(),
entity.color.b as f32, // color: [
entity.color.a as f32, // entity.color.r as f32,
], // entity.color.g as f32,
}; // entity.color.b as f32,
queue.write_buffer(&entity.uniform_buf, 0, bytemuck::bytes_of(&data)); // entity.color.a as f32,
} // ],
// };
if self.lights_are_dirty { // queue.write_buffer(&entity.uniform_buf, 0, bytemuck::bytes_of(&data));
self.lights_are_dirty = false; // }
for (i, light) in self.lights.iter().enumerate() {
queue.write_buffer( // if self.lights_are_dirty {
&self.light_uniform_buf, // self.lights_are_dirty = false;
(i * mem::size_of::<LightRaw>()) as wgpu::BufferAddress, // for (i, light) in self.lights.iter().enumerate() {
bytemuck::bytes_of(&light.to_raw()), // queue.write_buffer(
); // &self.light_uniform_buf,
} // (i * mem::size_of::<LightRaw>()) as wgpu::BufferAddress,
} // bytemuck::bytes_of(&light.to_raw()),
// );
// }
// }
let mut encoder = let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
encoder.push_debug_group("shadow passes"); encoder.push_debug_group("shadow passes");
for (i, light) in self.lights.iter().enumerate() { /*for (i, light) in self.lights.iter().enumerate() {
encoder.push_debug_group(&format!( encoder.push_debug_group(&format!(
"shadow pass {} (light at position {:?})", "shadow pass {} (light at position {:?})",
i, light.pos i, light.pos
@ -565,7 +576,7 @@ impl Renderer {
} }
encoder.pop_debug_group(); encoder.pop_debug_group();
} }*/
encoder.pop_debug_group(); encoder.pop_debug_group();
// forward pass // forward pass
@ -597,18 +608,22 @@ impl Renderer {
pass.set_pipeline(&self.forward_pass.pipeline); pass.set_pipeline(&self.forward_pass.pipeline);
pass.set_bind_group(0, &self.forward_pass.bind_group, &[]); pass.set_bind_group(0, &self.forward_pass.bind_group, &[]);
for entity in &self.entities { // for entity in &self.entities {
pass.set_bind_group(1, &entity.bind_group, &[]); // pass.set_bind_group(1, &entity.bind_group, &[]);
pass.set_index_buffer(entity.index_buf.slice(..)); // pass.set_index_buffer(entity.index_buf.slice(..));
pass.set_vertex_buffer(0, entity.vertex_buf.slice(..)); // pass.set_vertex_buffer(0, entity.vertex_buf.slice(..));
pass.draw_indexed(0..entity.index_count as u32, 0, 0..1); // pass.draw_indexed(0..entity.index_count as u32, 0, 0..1);
} // }
} }
encoder.pop_debug_group(); encoder.pop_debug_group();
queue.submit(iter::once(encoder.finish())); queue.submit(iter::once(encoder.finish()));
} }
pub(crate) fn required_features() -> wgpu::Features {
wgpu::Features::empty()
}
pub fn optional_features() -> wgpu::Features { pub fn optional_features() -> wgpu::Features {
wgpu::Features::DEPTH_CLAMPING wgpu::Features::DEPTH_CLAMPING
} }

@ -20,13 +20,15 @@ struct Entity {
mx_world: cgmath::Matrix4<f32>, mx_world: cgmath::Matrix4<f32>,
rotation_speed: f32, rotation_speed: f32,
color: wgpu::Color, color: wgpu::Color,
vertex_buf: Rc<wgpu::Buffer>,
// Could probably tie this along with index & count to some resource handle in the renderer
index_buf: Rc<wgpu::Buffer>,
index_count: usize, index_count: usize,
bind_group: wgpu::BindGroup, bind_group: wgpu::BindGroup,
// This is a little weird to have in the entity isn't it? // This is a little weird to have in the entity isn't it?
// uniform buf is tough...
uniform_buf: wgpu::Buffer, uniform_buf: wgpu::Buffer,
vertex_buf: Rc<wgpu::Buffer>,
index_buf: Rc<wgpu::Buffer>,
} }
pub struct Runtime { pub struct Runtime {
@ -68,6 +70,7 @@ impl Runtime {
label: None, label: None,
}); });
*/ */
// Defines the Uniform buffer for the Vertex and Fragment shaders // Defines the Uniform buffer for the Vertex and Fragment shaders
let local_bind_group_layout = let local_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
@ -88,23 +91,23 @@ impl Runtime {
let mut entities = Vec::default(); let mut entities = Vec::default();
entities.push(Entity { // entities.push(Entity {
mx_world: cgmath::Matrix4::identity(), // mx_world: cgmath::Matrix4::identity(),
rotation_speed: 0.0, // rotation_speed: 0.0,
color: wgpu::Color::WHITE, // color: wgpu::Color::WHITE,
vertex_buf: Rc::new(plane_vertex_buf), // vertex_buf: Rc::new(plane_vertex_buf),
index_buf: Rc::new(plane_index_buf), // index_buf: Rc::new(plane_index_buf),
index_count: plane_index_data.len(), // index_count: plane_index_data.len(),
bind_group: device.create_bind_group(&wgpu::BindGroupDescriptor { // bind_group: device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &local_bind_group_layout, // layout: &local_bind_group_layout,
entries: &[wgpu::BindGroupEntry { // entries: &[wgpu::BindGroupEntry {
binding: 0, // binding: 0,
resource: wgpu::BindingResource::Buffer(plane_uniform_buf.slice(..)), // resource: wgpu::BindingResource::Buffer(plane_uniform_buf.slice(..)),
}], // }],
label: None, // label: None,
}), // }),
uniform_buf: plane_uniform_buf, // uniform_buf: plane_uniform_buf,
}); // });
struct CubeDesc { struct CubeDesc {
@ -124,36 +127,36 @@ impl Runtime {
]; ];
for cube in &cube_descs { // for cube in &cube_descs {
let transform = Decomposed { // let transform = Decomposed {
disp: cube.offset.clone(), // disp: cube.offset.clone(),
rot: Quaternion::from_axis_angle(cube.offset.normalize(), Deg(cube.angle)), // rot: Quaternion::from_axis_angle(cube.offset.normalize(), Deg(cube.angle)),
scale: cube.scale, // scale: cube.scale,
}; // };
let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor { // let uniform_buf = device.create_buffer(&wgpu::BufferDescriptor {
label: None, // label: None,
size: entity_uniform_size, // size: entity_uniform_size,
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, // usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST,
mapped_at_creation: false, // mapped_at_creation: false,
}); // });
entities.push(Entity { // entities.push(Entity {
mx_world: cgmath::Matrix4::from(transform), // mx_world: cgmath::Matrix4::from(transform),
rotation_speed: cube.rotation, // rotation_speed: cube.rotation,
color: wgpu::Color::GREEN, // color: wgpu::Color::GREEN,
vertex_buf: Rc::clone(&cube_vertex_buf), // vertex_buf: Rc::clone(&cube_vertex_buf),
index_buf: Rc::clone(&cube_index_buf), // index_buf: Rc::clone(&cube_index_buf),
index_count: cube_index_data.len(), // index_count: cube_index_data.len(),
bind_group: device.create_bind_group(&wgpu::BindGroupDescriptor { // bind_group: device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: &local_bind_group_layout, // layout: &local_bind_group_layout,
entries: &[wgpu::BindGroupEntry { // entries: &[wgpu::BindGroupEntry {
binding: 0, // binding: 0,
resource: wgpu::BindingResource::Buffer(uniform_buf.slice(..)), // resource: wgpu::BindingResource::Buffer(uniform_buf.slice(..)),
}], // }],
label: None, // label: None,
}), // }),
uniform_buf, // uniform_buf,
}); // });
} //}
// Create other resources // Create other resources

@ -0,0 +1 @@
<mxfile host="Electron" modified="2021-02-01T07:58:02.975Z" agent="5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/14.1.8 Chrome/87.0.4280.88 Electron/11.1.1 Safari/537.36" etag="2NJNGVERa2KLkA7RE-2K" version="14.1.8" type="device"><diagram id="LqEz0sV-94yUqItN7aiY" name="Page-1">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</diagram></mxfile>
Loading…
Cancel
Save