2024-08-23 11:22:09 +00:00
|
|
|
#![allow(unused)]
|
|
|
|
|
|
|
|
use super::configs::WindowConfig;
|
|
|
|
use super::osspecific::osspecific::OsSpecific;
|
|
|
|
use super::vulkancore::VulkanCore;
|
|
|
|
|
|
|
|
#[cfg(feature = "sound")]
|
|
|
|
use audio::SoundHandler;
|
2025-02-26 13:51:44 +00:00
|
|
|
use ecs::World;
|
2024-08-23 11:22:09 +00:00
|
|
|
|
|
|
|
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,
|
|
|
|
|
2025-02-26 13:51:44 +00:00
|
|
|
fallback: Option<Box<dyn Fn(anyhow::Error) -> Result<()> + Send + Sync>>,
|
2024-08-23 11:22:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Context {
|
|
|
|
pub fn new() -> ContextBuilder {
|
|
|
|
ContextBuilder::default()
|
|
|
|
}
|
|
|
|
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "sound")]
|
|
|
|
pub fn sound(&self) -> MutexGuard<'_, SoundHandler> {
|
|
|
|
self.sound_handler.lock().unwrap()
|
|
|
|
}
|
|
|
|
|
2025-02-26 13:51:44 +00:00
|
|
|
pub fn run<C>(&self, world: &mut World) -> Result<()> {
|
2024-08-23 11:22:09 +00:00
|
|
|
'running: loop {
|
2025-02-26 13:51:44 +00:00
|
|
|
let render_core = self.render_core.clone();
|
|
|
|
|
2024-08-23 11:22:09 +00:00
|
|
|
match self.presentation.poll_events(
|
|
|
|
|event| {
|
2025-02-26 13:51:44 +00:00
|
|
|
// TODO
|
|
|
|
// if let Some(ctx_obj) = world.resources.get_mut_opt::<C>() {
|
|
|
|
// ctx_obj.event(event)?;
|
|
|
|
// }
|
2024-08-23 11:22:09 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
},
|
2025-02-26 13:51:44 +00:00
|
|
|
|w, h| render_core.write().unwrap().resize(world, w, h),
|
2024-08-23 11:22:09 +00:00
|
|
|
) {
|
|
|
|
Ok(res) => {
|
|
|
|
if !res {
|
|
|
|
break 'running;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(err) => {
|
2025-02-26 13:51:44 +00:00
|
|
|
if let Some(fallback) = &self.fallback {
|
2024-08-23 11:22:09 +00:00
|
|
|
(fallback)(err)?;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-26 13:51:44 +00:00
|
|
|
if !self.render_core_mut().next_frame(world)? {
|
2024-08-23 11:22:09 +00:00
|
|
|
break 'running;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
2025-02-26 13:51:44 +00:00
|
|
|
pub fn set_fallback<F>(&mut self, fallback: F)
|
2024-08-23 11:22:09 +00:00
|
|
|
where
|
|
|
|
F: Fn(anyhow::Error) -> Result<()> + 'static + Send + Sync,
|
|
|
|
{
|
2025-02-26 13:51:44 +00:00
|
|
|
self.fallback = Some(Box::new(fallback));
|
2024-08-23 11:22:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 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 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
|
|
|
|
}
|
|
|
|
|
2025-02-26 13:51:44 +00:00
|
|
|
pub fn build<S: TScene + 'static>(self) -> Result<Context> {
|
2024-08-23 11:22:09 +00:00
|
|
|
if self.enable_backtrace {
|
|
|
|
// set environment variable for Rust-debug-trace
|
2025-02-26 13:51:44 +00:00
|
|
|
unsafe { set_var("RUST_BACKTRACE", "1") };
|
2024-08-23 11:22:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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);
|
|
|
|
|
2025-02-26 13:51:44 +00:00
|
|
|
let (render_core, _target_mode) = create_render_core::<S>(
|
2024-08-23 11:22:09 +00:00
|
|
|
&presentation,
|
|
|
|
core.device(),
|
|
|
|
core.queue(),
|
|
|
|
self.render_core_create_info,
|
|
|
|
)?;
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
2025-02-26 13:51:44 +00:00
|
|
|
Ok(Context {
|
2024-08-23 11:22:09 +00:00
|
|
|
core,
|
|
|
|
presentation,
|
|
|
|
render_core: Arc::new(RwLock::new(render_core)),
|
|
|
|
|
|
|
|
#[cfg(feature = "sound")]
|
|
|
|
sound_handler: Mutex::new(self.create_sound_handler()?),
|
|
|
|
|
|
|
|
os_specific,
|
|
|
|
|
2025-02-26 13:51:44 +00:00
|
|
|
fallback: None,
|
|
|
|
})
|
2024-08-23 11:22:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[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
|
2025-02-26 13:51:44 +00:00
|
|
|
return self
|
|
|
|
.vr_mode
|
|
|
|
.map(|vr_mode| {
|
2024-08-23 11:22:09 +00:00
|
|
|
let available_vr_modes = PresentationCore::enabled_vr_modes();
|
|
|
|
|
|
|
|
// if requested VR mode is enabled, use it
|
|
|
|
if available_vr_modes.contains(&vr_mode) {
|
2025-02-26 13:51:44 +00:00
|
|
|
Some(vr_mode)
|
2024-08-23 11:22:09 +00:00
|
|
|
}
|
|
|
|
// 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
|
|
|
|
);
|
|
|
|
|
2025-02-26 13:51:44 +00:00
|
|
|
Some(mode)
|
2024-08-23 11:22:09 +00:00
|
|
|
}
|
|
|
|
// use default desktop, as last resort
|
|
|
|
else {
|
|
|
|
println!("No VRMode present, fallback to Window");
|
|
|
|
|
2025-02-26 13:51:44 +00:00
|
|
|
None
|
2024-08-23 11:22:09 +00:00
|
|
|
}
|
2025-02-26 13:51:44 +00:00
|
|
|
})
|
|
|
|
.flatten();
|
2024-08-23 11:22:09 +00:00
|
|
|
|
|
|
|
#[cfg(not(any(feature = "openvr", feature = "openxr")))]
|
|
|
|
None
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "sound")]
|
|
|
|
fn create_sound_handler(&self) -> Result<SoundHandler> {
|
|
|
|
SoundHandler::new(self.volume_info.clone())
|
|
|
|
}
|
|
|
|
}
|