291 lines
8.4 KiB
Rust
291 lines
8.4 KiB
Rust
//! GPU device initialization and management.
|
|
//!
|
|
//! This module provides the core GPU device abstraction using wgpu,
|
|
//! handling adapter selection, device creation, and queue management.
|
|
|
|
use std::sync::Arc;
|
|
use tracing::{debug, info, warn};
|
|
use wgpu::{Adapter, Device, Instance, Queue};
|
|
|
|
use super::error::{GpuError, GpuResult};
|
|
|
|
/// Information about the GPU device
|
|
#[derive(Debug, Clone)]
|
|
pub struct GpuDeviceInfo {
|
|
/// Device name
|
|
pub name: String,
|
|
/// Vendor ID
|
|
pub vendor: u32,
|
|
/// Device ID
|
|
pub device_id: u32,
|
|
/// Device type (discrete, integrated, etc.)
|
|
pub device_type: String,
|
|
/// Backend API (Vulkan, Metal, DX12, etc.)
|
|
pub backend: String,
|
|
/// Maximum buffer size
|
|
pub max_buffer_size: u64,
|
|
/// Maximum compute workgroup size per dimension
|
|
pub max_workgroup_size: [u32; 3],
|
|
/// Maximum compute workgroups per dimension
|
|
pub max_workgroups: [u32; 3],
|
|
/// Maximum storage buffers per shader stage
|
|
pub max_storage_buffers: u32,
|
|
}
|
|
|
|
/// GPU device wrapper providing access to wgpu resources
|
|
pub struct GpuDevice {
|
|
instance: Instance,
|
|
adapter: Adapter,
|
|
device: Arc<Device>,
|
|
queue: Arc<Queue>,
|
|
info: GpuDeviceInfo,
|
|
}
|
|
|
|
impl GpuDevice {
|
|
/// Create a new GPU device with default configuration.
|
|
///
|
|
/// This will:
|
|
/// 1. Create a wgpu instance with all available backends
|
|
/// 2. Request a high-performance adapter
|
|
/// 3. Create the device and queue
|
|
///
|
|
/// # Errors
|
|
///
|
|
/// Returns `GpuError::NoAdapter` if no suitable GPU is found.
|
|
/// Returns `GpuError::DeviceRequestFailed` if device creation fails.
|
|
pub async fn new() -> GpuResult<Self> {
|
|
Self::with_options(GpuDeviceOptions::default()).await
|
|
}
|
|
|
|
/// Create a new GPU device with custom options.
|
|
pub async fn with_options(options: GpuDeviceOptions) -> GpuResult<Self> {
|
|
let instance = Instance::new(wgpu::InstanceDescriptor {
|
|
backends: options.backends,
|
|
flags: wgpu::InstanceFlags::default(),
|
|
dx12_shader_compiler: wgpu::Dx12Compiler::default(),
|
|
gles_minor_version: wgpu::Gles3MinorVersion::default(),
|
|
});
|
|
|
|
debug!(
|
|
"Created wgpu instance with backends: {:?}",
|
|
options.backends
|
|
);
|
|
|
|
let adapter = instance
|
|
.request_adapter(&wgpu::RequestAdapterOptions {
|
|
power_preference: options.power_preference,
|
|
compatible_surface: None,
|
|
force_fallback_adapter: options.force_fallback,
|
|
})
|
|
.await
|
|
.ok_or(GpuError::NoAdapter)?;
|
|
|
|
let adapter_info = adapter.get_info();
|
|
info!(
|
|
"Selected GPU adapter: {} ({:?})",
|
|
adapter_info.name, adapter_info.backend
|
|
);
|
|
|
|
let limits = if options.use_downlevel_limits {
|
|
wgpu::Limits::downlevel_defaults()
|
|
} else {
|
|
wgpu::Limits::default()
|
|
};
|
|
|
|
let (device, queue) = adapter
|
|
.request_device(
|
|
&wgpu::DeviceDescriptor {
|
|
label: Some("prime-radiant-gpu"),
|
|
required_features: options.required_features,
|
|
required_limits: limits.clone(),
|
|
memory_hints: wgpu::MemoryHints::Performance,
|
|
},
|
|
None,
|
|
)
|
|
.await?;
|
|
|
|
// Set up error handling
|
|
device.on_uncaptured_error(Box::new(|error| {
|
|
warn!("Uncaptured GPU error: {:?}", error);
|
|
}));
|
|
|
|
let info = GpuDeviceInfo {
|
|
name: adapter_info.name.clone(),
|
|
vendor: adapter_info.vendor,
|
|
device_id: adapter_info.device,
|
|
device_type: format!("{:?}", adapter_info.device_type),
|
|
backend: format!("{:?}", adapter_info.backend),
|
|
max_buffer_size: limits.max_buffer_size as u64,
|
|
max_workgroup_size: [
|
|
limits.max_compute_workgroup_size_x,
|
|
limits.max_compute_workgroup_size_y,
|
|
limits.max_compute_workgroup_size_z,
|
|
],
|
|
max_workgroups: [
|
|
limits.max_compute_workgroups_per_dimension,
|
|
limits.max_compute_workgroups_per_dimension,
|
|
limits.max_compute_workgroups_per_dimension,
|
|
],
|
|
max_storage_buffers: limits.max_storage_buffers_per_shader_stage,
|
|
};
|
|
|
|
debug!("GPU device info: {:?}", info);
|
|
|
|
Ok(Self {
|
|
instance,
|
|
adapter,
|
|
device: Arc::new(device),
|
|
queue: Arc::new(queue),
|
|
info,
|
|
})
|
|
}
|
|
|
|
/// Get a reference to the wgpu device
|
|
pub fn device(&self) -> &Device {
|
|
&self.device
|
|
}
|
|
|
|
/// Get a shared reference to the wgpu device
|
|
pub fn device_arc(&self) -> Arc<Device> {
|
|
Arc::clone(&self.device)
|
|
}
|
|
|
|
/// Get a reference to the command queue
|
|
pub fn queue(&self) -> &Queue {
|
|
&self.queue
|
|
}
|
|
|
|
/// Get a shared reference to the command queue
|
|
pub fn queue_arc(&self) -> Arc<Queue> {
|
|
Arc::clone(&self.queue)
|
|
}
|
|
|
|
/// Get device information
|
|
pub fn info(&self) -> &GpuDeviceInfo {
|
|
&self.info
|
|
}
|
|
|
|
/// Get the wgpu instance
|
|
pub fn instance(&self) -> &Instance {
|
|
&self.instance
|
|
}
|
|
|
|
/// Get the wgpu adapter
|
|
pub fn adapter(&self) -> &Adapter {
|
|
&self.adapter
|
|
}
|
|
|
|
/// Check if a feature is supported
|
|
pub fn supports_feature(&self, feature: wgpu::Features) -> bool {
|
|
self.adapter.features().contains(feature)
|
|
}
|
|
|
|
/// Poll the device for completed work.
|
|
///
|
|
/// This is useful when you need to ensure GPU work has completed
|
|
/// before continuing on the CPU.
|
|
pub fn poll(&self, wait: bool) -> bool {
|
|
self.device
|
|
.poll(if wait {
|
|
wgpu::Maintain::Wait
|
|
} else {
|
|
wgpu::Maintain::Poll
|
|
})
|
|
.is_queue_empty()
|
|
}
|
|
|
|
/// Submit a command buffer to the queue
|
|
pub fn submit(&self, command_buffer: wgpu::CommandBuffer) -> wgpu::SubmissionIndex {
|
|
self.queue.submit(std::iter::once(command_buffer))
|
|
}
|
|
|
|
/// Submit multiple command buffers to the queue
|
|
pub fn submit_multiple(
|
|
&self,
|
|
command_buffers: impl IntoIterator<Item = wgpu::CommandBuffer>,
|
|
) -> wgpu::SubmissionIndex {
|
|
self.queue.submit(command_buffers)
|
|
}
|
|
}
|
|
|
|
/// Options for GPU device creation
|
|
#[derive(Debug, Clone)]
|
|
pub struct GpuDeviceOptions {
|
|
/// Backends to use (default: all)
|
|
pub backends: wgpu::Backends,
|
|
/// Power preference (default: high performance)
|
|
pub power_preference: wgpu::PowerPreference,
|
|
/// Required GPU features
|
|
pub required_features: wgpu::Features,
|
|
/// Use downlevel limits for broader compatibility
|
|
pub use_downlevel_limits: bool,
|
|
/// Force fallback adapter (software rendering)
|
|
pub force_fallback: bool,
|
|
}
|
|
|
|
impl Default for GpuDeviceOptions {
|
|
fn default() -> Self {
|
|
Self {
|
|
backends: wgpu::Backends::all(),
|
|
power_preference: wgpu::PowerPreference::HighPerformance,
|
|
required_features: wgpu::Features::empty(),
|
|
use_downlevel_limits: false,
|
|
force_fallback: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl GpuDeviceOptions {
|
|
/// Create options for low-power mode (integrated GPU preferred)
|
|
pub fn low_power() -> Self {
|
|
Self {
|
|
power_preference: wgpu::PowerPreference::LowPower,
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Create options for maximum compatibility
|
|
pub fn compatible() -> Self {
|
|
Self {
|
|
use_downlevel_limits: true,
|
|
..Default::default()
|
|
}
|
|
}
|
|
|
|
/// Create options for software fallback
|
|
pub fn software() -> Self {
|
|
Self {
|
|
force_fallback: true,
|
|
use_downlevel_limits: true,
|
|
..Default::default()
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_device_options_default() {
|
|
let options = GpuDeviceOptions::default();
|
|
assert_eq!(
|
|
options.power_preference,
|
|
wgpu::PowerPreference::HighPerformance
|
|
);
|
|
assert!(!options.force_fallback);
|
|
}
|
|
|
|
#[test]
|
|
fn test_device_options_low_power() {
|
|
let options = GpuDeviceOptions::low_power();
|
|
assert_eq!(options.power_preference, wgpu::PowerPreference::LowPower);
|
|
}
|
|
|
|
#[test]
|
|
fn test_device_options_compatible() {
|
|
let options = GpuDeviceOptions::compatible();
|
|
assert!(options.use_downlevel_limits);
|
|
}
|
|
}
|