mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-30 05:44:52 +00:00
235 lines
9.1 KiB
Rust
235 lines
9.1 KiB
Rust
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
|
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
|
|
|
use i_slint_core::api::PhysicalSize as PhysicalWindowSize;
|
|
use i_slint_core::platform::PlatformError;
|
|
use vulkano::device::physical::{PhysicalDevice, PhysicalDeviceType};
|
|
use vulkano::device::{DeviceExtensions, QueueFlags};
|
|
use vulkano::instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions};
|
|
use vulkano::swapchain::Surface;
|
|
use vulkano::VulkanLibrary;
|
|
|
|
use std::cell::Cell;
|
|
use std::rc::{Rc, Weak};
|
|
use std::sync::Arc;
|
|
|
|
use super::Presenter;
|
|
|
|
pub struct VulkanDisplay {
|
|
pub physical_device: Arc<PhysicalDevice>,
|
|
pub queue_family_index: u32,
|
|
pub surface: Arc<Surface>,
|
|
pub size: PhysicalWindowSize,
|
|
pub presenter: Rc<dyn Presenter>,
|
|
}
|
|
|
|
pub fn create_vulkan_display() -> Result<VulkanDisplay, PlatformError> {
|
|
let library = VulkanLibrary::new()
|
|
.map_err(|load_err| format!("Error loading vulkan library: {load_err}"))?;
|
|
|
|
let required_extensions = InstanceExtensions {
|
|
khr_surface: true,
|
|
khr_display: true,
|
|
khr_get_surface_capabilities2: true,
|
|
khr_get_physical_device_properties2: true,
|
|
..InstanceExtensions::empty()
|
|
}
|
|
.intersection(library.supported_extensions());
|
|
|
|
let instance = Instance::new(
|
|
library.clone(),
|
|
InstanceCreateInfo {
|
|
flags: InstanceCreateFlags::ENUMERATE_PORTABILITY,
|
|
enabled_extensions: required_extensions,
|
|
..Default::default()
|
|
},
|
|
)
|
|
.map_err(|instance_err| format!("Error creating Vulkan instance: {instance_err}"))?;
|
|
|
|
let device_extensions = DeviceExtensions { khr_swapchain: true, ..DeviceExtensions::empty() };
|
|
let (physical_device, queue_family_index) = instance
|
|
.enumerate_physical_devices()
|
|
.map_err(|vke| format!("Error enumerating physical Vulkan devices: {vke}"))?
|
|
.filter(|p| p.supported_extensions().contains(&device_extensions))
|
|
.filter_map(|p| {
|
|
p.queue_family_properties()
|
|
.iter()
|
|
.position(|q| {
|
|
q.queue_flags.intersects(QueueFlags::GRAPHICS)
|
|
&& p.display_properties().map_or(false, |displays| !displays.is_empty())
|
|
})
|
|
.map(|i| (p, i as u32))
|
|
})
|
|
.min_by_key(|(p, _)| match p.properties().device_type {
|
|
PhysicalDeviceType::DiscreteGpu => 0,
|
|
PhysicalDeviceType::IntegratedGpu => 1,
|
|
PhysicalDeviceType::VirtualGpu => 2,
|
|
PhysicalDeviceType::Cpu => 3,
|
|
PhysicalDeviceType::Other => 4,
|
|
_ => 5,
|
|
})
|
|
.ok_or_else(|| format!("Vulkan: Failed to find suitable physical device"))?;
|
|
|
|
let displays =
|
|
physical_device.display_properties().map_err(|e| format!("Error reading displays: {e}"))?;
|
|
|
|
let displays = displays.into_iter();
|
|
|
|
let Some(first_display) = displays.clone().next() else {
|
|
return Err(format!("Vulkan: No displays found").into());
|
|
};
|
|
|
|
let display = std::env::var("SLINT_VULKAN_DISPLAY").map_or_else(
|
|
|_| Ok(first_display),
|
|
|display_str| {
|
|
let mut displays_and_index = displays.enumerate();
|
|
|
|
if display_str.to_lowercase() == "list" {
|
|
let display_names: Vec<String> = displays_and_index
|
|
.map(|(index, display)| {
|
|
format!(
|
|
"Index: {} Name: {}",
|
|
index,
|
|
display.name().unwrap_or_else(|| "unknown")
|
|
)
|
|
})
|
|
.collect();
|
|
|
|
// Can't return error here because newlines are escaped.
|
|
eprintln!("\nVulkan Display List Requested:\n{}\nPlease select a display with the SLINT_VULKAN_DISPLAY environment variable and re-run the program.", display_names.join("\n"));
|
|
std::process::exit(1);
|
|
}
|
|
let display_index: usize =
|
|
display_str.parse().map_err(|_| format!("Invalid display index {display_str}"))?;
|
|
displays_and_index.nth(display_index).map_or_else(
|
|
|| Err(format!("Display index is out of bounds: {display_index}")),
|
|
|(_, dsp)| Ok(dsp),
|
|
)
|
|
},
|
|
)?;
|
|
|
|
let mode = std::env::var("SLINT_VULKAN_MODE").map_or_else(
|
|
|_| {
|
|
display
|
|
.display_mode_properties()
|
|
.map_err(|e| format!("Error reading display mode properties: {e}"))?
|
|
.into_iter()
|
|
.max_by(|current_mode, next_mode| {
|
|
let [current_mode_width, current_mode_height] = current_mode.visible_region();
|
|
let current_refresh_rate = current_mode.refresh_rate();
|
|
let [next_mode_width, next_mode_height] = next_mode.visible_region();
|
|
let next_refresh_rate = next_mode.refresh_rate();
|
|
(current_mode_width, current_mode_height, current_refresh_rate).cmp(&(
|
|
next_mode_width,
|
|
next_mode_height,
|
|
next_refresh_rate,
|
|
))
|
|
})
|
|
.ok_or_else(|| format!("Vulkan: No modes found for display"))
|
|
},
|
|
|mode_str| {
|
|
let mut modes_and_index = display
|
|
.display_mode_properties()
|
|
.expect("fatal: Unable to enumerate display properties")
|
|
.into_iter()
|
|
.enumerate();
|
|
|
|
if mode_str.to_lowercase() == "list" {
|
|
let mode_names: Vec<String> = modes_and_index
|
|
.map(|(index, mode)| {
|
|
let [width, height] = mode.visible_region();
|
|
format!(
|
|
"Index: {index} Width: {width} Height: {height} Refresh Rate: {}",
|
|
mode.refresh_rate() / 1000
|
|
)
|
|
})
|
|
.collect();
|
|
|
|
// Can't return error here because newlines are escaped.
|
|
eprintln!("\nVulkan Mode List Requested:\n{}\nPlease select a mode with the SLINT_VULKAN_MODE environment variable and re-run the program.", mode_names.join("\n"));
|
|
std::process::exit(1);
|
|
}
|
|
let mode_index: usize =
|
|
mode_str.parse().map_err(|_| format!("Invalid mode index {mode_str}"))?;
|
|
modes_and_index.nth(mode_index).map_or_else(
|
|
|| Err(format!("Mode index is out of bounds: {mode_index}")),
|
|
|(_, mode)| Ok(mode),
|
|
)
|
|
},
|
|
)?;
|
|
|
|
let vulkan_surface = vulkano::swapchain::Surface::from_display_plane(
|
|
mode.clone(),
|
|
vulkano::swapchain::DisplaySurfaceCreateInfo {
|
|
image_extent: [mode.visible_region()[0], mode.visible_region()[1]],
|
|
..Default::default()
|
|
},
|
|
)
|
|
.unwrap();
|
|
|
|
let size = PhysicalWindowSize::new(mode.visible_region()[0], mode.visible_region()[1]);
|
|
|
|
Ok(VulkanDisplay {
|
|
physical_device,
|
|
queue_family_index,
|
|
surface: vulkan_surface,
|
|
size,
|
|
presenter: TimerBasedAnimationDriver::new(),
|
|
})
|
|
}
|
|
|
|
struct TimerBasedAnimationDriver {
|
|
timer: i_slint_core::timers::Timer,
|
|
next_animation_frame_callback: Cell<Option<Box<dyn FnOnce()>>>,
|
|
}
|
|
|
|
impl TimerBasedAnimationDriver {
|
|
fn new() -> Rc<Self> {
|
|
Rc::new_cyclic(|self_weak: &Weak<Self>| {
|
|
let self_weak = self_weak.clone();
|
|
let timer = i_slint_core::timers::Timer::default();
|
|
timer.start(
|
|
i_slint_core::timers::TimerMode::Repeated,
|
|
std::time::Duration::from_millis(16),
|
|
move || {
|
|
let Some(this) = self_weak.upgrade() else { return };
|
|
// Stop the timer and let the callback decide if we need to continue. It will set
|
|
// `needs_redraw` to true of animations should continue, render() will be called,
|
|
// present_with_next_frame_callback() will be called and then the timer restarted.
|
|
this.timer.stop();
|
|
if let Some(next_animation_frame_callback) =
|
|
this.next_animation_frame_callback.take()
|
|
{
|
|
next_animation_frame_callback();
|
|
}
|
|
},
|
|
);
|
|
// Activate it only when we present a frame.
|
|
timer.stop();
|
|
|
|
Self { timer, next_animation_frame_callback: Default::default() }
|
|
})
|
|
}
|
|
}
|
|
|
|
impl Presenter for TimerBasedAnimationDriver {
|
|
fn is_ready_to_present(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn register_page_flip_handler(
|
|
&self,
|
|
_event_loop_handle: crate::calloop_backend::EventLoopHandle,
|
|
) -> Result<(), PlatformError> {
|
|
Ok(())
|
|
}
|
|
|
|
fn present_with_next_frame_callback(
|
|
&self,
|
|
ready_for_next_animation_frame: Box<dyn FnOnce()>,
|
|
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
|
self.next_animation_frame_callback.set(Some(ready_for_next_animation_frame));
|
|
self.timer.restart();
|
|
Ok(())
|
|
}
|
|
}
|