diff --git a/src/gpu.rs b/src/gpu.rs new file mode 100644 index 0000000..7ceccb9 --- /dev/null +++ b/src/gpu.rs @@ -0,0 +1,235 @@ +use std::ptr::NonNull; + +use eyre::{Context, Result}; +use raw_window_handle::{ + RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle, +}; +use wayland_client::{Proxy, protocol::wl_surface::WlSurface}; +use wgpu::util::DeviceExt; + +pub struct AppGpuState { + wgpu_instance: wgpu::Instance, + wgpu_adapter: wgpu::Adapter, + wgpu_device: wgpu::Device, + wgpu_queue: wgpu::Queue, + wgpu_render_pipeline: wgpu::RenderPipeline, + wgpu_screen_size_bind_group_layout: wgpu::BindGroupLayout, +} + +pub struct SurfaceGpuState { + wgpu_surface: wgpu::Surface<'static>, + wgpu_screen_size_buffer: wgpu::Buffer, + wgpu_screen_size_bind_group: wgpu::BindGroup, +} + +#[repr(C)] +#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] +struct ScreenSizeUniform { + size: [f32; 2], // width, height +} + +impl AppGpuState { + pub fn new() -> Result { + let wgpu_instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default()); + + let wgpu_adapter = pollster::block_on( + wgpu_instance.request_adapter(&wgpu::RequestAdapterOptions::default()), + ) + .wrap_err("failed to request adapter")?; + + let (wgpu_device, wgpu_queue) = + pollster::block_on(wgpu_adapter.request_device(&Default::default())) + .wrap_err("failed to request device")?; + + let shader = wgpu_device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); + let wgpu_screen_size_bind_group_layout = + wgpu_device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[wgpu::BindGroupLayoutEntry { + binding: 0, + visibility: wgpu::ShaderStages::FRAGMENT, + ty: wgpu::BindingType::Buffer { + ty: wgpu::BufferBindingType::Uniform, + has_dynamic_offset: false, + min_binding_size: None, + }, + count: None, + }], + }); + let render_pipeline_layout = + wgpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("Render Pipeline Layout"), + bind_group_layouts: &[&wgpu_screen_size_bind_group_layout], + immediate_size: 0, + }); + + let wgpu_render_pipeline = + wgpu_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: &[], + compilation_options: wgpu::PipelineCompilationOptions::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + targets: &[Some(wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba8UnormSrgb, + 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), + polygon_mode: wgpu::PolygonMode::Fill, + unclipped_depth: false, + conservative: false, + }, + depth_stencil: None, + multisample: wgpu::MultisampleState { + count: 1, + mask: !0, + alpha_to_coverage_enabled: false, + }, + multiview_mask: None, + cache: None, + }); + + Ok(Self { + wgpu_instance, + wgpu_adapter, + wgpu_device, + wgpu_queue, + wgpu_render_pipeline, + wgpu_screen_size_bind_group_layout, + }) + } +} + +impl SurfaceGpuState { + pub fn new( + gpu_state: &AppGpuState, + wayland_backend: &wayland_backend::client::Backend, + wl_surface: &WlSurface, + ) -> Result { + let wgpu_surface = unsafe { + gpu_state + .wgpu_instance + .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle { + raw_display_handle: RawDisplayHandle::Wayland(WaylandDisplayHandle::new( + NonNull::new(wayland_backend.display_ptr().cast()).unwrap(), + )), + raw_window_handle: RawWindowHandle::Wayland(WaylandWindowHandle::new( + NonNull::new(wl_surface.id().as_ptr().cast()).unwrap(), + )), + }) + } + .wrap_err("failed to create wgpu surface")?; + + let wgpu_screen_size_buffer = + gpu_state + .wgpu_device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Screen Size Uniform Buffer"), + contents: bytemuck::bytes_of(&ScreenSizeUniform { size: [0.0, 0.0] }), + usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, + }); + + let wgpu_screen_size_bind_group = + gpu_state + .wgpu_device + .create_bind_group(&wgpu::BindGroupDescriptor { + layout: &gpu_state.wgpu_screen_size_bind_group_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { + buffer: &wgpu_screen_size_buffer, + offset: 0, + size: None, + }), + }], + label: Some("screen_size_bind_group"), + }); + + Ok(Self { + wgpu_surface, + wgpu_screen_size_buffer, + wgpu_screen_size_bind_group, + }) + } + + pub fn resize(&self, gpu_state: &AppGpuState, width: u32, height: u32) { + gpu_state.wgpu_queue.write_buffer( + &self.wgpu_screen_size_buffer, + 0, + bytemuck::bytes_of(&ScreenSizeUniform { + size: [width as f32, height as f32], + }), + ); + + let cap = self.wgpu_surface.get_capabilities(&gpu_state.wgpu_adapter); + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format: cap.formats[0], + view_formats: vec![cap.formats[0]], + alpha_mode: wgpu::CompositeAlphaMode::Auto, + width, + height, + desired_maximum_frame_latency: 2, + // Wayland is inherently a mailbox system. + present_mode: wgpu::PresentMode::Mailbox, + }; + self.wgpu_surface + .configure(&gpu_state.wgpu_device, &surface_config); + + let surface_texture = self + .wgpu_surface + .get_current_texture() + .expect("failed to acquire next swapchain texture"); + let texture_view: wgpu::TextureView = surface_texture + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + + let mut encoder = gpu_state + .wgpu_device + .create_command_encoder(&Default::default()); + { + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("Render Pass"), + color_attachments: &[ + // This is what @location(0) in the fragment shader targets + Some(wgpu::RenderPassColorAttachment { + view: &texture_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color { + r: 0.1, + g: 0.5, + b: 0.3, + a: 1.0, + }), + store: wgpu::StoreOp::Store, + }, + depth_slice: None, + }), + ], + depth_stencil_attachment: None, + ..Default::default() + }); + + render_pass.set_pipeline(&gpu_state.wgpu_render_pipeline); + render_pass.set_bind_group(0, Some(&self.wgpu_screen_size_bind_group), &[]); + render_pass.draw(0..6, 0..1); + } + + gpu_state.wgpu_queue.submit(Some(encoder.finish())); + surface_texture.present(); + } +} diff --git a/src/main.rs b/src/main.rs index 48dfa9b..8112c73 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,8 @@ mod desktop; +mod gpu; use std::{ collections::HashMap, - ptr::NonNull, time::{Duration, Instant}, }; @@ -10,9 +10,6 @@ use eyre::{Context, Result, bail, eyre}; use freedesktop_file_parser::EntryType; use log::{error, info, warn}; use palette::{FromColor, IntoColor, Oklab}; -use raw_window_handle::{ - RawDisplayHandle, RawWindowHandle, WaylandDisplayHandle, WaylandWindowHandle, -}; use smithay_client_toolkit::{ compositor::{CompositorHandler, CompositorState}, output::{OutputHandler, OutputState}, @@ -32,16 +29,15 @@ use smithay_client_toolkit::{ shm::{Shm, ShmHandler}, }; use wayland_client::{ - Connection, Proxy, QueueHandle, + Connection, QueueHandle, globals::registry_queue_init, - protocol::{ - wl_buffer, wl_output::WlOutput, wl_pointer::WlPointer, wl_seat::WlSeat, - wl_surface::WlSurface, - }, + protocol::{wl_buffer, wl_output::WlOutput, wl_pointer::WlPointer, wl_seat::WlSeat}, }; -use wgpu::util::DeviceExt; -use crate::desktop::DesktopEntries; +use crate::{ + desktop::DesktopEntries, + gpu::{AppGpuState, SurfaceGpuState}, +}; fn main() -> Result<()> { env_logger::builder() @@ -63,82 +59,6 @@ fn main() -> Result<()> { let mut event_loop: EventLoop = EventLoop::try_new().wrap_err("creating event loop")?; let qh: &QueueHandle = &event_queue.handle(); - let wgpu_instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::default()); - - let wgpu_adapter = - pollster::block_on(wgpu_instance.request_adapter(&wgpu::RequestAdapterOptions::default())) - .wrap_err("failed to request adapter")?; - - let (wgpu_device, wgpu_queue) = - pollster::block_on(wgpu_adapter.request_device(&Default::default())) - .wrap_err("failed to request device")?; - - let shader = wgpu_device.create_shader_module(wgpu::include_wgsl!("shader.wgsl")); - let wgpu_screen_size_bind_group_layout = - wgpu_device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { - label: None, - entries: &[wgpu::BindGroupLayoutEntry { - binding: 0, - visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Buffer { - ty: wgpu::BufferBindingType::Uniform, - has_dynamic_offset: false, - min_binding_size: None, - }, - count: None, - }], - }); - let render_pipeline_layout = - wgpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { - label: Some("Render Pipeline Layout"), - bind_group_layouts: &[&wgpu_screen_size_bind_group_layout], - immediate_size: 0, - }); - - let wgpu_render_pipeline = - wgpu_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"), // 1. - buffers: &[], // 2. - compilation_options: wgpu::PipelineCompilationOptions::default(), - }, - fragment: Some(wgpu::FragmentState { - // 3. - module: &shader, - entry_point: Some("fs_main"), - targets: &[Some(wgpu::ColorTargetState { - // 4. - format: wgpu::TextureFormat::Rgba8UnormSrgb, - blend: Some(wgpu::BlendState::REPLACE), - write_mask: wgpu::ColorWrites::ALL, - })], - compilation_options: wgpu::PipelineCompilationOptions::default(), - }), - primitive: wgpu::PrimitiveState { - topology: wgpu::PrimitiveTopology::TriangleList, // 1. - strip_index_format: None, - front_face: wgpu::FrontFace::Ccw, // 2. - 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, // 1. - multisample: wgpu::MultisampleState { - count: 1, // 2. - mask: !0, // 3. - alpha_to_coverage_enabled: false, // 4. - }, - multiview_mask: None, // 5. - cache: None, // 6. - }); - let mut app = App { conn: conn.clone(), registry_state: RegistryState::new(&globals), @@ -150,16 +70,11 @@ fn main() -> Result<()> { shm: Shm::bind(&globals, qh).wrap_err("failed to bind shm")?, seat_state: SeatState::new(&globals, qh), + gpu: AppGpuState::new()?, + desktop_files, pointers: HashMap::new(), layer_surfaces: Vec::new(), - - wgpu_instance, - wgpu_adapter, - wgpu_device, - wgpu_queue, - wgpu_render_pipeline, - wgpu_screen_size_bind_group_layout }; WaylandSource::new(conn.clone(), event_queue) @@ -185,33 +100,20 @@ struct App { shm: Shm, seat_state: SeatState, + gpu: AppGpuState, + desktop_files: DesktopEntries, pointers: HashMap, layer_surfaces: Vec, - - wgpu_instance: wgpu::Instance, - wgpu_adapter: wgpu::Adapter, - wgpu_device: wgpu::Device, - wgpu_queue: wgpu::Queue, - wgpu_render_pipeline: wgpu::RenderPipeline, - wgpu_screen_size_bind_group_layout: wgpu::BindGroupLayout, } struct OutputSurface { // must be first to be dropped before the Wayland surface - wgpu_surface: wgpu::Surface<'static>, + gpu: SurfaceGpuState, output: WlOutput, layer_surface: LayerSurface, width: u32, height: u32, - wgpu_screen_size_buffer: wgpu::Buffer, - wgpu_screen_size_bind_group: wgpu::BindGroup, -} - -#[repr(C)] -#[derive(Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] -struct ScreenSizeUniform { - size: [f32; 2], // width, height } impl ProvidesRegistryState for App { @@ -255,39 +157,14 @@ impl OutputHandler for App { layer_surface.set_keyboard_interactivity(KeyboardInteractivity::None); layer_surface.wl_surface().commit(); - let wgpu_screen_size_buffer = - self.wgpu_device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("Screen Size Uniform Buffer"), - contents: bytemuck::bytes_of(&ScreenSizeUniform { size: [0.0, 0.0] }), - usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, - }); - - let wgpu_screen_size_bind_group = - self.wgpu_device - .create_bind_group(&wgpu::BindGroupDescriptor { - layout: &self.wgpu_screen_size_bind_group_layout, - entries: &[wgpu::BindGroupEntry { - binding: 0, - resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding { - buffer: &wgpu_screen_size_buffer, - offset: 0, - size: None, - }), - }], - label: Some("screen_size_bind_group"), - }); - - match setup_wgpu_surface(self, &surface) { - Ok(wgu_surface) => { + match SurfaceGpuState::new(&self.gpu, &self.conn.backend(), &surface) { + Ok(gpu_state) => { self.layer_surfaces.push(OutputSurface { - wgpu_surface: wgu_surface, + gpu: gpu_state, output, layer_surface, width: 0, height: 0, - wgpu_screen_size_buffer, - wgpu_screen_size_bind_group, }); } Err(err) => error!( @@ -330,23 +207,6 @@ impl OutputHandler for App { } } -fn setup_wgpu_surface(app: &App, surface: &WlSurface) -> Result> { - let wgu_surface = unsafe { - app.wgpu_instance - .create_surface_unsafe(wgpu::SurfaceTargetUnsafe::RawHandle { - raw_display_handle: RawDisplayHandle::Wayland(WaylandDisplayHandle::new( - NonNull::new(app.conn.backend().display_ptr().cast()).unwrap(), - )), - raw_window_handle: RawWindowHandle::Wayland(WaylandWindowHandle::new( - NonNull::new(surface.id().as_ptr().cast()).unwrap(), - )), - }) - } - .wrap_err("failed to create wgpu surface")?; - - Ok(wgu_surface) -} - impl CompositorHandler for App { fn scale_factor_changed( &mut self, @@ -432,71 +292,7 @@ impl LayerShellHandler for App { surface.width = width; surface.height = height; - self.wgpu_queue.write_buffer( - &surface.wgpu_screen_size_buffer, - 0, - bytemuck::bytes_of(&ScreenSizeUniform { - size: [width as f32, height as f32], - }), - ); - - let cap = surface.wgpu_surface.get_capabilities(&self.wgpu_adapter); - let surface_config = wgpu::SurfaceConfiguration { - usage: wgpu::TextureUsages::RENDER_ATTACHMENT, - format: cap.formats[0], - view_formats: vec![cap.formats[0]], - alpha_mode: wgpu::CompositeAlphaMode::Auto, - width, - height, - desired_maximum_frame_latency: 2, - // Wayland is inherently a mailbox system. - present_mode: wgpu::PresentMode::Mailbox, - }; - surface - .wgpu_surface - .configure(&self.wgpu_device, &surface_config); - - let surface_texture = surface - .wgpu_surface - .get_current_texture() - .expect("failed to acquire next swapchain texture"); - let texture_view: wgpu::TextureView = surface_texture - .texture - .create_view(&wgpu::TextureViewDescriptor::default()); - - let mut encoder = self.wgpu_device.create_command_encoder(&Default::default()); - { - let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { - label: Some("Render Pass"), - color_attachments: &[ - // This is what @location(0) in the fragment shader targets - Some(wgpu::RenderPassColorAttachment { - view: &texture_view, - resolve_target: None, - ops: wgpu::Operations { - load: wgpu::LoadOp::Clear(wgpu::Color { - r: 0.1, - g: 0.5, - b: 0.3, - a: 1.0, - }), - store: wgpu::StoreOp::Store, - }, - depth_slice: None, - }), - ], - depth_stencil_attachment: None, - ..Default::default() - }); - - // NEW! - render_pass.set_pipeline(&self.wgpu_render_pipeline); // 2. - render_pass.set_bind_group(0, Some(&surface.wgpu_screen_size_bind_group), &[]); - render_pass.draw(0..6, 0..1); // 3. - } - - self.wgpu_queue.submit(Some(encoder.finish())); - surface_texture.present(); + surface.gpu.resize(&self.gpu, width, height); } }