|
|
@@ -0,0 +1,429 @@
|
|
|
+use std::iter;
|
|
|
+use rand::Rng;
|
|
|
+use wgpu::util::DeviceExt;
|
|
|
+use winit::{
|
|
|
+ event::*,
|
|
|
+ event_loop::{ControlFlow, EventLoop},
|
|
|
+ window::{Window, WindowBuilder},
|
|
|
+};
|
|
|
+
|
|
|
+use sapvi::gui::texture;
|
|
|
+
|
|
|
+#[repr(C)]
|
|
|
+#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
|
|
|
+struct Vertex {
|
|
|
+ position: [f32; 3],
|
|
|
+ tex_coords: [f32; 2],
|
|
|
+}
|
|
|
+
|
|
|
+impl Vertex {
|
|
|
+ fn desc<'a>() -> wgpu::VertexBufferLayout<'a> {
|
|
|
+ use std::mem;
|
|
|
+ wgpu::VertexBufferLayout {
|
|
|
+ array_stride: mem::size_of::<Vertex>() as wgpu::BufferAddress,
|
|
|
+ step_mode: wgpu::InputStepMode::Vertex,
|
|
|
+ attributes: &[
|
|
|
+ wgpu::VertexAttribute {
|
|
|
+ offset: 0,
|
|
|
+ shader_location: 0,
|
|
|
+ format: wgpu::VertexFormat::Float3,
|
|
|
+ },
|
|
|
+ wgpu::VertexAttribute {
|
|
|
+ offset: mem::size_of::<[f32; 3]>() as wgpu::BufferAddress,
|
|
|
+ shader_location: 1,
|
|
|
+ format: wgpu::VertexFormat::Float2,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// (-1, 1) (1, 1)
|
|
|
+// +-----------------------+
|
|
|
+// | |
|
|
|
+// | |
|
|
|
+// | |
|
|
|
+// | |
|
|
|
+// | |
|
|
|
+// | |
|
|
|
+// | |
|
|
|
+// +-----------------------+
|
|
|
+// (-1, -1) (1, -1)
|
|
|
+const VERTICES: &[Vertex] = &[
|
|
|
+ Vertex {
|
|
|
+ position: [-1.0, 1.0, 0.0],
|
|
|
+ tex_coords: [0.0, 0.0],
|
|
|
+ },
|
|
|
+ Vertex {
|
|
|
+ position: [-1.0, -1.0, 0.0],
|
|
|
+ tex_coords: [0.0, 1.0],
|
|
|
+ },
|
|
|
+ Vertex {
|
|
|
+ position: [1.0, -1.0, 0.0],
|
|
|
+ tex_coords: [1.0, 1.0],
|
|
|
+ },
|
|
|
+ Vertex {
|
|
|
+ position: [1.0, 1.0, 0.0],
|
|
|
+ tex_coords: [1.0, 0.0],
|
|
|
+ },
|
|
|
+ /*
|
|
|
+ Vertex {
|
|
|
+ position: [-0.0868241, 0.49240386, 0.0],
|
|
|
+ tex_coords: [0.4131759, 0.00759614],
|
|
|
+ }, // A
|
|
|
+ Vertex {
|
|
|
+ position: [-0.49513406, 0.06958647, 0.0],
|
|
|
+ tex_coords: [0.0048659444, 0.43041354],
|
|
|
+ }, // B
|
|
|
+ Vertex {
|
|
|
+ position: [-0.21918549, -0.44939706, 0.0],
|
|
|
+ tex_coords: [0.28081453, 0.949397057],
|
|
|
+ }, // C
|
|
|
+ Vertex {
|
|
|
+ position: [0.35966998, -0.3473291, 0.0],
|
|
|
+ tex_coords: [0.85967, 0.84732911],
|
|
|
+ }, // D
|
|
|
+ Vertex {
|
|
|
+ position: [0.44147372, 0.2347359, 0.0],
|
|
|
+ tex_coords: [0.9414737, 0.2652641],
|
|
|
+ }, // E
|
|
|
+ */
|
|
|
+];
|
|
|
+
|
|
|
+//const INDICES: &[u16] = &[0, 1, 4, 1, 2, 4, 2, 3, 4];
|
|
|
+const INDICES: &[u16] = &[0, 1, 2, 0, 2, 3];
|
|
|
+
|
|
|
+struct State {
|
|
|
+ surface: wgpu::Surface,
|
|
|
+ device: wgpu::Device,
|
|
|
+ queue: wgpu::Queue,
|
|
|
+ sc_desc: wgpu::SwapChainDescriptor,
|
|
|
+ swap_chain: wgpu::SwapChain,
|
|
|
+ size: winit::dpi::PhysicalSize<u32>,
|
|
|
+ render_pipeline: wgpu::RenderPipeline,
|
|
|
+ vertex_buffer: wgpu::Buffer,
|
|
|
+ index_buffer: wgpu::Buffer,
|
|
|
+ num_indices: u32,
|
|
|
+ #[allow(dead_code)]
|
|
|
+ diffuse_texture: texture::Texture,
|
|
|
+ diffuse_bind_group: wgpu::BindGroup,
|
|
|
+ #[allow(dead_code)]
|
|
|
+ cartoon_texture: texture::Texture,
|
|
|
+ cartoon_bind_group: wgpu::BindGroup,
|
|
|
+ is_space_pressed: bool,
|
|
|
+}
|
|
|
+
|
|
|
+impl State {
|
|
|
+ async fn new(window: &Window) -> Self {
|
|
|
+ let size = window.inner_size();
|
|
|
+
|
|
|
+ // The instance is a handle to our GPU
|
|
|
+ // BackendBit::PRIMARY => Vulkan + Metal + DX12 + Browser WebGPU
|
|
|
+ let instance = wgpu::Instance::new(wgpu::BackendBit::PRIMARY);
|
|
|
+ let surface = unsafe { instance.create_surface(window) };
|
|
|
+ let adapter = instance
|
|
|
+ .request_adapter(&wgpu::RequestAdapterOptions {
|
|
|
+ power_preference: wgpu::PowerPreference::default(),
|
|
|
+ compatible_surface: Some(&surface),
|
|
|
+ })
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+ let (device, queue) = adapter
|
|
|
+ .request_device(
|
|
|
+ &wgpu::DeviceDescriptor {
|
|
|
+ label: None,
|
|
|
+ features: wgpu::Features::empty(),
|
|
|
+ limits: wgpu::Limits::default(),
|
|
|
+ },
|
|
|
+ None, // Trace path
|
|
|
+ )
|
|
|
+ .await
|
|
|
+ .unwrap();
|
|
|
+
|
|
|
+ let sc_desc = wgpu::SwapChainDescriptor {
|
|
|
+ usage: wgpu::TextureUsage::RENDER_ATTACHMENT,
|
|
|
+ format: adapter.get_swap_chain_preferred_format(&surface),
|
|
|
+ width: size.width,
|
|
|
+ height: size.height,
|
|
|
+ present_mode: wgpu::PresentMode::Fifo,
|
|
|
+ };
|
|
|
+ let swap_chain = device.create_swap_chain(&surface, &sc_desc);
|
|
|
+
|
|
|
+ let texture_bind_group_layout =
|
|
|
+ device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
|
|
+ entries: &[
|
|
|
+ wgpu::BindGroupLayoutEntry {
|
|
|
+ binding: 0,
|
|
|
+ visibility: wgpu::ShaderStage::FRAGMENT,
|
|
|
+ ty: wgpu::BindingType::Texture {
|
|
|
+ multisampled: false,
|
|
|
+ view_dimension: wgpu::TextureViewDimension::D2,
|
|
|
+ sample_type: wgpu::TextureSampleType::Float { filterable: false },
|
|
|
+ },
|
|
|
+ count: None,
|
|
|
+ },
|
|
|
+ wgpu::BindGroupLayoutEntry {
|
|
|
+ binding: 1,
|
|
|
+ visibility: wgpu::ShaderStage::FRAGMENT,
|
|
|
+ ty: wgpu::BindingType::Sampler {
|
|
|
+ comparison: false,
|
|
|
+ filtering: true,
|
|
|
+ },
|
|
|
+ count: None,
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ label: Some("texture_bind_group_layout"),
|
|
|
+ });
|
|
|
+
|
|
|
+ let diffuse_bytes = include_bytes!("assets/absolutely-proprietary.png");
|
|
|
+ let diffuse_texture =
|
|
|
+ texture::Texture::from_bytes(&device, &queue, diffuse_bytes, "assets/absolutely-proprietary.png").unwrap();
|
|
|
+
|
|
|
+ let diffuse_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
|
+ layout: &texture_bind_group_layout,
|
|
|
+ entries: &[
|
|
|
+ wgpu::BindGroupEntry {
|
|
|
+ binding: 0,
|
|
|
+ resource: wgpu::BindingResource::TextureView(&diffuse_texture.view),
|
|
|
+ },
|
|
|
+ wgpu::BindGroupEntry {
|
|
|
+ binding: 1,
|
|
|
+ resource: wgpu::BindingResource::Sampler(&diffuse_texture.sampler),
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ label: Some("diffuse_bind_group"),
|
|
|
+ });
|
|
|
+
|
|
|
+ let cartoon_bytes = include_bytes!("assets/absolutely-proprietary2.png");
|
|
|
+ let cartoon_texture =
|
|
|
+ texture::Texture::from_bytes(&device, &queue, cartoon_bytes, "happy-tree-cartoon.png")
|
|
|
+ .unwrap();
|
|
|
+
|
|
|
+ let cartoon_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
|
|
+ layout: &texture_bind_group_layout,
|
|
|
+ entries: &[
|
|
|
+ wgpu::BindGroupEntry {
|
|
|
+ binding: 0,
|
|
|
+ resource: wgpu::BindingResource::TextureView(&cartoon_texture.view),
|
|
|
+ },
|
|
|
+ wgpu::BindGroupEntry {
|
|
|
+ binding: 1,
|
|
|
+ resource: wgpu::BindingResource::Sampler(&cartoon_texture.sampler),
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ label: Some("cartoon_bind_group"),
|
|
|
+ });
|
|
|
+
|
|
|
+ let vs_module = device.create_shader_module(&wgpu::include_spirv!("assets/shader.vert.spv"));
|
|
|
+ let fs_module = device.create_shader_module(&wgpu::include_spirv!("assets/shader.frag.spv"));
|
|
|
+
|
|
|
+ let render_pipeline_layout =
|
|
|
+ device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
|
|
|
+ label: Some("Render Pipeline Layout"),
|
|
|
+ bind_group_layouts: &[&texture_bind_group_layout],
|
|
|
+ push_constant_ranges: &[],
|
|
|
+ });
|
|
|
+
|
|
|
+ let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
|
|
|
+ label: Some("Render Pipeline"),
|
|
|
+ layout: Some(&render_pipeline_layout),
|
|
|
+ vertex: wgpu::VertexState {
|
|
|
+ module: &vs_module,
|
|
|
+ entry_point: "main",
|
|
|
+ buffers: &[Vertex::desc()],
|
|
|
+ },
|
|
|
+ fragment: Some(wgpu::FragmentState {
|
|
|
+ module: &fs_module,
|
|
|
+ entry_point: "main",
|
|
|
+ targets: &[wgpu::ColorTargetState {
|
|
|
+ format: sc_desc.format,
|
|
|
+ alpha_blend: wgpu::BlendState::REPLACE,
|
|
|
+ color_blend: wgpu::BlendState::REPLACE,
|
|
|
+ write_mask: wgpu::ColorWrite::ALL,
|
|
|
+ }],
|
|
|
+ }),
|
|
|
+ primitive: wgpu::PrimitiveState {
|
|
|
+ topology: wgpu::PrimitiveTopology::TriangleList,
|
|
|
+ strip_index_format: None,
|
|
|
+ front_face: wgpu::FrontFace::Ccw,
|
|
|
+ cull_mode: wgpu::CullMode::Back,
|
|
|
+ // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE
|
|
|
+ polygon_mode: wgpu::PolygonMode::Fill,
|
|
|
+ },
|
|
|
+ depth_stencil: None,
|
|
|
+ multisample: wgpu::MultisampleState {
|
|
|
+ count: 1,
|
|
|
+ mask: !0,
|
|
|
+ alpha_to_coverage_enabled: false,
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
|
|
+ label: Some("Vertex Buffer"),
|
|
|
+ contents: bytemuck::cast_slice(VERTICES),
|
|
|
+ usage: wgpu::BufferUsage::VERTEX,
|
|
|
+ });
|
|
|
+ let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
|
|
|
+ label: Some("Index Buffer"),
|
|
|
+ contents: bytemuck::cast_slice(INDICES),
|
|
|
+ usage: wgpu::BufferUsage::INDEX,
|
|
|
+ });
|
|
|
+ let num_indices = INDICES.len() as u32;
|
|
|
+
|
|
|
+ Self {
|
|
|
+ surface,
|
|
|
+ device,
|
|
|
+ queue,
|
|
|
+ sc_desc,
|
|
|
+ swap_chain,
|
|
|
+ render_pipeline,
|
|
|
+ vertex_buffer,
|
|
|
+ index_buffer,
|
|
|
+ num_indices,
|
|
|
+ diffuse_texture,
|
|
|
+ diffuse_bind_group,
|
|
|
+ cartoon_texture,
|
|
|
+ cartoon_bind_group,
|
|
|
+ size,
|
|
|
+ is_space_pressed: false,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn resize(&mut self, new_size: winit::dpi::PhysicalSize<u32>) {
|
|
|
+ self.size = new_size;
|
|
|
+ self.sc_desc.width = new_size.width;
|
|
|
+ self.sc_desc.height = new_size.height;
|
|
|
+ self.swap_chain = self.device.create_swap_chain(&self.surface, &self.sc_desc);
|
|
|
+ }
|
|
|
+
|
|
|
+ fn input(&mut self, event: &WindowEvent) -> bool {
|
|
|
+ match event {
|
|
|
+ WindowEvent::KeyboardInput {
|
|
|
+ input:
|
|
|
+ KeyboardInput {
|
|
|
+ state,
|
|
|
+ virtual_keycode: Some(VirtualKeyCode::Space),
|
|
|
+ ..
|
|
|
+ },
|
|
|
+ ..
|
|
|
+ } => {
|
|
|
+ self.is_space_pressed = *state == ElementState::Pressed;
|
|
|
+ true
|
|
|
+ }
|
|
|
+ _ => false,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn update(&mut self) {
|
|
|
+ if rand::thread_rng().gen_range(0, 5) == 0 {
|
|
|
+ self.is_space_pressed = !self.is_space_pressed;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn render(&mut self) -> Result<(), wgpu::SwapChainError> {
|
|
|
+ let frame = self.swap_chain.get_current_frame()?.output;
|
|
|
+
|
|
|
+ let mut encoder = self
|
|
|
+ .device
|
|
|
+ .create_command_encoder(&wgpu::CommandEncoderDescriptor {
|
|
|
+ label: Some("Render Encoder"),
|
|
|
+ });
|
|
|
+
|
|
|
+ {
|
|
|
+ let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
|
|
+ label: Some("Render Pass"),
|
|
|
+ color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
|
|
|
+ attachment: &frame.view,
|
|
|
+ resolve_target: None,
|
|
|
+ ops: wgpu::Operations {
|
|
|
+ load: wgpu::LoadOp::Clear(wgpu::Color {
|
|
|
+ r: 0.1,
|
|
|
+ g: 0.2,
|
|
|
+ b: 0.3,
|
|
|
+ a: 1.0,
|
|
|
+ }),
|
|
|
+ store: true,
|
|
|
+ },
|
|
|
+ }],
|
|
|
+ depth_stencil_attachment: None,
|
|
|
+ });
|
|
|
+
|
|
|
+ let bind_group = if self.is_space_pressed {
|
|
|
+ &self.cartoon_bind_group
|
|
|
+ } else {
|
|
|
+ &self.diffuse_bind_group
|
|
|
+ };
|
|
|
+
|
|
|
+ render_pass.set_pipeline(&self.render_pipeline);
|
|
|
+ render_pass.set_bind_group(0, bind_group, &[]);
|
|
|
+ render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
|
|
|
+ render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16);
|
|
|
+ render_pass.draw_indexed(0..self.num_indices, 0, 0..1);
|
|
|
+ }
|
|
|
+
|
|
|
+ self.queue.submit(iter::once(encoder.finish()));
|
|
|
+
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+fn main() {
|
|
|
+ env_logger::init();
|
|
|
+ let event_loop = EventLoop::new();
|
|
|
+ let window = WindowBuilder::new().build(&event_loop).unwrap();
|
|
|
+
|
|
|
+ use futures::executor::block_on;
|
|
|
+
|
|
|
+ // Since main can't be async, we're going to need to block
|
|
|
+ let mut state = block_on(State::new(&window));
|
|
|
+
|
|
|
+ event_loop.run(move |event, _, control_flow| {
|
|
|
+ match event {
|
|
|
+ Event::WindowEvent {
|
|
|
+ ref event,
|
|
|
+ window_id,
|
|
|
+ } if window_id == window.id() => {
|
|
|
+ if !state.input(event) {
|
|
|
+ match event {
|
|
|
+ WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
|
|
+ WindowEvent::KeyboardInput { input, .. } => match 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);
|
|
|
+ }
|
|
|
+ _ => {}
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Event::RedrawRequested(_) => {
|
|
|
+ state.update();
|
|
|
+ match state.render() {
|
|
|
+ Ok(_) => {}
|
|
|
+ // Recreate the swap_chain if lost
|
|
|
+ Err(wgpu::SwapChainError::Lost) => state.resize(state.size),
|
|
|
+ // The system is out of memory, we should probably quit
|
|
|
+ Err(wgpu::SwapChainError::OutOfMemory) => *control_flow = ControlFlow::Exit,
|
|
|
+ // All other errors (Outdated, Timeout) should be resolved by the next frame
|
|
|
+ Err(e) => eprintln!("{:?}", e),
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Event::MainEventsCleared => {
|
|
|
+ // RedrawRequested will only trigger once, unless we manually
|
|
|
+ // request it.
|
|
|
+ window.request_redraw();
|
|
|
+ }
|
|
|
+ _ => {}
|
|
|
+ }
|
|
|
+ });
|
|
|
+}
|
|
|
+
|