summaryrefslogtreecommitdiff
path: root/src/gfx
diff options
context:
space:
mode:
Diffstat (limited to 'src/gfx')
-rw-r--r--src/gfx/map.rs447
-rw-r--r--src/gfx/media.rs42
-rw-r--r--src/gfx/state.rs233
-rw-r--r--src/gfx/util.rs63
4 files changed, 785 insertions, 0 deletions
diff --git a/src/gfx/map.rs b/src/gfx/map.rs
new file mode 100644
index 0000000..d95a4d2
--- /dev/null
+++ b/src/gfx/map.rs
@@ -0,0 +1,447 @@
+use super::{media::MediaMgr, state::State, util::MatrixUniform};
+use cgmath::{prelude::*, Matrix4, Point3, Vector3};
+use mt_net::{MapBlock, NodeDef};
+use rand::Rng;
+use std::{collections::HashMap, ops::Range};
+use wgpu::util::DeviceExt;
+
+pub struct MapRender {
+ pipeline: wgpu::RenderPipeline,
+ textures: HashMap<String, [Range<f32>; 2]>,
+ nodes: HashMap<u16, NodeDef>,
+ atlas: wgpu::BindGroup,
+ model: wgpu::BindGroupLayout,
+ blocks: HashMap<[i16; 3], BlockMesh>,
+}
+
+#[repr(C)]
+#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
+struct Vertex {
+ pos: [f32; 3],
+ tex_coords: [f32; 2],
+}
+
+impl Vertex {
+ const ATTRIBS: [wgpu::VertexAttribute; 2] =
+ wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x2];
+
+ fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
+ wgpu::VertexBufferLayout {
+ array_stride: std::mem::size_of::<Self>() as wgpu::BufferAddress,
+ step_mode: wgpu::VertexStepMode::Vertex,
+ attributes: &Self::ATTRIBS,
+ }
+ }
+}
+
+struct BlockMesh {
+ vertex_buffer: wgpu::Buffer,
+ num_vertices: u32,
+ model: MatrixUniform,
+}
+
+impl MapRender {
+ pub fn render<'a>(&'a self, state: &'a State, pass: &mut wgpu::RenderPass<'a>) {
+ pass.set_pipeline(&self.pipeline);
+ pass.set_bind_group(0, &self.atlas, &[]);
+ pass.set_bind_group(1, &state.camera_uniform.bind_group, &[]);
+
+ for mesh in self.blocks.values() {
+ pass.set_bind_group(2, &mesh.model.bind_group, &[]);
+ pass.set_vertex_buffer(0, mesh.vertex_buffer.slice(..));
+ pass.draw(0..mesh.num_vertices, 0..1);
+ }
+ }
+
+ pub fn add_block(&mut self, state: &mut State, pos: Point3<i16>, block: Box<MapBlock>) {
+ let mut vertices = Vec::with_capacity(10000);
+ for (index, content) in block.param_0.iter().enumerate() {
+ let def = match self.nodes.get(content) {
+ Some(x) => x,
+ None => continue,
+ };
+
+ use lerp::Lerp;
+ use mt_net::DrawType;
+ use std::array::from_fn as array;
+
+ match def.draw_type {
+ DrawType::Cube => {
+ let pos: [i16; 3] = array(|i| ((index >> (4 * i)) & 0xf) as i16);
+ for (f, face) in CUBE.iter().enumerate() {
+ let dir = FACE_DIR[f];
+ let npos: [i16; 3] = array(|i| dir[i] + pos[i]);
+ if npos.iter().all(|x| (0..16).contains(x)) {
+ let nindex = npos[0] | (npos[1] << 4) | (npos[2] << 8);
+
+ if let Some(ndef) = self.nodes.get(&block.param_0[nindex as usize]) {
+ if ndef.draw_type == DrawType::Cube {
+ continue;
+ }
+ }
+ }
+
+ let tile = &def.tiles[f];
+ let rect = self.textures.get(&tile.texture).unwrap();
+
+ for vertex in face.iter() {
+ /*println!(
+ "{:?} {:?} {:?} {:?}",
+ (vertex.1[0], vertex.1[1]),
+ (rect[0].start, rect[1].start),
+ (rect[0].end, rect[1].end),
+ (
+ vertex.1[0].lerp(rect[0].start, rect[0].end),
+ vertex.1[1].lerp(rect[1].start, rect[1].end)
+ )
+ );*/
+ vertices.push(Vertex {
+ pos: array(|i| pos[i] as f32 - 8.5 + vertex.0[i]),
+ tex_coords: array(|i| rect[i].start.lerp(rect[i].end, vertex.1[i])),
+ })
+ }
+ }
+ }
+ DrawType::None => {}
+ _ => {
+ // TODO
+ }
+ }
+ }
+
+ self.blocks.insert(
+ pos.into(),
+ BlockMesh {
+ vertex_buffer: state
+ .device
+ .create_buffer_init(&wgpu::util::BufferInitDescriptor {
+ label: Some("mapblock.vertex_buffer"),
+ contents: bytemuck::cast_slice(&vertices),
+ usage: wgpu::BufferUsages::VERTEX,
+ }),
+ num_vertices: vertices.len() as u32,
+ model: MatrixUniform::new(
+ &state.device,
+ &self.model,
+ Matrix4::from_translation(
+ pos.cast::<f32>().unwrap().to_vec() * 16.0 + Vector3::new(8.5, 8.5, 8.5),
+ ),
+ "mapblock",
+ false,
+ ),
+ },
+ );
+ }
+
+ pub fn new(state: &mut State, media: &MediaMgr, nodes: HashMap<u16, NodeDef>) -> Self {
+ let mut rng = rand::thread_rng();
+ let mut atlas_map = HashMap::new();
+ let mut atlas_alloc = guillotiere::SimpleAtlasAllocator::new(guillotiere::size2(1, 1));
+
+ for node in nodes.values() {
+ let tiles = node
+ .tiles
+ .iter()
+ .chain(node.overlay_tiles.iter())
+ .chain(node.special_tiles.iter());
+
+ let load_texture = |texture: &str| {
+ let payload = media
+ .get(texture)
+ .ok_or_else(|| format!("texture not found: {texture}"))?;
+
+ image::load_from_memory(payload)
+ .or_else(|_| {
+ image::load_from_memory_with_format(payload, image::ImageFormat::Tga)
+ })
+ .map_err(|e| format!("failed to load texture {texture}: {e}"))
+ .map(|x| image::imageops::flip_vertical(&x))
+ };
+
+ let mut make_texture = |texture: &str| {
+ texture
+ .split('^')
+ .map(|part| match load_texture(part) {
+ Ok(v) => v,
+ Err(e) => {
+ if !texture.is_empty() && !texture.contains('[') {
+ eprintln!("{e}");
+ }
+
+ let mut img = image::RgbImage::new(1, 1);
+ rng.fill(&mut img.get_pixel_mut(0, 0).0);
+
+ image::DynamicImage::from(img).to_rgba8()
+ }
+ })
+ .reduce(|mut base, top| {
+ image::imageops::overlay(&mut base, &top, 0, 0);
+ base
+ })
+ .unwrap()
+ };
+
+ for tile in tiles {
+ atlas_map.entry(tile.texture.clone()).or_insert_with(|| {
+ let img = make_texture(&tile.texture);
+
+ let dimensions = img.dimensions();
+ let size = guillotiere::size2(dimensions.0 as i32, dimensions.1 as i32);
+
+ loop {
+ match atlas_alloc.allocate(size) {
+ None => {
+ let mut atlas_size = atlas_alloc.size();
+ atlas_size.width *= 2;
+ atlas_size.height *= 2;
+ atlas_alloc.grow(atlas_size);
+ }
+ Some(v) => return (img, v),
+ }
+ }
+ });
+ }
+ }
+
+ let atlas_size = atlas_alloc.size();
+ let mut atlas = image::RgbaImage::new(atlas_size.width as u32, atlas_size.height as u32);
+
+ let textures = atlas_map
+ .into_iter()
+ .map(|(name, (img, rect))| {
+ let w = atlas_size.width as f32;
+ let h = atlas_size.height as f32;
+
+ let x = (rect.min.x as f32 / w)..(rect.max.x as f32 / w);
+ let y = (rect.min.y as f32 / h)..(rect.max.y as f32 / h);
+
+ use image::GenericImage;
+ atlas
+ .copy_from(&img, rect.min.x as u32, rect.min.y as u32)
+ .unwrap();
+
+ (name, [x, y])
+ })
+ .collect();
+
+ let size = wgpu::Extent3d {
+ width: atlas_size.width as u32,
+ height: atlas_size.height as u32,
+ depth_or_array_layers: 1,
+ };
+
+ let atlas_texture = state.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("tile_atlas"),
+ view_formats: &[],
+ });
+
+ state.queue.write_texture(
+ wgpu::ImageCopyTexture {
+ texture: &atlas_texture,
+ mip_level: 0,
+ origin: wgpu::Origin3d::ZERO,
+ aspect: wgpu::TextureAspect::All,
+ },
+ &atlas,
+ wgpu::ImageDataLayout {
+ offset: 0,
+ bytes_per_row: std::num::NonZeroU32::new(4 * atlas_size.width as u32),
+ rows_per_image: std::num::NonZeroU32::new(atlas_size.height as u32),
+ },
+ size,
+ );
+
+ let atlas_view = atlas_texture.create_view(&wgpu::TextureViewDescriptor::default());
+
+ let atlas_sampler = state.device.create_sampler(&wgpu::SamplerDescriptor {
+ address_mode_u: wgpu::AddressMode::ClampToEdge,
+ address_mode_v: wgpu::AddressMode::ClampToEdge,
+ address_mode_w: wgpu::AddressMode::ClampToEdge,
+ // "We've got you surrounded, stop using Nearest filter"
+ // - "I hate bilinear filtering I hate bilinear filtering I hate bilinear filtering"
+ mag_filter: wgpu::FilterMode::Nearest,
+ min_filter: wgpu::FilterMode::Nearest,
+ mipmap_filter: wgpu::FilterMode::Nearest,
+ ..Default::default()
+ });
+
+ let atlas_bind_group_layout =
+ state
+ .device
+ .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ entries: &[
+ wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ 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: 1,
+ visibility: wgpu::ShaderStages::FRAGMENT,
+ ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
+ count: None,
+ },
+ ],
+ label: Some("atlas.bind_group_layout"),
+ });
+
+ let atlas_bind_group = state.device.create_bind_group(&wgpu::BindGroupDescriptor {
+ layout: &atlas_bind_group_layout,
+ entries: &[
+ wgpu::BindGroupEntry {
+ binding: 0,
+ resource: wgpu::BindingResource::TextureView(&atlas_view),
+ },
+ wgpu::BindGroupEntry {
+ binding: 1,
+ resource: wgpu::BindingResource::Sampler(&atlas_sampler),
+ },
+ ],
+ label: Some("atlas.bind_group"),
+ });
+
+ let model_bind_group_layout = MatrixUniform::layout(&state.device, "mapblock");
+
+ let shader = state
+ .device
+ .create_shader_module(wgpu::include_wgsl!("../../assets/shaders/map.wgsl"));
+
+ let pipeline_layout =
+ state
+ .device
+ .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
+ label: None,
+ bind_group_layouts: &[
+ &atlas_bind_group_layout,
+ &model_bind_group_layout,
+ &state.camera_bind_group_layout,
+ ],
+ push_constant_ranges: &[],
+ });
+
+ let pipeline = state
+ .device
+ .create_render_pipeline(&wgpu::RenderPipelineDescriptor {
+ label: None,
+ layout: Some(&pipeline_layout),
+ vertex: wgpu::VertexState {
+ module: &shader,
+ entry_point: "vs_main",
+ buffers: &[Vertex::desc()],
+ },
+ fragment: Some(wgpu::FragmentState {
+ module: &shader,
+ entry_point: "fs_main",
+ targets: &[Some(wgpu::ColorTargetState {
+ format: state.config.format,
+ blend: Some(wgpu::BlendState::REPLACE),
+ write_mask: wgpu::ColorWrites::ALL,
+ })],
+ }),
+ primitive: wgpu::PrimitiveState {
+ topology: wgpu::PrimitiveTopology::TriangleList,
+ strip_index_format: None,
+ front_face: wgpu::FrontFace::Ccw,
+ cull_mode: Some(wgpu::Face::Back),
+ polygon_mode: wgpu::PolygonMode::Fill,
+ unclipped_depth: false,
+ conservative: false,
+ },
+ depth_stencil: Some(wgpu::DepthStencilState {
+ format: wgpu::TextureFormat::Depth32Float,
+ depth_write_enabled: true,
+ depth_compare: wgpu::CompareFunction::Less,
+ stencil: wgpu::StencilState::default(),
+ bias: wgpu::DepthBiasState::default(),
+ }),
+ multisample: wgpu::MultisampleState {
+ count: 1,
+ mask: !0,
+ alpha_to_coverage_enabled: false,
+ },
+ multiview: None,
+ });
+
+ Self {
+ pipeline,
+ nodes,
+ textures,
+ atlas: atlas_bind_group,
+ model: model_bind_group_layout,
+ blocks: HashMap::new(),
+ }
+ }
+}
+
+#[rustfmt::skip]
+const CUBE: [[([f32; 3], [f32; 2]); 6]; 6] = [
+ [
+ ([-0.5, 0.5, -0.5], [ 0.0, 1.0]),
+ ([ 0.5, 0.5, 0.5], [ 1.0, 0.0]),
+ ([ 0.5, 0.5, -0.5], [ 1.0, 1.0]),
+ ([ 0.5, 0.5, 0.5], [ 1.0, 0.0]),
+ ([-0.5, 0.5, -0.5], [ 0.0, 1.0]),
+ ([-0.5, 0.5, 0.5], [ 0.0, 0.0]),
+ ],
+ [
+ ([-0.5, -0.5, -0.5], [ 0.0, 1.0]),
+ ([ 0.5, -0.5, -0.5], [ 1.0, 1.0]),
+ ([ 0.5, -0.5, 0.5], [ 1.0, 0.0]),
+ ([ 0.5, -0.5, 0.5], [ 1.0, 0.0]),
+ ([-0.5, -0.5, 0.5], [ 0.0, 0.0]),
+ ([-0.5, -0.5, -0.5], [ 0.0, 1.0]),
+ ],
+ [
+ ([ 0.5, 0.5, 0.5], [ 1.0, 1.0]),
+ ([ 0.5, -0.5, -0.5], [ 0.0, 0.0]),
+ ([ 0.5, 0.5, -0.5], [ 0.0, 1.0]),
+ ([ 0.5, -0.5, -0.5], [ 0.0, 0.0]),
+ ([ 0.5, 0.5, 0.5], [ 1.0, 1.0]),
+ ([ 0.5, -0.5, 0.5], [ 1.0, 0.0]),
+ ],
+ [
+ ([-0.5, 0.5, 0.5], [ 1.0, 1.0]),
+ ([-0.5, 0.5, -0.5], [ 0.0, 1.0]),
+ ([-0.5, -0.5, -0.5], [ 0.0, 0.0]),
+ ([-0.5, -0.5, -0.5], [ 0.0, 0.0]),
+ ([-0.5, -0.5, 0.5], [ 1.0, 0.0]),
+ ([-0.5, 0.5, 0.5], [ 1.0, 1.0]),
+ ],
+ [
+ ([-0.5, -0.5, 0.5], [ 0.0, 0.0]),
+ ([ 0.5, -0.5, 0.5], [ 1.0, 0.0]),
+ ([ 0.5, 0.5, 0.5], [ 1.0, 1.0]),
+ ([ 0.5, 0.5, 0.5], [ 1.0, 1.0]),
+ ([-0.5, 0.5, 0.5], [ 0.0, 1.0]),
+ ([-0.5, -0.5, 0.5], [ 0.0, 0.0]),
+ ],
+ [
+ ([-0.5, -0.5, -0.5], [ 0.0, 0.0]),
+ ([ 0.5, 0.5, -0.5], [ 1.0, 1.0]),
+ ([ 0.5, -0.5, -0.5], [ 1.0, 0.0]),
+ ([ 0.5, 0.5, -0.5], [ 1.0, 1.0]),
+ ([-0.5, -0.5, -0.5], [ 0.0, 0.0]),
+ ([-0.5, 0.5, -0.5], [ 0.0, 1.0]),
+ ],
+];
+
+#[rustfmt::skip]
+const FACE_DIR: [[i16; 3]; 6] = [
+ [ 0, 1, 0],
+ [ 0, -1, 0],
+ [ 1, 0, 0],
+ [-1, 0, 0],
+ [ 0, 0, 1],
+ [ 0, 0, -1],
+];
diff --git a/src/gfx/media.rs b/src/gfx/media.rs
new file mode 100644
index 0000000..ac5d158
--- /dev/null
+++ b/src/gfx/media.rs
@@ -0,0 +1,42 @@
+use std::collections::HashMap;
+
+#[derive(rust_embed::RustEmbed)]
+#[folder = "assets/textures"]
+pub struct BaseFolder; // copied from github.com/minetest/minetest
+
+pub struct MediaMgr {
+ packs: Vec<HashMap<String, Vec<u8>>>,
+ srv_idx: usize,
+}
+
+impl MediaMgr {
+ pub fn new() -> Self {
+ Self {
+ packs: [
+ BaseFolder::iter()
+ .map(|file| {
+ (
+ file.to_string(),
+ BaseFolder::get(&file).unwrap().data.into_owned(),
+ )
+ })
+ .collect(),
+ HashMap::new(),
+ ]
+ .into(),
+ srv_idx: 1,
+ }
+ }
+
+ pub fn add_server_media(&mut self, files: HashMap<String, Vec<u8>>) {
+ self.packs[self.srv_idx].extend(files.into_iter());
+ }
+
+ pub fn get(&self, file: &str) -> Option<&[u8]> {
+ self.packs
+ .iter()
+ .rev()
+ .find_map(|pack| pack.get(file))
+ .map(Vec::as_slice)
+ }
+}
diff --git a/src/gfx/state.rs b/src/gfx/state.rs
new file mode 100644
index 0000000..9d1cfd1
--- /dev/null
+++ b/src/gfx/state.rs
@@ -0,0 +1,233 @@
+use super::util::MatrixUniform;
+use cgmath::{prelude::*, Deg, Matrix4, Rad};
+use fps_camera::{FirstPerson, FirstPersonSettings};
+use std::time::Duration;
+
+pub struct State {
+ pub surface: wgpu::Surface,
+ pub device: wgpu::Device,
+ pub queue: wgpu::Queue,
+ pub config: wgpu::SurfaceConfiguration,
+ pub fov: Rad<f32>,
+ pub view: Matrix4<f32>,
+ pub proj: Matrix4<f32>,
+ pub camera: FirstPerson,
+ pub camera_uniform: MatrixUniform,
+ pub camera_bind_group_layout: wgpu::BindGroupLayout,
+ pub depth_texture: wgpu::Texture,
+ pub depth_view: wgpu::TextureView,
+ pub depth_sampler: wgpu::Sampler,
+}
+
+impl State {
+ pub async fn new(window: &winit::window::Window) -> Self {
+ let size = window.inner_size();
+
+ let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
+ backends: wgpu::Backends::all(),
+ dx12_shader_compiler: Default::default(),
+ });
+
+ let surface = unsafe { instance.create_surface(window) }.unwrap();
+
+ let adapter = instance
+ .request_adapter(&wgpu::RequestAdapterOptions {
+ power_preference: wgpu::PowerPreference::default(),
+ compatible_surface: Some(&surface),
+ force_fallback_adapter: false,
+ })
+ .await
+ .unwrap();
+
+ let (device, queue) = adapter
+ .request_device(
+ &wgpu::DeviceDescriptor {
+ features: wgpu::Features::empty(),
+ limits: Default::default(),
+ label: None,
+ },
+ None,
+ )
+ .await
+ .unwrap();
+
+ let surface_caps = surface.get_capabilities(&adapter);
+ let surface_format = surface_caps
+ .formats
+ .iter()
+ .copied()
+ .find(|f| f.describe().srgb)
+ .unwrap_or(surface_caps.formats[0]);
+
+ let config = wgpu::SurfaceConfiguration {
+ usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
+ format: surface_format,
+ width: size.width,
+ height: size.height,
+ present_mode: surface_caps.present_modes[0],
+ alpha_mode: surface_caps.alpha_modes[0],
+ view_formats: vec![],
+ };
+
+ let (depth_texture, depth_view, depth_sampler) =
+ Self::create_depth_texture(&config, &device);
+
+ let camera = FirstPerson::new(
+ [0.0, 0.0, 0.0],
+ FirstPersonSettings {
+ speed_horizontal: 10.0,
+ speed_vertical: 10.0,
+ mouse_sensitivity_horizontal: 1.0,
+ mouse_sensitivity_vertical: 1.0,
+ },
+ );
+
+ let camera_bind_group_layout = MatrixUniform::layout(&device, "camera");
+
+ let camera_uniform = MatrixUniform::new(
+ &device,
+ &camera_bind_group_layout,
+ Matrix4::identity(),
+ "camera",
+ true,
+ );
+
+ let mut state = Self {
+ surface,
+ device,
+ queue,
+ config,
+ fov: Deg(90.0).into(),
+ proj: Matrix4::identity(),
+ view: Matrix4::identity(),
+ camera,
+ camera_uniform,
+ camera_bind_group_layout,
+ depth_texture,
+ depth_view,
+ depth_sampler,
+ };
+
+ state.resize(size);
+
+ state
+ }
+
+ pub fn create_depth_texture(
+ config: &wgpu::SurfaceConfiguration,
+ device: &wgpu::Device,
+ ) -> (wgpu::Texture, wgpu::TextureView, wgpu::Sampler) {
+ let depth_size = wgpu::Extent3d {
+ width: config.width,
+ height: config.height,
+ depth_or_array_layers: 1,
+ };
+ let depth_descriptor = wgpu::TextureDescriptor {
+ label: Some("depth texture"),
+ size: depth_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(&depth_descriptor);
+
+ let depth_view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default());
+ let depth_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+ 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::FilterMode::Nearest,
+ compare: Some(wgpu::CompareFunction::LessEqual),
+ lod_min_clamp: 0.0,
+ lod_max_clamp: 100.0,
+ ..Default::default()
+ });
+
+ (depth_texture, depth_view, depth_sampler)
+ }
+
+ pub fn resize(&mut self, size: winit::dpi::PhysicalSize<u32>) {
+ if size.width > 0 && size.height > 0 {
+ self.config.width = size.width;
+ self.config.height = size.height;
+ self.configure_surface();
+ self.update_projection();
+ (self.depth_texture, self.depth_view, self.depth_sampler) =
+ Self::create_depth_texture(&self.config, &self.device);
+ }
+ }
+
+ pub fn configure_surface(&mut self) {
+ self.surface.configure(&self.device, &self.config);
+ }
+
+ pub fn update_projection(&mut self) {
+ self.proj = cgmath::perspective(
+ self.fov,
+ self.config.width as f32 / self.config.height as f32,
+ 0.1,
+ 100000.0,
+ );
+ }
+
+ pub fn update(&mut self, dt: Duration) {
+ let cam = self.camera.camera(dt.as_secs_f32());
+ self.camera.position = cam.position;
+ self.view = Matrix4::from(cam.orthogonal());
+
+ self.camera_uniform.set(&self.queue, self.proj * self.view);
+ }
+
+ pub fn render(&self, map: &Option<super::map::MapRender>) -> Result<(), wgpu::SurfaceError> {
+ let output = self.surface.get_current_texture()?;
+ let view = output
+ .texture
+ .create_view(&wgpu::TextureViewDescriptor::default());
+
+ let mut encoder = self
+ .device
+ .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
+
+ {
+ let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+ label: None,
+ color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+ view: &view,
+ resolve_target: None,
+ ops: wgpu::Operations {
+ load: wgpu::LoadOp::Clear(wgpu::Color {
+ r: 0x87 as f64 / 255.0,
+ g: 0xCE as f64 / 255.0,
+ b: 0xEB as f64 / 255.0,
+ a: 1.0,
+ }),
+ store: true,
+ },
+ })],
+ depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment {
+ view: &self.depth_view,
+ depth_ops: Some(wgpu::Operations {
+ load: wgpu::LoadOp::Clear(1.0),
+ store: true,
+ }),
+ stencil_ops: None,
+ }),
+ });
+
+ if let Some(map) = map.as_ref() {
+ map.render(self, &mut render_pass);
+ }
+ }
+
+ self.queue.submit(std::iter::once(encoder.finish()));
+ output.present();
+
+ Ok(())
+ }
+}
diff --git a/src/gfx/util.rs b/src/gfx/util.rs
new file mode 100644
index 0000000..f93accd
--- /dev/null
+++ b/src/gfx/util.rs
@@ -0,0 +1,63 @@
+use cgmath::Matrix4;
+use wgpu::util::DeviceExt;
+
+pub struct MatrixUniform {
+ buffer: wgpu::Buffer,
+ pub bind_group: wgpu::BindGroup,
+}
+
+impl MatrixUniform {
+ pub fn new(
+ device: &wgpu::Device,
+ bind_group_layout: &wgpu::BindGroupLayout,
+ init: Matrix4<f32>,
+ name: &str,
+ writable: bool,
+ ) -> Self {
+ let uniform: [[f32; 4]; 4] = init.into();
+
+ let mut usage = wgpu::BufferUsages::UNIFORM;
+
+ if writable {
+ usage |= wgpu::BufferUsages::COPY_DST;
+ }
+
+ let buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
+ label: Some(&format!("{name}.buffer")),
+ contents: bytemuck::cast_slice(&[uniform]),
+ usage,
+ });
+
+ let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
+ layout: bind_group_layout,
+ entries: &[wgpu::BindGroupEntry {
+ binding: 0,
+ resource: buffer.as_entire_binding(),
+ }],
+ label: Some(&format!("{name}.bind_group")),
+ });
+
+ Self { buffer, bind_group }
+ }
+
+ pub fn layout(device: &wgpu::Device, name: &str) -> wgpu::BindGroupLayout {
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+ entries: &[wgpu::BindGroupLayoutEntry {
+ binding: 0,
+ visibility: wgpu::ShaderStages::VERTEX,
+ ty: wgpu::BindingType::Buffer {
+ ty: wgpu::BufferBindingType::Uniform,
+ has_dynamic_offset: false,
+ min_binding_size: None,
+ },
+ count: None,
+ }],
+ label: Some(&format!("{name}.bind_group_layout")),
+ })
+ }
+
+ pub fn set(&self, queue: &wgpu::Queue, to: Matrix4<f32>) {
+ let uniform: [[f32; 4]; 4] = to.into();
+ queue.write_buffer(&self.buffer, 0, bytemuck::cast_slice(&[uniform]));
+ }
+}