1use std::sync::OnceLock;
4use threecrate_core::{Error, Result};
5use wgpu::util::DeviceExt;
6
7#[derive(Default)]
11pub(crate) struct PipelineCache {
12 pub(crate) knn: OnceLock<wgpu::ComputePipeline>,
13 pub(crate) normals: OnceLock<wgpu::ComputePipeline>,
14 pub(crate) icp_match: OnceLock<wgpu::ComputePipeline>,
15 pub(crate) icp_match_plane: OnceLock<wgpu::ComputePipeline>,
16 pub(crate) radius_outlier: OnceLock<wgpu::ComputePipeline>,
17}
18
19pub struct GpuContext {
21 pub instance: wgpu::Instance,
22 pub device: wgpu::Device,
23 pub queue: wgpu::Queue,
24 pub adapter: wgpu::Adapter,
25 pub(crate) pipelines: PipelineCache,
26}
27
28impl GpuContext {
29 pub async fn new() -> Result<Self> {
31 let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
32 backends: wgpu::Backends::all(),
33 flags: wgpu::InstanceFlags::default(),
34 ..wgpu::InstanceDescriptor::new_without_display_handle_from_env()
35 });
36
37 let adapter = instance
38 .request_adapter(&wgpu::RequestAdapterOptions {
39 power_preference: wgpu::PowerPreference::HighPerformance,
40 compatible_surface: None,
41 force_fallback_adapter: false,
42 })
43 .await
44 .map_err(|e| {
45 threecrate_core::Error::Gpu(format!("Failed to find suitable adapter: {:?}", e))
46 })?;
47
48 let (device, queue) = adapter
49 .request_device(&wgpu::DeviceDescriptor {
50 label: Some("ThreeCrate GPU Device"),
51 required_features: wgpu::Features::empty(),
52 required_limits: wgpu::Limits::default(),
53 ..Default::default()
54 })
55 .await
56 .map_err(|e| threecrate_core::Error::Gpu(format!("Failed to create device: {}", e)))?;
57
58 Ok(Self::from_parts(instance, adapter, device, queue))
59 }
60
61 pub fn from_parts(
64 instance: wgpu::Instance,
65 adapter: wgpu::Adapter,
66 device: wgpu::Device,
67 queue: wgpu::Queue,
68 ) -> Self {
69 Self {
70 instance,
71 device,
72 queue,
73 adapter,
74 pipelines: PipelineCache::default(),
75 }
76 }
77
78 pub(crate) fn cached_pipeline<'a>(
81 &self,
82 slot: &'a OnceLock<wgpu::ComputePipeline>,
83 label: &str,
84 source: impl FnOnce() -> String,
85 entry_point: &str,
86 ) -> &'a wgpu::ComputePipeline {
87 slot.get_or_init(|| {
88 let shader = self.create_shader_module(label, &source());
89 self.create_compute_pipeline(label, &shader, entry_point)
90 })
91 }
92
93 pub(crate) fn read_buffer<T: bytemuck::Pod>(&self, buffer: &wgpu::Buffer) -> Result<Vec<T>> {
98 let staging = self.create_staging_buffer("Readback", buffer.size());
99 let mut encoder = self
100 .device
101 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
102 label: Some("Readback"),
103 });
104 encoder.copy_buffer_to_buffer(buffer, 0, &staging, 0, buffer.size());
105 self.queue.submit(std::iter::once(encoder.finish()));
106 self.read_staging(&staging)
107 }
108
109 pub(crate) fn create_staging_buffer(&self, label: &str, size: u64) -> wgpu::Buffer {
111 self.create_buffer(
112 label,
113 size,
114 wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
115 )
116 }
117
118 pub(crate) fn read_staging<T: bytemuck::Pod>(&self, staging: &wgpu::Buffer) -> Result<Vec<T>> {
121 let slice = staging.slice(..);
122 let (sender, receiver) = std::sync::mpsc::channel();
123 slice.map_async(wgpu::MapMode::Read, move |result| {
124 let _ = sender.send(result);
125 });
126 self.device
127 .poll(wgpu::PollType::Wait {
128 submission_index: None,
129 timeout: None,
130 })
131 .map_err(|e| Error::Gpu(format!("GPU poll failed: {e}")))?;
132 receiver
133 .recv()
134 .map_err(|_| Error::Gpu("GPU readback was dropped".to_string()))?
135 .map_err(|e| Error::Gpu(format!("Failed to map GPU buffer: {e}")))?;
136
137 let data = bytemuck::pod_collect_to_vec(&slice.get_mapped_range());
140 staging.unmap();
141 Ok(data)
142 }
143
144 pub fn create_buffer_init<T: bytemuck::Pod>(
146 &self,
147 label: &str,
148 data: &[T],
149 usage: wgpu::BufferUsages,
150 ) -> wgpu::Buffer {
151 self.device
152 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
153 label: Some(label),
154 contents: bytemuck::cast_slice(data),
155 usage,
156 })
157 }
158
159 pub fn create_buffer(&self, label: &str, size: u64, usage: wgpu::BufferUsages) -> wgpu::Buffer {
161 self.device.create_buffer(&wgpu::BufferDescriptor {
162 label: Some(label),
163 size,
164 usage,
165 mapped_at_creation: false,
166 })
167 }
168
169 pub fn create_compute_pipeline(
171 &self,
172 label: &str,
173 shader: &wgpu::ShaderModule,
174 entry_point: &str,
175 ) -> wgpu::ComputePipeline {
176 self.device
177 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
178 label: Some(label),
179 layout: None,
180 module: shader,
181 entry_point: Some(entry_point),
182 compilation_options: wgpu::PipelineCompilationOptions::default(),
183 cache: None,
184 })
185 }
186
187 pub fn create_shader_module(&self, label: &str, source: &str) -> wgpu::ShaderModule {
189 self.device
190 .create_shader_module(wgpu::ShaderModuleDescriptor {
191 label: Some(label),
192 source: wgpu::ShaderSource::Wgsl(source.into()),
193 })
194 }
195
196 pub fn create_bind_group_layout(
198 &self,
199 label: &str,
200 entries: &[wgpu::BindGroupLayoutEntry],
201 ) -> wgpu::BindGroupLayout {
202 self.device
203 .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
204 label: Some(label),
205 entries,
206 })
207 }
208
209 pub fn create_bind_group(
211 &self,
212 label: &str,
213 layout: &wgpu::BindGroupLayout,
214 entries: &[wgpu::BindGroupEntry],
215 ) -> wgpu::BindGroup {
216 self.device.create_bind_group(&wgpu::BindGroupDescriptor {
217 label: Some(label),
218 layout,
219 entries,
220 })
221 }
222}