From a7bc21ab70b4a890e07f0173fca8614b74e76b2d Mon Sep 17 00:00:00 2001 From: Maximilian Ammann Date: Thu, 2 Dec 2021 16:20:48 +0100 Subject: [PATCH] Refactor complete project by splitting it up and introducing a State --- src/lyon_test/fps_meter.rs | 31 ++ src/lyon_test/main.rs | 449 ++++------------------------ src/lyon_test/multisampling.rs | 25 ++ src/lyon_test/piplines.rs | 28 +- src/lyon_test/scene.rs | 128 -------- src/lyon_test/shader.rs | 2 +- src/lyon_test/state.rs | 493 +++++++++++++++++++++++++++++++ src/lyon_test/texture.rs | 36 +++ src/lyon_test/tile_downloader.rs | 2 +- 9 files changed, 653 insertions(+), 541 deletions(-) create mode 100644 src/lyon_test/fps_meter.rs create mode 100644 src/lyon_test/multisampling.rs delete mode 100644 src/lyon_test/scene.rs create mode 100644 src/lyon_test/state.rs create mode 100644 src/lyon_test/texture.rs diff --git a/src/lyon_test/fps_meter.rs b/src/lyon_test/fps_meter.rs new file mode 100644 index 00000000..bbf20dc4 --- /dev/null +++ b/src/lyon_test/fps_meter.rs @@ -0,0 +1,31 @@ +use std::time::{Duration, Instant}; + +pub struct FPSMeter { + start: Instant, + next_report: Instant, + frame_count: u32, + pub time_secs: f32, +} + +impl FPSMeter { + pub fn new() -> Self { + let start = Instant::now(); + Self { + start, + next_report: start + Duration::from_secs(1), + frame_count: 0, + time_secs: 0.0, + } + } + + pub fn update_and_print(&mut self) { + self.frame_count += 1; + let now = Instant::now(); + self.time_secs = (now - self.start).as_secs_f32(); + if now >= self.next_report { + println!("{} FPS", self.frame_count); + self.frame_count = 0; + self.next_report = now + Duration::from_secs(1); + } + } +} diff --git a/src/lyon_test/main.rs b/src/lyon_test/main.rs index ecbf1ee3..32855bf7 100644 --- a/src/lyon_test/main.rs +++ b/src/lyon_test/main.rs @@ -1,57 +1,18 @@ -use std::time::{Duration, Instant}; - -use futures::executor::block_on; -use lyon::tessellation::VertexBuffers; -use vector_tile::parse_tile; -use wgpu::util::DeviceExt; -use winit::event_loop::EventLoop; +use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; +use winit::event_loop::{ControlFlow, EventLoop}; use winit::window::Window; -use crate::piplines::create_map_render_pipeline_description; -use crate::scene::SceneParams; -use crate::shader::{ - create_fragment_module_descriptor, create_map_fragment_state, create_map_vertex_state, - create_vertex_module_descriptor, -}; -use crate::shader_ffi::*; -use crate::tesselation::{RustLogo, Tesselated}; +use crate::state::State; +mod fps_meter; +mod multisampling; mod piplines; -mod scene; mod shader; mod shader_ffi; +mod state; mod tesselation; mod tile_downloader; - -const PRIM_BUFFER_LEN: usize = 256; - -const DEFAULT_WINDOW_WIDTH: f32 = 800.0; -const DEFAULT_WINDOW_HEIGHT: f32 = 800.0; - -/// Creates a texture that uses MSAA and fits a given swap chain -fn create_multisampled_framebuffer( - device: &wgpu::Device, - desc: &wgpu::SurfaceConfiguration, - sample_count: u32, -) -> wgpu::TextureView { - let multisampled_frame_descriptor = &wgpu::TextureDescriptor { - label: Some("Multisampled frame descriptor"), - size: wgpu::Extent3d { - width: desc.width, - height: desc.height, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count, - dimension: wgpu::TextureDimension::D2, - format: desc.format, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - }; - - device - .create_texture(multisampled_frame_descriptor) - .create_view(&wgpu::TextureViewDescriptor::default()) -} +mod texture; fn main() { env_logger::init(); @@ -61,365 +22,59 @@ fn main() { println!(" PgUp/PgDown: zoom in/out"); println!(" a/z: increase/decrease the stroke width"); - // Number of samples for anti-aliasing - // Set to 1 to disable - let sample_count = 4; - - let num_instances: u32 = 1; - - let stroke_prim_id = 0; - let fill_prim_id = 1; - - let mut geometry: VertexBuffers = VertexBuffers::new(); - - let (stroke_range, fill_range) = if true { - let tile = - parse_tile("test-data/12-2176-1425.pbf").expect("failed loading tile"); - ( - 0..tile.tesselate_stroke(&mut geometry, stroke_prim_id), - 0..0, - ) - } else { - let logo = RustLogo(); - let max_index = logo.tesselate_stroke(&mut geometry, stroke_prim_id); - ( - 0..max_index, - max_index..max_index + logo.tesselate_fill(&mut geometry, fill_prim_id), - ) - }; - - let mut cpu_primitives = [Primitive { - color: [1.0, 0.0, 0.0, 1.0], - z_index: 0, - width: 0.0, - translate: [0.0, 0.0], - angle: 0.0, - ..Primitive::DEFAULT - }; PRIM_BUFFER_LEN]; - - // Stroke primitive - cpu_primitives[stroke_prim_id as usize] = Primitive { - color: [0.0, 0.0, 0.0, 1.0], - z_index: num_instances as i32 + 2, - width: 1.0, - ..Primitive::DEFAULT - }; - // Main fill primitive - cpu_primitives[fill_prim_id as usize] = Primitive { - color: [1.0, 1.0, 1.0, 1.0], - z_index: num_instances as i32 + 1, - ..Primitive::DEFAULT - }; - - let mut scene = SceneParams::DEFAULT; - let event_loop = EventLoop::new(); let window = Window::new(&event_loop).unwrap(); - - // create an instance - let instance = wgpu::Instance::new(wgpu::Backends::all()); - - // create an surface - let surface = unsafe { instance.create_surface(&window) }; - - // create an adapter - let adapter = block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { - power_preference: wgpu::PowerPreference::LowPower, - compatible_surface: Some(&surface), - force_fallback_adapter: false, - })) - .unwrap(); - // create a device and a queue - let (device, queue) = block_on(adapter.request_device( - &wgpu::DeviceDescriptor { - label: None, - features: wgpu::Features::default(), - limits: wgpu::Limits::default(), - }, - None, - )) - .unwrap(); - - let vertex_uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: None, - contents: bytemuck::cast_slice(&geometry.vertices), - usage: wgpu::BufferUsages::VERTEX, - }); - - let indices_uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: None, - contents: bytemuck::cast_slice(&geometry.indices), - usage: wgpu::BufferUsages::INDEX, - }); - - let prim_buffer_byte_size = (PRIM_BUFFER_LEN * std::mem::size_of::()) as u64; - let globals_buffer_byte_size = std::mem::size_of::() as u64; - - let prims_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Prims ubo"), - size: prim_buffer_byte_size, - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - let globals_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { - label: Some("Globals ubo"), - size: globals_buffer_byte_size, - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - mapped_at_creation: false, - }); - - let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: Some("Bind group layout"), - entries: &[ - wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: wgpu::BufferSize::new(globals_buffer_byte_size), - }, - count: None, - }, - wgpu::BindGroupLayoutEntry { - binding: 1, - visibility: wgpu::ShaderStages::VERTEX, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: wgpu::BufferSize::new(prim_buffer_byte_size), - }, - count: None, - }, - ], - }); - let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { - label: Some("Bind group"), - layout: &bind_group_layout, - entries: &[ - wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer( - globals_uniform_buffer.as_entire_buffer_binding(), - ), - }, - wgpu::BindGroupEntry { - binding: 1, - resource: wgpu::BindingResource::Buffer( - prims_uniform_buffer.as_entire_buffer_binding(), - ), - }, - ], - }); - - let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - bind_group_layouts: &[&bind_group_layout], - push_constant_ranges: &[], - label: None, - }); - - let vertex_module = device.create_shader_module(&create_vertex_module_descriptor()); - let fragment_module = device.create_shader_module(&create_fragment_module_descriptor()); - let mut render_pipeline_descriptor = create_map_render_pipeline_description( - &pipeline_layout, - create_map_vertex_state(&vertex_module), - create_map_fragment_state(&fragment_module), - sample_count, - ); - let render_pipeline = device.create_render_pipeline(&render_pipeline_descriptor); - - // TODO: this isn't what we want: we'd need the equivalent of VK_POLYGON_MODE_LINE, - // but it doesn't seem to be exposed by wgpu? - render_pipeline_descriptor.primitive.topology = wgpu::PrimitiveTopology::LineList; - - let size = window.inner_size(); - - let mut surface_desc = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: wgpu::TextureFormat::Bgra8UnormSrgb, - width: size.width, - height: size.height, - present_mode: wgpu::PresentMode::Mailbox, - }; - - let mut multisampled_render_target = None; - - surface.configure(&device, &surface_desc); - - let mut depth_texture_view = None; - - let start = Instant::now(); - let mut next_report = start + Duration::from_secs(1); - let mut frame_count: u32 = 0; - let mut time_secs: f32 = 0.0; + let mut state = pollster::block_on(State::new(&window)); window.request_redraw(); event_loop.run(move |event, _, control_flow| { - if !scene.update_inputs(event, &window, control_flow) { - // keep polling inputs. - return; - } - - if scene.size_changed { - scene.size_changed = false; - let physical = scene.window_size; - surface_desc.width = physical.width; - surface_desc.height = physical.height; - surface.configure(&device, &surface_desc); - - let depth_texture = device.create_texture(&wgpu::TextureDescriptor { - label: Some("Depth texture"), - size: wgpu::Extent3d { - width: surface_desc.width, - height: surface_desc.height, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count, - dimension: wgpu::TextureDimension::D2, - format: wgpu::TextureFormat::Depth32Float, - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - }); - - depth_texture_view = - Some(depth_texture.create_view(&wgpu::TextureViewDescriptor::default())); - - multisampled_render_target = if sample_count > 1 { - Some(create_multisampled_framebuffer( - &device, - &surface_desc, - sample_count, - )) - } else { - None - }; - } - - // TODO: Without this we are not able to close the window - if !scene.render { - return; - } - - scene.render = false; - - let frame = match surface.get_current_texture() { - Ok(texture) => texture, - Err(e) => { - println!("Swap-chain error: {:?}", e); - return; + match event { + Event::WindowEvent { + ref event, + window_id, + } if window_id == window.id() => { + if !state.input(event) { + match event { + WindowEvent::CloseRequested + | WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(VirtualKeyCode::Escape), + .. + }, + .. + } => *control_flow = ControlFlow::Exit, + WindowEvent::Resized(physical_size) => { + state.resize(*physical_size); + } + WindowEvent::ScaleFactorChanged { new_inner_size, .. } => { + // new_inner_size is &mut so w have to dereference it twice + state.resize(**new_inner_size); + } + _ => {} + } + } } - }; - - let frame_view = frame - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { - label: Some("Encoder"), - }); - - /* cpu_primitives[fill_prim_id as usize].color = [ - (time_secs * 0.8 - 1.6).sin() * -0.1 + 0.1, - (time_secs * 0.5 - 1.6).sin() * -0.1 + 0.1, - (time_secs - 1.6).sin() * -0.1 + 0.1, - 1.0, - ];*/ - - cpu_primitives[stroke_prim_id as usize].width = scene.stroke_width; - cpu_primitives[stroke_prim_id as usize].color = [ - (time_secs * 0.8 - 1.6).sin() * 0.1 + 0.1, - (time_secs * 0.5 - 1.6).sin() * 0.1 + 0.1, - (time_secs - 1.6).sin() * 0.1 + 0.1, - 1.0, - ]; - - for idx in 2..(num_instances + 1) { - cpu_primitives[idx as usize].translate = [ - (time_secs * 0.05 * idx as f32).sin() * (100.0 + idx as f32 * 10.0), - (time_secs * 0.1 * idx as f32).sin() * (100.0 + idx as f32 * 10.0), - ]; - } - - queue.write_buffer( - &globals_uniform_buffer, - 0, - bytemuck::cast_slice(&[Globals { - resolution: [ - scene.window_size.width as f32, - scene.window_size.height as f32, - ], - zoom: scene.zoom, - scroll_offset: scene.scroll.to_array(), - _pad: 0.0, - }]), - ); - - queue.write_buffer( - &prims_uniform_buffer, - 0, - bytemuck::cast_slice(&cpu_primitives), - ); - - { - // A resolve target is only supported if the attachment actually uses anti-aliasing - // So if sample_count == 1 then we must render directly to the surface's buffer - let color_attachment = if let Some(msaa_target) = &multisampled_render_target { - wgpu::RenderPassColorAttachment { - view: msaa_target, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::WHITE), - store: true, - }, - resolve_target: Some(&frame_view), + Event::RedrawRequested(_) => { + state.update(); + match state.render() { + Ok(_) => {} + // Reconfigure the surface if lost + Err(wgpu::SurfaceError::Lost) => state.resize(state.size), + // The system is out of memory, we should probably quit + Err(wgpu::SurfaceError::OutOfMemory) => *control_flow = ControlFlow::Exit, + // All other errors (Outdated, Timeout) should be resolved by the next frame + Err(e) => eprintln!("{:?}", e), } - } else { - wgpu::RenderPassColorAttachment { - view: &frame_view, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color::WHITE), - store: true, - }, - resolve_target: None, - } - }; - - let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: None, - color_attachments: &[color_attachment], - depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { - view: depth_texture_view.as_ref().unwrap(), - depth_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(0.0), - store: true, - }), - stencil_ops: Some(wgpu::Operations { - load: wgpu::LoadOp::Clear(0), - store: true, - }), - }), - }); - - pass.set_pipeline(&render_pipeline); - pass.set_bind_group(0, &bind_group, &[]); - pass.set_index_buffer(indices_uniform_buffer.slice(..), wgpu::IndexFormat::Uint16); - pass.set_vertex_buffer(0, vertex_uniform_buffer.slice(..)); - - pass.draw_indexed(fill_range.clone(), 0, 0..(num_instances as u32)); - pass.draw_indexed(stroke_range.clone(), 0, 0..1); - } - - queue.submit(Some(encoder.finish())); - frame.present(); - - frame_count += 1; - let now = Instant::now(); - time_secs = (now - start).as_secs_f32(); - if now >= next_report { - println!("{} FPS", frame_count); - frame_count = 0; - next_report = now + Duration::from_secs(1); + } + Event::MainEventsCleared => { + // RedrawRequested will only trigger once, unless we manually + // request it. + window.request_redraw(); + } + _ => {} } }); } diff --git a/src/lyon_test/multisampling.rs b/src/lyon_test/multisampling.rs new file mode 100644 index 00000000..a8044c68 --- /dev/null +++ b/src/lyon_test/multisampling.rs @@ -0,0 +1,25 @@ + +/// Creates a texture that uses MSAA and fits a given swap chain +pub fn create_multisampled_framebuffer( + device: &wgpu::Device, + desc: &wgpu::SurfaceConfiguration, + sample_count: u32, +) -> wgpu::TextureView { + let multisampled_frame_descriptor = &wgpu::TextureDescriptor { + label: Some("Multisampled frame descriptor"), + size: wgpu::Extent3d { + width: desc.width, + height: desc.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count, + dimension: wgpu::TextureDimension::D2, + format: desc.format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + }; + + device + .create_texture(multisampled_frame_descriptor) + .create_view(&wgpu::TextureViewDescriptor::default()) +} diff --git a/src/lyon_test/piplines.rs b/src/lyon_test/piplines.rs index ad09ab3b..adb8dba0 100644 --- a/src/lyon_test/piplines.rs +++ b/src/lyon_test/piplines.rs @@ -1,4 +1,5 @@ use wgpu::{FragmentState, PipelineLayout, RenderPipelineDescriptor, VertexState}; +use crate::texture::Texture; pub fn create_map_render_pipeline_description<'a>( pipeline_layout: &'a PipelineLayout, @@ -6,19 +7,6 @@ pub fn create_map_render_pipeline_description<'a>( fragment_state: FragmentState<'a>, sample_count: u32, ) -> RenderPipelineDescriptor<'a> { - let depth_stencil_state = wgpu::DepthStencilState { - format: wgpu::TextureFormat::Depth32Float, - depth_write_enabled: true, - depth_compare: wgpu::CompareFunction::Greater, - stencil: wgpu::StencilState { - front: wgpu::StencilFaceState::IGNORE, - back: wgpu::StencilFaceState::IGNORE, - read_mask: 0, - write_mask: 0, - }, - bias: wgpu::DepthBiasState::default(), - }; - let descriptor = wgpu::RenderPipelineDescriptor { label: None, layout: Some(&pipeline_layout), @@ -33,7 +21,19 @@ pub fn create_map_render_pipeline_description<'a>( clamp_depth: false, conservative: false, }, - depth_stencil: Some(depth_stencil_state), + /*depth_stencil: None,*/ + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::Greater, + stencil: wgpu::StencilState { + front: wgpu::StencilFaceState::IGNORE, + back: wgpu::StencilFaceState::IGNORE, + read_mask: 0, + write_mask: 0, + }, + bias: wgpu::DepthBiasState::default(), + }), multisample: wgpu::MultisampleState { count: sample_count, mask: !0, diff --git a/src/lyon_test/scene.rs b/src/lyon_test/scene.rs deleted file mode 100644 index a488fda9..00000000 --- a/src/lyon_test/scene.rs +++ /dev/null @@ -1,128 +0,0 @@ -use lyon::math::Vector; -use winit::dpi::PhysicalSize; -use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; -use winit::event_loop::ControlFlow; -use winit::window::Window; - -use crate::{DEFAULT_WINDOW_HEIGHT, DEFAULT_WINDOW_WIDTH}; - -pub struct SceneParams { - pub target_zoom: f32, - pub zoom: f32, - pub target_scroll: Vector, - pub scroll: Vector, - pub show_points: bool, - pub stroke_width: f32, - pub target_stroke_width: f32, - pub window_size: PhysicalSize, - pub size_changed: bool, - pub render: bool, -} - -impl SceneParams { - pub const DEFAULT: SceneParams = SceneParams { - target_zoom: 5.0, - zoom: 5.0, - target_scroll: Vector::new(70.0, 70.0), - scroll: Vector::new(70.0, 70.0), - show_points: false, - stroke_width: 1.0, - target_stroke_width: 1.0, - window_size: PhysicalSize::new(DEFAULT_WINDOW_WIDTH as u32, DEFAULT_WINDOW_HEIGHT as u32), - size_changed: true, - render: false, - }; - - pub fn update_inputs( - self: &mut SceneParams, - event: Event<()>, - window: &Window, - control_flow: &mut ControlFlow, - ) -> bool { - match event { - Event::RedrawRequested(_) => { - self.render = true; - } - Event::RedrawEventsCleared => { - window.request_redraw(); - } - Event::WindowEvent { - event: WindowEvent::Destroyed, - .. - } - | Event::WindowEvent { - event: WindowEvent::CloseRequested, - .. - } => { - *control_flow = ControlFlow::Exit; - return false; - } - Event::WindowEvent { - event: WindowEvent::Resized(size), - .. - } => { - self.window_size = size; - self.size_changed = true - } - Event::WindowEvent { - event: - WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Pressed, - virtual_keycode: Some(key), - .. - }, - .. - }, - .. - } => match key { - VirtualKeyCode::Escape => { - *control_flow = ControlFlow::Exit; - return false; - } - VirtualKeyCode::PageDown => { - self.target_zoom *= 0.8; - } - VirtualKeyCode::PageUp => { - self.target_zoom *= 1.25; - } - VirtualKeyCode::Left => { - self.target_scroll.x -= 50.0 / self.target_zoom; - } - VirtualKeyCode::Right => { - self.target_scroll.x += 50.0 / self.target_zoom; - } - VirtualKeyCode::Up => { - self.target_scroll.y -= 50.0 / self.target_zoom; - } - VirtualKeyCode::Down => { - self.target_scroll.y += 50.0 / self.target_zoom; - } - VirtualKeyCode::P => { - self.show_points = !self.show_points; - } - VirtualKeyCode::A => { - self.target_stroke_width /= 0.8; - } - VirtualKeyCode::Z => { - self.target_stroke_width *= 0.8; - } - _key => {} - }, - _evt => { - //println!("{:?}", _evt); - } - } - //println!(" -- zoom: {}, scroll: {:?}", self.target_zoom, self.target_scroll); - - self.zoom += (self.target_zoom - self.zoom) / 3.0; - self.scroll = self.scroll + (self.target_scroll - self.scroll) / 3.0; - self.stroke_width = - self.stroke_width + (self.target_stroke_width - self.stroke_width) / 5.0; - - *control_flow = ControlFlow::Poll; - - true - } -} diff --git a/src/lyon_test/shader.rs b/src/lyon_test/shader.rs index 264e84a8..084fc59b 100644 --- a/src/lyon_test/shader.rs +++ b/src/lyon_test/shader.rs @@ -1,6 +1,6 @@ use wgpu::{ColorTargetState, FragmentState, ShaderModule, ShaderModuleDescriptor, VertexAttribute, VertexBufferLayout, VertexState}; -use crate::GpuVertex; +use crate::shader_ffi::GpuVertex; const MAP_VERTEX_SHADER_ARGUMENTS: [VertexAttribute; 3] = [ wgpu::VertexAttribute { diff --git a/src/lyon_test/state.rs b/src/lyon_test/state.rs new file mode 100644 index 00000000..3b8ca68c --- /dev/null +++ b/src/lyon_test/state.rs @@ -0,0 +1,493 @@ +use std::ops::Range; + +use lyon::math::Vector; +use lyon::tessellation::VertexBuffers; +use vector_tile::parse_tile; +use wgpu::util::DeviceExt; +use winit::dpi::PhysicalSize; +use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; +use winit::event_loop::ControlFlow; +use winit::window::Window; + +use crate::fps_meter::FPSMeter; +use crate::multisampling::create_multisampled_framebuffer; +use crate::piplines::*; +use crate::shader::*; +use crate::shader_ffi::*; +use crate::tesselation::{RustLogo, Tesselated}; +use crate::texture::Texture; + +pub struct SceneParams { + pub target_zoom: f32, + pub zoom: f32, + pub target_scroll: Vector, + pub scroll: Vector, + pub show_points: bool, + pub stroke_width: f32, + pub target_stroke_width: f32, +} + +const PRIM_BUFFER_LEN: usize = 256; + +pub const DEFAULT_SCENE: SceneParams = SceneParams { + target_zoom: 5.0, + zoom: 5.0, + target_scroll: Vector::new(70.0, 70.0), + scroll: Vector::new(70.0, 70.0), + show_points: false, + stroke_width: 1.0, + target_stroke_width: 1.0, +}; + +pub struct State { + device: wgpu::Device, + queue: wgpu::Queue, + + fps_meter: FPSMeter, + + surface: wgpu::Surface, + surface_config: wgpu::SurfaceConfiguration, + + pub size: winit::dpi::PhysicalSize, + + render_pipeline: wgpu::RenderPipeline, + bind_group: wgpu::BindGroup, + + sample_count: u32, + multisampled_render_target: Option, + depth_texture: Texture, + + prims_uniform_buffer: wgpu::Buffer, + globals_uniform_buffer: wgpu::Buffer, + vertex_uniform_buffer: wgpu::Buffer, + indices_uniform_buffer: wgpu::Buffer, + + num_instances: u32, + stroke_prim_id: u32, + fill_prim_id: u32, + cpu_primitives: Vec, + fill_range: Range, + stroke_range: Range, + + scene: SceneParams, +} + +impl State { + pub async fn new(window: &Window) -> Self { + let sample_count = 4; + let stroke_prim_id = 0; + let fill_prim_id = 1; + + let size = window.inner_size(); + + let mut geometry: VertexBuffers = VertexBuffers::new(); + + let (stroke_range, fill_range) = if true { + let tile = parse_tile("test-data/12-2176-1425.pbf").expect("failed loading tile"); + ( + 0..tile.tesselate_stroke(&mut geometry, stroke_prim_id), + 0..0, + ) + } else { + let logo = RustLogo(); + let max_index = logo.tesselate_stroke(&mut geometry, stroke_prim_id); + ( + 0..max_index, + max_index..max_index + logo.tesselate_fill(&mut geometry, fill_prim_id), + ) + }; + + let mut cpu_primitives = Vec::with_capacity(PRIM_BUFFER_LEN); + for _ in 0..PRIM_BUFFER_LEN { + cpu_primitives.push(Primitive { + color: [1.0, 0.0, 0.0, 1.0], + z_index: 0, + width: 0.0, + translate: [0.0, 0.0], + angle: 0.0, + ..Primitive::DEFAULT + }); + } + + // Stroke primitive + cpu_primitives[stroke_prim_id as usize] = Primitive { + color: [0.0, 0.0, 0.0, 1.0], + z_index: 3, + width: 1.0, + ..Primitive::DEFAULT + }; + // Main fill primitive + cpu_primitives[fill_prim_id as usize] = Primitive { + color: [1.0, 1.0, 1.0, 1.0], + z_index: 1, + ..Primitive::DEFAULT + }; + + // create an instance + let instance = wgpu::Instance::new(wgpu::Backends::all()); + + // create an surface + let surface = unsafe { instance.create_surface(window) }; + + // create an adapter + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::LowPower, + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .await + .unwrap(); + + // create a device and a queue + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: wgpu::Features::default(), + limits: wgpu::Limits::default(), + }, + None, + ) + .await + .unwrap(); + + let vertex_uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&geometry.vertices), + usage: wgpu::BufferUsages::VERTEX, + }); + + let indices_uniform_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: None, + contents: bytemuck::cast_slice(&geometry.indices), + usage: wgpu::BufferUsages::INDEX, + }); + + let prim_buffer_byte_size = (PRIM_BUFFER_LEN * std::mem::size_of::()) as u64; + let globals_buffer_byte_size = std::mem::size_of::() as u64; + + let prims_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Prims ubo"), + size: prim_buffer_byte_size, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let globals_uniform_buffer = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Globals ubo"), + size: globals_buffer_byte_size, + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: Some("Bind group layout"), + entries: &[ + wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: wgpu::BufferSize::new(globals_buffer_byte_size), + }, + count: None, + }, + wgpu::BindGroupLayoutEntry { + binding: 1, + visibility: wgpu::ShaderStages::VERTEX, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: wgpu::BufferSize::new(prim_buffer_byte_size), + }, + count: None, + }, + ], + }); + let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("Bind group"), + layout: &bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer( + globals_uniform_buffer.as_entire_buffer_binding(), + ), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: wgpu::BindingResource::Buffer( + prims_uniform_buffer.as_entire_buffer_binding(), + ), + }, + ], + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + label: None, + }); + + let vertex_module = device.create_shader_module(&create_vertex_module_descriptor()); + let fragment_module = device.create_shader_module(&create_fragment_module_descriptor()); + let mut render_pipeline_descriptor = create_map_render_pipeline_description( + &pipeline_layout, + create_map_vertex_state(&vertex_module), + create_map_fragment_state(&fragment_module), + sample_count, + ); + let render_pipeline = device.create_render_pipeline(&render_pipeline_descriptor); + + // TODO: this isn't what we want: we'd need the equivalent of VK_POLYGON_MODE_LINE, + // but it doesn't seem to be exposed by wgpu? + render_pipeline_descriptor.primitive.topology = wgpu::PrimitiveTopology::LineList; + + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: wgpu::TextureFormat::Bgra8UnormSrgb, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Mailbox, + }; + + surface.configure(&device, &surface_config); + + let depth_texture = Texture::create_depth_texture(&device, &surface_config, "depth_texture", sample_count); + + let multisampled_render_target = if sample_count > 1 { + Some(create_multisampled_framebuffer( + &device, + &surface_config, + sample_count, + )) + } else { + None + }; + + Self { + surface, + device, + queue, + size, + surface_config, + render_pipeline, + bind_group, + multisampled_render_target, + depth_texture, + sample_count, + fill_range, + num_instances: 1, + stroke_prim_id: 0, + fill_prim_id: 1, + scene: DEFAULT_SCENE, + vertex_uniform_buffer, + globals_uniform_buffer, + prims_uniform_buffer, + indices_uniform_buffer, + fps_meter: FPSMeter::new(), + stroke_range, + cpu_primitives + } + } + + pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + if new_size.width > 0 && new_size.height > 0 { + self.size = new_size; + self.surface_config.width = new_size.width; + self.surface_config.height = new_size.height; + self.surface.configure(&self.device, &self.surface_config); + + // Re-configure depth buffer + self.depth_texture = Texture::create_depth_texture(&self.device, &self.surface_config, "depth_texture", self.sample_count); + + // Re-configure multi-sampling buffer + self.multisampled_render_target = if self.sample_count > 1 { + Some(create_multisampled_framebuffer( + &self.device, + &self.surface_config, + self.sample_count, + )) + } else { + None + }; + } + } + + pub fn input(&mut self, event: &WindowEvent) -> bool { + let scene = &mut self.scene; + let found = match event { + WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(key), + .. + }, + .. + } => match key { + VirtualKeyCode::PageDown => { + scene.target_zoom *= 0.8; + true + } + VirtualKeyCode::PageUp => { + scene.target_zoom *= 1.25; + true + } + VirtualKeyCode::Left => { + scene.target_scroll.x -= 50.0 / scene.target_zoom; + true + } + VirtualKeyCode::Right => { + scene.target_scroll.x += 50.0 / scene.target_zoom; + true + } + VirtualKeyCode::Up => { + scene.target_scroll.y -= 50.0 / scene.target_zoom; + true + } + VirtualKeyCode::Down => { + scene.target_scroll.y += 50.0 / scene.target_zoom; + true + } + VirtualKeyCode::P => { + scene.show_points = !scene.show_points; + true + } + VirtualKeyCode::A => { + scene.target_stroke_width /= 0.8; + true + } + VirtualKeyCode::Z => { + scene.target_stroke_width *= 0.8; + true + } + _key => false + }, + _evt => false + }; + + found + } + + pub fn render(&mut self) -> Result<(), wgpu::SurfaceError> { + let frame = self.surface.get_current_texture()?; + let scene = &mut self.scene; + + let frame_view = frame + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Encoder"), + }); + + self.queue.write_buffer( + &self.globals_uniform_buffer, + 0, + bytemuck::cast_slice(&[Globals { + resolution: [ + self.size.width as f32, + self.size.height as f32, + ], + zoom: scene.zoom, + scroll_offset: scene.scroll.to_array(), + _pad: 0.0, + }]), + ); + + self.queue.write_buffer( + &self.prims_uniform_buffer, + 0, + bytemuck::cast_slice(&self.cpu_primitives), + ); + + { + // A resolve target is only supported if the attachment actually uses anti-aliasing + // So if sample_count == 1 then we must render directly to the surface's buffer + let color_attachment = if let Some(msaa_target) = &self.multisampled_render_target { + wgpu::RenderPassColorAttachment { + view: msaa_target, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::WHITE), + store: true, + }, + resolve_target: Some(&frame_view), + } + } else { + wgpu::RenderPassColorAttachment { + view: &frame_view, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::WHITE), + store: true, + }, + resolve_target: None, + } + }; + + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[color_attachment], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &self.depth_texture.view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(0.0), + store: true, + }), + stencil_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Clear(0), + store: true, + }), + }), + }); + + pass.set_pipeline(&self.render_pipeline); + pass.set_bind_group(0, &self.bind_group, &[]); + pass.set_index_buffer( + self.indices_uniform_buffer.slice(..), + wgpu::IndexFormat::Uint16, + ); + pass.set_vertex_buffer(0, self.vertex_uniform_buffer.slice(..)); + + pass.draw_indexed(self.fill_range.clone(), 0, 0..(self.num_instances as u32)); + pass.draw_indexed(self.stroke_range.clone(), 0, 0..1); + } + + self.queue.submit(Some(encoder.finish())); + frame.present(); + + Ok(()) + } + + pub fn update(&mut self) { + let scene = &mut self.scene; + let time_secs = self.fps_meter.time_secs; + + + scene.zoom += (scene.target_zoom - scene.zoom) / 3.0; + scene.scroll = scene.scroll + (scene.target_scroll - scene.scroll) / 3.0; + scene.stroke_width = + scene.stroke_width + (scene.target_stroke_width - scene.stroke_width) / 5.0; + + self.cpu_primitives[self.stroke_prim_id as usize].width = scene.stroke_width; + self.cpu_primitives[self.stroke_prim_id as usize].color = [ + (time_secs * 0.8 - 1.6).sin() * 0.1 + 0.1, + (time_secs * 0.5 - 1.6).sin() * 0.1 + 0.1, + (time_secs - 1.6).sin() * 0.1 + 0.1, + 1.0, + ]; + + for idx in 2..(self.num_instances + 1) { + self.cpu_primitives[idx as usize].translate = [ + (time_secs * 0.05 * idx as f32).sin() * (100.0 + idx as f32 * 10.0), + (time_secs * 0.1 * idx as f32).sin() * (100.0 + idx as f32 * 10.0), + ]; + } + + self.fps_meter.update_and_print() + } +} diff --git a/src/lyon_test/texture.rs b/src/lyon_test/texture.rs new file mode 100644 index 00000000..6173c02f --- /dev/null +++ b/src/lyon_test/texture.rs @@ -0,0 +1,36 @@ +pub struct Texture { + pub texture: wgpu::Texture, + pub view: wgpu::TextureView, +} + +impl Texture { + pub const DEPTH_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Depth32Float; + + pub fn create_depth_texture( + device: &wgpu::Device, + config: &wgpu::SurfaceConfiguration, + label: &str, + sample_count: u32, + ) -> Self { + + let depth_texture = device.create_texture(&wgpu::TextureDescriptor { + label: Some("Depth texture"), + size: wgpu::Extent3d { + width: config.width, + height: config.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Depth32Float, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + }); + let view = depth_texture.create_view(&wgpu::TextureViewDescriptor::default()); + + Self { + texture: depth_texture, + view, + } + } +} diff --git a/src/lyon_test/tile_downloader.rs b/src/lyon_test/tile_downloader.rs index d6aea467..3e401865 100644 --- a/src/lyon_test/tile_downloader.rs +++ b/src/lyon_test/tile_downloader.rs @@ -5,7 +5,7 @@ use std::path::Path; use vector_tile::grid::*; pub async fn download_tiles() { - for (z, x, y) in get_tile_coordinates_tutzing() { + for (z, x, y) in tile_coordinates_bavaria(&google_mercator(), 6) { let target = format!( "https://maps.tuerantuer.org/europe_germany/{z}/{x}/{y}.pbf", z = z,