use std::sync::Arc;
use std::time::Duration;
use futures_executor::block_on;
use smudge_core::app::FrameOutput;
use smudge_gpu::AdapterSummary;
use smudge_gpu::wgpu::{AdapterInfo, DeviceType};
use smudge_gpu::{GpuSurface, wgpu};
use web_time::Instant;
use winit::window::Window;
use super::present::{PresentStats, present_skipped};
use crate::error::PlatformError;
use crate::window::SharedWindow;
const SOFTWARE_MARKERS: [&str; 5] = [
"llvmpipe",
"softpipe",
"lavapipe",
"swiftshader",
"microsoft basic render driver",
];
pub(crate) fn adapter_is_software(info: &AdapterInfo) -> bool {
if info.device_type == DeviceType::Cpu {
return true;
}
let name = info.name.to_lowercase();
let driver = info.driver.to_lowercase();
SOFTWARE_MARKERS
.iter()
.any(|marker| name.contains(marker) || driver.contains(marker))
}
pub(crate) struct GpuPath {
surface: GpuSurface,
window: Arc<Window>,
summary: AdapterSummary,
}
impl GpuPath {
pub(crate) fn new(
window: &Arc<Window>,
width: u32,
height: u32,
explicit: bool,
) -> Result<Option<GpuPath>, PlatformError> {
let fail = |message: String| {
if explicit {
Err(PlatformError::Renderer(message))
} else {
Ok(None)
}
};
if width == 0 || height == 0 {
return fail(String::from(
"a window without pixels cannot present GPU frames",
));
}
let instance =
wgpu::Instance::new(wgpu::InstanceDescriptor::new_without_display_handle_from_env());
let surface = instance
.create_surface(Box::new(SharedWindow(Arc::clone(window))))
.map_err(|error| {
PlatformError::Renderer(format!(
"the window surface could not be created for the GPU renderer: {error}"
))
})?;
let adapter = match block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
compatible_surface: Some(&surface),
..Default::default()
})) {
Ok(adapter) => adapter,
Err(error) => {
return fail(format!("no adapter can present to the window: {error}"));
}
};
if !explicit && adapter_is_software(&adapter.get_info()) {
return Ok(None);
}
let (device, queue) =
match block_on(adapter.request_device(&wgpu::DeviceDescriptor::default())) {
Ok(device) => device,
Err(error) => {
return fail(format!("the adapter could not provide a device: {error}"));
}
};
let surface = match GpuSurface::new(surface, &adapter, device, queue, width, height) {
Ok(surface) => surface,
Err(error) => return fail(format!("the window cannot present GPU frames: {error}")),
};
Ok(Some(GpuPath {
surface,
window: Arc::clone(window),
summary: AdapterSummary::of(&adapter.get_info()),
}))
}
pub(crate) fn adapter_summary(&self) -> AdapterSummary {
self.summary.clone()
}
pub(crate) fn render_and_present(
&mut self,
frame: &FrameOutput<'_>,
has_tick: bool,
) -> Result<Option<PresentStats>, PlatformError> {
if present_skipped(frame.damage, has_tick, false) {
return Ok(None);
}
let acquire_start = Instant::now();
let texture = match self.surface.acquire() {
Ok(Some(texture)) => texture,
Ok(None) => return Ok(None),
Err(error) => {
return Err(PlatformError::Renderer(format!(
"the GPU surface failed: {error}"
)));
}
};
let acquire = acquire_start.elapsed();
self.window.pre_present_notify();
let start = Instant::now();
self.surface.draw(frame, texture).map_err(|error| {
PlatformError::Renderer(format!("the GPU renderer failed: {error}"))
})?;
let raster = start.elapsed();
Ok(Some(PresentStats {
buffer_age: 0,
with_damage: false,
bytes_copied: 0,
surface_width: self.surface.width(),
surface_height: self.surface.height(),
raster,
acquire,
present: Duration::ZERO,
renderer: "gpu",
}))
}
pub(crate) fn resize(&mut self, width: u32, height: u32) -> Result<(), PlatformError> {
self.surface.resize(width, height).map_err(|error| {
PlatformError::Renderer(format!("the GPU surface could not be resized: {error}"))
})
}
}
#[cfg(test)]
#[path = "gpu_tests.rs"]
mod tests;