use crate::core::error::PlottingError;
use crate::render::gpu::GpuConfig;
use std::sync::Arc;
use wgpu::util::DeviceExt;
pub struct GpuDevice {
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
adapter: wgpu::Adapter,
info: GpuDeviceInfo,
}
#[derive(Debug, Clone)]
pub struct GpuDeviceInfo {
pub name: String,
pub vendor: String,
pub device_type: wgpu::DeviceType,
pub backend: wgpu::Backend,
pub driver_name: String,
pub driver_info: String,
pub features: wgpu::Features,
pub limits: wgpu::Limits,
}
pub struct DeviceSelector {
power_preference: wgpu::PowerPreference,
required_features: wgpu::Features,
required_limits: wgpu::Limits,
prefer_integrated: bool,
prefer_discrete: bool,
}
impl Default for DeviceSelector {
fn default() -> Self {
Self {
power_preference: wgpu::PowerPreference::HighPerformance,
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
prefer_integrated: false,
prefer_discrete: true, }
}
}
impl DeviceSelector {
pub fn new() -> Self {
Self::default()
}
pub fn power_preference(mut self, preference: wgpu::PowerPreference) -> Self {
self.power_preference = preference;
self
}
pub fn require_features(mut self, features: wgpu::Features) -> Self {
self.required_features = features;
self
}
pub fn require_limits(mut self, limits: wgpu::Limits) -> Self {
self.required_limits = limits;
self
}
pub fn prefer_integrated(mut self) -> Self {
self.prefer_integrated = true;
self.prefer_discrete = false;
self
}
pub fn prefer_discrete(mut self) -> Self {
self.prefer_discrete = true;
self.prefer_integrated = false;
self
}
pub async fn select_adapter(
&self,
instance: &wgpu::Instance,
) -> Result<wgpu::Adapter, PlottingError> {
let adapters: Vec<_> = instance.enumerate_adapters(wgpu::Backends::all()).await;
if adapters.is_empty() {
return Err(PlottingError::GpuNotAvailable(
"No GPU adapters found".to_string(),
));
}
let mut scored_adapters: Vec<_> = adapters
.into_iter()
.map(|adapter| {
let info = adapter.get_info();
let score = self.score_adapter(&adapter, &info);
(adapter, info, score)
})
.collect();
scored_adapters.sort_by(|a, b| b.2.cmp(&a.2));
scored_adapters
.into_iter()
.next()
.map(|(adapter, _, _)| adapter)
.ok_or_else(|| {
PlottingError::GpuNotAvailable("No suitable GPU adapter found".to_string())
})
}
fn score_adapter(&self, adapter: &wgpu::Adapter, info: &wgpu::AdapterInfo) -> i32 {
let mut score = 0;
match info.device_type {
wgpu::DeviceType::DiscreteGpu if self.prefer_discrete => score += 100,
wgpu::DeviceType::IntegratedGpu if self.prefer_integrated => score += 100,
wgpu::DeviceType::DiscreteGpu => score += 50,
wgpu::DeviceType::IntegratedGpu => score += 30,
wgpu::DeviceType::VirtualGpu => score += 10,
wgpu::DeviceType::Cpu => score += 1,
wgpu::DeviceType::Other => score += 5,
}
match info.backend {
#[cfg(target_os = "windows")]
wgpu::Backend::Dx12 => score += 20,
#[cfg(target_os = "macos")]
wgpu::Backend::Metal => score += 20,
#[cfg(target_os = "linux")]
wgpu::Backend::Vulkan => score += 20,
wgpu::Backend::Vulkan => score += 15,
wgpu::Backend::Dx12 => score += 15,
wgpu::Backend::Metal => score += 15,
wgpu::Backend::Gl => score += 5,
wgpu::Backend::BrowserWebGpu => score += 5,
_ => score += 1,
}
let features = adapter.features();
if !features.contains(self.required_features) {
return -1000; }
if features.contains(wgpu::Features::empty()) {
score += 30;
}
if features.contains(wgpu::Features::STORAGE_RESOURCE_BINDING_ARRAY) {
score += 20;
}
if features.contains(wgpu::Features::TIMESTAMP_QUERY) {
score += 10;
}
if features.contains(wgpu::Features::PIPELINE_STATISTICS_QUERY) {
score += 10;
}
let limits = adapter.limits();
if limits.max_texture_dimension_2d >= 16384 {
score += 20;
} else if limits.max_texture_dimension_2d >= 8192 {
score += 10;
} else if limits.max_texture_dimension_2d < 4096 {
return -1000; }
if limits.max_buffer_size >= 1024 * 1024 * 1024 {
score += 15; } else if limits.max_buffer_size >= 512 * 1024 * 1024 {
score += 10; } else if limits.max_buffer_size < 256 * 1024 * 1024 {
return -1000; }
score
}
}
impl GpuDevice {
pub async fn new(instance: &wgpu::Instance, config: &GpuConfig) -> Result<Self, PlottingError> {
let selector = DeviceSelector::default()
.power_preference(config.power_preference)
.require_features(config.required_features)
.require_limits(config.required_limits.clone());
let adapter = selector.select_adapter(instance).await?;
Self::from_adapter(adapter, config).await
}
pub async fn from_adapter(
adapter: wgpu::Adapter,
config: &GpuConfig,
) -> Result<Self, PlottingError> {
let adapter_info = adapter.get_info();
log::info!(
"Selected GPU: {} ({:?} on {:?})",
adapter_info.name,
adapter_info.device_type,
adapter_info.backend
);
let (device, queue) = adapter
.request_device(&wgpu::DeviceDescriptor {
label: Some("Plotting GPU Device"),
required_features: config.required_features,
required_limits: config.required_limits.clone(),
experimental_features: wgpu::ExperimentalFeatures::disabled(),
memory_hints: wgpu::MemoryHints::Performance,
trace: wgpu::Trace::Off,
})
.await
.map_err(|e| PlottingError::GpuInitError {
backend: format!("{:?}", adapter_info.backend),
error: e.to_string(),
})?;
device.on_uncaptured_error(Arc::new(|error| {
log::error!("GPU Error: {}", error);
}));
let info = GpuDeviceInfo {
name: adapter_info.name.clone(),
vendor: format!("{:?}", adapter_info.vendor),
device_type: adapter_info.device_type,
backend: adapter_info.backend,
driver_name: adapter_info.driver.clone(),
driver_info: adapter_info.driver_info.clone(),
features: device.features(),
limits: device.limits(),
};
log::info!("GPU device created successfully");
log::debug!("Device features: {:?}", device.features());
log::debug!("Device limits: {:?}", device.limits());
Ok(Self {
device: Arc::new(device),
queue: Arc::new(queue),
adapter,
info,
})
}
pub fn device(&self) -> &Arc<wgpu::Device> {
&self.device
}
pub fn queue(&self) -> &Arc<wgpu::Queue> {
&self.queue
}
pub fn adapter(&self) -> &wgpu::Adapter {
&self.adapter
}
pub fn info(&self) -> &GpuDeviceInfo {
&self.info
}
pub fn features(&self) -> wgpu::Features {
self.device.features()
}
pub fn limits(&self) -> wgpu::Limits {
self.device.limits()
}
pub fn is_valid(&self) -> bool {
true
}
pub fn create_buffer_init(&self, desc: &wgpu::util::BufferInitDescriptor) -> wgpu::Buffer {
self.device.create_buffer_init(desc)
}
pub fn create_texture(&self, desc: &wgpu::TextureDescriptor) -> wgpu::Texture {
self.device.create_texture(desc)
}
pub fn create_render_pipeline(
&self,
desc: &wgpu::RenderPipelineDescriptor,
) -> wgpu::RenderPipeline {
self.device.create_render_pipeline(desc)
}
pub fn create_compute_pipeline(
&self,
desc: &wgpu::ComputePipelineDescriptor,
) -> wgpu::ComputePipeline {
self.device.create_compute_pipeline(desc)
}
pub fn create_shader_module(&self, desc: wgpu::ShaderModuleDescriptor) -> wgpu::ShaderModule {
self.device.create_shader_module(desc)
}
pub fn create_bind_group_layout(
&self,
desc: &wgpu::BindGroupLayoutDescriptor,
) -> wgpu::BindGroupLayout {
self.device.create_bind_group_layout(desc)
}
pub fn create_bind_group(&self, desc: &wgpu::BindGroupDescriptor) -> wgpu::BindGroup {
self.device.create_bind_group(desc)
}
pub fn submit<I: IntoIterator<Item = wgpu::CommandBuffer>>(
&self,
command_buffers: I,
) -> wgpu::SubmissionIndex {
self.queue.submit(command_buffers)
}
pub fn write_buffer(&self, buffer: &wgpu::Buffer, offset: wgpu::BufferAddress, data: &[u8]) {
self.queue.write_buffer(buffer, offset, data);
}
pub fn write_texture(
&self,
texture: wgpu::TexelCopyTextureInfo<'_>,
data: &[u8],
data_layout: wgpu::TexelCopyBufferLayout,
size: wgpu::Extent3d,
) {
self.queue.write_texture(texture, data, data_layout, size);
}
pub fn poll(&self, poll_type: wgpu::PollType) -> Result<wgpu::PollStatus, wgpu::PollError> {
self.device.poll(poll_type)
}
pub fn get_memory_limits(&self) -> (u64, u64) {
let limits = self.device.limits();
(
limits.max_buffer_size,
limits.max_texture_dimension_2d as u64,
)
}
pub fn debug_info(&self) -> String {
format!(
"GPU Device: {}\n\
Vendor: {}\n\
Type: {:?}\n\
Backend: {:?}\n\
Driver: {} ({})\n\
Features: {:?}\n\
Max Texture Size: {}x{}\n\
Max Buffer Size: {} MB",
self.info.name,
self.info.vendor,
self.info.device_type,
self.info.backend,
self.info.driver_name,
self.info.driver_info,
self.info.features,
self.info.limits.max_texture_dimension_2d,
self.info.limits.max_texture_dimension_2d,
self.info.limits.max_buffer_size / (1024 * 1024)
)
}
}
impl std::ops::Deref for GpuDevice {
type Target = wgpu::Device;
fn deref(&self) -> &Self::Target {
&self.device
}
}