diff --git a/Cargo.lock b/Cargo.lock index ecbc939..4af9305 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,15 +18,6 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046" -[[package]] -name = "addr2line" -version = "0.24.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" -dependencies = [ - "gimli", -] - [[package]] name = "adler2" version = "2.0.0" @@ -351,21 +342,6 @@ dependencies = [ "arrayvec", ] -[[package]] -name = "backtrace" -version = "0.3.74" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" -dependencies = [ - "addr2line", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", - "windows-targets 0.52.6", -] - [[package]] name = "bit-set" version = "0.8.0" @@ -1010,12 +986,6 @@ dependencies = [ "weezl", ] -[[package]] -name = "gimli" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" - [[package]] name = "gl_generator" version = "0.14.0" @@ -1823,15 +1793,6 @@ dependencies = [ "objc2-foundation", ] -[[package]] -name = "object" -version = "0.36.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.20.2" @@ -1984,6 +1945,12 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "pollster" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f3a9f18d041e6d0e102a0a46750538147e5e8992d3b4873aaafee2520b00ce3" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -2210,12 +2177,6 @@ version = "0.8.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - [[package]] name = "rustc-hash" version = "1.1.0" @@ -2552,28 +2513,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" -[[package]] -name = "tokio" -version = "1.41.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "145f3413504347a2be84393cc8a7d2fb4d863b375909ea59f2158261aa258bbb" -dependencies = [ - "backtrace", - "pin-project-lite", - "tokio-macros", -] - -[[package]] -name = "tokio-macros" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "toml" version = "0.8.19" @@ -3585,7 +3524,7 @@ dependencies = [ "ashpd", "bytemuck", "image", - "tokio", + "pollster", "wgpu", "winit", ] diff --git a/Cargo.toml b/Cargo.toml index 032b64a..b6904b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,13 +13,13 @@ path = "src/lib.rs" unsafe_code = { level = "forbid" } [dependencies] -tokio = { version = "1.41.0", features = ["macros", "rt-multi-thread"] } ashpd = "0.9.2" winit = "0.30.5" image = "0.25.4" wgpu = "23.0.0" bytemuck = { version = "1.19.0", features = ["derive"] } anyhow = "1.0.92" +pollster = "0.4.0" [profile.dev] codegen-backend = "cranelift" diff --git a/src/app.rs b/src/app.rs index 5cde8e6..6f43361 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,246 +1,47 @@ -use image::GenericImageView; -use wgpu::util::DeviceExt; +use std::{fs::File, io::BufReader, sync::Arc}; + +use ashpd::desktop::screenshot::Screenshot; +use pollster::FutureExt; use winit::{ application::ApplicationHandler, event::{MouseScrollDelta, WindowEvent}, keyboard::{Key, NamedKey}, - window::Window, + window::{Fullscreen, Window}, }; -use crate::{texture::Texture, vertex::Vertex}; - -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], - }, -]; - -const INDICES: &[u16] = &[0, 1, 2, 2, 3, 0]; +use crate::state::State; +#[derive(Default)] pub struct App<'a> { - surface: wgpu::Surface<'a>, - device: wgpu::Device, - queue: wgpu::Queue, - config: wgpu::SurfaceConfiguration, - window_size: winit::dpi::PhysicalSize, - window: &'a Window, - render_pipeline: wgpu::RenderPipeline, - vertex_buffer: wgpu::Buffer, - index_buffer: wgpu::Buffer, - bind_group: wgpu::BindGroup, - zoom: f32, -} - -impl<'a> App<'a> { - pub async fn new(window: &'a Window, image: image::DynamicImage) -> Self { - let window_size = window.inner_size(); - - let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { - backends: wgpu::Backends::PRIMARY, - ..Default::default() - }); - - let surface = 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 { - required_features: wgpu::Features::empty(), - required_limits: wgpu::Limits::default(), - label: None, - memory_hints: Default::default(), - }, - None, - ) - .await - .unwrap(); - - let surface_caps = surface.get_capabilities(&adapter); - 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: window_size.width, - height: window_size.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!("shader.wgsl")); - - let texture = Texture::new(image, &device, &queue); - - let render_pipeline_layout = - device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &[&texture.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: &shader, - entry_point: Some("vs_main"), - buffers: &[Vertex::layout()], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - module: &shader, - entry_point: Some("fs_main"), - targets: &[Some(wgpu::ColorTargetState { - format: config.format, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, - cull_mode: Some(wgpu::Face::Back), - // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE - polygon_mode: wgpu::PolygonMode::Fill, - // Requires Features::DEPTH_CLIP_CONTROL - unclipped_depth: false, - // Requires Features::CONSERVATIVE_RASTERIZATION - conservative: false, - }, - depth_stencil: None, - multisample: wgpu::MultisampleState { - count: 1, - mask: !0, - alpha_to_coverage_enabled: false, - }, - multiview: None, - cache: None, - }); - - let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Vertex Buffer"), - contents: bytemuck::cast_slice(VERTICES), - usage: wgpu::BufferUsages::VERTEX, - }); - - let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Index Buffer"), - contents: bytemuck::cast_slice(INDICES), - usage: wgpu::BufferUsages::INDEX, - }); - - Self { - surface, - device, - queue, - config, - window_size, - window, - render_pipeline, - vertex_buffer, - index_buffer, - bind_group: texture.group, - zoom: 1.0, - } - } - - pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { - if new_size.width > 0 && new_size.height > 0 { - self.window_size = new_size; - self.config.width = new_size.width; - self.config.height = new_size.height; - self.surface.configure(&self.device, &self.config); - } - } - - pub fn key_input(&mut self, event: &winit::event::KeyEvent) -> bool { - false - } - - pub fn update(&mut self) {} - - pub fn render(&mut self) -> 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: Some("Render Encoder"), - }); - - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Render Pass"), - color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.0, - g: 0.0, - b: 0.0, - a: 1.0, - }), - store: wgpu::StoreOp::Store, - }, - })], - depth_stencil_attachment: None, - occlusion_query_set: None, - timestamp_writes: None, - }); - - render_pass.set_pipeline(&self.render_pipeline); - render_pass.set_bind_group(0, &self.bind_group, &[]); - render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); - render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); - - let num_indices = INDICES.len() as u32; - render_pass.draw_indexed(0..num_indices, 0, 0..1); - } - - // submit will accept anything that implements IntoIter - self.queue.submit(std::iter::once(encoder.finish())); - output.present(); - - Ok(()) - } + pub state: Option>, } impl<'a> ApplicationHandler for App<'a> { - fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) {} + fn resumed(&mut self, event_loop: &winit::event_loop::ActiveEventLoop) { + let response = Screenshot::request() + .interactive(false) + .modal(false) + .send() + .block_on() + .expect("failed to send screenshot request") + .response() + .expect("failed to receive screenshot response"); + + let path = response.uri().to_file_path().unwrap(); + let file = File::open(&path).unwrap(); + let buffer = BufReader::new(file); + let image = image::load(buffer, image::ImageFormat::Png).unwrap(); + + let window = event_loop + .create_window( + Window::default_attributes().with_fullscreen(Some(Fullscreen::Borderless(None))), + ) + .unwrap(); + + self.state = Some(State::new(Arc::new(window), image)); + + std::fs::remove_file(path).unwrap(); + } fn window_event( &mut self, @@ -248,23 +49,21 @@ impl<'a> ApplicationHandler for App<'a> { window_id: winit::window::WindowId, event: winit::event::WindowEvent, ) { + let state = self.state.as_mut().unwrap(); match event { WindowEvent::CloseRequested => { event_loop.exit(); } WindowEvent::RedrawRequested => { - self.window.request_redraw(); - - self.update(); - self.render().unwrap(); - - println!("draw"); + state.window.request_redraw(); + state.update(); + state.render().unwrap(); } WindowEvent::Resized(new_size) => { - self.resize(new_size); + state.resize(new_size); } WindowEvent::KeyboardInput { event, .. } => { - self.key_input(&event); + state.key_input(&event); let key = event.logical_key; if key == Key::Named(NamedKey::Escape) { event_loop.exit(); @@ -277,12 +76,12 @@ impl<'a> ApplicationHandler for App<'a> { } => match delta { MouseScrollDelta::LineDelta(x, y) => { if y > 0.0 { - self.zoom += 0.1; - self.zoom = self.zoom.clamp(0.0, 2.0); + state.zoom += 0.1; + state.zoom = state.zoom.clamp(0.0, 2.0); } else if y < 0.0 { - self.zoom -= 0.1; - self.zoom = self.zoom.clamp(0.0, 2.0); + state.zoom -= 0.1; + state.zoom = state.zoom.clamp(0.0, 2.0); } } _ => (), diff --git a/src/lib.rs b/src/lib.rs index 2da65b2..b038d6e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,45 +1,18 @@ -use std::{fs::File, io::BufReader}; - use app::App; -use ashpd::desktop::screenshot::Screenshot; -use winit::{ - event_loop::{ControlFlow, EventLoop}, - window::{Fullscreen, Window}, -}; +use winit::event_loop::{ControlFlow, EventLoop}; pub mod app; +pub mod state; pub mod texture; pub mod vertex; -pub async fn capture() -> anyhow::Result<()> { - let response = Screenshot::request() - .interactive(false) - .modal(false) - .send() - .await - .expect("failed to send screenshot request") - .response() - .expect("failed to receive screenshot response"); - - let path = response.uri().to_file_path().unwrap(); - let file = File::open(&path).unwrap(); - let buffer = BufReader::new(file); - let image = image::load(buffer, image::ImageFormat::Png).unwrap(); - +pub fn capture() -> anyhow::Result<()> { let event_loop = EventLoop::new()?; event_loop.set_control_flow(ControlFlow::Wait); - let window = event_loop - .create_window( - Window::default_attributes().with_fullscreen(Some(Fullscreen::Borderless(None))), - ) - .unwrap(); - - let mut app = App::new(&window, image).await; + let mut app = App::default(); event_loop.run_app(&mut app)?; - std::fs::remove_file(path)?; - Ok(()) } diff --git a/src/main.rs b/src/main.rs index 3219997..0c27c45 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -#[tokio::main] -async fn main() -> anyhow::Result<()> { - libzoomie::capture().await +fn main() -> anyhow::Result<()> { + libzoomie::capture() } diff --git a/src/shader.wgsl b/src/shader.wgsl index d84e2fe..3fca3b0 100644 --- a/src/shader.wgsl +++ b/src/shader.wgsl @@ -1,7 +1,7 @@ // Vertex shader struct VertexInput { - @location(0) position: vec3, + @location(0) position: vec2, @location(1) tex_coords: vec2, }; @@ -16,7 +16,7 @@ fn vs_main( ) -> VertexOutput { var out: VertexOutput; out.tex_coords = model.tex_coords; - out.position = vec4(model.position, 1.0); + out.position = vec4(model.position, 0.0, 1.0); return out; } diff --git a/src/state.rs b/src/state.rs new file mode 100644 index 0000000..d41d49a --- /dev/null +++ b/src/state.rs @@ -0,0 +1,237 @@ +use std::sync::Arc; + +use pollster::FutureExt; +use wgpu::util::DeviceExt; +use winit::window::Window; + +use crate::{texture::Texture, vertex::Vertex}; + +const VERTICES: &[Vertex] = &[ + Vertex { + position: [-1.0, 1.0], + tex_coords: [0.0, 0.0], + }, + Vertex { + position: [-1.0, -1.0], + tex_coords: [0.0, 1.0], + }, + Vertex { + position: [1.0, -1.0], + tex_coords: [1.0, 1.0], + }, + Vertex { + position: [1.0, 1.0], + tex_coords: [1.0, 0.0], + }, +]; + +const INDICES: &[u16] = &[0, 1, 2, 2, 3, 0]; + +pub struct State<'a> { + surface: wgpu::Surface<'a>, + device: wgpu::Device, + queue: wgpu::Queue, + config: wgpu::SurfaceConfiguration, + size: winit::dpi::PhysicalSize, + pub window: Arc, + render_pipeline: wgpu::RenderPipeline, + vertex_buffer: wgpu::Buffer, + index_buffer: wgpu::Buffer, + bind_group: wgpu::BindGroup, + pub zoom: f32, +} + +impl<'a> State<'a> { + pub fn new(window: Arc, image: image::DynamicImage) -> Self { + let window_size = window.inner_size(); + + let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { + backends: wgpu::Backends::PRIMARY, + ..Default::default() + }); + + let surface = instance.create_surface(window.clone()).unwrap(); + + let adapter = instance + .request_adapter(&wgpu::RequestAdapterOptions { + power_preference: wgpu::PowerPreference::default(), + compatible_surface: Some(&surface), + force_fallback_adapter: false, + }) + .block_on() + .unwrap(); + + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + required_features: wgpu::Features::empty(), + required_limits: wgpu::Limits::default(), + label: None, + memory_hints: Default::default(), + }, + None, + ) + .block_on() + .unwrap(); + + let surface_caps = surface.get_capabilities(&adapter); + 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: window_size.width, + height: window_size.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!("shader.wgsl")); + + let texture = Texture::new(image, &device, &queue); + + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[&texture.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: &shader, + entry_point: Some("vs_main"), + buffers: &[Vertex::layout()], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format: config.format, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrites::ALL, + })], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }), + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + // Setting this to anything other than Fill requires Features::NON_FILL_POLYGON_MODE + polygon_mode: wgpu::PolygonMode::Fill, + // Requires Features::DEPTH_CLIP_CONTROL + unclipped_depth: false, + // Requires Features::CONSERVATIVE_RASTERIZATION + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview: None, + cache: None, + }); + + let vertex_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Vertex Buffer"), + contents: bytemuck::cast_slice(VERTICES), + usage: wgpu::BufferUsages::VERTEX, + }); + + let index_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Index Buffer"), + contents: bytemuck::cast_slice(INDICES), + usage: wgpu::BufferUsages::INDEX, + }); + + Self { + surface, + device, + queue, + config, + size: window_size, + window, + render_pipeline, + vertex_buffer, + index_buffer, + bind_group: texture.group, + zoom: 1.0, + } + } + + pub fn resize(&mut self, new_size: winit::dpi::PhysicalSize) { + if new_size.width > 0 && new_size.height > 0 { + self.size = new_size; + self.config.width = new_size.width; + self.config.height = new_size.height; + self.surface.configure(&self.device, &self.config); + } + } + + pub fn key_input(&mut self, event: &winit::event::KeyEvent) -> bool { + false + } + + pub fn update(&mut self) {} + + pub fn render(&mut self) -> 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: Some("Render Encoder"), + }); + + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + occlusion_query_set: None, + timestamp_writes: None, + }); + + render_pass.set_pipeline(&self.render_pipeline); + render_pass.set_bind_group(0, &self.bind_group, &[]); + render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..)); + render_pass.set_index_buffer(self.index_buffer.slice(..), wgpu::IndexFormat::Uint16); + + let num_indices = INDICES.len() as u32; + render_pass.draw_indexed(0..num_indices, 0, 0..1); + } + + // submit will accept anything that implements IntoIter + self.queue.submit(std::iter::once(encoder.finish())); + output.present(); + + Ok(()) + } +} diff --git a/src/vertex.rs b/src/vertex.rs index 34a7fbe..814d448 100644 --- a/src/vertex.rs +++ b/src/vertex.rs @@ -1,7 +1,7 @@ #[repr(C)] #[derive(Debug, Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] pub struct Vertex { - pub position: [f32; 3], + pub position: [f32; 2], pub tex_coords: [f32; 2], } @@ -14,10 +14,10 @@ impl Vertex { wgpu::VertexAttribute { offset: 0, shader_location: 0, - format: wgpu::VertexFormat::Float32x3, + format: wgpu::VertexFormat::Float32x2, }, wgpu::VertexAttribute { - offset: std::mem::size_of::<[f32; 3]>() as wgpu::BufferAddress, + offset: std::mem::size_of::<[f32; 2]>() as wgpu::BufferAddress, shader_location: 1, format: wgpu::VertexFormat::Float32x2, },