This commit is contained in:
nora 2026-01-01 03:01:10 +01:00
parent 910568432c
commit 45e2971d69
2 changed files with 251 additions and 220 deletions

235
src/gpu.rs Normal file
View file

@ -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<Self> {
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<Self> {
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();
}
}

View file

@ -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<App> = EventLoop::try_new().wrap_err("creating event loop")?;
let qh: &QueueHandle<App> = &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<WlSeat, WlPointer>,
layer_surfaces: Vec<OutputSurface>,
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<wgpu::Surface<'static>> {
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);
}
}