refactor(everything): streamline stereokit and main loops
This commit is contained in:
@@ -18,7 +18,7 @@ path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
default = ["wayland"]
|
||||
wayland = ["dep:smithay", "dep:xkbcommon"]
|
||||
wayland = ["dep:smithay"]
|
||||
profile_tokio = ["dep:console-subscriber", "tokio/tracing"]
|
||||
profile_app = ["dep:tracing-tracy"]
|
||||
|
||||
@@ -59,7 +59,7 @@ serde_repr = "0.1.19"
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
rand = "0.8.5"
|
||||
xkbcommon = { version = "0.7.0", default-features = false, optional = true }
|
||||
xkbcommon = { version = "0.7.0", default-features = false }
|
||||
libc = "0.2.155"
|
||||
input-event-codes = "6.2.0"
|
||||
nix = "0.29.0"
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
use super::client::Client;
|
||||
use super::task;
|
||||
use color_eyre::eyre::Result;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tokio::net::UnixListener;
|
||||
use tokio::task::JoinHandle;
|
||||
use tracing::error;
|
||||
|
||||
pub struct EventLoop {
|
||||
join_handle: JoinHandle<()>,
|
||||
}
|
||||
|
||||
impl EventLoop {
|
||||
pub fn new(socket_path: PathBuf) -> Result<Arc<Self>> {
|
||||
let socket = UnixListener::bind(socket_path)?;
|
||||
|
||||
let join_handle = task::new(|| "event loop", async move {
|
||||
loop {
|
||||
let Ok((socket, _)) = socket.accept().await else { continue };
|
||||
if let Err(e) = Client::from_connection(socket) {
|
||||
error!(?e, "Unable to create client from connection");
|
||||
}
|
||||
}
|
||||
})?;
|
||||
let event_loop = Arc::new(EventLoop { join_handle });
|
||||
|
||||
Ok(event_loop)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EventLoop {
|
||||
fn drop(&mut self) {
|
||||
self.join_handle.abort();
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,6 @@ pub mod client;
|
||||
pub mod client_state;
|
||||
pub mod delta;
|
||||
pub mod destroy_queue;
|
||||
pub mod eventloop;
|
||||
pub mod idl_utils;
|
||||
pub mod registry;
|
||||
pub mod resource;
|
||||
|
||||
342
src/main.rs
342
src/main.rs
@@ -2,42 +2,34 @@
|
||||
mod core;
|
||||
mod nodes;
|
||||
mod objects;
|
||||
mod session;
|
||||
#[cfg(feature = "wayland")]
|
||||
mod wayland;
|
||||
|
||||
use crate::core::client::CLIENTS;
|
||||
use crate::core::destroy_queue;
|
||||
use crate::nodes::items::camera;
|
||||
use crate::nodes::{audio, drawable, hmd, input};
|
||||
use crate::objects::input::eye_pointer::EyePointer;
|
||||
use crate::objects::input::mouse_pointer::MousePointer;
|
||||
use crate::objects::input::sk_controller::SkController;
|
||||
use crate::objects::input::sk_hand::SkHand;
|
||||
use crate::objects::play_space::PlaySpace;
|
||||
|
||||
use self::core::eventloop::EventLoop;
|
||||
use clap::Parser;
|
||||
use core::client_state::ClientStateParsed;
|
||||
use core::client::Client;
|
||||
use core::task;
|
||||
use directories::ProjectDirs;
|
||||
use objects::ServerObjects;
|
||||
use once_cell::sync::OnceCell;
|
||||
use session::{launch_start, save_session};
|
||||
use stardust_xr::server;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use stereokit_rust::material::Material;
|
||||
use stereokit_rust::shader::Shader;
|
||||
use stereokit_rust::sk::{
|
||||
sk_quit, AppMode, DepthMode, DisplayBlend, DisplayMode, QuitReason, SkSettings,
|
||||
};
|
||||
use stereokit_rust::system::{Handed, LogLevel, Renderer, World};
|
||||
use stereokit_rust::sk::{sk_quit, AppMode, DepthMode, DisplayBlend, QuitReason, SkSettings};
|
||||
use stereokit_rust::system::{LogLevel, Renderer};
|
||||
use stereokit_rust::tex::{SHCubemap, Tex, TexFormat, TexType};
|
||||
use stereokit_rust::ui::Ui;
|
||||
use stereokit_rust::util::{Color128, Device, Time};
|
||||
use stereokit_rust::util::{Color128, Time};
|
||||
use tokio::net::UnixListener;
|
||||
use tokio::sync::Notify;
|
||||
use tokio::task::LocalSet;
|
||||
use tokio::{runtime::Handle, sync::oneshot};
|
||||
use tracing::metadata::LevelFilter;
|
||||
use tracing::{debug_span, error, info};
|
||||
use tracing_subscriber::{fmt, prelude::*, EnvFilter};
|
||||
@@ -63,14 +55,12 @@ struct CliArgs {
|
||||
}
|
||||
|
||||
static STARDUST_INSTANCE: OnceCell<String> = OnceCell::new();
|
||||
static STOP_NOTIFIER: Notify = Notify::const_new();
|
||||
|
||||
struct EventLoopInfo {
|
||||
tokio_handle: Handle,
|
||||
socket_path: PathBuf,
|
||||
}
|
||||
// #[tokio::main]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() {
|
||||
color_eyre::install().unwrap();
|
||||
|
||||
fn main() {
|
||||
let registry = tracing_subscriber::registry();
|
||||
|
||||
#[cfg(feature = "profile_app")]
|
||||
@@ -88,15 +78,80 @@ fn main() {
|
||||
.with_filter(EnvFilter::from_default_env());
|
||||
registry.with(log_layer).init();
|
||||
|
||||
let cli_args = Arc::new(CliArgs::parse());
|
||||
|
||||
let socket_path =
|
||||
server::get_free_socket_path().expect("Unable to find a free stardust socket path");
|
||||
STARDUST_INSTANCE.set(socket_path.file_name().unwrap().to_string_lossy().into_owned()).expect("Someone hasn't done their job, yell at Nova because how is this set multiple times what the hell");
|
||||
info!(
|
||||
socket_path = ?socket_path.display(),
|
||||
"Stardust socket created"
|
||||
);
|
||||
let socket =
|
||||
UnixListener::bind(socket_path).expect("Couldn't spawn stardust server at {socket_path}");
|
||||
task::new(|| "client join loop", async move {
|
||||
loop {
|
||||
let Ok((stream, _)) = socket.accept().await else {
|
||||
continue;
|
||||
};
|
||||
if let Err(e) = Client::from_connection(stream) {
|
||||
error!(?e, "Unable to create client from connection");
|
||||
}
|
||||
}
|
||||
})
|
||||
.unwrap();
|
||||
info!("Init client join loop");
|
||||
|
||||
let project_dirs = ProjectDirs::from("", "", "stardust");
|
||||
if project_dirs.is_none() {
|
||||
error!("Unable to get Stardust project directories, default skybox and startup script will not work.");
|
||||
}
|
||||
let cli_args = Arc::new(CliArgs::parse());
|
||||
|
||||
let sk_ready_notifier = Arc::new(Notify::new());
|
||||
let stereokit_loop = tokio::task::spawn_blocking({
|
||||
let sk_ready_notifier = sk_ready_notifier.clone();
|
||||
let project_dirs = project_dirs.clone();
|
||||
let flatscreen = cli_args.flatscreen;
|
||||
let overlay_priority = cli_args.overlay_priority;
|
||||
move || {
|
||||
stereokit_loop(
|
||||
sk_ready_notifier,
|
||||
project_dirs,
|
||||
flatscreen,
|
||||
overlay_priority,
|
||||
)
|
||||
}
|
||||
});
|
||||
sk_ready_notifier.notified().await;
|
||||
let mut startup_children = project_dirs
|
||||
.as_ref()
|
||||
.map(|project_dirs| launch_start(&cli_args, project_dirs))
|
||||
.unwrap_or_default();
|
||||
|
||||
tokio::select! {
|
||||
_ = stereokit_loop => (),
|
||||
_ = tokio::signal::ctrl_c() => unsafe {sk_quit(QuitReason::SystemClose)},
|
||||
}
|
||||
println!("Stopping...");
|
||||
if let Some(project_dirs) = project_dirs {
|
||||
save_session(&project_dirs).await;
|
||||
}
|
||||
for mut startup_child in startup_children.drain(..) {
|
||||
let _ = startup_child.kill();
|
||||
}
|
||||
|
||||
info!("Cleanly shut down Stardust");
|
||||
}
|
||||
|
||||
fn stereokit_loop(
|
||||
sk_ready_notifier: Arc<Notify>,
|
||||
project_dirs: Option<ProjectDirs>,
|
||||
intentional_flatscreen: bool,
|
||||
overlay_priority: Option<u32>,
|
||||
) {
|
||||
let sk = SkSettings::default()
|
||||
.app_name("Stardust XR")
|
||||
.mode(if cli_args.flatscreen {
|
||||
.mode(if intentional_flatscreen {
|
||||
AppMode::Simulator
|
||||
} else {
|
||||
AppMode::XR
|
||||
@@ -112,8 +167,8 @@ fn main() {
|
||||
Some(LevelFilter::OFF) => LogLevel::None,
|
||||
None => LogLevel::Warning,
|
||||
})
|
||||
.overlay_app(cli_args.overlay_priority.is_some())
|
||||
.overlay_priority(cli_args.overlay_priority.unwrap_or(u32::MAX))
|
||||
.overlay_app(overlay_priority.is_some())
|
||||
.overlay_priority(overlay_priority.unwrap_or(u32::MAX))
|
||||
.disable_desktop_input_window(true)
|
||||
.render_scaling(2.0)
|
||||
.init()
|
||||
@@ -144,60 +199,12 @@ fn main() {
|
||||
}
|
||||
}
|
||||
|
||||
let mut mouse_pointer = cli_args
|
||||
.flatscreen
|
||||
.then(MousePointer::new)
|
||||
.transpose()
|
||||
.unwrap();
|
||||
let mut hands = (!cli_args.flatscreen)
|
||||
.then(|| {
|
||||
let left = SkHand::new(Handed::Left).ok();
|
||||
let right = SkHand::new(Handed::Right).ok();
|
||||
left.zip(right)
|
||||
})
|
||||
.flatten();
|
||||
let mut controllers = (!cli_args.flatscreen)
|
||||
.then(|| {
|
||||
let left = SkController::new(Handed::Left).ok();
|
||||
let right = SkController::new(Handed::Right).ok();
|
||||
left.zip(right)
|
||||
})
|
||||
.flatten();
|
||||
let eye_pointer = (sk.get_active_display_mode() == DisplayMode::MixedReality
|
||||
&& Device::has_eye_gaze())
|
||||
.then(EyePointer::new)
|
||||
.transpose()
|
||||
.unwrap();
|
||||
|
||||
let play_space = World::has_bounds().then(|| PlaySpace::new().ok()).flatten();
|
||||
|
||||
let (info_sender, info_receiver) = oneshot::channel::<EventLoopInfo>();
|
||||
let event_thread = std::thread::Builder::new()
|
||||
.name("event_loop".to_owned())
|
||||
.spawn({
|
||||
let project_dirs = project_dirs.clone();
|
||||
move || event_loop(info_sender, project_dirs.clone())
|
||||
})
|
||||
.unwrap();
|
||||
let event_loop_info = info_receiver.blocking_recv().unwrap();
|
||||
let _tokio_handle = event_loop_info.tokio_handle.enter();
|
||||
|
||||
#[cfg(feature = "wayland")]
|
||||
let mut wayland = wayland::Wayland::new().expect("Could not initialize wayland");
|
||||
sk_ready_notifier.notify_waiters();
|
||||
info!("Stardust ready!");
|
||||
|
||||
let mut startup_children = project_dirs
|
||||
.as_ref()
|
||||
.map(|project_dirs| {
|
||||
launch_start(
|
||||
&cli_args,
|
||||
project_dirs,
|
||||
&event_loop_info,
|
||||
#[cfg(feature = "wayland")]
|
||||
&wayland,
|
||||
)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
let mut objects = ServerObjects::new(intentional_flatscreen, &sk);
|
||||
|
||||
let mut last_frame_delta = Duration::ZERO;
|
||||
let mut sleep_duration = Duration::ZERO;
|
||||
@@ -212,23 +219,7 @@ fn main() {
|
||||
wayland.frame_event();
|
||||
destroy_queue::clear();
|
||||
|
||||
if let Some(mouse_pointer) = &mut mouse_pointer {
|
||||
mouse_pointer.update();
|
||||
}
|
||||
if let Some((left_hand, right_hand)) = &mut hands {
|
||||
left_hand.update(&sk, token);
|
||||
right_hand.update(&sk, token);
|
||||
}
|
||||
if let Some((left_controller, right_controller)) = &mut controllers {
|
||||
left_controller.update(token);
|
||||
right_controller.update(token);
|
||||
}
|
||||
if let Some(eye_pointer) = &eye_pointer {
|
||||
eye_pointer.update();
|
||||
}
|
||||
if let Some(play_space) = &play_space {
|
||||
play_space.update();
|
||||
}
|
||||
objects.update(&sk, token);
|
||||
input::process_input();
|
||||
nodes::root::Root::send_frame_events(Time::get_step_unscaled());
|
||||
adaptive_sleep(
|
||||
@@ -249,17 +240,6 @@ fn main() {
|
||||
info!("Cleanly shut down StereoKit");
|
||||
#[cfg(feature = "wayland")]
|
||||
drop(wayland);
|
||||
|
||||
STOP_NOTIFIER.notify_waiters();
|
||||
event_thread
|
||||
.join()
|
||||
.expect("Failed to cleanly shut down event loop")
|
||||
.unwrap();
|
||||
for mut startup_child in startup_children.drain(..) {
|
||||
let _ = startup_child.kill();
|
||||
}
|
||||
|
||||
info!("Cleanly shut down Stardust");
|
||||
}
|
||||
|
||||
fn adaptive_sleep(
|
||||
@@ -283,153 +263,3 @@ fn adaptive_sleep(
|
||||
std::thread::sleep(*sleep_duration); // to give clients a chance to even update anything before drawing
|
||||
});
|
||||
}
|
||||
|
||||
// #[tokio::main]
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn event_loop(
|
||||
info_sender: oneshot::Sender<EventLoopInfo>,
|
||||
project_dirs: Option<ProjectDirs>,
|
||||
) -> color_eyre::eyre::Result<()> {
|
||||
let socket_path =
|
||||
server::get_free_socket_path().expect("Unable to find a free stardust socket path");
|
||||
STARDUST_INSTANCE.set(socket_path.file_name().unwrap().to_string_lossy().into_owned()).expect("Someone hasn't done their job, yell at Nova because how is this set multiple times what the hell");
|
||||
let _event_loop = EventLoop::new(socket_path.clone()).expect("Couldn't create server socket");
|
||||
info!("Init event loop");
|
||||
info!(
|
||||
socket_path = ?socket_path.display(),
|
||||
"Stardust socket created"
|
||||
);
|
||||
let _ = info_sender.send(EventLoopInfo {
|
||||
tokio_handle: Handle::current(),
|
||||
socket_path,
|
||||
});
|
||||
|
||||
STOP_NOTIFIER.notified().await;
|
||||
println!("Stopping...");
|
||||
if let Some(project_dirs) = project_dirs {
|
||||
save_session(&project_dirs).await;
|
||||
}
|
||||
|
||||
info!("Cleanly shut down event loop");
|
||||
|
||||
unsafe {
|
||||
sk_quit(QuitReason::SystemClose);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn launch_start(
|
||||
cli_args: &CliArgs,
|
||||
project_dirs: &ProjectDirs,
|
||||
event_loop_info: &EventLoopInfo,
|
||||
#[cfg(feature = "wayland")] wayland: &wayland::Wayland,
|
||||
) -> Vec<Child> {
|
||||
if let Some(session_id) = &cli_args.restore {
|
||||
let session_dir = project_dirs.state_dir().unwrap().join(session_id);
|
||||
return restore_session(&session_dir, event_loop_info, wayland);
|
||||
}
|
||||
let startup_script_path = cli_args
|
||||
.startup_script
|
||||
.clone()
|
||||
.and_then(|p| p.canonicalize().ok())
|
||||
.unwrap_or_else(|| project_dirs.config_dir().join("startup"));
|
||||
run_script(&startup_script_path, event_loop_info, wayland)
|
||||
}
|
||||
|
||||
fn restore_session(
|
||||
session_dir: &Path,
|
||||
event_loop_info: &EventLoopInfo,
|
||||
#[cfg(feature = "wayland")] wayland: &wayland::Wayland,
|
||||
) -> Vec<Child> {
|
||||
let Ok(clients) = session_dir.read_dir() else {
|
||||
return Vec::new();
|
||||
};
|
||||
clients
|
||||
.filter_map(Result::ok)
|
||||
.filter_map(|c| ClientStateParsed::from_file(&c.path()))
|
||||
.filter_map(ClientStateParsed::launch_command)
|
||||
.filter_map(|startup_command| {
|
||||
run_client(
|
||||
startup_command,
|
||||
event_loop_info,
|
||||
#[cfg(feature = "wayland")]
|
||||
wayland,
|
||||
)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn run_script(
|
||||
script_path: &Path,
|
||||
event_loop_info: &EventLoopInfo,
|
||||
#[cfg(feature = "wayland")] wayland: &wayland::Wayland,
|
||||
) -> Vec<Child> {
|
||||
let _ = std::fs::set_permissions(script_path, std::fs::Permissions::from_mode(0o755));
|
||||
let startup_command = Command::new(script_path);
|
||||
run_client(
|
||||
startup_command,
|
||||
event_loop_info,
|
||||
#[cfg(feature = "wayland")]
|
||||
wayland,
|
||||
)
|
||||
.map(|c| vec![c])
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn run_client(
|
||||
mut command: Command,
|
||||
event_loop_info: &EventLoopInfo,
|
||||
#[cfg(feature = "wayland")] wayland: &wayland::Wayland,
|
||||
) -> Option<Child> {
|
||||
command.stdin(Stdio::null());
|
||||
command.stdout(Stdio::null());
|
||||
command.stderr(Stdio::null());
|
||||
command.env(
|
||||
"FLAT_WAYLAND_DISPLAY",
|
||||
std::env::var_os("WAYLAND_DISPLAY").unwrap_or_default(),
|
||||
);
|
||||
command.env(
|
||||
"STARDUST_INSTANCE",
|
||||
event_loop_info
|
||||
.socket_path
|
||||
.file_name()
|
||||
.expect("Stardust socket path not found"),
|
||||
);
|
||||
#[cfg(feature = "wayland")]
|
||||
{
|
||||
if let Some(wayland_socket) = wayland.socket_name.as_ref() {
|
||||
command.env("WAYLAND_DISPLAY", wayland_socket);
|
||||
}
|
||||
command.env("GDK_BACKEND", "wayland");
|
||||
command.env("QT_QPA_PLATFORM", "wayland");
|
||||
command.env("MOZ_ENABLE_WAYLAND", "1");
|
||||
command.env("CLUTTER_BACKEND", "wayland");
|
||||
command.env("SDL_VIDEODRIVER", "wayland");
|
||||
}
|
||||
let child = command.spawn().ok()?;
|
||||
Some(child)
|
||||
}
|
||||
|
||||
async fn save_session(project_dirs: &ProjectDirs) {
|
||||
let session_id = nanoid::nanoid!();
|
||||
let state_dir = project_dirs.state_dir().unwrap();
|
||||
let session_dir = state_dir.join(&session_id);
|
||||
std::fs::create_dir_all(&session_dir).unwrap();
|
||||
let _ = std::fs::remove_dir_all(state_dir.join("latest"));
|
||||
std::os::unix::fs::symlink(&session_dir, state_dir.join("latest")).unwrap();
|
||||
|
||||
let local_set = LocalSet::new();
|
||||
for client in CLIENTS.get_vec() {
|
||||
let session_dir = session_dir.clone();
|
||||
local_set.spawn_local(async move {
|
||||
tokio::select! {
|
||||
biased;
|
||||
s = client.save_state() => {if let Some(s) = s { s.to_file(&session_dir) }},
|
||||
_ = tokio::time::sleep(Duration::from_millis(100)) => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
local_set.await;
|
||||
println!("Session ID for restore is {session_id}");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod lines;
|
||||
pub mod model;
|
||||
#[cfg(feature = "wayland")]
|
||||
pub mod shader_manipulation;
|
||||
pub mod shaders;
|
||||
pub mod text;
|
||||
|
||||
|
||||
137
src/nodes/drawable/shader_manipulation.rs
Normal file
137
src/nodes/drawable/shader_manipulation.rs
Normal file
@@ -0,0 +1,137 @@
|
||||
#![allow(dead_code)]
|
||||
use smithay::backend::renderer::gles::{
|
||||
ffi::{self, Gles2, FRAGMENT_SHADER, VERTEX_SHADER},
|
||||
GlesError,
|
||||
};
|
||||
use stereokit_rust::shader::{Shader, _ShaderT};
|
||||
use tracing::error;
|
||||
|
||||
struct FfiAssetHeader {
|
||||
asset_type: i32,
|
||||
asset_state: i32,
|
||||
id: u64,
|
||||
index: u64,
|
||||
refs: i32,
|
||||
debug: *mut u8,
|
||||
}
|
||||
|
||||
struct FfiSkgShader {
|
||||
meta: *mut u8,
|
||||
vertex: u32,
|
||||
pixel: u32,
|
||||
program: u32,
|
||||
compute: u32,
|
||||
}
|
||||
|
||||
struct FfiShader {
|
||||
header: FfiAssetHeader,
|
||||
shader: FfiSkgShader,
|
||||
}
|
||||
|
||||
unsafe fn compile_shader(
|
||||
gl: &ffi::Gles2,
|
||||
variant: ffi::types::GLuint,
|
||||
src: &str,
|
||||
) -> Result<ffi::types::GLuint, GlesError> {
|
||||
let shader = gl.CreateShader(variant);
|
||||
if shader == 0 {
|
||||
return Err(GlesError::CreateShaderObject);
|
||||
}
|
||||
|
||||
gl.ShaderSource(
|
||||
shader,
|
||||
1,
|
||||
&src.as_ptr() as *const *const u8 as *const *const ffi::types::GLchar,
|
||||
&(src.len() as i32) as *const _,
|
||||
);
|
||||
gl.CompileShader(shader);
|
||||
|
||||
let mut status = ffi::FALSE as i32;
|
||||
gl.GetShaderiv(shader, ffi::COMPILE_STATUS, &mut status as *mut _);
|
||||
if status == ffi::FALSE as i32 {
|
||||
let mut max_len = 0;
|
||||
gl.GetShaderiv(shader, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
|
||||
|
||||
let mut error = Vec::with_capacity(max_len as usize);
|
||||
let mut len = 0;
|
||||
gl.GetShaderInfoLog(
|
||||
shader,
|
||||
max_len as _,
|
||||
&mut len as *mut _,
|
||||
error.as_mut_ptr() as *mut _,
|
||||
);
|
||||
error.set_len(len as usize);
|
||||
|
||||
error!(
|
||||
"[GL] {}",
|
||||
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
|
||||
);
|
||||
|
||||
gl.DeleteShader(shader);
|
||||
return Err(GlesError::ShaderCompileError);
|
||||
}
|
||||
|
||||
Ok(shader)
|
||||
}
|
||||
|
||||
unsafe fn link_program(
|
||||
gl: &ffi::Gles2,
|
||||
vert: ffi::types::GLuint,
|
||||
frag: ffi::types::GLuint,
|
||||
) -> Result<ffi::types::GLuint, GlesError> {
|
||||
let program = gl.CreateProgram();
|
||||
gl.AttachShader(program, vert);
|
||||
gl.AttachShader(program, frag);
|
||||
gl.LinkProgram(program);
|
||||
// gl.DetachShader(program, vert);
|
||||
// gl.DetachShader(program, frag);
|
||||
// gl.DeleteShader(vert);
|
||||
// gl.DeleteShader(frag);
|
||||
|
||||
let mut status = ffi::FALSE as i32;
|
||||
gl.GetProgramiv(program, ffi::LINK_STATUS, &mut status as *mut _);
|
||||
if status == ffi::FALSE as i32 {
|
||||
let mut max_len = 0;
|
||||
gl.GetProgramiv(program, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
|
||||
|
||||
let mut error = Vec::with_capacity(max_len as usize);
|
||||
let mut len = 0;
|
||||
gl.GetProgramInfoLog(
|
||||
program,
|
||||
max_len as _,
|
||||
&mut len as *mut _,
|
||||
error.as_mut_ptr() as *mut _,
|
||||
);
|
||||
error.set_len(len as usize);
|
||||
|
||||
error!(
|
||||
"[GL] {}",
|
||||
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
|
||||
);
|
||||
|
||||
gl.DeleteProgram(program);
|
||||
return Err(GlesError::ProgramLinkError);
|
||||
}
|
||||
|
||||
Ok(program)
|
||||
}
|
||||
|
||||
pub unsafe fn shader_inject(
|
||||
c: &Gles2,
|
||||
sk_shader: &mut Shader,
|
||||
vert_str: &str,
|
||||
frag_str: &str,
|
||||
) -> Result<(), GlesError> {
|
||||
let gl_vert = compile_shader(c, VERTEX_SHADER, vert_str)?;
|
||||
let gl_frag = compile_shader(c, FRAGMENT_SHADER, frag_str)?;
|
||||
let gl_prog = link_program(c, gl_vert, gl_frag)?;
|
||||
|
||||
let shader = sk_shader.0.as_mut() as *mut _ShaderT as *mut FfiShader;
|
||||
if let Some(shader) = shader.as_mut() {
|
||||
shader.shader.vertex = gl_vert;
|
||||
shader.shader.pixel = gl_frag;
|
||||
shader.shader.program = gl_prog;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,143 +1,5 @@
|
||||
#![allow(dead_code)]
|
||||
use smithay::backend::renderer::gles::{
|
||||
ffi::{self, Gles2, FRAGMENT_SHADER, VERTEX_SHADER},
|
||||
GlesError,
|
||||
};
|
||||
use stereokit_rust::shader::{Shader, _ShaderT};
|
||||
use tracing::error;
|
||||
|
||||
// Simula shader with fancy lanzcos sampling
|
||||
pub const UNLIT_SHADER_BYTES: &[u8] = include_bytes!("assets/shaders/shader_unlit_gamma.hlsl.sks");
|
||||
|
||||
// Simula shader with fancy lanzcos sampling
|
||||
pub const PANEL_SHADER_BYTES: &[u8] = include_bytes!("assets/shaders/shader_unlit_simula.hlsl.sks");
|
||||
|
||||
struct FfiAssetHeader {
|
||||
asset_type: i32,
|
||||
asset_state: i32,
|
||||
id: u64,
|
||||
index: u64,
|
||||
refs: i32,
|
||||
debug: *mut u8,
|
||||
}
|
||||
|
||||
struct FfiSkgShader {
|
||||
meta: *mut u8,
|
||||
vertex: u32,
|
||||
pixel: u32,
|
||||
program: u32,
|
||||
compute: u32,
|
||||
}
|
||||
|
||||
struct FfiShader {
|
||||
header: FfiAssetHeader,
|
||||
shader: FfiSkgShader,
|
||||
}
|
||||
|
||||
unsafe fn compile_shader(
|
||||
gl: &ffi::Gles2,
|
||||
variant: ffi::types::GLuint,
|
||||
src: &str,
|
||||
) -> Result<ffi::types::GLuint, GlesError> {
|
||||
let shader = gl.CreateShader(variant);
|
||||
if shader == 0 {
|
||||
return Err(GlesError::CreateShaderObject);
|
||||
}
|
||||
|
||||
gl.ShaderSource(
|
||||
shader,
|
||||
1,
|
||||
&src.as_ptr() as *const *const u8 as *const *const ffi::types::GLchar,
|
||||
&(src.len() as i32) as *const _,
|
||||
);
|
||||
gl.CompileShader(shader);
|
||||
|
||||
let mut status = ffi::FALSE as i32;
|
||||
gl.GetShaderiv(shader, ffi::COMPILE_STATUS, &mut status as *mut _);
|
||||
if status == ffi::FALSE as i32 {
|
||||
let mut max_len = 0;
|
||||
gl.GetShaderiv(shader, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
|
||||
|
||||
let mut error = Vec::with_capacity(max_len as usize);
|
||||
let mut len = 0;
|
||||
gl.GetShaderInfoLog(
|
||||
shader,
|
||||
max_len as _,
|
||||
&mut len as *mut _,
|
||||
error.as_mut_ptr() as *mut _,
|
||||
);
|
||||
error.set_len(len as usize);
|
||||
|
||||
error!(
|
||||
"[GL] {}",
|
||||
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
|
||||
);
|
||||
|
||||
gl.DeleteShader(shader);
|
||||
return Err(GlesError::ShaderCompileError);
|
||||
}
|
||||
|
||||
Ok(shader)
|
||||
}
|
||||
|
||||
unsafe fn link_program(
|
||||
gl: &ffi::Gles2,
|
||||
vert: ffi::types::GLuint,
|
||||
frag: ffi::types::GLuint,
|
||||
) -> Result<ffi::types::GLuint, GlesError> {
|
||||
let program = gl.CreateProgram();
|
||||
gl.AttachShader(program, vert);
|
||||
gl.AttachShader(program, frag);
|
||||
gl.LinkProgram(program);
|
||||
// gl.DetachShader(program, vert);
|
||||
// gl.DetachShader(program, frag);
|
||||
// gl.DeleteShader(vert);
|
||||
// gl.DeleteShader(frag);
|
||||
|
||||
let mut status = ffi::FALSE as i32;
|
||||
gl.GetProgramiv(program, ffi::LINK_STATUS, &mut status as *mut _);
|
||||
if status == ffi::FALSE as i32 {
|
||||
let mut max_len = 0;
|
||||
gl.GetProgramiv(program, ffi::INFO_LOG_LENGTH, &mut max_len as *mut _);
|
||||
|
||||
let mut error = Vec::with_capacity(max_len as usize);
|
||||
let mut len = 0;
|
||||
gl.GetProgramInfoLog(
|
||||
program,
|
||||
max_len as _,
|
||||
&mut len as *mut _,
|
||||
error.as_mut_ptr() as *mut _,
|
||||
);
|
||||
error.set_len(len as usize);
|
||||
|
||||
error!(
|
||||
"[GL] {}",
|
||||
std::str::from_utf8(&error).unwrap_or("<Error Message no utf8>")
|
||||
);
|
||||
|
||||
gl.DeleteProgram(program);
|
||||
return Err(GlesError::ProgramLinkError);
|
||||
}
|
||||
|
||||
Ok(program)
|
||||
}
|
||||
|
||||
pub unsafe fn shader_inject(
|
||||
c: &Gles2,
|
||||
sk_shader: &mut Shader,
|
||||
vert_str: &str,
|
||||
frag_str: &str,
|
||||
) -> Result<(), GlesError> {
|
||||
let gl_vert = compile_shader(c, VERTEX_SHADER, vert_str)?;
|
||||
let gl_frag = compile_shader(c, FRAGMENT_SHADER, frag_str)?;
|
||||
let gl_prog = link_program(c, gl_vert, gl_frag)?;
|
||||
|
||||
let shader = sk_shader.0.as_mut() as *mut _ShaderT as *mut FfiShader;
|
||||
if let Some(shader) = shader.as_mut() {
|
||||
shader.shader.vertex = gl_vert;
|
||||
shader.shader.pixel = gl_frag;
|
||||
shader.shader.program = gl_prog;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4,12 +4,9 @@ use crate::core::client::Client;
|
||||
use crate::core::client_state::ClientStateParsed;
|
||||
use crate::core::registry::Registry;
|
||||
use crate::nodes::spatial::SPATIAL_REF_ASPECT_ALIAS_INFO;
|
||||
#[cfg(feature = "wayland")]
|
||||
use crate::wayland::WAYLAND_DISPLAY;
|
||||
use crate::STARDUST_INSTANCE;
|
||||
use crate::session::connection_env;
|
||||
use color_eyre::eyre::{bail, Result};
|
||||
use glam::Mat4;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use tracing::info;
|
||||
@@ -61,25 +58,7 @@ impl RootAspect for Root {
|
||||
_node: Arc<Node>,
|
||||
_calling_client: Arc<Client>,
|
||||
) -> Result<stardust_xr::values::Map<String, String>> {
|
||||
macro_rules! var_env_insert {
|
||||
($env:ident, $name:ident) => {
|
||||
$env.insert(stringify!($name).to_string(), $name.get().unwrap().clone());
|
||||
};
|
||||
}
|
||||
|
||||
let mut env: FxHashMap<String, String> = FxHashMap::default();
|
||||
var_env_insert!(env, STARDUST_INSTANCE);
|
||||
#[cfg(feature = "wayland")]
|
||||
{
|
||||
var_env_insert!(env, WAYLAND_DISPLAY);
|
||||
env.insert("GDK_BACKEND".to_string(), "wayland".to_string());
|
||||
env.insert("QT_QPA_PLATFORM".to_string(), "wayland".to_string());
|
||||
env.insert("MOZ_ENABLE_WAYLAND".to_string(), "1".to_string());
|
||||
env.insert("CLUTTER_BACKEND".to_string(), "wayland".to_string());
|
||||
env.insert("SDL_VIDEODRIVER".to_string(), "wayland".to_string());
|
||||
}
|
||||
|
||||
Ok(env)
|
||||
Ok(connection_env())
|
||||
}
|
||||
|
||||
#[doc = "Generate a client state token and return it back.\n\n When launching a new client, set the environment variable `STARDUST_STARTUP_TOKEN` to the returned string.\n Make sure the environment variable shows in `/proc/{pid}/environ` as that's the only reliable way to pass the value to the server (suggestions welcome).\n"]
|
||||
|
||||
@@ -1,2 +1,71 @@
|
||||
use input::{
|
||||
eye_pointer::EyePointer, mouse_pointer::MousePointer, sk_controller::SkController,
|
||||
sk_hand::SkHand,
|
||||
};
|
||||
use play_space::PlaySpace;
|
||||
use stereokit_rust::{
|
||||
sk::{DisplayMode, MainThreadToken, Sk},
|
||||
system::{Handed, World},
|
||||
util::Device,
|
||||
};
|
||||
|
||||
pub mod input;
|
||||
pub mod play_space;
|
||||
|
||||
pub struct ServerObjects {
|
||||
mouse_pointer: Option<MousePointer>,
|
||||
hands: Option<(SkHand, SkHand)>,
|
||||
controllers: Option<(SkController, SkController)>,
|
||||
eye_pointer: Option<EyePointer>,
|
||||
play_space: Option<PlaySpace>,
|
||||
}
|
||||
impl ServerObjects {
|
||||
pub fn new(intentional_flatscreen: bool, sk: &Sk) -> ServerObjects {
|
||||
ServerObjects {
|
||||
mouse_pointer: intentional_flatscreen
|
||||
.then(MousePointer::new)
|
||||
.transpose()
|
||||
.unwrap(),
|
||||
hands: (!intentional_flatscreen)
|
||||
.then(|| {
|
||||
let left = SkHand::new(Handed::Left).ok();
|
||||
let right = SkHand::new(Handed::Right).ok();
|
||||
left.zip(right)
|
||||
})
|
||||
.flatten(),
|
||||
controllers: (!intentional_flatscreen)
|
||||
.then(|| {
|
||||
let left = SkController::new(Handed::Left).ok();
|
||||
let right = SkController::new(Handed::Right).ok();
|
||||
left.zip(right)
|
||||
})
|
||||
.flatten(),
|
||||
eye_pointer: (sk.get_active_display_mode() == DisplayMode::MixedReality
|
||||
&& Device::has_eye_gaze())
|
||||
.then(EyePointer::new)
|
||||
.transpose()
|
||||
.unwrap(),
|
||||
play_space: World::has_bounds().then(|| PlaySpace::new().ok()).flatten(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, sk: &Sk, token: &MainThreadToken) {
|
||||
if let Some(mouse_pointer) = self.mouse_pointer.as_mut() {
|
||||
mouse_pointer.update();
|
||||
}
|
||||
if let Some((left_hand, right_hand)) = self.hands.as_mut() {
|
||||
left_hand.update(&sk, token);
|
||||
right_hand.update(&sk, token);
|
||||
}
|
||||
if let Some((left_controller, right_controller)) = self.controllers.as_mut() {
|
||||
left_controller.update(token);
|
||||
right_controller.update(token);
|
||||
}
|
||||
if let Some(eye_pointer) = self.eye_pointer.as_ref() {
|
||||
eye_pointer.update();
|
||||
}
|
||||
if let Some(play_space) = self.play_space.as_ref() {
|
||||
play_space.update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
105
src/session.rs
Normal file
105
src/session.rs
Normal file
@@ -0,0 +1,105 @@
|
||||
use crate::core::client::CLIENTS;
|
||||
use crate::core::client_state::ClientStateParsed;
|
||||
use crate::wayland::WAYLAND_DISPLAY;
|
||||
use crate::{CliArgs, STARDUST_INSTANCE};
|
||||
use directories::ProjectDirs;
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
use std::path::Path;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use std::time::Duration;
|
||||
use tokio::task::LocalSet;
|
||||
|
||||
pub async fn save_session(project_dirs: &ProjectDirs) {
|
||||
let session_id = nanoid::nanoid!();
|
||||
let state_dir = project_dirs.state_dir().unwrap();
|
||||
let session_dir = state_dir.join(&session_id);
|
||||
std::fs::create_dir_all(&session_dir).unwrap();
|
||||
let _ = std::fs::remove_dir_all(state_dir.join("latest"));
|
||||
std::os::unix::fs::symlink(&session_dir, state_dir.join("latest")).unwrap();
|
||||
|
||||
let local_set = LocalSet::new();
|
||||
for client in CLIENTS.get_vec() {
|
||||
let session_dir = session_dir.clone();
|
||||
local_set.spawn_local(async move {
|
||||
tokio::select! {
|
||||
biased;
|
||||
s = client.save_state() => {if let Some(s) = s { s.to_file(&session_dir) }},
|
||||
_ = tokio::time::sleep(Duration::from_millis(100)) => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
local_set.await;
|
||||
println!("Session ID for restore is {session_id}");
|
||||
}
|
||||
|
||||
pub fn launch_start(cli_args: &CliArgs, project_dirs: &ProjectDirs) -> Vec<Child> {
|
||||
match (&cli_args.restore, &cli_args.startup_script) {
|
||||
(Some(session_id), _) => {
|
||||
restore_session(&project_dirs.state_dir().unwrap().join(session_id))
|
||||
}
|
||||
(None, Some(startup_script)) => {
|
||||
run_script(&startup_script.clone().canonicalize().unwrap_or_default())
|
||||
}
|
||||
(None, None) => run_script(&project_dirs.config_dir().join("startup")),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn restore_session(session_dir: &Path) -> Vec<Child> {
|
||||
let Ok(clients) = session_dir.read_dir() else {
|
||||
return Vec::new();
|
||||
};
|
||||
clients
|
||||
.filter_map(Result::ok)
|
||||
.filter_map(|c| ClientStateParsed::from_file(&c.path()))
|
||||
.filter_map(ClientStateParsed::launch_command)
|
||||
.filter_map(|startup_command| run_client(startup_command))
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn run_script(script_path: &Path) -> Vec<Child> {
|
||||
let _ = std::fs::set_permissions(script_path, std::fs::Permissions::from_mode(0o755));
|
||||
let startup_command = Command::new(script_path);
|
||||
run_client(startup_command)
|
||||
.map(|c| vec![c])
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn run_client(mut command: Command) -> Option<Child> {
|
||||
command.stdin(Stdio::null());
|
||||
command.stdout(Stdio::null());
|
||||
command.stderr(Stdio::null());
|
||||
for (var, value) in connection_env() {
|
||||
command.env(var, value);
|
||||
}
|
||||
let child = command.spawn().ok()?;
|
||||
Some(child)
|
||||
}
|
||||
|
||||
pub fn connection_env() -> FxHashMap<String, String> {
|
||||
macro_rules! var_env_insert {
|
||||
($env:ident, $name:ident) => {
|
||||
$env.insert(stringify!($name).to_string(), $name.get().unwrap().clone());
|
||||
};
|
||||
}
|
||||
|
||||
let mut env: FxHashMap<String, String> = FxHashMap::default();
|
||||
var_env_insert!(env, STARDUST_INSTANCE);
|
||||
|
||||
if let Some(flat_wayland_display) = std::env::var_os("WAYLAND_DISPLAY") {
|
||||
env.insert(
|
||||
"FLAT_WAYLAND_DISPLAY".to_string(),
|
||||
flat_wayland_display.to_string_lossy().into_owned(),
|
||||
);
|
||||
}
|
||||
#[cfg(feature = "wayland")]
|
||||
{
|
||||
var_env_insert!(env, WAYLAND_DISPLAY);
|
||||
env.insert("GDK_BACKEND".to_string(), "wayland".to_string());
|
||||
env.insert("QT_QPA_PLATFORM".to_string(), "wayland".to_string());
|
||||
env.insert("MOZ_ENABLE_WAYLAND".to_string(), "1".to_string());
|
||||
env.insert("CLUTTER_BACKEND".to_string(), "wayland".to_string());
|
||||
env.insert("SDL_VIDEODRIVER".to_string(), "wayland".to_string());
|
||||
}
|
||||
env
|
||||
}
|
||||
@@ -24,7 +24,7 @@ use smithay::reexports::wayland_server::DisplayHandle;
|
||||
use smithay::reexports::wayland_server::{Display, ListeningSocket};
|
||||
use smithay::wayland::dmabuf;
|
||||
use std::ffi::OsStr;
|
||||
use std::os::fd::OwnedFd;
|
||||
use std::os::fd::{IntoRawFd, OwnedFd};
|
||||
use std::os::unix::prelude::AsRawFd;
|
||||
use std::{
|
||||
ffi::c_void,
|
||||
@@ -32,6 +32,7 @@ use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
use stereokit_rust::system::{Backend, BackendGraphics};
|
||||
use tokio::io::unix::AsyncFdReadyGuard;
|
||||
use tokio::sync::mpsc::UnboundedReceiver;
|
||||
use tokio::{
|
||||
io::unix::AsyncFd, net::UnixListener as AsyncUnixListener, sync::mpsc, task::JoinHandle,
|
||||
@@ -78,6 +79,18 @@ impl DisplayWrapper {
|
||||
}
|
||||
}
|
||||
|
||||
struct UnownedFd(Option<AsyncFd<OwnedFd>>);
|
||||
impl UnownedFd {
|
||||
async fn readable(&self) -> std::io::Result<AsyncFdReadyGuard<'_, OwnedFd>> {
|
||||
self.0.as_ref().unwrap().readable().await
|
||||
}
|
||||
}
|
||||
impl Drop for UnownedFd {
|
||||
fn drop(&mut self) {
|
||||
self.0.take().unwrap().into_inner().into_raw_fd();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Wayland {
|
||||
display: Arc<DisplayWrapper>,
|
||||
pub socket_name: Option<String>,
|
||||
@@ -137,7 +150,7 @@ impl Wayland {
|
||||
AsyncUnixListener::from_std(unsafe { UnixListener::from_raw_fd(socket.as_raw_fd()) })?;
|
||||
|
||||
let dispatch_poll_fd = display.poll_fd()?;
|
||||
let dispatch_poll_listener = AsyncFd::new(dispatch_poll_fd)?;
|
||||
let dispatch_poll_listener = UnownedFd(Some(AsyncFd::new(dispatch_poll_fd)?));
|
||||
|
||||
let dh1 = display.handle();
|
||||
let mut dh2 = dh1.clone();
|
||||
|
||||
@@ -195,7 +195,6 @@ impl XdgShellHandler for WaylandState {
|
||||
return;
|
||||
}
|
||||
utils::insert_data(popup.wl_surface(), SurfaceId::Child(uid));
|
||||
utils::insert_data(popup.wl_surface(), uid);
|
||||
utils::insert_data(popup.wl_surface(), Arc::downgrade(&panel_item));
|
||||
CoreSurface::add_to(
|
||||
popup.wl_surface(),
|
||||
|
||||
Reference in New Issue
Block a user