From a80861fc7c66cc8a39542d88c53eb719aa477009 Mon Sep 17 00:00:00 2001 From: Kane Rogers Date: Wed, 8 Feb 2023 23:07:14 +1100 Subject: [PATCH 01/14] Begin work on editor - Create stub app for Editor - Begin work on new OpenXR client - Create "editor" feature flag to hide new work --- .gitignore | 3 + .vscode/tasks.json | 11 +- Cargo.toml | 3 + hotham-editor-client/Cargo.toml | 8 + hotham-editor-client/src/main.rs | 3 + hotham-editor/Cargo.toml | 14 + hotham-editor/src/main.rs | 362 ++++++++ hotham-editor/src/shaders/triangle.frag | 29 + hotham-editor/src/shaders/triangle.vert | 14 + hotham-openxr-client/Cargo.toml | 19 + hotham-openxr-client/README.md | 19 + .../hotham-openxr-client.json | 8 + hotham-openxr-client/openxr_client.reg | Bin 0 -> 390 bytes hotham-openxr-client/src/client.rs | 802 ++++++++++++++++++ hotham-openxr-client/src/lib.rs | 163 ++++ hotham-openxr-client/src/space_state.rs | 41 + hotham-openxr-client/src/state.rs | 16 + hotham-openxr-client/test.ps1 | 13 + hotham-simulator/src/simulator.rs | 1 + hotham/Cargo.toml | 3 + hotham/src/contexts/xr_context/mod.rs | 8 +- 21 files changed, 1537 insertions(+), 3 deletions(-) create mode 100644 hotham-editor-client/Cargo.toml create mode 100644 hotham-editor-client/src/main.rs create mode 100644 hotham-editor/Cargo.toml create mode 100644 hotham-editor/src/main.rs create mode 100644 hotham-editor/src/shaders/triangle.frag create mode 100644 hotham-editor/src/shaders/triangle.vert create mode 100644 hotham-openxr-client/Cargo.toml create mode 100644 hotham-openxr-client/README.md create mode 100644 hotham-openxr-client/hotham-openxr-client.json create mode 100644 hotham-openxr-client/openxr_client.reg create mode 100644 hotham-openxr-client/src/client.rs create mode 100644 hotham-openxr-client/src/lib.rs create mode 100644 hotham-openxr-client/src/space_state.rs create mode 100644 hotham-openxr-client/src/state.rs create mode 100644 hotham-openxr-client/test.ps1 diff --git a/.gitignore b/.gitignore index fdd7db61..1a7f33a3 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ sponza* # Ignore compiled shaders *.spv + +# Checking in a socket is probably a terrible idea +*.socket diff --git a/.vscode/tasks.json b/.vscode/tasks.json index a96d5f66..3f475813 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -98,8 +98,17 @@ "label": "Run Hotham simple scene on the simulator", "group": { "kind": "test", - "isDefault": true, + "isDefault": false, } }, + { + "label": "Test OpenXR Client", + "type": "shell", + "command": "./hotham-openxr-client/test.ps1", + "group": { + "kind": "test", + "isDefault": true, + } + } ] } diff --git a/Cargo.toml b/Cargo.toml index 39d54f14..530c1e2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,9 @@ members = [ "hotham-asset-client", "hotham-asset-server", "hotham-simulator", + "hotham-editor", + "hotham-editor-client", + "hotham-openxr-client", "hotham", ] diff --git a/hotham-editor-client/Cargo.toml b/hotham-editor-client/Cargo.toml new file mode 100644 index 00000000..9170ffe2 --- /dev/null +++ b/hotham-editor-client/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "hotham-editor-client" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/hotham-editor-client/src/main.rs b/hotham-editor-client/src/main.rs new file mode 100644 index 00000000..e7a11a96 --- /dev/null +++ b/hotham-editor-client/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/hotham-editor/Cargo.toml b/hotham-editor/Cargo.toml new file mode 100644 index 00000000..09d6420b --- /dev/null +++ b/hotham-editor/Cargo.toml @@ -0,0 +1,14 @@ +[package] +edition = "2021" +name = "hotham-editor" +version = "0.1.0" + +[dependencies] +ash = "0.37.2" +env_logger = "0.10.0" +lazy_vulkan = {git = "https://github.com/leetvr/lazy_vulkan", rev = "5347dd3295fd4c687c35adbe7c0b4aad00c893e3"} +log = "0.4.17" +winit = "0.28.1" + +[target.'cfg(windows)'.dependencies] +uds_windows = "1.0.2" diff --git a/hotham-editor/src/main.rs b/hotham-editor/src/main.rs new file mode 100644 index 00000000..c964b800 --- /dev/null +++ b/hotham-editor/src/main.rs @@ -0,0 +1,362 @@ +use ash::vk; +use lazy_vulkan::{ + find_memorytype_index, vulkan_texture::VulkanTexture, DrawCall, LazyRenderer, LazyVulkan, + SwapchainInfo, Vertex, +}; +use log::{debug, info}; +use std::io::{Read, Write}; +use uds_windows::{UnixListener, UnixStream}; +use winit::{ + event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event_loop::ControlFlow, + platform::run_return::EventLoopExtRunReturn, +}; + +/// Compile your own damn shaders! LazyVulkan is just as lazy as you are! +static FRAGMENT_SHADER: &'_ [u8] = include_bytes!("shaders/triangle.frag.spv"); +static VERTEX_SHADER: &'_ [u8] = include_bytes!("shaders/triangle.vert.spv"); +const SWAPCHAIN_FORMAT: vk::Format = vk::Format::R8G8B8A8_UNORM; +static UNIX_SOCKET_PATH: &'_ str = "hotham_editor.socket"; + +pub fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Info) + .init(); + + // Oh, you thought you could supply your own Vertex type? What is this, a rendergraph?! + // Better make sure those shaders use the right layout! + // **LAUGHS IN VULKAN** + let vertices = [ + Vertex::new([1.0, 1.0, 0.0, 1.0], [1.0, 1.0, 1.0, 0.0], [1.0, 1.0]), // bottom right + Vertex::new([-1.0, 1.0, 0.0, 1.0], [1.0, 1.0, 1.0, 0.0], [0.0, 1.0]), // bottom left + Vertex::new([1.0, -1.0, 0.0, 1.0], [1.0, 1.0, 1.0, 0.0], [1.0, 0.0]), // top right + Vertex::new([-1.0, -1.0, 0.0, 1.0], [1.0, 1.0, 1.0, 0.0], [0.0, 0.0]), // top left + ]; + + // Your own index type?! What are you going to use, `u16`? + let indices = [0, 1, 2, 2, 1, 3]; + + // Alright, let's build some stuff + let (mut lazy_vulkan, mut lazy_renderer, mut event_loop) = LazyVulkan::builder() + .initial_vertices(&vertices) + .initial_indices(&indices) + .fragment_shader(FRAGMENT_SHADER) + .vertex_shader(VERTEX_SHADER) + .with_present(true) + .build(); + + // Let's do something totally normal and wait for a TCP connection + if std::fs::remove_file(UNIX_SOCKET_PATH).is_ok() { + debug!("Removed pre-existing unix socket at {UNIX_SOCKET_PATH}"); + } + let listener = UnixListener::bind(UNIX_SOCKET_PATH).unwrap(); + info!("Listening on {UNIX_SOCKET_PATH} - waiting for client.."); + let swapchain_info = SwapchainInfo { + image_count: lazy_vulkan.surface.desired_image_count, + resolution: lazy_vulkan.surface.surface_resolution, + format: SWAPCHAIN_FORMAT, + }; + let (images, image_memory_handles) = + unsafe { create_render_images(lazy_vulkan.context(), &swapchain_info) }; + let (semaphores, semaphore_handles) = + unsafe { create_semaphores(lazy_vulkan.context(), swapchain_info.image_count) }; + let textures = create_render_textures(lazy_vulkan.context(), &mut lazy_renderer, images); + let (mut stream, _) = listener.accept().unwrap(); + info!("Client connected!"); + let mut buf: [u8; 1024] = [0; 1024]; + send_swapchain_info(&mut stream, &swapchain_info, &mut buf).unwrap(); + send_image_memory_handles(&mut stream, image_memory_handles, &mut buf).unwrap(); + send_semaphore_handles(&mut stream, semaphore_handles, &mut buf); + + // Off we go! + let mut winit_initializing = true; + event_loop.run_return(|event, _, control_flow| { + *control_flow = ControlFlow::Poll; + match event { + Event::WindowEvent { + event: + WindowEvent::CloseRequested + | WindowEvent::KeyboardInput { + input: + KeyboardInput { + state: ElementState::Pressed, + virtual_keycode: Some(VirtualKeyCode::Escape), + .. + }, + .. + }, + .. + } => *control_flow = ControlFlow::Exit, + + Event::NewEvents(cause) => { + if cause == winit::event::StartCause::Init { + winit_initializing = true; + } else { + winit_initializing = false; + } + } + + Event::MainEventsCleared => { + let framebuffer_index = lazy_vulkan.render_begin(); + send_swapchain_image_index(&mut stream, &mut buf, framebuffer_index); + get_render_complete(&mut stream, &mut buf); + let texture_id = textures[framebuffer_index as usize].id; + lazy_renderer.render( + lazy_vulkan.context(), + framebuffer_index, + &[DrawCall::new( + 0, + indices.len() as _, + texture_id, + lazy_vulkan::Workflow::Main, + )], + ); + + let semaphore = semaphores[framebuffer_index as usize]; + lazy_vulkan.render_end( + framebuffer_index, + &[semaphore, lazy_vulkan.rendering_complete_semaphore], + ); + } + Event::WindowEvent { + event: WindowEvent::Resized(size), + .. + } => { + if !winit_initializing { + let new_render_surface = lazy_vulkan.resized(size.width, size.height); + lazy_renderer.update_surface(new_render_surface, &lazy_vulkan.context().device); + } + } + _ => (), + } + }); + + // I guess we better do this or else the Dreaded Validation Layers will complain + unsafe { + lazy_renderer.cleanup(&lazy_vulkan.context().device); + } +} + +fn send_semaphore_handles( + stream: &mut UnixStream, + semaphore_handles: Vec<*mut std::ffi::c_void>, + buf: &mut [u8; 1024], +) { + stream.read(buf).unwrap(); + let value = buf[0]; + debug!("Read {value}"); + + debug!("Sending handles: {semaphore_handles:?}"); + let write = stream.write(bytes_of_slice(&semaphore_handles)).unwrap(); + debug!("Wrote {write} bytes"); +} + +unsafe fn create_semaphores( + context: &lazy_vulkan::vulkan_context::VulkanContext, + image_count: u32, +) -> (Vec, Vec) { + let device = &context.device; + let external_semaphore = + ash::extensions::khr::ExternalSemaphoreWin32::new(&context.instance, &context.device); + let handle_type = vk::ExternalSemaphoreHandleTypeFlags::OPAQUE_WIN32_KMT; + (0..image_count) + .map(|_| { + let mut external_semaphore_info = + vk::ExportSemaphoreCreateInfo::builder().handle_types(handle_type); + let semaphore = device + .create_semaphore( + &vk::SemaphoreCreateInfo::builder().push_next(&mut external_semaphore_info), + None, + ) + .unwrap(); + + let handle = external_semaphore + .get_semaphore_win32_handle( + &vk::SemaphoreGetWin32HandleInfoKHR::builder() + .handle_type(handle_type) + .semaphore(semaphore), + ) + .unwrap(); + + (semaphore, handle) + }) + .unzip() +} + +fn create_render_textures( + vulkan_context: &lazy_vulkan::vulkan_context::VulkanContext, + renderer: &mut LazyRenderer, + mut images: Vec, +) -> Vec { + let descriptors = &mut renderer.descriptors; + let address_mode = vk::SamplerAddressMode::REPEAT; + let filter = vk::Filter::LINEAR; + images + .drain(..) + .map(|image| { + let view = unsafe { vulkan_context.create_image_view(image, SWAPCHAIN_FORMAT) }; + let sampler = unsafe { + vulkan_context + .device + .create_sampler( + &vk::SamplerCreateInfo::builder() + .address_mode_u(address_mode) + .address_mode_v(address_mode) + .address_mode_w(address_mode) + .mag_filter(filter) + .min_filter(filter), + None, + ) + .unwrap() + }; + + let id = + unsafe { descriptors.update_texture_descriptor_set(view, sampler, vulkan_context) }; + + lazy_vulkan::vulkan_texture::VulkanTexture { + image, + memory: vk::DeviceMemory::null(), // todo + sampler, + view, + id, + } + }) + .collect() +} + +fn get_render_complete(stream: &mut UnixStream, buf: &mut [u8]) { + stream.read(buf).unwrap(); +} + +fn send_swapchain_image_index( + stream: &mut UnixStream, + buf: &mut [u8; 1024], + framebuffer_index: u32, +) { + stream.read(buf).unwrap(); + stream.write(&mut [framebuffer_index as u8]).unwrap(); +} + +unsafe fn create_render_images( + context: &lazy_vulkan::vulkan_context::VulkanContext, + swapchain_info: &SwapchainInfo, +) -> (Vec, Vec) { + let device = &context.device; + let SwapchainInfo { + resolution, + format, + image_count, + } = swapchain_info; + let handle_type = vk::ExternalMemoryHandleTypeFlags::OPAQUE_WIN32_KMT; + + (0..(*image_count)) + .map(|_| { + let mut handle_info = + vk::ExternalMemoryImageCreateInfo::builder().handle_types(handle_type); + let image = device + .create_image( + &vk::ImageCreateInfo { + image_type: vk::ImageType::TYPE_2D, + format: *format, + extent: (*resolution).into(), + mip_levels: 1, + array_layers: 1, + samples: vk::SampleCountFlags::TYPE_1, + tiling: vk::ImageTiling::OPTIMAL, + usage: vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED, + sharing_mode: vk::SharingMode::EXCLUSIVE, + p_next: &mut handle_info as *mut _ as *mut _, + ..Default::default() + }, + None, + ) + .unwrap(); + + let memory_requirements = device.get_image_memory_requirements(image); + let memory_index = find_memorytype_index( + &memory_requirements, + &context.memory_properties, + vk::MemoryPropertyFlags::DEVICE_LOCAL, + ) + .expect("Unable to find suitable memory type for image"); + let mut export_handle_info = + vk::ExportMemoryAllocateInfo::builder().handle_types(handle_type); + let memory = context + .device + .allocate_memory( + &vk::MemoryAllocateInfo::builder() + .allocation_size(memory_requirements.size) + .memory_type_index(memory_index) + .push_next(&mut export_handle_info), + None, + ) + .unwrap(); + + device.bind_image_memory(image, memory, 0).unwrap(); + + let external_memory = + ash::extensions::khr::ExternalMemoryWin32::new(&context.instance, &context.device); + let handle = external_memory + .get_memory_win32_handle( + &vk::MemoryGetWin32HandleInfoKHR::builder() + .handle_type(handle_type) + .memory(memory), + ) + .unwrap(); + debug!("Created handle {handle:?}"); + + (image, handle) + }) + .unzip() +} + +fn send_swapchain_info( + stream: &mut UnixStream, + swapchain_info: &SwapchainInfo, + buf: &mut [u8], +) -> std::io::Result<()> { + stream.read(buf)?; + let value = buf[0]; + debug!("Read {value}"); + + if value == 0 { + let write = stream.write(bytes_of(swapchain_info)).unwrap(); + debug!("Write {write} bytes"); + return Ok(()); + } else { + panic!("Invalid request!"); + } +} + +fn send_image_memory_handles( + stream: &mut UnixStream, + handles: Vec, + buf: &mut [u8], +) -> std::io::Result<()> { + stream.read(buf)?; + let value = buf[0]; + debug!("Read {value}"); + + if value == 1 { + debug!("Sending handles: {handles:?}"); + let write = stream.write(bytes_of_slice(&handles)).unwrap(); + debug!("Write {write} bytes"); + return Ok(()); + } else { + panic!("Invalid request!"); + } +} + +fn bytes_of_slice(t: &[T]) -> &[u8] { + unsafe { + let ptr = t.as_ptr(); + std::slice::from_raw_parts(ptr.cast(), std::mem::size_of::() * t.len()) + } +} + +fn bytes_of(t: &T) -> &[u8] { + unsafe { + let ptr = t as *const T; + std::slice::from_raw_parts(ptr.cast(), std::mem::size_of::()) + } +} diff --git a/hotham-editor/src/shaders/triangle.frag b/hotham-editor/src/shaders/triangle.frag new file mode 100644 index 00000000..d0eaa73f --- /dev/null +++ b/hotham-editor/src/shaders/triangle.frag @@ -0,0 +1,29 @@ +#version 450 +#define NO_TEXTURE 4294967295 +#define WORKFLOW_MAIN 0 +#define WORKFLOW_TEXT 1 + +layout (location = 0) in vec4 in_color; +layout (location = 1) in vec2 in_uv; +layout (location = 0) out vec4 out_color; + +layout(set = 0, binding = 0) uniform sampler2D textures[16]; +layout(push_constant) uniform push_constants { + uint texture_id; + uint workflow; +}; + +void main() { + if (texture_id == NO_TEXTURE) { + out_color = in_color; + return; + } + + if (workflow == WORKFLOW_TEXT) { + float coverage = texture(textures[texture_id], in_uv).r; + out_color = in_color * coverage; + } else { + vec4 user_texture = texture(textures[texture_id], in_uv); + out_color = in_color * user_texture; + } +} \ No newline at end of file diff --git a/hotham-editor/src/shaders/triangle.vert b/hotham-editor/src/shaders/triangle.vert new file mode 100644 index 00000000..c2f2858e --- /dev/null +++ b/hotham-editor/src/shaders/triangle.vert @@ -0,0 +1,14 @@ +#version 450 + +layout(location = 0) in vec4 in_position; +layout(location = 1) in vec4 in_colour; +layout(location = 2) in vec2 in_uv; + +layout(location = 0) out vec4 out_colour; +layout(location = 1) out vec2 out_uv; + +void main() { + gl_Position = in_position; + out_colour = in_colour; + out_uv = in_uv; +} \ No newline at end of file diff --git a/hotham-openxr-client/Cargo.toml b/hotham-openxr-client/Cargo.toml new file mode 100644 index 00000000..2846a310 --- /dev/null +++ b/hotham-openxr-client/Cargo.toml @@ -0,0 +1,19 @@ +[package] +edition = "2021" +name = "hotham_openxr_client" +version = "0.1.0" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +ash = "0.37.2" +env_logger = "0.10.0" +lazy_vulkan = {git = "https://github.com/leetvr/lazy_vulkan", rev = "aad2f44"}# main @ 8/2, year of our lord twenty twenty three +log = "0.4.17" +once_cell = "1.17.0" +openxr-sys = "0.9.3" +rand = "0.8.5" + +[target.'cfg(windows)'.dependencies] +uds_windows = "1.0.2" diff --git a/hotham-openxr-client/README.md b/hotham-openxr-client/README.md new file mode 100644 index 00000000..85171660 --- /dev/null +++ b/hotham-openxr-client/README.md @@ -0,0 +1,19 @@ +# Hotham OpenXR Client +This package will eventually replace the `hotham-simulator` package, along with `hotham-editor`. + +Currently, `hotham-simulator` does a lot. It: + +- Implements (most) of the OpenXR runtime +- It acts as an OpenXR server +- It controls the window and inputs for the OpenXR runtime + +This is a bit too much and is mostly a result of Kane's misunderstanding of how OpenXR works. + +# The new model +In The New Model, which is shiny and perfect and has no flaws, the OpenXR client is much more limited in scope. + +- It creates a dynamic library that the OpenXR loader can call into +- It handles Vulkan instance and device creation on behalf of the OpenXR app +- It communicates with the OpenXR server (that is, `hotham-editor`) to submit frames and check for input events + +You can see a flowchart of how this all works [here](https://www.figma.com/file/5kDF7s5wNewPQY7hw1WXsd/Hotham-Editor?node-id=0%3A1&t=iZ09gupiiA5nqFYR-1) diff --git a/hotham-openxr-client/hotham-openxr-client.json b/hotham-openxr-client/hotham-openxr-client.json new file mode 100644 index 00000000..f6a32f73 --- /dev/null +++ b/hotham-openxr-client/hotham-openxr-client.json @@ -0,0 +1,8 @@ +{ + "file_format_version": "1.0.0", + "runtime": { + "api_version": "1.0", + "name": "Hotham OpenXR Client", + "library_path": "..\\target\\debug\\hotham_openxr_client.dll" + } +} diff --git a/hotham-openxr-client/openxr_client.reg b/hotham-openxr-client/openxr_client.reg new file mode 100644 index 0000000000000000000000000000000000000000..648f339ce44512e548db9cb5135df8f8dc471ac1 GIT binary patch literal 390 zcmaKn%Sr=L5Jb;f@E^kL6U@Sm;6g^lkf<;|qBs(QhGaC(ge1OxUOjzvAqaB0kLs$f zuKW2hq@w1YhF4a!D3~y{wqnhacPj_7m8cQ7xRhVYR^nQ`O@}SlCL3wra75ppYeoz> zC1=lj#IbiS*W*OCc^T z5@qbVb9U61T@oz#)(&7^H}%$*$g QZ|{HF4i9FFpJwS#Usr=hDF6Tf literal 0 HcmV?d00001 diff --git a/hotham-openxr-client/src/client.rs b/hotham-openxr-client/src/client.rs new file mode 100644 index 00000000..137c92a5 --- /dev/null +++ b/hotham-openxr-client/src/client.rs @@ -0,0 +1,802 @@ +#![allow( + non_snake_case, + dead_code, + non_upper_case_globals, + non_camel_case_types +)] +use crate::space_state::SpaceState; +use ash::vk::{self, Handle}; +use log::{debug, error, trace}; +use once_cell::sync::OnceCell; +use openxr_sys::{ + platform::{VkDevice, VkInstance, VkPhysicalDevice, VkResult}, + Action, ActionCreateInfo, ActionSet, ActionSetCreateInfo, ActionSpaceCreateInfo, + ActionStateBoolean, ActionStateFloat, ActionStateGetInfo, ActionStatePose, ActionsSyncInfo, + EnvironmentBlendMode, EventDataBuffer, ExtensionProperties, FrameBeginInfo, FrameEndInfo, + FrameState, FrameWaitInfo, GraphicsBindingVulkanKHR, GraphicsRequirementsVulkanKHR, + HapticActionInfo, HapticBaseHeader, Instance, InstanceCreateInfo, InstanceProperties, + InteractionProfileSuggestedBinding, Path, ReferenceSpaceCreateInfo, ReferenceSpaceType, Result, + Session, SessionActionSetsAttachInfo, SessionBeginInfo, SessionCreateInfo, Space, + SpaceLocation, StructureType, Swapchain, SwapchainCreateInfo, SwapchainImageAcquireInfo, + SwapchainImageBaseHeader, SwapchainImageReleaseInfo, SwapchainImageWaitInfo, SystemGetInfo, + SystemId, SystemProperties, Time, Version, View, ViewConfigurationType, ViewConfigurationView, + ViewLocateInfo, ViewState, VulkanDeviceCreateInfoKHR, VulkanGraphicsDeviceGetInfoKHR, + VulkanInstanceCreateInfoKHR, FALSE, TRUE, +}; + +use lazy_vulkan::vulkan_context::VulkanContext; +use std::{ + collections::HashMap, + ffi::{c_char, CStr}, + mem::transmute, +}; +use std::{ptr, slice}; +use uds_windows::UnixStream; + +type PartialVulkan = (ash::Entry, ash::Instance); +type SpaceMap = HashMap; + +// Used during the init phase +static mut PARTIAL_VULKAN: OnceCell = OnceCell::new(); +static INSTANCE: OnceCell = OnceCell::new(); +static SESSION: OnceCell = OnceCell::new(); +static VULKAN_CONTEXT: OnceCell = OnceCell::new(); +static mut SPACES: OnceCell = OnceCell::new(); +static STREAM: OnceCell = OnceCell::new(); + +pub unsafe extern "system" fn enumerate_instance_extension_properties( + _layer_names: *const ::std::os::raw::c_char, + property_capacity_input: u32, + property_count_output: *mut u32, + properties: *mut ExtensionProperties, +) -> Result { + // If the client didn't initialise env logger, do it now + let _ = env_logger::builder() + .filter_module("hotham_openxr_client", log::LevelFilter::Trace) + .try_init(); + + trace!("enumerate_instance_extension_properties"); + + set_array( + property_capacity_input, + property_count_output, + properties, + [ + ExtensionProperties { + ty: StructureType::EXTENSION_PROPERTIES, + next: ptr::null_mut(), + extension_name: str_to_fixed_bytes("XR_KHR_vulkan_enable"), + extension_version: 1, + }, + ExtensionProperties { + ty: StructureType::EXTENSION_PROPERTIES, + next: ptr::null_mut(), + extension_name: str_to_fixed_bytes("XR_KHR_vulkan_enable2"), + extension_version: 1, + }, + ], + ); + Result::SUCCESS +} + +pub unsafe extern "system" fn create_instance( + _create_info: *const InstanceCreateInfo, + instance: *mut Instance, +) -> Result { + // If the client didn't initialise env logger, do it now + let _ = env_logger::builder() + .filter_module("hotham_openxr_client", log::LevelFilter::Trace) + .try_init(); + + trace!("create_instance"); + + // Initialise our spaces map + let _ = SPACES.set(Default::default()); + + // New instance, new luck. + *instance = Instance::from_raw(rand::random()); + let _ = INSTANCE.set(*instance); + + // Connect to the server + match UnixStream::connect("hotham_editor.socket") { + Ok(stream) => { + trace!("Successfully connected to editor!"); + drop(STREAM.set(stream)); + Result::SUCCESS + } + Err(e) => { + error!("Unable to establish connection to editor: {e:?}"); + Result::ERROR_INITIALIZATION_FAILED + } + } +} + +pub unsafe extern "system" fn get_system( + _instance: Instance, + _get_info: *const SystemGetInfo, + system_id: *mut SystemId, +) -> Result { + trace!("get_system"); + // we are teh leetzor systemz + *system_id = SystemId::from_raw(1337); + Result::SUCCESS +} + +pub unsafe extern "system" fn create_vulkan_instance( + _instance: Instance, + create_info: *const VulkanInstanceCreateInfoKHR, + vulkan_instance: *mut VkInstance, + vulkan_result: *mut VkResult, +) -> Result { + trace!("create_vulkan_instance"); + + // I mean, look, we're *meant* to use the pfnGetInstanceProcAddr from the client + // but what are the odds that it's going to be any different from ours? + // + // We do care about the extensions though - they're important. + let create_info = *create_info; + + let instance_create_info: &vk::InstanceCreateInfo = &(*create_info.vulkan_create_info.cast()); + + let extension_names = if instance_create_info.enabled_extension_count > 0 { + std::slice::from_raw_parts( + instance_create_info.pp_enabled_extension_names, + instance_create_info.enabled_extension_count as _, + ) + } else { + &[] + }; + + trace!("Application requested extension names: {extension_names:?}"); + + let (entry, instance) = lazy_vulkan::vulkan_context::init(&mut extension_names.to_vec()); + *vulkan_instance = transmute(instance.handle().as_raw()); + + let _ = PARTIAL_VULKAN.set((entry, instance)); + + *vulkan_result = vk::Result::SUCCESS.as_raw(); + Result::SUCCESS +} + +pub unsafe extern "system" fn get_vulkan_graphics_device_2( + _instance: Instance, + _get_info: *const VulkanGraphicsDeviceGetInfoKHR, + vulkan_physical_device: *mut VkPhysicalDevice, +) -> Result { + trace!("get_vulkan_graphics_device_2"); + let (_, instance) = PARTIAL_VULKAN.get().unwrap(); + let physical_device = lazy_vulkan::vulkan_context::get_physical_device(instance, None, None).0; + trace!("Physical device: {physical_device:?}"); + *vulkan_physical_device = transmute(physical_device.as_raw()); + Result::SUCCESS +} + +pub unsafe extern "system" fn create_vulkan_device( + _instance: Instance, + create_info: *const VulkanDeviceCreateInfoKHR, + vulkan_device: *mut VkDevice, + vulkan_result: *mut VkResult, +) -> Result { + trace!("create_vulkan_device"); + let (_, instance) = PARTIAL_VULKAN.get().unwrap(); + let create_info = &*create_info; + let physical_device: vk::PhysicalDevice = + vk::PhysicalDevice::from_raw(transmute(create_info.vulkan_physical_device)); + let device_create_info: &vk::DeviceCreateInfo = &*create_info.vulkan_create_info.cast(); + trace!("Physical device: {physical_device:?}"); + trace!("Create info: {device_create_info:?}"); + + // let t = instance. + + // Create a Vulkan device for the *application*. We'll stash our own away soon enough, + // don't you worry about that. + let device = instance + .create_device(physical_device, &vk::DeviceCreateInfo::builder(), None) + .unwrap(); + + *vulkan_device = transmute(device.handle().as_raw()); + + *vulkan_result = vk::Result::SUCCESS.as_raw(); + Result::SUCCESS +} + +pub unsafe extern "system" fn get_vulkan_physical_device( + _instance: Instance, + _system_id: SystemId, + _vk_instance: VkInstance, + _vk_physical_device: *mut VkPhysicalDevice, +) -> Result { + trace!("get_vulkan_physical_device"); + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn get_vulkan_graphics_requirements( + _instance: Instance, + _system_id: SystemId, + graphics_requirements: *mut GraphicsRequirementsVulkanKHR, +) -> Result { + trace!("get_vulkan_graphics_requirements"); + *graphics_requirements = GraphicsRequirementsVulkanKHR { + ty: GraphicsRequirementsVulkanKHR::TYPE, + next: ptr::null_mut(), + min_api_version_supported: Version::new(1, 1, 0), + max_api_version_supported: Version::new(1, 3, 0), + }; + Result::SUCCESS +} + +pub unsafe extern "system" fn get_instance_properties( + _instance: Instance, + instance_properties: *mut InstanceProperties, +) -> Result { + trace!("get_instance_properties"); + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn enumerate_environment_blend_modes( + _instance: Instance, + _system_id: SystemId, + _view_configuration_type: ViewConfigurationType, + environment_blend_mode_capacity_input: u32, + environment_blend_mode_count_output: *mut u32, + environment_blend_modes: *mut EnvironmentBlendMode, +) -> Result { + trace!("enumerate_environment_blend_modes"); + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn create_session( + _instance: Instance, + create_info: *const SessionCreateInfo, + session: *mut Session, +) -> Result { + trace!("create_session"); + *session = Session::from_raw(rand::random()); + let _ = SESSION.set(*session); // TODO: I'm not sure if it should be an error to create a new session again + let graphics_binding = &*((*create_info).next as *const GraphicsBindingVulkanKHR); + let (entry, instance) = PARTIAL_VULKAN.take().unwrap(); + let physical_device = vk::PhysicalDevice::from_raw(transmute(graphics_binding.physical_device)); + let device = ash::Device::load(instance.fp_v1_0(), transmute(graphics_binding.device)); + let queue_family_index = graphics_binding.queue_family_index; + + // It's probably fine if the vulkan context already exists + let _ = VULKAN_CONTEXT.set(VulkanContext::new_with_niche_use_case( + entry, + instance, + physical_device, + device, + queue_family_index, + )); + + Result::SUCCESS +} + +pub unsafe extern "system" fn create_action_set( + _instance: Instance, + create_info: *const ActionSetCreateInfo, + action_set: *mut ActionSet, +) -> Result { + trace!("create_action_set"); + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn create_action( + _action_set: ActionSet, + _create_info: *const ActionCreateInfo, + action_out: *mut Action, +) -> Result { + trace!("create_action"); + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn suggest_interaction_profile_bindings( + _instance: Instance, + suggested_bindings: *const InteractionProfileSuggestedBinding, +) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn string_to_path( + _instance: Instance, + path_string: *const c_char, + path_out: *mut Path, +) -> Result { + match CStr::from_ptr(path_string).to_str() { + Ok(s) => { + let path = Path::from_raw(rand::random()); + Result::ERROR_FEATURE_UNSUPPORTED + } + Err(_) => Result::ERROR_VALIDATION_FAILURE, + } +} + +pub unsafe extern "system" fn attach_action_sets( + _session: Session, + _attach_info: *const SessionActionSetsAttachInfo, +) -> Result { + println!("[HOTHAM_SIMULATOR] Attach action sets called"); + Result::ERROR_FEATURE_UNSUPPORTED +} + +// TODO: Handle aim pose. +pub unsafe extern "system" fn create_action_space( + _session: Session, + create_info: *const ActionSpaceCreateInfo, + space_out: *mut Space, +) -> Result { + // match state + // .path_string + // .get(&(*create_info).subaction_path) + // .map(|s| s.as_str()) + // { + // Some("/user/hand/left") => { + // let mut space_state = SpaceState::new("Left Hand"); + // space_state.position = Vector3f { + // x: -0.20, + // y: 1.4, + // z: -0.50, + // }; + // space_state.orientation = Quaternionf { + // x: 0.707, + // y: 0., + // z: 0., + // w: 0.707, + // }; + // println!("[HOTHAM_SIMULATOR] Created left hand space: {space_state:?}, {space:?}"); + // state.left_hand_space = raw; + // state.spaces.insert(raw, space_state); + // } + // Some("/user/hand/right") => { + // let mut space_state = SpaceState::new("Right Hand"); + // space_state.orientation = Quaternionf { + // x: 0.707, + // y: 0., + // z: 0., + // w: 0.707, + // }; + // space_state.position = Vector3f { + // x: 0.20, + // y: 1.4, + // z: -0.50, + // }; + // println!("[HOTHAM_SIMULATOR] Created right hand space: {space_state:?}, {space:?}"); + // state.right_hand_space = raw; + // state.spaces.insert(raw, space_state); + // } + // Some(path) => { + // let space_state = SpaceState::new(path); + // println!("[HOTHAM_SIMULATOR] Created space for path: {path}"); + // state.spaces.insert(raw, space_state); + // } + // _ => {} + // } + + // *space_out = space; + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn create_reference_space( + _session: Session, + create_info: *const ReferenceSpaceCreateInfo, + out_space: *mut Space, +) -> Result { + trace!("create_reference_space"); + let create_info = *create_info; + + // Our "reference space" is Stage with no rotation + let (reference_space, mut space_state) = if create_info.reference_space_type + == ReferenceSpaceType::STAGE + && create_info.pose_in_reference_space.orientation.w != 1.0 + { + // Magic value + (Space::from_raw(0), SpaceState::new("Stage")) + } else { + (Space::from_raw(rand::random()), SpaceState::new("View")) + }; + + space_state.position = create_info.pose_in_reference_space.position; + space_state.orientation = create_info.pose_in_reference_space.orientation; + + SPACES + .get_mut() + .unwrap() + .insert(reference_space.into_raw(), space_state); + + *out_space = reference_space; + Result::SUCCESS +} + +pub unsafe extern "system" fn poll_event( + _instance: Instance, + event_data: *mut EventDataBuffer, +) -> Result { + // let mut state = STATE.lock().unwrap(); + // let mut next_state = state.session_state; + // if state.session_state == SessionState::UNKNOWN { + // next_state = SessionState::IDLE; + // state.has_event = true; + // } + // if state.session_state == SessionState::IDLE { + // next_state = SessionState::READY; + // state.has_event = true; + // } + // if state.session_state == SessionState::READY { + // next_state = SessionState::SYNCHRONIZED; + // state.has_event = true; + // } + // if state.session_state == SessionState::SYNCHRONIZED { + // next_state = SessionState::VISIBLE; + // state.has_event = true; + // } + // if state.session_state == SessionState::SYNCHRONIZED { + // next_state = SessionState::FOCUSED; + // state.has_event = true; + // } + + // if state.has_event { + // let data = EventDataSessionStateChanged { + // ty: StructureType::EVENT_DATA_SESSION_STATE_CHANGED, + // next: ptr::null(), + // session: Session::from_raw(42), + // state: next_state, + // time: openxr_sys::Time::from_nanos(10), + // }; + // copy_nonoverlapping(&data, transmute(event_data), 1); + // state.has_event = false; + // state.session_state = next_state; + + // Result::ERROR_FEATURE_UNSUPPORTED + // } else { + Result::EVENT_UNAVAILABLE + // } +} + +pub unsafe extern "system" fn begin_session( + session: Session, + _begin_info: *const SessionBeginInfo, +) -> Result { + debug!("[HOTHAM_SIMULATOR] Beginning session: {session:?}"); + Result::ERROR_FEATURE_UNSUPPORTED +} +pub unsafe extern "system" fn wait_frame( + _session: Session, + _frame_wait_info: *const FrameWaitInfo, + frame_state: *mut FrameState, +) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn begin_frame( + _session: Session, + _frame_begin_info: *const FrameBeginInfo, +) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn enumerate_view_configuration_views( + _instance: Instance, + _system_id: SystemId, + _view_configuration_type: ViewConfigurationType, + view_capacity_input: u32, + view_count_output: *mut u32, + views: *mut ViewConfigurationView, +) -> Result { + // if view_capacity_input == 0 { + // *view_count_output = NUM_VIEWS as _; + // return Result::ERROR_FEATURE_UNSUPPORTED; + // } + + // println!( + // "[HOTHAM_SIMULATOR] enumerate_view_configuration_views called with: {view_capacity_input}" + // ); + + // let views = std::ptr::slice_from_raw_parts_mut(views, NUM_VIEWS); + + // for i in 0..NUM_VIEWS { + // (*views)[i] = ViewConfigurationView { + // ty: StructureType::VIEW_CONFIGURATION_VIEW, + // next: null_mut(), + // recommended_image_rect_width: VIEWPORT_WIDTH as _, + // max_image_rect_width: VIEWPORT_WIDTH as _, + // recommended_image_rect_height: VIEWPORT_HEIGHT as _, + // max_image_rect_height: VIEWPORT_HEIGHT as _, + // recommended_swapchain_sample_count: 3, + // max_swapchain_sample_count: 3, + // }; + // } + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn create_xr_swapchain( + _session: Session, + create_info: *const SwapchainCreateInfo, + swapchain: *mut Swapchain, +) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn enumerate_swapchain_images( + _swapchain: Swapchain, + image_capacity_input: u32, + image_count_output: *mut u32, + images: *mut SwapchainImageBaseHeader, +) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn acquire_swapchain_image( + swapchain: Swapchain, + _acquire_info: *const SwapchainImageAcquireInfo, + index: *mut u32, +) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn wait_swapchain_image( + _swapchain: Swapchain, + _wait_info: *const SwapchainImageWaitInfo, +) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn dummy() -> Result { + error!("[HOTHAM_OPENXR_CLIENT] Uh oh, dummy called"); + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn locate_space( + space: Space, + _base_space: Space, + _time: Time, + location_out: *mut SpaceLocation, +) -> Result { + // match STATE.lock().unwrap().spaces.get(&space.into_raw()) { + // Some(space_state) => { + // let pose = Posef { + // position: space_state.position, + // orientation: space_state.orientation, + // }; + // *location_out = SpaceLocation { + // ty: StructureType::SPACE_LOCATION, + // next: null_mut(), + // location_flags: SpaceLocationFlags::ORIENTATION_TRACKED + // | SpaceLocationFlags::POSITION_VALID + // | SpaceLocationFlags::ORIENTATION_VALID, + // pose, + // }; + // Result::ERROR_FEATURE_UNSUPPORTED + // } + // None => Result::ERROR_HANDLE_INVALID, + // } + Result::ERROR_FEATURE_UNSUPPORTED +} +pub unsafe extern "system" fn get_action_state_pose( + _session: Session, + _get_info: *const ActionStateGetInfo, + state: *mut ActionStatePose, +) -> Result { + *state = ActionStatePose { + ty: StructureType::ACTION_STATE_POSE, + next: ptr::null_mut(), + is_active: TRUE, + }; + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn sync_actions( + _session: Session, + _sync_info: *const ActionsSyncInfo, +) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn locate_views( + _session: Session, + _view_locate_info: *const ViewLocateInfo, + view_state: *mut ViewState, + view_capacity_input: u32, + view_count_output: *mut u32, + views: *mut View, +) -> Result { + // *view_count_output = NUM_VIEWS as _; + + // if view_capacity_input == 0 { + // return Result::ERROR_FEATURE_UNSUPPORTED; + // } + + // *view_state = ViewState { + // ty: StructureType::VIEW_STATE, + // next: null_mut(), + // view_state_flags: ViewStateFlags::ORIENTATION_VALID | ViewStateFlags::POSITION_VALID, + // }; + // let views = slice::from_raw_parts_mut(views, NUM_VIEWS); + // let state = STATE.lock().unwrap(); + // #[allow(clippy::approx_constant)] + // for (i, view) in views.iter_mut().enumerate() { + // let pose = state.view_poses[i]; + + // // The actual fov is defined as (right - left). As these are all symetrical, we just divide the fov variable by 2. + // *view = View { + // ty: StructureType::VIEW, + // next: null_mut(), + // pose, + // fov: Fovf { + // angle_down: -CAMERA_FIELD_OF_VIEW / 2., + // angle_up: CAMERA_FIELD_OF_VIEW / 2., + // angle_left: -CAMERA_FIELD_OF_VIEW / 2., + // angle_right: CAMERA_FIELD_OF_VIEW / 2., + // }, + // }; + // } + + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn release_swapchain_image( + _swapchain: Swapchain, + _release_info: *const SwapchainImageReleaseInfo, +) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn end_frame( + _session: Session, + frame_end_info: *const FrameEndInfo, +) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn request_exit_session(_session: Session) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn destroy_space(_space: Space) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn destroy_action(_action: Action) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn destroy_action_set(_action_set: ActionSet) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn destroy_swapchain(_swapchain: Swapchain) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn destroy_session(_session: Session) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn destroy_instance(_instance: Instance) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn enumerate_view_configurations( + _instance: Instance, + _system_id: SystemId, + _view_configuration_type_capacity_input: u32, + view_configuration_type_count_output: *mut u32, + _view_configuration_types: *mut ViewConfigurationType, +) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn enumerate_reference_spaces( + _session: Session, + space_capacity_input: u32, + space_count_output: *mut u32, + spaces: *mut ReferenceSpaceType, +) -> Result { + *space_count_output = 1; + if space_capacity_input == 0 { + return Result::ERROR_FEATURE_UNSUPPORTED; + } + + let spaces = slice::from_raw_parts_mut(spaces, 1); + spaces[0] = ReferenceSpaceType::STAGE; + + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn get_system_properties( + _instance: Instance, + _system_id: SystemId, + _properties: *mut SystemProperties, +) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn enumerate_swapchain_formats( + _session: Session, + format_capacity_input: u32, + format_count_output: *mut u32, + formats: *mut i64, +) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn get_action_state_float( + _session: Session, + _get_info: *const ActionStateGetInfo, + state: *mut ActionStateFloat, +) -> Result { + *state = ActionStateFloat { + ty: StructureType::ACTION_STATE_FLOAT, + next: ptr::null_mut(), + current_state: 0.0, + changed_since_last_sync: FALSE, + last_change_time: openxr_sys::Time::from_nanos(0), + is_active: TRUE, + }; + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn end_session(_session: Session) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn get_action_state_boolean( + _session: Session, + get_info: *const ActionStateGetInfo, + action_state: *mut ActionStateBoolean, +) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn apply_haptic_feedback( + _session: Session, + _haptic_action_info: *const HapticActionInfo, + _haptic_feedback: *const HapticBaseHeader, +) -> Result { + /* explicit no-op, could possibly be extended with controller support in future if winit ever + * provides such APIs */ + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn get_vulkan_instance_extensions( + _instance: Instance, + _system_id: SystemId, + buffer_capacity_input: u32, + buffer_count_output: *mut u32, + buffer: *mut c_char, +) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +pub unsafe extern "system" fn get_vulkan_device_extensions( + _instance: Instance, + _system_id: SystemId, + buffer_capacity_input: u32, + buffer_count_output: *mut u32, + buffer: *mut c_char, +) -> Result { + Result::ERROR_FEATURE_UNSUPPORTED +} + +fn str_to_fixed_bytes(string: &str) -> [i8; 128] { + let mut name = [0i8; 128]; + string + .bytes() + .zip(name.iter_mut()) + .for_each(|(b, ptr)| *ptr = b as i8); + name +} + +unsafe fn set_array( + input_count: u32, + output_count: *mut u32, + array_ptr: *mut T, + data: [T; COUNT], +) { + if input_count == 0 { + *output_count = data.len() as _; + return; + } + + // There's probably some clever way to do this without copying, but whatever + let slice = slice::from_raw_parts_mut(array_ptr, COUNT); + slice.copy_from_slice(&data); +} diff --git a/hotham-openxr-client/src/lib.rs b/hotham-openxr-client/src/lib.rs new file mode 100644 index 00000000..61288bef --- /dev/null +++ b/hotham-openxr-client/src/lib.rs @@ -0,0 +1,163 @@ +// TODO Safety doc would be nice +#![allow(clippy::missing_safety_doc)] + +mod client; + +mod space_state; +mod state; + +use std::ffi::c_char; + +use crate::client::*; + +use openxr_sys::{loader, pfn, Instance, Result}; + +type DummyFn = unsafe extern "system" fn() -> Result; + +pub unsafe extern "system" fn get_instance_proc_addr( + _instance: Instance, + name: *const c_char, + function: *mut Option, +) -> Result { + use std::{ffi::CStr, intrinsics::transmute}; + + let name = CStr::from_ptr(name); + let name = name.to_bytes(); + if name == b"xrGetInstanceProcAddr" { + *function = transmute::(get_instance_proc_addr); + } else if name == b"xrEnumerateInstanceExtensionProperties" { + *function = transmute::( + enumerate_instance_extension_properties, + ); + } else if name == b"xrCreateInstance" { + *function = transmute::(create_instance); + } else if name == b"xrCreateVulkanInstanceKHR" { + *function = transmute::(create_vulkan_instance); + } else if name == b"xrCreateVulkanDeviceKHR" { + *function = transmute::(create_vulkan_device); + } else if name == b"xrGetVulkanGraphicsDevice2KHR" { + *function = transmute::(get_vulkan_graphics_device_2); + } else if name == b"xrGetInstanceProperties" { + *function = transmute::(get_instance_properties); + } else if name == b"xrGetVulkanGraphicsRequirements2KHR" { + *function = transmute::( + get_vulkan_graphics_requirements, + ); + } else if name == b"xrGetVulkanGraphicsDeviceKHR" { + *function = transmute::(get_vulkan_physical_device); + } else if name == b"xrGetVulkanGraphicsRequirementsKHR" { + *function = + transmute::(get_vulkan_graphics_requirements); + } else if name == b"xrGetVulkanInstanceExtensionsKHR" { + *function = + transmute::(get_vulkan_instance_extensions); + } else if name == b"xrGetVulkanDeviceExtensionsKHR" { + *function = transmute::(get_vulkan_device_extensions); + } else if name == b"xrEnumerateEnvironmentBlendModes" { + *function = + transmute::(enumerate_environment_blend_modes); + } else if name == b"xrGetSystem" { + *function = transmute::(get_system); + } else if name == b"xrCreateSession" { + *function = transmute::(create_session); + } else if name == b"xrCreateActionSet" { + *function = transmute::(create_action_set); + } else if name == b"xrCreateAction" { + *function = transmute::(create_action); + } else if name == b"xrSuggestInteractionProfileBindings" { + *function = transmute::( + suggest_interaction_profile_bindings, + ); + } else if name == b"xrStringToPath" { + *function = transmute::(string_to_path); + } else if name == b"xrAttachSessionActionSets" { + *function = transmute::(attach_action_sets); + } else if name == b"xrCreateActionSpace" { + *function = transmute::(create_action_space); + } else if name == b"xrCreateReferenceSpace" { + *function = transmute::(create_reference_space); + } else if name == b"xrPollEvent" { + *function = transmute::(poll_event); + } else if name == b"xrBeginSession" { + *function = transmute::(begin_session); + } else if name == b"xrWaitFrame" { + *function = transmute::(wait_frame); + } else if name == b"xrBeginFrame" { + *function = transmute::(begin_frame); + } else if name == b"xrEnumerateViewConfigurationViews" { + *function = transmute::( + enumerate_view_configuration_views, + ); + } else if name == b"xrCreateSwapchain" { + *function = transmute::(create_xr_swapchain); + } else if name == b"xrEnumerateSwapchainImages" { + *function = transmute::(enumerate_swapchain_images); + } else if name == b"xrAcquireSwapchainImage" { + *function = transmute::(acquire_swapchain_image); + } else if name == b"xrWaitSwapchainImage" { + *function = transmute::(wait_swapchain_image); + } else if name == b"xrSyncActions" { + *function = transmute::(sync_actions); + } else if name == b"xrLocateSpace" { + *function = transmute::(locate_space); + } else if name == b"xrGetActionStatePose" { + *function = transmute::(get_action_state_pose); + } else if name == b"xrLocateViews" { + *function = transmute::(locate_views); + } else if name == b"xrReleaseSwapchainImage" { + *function = transmute::(release_swapchain_image); + } else if name == b"xrEndFrame" { + *function = transmute::(end_frame); + } else if name == b"xrRequestExitSession" { + *function = transmute::(request_exit_session); + } else if name == b"xrDestroySpace" { + *function = transmute::(destroy_space); + } else if name == b"xrDestroyAction" { + *function = transmute::(destroy_action); + } else if name == b"xrDestroyActionSet" { + *function = transmute::(destroy_action_set); + } else if name == b"xrDestroySwapchain" { + *function = transmute::(destroy_swapchain); + } else if name == b"xrDestroySession" { + *function = transmute::(destroy_session); + } else if name == b"xrDestroyInstance" { + *function = transmute::(destroy_instance); + } else if name == b"xrEnumerateViewConfigurations" { + *function = transmute::(enumerate_view_configurations); + } else if name == b"xrEnumerateReferenceSpaces" { + *function = transmute::(enumerate_reference_spaces); + } else if name == b"xrGetSystemProperties" { + *function = transmute::(get_system_properties); + } else if name == b"xrEnumerateSwapchainFormats" { + *function = transmute::(enumerate_swapchain_formats); + } else if name == b"xrGetActionStateFloat" { + *function = transmute::(get_action_state_float); + } else if name == b"xrGetActionStateBoolean" { + *function = transmute::(get_action_state_boolean); + } else if name == b"xrApplyHapticFeedback" { + *function = transmute::(apply_haptic_feedback); + } else if name == b"xrEndSession" { + *function = transmute::(end_session); + } else { + let _name = String::from_utf8_unchecked(name.to_vec()); + unsafe extern "system" fn bang() -> Result { + panic!("UNIMPLEMENTED FUNCTION!"); + } + *function = transmute::(bang); + // return Result::ERROR_HANDLE_INVALID.into_raw(); + } + Result::SUCCESS +} + +#[no_mangle] +pub unsafe extern "system" fn xrNegotiateLoaderRuntimeInterface( + _loader_info: *const loader::XrNegotiateLoaderInfo, + runtime_request: *mut loader::XrNegotiateRuntimeRequest, +) -> Result { + let runtime_request = &mut *runtime_request; + runtime_request.runtime_interface_version = 1; + runtime_request.runtime_api_version = openxr_sys::CURRENT_API_VERSION; + runtime_request.get_instance_proc_addr = Some(get_instance_proc_addr); + + Result::SUCCESS +} diff --git a/hotham-openxr-client/src/space_state.rs b/hotham-openxr-client/src/space_state.rs new file mode 100644 index 00000000..d839a398 --- /dev/null +++ b/hotham-openxr-client/src/space_state.rs @@ -0,0 +1,41 @@ +use core::fmt::Debug; +use openxr_sys::{Quaternionf, Vector3f}; + +#[derive(Clone)] +pub struct SpaceState { + pub name: String, + pub position: Vector3f, + pub orientation: Quaternionf, +} + +impl SpaceState { + pub fn new(name: &str) -> Self { + Self { + name: name.to_string(), + position: Default::default(), + orientation: Quaternionf::IDENTITY, + } + } +} + +impl Debug for SpaceState { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("SpaceState") + .field("name", &self.name) + .field( + "position", + &format!( + "x: {}, y: {}, z: {}", + self.position.x, self.position.y, self.position.z + ), + ) + .field( + "orientation", + &format!( + "x: {}, y: {}, z: {}, w: {}", + self.orientation.x, self.orientation.y, self.orientation.z, self.orientation.w + ), + ) + .finish() + } +} diff --git a/hotham-openxr-client/src/state.rs b/hotham-openxr-client/src/state.rs new file mode 100644 index 00000000..51d82863 --- /dev/null +++ b/hotham-openxr-client/src/state.rs @@ -0,0 +1,16 @@ +use lazy_vulkan::vulkan_context::VulkanContext; +use openxr_sys::Instance; + +pub struct State { + instance: Instance, + vulkan_context: Option, +} + +impl State { + pub fn new(instance: Instance) -> Self { + Self { + instance, + vulkan_context: None, + } + } +} diff --git a/hotham-openxr-client/test.ps1 b/hotham-openxr-client/test.ps1 new file mode 100644 index 00000000..72e69a2a --- /dev/null +++ b/hotham-openxr-client/test.ps1 @@ -0,0 +1,13 @@ +Write-Output "All your OpenXR are belong to crab" +${Env:RUST_BACKTRACE} = 1 + +try { + cargo build -p hotham_openxr_client + cargo run --bin hotham_simple_scene_example +} +catch { + Write-Warning "Problem!" +} +finally { + Pop-Location +} diff --git a/hotham-simulator/src/simulator.rs b/hotham-simulator/src/simulator.rs index aba73048..873c2ad1 100644 --- a/hotham-simulator/src/simulator.rs +++ b/hotham-simulator/src/simulator.rs @@ -173,6 +173,7 @@ pub unsafe extern "system" fn create_vulkan_instance( let mut state = STATE.lock().unwrap(); state.vulkan_entry.replace(entry); + println!("[HOTHAM_SIMULATOR] Setting Vulkan instance.."); state.vulkan_instance.replace(ash_instance); Result::SUCCESS } diff --git a/hotham/Cargo.toml b/hotham/Cargo.toml index f8258932..4de27dbc 100644 --- a/hotham/Cargo.toml +++ b/hotham/Cargo.toml @@ -40,6 +40,9 @@ thiserror = "1.0" tokio = {version = "1.0.1", default-features = false, features = ["rt"]} vk-shader-macros = "0.2.8" +[features] +editor = [] + [target.'cfg(not(any(target_os = "macos", target_os = "ios")))'.dev-dependencies] renderdoc = "0.10" diff --git a/hotham/src/contexts/xr_context/mod.rs b/hotham/src/contexts/xr_context/mod.rs index fc10eb4b..29bc9cab 100644 --- a/hotham/src/contexts/xr_context/mod.rs +++ b/hotham/src/contexts/xr_context/mod.rs @@ -255,7 +255,7 @@ impl XrContext { } } -#[cfg(target_os = "android")] +#[cfg(any(target_os = "android", feature = "editor"))] pub(crate) fn create_vulkan_context( xr_instance: &xr::Instance, system: xr::SystemId, @@ -272,7 +272,7 @@ pub(crate) fn create_vulkan_context( Ok(vulkan_context) } -#[cfg(not(target_os = "android"))] +#[cfg(all(not(target_os = "android"), not(feature = "editor")))] fn create_vulkan_context( xr_instance: &xr::Instance, system: xr::SystemId, @@ -464,6 +464,10 @@ fn enable_xr_extensions(required_extensions: &mut xr::ExtensionSet) { #[cfg(not(target_os = "android"))] fn enable_xr_extensions(required_extensions: &mut xr::ExtensionSet) { required_extensions.khr_vulkan_enable = true; + + if cfg!(feature = "editor") { + required_extensions.khr_vulkan_enable2 = true; + } } #[cfg(target_os = "windows")] From 774bbf348720051cdb528345d373a0cb3a0c2f60 Mon Sep 17 00:00:00 2001 From: Kane Rogers Date: Thu, 9 Feb 2023 23:03:58 +1100 Subject: [PATCH 02/14] Add `hotham-editor-protocol` - Define a way for the editor and clients to talk to eachother - Simple request/response protocol that's transport agnostic - Works perfectly and has no flaws --- .vscode/settings.json | 6 +- Cargo.toml | 2 +- hotham-editor-client/src/main.rs | 3 - .../Cargo.toml | 3 +- hotham-editor-protocol/README.md | 39 +++ hotham-editor-protocol/src/lib.rs | 324 ++++++++++++++++++ hotham-editor/Cargo.toml | 2 + hotham-editor/src/main.rs | 75 +++- hotham-openxr-client/Cargo.toml | 1 + hotham-openxr-client/src/client.rs | 54 +-- hotham-openxr-client/src/lib.rs | 5 - hotham-openxr-client/src/state.rs | 16 - hotham-openxr-client/test.ps1 | 14 +- 13 files changed, 465 insertions(+), 79 deletions(-) delete mode 100644 hotham-editor-client/src/main.rs rename {hotham-editor-client => hotham-editor-protocol}/Cargo.toml (75%) create mode 100644 hotham-editor-protocol/README.md create mode 100644 hotham-editor-protocol/src/lib.rs delete mode 100644 hotham-openxr-client/src/state.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index b15158c1..c66e9925 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -6,5 +6,9 @@ "files.insertFinalNewline": true, "files.trimFinalNewlines": true, "files.trimTrailingWhitespace": true, - "webgl-glsl-editor.format.placeSpaceAfterKeywords": true + "webgl-glsl-editor.format.placeSpaceAfterKeywords": true, + "rust-analyzer.check.extraArgs": [ + "--target-dir", + "C:/windows/temp/rust-analyzer-check" + ], } diff --git a/Cargo.toml b/Cargo.toml index 530c1e2b..8c3c9d97 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ members = [ "hotham-asset-server", "hotham-simulator", "hotham-editor", - "hotham-editor-client", + "hotham-editor-protocol", "hotham-openxr-client", "hotham", ] diff --git a/hotham-editor-client/src/main.rs b/hotham-editor-client/src/main.rs deleted file mode 100644 index e7a11a96..00000000 --- a/hotham-editor-client/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -} diff --git a/hotham-editor-client/Cargo.toml b/hotham-editor-protocol/Cargo.toml similarity index 75% rename from hotham-editor-client/Cargo.toml rename to hotham-editor-protocol/Cargo.toml index 9170ffe2..f073f7ed 100644 --- a/hotham-editor-client/Cargo.toml +++ b/hotham-editor-protocol/Cargo.toml @@ -1,8 +1,9 @@ [package] -name = "hotham-editor-client" +name = "hotham-editor-protocol" version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +openxr-sys = "0.9.3" diff --git a/hotham-editor-protocol/README.md b/hotham-editor-protocol/README.md new file mode 100644 index 00000000..4d20ab98 --- /dev/null +++ b/hotham-editor-protocol/README.md @@ -0,0 +1,39 @@ +# Hotham Editor Protocol +## Why? +Whenever two programs want to talk to eachother, the most complicated question is *where is the protocol defined?*. Sometimes the protocol is inferred from the way the programs serialise and deserialise their messages, but this leads to all sorts of problems. + +In essence, this is a *contract* problem. What is the contract, where is it, and how is it enforced? + +For more on this topic, we can, as always, defer to [Joe Armstrong (RIP)](https://www.youtube.com/watch?v=ed7A7r6DBsM). + +## How? +Simple. We define: + +- What the messages of the protocol are +- A means to **encode** them to bytes +- A means to **decode** them to bytes + +We can even take that a step further and define FSMs (as Joe would suggest), but that is future work. + + +## Examples +Let's say we're using Unix sockets: + +```rust +let socket = UnixStream::connect("hotham_editor.socket").unwrap(); +let client = EditorClient::new(socket); // woah, generics + +let view_configuration = client.request(&requests::GetViewConfiguration {}).unwrap(); // view_configuration is the correct type!! +``` + +This magic is made possible through the `Request` trait: + +```rust +pub trait Request { + /// What should the response to this request be? + type Response: Clone; + + /// Get a `RequestType` tag that we can use to identify requests + fn request_type(&self) -> RequestType; +} +``` diff --git a/hotham-editor-protocol/src/lib.rs b/hotham-editor-protocol/src/lib.rs new file mode 100644 index 00000000..d84ea316 --- /dev/null +++ b/hotham-editor-protocol/src/lib.rs @@ -0,0 +1,324 @@ +use std::io::{Read, Write}; + +pub use openxr_sys::ViewConfigurationView; + +#[repr(u32)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum RequestType { + GetViewConfiguration, + GetViewCount, +} + +pub trait Request { + type Response: Clone; + fn request_type(&self) -> RequestType; +} + +pub trait RequestWithVecResponse { + type ResponseItem: Clone; // shitty name +} + +pub mod requests { + use crate::{responses::ViewConfiguration, Request, RequestType}; + + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct GetViewConfiguration {} + + impl Request for GetViewConfiguration { + type Response = ViewConfiguration; + fn request_type(&self) -> RequestType { + RequestType::GetViewConfiguration + } + } + + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct GetViewCount {} + + impl Request for GetViewCount { + type Response = u32; + fn request_type(&self) -> RequestType { + RequestType::GetViewCount + } + } +} + +pub mod responses { + #[derive(Debug, Clone, Copy)] + pub struct ViewConfiguration { + pub width: u32, + pub height: u32, + } +} + +#[derive(Debug, Clone)] +pub struct RequestHeader { + pub payload_length: u32, + pub request_type: RequestType, +} + +impl From for Vec { + fn from(h: RequestHeader) -> Self { + unsafe { bytes_from_t(&h).to_vec() } + } +} + +fn write_request(request: &R, writer: &mut S) -> std::io::Result<()> { + let header = RequestHeader { + request_type: request.request_type(), + payload_length: std::mem::size_of::() as u32, + }; + + writer.write_all(&{ unsafe { bytes_from_t(&header) } })?; + writer.write_all(&{ unsafe { bytes_from_t(request) } })?; + + Ok(()) +} + +fn read_request_header<'a, S: Read>( + reader: &mut S, + buf: &'a mut [u8], +) -> std::io::Result { + reader.read_exact(&mut buf[..std::mem::size_of::()])?; + let header: RequestHeader = + unsafe { t_from_bytes(&mut buf[..std::mem::size_of::()]) }; + Ok(header) +} + +fn read_request_payload<'a, R: Request + Clone, S: Read>( + reader: &mut S, + buf: &'a mut [u8], + payload_length: usize, +) -> std::io::Result { + reader.read_exact(&mut buf[..payload_length])?; + let payload = &buf[..payload_length]; + Ok(unsafe { t_from_bytes(payload) }) +} + +pub struct EditorClient { + socket: S, + buffer: Vec, +} + +impl EditorClient { + pub fn new(socket: S) -> Self { + Self { + socket, + buffer: vec![0; 1024 * 1024], + } + } + + pub fn request(&mut self, request: &R) -> std::io::Result { + self.send_request(request)?; + self.get_response::() + } + + pub fn request_vec( + &mut self, + request: &R, + ) -> std::io::Result> { + self.send_request(request)?; + self.get_response_vec::() + } + + pub fn send_request(&mut self, request: &R) -> std::io::Result<()> { + write_request(request, &mut self.socket) + } + + pub fn get_response(&mut self) -> std::io::Result { + let socket = &mut self.socket; + let buf = &mut self.buffer; + + let header_size = std::mem::size_of::(); + socket.read_exact(&mut buf[..header_size])?; + let message_size = u32::from_be_bytes(buf[..header_size].try_into().unwrap()) as usize; + + self.socket.read_exact(&mut buf[..message_size])?; + Ok(unsafe { t_from_bytes(&buf[..message_size]) }) + } + + pub fn get_response_vec(&mut self) -> std::io::Result> { + let socket = &mut self.socket; + let buf = &mut self.buffer; + + let header_size = std::mem::size_of::(); + socket.read_exact(&mut buf[..header_size])?; + let message_size = u32::from_be_bytes(buf[..header_size].try_into().unwrap()) as usize; + + self.socket.read_exact(&mut buf[..message_size])?; + Ok(unsafe { vec_from_bytes(&buf[..message_size]) }) + } +} + +pub struct EditorServer { + socket: S, + buffer: Vec, +} + +impl EditorServer { + pub fn new(socket: S) -> Self { + Self { + socket, + buffer: vec![0; 1024 * 1024], + } + } + + /// Helpful if you already know in advance what the request type is + pub fn get_request(&mut self) -> std::io::Result { + let request_header = read_request_header(&mut self.socket, &mut self.buffer)?; + read_request_payload( + &mut self.socket, + &mut self.buffer, + request_header.payload_length as usize, + ) + } + + pub fn get_request_header(&mut self) -> std::io::Result { + read_request_header(&mut self.socket, &mut self.buffer) + } + + pub fn get_request_payload( + &mut self, + payload_length: u32, + ) -> std::io::Result { + read_request_payload(&mut self.socket, &mut self.buffer, payload_length as usize) + } + + pub fn send_response(&mut self, response: &T) -> std::io::Result<()> { + let message_size = std::mem::size_of::() as u32; + self.socket.write(&message_size.to_be_bytes())?; + self.socket.write(&unsafe { bytes_from_t(response) })?; + + Ok(()) + } +} + +unsafe fn vec_from_bytes(data: &[u8]) -> Vec { + let len = data.len() / std::mem::size_of::(); + std::slice::from_raw_parts(data.as_ptr().cast(), len).to_vec() +} + +unsafe fn t_from_bytes(data: &[u8]) -> T { + std::ptr::read(data.as_ptr().cast::()).clone() +} + +unsafe fn bytes_from_t(data: &T) -> Vec { + std::slice::from_raw_parts(data as *const _ as *const u8, std::mem::size_of::()).to_vec() +} + +#[cfg(test)] +mod tests { + use crate::requests::GetViewConfiguration; + + use super::*; + use openxr_sys::{StructureType, ViewConfigurationView}; + use std::{cell::RefCell, rc::Rc}; + + #[derive(Default, Clone)] + struct MockSocket { + pub data: Rc>>, + } + + impl MockSocket { + pub fn reset(&self) { + self.data.borrow_mut().clear(); + } + } + + impl Write for MockSocket { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.data.borrow_mut().write(buf) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } + } + + impl Read for MockSocket { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + let mut data = self.data.borrow_mut(); + let read_length = buf.len(); + buf.copy_from_slice(&data[..buf.len()]); + data.rotate_left(read_length); // shuffle the bytes that we just read back over to the end + Ok(buf.len()) + } + } + + #[test] + pub fn test_request_response() { + let socket = MockSocket::default(); + let mut client = EditorClient::new(socket.clone()); + let mut server = EditorServer::new(socket); + + let request = GetViewConfiguration {}; + client.send_request(&request).unwrap(); + let request_header = server.get_request_header().unwrap(); + match request_header.request_type { + RequestType::GetViewConfiguration => { + let request_from_client: GetViewConfiguration = server + .get_request_payload(request_header.payload_length) + .unwrap(); + assert_eq!(request, request_from_client) + } + _ => panic!("Bad request"), + }; + + server.socket.reset(); + + let response = ViewConfigurationView { + ty: StructureType::VIEW_CONFIGURATION_VIEW, + next: std::ptr::null_mut(), + recommended_image_rect_width: 100, + max_image_rect_width: 100, + recommended_image_rect_height: 100, + max_image_rect_height: 100, + recommended_swapchain_sample_count: 100, + max_swapchain_sample_count: 100, + }; + + server.send_response(&response).unwrap(); + let response_from_server: ViewConfigurationView = client.get_response().unwrap(); + assert_eq!(response.ty, response_from_server.ty); + assert_eq!( + response.max_swapchain_sample_count, + response_from_server.max_swapchain_sample_count + ); + + server.socket.reset(); + + let response = [ + ViewConfigurationView { + ty: StructureType::VIEW_CONFIGURATION_VIEW, + next: std::ptr::null_mut(), + recommended_image_rect_width: 100, + max_image_rect_width: 100, + recommended_image_rect_height: 100, + max_image_rect_height: 100, + recommended_swapchain_sample_count: 100, + max_swapchain_sample_count: 100, + }, + ViewConfigurationView { + ty: StructureType::VIEW_CONFIGURATION_VIEW, + next: std::ptr::null_mut(), + recommended_image_rect_width: 200, + max_image_rect_width: 100, + recommended_image_rect_height: 100, + max_image_rect_height: 100, + recommended_swapchain_sample_count: 100, + max_swapchain_sample_count: 100, + }, + ]; + + server.send_response(&response).unwrap(); + let response_from_server: Vec = client.get_response_vec().unwrap(); + assert_eq!(response.len(), response_from_server.len()); + assert_eq!( + response[0].max_swapchain_sample_count, + response_from_server[0].max_swapchain_sample_count + ); + assert_eq!( + response[1].max_swapchain_sample_count, + response_from_server[1].max_swapchain_sample_count + ); + } +} diff --git a/hotham-editor/Cargo.toml b/hotham-editor/Cargo.toml index 09d6420b..8e5a4cb3 100644 --- a/hotham-editor/Cargo.toml +++ b/hotham-editor/Cargo.toml @@ -4,8 +4,10 @@ name = "hotham-editor" version = "0.1.0" [dependencies] +anyhow = "1.0.69" ash = "0.37.2" env_logger = "0.10.0" +hotham-editor-protocol = {path = "../hotham-editor-protocol"} lazy_vulkan = {git = "https://github.com/leetvr/lazy_vulkan", rev = "5347dd3295fd4c687c35adbe7c0b4aad00c893e3"} log = "0.4.17" winit = "0.28.1" diff --git a/hotham-editor/src/main.rs b/hotham-editor/src/main.rs index c964b800..66ae020e 100644 --- a/hotham-editor/src/main.rs +++ b/hotham-editor/src/main.rs @@ -1,9 +1,11 @@ +use anyhow::{bail, Result}; use ash::vk; +use hotham_editor_protocol::{requests, responses, EditorServer, RequestType}; use lazy_vulkan::{ - find_memorytype_index, vulkan_texture::VulkanTexture, DrawCall, LazyRenderer, LazyVulkan, - SwapchainInfo, Vertex, + find_memorytype_index, vulkan_context::VulkanContext, vulkan_texture::VulkanTexture, DrawCall, + LazyRenderer, LazyVulkan, SwapchainInfo, Vertex, }; -use log::{debug, info}; +use log::{debug, info, trace}; use std::io::{Read, Write}; use uds_windows::{UnixListener, UnixStream}; use winit::{ @@ -18,7 +20,7 @@ static VERTEX_SHADER: &'_ [u8] = include_bytes!("shaders/triangle.vert.spv"); const SWAPCHAIN_FORMAT: vk::Format = vk::Format::R8G8B8A8_UNORM; static UNIX_SOCKET_PATH: &'_ str = "hotham_editor.socket"; -pub fn main() { +pub fn main() -> Result<()> { env_logger::builder() .filter_level(log::LevelFilter::Info) .init(); @@ -49,6 +51,7 @@ pub fn main() { if std::fs::remove_file(UNIX_SOCKET_PATH).is_ok() { debug!("Removed pre-existing unix socket at {UNIX_SOCKET_PATH}"); } + let listener = UnixListener::bind(UNIX_SOCKET_PATH).unwrap(); info!("Listening on {UNIX_SOCKET_PATH} - waiting for client.."); let swapchain_info = SwapchainInfo { @@ -56,17 +59,14 @@ pub fn main() { resolution: lazy_vulkan.surface.surface_resolution, format: SWAPCHAIN_FORMAT, }; - let (images, image_memory_handles) = - unsafe { create_render_images(lazy_vulkan.context(), &swapchain_info) }; - let (semaphores, semaphore_handles) = - unsafe { create_semaphores(lazy_vulkan.context(), swapchain_info.image_count) }; - let textures = create_render_textures(lazy_vulkan.context(), &mut lazy_renderer, images); let (mut stream, _) = listener.accept().unwrap(); - info!("Client connected!"); - let mut buf: [u8; 1024] = [0; 1024]; - send_swapchain_info(&mut stream, &swapchain_info, &mut buf).unwrap(); - send_image_memory_handles(&mut stream, image_memory_handles, &mut buf).unwrap(); - send_semaphore_handles(&mut stream, semaphore_handles, &mut buf); + let mut server = EditorServer::new(stream); + let xr_swapchain = do_openxr_setup(&mut server, lazy_vulkan.context(), &swapchain_info)?; + let textures = create_render_textures( + lazy_vulkan.context(), + &mut lazy_renderer, + xr_swapchain.images, + ); // Off we go! let mut winit_initializing = true; @@ -98,8 +98,8 @@ pub fn main() { Event::MainEventsCleared => { let framebuffer_index = lazy_vulkan.render_begin(); - send_swapchain_image_index(&mut stream, &mut buf, framebuffer_index); - get_render_complete(&mut stream, &mut buf); + // send_swapchain_image_index(&mut stream, &mut buf, framebuffer_index); + // get_render_complete(&mut stream, &mut buf); let texture_id = textures[framebuffer_index as usize].id; lazy_renderer.render( lazy_vulkan.context(), @@ -112,7 +112,7 @@ pub fn main() { )], ); - let semaphore = semaphores[framebuffer_index as usize]; + let semaphore = xr_swapchain.semaphores[framebuffer_index as usize]; lazy_vulkan.render_end( framebuffer_index, &[semaphore, lazy_vulkan.rendering_complete_semaphore], @@ -135,6 +135,47 @@ pub fn main() { unsafe { lazy_renderer.cleanup(&lazy_vulkan.context().device); } + + Ok(()) +} + +pub struct XrSwapchain { + images: Vec, + semaphores: Vec, +} + +fn do_openxr_setup( + server: &mut EditorServer, + vulkan_context: &VulkanContext, + swapchain_info: &SwapchainInfo, +) -> Result { + check_request(server, RequestType::GetViewCount)?; + server.send_response(&swapchain_info.image_count)?; + + check_request(server, RequestType::GetViewConfiguration)?; + server.send_response(&responses::ViewConfiguration { + width: swapchain_info.resolution.width, + height: swapchain_info.resolution.height, + })?; + + let (images, image_memory_handles) = + unsafe { create_render_images(vulkan_context, &swapchain_info) }; + let (semaphores, semaphore_handles) = + unsafe { create_semaphores(vulkan_context, swapchain_info.image_count) }; + + Ok(XrSwapchain { images, semaphores }) +} + +fn check_request( + server: &mut EditorServer, + expected_request_type: RequestType, +) -> Result<(), anyhow::Error> { + let header = server.get_request_header()?; + if header.request_type != expected_request_type { + bail!("Invalid request type: {:?}!", header.request_type); + } + trace!("Received request from cilent {expected_request_type:?}"); + Ok(()) } fn send_semaphore_handles( diff --git a/hotham-openxr-client/Cargo.toml b/hotham-openxr-client/Cargo.toml index 2846a310..44a028a8 100644 --- a/hotham-openxr-client/Cargo.toml +++ b/hotham-openxr-client/Cargo.toml @@ -9,6 +9,7 @@ crate-type = ["cdylib"] [dependencies] ash = "0.37.2" env_logger = "0.10.0" +hotham-editor-protocol = {path = "../hotham-editor-protocol"} lazy_vulkan = {git = "https://github.com/leetvr/lazy_vulkan", rev = "aad2f44"}# main @ 8/2, year of our lord twenty twenty three log = "0.4.17" once_cell = "1.17.0" diff --git a/hotham-openxr-client/src/client.rs b/hotham-openxr-client/src/client.rs index 137c92a5..10da2379 100644 --- a/hotham-openxr-client/src/client.rs +++ b/hotham-openxr-client/src/client.rs @@ -6,6 +6,7 @@ )] use crate::space_state::SpaceState; use ash::vk::{self, Handle}; +use hotham_editor_protocol::{requests, responses, EditorClient, Request}; use log::{debug, error, trace}; use once_cell::sync::OnceCell; use openxr_sys::{ @@ -29,6 +30,7 @@ use std::{ collections::HashMap, ffi::{c_char, CStr}, mem::transmute, + ptr::null_mut, }; use std::{ptr, slice}; use uds_windows::UnixStream; @@ -42,7 +44,7 @@ static INSTANCE: OnceCell = OnceCell::new(); static SESSION: OnceCell = OnceCell::new(); static VULKAN_CONTEXT: OnceCell = OnceCell::new(); static mut SPACES: OnceCell = OnceCell::new(); -static STREAM: OnceCell = OnceCell::new(); +static mut EDITOR_CLIENT: OnceCell> = OnceCell::new(); pub unsafe extern "system" fn enumerate_instance_extension_properties( _layer_names: *const ::std::os::raw::c_char, @@ -101,7 +103,7 @@ pub unsafe extern "system" fn create_instance( match UnixStream::connect("hotham_editor.socket") { Ok(stream) => { trace!("Successfully connected to editor!"); - drop(STREAM.set(stream)); + drop(EDITOR_CLIENT.set(EditorClient::new(stream))); Result::SUCCESS } Err(e) => { @@ -186,8 +188,6 @@ pub unsafe extern "system" fn create_vulkan_device( trace!("Physical device: {physical_device:?}"); trace!("Create info: {device_create_info:?}"); - // let t = instance. - // Create a Vulkan device for the *application*. We'll stash our own away soon enough, // don't you worry about that. let device = instance @@ -481,30 +481,34 @@ pub unsafe extern "system" fn enumerate_view_configuration_views( view_count_output: *mut u32, views: *mut ViewConfigurationView, ) -> Result { - // if view_capacity_input == 0 { - // *view_count_output = NUM_VIEWS as _; - // return Result::ERROR_FEATURE_UNSUPPORTED; - // } + trace!("enumerate_view_configuration_views"); + let client = EDITOR_CLIENT.get_mut().unwrap(); + if view_capacity_input == 0 { + let view_count = client.request(&requests::GetViewCount {}).unwrap(); + trace!("Received view count from server {view_count}"); + *view_count_output = view_count; + return Result::SUCCESS; + } - // println!( - // "[HOTHAM_SIMULATOR] enumerate_view_configuration_views called with: {view_capacity_input}" - // ); + let view_configuration = client.request(&requests::GetViewConfiguration {}).unwrap(); - // let views = std::ptr::slice_from_raw_parts_mut(views, NUM_VIEWS); + set_array( + view_capacity_input, + view_count_output, + views, + [ViewConfigurationView { + ty: StructureType::VIEW_CONFIGURATION_VIEW, + next: null_mut(), + recommended_image_rect_width: view_configuration.width, + max_image_rect_height: view_configuration.height, + recommended_swapchain_sample_count: 1, + max_swapchain_sample_count: 1, + max_image_rect_width: view_configuration.width, + recommended_image_rect_height: view_configuration.height, + }; 3], + ); - // for i in 0..NUM_VIEWS { - // (*views)[i] = ViewConfigurationView { - // ty: StructureType::VIEW_CONFIGURATION_VIEW, - // next: null_mut(), - // recommended_image_rect_width: VIEWPORT_WIDTH as _, - // max_image_rect_width: VIEWPORT_WIDTH as _, - // recommended_image_rect_height: VIEWPORT_HEIGHT as _, - // max_image_rect_height: VIEWPORT_HEIGHT as _, - // recommended_swapchain_sample_count: 3, - // max_swapchain_sample_count: 3, - // }; - // } - Result::ERROR_FEATURE_UNSUPPORTED + Result::SUCCESS } pub unsafe extern "system" fn create_xr_swapchain( diff --git a/hotham-openxr-client/src/lib.rs b/hotham-openxr-client/src/lib.rs index 61288bef..be2cab6e 100644 --- a/hotham-openxr-client/src/lib.rs +++ b/hotham-openxr-client/src/lib.rs @@ -1,10 +1,6 @@ -// TODO Safety doc would be nice -#![allow(clippy::missing_safety_doc)] - mod client; mod space_state; -mod state; use std::ffi::c_char; @@ -144,7 +140,6 @@ pub unsafe extern "system" fn get_instance_proc_addr( panic!("UNIMPLEMENTED FUNCTION!"); } *function = transmute::(bang); - // return Result::ERROR_HANDLE_INVALID.into_raw(); } Result::SUCCESS } diff --git a/hotham-openxr-client/src/state.rs b/hotham-openxr-client/src/state.rs deleted file mode 100644 index 51d82863..00000000 --- a/hotham-openxr-client/src/state.rs +++ /dev/null @@ -1,16 +0,0 @@ -use lazy_vulkan::vulkan_context::VulkanContext; -use openxr_sys::Instance; - -pub struct State { - instance: Instance, - vulkan_context: Option, -} - -impl State { - pub fn new(instance: Instance) -> Self { - Self { - instance, - vulkan_context: None, - } - } -} diff --git a/hotham-openxr-client/test.ps1 b/hotham-openxr-client/test.ps1 index 72e69a2a..e8fe4cec 100644 --- a/hotham-openxr-client/test.ps1 +++ b/hotham-openxr-client/test.ps1 @@ -1,13 +1,7 @@ Write-Output "All your OpenXR are belong to crab" ${Env:RUST_BACKTRACE} = 1 -try { - cargo build -p hotham_openxr_client - cargo run --bin hotham_simple_scene_example -} -catch { - Write-Warning "Problem!" -} -finally { - Pop-Location -} +cargo build -p hotham_openxr_client +# Start-Job -ScriptBlock { cargo run --bin hotham-editor } +# Start-Sleep -seconds 1 +cargo run --bin hotham_simple_scene_example From 153463f4ca27cab0d5e96a2f1c12ad3e468ff375 Mon Sep 17 00:00:00 2001 From: Kane Rogers Date: Fri, 10 Feb 2023 16:31:35 +1100 Subject: [PATCH 03/14] Actually, no bytemuck yet due to dependency hell --- hotham-openxr-client/src/client.rs | 1 + hotham-openxr-client/test.ps1 | 2 +- hotham-simulator/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/hotham-openxr-client/src/client.rs b/hotham-openxr-client/src/client.rs index 10da2379..ab35d748 100644 --- a/hotham-openxr-client/src/client.rs +++ b/hotham-openxr-client/src/client.rs @@ -516,6 +516,7 @@ pub unsafe extern "system" fn create_xr_swapchain( create_info: *const SwapchainCreateInfo, swapchain: *mut Swapchain, ) -> Result { + trace!("create_swapchain"); Result::ERROR_FEATURE_UNSUPPORTED } diff --git a/hotham-openxr-client/test.ps1 b/hotham-openxr-client/test.ps1 index e8fe4cec..638c01a2 100644 --- a/hotham-openxr-client/test.ps1 +++ b/hotham-openxr-client/test.ps1 @@ -4,4 +4,4 @@ ${Env:RUST_BACKTRACE} = 1 cargo build -p hotham_openxr_client # Start-Job -ScriptBlock { cargo run --bin hotham-editor } # Start-Sleep -seconds 1 -cargo run --bin hotham_simple_scene_example +cargo run --bin hotham_simple_scene_example --features editor diff --git a/hotham-simulator/Cargo.toml b/hotham-simulator/Cargo.toml index 434dd553..e63c428e 100644 --- a/hotham-simulator/Cargo.toml +++ b/hotham-simulator/Cargo.toml @@ -16,7 +16,7 @@ lazy_static = "1.4.0" openxr-sys = "0.9" rand = "0.8" vk-shader-macros = "0.2.8" -winit = "0.26" +winit = "0.28.1" [build-dependencies] bindgen = "*" From f9e8325b53b10f52d555aa1b39eac0efbf57a78c Mon Sep 17 00:00:00 2001 From: Kane Rogers Date: Fri, 10 Feb 2023 22:55:57 +1100 Subject: [PATCH 04/14] Get images transferring from server to client --- hotham-editor-protocol/Cargo.toml | 1 + hotham-editor-protocol/src/lib.rs | 74 +++++- hotham-editor/src/main.rs | 26 +- hotham-openxr-client/src/client.rs | 363 +++++++++++++++++++++----- hotham-simulator/Cargo.toml | 2 +- hotham/src/contexts/vulkan_context.rs | 5 + 6 files changed, 392 insertions(+), 79 deletions(-) diff --git a/hotham-editor-protocol/Cargo.toml b/hotham-editor-protocol/Cargo.toml index f073f7ed..baf29211 100644 --- a/hotham-editor-protocol/Cargo.toml +++ b/hotham-editor-protocol/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ash = "0.37.2" openxr-sys = "0.9.3" diff --git a/hotham-editor-protocol/src/lib.rs b/hotham-editor-protocol/src/lib.rs index d84ea316..168fbde1 100644 --- a/hotham-editor-protocol/src/lib.rs +++ b/hotham-editor-protocol/src/lib.rs @@ -7,6 +7,9 @@ pub use openxr_sys::ViewConfigurationView; pub enum RequestType { GetViewConfiguration, GetViewCount, + GetSwapchainInfo, + GetSwapchainImages, + GetSwapchainSemaphores, } pub trait Request { @@ -19,8 +22,13 @@ pub trait RequestWithVecResponse { } pub mod requests { - use crate::{responses::ViewConfiguration, Request, RequestType}; + use crate::{ + responses::{SwapchainInfo, ViewConfiguration}, + Request, RequestType, RequestWithVecResponse, + }; + use ash::vk; + #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq)] pub struct GetViewConfiguration {} @@ -31,6 +39,7 @@ pub mod requests { } } + #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq)] pub struct GetViewCount {} @@ -40,14 +49,65 @@ pub mod requests { RequestType::GetViewCount } } + + #[repr(C)] + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct GetSwapchainInfo {} + + impl Request for GetSwapchainInfo { + type Response = SwapchainInfo; + fn request_type(&self) -> RequestType { + RequestType::GetSwapchainInfo + } + } + + #[repr(C)] + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct GetSwapchainImages {} + + impl Request for GetSwapchainImages { + type Response = bool; // TODO: might need to split up the Response trait + fn request_type(&self) -> RequestType { + RequestType::GetSwapchainImages + } + } + + impl RequestWithVecResponse for GetSwapchainImages { + type ResponseItem = vk::HANDLE; + } + + #[repr(C)] + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct GetSwapchainSemaphores {} + + impl Request for GetSwapchainSemaphores { + type Response = bool; // TODO: might need to split up the Response trait + fn request_type(&self) -> RequestType { + RequestType::GetSwapchainSemaphores + } + } + + impl RequestWithVecResponse for GetSwapchainSemaphores { + type ResponseItem = vk::HANDLE; + } } pub mod responses { + use ash::vk; + + #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct ViewConfiguration { pub width: u32, pub height: u32, } + + #[repr(C)] + #[derive(Debug, Copy, Clone)] + pub struct SwapchainInfo { + pub resolution: vk::Extent2D, + pub format: vk::Format, + } } #[derive(Debug, Clone)] @@ -190,6 +250,18 @@ impl EditorServer { Ok(()) } + + pub fn send_response_vec(&mut self, response: &Vec) -> std::io::Result<()> { + let message_size = (std::mem::size_of::() * response.len()) as u32; + self.socket.write(&message_size.to_be_bytes())?; + self.socket.write(&unsafe { bytes_from_vec(response) })?; + + Ok(()) + } +} +unsafe fn bytes_from_vec(data: &[T]) -> Vec { + let len = std::mem::size_of::() * data.len(); + std::slice::from_raw_parts(data as *const _ as *const u8, len).to_vec() } unsafe fn vec_from_bytes(data: &[u8]) -> Vec { diff --git a/hotham-editor/src/main.rs b/hotham-editor/src/main.rs index 66ae020e..3b77b95f 100644 --- a/hotham-editor/src/main.rs +++ b/hotham-editor/src/main.rs @@ -57,9 +57,9 @@ pub fn main() -> Result<()> { let swapchain_info = SwapchainInfo { image_count: lazy_vulkan.surface.desired_image_count, resolution: lazy_vulkan.surface.surface_resolution, - format: SWAPCHAIN_FORMAT, + format: vk::Format::R8G8B8A8_SRGB, }; - let (mut stream, _) = listener.accept().unwrap(); + let (stream, _) = listener.accept().unwrap(); let mut server = EditorServer::new(stream); let xr_swapchain = do_openxr_setup(&mut server, lazy_vulkan.context(), &swapchain_info)?; let textures = create_render_textures( @@ -149,6 +149,11 @@ fn do_openxr_setup( vulkan_context: &VulkanContext, swapchain_info: &SwapchainInfo, ) -> Result { + let (images, image_memory_handles) = + unsafe { create_render_images(vulkan_context, &swapchain_info) }; + let (semaphores, semaphore_handles) = + unsafe { create_semaphores(vulkan_context, swapchain_info.image_count) }; + check_request(server, RequestType::GetViewCount)?; server.send_response(&swapchain_info.image_count)?; @@ -158,10 +163,17 @@ fn do_openxr_setup( height: swapchain_info.resolution.height, })?; - let (images, image_memory_handles) = - unsafe { create_render_images(vulkan_context, &swapchain_info) }; - let (semaphores, semaphore_handles) = - unsafe { create_semaphores(vulkan_context, swapchain_info.image_count) }; + check_request(server, RequestType::GetSwapchainInfo)?; + server.send_response(&responses::SwapchainInfo { + format: swapchain_info.format, + resolution: swapchain_info.resolution, + })?; + + check_request(server, RequestType::GetSwapchainImages)?; + server.send_response_vec(&image_memory_handles)?; + + check_request(server, RequestType::GetSwapchainSemaphores)?; + server.send_response_vec(&semaphore_handles)?; Ok(XrSwapchain { images, semaphores }) } @@ -301,7 +313,7 @@ unsafe fn create_render_images( format: *format, extent: (*resolution).into(), mip_levels: 1, - array_layers: 1, + array_layers: 2, samples: vk::SampleCountFlags::TYPE_1, tiling: vk::ImageTiling::OPTIMAL, usage: vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED, diff --git a/hotham-openxr-client/src/client.rs b/hotham-openxr-client/src/client.rs index ab35d748..9bf9afb1 100644 --- a/hotham-openxr-client/src/client.rs +++ b/hotham-openxr-client/src/client.rs @@ -6,7 +6,11 @@ )] use crate::space_state::SpaceState; use ash::vk::{self, Handle}; -use hotham_editor_protocol::{requests, responses, EditorClient, Request}; +use hotham_editor_protocol::{ + requests, + responses::{self, SwapchainInfo}, + EditorClient, Request, +}; use log::{debug, error, trace}; use once_cell::sync::OnceCell; use openxr_sys::{ @@ -16,12 +20,13 @@ use openxr_sys::{ EnvironmentBlendMode, EventDataBuffer, ExtensionProperties, FrameBeginInfo, FrameEndInfo, FrameState, FrameWaitInfo, GraphicsBindingVulkanKHR, GraphicsRequirementsVulkanKHR, HapticActionInfo, HapticBaseHeader, Instance, InstanceCreateInfo, InstanceProperties, - InteractionProfileSuggestedBinding, Path, ReferenceSpaceCreateInfo, ReferenceSpaceType, Result, - Session, SessionActionSetsAttachInfo, SessionBeginInfo, SessionCreateInfo, Space, - SpaceLocation, StructureType, Swapchain, SwapchainCreateInfo, SwapchainImageAcquireInfo, - SwapchainImageBaseHeader, SwapchainImageReleaseInfo, SwapchainImageWaitInfo, SystemGetInfo, - SystemId, SystemProperties, Time, Version, View, ViewConfigurationType, ViewConfigurationView, - ViewLocateInfo, ViewState, VulkanDeviceCreateInfoKHR, VulkanGraphicsDeviceGetInfoKHR, + InteractionProfileSuggestedBinding, Path, Quaternionf, ReferenceSpaceCreateInfo, + ReferenceSpaceType, Result, Session, SessionActionSetsAttachInfo, SessionBeginInfo, + SessionCreateInfo, Space, SpaceLocation, StructureType, Swapchain, SwapchainCreateInfo, + SwapchainImageAcquireInfo, SwapchainImageBaseHeader, SwapchainImageReleaseInfo, + SwapchainImageVulkanKHR, SwapchainImageWaitInfo, SystemGetInfo, SystemId, SystemProperties, + Time, Vector3f, Version, View, ViewConfigurationType, ViewConfigurationView, ViewLocateInfo, + ViewState, VulkanDeviceCreateInfoKHR, VulkanGraphicsDeviceGetInfoKHR, VulkanInstanceCreateInfoKHR, FALSE, TRUE, }; @@ -37,6 +42,9 @@ use uds_windows::UnixStream; type PartialVulkan = (ash::Entry, ash::Instance); type SpaceMap = HashMap; +type StringToPathMap = HashMap; +type PathToStringMap = HashMap; +type BindingMap = HashMap; // Used during the init phase static mut PARTIAL_VULKAN: OnceCell = OnceCell::new(); @@ -45,6 +53,12 @@ static SESSION: OnceCell = OnceCell::new(); static VULKAN_CONTEXT: OnceCell = OnceCell::new(); static mut SPACES: OnceCell = OnceCell::new(); static mut EDITOR_CLIENT: OnceCell> = OnceCell::new(); +static mut STRING_TO_PATH: OnceCell = OnceCell::new(); +static mut PATH_TO_STRING: OnceCell = OnceCell::new(); +static mut BINDINGS: OnceCell = OnceCell::new(); +static mut VIEW_COUNT: u32 = 0; // handy to keep around +static mut SWAPCHAIN_IMAGES: OnceCell> = OnceCell::new(); +static mut SWAPCHAIN_SEMAPHORES: OnceCell> = OnceCell::new(); pub unsafe extern "system" fn enumerate_instance_extension_properties( _layer_names: *const ::std::os::raw::c_char, @@ -92,8 +106,11 @@ pub unsafe extern "system" fn create_instance( trace!("create_instance"); - // Initialise our spaces map + // Initialise our various maps let _ = SPACES.set(Default::default()); + let _ = STRING_TO_PATH.set(Default::default()); + let _ = PATH_TO_STRING.set(Default::default()); + let _ = BINDINGS.set(Default::default()); // New instance, new luck. *instance = Instance::from_raw(rand::random()); @@ -184,14 +201,33 @@ pub unsafe extern "system" fn create_vulkan_device( let create_info = &*create_info; let physical_device: vk::PhysicalDevice = vk::PhysicalDevice::from_raw(transmute(create_info.vulkan_physical_device)); - let device_create_info: &vk::DeviceCreateInfo = &*create_info.vulkan_create_info.cast(); + let device_create_info: &mut vk::DeviceCreateInfo = + &mut *create_info.vulkan_create_info.cast_mut().cast(); // evil? probably + let mut extension_names = std::slice::from_raw_parts( + device_create_info.pp_enabled_extension_names, + device_create_info.enabled_extension_count as _, + ) + .to_vec(); + + #[cfg(target_os = "windows")] + extension_names.push(ash::extensions::khr::ExternalMemoryWin32::name().as_ptr()); + + #[cfg(target_os = "windows")] + extension_names.push(ash::extensions::khr::ExternalSemaphoreWin32::name().as_ptr()); + + for e in &extension_names { + let e = CStr::from_ptr(*e).to_str().unwrap(); + trace!("Application requested Vulkan extension: {e}") + } + + device_create_info.enabled_extension_count = extension_names.len() as _; + device_create_info.pp_enabled_extension_names = extension_names.as_ptr(); + trace!("Physical device: {physical_device:?}"); trace!("Create info: {device_create_info:?}"); - // Create a Vulkan device for the *application*. We'll stash our own away soon enough, - // don't you worry about that. let device = instance - .create_device(physical_device, &vk::DeviceCreateInfo::builder(), None) + .create_device(physical_device, device_create_info, None) .unwrap(); *vulkan_device = transmute(device.handle().as_raw()); @@ -277,7 +313,10 @@ pub unsafe extern "system" fn create_action_set( action_set: *mut ActionSet, ) -> Result { trace!("create_action_set"); - Result::ERROR_FEATURE_UNSUPPORTED + let name = CStr::from_ptr((*create_info).action_set_name.as_ptr()); + trace!("Creating action set with name {name:?}"); + *action_set = ActionSet::from_raw(rand::random()); + Result::SUCCESS } pub unsafe extern "system" fn create_action( @@ -286,14 +325,29 @@ pub unsafe extern "system" fn create_action( action_out: *mut Action, ) -> Result { trace!("create_action"); - Result::ERROR_FEATURE_UNSUPPORTED + *action_out = Action::from_raw(rand::random()); + Result::SUCCESS } pub unsafe extern "system" fn suggest_interaction_profile_bindings( _instance: Instance, suggested_bindings: *const InteractionProfileSuggestedBinding, ) -> Result { - Result::ERROR_FEATURE_UNSUPPORTED + trace!("suggest_interaction_profile_bindings"); + let suggested_bindings = *suggested_bindings; + + let bindings = std::slice::from_raw_parts( + suggested_bindings.suggested_bindings, + suggested_bindings.count_suggested_bindings as _, + ); + + let bindings_map = BINDINGS.get_mut().unwrap(); + + for binding in bindings { + bindings_map.insert(binding.binding, binding.action); + } + + Result::SUCCESS } pub unsafe extern "system" fn string_to_path( @@ -301,10 +355,21 @@ pub unsafe extern "system" fn string_to_path( path_string: *const c_char, path_out: *mut Path, ) -> Result { + trace!("string_to_path"); match CStr::from_ptr(path_string).to_str() { - Ok(s) => { + Ok(path_string) => { let path = Path::from_raw(rand::random()); - Result::ERROR_FEATURE_UNSUPPORTED + trace!("Adding ({path_string}, {path:?}) to path map"); + STRING_TO_PATH + .get_mut() + .unwrap() + .insert(path_string.to_string(), path.clone()); + PATH_TO_STRING + .get_mut() + .unwrap() + .insert(path.clone(), path_string.to_string()); + *path_out = path; + Result::SUCCESS } Err(_) => Result::ERROR_VALIDATION_FAILURE, } @@ -314,8 +379,8 @@ pub unsafe extern "system" fn attach_action_sets( _session: Session, _attach_info: *const SessionActionSetsAttachInfo, ) -> Result { - println!("[HOTHAM_SIMULATOR] Attach action sets called"); - Result::ERROR_FEATURE_UNSUPPORTED + trace!("attach_action_sets"); + Result::SUCCESS } // TODO: Handle aim pose. @@ -324,55 +389,58 @@ pub unsafe extern "system" fn create_action_space( create_info: *const ActionSpaceCreateInfo, space_out: *mut Space, ) -> Result { - // match state - // .path_string - // .get(&(*create_info).subaction_path) - // .map(|s| s.as_str()) - // { - // Some("/user/hand/left") => { - // let mut space_state = SpaceState::new("Left Hand"); - // space_state.position = Vector3f { - // x: -0.20, - // y: 1.4, - // z: -0.50, - // }; - // space_state.orientation = Quaternionf { - // x: 0.707, - // y: 0., - // z: 0., - // w: 0.707, - // }; - // println!("[HOTHAM_SIMULATOR] Created left hand space: {space_state:?}, {space:?}"); - // state.left_hand_space = raw; - // state.spaces.insert(raw, space_state); - // } - // Some("/user/hand/right") => { - // let mut space_state = SpaceState::new("Right Hand"); - // space_state.orientation = Quaternionf { - // x: 0.707, - // y: 0., - // z: 0., - // w: 0.707, - // }; - // space_state.position = Vector3f { - // x: 0.20, - // y: 1.4, - // z: -0.50, - // }; - // println!("[HOTHAM_SIMULATOR] Created right hand space: {space_state:?}, {space:?}"); - // state.right_hand_space = raw; - // state.spaces.insert(raw, space_state); - // } - // Some(path) => { - // let space_state = SpaceState::new(path); - // println!("[HOTHAM_SIMULATOR] Created space for path: {path}"); - // state.spaces.insert(raw, space_state); - // } - // _ => {} - // } + trace!("create_action_space"); + let path_string = PATH_TO_STRING + .get() + .unwrap() + .get(&(*create_info).subaction_path) + .map(|s| s.as_str()); + let space = Space::from_raw(rand::random()); + let spaces = SPACES.get_mut().unwrap(); + + match path_string { + Some("/user/hand/left") => { + let mut space_state = SpaceState::new("Left Hand"); + space_state.position = Vector3f { + x: -0.20, + y: 1.4, + z: -0.50, + }; + space_state.orientation = Quaternionf { + x: 0.707, + y: 0., + z: 0., + w: 0.707, + }; + trace!("Created left hand space: {space_state:?}, {space:?}"); + spaces.insert(space.into_raw(), space_state); + } + Some("/user/hand/right") => { + let mut space_state = SpaceState::new("Right Hand"); + space_state.orientation = Quaternionf { + x: 0.707, + y: 0., + z: 0., + w: 0.707, + }; + space_state.position = Vector3f { + x: 0.20, + y: 1.4, + z: -0.50, + }; + trace!("Created right hand space: {space_state:?}, {space:?}"); + spaces.insert(space.into_raw(), space_state); + } + Some(path) => { + let space_state = SpaceState::new(path); + trace!("Created new space: {space_state:?}, {space:?}"); + spaces.insert(space.into_raw(), space_state); + } + _ => return Result::ERROR_PATH_INVALID, + }; - // *space_out = space; - Result::ERROR_FEATURE_UNSUPPORTED + *space_out = space; + Result::SUCCESS } pub unsafe extern "system" fn create_reference_space( @@ -487,6 +555,7 @@ pub unsafe extern "system" fn enumerate_view_configuration_views( let view_count = client.request(&requests::GetViewCount {}).unwrap(); trace!("Received view count from server {view_count}"); *view_count_output = view_count; + VIEW_COUNT = view_count; return Result::SUCCESS; } @@ -513,11 +582,12 @@ pub unsafe extern "system" fn enumerate_view_configuration_views( pub unsafe extern "system" fn create_xr_swapchain( _session: Session, - create_info: *const SwapchainCreateInfo, + _create_info: *const SwapchainCreateInfo, swapchain: *mut Swapchain, ) -> Result { trace!("create_swapchain"); - Result::ERROR_FEATURE_UNSUPPORTED + *swapchain = Swapchain::from_raw(rand::random()); + Result::SUCCESS } pub unsafe extern "system" fn enumerate_swapchain_images( @@ -526,7 +596,47 @@ pub unsafe extern "system" fn enumerate_swapchain_images( image_count_output: *mut u32, images: *mut SwapchainImageBaseHeader, ) -> Result { - Result::ERROR_FEATURE_UNSUPPORTED + trace!("enumerate_swapchain_images"); + if image_capacity_input == 0 { + *image_count_output = VIEW_COUNT; + return Result::SUCCESS; + } + + let client = EDITOR_CLIENT.get_mut().unwrap(); + + trace!("Requesting swapchain info"); + let swapchain_info = client.request(&requests::GetSwapchainInfo {}).unwrap(); + trace!("Got swapchain info {swapchain_info:?}"); + + trace!("Requesting swapchain image handles.."); + let swapchain_image_handles = client + .request_vec(&requests::GetSwapchainImages {}) + .unwrap(); + trace!("Got swapchain image handles {swapchain_image_handles:?}"); + + trace!("Requesting semaphore handles.."); + let semaphore_handles = client + .request_vec(&requests::GetSwapchainSemaphores {}) + .unwrap(); + trace!("Got semaphore handles {semaphore_handles:?}"); + + let swapchain_images = create_swapchain_images(swapchain_image_handles, swapchain_info); + trace!("Created swapchain images {swapchain_images:?}"); + + let _ = SWAPCHAIN_SEMAPHORES.set(create_swapchain_semaphores(semaphore_handles)); + let _ = SWAPCHAIN_IMAGES.set(swapchain_images.clone()); + + let output_images = + std::slice::from_raw_parts_mut(images as *mut SwapchainImageVulkanKHR, VIEW_COUNT as _); + for (output_image, swapchain_image) in output_images.iter_mut().zip(swapchain_images.iter()) { + *output_image = SwapchainImageVulkanKHR { + ty: StructureType::SWAPCHAIN_IMAGE_VULKAN_KHR, + next: null_mut(), + image: swapchain_image.as_raw(), + }; + } + + Result::SUCCESS } pub unsafe extern "system" fn acquire_swapchain_image( @@ -534,6 +644,7 @@ pub unsafe extern "system" fn acquire_swapchain_image( _acquire_info: *const SwapchainImageAcquireInfo, index: *mut u32, ) -> Result { + trace!("acquire_swapchain_image"); Result::ERROR_FEATURE_UNSUPPORTED } @@ -541,6 +652,7 @@ pub unsafe extern "system" fn wait_swapchain_image( _swapchain: Swapchain, _wait_info: *const SwapchainImageWaitInfo, ) -> Result { + trace!("wait_swapchain_image"); Result::ERROR_FEATURE_UNSUPPORTED } @@ -555,6 +667,7 @@ pub unsafe extern "system" fn locate_space( _time: Time, location_out: *mut SpaceLocation, ) -> Result { + trace!("locate_space"); // match STATE.lock().unwrap().spaces.get(&space.into_raw()) { // Some(space_state) => { // let pose = Posef { @@ -580,6 +693,7 @@ pub unsafe extern "system" fn get_action_state_pose( _get_info: *const ActionStateGetInfo, state: *mut ActionStatePose, ) -> Result { + trace!("get_action_state_pose"); *state = ActionStatePose { ty: StructureType::ACTION_STATE_POSE, next: ptr::null_mut(), @@ -592,6 +706,7 @@ pub unsafe extern "system" fn sync_actions( _session: Session, _sync_info: *const ActionsSyncInfo, ) -> Result { + trace!("sync_actions"); Result::ERROR_FEATURE_UNSUPPORTED } @@ -603,6 +718,7 @@ pub unsafe extern "system" fn locate_views( view_count_output: *mut u32, views: *mut View, ) -> Result { + trace!("locate_views"); // *view_count_output = NUM_VIEWS as _; // if view_capacity_input == 0 { @@ -641,6 +757,7 @@ pub unsafe extern "system" fn release_swapchain_image( _swapchain: Swapchain, _release_info: *const SwapchainImageReleaseInfo, ) -> Result { + trace!("release_swapchain_images"); Result::ERROR_FEATURE_UNSUPPORTED } @@ -648,34 +765,42 @@ pub unsafe extern "system" fn end_frame( _session: Session, frame_end_info: *const FrameEndInfo, ) -> Result { + trace!("end_frame"); Result::ERROR_FEATURE_UNSUPPORTED } pub unsafe extern "system" fn request_exit_session(_session: Session) -> Result { + trace!("request_exit_session"); Result::ERROR_FEATURE_UNSUPPORTED } pub unsafe extern "system" fn destroy_space(_space: Space) -> Result { + trace!("destroy_space"); Result::ERROR_FEATURE_UNSUPPORTED } pub unsafe extern "system" fn destroy_action(_action: Action) -> Result { + trace!("destroy_actions"); Result::ERROR_FEATURE_UNSUPPORTED } pub unsafe extern "system" fn destroy_action_set(_action_set: ActionSet) -> Result { + trace!("destroy_action_set"); Result::ERROR_FEATURE_UNSUPPORTED } pub unsafe extern "system" fn destroy_swapchain(_swapchain: Swapchain) -> Result { + trace!("destroy_swapchain"); Result::ERROR_FEATURE_UNSUPPORTED } pub unsafe extern "system" fn destroy_session(_session: Session) -> Result { + trace!("destroy_session"); Result::ERROR_FEATURE_UNSUPPORTED } pub unsafe extern "system" fn destroy_instance(_instance: Instance) -> Result { + trace!("destroy_instance"); Result::ERROR_FEATURE_UNSUPPORTED } @@ -686,6 +811,7 @@ pub unsafe extern "system" fn enumerate_view_configurations( view_configuration_type_count_output: *mut u32, _view_configuration_types: *mut ViewConfigurationType, ) -> Result { + trace!("enumerate_view_configurations"); Result::ERROR_FEATURE_UNSUPPORTED } @@ -695,6 +821,7 @@ pub unsafe extern "system" fn enumerate_reference_spaces( space_count_output: *mut u32, spaces: *mut ReferenceSpaceType, ) -> Result { + trace!("enumerate_reference_spaces"); *space_count_output = 1; if space_capacity_input == 0 { return Result::ERROR_FEATURE_UNSUPPORTED; @@ -711,6 +838,7 @@ pub unsafe extern "system" fn get_system_properties( _system_id: SystemId, _properties: *mut SystemProperties, ) -> Result { + trace!("get_system_properties"); Result::ERROR_FEATURE_UNSUPPORTED } @@ -720,6 +848,7 @@ pub unsafe extern "system" fn enumerate_swapchain_formats( format_count_output: *mut u32, formats: *mut i64, ) -> Result { + trace!("enumerate_swapchain_formats"); Result::ERROR_FEATURE_UNSUPPORTED } @@ -728,6 +857,7 @@ pub unsafe extern "system" fn get_action_state_float( _get_info: *const ActionStateGetInfo, state: *mut ActionStateFloat, ) -> Result { + trace!("get_action_state_float"); *state = ActionStateFloat { ty: StructureType::ACTION_STATE_FLOAT, next: ptr::null_mut(), @@ -740,6 +870,7 @@ pub unsafe extern "system" fn get_action_state_float( } pub unsafe extern "system" fn end_session(_session: Session) -> Result { + trace!("end_session"); Result::ERROR_FEATURE_UNSUPPORTED } @@ -748,6 +879,7 @@ pub unsafe extern "system" fn get_action_state_boolean( get_info: *const ActionStateGetInfo, action_state: *mut ActionStateBoolean, ) -> Result { + trace!("get_action_state_boolean"); Result::ERROR_FEATURE_UNSUPPORTED } @@ -756,6 +888,7 @@ pub unsafe extern "system" fn apply_haptic_feedback( _haptic_action_info: *const HapticActionInfo, _haptic_feedback: *const HapticBaseHeader, ) -> Result { + trace!("apply_haptic_feedback"); /* explicit no-op, could possibly be extended with controller support in future if winit ever * provides such APIs */ Result::ERROR_FEATURE_UNSUPPORTED @@ -768,6 +901,7 @@ pub unsafe extern "system" fn get_vulkan_instance_extensions( buffer_count_output: *mut u32, buffer: *mut c_char, ) -> Result { + trace!("get_vulkan_instance_extensions"); Result::ERROR_FEATURE_UNSUPPORTED } @@ -778,6 +912,7 @@ pub unsafe extern "system" fn get_vulkan_device_extensions( buffer_count_output: *mut u32, buffer: *mut c_char, ) -> Result { + trace!("get_vulkan_device_extensions"); Result::ERROR_FEATURE_UNSUPPORTED } @@ -805,3 +940,91 @@ unsafe fn set_array( let slice = slice::from_raw_parts_mut(array_ptr, COUNT); slice.copy_from_slice(&data); } + +fn create_swapchain_images( + handles: Vec, + swapchain_info: SwapchainInfo, +) -> Vec { + let vulkan_context = VULKAN_CONTEXT.get().unwrap(); + let device = &vulkan_context.device; + + handles + .into_iter() + .map(|handle| unsafe { + trace!("Creating image.."); + let handle_type = vk::ExternalMemoryHandleTypeFlags::OPAQUE_WIN32_KMT; + + let mut external_memory_image_create_info = + vk::ExternalMemoryImageCreateInfo::builder().handle_types(handle_type); + let image = device + .create_image( + &vk::ImageCreateInfo { + image_type: vk::ImageType::TYPE_2D, + format: swapchain_info.format, + extent: swapchain_info.resolution.into(), + mip_levels: 1, + array_layers: 2, + samples: vk::SampleCountFlags::TYPE_1, + tiling: vk::ImageTiling::OPTIMAL, + usage: vk::ImageUsageFlags::COLOR_ATTACHMENT, + sharing_mode: vk::SharingMode::EXCLUSIVE, + p_next: &mut external_memory_image_create_info as *mut _ as *mut _, + ..Default::default() + }, + None, + ) + .unwrap(); + trace!("Allocating image memory.."); + let requirements = device.get_image_memory_requirements(image); + let mut external_memory_allocate_info = vk::ImportMemoryWin32HandleInfoKHR::builder() + .handle(handle) + .handle_type(handle_type); + let memory = device + .allocate_memory( + &vk::MemoryAllocateInfo::builder() + .allocation_size(requirements.size) + .push_next(&mut external_memory_allocate_info), + None, + ) + .unwrap(); + trace!("Done, allocating.."); + device.bind_image_memory(image, memory, 0).unwrap(); + image + }) + .collect() +} + +fn create_swapchain_semaphores(handles: Vec) -> Vec { + let vulkan_context = VULKAN_CONTEXT.get().unwrap(); + let device = &vulkan_context.device; + let external_semaphore = ash::extensions::khr::ExternalSemaphoreWin32::new( + &vulkan_context.instance, + &vulkan_context.device, + ); + let handle_type = vk::ExternalSemaphoreHandleTypeFlags::OPAQUE_WIN32_KMT; + + handles + .iter() + .map(|h| unsafe { + let mut external_semaphore_info = + vk::ExportSemaphoreCreateInfo::builder().handle_types(handle_type); + let semaphore = device + .create_semaphore( + &vk::SemaphoreCreateInfo::builder().push_next(&mut external_semaphore_info), + None, + ) + .unwrap(); + + external_semaphore + .import_semaphore_win32_handle( + &vk::ImportSemaphoreWin32HandleInfoKHR::builder() + .handle(*h) + .semaphore(semaphore) + .handle_type(handle_type), + ) + .unwrap(); + + semaphore + }) + .collect() +} diff --git a/hotham-simulator/Cargo.toml b/hotham-simulator/Cargo.toml index e63c428e..434dd553 100644 --- a/hotham-simulator/Cargo.toml +++ b/hotham-simulator/Cargo.toml @@ -16,7 +16,7 @@ lazy_static = "1.4.0" openxr-sys = "0.9" rand = "0.8" vk-shader-macros = "0.2.8" -winit = "0.28.1" +winit = "0.26" [build-dependencies] bindgen = "*" diff --git a/hotham/src/contexts/vulkan_context.rs b/hotham/src/contexts/vulkan_context.rs index 022994e6..4a949202 100644 --- a/hotham/src/contexts/vulkan_context.rs +++ b/hotham/src/contexts/vulkan_context.rs @@ -100,6 +100,7 @@ impl VulkanContext { }; // Seems fine. + #[cfg(target_os = "android")] let enabled_extensions = [ "VK_EXT_astc_decode_mode", "VK_EXT_descriptor_indexing", @@ -107,6 +108,10 @@ impl VulkanContext { ] .map(|s| CString::new(s).unwrap().into_raw() as *const c_char); + #[cfg(not(target_os = "android"))] + let enabled_extensions = ["VK_EXT_descriptor_indexing", "VK_KHR_shader_float16_int8"] + .map(|s| CString::new(s).unwrap().into_raw() as *const c_char); + let mut descriptor_indexing_features = vk::PhysicalDeviceDescriptorIndexingFeatures::builder() .shader_sampled_image_array_non_uniform_indexing(true) From f7fa44c3e51d12c0c62856272906ef76f94aff97 Mon Sep 17 00:00:00 2001 From: Kane Rogers Date: Thu, 16 Feb 2023 22:54:01 +1100 Subject: [PATCH 05/14] Finish first pass at editor input --- examples/simple-scene/Cargo.toml | 2 + examples/simple-scene/src/lib.rs | 12 + hotham-editor-protocol/src/lib.rs | 79 ++++- hotham-editor/Cargo.toml | 2 + hotham-editor/src/main.rs | 210 +++++++++----- hotham-openxr-client/src/action_state.rs | 34 +++ hotham-openxr-client/src/client.rs | 348 +++++++++++++---------- hotham-openxr-client/src/lib.rs | 1 + hotham/src/contexts/vulkan_context.rs | 10 +- hotham/src/contexts/xr_context/mod.rs | 4 +- 10 files changed, 466 insertions(+), 236 deletions(-) create mode 100644 hotham-openxr-client/src/action_state.rs diff --git a/examples/simple-scene/Cargo.toml b/examples/simple-scene/Cargo.toml index 7a60a96f..4dbf019c 100644 --- a/examples/simple-scene/Cargo.toml +++ b/examples/simple-scene/Cargo.toml @@ -12,7 +12,9 @@ name = "hotham_simple_scene_example" path = "src/main.rs" [dependencies] +env_logger = "0.10.0" hotham = {path = "../../hotham"} +log = "0.4.17" [target.'cfg(target_os = "android")'.dependencies] ndk-glue = "0.6" diff --git a/examples/simple-scene/src/lib.rs b/examples/simple-scene/src/lib.rs index 16e94159..02f341b6 100644 --- a/examples/simple-scene/src/lib.rs +++ b/examples/simple-scene/src/lib.rs @@ -9,6 +9,7 @@ use hotham::{ }, xr, Engine, HothamResult, TickData, }; +use log::info; #[derive(Clone, Debug, Default)] /// Most Hotham applications will want to keep track of some sort of state. @@ -24,9 +25,20 @@ pub fn main() { } pub fn real_main() -> HothamResult<()> { + let _ = env_logger::builder() + .filter_module("hotham-openxr-client", log::LevelFilter::Trace) + .filter_module("simple_scene_example", log::LevelFilter::Trace) + .init(); + + println!("Hello!"); + info!("Building engine.."); let mut engine = Engine::new(); + info!("..done!"); + + info!("Initialising app.."); let mut state = Default::default(); init(&mut engine)?; + info!("Done! Entering main loop.."); while let Ok(tick_data) = engine.update() { tick(tick_data, &mut engine, &mut state); diff --git a/hotham-editor-protocol/src/lib.rs b/hotham-editor-protocol/src/lib.rs index 168fbde1..e2c96b27 100644 --- a/hotham-editor-protocol/src/lib.rs +++ b/hotham-editor-protocol/src/lib.rs @@ -10,6 +10,11 @@ pub enum RequestType { GetSwapchainInfo, GetSwapchainImages, GetSwapchainSemaphores, + WaitFrame, + AcquireSwapchainImage, + EndFrame, + GetInputEvents, + LocateView, } pub trait Request { @@ -23,7 +28,7 @@ pub trait RequestWithVecResponse { pub mod requests { use crate::{ - responses::{SwapchainInfo, ViewConfiguration}, + responses::{InputEvent, SwapchainInfo, ViewConfiguration}, Request, RequestType, RequestWithVecResponse, }; use ash::vk; @@ -61,6 +66,50 @@ pub mod requests { } } + #[repr(C)] + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct WaitFrame; + + impl Request for WaitFrame { + type Response = u32; + fn request_type(&self) -> RequestType { + RequestType::WaitFrame + } + } + + #[repr(C)] + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct AcquireSwapchainImage; + + impl Request for AcquireSwapchainImage { + type Response = u32; + fn request_type(&self) -> RequestType { + RequestType::AcquireSwapchainImage + } + } + + #[repr(C)] + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct EndFrame; + + impl Request for EndFrame { + type Response = u32; + fn request_type(&self) -> RequestType { + RequestType::EndFrame + } + } + + #[repr(C)] + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct LocateView; + + impl Request for LocateView { + type Response = openxr_sys::Posef; + fn request_type(&self) -> RequestType { + RequestType::LocateView + } + } + #[repr(C)] #[derive(Debug, Clone, Copy, PartialEq)] pub struct GetSwapchainImages {} @@ -90,6 +139,21 @@ pub mod requests { impl RequestWithVecResponse for GetSwapchainSemaphores { type ResponseItem = vk::HANDLE; } + + #[repr(C)] + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct GetInputEvents; + + impl Request for GetInputEvents { + type Response = bool; // TODO: might need to split up the Response trait + fn request_type(&self) -> RequestType { + RequestType::GetInputEvents + } + } + + impl RequestWithVecResponse for GetInputEvents { + type ResponseItem = InputEvent; + } } pub mod responses { @@ -108,6 +172,19 @@ pub mod responses { pub resolution: vk::Extent2D, pub format: vk::Format, } + + #[repr(C)] + #[derive(Debug, Clone, Copy)] + pub enum InputEvent { + AButtonPressed, + AButtonReleased, + BButtonPressed, + BButtonReleased, + XButtonPressed, + XButtonReleased, + YButtonPressed, + YButtonReleased, + } } #[derive(Debug, Clone)] diff --git a/hotham-editor/Cargo.toml b/hotham-editor/Cargo.toml index 8e5a4cb3..7f398439 100644 --- a/hotham-editor/Cargo.toml +++ b/hotham-editor/Cargo.toml @@ -7,9 +7,11 @@ version = "0.1.0" anyhow = "1.0.69" ash = "0.37.2" env_logger = "0.10.0" +glam = "0.22.0" hotham-editor-protocol = {path = "../hotham-editor-protocol"} lazy_vulkan = {git = "https://github.com/leetvr/lazy_vulkan", rev = "5347dd3295fd4c687c35adbe7c0b4aad00c893e3"} log = "0.4.17" +openxr-sys = "0.9.3" winit = "0.28.1" [target.'cfg(windows)'.dependencies] diff --git a/hotham-editor/src/main.rs b/hotham-editor/src/main.rs index 3b77b95f..7d1ea160 100644 --- a/hotham-editor/src/main.rs +++ b/hotham-editor/src/main.rs @@ -1,12 +1,14 @@ use anyhow::{bail, Result}; use ash::vk; -use hotham_editor_protocol::{requests, responses, EditorServer, RequestType}; +use glam::{Quat, Vec3}; +use hotham_editor_protocol::{responses, EditorServer, RequestType}; use lazy_vulkan::{ find_memorytype_index, vulkan_context::VulkanContext, vulkan_texture::VulkanTexture, DrawCall, LazyRenderer, LazyVulkan, SwapchainInfo, Vertex, }; use log::{debug, info, trace}; -use std::io::{Read, Write}; +use openxr_sys::{Posef, Vector3f}; +use std::time::Instant; use uds_windows::{UnixListener, UnixStream}; use winit::{ event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, @@ -17,7 +19,7 @@ use winit::{ /// Compile your own damn shaders! LazyVulkan is just as lazy as you are! static FRAGMENT_SHADER: &'_ [u8] = include_bytes!("shaders/triangle.frag.spv"); static VERTEX_SHADER: &'_ [u8] = include_bytes!("shaders/triangle.vert.spv"); -const SWAPCHAIN_FORMAT: vk::Format = vk::Format::R8G8B8A8_UNORM; +const SWAPCHAIN_FORMAT: vk::Format = vk::Format::R8G8B8A8_SRGB; // OpenXR really, really wants us to use SRGB swapchains static UNIX_SOCKET_PATH: &'_ str = "hotham_editor.socket"; pub fn main() -> Result<()> { @@ -57,19 +59,34 @@ pub fn main() -> Result<()> { let swapchain_info = SwapchainInfo { image_count: lazy_vulkan.surface.desired_image_count, resolution: lazy_vulkan.surface.surface_resolution, - format: vk::Format::R8G8B8A8_SRGB, + format: SWAPCHAIN_FORMAT, }; let (stream, _) = listener.accept().unwrap(); + info!("Client connected! Doing OpenXR setup.."); let mut server = EditorServer::new(stream); let xr_swapchain = do_openxr_setup(&mut server, lazy_vulkan.context(), &swapchain_info)?; + info!("..done!"); let textures = create_render_textures( lazy_vulkan.context(), &mut lazy_renderer, xr_swapchain.images, ); + let mut view_state = Posef { + position: Vector3f { + x: 0., + y: 1.4, + z: 0., + }, + ..Posef::IDENTITY + }; + let mut last_frame_time = Instant::now(); + let mut keyboard_events = Vec::new(); + let mut last_key_pressed = None; + // Off we go! let mut winit_initializing = true; + let mut focused = false; event_loop.run_return(|event, _, control_flow| { *control_flow = ControlFlow::Poll; match event { @@ -88,6 +105,13 @@ pub fn main() -> Result<()> { .. } => *control_flow = ControlFlow::Exit, + Event::WindowEvent { + event: WindowEvent::Focused(window_focused), + .. + } => { + focused = window_focused; + } + Event::NewEvents(cause) => { if cause == winit::event::StartCause::Init { winit_initializing = true; @@ -98,8 +122,31 @@ pub fn main() -> Result<()> { Event::MainEventsCleared => { let framebuffer_index = lazy_vulkan.render_begin(); - // send_swapchain_image_index(&mut stream, &mut buf, framebuffer_index); - // get_render_complete(&mut stream, &mut buf); + + update_camera( + &mut view_state, + last_frame_time, + &mut last_key_pressed, + &keyboard_events, + ); + + keyboard_events.clear(); + + check_request(&mut server, RequestType::LocateView).unwrap(); + server.send_response(&view_state).unwrap(); + + check_request(&mut server, RequestType::WaitFrame).unwrap(); + server.send_response(&0).unwrap(); + + check_request(&mut server, RequestType::AcquireSwapchainImage).unwrap(); + server.send_response(&framebuffer_index).unwrap(); + + check_request(&mut server, RequestType::LocateView).unwrap(); + server.send_response(&view_state).unwrap(); + + check_request(&mut server, RequestType::EndFrame).unwrap(); + server.send_response(&0).unwrap(); + let texture_id = textures[framebuffer_index as usize].id; lazy_renderer.render( lazy_vulkan.context(), @@ -117,6 +164,7 @@ pub fn main() -> Result<()> { framebuffer_index, &[semaphore, lazy_vulkan.rendering_complete_semaphore], ); + last_frame_time = Instant::now(); } Event::WindowEvent { event: WindowEvent::Resized(size), @@ -127,6 +175,14 @@ pub fn main() -> Result<()> { lazy_renderer.update_surface(new_render_surface, &lazy_vulkan.context().device); } } + Event::WindowEvent { + event: WindowEvent::KeyboardInput { input, .. }, + .. + } => { + if focused && input.virtual_keycode.is_some() { + keyboard_events.push(input); + } + } _ => (), } }); @@ -190,20 +246,6 @@ fn check_request( Ok(()) } -fn send_semaphore_handles( - stream: &mut UnixStream, - semaphore_handles: Vec<*mut std::ffi::c_void>, - buf: &mut [u8; 1024], -) { - stream.read(buf).unwrap(); - let value = buf[0]; - debug!("Read {value}"); - - debug!("Sending handles: {semaphore_handles:?}"); - let write = stream.write(bytes_of_slice(&semaphore_handles)).unwrap(); - debug!("Wrote {write} bytes"); -} - unsafe fn create_semaphores( context: &lazy_vulkan::vulkan_context::VulkanContext, image_count: u32, @@ -277,19 +319,6 @@ fn create_render_textures( .collect() } -fn get_render_complete(stream: &mut UnixStream, buf: &mut [u8]) { - stream.read(buf).unwrap(); -} - -fn send_swapchain_image_index( - stream: &mut UnixStream, - buf: &mut [u8; 1024], - framebuffer_index: u32, -) { - stream.read(buf).unwrap(); - stream.write(&mut [framebuffer_index as u8]).unwrap(); -} - unsafe fn create_render_images( context: &lazy_vulkan::vulkan_context::VulkanContext, swapchain_info: &SwapchainInfo, @@ -363,53 +392,86 @@ unsafe fn create_render_images( .unzip() } -fn send_swapchain_info( - stream: &mut UnixStream, - swapchain_info: &SwapchainInfo, - buf: &mut [u8], -) -> std::io::Result<()> { - stream.read(buf)?; - let value = buf[0]; - debug!("Read {value}"); - - if value == 0 { - let write = stream.write(bytes_of(swapchain_info)).unwrap(); - debug!("Write {write} bytes"); - return Ok(()); - } else { - panic!("Invalid request!"); +// TODO: This needs to be replaced with a nice state machine that can handle keyboard input, mouse input +// and gamepad input. +pub fn update_camera( + pose: &mut Posef, + last_frame_time: Instant, + last_key_pressed: &mut Option, + input_events: &[KeyboardInput], +) { + // We need to adjust the speed value so its always the same speed even if the frame rate isn't consistent + // The delta time is the the current time - last frame time + let dt = (Instant::now() - last_frame_time).as_secs_f32(); + + let movement_speed = 10. * dt; + + // Process the event queue + for event in input_events { + if event.state == ElementState::Pressed { + move_camera(pose, movement_speed, event.virtual_keycode.unwrap()); + } } -} -fn send_image_memory_handles( - stream: &mut UnixStream, - handles: Vec, - buf: &mut [u8], -) -> std::io::Result<()> { - stream.read(buf)?; - let value = buf[0]; - debug!("Read {value}"); - - if value == 1 { - debug!("Sending handles: {handles:?}"); - let write = stream.write(bytes_of_slice(&handles)).unwrap(); - debug!("Write {write} bytes"); - return Ok(()); - } else { - panic!("Invalid request!"); + // Finally, we want to handle someone holding a key down. This is annoying. + + // First, we need to check if the key was released + let key_has_been_released = !input_events.is_empty(); + + // If the user is still holding down the key, we need to treat that as an input event + if !key_has_been_released { + if let Some(last_key) = last_key_pressed { + move_camera(pose, movement_speed, *last_key); + } } -} -fn bytes_of_slice(t: &[T]) -> &[u8] { - unsafe { - let ptr = t.as_ptr(); - std::slice::from_raw_parts(ptr.cast(), std::mem::size_of::() * t.len()) + // Otherwise, find the last key pressed and mark that as the final one + for event in input_events { + if event.state == ElementState::Pressed { + *last_key_pressed = event.virtual_keycode; + } else if last_key_pressed == &event.virtual_keycode { + *last_key_pressed = None; + } } } -fn bytes_of(t: &T) -> &[u8] { - unsafe { - let ptr = t as *const T; - std::slice::from_raw_parts(ptr.cast(), std::mem::size_of::()) +fn move_camera(pose: &mut Posef, movement_speed: f32, virtual_keycode: VirtualKeyCode) { + let position = &mut pose.position; + let o = pose.orientation; + let orientation = Quat::from_xyzw(o.x, o.y, o.z, o.w); + // get the forward vector rotated by the camera rotation quaternion + let forward = orientation * -Vec3::Z; + // get the right vector rotated by the camera rotation quaternion + let right = orientation * Vec3::X; + let up = Vec3::Y; + + match virtual_keycode { + winit::event::VirtualKeyCode::W => { + position.x += forward.x * movement_speed; + position.y += forward.y * movement_speed; + position.z += forward.z * movement_speed; + } + winit::event::VirtualKeyCode::S => { + position.x -= forward.x * movement_speed; + position.y -= forward.y * movement_speed; + position.z -= forward.z * movement_speed; + } + winit::event::VirtualKeyCode::A => { + position.x -= right.x * movement_speed; + position.y -= right.y * movement_speed; + position.z -= right.z * movement_speed; + } + winit::event::VirtualKeyCode::D => { + position.x += right.x * movement_speed; + position.y += right.y * movement_speed; + position.z += right.z * movement_speed; + } + winit::event::VirtualKeyCode::Space => { + position.y += up.y * movement_speed; + } + winit::event::VirtualKeyCode::LShift => { + position.y -= up.y * movement_speed; + } + _ => {} } } diff --git a/hotham-openxr-client/src/action_state.rs b/hotham-openxr-client/src/action_state.rs new file mode 100644 index 00000000..dec005cd --- /dev/null +++ b/hotham-openxr-client/src/action_state.rs @@ -0,0 +1,34 @@ +use std::collections::HashMap; + +use openxr_sys::{Action, Path, FALSE}; + +#[derive(Debug, Clone, Default)] +/// Stores Action state, allowing the simulator to simulate input for an application. +// A bit yuck to use u64 instead of Action, but it doesn't support Hash.. but whatever. +pub struct ActionState { + boolean_actions: HashMap, + bindings: HashMap, +} +impl ActionState { + pub(crate) fn get_boolean(&self, action: Action) -> openxr_sys::Bool32 { + self.boolean_actions + .get(&action.into_raw()) + .map(|p| (*p).into()) + .unwrap_or(FALSE) + } + + pub(crate) fn add_binding(&mut self, path: Path, action: Action) { + self.bindings.insert(path, action.into_raw()); + } + + /// Resets all action state. + pub(crate) fn clear(&mut self) { + // Set all the booleans to false. + self.boolean_actions.values_mut().for_each(|v| *v = false); + } + + pub(crate) fn set_boolean(&mut self, path: &Path, value: bool) { + let action = self.bindings.get(path).unwrap(); + self.boolean_actions.insert(*action, value); + } +} diff --git a/hotham-openxr-client/src/client.rs b/hotham-openxr-client/src/client.rs index 9bf9afb1..7ed7dfde 100644 --- a/hotham-openxr-client/src/client.rs +++ b/hotham-openxr-client/src/client.rs @@ -4,12 +4,12 @@ non_upper_case_globals, non_camel_case_types )] -use crate::space_state::SpaceState; +use crate::{action_state::ActionState, space_state::SpaceState}; use ash::vk::{self, Handle}; use hotham_editor_protocol::{ - requests, - responses::{self, SwapchainInfo}, - EditorClient, Request, + requests::{self, AcquireSwapchainImage, EndFrame, LocateView}, + responses::SwapchainInfo, + EditorClient, }; use log::{debug, error, trace}; use once_cell::sync::OnceCell; @@ -17,16 +17,17 @@ use openxr_sys::{ platform::{VkDevice, VkInstance, VkPhysicalDevice, VkResult}, Action, ActionCreateInfo, ActionSet, ActionSetCreateInfo, ActionSpaceCreateInfo, ActionStateBoolean, ActionStateFloat, ActionStateGetInfo, ActionStatePose, ActionsSyncInfo, - EnvironmentBlendMode, EventDataBuffer, ExtensionProperties, FrameBeginInfo, FrameEndInfo, - FrameState, FrameWaitInfo, GraphicsBindingVulkanKHR, GraphicsRequirementsVulkanKHR, - HapticActionInfo, HapticBaseHeader, Instance, InstanceCreateInfo, InstanceProperties, - InteractionProfileSuggestedBinding, Path, Quaternionf, ReferenceSpaceCreateInfo, - ReferenceSpaceType, Result, Session, SessionActionSetsAttachInfo, SessionBeginInfo, - SessionCreateInfo, Space, SpaceLocation, StructureType, Swapchain, SwapchainCreateInfo, + EnvironmentBlendMode, EventDataBuffer, EventDataSessionStateChanged, ExtensionProperties, Fovf, + FrameBeginInfo, FrameEndInfo, FrameState, FrameWaitInfo, GraphicsBindingVulkanKHR, + GraphicsRequirementsVulkanKHR, HapticActionInfo, HapticBaseHeader, Instance, + InstanceCreateInfo, InstanceProperties, InteractionProfileSuggestedBinding, Path, Posef, + Quaternionf, ReferenceSpaceCreateInfo, ReferenceSpaceType, Result, Session, + SessionActionSetsAttachInfo, SessionBeginInfo, SessionCreateInfo, SessionState, Space, + SpaceLocation, SpaceLocationFlags, StructureType, Swapchain, SwapchainCreateInfo, SwapchainImageAcquireInfo, SwapchainImageBaseHeader, SwapchainImageReleaseInfo, SwapchainImageVulkanKHR, SwapchainImageWaitInfo, SystemGetInfo, SystemId, SystemProperties, Time, Vector3f, Version, View, ViewConfigurationType, ViewConfigurationView, ViewLocateInfo, - ViewState, VulkanDeviceCreateInfoKHR, VulkanGraphicsDeviceGetInfoKHR, + ViewState, ViewStateFlags, VulkanDeviceCreateInfoKHR, VulkanGraphicsDeviceGetInfoKHR, VulkanInstanceCreateInfoKHR, FALSE, TRUE, }; @@ -37,7 +38,7 @@ use std::{ mem::transmute, ptr::null_mut, }; -use std::{ptr, slice}; +use std::{ptr, slice, time::Instant}; use uds_windows::UnixStream; type PartialVulkan = (ash::Entry, ash::Instance); @@ -56,9 +57,15 @@ static mut EDITOR_CLIENT: OnceCell> = OnceCell::new(); static mut STRING_TO_PATH: OnceCell = OnceCell::new(); static mut PATH_TO_STRING: OnceCell = OnceCell::new(); static mut BINDINGS: OnceCell = OnceCell::new(); -static mut VIEW_COUNT: u32 = 0; // handy to keep around +static mut SWAPCHAIN_IMAGE_COUNT: u32 = 0; // handy to keep around static mut SWAPCHAIN_IMAGES: OnceCell> = OnceCell::new(); static mut SWAPCHAIN_SEMAPHORES: OnceCell> = OnceCell::new(); +static mut SESSION_STATE: SessionState = SessionState::UNKNOWN; +static mut ACTION_STATE: OnceCell = OnceCell::new(); +static CLOCK: OnceCell = OnceCell::new(); + +// Camera, etc +pub const CAMERA_FIELD_OF_VIEW: f32 = 1.; // about 57 degrees pub unsafe extern "system" fn enumerate_instance_extension_properties( _layer_names: *const ::std::os::raw::c_char, @@ -66,31 +73,18 @@ pub unsafe extern "system" fn enumerate_instance_extension_properties( property_count_output: *mut u32, properties: *mut ExtensionProperties, ) -> Result { - // If the client didn't initialise env logger, do it now - let _ = env_logger::builder() - .filter_module("hotham_openxr_client", log::LevelFilter::Trace) - .try_init(); - trace!("enumerate_instance_extension_properties"); set_array( property_capacity_input, property_count_output, properties, - [ - ExtensionProperties { - ty: StructureType::EXTENSION_PROPERTIES, - next: ptr::null_mut(), - extension_name: str_to_fixed_bytes("XR_KHR_vulkan_enable"), - extension_version: 1, - }, - ExtensionProperties { - ty: StructureType::EXTENSION_PROPERTIES, - next: ptr::null_mut(), - extension_name: str_to_fixed_bytes("XR_KHR_vulkan_enable2"), - extension_version: 1, - }, - ], + [ExtensionProperties { + ty: StructureType::EXTENSION_PROPERTIES, + next: ptr::null_mut(), + extension_name: str_to_fixed_bytes("XR_KHR_vulkan_enable2"), + extension_version: 1, + }], ); Result::SUCCESS } @@ -99,11 +93,9 @@ pub unsafe extern "system" fn create_instance( _create_info: *const InstanceCreateInfo, instance: *mut Instance, ) -> Result { - // If the client didn't initialise env logger, do it now - let _ = env_logger::builder() - .filter_module("hotham_openxr_client", log::LevelFilter::Trace) - .try_init(); - + env_logger::builder() + .filter_level(log::LevelFilter::Debug) + .init(); trace!("create_instance"); // Initialise our various maps @@ -111,11 +103,15 @@ pub unsafe extern "system" fn create_instance( let _ = STRING_TO_PATH.set(Default::default()); let _ = PATH_TO_STRING.set(Default::default()); let _ = BINDINGS.set(Default::default()); + let _ = ACTION_STATE.set(Default::default()); // New instance, new luck. *instance = Instance::from_raw(rand::random()); let _ = INSTANCE.set(*instance); + // Mr. Gaeta, start the clock. + CLOCK.set(Instant::now()).unwrap(); + // Connect to the server match UnixStream::connect("hotham_editor.socket") { Ok(stream) => { @@ -263,7 +259,7 @@ pub unsafe extern "system" fn get_vulkan_graphics_requirements( pub unsafe extern "system" fn get_instance_properties( _instance: Instance, - instance_properties: *mut InstanceProperties, + _instance_properties: *mut InstanceProperties, ) -> Result { trace!("get_instance_properties"); Result::ERROR_FEATURE_UNSUPPORTED @@ -273,9 +269,9 @@ pub unsafe extern "system" fn enumerate_environment_blend_modes( _instance: Instance, _system_id: SystemId, _view_configuration_type: ViewConfigurationType, - environment_blend_mode_capacity_input: u32, - environment_blend_mode_count_output: *mut u32, - environment_blend_modes: *mut EnvironmentBlendMode, + _environment_blend_mode_capacity_input: u32, + _environment_blend_mode_count_output: *mut u32, + _environment_blend_modes: *mut EnvironmentBlendMode, ) -> Result { trace!("enumerate_environment_blend_modes"); Result::ERROR_FEATURE_UNSUPPORTED @@ -478,67 +474,80 @@ pub unsafe extern "system" fn poll_event( _instance: Instance, event_data: *mut EventDataBuffer, ) -> Result { - // let mut state = STATE.lock().unwrap(); - // let mut next_state = state.session_state; - // if state.session_state == SessionState::UNKNOWN { - // next_state = SessionState::IDLE; - // state.has_event = true; - // } - // if state.session_state == SessionState::IDLE { - // next_state = SessionState::READY; - // state.has_event = true; - // } - // if state.session_state == SessionState::READY { - // next_state = SessionState::SYNCHRONIZED; - // state.has_event = true; - // } - // if state.session_state == SessionState::SYNCHRONIZED { - // next_state = SessionState::VISIBLE; - // state.has_event = true; - // } - // if state.session_state == SessionState::SYNCHRONIZED { - // next_state = SessionState::FOCUSED; - // state.has_event = true; - // } - - // if state.has_event { - // let data = EventDataSessionStateChanged { - // ty: StructureType::EVENT_DATA_SESSION_STATE_CHANGED, - // next: ptr::null(), - // session: Session::from_raw(42), - // state: next_state, - // time: openxr_sys::Time::from_nanos(10), - // }; - // copy_nonoverlapping(&data, transmute(event_data), 1); - // state.has_event = false; - // state.session_state = next_state; - - // Result::ERROR_FEATURE_UNSUPPORTED - // } else { - Result::EVENT_UNAVAILABLE - // } + let next_state = match SESSION_STATE { + SessionState::UNKNOWN => Some(SessionState::IDLE), + SessionState::IDLE => Some(SessionState::READY), + SessionState::READY => Some(SessionState::SYNCHRONIZED), + SessionState::SYNCHRONIZED => Some(SessionState::VISIBLE), + SessionState::VISIBLE => Some(SessionState::FOCUSED), + _ => None, + }; + + if let Some(next_state) = next_state { + SESSION_STATE = next_state; + let event_data = event_data as *mut EventDataSessionStateChanged; + *event_data = EventDataSessionStateChanged { + ty: StructureType::EVENT_DATA_SESSION_STATE_CHANGED, + next: ptr::null(), + session: *SESSION.get().unwrap(), + state: next_state, + time: now(), + }; + return Result::SUCCESS; + } + + return Result::EVENT_UNAVAILABLE; } pub unsafe extern "system" fn begin_session( session: Session, _begin_info: *const SessionBeginInfo, ) -> Result { - debug!("[HOTHAM_SIMULATOR] Beginning session: {session:?}"); - Result::ERROR_FEATURE_UNSUPPORTED + trace!("begin_session"); + debug!("Beginning session: {session:?}"); + Result::SUCCESS } pub unsafe extern "system" fn wait_frame( _session: Session, _frame_wait_info: *const FrameWaitInfo, frame_state: *mut FrameState, ) -> Result { - Result::ERROR_FEATURE_UNSUPPORTED + trace!("wait_frame"); + + // This is a bit of a hack, but if we're not in the FOCUSED state, we'll be sending `wait_frame` before + // `locate_views` which will annoy the editor. + if SESSION_STATE != SessionState::FOCUSED { + *frame_state = FrameState { + ty: StructureType::FRAME_STATE, + next: null_mut(), + predicted_display_time: now(), + predicted_display_period: openxr_sys::Duration::from_nanos(1), + should_render: false.into(), + }; + + return Result::SUCCESS; + } + + let client = EDITOR_CLIENT.get_mut().unwrap(); + client.request(&requests::WaitFrame).unwrap(); + + *frame_state = FrameState { + ty: StructureType::FRAME_STATE, + next: null_mut(), + predicted_display_time: now(), + predicted_display_period: openxr_sys::Duration::from_nanos(1), + should_render: true.into(), + }; + + Result::SUCCESS } pub unsafe extern "system" fn begin_frame( _session: Session, _frame_begin_info: *const FrameBeginInfo, ) -> Result { - Result::ERROR_FEATURE_UNSUPPORTED + trace!("begin_frame"); + Result::SUCCESS } pub unsafe extern "system" fn enumerate_view_configuration_views( @@ -555,7 +564,7 @@ pub unsafe extern "system" fn enumerate_view_configuration_views( let view_count = client.request(&requests::GetViewCount {}).unwrap(); trace!("Received view count from server {view_count}"); *view_count_output = view_count; - VIEW_COUNT = view_count; + SWAPCHAIN_IMAGE_COUNT = view_count; return Result::SUCCESS; } @@ -598,7 +607,7 @@ pub unsafe extern "system" fn enumerate_swapchain_images( ) -> Result { trace!("enumerate_swapchain_images"); if image_capacity_input == 0 { - *image_count_output = VIEW_COUNT; + *image_count_output = SWAPCHAIN_IMAGE_COUNT; return Result::SUCCESS; } @@ -626,8 +635,10 @@ pub unsafe extern "system" fn enumerate_swapchain_images( let _ = SWAPCHAIN_SEMAPHORES.set(create_swapchain_semaphores(semaphore_handles)); let _ = SWAPCHAIN_IMAGES.set(swapchain_images.clone()); - let output_images = - std::slice::from_raw_parts_mut(images as *mut SwapchainImageVulkanKHR, VIEW_COUNT as _); + let output_images = std::slice::from_raw_parts_mut( + images as *mut SwapchainImageVulkanKHR, + SWAPCHAIN_IMAGE_COUNT as _, + ); for (output_image, swapchain_image) in output_images.iter_mut().zip(swapchain_images.iter()) { *output_image = SwapchainImageVulkanKHR { ty: StructureType::SWAPCHAIN_IMAGE_VULKAN_KHR, @@ -640,12 +651,21 @@ pub unsafe extern "system" fn enumerate_swapchain_images( } pub unsafe extern "system" fn acquire_swapchain_image( - swapchain: Swapchain, + _swapchain: Swapchain, _acquire_info: *const SwapchainImageAcquireInfo, index: *mut u32, ) -> Result { trace!("acquire_swapchain_image"); - Result::ERROR_FEATURE_UNSUPPORTED + // This is a bit of a hack, but if we're not in the FOCUSED state, we'll be sending `acquire_swapchain_image` before + // `locate_views` which will annoy the editor. + if SESSION_STATE != SessionState::FOCUSED { + *index = 0; + return Result::SUCCESS; + } + + let client = EDITOR_CLIENT.get_mut().unwrap(); + *index = client.request(&AcquireSwapchainImage).unwrap(); + Result::SUCCESS } pub unsafe extern "system" fn wait_swapchain_image( @@ -653,7 +673,7 @@ pub unsafe extern "system" fn wait_swapchain_image( _wait_info: *const SwapchainImageWaitInfo, ) -> Result { trace!("wait_swapchain_image"); - Result::ERROR_FEATURE_UNSUPPORTED + Result::SUCCESS } pub unsafe extern "system" fn dummy() -> Result { @@ -668,25 +688,24 @@ pub unsafe extern "system" fn locate_space( location_out: *mut SpaceLocation, ) -> Result { trace!("locate_space"); - // match STATE.lock().unwrap().spaces.get(&space.into_raw()) { - // Some(space_state) => { - // let pose = Posef { - // position: space_state.position, - // orientation: space_state.orientation, - // }; - // *location_out = SpaceLocation { - // ty: StructureType::SPACE_LOCATION, - // next: null_mut(), - // location_flags: SpaceLocationFlags::ORIENTATION_TRACKED - // | SpaceLocationFlags::POSITION_VALID - // | SpaceLocationFlags::ORIENTATION_VALID, - // pose, - // }; - // Result::ERROR_FEATURE_UNSUPPORTED - // } - // None => Result::ERROR_HANDLE_INVALID, - // } - Result::ERROR_FEATURE_UNSUPPORTED + match SPACES.get().unwrap().get(&space.into_raw()) { + Some(space_state) => { + let pose = Posef { + position: space_state.position, + orientation: space_state.orientation, + }; + *location_out = SpaceLocation { + ty: StructureType::SPACE_LOCATION, + next: null_mut(), + location_flags: SpaceLocationFlags::ORIENTATION_TRACKED + | SpaceLocationFlags::POSITION_VALID + | SpaceLocationFlags::ORIENTATION_VALID, + pose, + }; + Result::SUCCESS + } + None => Result::ERROR_HANDLE_INVALID, + } } pub unsafe extern "system" fn get_action_state_pose( _session: Session, @@ -707,7 +726,7 @@ pub unsafe extern "system" fn sync_actions( _sync_info: *const ActionsSyncInfo, ) -> Result { trace!("sync_actions"); - Result::ERROR_FEATURE_UNSUPPORTED + Result::SUCCESS } pub unsafe extern "system" fn locate_views( @@ -719,38 +738,35 @@ pub unsafe extern "system" fn locate_views( views: *mut View, ) -> Result { trace!("locate_views"); - // *view_count_output = NUM_VIEWS as _; - - // if view_capacity_input == 0 { - // return Result::ERROR_FEATURE_UNSUPPORTED; - // } - - // *view_state = ViewState { - // ty: StructureType::VIEW_STATE, - // next: null_mut(), - // view_state_flags: ViewStateFlags::ORIENTATION_VALID | ViewStateFlags::POSITION_VALID, - // }; - // let views = slice::from_raw_parts_mut(views, NUM_VIEWS); - // let state = STATE.lock().unwrap(); - // #[allow(clippy::approx_constant)] - // for (i, view) in views.iter_mut().enumerate() { - // let pose = state.view_poses[i]; - - // // The actual fov is defined as (right - left). As these are all symetrical, we just divide the fov variable by 2. - // *view = View { - // ty: StructureType::VIEW, - // next: null_mut(), - // pose, - // fov: Fovf { - // angle_down: -CAMERA_FIELD_OF_VIEW / 2., - // angle_up: CAMERA_FIELD_OF_VIEW / 2., - // angle_left: -CAMERA_FIELD_OF_VIEW / 2., - // angle_right: CAMERA_FIELD_OF_VIEW / 2., - // }, - // }; - // } - Result::ERROR_FEATURE_UNSUPPORTED + // To avoid hitting the editor twice, early return + if view_capacity_input == 0 { + *view_count_output = 2; + return Result::SUCCESS; + } + + let editor_client = EDITOR_CLIENT.get_mut().unwrap(); + let pose = editor_client.request(&LocateView).unwrap(); + + let view = View { + ty: StructureType::VIEW, + next: null_mut(), + pose, + fov: Fovf { + angle_down: -CAMERA_FIELD_OF_VIEW / 2., + angle_up: CAMERA_FIELD_OF_VIEW / 2., + angle_left: -CAMERA_FIELD_OF_VIEW / 2., + angle_right: CAMERA_FIELD_OF_VIEW / 2., + }, + }; + set_array(view_capacity_input, view_count_output, views, [view; 2]); + *view_state = ViewState { + ty: StructureType::VIEW_STATE, + next: null_mut(), + view_state_flags: ViewStateFlags::ORIENTATION_VALID | ViewStateFlags::POSITION_VALID, + }; + + Result::SUCCESS } pub unsafe extern "system" fn release_swapchain_image( @@ -758,15 +774,16 @@ pub unsafe extern "system" fn release_swapchain_image( _release_info: *const SwapchainImageReleaseInfo, ) -> Result { trace!("release_swapchain_images"); - Result::ERROR_FEATURE_UNSUPPORTED + Result::SUCCESS } pub unsafe extern "system" fn end_frame( _session: Session, - frame_end_info: *const FrameEndInfo, + _frame_end_info: *const FrameEndInfo, ) -> Result { trace!("end_frame"); - Result::ERROR_FEATURE_UNSUPPORTED + EDITOR_CLIENT.get_mut().unwrap().request(&EndFrame).unwrap(); + Result::SUCCESS } pub unsafe extern "system" fn request_exit_session(_session: Session) -> Result { @@ -808,7 +825,7 @@ pub unsafe extern "system" fn enumerate_view_configurations( _instance: Instance, _system_id: SystemId, _view_configuration_type_capacity_input: u32, - view_configuration_type_count_output: *mut u32, + _view_configuration_type_count_output: *mut u32, _view_configuration_types: *mut ViewConfigurationType, ) -> Result { trace!("enumerate_view_configurations"); @@ -844,9 +861,9 @@ pub unsafe extern "system" fn get_system_properties( pub unsafe extern "system" fn enumerate_swapchain_formats( _session: Session, - format_capacity_input: u32, - format_count_output: *mut u32, - formats: *mut i64, + _format_capacity_input: u32, + _format_count_output: *mut u32, + _formats: *mut i64, ) -> Result { trace!("enumerate_swapchain_formats"); Result::ERROR_FEATURE_UNSUPPORTED @@ -866,7 +883,7 @@ pub unsafe extern "system" fn get_action_state_float( last_change_time: openxr_sys::Time::from_nanos(0), is_active: TRUE, }; - Result::ERROR_FEATURE_UNSUPPORTED + Result::SUCCESS } pub unsafe extern "system" fn end_session(_session: Session) -> Result { @@ -880,7 +897,16 @@ pub unsafe extern "system" fn get_action_state_boolean( action_state: *mut ActionStateBoolean, ) -> Result { trace!("get_action_state_boolean"); - Result::ERROR_FEATURE_UNSUPPORTED + let current_state = ACTION_STATE.get().unwrap().get_boolean((*get_info).action); + *action_state = ActionStateBoolean { + ty: StructureType::ACTION_STATE_BOOLEAN, + next: ptr::null_mut(), + current_state, + changed_since_last_sync: FALSE, + last_change_time: openxr_sys::Time::from_nanos(0), + is_active: TRUE, + }; + Result::SUCCESS } pub unsafe extern "system" fn apply_haptic_feedback( @@ -891,15 +917,15 @@ pub unsafe extern "system" fn apply_haptic_feedback( trace!("apply_haptic_feedback"); /* explicit no-op, could possibly be extended with controller support in future if winit ever * provides such APIs */ - Result::ERROR_FEATURE_UNSUPPORTED + Result::SUCCESS } pub unsafe extern "system" fn get_vulkan_instance_extensions( _instance: Instance, _system_id: SystemId, - buffer_capacity_input: u32, - buffer_count_output: *mut u32, - buffer: *mut c_char, + _buffer_capacity_input: u32, + _buffer_count_output: *mut u32, + _buffer: *mut c_char, ) -> Result { trace!("get_vulkan_instance_extensions"); Result::ERROR_FEATURE_UNSUPPORTED @@ -908,9 +934,9 @@ pub unsafe extern "system" fn get_vulkan_instance_extensions( pub unsafe extern "system" fn get_vulkan_device_extensions( _instance: Instance, _system_id: SystemId, - buffer_capacity_input: u32, - buffer_count_output: *mut u32, - buffer: *mut c_char, + _buffer_capacity_input: u32, + _buffer_count_output: *mut u32, + _buffer: *mut c_char, ) -> Result { trace!("get_vulkan_device_extensions"); Result::ERROR_FEATURE_UNSUPPORTED @@ -1028,3 +1054,9 @@ fn create_swapchain_semaphores(handles: Vec) -> Vec { }) .collect() } + +fn now() -> openxr_sys::Time { + openxr_sys::Time::from_nanos( + (std::time::Instant::now() - *CLOCK.get().unwrap()).as_nanos() as _, + ) +} diff --git a/hotham-openxr-client/src/lib.rs b/hotham-openxr-client/src/lib.rs index be2cab6e..0f82cf4b 100644 --- a/hotham-openxr-client/src/lib.rs +++ b/hotham-openxr-client/src/lib.rs @@ -1,5 +1,6 @@ mod client; +mod action_state; mod space_state; use std::ffi::c_char; diff --git a/hotham/src/contexts/vulkan_context.rs b/hotham/src/contexts/vulkan_context.rs index 4a949202..7f1be616 100644 --- a/hotham/src/contexts/vulkan_context.rs +++ b/hotham/src/contexts/vulkan_context.rs @@ -75,7 +75,15 @@ impl VulkanContext { .engine_version(1) .build(); - let create_info = vk::InstanceCreateInfo::builder().application_info(&app_info); + #[allow(unused_mut)] + let mut instance_extensions = vec![]; + + #[cfg(debug_assertions)] + instance_extensions.push(vk::ExtDebugUtilsFn::name().as_ptr()); + + let create_info = vk::InstanceCreateInfo::builder() + .application_info(&app_info) + .enabled_extension_names(&instance_extensions); let instance_handle = unsafe { xr_instance.create_vulkan_instance( diff --git a/hotham/src/contexts/xr_context/mod.rs b/hotham/src/contexts/xr_context/mod.rs index 29bc9cab..18531694 100644 --- a/hotham/src/contexts/xr_context/mod.rs +++ b/hotham/src/contexts/xr_context/mod.rs @@ -463,10 +463,10 @@ fn enable_xr_extensions(required_extensions: &mut xr::ExtensionSet) { #[cfg(not(target_os = "android"))] fn enable_xr_extensions(required_extensions: &mut xr::ExtensionSet) { - required_extensions.khr_vulkan_enable = true; - if cfg!(feature = "editor") { required_extensions.khr_vulkan_enable2 = true; + } else { + required_extensions.khr_vulkan_enable = true; } } From 2b269f3bb42197cd8867e83f6bd9fd3ddc220e1f Mon Sep 17 00:00:00 2001 From: Kane Rogers Date: Fri, 17 Feb 2023 16:57:34 +1100 Subject: [PATCH 06/14] Improve input handling --- hotham-editor/src/camera.rs | 64 ++++++++++++ hotham-editor/src/input_context.rs | 150 +++++++++++++++++++++++++++++ hotham-editor/src/main.rs | 116 +++------------------- 3 files changed, 225 insertions(+), 105 deletions(-) create mode 100644 hotham-editor/src/camera.rs create mode 100644 hotham-editor/src/input_context.rs diff --git a/hotham-editor/src/camera.rs b/hotham-editor/src/camera.rs new file mode 100644 index 00000000..956d68bc --- /dev/null +++ b/hotham-editor/src/camera.rs @@ -0,0 +1,64 @@ +use std::time::Instant; + +use glam::{Quat, Vec3}; +use winit::event::KeyboardInput; + +use crate::input_context::InputContext; + +#[derive(Debug, Clone)] +pub struct Camera { + pose: Pose, + input_context: InputContext, +} + +impl Default for Camera { + fn default() -> Self { + let pose = Pose { + position: [0., 1.4, 0.].into(), + orientation: Default::default(), + }; + Self { + pose, + input_context: Default::default(), + } + } +} + +impl Camera { + pub fn as_pose(&self) -> openxr_sys::Posef { + (&self.pose).into() + } + + pub fn process_input(&mut self, last_frame_time: Instant, keyboard_input: &[KeyboardInput]) { + let delta_time = (Instant::now() - last_frame_time).as_secs_f32(); + self.input_context + .update(delta_time, keyboard_input, &mut self.pose) + } +} + +#[derive(Debug, Clone)] +pub struct Pose { + pub position: Vec3, + pub orientation: Quat, +} + +impl From<&Pose> for openxr_sys::Posef { + fn from(pose: &Pose) -> Self { + let p = pose.position; + let o = pose.orientation; + + openxr_sys::Posef { + orientation: openxr_sys::Quaternionf { + x: o.x, + y: o.y, + z: o.z, + w: o.w, + }, + position: openxr_sys::Vector3f { + x: p.x, + y: p.y, + z: p.z, + }, + } + } +} diff --git a/hotham-editor/src/input_context.rs b/hotham-editor/src/input_context.rs new file mode 100644 index 00000000..156da4a1 --- /dev/null +++ b/hotham-editor/src/input_context.rs @@ -0,0 +1,150 @@ +use glam::{Quat, Vec3}; +use winit::event::{ElementState, KeyboardInput, VirtualKeyCode}; + +use crate::camera::Pose; + +const INPUT_SPEED: f32 = 10.; + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct InputContext { + keyboard_state: KeyboardState, + mouse_state: MouseState, +} + +impl Default for InputContext { + fn default() -> Self { + Self { + keyboard_state: KeyboardState::Idle, + mouse_state: MouseState::Idle, + } + } +} + +impl InputContext { + pub fn update(&mut self, delta_time: f32, keyboard_input: &[KeyboardInput], pose: &mut Pose) { + if self.keyboard_state != KeyboardState::Idle || !keyboard_input.is_empty() { + dbg!(keyboard_input); + dbg!(&self.keyboard_state); + } + let movement_speed = INPUT_SPEED * delta_time; + + // process event queue + for event in keyboard_input { + let (state, keycode) = (event.state, event.virtual_keycode.unwrap()); + //safe as we only receive events with a keycode + + let next_state = match (&self.keyboard_state, state, keycode) { + (_, ElementState::Pressed, _) => KeyboardState::HoldingKey(keycode), + _ => KeyboardState::Idle, + }; + + self.keyboard_state = next_state; + } + + match self.keyboard_state { + KeyboardState::HoldingKey(key) => update_pose(key, movement_speed, pose), + _ => {} + }; + } +} + +fn update_pose(key: VirtualKeyCode, movement_speed: f32, pose: &mut Pose) { + let position = &mut pose.position; + let orientation = pose.orientation; + // get the forward vector rotated by the camera rotation quaternion + let forward = orientation * -Vec3::Z; + // get the right vector rotated by the camera rotation quaternion + let right = orientation * Vec3::X; + let up = Vec3::Y; + + match key { + winit::event::VirtualKeyCode::W => { + position.x += forward.x * movement_speed; + position.y += forward.y * movement_speed; + position.z += forward.z * movement_speed; + } + winit::event::VirtualKeyCode::S => { + position.x -= forward.x * movement_speed; + position.y -= forward.y * movement_speed; + position.z -= forward.z * movement_speed; + } + winit::event::VirtualKeyCode::A => { + position.x -= right.x * movement_speed; + position.y -= right.y * movement_speed; + position.z -= right.z * movement_speed; + } + winit::event::VirtualKeyCode::D => { + position.x += right.x * movement_speed; + position.y += right.y * movement_speed; + position.z += right.z * movement_speed; + } + winit::event::VirtualKeyCode::Space => { + position.y += up.y * movement_speed; + } + winit::event::VirtualKeyCode::LShift => { + position.y -= up.y * movement_speed; + } + _ => {} + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum KeyboardState { + Idle, + HoldingKey(VirtualKeyCode), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +enum MouseState { + Idle, + HoldingLeftClick, +} + +#[cfg(test)] +#[allow(deprecated)] +mod tests { + use winit::event::{ElementState, KeyboardInput, ModifiersState, VirtualKeyCode}; + + use crate::{camera::Pose, input_context::INPUT_SPEED}; + + use super::InputContext; + + #[test] + pub fn test_keyboard_input() { + let mut input_context = InputContext::default(); + let mut pose = Pose { + position: Default::default(), + orientation: Default::default(), + }; + + // press w + input_context.update(1.0, &[press(VirtualKeyCode::W)], &mut pose); + assert_eq!(pose.position, [0., 0., -INPUT_SPEED].into()); + + // keep holding it + input_context.update(1.0, &[], &mut pose); + assert_eq!(pose.position, [0., 0., -INPUT_SPEED * 2.0].into()); + + // release + input_context.update(1.0, &[release(VirtualKeyCode::W)], &mut pose); + assert_eq!(pose.position, [0., 0., -INPUT_SPEED * 2.0].into()); + } + + fn press(virtual_code: VirtualKeyCode) -> KeyboardInput { + KeyboardInput { + scancode: 0, + state: ElementState::Pressed, + virtual_keycode: Some(virtual_code), + modifiers: ModifiersState::empty(), + } + } + + fn release(virtual_code: VirtualKeyCode) -> KeyboardInput { + KeyboardInput { + scancode: 0, + state: ElementState::Released, + virtual_keycode: Some(virtual_code), + modifiers: ModifiersState::empty(), + } + } +} diff --git a/hotham-editor/src/main.rs b/hotham-editor/src/main.rs index 7d1ea160..e8534f28 100644 --- a/hotham-editor/src/main.rs +++ b/hotham-editor/src/main.rs @@ -1,13 +1,16 @@ +mod camera; +mod input_context; + use anyhow::{bail, Result}; use ash::vk; -use glam::{Quat, Vec3}; + use hotham_editor_protocol::{responses, EditorServer, RequestType}; use lazy_vulkan::{ find_memorytype_index, vulkan_context::VulkanContext, vulkan_texture::VulkanTexture, DrawCall, LazyRenderer, LazyVulkan, SwapchainInfo, Vertex, }; use log::{debug, info, trace}; -use openxr_sys::{Posef, Vector3f}; + use std::time::Instant; use uds_windows::{UnixListener, UnixStream}; use winit::{ @@ -16,6 +19,8 @@ use winit::{ platform::run_return::EventLoopExtRunReturn, }; +use crate::camera::Camera; + /// Compile your own damn shaders! LazyVulkan is just as lazy as you are! static FRAGMENT_SHADER: &'_ [u8] = include_bytes!("shaders/triangle.frag.spv"); static VERTEX_SHADER: &'_ [u8] = include_bytes!("shaders/triangle.vert.spv"); @@ -72,17 +77,9 @@ pub fn main() -> Result<()> { xr_swapchain.images, ); - let mut view_state = Posef { - position: Vector3f { - x: 0., - y: 1.4, - z: 0., - }, - ..Posef::IDENTITY - }; let mut last_frame_time = Instant::now(); let mut keyboard_events = Vec::new(); - let mut last_key_pressed = None; + let mut camera = Camera::default(); // Off we go! let mut winit_initializing = true; @@ -122,18 +119,11 @@ pub fn main() -> Result<()> { Event::MainEventsCleared => { let framebuffer_index = lazy_vulkan.render_begin(); - - update_camera( - &mut view_state, - last_frame_time, - &mut last_key_pressed, - &keyboard_events, - ); - + camera.process_input(last_frame_time, &keyboard_events); keyboard_events.clear(); check_request(&mut server, RequestType::LocateView).unwrap(); - server.send_response(&view_state).unwrap(); + server.send_response(&camera.as_pose()).unwrap(); check_request(&mut server, RequestType::WaitFrame).unwrap(); server.send_response(&0).unwrap(); @@ -142,7 +132,7 @@ pub fn main() -> Result<()> { server.send_response(&framebuffer_index).unwrap(); check_request(&mut server, RequestType::LocateView).unwrap(); - server.send_response(&view_state).unwrap(); + server.send_response(&camera.as_pose()).unwrap(); check_request(&mut server, RequestType::EndFrame).unwrap(); server.send_response(&0).unwrap(); @@ -391,87 +381,3 @@ unsafe fn create_render_images( }) .unzip() } - -// TODO: This needs to be replaced with a nice state machine that can handle keyboard input, mouse input -// and gamepad input. -pub fn update_camera( - pose: &mut Posef, - last_frame_time: Instant, - last_key_pressed: &mut Option, - input_events: &[KeyboardInput], -) { - // We need to adjust the speed value so its always the same speed even if the frame rate isn't consistent - // The delta time is the the current time - last frame time - let dt = (Instant::now() - last_frame_time).as_secs_f32(); - - let movement_speed = 10. * dt; - - // Process the event queue - for event in input_events { - if event.state == ElementState::Pressed { - move_camera(pose, movement_speed, event.virtual_keycode.unwrap()); - } - } - - // Finally, we want to handle someone holding a key down. This is annoying. - - // First, we need to check if the key was released - let key_has_been_released = !input_events.is_empty(); - - // If the user is still holding down the key, we need to treat that as an input event - if !key_has_been_released { - if let Some(last_key) = last_key_pressed { - move_camera(pose, movement_speed, *last_key); - } - } - - // Otherwise, find the last key pressed and mark that as the final one - for event in input_events { - if event.state == ElementState::Pressed { - *last_key_pressed = event.virtual_keycode; - } else if last_key_pressed == &event.virtual_keycode { - *last_key_pressed = None; - } - } -} - -fn move_camera(pose: &mut Posef, movement_speed: f32, virtual_keycode: VirtualKeyCode) { - let position = &mut pose.position; - let o = pose.orientation; - let orientation = Quat::from_xyzw(o.x, o.y, o.z, o.w); - // get the forward vector rotated by the camera rotation quaternion - let forward = orientation * -Vec3::Z; - // get the right vector rotated by the camera rotation quaternion - let right = orientation * Vec3::X; - let up = Vec3::Y; - - match virtual_keycode { - winit::event::VirtualKeyCode::W => { - position.x += forward.x * movement_speed; - position.y += forward.y * movement_speed; - position.z += forward.z * movement_speed; - } - winit::event::VirtualKeyCode::S => { - position.x -= forward.x * movement_speed; - position.y -= forward.y * movement_speed; - position.z -= forward.z * movement_speed; - } - winit::event::VirtualKeyCode::A => { - position.x -= right.x * movement_speed; - position.y -= right.y * movement_speed; - position.z -= right.z * movement_speed; - } - winit::event::VirtualKeyCode::D => { - position.x += right.x * movement_speed; - position.y += right.y * movement_speed; - position.z += right.z * movement_speed; - } - winit::event::VirtualKeyCode::Space => { - position.y += up.y * movement_speed; - } - winit::event::VirtualKeyCode::LShift => { - position.y -= up.y * movement_speed; - } - _ => {} - } -} From a40f4084a0baea094268afd8286bd6a069cbb33e Mon Sep 17 00:00:00 2001 From: Kane Rogers Date: Fri, 17 Feb 2023 18:29:46 +1100 Subject: [PATCH 07/14] Fix up input --- hotham-editor/src/camera.rs | 25 +++++-- hotham-editor/src/input_context.rs | 116 ++++++++++++++++++----------- hotham-editor/src/main.rs | 37 ++++++++- 3 files changed, 129 insertions(+), 49 deletions(-) diff --git a/hotham-editor/src/camera.rs b/hotham-editor/src/camera.rs index 956d68bc..089ef602 100644 --- a/hotham-editor/src/camera.rs +++ b/hotham-editor/src/camera.rs @@ -3,7 +3,7 @@ use std::time::Instant; use glam::{Quat, Vec3}; use winit::event::KeyboardInput; -use crate::input_context::InputContext; +use crate::{input_context::InputContext, MouseInput}; #[derive(Debug, Clone)] pub struct Camera { @@ -15,7 +15,8 @@ impl Default for Camera { fn default() -> Self { let pose = Pose { position: [0., 1.4, 0.].into(), - orientation: Default::default(), + pitch: 0., + yaw: 0., }; Self { pose, @@ -29,23 +30,35 @@ impl Camera { (&self.pose).into() } - pub fn process_input(&mut self, last_frame_time: Instant, keyboard_input: &[KeyboardInput]) { + pub fn process_input( + &mut self, + last_frame_time: Instant, + keyboard_input: &[KeyboardInput], + mouse_input: &[MouseInput], + ) { let delta_time = (Instant::now() - last_frame_time).as_secs_f32(); self.input_context - .update(delta_time, keyboard_input, &mut self.pose) + .update(delta_time, keyboard_input, mouse_input, &mut self.pose) } } #[derive(Debug, Clone)] pub struct Pose { pub position: Vec3, - pub orientation: Quat, + pub pitch: f32, + pub yaw: f32, +} + +impl Pose { + pub fn orientation(&self) -> Quat { + Quat::from_euler(glam::EulerRot::YXZ, self.yaw, self.pitch, 0.) + } } impl From<&Pose> for openxr_sys::Posef { fn from(pose: &Pose) -> Self { let p = pose.position; - let o = pose.orientation; + let o = pose.orientation(); openxr_sys::Posef { orientation: openxr_sys::Quaternionf { diff --git a/hotham-editor/src/input_context.rs b/hotham-editor/src/input_context.rs index 156da4a1..dad0a999 100644 --- a/hotham-editor/src/input_context.rs +++ b/hotham-editor/src/input_context.rs @@ -1,7 +1,7 @@ -use glam::{Quat, Vec3}; +use glam::{Vec2, Vec3}; use winit::event::{ElementState, KeyboardInput, VirtualKeyCode}; -use crate::camera::Pose; +use crate::{camera::Pose, MouseInput}; const INPUT_SPEED: f32 = 10.; @@ -15,77 +15,108 @@ impl Default for InputContext { fn default() -> Self { Self { keyboard_state: KeyboardState::Idle, - mouse_state: MouseState::Idle, + mouse_state: MouseState::HoldingLeftClick, } } } impl InputContext { - pub fn update(&mut self, delta_time: f32, keyboard_input: &[KeyboardInput], pose: &mut Pose) { - if self.keyboard_state != KeyboardState::Idle || !keyboard_input.is_empty() { - dbg!(keyboard_input); - dbg!(&self.keyboard_state); - } + pub fn update( + &mut self, + delta_time: f32, + keyboard_input: &[KeyboardInput], + mouse_input: &[MouseInput], + pose: &mut Pose, + ) { let movement_speed = INPUT_SPEED * delta_time; - // process event queue - for event in keyboard_input { - let (state, keycode) = (event.state, event.virtual_keycode.unwrap()); - //safe as we only receive events with a keycode - - let next_state = match (&self.keyboard_state, state, keycode) { - (_, ElementState::Pressed, _) => KeyboardState::HoldingKey(keycode), - _ => KeyboardState::Idle, + let mut mouse_motion = Vec2::ZERO; + for event in mouse_input { + match event { + MouseInput::LeftClickPressed => self.mouse_state = MouseState::HoldingLeftClick, + MouseInput::LeftClickReleased => self.mouse_state = MouseState::Idle, + MouseInput::MouseMoved(delta) => { + if self.mouse_state == MouseState::HoldingLeftClick { + mouse_motion += *delta; + } + } }; + } + handle_mouse_movement(mouse_motion * movement_speed, pose); - self.keyboard_state = next_state; + let mut keyboard_motion = Vec3::ZERO; + for event in keyboard_input { + //safe as we only receive events with a keycode + let (state, key) = (event.state, event.virtual_keycode.unwrap()); + + match state { + ElementState::Pressed => { + self.keyboard_state = KeyboardState::HoldingKey(key); + keyboard_motion += handle_keypress(key, pose); + } + ElementState::Released => { + self.keyboard_state = KeyboardState::Idle; + } + } } - match self.keyboard_state { - KeyboardState::HoldingKey(key) => update_pose(key, movement_speed, pose), + // If there were no keyboard inputs, but we're still holding down a key, act as if that key was pressed + match (&self.keyboard_state, keyboard_input.is_empty()) { + (KeyboardState::HoldingKey(key), true) => keyboard_motion = handle_keypress(*key, pose), _ => {} - }; + } + + pose.position += keyboard_motion * movement_speed; } } -fn update_pose(key: VirtualKeyCode, movement_speed: f32, pose: &mut Pose) { - let position = &mut pose.position; - let orientation = pose.orientation; +fn handle_mouse_movement(movement: Vec2, pose: &mut Pose) { + pose.yaw -= movement.x; + const MIN_PITCH: f32 = -std::f32::consts::FRAC_PI_4; + const MAX_PITCH: f32 = std::f32::consts::FRAC_PI_4; + pose.pitch = (pose.pitch - movement.y).clamp(MIN_PITCH, MAX_PITCH); +} + +fn handle_keypress(key: VirtualKeyCode, pose: &mut Pose) -> Vec3 { + let orientation = pose.orientation(); + let mut position = Vec3::ZERO; // get the forward vector rotated by the camera rotation quaternion - let forward = orientation * -Vec3::Z; + let forward = orientation * Vec3::NEG_Z; // get the right vector rotated by the camera rotation quaternion let right = orientation * Vec3::X; let up = Vec3::Y; match key { winit::event::VirtualKeyCode::W => { - position.x += forward.x * movement_speed; - position.y += forward.y * movement_speed; - position.z += forward.z * movement_speed; + position.x += forward.x; + position.y += forward.y; + position.z += forward.z; } winit::event::VirtualKeyCode::S => { - position.x -= forward.x * movement_speed; - position.y -= forward.y * movement_speed; - position.z -= forward.z * movement_speed; + position.x -= forward.x; + position.y -= forward.y; + position.z -= forward.z; } winit::event::VirtualKeyCode::A => { - position.x -= right.x * movement_speed; - position.y -= right.y * movement_speed; - position.z -= right.z * movement_speed; + position.x -= right.x; + position.y -= right.y; + position.z -= right.z; } winit::event::VirtualKeyCode::D => { - position.x += right.x * movement_speed; - position.y += right.y * movement_speed; - position.z += right.z * movement_speed; + position.x += right.x; + position.y += right.y; + position.z += right.z; } winit::event::VirtualKeyCode::Space => { - position.y += up.y * movement_speed; + position.y += up.y; } winit::event::VirtualKeyCode::LShift => { - position.y -= up.y * movement_speed; + position.y -= up.y; } _ => {} } + + position } #[derive(Debug, Clone, PartialEq, Eq)] @@ -114,19 +145,20 @@ mod tests { let mut input_context = InputContext::default(); let mut pose = Pose { position: Default::default(), - orientation: Default::default(), + pitch: 0., + yaw: 0., }; // press w - input_context.update(1.0, &[press(VirtualKeyCode::W)], &mut pose); + input_context.update(1.0, &[press(VirtualKeyCode::W)], &[], &mut pose); assert_eq!(pose.position, [0., 0., -INPUT_SPEED].into()); // keep holding it - input_context.update(1.0, &[], &mut pose); + input_context.update(1.0, &[], &[], &mut pose); assert_eq!(pose.position, [0., 0., -INPUT_SPEED * 2.0].into()); // release - input_context.update(1.0, &[release(VirtualKeyCode::W)], &mut pose); + input_context.update(1.0, &[release(VirtualKeyCode::W)], &[], &mut pose); assert_eq!(pose.position, [0., 0., -INPUT_SPEED * 2.0].into()); } diff --git a/hotham-editor/src/main.rs b/hotham-editor/src/main.rs index e8534f28..176b7a51 100644 --- a/hotham-editor/src/main.rs +++ b/hotham-editor/src/main.rs @@ -4,6 +4,7 @@ mod input_context; use anyhow::{bail, Result}; use ash::vk; +use glam::Vec2; use hotham_editor_protocol::{responses, EditorServer, RequestType}; use lazy_vulkan::{ find_memorytype_index, vulkan_context::VulkanContext, vulkan_texture::VulkanTexture, DrawCall, @@ -79,6 +80,7 @@ pub fn main() -> Result<()> { let mut last_frame_time = Instant::now(); let mut keyboard_events = Vec::new(); + let mut mouse_events = Vec::new(); let mut camera = Camera::default(); // Off we go! @@ -119,8 +121,9 @@ pub fn main() -> Result<()> { Event::MainEventsCleared => { let framebuffer_index = lazy_vulkan.render_begin(); - camera.process_input(last_frame_time, &keyboard_events); + camera.process_input(last_frame_time, &keyboard_events, &mouse_events); keyboard_events.clear(); + mouse_events.clear(); check_request(&mut server, RequestType::LocateView).unwrap(); server.send_response(&camera.as_pose()).unwrap(); @@ -173,6 +176,32 @@ pub fn main() -> Result<()> { keyboard_events.push(input); } } + Event::DeviceEvent { event, .. } => match event { + winit::event::DeviceEvent::MouseMotion { delta } => { + if focused { + mouse_events.push(MouseInput::MouseMoved( + [delta.0 as f32, delta.1 as f32].into(), // translate from screen space to world space.. sort of + )) + } + } + winit::event::DeviceEvent::Button { + button: 1, + state: ElementState::Pressed, + } => { + if focused { + mouse_events.push(MouseInput::LeftClickPressed) + } + } + winit::event::DeviceEvent::Button { + button: 1, + state: ElementState::Released, + } => { + if focused { + mouse_events.push(MouseInput::LeftClickReleased) + } + } + _ => {} + }, _ => (), } }); @@ -185,6 +214,12 @@ pub fn main() -> Result<()> { Ok(()) } +pub enum MouseInput { + LeftClickPressed, + LeftClickReleased, + MouseMoved(Vec2), +} + pub struct XrSwapchain { images: Vec, semaphores: Vec, From 9b92f67ab1228a7e0dff6a92f9a384de8a12b14b Mon Sep 17 00:00:00 2001 From: Kane Rogers Date: Wed, 22 Feb 2023 20:40:24 +1100 Subject: [PATCH 08/14] Begin adding support for JSON values --- examples/simple-scene/Cargo.toml | 12 ++- examples/simple-scene/src/lib.rs | 34 ++++++- hotham-editor-protocol/Cargo.toml | 5 +- hotham-editor-protocol/src/lib.rs | 77 ++++++++++++++ hotham-editor/Cargo.toml | 5 +- hotham-editor/src/gui.rs | 19 ++++ hotham-editor/src/main.rs | 162 +++++++++++++++++++++++------- hotham-openxr-client/test.ps1 | 2 +- 8 files changed, 270 insertions(+), 46 deletions(-) create mode 100644 hotham-editor/src/gui.rs diff --git a/examples/simple-scene/Cargo.toml b/examples/simple-scene/Cargo.toml index 4dbf019c..485291ca 100644 --- a/examples/simple-scene/Cargo.toml +++ b/examples/simple-scene/Cargo.toml @@ -13,9 +13,17 @@ path = "src/main.rs" [dependencies] env_logger = "0.10.0" -hotham = {path = "../../hotham"} +hotham = {path = "../../hotham", features = ["editor"]} +hotham-editor-protocol = {path = "../../hotham-editor-protocol", optional = true} log = "0.4.17" +[features] +default = ["editor"] +editor = ["dep:uds_windows", "dep:hotham-editor-protocol"] + +[target.'cfg(windows)'.dependencies] +uds_windows = {version = "1.0.2", optional = true} + [target.'cfg(target_os = "android")'.dependencies] ndk-glue = "0.6" @@ -78,5 +86,5 @@ version = 1 # # !! IMPORTANT !! [package.metadata.android.signing.release] -path = "../hotham_examples.keystore" keystore_password = "chomsky-vigilant-spa" +path = "../hotham_examples.keystore" diff --git a/examples/simple-scene/src/lib.rs b/examples/simple-scene/src/lib.rs index 02f341b6..dd0d5060 100644 --- a/examples/simple-scene/src/lib.rs +++ b/examples/simple-scene/src/lib.rs @@ -1,6 +1,9 @@ use hotham::{ asset_importer::{self, add_model_to_world}, - components::{hand::Handedness, physics::SharedShape, Collider, LocalTransform, RigidBody}, + components::{ + hand::Handedness, physics::SharedShape, Collider, GlobalTransform, LocalTransform, + RigidBody, + }, hecs::World, systems::{ animation_system, debug::debug_system, grabbing_system, hands::add_hand, hands_system, @@ -30,7 +33,17 @@ pub fn real_main() -> HothamResult<()> { .filter_module("simple_scene_example", log::LevelFilter::Trace) .init(); - println!("Hello!"); + info!("Initialising Simple Scene example.."); + + #[cfg(feature = "editor")] + let mut editor = { + use hotham_editor_protocol::EditorClient; + use uds_windows::UnixStream; + info!("Connecting to editor.."); + let stream = UnixStream::connect("hotham_editor.socket")?; + EditorClient::new(stream) + }; + info!("Building engine.."); let mut engine = Engine::new(); info!("..done!"); @@ -41,6 +54,9 @@ pub fn real_main() -> HothamResult<()> { info!("Done! Entering main loop.."); while let Ok(tick_data) = engine.update() { + #[cfg(feature = "editor")] + send_scene(&mut engine.world, &mut editor)?; + tick(tick_data, &mut engine, &mut state); engine.finish()?; } @@ -48,6 +64,20 @@ pub fn real_main() -> HothamResult<()> { Ok(()) } +fn send_scene( + world: &mut World, + editor: &mut hotham_editor_protocol::EditorClient, +) -> HothamResult<()> { + let scene = hotham_editor_protocol::scene::Scene { + name: "Simple Scene".to_string(), + entities: vec![], + }; + + editor.send_json(&scene).unwrap(); // TODO: error types + + Ok(()) +} + fn tick(tick_data: TickData, engine: &mut Engine, _state: &mut State) { if tick_data.current_state == xr::SessionState::FOCUSED { hands_system(engine); diff --git a/hotham-editor-protocol/Cargo.toml b/hotham-editor-protocol/Cargo.toml index baf29211..31c90b95 100644 --- a/hotham-editor-protocol/Cargo.toml +++ b/hotham-editor-protocol/Cargo.toml @@ -1,10 +1,13 @@ [package] +edition = "2021" name = "hotham-editor-protocol" version = "0.1.0" -edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] ash = "0.37.2" +mint = "0.5.9" openxr-sys = "0.9.3" +serde = {version = "1.0", features = ["derive"]} +serde_json = "1.0" diff --git a/hotham-editor-protocol/src/lib.rs b/hotham-editor-protocol/src/lib.rs index e2c96b27..8a3d65f8 100644 --- a/hotham-editor-protocol/src/lib.rs +++ b/hotham-editor-protocol/src/lib.rs @@ -1,6 +1,7 @@ use std::io::{Read, Write}; pub use openxr_sys::ViewConfigurationView; +use serde::{de::DeserializeOwned, Deserialize, Serialize}; #[repr(u32)] #[derive(Debug, Clone, Copy, PartialEq)] @@ -15,6 +16,9 @@ pub enum RequestType { EndFrame, GetInputEvents, LocateView, + InitEditor, + PutEntities, + JSON, // some arbitrary sized data } pub trait Request { @@ -26,6 +30,22 @@ pub trait RequestWithVecResponse { type ResponseItem: Clone; // shitty name } +pub mod scene { + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, Clone)] + pub struct Scene { + pub name: String, + pub entities: Vec, + } + + #[derive(Serialize, Deserialize, Clone)] + pub struct EditorEntity { + pub name: String, + pub id: u64, + } +} + pub mod requests { use crate::{ responses::{InputEvent, SwapchainInfo, ViewConfiguration}, @@ -154,10 +174,22 @@ pub mod requests { impl RequestWithVecResponse for GetInputEvents { type ResponseItem = InputEvent; } + + #[repr(C)] + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct PutEntities; + + impl Request for PutEntities { + type Response = bool; // TODO: might need to split up the Response trait + fn request_type(&self) -> RequestType { + RequestType::PutEntities + } + } } pub mod responses { use ash::vk; + use serde::{Deserialize, Serialize}; #[repr(C)] #[derive(Debug, Clone, Copy)] @@ -261,6 +293,24 @@ impl EditorClient { write_request(request, &mut self.socket) } + pub fn send_json( + &mut self, + value: &J, + ) -> serde_json::Result<()> { + let json_bytes = serde_json::to_vec(value)?; + let header = RequestHeader { + request_type: RequestType::JSON, + payload_length: json_bytes.len() as u32, + }; + + self.socket + .write_all(&{ unsafe { bytes_from_t(&header) } }) + .unwrap(); // TODO error types + self.socket.write_all(&json_bytes).unwrap(); // TODO error types + + Ok(()) + } + pub fn get_response(&mut self) -> std::io::Result { let socket = &mut self.socket; let buf = &mut self.buffer; @@ -309,6 +359,14 @@ impl EditorServer { ) } + pub fn get_json(&mut self) -> serde_json::Result { + let request_header = self.get_request_header().unwrap(); // TODO error types + assert_eq!(request_header.request_type, RequestType::JSON); + let buffer = &mut self.buffer[..request_header.payload_length as _]; + self.socket.read_exact(buffer).unwrap(); // TODO: error types + serde_json::from_slice(buffer) + } + pub fn get_request_header(&mut self) -> std::io::Result { read_request_header(&mut self.socket, &mut self.buffer) } @@ -360,6 +418,7 @@ mod tests { use super::*; use openxr_sys::{StructureType, ViewConfigurationView}; + use serde::Deserialize; use std::{cell::RefCell, rc::Rc}; #[derive(Default, Clone)] @@ -470,4 +529,22 @@ mod tests { response_from_server[1].max_swapchain_sample_count ); } + + #[test] + pub fn test_json() -> std::io::Result<()> { + let socket = MockSocket::default(); + let mut client = EditorClient::new(socket.clone()); + let mut server = EditorServer::new(socket); + + #[derive(Serialize, Deserialize, Clone)] + struct GetThing { + thing_amount: usize, + } + + client.send_json(&GetThing { thing_amount: 5 }).unwrap(); + let request: GetThing = server.get_json().unwrap(); + assert_eq!(request.thing_amount, 5); + + Ok(()) + } } diff --git a/hotham-editor/Cargo.toml b/hotham-editor/Cargo.toml index 7f398439..192cccfa 100644 --- a/hotham-editor/Cargo.toml +++ b/hotham-editor/Cargo.toml @@ -9,10 +9,13 @@ ash = "0.37.2" env_logger = "0.10.0" glam = "0.22.0" hotham-editor-protocol = {path = "../hotham-editor-protocol"} -lazy_vulkan = {git = "https://github.com/leetvr/lazy_vulkan", rev = "5347dd3295fd4c687c35adbe7c0b4aad00c893e3"} +lazy_vulkan = {git = "https://github.com/leetvr/lazy_vulkan", rev = "4e8b4982328f7bda76b10e6b7e83c65398917fef"}# main @ 20/2/2023 log = "0.4.17" openxr-sys = "0.9.3" winit = "0.28.1" +# yakui = {git = "https://github.com/LPGhatguy/yakui", rev = "0b7d6f7ded117525f0986476c3f34a03fd9391a8"}# main @ 20/02/2023 +yakui = {path = "../../yakui/crates/yakui"} +yakui-vulkan = {path = "../../yakui/crates/yakui-vulkan"} [target.'cfg(windows)'.dependencies] uds_windows = "1.0.2" diff --git a/hotham-editor/src/gui.rs b/hotham-editor/src/gui.rs new file mode 100644 index 00000000..98342f86 --- /dev/null +++ b/hotham-editor/src/gui.rs @@ -0,0 +1,19 @@ +use glam::Vec2; +use yakui::TextureId; +use yakui::{column, image, label, row, text, widgets::Text, Color}; + +pub fn gui(gui_state: &GuiState) { + row(|| { + column(|| { + image(gui_state.texture_id, Vec2::new(1500.0, 1500.0)); + }); + column(|| { + text(42., gui_state.scene_name.clone()); + }); + }); +} + +pub struct GuiState { + pub texture_id: TextureId, + pub scene_name: String, +} diff --git a/hotham-editor/src/main.rs b/hotham-editor/src/main.rs index 176b7a51..34f3719d 100644 --- a/hotham-editor/src/main.rs +++ b/hotham-editor/src/main.rs @@ -1,4 +1,5 @@ mod camera; +mod gui; mod input_context; use anyhow::{bail, Result}; @@ -7,7 +8,7 @@ use ash::vk; use glam::Vec2; use hotham_editor_protocol::{responses, EditorServer, RequestType}; use lazy_vulkan::{ - find_memorytype_index, vulkan_context::VulkanContext, vulkan_texture::VulkanTexture, DrawCall, + find_memorytype_index, vulkan_context::VulkanContext, vulkan_texture::VulkanTexture, LazyRenderer, LazyVulkan, SwapchainInfo, Vertex, }; use log::{debug, info, trace}; @@ -20,12 +21,15 @@ use winit::{ platform::run_return::EventLoopExtRunReturn, }; -use crate::camera::Camera; +use crate::{ + camera::Camera, + gui::{gui, GuiState}, +}; /// Compile your own damn shaders! LazyVulkan is just as lazy as you are! static FRAGMENT_SHADER: &'_ [u8] = include_bytes!("shaders/triangle.frag.spv"); static VERTEX_SHADER: &'_ [u8] = include_bytes!("shaders/triangle.vert.spv"); -const SWAPCHAIN_FORMAT: vk::Format = vk::Format::R8G8B8A8_SRGB; // OpenXR really, really wants us to use SRGB swapchains +const SWAPCHAIN_FORMAT: vk::Format = vk::Format::R8G8B8A8_SRGB; // OpenXR really, really wants us to use sRGB swapchains static UNIX_SOCKET_PATH: &'_ str = "hotham_editor.socket"; pub fn main() -> Result<()> { @@ -46,6 +50,26 @@ pub fn main() -> Result<()> { // Your own index type?! What are you going to use, `u16`? let indices = [0, 1, 2, 2, 1, 3]; + let window_size = vk::Extent2D { + width: 800, + height: 500, + }; + + // Let's do something totally normal and wait for a TCP connection + if std::fs::remove_file(UNIX_SOCKET_PATH).is_ok() { + debug!("Removed pre-existing unix socket at {UNIX_SOCKET_PATH}"); + } + + let listener = UnixListener::bind(UNIX_SOCKET_PATH).unwrap(); + info!("Listening on {UNIX_SOCKET_PATH}: waiting for game client.."); + let (stream, _) = listener.accept().unwrap(); + let mut game_server = EditorServer::new(stream); + + info!("Game connected! Waiting for OpenXR client..",); + let (stream, _) = listener.accept().unwrap(); + let mut openxr_server = EditorServer::new(stream); + info!("OpenXR client connected! Opening window and doing OpenXR setup"); + // Alright, let's build some stuff let (mut lazy_vulkan, mut lazy_renderer, mut event_loop) = LazyVulkan::builder() .initial_vertices(&vertices) @@ -53,26 +77,19 @@ pub fn main() -> Result<()> { .fragment_shader(FRAGMENT_SHADER) .vertex_shader(VERTEX_SHADER) .with_present(true) + .window_size(window_size) .build(); - - // Let's do something totally normal and wait for a TCP connection - if std::fs::remove_file(UNIX_SOCKET_PATH).is_ok() { - debug!("Removed pre-existing unix socket at {UNIX_SOCKET_PATH}"); - } - - let listener = UnixListener::bind(UNIX_SOCKET_PATH).unwrap(); - info!("Listening on {UNIX_SOCKET_PATH} - waiting for client.."); let swapchain_info = SwapchainInfo { image_count: lazy_vulkan.surface.desired_image_count, - resolution: lazy_vulkan.surface.surface_resolution, + resolution: vk::Extent2D { + width: 1500, + height: 1500, + }, format: SWAPCHAIN_FORMAT, }; - let (stream, _) = listener.accept().unwrap(); - info!("Client connected! Doing OpenXR setup.."); - let mut server = EditorServer::new(stream); - let xr_swapchain = do_openxr_setup(&mut server, lazy_vulkan.context(), &swapchain_info)?; + let xr_swapchain = do_openxr_setup(&mut openxr_server, lazy_vulkan.context(), &swapchain_info)?; info!("..done!"); - let textures = create_render_textures( + let mut yak_images = create_render_textures( lazy_vulkan.context(), &mut lazy_renderer, xr_swapchain.images, @@ -82,6 +99,40 @@ pub fn main() -> Result<()> { let mut keyboard_events = Vec::new(); let mut mouse_events = Vec::new(); let mut camera = Camera::default(); + let mut yak = yakui::Yakui::new(); + yak.set_surface_size([800 as f32, 500 as f32].into()); + + let (mut yakui_vulkan, yak_images) = { + let context = lazy_vulkan.context(); + let yakui_vulkan_context = yakui_vulkan::VulkanContext::new( + &context.device, + context.queue, + context.draw_command_buffer, + context.command_pool, + context.memory_properties, + ); + let render_surface = yakui_vulkan::RenderSurface { + resolution: window_size, + format: lazy_vulkan.surface.surface_format.format, + image_views: lazy_renderer.render_surface.image_views.clone(), + }; + let mut yakui_vulkan = + yakui_vulkan::YakuiVulkan::new(&yakui_vulkan_context, render_surface); + let yak_images = yak_images + .drain(..) + .map(|t| { + let yak_texture = lazy_to_yak(&yakui_vulkan_context, yakui_vulkan.descriptors(), t); + yakui_vulkan.add_user_texture(&yakui_vulkan_context, yak_texture) + }) + .collect::>(); + + (yakui_vulkan, yak_images) + }; + + let mut gui_state = GuiState { + texture_id: yak_images[0], + scene_name: "".into(), + }; // Off we go! let mut winit_initializing = true; @@ -125,33 +176,42 @@ pub fn main() -> Result<()> { keyboard_events.clear(); mouse_events.clear(); - check_request(&mut server, RequestType::LocateView).unwrap(); - server.send_response(&camera.as_pose()).unwrap(); + check_request(&mut openxr_server, RequestType::LocateView).unwrap(); + openxr_server.send_response(&camera.as_pose()).unwrap(); - check_request(&mut server, RequestType::WaitFrame).unwrap(); - server.send_response(&0).unwrap(); + check_request(&mut openxr_server, RequestType::WaitFrame).unwrap(); + openxr_server.send_response(&0).unwrap(); - check_request(&mut server, RequestType::AcquireSwapchainImage).unwrap(); - server.send_response(&framebuffer_index).unwrap(); + check_request(&mut openxr_server, RequestType::AcquireSwapchainImage).unwrap(); + openxr_server.send_response(&framebuffer_index).unwrap(); - check_request(&mut server, RequestType::LocateView).unwrap(); - server.send_response(&camera.as_pose()).unwrap(); + let scene: hotham_editor_protocol::scene::Scene = game_server.get_json().unwrap(); + gui_state.scene_name = scene.name; - check_request(&mut server, RequestType::EndFrame).unwrap(); - server.send_response(&0).unwrap(); + // game has finished frame here - let texture_id = textures[framebuffer_index as usize].id; - lazy_renderer.render( - lazy_vulkan.context(), - framebuffer_index, - &[DrawCall::new( - 0, - indices.len() as _, - texture_id, - lazy_vulkan::Workflow::Main, - )], + check_request(&mut openxr_server, RequestType::LocateView).unwrap(); + openxr_server.send_response(&camera.as_pose()).unwrap(); + + check_request(&mut openxr_server, RequestType::EndFrame).unwrap(); + openxr_server.send_response(&0).unwrap(); + + gui_state.texture_id = yak_images[framebuffer_index as usize]; + yak.start(); + gui(&gui_state); + yak.finish(); + + let context = lazy_vulkan.context(); + let yakui_vulkan_context = yakui_vulkan::VulkanContext::new( + &context.device, + context.queue, + context.draw_command_buffer, + context.command_pool, + context.memory_properties, ); + yakui_vulkan.paint(&mut yak, &yakui_vulkan_context, framebuffer_index); + let semaphore = xr_swapchain.semaphores[framebuffer_index as usize]; lazy_vulkan.render_end( framebuffer_index, @@ -165,9 +225,19 @@ pub fn main() -> Result<()> { } => { if !winit_initializing { let new_render_surface = lazy_vulkan.resized(size.width, size.height); - lazy_renderer.update_surface(new_render_surface, &lazy_vulkan.context().device); + let render_surface = yakui_vulkan::RenderSurface { + resolution: new_render_surface.resolution, + format: new_render_surface.format, + image_views: new_render_surface.image_views, + }; + yakui_vulkan.update_surface(render_surface, &lazy_vulkan.context().device); + yak.set_surface_size([size.width as f32, size.height as f32].into()); } } + Event::WindowEvent { + event: WindowEvent::ScaleFactorChanged { scale_factor, .. }, + .. + } => yak.set_scale_factor(scale_factor as _), Event::WindowEvent { event: WindowEvent::KeyboardInput { input, .. }, .. @@ -180,7 +250,7 @@ pub fn main() -> Result<()> { winit::event::DeviceEvent::MouseMotion { delta } => { if focused { mouse_events.push(MouseInput::MouseMoved( - [delta.0 as f32, delta.1 as f32].into(), // translate from screen space to world space.. sort of + [delta.0 as f32, delta.1 as f32].into(), )) } } @@ -214,6 +284,20 @@ pub fn main() -> Result<()> { Ok(()) } +fn lazy_to_yak( + yakui_vulkan_context: &yakui_vulkan::VulkanContext, + descriptors: &mut yakui_vulkan::Descriptors, + t: VulkanTexture, +) -> yakui_vulkan::VulkanTexture { + yakui_vulkan::VulkanTexture::from_image( + yakui_vulkan_context, + descriptors, + t.image, + t.memory, + t.view, + ) +} + pub enum MouseInput { LeftClickPressed, LeftClickReleased, diff --git a/hotham-openxr-client/test.ps1 b/hotham-openxr-client/test.ps1 index 638c01a2..62223c70 100644 --- a/hotham-openxr-client/test.ps1 +++ b/hotham-openxr-client/test.ps1 @@ -4,4 +4,4 @@ ${Env:RUST_BACKTRACE} = 1 cargo build -p hotham_openxr_client # Start-Job -ScriptBlock { cargo run --bin hotham-editor } # Start-Sleep -seconds 1 -cargo run --bin hotham_simple_scene_example --features editor +cargo run --release --bin hotham_simple_scene_example --features editor From 4219b67f5e696f8cd28cd5e741985288708f5ae9 Mon Sep 17 00:00:00 2001 From: Kane Rogers Date: Thu, 23 Feb 2023 22:13:27 +1100 Subject: [PATCH 09/14] WIP --- examples/simple-scene/src/lib.rs | 45 +++++++++++++---- hotham-editor-protocol/Cargo.toml | 2 +- hotham-editor-protocol/src/lib.rs | 43 ++++++++++++++-- hotham-editor/Cargo.toml | 1 + hotham-editor/src/gui.rs | 47 ++++++++++++++--- hotham-editor/src/main.rs | 83 ++++++++++++++++++++++++------- hotham-openxr-client/test.ps1 | 5 +- 7 files changed, 185 insertions(+), 41 deletions(-) diff --git a/examples/simple-scene/src/lib.rs b/examples/simple-scene/src/lib.rs index dd0d5060..26b35dc7 100644 --- a/examples/simple-scene/src/lib.rs +++ b/examples/simple-scene/src/lib.rs @@ -1,8 +1,8 @@ use hotham::{ asset_importer::{self, add_model_to_world}, components::{ - hand::Handedness, physics::SharedShape, Collider, GlobalTransform, LocalTransform, - RigidBody, + hand::Handedness, physics::SharedShape, Collider, GlobalTransform, Info, LocalTransform, + Mesh, RigidBody, HMD, }, hecs::World, systems::{ @@ -12,7 +12,8 @@ use hotham::{ }, xr, Engine, HothamResult, TickData, }; -use log::info; +use hotham_editor_protocol::scene::{EditorEntity, EditorUpdates, Transform}; +use log::{debug, info}; #[derive(Clone, Debug, Default)] /// Most Hotham applications will want to keep track of some sort of state. @@ -55,7 +56,7 @@ pub fn real_main() -> HothamResult<()> { while let Ok(tick_data) = engine.update() { #[cfg(feature = "editor")] - send_scene(&mut engine.world, &mut editor)?; + sync_with_editor(&mut engine.world, &mut editor)?; tick(tick_data, &mut engine, &mut state); engine.finish()?; @@ -64,17 +65,45 @@ pub fn real_main() -> HothamResult<()> { Ok(()) } -fn send_scene( +fn sync_with_editor( world: &mut World, editor: &mut hotham_editor_protocol::EditorClient, ) -> HothamResult<()> { + use hotham::hecs::{Entity, Or}; + let entities = world + .query_mut::<(&GlobalTransform, &Info)>() + .with::<&Mesh>() + .into_iter() + .map(|(entity, (transform, info))| { + let (_, _, translation) = transform.to_scale_rotation_translation(); + EditorEntity { + name: info.name.clone(), + id: entity.to_bits().get(), + transform: Transform { + translation: translation.into(), + }, + } + }) + .collect(); + let scene = hotham_editor_protocol::scene::Scene { name: "Simple Scene".to_string(), - entities: vec![], + entities, }; editor.send_json(&scene).unwrap(); // TODO: error types + let editor_updates: EditorUpdates = editor.get_json().unwrap(); // TODO: error types + for entity in editor_updates.entity_updates { + debug!("Received update: {entity:?}"); + let mut entity_transform = world + .entity(Entity::from_bits(entity.id).unwrap()) + .unwrap() + .get::<&mut LocalTransform>() + .unwrap(); + entity_transform.translation = entity.transform.translation.into(); + } + Ok(()) } @@ -136,7 +165,5 @@ fn add_helmet(models: &std::collections::HashMap, world: &mut Wor let collider = Collider::new(SharedShape::ball(0.35)); - world - .insert(helmet, (collider, RigidBody::default())) - .unwrap(); + world.insert_one(helmet, collider).unwrap(); } diff --git a/hotham-editor-protocol/Cargo.toml b/hotham-editor-protocol/Cargo.toml index 31c90b95..f4aac57b 100644 --- a/hotham-editor-protocol/Cargo.toml +++ b/hotham-editor-protocol/Cargo.toml @@ -7,7 +7,7 @@ version = "0.1.0" [dependencies] ash = "0.37.2" -mint = "0.5.9" +mint = {version = "0.5.9", features = ["serde"]} openxr-sys = "0.9.3" serde = {version = "1.0", features = ["derive"]} serde_json = "1.0" diff --git a/hotham-editor-protocol/src/lib.rs b/hotham-editor-protocol/src/lib.rs index 8a3d65f8..6de682be 100644 --- a/hotham-editor-protocol/src/lib.rs +++ b/hotham-editor-protocol/src/lib.rs @@ -1,7 +1,7 @@ use std::io::{Read, Write}; pub use openxr_sys::ViewConfigurationView; -use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use serde::{de::DeserializeOwned, Serialize}; #[repr(u32)] #[derive(Debug, Clone, Copy, PartialEq)] @@ -33,16 +33,27 @@ pub trait RequestWithVecResponse { pub mod scene { use serde::{Deserialize, Serialize}; - #[derive(Serialize, Deserialize, Clone)] + #[derive(Serialize, Deserialize, Clone, Debug)] + pub struct EditorUpdates { + pub entity_updates: Vec, + } + + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Scene { pub name: String, pub entities: Vec, } - #[derive(Serialize, Deserialize, Clone)] + #[derive(Serialize, Deserialize, Clone, Debug)] pub struct EditorEntity { pub name: String, pub id: u64, + pub transform: Transform, + } + + #[derive(Serialize, Deserialize, Clone, Debug)] + pub struct Transform { + pub translation: mint::Vector3, } } @@ -293,6 +304,14 @@ impl EditorClient { write_request(request, &mut self.socket) } + pub fn get_json(&mut self) -> serde_json::Result { + let request_header = read_request_header(&mut self.socket, &mut self.buffer).unwrap(); // TODO error types + assert_eq!(request_header.request_type, RequestType::JSON); + let buffer = &mut self.buffer[..request_header.payload_length as _]; + self.socket.read_exact(buffer).unwrap(); // TODO: error types + serde_json::from_slice(buffer) + } + pub fn send_json( &mut self, value: &J, @@ -367,6 +386,24 @@ impl EditorServer { serde_json::from_slice(buffer) } + pub fn send_json( + &mut self, + value: &J, + ) -> serde_json::Result<()> { + let json_bytes = serde_json::to_vec(value)?; + let header = RequestHeader { + request_type: RequestType::JSON, + payload_length: json_bytes.len() as u32, + }; + + self.socket + .write_all(&{ unsafe { bytes_from_t(&header) } }) + .unwrap(); // TODO error types + self.socket.write_all(&json_bytes).unwrap(); // TODO error types + + Ok(()) + } + pub fn get_request_header(&mut self) -> std::io::Result { read_request_header(&mut self.socket, &mut self.buffer) } diff --git a/hotham-editor/Cargo.toml b/hotham-editor/Cargo.toml index 192cccfa..7cd2dbee 100644 --- a/hotham-editor/Cargo.toml +++ b/hotham-editor/Cargo.toml @@ -16,6 +16,7 @@ winit = "0.28.1" # yakui = {git = "https://github.com/LPGhatguy/yakui", rev = "0b7d6f7ded117525f0986476c3f34a03fd9391a8"}# main @ 20/02/2023 yakui = {path = "../../yakui/crates/yakui"} yakui-vulkan = {path = "../../yakui/crates/yakui-vulkan"} +yakui-winit = {path = "../../yakui/crates/yakui-winit"} [target.'cfg(windows)'.dependencies] uds_windows = "1.0.2" diff --git a/hotham-editor/src/gui.rs b/hotham-editor/src/gui.rs index 98342f86..3fc5e5b2 100644 --- a/hotham-editor/src/gui.rs +++ b/hotham-editor/src/gui.rs @@ -1,19 +1,54 @@ use glam::Vec2; -use yakui::TextureId; -use yakui::{column, image, label, row, text, widgets::Text, Color}; +use yakui::widgets::{List, Panel}; +use yakui::{ + column, expanded, image, label, row, text, textbox, use_state, CrossAxisAlignment, TextureId, +}; -pub fn gui(gui_state: &GuiState) { +pub fn gui(gui_state: GuiState) { + let scene = gui_state.scene; row(|| { column(|| { - image(gui_state.texture_id, Vec2::new(1500.0, 1500.0)); + image(gui_state.texture_id, Vec2::new(500., 500.)); }); column(|| { - text(42., gui_state.scene_name.clone()); + text(42., scene.name); + expanded(|| { + let panel = Panel::side(); + panel.show(|| { + let mut column = List::column(); + column.cross_axis_alignment = CrossAxisAlignment::Start; + + column.show(|| { + for entity in scene.entities { + row(|| { + text(20., "Name"); + }); + row(|| { + text(20., entity.name); + }); + row(|| { + text(20., "Translation"); + }); + row(|| { + text(20., format!("{:?}", entity.transform.translation)); + }); + } + + label("Input"); + let name = use_state(|| String::from("Hello")); + + let res = textbox(name.borrow().clone()); + if let Some(new_name) = res.text.as_ref() { + name.set(new_name.clone()); + } + }); + }); + }); }); }); } pub struct GuiState { pub texture_id: TextureId, - pub scene_name: String, + pub scene: hotham_editor_protocol::scene::Scene, } diff --git a/hotham-editor/src/main.rs b/hotham-editor/src/main.rs index 34f3719d..e09d43fa 100644 --- a/hotham-editor/src/main.rs +++ b/hotham-editor/src/main.rs @@ -6,12 +6,13 @@ use anyhow::{bail, Result}; use ash::vk; use glam::Vec2; -use hotham_editor_protocol::{responses, EditorServer, RequestType}; +use hotham_editor_protocol::{responses, scene::EditorUpdates, EditorServer, RequestType}; use lazy_vulkan::{ find_memorytype_index, vulkan_context::VulkanContext, vulkan_texture::VulkanTexture, LazyRenderer, LazyVulkan, SwapchainInfo, Vertex, }; use log::{debug, info, trace}; +use yakui_winit::YakuiWinit; use std::time::Instant; use uds_windows::{UnixListener, UnixStream}; @@ -26,7 +27,6 @@ use crate::{ gui::{gui, GuiState}, }; -/// Compile your own damn shaders! LazyVulkan is just as lazy as you are! static FRAGMENT_SHADER: &'_ [u8] = include_bytes!("shaders/triangle.frag.spv"); static VERTEX_SHADER: &'_ [u8] = include_bytes!("shaders/triangle.vert.spv"); const SWAPCHAIN_FORMAT: vk::Format = vk::Format::R8G8B8A8_SRGB; // OpenXR really, really wants us to use sRGB swapchains @@ -37,9 +37,6 @@ pub fn main() -> Result<()> { .filter_level(log::LevelFilter::Info) .init(); - // Oh, you thought you could supply your own Vertex type? What is this, a rendergraph?! - // Better make sure those shaders use the right layout! - // **LAUGHS IN VULKAN** let vertices = [ Vertex::new([1.0, 1.0, 0.0, 1.0], [1.0, 1.0, 1.0, 0.0], [1.0, 1.0]), // bottom right Vertex::new([-1.0, 1.0, 0.0, 1.0], [1.0, 1.0, 1.0, 0.0], [0.0, 1.0]), // bottom left @@ -82,8 +79,8 @@ pub fn main() -> Result<()> { let swapchain_info = SwapchainInfo { image_count: lazy_vulkan.surface.desired_image_count, resolution: vk::Extent2D { - width: 1500, - height: 1500, + width: 500, + height: 500, }, format: SWAPCHAIN_FORMAT, }; @@ -95,12 +92,14 @@ pub fn main() -> Result<()> { xr_swapchain.images, ); + let window = &lazy_vulkan.window; + let mut last_frame_time = Instant::now(); let mut keyboard_events = Vec::new(); let mut mouse_events = Vec::new(); let mut camera = Camera::default(); let mut yak = yakui::Yakui::new(); - yak.set_surface_size([800 as f32, 500 as f32].into()); + let mut yakui_window = YakuiWinit::new(window); let (mut yakui_vulkan, yak_images) = { let context = lazy_vulkan.context(); @@ -129,16 +128,14 @@ pub fn main() -> Result<()> { (yakui_vulkan, yak_images) }; - let mut gui_state = GuiState { - texture_id: yak_images[0], - scene_name: "".into(), - }; - // Off we go! let mut winit_initializing = true; let mut focused = false; + let mut right_mouse_clicked = false; + event_loop.run_return(|event, _, control_flow| { *control_flow = ControlFlow::Poll; + yakui_window.handle_event(&mut yak, &event); match event { Event::WindowEvent { event: @@ -186,9 +183,26 @@ pub fn main() -> Result<()> { openxr_server.send_response(&framebuffer_index).unwrap(); let scene: hotham_editor_protocol::scene::Scene = game_server.get_json().unwrap(); - gui_state.scene_name = scene.name; - // game has finished frame here + let entity_updates = if right_mouse_clicked { + let mut helmet = scene + .entities + .iter() + .find(|e| e.name.as_str() == "Damaged Helmet") + .unwrap() + .clone(); + helmet.transform.translation.y += 0.1; + debug!("Sending update: {helmet:?}"); + vec![helmet] + } else { + vec![] + }; + + game_server + .send_json(&EditorUpdates { entity_updates }) + .unwrap(); + + // game has finished rendering its frame here check_request(&mut openxr_server, RequestType::LocateView).unwrap(); openxr_server.send_response(&camera.as_pose()).unwrap(); @@ -196,9 +210,13 @@ pub fn main() -> Result<()> { check_request(&mut openxr_server, RequestType::EndFrame).unwrap(); openxr_server.send_response(&0).unwrap(); - gui_state.texture_id = yak_images[framebuffer_index as usize]; + let gui_state = GuiState { + texture_id: yak_images[framebuffer_index as usize], + scene, + }; + yak.start(); - gui(&gui_state); + gui(gui_state); yak.finish(); let context = lazy_vulkan.context(); @@ -218,6 +236,7 @@ pub fn main() -> Result<()> { &[semaphore, lazy_vulkan.rendering_complete_semaphore], ); last_frame_time = Instant::now(); + right_mouse_clicked = false; } Event::WindowEvent { event: WindowEvent::Resized(size), @@ -235,9 +254,15 @@ pub fn main() -> Result<()> { } } Event::WindowEvent { - event: WindowEvent::ScaleFactorChanged { scale_factor, .. }, + event: + WindowEvent::ScaleFactorChanged { + scale_factor, + new_inner_size, + }, .. - } => yak.set_scale_factor(scale_factor as _), + } => { + debug!("Scale factor changed! Scale factor: {scale_factor}, new inner size: {new_inner_size:?}"); + } Event::WindowEvent { event: WindowEvent::KeyboardInput { input, .. }, .. @@ -246,6 +271,18 @@ pub fn main() -> Result<()> { keyboard_events.push(input); } } + Event::WindowEvent { + event: WindowEvent::CursorMoved { .. }, + .. + } => { + focused = true; + } + Event::WindowEvent { + event: WindowEvent::CursorLeft { .. }, + .. + } => { + focused = false; + } Event::DeviceEvent { event, .. } => match event { winit::event::DeviceEvent::MouseMotion { delta } => { if focused { @@ -254,6 +291,14 @@ pub fn main() -> Result<()> { )) } } + winit::event::DeviceEvent::Button { + button: 3, + state: ElementState::Pressed, + } => { + debug!("Right mouse clicked"); + right_mouse_clicked = true; + } + winit::event::DeviceEvent::Button { button: 1, state: ElementState::Pressed, diff --git a/hotham-openxr-client/test.ps1 b/hotham-openxr-client/test.ps1 index 62223c70..aea1e4dc 100644 --- a/hotham-openxr-client/test.ps1 +++ b/hotham-openxr-client/test.ps1 @@ -1,7 +1,6 @@ Write-Output "All your OpenXR are belong to crab" ${Env:RUST_BACKTRACE} = 1 -cargo build -p hotham_openxr_client -# Start-Job -ScriptBlock { cargo run --bin hotham-editor } -# Start-Sleep -seconds 1 +Start-Job -ScriptBlock { cargo run --bin hotham-editor } +Start-Sleep -Seconds 1 cargo run --release --bin hotham_simple_scene_example --features editor From 901a0c9655694a4eb9043d87b3ba48453867f293 Mon Sep 17 00:00:00 2001 From: Kane Rogers Date: Wed, 1 Mar 2023 17:48:44 +1100 Subject: [PATCH 10/14] [Editor] Finish first pass at editor GUI Fixes #419 --- .gitignore | 3 ++ .vscode/settings.json | 14 ------- examples/simple-scene/src/lib.rs | 4 +- hotham-editor/README.md | 22 ++++++++++ hotham-editor/src/gui.rs | 71 +++++++++++++++++--------------- hotham-editor/src/main.rs | 32 +++++--------- hotham-openxr-client/test.ps1 | 7 +++- 7 files changed, 81 insertions(+), 72 deletions(-) delete mode 100644 .vscode/settings.json create mode 100644 hotham-editor/README.md diff --git a/.gitignore b/.gitignore index 1a7f33a3..7b6a0165 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ sponza* # Checking in a socket is probably a terrible idea *.socket + +# Ignore vscode settings +.vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index c66e9925..00000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools", - "[glsl]": { - "editor.formatOnSave": false - }, - "files.insertFinalNewline": true, - "files.trimFinalNewlines": true, - "files.trimTrailingWhitespace": true, - "webgl-glsl-editor.format.placeSpaceAfterKeywords": true, - "rust-analyzer.check.extraArgs": [ - "--target-dir", - "C:/windows/temp/rust-analyzer-check" - ], -} diff --git a/examples/simple-scene/src/lib.rs b/examples/simple-scene/src/lib.rs index 26b35dc7..d928b3cc 100644 --- a/examples/simple-scene/src/lib.rs +++ b/examples/simple-scene/src/lib.rs @@ -2,7 +2,7 @@ use hotham::{ asset_importer::{self, add_model_to_world}, components::{ hand::Handedness, physics::SharedShape, Collider, GlobalTransform, Info, LocalTransform, - Mesh, RigidBody, HMD, + Mesh, }, hecs::World, systems::{ @@ -69,7 +69,7 @@ fn sync_with_editor( world: &mut World, editor: &mut hotham_editor_protocol::EditorClient, ) -> HothamResult<()> { - use hotham::hecs::{Entity, Or}; + use hotham::hecs::Entity; let entities = world .query_mut::<(&GlobalTransform, &Info)>() .with::<&Mesh>() diff --git a/hotham-editor/README.md b/hotham-editor/README.md new file mode 100644 index 00000000..73677c74 --- /dev/null +++ b/hotham-editor/README.md @@ -0,0 +1,22 @@ +# Hotham Editor +> 🚧 **UNDER CONSTRUCTION** 🚧 +> +> **WARNING**: Even more so than the rest of Hotham, this crate is under *heavy* construction. While it is technically usable in its current state its design may change: at any time. You have been warned! + +## Future plans +The ideal state for the Hotham editor is that it will make it easier to build VR games. That's a pretty ambitious goal, but that is, after all, the whole *point* of Hotham. + +Game Editors are notoriously tricky pieces of technology to define, but here are a couple of things we'd like to be able to do: + +1. Define the *initial state* for some part of a game (eg. a "scene" or a "level") +1. Inspect the *current state* of the game to debug it + +That shouldn't be too hard, right? + +It's also worth noting that the editor will *completely replace* Hotham simulator. + +## Current state +Currently, it's possible to do the following: + +- Run `simple-scene-example` with the `editor` feature +- Manipulate the transform of an entity diff --git a/hotham-editor/src/gui.rs b/hotham-editor/src/gui.rs index 3fc5e5b2..a5660c4a 100644 --- a/hotham-editor/src/gui.rs +++ b/hotham-editor/src/gui.rs @@ -1,48 +1,50 @@ use glam::Vec2; -use yakui::widgets::{List, Panel}; +use hotham_editor_protocol::scene::EditorEntity; +use yakui::widgets::{List, Pad}; use yakui::{ - column, expanded, image, label, row, text, textbox, use_state, CrossAxisAlignment, TextureId, + column, image, label, pad, row, slider, text, textbox, use_state, CrossAxisAlignment, TextureId, }; -pub fn gui(gui_state: GuiState) { - let scene = gui_state.scene; +pub fn gui(gui_state: &mut GuiState) { + let scene = &gui_state.scene; + let updates = &mut gui_state.updates; + let mut main_row = List::row(); row(|| { column(|| { image(gui_state.texture_id, Vec2::new(500., 500.)); }); - column(|| { - text(42., scene.name); - expanded(|| { - let panel = Panel::side(); - panel.show(|| { - let mut column = List::column(); - column.cross_axis_alignment = CrossAxisAlignment::Start; - - column.show(|| { - for entity in scene.entities { - row(|| { - text(20., "Name"); - }); - row(|| { - text(20., entity.name); - }); - row(|| { - text(20., "Translation"); - }); - row(|| { - text(20., format!("{:?}", entity.transform.translation)); - }); - } + pad(Pad::all(20.0), || { + let mut column = List::column(); + column.cross_axis_alignment = CrossAxisAlignment::Start; - label("Input"); - let name = use_state(|| String::from("Hello")); + column.show(|| { + text(42., scene.name.clone()); + for entity in &scene.entities { + row(|| { + label("Name"); + }); + row(|| { + text(20., entity.name.clone()); + }); + row(|| { + label("Translation"); + }); + row(|| { + yakui::column(|| { + label("x"); + let x = entity.transform.translation.x as f64; + let x_state = use_state(move || x); + label(x_state.get().to_string()); - let res = textbox(name.borrow().clone()); - if let Some(new_name) = res.text.as_ref() { - name.set(new_name.clone()); - } + if let Some(new_x) = slider(x_state.get(), -5.0, 5.0).value { + let mut new_entity = entity.clone(); + x_state.set(new_x); + new_entity.transform.translation.x = new_x as _; + updates.push(new_entity); + } + }); }); - }); + } }); }); }); @@ -51,4 +53,5 @@ pub fn gui(gui_state: GuiState) { pub struct GuiState { pub texture_id: TextureId, pub scene: hotham_editor_protocol::scene::Scene, + pub updates: Vec, } diff --git a/hotham-editor/src/main.rs b/hotham-editor/src/main.rs index e09d43fa..2732c5fb 100644 --- a/hotham-editor/src/main.rs +++ b/hotham-editor/src/main.rs @@ -132,6 +132,9 @@ pub fn main() -> Result<()> { let mut winit_initializing = true; let mut focused = false; let mut right_mouse_clicked = false; + let mut updates = EditorUpdates { + entity_updates: vec![], + }; event_loop.run_return(|event, _, control_flow| { *control_flow = ControlFlow::Poll; @@ -183,25 +186,13 @@ pub fn main() -> Result<()> { openxr_server.send_response(&framebuffer_index).unwrap(); let scene: hotham_editor_protocol::scene::Scene = game_server.get_json().unwrap(); - - let entity_updates = if right_mouse_clicked { - let mut helmet = scene - .entities - .iter() - .find(|e| e.name.as_str() == "Damaged Helmet") - .unwrap() - .clone(); - helmet.transform.translation.y += 0.1; - debug!("Sending update: {helmet:?}"); - vec![helmet] - } else { - vec![] - }; - + let mut gui_state = GuiState { texture_id: yak_images[framebuffer_index as usize], scene, updates: vec![] }; game_server - .send_json(&EditorUpdates { entity_updates }) + .send_json(&updates) .unwrap(); + updates.entity_updates.clear(); + // game has finished rendering its frame here check_request(&mut openxr_server, RequestType::LocateView).unwrap(); @@ -210,15 +201,14 @@ pub fn main() -> Result<()> { check_request(&mut openxr_server, RequestType::EndFrame).unwrap(); openxr_server.send_response(&0).unwrap(); - let gui_state = GuiState { - texture_id: yak_images[framebuffer_index as usize], - scene, - }; yak.start(); - gui(gui_state); + gui(&mut gui_state); yak.finish(); + updates.entity_updates = gui_state.updates; + + let context = lazy_vulkan.context(); let yakui_vulkan_context = yakui_vulkan::VulkanContext::new( &context.device, diff --git a/hotham-openxr-client/test.ps1 b/hotham-openxr-client/test.ps1 index aea1e4dc..9fdfad98 100644 --- a/hotham-openxr-client/test.ps1 +++ b/hotham-openxr-client/test.ps1 @@ -1,6 +1,11 @@ Write-Output "All your OpenXR are belong to crab" ${Env:RUST_BACKTRACE} = 1 +Write-Output "Starting editor.." Start-Job -ScriptBlock { cargo run --bin hotham-editor } -Start-Sleep -Seconds 1 + +Write-Output "Sleeping.." +Start-Sleep -Seconds 5 + +Write-Output "Starting game.." cargo run --release --bin hotham_simple_scene_example --features editor From 854282f7d5eedf8be30a7dd17f17ee4f64bfbf42 Mon Sep 17 00:00:00 2001 From: Kane Rogers Date: Wed, 1 Mar 2023 18:08:11 +1100 Subject: [PATCH 11/14] Add missing SPRIV files --- hotham-editor/src/shaders/triangle.frag.spv | Bin 0 -> 1760 bytes hotham-editor/src/shaders/triangle.vert.spv | Bin 0 -> 1100 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 hotham-editor/src/shaders/triangle.frag.spv create mode 100644 hotham-editor/src/shaders/triangle.vert.spv diff --git a/hotham-editor/src/shaders/triangle.frag.spv b/hotham-editor/src/shaders/triangle.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..d0241f6d2d73d5a84a60cd8567941a2648546f8b GIT binary patch literal 1760 zcmZ9L*=`d-5JfxjLP*F)HUc5oggs!kBm_c20)*kgQiOQmab(#8j4&Q+76(3nS3ZQ# z;HOw51ozBLhf&%pRn_g=-F2(RrQwwm#tfNJGj3j(Y)zYCA;yfEYU-Wl>t-X0JB_vV zJ1{0pDJ8<0G$W=gk9}? z7IW&k$fg$if{`G(2&#A1HqGG@qJx5c4<6Eg5`;-Gsv$C&L8y)q-u`Yk?t zr)6NgP0T&icUH!%c65fBlK+u1c<^lv=M_@}bF?+^ZG+nyYKp;_Ej8Hx1~sr(mpzhk zH?h5YRq=6sH-&-2VLlBRGXv*+=6hQPPR?xZ%b3d>ok8203=a4!Va#+x2F^k=y$7@X zc%Ybmki*}U!TFls`A{(&aN9GmU($qLw4u>z+g5yB+m0}BtL=$mv}JgvZC5$C)wZX& ssO_opT^Tra@Xdf(9nTb79rWV449=Ha$G&1X;8q7%Q3rbX7B*%70BO2`dH?_b literal 0 HcmV?d00001 diff --git a/hotham-editor/src/shaders/triangle.vert.spv b/hotham-editor/src/shaders/triangle.vert.spv new file mode 100644 index 0000000000000000000000000000000000000000..8f067adcd6b28941e1878df5dd042f40066018c4 GIT binary patch literal 1100 zcmYk4TT8=05QW#avEJ+bu2g&!#0M2Y6htLH6ch@+21$b~#H6%IQGcGl$``@&C2q}@ z>FhakW@mOb_1a<6mHQBmsTed5!>sOQiQes0D{Z8ONKKh;eAnf;pVfGP6L2Q#K=-ELOW_A>z zo2uw+62^J2D)6MJ*7nph9lgfEQ#5#v23hnjD7NI;6HA9SvuPr>S?IY-!P_M3+D}y` zr;O3FVz;sF-`QdIl7!JX#w3X4aXgNBx+gPWdHOL6n*@DV6OP%Wc@~6eoaTd4$E>7t zlvIskcIfgEnnp24X@KTIOgVkts$7mM-;bC5*KrSY^z3x(;h3SX2zzwC82WbK1el(y zn0~?3AkI5iF>em$ycPZ94^)Gi?ANuMlyA#6l&=r5!1S^vJSE?jv5x-mwD#276`qkN zA8ubedUt0K!`jl#$-8@A{$bt+jyem4t~{4I;FgR<{-QiJIh%Yivw Date: Wed, 1 Mar 2023 21:20:45 +1100 Subject: [PATCH 12/14] Appease clippy --- examples/simple-scene/src/lib.rs | 2 +- hotham-editor-protocol/src/lib.rs | 47 ++++++++++++------------ hotham-editor/Cargo.toml | 7 ++-- hotham-editor/src/gui.rs | 3 +- hotham-editor/src/input_context.rs | 7 ++-- hotham-editor/src/main.rs | 4 +- hotham-openxr-client/src/action_state.rs | 12 +++--- hotham-openxr-client/src/client.rs | 16 ++++---- hotham-openxr-client/src/lib.rs | 7 ++-- hotham/src/contexts/vulkan_context.rs | 6 +-- 10 files changed, 54 insertions(+), 57 deletions(-) diff --git a/examples/simple-scene/src/lib.rs b/examples/simple-scene/src/lib.rs index d928b3cc..dddbf18e 100644 --- a/examples/simple-scene/src/lib.rs +++ b/examples/simple-scene/src/lib.rs @@ -29,7 +29,7 @@ pub fn main() { } pub fn real_main() -> HothamResult<()> { - let _ = env_logger::builder() + env_logger::builder() .filter_module("hotham-openxr-client", log::LevelFilter::Trace) .filter_module("simple_scene_example", log::LevelFilter::Trace) .init(); diff --git a/hotham-editor-protocol/src/lib.rs b/hotham-editor-protocol/src/lib.rs index 6de682be..46c86b95 100644 --- a/hotham-editor-protocol/src/lib.rs +++ b/hotham-editor-protocol/src/lib.rs @@ -4,7 +4,7 @@ pub use openxr_sys::ViewConfigurationView; use serde::{de::DeserializeOwned, Serialize}; #[repr(u32)] -#[derive(Debug, Clone, Copy, PartialEq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum RequestType { GetViewConfiguration, GetViewCount, @@ -65,7 +65,7 @@ pub mod requests { use ash::vk; #[repr(C)] - #[derive(Debug, Clone, Copy, PartialEq)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct GetViewConfiguration {} impl Request for GetViewConfiguration { @@ -76,7 +76,7 @@ pub mod requests { } #[repr(C)] - #[derive(Debug, Clone, Copy, PartialEq)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct GetViewCount {} impl Request for GetViewCount { @@ -87,7 +87,7 @@ pub mod requests { } #[repr(C)] - #[derive(Debug, Clone, Copy, PartialEq)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct GetSwapchainInfo {} impl Request for GetSwapchainInfo { @@ -98,7 +98,7 @@ pub mod requests { } #[repr(C)] - #[derive(Debug, Clone, Copy, PartialEq)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct WaitFrame; impl Request for WaitFrame { @@ -109,7 +109,7 @@ pub mod requests { } #[repr(C)] - #[derive(Debug, Clone, Copy, PartialEq)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct AcquireSwapchainImage; impl Request for AcquireSwapchainImage { @@ -120,7 +120,7 @@ pub mod requests { } #[repr(C)] - #[derive(Debug, Clone, Copy, PartialEq)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct EndFrame; impl Request for EndFrame { @@ -131,7 +131,7 @@ pub mod requests { } #[repr(C)] - #[derive(Debug, Clone, Copy, PartialEq)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct LocateView; impl Request for LocateView { @@ -142,7 +142,7 @@ pub mod requests { } #[repr(C)] - #[derive(Debug, Clone, Copy, PartialEq)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct GetSwapchainImages {} impl Request for GetSwapchainImages { @@ -157,7 +157,7 @@ pub mod requests { } #[repr(C)] - #[derive(Debug, Clone, Copy, PartialEq)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct GetSwapchainSemaphores {} impl Request for GetSwapchainSemaphores { @@ -172,7 +172,7 @@ pub mod requests { } #[repr(C)] - #[derive(Debug, Clone, Copy, PartialEq)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct GetInputEvents; impl Request for GetInputEvents { @@ -187,7 +187,7 @@ pub mod requests { } #[repr(C)] - #[derive(Debug, Clone, Copy, PartialEq)] + #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct PutEntities; impl Request for PutEntities { @@ -200,7 +200,6 @@ pub mod requests { pub mod responses { use ash::vk; - use serde::{Deserialize, Serialize}; #[repr(C)] #[derive(Debug, Clone, Copy)] @@ -254,19 +253,16 @@ fn write_request(request: &R, writer: &mut S) -> std::io:: Ok(()) } -fn read_request_header<'a, S: Read>( - reader: &mut S, - buf: &'a mut [u8], -) -> std::io::Result { +fn read_request_header(reader: &mut S, buf: &mut [u8]) -> std::io::Result { reader.read_exact(&mut buf[..std::mem::size_of::()])?; let header: RequestHeader = - unsafe { t_from_bytes(&mut buf[..std::mem::size_of::()]) }; + unsafe { t_from_bytes(&buf[..std::mem::size_of::()]) }; Ok(header) } -fn read_request_payload<'a, R: Request + Clone, S: Read>( +fn read_request_payload( reader: &mut S, - buf: &'a mut [u8], + buf: &mut [u8], payload_length: usize, ) -> std::io::Result { reader.read_exact(&mut buf[..payload_length])?; @@ -417,16 +413,17 @@ impl EditorServer { pub fn send_response(&mut self, response: &T) -> std::io::Result<()> { let message_size = std::mem::size_of::() as u32; - self.socket.write(&message_size.to_be_bytes())?; - self.socket.write(&unsafe { bytes_from_t(response) })?; + self.socket.write_all(&message_size.to_be_bytes())?; + self.socket.write_all(&unsafe { bytes_from_t(response) })?; Ok(()) } pub fn send_response_vec(&mut self, response: &Vec) -> std::io::Result<()> { let message_size = (std::mem::size_of::() * response.len()) as u32; - self.socket.write(&message_size.to_be_bytes())?; - self.socket.write(&unsafe { bytes_from_vec(response) })?; + self.socket.write_all(&message_size.to_be_bytes())?; + self.socket + .write_all(&unsafe { bytes_from_vec(response) })?; Ok(()) } @@ -441,6 +438,8 @@ unsafe fn vec_from_bytes(data: &[u8]) -> Vec { std::slice::from_raw_parts(data.as_ptr().cast(), len).to_vec() } +// Clippy knows not what evil we do +#[allow(clippy::redundant_clone)] unsafe fn t_from_bytes(data: &[u8]) -> T { std::ptr::read(data.as_ptr().cast::()).clone() } diff --git a/hotham-editor/Cargo.toml b/hotham-editor/Cargo.toml index 7cd2dbee..5a5ed345 100644 --- a/hotham-editor/Cargo.toml +++ b/hotham-editor/Cargo.toml @@ -13,10 +13,9 @@ lazy_vulkan = {git = "https://github.com/leetvr/lazy_vulkan", rev = "4e8b4982328 log = "0.4.17" openxr-sys = "0.9.3" winit = "0.28.1" -# yakui = {git = "https://github.com/LPGhatguy/yakui", rev = "0b7d6f7ded117525f0986476c3f34a03fd9391a8"}# main @ 20/02/2023 -yakui = {path = "../../yakui/crates/yakui"} -yakui-vulkan = {path = "../../yakui/crates/yakui-vulkan"} -yakui-winit = {path = "../../yakui/crates/yakui-winit"} +yakui = {git = "https://github.com/leetvr/yakui", rev = "a562328"}#add-texture-from-image branch @ 1/3/23 +yakui-vulkan = {git = "https://github.com/leetvr/yakui", rev = "a562328"}#add-texture-from-image branch @ 1/3/23 +yakui-winit = {git = "https://github.com/leetvr/yakui", rev = "a562328"}#add-texture-from-image branch @ 1/3/23 [target.'cfg(windows)'.dependencies] uds_windows = "1.0.2" diff --git a/hotham-editor/src/gui.rs b/hotham-editor/src/gui.rs index a5660c4a..8e555c98 100644 --- a/hotham-editor/src/gui.rs +++ b/hotham-editor/src/gui.rs @@ -2,13 +2,12 @@ use glam::Vec2; use hotham_editor_protocol::scene::EditorEntity; use yakui::widgets::{List, Pad}; use yakui::{ - column, image, label, pad, row, slider, text, textbox, use_state, CrossAxisAlignment, TextureId, + column, image, label, pad, row, slider, text, use_state, CrossAxisAlignment, TextureId, }; pub fn gui(gui_state: &mut GuiState) { let scene = &gui_state.scene; let updates = &mut gui_state.updates; - let mut main_row = List::row(); row(|| { column(|| { image(gui_state.texture_id, Vec2::new(500., 500.)); diff --git a/hotham-editor/src/input_context.rs b/hotham-editor/src/input_context.rs index dad0a999..d13399b9 100644 --- a/hotham-editor/src/input_context.rs +++ b/hotham-editor/src/input_context.rs @@ -61,9 +61,10 @@ impl InputContext { } // If there were no keyboard inputs, but we're still holding down a key, act as if that key was pressed - match (&self.keyboard_state, keyboard_input.is_empty()) { - (KeyboardState::HoldingKey(key), true) => keyboard_motion = handle_keypress(*key, pose), - _ => {} + if let (KeyboardState::HoldingKey(key), true) = + (&self.keyboard_state, keyboard_input.is_empty()) + { + keyboard_motion = handle_keypress(*key, pose); } pose.position += keyboard_motion * movement_speed; diff --git a/hotham-editor/src/main.rs b/hotham-editor/src/main.rs index 2732c5fb..d154a20e 100644 --- a/hotham-editor/src/main.rs +++ b/hotham-editor/src/main.rs @@ -121,7 +121,7 @@ pub fn main() -> Result<()> { .drain(..) .map(|t| { let yak_texture = lazy_to_yak(&yakui_vulkan_context, yakui_vulkan.descriptors(), t); - yakui_vulkan.add_user_texture(&yakui_vulkan_context, yak_texture) + yakui_vulkan.add_user_texture(yak_texture) }) .collect::>(); @@ -350,7 +350,7 @@ fn do_openxr_setup( swapchain_info: &SwapchainInfo, ) -> Result { let (images, image_memory_handles) = - unsafe { create_render_images(vulkan_context, &swapchain_info) }; + unsafe { create_render_images(vulkan_context, swapchain_info) }; let (semaphores, semaphore_handles) = unsafe { create_semaphores(vulkan_context, swapchain_info.image_count) }; diff --git a/hotham-openxr-client/src/action_state.rs b/hotham-openxr-client/src/action_state.rs index dec005cd..573043ff 100644 --- a/hotham-openxr-client/src/action_state.rs +++ b/hotham-openxr-client/src/action_state.rs @@ -7,7 +7,7 @@ use openxr_sys::{Action, Path, FALSE}; // A bit yuck to use u64 instead of Action, but it doesn't support Hash.. but whatever. pub struct ActionState { boolean_actions: HashMap, - bindings: HashMap, + _bindings: HashMap, } impl ActionState { pub(crate) fn get_boolean(&self, action: Action) -> openxr_sys::Bool32 { @@ -17,18 +17,18 @@ impl ActionState { .unwrap_or(FALSE) } - pub(crate) fn add_binding(&mut self, path: Path, action: Action) { - self.bindings.insert(path, action.into_raw()); + pub(crate) fn _add_binding(&mut self, path: Path, action: Action) { + self._bindings.insert(path, action.into_raw()); } /// Resets all action state. - pub(crate) fn clear(&mut self) { + pub(crate) fn _clear(&mut self) { // Set all the booleans to false. self.boolean_actions.values_mut().for_each(|v| *v = false); } - pub(crate) fn set_boolean(&mut self, path: &Path, value: bool) { - let action = self.bindings.get(path).unwrap(); + pub(crate) fn _set_boolean(&mut self, path: &Path, value: bool) { + let action = self._bindings.get(path).unwrap(); self.boolean_actions.insert(*action, value); } } diff --git a/hotham-openxr-client/src/client.rs b/hotham-openxr-client/src/client.rs index 7ed7dfde..d4adb9e5 100644 --- a/hotham-openxr-client/src/client.rs +++ b/hotham-openxr-client/src/client.rs @@ -165,7 +165,7 @@ pub unsafe extern "system" fn create_vulkan_instance( trace!("Application requested extension names: {extension_names:?}"); let (entry, instance) = lazy_vulkan::vulkan_context::init(&mut extension_names.to_vec()); - *vulkan_instance = transmute(instance.handle().as_raw()); + *vulkan_instance = instance.handle().as_raw() as *const _; let _ = PARTIAL_VULKAN.set((entry, instance)); @@ -182,7 +182,7 @@ pub unsafe extern "system" fn get_vulkan_graphics_device_2( let (_, instance) = PARTIAL_VULKAN.get().unwrap(); let physical_device = lazy_vulkan::vulkan_context::get_physical_device(instance, None, None).0; trace!("Physical device: {physical_device:?}"); - *vulkan_physical_device = transmute(physical_device.as_raw()); + *vulkan_physical_device = physical_device.as_raw() as *const _; Result::SUCCESS } @@ -196,7 +196,7 @@ pub unsafe extern "system" fn create_vulkan_device( let (_, instance) = PARTIAL_VULKAN.get().unwrap(); let create_info = &*create_info; let physical_device: vk::PhysicalDevice = - vk::PhysicalDevice::from_raw(transmute(create_info.vulkan_physical_device)); + vk::PhysicalDevice::from_raw(create_info.vulkan_physical_device as u64); let device_create_info: &mut vk::DeviceCreateInfo = &mut *create_info.vulkan_create_info.cast_mut().cast(); // evil? probably let mut extension_names = std::slice::from_raw_parts( @@ -226,7 +226,7 @@ pub unsafe extern "system" fn create_vulkan_device( .create_device(physical_device, device_create_info, None) .unwrap(); - *vulkan_device = transmute(device.handle().as_raw()); + *vulkan_device = device.handle().as_raw() as *const _; *vulkan_result = vk::Result::SUCCESS.as_raw(); Result::SUCCESS @@ -287,7 +287,7 @@ pub unsafe extern "system" fn create_session( let _ = SESSION.set(*session); // TODO: I'm not sure if it should be an error to create a new session again let graphics_binding = &*((*create_info).next as *const GraphicsBindingVulkanKHR); let (entry, instance) = PARTIAL_VULKAN.take().unwrap(); - let physical_device = vk::PhysicalDevice::from_raw(transmute(graphics_binding.physical_device)); + let physical_device = vk::PhysicalDevice::from_raw(graphics_binding.physical_device as u64); let device = ash::Device::load(instance.fp_v1_0(), transmute(graphics_binding.device)); let queue_family_index = graphics_binding.queue_family_index; @@ -359,11 +359,11 @@ pub unsafe extern "system" fn string_to_path( STRING_TO_PATH .get_mut() .unwrap() - .insert(path_string.to_string(), path.clone()); + .insert(path_string.to_string(), path); PATH_TO_STRING .get_mut() .unwrap() - .insert(path.clone(), path_string.to_string()); + .insert(path, path_string.to_string()); *path_out = path; Result::SUCCESS } @@ -496,7 +496,7 @@ pub unsafe extern "system" fn poll_event( return Result::SUCCESS; } - return Result::EVENT_UNAVAILABLE; + Result::EVENT_UNAVAILABLE } pub unsafe extern "system" fn begin_session( diff --git a/hotham-openxr-client/src/lib.rs b/hotham-openxr-client/src/lib.rs index 0f82cf4b..ca861f99 100644 --- a/hotham-openxr-client/src/lib.rs +++ b/hotham-openxr-client/src/lib.rs @@ -1,13 +1,12 @@ -mod client; +#![allow(clippy::missing_safety_doc)] mod action_state; +mod client; mod space_state; -use std::ffi::c_char; - use crate::client::*; - use openxr_sys::{loader, pfn, Instance, Result}; +use std::ffi::c_char; type DummyFn = unsafe extern "system" fn() -> Result; diff --git a/hotham/src/contexts/vulkan_context.rs b/hotham/src/contexts/vulkan_context.rs index 7f1be616..6524e988 100644 --- a/hotham/src/contexts/vulkan_context.rs +++ b/hotham/src/contexts/vulkan_context.rs @@ -75,11 +75,11 @@ impl VulkanContext { .engine_version(1) .build(); - #[allow(unused_mut)] - let mut instance_extensions = vec![]; + #[cfg(not(debug_assertions))] + let instance_extensions = vec![]; #[cfg(debug_assertions)] - instance_extensions.push(vk::ExtDebugUtilsFn::name().as_ptr()); + let instance_extensions = vec![vk::ExtDebugUtilsFn::name().as_ptr()]; let create_info = vk::InstanceCreateInfo::builder() .application_info(&app_info) From 16601dd1157c9ee419daef913332aaf61eff87e4 Mon Sep 17 00:00:00 2001 From: Kane Rogers Date: Wed, 1 Mar 2023 21:46:31 +1100 Subject: [PATCH 13/14] Specifically allow the editor's SPIRV files --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 7b6a0165..f8a4ca7e 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,9 @@ sponza* # Ignore compiled shaders *.spv +# ..except for the editor's, which are special and magic +!hotham-editor/src/shaders/*.spv + # Checking in a socket is probably a terrible idea *.socket From 893f1a2276ed6d8352247aaa0c8301a508cebf9d Mon Sep 17 00:00:00 2001 From: Kane Rogers Date: Wed, 1 Mar 2023 21:52:55 +1100 Subject: [PATCH 14/14] Accept the tyrany of The Republic --- hotham-editor/src/shaders/triangle.vert | 6 +++--- hotham/src/rendering/material.rs | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hotham-editor/src/shaders/triangle.vert b/hotham-editor/src/shaders/triangle.vert index c2f2858e..77381b16 100644 --- a/hotham-editor/src/shaders/triangle.vert +++ b/hotham-editor/src/shaders/triangle.vert @@ -1,14 +1,14 @@ #version 450 layout(location = 0) in vec4 in_position; -layout(location = 1) in vec4 in_colour; +layout(location = 1) in vec4 in_color; layout(location = 2) in vec2 in_uv; -layout(location = 0) out vec4 out_colour; +layout(location = 0) out vec4 out_color; layout(location = 1) out vec2 out_uv; void main() { gl_Position = in_position; - out_colour = in_colour; + out_color = in_color; out_uv = in_uv; } \ No newline at end of file diff --git a/hotham/src/rendering/material.rs b/hotham/src/rendering/material.rs index d435b6c0..1b267aef 100644 --- a/hotham/src/rendering/material.rs +++ b/hotham/src/rendering/material.rs @@ -162,7 +162,7 @@ impl Material { } } - /// Create a simple, unlit, white coloured material. + /// Create a simple, unlit, white colored material. pub fn unlit_white() -> Material { Material { packed_flags_and_base_texture_id: MaterialFlags::UNLIT_WORKFLOW.bits,