Pool/src/render/mod.rs

1280 lines
47 KiB
Rust

pub mod eye;
use crate::render::eye::Eye;
use crate::world::{Shape, SimpleColliderData, SimpleLight, SimpleObject, SimpleObjectData};
use bytemuck::{Pod, Zeroable};
use glam::prelude::*;
use gltf::Semantic;
use image::EncodableLayout;
use std::cell::RefCell;
use std::collections::HashMap;
use std::error::Error;
use std::fmt::{Debug, Display, Formatter};
use std::hash::{BuildHasherDefault, Hash, Hasher};
use std::ops::Range;
use std::rc::Rc;
use std::sync::Arc;
use wgpu::naga::{FastHashMap, FastHashSet};
use wgpu::util::DeviceExt;
use wgpu::{Device, Queue};
use winit::window::Window;
const DEFAULT_VERTICES: [SimpleVertex; 0] = [];
#[repr(C)]
#[derive(Pod, Zeroable, Copy, Clone)]
pub struct SimpleModelInstance {
pub transform: Mat4,
pub color: Vec4,
pub lights: [u16; 16],
pub point_lights: u32,
pub spot_lights: u32,
pub metal: f32,
pub rough: f32,
}
#[repr(C)]
#[derive(Pod, Copy, Clone, Zeroable)]
pub struct SimpleLightInstance {
pub location: Vec4,
pub rotation: Vec4,
pub color: Vec4,
}
#[derive(Clone)]
pub struct SimpleLightData {
pub index: usize,
pub instance: SimpleLightInstance,
pub transform: Affine3,
}
#[derive(Clone)]
pub struct SimpleModelData {
pub instance: SimpleModelInstance,
pub material: Id<SimpleMaterial>,
pub mesh: Id<SimpleMesh>,
}
pub struct SimpleInstances {
light_count: usize,
light_buffer: wgpu::Buffer,
instance_count: usize,
instance_buffer: wgpu::Buffer,
//light_ref_last: usize,
//light_ref_buffer: wgpu::Buffer,
objects:
FastHashMap<Id<SimpleMesh>, FastHashMap<Id<SimpleMaterial>, FastHashSet<SimpleObject>>>,
lights: FastHashMap<SimpleLight, usize>,
}
enum SimpleRenderCode {
Material(wgpu::BindGroup),
Mesh((wgpu::Buffer, u32), Option<(wgpu::Buffer, u32)>),
Draw(Range<u32>),
}
struct SimpleRenderProgram(Vec<SimpleRenderCode>);
impl SimpleRenderProgram {
fn push(&mut self, item: SimpleRenderCode) {
self.0.push(item)
}
fn new() -> SimpleRenderProgram {
SimpleRenderProgram(Vec::new())
}
fn render(self, pass: &mut wgpu::RenderPass) {
let mut count = 0;
let mut indexed = false;
for code in self.0 {
match code {
SimpleRenderCode::Material(material) => pass.set_bind_group(
Renderer::SIMPLE_RENDER_TEXTURE_GROUP_POSITION,
&material,
&[],
),
SimpleRenderCode::Mesh(vertices, indices) => {
pass.set_vertex_buffer(0, vertices.0.slice(..));
if let Some(indices) = indices {
pass.set_index_buffer(indices.0.slice(..), wgpu::IndexFormat::Uint32);
count = indices.1;
indexed = true;
} else {
count = vertices.1;
indexed = false;
}
}
SimpleRenderCode::Draw(instances) => {
if indexed {
pass.draw_indexed(0..count, 0, instances)
} else {
pass.draw(0..count, instances);
}
}
}
}
}
}
impl SimpleInstances {
const MIN_SIZE: u64 = 64;
pub fn add_object(&mut self, object: SimpleObject) {
if let Some(ref model) = object.0.borrow().model {
self.instance_count += 1;
self.objects
.entry(model.mesh.clone())
.or_insert(FastHashMap::with_hasher(BuildHasherDefault::new()))
.entry(model.material.clone())
.or_insert(FastHashSet::with_hasher(BuildHasherDefault::new()))
.insert(object.clone());
}
}
pub fn remove_object(&mut self, object: SimpleObject) {
if let Some(ref model) = object.0.borrow().model {
self.instance_count -= 1;
self.objects
.entry(model.mesh.clone())
.or_insert(FastHashMap::with_hasher(BuildHasherDefault::new()))
.entry(model.material.clone())
.or_insert(FastHashSet::with_hasher(BuildHasherDefault::new()))
.remove(&object);
}
}
pub fn add_light(&mut self, light: SimpleLight) {
self.light_count += 1;
self.lights.insert(light, 0);
}
pub fn remove_light(&mut self, light: &SimpleLight) {
self.light_count -= 1;
self.lights.remove(light);
}
pub fn reallocate_buffer(
device: &wgpu::Device,
buffer: &mut wgpu::Buffer,
count: usize,
item_size: usize,
) {
let size = buffer.size() / item_size as wgpu::BufferAddress;
if count > SimpleInstances::MIN_SIZE as usize {
let mut reallocate: Option<usize> = None;
if count < (size / 2) as usize {
reallocate = Some(count / 2);
} else if count > size as usize {
reallocate = Some(count * 2);
}
if let Some(new_size) = reallocate {
*buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Instance Buffer"),
size: (item_size * new_size) as wgpu::BufferAddress,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::VERTEX,
mapped_at_creation: false,
});
}
}
}
pub fn write_lights(&mut self, device: &wgpu::Device, queue: &wgpu::Queue) {
//SimpleInstances::reallocate_buffer(device, queue, &mut self.light_ref_buffer, self.light_ref_last, size_of::<u32>());
/*
let mut light_ref_buffer = queue.write_buffer_with(
&self.light_ref_buffer,
0 as wgpu::BufferAddress,
wgpu::BufferSize::new(self.instance_buffer.size()).unwrap()
).unwrap();
*/
SimpleInstances::reallocate_buffer(
device,
&mut self.light_buffer,
self.light_count,
size_of::<SimpleLight>(),
);
let mut buffer = queue
.write_buffer_with(
&self.light_buffer,
0 as wgpu::BufferAddress,
wgpu::BufferSize::new(self.light_buffer.size()).unwrap(),
)
.unwrap();
let stride = size_of::<SimpleLight>();
for (new_index, (light, index)) in self.lights.iter_mut().enumerate() {
*index = new_index + 1;
let begin = *index * stride;
buffer
.slice(begin..begin + stride)
.copy_from_slice(bytemuck::cast_slice(&[light.0.borrow().instance]));
}
}
fn write_instances(
&mut self,
device: &wgpu::Device,
queue: &wgpu::Queue,
) -> SimpleRenderProgram {
let mut program = SimpleRenderProgram::new();
SimpleInstances::reallocate_buffer(
device,
&mut self.instance_buffer,
self.instance_count,
size_of::<SimpleModelInstance>(),
);
let mut buffer = queue
.write_buffer_with(
&self.instance_buffer,
0 as wgpu::BufferAddress,
wgpu::BufferSize::new(self.instance_buffer.size()).unwrap(),
)
.unwrap();
let mut index: u32 = 0;
let stride = size_of::<SimpleModelInstance>();
for (mesh, materials) in self.objects.iter() {
program.push(SimpleRenderCode::Mesh(
mesh.it.vertices.clone(),
mesh.it.indices.clone(),
));
for (material, objects) in materials.iter() {
program.push(SimpleRenderCode::Material(material.it.group.clone()));
let before = index;
for object in objects.iter() {
let begin = index as usize * stride;
buffer
.slice(begin..begin + stride)
.copy_from_slice(bytemuck::cast_slice(&[object
.0
.borrow()
.model
.as_ref()
.unwrap()
.instance]));
index += 1;
}
program.push(SimpleRenderCode::Draw(Range::from(before..index)));
}
}
program
}
fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: size_of::<SimpleModelInstance>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &[
// todo: is this too big?
wgpu::VertexAttribute {
offset: 0,
shader_location: 4,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: size_of::<[f32; 4]>() as wgpu::BufferAddress,
shader_location: 5,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: size_of::<[f32; 8]>() as wgpu::BufferAddress,
shader_location: 6,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: size_of::<[f32; 12]>() as wgpu::BufferAddress,
shader_location: 7,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: size_of::<[f32; 16]>() as wgpu::BufferAddress,
shader_location: 8,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: size_of::<[f32; 16]>() as wgpu::BufferAddress,
shader_location: 9,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: size_of::<[f32; 16]>() as wgpu::BufferAddress,
shader_location: 10,
format: wgpu::VertexFormat::Float32x4,
},
wgpu::VertexAttribute {
offset: size_of::<[f32; 16]>() as wgpu::BufferAddress,
shader_location: 11,
format: wgpu::VertexFormat::Float32x4,
},
],
}
}
pub fn new(device: &wgpu::Device) -> SimpleInstances {
let instance_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Instance Buffer"),
size: (size_of::<SimpleModelInstance>() * SimpleInstances::MIN_SIZE as usize)
as wgpu::BufferAddress,
usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let light_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Light Buffer"),
size: (size_of::<SimpleLightInstance>() * SimpleInstances::MIN_SIZE as usize)
as wgpu::BufferAddress,
usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
/*let light_ref_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Light Ref Buffer"),
size: (size_of::<u32>() * SimpleInstances::MIN_SIZE as usize) as wgpu::BufferAddress,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});*/
SimpleInstances {
light_count: 0,
//light_ref_last: 0,
instance_count: 0,
light_buffer,
instance_buffer,
//light_ref_buffer,
objects: FastHashMap::with_hasher(BuildHasherDefault::new()),
lights: Default::default(),
}
}
}
pub struct Renderer {
id_count: u64,
surface: wgpu::Surface<'static>,
config: wgpu::SurfaceConfiguration,
device: wgpu::Device,
queue: wgpu::Queue,
pub(crate) eye: eye::Eye,
material_layout: wgpu::BindGroupLayout,
default_texture: SimpleTexture,
default_material: SimpleMaterial,
sky_pipeline: wgpu::RenderPipeline,
instance_pipeline: wgpu::RenderPipeline,
depth_texture: SimpleTexture,
pub(crate) instances: SimpleInstances,
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
struct SimpleVertex {
position: [f32; 3],
normal: [f32; 3],
tex_coord: [f32; 2],
tangent: [f32; 3],
bitangent: [f32; 3],
}
impl SimpleVertex {
fn desc() -> wgpu::VertexBufferLayout<'static> {
wgpu::VertexBufferLayout {
array_stride: size_of::<SimpleVertex>() as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
offset: 0,
shader_location: 0,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: size_of::<[f32; 3]>() as wgpu::BufferAddress,
shader_location: 1,
format: wgpu::VertexFormat::Float32x3,
},
wgpu::VertexAttribute {
offset: size_of::<[f32; 6]>() as wgpu::BufferAddress,
shader_location: 2,
format: wgpu::VertexFormat::Float32x2,
},
],
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct SimpleMesh {
indices: Option<(wgpu::Buffer, u32)>,
vertices: (wgpu::Buffer, u32),
}
#[derive(Debug, Clone, Hash, Eq, PartialEq)]
pub struct SimpleTexture {
texture: wgpu::Texture,
view: wgpu::TextureView,
}
pub struct TextureProperties {
width: u32,
height: u32,
}
// todo: use texture compression!
impl SimpleTexture {
pub fn load(
device: &Device,
queue: &Queue,
slice: impl AsRef<[u8]>,
properties: TextureProperties,
) -> SimpleTexture {
let size = wgpu::Extent3d {
width: properties.width,
height: properties.height,
depth_or_array_layers: 1,
};
let diffuse_texture = device.create_texture(&wgpu::TextureDescriptor {
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8UnormSrgb,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
label: Some("texture"),
view_formats: &[],
});
let diffuse_texture_view =
diffuse_texture.create_view(&wgpu::TextureViewDescriptor::default());
queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &diffuse_texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
slice.as_ref(),
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * size.width),
rows_per_image: Some(size.height),
},
size,
);
SimpleTexture {
texture: diffuse_texture,
view: diffuse_texture_view,
}
}
}
#[derive(Clone)]
pub struct SimpleMaterial {
group: wgpu::BindGroup,
}
#[derive(Clone)]
pub struct Id<T> {
id: u64,
it: T,
}
impl<T> Hash for Id<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state)
}
}
impl<T> PartialEq for Id<T> {
fn eq(&self, other: &Self) -> bool {
other.id == self.id
}
}
impl<T> Eq for Id<T> {}
struct MaterialProperties {
edge: wgpu::AddressMode,
filter: wgpu::FilterMode,
}
impl Default for MaterialProperties {
fn default() -> MaterialProperties {
MaterialProperties {
edge: wgpu::AddressMode::Repeat,
filter: wgpu::FilterMode::Linear,
}
}
}
impl MaterialProperties {
fn sampler(&self, device: &wgpu::Device) -> wgpu::Sampler {
device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: self.edge,
address_mode_v: self.edge,
address_mode_w: self.edge,
mag_filter: self.filter,
min_filter: self.filter,
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
..Default::default()
})
}
}
impl SimpleMaterial {
fn new(
device: &wgpu::Device,
layout: &wgpu::BindGroupLayout,
base: &SimpleTexture,
normal: &SimpleTexture,
reflect: &SimpleTexture,
config: MaterialProperties,
) -> SimpleMaterial {
let sampler = config.sampler(&device);
let group = device.create_bind_group(&wgpu::BindGroupDescriptor {
layout: layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Sampler(&sampler),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&base.view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::TextureView(&normal.view),
},
wgpu::BindGroupEntry {
binding: 3,
resource: wgpu::BindingResource::TextureView(&reflect.view),
},
],
label: Some("diffuse_bind_group"),
});
SimpleMaterial { group }
}
}
#[derive(Debug)]
struct PoolError(String);
impl Display for PoolError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Error for PoolError {}
pub struct SimpleTreeNode {
object: Option<SimpleObject>,
children: Vec<SimpleTreeNode>,
name: String,
}
impl SimpleTreeNode {
pub fn first_object(&self) -> Option<SimpleObject> {
if let Some(object) = self.object.clone() {
Some(object)
} else {
for child in self.children.iter() {
if let Some(object) = child.first_object() {
return Some(object);
}
}
None
}
}
}
type GltfVertexBufferKey = (Option<usize>, Option<usize>, Option<usize>, Option<usize>);
impl Renderer {
const SIMPLE_RENDER_EYE_GROUP_POSITION: u32 = 0;
const SIMPLE_RENDER_TEXTURE_GROUP_POSITION: u32 = 1;
const SIMPLE_RENDER_MODEL_GROUP_POSITION: u32 = 2;
pub fn new_id<T>(&mut self, value: T) -> Id<T> {
self.id_count += 1;
Id {
it: value,
id: self.id_count,
}
}
pub fn load_texture_from_bytes(
&mut self,
slice: impl AsRef<[u8]>,
format: Option<image::ImageFormat>,
) -> SimpleTexture {
let image = if let Some(format) = format {
image::load_from_memory_with_format(slice.as_ref(), format)
} else {
image::load_from_memory(slice.as_ref())
};
if let Ok(data) = image {
let data = data.into_rgba8();
SimpleTexture::load(
&self.device,
&self.queue,
data.as_bytes(),
TextureProperties {
width: data.width(),
height: data.height(),
},
)
} else {
println!("failed to load texture! error: {:?}", image.unwrap_err());
self.default_texture.clone()
}
}
pub fn new_material(
&mut self,
base: &SimpleTexture,
normal: &SimpleTexture,
reflect: &SimpleTexture,
) -> Id<SimpleMaterial> {
self.new_id(SimpleMaterial::new(
&self.device,
&self.material_layout,
base,
normal,
reflect,
MaterialProperties {
edge: wgpu::AddressMode::Repeat,
filter: wgpu::FilterMode::Linear,
},
))
}
pub fn new_texture_from_gltf(
&mut self,
info: &gltf::texture::Texture,
images: &Vec<gltf::image::Data>,
) -> SimpleTexture {
if let Some(image) = images.get(info.source().index()) {
let mut new_pixels = Vec::new();
let pixels: &Vec<u8>;
match image.format {
gltf::image::Format::R8G8B8 => {
for pixel in image.pixels.chunks(3) {
new_pixels.push(pixel[0]);
new_pixels.push(pixel[0]);
new_pixels.push(pixel[0]);
new_pixels.push(255);
}
pixels = &new_pixels;
}
gltf::image::Format::R8G8B8A8 => pixels = &image.pixels,
_ => return self.default_texture.clone(),
}
SimpleTexture::load(
&self.device,
&self.queue,
pixels.as_slice(),
TextureProperties {
width: image.width,
height: image.height,
},
)
} else {
self.default_texture.clone()
}
}
pub fn new_mesh_from_gltf(
&mut self,
primitive: gltf::Primitive,
meshes: &mut HashMap<GltfVertexBufferKey, SimpleMesh>,
buffers: &[gltf::buffer::Data],
) -> Id<SimpleMesh> {
let position_index = primitive
.get(&Semantic::Positions)
.and_then(|it| it.view())
.and_then(|it| Some(it.buffer().index()));
let normals_index = primitive
.get(&Semantic::Normals)
.and_then(|it| it.view())
.and_then(|it| Some(it.buffer().index()));
let tex_coords_index = primitive
.get(&Semantic::TexCoords(0))
.and_then(|it| it.view())
.and_then(|it| Some(it.buffer().index()));
let indices_index = primitive
.indices()
.and_then(|it| it.view())
.and_then(|it| Some(it.buffer().index()));
let tangent_index = primitive
.get(&Semantic::Tangents)
.and_then(|it| it.view())
.and_then(|it| Some(it.buffer().index()));
let key = (
position_index,
normals_index,
tex_coords_index,
indices_index,
);
self.new_id(
meshes
.entry(key)
.or_insert_with(|| {
let mut tangents = false;
let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()]));
let mut vertex_data: Vec<SimpleVertex>;
if let Some(positions) = reader.read_positions() {
vertex_data = Vec::with_capacity(positions.len());
let mut normal = reader.read_normals().map(|it| it.into_iter());
let mut tex_coord = reader
.read_tex_coords(0)
.map(|it| it.into_f32().into_iter());
let mut tangent = reader.read_tangents().map(|it| it.into_iter());
tangents = tangent.is_some();
for position in positions {
vertex_data.push(SimpleVertex {
position,
normal: if let Some(ref mut normals) = normal {
if let Some(normal) = normals.next() {
normal
} else {
[0.0; 3]
}
} else {
[0.0; 3]
},
tex_coord: if let Some(ref mut tex_coords) = tex_coord {
tex_coords.next()
} else {
None
}
.unwrap_or([0.0; 2]),
tangent: [0.0; 3], // todo: tangent from model
bitangent: [0.0; 3],
})
}
} else {
vertex_data = Vec::new();
}
let vertex_buffer =
self.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(vertex_data.as_slice()),
usage: wgpu::BufferUsages::VERTEX,
});
let vertices_result = (vertex_buffer, vertex_data.len() as u32);
let indices_result = if let Some(indices) = reader.read_indices() {
let index_data: Vec<u32> = indices.into_u32().collect();
Some((
self.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Index Buffer"),
contents: bytemuck::cast_slice(index_data.as_slice()),
usage: wgpu::BufferUsages::INDEX,
}),
index_data.len() as u32,
))
} else {
None
};
SimpleMesh {
indices: indices_result,
vertices: vertices_result,
}
})
.clone(),
)
}
pub fn load_node_from_gltf(
&mut self,
node: gltf::Node,
meshes: &mut HashMap<GltfVertexBufferKey, SimpleMesh>,
textures: &mut HashMap<usize, SimpleTexture>,
images: &Vec<gltf::image::Data>,
buffers: &Vec<gltf::buffer::Data>,
) -> SimpleTreeNode {
let mut tree_node = SimpleTreeNode {
object: None,
children: Vec::new(),
name: node.name().unwrap_or("Node").to_string(),
};
if let Some(mesh) = node.mesh() {
let len = mesh.primitives().len();
for primitive in mesh.primitives() {
let pbr = primitive.material().pbr_metallic_roughness();
let material = primitive.material();
let color = pbr.base_color_factor();
let metal = pbr.metallic_factor();
let rough = pbr.roughness_factor();
let light = primitive.material().emissive_factor();
let base = match pbr.base_color_texture() {
Some(info) => textures
.entry(info.texture().source().index())
.or_insert_with(|| self.new_texture_from_gltf(&info.texture(), &images))
.clone(),
None => self.default_texture.clone(),
};
let reflect = match pbr.metallic_roughness_texture() {
Some(info) => textures
.entry(info.texture().source().index())
.or_insert_with(|| self.new_texture_from_gltf(&info.texture(), &images))
.clone(),
None => self.default_texture.clone(),
};
let normal = match material.normal_texture() {
Some(info) => textures
.entry(info.texture().source().index())
.or_insert_with(|| self.new_texture_from_gltf(&info.texture(), &images))
.clone(),
None => self.default_texture.clone(),
};
let material = self.new_material(&base, &normal, &reflect);
let mesh = self.new_mesh_from_gltf(primitive, meshes, buffers);
let object = SimpleObject(Rc::new(RefCell::new(SimpleObjectData {
model: Some(SimpleModelData {
instance: SimpleModelInstance {
transform: Mat4::default(),
color: Vec4::from_array(color),
lights: [0; 16],
point_lights: 0,
spot_lights: 0,
metal,
rough,
},
material,
mesh,
}),
collider: SimpleColliderData {
transform: Mat4::default(),
shape: Shape::None,
radius: 0.0,
},
})));
if len == 1 {
tree_node.object = Some(object);
} else {
tree_node.children.push(SimpleTreeNode {
object: Some(object),
children: Vec::new(),
name: "Primitive".to_string(),
})
}
}
}
for node in node.children() {
tree_node
.children
.push(self.load_node_from_gltf(node, meshes, textures, images, buffers));
}
tree_node
}
pub fn load_from_gltf(&mut self, slice: impl AsRef<[u8]>) -> SimpleTreeNode {
let mut root = SimpleTreeNode {
object: None,
children: Vec::new(),
name: "Root".to_string(),
};
if let Ok((document, buffers, images)) = gltf::import_slice(slice) {
let mut meshes: HashMap<GltfVertexBufferKey, SimpleMesh> = HashMap::new();
let mut textures: HashMap<usize, SimpleTexture> = HashMap::new();
for scene in document.scenes() {
let mut scene_node = SimpleTreeNode {
object: None,
children: Vec::new(),
name: scene.name().unwrap_or("Scene").to_string(),
};
for node in scene.nodes() {
scene_node.children.push(self.load_node_from_gltf(
node,
&mut meshes,
&mut textures,
&images,
&buffers,
));
}
root.children.push(scene_node)
}
}
root
}
pub fn resize(&mut self, width: u32, height: u32) {
self.surface.configure(&self.device, &self.config);
self.eye.resize(width, height);
}
pub fn set_skybox(&mut self, skybox: SimpleTexture) {
self.eye.skybox(&self.device, skybox);
}
pub async fn new(window: &Arc<Window>) -> anyhow::Result<Self> {
static SKIP_EXTERNAL_OVERLAYS : bool = true; // use a config file or something to allow this to be toggled
let bounds = window.inner_size();
let width = bounds.width;
let height = bounds.height;
if(SKIP_EXTERNAL_OVERLAYS) {
unsafe { // currently will crash on most Windows systems because wGPU in its infinite
// wisdom tries to attach overlays from closed or non-existent programs
std::env::set_var("VK_LOADER_LAYERS_DISABLE", "~implicit~");
}
}
// The instance is a handle to our GPU
// BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
#[cfg(not(target_arch = "wasm32"))]
backends: wgpu::Backends::PRIMARY,
#[cfg(target_arch = "wasm32")]
backends: wgpu::Backends::GL,
flags: Default::default(),
memory_budget_thresholds: Default::default(),
backend_options: Default::default(),
display: None,
});
let surface = instance.create_surface(window.clone())?;
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: Some(&surface),
force_fallback_adapter: false,
})
.await?;
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
label: None,
required_features: wgpu::Features::empty(),
experimental_features: wgpu::ExperimentalFeatures::disabled(),
// WebGL doesn't support all of wgpu's features, so if
// we're building for the web we'll have to disable some.
required_limits: if cfg!(target_arch = "wasm32") {
wgpu::Limits::downlevel_webgl2_defaults()
} else {
wgpu::Limits::default()
},
memory_hints: Default::default(),
trace: wgpu::Trace::Off,
})
.await?;
let surface_caps = surface.get_capabilities(&adapter);
// Shader code in this tutorial assumes an sRGB surface texture. Using a different
// one will result in all the colors coming out darker. If you want to support non
// sRGB surfaces, you'll need to account for that when drawing to the frame.
let surface_format = surface_caps
.formats
.iter()
.find(|f| f.is_srgb())
.copied()
.unwrap_or(surface_caps.formats[0]);
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width,
height,
present_mode: surface_caps.present_modes[0],
alpha_mode: surface_caps.alpha_modes[0],
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
let shader =
device.create_shader_module(wgpu::include_wgsl!("../assets/SimpleShader.wgsl"));
let default_texture = SimpleTexture::load(
&device,
&queue,
[0, 0, 255, 255],
TextureProperties {
width: 1,
height: 1,
},
);
let eye = Eye::new(&device, width, height, default_texture.clone());
let texture_bind_group_layout =
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 3,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
multisampled: false,
view_dimension: wgpu::TextureViewDimension::D2,
sample_type: wgpu::TextureSampleType::Float { filterable: true },
},
count: None,
},
],
label: Some("texture_bind_group_layout"),
});
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Render Pipeline Layout"),
bind_group_layouts: &[Some(&eye.layout), Some(&texture_bind_group_layout)],
immediate_size: 0,
});
let instance_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Render Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_main"),
buffers: &[SimpleVertex::desc(), SimpleInstances::desc()],
compilation_options: wgpu::PipelineCompilationOptions::default(),
},
fragment: Some(wgpu::FragmentState {
// 3.
module: &shader,
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
// 4.
format: config.format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
compilation_options: wgpu::PipelineCompilationOptions::default(),
}),
primitive: Default::default(), /*wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: None,
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
}*/
depth_stencil: Some(wgpu::DepthStencilState {
stencil: wgpu::StencilState::default(),
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: Some(true),
depth_compare: Some(wgpu::CompareFunction::Less),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview_mask: None,
cache: None,
});
//todo: use a pipeline cache!
let sky_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: Some("Sky Pipeline"),
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: Some("vs_sky"),
compilation_options: Default::default(),
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: Some("fs_sky"),
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
// 4.
format: config.format,
blend: Some(wgpu::BlendState::REPLACE),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
front_face: wgpu::FrontFace::Cw,
..Default::default()
},
depth_stencil: Some(wgpu::DepthStencilState {
format: wgpu::TextureFormat::Depth32Float,
depth_write_enabled: Some(false),
depth_compare: Some(wgpu::CompareFunction::LessEqual),
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,
});
let size = wgpu::Extent3d {
// 2.
width: config.width.max(1),
height: config.height.max(1),
depth_or_array_layers: 1,
};
let desc = wgpu::TextureDescriptor {
label: Some("depth texture"),
size,
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Depth32Float,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT // 3.
| wgpu::TextureUsages::TEXTURE_BINDING,
view_formats: &[],
};
let depth_texture = device.create_texture(&desc);
let depth_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
let depth_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
// 4.
address_mode_u: wgpu::AddressMode::ClampToEdge,
address_mode_v: wgpu::AddressMode::ClampToEdge,
address_mode_w: wgpu::AddressMode::ClampToEdge,
mag_filter: wgpu::FilterMode::Linear,
min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::MipmapFilterMode::Nearest,
compare: Some(wgpu::CompareFunction::LessEqual), // 5.
lod_min_clamp: 0.0,
lod_max_clamp: 100.0,
..Default::default()
});
let instances = SimpleInstances::new(&device);
let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Vertex Buffer"),
contents: bytemuck::cast_slice(&DEFAULT_VERTICES),
usage: wgpu::BufferUsages::VERTEX,
});
let default_material = SimpleMaterial::new(
&device,
&texture_bind_group_layout,
&default_texture,
&default_texture,
&default_texture,
MaterialProperties::default(),
);
Ok(Renderer {
id_count: 0,
material_layout: texture_bind_group_layout,
default_texture,
default_material,
depth_texture: SimpleTexture {
texture: depth_texture,
view: depth_view,
},
instances,
sky_pipeline,
instance_pipeline,
surface,
config,
device,
queue,
eye,
})
}
pub(crate) fn render(&mut self, window: &Arc<Window>) -> anyhow::Result<()> {
window.request_redraw();
let mut resize_renderer = false;
let output = match self.surface.get_current_texture() {
wgpu::CurrentSurfaceTexture::Success(surface_texture) => surface_texture,
wgpu::CurrentSurfaceTexture::Suboptimal(surface_texture) => {
resize_renderer = true; //moved reconfigure to avoid a crash when resizing window
surface_texture
}
wgpu::CurrentSurfaceTexture::Timeout
| wgpu::CurrentSurfaceTexture::Occluded
| wgpu::CurrentSurfaceTexture::Validation => {
// Skip this frame
return Ok(());
}
wgpu::CurrentSurfaceTexture::Outdated => {
self.surface.configure(&self.device, &self.config);
return Ok(());
}
wgpu::CurrentSurfaceTexture::Lost => {
// You could recreate the devices and all resources
// created with it here, but we'll just bail
anyhow::bail!("Lost device");
}
};
let view = output
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let program = self.instances.write_instances(&self.device, &self.queue);
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Render Encoder"),
});
{
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("Render Pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &view,
resolve_target: None,
depth_slice: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color {
r: 0.5,
g: 0.6,
b: 0.8,
a: 1.0,
}),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
view: &self.depth_texture.view,
depth_ops: Some(wgpu::Operations {
load: wgpu::LoadOp::Clear(1.0),
store: wgpu::StoreOp::Store,
}),
stencil_ops: None,
}),
occlusion_query_set: None,
timestamp_writes: None,
multiview_mask: None,
});
pass.set_pipeline(&self.instance_pipeline);
pass.set_bind_group(0, &self.eye.group, &[]);
pass.set_vertex_buffer(1, self.instances.instance_buffer.slice(..));
program.render(&mut pass);
pass.set_bind_group(1, &self.default_material.group, &[]);
pass.set_pipeline(&self.sky_pipeline);
pass.draw(0..6, 0..1);
}
self.queue.submit(Some(encoder.finish()));
self.eye.write(&self.queue);
output.present();
if(resize_renderer){
std::mem::drop(view); //idk how this would affect a multi-pass system like deferred rendering, but this makes sure it's not gonna collide with resizing the swap chain or whatever wGPU does
self.surface.configure(&self.device, &self.config);
/*
also in this snippet call the functions to
a: resize window resolution
b: resize projection
currently when you resize the window the projection matrix is not updated
and therefore it gets very skewed and weird looking if you resize the current
small window to a big screen like mine(halbear) which is 3440x1440
i would add it myself but i don't know wGPU or rust all that well sooooo
*/
}
Ok(())
}
}