use std::sync::Arc;
use sable_platform::prelude::Window;
use tracing::{debug, info};
use wgpu::{
Adapter, Device, DeviceDescriptor, Features, Instance, InstanceDescriptor, Limits, PresentMode,
Queue, Surface, SurfaceCapabilities, SurfaceConfiguration, TextureFormat, TextureUsages,
};
use crate::Result;
pub struct GpuContext {
#[allow(dead_code)]
instance: Instance,
adapter: Adapter,
device: Arc<Device>,
queue: Arc<Queue>,
surface: Surface<'static>,
surface_config: SurfaceConfiguration,
#[allow(dead_code)]
surface_capabilities: SurfaceCapabilities,
}
impl GpuContext {
pub async fn new(window: &Window) -> Result<Self> {
let instance = Instance::new(&InstanceDescriptor {
backends: wgpu::Backends::all(),
..Default::default()
});
let surface = unsafe {
let target = wgpu::SurfaceTargetUnsafe::from_window(&window.winit_window())?;
instance.create_surface_unsafe(target)?
};
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
force_fallback_adapter: false,
compatible_surface: Some(&surface),
})
.await?;
info!(
"Selected GPU adapter: {} ({:?})",
adapter.get_info().name,
adapter.get_info().backend
);
debug!("Adapter features: {:?}", adapter.features());
debug!("Adapter limits: {:?}", adapter.limits());
let (device, queue) = adapter
.request_device(&DeviceDescriptor {
label: Some("Sable GPU Device"),
required_features: Features::empty(),
required_limits: Limits::default(),
memory_hints: wgpu::MemoryHints::Performance,
trace: wgpu::Trace::Off,
})
.await?;
let device = Arc::new(device);
let queue = Arc::new(queue);
let surface_capabilities = surface.get_capabilities(&adapter);
let surface_format = surface_capabilities
.formats
.iter()
.find(|f| f.is_srgb())
.copied()
.unwrap_or(surface_capabilities.formats[0]);
debug!("Surface format: {:?}", surface_format);
debug!(
"Available present modes: {:?}",
surface_capabilities.present_modes
);
let size = window.inner_size();
let present_mode = if window.vsync() {
if surface_capabilities
.present_modes
.contains(&PresentMode::Fifo)
{
PresentMode::Fifo
} else {
surface_capabilities.present_modes[0]
}
} else if surface_capabilities
.present_modes
.contains(&PresentMode::Immediate)
{
PresentMode::Immediate
} else if surface_capabilities
.present_modes
.contains(&PresentMode::Mailbox)
{
PresentMode::Mailbox
} else {
surface_capabilities.present_modes[0]
};
let surface_config = SurfaceConfiguration {
usage: TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: size.width.max(1),
height: size.height.max(1),
present_mode,
alpha_mode: surface_capabilities.alpha_modes[0],
view_formats: vec![],
desired_maximum_frame_latency: 2,
};
surface.configure(&device, &surface_config);
Ok(Self {
instance,
adapter,
device,
queue,
surface,
surface_config,
surface_capabilities,
})
}
pub fn resize(&mut self, width: u32, height: u32) {
if width > 0 && height > 0 {
self.surface_config.width = width;
self.surface_config.height = height;
self.surface.configure(&self.device, &self.surface_config);
}
}
pub fn get_current_texture(&self) -> Result<wgpu::SurfaceTexture> {
Ok(self.surface.get_current_texture()?)
}
#[must_use]
pub fn device(&self) -> &Arc<Device> {
&self.device
}
#[must_use]
pub fn queue(&self) -> &Arc<Queue> {
&self.queue
}
#[must_use]
pub fn surface_format(&self) -> TextureFormat {
self.surface_config.format
}
#[must_use]
pub fn surface_size(&self) -> (u32, u32) {
(self.surface_config.width, self.surface_config.height)
}
#[must_use]
pub fn adapter_info(&self) -> wgpu::AdapterInfo {
self.adapter.get_info()
}
#[must_use]
pub fn create_command_encoder(&self) -> wgpu::CommandEncoder {
self.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Sable Command Encoder"),
})
}
pub fn submit<I: IntoIterator<Item = wgpu::CommandBuffer>>(&self, commands: I) {
self.queue.submit(commands);
}
}