use std::sync::OnceLock;
use threecrate_core::{Error, Result};
use wgpu::util::DeviceExt;
#[derive(Default)]
pub(crate) struct PipelineCache {
pub(crate) knn: OnceLock<wgpu::ComputePipeline>,
pub(crate) normals: OnceLock<wgpu::ComputePipeline>,
pub(crate) icp_match: OnceLock<wgpu::ComputePipeline>,
pub(crate) icp_match_plane: OnceLock<wgpu::ComputePipeline>,
pub(crate) radius_outlier: OnceLock<wgpu::ComputePipeline>,
}
pub struct GpuContext {
pub instance: wgpu::Instance,
pub device: wgpu::Device,
pub queue: wgpu::Queue,
pub adapter: wgpu::Adapter,
pub(crate) pipelines: PipelineCache,
}
impl GpuContext {
pub async fn new() -> Result<Self> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
flags: wgpu::InstanceFlags::default(),
..wgpu::InstanceDescriptor::new_without_display_handle_from_env()
});
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: None,
force_fallback_adapter: false,
})
.await
.map_err(|e| {
threecrate_core::Error::Gpu(format!("Failed to find suitable adapter: {:?}", e))
})?;
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
label: Some("ThreeCrate GPU Device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
..Default::default()
})
.await
.map_err(|e| threecrate_core::Error::Gpu(format!("Failed to create device: {}", e)))?;
Ok(Self::from_parts(instance, adapter, device, queue))
}
pub fn from_parts(
instance: wgpu::Instance,
adapter: wgpu::Adapter,
device: wgpu::Device,
queue: wgpu::Queue,
) -> Self {
Self {
instance,
device,
queue,
adapter,
pipelines: PipelineCache::default(),
}
}
pub(crate) fn cached_pipeline<'a>(
&self,
slot: &'a OnceLock<wgpu::ComputePipeline>,
label: &str,
source: impl FnOnce() -> String,
entry_point: &str,
) -> &'a wgpu::ComputePipeline {
slot.get_or_init(|| {
let shader = self.create_shader_module(label, &source());
self.create_compute_pipeline(label, &shader, entry_point)
})
}
pub(crate) fn read_buffer<T: bytemuck::Pod>(&self, buffer: &wgpu::Buffer) -> Result<Vec<T>> {
let staging = self.create_staging_buffer("Readback", buffer.size());
let mut encoder = self
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Readback"),
});
encoder.copy_buffer_to_buffer(buffer, 0, &staging, 0, buffer.size());
self.queue.submit(std::iter::once(encoder.finish()));
self.read_staging(&staging)
}
pub(crate) fn create_staging_buffer(&self, label: &str, size: u64) -> wgpu::Buffer {
self.create_buffer(
label,
size,
wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
)
}
pub(crate) fn read_staging<T: bytemuck::Pod>(&self, staging: &wgpu::Buffer) -> Result<Vec<T>> {
let slice = staging.slice(..);
let (sender, receiver) = std::sync::mpsc::channel();
slice.map_async(wgpu::MapMode::Read, move |result| {
let _ = sender.send(result);
});
self.device
.poll(wgpu::PollType::Wait {
submission_index: None,
timeout: None,
})
.map_err(|e| Error::Gpu(format!("GPU poll failed: {e}")))?;
receiver
.recv()
.map_err(|_| Error::Gpu("GPU readback was dropped".to_string()))?
.map_err(|e| Error::Gpu(format!("Failed to map GPU buffer: {e}")))?;
let data = bytemuck::pod_collect_to_vec(&slice.get_mapped_range());
staging.unmap();
Ok(data)
}
pub fn create_buffer_init<T: bytemuck::Pod>(
&self,
label: &str,
data: &[T],
usage: wgpu::BufferUsages,
) -> wgpu::Buffer {
self.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some(label),
contents: bytemuck::cast_slice(data),
usage,
})
}
pub fn create_buffer(&self, label: &str, size: u64, usage: wgpu::BufferUsages) -> wgpu::Buffer {
self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some(label),
size,
usage,
mapped_at_creation: false,
})
}
pub fn create_compute_pipeline(
&self,
label: &str,
shader: &wgpu::ShaderModule,
entry_point: &str,
) -> wgpu::ComputePipeline {
self.device
.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some(label),
layout: None,
module: shader,
entry_point: Some(entry_point),
compilation_options: wgpu::PipelineCompilationOptions::default(),
cache: None,
})
}
pub fn create_shader_module(&self, label: &str, source: &str) -> wgpu::ShaderModule {
self.device
.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some(label),
source: wgpu::ShaderSource::Wgsl(source.into()),
})
}
pub fn create_bind_group_layout(
&self,
label: &str,
entries: &[wgpu::BindGroupLayoutEntry],
) -> wgpu::BindGroupLayout {
self.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some(label),
entries,
})
}
pub fn create_bind_group(
&self,
label: &str,
layout: &wgpu::BindGroupLayout,
entries: &[wgpu::BindGroupEntry],
) -> wgpu::BindGroup {
self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some(label),
layout,
entries,
})
}
}