611 lines
16 KiB
Rust
611 lines
16 KiB
Rust
|
#![allow(unused)]
|
||
|
|
||
|
use super::configs::WindowConfig;
|
||
|
use super::osspecific::osspecific::OsSpecific;
|
||
|
use super::vulkancore::VulkanCore;
|
||
|
|
||
|
#[cfg(feature = "sound")]
|
||
|
use audio::SoundHandler;
|
||
|
|
||
|
use crate::prelude::*;
|
||
|
use anyhow::Result;
|
||
|
|
||
|
use presentation::{input::eventsystem::Event, prelude::*};
|
||
|
|
||
|
use std::collections::HashMap;
|
||
|
use std::env::set_var;
|
||
|
use std::mem::{self, swap};
|
||
|
use std::ops::{Deref, DerefMut};
|
||
|
use std::path::Path;
|
||
|
use std::rc::Rc;
|
||
|
use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard};
|
||
|
use std::time::{Duration, Instant};
|
||
|
|
||
|
pub trait ContextObject {
|
||
|
fn name(&self) -> &str;
|
||
|
|
||
|
fn update(&mut self) -> Result<()>;
|
||
|
|
||
|
fn event(&mut self, event: Event) -> Result<()>;
|
||
|
}
|
||
|
|
||
|
pub struct Context {
|
||
|
core: VulkanCore,
|
||
|
pub(crate) presentation: PresentationCore,
|
||
|
render_core: Arc<RwLock<Box<dyn RenderCore>>>,
|
||
|
|
||
|
#[cfg(feature = "sound")]
|
||
|
sound_handler: Mutex<SoundHandler>,
|
||
|
|
||
|
os_specific: OsSpecific,
|
||
|
|
||
|
application_start_time: Instant,
|
||
|
|
||
|
context_object: Arc<RwLock<Option<Box<dyn ContextObject + Send + Sync>>>>,
|
||
|
|
||
|
fallback: Mutex<Option<Box<dyn Fn(anyhow::Error) -> Result<()> + Send + Sync>>>,
|
||
|
|
||
|
push_events: Mutex<Vec<Box<dyn FnOnce() -> Result<()> + Send + Sync>>>,
|
||
|
|
||
|
// queue timer
|
||
|
last_check: Mutex<Duration>,
|
||
|
}
|
||
|
|
||
|
impl Context {
|
||
|
pub fn new() -> ContextBuilder {
|
||
|
ContextBuilder::default()
|
||
|
}
|
||
|
|
||
|
pub fn set_context_object<C>(&self, context_object: Option<C>)
|
||
|
where
|
||
|
C: ContextObject + Send + Sync + 'static,
|
||
|
{
|
||
|
let tmp = self.context_object.clone();
|
||
|
|
||
|
self.push_event(move || {
|
||
|
*tmp.write().unwrap() =
|
||
|
context_object.map(|c| Box::new(c) as Box<dyn ContextObject + Send + Sync>);
|
||
|
|
||
|
Ok(())
|
||
|
})
|
||
|
}
|
||
|
|
||
|
pub fn window_config(&self) -> WindowConfig<'_> {
|
||
|
match self.presentation.backend() {
|
||
|
PresentationBackend::Window(wsi) => WindowConfig::new(wsi),
|
||
|
PresentationBackend::OpenXR(_xri) => {
|
||
|
panic!("OpenXR backend has no window config")
|
||
|
}
|
||
|
PresentationBackend::OpenVR(_vri) => {
|
||
|
panic!("OpenVR backend has no window config")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn push_event(&self, event: impl FnOnce() -> Result<()> + 'static + Send + Sync) {
|
||
|
self.push_events.lock().unwrap().push(Box::new(event));
|
||
|
}
|
||
|
|
||
|
#[cfg(feature = "sound")]
|
||
|
pub fn sound(&self) -> MutexGuard<'_, SoundHandler> {
|
||
|
self.sound_handler.lock().unwrap()
|
||
|
}
|
||
|
|
||
|
pub fn run(&self) -> Result<()> {
|
||
|
'running: loop {
|
||
|
match self.presentation.poll_events(
|
||
|
|event| {
|
||
|
if let Some(ctx_obj) = &mut *self.context_object.write().unwrap() {
|
||
|
ctx_obj.event(event)?;
|
||
|
}
|
||
|
|
||
|
Ok(())
|
||
|
},
|
||
|
{
|
||
|
let render_core = self.render_core.clone();
|
||
|
|
||
|
move |w, h| render_core.write().unwrap().resize(w, h)
|
||
|
},
|
||
|
) {
|
||
|
Ok(res) => {
|
||
|
if !res {
|
||
|
break 'running;
|
||
|
}
|
||
|
}
|
||
|
Err(err) => {
|
||
|
if let Some(fallback) = self.fallback.lock().unwrap().as_ref() {
|
||
|
(fallback)(err)?;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if let Err(err) = self.update() {
|
||
|
if let Some(fallback) = &self.fallback.lock().unwrap().as_ref() {
|
||
|
(fallback)(err)?;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !self.render_core_mut().next_frame()? {
|
||
|
break 'running;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
*self.context_object.write().unwrap() = None;
|
||
|
self.render_core_mut().clear_post_processing_routines();
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
|
||
|
pub fn render_core(&self) -> impl Deref<Target = Box<dyn RenderCore>> + '_ {
|
||
|
self.render_core.read().unwrap()
|
||
|
}
|
||
|
|
||
|
pub fn render_core_mut(&self) -> impl DerefMut<Target = Box<dyn RenderCore>> + '_ {
|
||
|
self.render_core.write().unwrap()
|
||
|
}
|
||
|
|
||
|
pub fn set_fallback<F>(&self, fallback: F)
|
||
|
where
|
||
|
F: Fn(anyhow::Error) -> Result<()> + 'static + Send + Sync,
|
||
|
{
|
||
|
*self.fallback.lock().unwrap() = Some(Box::new(fallback));
|
||
|
}
|
||
|
|
||
|
pub fn close(&self) -> Result<()> {
|
||
|
Ok(self.presentation.event_system().quit()?)
|
||
|
}
|
||
|
|
||
|
pub fn device(&self) -> &Arc<Device> {
|
||
|
&self.core.device()
|
||
|
}
|
||
|
|
||
|
pub fn queue(&self) -> &Arc<Mutex<Queue>> {
|
||
|
self.core.queue()
|
||
|
}
|
||
|
|
||
|
pub fn time(&self) -> Duration {
|
||
|
self.application_start_time.elapsed()
|
||
|
}
|
||
|
|
||
|
pub fn controllers(&self) -> RwLockReadGuard<'_, Vec<Arc<RwLock<Controller>>>> {
|
||
|
self.presentation.event_system().controllers()
|
||
|
}
|
||
|
|
||
|
pub fn active_controller(&self) -> Result<Option<Arc<RwLock<Controller>>>> {
|
||
|
Ok(self.presentation.event_system().active_controller()?)
|
||
|
}
|
||
|
|
||
|
pub fn set_active_controller(&self, controller: &Arc<RwLock<Controller>>) {
|
||
|
self.presentation
|
||
|
.event_system()
|
||
|
.set_active_controller(controller)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl std::fmt::Debug for Context {
|
||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||
|
write!(f, "Context {{ TODO }}")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl Context {
|
||
|
#[inline]
|
||
|
fn update(&self) -> Result<()> {
|
||
|
if let Some(ctx_obj) = &mut *self.context_object.write().unwrap() {
|
||
|
if let Err(err) = ctx_obj.update() {
|
||
|
return Err(err);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let mut events = Vec::new();
|
||
|
|
||
|
{
|
||
|
let mut push_events_lock = self.push_events.lock().unwrap();
|
||
|
mem::swap(&mut events, &mut push_events_lock);
|
||
|
}
|
||
|
|
||
|
for event in events {
|
||
|
event()?;
|
||
|
}
|
||
|
|
||
|
let one_second = Duration::from_secs(1);
|
||
|
let mut last_check = self.last_check.lock().unwrap();
|
||
|
|
||
|
if (self.time() - *last_check) > one_second {
|
||
|
*last_check += one_second;
|
||
|
|
||
|
#[cfg(feature = "sound")]
|
||
|
{
|
||
|
self.sound().check_clear_queue()?;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl ContextInterface for Context {
|
||
|
fn device(&self) -> &Arc<Device> {
|
||
|
self.device()
|
||
|
}
|
||
|
|
||
|
fn queue(&self) -> &Arc<Mutex<Queue>> {
|
||
|
self.queue()
|
||
|
}
|
||
|
|
||
|
fn format(&self) -> VkFormat {
|
||
|
self.render_core().format()
|
||
|
}
|
||
|
|
||
|
fn image_layout(&self) -> VkImageLayout {
|
||
|
self.render_core().image_layout()
|
||
|
}
|
||
|
|
||
|
fn image_count(&self) -> usize {
|
||
|
self.render_core().image_count()
|
||
|
}
|
||
|
|
||
|
fn images(&self) -> TargetMode<Vec<Arc<Image>>> {
|
||
|
self.render_core().images()
|
||
|
}
|
||
|
|
||
|
fn width(&self) -> u32 {
|
||
|
self.render_core().width()
|
||
|
}
|
||
|
|
||
|
fn height(&self) -> u32 {
|
||
|
self.render_core().height()
|
||
|
}
|
||
|
|
||
|
#[cfg(feature = "sound")]
|
||
|
fn sound_handler(&self) -> MutexGuard<'_, SoundHandler> {
|
||
|
self.sound()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub struct ContextBuilder {
|
||
|
#[cfg(feature = "sound")]
|
||
|
volume_info: HashMap<String, f32>,
|
||
|
|
||
|
#[cfg(any(feature = "openvr", feature = "openxr"))]
|
||
|
vr_mode: Option<VRMode>,
|
||
|
|
||
|
#[cfg(feature = "openxr")]
|
||
|
openxr_runtime_json: Option<String>,
|
||
|
|
||
|
enable_backtrace: bool,
|
||
|
|
||
|
// app info
|
||
|
app_info: ApplicationInfo,
|
||
|
|
||
|
// window information
|
||
|
window_create_info: WindowCreateInfo,
|
||
|
|
||
|
// os specifics
|
||
|
os_specific_config: OsSpecificConfig,
|
||
|
|
||
|
// vulkan core settings
|
||
|
device_features: DeviceFeatures,
|
||
|
sample_count: VkSampleCountFlags,
|
||
|
enable_raytracing: bool,
|
||
|
render_core_create_info: RenderCoreCreateInfo,
|
||
|
|
||
|
// vulkan debug extension selection
|
||
|
vulkan_debug_info: VulkanDebugInfo,
|
||
|
|
||
|
// input settings
|
||
|
enable_mouse: bool,
|
||
|
enable_keyboard: bool,
|
||
|
enable_controller: bool,
|
||
|
controller_deadzones: ControllerDeadzones,
|
||
|
}
|
||
|
|
||
|
impl Default for ContextBuilder {
|
||
|
fn default() -> Self {
|
||
|
ContextBuilder {
|
||
|
#[cfg(feature = "sound")]
|
||
|
volume_info: HashMap::new(),
|
||
|
|
||
|
#[cfg(any(feature = "openvr", feature = "openxr"))]
|
||
|
vr_mode: None,
|
||
|
|
||
|
#[cfg(feature = "openxr")]
|
||
|
openxr_runtime_json: None,
|
||
|
|
||
|
enable_backtrace: false,
|
||
|
|
||
|
// app info
|
||
|
app_info: ApplicationInfo {
|
||
|
application_name: "not set".to_string(),
|
||
|
application_version: 0,
|
||
|
engine_name: "not set".to_string(),
|
||
|
engine_version: 0,
|
||
|
},
|
||
|
|
||
|
// window information
|
||
|
window_create_info: WindowCreateInfo {
|
||
|
title: "Vulkan Application".to_string(),
|
||
|
width: 800,
|
||
|
height: 600,
|
||
|
fullscreen: false,
|
||
|
requested_display: None,
|
||
|
},
|
||
|
|
||
|
// os specifics
|
||
|
os_specific_config: OsSpecificConfig::default(),
|
||
|
|
||
|
// vulkan core settings
|
||
|
device_features: DeviceFeatures::default(),
|
||
|
sample_count: VK_SAMPLE_COUNT_1_BIT,
|
||
|
enable_raytracing: false,
|
||
|
render_core_create_info: RenderCoreCreateInfo {
|
||
|
format: VK_FORMAT_R8G8B8A8_UNORM,
|
||
|
usage: 0.into(),
|
||
|
vsync: false,
|
||
|
},
|
||
|
|
||
|
// vulkan debug extension selection
|
||
|
vulkan_debug_info: VulkanDebugInfo::default(),
|
||
|
|
||
|
// input settings
|
||
|
enable_mouse: false,
|
||
|
enable_keyboard: false,
|
||
|
enable_controller: false,
|
||
|
controller_deadzones: ControllerDeadzones::default(),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
impl ContextBuilder {
|
||
|
#[cfg(feature = "sound")]
|
||
|
pub fn set_volume_info(mut self, volume_info: HashMap<String, f32>) -> Self {
|
||
|
self.volume_info = volume_info;
|
||
|
|
||
|
self
|
||
|
}
|
||
|
|
||
|
#[cfg(any(feature = "openvr", feature = "openxr"))]
|
||
|
pub fn set_vr_mode(mut self, vr_mode: VRMode) -> Self {
|
||
|
self.vr_mode = Some(vr_mode);
|
||
|
|
||
|
self
|
||
|
}
|
||
|
|
||
|
#[cfg(feature = "openxr")]
|
||
|
pub fn set_openxr_json(mut self, openxr_json_path: &str) -> Self {
|
||
|
self.openxr_runtime_json = Some(openxr_json_path.to_string());
|
||
|
|
||
|
self
|
||
|
}
|
||
|
|
||
|
pub fn set_app_info(mut self, app_info: ApplicationInfo) -> Self {
|
||
|
self.app_info = app_info;
|
||
|
|
||
|
self
|
||
|
}
|
||
|
|
||
|
pub fn set_window_info(mut self, window_info: WindowCreateInfo) -> Self {
|
||
|
self.window_create_info = window_info;
|
||
|
|
||
|
self
|
||
|
}
|
||
|
|
||
|
pub fn set_os_specific_info(mut self, os_specific: OsSpecificConfig) -> Self {
|
||
|
self.os_specific_config = os_specific;
|
||
|
|
||
|
self
|
||
|
}
|
||
|
|
||
|
pub fn set_device_features(mut self, device_features: DeviceFeatures) -> Self {
|
||
|
self.device_features = device_features;
|
||
|
|
||
|
self
|
||
|
}
|
||
|
|
||
|
pub fn set_sample_count(mut self, sample_count: VkSampleCountFlags) -> Self {
|
||
|
self.sample_count = sample_count;
|
||
|
|
||
|
self
|
||
|
}
|
||
|
|
||
|
pub fn enable_ray_tracing(mut self) -> Self {
|
||
|
self.enable_raytracing = true;
|
||
|
|
||
|
self
|
||
|
}
|
||
|
|
||
|
pub fn set_render_core_info(
|
||
|
mut self,
|
||
|
format: VkFormat,
|
||
|
usage: impl Into<VkImageUsageFlagBits>,
|
||
|
vsync: bool,
|
||
|
) -> Self {
|
||
|
self.render_core_create_info = RenderCoreCreateInfo {
|
||
|
format,
|
||
|
usage: usage.into(),
|
||
|
vsync,
|
||
|
};
|
||
|
|
||
|
self
|
||
|
}
|
||
|
|
||
|
pub fn enable_backtrace(mut self) -> Self {
|
||
|
self.enable_backtrace = true;
|
||
|
|
||
|
self
|
||
|
}
|
||
|
|
||
|
pub fn set_vulkan_debug_info(mut self, vulkan_debug_info: VulkanDebugInfo) -> Self {
|
||
|
self.vulkan_debug_info = vulkan_debug_info;
|
||
|
|
||
|
self
|
||
|
}
|
||
|
|
||
|
pub fn enable_mouse(mut self) -> Self {
|
||
|
self.enable_mouse = true;
|
||
|
|
||
|
self
|
||
|
}
|
||
|
|
||
|
pub fn enable_keyboard(mut self) -> Self {
|
||
|
self.enable_keyboard = true;
|
||
|
|
||
|
self
|
||
|
}
|
||
|
|
||
|
pub fn enable_controller(mut self) -> Self {
|
||
|
self.enable_controller = true;
|
||
|
|
||
|
self
|
||
|
}
|
||
|
|
||
|
pub fn set_controller_deadzones(mut self, controller_deadzones: ControllerDeadzones) -> Self {
|
||
|
self.controller_deadzones = controller_deadzones;
|
||
|
|
||
|
self
|
||
|
}
|
||
|
|
||
|
pub fn build<F, SCENE>(self, create_scene: F) -> Result<Arc<Context>>
|
||
|
where
|
||
|
SCENE: TScene + 'static,
|
||
|
F: FnOnce(
|
||
|
&Arc<Device>,
|
||
|
&Arc<Mutex<Queue>>,
|
||
|
(f32, f32),
|
||
|
&TargetMode<Vec<Arc<Image>>>,
|
||
|
) -> Result<SCENE>,
|
||
|
{
|
||
|
if self.enable_backtrace {
|
||
|
// set environment variable for Rust-debug-trace
|
||
|
set_var("RUST_BACKTRACE", "1");
|
||
|
}
|
||
|
|
||
|
#[cfg(feature = "openxr")]
|
||
|
self.use_openxr_json();
|
||
|
|
||
|
let vr_mode = self.get_vr_mode();
|
||
|
|
||
|
let presentation =
|
||
|
PresentationCore::new(vr_mode, &self.window_create_info, self.app_info.clone())?;
|
||
|
|
||
|
presentation
|
||
|
.event_system()
|
||
|
.set_controller_axis_enable_deadzone(self.controller_deadzones.axis_enable_deadzone);
|
||
|
presentation
|
||
|
.event_system()
|
||
|
.set_controller_axis_disable_deadzone(self.controller_deadzones.axis_disable_deadzone);
|
||
|
presentation
|
||
|
.event_system()
|
||
|
.set_controller_trigger_enable_deadzone(
|
||
|
self.controller_deadzones.trigger_enable_deadzone,
|
||
|
);
|
||
|
presentation
|
||
|
.event_system()
|
||
|
.set_controller_trigger_disable_deadzone(
|
||
|
self.controller_deadzones.trigger_disable_deadzone,
|
||
|
);
|
||
|
|
||
|
// vulkan core objects (VkInstance, VkDevice, ...)
|
||
|
let core = VulkanCore::new(
|
||
|
&presentation,
|
||
|
&self.vulkan_debug_info,
|
||
|
&self.app_info,
|
||
|
self.device_features,
|
||
|
)?;
|
||
|
|
||
|
let os_specific = OsSpecific::new(&self.os_specific_config);
|
||
|
|
||
|
let (render_core, _target_mode) = create_render_core(
|
||
|
&presentation,
|
||
|
core.device(),
|
||
|
core.queue(),
|
||
|
self.render_core_create_info,
|
||
|
create_scene,
|
||
|
)?;
|
||
|
|
||
|
if self.enable_mouse {
|
||
|
presentation.event_system().enable_mouse();
|
||
|
}
|
||
|
|
||
|
if self.enable_keyboard {
|
||
|
presentation.event_system().enable_keyboard();
|
||
|
}
|
||
|
|
||
|
if self.enable_controller {
|
||
|
presentation.event_system().enable_controller();
|
||
|
}
|
||
|
|
||
|
Ok(Arc::new(Context {
|
||
|
core,
|
||
|
presentation,
|
||
|
render_core: Arc::new(RwLock::new(render_core)),
|
||
|
|
||
|
#[cfg(feature = "sound")]
|
||
|
sound_handler: Mutex::new(self.create_sound_handler()?),
|
||
|
|
||
|
os_specific,
|
||
|
|
||
|
application_start_time: Instant::now(),
|
||
|
|
||
|
context_object: Arc::new(RwLock::new(None)),
|
||
|
|
||
|
fallback: Mutex::new(None),
|
||
|
|
||
|
push_events: Mutex::new(Vec::new()),
|
||
|
|
||
|
last_check: Mutex::new(Duration::from_secs(0)),
|
||
|
}))
|
||
|
}
|
||
|
|
||
|
#[cfg(feature = "openxr")]
|
||
|
fn use_openxr_json(&self) {
|
||
|
if let Some(openxr_json) = &self.openxr_runtime_json {
|
||
|
// set environment variable for OpenXR
|
||
|
set_var("XR_RUNTIME_JSON", openxr_json);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn get_vr_mode(&self) -> Option<VRMode> {
|
||
|
#[cfg(any(feature = "openvr", feature = "openxr"))]
|
||
|
// if we requested a VR mode, check if it is available
|
||
|
match self.vr_mode {
|
||
|
Some(vr_mode) => {
|
||
|
let available_vr_modes = PresentationCore::enabled_vr_modes();
|
||
|
|
||
|
// if requested VR mode is enabled, use it
|
||
|
if available_vr_modes.contains(&vr_mode) {
|
||
|
return Some(vr_mode);
|
||
|
}
|
||
|
// fallback to the first available
|
||
|
else if !available_vr_modes.is_empty() {
|
||
|
let mode = available_vr_modes[0];
|
||
|
|
||
|
println!(
|
||
|
"Requested VRMode ({:?}) is not available, using {:?} instead.",
|
||
|
vr_mode, mode
|
||
|
);
|
||
|
|
||
|
return Some(mode);
|
||
|
}
|
||
|
// use default desktop, as last resort
|
||
|
else {
|
||
|
println!("No VRMode present, fallback to Window");
|
||
|
|
||
|
return None;
|
||
|
}
|
||
|
}
|
||
|
None => {
|
||
|
return None;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[cfg(not(any(feature = "openvr", feature = "openxr")))]
|
||
|
None
|
||
|
}
|
||
|
|
||
|
#[cfg(feature = "sound")]
|
||
|
fn create_sound_handler(&self) -> Result<SoundHandler> {
|
||
|
SoundHandler::new(self.volume_info.clone())
|
||
|
}
|
||
|
}
|