├── .gitattributes ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── RepoAssets └── banner.webp ├── assets ├── ambient_dome.obj ├── ambient_dome_old.obj ├── blank_grey.png └── icon.ico ├── build.rs ├── libs └── arm64-v8a │ └── libopenxr_loader.so └── src ├── blit.wgsl ├── config.rs ├── conversions.rs ├── engine.rs ├── engine ├── camera.rs ├── entity.rs ├── geometry.rs ├── input.rs ├── jitter.rs ├── screen.rs ├── texture.rs └── vr.rs ├── lib.rs ├── loaders.rs ├── loaders └── katanga_loader.rs ├── main.rs └── shader.wgsl /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /keys -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Debug", 5 | "type": "cppvsdbg", 6 | "request": "launch", 7 | "program": "${workspaceRoot}/target/debug/vr-screen-cap.exe", 8 | "cwd": "${workspaceRoot}/target/debug", 9 | "preLaunchTask": "build", 10 | "args": ["--config-file", "E:/ArtumRepos/Rust/VRScreenCap/target/release/config.json"], 11 | "environment": [ 12 | { 13 | "name": "RUST_BACKTRACE", 14 | "value": "1" 15 | }, 16 | { 17 | "name": "RUST_LOG", 18 | "value": "trace" 19 | } 20 | ] 21 | }, 22 | { 23 | "name": "Release", 24 | "type": "cppvsdbg", 25 | "request": "launch", 26 | "program": "${workspaceRoot}/target/release/vr-screen-cap.exe", 27 | "cwd": "${workspaceRoot}/target/release", 28 | "preLaunchTask": "build-release", 29 | "args": ["--config-file", "E:/ArtumRepos/Rust/VRScreenCap/target/release/config.json"], 30 | "environment": [ 31 | { 32 | "name": "RUST_BACKTRACE", 33 | "value": "1" 34 | }, 35 | { 36 | "name": "RUST_LOG", 37 | "value": "trace" 38 | } 39 | ] 40 | }, 41 | ], 42 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "cargo", 7 | "type": "shell", 8 | "args": ["build", "--no-default-features"], 9 | "problemMatcher": [ 10 | "$tsc" 11 | ], 12 | "presentation": { 13 | "reveal": "always" 14 | }, 15 | "group": "build" 16 | }, 17 | { 18 | "label": "build-release", 19 | "command": "cargo", 20 | "type": "shell", 21 | "args": ["build", "--release", "--no-default-features"], 22 | "problemMatcher": [ 23 | "$tsc" 24 | ], 25 | "presentation": { 26 | "reveal": "always" 27 | }, 28 | "group": "build" 29 | } 30 | ] 31 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vr-screen-cap" 3 | version = "0.4.0" 4 | edition = "2021" 5 | build = "build.rs" 6 | authors = ["Jacopo Libe "] 7 | description = "A very simple rust program that uses the Vulkan backend of WPGU combined with OpenXR to show a virtual screen for geo11 games." 8 | license = "MIT" 9 | repository = "https://github.com/artumino/VRScreenCap" 10 | 11 | [lib] 12 | name = "vr_screen_cap_core" 13 | crate-type = ["lib", "cdylib"] 14 | 15 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 16 | 17 | [dependencies] 18 | wgpu = { git = "https://github.com/artumino/wgpu.git", branch = "feature/multiview_relaxed_validation" } 19 | wgpu-hal = { git = "https://github.com/artumino/wgpu.git", branch = "feature/multiview_relaxed_validation", features = ["vulkan"] } 20 | ash = "0.37.2" 21 | mint = "0.5.9" 22 | cgmath = { version = "0.18.0", features=["mint"] } 23 | bytemuck = { version = "1.13.1", features = [ "derive" ] } 24 | log4rs = "1.2.0" 25 | log = "0.4.17" 26 | log-panics = { version = "2.1.0", features=["with-backtrace"] } 27 | clap = { version = "4.2.2", features=["derive"] } 28 | thread-priority = "0.13.1" 29 | serde = "1.0.160" 30 | serde_json = "1.0.96" 31 | notify = "5.1.0" 32 | image = { version = "0.24", default-features = false, features = ["png", "jpeg"] } 33 | dhat = { version = "0.3.2", optional = true } 34 | renderdoc = { version = "0.11.0", optional = true } 35 | profiling = { version = "1.0.8", optional = true, features = ["profile-with-tracy"] } 36 | tracy-client = { version = "0.15.2", optional = true } 37 | anyhow = "1.0.70" 38 | tobj = "3.2.5" 39 | 40 | # PLATFORM DEPENDENT 41 | [target.'cfg(not(target_os = "android"))'.dependencies] 42 | tray-item = "0.7.1" 43 | windows = { version = "0.48.0", features = [ 44 | "Win32_System_Memory", 45 | "Win32_Security", 46 | "Win32_Foundation", 47 | "Win32_Graphics_Direct3D11", 48 | "Win32_Graphics_Direct3D12", 49 | "Win32_Graphics_Dxgi_Common", 50 | "Win32_Graphics_Direct3D", 51 | "Win32_Graphics_Dxgi", 52 | "Win32_System_Threading" 53 | ]} 54 | openxr = { version = "0.17.1", features = [ "static", "mint" ] } 55 | 56 | [target.'cfg(target_os = "android")'.dependencies] 57 | openxr = { version = "0.17.1", features = [ "loaded", "mint" ] } 58 | jni = "0.21.1" 59 | ndk = "0.7" 60 | ndk-glue = "0.7" 61 | 62 | # BUILD 63 | 64 | [target.'cfg(windows)'.build-dependencies] 65 | winres = "0.1" 66 | 67 | [features] 68 | default = ["renderdoc"] 69 | dhat-heap = ["dep:dhat"] 70 | renderdoc = ["dep:renderdoc"] 71 | profiling = ["dep:profiling", "dep:tracy-client"] 72 | 73 | # ANDROID 74 | [package.metadata.android] 75 | apk_label = "VR Screen Cap" 76 | fullscreen = true 77 | runtime_libs = "libs" 78 | target_sdk_version = 21 79 | 80 | [[package.metadata.android.uses_permission]] 81 | name = "android.permission.INTERNET" 82 | 83 | [[package.metadata.android.uses_permission]] 84 | name = "android.permission.ACCESS_NETWORK_STATE" 85 | 86 | [[package.metadata.android.uses_permission]] 87 | name = "android.permission.WRITE_EXTERNAL_STORAGE" 88 | 89 | [package.metadata.android.application] 90 | debuggable = true 91 | label = "VR Screen Cap" 92 | theme = "@android:style/Theme.DeviceDefault.NoActionBar.Fullscreen" 93 | 94 | [package.metadata.android.application.activity] 95 | config_changes = "screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode" 96 | launch_mode = "singleTask" 97 | orientation = "landscape" 98 | 99 | [[package.metadata.android.application.meta_data]] 100 | name = "com.oculus.supportedDevices" 101 | value = "quest|quest2" 102 | 103 | [[package.metadata.android.application.meta_data]] 104 | name = "com.oculus.intent.category.VR" 105 | value = "vr_only" 106 | 107 | [[package.metadata.android.application.activity.intent_filter]] 108 | actions = ["android.intent.action.MAIN"] 109 | categories = ["com.oculus.intent.category.VR", "android.intent.category.LAUNCHER"] 110 | 111 | [[package.metadata.android.application.activity.meta_data]] 112 | name = "com.oculus.vr.focusaware" 113 | value = "true" 114 | 115 | [[package.metadata.android.uses_feature]] 116 | name = "android.hardware.vulkan.level" 117 | required = true 118 | version = 0x00401000 119 | 120 | [[package.metadata.android.uses_feature]] 121 | name = "android.hardware.vr.headtracking" 122 | required = true 123 | version = 1 124 | 125 | [package.metadata.android.signing.dev] 126 | path = "keys/artum-dev.keystore" 127 | keystore_password = "test1234" 128 | 129 | [package.metadata.android.signing.release] 130 | path = "keys/artum-dev.keystore" 131 | keystore_password = "test1234" 132 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 artum 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ![VRScreenCap](RepoAssets/banner.webp) 2 | 3 | VRScreenCap is a very simple rust program that uses the Vulkan backend of WPGU combined with OpenXR to show a virtual screen. 4 | 5 | ## Disclaimer 6 | This was a pet project done in a few days of work, it doesn't offer any customization options and QoL features since It was meant to do just one thing. 7 | The application was also only tested on a limited set of geo-11 games, with an nvidia card, so different configurations could have issues. 8 | One of the underlying components of the application (WGPU) is still in development phase and could have issues on its own. 9 | 10 | ## Supported Inputs 11 | Right now it only supports full side-by-side 3D feeds coming from [geo-11](https://helixmod.blogspot.com/2022/06/announcing-new-geo-11-3d-driver.html)'s 12 | DX11 games. 13 | 14 | ## How to Use 15 | - Setup geo-11's files for the game 16 | - In `d3dxdm.ini` set `direct_mode` to `katanga_vr`, then adjust `dm_separation` to your likings (usually 20-30 is the range for VR viewing) 17 | - Run the game 18 | - Start VRScreenCap 19 | 20 | The feed should then be visible on a curved screen. In case the video feed freezes, restart VRScreenCap. 21 | Some VR runtimes don't seem to allow for screen recentering, a future update will probably take care of this in-app. 22 | 23 | ### Recentering 24 | You can recenter the viewer at any point in time by holding one or two hand controller near the HMD for more than 3 seconds. 25 | Doing it with one hand will keep the screen locked to the horizon, while using two hands will allow you to also change the screen's pitch. 26 | Recentering can also be triggered by selecting the proper options from the menu in the icon tray. 27 | 28 | **ATTENTION**: VRScreenCap doesn't open any window on the desktop, it only appears as a tray icon (and in your VR runtime's dashboard). 29 | 30 | 31 | ## Launch Parameters 32 | 33 | VR-Screen-Cap offers a few configuration launch parameters (all of them are optional): 34 | ``` 35 | vr-screen-cap.exe [OPTIONS] 36 | OPTIONS: 37 | --x-curvature=0.4 38 | --y-curvature=0.08 39 | --swap-eyes=true 40 | --flip-x=false 41 | --flip-y=false 42 | --distance=20.0 43 | --scale=10.0 44 | --ambient=false 45 | --config-file= 46 | ``` 47 | Where every distance is in meters. The effects of horizontal and vertical curvature are summed together, with a curvature of 1.0 the center of the screen will be bent inwards of about half its size. 48 | 49 | A json configuration file can be provided and it will be watched for changes, the structure of the json config is similar to the launch parameters: 50 | ```json 51 | { 52 | "distance" : 20.0, 53 | "scale": 10.0, 54 | "x_curvature": 0.4, 55 | "y_curvature": 0.08 56 | } 57 | ``` 58 | 59 | ## Build Info 60 | 61 | ### Windows 62 | The release version published here were compiled with the following configuration: 63 | ``` 64 | cargo build --release --no-default-features 65 | ``` 66 | 67 | ### Android Targets 68 | Standalone is still in experimental phase: 69 | 1. Install *cargo-apk* `cargo install cargo-apk` 70 | 2. Add android targets to your rust installation `rustup target add aarch64-linux-android` 71 | 3. Build through `cargo apk build --no-default-features` 72 | 73 | ## WMR Users Disclaimer 74 | 75 | This application uses Vulkan as its backend. To the best of my knowledge WMR still doesn't support OpenXR Vulkan applications so this one won't work out of the box for you. You can try to run VR Screen Cap through projects like [OpenXR-Vk-D3D12](https://github.com/mbucchia/OpenXR-Vk-D3D12) but I can't guarantee It'll work. 76 | -------------------------------------------------------------------------------- /RepoAssets/banner.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artumino/VRScreenCap/a2866bd353f237b9f65969a879f78366b72c6a57/RepoAssets/banner.webp -------------------------------------------------------------------------------- /assets/ambient_dome.obj: -------------------------------------------------------------------------------- 1 | # Blender 3.1.0 2 | # www.blender.org 3 | o Sphere 4 | v -0.707107 0.707107 0.000000 5 | v -0.555570 0.831470 0.000000 6 | v -0.544895 0.831470 -0.108386 7 | v -0.693520 0.707107 -0.137949 8 | v -0.707107 -0.707107 -0.000000 9 | v -0.831469 -0.555570 -0.000000 10 | v -0.815493 -0.555570 -0.162211 11 | v -0.693520 -0.707107 -0.137949 12 | v -0.831469 0.555570 0.000000 13 | v -0.815493 0.555570 -0.162211 14 | v -0.555570 -0.831470 -0.000000 15 | v -0.544895 -0.831470 -0.108386 16 | v -0.923879 0.382683 0.000000 17 | v -0.906127 0.382683 -0.180240 18 | v -0.382684 -0.923880 -0.000000 19 | v -0.375330 -0.923880 -0.074658 20 | v -0.980785 0.195090 0.000000 21 | v -0.961940 0.195090 -0.191341 22 | v -0.195090 -0.980785 -0.000000 23 | v -0.191342 -0.980785 -0.038060 24 | v -1.000000 0.000000 0.000000 25 | v -0.980785 0.000000 -0.195090 26 | v -0.195090 0.980785 0.000000 27 | v 0.000000 1.000000 0.000000 28 | v -0.191342 0.980785 -0.038060 29 | v 0.000000 -1.000000 -0.000000 30 | v -0.980785 -0.195090 -0.000000 31 | v -0.961940 -0.195090 -0.191341 32 | v -0.382684 0.923880 0.000000 33 | v -0.375330 0.923880 -0.074658 34 | v -0.923880 -0.382683 -0.000000 35 | v -0.906128 -0.382683 -0.180240 36 | v -0.906127 0.195090 -0.375330 37 | v -0.923879 0.000000 -0.382683 38 | v -0.180240 0.980785 -0.074658 39 | v -0.180240 -0.980785 -0.074658 40 | v -0.906127 -0.195090 -0.375330 41 | v -0.353554 0.923880 -0.146447 42 | v -0.853553 -0.382683 -0.353553 43 | v -0.513280 0.831470 -0.212607 44 | v -0.768178 -0.555570 -0.318189 45 | v -0.653281 0.707107 -0.270598 46 | v -0.653281 -0.707107 -0.270598 47 | v -0.768178 0.555570 -0.318189 48 | v -0.513280 -0.831470 -0.212607 49 | v -0.853553 0.382683 -0.353553 50 | v -0.353554 -0.923880 -0.146447 51 | v -0.691342 -0.555570 -0.461940 52 | v -0.587938 -0.707107 -0.392847 53 | v -0.587938 0.707107 -0.392847 54 | v -0.691342 0.555570 -0.461939 55 | v -0.461940 -0.831470 -0.308658 56 | v -0.768178 0.382683 -0.513280 57 | v -0.318190 -0.923880 -0.212608 58 | v -0.815493 0.195090 -0.544895 59 | v -0.162212 -0.980785 -0.108386 60 | v -0.831469 0.000000 -0.555570 61 | v -0.162212 0.980785 -0.108386 62 | v -0.815493 -0.195090 -0.544895 63 | v -0.318190 0.923880 -0.212608 64 | v -0.768178 -0.382683 -0.513280 65 | v -0.461940 0.831470 -0.308658 66 | v -0.137950 -0.980785 -0.137950 67 | v -0.707106 0.000000 -0.707106 68 | v -0.693520 -0.195090 -0.693520 69 | v -0.137950 0.980785 -0.137950 70 | v -0.270598 0.923880 -0.270598 71 | v -0.653282 -0.382683 -0.653281 72 | v -0.392847 0.831470 -0.392847 73 | v -0.587938 -0.555570 -0.587938 74 | v -0.500000 0.707107 -0.500000 75 | v -0.500000 -0.707107 -0.500000 76 | v -0.587938 0.555570 -0.587937 77 | v -0.392847 -0.831470 -0.392847 78 | v -0.653282 0.382683 -0.653281 79 | v -0.270598 -0.923880 -0.270598 80 | v -0.693520 0.195090 -0.693520 81 | v -0.392848 -0.707107 -0.587938 82 | v -0.308658 -0.831470 -0.461940 83 | v -0.461940 0.555570 -0.691341 84 | v -0.513280 0.382683 -0.768177 85 | v -0.212608 -0.923880 -0.318190 86 | v -0.544895 0.195090 -0.815493 87 | v -0.108386 -0.980785 -0.162212 88 | v -0.555570 0.000000 -0.831469 89 | v -0.108386 0.980785 -0.162212 90 | v -0.544895 -0.195090 -0.815493 91 | v -0.212608 0.923880 -0.318190 92 | v -0.513280 -0.382683 -0.768178 93 | v -0.308658 0.831470 -0.461940 94 | v -0.461940 -0.555570 -0.691341 95 | v -0.392848 0.707107 -0.587938 96 | v -0.074658 0.980785 -0.180240 97 | v -0.146447 0.923880 -0.353553 98 | v -0.375330 -0.195090 -0.906127 99 | v -0.353554 -0.382683 -0.853553 100 | v -0.212608 0.831470 -0.513280 101 | v -0.318190 -0.555570 -0.768177 102 | v -0.270598 0.707107 -0.653281 103 | v -0.270598 -0.707107 -0.653281 104 | v -0.318190 0.555570 -0.768177 105 | v -0.212608 -0.831470 -0.513280 106 | v -0.353554 0.382683 -0.853553 107 | v -0.146447 -0.923880 -0.353553 108 | v -0.375330 0.195090 -0.906127 109 | v -0.074658 -0.980785 -0.180240 110 | v -0.382683 0.000000 -0.923879 111 | v -0.162212 0.555570 -0.815493 112 | v -0.180240 0.382683 -0.906127 113 | v -0.108386 -0.831470 -0.544895 114 | v -0.074658 -0.923880 -0.375330 115 | v -0.191342 0.195090 -0.961939 116 | v -0.038060 -0.980785 -0.191342 117 | v -0.195090 0.000000 -0.980785 118 | v -0.038060 0.980785 -0.191342 119 | v -0.191342 -0.195090 -0.961939 120 | v -0.074658 0.923880 -0.375330 121 | v -0.180240 -0.382683 -0.906127 122 | v -0.108386 0.831470 -0.544895 123 | v -0.162212 -0.555570 -0.815493 124 | v -0.137950 0.707107 -0.693520 125 | v -0.137950 -0.707107 -0.693520 126 | v 0.000000 -0.195090 -0.980785 127 | v 0.000000 -0.382683 -0.923880 128 | v 0.000000 0.923880 -0.382684 129 | v 0.000000 0.831470 -0.555570 130 | v 0.000000 -0.555570 -0.831469 131 | v 0.000000 0.707107 -0.707107 132 | v 0.000000 -0.707107 -0.707107 133 | v 0.000000 0.555570 -0.831469 134 | v 0.000000 -0.831470 -0.555570 135 | v 0.000000 0.382683 -0.923879 136 | v 0.000000 -0.923880 -0.382684 137 | v 0.000000 0.195090 -0.980785 138 | v 0.000000 -0.980785 -0.195090 139 | v 0.000000 0.000000 -0.999999 140 | v 0.000000 0.980785 -0.195090 141 | v 0.108386 -0.831470 -0.544895 142 | v 0.074658 -0.923880 -0.375330 143 | v 0.180240 0.382683 -0.906127 144 | v 0.191341 0.195090 -0.961939 145 | v 0.038060 -0.980785 -0.191342 146 | v 0.195090 0.000000 -0.980785 147 | v 0.038060 0.980785 -0.191342 148 | v 0.191341 -0.195090 -0.961939 149 | v 0.074658 0.923880 -0.375330 150 | v 0.180240 -0.382683 -0.906127 151 | v 0.108386 0.831470 -0.544895 152 | v 0.162211 -0.555570 -0.815493 153 | v 0.137949 0.707107 -0.693520 154 | v 0.137949 -0.707107 -0.693520 155 | v 0.162211 0.555570 -0.815493 156 | v 0.146447 0.923880 -0.353554 157 | v 0.212607 0.831470 -0.513280 158 | v 0.353553 -0.382683 -0.853553 159 | v 0.318189 -0.555570 -0.768178 160 | v 0.270598 0.707107 -0.653281 161 | v 0.270598 -0.707107 -0.653281 162 | v 0.318189 0.555570 -0.768178 163 | v 0.212607 -0.831470 -0.513280 164 | v 0.353553 0.382683 -0.853553 165 | v 0.146447 -0.923880 -0.353554 166 | v 0.375330 0.195090 -0.906127 167 | v 0.074658 -0.980785 -0.180240 168 | v 0.382683 0.000000 -0.923879 169 | v 0.074658 0.980785 -0.180240 170 | v 0.375330 -0.195090 -0.906127 171 | v 0.513280 0.382683 -0.768178 172 | v 0.544895 0.195090 -0.815493 173 | v 0.212607 -0.923880 -0.318190 174 | v 0.108386 -0.980785 -0.162212 175 | v 0.555570 0.000000 -0.831469 176 | v 0.108386 0.980785 -0.162212 177 | v 0.544895 -0.195090 -0.815493 178 | v 0.212607 0.923880 -0.318190 179 | v 0.513280 -0.382683 -0.768178 180 | v 0.308658 0.831470 -0.461940 181 | v 0.461939 -0.555570 -0.691342 182 | v 0.392847 0.707107 -0.587938 183 | v 0.392847 -0.707107 -0.587938 184 | v 0.461939 0.555570 -0.691342 185 | v 0.308658 -0.831470 -0.461940 186 | v 0.653281 -0.382683 -0.653282 187 | v 0.587937 -0.555570 -0.587938 188 | v 0.392847 0.831470 -0.392847 189 | v 0.500000 0.707107 -0.500000 190 | v 0.500000 -0.707107 -0.500000 191 | v 0.587937 0.555570 -0.587938 192 | v 0.392847 -0.831470 -0.392847 193 | v 0.653281 0.382683 -0.653282 194 | v 0.270598 -0.923880 -0.270598 195 | v 0.693519 0.195090 -0.693520 196 | v 0.137950 -0.980785 -0.137950 197 | v 0.707106 0.000000 -0.707106 198 | v 0.137950 0.980785 -0.137950 199 | v 0.693519 -0.195090 -0.693520 200 | v 0.270598 0.923880 -0.270598 201 | v 0.318190 -0.923880 -0.212608 202 | v 0.162212 -0.980785 -0.108386 203 | v 0.815493 0.195090 -0.544895 204 | v 0.831469 0.000000 -0.555570 205 | v 0.162212 0.980785 -0.108386 206 | v 0.815493 -0.195090 -0.544895 207 | v 0.318190 0.923880 -0.212608 208 | v 0.768178 -0.382683 -0.513280 209 | v 0.461939 0.831470 -0.308658 210 | v 0.691341 -0.555570 -0.461940 211 | v 0.587937 0.707107 -0.392848 212 | v 0.587937 -0.707107 -0.392848 213 | v 0.691341 0.555570 -0.461940 214 | v 0.461939 -0.831470 -0.308658 215 | v 0.768177 0.382683 -0.513280 216 | v 0.768177 -0.555570 -0.318190 217 | v 0.653281 -0.707107 -0.270598 218 | v 0.653281 0.707107 -0.270598 219 | v 0.768177 0.555570 -0.318190 220 | v 0.513280 -0.831470 -0.212608 221 | v 0.853553 0.382683 -0.353554 222 | v 0.353553 -0.923880 -0.146447 223 | v 0.906127 0.195090 -0.375330 224 | v 0.180240 -0.980785 -0.074658 225 | v 0.923879 0.000000 -0.382683 226 | v 0.180240 0.980785 -0.074658 227 | v 0.906127 -0.195090 -0.375330 228 | v 0.353553 0.923880 -0.146447 229 | v 0.853553 -0.382683 -0.353554 230 | v 0.513280 0.831470 -0.212608 231 | v 0.191342 0.980785 -0.038060 232 | v 0.191342 -0.980785 -0.038060 233 | v 0.980784 0.000000 -0.195090 234 | v 0.961939 -0.195090 -0.191342 235 | v 0.375330 0.923880 -0.074658 236 | v 0.906127 -0.382683 -0.180240 237 | v 0.544895 0.831470 -0.108386 238 | v 0.815493 -0.555570 -0.162212 239 | v 0.693520 0.707107 -0.137950 240 | v 0.693520 -0.707107 -0.137950 241 | v 0.815493 0.555570 -0.162212 242 | v 0.544895 -0.831470 -0.108386 243 | v 0.906127 0.382683 -0.180240 244 | v 0.375330 -0.923880 -0.074658 245 | v 0.961939 0.195090 -0.191342 246 | v 0.707107 0.707107 0.000000 247 | v 0.831469 0.555570 0.000000 248 | v 0.707107 -0.707107 -0.000000 249 | v 0.555570 -0.831470 -0.000000 250 | v 0.923879 0.382683 0.000000 251 | v 0.382683 -0.923880 -0.000000 252 | v 0.980785 0.195090 0.000000 253 | v 0.195090 -0.980785 -0.000000 254 | v 0.999999 0.000000 0.000000 255 | v 0.195090 0.980785 0.000000 256 | v 0.980785 -0.195090 -0.000000 257 | v 0.382683 0.923880 0.000000 258 | v 0.923879 -0.382683 -0.000000 259 | v 0.555570 0.831470 0.000000 260 | v 0.831469 -0.555570 -0.000000 261 | vt -0.023741 0.003558 262 | vt 0.096186 -0.072849 263 | vt 0.105038 -0.071269 264 | vt -0.013467 0.003762 265 | vt -0.023741 0.996442 266 | vt -0.083925 0.869829 267 | vt -0.073918 0.870613 268 | vt -0.013467 0.996239 269 | vt -0.083924 0.130171 270 | vt -0.073918 0.129387 271 | vt 0.096186 1.072850 272 | vt 0.105038 1.071270 273 | vt -0.096382 0.265847 274 | vt -0.086221 0.265327 275 | vt 0.238770 1.097793 276 | vt 0.244348 1.096489 277 | vt -0.097155 0.387410 278 | vt -0.085986 0.387351 279 | vt 0.371860 1.110627 280 | vt 0.374370 1.110394 281 | vt -0.097956 0.500000 282 | vt -0.086595 0.500000 283 | vt 0.371860 -0.110627 284 | vt 0.500000 -0.115804 285 | vt 0.374370 -0.110393 286 | vt 0.500000 1.115805 287 | vt -0.097155 0.612590 288 | vt -0.085986 0.612649 289 | vt 0.238770 -0.097793 290 | vt 0.244348 -0.096489 291 | vt -0.096382 0.734153 292 | vt -0.086221 0.734673 293 | vt -0.053919 0.386957 294 | vt -0.054263 0.500000 295 | vt 0.381790 -0.109723 296 | vt 0.381790 1.109723 297 | vt -0.053919 0.613043 298 | vt 0.260679 -0.092780 299 | vt -0.056012 0.736289 300 | vt 0.131203 -0.066280 301 | vt -0.043432 0.872541 302 | vt 0.017768 0.005242 303 | vt 0.017768 0.994758 304 | vt -0.043431 0.127459 305 | vt 0.131203 1.066281 306 | vt -0.056011 0.263711 307 | vt 0.260679 1.092780 308 | vt 0.008796 0.874162 309 | vt 0.070179 0.989997 310 | vt 0.070179 0.010003 311 | vt 0.008796 0.125838 312 | vt 0.173245 1.057489 313 | vt -0.006142 0.260998 314 | vt 0.286593 1.087341 315 | vt -0.004232 0.385660 316 | vt 0.393792 1.108702 317 | vt -0.004723 0.500000 318 | vt 0.393792 -0.108702 319 | vt -0.004232 0.614340 320 | vt 0.286593 -0.087341 321 | vt -0.006142 0.739002 322 | vt 0.173245 -0.057488 323 | vt 0.409860 1.107469 324 | vt 0.059553 0.500000 325 | vt 0.061022 0.617050 326 | vt 0.409860 -0.107469 327 | vt 0.320324 -0.081477 328 | vt 0.063551 0.742340 329 | vt 0.228224 -0.045237 330 | vt 0.083429 0.873120 331 | vt 0.141653 0.019636 332 | vt 0.141653 0.980364 333 | vt 0.083429 0.126880 334 | vt 0.228224 1.045238 335 | vt 0.063551 0.257661 336 | vt 0.320324 1.081478 337 | vt 0.061022 0.382950 338 | vt 0.226534 0.966570 339 | vt 0.291819 1.031574 340 | vt 0.177261 0.132077 341 | vt 0.152966 0.254750 342 | vt 0.359920 1.076987 343 | vt 0.143201 0.378913 344 | vt 0.429326 1.106199 345 | vt 0.139740 0.500000 346 | vt 0.429326 -0.106199 347 | vt 0.143201 0.621087 348 | vt 0.359920 -0.076986 349 | vt 0.152966 0.745250 350 | vt 0.291819 -0.031573 351 | vt 0.177261 0.867923 352 | vt 0.226534 0.033431 353 | vt 0.451408 -0.105086 354 | vt 0.404113 -0.073386 355 | vt 0.244724 0.625772 356 | vt 0.258690 0.747577 357 | vt 0.359217 -0.021879 358 | vt 0.282048 0.860717 359 | vt 0.317103 0.046978 360 | vt 0.317103 0.953022 361 | vt 0.282048 0.139284 362 | vt 0.359217 1.021880 363 | vt 0.258691 0.252423 364 | vt 0.404113 1.073386 365 | vt 0.244724 0.374228 366 | vt 0.451408 1.105086 367 | vt 0.239742 0.500000 368 | vt 0.389716 0.141969 369 | vt 0.375210 0.248859 370 | vt 0.428830 1.017511 371 | vt 0.451337 1.070809 372 | vt 0.365666 0.370173 373 | vt 0.475259 1.104316 374 | vt 0.361724 0.500000 375 | vt 0.475259 -0.104316 376 | vt 0.365666 0.629827 377 | vt 0.451337 -0.070809 378 | vt 0.375211 0.751141 379 | vt 0.428830 -0.017510 380 | vt 0.389716 0.858032 381 | vt 0.408115 0.053561 382 | vt 0.408115 0.946440 383 | vt 0.500000 0.631798 384 | vt 0.500000 0.752171 385 | vt 0.500000 -0.069857 386 | vt 0.500000 -0.015872 387 | vt 0.500000 0.856653 388 | vt 0.500000 0.055509 389 | vt 0.500000 0.944492 390 | vt 0.500000 0.143347 391 | vt 0.500000 1.015873 392 | vt 0.500000 0.247829 393 | vt 0.500000 1.069857 394 | vt 0.500000 0.368202 395 | vt 0.500000 1.104038 396 | vt 0.500000 0.500000 397 | vt 0.500000 -0.104038 398 | vt 0.571169 1.017511 399 | vt 0.548663 1.070809 400 | vt 0.624790 0.248859 401 | vt 0.634334 0.370173 402 | vt 0.524741 1.104316 403 | vt 0.638276 0.500000 404 | vt 0.524741 -0.104316 405 | vt 0.634334 0.629827 406 | vt 0.548664 -0.070809 407 | vt 0.624790 0.751141 408 | vt 0.571169 -0.017510 409 | vt 0.610284 0.858032 410 | vt 0.591885 0.053561 411 | vt 0.591885 0.946440 412 | vt 0.610284 0.141969 413 | vt 0.595887 -0.073386 414 | vt 0.640784 -0.021879 415 | vt 0.741310 0.747577 416 | vt 0.717952 0.860717 417 | vt 0.682897 0.046978 418 | vt 0.682897 0.953022 419 | vt 0.717952 0.139284 420 | vt 0.640784 1.021881 421 | vt 0.741310 0.252423 422 | vt 0.595887 1.073386 423 | vt 0.755276 0.374228 424 | vt 0.548593 1.105086 425 | vt 0.760258 0.500000 426 | vt 0.548593 -0.105086 427 | vt 0.755276 0.625772 428 | vt 0.847034 0.254750 429 | vt 0.856799 0.378913 430 | vt 0.640081 1.076987 431 | vt 0.570675 1.106199 432 | vt 0.860261 0.500000 433 | vt 0.570675 -0.106199 434 | vt 0.856799 0.621087 435 | vt 0.640081 -0.076987 436 | vt 0.847034 0.745250 437 | vt 0.708182 -0.031573 438 | vt 0.822739 0.867923 439 | vt 0.773466 0.033431 440 | vt 0.773466 0.966569 441 | vt 0.822739 0.132077 442 | vt 0.708182 1.031574 443 | vt 0.936449 0.742340 444 | vt 0.916571 0.873120 445 | vt 0.771776 -0.045237 446 | vt 0.858347 0.019636 447 | vt 0.858347 0.980364 448 | vt 0.916571 0.126880 449 | vt 0.771776 1.045238 450 | vt 0.936449 0.257660 451 | vt 0.679676 1.081478 452 | vt 0.938978 0.382950 453 | vt 0.590140 1.107469 454 | vt 0.940447 0.500000 455 | vt 0.590140 -0.107469 456 | vt 0.938978 0.617050 457 | vt 0.679676 -0.081477 458 | vt 0.713407 1.087341 459 | vt 0.606209 1.108702 460 | vt 1.004233 0.385660 461 | vt 1.004724 0.500000 462 | vt 0.606209 -0.108702 463 | vt 1.004233 0.614340 464 | vt 0.713407 -0.087341 465 | vt 1.006142 0.739002 466 | vt 0.826755 -0.057487 467 | vt 0.991204 0.874162 468 | vt 0.929821 0.010003 469 | vt 0.929821 0.989997 470 | vt 0.991204 0.125838 471 | vt 0.826755 1.057488 472 | vt 1.006142 0.260998 473 | vt 1.043432 0.872541 474 | vt 0.982233 0.994758 475 | vt 0.982232 0.005242 476 | vt 1.043432 0.127459 477 | vt 0.868797 1.066281 478 | vt 1.056012 0.263711 479 | vt 0.739321 1.092780 480 | vt 1.053919 0.386957 481 | vt 0.618211 1.109723 482 | vt 1.054264 0.500000 483 | vt 0.618211 -0.109723 484 | vt 1.053919 0.613043 485 | vt 0.739321 -0.092780 486 | vt 1.056012 0.736290 487 | vt 0.868797 -0.066280 488 | vt 0.625631 -0.110394 489 | vt 0.625631 1.110394 490 | vt 1.086595 0.500000 491 | vt 1.085986 0.612649 492 | vt 0.755652 -0.096489 493 | vt 1.086221 0.734673 494 | vt 0.894962 -0.071269 495 | vt 1.073918 0.870613 496 | vt 1.013468 0.003762 497 | vt 1.013468 0.996239 498 | vt 1.073918 0.129387 499 | vt 0.894962 1.071270 500 | vt 1.086221 0.265327 501 | vt 0.755652 1.096489 502 | vt 1.085986 0.387351 503 | vt 1.023742 0.003558 504 | vt 1.083925 0.130171 505 | vt 1.023742 0.996442 506 | vt 0.903815 1.072851 507 | vt 1.096382 0.265847 508 | vt 0.761231 1.097793 509 | vt 1.097156 0.387410 510 | vt 0.628140 1.110627 511 | vt 1.097957 0.500000 512 | vt 0.628140 -0.110627 513 | vt 1.097156 0.612590 514 | vt 0.761231 -0.097793 515 | vt 1.096382 0.734153 516 | vt 0.903815 -0.072849 517 | vt 1.083925 0.869829 518 | s 1 519 | f 1/1 2/2 3/3 4/4 520 | f 5/5 6/6 7/7 8/8 521 | f 9/9 1/1 4/4 10/10 522 | f 11/11 5/5 8/8 12/12 523 | f 13/13 9/9 10/10 14/14 524 | f 15/15 11/11 12/12 16/16 525 | f 17/17 13/13 14/14 18/18 526 | f 19/19 15/15 16/16 20/20 527 | f 21/21 17/17 18/18 22/22 528 | f 23/23 24/24 25/25 529 | f 26/26 19/19 20/20 530 | f 27/27 21/21 22/22 28/28 531 | f 29/29 23/23 25/25 30/30 532 | f 31/31 27/27 28/28 32/32 533 | f 2/2 29/29 30/30 3/3 534 | f 6/6 31/31 32/32 7/7 535 | f 22/22 18/18 33/33 34/34 536 | f 25/25 24/24 35/35 537 | f 26/26 20/20 36/36 538 | f 28/28 22/22 34/34 37/37 539 | f 30/30 25/25 35/35 38/38 540 | f 32/32 28/28 37/37 39/39 541 | f 3/3 30/30 38/38 40/40 542 | f 7/7 32/32 39/39 41/41 543 | f 4/4 3/3 40/40 42/42 544 | f 8/8 7/7 41/41 43/43 545 | f 10/10 4/4 42/42 44/44 546 | f 12/12 8/8 43/43 45/45 547 | f 14/14 10/10 44/44 46/46 548 | f 16/16 12/12 45/45 47/47 549 | f 18/18 14/14 46/46 33/33 550 | f 20/20 16/16 47/47 36/36 551 | f 43/43 41/41 48/48 49/49 552 | f 44/44 42/42 50/50 51/51 553 | f 45/45 43/43 49/49 52/52 554 | f 46/46 44/44 51/51 53/53 555 | f 47/47 45/45 52/52 54/54 556 | f 33/33 46/46 53/53 55/55 557 | f 36/36 47/47 54/54 56/56 558 | f 34/34 33/33 55/55 57/57 559 | f 35/35 24/24 58/58 560 | f 26/26 36/36 56/56 561 | f 37/37 34/34 57/57 59/59 562 | f 38/38 35/35 58/58 60/60 563 | f 39/39 37/37 59/59 61/61 564 | f 40/40 38/38 60/60 62/62 565 | f 41/41 39/39 61/61 48/48 566 | f 42/42 40/40 62/62 50/50 567 | f 26/26 56/56 63/63 568 | f 59/59 57/57 64/64 65/65 569 | f 60/60 58/58 66/66 67/67 570 | f 61/61 59/59 65/65 68/68 571 | f 62/62 60/60 67/67 69/69 572 | f 48/48 61/61 68/68 70/70 573 | f 50/50 62/62 69/69 71/71 574 | f 49/49 48/48 70/70 72/72 575 | f 51/51 50/50 71/71 73/73 576 | f 52/52 49/49 72/72 74/74 577 | f 53/53 51/51 73/73 75/75 578 | f 54/54 52/52 74/74 76/76 579 | f 55/55 53/53 75/75 77/77 580 | f 56/56 54/54 76/76 63/63 581 | f 57/57 55/55 77/77 64/64 582 | f 58/58 24/24 66/66 583 | f 74/74 72/72 78/78 79/79 584 | f 75/75 73/73 80/80 81/81 585 | f 76/76 74/74 79/79 82/82 586 | f 77/77 75/75 81/81 83/83 587 | f 63/63 76/76 82/82 84/84 588 | f 64/64 77/77 83/83 85/85 589 | f 66/66 24/24 86/86 590 | f 26/26 63/63 84/84 591 | f 65/65 64/64 85/85 87/87 592 | f 67/67 66/66 86/86 88/88 593 | f 68/68 65/65 87/87 89/89 594 | f 69/69 67/67 88/88 90/90 595 | f 70/70 68/68 89/89 91/91 596 | f 71/71 69/69 90/90 92/92 597 | f 72/72 70/70 91/91 78/78 598 | f 73/73 71/71 92/92 80/80 599 | f 88/88 86/86 93/93 94/94 600 | f 89/89 87/87 95/95 96/96 601 | f 90/90 88/88 94/94 97/97 602 | f 91/91 89/89 96/96 98/98 603 | f 92/92 90/90 97/97 99/99 604 | f 78/78 91/91 98/98 100/100 605 | f 80/80 92/92 99/99 101/101 606 | f 79/79 78/78 100/100 102/102 607 | f 81/81 80/80 101/101 103/103 608 | f 82/82 79/79 102/102 104/104 609 | f 83/83 81/81 103/103 105/105 610 | f 84/84 82/82 104/104 106/106 611 | f 85/85 83/83 105/105 107/107 612 | f 86/86 24/24 93/93 613 | f 26/26 84/84 106/106 614 | f 87/87 85/85 107/107 95/95 615 | f 103/103 101/101 108/108 109/109 616 | f 104/104 102/102 110/110 111/111 617 | f 105/105 103/103 109/109 112/112 618 | f 106/106 104/104 111/111 113/113 619 | f 107/107 105/105 112/112 114/114 620 | f 93/93 24/24 115/115 621 | f 26/26 106/106 113/113 622 | f 95/95 107/107 114/114 116/116 623 | f 94/94 93/93 115/115 117/117 624 | f 96/96 95/95 116/116 118/118 625 | f 97/97 94/94 117/117 119/119 626 | f 98/98 96/96 118/118 120/120 627 | f 99/99 97/97 119/119 121/121 628 | f 100/100 98/98 120/120 122/122 629 | f 101/101 99/99 121/121 108/108 630 | f 102/102 100/100 122/122 110/110 631 | f 118/118 116/116 123/123 124/124 632 | f 119/119 117/117 125/125 126/126 633 | f 120/120 118/118 124/124 127/127 634 | f 121/121 119/119 126/126 128/128 635 | f 122/122 120/120 127/127 129/129 636 | f 108/108 121/121 128/128 130/130 637 | f 110/110 122/122 129/129 131/131 638 | f 109/109 108/108 130/130 132/132 639 | f 111/111 110/110 131/131 133/133 640 | f 112/112 109/109 132/132 134/134 641 | f 113/113 111/111 133/133 135/135 642 | f 114/114 112/112 134/134 136/136 643 | f 115/115 24/24 137/137 644 | f 26/26 113/113 135/135 645 | f 116/116 114/114 136/136 123/123 646 | f 117/117 115/115 137/137 125/125 647 | f 133/133 131/131 138/138 139/139 648 | f 134/134 132/132 140/140 141/141 649 | f 135/135 133/133 139/139 142/142 650 | f 136/136 134/134 141/141 143/143 651 | f 137/137 24/24 144/144 652 | f 26/26 135/135 142/142 653 | f 123/123 136/136 143/143 145/145 654 | f 125/125 137/137 144/144 146/146 655 | f 124/124 123/123 145/145 147/147 656 | f 126/126 125/125 146/146 148/148 657 | f 127/127 124/124 147/147 149/149 658 | f 128/128 126/126 148/148 150/150 659 | f 129/129 127/127 149/149 151/151 660 | f 130/130 128/128 150/150 152/152 661 | f 131/131 129/129 151/151 138/138 662 | f 132/132 130/130 152/152 140/140 663 | f 148/148 146/146 153/153 154/154 664 | f 149/149 147/147 155/155 156/156 665 | f 150/150 148/148 154/154 157/157 666 | f 151/151 149/149 156/156 158/158 667 | f 152/152 150/150 157/157 159/159 668 | f 138/138 151/151 158/158 160/160 669 | f 140/140 152/152 159/159 161/161 670 | f 139/139 138/138 160/160 162/162 671 | f 141/141 140/140 161/161 163/163 672 | f 142/142 139/139 162/162 164/164 673 | f 143/143 141/141 163/163 165/165 674 | f 144/144 24/24 166/166 675 | f 26/26 142/142 164/164 676 | f 145/145 143/143 165/165 167/167 677 | f 146/146 144/144 166/166 153/153 678 | f 147/147 145/145 167/167 155/155 679 | f 163/163 161/161 168/168 169/169 680 | f 164/164 162/162 170/170 171/171 681 | f 165/165 163/163 169/169 172/172 682 | f 166/166 24/24 173/173 683 | f 26/26 164/164 171/171 684 | f 167/167 165/165 172/172 174/174 685 | f 153/153 166/166 173/173 175/175 686 | f 155/155 167/167 174/174 176/176 687 | f 154/154 153/153 175/175 177/177 688 | f 156/156 155/155 176/176 178/178 689 | f 157/157 154/154 177/177 179/179 690 | f 158/158 156/156 178/178 180/180 691 | f 159/159 157/157 179/179 181/181 692 | f 160/160 158/158 180/180 182/182 693 | f 161/161 159/159 181/181 168/168 694 | f 162/162 160/160 182/182 170/170 695 | f 178/178 176/176 183/183 184/184 696 | f 179/179 177/177 185/185 186/186 697 | f 180/180 178/178 184/184 187/187 698 | f 181/181 179/179 186/186 188/188 699 | f 182/182 180/180 187/187 189/189 700 | f 168/168 181/181 188/188 190/190 701 | f 170/170 182/182 189/189 191/191 702 | f 169/169 168/168 190/190 192/192 703 | f 171/171 170/170 191/191 193/193 704 | f 172/172 169/169 192/192 194/194 705 | f 173/173 24/24 195/195 706 | f 26/26 171/171 193/193 707 | f 174/174 172/172 194/194 196/196 708 | f 175/175 173/173 195/195 197/197 709 | f 176/176 174/174 196/196 183/183 710 | f 177/177 175/175 197/197 185/185 711 | f 193/193 191/191 198/198 199/199 712 | f 194/194 192/192 200/200 201/201 713 | f 195/195 24/24 202/202 714 | f 26/26 193/193 199/199 715 | f 196/196 194/194 201/201 203/203 716 | f 197/197 195/195 202/202 204/204 717 | f 183/183 196/196 203/203 205/205 718 | f 185/185 197/197 204/204 206/206 719 | f 184/184 183/183 205/205 207/207 720 | f 186/186 185/185 206/206 208/208 721 | f 187/187 184/184 207/207 209/209 722 | f 188/188 186/186 208/208 210/210 723 | f 189/189 187/187 209/209 211/211 724 | f 190/190 188/188 210/210 212/212 725 | f 191/191 189/189 211/211 198/198 726 | f 192/192 190/190 212/212 200/200 727 | f 209/209 207/207 213/213 214/214 728 | f 210/210 208/208 215/215 216/216 729 | f 211/211 209/209 214/214 217/217 730 | f 212/212 210/210 216/216 218/218 731 | f 198/198 211/211 217/217 219/219 732 | f 200/200 212/212 218/218 220/220 733 | f 199/199 198/198 219/219 221/221 734 | f 201/201 200/200 220/220 222/222 735 | f 202/202 24/24 223/223 736 | f 26/26 199/199 221/221 737 | f 203/203 201/201 222/222 224/224 738 | f 204/204 202/202 223/223 225/225 739 | f 205/205 203/203 224/224 226/226 740 | f 206/206 204/204 225/225 227/227 741 | f 207/207 205/205 226/226 213/213 742 | f 208/208 206/206 227/227 215/215 743 | f 223/223 24/24 228/228 744 | f 26/26 221/221 229/229 745 | f 224/224 222/222 230/230 231/231 746 | f 225/225 223/223 228/228 232/232 747 | f 226/226 224/224 231/231 233/233 748 | f 227/227 225/225 232/232 234/234 749 | f 213/213 226/226 233/233 235/235 750 | f 215/215 227/227 234/234 236/236 751 | f 214/214 213/213 235/235 237/237 752 | f 216/216 215/215 236/236 238/238 753 | f 217/217 214/214 237/237 239/239 754 | f 218/218 216/216 238/238 240/240 755 | f 219/219 217/217 239/239 241/241 756 | f 220/220 218/218 240/240 242/242 757 | f 221/221 219/219 241/241 229/229 758 | f 222/222 220/220 242/242 230/230 759 | f 238/238 236/236 243/243 244/244 760 | f 239/239 237/237 245/245 246/246 761 | f 240/240 238/238 244/244 247/247 762 | f 241/241 239/239 246/246 248/248 763 | f 242/242 240/240 247/247 249/249 764 | f 229/229 241/241 248/248 250/250 765 | f 230/230 242/242 249/249 251/251 766 | f 228/228 24/24 252/252 767 | f 26/26 229/229 250/250 768 | f 231/231 230/230 251/251 253/253 769 | f 232/232 228/228 252/252 254/254 770 | f 233/233 231/231 253/253 255/255 771 | f 234/234 232/232 254/254 256/256 772 | f 235/235 233/233 255/255 257/257 773 | f 236/236 234/234 256/256 243/243 774 | f 237/237 235/235 257/257 245/245 775 | -------------------------------------------------------------------------------- /assets/ambient_dome_old.obj: -------------------------------------------------------------------------------- 1 | # Blender 3.1.0 2 | # www.blender.org 3 | o Sphere 4 | v -0.195090 0.980785 -0.000000 5 | v -0.382684 0.923880 0.000000 6 | v -0.555570 0.831470 0.000000 7 | v -0.707107 0.707107 0.000000 8 | v -0.831469 0.555570 0.000000 9 | v -0.923879 0.382683 0.000000 10 | v -0.980785 0.195090 0.000000 11 | v -1.000000 -0.000000 0.000000 12 | v -0.980785 -0.195090 0.000000 13 | v -0.923880 -0.382683 0.000000 14 | v -0.831469 -0.555570 0.000000 15 | v -0.707107 -0.707107 0.000000 16 | v -0.555570 -0.831470 0.000000 17 | v -0.382684 -0.923880 0.000000 18 | v -0.195090 -0.980785 -0.000000 19 | v -0.191342 0.980785 -0.038060 20 | v -0.375330 0.923880 -0.074658 21 | v -0.544895 0.831470 -0.108386 22 | v -0.693520 0.707107 -0.137949 23 | v -0.815493 0.555570 -0.162211 24 | v -0.906127 0.382683 -0.180240 25 | v -0.961940 0.195090 -0.191341 26 | v -0.980785 -0.000000 -0.195090 27 | v -0.961940 -0.195090 -0.191341 28 | v -0.906128 -0.382683 -0.180240 29 | v -0.815493 -0.555570 -0.162211 30 | v -0.693520 -0.707107 -0.137949 31 | v -0.544895 -0.831470 -0.108386 32 | v -0.375330 -0.923880 -0.074658 33 | v -0.191342 -0.980785 -0.038060 34 | v -0.180240 0.980785 -0.074658 35 | v -0.353554 0.923880 -0.146447 36 | v -0.513280 0.831470 -0.212607 37 | v -0.653281 0.707107 -0.270598 38 | v -0.768178 0.555570 -0.318189 39 | v -0.853553 0.382683 -0.353553 40 | v -0.906127 0.195090 -0.375330 41 | v -0.923879 -0.000000 -0.382683 42 | v -0.906127 -0.195090 -0.375330 43 | v -0.853553 -0.382683 -0.353553 44 | v -0.768178 -0.555570 -0.318189 45 | v -0.653281 -0.707107 -0.270598 46 | v -0.513280 -0.831470 -0.212607 47 | v -0.353554 -0.923880 -0.146447 48 | v -0.180240 -0.980785 -0.074658 49 | v -0.162212 0.980785 -0.108386 50 | v -0.318190 0.923880 -0.212608 51 | v -0.461940 0.831470 -0.308658 52 | v -0.587938 0.707107 -0.392847 53 | v -0.691342 0.555570 -0.461939 54 | v -0.768178 0.382683 -0.513280 55 | v -0.815493 0.195090 -0.544895 56 | v -0.831469 -0.000000 -0.555570 57 | v -0.815493 -0.195090 -0.544895 58 | v -0.768178 -0.382683 -0.513280 59 | v -0.691342 -0.555570 -0.461940 60 | v -0.587938 -0.707107 -0.392847 61 | v -0.461940 -0.831470 -0.308658 62 | v -0.318190 -0.923880 -0.212608 63 | v -0.162212 -0.980785 -0.108386 64 | v -0.137950 0.980785 -0.137950 65 | v -0.270598 0.923880 -0.270598 66 | v -0.392847 0.831470 -0.392847 67 | v -0.500000 0.707107 -0.500000 68 | v -0.587938 0.555570 -0.587937 69 | v -0.653282 0.382683 -0.653281 70 | v -0.693520 0.195090 -0.693520 71 | v -0.707106 -0.000000 -0.707106 72 | v -0.693520 -0.195090 -0.693520 73 | v -0.653282 -0.382683 -0.653281 74 | v -0.587938 -0.555570 -0.587938 75 | v -0.500000 -0.707107 -0.500000 76 | v -0.392847 -0.831470 -0.392847 77 | v -0.270598 -0.923880 -0.270598 78 | v -0.137950 -0.980785 -0.137950 79 | v -0.108386 0.980785 -0.162212 80 | v -0.212608 0.923880 -0.318190 81 | v -0.308658 0.831470 -0.461940 82 | v -0.392848 0.707107 -0.587938 83 | v -0.461940 0.555570 -0.691341 84 | v -0.513280 0.382683 -0.768177 85 | v -0.544895 0.195090 -0.815493 86 | v -0.555570 -0.000000 -0.831469 87 | v -0.544895 -0.195090 -0.815493 88 | v -0.513280 -0.382683 -0.768178 89 | v -0.461940 -0.555570 -0.691341 90 | v -0.392848 -0.707107 -0.587938 91 | v -0.308658 -0.831470 -0.461940 92 | v -0.212608 -0.923880 -0.318190 93 | v -0.108386 -0.980785 -0.162212 94 | v -0.074658 0.980785 -0.180240 95 | v -0.146447 0.923880 -0.353553 96 | v -0.212608 0.831470 -0.513280 97 | v -0.270598 0.707107 -0.653281 98 | v -0.318190 0.555570 -0.768177 99 | v -0.353554 0.382683 -0.853553 100 | v -0.375330 0.195090 -0.906127 101 | v -0.382683 -0.000000 -0.923879 102 | v -0.375330 -0.195090 -0.906127 103 | v -0.353554 -0.382683 -0.853553 104 | v -0.318190 -0.555570 -0.768177 105 | v -0.270598 -0.707107 -0.653281 106 | v -0.212608 -0.831470 -0.513280 107 | v -0.146447 -0.923880 -0.353553 108 | v -0.074658 -0.980785 -0.180240 109 | v -0.038060 0.980785 -0.191342 110 | v -0.074658 0.923880 -0.375330 111 | v -0.108386 0.831470 -0.544895 112 | v -0.137950 0.707107 -0.693520 113 | v -0.162212 0.555570 -0.815493 114 | v -0.180240 0.382683 -0.906127 115 | v -0.191342 0.195090 -0.961939 116 | v -0.195090 -0.000000 -0.980785 117 | v -0.191342 -0.195090 -0.961939 118 | v -0.180240 -0.382683 -0.906127 119 | v -0.162212 -0.555570 -0.815493 120 | v -0.137950 -0.707107 -0.693520 121 | v -0.108386 -0.831470 -0.544895 122 | v -0.074658 -0.923880 -0.375330 123 | v -0.038060 -0.980785 -0.191342 124 | v -0.000000 0.980785 -0.195090 125 | v -0.000000 0.923880 -0.382684 126 | v -0.000000 0.831470 -0.555570 127 | v -0.000000 0.707107 -0.707107 128 | v -0.000000 0.555570 -0.831469 129 | v -0.000000 0.382683 -0.923879 130 | v -0.000000 0.195090 -0.980785 131 | v -0.000000 -0.000000 -0.999999 132 | v -0.000000 -0.195090 -0.980785 133 | v -0.000000 -0.382683 -0.923880 134 | v -0.000000 -0.555570 -0.831469 135 | v -0.000000 -0.707107 -0.707107 136 | v -0.000000 -0.831470 -0.555570 137 | v -0.000000 -0.923880 -0.382684 138 | v -0.000000 -0.980785 -0.195090 139 | v 0.038060 0.980785 -0.191342 140 | v 0.074658 0.923880 -0.375330 141 | v 0.108386 0.831470 -0.544895 142 | v 0.137949 0.707107 -0.693520 143 | v 0.162211 0.555570 -0.815493 144 | v 0.180240 0.382683 -0.906127 145 | v 0.191341 0.195090 -0.961939 146 | v 0.195090 -0.000000 -0.980785 147 | v 0.191341 -0.195090 -0.961939 148 | v 0.180240 -0.382683 -0.906127 149 | v 0.162211 -0.555570 -0.815493 150 | v 0.137949 -0.707107 -0.693520 151 | v 0.108386 -0.831470 -0.544895 152 | v 0.074658 -0.923880 -0.375330 153 | v 0.038060 -0.980785 -0.191342 154 | v 0.074658 0.980785 -0.180240 155 | v 0.146447 0.923880 -0.353554 156 | v 0.212607 0.831470 -0.513280 157 | v 0.270598 0.707107 -0.653281 158 | v 0.318189 0.555570 -0.768178 159 | v 0.353553 0.382683 -0.853553 160 | v 0.375330 0.195090 -0.906127 161 | v 0.382683 -0.000000 -0.923879 162 | v 0.375330 -0.195090 -0.906127 163 | v 0.353553 -0.382683 -0.853553 164 | v 0.318189 -0.555570 -0.768178 165 | v 0.270598 -0.707107 -0.653281 166 | v 0.212607 -0.831470 -0.513280 167 | v 0.146447 -0.923880 -0.353554 168 | v 0.074658 -0.980785 -0.180240 169 | v 0.108386 0.980785 -0.162212 170 | v 0.212607 0.923880 -0.318190 171 | v 0.308658 0.831470 -0.461940 172 | v 0.392847 0.707107 -0.587938 173 | v 0.461939 0.555570 -0.691342 174 | v 0.513280 0.382683 -0.768178 175 | v 0.544895 0.195090 -0.815493 176 | v 0.555570 -0.000000 -0.831469 177 | v 0.544895 -0.195090 -0.815493 178 | v 0.513280 -0.382683 -0.768178 179 | v 0.461939 -0.555570 -0.691342 180 | v 0.392847 -0.707107 -0.587938 181 | v 0.308658 -0.831470 -0.461940 182 | v 0.212607 -0.923880 -0.318190 183 | v 0.108386 -0.980785 -0.162212 184 | v 0.137950 0.980785 -0.137950 185 | v 0.270598 0.923880 -0.270598 186 | v 0.392847 0.831470 -0.392847 187 | v 0.500000 0.707107 -0.500000 188 | v 0.587937 0.555570 -0.587938 189 | v 0.653281 0.382683 -0.653282 190 | v 0.693519 0.195090 -0.693520 191 | v 0.707106 -0.000000 -0.707106 192 | v 0.693519 -0.195090 -0.693520 193 | v 0.653281 -0.382683 -0.653282 194 | v 0.587937 -0.555570 -0.587938 195 | v 0.500000 -0.707107 -0.500000 196 | v 0.392847 -0.831470 -0.392847 197 | v 0.270598 -0.923880 -0.270598 198 | v 0.137950 -0.980785 -0.137950 199 | v 0.162212 0.980785 -0.108386 200 | v 0.318190 0.923880 -0.212608 201 | v 0.461939 0.831470 -0.308658 202 | v 0.587937 0.707107 -0.392848 203 | v 0.691341 0.555570 -0.461940 204 | v 0.768177 0.382683 -0.513280 205 | v 0.815493 0.195090 -0.544895 206 | v 0.831469 -0.000000 -0.555570 207 | v 0.815493 -0.195090 -0.544895 208 | v 0.768178 -0.382683 -0.513280 209 | v 0.691341 -0.555570 -0.461940 210 | v 0.587937 -0.707107 -0.392848 211 | v 0.461939 -0.831470 -0.308658 212 | v 0.318190 -0.923880 -0.212608 213 | v 0.162212 -0.980785 -0.108386 214 | v 0.000000 1.000000 -0.000000 215 | v 0.180240 0.980785 -0.074658 216 | v 0.353553 0.923880 -0.146447 217 | v 0.513280 0.831470 -0.212608 218 | v 0.653281 0.707107 -0.270598 219 | v 0.768177 0.555570 -0.318190 220 | v 0.853553 0.382683 -0.353554 221 | v 0.906127 0.195090 -0.375330 222 | v 0.923879 -0.000000 -0.382683 223 | v 0.906127 -0.195090 -0.375330 224 | v 0.853553 -0.382683 -0.353554 225 | v 0.768177 -0.555570 -0.318190 226 | v 0.653281 -0.707107 -0.270598 227 | v 0.513280 -0.831470 -0.212608 228 | v 0.353553 -0.923880 -0.146447 229 | v 0.180240 -0.980785 -0.074658 230 | v 0.191342 0.980785 -0.038060 231 | v 0.375330 0.923880 -0.074658 232 | v 0.544895 0.831470 -0.108386 233 | v 0.693520 0.707107 -0.137950 234 | v 0.815493 0.555570 -0.162212 235 | v 0.906127 0.382683 -0.180240 236 | v 0.961939 0.195090 -0.191342 237 | v 0.980784 -0.000000 -0.195090 238 | v 0.961939 -0.195090 -0.191342 239 | v 0.906127 -0.382683 -0.180240 240 | v 0.815493 -0.555570 -0.162212 241 | v 0.693520 -0.707107 -0.137950 242 | v 0.544895 -0.831470 -0.108386 243 | v 0.375330 -0.923880 -0.074658 244 | v 0.191342 -0.980785 -0.038060 245 | v 0.195090 0.980785 -0.000000 246 | v 0.382683 0.923880 -0.000000 247 | v 0.555570 0.831470 -0.000000 248 | v 0.707107 0.707107 -0.000000 249 | v 0.831469 0.555570 -0.000000 250 | v 0.923879 0.382683 -0.000000 251 | v 0.980785 0.195090 -0.000000 252 | v 0.999999 -0.000000 0.000000 253 | v 0.980785 -0.195090 -0.000000 254 | v 0.923879 -0.382683 -0.000000 255 | v 0.831469 -0.555570 -0.000000 256 | v 0.707107 -0.707107 -0.000000 257 | v 0.555570 -0.831470 -0.000000 258 | v 0.382683 -0.923880 -0.000000 259 | v 0.195090 -0.980785 -0.000000 260 | v -0.000000 -1.000000 -0.000000 261 | vt 0.348143 -0.263437 262 | vt 0.202122 -0.219142 263 | vt 0.067548 -0.147210 264 | vt -0.050407 -0.050407 265 | vt -0.147210 0.067548 266 | vt -0.219142 0.202122 267 | vt -0.263437 0.348143 268 | vt -0.278393 0.500000 269 | vt -0.263437 0.651857 270 | vt -0.219142 0.797878 271 | vt -0.147211 0.932452 272 | vt -0.050407 1.050407 273 | vt 0.067548 1.147211 274 | vt 0.202122 1.219142 275 | vt 0.348143 1.263437 276 | vt 0.351061 -0.263437 277 | vt 0.207845 -0.219142 278 | vt 0.075857 -0.147210 279 | vt -0.039831 -0.050407 280 | vt -0.134775 0.067548 281 | vt -0.205324 0.202122 282 | vt -0.248768 0.348143 283 | vt -0.263437 0.500000 284 | vt -0.248768 0.651857 285 | vt -0.205324 0.797878 286 | vt -0.134775 0.932452 287 | vt -0.039831 1.050407 288 | vt 0.075857 1.147211 289 | vt 0.207845 1.219142 290 | vt 0.351061 1.263437 291 | vt 0.359703 -0.263437 292 | vt 0.224796 -0.219142 293 | vt 0.100466 -0.147210 294 | vt -0.008510 -0.050407 295 | vt -0.097944 0.067548 296 | vt -0.164400 0.202122 297 | vt -0.205324 0.348143 298 | vt -0.219142 0.500000 299 | vt -0.205324 0.651857 300 | vt -0.164401 0.797878 301 | vt -0.097945 0.932452 302 | vt -0.008510 1.050407 303 | vt 0.100466 1.147211 304 | vt 0.224796 1.219142 305 | vt 0.359703 1.263437 306 | vt 0.373736 -0.263437 307 | vt 0.252323 -0.219142 308 | vt 0.140429 -0.147210 309 | vt 0.042353 -0.050407 310 | vt -0.038136 0.067548 311 | vt -0.097945 0.202122 312 | vt -0.134775 0.348143 313 | vt -0.147210 0.500000 314 | vt -0.134775 0.651857 315 | vt -0.097945 0.797878 316 | vt -0.038136 0.932452 317 | vt 0.042353 1.050407 318 | vt 0.140429 1.147211 319 | vt 0.252323 1.219142 320 | vt 0.373736 1.263437 321 | vt 0.392621 -0.263437 322 | vt 0.289368 -0.219142 323 | vt 0.194210 -0.147210 324 | vt 0.110803 -0.050407 325 | vt 0.042353 0.067548 326 | vt -0.008510 0.202122 327 | vt -0.039831 0.348143 328 | vt -0.050407 0.500000 329 | vt -0.039831 0.651857 330 | vt -0.008510 0.797878 331 | vt 0.042353 0.932452 332 | vt 0.110803 1.050407 333 | vt 0.194210 1.147211 334 | vt 0.289368 1.219142 335 | vt 0.392621 1.263437 336 | vt 0.415633 -0.263437 337 | vt 0.334508 -0.219142 338 | vt 0.259743 -0.147210 339 | vt 0.194210 -0.050407 340 | vt 0.140429 0.067548 341 | vt 0.100466 0.202122 342 | vt 0.075857 0.348143 343 | vt 0.067548 0.500000 344 | vt 0.075857 0.651857 345 | vt 0.100466 0.797878 346 | vt 0.140429 0.932452 347 | vt 0.194210 1.050407 348 | vt 0.259743 1.147211 349 | vt 0.334508 1.219142 350 | vt 0.415633 1.263437 351 | vt 0.441887 -0.263437 352 | vt 0.386007 -0.219142 353 | vt 0.334508 -0.147210 354 | vt 0.289368 -0.050407 355 | vt 0.252323 0.067548 356 | vt 0.224796 0.202122 357 | vt 0.207845 0.348143 358 | vt 0.202122 0.500000 359 | vt 0.207845 0.651857 360 | vt 0.224796 0.797878 361 | vt 0.252323 0.932452 362 | vt 0.289368 1.050407 363 | vt 0.334508 1.147211 364 | vt 0.386007 1.219142 365 | vt 0.441887 1.263437 366 | vt 0.470374 -0.263437 367 | vt 0.441887 -0.219142 368 | vt 0.415633 -0.147210 369 | vt 0.392621 -0.050407 370 | vt 0.373736 0.067548 371 | vt 0.359702 0.202122 372 | vt 0.351061 0.348143 373 | vt 0.348143 0.500000 374 | vt 0.351061 0.651857 375 | vt 0.359703 0.797878 376 | vt 0.373736 0.932452 377 | vt 0.392621 1.050407 378 | vt 0.415633 1.147211 379 | vt 0.441887 1.219142 380 | vt 0.470374 1.263437 381 | vt 0.500000 -0.263437 382 | vt 0.500000 -0.219142 383 | vt 0.500000 -0.147210 384 | vt 0.500000 -0.050407 385 | vt 0.500000 0.067548 386 | vt 0.500000 0.202122 387 | vt 0.500000 0.348143 388 | vt 0.500000 0.500000 389 | vt 0.500000 0.651857 390 | vt 0.500000 0.797878 391 | vt 0.500000 0.932452 392 | vt 0.500000 1.050407 393 | vt 0.500000 1.147211 394 | vt 0.500000 1.219142 395 | vt 0.500000 1.263437 396 | vt 0.529626 -0.263437 397 | vt 0.558114 -0.219142 398 | vt 0.584367 -0.147210 399 | vt 0.607379 -0.050407 400 | vt 0.626265 0.067548 401 | vt 0.640298 0.202122 402 | vt 0.648939 0.348143 403 | vt 0.651857 0.500000 404 | vt 0.648939 0.651857 405 | vt 0.640298 0.797878 406 | vt 0.626265 0.932452 407 | vt 0.607379 1.050407 408 | vt 0.584367 1.147211 409 | vt 0.558113 1.219142 410 | vt 0.529626 1.263437 411 | vt 0.558114 -0.263437 412 | vt 0.613993 -0.219142 413 | vt 0.665493 -0.147210 414 | vt 0.710632 -0.050407 415 | vt 0.747677 0.067548 416 | vt 0.775204 0.202122 417 | vt 0.792155 0.348143 418 | vt 0.797879 0.500000 419 | vt 0.792155 0.651857 420 | vt 0.775204 0.797878 421 | vt 0.747677 0.932452 422 | vt 0.710632 1.050407 423 | vt 0.665493 1.147211 424 | vt 0.613993 1.219142 425 | vt 0.558114 1.263437 426 | vt 0.584368 -0.263437 427 | vt 0.665493 -0.219142 428 | vt 0.740258 -0.147210 429 | vt 0.805790 -0.050407 430 | vt 0.859571 0.067548 431 | vt 0.899534 0.202122 432 | vt 0.924143 0.348143 433 | vt 0.932453 0.500000 434 | vt 0.924143 0.651857 435 | vt 0.899534 0.797878 436 | vt 0.859571 0.932452 437 | vt 0.805790 1.050407 438 | vt 0.740258 1.147211 439 | vt 0.665493 1.219142 440 | vt 0.584368 1.263437 441 | vt 0.607379 -0.263437 442 | vt 0.710632 -0.219142 443 | vt 0.805790 -0.147210 444 | vt 0.889197 -0.050407 445 | vt 0.957647 0.067548 446 | vt 1.008510 0.202122 447 | vt 1.039832 0.348143 448 | vt 1.050407 0.500000 449 | vt 1.039832 0.651857 450 | vt 1.008510 0.797878 451 | vt 0.957647 0.932452 452 | vt 0.889197 1.050407 453 | vt 0.805790 1.147211 454 | vt 0.710632 1.219142 455 | vt 0.607379 1.263437 456 | vt 0.626265 -0.263437 457 | vt 0.747677 -0.219142 458 | vt 0.859571 -0.147210 459 | vt 0.957647 -0.050407 460 | vt 1.038136 0.067548 461 | vt 1.097945 0.202122 462 | vt 1.134775 0.348143 463 | vt 1.147211 0.500000 464 | vt 1.134775 0.651857 465 | vt 1.097945 0.797878 466 | vt 1.038136 0.932452 467 | vt 0.957647 1.050407 468 | vt 0.859571 1.147211 469 | vt 0.747677 1.219142 470 | vt 0.626265 1.263437 471 | vt 0.500000 -0.278393 472 | vt 0.640298 -0.263437 473 | vt 0.775204 -0.219142 474 | vt 0.899534 -0.147210 475 | vt 1.008510 -0.050407 476 | vt 1.097945 0.067548 477 | vt 1.164401 0.202122 478 | vt 1.205324 0.348143 479 | vt 1.219142 0.500000 480 | vt 1.205324 0.651857 481 | vt 1.164401 0.797878 482 | vt 1.097945 0.932452 483 | vt 1.008510 1.050407 484 | vt 0.899534 1.147211 485 | vt 0.775204 1.219142 486 | vt 0.640298 1.263437 487 | vt 0.648940 -0.263437 488 | vt 0.792155 -0.219142 489 | vt 0.924143 -0.147210 490 | vt 1.039832 -0.050407 491 | vt 1.134775 0.067548 492 | vt 1.205324 0.202122 493 | vt 1.248768 0.348143 494 | vt 1.263437 0.500000 495 | vt 1.248768 0.651857 496 | vt 1.205324 0.797878 497 | vt 1.134775 0.932452 498 | vt 1.039832 1.050407 499 | vt 0.924143 1.147211 500 | vt 0.792155 1.219142 501 | vt 0.648940 1.263437 502 | vt 0.651857 -0.263437 503 | vt 0.797879 -0.219142 504 | vt 0.932453 -0.147210 505 | vt 1.050408 -0.050407 506 | vt 1.147211 0.067548 507 | vt 1.219142 0.202122 508 | vt 1.263437 0.348143 509 | vt 1.278394 0.500000 510 | vt 1.263437 0.651857 511 | vt 1.219142 0.797878 512 | vt 1.147211 0.932452 513 | vt 1.050408 1.050407 514 | vt 0.932453 1.147211 515 | vt 0.797879 1.219142 516 | vt 0.651857 1.263437 517 | vt 0.500000 1.278394 518 | s 0 519 | f 4/4 3/3 18/18 19/19 520 | f 12/12 11/11 26/26 27/27 521 | f 5/5 4/4 19/19 20/20 522 | f 13/13 12/12 27/27 28/28 523 | f 6/6 5/5 20/20 21/21 524 | f 14/14 13/13 28/28 29/29 525 | f 7/7 6/6 21/21 22/22 526 | f 15/15 14/14 29/29 30/30 527 | f 8/8 7/7 22/22 23/23 528 | f 1/1 211/211 16/16 529 | f 257/257 15/15 30/30 530 | f 9/9 8/8 23/23 24/24 531 | f 2/2 1/1 16/16 17/17 532 | f 10/10 9/9 24/24 25/25 533 | f 3/3 2/2 17/17 18/18 534 | f 11/11 10/10 25/25 26/26 535 | f 23/23 22/22 37/37 38/38 536 | f 16/16 211/211 31/31 537 | f 257/257 30/30 45/45 538 | f 24/24 23/23 38/38 39/39 539 | f 17/17 16/16 31/31 32/32 540 | f 25/25 24/24 39/39 40/40 541 | f 18/18 17/17 32/32 33/33 542 | f 26/26 25/25 40/40 41/41 543 | f 19/19 18/18 33/33 34/34 544 | f 27/27 26/26 41/41 42/42 545 | f 20/20 19/19 34/34 35/35 546 | f 28/28 27/27 42/42 43/43 547 | f 21/21 20/20 35/35 36/36 548 | f 29/29 28/28 43/43 44/44 549 | f 22/22 21/21 36/36 37/37 550 | f 30/30 29/29 44/44 45/45 551 | f 42/42 41/41 56/56 57/57 552 | f 35/35 34/34 49/49 50/50 553 | f 43/43 42/42 57/57 58/58 554 | f 36/36 35/35 50/50 51/51 555 | f 44/44 43/43 58/58 59/59 556 | f 37/37 36/36 51/51 52/52 557 | f 45/45 44/44 59/59 60/60 558 | f 38/38 37/37 52/52 53/53 559 | f 31/31 211/211 46/46 560 | f 257/257 45/45 60/60 561 | f 39/39 38/38 53/53 54/54 562 | f 32/32 31/31 46/46 47/47 563 | f 40/40 39/39 54/54 55/55 564 | f 33/33 32/32 47/47 48/48 565 | f 41/41 40/40 55/55 56/56 566 | f 34/34 33/33 48/48 49/49 567 | f 257/257 60/60 75/75 568 | f 54/54 53/53 68/68 69/69 569 | f 47/47 46/46 61/61 62/62 570 | f 55/55 54/54 69/69 70/70 571 | f 48/48 47/47 62/62 63/63 572 | f 56/56 55/55 70/70 71/71 573 | f 49/49 48/48 63/63 64/64 574 | f 57/57 56/56 71/71 72/72 575 | f 50/50 49/49 64/64 65/65 576 | f 58/58 57/57 72/72 73/73 577 | f 51/51 50/50 65/65 66/66 578 | f 59/59 58/58 73/73 74/74 579 | f 52/52 51/51 66/66 67/67 580 | f 60/60 59/59 74/74 75/75 581 | f 53/53 52/52 67/67 68/68 582 | f 46/46 211/211 61/61 583 | f 73/73 72/72 87/87 88/88 584 | f 66/66 65/65 80/80 81/81 585 | f 74/74 73/73 88/88 89/89 586 | f 67/67 66/66 81/81 82/82 587 | f 75/75 74/74 89/89 90/90 588 | f 68/68 67/67 82/82 83/83 589 | f 61/61 211/211 76/76 590 | f 257/257 75/75 90/90 591 | f 69/69 68/68 83/83 84/84 592 | f 62/62 61/61 76/76 77/77 593 | f 70/70 69/69 84/84 85/85 594 | f 63/63 62/62 77/77 78/78 595 | f 71/71 70/70 85/85 86/86 596 | f 64/64 63/63 78/78 79/79 597 | f 72/72 71/71 86/86 87/87 598 | f 65/65 64/64 79/79 80/80 599 | f 77/77 76/76 91/91 92/92 600 | f 85/85 84/84 99/99 100/100 601 | f 78/78 77/77 92/92 93/93 602 | f 86/86 85/85 100/100 101/101 603 | f 79/79 78/78 93/93 94/94 604 | f 87/87 86/86 101/101 102/102 605 | f 80/80 79/79 94/94 95/95 606 | f 88/88 87/87 102/102 103/103 607 | f 81/81 80/80 95/95 96/96 608 | f 89/89 88/88 103/103 104/104 609 | f 82/82 81/81 96/96 97/97 610 | f 90/90 89/89 104/104 105/105 611 | f 83/83 82/82 97/97 98/98 612 | f 76/76 211/211 91/91 613 | f 257/257 90/90 105/105 614 | f 84/84 83/83 98/98 99/99 615 | f 96/96 95/95 110/110 111/111 616 | f 104/104 103/103 118/118 119/119 617 | f 97/97 96/96 111/111 112/112 618 | f 105/105 104/104 119/119 120/120 619 | f 98/98 97/97 112/112 113/113 620 | f 91/91 211/211 106/106 621 | f 257/257 105/105 120/120 622 | f 99/99 98/98 113/113 114/114 623 | f 92/92 91/91 106/106 107/107 624 | f 100/100 99/99 114/114 115/115 625 | f 93/93 92/92 107/107 108/108 626 | f 101/101 100/100 115/115 116/116 627 | f 94/94 93/93 108/108 109/109 628 | f 102/102 101/101 116/116 117/117 629 | f 95/95 94/94 109/109 110/110 630 | f 103/103 102/102 117/117 118/118 631 | f 115/115 114/114 129/129 130/130 632 | f 108/108 107/107 122/122 123/123 633 | f 116/116 115/115 130/130 131/131 634 | f 109/109 108/108 123/123 124/124 635 | f 117/117 116/116 131/131 132/132 636 | f 110/110 109/109 124/124 125/125 637 | f 118/118 117/117 132/132 133/133 638 | f 111/111 110/110 125/125 126/126 639 | f 119/119 118/118 133/133 134/134 640 | f 112/112 111/111 126/126 127/127 641 | f 120/120 119/119 134/134 135/135 642 | f 113/113 112/112 127/127 128/128 643 | f 106/106 211/211 121/121 644 | f 257/257 120/120 135/135 645 | f 114/114 113/113 128/128 129/129 646 | f 107/107 106/106 121/121 122/122 647 | f 134/134 133/133 148/148 149/149 648 | f 127/127 126/126 141/141 142/142 649 | f 135/135 134/134 149/149 150/150 650 | f 128/128 127/127 142/142 143/143 651 | f 121/121 211/211 136/136 652 | f 257/257 135/135 150/150 653 | f 129/129 128/128 143/143 144/144 654 | f 122/122 121/121 136/136 137/137 655 | f 130/130 129/129 144/144 145/145 656 | f 123/123 122/122 137/137 138/138 657 | f 131/131 130/130 145/145 146/146 658 | f 124/124 123/123 138/138 139/139 659 | f 132/132 131/131 146/146 147/147 660 | f 125/125 124/124 139/139 140/140 661 | f 133/133 132/132 147/147 148/148 662 | f 126/126 125/125 140/140 141/141 663 | f 138/138 137/137 152/152 153/153 664 | f 146/146 145/145 160/160 161/161 665 | f 139/139 138/138 153/153 154/154 666 | f 147/147 146/146 161/161 162/162 667 | f 140/140 139/139 154/154 155/155 668 | f 148/148 147/147 162/162 163/163 669 | f 141/141 140/140 155/155 156/156 670 | f 149/149 148/148 163/163 164/164 671 | f 142/142 141/141 156/156 157/157 672 | f 150/150 149/149 164/164 165/165 673 | f 143/143 142/142 157/157 158/158 674 | f 136/136 211/211 151/151 675 | f 257/257 150/150 165/165 676 | f 144/144 143/143 158/158 159/159 677 | f 137/137 136/136 151/151 152/152 678 | f 145/145 144/144 159/159 160/160 679 | f 157/157 156/156 171/171 172/172 680 | f 165/165 164/164 179/179 180/180 681 | f 158/158 157/157 172/172 173/173 682 | f 151/151 211/211 166/166 683 | f 257/257 165/165 180/180 684 | f 159/159 158/158 173/173 174/174 685 | f 152/152 151/151 166/166 167/167 686 | f 160/160 159/159 174/174 175/175 687 | f 153/153 152/152 167/167 168/168 688 | f 161/161 160/160 175/175 176/176 689 | f 154/154 153/153 168/168 169/169 690 | f 162/162 161/161 176/176 177/177 691 | f 155/155 154/154 169/169 170/170 692 | f 163/163 162/162 177/177 178/178 693 | f 156/156 155/155 170/170 171/171 694 | f 164/164 163/163 178/178 179/179 695 | f 176/176 175/175 190/190 191/191 696 | f 169/169 168/168 183/183 184/184 697 | f 177/177 176/176 191/191 192/192 698 | f 170/170 169/169 184/184 185/185 699 | f 178/178 177/177 192/192 193/193 700 | f 171/171 170/170 185/185 186/186 701 | f 179/179 178/178 193/193 194/194 702 | f 172/172 171/171 186/186 187/187 703 | f 180/180 179/179 194/194 195/195 704 | f 173/173 172/172 187/187 188/188 705 | f 166/166 211/211 181/181 706 | f 257/257 180/180 195/195 707 | f 174/174 173/173 188/188 189/189 708 | f 167/167 166/166 181/181 182/182 709 | f 175/175 174/174 189/189 190/190 710 | f 168/168 167/167 182/182 183/183 711 | f 195/195 194/194 209/209 210/210 712 | f 188/188 187/187 202/202 203/203 713 | f 181/181 211/211 196/196 714 | f 257/257 195/195 210/210 715 | f 189/189 188/188 203/203 204/204 716 | f 182/182 181/181 196/196 197/197 717 | f 190/190 189/189 204/204 205/205 718 | f 183/183 182/182 197/197 198/198 719 | f 191/191 190/190 205/205 206/206 720 | f 184/184 183/183 198/198 199/199 721 | f 192/192 191/191 206/206 207/207 722 | f 185/185 184/184 199/199 200/200 723 | f 193/193 192/192 207/207 208/208 724 | f 186/186 185/185 200/200 201/201 725 | f 194/194 193/193 208/208 209/209 726 | f 187/187 186/186 201/201 202/202 727 | f 207/207 206/206 222/222 223/223 728 | f 200/200 199/199 215/215 216/216 729 | f 208/208 207/207 223/223 224/224 730 | f 201/201 200/200 216/216 217/217 731 | f 209/209 208/208 224/224 225/225 732 | f 202/202 201/201 217/217 218/218 733 | f 210/210 209/209 225/225 226/226 734 | f 203/203 202/202 218/218 219/219 735 | f 196/196 211/211 212/212 736 | f 257/257 210/210 226/226 737 | f 204/204 203/203 219/219 220/220 738 | f 197/197 196/196 212/212 213/213 739 | f 205/205 204/204 220/220 221/221 740 | f 198/198 197/197 213/213 214/214 741 | f 206/206 205/205 221/221 222/222 742 | f 199/199 198/198 214/214 215/215 743 | f 212/212 211/211 227/227 744 | f 257/257 226/226 241/241 745 | f 220/220 219/219 234/234 235/235 746 | f 213/213 212/212 227/227 228/228 747 | f 221/221 220/220 235/235 236/236 748 | f 214/214 213/213 228/228 229/229 749 | f 222/222 221/221 236/236 237/237 750 | f 215/215 214/214 229/229 230/230 751 | f 223/223 222/222 237/237 238/238 752 | f 216/216 215/215 230/230 231/231 753 | f 224/224 223/223 238/238 239/239 754 | f 217/217 216/216 231/231 232/232 755 | f 225/225 224/224 239/239 240/240 756 | f 218/218 217/217 232/232 233/233 757 | f 226/226 225/225 240/240 241/241 758 | f 219/219 218/218 233/233 234/234 759 | f 231/231 230/230 245/245 246/246 760 | f 239/239 238/238 253/253 254/254 761 | f 232/232 231/231 246/246 247/247 762 | f 240/240 239/239 254/254 255/255 763 | f 233/233 232/232 247/247 248/248 764 | f 241/241 240/240 255/255 256/256 765 | f 234/234 233/233 248/248 249/249 766 | f 227/227 211/211 242/242 767 | f 257/257 241/241 256/256 768 | f 235/235 234/234 249/249 250/250 769 | f 228/228 227/227 242/242 243/243 770 | f 236/236 235/235 250/250 251/251 771 | f 229/229 228/228 243/243 244/244 772 | f 237/237 236/236 251/251 252/252 773 | f 230/230 229/229 244/244 245/245 774 | f 238/238 237/237 252/252 253/253 775 | -------------------------------------------------------------------------------- /assets/blank_grey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artumino/VRScreenCap/a2866bd353f237b9f65969a879f78366b72c6a57/assets/blank_grey.png -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artumino/VRScreenCap/a2866bd353f237b9f65969a879f78366b72c6a57/assets/icon.ico -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | use std::env::var; 2 | use std::io; 3 | #[cfg(windows)] 4 | use winres::WindowsResource; 5 | 6 | fn main() -> io::Result<()> { 7 | if var("CARGO_CFG_TARGET_OS") 8 | .map(|target_os| target_os == "windows") 9 | .unwrap_or(false) 10 | { 11 | #[cfg(windows)] 12 | { 13 | WindowsResource::new() 14 | // This path can be absolute, or relative to your crate root. 15 | .set_icon_with_id("assets/icon.ico", "tray-icon") 16 | .compile()?; 17 | } 18 | } 19 | 20 | // On Android, we must ensure that we're dynamically linking against the C++ standard library. 21 | // For more details, see https://github.com/rust-windowing/android-ndk-rs/issues/167 22 | if var("TARGET") 23 | .map(|target| target == "aarch64-linux-android") 24 | .unwrap_or(false) 25 | { 26 | println!("cargo:rustc-link-lib=dylib=c++"); 27 | } 28 | 29 | Ok(()) 30 | } 31 | -------------------------------------------------------------------------------- /libs/arm64-v8a/libopenxr_loader.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artumino/VRScreenCap/a2866bd353f237b9f65969a879f78366b72c6a57/libs/arm64-v8a/libopenxr_loader.so -------------------------------------------------------------------------------- /src/blit.wgsl: -------------------------------------------------------------------------------- 1 | struct VertexOutput { 2 | @builtin(position) clip_position: vec4, 3 | @location(0) tex_coords: vec2, 4 | }; 5 | 6 | struct TemporalBlurParams { 7 | jitter: vec2, 8 | scale: vec2, 9 | resolution: vec2, 10 | history_decay: f32, 11 | }; 12 | 13 | @vertex 14 | fn vs_main( 15 | @builtin(vertex_index) vertex_index: u32 16 | ) -> VertexOutput { 17 | var out: VertexOutput; 18 | let x = i32(vertex_index) / 2; 19 | let y = i32(vertex_index) & 1; 20 | let tc = vec2( 21 | f32(x) * 2.0, 22 | f32(y) * 2.0 23 | ); 24 | out.clip_position = vec4( 25 | tc.x * 2.0 - 1.0, 26 | 1.0 - tc.y * 2.0, 27 | 0.0, 1.0 28 | ); 29 | out.tex_coords = tc; 30 | return out; 31 | } 32 | 33 | @group(0) @binding(0) 34 | var t_diffuse: texture_2d; 35 | @group(0) @binding(1) 36 | var s_diffuse: sampler; 37 | @group(1) @binding(0) 38 | var t_history: texture_2d; 39 | @group(1) @binding(1) 40 | var s_history: sampler; 41 | @group(2) @binding(0) 42 | var blur_params: TemporalBlurParams; 43 | 44 | struct TemporalOutput { 45 | @location(0) color: vec4, 46 | @location(1) history: vec4, 47 | } 48 | @fragment 49 | fn temporal_fs_main(in: VertexOutput) -> TemporalOutput { 50 | var out: TemporalOutput; 51 | let current_color = textureSample(t_diffuse, s_diffuse, in.tex_coords + blur_params.jitter * blur_params.scale); 52 | let history_color = textureSample(t_history, s_history, in.tex_coords); 53 | let mixed_color = mix(current_color, history_color, blur_params.history_decay).rgb; 54 | let brightness = smoothstep(0.05, 0.35, dot(mixed_color.rgb, vec3(0.2126, 0.7152, 0.0722))); 55 | out.color = vec4(mixed_color * brightness, 1.0); 56 | out.history = vec4(mixed_color.rgb, 1.0); 57 | return out; 58 | } -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use std::sync::mpsc::{channel, Receiver}; 2 | 3 | use clap::Parser; 4 | use notify::{RecommendedWatcher, RecursiveMode, Watcher}; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | #[repr(C)] 8 | #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] 9 | pub struct ScreenParamsUniform { 10 | x_curvature: f32, 11 | y_curvature: f32, 12 | eye_offset: f32, 13 | y_offset: f32, 14 | x_offset: f32, 15 | aspect_ratio: f32, 16 | screen_width: u32, 17 | ambient_width: u32, 18 | } 19 | 20 | #[derive(Parser, Serialize, Deserialize, Debug, Clone)] 21 | #[serde(default)] 22 | pub struct AppConfig { 23 | // Maximum depth at the center in meters, default: 0.4, usage: --x-curvature=0.4 24 | #[clap(short, long, value_parser, default_value_t = 0.4)] 25 | pub x_curvature: f32, 26 | // Maximum depth at the center in meters, default: 0.08, usage: --y-curvature=0.08 27 | #[clap(long, value_parser, default_value_t = 0.08)] 28 | pub y_curvature: f32, 29 | // default: true, usage: --swap-eyes=true (geo11 has them swapped, might be GPU dependent) 30 | #[clap(long, value_parser, default_value_t = true)] 31 | pub swap_eyes: bool, 32 | // default: false, usage: --flip-x=false 33 | #[clap(long, value_parser, default_value_t = false)] 34 | pub flip_x: bool, 35 | // default: false, usage: --flip-y=false 36 | #[clap(long, value_parser, default_value_t = false)] 37 | pub flip_y: bool, 38 | // Distance from user in meters, default: 20.0, usage: --distance=20.0 39 | #[clap(short, long, value_parser, default_value_t = 20.0)] 40 | pub distance: f32, 41 | // Screen scaling factor (screen width in meters), default: 40.0, usage: --scale=40.0 42 | #[clap(short, long, value_parser, default_value_t = 40.0)] 43 | pub scale: f32, 44 | // Wether ambient light should be used, default: false, usage: --ambient=true 45 | #[clap(short, long, value_parser, default_value_t = false)] 46 | pub ambient: bool, 47 | // Configuration file to watch for live changes, usage: --config-file=config.json 48 | #[clap(short, long, value_parser)] 49 | pub config_file: Option, 50 | } 51 | 52 | impl AppConfig { 53 | pub fn uniform( 54 | &self, 55 | aspect_ratio: f32, 56 | screen_width: u32, 57 | ambient_width: u32, 58 | ) -> ScreenParamsUniform { 59 | ScreenParamsUniform { 60 | x_curvature: self.x_curvature, 61 | y_curvature: self.y_curvature, 62 | eye_offset: match self.swap_eyes { 63 | true => 1.0, 64 | _ => 0.0, 65 | }, 66 | y_offset: match self.flip_y { 67 | true => 1.0, 68 | _ => 0.0, 69 | }, 70 | x_offset: match self.flip_x { 71 | true => 1.0, 72 | _ => 0.0, 73 | }, 74 | aspect_ratio, 75 | screen_width, 76 | ambient_width, 77 | } 78 | } 79 | } 80 | 81 | impl Default for AppConfig { 82 | fn default() -> Self { 83 | Self { 84 | x_curvature: 0.4, 85 | y_curvature: 0.08, 86 | swap_eyes: true, 87 | flip_x: false, 88 | flip_y: false, 89 | distance: 20.0, 90 | scale: 40.0, 91 | config_file: None, 92 | ambient: false, 93 | } 94 | } 95 | } 96 | 97 | //Blur Settings 98 | 99 | #[repr(C)] 100 | #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] 101 | pub struct TemporalBlurParamsUniform { 102 | jitter: [f32; 2], 103 | scale: [f32; 2], 104 | resolution: [f32; 2], 105 | history_decay: f32, 106 | _padding: f32, 107 | } 108 | 109 | pub struct TemporalBlurParams { 110 | pub jitter: [f32; 2], 111 | pub scale: [f32; 2], 112 | pub resolution: [f32; 2], 113 | pub history_decay: f32, 114 | } 115 | 116 | impl TemporalBlurParams { 117 | pub fn uniform(&self) -> TemporalBlurParamsUniform { 118 | TemporalBlurParamsUniform { 119 | jitter: self.jitter, 120 | scale: self.scale, 121 | resolution: self.resolution, 122 | history_decay: self.history_decay, 123 | _padding: 0.0, 124 | } 125 | } 126 | } 127 | 128 | //Notifications 129 | 130 | pub struct ConfigContext { 131 | pub config_notifier: Option>>, 132 | pub config_watcher: Option, 133 | pub config_file: Option, 134 | pub last_config: Option, 135 | } 136 | 137 | impl ConfigContext { 138 | pub fn try_setup() -> anyhow::Result> { 139 | let config = AppConfig::parse(); 140 | if let Some(config_file_path) = config.config_file { 141 | log::info!("Using config file: {}", config_file_path); 142 | let params = serde_json::from_reader(std::io::BufReader::new(std::fs::File::open( 143 | config_file_path.clone(), 144 | )?))?; 145 | let (tx, rx) = channel(); 146 | let mut watcher = notify::RecommendedWatcher::new(tx, notify::Config::default())?; 147 | watcher.watch( 148 | std::path::Path::new(&config_file_path), 149 | RecursiveMode::NonRecursive, 150 | )?; 151 | return Ok(Some(ConfigContext { 152 | config_notifier: Some(rx), 153 | config_watcher: Some(watcher), 154 | config_file: Some(config_file_path), 155 | last_config: Some(params), 156 | })); 157 | } 158 | Ok(None) 159 | } 160 | 161 | pub fn update_config(&mut self) -> anyhow::Result<()> { 162 | if let Some(config_file_path) = self.config_file.clone() { 163 | let params = serde_json::from_reader(std::io::BufReader::new(std::fs::File::open( 164 | config_file_path, 165 | )?))?; 166 | self.last_config = Some(params); 167 | } 168 | Ok(()) 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/conversions.rs: -------------------------------------------------------------------------------- 1 | use ash::vk; 2 | use wgpu::{Device, TextureDescriptor, TextureFormat}; 3 | use wgpu_hal::api::Vulkan; 4 | #[cfg(target_os = "windows")] 5 | use windows::Win32::Graphics::Dxgi::Common::*; 6 | 7 | pub fn vulkan_image_to_texture( 8 | device: &Device, 9 | image: vk::Image, 10 | tex_desc: TextureDescriptor, 11 | hal_tex_desc: wgpu_hal::TextureDescriptor, 12 | ) -> wgpu::Texture { 13 | let texture = unsafe { 14 | ::Device::texture_from_raw( 15 | image, 16 | &hal_tex_desc, 17 | None, 18 | ) 19 | }; 20 | 21 | unsafe { device.create_texture_from_hal::(texture, &tex_desc) } 22 | } 23 | 24 | #[cfg(target_os = "windows")] 25 | pub fn unmap_texture_format(format: DXGI_FORMAT) -> TextureFormat { 26 | match format { 27 | DXGI_FORMAT_R8_UNORM => TextureFormat::R8Unorm, 28 | DXGI_FORMAT_R8_SNORM => TextureFormat::R8Snorm, 29 | DXGI_FORMAT_R8_UINT => TextureFormat::R8Uint, 30 | DXGI_FORMAT_R8_SINT => TextureFormat::R8Sint, 31 | DXGI_FORMAT_R16_UINT => TextureFormat::R16Uint, 32 | DXGI_FORMAT_R16_SINT => TextureFormat::R16Sint, 33 | DXGI_FORMAT_R16_UNORM => TextureFormat::R16Unorm, 34 | DXGI_FORMAT_R16_SNORM => TextureFormat::R16Snorm, 35 | DXGI_FORMAT_R16_FLOAT => TextureFormat::R16Float, 36 | DXGI_FORMAT_R8G8_UNORM => TextureFormat::Rg8Unorm, 37 | DXGI_FORMAT_R8G8_SNORM => TextureFormat::Rg8Snorm, 38 | DXGI_FORMAT_R8G8_UINT => TextureFormat::Rg8Uint, 39 | DXGI_FORMAT_R8G8_SINT => TextureFormat::Rg8Sint, 40 | DXGI_FORMAT_R16G16_UNORM => TextureFormat::Rg16Unorm, 41 | DXGI_FORMAT_R16G16_SNORM => TextureFormat::Rg16Snorm, 42 | DXGI_FORMAT_R32_UINT => TextureFormat::R32Uint, 43 | DXGI_FORMAT_R32_SINT => TextureFormat::R32Sint, 44 | DXGI_FORMAT_R32_FLOAT => TextureFormat::R32Float, 45 | DXGI_FORMAT_R16G16_UINT => TextureFormat::Rg16Uint, 46 | DXGI_FORMAT_R16G16_SINT => TextureFormat::Rg16Sint, 47 | DXGI_FORMAT_R16G16_FLOAT => TextureFormat::Rg16Float, 48 | DXGI_FORMAT_R8G8B8A8_TYPELESS => TextureFormat::Rgba8Unorm, 49 | DXGI_FORMAT_R8G8B8A8_UNORM => TextureFormat::Rgba8Unorm, 50 | DXGI_FORMAT_R8G8B8A8_UNORM_SRGB => TextureFormat::Rgba8UnormSrgb, 51 | DXGI_FORMAT_B8G8R8A8_UNORM_SRGB => TextureFormat::Bgra8UnormSrgb, 52 | DXGI_FORMAT_R8G8B8A8_SNORM => TextureFormat::Rgba8Snorm, 53 | DXGI_FORMAT_B8G8R8A8_UNORM => TextureFormat::Bgra8Unorm, 54 | DXGI_FORMAT_R8G8B8A8_UINT => TextureFormat::Rgba8Uint, 55 | DXGI_FORMAT_R8G8B8A8_SINT => TextureFormat::Rgba8Sint, 56 | DXGI_FORMAT_R10G10B10A2_UNORM => TextureFormat::Rgb10a2Unorm, 57 | DXGI_FORMAT_R11G11B10_FLOAT => TextureFormat::Rg11b10Float, 58 | DXGI_FORMAT_R32G32_UINT => TextureFormat::Rg32Uint, 59 | DXGI_FORMAT_R32G32_SINT => TextureFormat::Rg32Sint, 60 | DXGI_FORMAT_R32G32_FLOAT => TextureFormat::Rg32Float, 61 | DXGI_FORMAT_R16G16B16A16_UINT => TextureFormat::Rgba16Uint, 62 | DXGI_FORMAT_R16G16B16A16_SINT => TextureFormat::Rgba16Sint, 63 | DXGI_FORMAT_R16G16B16A16_UNORM => TextureFormat::Rgba16Unorm, 64 | DXGI_FORMAT_R16G16B16A16_SNORM => TextureFormat::Rgba16Snorm, 65 | DXGI_FORMAT_R16G16B16A16_FLOAT => TextureFormat::Rgba16Float, 66 | DXGI_FORMAT_R32G32B32A32_UINT => TextureFormat::Rgba32Uint, 67 | DXGI_FORMAT_R32G32B32A32_SINT => TextureFormat::Rgba32Sint, 68 | DXGI_FORMAT_R32G32B32A32_FLOAT => TextureFormat::Rgba32Float, 69 | DXGI_FORMAT_D32_FLOAT => TextureFormat::Depth32Float, 70 | DXGI_FORMAT_D32_FLOAT_S8X24_UINT => TextureFormat::Depth32FloatStencil8, 71 | DXGI_FORMAT_D24_UNORM_S8_UINT => TextureFormat::Depth24PlusStencil8, 72 | DXGI_FORMAT_R9G9B9E5_SHAREDEXP => TextureFormat::Rgb9e5Ufloat, 73 | DXGI_FORMAT_BC1_UNORM => TextureFormat::Bc1RgbaUnorm, 74 | DXGI_FORMAT_BC1_UNORM_SRGB => TextureFormat::Bc1RgbaUnormSrgb, 75 | DXGI_FORMAT_BC2_UNORM => TextureFormat::Bc2RgbaUnorm, 76 | DXGI_FORMAT_BC2_UNORM_SRGB => TextureFormat::Bc2RgbaUnormSrgb, 77 | DXGI_FORMAT_BC3_UNORM => TextureFormat::Bc3RgbaUnorm, 78 | DXGI_FORMAT_BC3_UNORM_SRGB => TextureFormat::Bc3RgbaUnormSrgb, 79 | DXGI_FORMAT_BC4_UNORM => TextureFormat::Bc4RUnorm, 80 | DXGI_FORMAT_BC4_SNORM => TextureFormat::Bc4RSnorm, 81 | DXGI_FORMAT_BC5_UNORM => TextureFormat::Bc5RgUnorm, 82 | DXGI_FORMAT_BC5_SNORM => TextureFormat::Bc5RgSnorm, 83 | DXGI_FORMAT_BC6H_UF16 => TextureFormat::Bc6hRgbUfloat, 84 | DXGI_FORMAT_BC6H_SF16 => TextureFormat::Bc6hRgbFloat, 85 | DXGI_FORMAT_BC7_UNORM => TextureFormat::Bc7RgbaUnorm, 86 | DXGI_FORMAT_BC7_UNORM_SRGB => TextureFormat::Bc7RgbaUnormSrgb, 87 | DXGI_FORMAT_D16_UNORM => TextureFormat::Depth16Unorm, 88 | _ => panic!("Unsupported texture format: {:?}", format), 89 | } 90 | } 91 | 92 | pub fn map_texture_format(format: wgpu::TextureFormat) -> vk::Format { 93 | use ash::vk::Format as F; 94 | use wgpu::TextureFormat as Tf; 95 | use wgpu::{AstcBlock, AstcChannel}; 96 | match format { 97 | Tf::R8Unorm => F::R8_UNORM, 98 | Tf::R8Snorm => F::R8_SNORM, 99 | Tf::R8Uint => F::R8_UINT, 100 | Tf::R8Sint => F::R8_SINT, 101 | Tf::R16Uint => F::R16_UINT, 102 | Tf::R16Sint => F::R16_SINT, 103 | Tf::R16Unorm => F::R16_UNORM, 104 | Tf::R16Snorm => F::R16_SNORM, 105 | Tf::R16Float => F::R16_SFLOAT, 106 | Tf::Rg8Unorm => F::R8G8_UNORM, 107 | Tf::Rg8Snorm => F::R8G8_SNORM, 108 | Tf::Rg8Uint => F::R8G8_UINT, 109 | Tf::Rg8Sint => F::R8G8_SINT, 110 | Tf::Rg16Unorm => F::R16G16_UNORM, 111 | Tf::Rg16Snorm => F::R16G16_SNORM, 112 | Tf::R32Uint => F::R32_UINT, 113 | Tf::R32Sint => F::R32_SINT, 114 | Tf::R32Float => F::R32_SFLOAT, 115 | Tf::Rg16Uint => F::R16G16_UINT, 116 | Tf::Rg16Sint => F::R16G16_SINT, 117 | Tf::Rg16Float => F::R16G16_SFLOAT, 118 | Tf::Rgba8Unorm => F::R8G8B8A8_UNORM, 119 | Tf::Rgba8UnormSrgb => F::R8G8B8A8_SRGB, 120 | Tf::Bgra8UnormSrgb => F::B8G8R8A8_SRGB, 121 | Tf::Rgba8Snorm => F::R8G8B8A8_SNORM, 122 | Tf::Bgra8Unorm => F::B8G8R8A8_UNORM, 123 | Tf::Rgba8Uint => F::R8G8B8A8_UINT, 124 | Tf::Rgba8Sint => F::R8G8B8A8_SINT, 125 | Tf::Rgb10a2Unorm => F::A2B10G10R10_UNORM_PACK32, 126 | Tf::Rg11b10Float => F::B10G11R11_UFLOAT_PACK32, 127 | Tf::Rg32Uint => F::R32G32_UINT, 128 | Tf::Rg32Sint => F::R32G32_SINT, 129 | Tf::Rg32Float => F::R32G32_SFLOAT, 130 | Tf::Rgba16Uint => F::R16G16B16A16_UINT, 131 | Tf::Rgba16Sint => F::R16G16B16A16_SINT, 132 | Tf::Rgba16Unorm => F::R16G16B16A16_UNORM, 133 | Tf::Rgba16Snorm => F::R16G16B16A16_SNORM, 134 | Tf::Rgba16Float => F::R16G16B16A16_SFLOAT, 135 | Tf::Rgba32Uint => F::R32G32B32A32_UINT, 136 | Tf::Rgba32Sint => F::R32G32B32A32_SINT, 137 | Tf::Rgba32Float => F::R32G32B32A32_SFLOAT, 138 | Tf::Depth32Float => F::D32_SFLOAT, 139 | Tf::Depth32FloatStencil8 => F::D32_SFLOAT_S8_UINT, 140 | Tf::Depth24Plus => F::D32_SFLOAT, 141 | Tf::Depth24PlusStencil8 => F::D24_UNORM_S8_UINT, 142 | Tf::Depth16Unorm => F::D16_UNORM, 143 | Tf::Rgb9e5Ufloat => F::E5B9G9R9_UFLOAT_PACK32, 144 | Tf::Bc1RgbaUnorm => F::BC1_RGBA_UNORM_BLOCK, 145 | Tf::Bc1RgbaUnormSrgb => F::BC1_RGBA_SRGB_BLOCK, 146 | Tf::Bc2RgbaUnorm => F::BC2_UNORM_BLOCK, 147 | Tf::Bc2RgbaUnormSrgb => F::BC2_SRGB_BLOCK, 148 | Tf::Bc3RgbaUnorm => F::BC3_UNORM_BLOCK, 149 | Tf::Bc3RgbaUnormSrgb => F::BC3_SRGB_BLOCK, 150 | Tf::Bc4RUnorm => F::BC4_UNORM_BLOCK, 151 | Tf::Bc4RSnorm => F::BC4_SNORM_BLOCK, 152 | Tf::Bc5RgUnorm => F::BC5_UNORM_BLOCK, 153 | Tf::Bc5RgSnorm => F::BC5_SNORM_BLOCK, 154 | Tf::Bc6hRgbUfloat => F::BC6H_UFLOAT_BLOCK, 155 | Tf::Bc6hRgbFloat => F::BC6H_SFLOAT_BLOCK, 156 | Tf::Bc7RgbaUnorm => F::BC7_UNORM_BLOCK, 157 | Tf::Bc7RgbaUnormSrgb => F::BC7_SRGB_BLOCK, 158 | Tf::Etc2Rgb8Unorm => F::ETC2_R8G8B8_UNORM_BLOCK, 159 | Tf::Etc2Rgb8UnormSrgb => F::ETC2_R8G8B8_SRGB_BLOCK, 160 | Tf::Etc2Rgb8A1Unorm => F::ETC2_R8G8B8A1_UNORM_BLOCK, 161 | Tf::Etc2Rgb8A1UnormSrgb => F::ETC2_R8G8B8A1_SRGB_BLOCK, 162 | Tf::Etc2Rgba8Unorm => F::ETC2_R8G8B8A8_UNORM_BLOCK, 163 | Tf::Etc2Rgba8UnormSrgb => F::ETC2_R8G8B8A8_SRGB_BLOCK, 164 | Tf::EacR11Unorm => F::EAC_R11_UNORM_BLOCK, 165 | Tf::EacR11Snorm => F::EAC_R11_SNORM_BLOCK, 166 | Tf::EacRg11Unorm => F::EAC_R11G11_UNORM_BLOCK, 167 | Tf::EacRg11Snorm => F::EAC_R11G11_SNORM_BLOCK, 168 | Tf::Astc { block, channel } => match channel { 169 | AstcChannel::Unorm => match block { 170 | AstcBlock::B4x4 => F::ASTC_4X4_UNORM_BLOCK, 171 | AstcBlock::B5x4 => F::ASTC_5X4_UNORM_BLOCK, 172 | AstcBlock::B5x5 => F::ASTC_5X5_UNORM_BLOCK, 173 | AstcBlock::B6x5 => F::ASTC_6X5_UNORM_BLOCK, 174 | AstcBlock::B6x6 => F::ASTC_6X6_UNORM_BLOCK, 175 | AstcBlock::B8x5 => F::ASTC_8X5_UNORM_BLOCK, 176 | AstcBlock::B8x6 => F::ASTC_8X6_UNORM_BLOCK, 177 | AstcBlock::B8x8 => F::ASTC_8X8_UNORM_BLOCK, 178 | AstcBlock::B10x5 => F::ASTC_10X5_UNORM_BLOCK, 179 | AstcBlock::B10x6 => F::ASTC_10X6_UNORM_BLOCK, 180 | AstcBlock::B10x8 => F::ASTC_10X8_UNORM_BLOCK, 181 | AstcBlock::B10x10 => F::ASTC_10X10_UNORM_BLOCK, 182 | AstcBlock::B12x10 => F::ASTC_12X10_UNORM_BLOCK, 183 | AstcBlock::B12x12 => F::ASTC_12X12_UNORM_BLOCK, 184 | }, 185 | AstcChannel::UnormSrgb => match block { 186 | AstcBlock::B4x4 => F::ASTC_4X4_SRGB_BLOCK, 187 | AstcBlock::B5x4 => F::ASTC_5X4_SRGB_BLOCK, 188 | AstcBlock::B5x5 => F::ASTC_5X5_SRGB_BLOCK, 189 | AstcBlock::B6x5 => F::ASTC_6X5_SRGB_BLOCK, 190 | AstcBlock::B6x6 => F::ASTC_6X6_SRGB_BLOCK, 191 | AstcBlock::B8x5 => F::ASTC_8X5_SRGB_BLOCK, 192 | AstcBlock::B8x6 => F::ASTC_8X6_SRGB_BLOCK, 193 | AstcBlock::B8x8 => F::ASTC_8X8_SRGB_BLOCK, 194 | AstcBlock::B10x5 => F::ASTC_10X5_SRGB_BLOCK, 195 | AstcBlock::B10x6 => F::ASTC_10X6_SRGB_BLOCK, 196 | AstcBlock::B10x8 => F::ASTC_10X8_SRGB_BLOCK, 197 | AstcBlock::B10x10 => F::ASTC_10X10_SRGB_BLOCK, 198 | AstcBlock::B12x10 => F::ASTC_12X10_SRGB_BLOCK, 199 | AstcBlock::B12x12 => F::ASTC_12X12_SRGB_BLOCK, 200 | }, 201 | AstcChannel::Hdr => match block { 202 | AstcBlock::B4x4 => F::ASTC_4X4_SFLOAT_BLOCK_EXT, 203 | AstcBlock::B5x4 => F::ASTC_5X4_SFLOAT_BLOCK_EXT, 204 | AstcBlock::B5x5 => F::ASTC_5X5_SFLOAT_BLOCK_EXT, 205 | AstcBlock::B6x5 => F::ASTC_6X5_SFLOAT_BLOCK_EXT, 206 | AstcBlock::B6x6 => F::ASTC_6X6_SFLOAT_BLOCK_EXT, 207 | AstcBlock::B8x5 => F::ASTC_8X5_SFLOAT_BLOCK_EXT, 208 | AstcBlock::B8x6 => F::ASTC_8X6_SFLOAT_BLOCK_EXT, 209 | AstcBlock::B8x8 => F::ASTC_8X8_SFLOAT_BLOCK_EXT, 210 | AstcBlock::B10x5 => F::ASTC_10X5_SFLOAT_BLOCK_EXT, 211 | AstcBlock::B10x6 => F::ASTC_10X6_SFLOAT_BLOCK_EXT, 212 | AstcBlock::B10x8 => F::ASTC_10X8_SFLOAT_BLOCK_EXT, 213 | AstcBlock::B10x10 => F::ASTC_10X10_SFLOAT_BLOCK_EXT, 214 | AstcBlock::B12x10 => F::ASTC_12X10_SFLOAT_BLOCK_EXT, 215 | AstcBlock::B12x12 => F::ASTC_12X12_SFLOAT_BLOCK_EXT, 216 | }, 217 | }, 218 | Tf::Stencil8 => F::S8_UINT, 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/engine.rs: -------------------------------------------------------------------------------- 1 | use ash::vk; 2 | 3 | pub mod camera; 4 | pub mod entity; 5 | pub mod geometry; 6 | pub mod input; 7 | pub mod jitter; 8 | pub mod screen; 9 | pub mod texture; 10 | pub mod vr; 11 | 12 | pub const TARGET_VULKAN_VERSION: u32 = vk::make_api_version(0, 1, 1, 0); 13 | 14 | //TODO: Actually modularize engine... 15 | 16 | pub struct WgpuContext { 17 | pub vk_entry: ash::Entry, 18 | pub vk_instance_ptr: u64, 19 | pub vk_phys_device_ptr: u64, 20 | pub vk_device_ptr: u64, 21 | pub queue_index: u32, 22 | pub instance: wgpu::Instance, 23 | pub device: wgpu::Device, 24 | pub physical_device: wgpu::Adapter, 25 | pub queue: wgpu::Queue, 26 | } 27 | 28 | pub trait WgpuLoader { 29 | fn load_wgpu(&mut self) -> anyhow::Result; 30 | } 31 | 32 | pub trait WgpuRunner { 33 | fn run(&mut self, wgpu_context: &WgpuContext); 34 | } 35 | -------------------------------------------------------------------------------- /src/engine/camera.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Context; 2 | use cgmath::{Matrix4, Rad, SquareMatrix}; 3 | use openxr::Fovf; 4 | 5 | use super::entity::Entity; 6 | 7 | pub struct Camera { 8 | pub entity: Entity, 9 | pub projection: Matrix4, 10 | pub near: f32, 11 | pub far: f32, 12 | } 13 | 14 | impl Default for Camera { 15 | fn default() -> Self { 16 | Self { 17 | entity: Default::default(), 18 | projection: Matrix4::::identity(), 19 | near: 0.1, 20 | far: 3000.0, 21 | } 22 | } 23 | } 24 | 25 | impl Camera { 26 | pub fn update_projection_from_tangents(&mut self, fov: Fovf) { 27 | let tan_right = fov.angle_right.tan(); 28 | let tan_left = fov.angle_left.tan(); 29 | let tan_top = fov.angle_up.tan(); 30 | let tan_bottom = fov.angle_down.tan(); 31 | let tan_angle_width = tan_right - tan_left; 32 | let tan_angle_height = tan_top - tan_bottom; 33 | 34 | self.projection = Matrix4::new( 35 | 2.0 / tan_angle_width, 36 | 0.0, 37 | 0.0, 38 | 0.0, 39 | 0.0, 40 | 2.0 / tan_angle_height, 41 | 0.0, 42 | 0.0, 43 | (tan_right + tan_left) / tan_angle_width, 44 | (tan_top + tan_bottom) / tan_angle_height, 45 | -1.0, 46 | -1.0, 47 | 0.0, 48 | 0.0, 49 | -self.near, 50 | 0.0, 51 | ); 52 | } 53 | 54 | #[allow(unused)] 55 | pub fn update_projection(&mut self, fov: Rad, aspect_ratio: f32) { 56 | self.projection = cgmath::perspective(fov, aspect_ratio, self.near, self.far) 57 | } 58 | 59 | pub fn build_view_projection_matrix(&self) -> anyhow::Result> { 60 | Ok(self.projection 61 | * self 62 | .entity 63 | .world_matrix 64 | .invert() 65 | .context("Provided world matrix is not invertible")?) 66 | } 67 | } 68 | 69 | // We need this for Rust to store our data correctly for the shaders 70 | #[repr(C)] 71 | // This is so we can store this in a buffer 72 | #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] 73 | pub struct CameraUniform { 74 | // We can't use cgmath with bytemuck directly so we'll have 75 | // to convert the Matrix4 into a 4x4 f32 array 76 | view_proj: [[f32; 4]; 4], 77 | } 78 | 79 | impl CameraUniform { 80 | pub fn new() -> Self { 81 | Self { 82 | view_proj: cgmath::Matrix4::identity().into(), 83 | } 84 | } 85 | 86 | pub fn update_view_proj(&mut self, camera: &Camera) -> anyhow::Result<()> { 87 | self.view_proj = camera.build_view_projection_matrix()?.into(); 88 | Ok(()) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/engine/entity.rs: -------------------------------------------------------------------------------- 1 | use cgmath::{Matrix4, Quaternion, SquareMatrix, Vector3, Zero}; 2 | 3 | pub struct Entity { 4 | pub id: usize, 5 | pub parent_id: Option, 6 | pub position: Vector3, 7 | pub rotation: Quaternion, 8 | pub scale: Vector3, 9 | pub world_matrix: Matrix4, 10 | pub local_matrix: Matrix4, 11 | uniform_matrix: ModelUniform, 12 | } 13 | 14 | impl Default for Entity { 15 | fn default() -> Self { 16 | //TODO registry 17 | Self::new( 18 | Default::default(), 19 | Vector3::zero(), 20 | Quaternion::zero(), 21 | Vector3::new(1.0, 1.0, 1.0), 22 | ) 23 | } 24 | } 25 | 26 | impl Entity { 27 | pub fn new( 28 | id: usize, 29 | position: Vector3, 30 | rotation: Quaternion, 31 | scale: Vector3, 32 | ) -> Self { 33 | let mut entity = Entity { 34 | id, 35 | parent_id: None, 36 | position, 37 | rotation, 38 | scale, 39 | world_matrix: Matrix4::identity(), 40 | local_matrix: Matrix4::identity(), 41 | uniform_matrix: ModelUniform::new(), 42 | }; 43 | 44 | entity.update_matrices(&[]); 45 | entity 46 | } 47 | 48 | pub fn update_matrices(&mut self, registry: &[Entity]) { 49 | self.local_matrix = Matrix4::from_translation(self.position) 50 | * Matrix4::from(self.rotation) 51 | * Matrix4::from_nonuniform_scale(self.scale.x, self.scale.y, self.scale.z); 52 | 53 | if let Some(parent_id) = self.parent_id { 54 | self.world_matrix = registry[parent_id].world_matrix * self.local_matrix; 55 | } else { 56 | self.world_matrix = self.local_matrix; 57 | } 58 | 59 | self.uniform_matrix.model_matrix = self.world_matrix.into(); 60 | } 61 | 62 | pub fn uniform(&self) -> ModelUniform { 63 | self.uniform_matrix 64 | } 65 | } 66 | 67 | // We need this for Rust to store our data correctly for the shaders 68 | #[repr(C)] 69 | // This is so we can store this in a buffer 70 | #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] 71 | pub struct ModelUniform { 72 | // We can't use cgmath with bytemuck directly so we'll have 73 | // to convert the Matrix4 into a 4x4 f32 array 74 | model_matrix: [[f32; 4]; 4], 75 | } 76 | 77 | impl ModelUniform { 78 | pub fn new() -> Self { 79 | Self { 80 | model_matrix: cgmath::Matrix4::identity().into(), 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/engine/geometry.rs: -------------------------------------------------------------------------------- 1 | use std::io::{BufReader, Cursor}; 2 | 3 | use wgpu::util::DeviceExt; 4 | 5 | pub trait Vertex { 6 | fn desc<'a>() -> wgpu::VertexBufferLayout<'a>; 7 | } 8 | 9 | #[repr(C)] 10 | #[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)] 11 | pub struct ModelVertex { 12 | position: [f32; 3], 13 | tex_coords: [f32; 2], 14 | } 15 | 16 | impl ModelVertex { 17 | pub const ATTRIBS: &'static [wgpu::VertexAttribute] = &wgpu::vertex_attr_array![0 => Float32x3, 18 | 1 => Float32x2, 19 | 2 => Float32x3]; 20 | } 21 | 22 | impl Vertex for ModelVertex { 23 | fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { 24 | use std::mem; 25 | 26 | wgpu::VertexBufferLayout { 27 | array_stride: mem::size_of::() as wgpu::BufferAddress, 28 | step_mode: wgpu::VertexStepMode::Vertex, 29 | attributes: &Self::ATTRIBS, 30 | } 31 | } 32 | } 33 | 34 | pub struct Mesh { 35 | num_indeces: u32, 36 | vertex_buffer: wgpu::Buffer, 37 | index_buffer: wgpu::Buffer, 38 | } 39 | 40 | impl Mesh { 41 | fn get_buffers( 42 | device: &wgpu::Device, 43 | vertices: &[ModelVertex], 44 | indices: &[u32], 45 | ) -> (wgpu::Buffer, wgpu::Buffer) { 46 | ( 47 | device.create_buffer_init(&wgpu::util::BufferInitDescriptor { 48 | label: Some("Vertex Buffer"), 49 | contents: bytemuck::cast_slice(vertices), 50 | usage: wgpu::BufferUsages::VERTEX, 51 | }), 52 | device.create_buffer_init(&wgpu::util::BufferInitDescriptor { 53 | label: Some("Index Buffer"), 54 | contents: bytemuck::cast_slice(indices), 55 | usage: wgpu::BufferUsages::INDEX, 56 | }), 57 | ) 58 | } 59 | 60 | pub fn indices(&self) -> u32 { 61 | self.num_indeces 62 | } 63 | 64 | pub fn vertex_buffer(&self) -> &wgpu::Buffer { 65 | &self.vertex_buffer 66 | } 67 | 68 | pub fn index_buffer(&self) -> &wgpu::Buffer { 69 | &self.index_buffer 70 | } 71 | 72 | //TODO: Actually use entity + mesh and world matrix in shader 73 | pub fn get_plane_rectangle( 74 | device: &wgpu::Device, 75 | rows: u32, 76 | columns: u32, 77 | aspect_ratio: f32, 78 | scale: f32, 79 | distance: f32, 80 | ) -> Mesh { 81 | let mut vertices = vec![]; 82 | let x_increment = 2.0 / (columns as f32); 83 | let y_increment = 2.0 / (columns as f32); 84 | for row in 0..rows { 85 | for column in 0..columns { 86 | vertices.push(ModelVertex { 87 | position: [ 88 | (-1.0 + (column as f32) * x_increment) * scale * aspect_ratio, 89 | (-1.0 + (row as f32) * y_increment) * scale, 90 | distance, 91 | ], 92 | tex_coords: [ 93 | (column as f32) / (columns as f32), 94 | 1.0 - (row as f32) / (rows as f32), 95 | ], 96 | }); 97 | } 98 | } 99 | 100 | let mut indices = vec![]; 101 | for row in 0..rows - 1 { 102 | for column in 0..columns - 1 { 103 | indices.push(row * columns + column); 104 | indices.push(row * columns + column + 1); 105 | indices.push((row + 1) * columns + column); 106 | indices.push((row + 1) * columns + column); 107 | indices.push(row * columns + column + 1); 108 | indices.push((row + 1) * columns + column + 1); 109 | } 110 | } 111 | 112 | let (vertex_buffer, index_buffer) = Mesh::get_buffers(&device, &vertices, &indices); 113 | Mesh { 114 | //FIXME: Handle flipping properly 115 | num_indeces: indices.len() as u32, 116 | vertex_buffer, 117 | index_buffer, 118 | } 119 | } 120 | 121 | pub fn from_asset( 122 | device: &wgpu::Device, 123 | asset: &'static [u8], 124 | scale: f32, 125 | distance: f32, 126 | ) -> Mesh { 127 | let obj_cursor = Cursor::new(asset); 128 | let mut obj_reader = BufReader::new(obj_cursor); 129 | let (models, _) = tobj::load_obj_buf( 130 | &mut obj_reader, 131 | &tobj::LoadOptions { 132 | triangulate: true, 133 | single_index: true, 134 | ..Default::default() 135 | }, 136 | |_| Err(tobj::LoadError::ReadError), 137 | ) 138 | .unwrap(); 139 | 140 | let mesh = &models[0].mesh; 141 | let vertices = (0..mesh.positions.len() / 3) 142 | .map(|i| ModelVertex { 143 | position: [ 144 | mesh.positions[i * 3] * scale, 145 | mesh.positions[i * 3 + 1] * scale, 146 | mesh.positions[i * 3 + 2] * scale + distance, 147 | ], 148 | tex_coords: [mesh.texcoords[i * 2], mesh.texcoords[i * 2 + 1]], 149 | }) 150 | .collect::>(); 151 | let indices = mesh.indices.clone(); 152 | let (vertex_buffer, index_buffer) = Mesh::get_buffers(&device, &vertices, &indices); 153 | Mesh { 154 | num_indeces: indices.len() as u32, 155 | vertex_buffer: vertex_buffer, 156 | index_buffer: index_buffer, 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /src/engine/input.rs: -------------------------------------------------------------------------------- 1 | use std::time::Instant; 2 | 3 | use anyhow::Context; 4 | use openxr::{Action, ActionSet, Binding, FrameState, Instance, Path, Posef, Session, Space}; 5 | 6 | pub struct InputContext { 7 | pub default: ActionSet, 8 | pub default_right_hand: Action, 9 | pub default_left_hand: Action, 10 | pub default_right_hand_space: Option, 11 | pub default_left_hand_space: Option, 12 | pub input_state: Option, 13 | } 14 | 15 | pub struct InputState { 16 | pub hands_near_head: u8, 17 | pub near_start: Instant, 18 | pub count_change: Instant, 19 | } 20 | 21 | impl InputContext { 22 | pub fn init(xr_instance: &Instance) -> anyhow::Result { 23 | let default_set = 24 | xr_instance.create_action_set("default", "Default controller actions", 0)?; 25 | 26 | let right_hand = default_set.create_action("right_hand", "Right Hand Controller", &[])?; 27 | 28 | let left_hand = default_set.create_action("left_hand", "Left Hand Controller", &[])?; 29 | 30 | xr_instance.suggest_interaction_profile_bindings( 31 | xr_instance.string_to_path("/interaction_profiles/khr/simple_controller")?, 32 | &[ 33 | Binding::new( 34 | &right_hand, 35 | xr_instance.string_to_path("/user/hand/right/input/grip/pose")?, 36 | ), 37 | Binding::new( 38 | &left_hand, 39 | xr_instance.string_to_path("/user/hand/left/input/grip/pose")?, 40 | ), 41 | ], 42 | )?; 43 | 44 | Ok(InputContext { 45 | default: default_set, 46 | default_right_hand: right_hand, 47 | default_left_hand: left_hand, 48 | default_right_hand_space: None, 49 | default_left_hand_space: None, 50 | input_state: None, 51 | }) 52 | } 53 | 54 | pub fn attach_to_session(&mut self, xr_session: &Session) -> anyhow::Result<()> { 55 | xr_session.attach_action_sets(&[&self.default])?; 56 | 57 | self.default_right_hand_space = Some(self.default_right_hand.create_space( 58 | xr_session.clone(), 59 | Path::NULL, 60 | Posef::IDENTITY, 61 | )?); 62 | 63 | self.default_left_hand_space = Some(self.default_left_hand.create_space( 64 | xr_session.clone(), 65 | Path::NULL, 66 | Posef::IDENTITY, 67 | )?); 68 | 69 | Ok(()) 70 | } 71 | 72 | pub fn process_inputs( 73 | &mut self, 74 | xr_session: &Session, 75 | xr_frame_state: &FrameState, 76 | _xr_reference_space: &Space, 77 | xr_view_space: &Space, 78 | ) -> anyhow::Result<()> { 79 | xr_session.sync_actions(&[(&self.default).into()])?; 80 | 81 | let right_location = self 82 | .default_right_hand_space 83 | .as_ref() 84 | .context("Right hand space not initialized")? 85 | .locate(xr_view_space, xr_frame_state.predicted_display_time)?; 86 | 87 | let left_location = self 88 | .default_left_hand_space 89 | .as_ref() 90 | .context("Left hand space not initialized")? 91 | .locate(xr_view_space, xr_frame_state.predicted_display_time)?; 92 | 93 | let right_hand_distance = (right_location.pose.position.x.powi(2) 94 | + right_location.pose.position.y.powi(2) 95 | + right_location.pose.position.z.powi(2)) 96 | .sqrt(); 97 | 98 | let left_hand_distance = (left_location.pose.position.x.powi(2) 99 | + left_location.pose.position.y.powi(2) 100 | + left_location.pose.position.z.powi(2)) 101 | .sqrt(); 102 | 103 | let right_active = self.default_right_hand.is_active(xr_session, Path::NULL)? 104 | && right_location 105 | .location_flags 106 | .contains(openxr::SpaceLocationFlags::POSITION_TRACKED) 107 | && right_location 108 | .location_flags 109 | .contains(openxr::SpaceLocationFlags::POSITION_VALID); 110 | let left_active = self.default_left_hand.is_active(xr_session, Path::NULL)? 111 | && left_location 112 | .location_flags 113 | .contains(openxr::SpaceLocationFlags::POSITION_TRACKED) 114 | && left_location 115 | .location_flags 116 | .contains(openxr::SpaceLocationFlags::POSITION_VALID); 117 | 118 | let new_state = Self::compute_input_state( 119 | &self.input_state, 120 | right_active, 121 | right_hand_distance, 122 | left_active, 123 | left_hand_distance, 124 | ); 125 | self.input_state = Some(new_state); 126 | 127 | Ok(()) 128 | } 129 | 130 | fn compute_input_state( 131 | input_state: &Option, 132 | right_active: bool, 133 | right_hand_distance: f32, 134 | left_active: bool, 135 | left_hand_distance: f32, 136 | ) -> InputState { 137 | let hands_near_head = ((right_active && right_hand_distance < 0.3) as u8) 138 | + ((left_active && left_hand_distance < 0.3) as u8); 139 | 140 | if input_state.is_none() { 141 | return InputState { 142 | hands_near_head, 143 | near_start: Instant::now(), 144 | count_change: Instant::now(), 145 | }; 146 | } 147 | 148 | let input_state = input_state.as_ref().unwrap(); 149 | let near_start = if input_state.hands_near_head > 0 { 150 | input_state.near_start 151 | } else { 152 | Instant::now() 153 | }; 154 | 155 | let count_change = if input_state.hands_near_head != hands_near_head { 156 | Instant::now() 157 | } else { 158 | input_state.count_change 159 | }; 160 | 161 | InputState { 162 | hands_near_head, 163 | near_start, 164 | count_change, 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /src/engine/jitter.rs: -------------------------------------------------------------------------------- 1 | pub fn halton(i: u32, b: u32) -> f32 { 2 | let mut f = 1.0; 3 | let mut r = 0.0; 4 | let mut i = i; 5 | while i > 0 { 6 | f /= b as f32; 7 | r += f * (i % b) as f32; 8 | i /= b; 9 | } 10 | r 11 | } 12 | 13 | pub fn get_jitter(jitter_index: u32, resolution: &[f32; 2]) -> [f32; 2] { 14 | let jitter = [ 15 | 2.0 * halton(jitter_index, 2) - 1.0, 16 | 2.0 * halton(jitter_index, 3) - 1.0, 17 | ]; 18 | 19 | [jitter[0] / resolution[0], jitter[1] / resolution[1]] 20 | } 21 | -------------------------------------------------------------------------------- /src/engine/screen.rs: -------------------------------------------------------------------------------- 1 | use cgmath::Zero; 2 | 3 | use super::{entity::Entity, geometry::Mesh}; 4 | 5 | pub struct Screen { 6 | pub mesh: Mesh, 7 | pub ambient_mesh: Mesh, 8 | pub ambient_enabled: bool, 9 | pub entity: Entity, 10 | pub aspect_ratio: f32, 11 | pub scale: f32, 12 | } 13 | 14 | impl Screen { 15 | pub fn new( 16 | device: &wgpu::Device, 17 | distance: f32, 18 | scale: f32, 19 | aspect_ratio: f32, 20 | ambient_enabled: bool, 21 | ) -> Screen { 22 | Screen { 23 | mesh: Mesh::get_plane_rectangle(device, 100, 100, 1.0, 1.0, 0.0), 24 | ambient_mesh: Mesh::from_asset( 25 | device, 26 | include_bytes!("../../assets/ambient_dome.obj"), 27 | 100.0, 28 | 65.0, 29 | ), 30 | ambient_enabled, 31 | entity: Entity::new( 32 | 0, 33 | cgmath::Vector3 { 34 | x: 0.0, 35 | y: 0.0, 36 | z: distance, 37 | }, 38 | cgmath::Quaternion::zero(), 39 | //Screen is 2m wide as a base 40 | cgmath::Vector3 { 41 | x: scale / 2.0, 42 | y: scale / (2.0 * aspect_ratio), 43 | z: scale / 2.0, 44 | }, 45 | ), 46 | scale, 47 | aspect_ratio, 48 | } 49 | } 50 | 51 | pub fn change_aspect_ratio(&mut self, aspect_ratio: f32) { 52 | self.aspect_ratio = aspect_ratio; 53 | self.entity.scale.y = self.scale / (2.0 * self.aspect_ratio); 54 | self.entity.update_matrices(&[]); 55 | } 56 | 57 | pub fn change_scale(&mut self, scale: f32) { 58 | self.scale = scale; 59 | self.entity.scale.x = self.scale / 2.0; 60 | self.entity.scale.y = self.scale / (2.0 * self.aspect_ratio); 61 | self.entity.scale.z = self.scale / 2.0; 62 | self.entity.update_matrices(&[]); 63 | } 64 | 65 | pub fn change_distance(&mut self, distance: f32) { 66 | self.entity.position.z = distance; 67 | self.entity.update_matrices(&[]); 68 | } 69 | 70 | pub fn change_ambient_mode(&mut self, ambient_mode: bool) { 71 | self.ambient_enabled = ambient_mode; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/engine/texture.rs: -------------------------------------------------------------------------------- 1 | use anyhow::*; 2 | use image::GenericImageView; 3 | use wgpu::TextureDescriptor; 4 | 5 | use super::WgpuContext; 6 | 7 | pub struct Bound; 8 | pub struct Unbound; 9 | 10 | pub struct Texture2D { 11 | pub texture: wgpu::Texture, 12 | pub view: wgpu::TextureView, 13 | pub sampler: wgpu::Sampler, 14 | pub bind_group: Option, 15 | pub state: std::marker::PhantomData, 16 | } 17 | 18 | impl Texture2D { 19 | pub fn from_bytes( 20 | device: &wgpu::Device, 21 | queue: &wgpu::Queue, 22 | bytes: &[u8], 23 | label: &str, 24 | ) -> anyhow::Result> { 25 | let img = image::load_from_memory(bytes)?; 26 | Ok(Self::from_image(device, queue, &img, Some(label))) 27 | } 28 | 29 | pub fn from_image( 30 | device: &wgpu::Device, 31 | queue: &wgpu::Queue, 32 | img: &image::DynamicImage, 33 | label: Option<&str>, 34 | ) -> Texture2D { 35 | let rgba = img.to_rgba8(); 36 | let dimensions = img.dimensions(); 37 | 38 | let size = wgpu::Extent3d { 39 | width: dimensions.0, 40 | height: dimensions.1, 41 | depth_or_array_layers: 1, 42 | }; 43 | let texture = device.create_texture(&wgpu::TextureDescriptor { 44 | label, 45 | size, 46 | mip_level_count: 1, 47 | sample_count: 1, 48 | dimension: wgpu::TextureDimension::D2, 49 | format: wgpu::TextureFormat::Rgba8UnormSrgb, 50 | usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST, 51 | view_formats: &[], 52 | }); 53 | 54 | queue.write_texture( 55 | wgpu::ImageCopyTexture { 56 | aspect: wgpu::TextureAspect::All, 57 | texture: &texture, 58 | mip_level: 0, 59 | origin: wgpu::Origin3d::ZERO, 60 | }, 61 | &rgba, 62 | wgpu::ImageDataLayout { 63 | offset: 0, 64 | bytes_per_row: Some(4 * dimensions.0), 65 | rows_per_image: Some(dimensions.1), 66 | }, 67 | size, 68 | ); 69 | 70 | let (view, sampler) = 71 | Self::get_view_and_sampler(device, &texture, wgpu::FilterMode::Linear); 72 | 73 | Texture2D:: { 74 | texture, 75 | view, 76 | sampler, 77 | bind_group: None, 78 | state: std::marker::PhantomData, 79 | } 80 | } 81 | 82 | pub fn as_render_target_with_extent( 83 | &self, 84 | label: &str, 85 | extent: wgpu::Extent3d, 86 | format: wgpu::TextureFormat, 87 | device: &wgpu::Device, 88 | ) -> Texture2D { 89 | let desc = &TextureDescriptor { 90 | label: Some(label), 91 | size: extent, 92 | mip_level_count: 1, 93 | sample_count: 1, 94 | dimension: self.texture.dimension(), 95 | format: format, 96 | usage: self.texture.usage() | wgpu::TextureUsages::RENDER_ATTACHMENT, 97 | view_formats: &[], 98 | }; 99 | let texture = device.create_texture(desc); 100 | let (view, sampler) = 101 | Self::get_view_and_sampler(device, &texture, wgpu::FilterMode::Linear); 102 | Texture2D:: { 103 | texture, 104 | view, 105 | sampler, 106 | bind_group: None, 107 | state: std::marker::PhantomData, 108 | } 109 | } 110 | 111 | pub fn from_wgpu(device: &wgpu::Device, texture: wgpu::Texture) -> Texture2D { 112 | let (view, sampler) = 113 | Self::get_view_and_sampler(device, &texture, wgpu::FilterMode::Linear); 114 | Texture2D:: { 115 | texture, 116 | view, 117 | sampler, 118 | bind_group: None, 119 | state: std::marker::PhantomData, 120 | } 121 | } 122 | 123 | fn get_view_and_sampler( 124 | device: &wgpu::Device, 125 | texture: &wgpu::Texture, 126 | filter_mode: wgpu::FilterMode, 127 | ) -> (wgpu::TextureView, wgpu::Sampler) { 128 | ( 129 | texture.create_view(&wgpu::TextureViewDescriptor::default()), 130 | device.create_sampler(&wgpu::SamplerDescriptor { 131 | address_mode_u: wgpu::AddressMode::ClampToEdge, 132 | address_mode_v: wgpu::AddressMode::ClampToEdge, 133 | address_mode_w: wgpu::AddressMode::ClampToEdge, 134 | mag_filter: filter_mode, 135 | min_filter: filter_mode, 136 | mipmap_filter: wgpu::FilterMode::Nearest, 137 | ..Default::default() 138 | }), 139 | ) 140 | } 141 | } 142 | 143 | impl Texture2D { 144 | pub fn bind_to_context( 145 | self, 146 | wgpu_context: &WgpuContext, 147 | bind_group_layout: &wgpu::BindGroupLayout, 148 | ) -> Texture2D { 149 | Texture2D:: { 150 | bind_group: Some( 151 | wgpu_context 152 | .device 153 | .create_bind_group(&wgpu::BindGroupDescriptor { 154 | layout: bind_group_layout, 155 | entries: &[ 156 | wgpu::BindGroupEntry { 157 | binding: 0, 158 | resource: wgpu::BindingResource::TextureView(&self.view), 159 | }, 160 | wgpu::BindGroupEntry { 161 | binding: 1, 162 | resource: wgpu::BindingResource::Sampler(&self.sampler), 163 | }, 164 | ], 165 | label: Some("Texture Bind Group"), 166 | }), 167 | ), 168 | texture: self.texture, 169 | view: self.view, 170 | sampler: self.sampler, 171 | state: std::marker::PhantomData, 172 | } 173 | } 174 | } 175 | 176 | impl Texture2D { 177 | #[inline] 178 | pub fn bind_group(&self) -> &wgpu::BindGroup { 179 | self.bind_group.as_ref().unwrap() 180 | } 181 | } 182 | 183 | pub struct RoundRobinTextureBuffer { 184 | textures: [TextureType; SIZE], 185 | index: usize, 186 | } 187 | 188 | impl RoundRobinTextureBuffer { 189 | pub fn new(textures: [TextureType; SIZE]) -> Self { 190 | Self { textures, index: 0 } 191 | } 192 | 193 | pub fn current(&self) -> &TextureType { 194 | &self.textures[self.index] 195 | } 196 | 197 | pub fn previous(&self, idx: usize) -> &TextureType { 198 | let index = (self.index + SIZE - idx) % SIZE; 199 | &self.textures[index] 200 | } 201 | 202 | pub fn next(&mut self) -> &TextureType { 203 | let index = self.index; 204 | self.index = (self.index + 1) % SIZE; 205 | &mut self.textures[index] 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/engine/vr.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context}; 2 | use ash::vk::{self, Handle, QueueGlobalPriorityKHR}; 3 | use hal::MemoryFlags; 4 | use openxr as xr; 5 | use wgpu::{Device, Extent3d}; 6 | use wgpu_hal as hal; 7 | 8 | use crate::conversions::vulkan_image_to_texture; 9 | 10 | use super::{ 11 | texture::{Texture2D, Unbound}, 12 | WgpuLoader, WgpuRunner, TARGET_VULKAN_VERSION, 13 | }; 14 | 15 | pub struct OpenXRContext { 16 | pub entry: openxr::Entry, 17 | pub instance: openxr::Instance, 18 | pub props: openxr::InstanceProperties, 19 | pub system: openxr::SystemId, 20 | pub blend_mode: openxr::EnvironmentBlendMode, 21 | } 22 | 23 | pub const VIEW_TYPE: openxr::ViewConfigurationType = openxr::ViewConfigurationType::PRIMARY_STEREO; 24 | pub const VIEW_COUNT: u32 = 2; 25 | pub const SWAPCHAIN_COLOR_FORMAT: wgpu::TextureFormat = wgpu::TextureFormat::Bgra8Unorm; 26 | pub const VK_SWAPCHAIN_COLOR_FORMAT: vk::Format = vk::Format::B8G8R8A8_SRGB; 27 | 28 | #[cfg(debug_assertions)] 29 | pub fn openxr_layers() -> [&'static str; 0] { 30 | [] //TODO: ["VK_LAYER_KHRONOS_validation"] 31 | } 32 | 33 | #[cfg(not(debug_assertions))] 34 | pub fn openxr_layers() -> [&'static str; 0] { 35 | [] 36 | } 37 | 38 | pub fn enable_xr_runtime() -> anyhow::Result { 39 | #[cfg(not(target_os = "android"))] 40 | let entry = openxr::Entry::linked(); 41 | #[cfg(target_os = "android")] 42 | let entry = unsafe { openxr::Entry::load()? }; 43 | 44 | #[cfg(target_os = "android")] 45 | entry.initialize_android_loader()?; 46 | 47 | let available_extensions = entry.enumerate_extensions()?; 48 | log::info!("Available extensions: {:?}", available_extensions); 49 | assert!(available_extensions.khr_vulkan_enable2); 50 | 51 | let mut enabled_extensions = openxr::ExtensionSet::default(); 52 | enabled_extensions.khr_vulkan_enable2 = true; 53 | 54 | #[cfg(target_os = "android")] 55 | { 56 | enabled_extensions.khr_android_create_instance = true; 57 | } 58 | log::info!("Enabled extensions: {:?}", enabled_extensions); 59 | 60 | log::info!("Loading OpenXR Runtime..."); 61 | let instance = entry.create_instance( 62 | &openxr::ApplicationInfo { 63 | application_name: "VR Screen Viewer", 64 | application_version: 0, 65 | engine_name: "void*", 66 | engine_version: 0, 67 | }, 68 | &enabled_extensions, 69 | &openxr_layers(), 70 | )?; 71 | 72 | let props = instance.properties()?; 73 | log::info!( 74 | "loaded OpenXR runtime: {} {}", 75 | props.runtime_name, 76 | props.runtime_version 77 | ); 78 | 79 | // Request a form factor from the device (HMD, Handheld, etc.) 80 | let system = instance.system(openxr::FormFactor::HEAD_MOUNTED_DISPLAY)?; 81 | 82 | // Check what blend mode is valid for this device (opaque vs transparent displays). We'll just 83 | // take the first one available! 84 | let blend_mode = instance.enumerate_environment_blend_modes(system, VIEW_TYPE)?[0]; 85 | 86 | log::info!( 87 | "Created OpenXR context with : {:?} {:?}", 88 | system, 89 | blend_mode 90 | ); 91 | 92 | Ok(OpenXRContext { 93 | entry, 94 | instance, 95 | props, 96 | system, 97 | blend_mode, 98 | }) 99 | } 100 | 101 | #[cfg(not(debug_assertions))] 102 | fn instance_flags() -> hal::InstanceFlags { 103 | hal::InstanceFlags::empty() 104 | } 105 | 106 | #[cfg(debug_assertions)] 107 | fn instance_flags() -> hal::InstanceFlags { 108 | hal::InstanceFlags::empty() | hal::InstanceFlags::VALIDATION | hal::InstanceFlags::DEBUG 109 | } 110 | 111 | impl WgpuLoader for OpenXRContext { 112 | fn load_wgpu(&mut self) -> anyhow::Result { 113 | // OpenXR wants to ensure apps are using the correct graphics card and Vulkan features and 114 | // extensions, so the instance and device MUST be set up before Instance::create_session. 115 | 116 | let wgpu_limits = wgpu::Limits::downlevel_webgl2_defaults(); 117 | 118 | let wgpu_features = wgpu::Features::MULTIVIEW; 119 | let vk_target_version = TARGET_VULKAN_VERSION; // Vulkan 1.1 guarantees multiview support 120 | let vk_target_version_xr = xr::Version::new(1, 1, 0); 121 | 122 | let reqs = self 123 | .instance 124 | .graphics_requirements::(self.system)?; 125 | 126 | if vk_target_version_xr < reqs.min_api_version_supported 127 | || vk_target_version_xr.major() > reqs.max_api_version_supported.major() 128 | { 129 | bail!( 130 | "OpenXR runtime requires Vulkan version > {}, < {}.0.0", 131 | reqs.min_api_version_supported, 132 | reqs.max_api_version_supported.major() + 1 133 | ); 134 | } 135 | 136 | let vk_entry = unsafe { ash::Entry::load()? }; 137 | log::info!("Successfully loaded Vulkan entry"); 138 | 139 | let vk_app_info = vk::ApplicationInfo::builder() 140 | .application_version(0) 141 | .engine_version(0) 142 | .api_version(vk_target_version); 143 | 144 | let flags = instance_flags(); 145 | 146 | let instance_extensions = ::Instance::required_extensions( 147 | &vk_entry, 148 | vk_target_version, 149 | flags, 150 | )?; 151 | 152 | log::info!("Requested instance extensions: {:?}", instance_extensions); 153 | let instance_extensions_ptrs: Vec<_> = 154 | instance_extensions.iter().map(|x| x.as_ptr()).collect(); 155 | 156 | let create_info = vk::InstanceCreateInfo::builder() 157 | .application_info(&vk_app_info) 158 | .enabled_extension_names(&instance_extensions_ptrs); 159 | 160 | let vk_instance = unsafe { 161 | let vk_instance = self 162 | .instance 163 | .create_vulkan_instance( 164 | self.system, 165 | std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr), 166 | &create_info as *const _ as *const _, 167 | ) 168 | .context("XR error creating Vulkan instance")? 169 | .map_err(vk::Result::from_raw) 170 | .context("Vulkan error creating Vulkan instance")?; 171 | ash::Instance::load( 172 | vk_entry.static_fn(), 173 | vk::Instance::from_raw(vk_instance as _), 174 | ) 175 | }; 176 | 177 | log::info!("Successfully created Vulkan instance"); 178 | 179 | let vk_physical_device = vk::PhysicalDevice::from_raw(unsafe { 180 | self.instance 181 | .vulkan_graphics_device(self.system, vk_instance.handle().as_raw() as _)? 182 | as _ 183 | }); 184 | 185 | let vk_device_properties = 186 | unsafe { vk_instance.get_physical_device_properties(vk_physical_device) }; 187 | if vk_device_properties.api_version < vk_target_version { 188 | unsafe { vk_instance.destroy_instance(None) }; 189 | panic!("Vulkan phyiscal device doesn't support version 1.1"); 190 | } 191 | 192 | log::info!( 193 | "Got Vulkan physical device with properties {:?}", 194 | vk_device_properties 195 | ); 196 | 197 | let queue_family_index = unsafe { 198 | vk_instance 199 | .get_physical_device_queue_family_properties(vk_physical_device) 200 | .into_iter() 201 | .enumerate() 202 | .find_map(|(queue_family_index, info)| { 203 | if info.queue_flags.contains(vk::QueueFlags::GRAPHICS) { 204 | Some(queue_family_index as u32) 205 | } else { 206 | None 207 | } 208 | }) 209 | .context("Vulkan device has no graphics queue")? 210 | }; 211 | 212 | log::info!("Got Vulkan queue family index {}", queue_family_index); 213 | 214 | let hal_instance = unsafe { 215 | ::Instance::from_raw( 216 | vk_entry.clone(), 217 | vk_instance.clone(), 218 | vk_target_version, 219 | 0, 220 | instance_extensions, 221 | flags, 222 | false, 223 | Some(Box::new(())), 224 | )? 225 | }; 226 | let hal_exposed_adapter = hal_instance 227 | .expose_adapter(vk_physical_device) 228 | .context("Cannot expose WGpu-Hal adapter")?; 229 | 230 | log::info!("Created WGPU-HAL instance and adapter"); 231 | 232 | //TODO actually check if the extensions are available and avoid using them in the loaders 233 | let mut device_extensions = hal_exposed_adapter 234 | .adapter 235 | .required_device_extensions(wgpu_features); 236 | 237 | #[cfg(target_os = "windows")] 238 | device_extensions.push(ash::extensions::khr::ExternalMemoryWin32::name()); 239 | 240 | log::info!("Requested device extensions: {:?}", device_extensions); 241 | 242 | let family_info = vk::DeviceQueueCreateInfo::builder() 243 | .queue_family_index(queue_family_index) 244 | .queue_priorities(&[1.0]) 245 | .push_next( 246 | &mut vk::DeviceQueueGlobalPriorityCreateInfoKHR::builder() 247 | .global_priority(QueueGlobalPriorityKHR::REALTIME_EXT), 248 | ) 249 | .build(); 250 | 251 | let device_extensions_ptrs = device_extensions 252 | .iter() 253 | .map(|x| x.as_ptr()) 254 | .collect::>(); 255 | 256 | let mut enabled_features = hal_exposed_adapter 257 | .adapter 258 | .physical_device_features(&device_extensions, wgpu_features); 259 | 260 | let device_create_info = enabled_features 261 | .add_to_device_create_builder( 262 | vk::DeviceCreateInfo::builder() 263 | .enabled_extension_names(&device_extensions_ptrs) 264 | .queue_create_infos(&[family_info]) 265 | .push_next(&mut vk::PhysicalDeviceMultiviewFeatures { 266 | multiview: vk::TRUE, 267 | ..Default::default() 268 | }), 269 | ) 270 | .build(); 271 | 272 | let vk_device = { 273 | unsafe { 274 | let vk_device = self 275 | .instance 276 | .create_vulkan_device( 277 | self.system, 278 | std::mem::transmute(vk_entry.static_fn().get_instance_proc_addr), 279 | vk_physical_device.as_raw() as _, 280 | &device_create_info as *const _ as *const _, 281 | ) 282 | .context("XR error creating Vulkan device")? 283 | .map_err(vk::Result::from_raw) 284 | .context("Vulkan error creating Vulkan device")?; 285 | 286 | log::info!("Creating ash vulkan device device from native"); 287 | ash::Device::load(vk_instance.fp_v1_0(), vk::Device::from_raw(vk_device as _)) 288 | } 289 | }; 290 | 291 | let vk_device_ptr = vk_device.handle().as_raw(); 292 | log::info!("Successfully created Vulkan device"); 293 | 294 | let hal_device = unsafe { 295 | hal_exposed_adapter.adapter.device_from_raw( 296 | vk_device, 297 | true, // TODO: is this right? 298 | &device_extensions, 299 | wgpu_features, 300 | family_info.queue_family_index, 301 | 0, 302 | )? 303 | }; 304 | 305 | log::info!("Successfully created WGPU-HAL device from vulkan device"); 306 | 307 | let wgpu_instance = 308 | unsafe { wgpu::Instance::from_hal::(hal_instance) }; 309 | let wgpu_adapter = unsafe { wgpu_instance.create_adapter_from_hal(hal_exposed_adapter) }; 310 | let (wgpu_device, wgpu_queue) = unsafe { 311 | wgpu_adapter.create_device_from_hal( 312 | hal_device, 313 | &wgpu::DeviceDescriptor { 314 | features: wgpu_features, 315 | limits: wgpu_limits, 316 | label: None, 317 | }, 318 | None, 319 | )? 320 | }; 321 | 322 | log::info!("Successfully created WGPU context"); 323 | 324 | log::info!( 325 | "Queue timestamp period: {}", 326 | wgpu_queue.get_timestamp_period() 327 | ); 328 | 329 | Ok(super::WgpuContext { 330 | instance: wgpu_instance, 331 | device: wgpu_device, 332 | physical_device: wgpu_adapter, 333 | queue: wgpu_queue, 334 | queue_index: family_info.queue_family_index, 335 | vk_entry, 336 | vk_device_ptr, 337 | vk_instance_ptr: vk_instance.handle().as_raw(), 338 | vk_phys_device_ptr: vk_physical_device.as_raw(), 339 | }) 340 | } 341 | } 342 | 343 | impl WgpuRunner for OpenXRContext { 344 | fn run(&mut self, _wgpu_context: &super::WgpuContext) { 345 | todo!() 346 | } 347 | } 348 | 349 | impl OpenXRContext { 350 | pub fn create_swapchain( 351 | &self, 352 | xr_session: &openxr::Session, 353 | device: &Device, 354 | ) -> anyhow::Result<( 355 | openxr::Swapchain, 356 | vk::Extent2D, 357 | Vec>, 358 | )> { 359 | log::info!("Creating OpenXR swapchain"); 360 | 361 | // Fetch the views we need to render to (the eye screens on the HMD) 362 | let views = self 363 | .instance 364 | .enumerate_view_configuration_views(self.system, VIEW_TYPE)?; 365 | assert_eq!(views.len(), VIEW_COUNT as usize); 366 | assert_eq!(views[0], views[1]); 367 | 368 | // Create the OpenXR swapchain 369 | let resolution = vk::Extent2D { 370 | width: views[0].recommended_image_rect_width, 371 | height: views[0].recommended_image_rect_height, 372 | }; 373 | let xr_swapchain = xr_session.create_swapchain(&openxr::SwapchainCreateInfo { 374 | create_flags: openxr::SwapchainCreateFlags::EMPTY, 375 | usage_flags: openxr::SwapchainUsageFlags::COLOR_ATTACHMENT 376 | | openxr::SwapchainUsageFlags::SAMPLED, 377 | format: VK_SWAPCHAIN_COLOR_FORMAT.as_raw() as _, 378 | sample_count: 1, 379 | width: resolution.width, 380 | height: resolution.height, 381 | face_count: 1, 382 | array_size: VIEW_COUNT, 383 | mip_count: 1, 384 | })?; 385 | 386 | // Create image views for the swapchain 387 | let swapcain_textures: Vec<_> = xr_swapchain 388 | .enumerate_images()? 389 | .into_iter() 390 | .map(|image| { 391 | let wgpu_tex_desc = wgpu::TextureDescriptor { 392 | label: Some("Swapchain Target"), 393 | size: Extent3d { 394 | width: resolution.width, 395 | height: resolution.height, 396 | depth_or_array_layers: VIEW_COUNT, 397 | }, 398 | mip_level_count: 1, 399 | sample_count: 1, 400 | dimension: wgpu::TextureDimension::D2, 401 | format: SWAPCHAIN_COLOR_FORMAT, 402 | view_formats: &[], 403 | usage: wgpu::TextureUsages::RENDER_ATTACHMENT 404 | | wgpu::TextureUsages::TEXTURE_BINDING 405 | | wgpu::TextureUsages::COPY_DST, 406 | }; 407 | 408 | let wgpu_hal_tex_desc = wgpu_hal::TextureDescriptor { 409 | label: wgpu_tex_desc.label, 410 | size: wgpu_tex_desc.size, 411 | mip_level_count: wgpu_tex_desc.mip_level_count, 412 | sample_count: wgpu_tex_desc.sample_count, 413 | dimension: wgpu_tex_desc.dimension, 414 | format: wgpu_tex_desc.format, 415 | view_formats: vec![], 416 | usage: wgpu_hal::TextureUses::COLOR_TARGET | wgpu_hal::TextureUses::COPY_DST, 417 | memory_flags: MemoryFlags::empty(), 418 | }; 419 | 420 | // Create a WGPU image view for this image 421 | // TODO: Move this to Texture2D::from_vk_image 422 | let wgpu_texture = vulkan_image_to_texture( 423 | device, 424 | vk::Image::from_raw(image), 425 | wgpu_tex_desc, 426 | wgpu_hal_tex_desc, 427 | ); 428 | 429 | Texture2D::::from_wgpu(device, wgpu_texture) 430 | }) 431 | .collect(); 432 | 433 | Ok((xr_swapchain, resolution, swapcain_textures)) 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "windows")] 2 | use ::windows::Win32::System::Threading::{ 3 | GetCurrentProcess, SetPriorityClass, HIGH_PRIORITY_CLASS, 4 | }; 5 | use anyhow::Context; 6 | use cgmath::Rotation3; 7 | use clap::Parser; 8 | use config::{AppConfig, TemporalBlurParams}; 9 | use engine::{ 10 | camera::{Camera, CameraUniform}, 11 | geometry::{ModelVertex, Vertex}, 12 | input::InputContext, 13 | screen::Screen, 14 | texture::{Bound, RoundRobinTextureBuffer, Texture2D, Unbound}, 15 | vr::{enable_xr_runtime, OpenXRContext, SWAPCHAIN_COLOR_FORMAT, VIEW_COUNT, VIEW_TYPE}, 16 | WgpuContext, WgpuLoader, 17 | }; 18 | use loaders::{katanga_loader::KatangaLoaderContext, Loader, StereoMode}; 19 | use log::LevelFilter; 20 | use log4rs::{ 21 | append::file::FileAppender, 22 | config::{Appender, Root}, 23 | encode::pattern::PatternEncoder, 24 | Config, 25 | }; 26 | use openxr::ReferenceSpaceType; 27 | use std::{ 28 | iter, 29 | num::NonZeroU32, 30 | sync::{Arc, Mutex}, 31 | }; 32 | use thread_priority::*; 33 | #[cfg(not(target_os = "android"))] 34 | use tray_item::TrayItem; 35 | use wgpu::{util::DeviceExt, BindGroupLayout}; 36 | 37 | use crate::config::ConfigContext; 38 | 39 | mod config; 40 | mod conversions; 41 | mod engine; 42 | mod loaders; 43 | 44 | #[derive(Clone)] 45 | enum TrayMessages { 46 | Quit, 47 | Reload, 48 | Recenter(bool), 49 | ToggleSettings(ToggleSetting), 50 | } 51 | 52 | #[derive(Clone)] 53 | enum ToggleSetting { 54 | FlipX, 55 | FlipY, 56 | SwapEyes, 57 | AmbientLight, 58 | } 59 | 60 | struct TrayState { 61 | pub message: Option<&'static TrayMessages>, 62 | } 63 | 64 | struct RecenterRequest { 65 | pub delay: i64, 66 | pub horizon_locked: bool, 67 | } 68 | 69 | const AMBIENT_BLUR_BASE_RES: u32 = 16; 70 | const AMBIENT_BLUR_TEMPORAL_SAMPLES: u32 = 16; 71 | #[cfg(feature = "dhat-heap")] 72 | #[global_allocator] 73 | static ALLOC: dhat::Alloc = dhat::Alloc; 74 | 75 | pub fn launch() -> anyhow::Result<()> { 76 | #[cfg(feature = "dhat-heap")] 77 | let _profiler = dhat::Profiler::new_heap(); 78 | 79 | #[cfg(feature = "profiling")] 80 | tracy_client::Client::start(); 81 | 82 | #[cfg(feature = "profiling")] 83 | profiling::register_thread!("Main Thread"); 84 | 85 | #[cfg(feature = "renderdoc")] 86 | let _rd: renderdoc::RenderDoc = 87 | renderdoc::RenderDoc::new().context("Unable to connect to renderdoc")?; 88 | 89 | #[cfg(not(target_os = "android"))] 90 | { 91 | let logfile = FileAppender::builder() 92 | .encoder(Box::new(PatternEncoder::new("{l} - {m}\n"))) 93 | .build("output.log")?; 94 | 95 | let config = Config::builder() 96 | .appender(Appender::builder().build("logfile", Box::new(logfile))) 97 | .build(Root::builder().appender("logfile").build(LevelFilter::Info))?; 98 | 99 | log4rs::init_config(config)?; 100 | log_panics::init(); 101 | } 102 | 103 | try_elevate_priority(); 104 | 105 | let mut xr_context = enable_xr_runtime()?; 106 | let wgpu_context = xr_context.load_wgpu()?; 107 | 108 | #[cfg(not(target_os = "android"))] 109 | let (_tray, tray_state) = build_tray()?; 110 | #[cfg(target_os = "android")] 111 | let tray_state = Arc::new(Mutex::new(TrayState { message: None })); 112 | 113 | let mut config_context = config::ConfigContext::try_setup().unwrap_or(None); 114 | 115 | log::info!("Finished initial setup, running main loop"); 116 | run( 117 | &mut xr_context, 118 | &wgpu_context, 119 | &tray_state, 120 | &mut config_context, 121 | )?; 122 | 123 | Ok(()) 124 | } 125 | 126 | #[cfg(not(target_os = "android"))] 127 | fn add_tray_message_sender( 128 | tray_state: &Arc>, 129 | tray: &mut TrayItem, 130 | entry_name: &'static str, 131 | message: &'static TrayMessages, 132 | ) -> anyhow::Result<()> { 133 | let cloned_state = tray_state.clone(); 134 | Ok(tray.add_menu_item(entry_name, move || { 135 | if let Ok(mut locked_state) = cloned_state.lock() { 136 | locked_state.message = Some(message); 137 | } 138 | })?) 139 | } 140 | 141 | #[cfg(not(target_os = "android"))] 142 | fn add_all_tray_message_senders( 143 | tray_state: &Arc>, 144 | tray: &mut TrayItem, 145 | entries: Vec<(&'static str, &'static TrayMessages)>, 146 | ) -> anyhow::Result<()> { 147 | for (entry_name, message) in entries { 148 | add_tray_message_sender(tray_state, tray, entry_name, message)?; 149 | } 150 | Ok(()) 151 | } 152 | 153 | #[cfg(not(target_os = "android"))] 154 | fn build_tray() -> anyhow::Result<(TrayItem, Arc>)> { 155 | log::info!("Building system tray"); 156 | let mut tray = TrayItem::new("VR Screen Cap", "tray-icon")?; 157 | let tray_state = Arc::new(Mutex::new(TrayState { message: None })); 158 | 159 | tray.add_label("Settings")?; 160 | add_all_tray_message_senders( 161 | &tray_state, 162 | &mut tray, 163 | vec![ 164 | ( 165 | "Swap Eyes", 166 | &TrayMessages::ToggleSettings(ToggleSetting::SwapEyes), 167 | ), 168 | ( 169 | "Flip X", 170 | &TrayMessages::ToggleSettings(ToggleSetting::FlipX), 171 | ), 172 | ( 173 | "Flip Y", 174 | &TrayMessages::ToggleSettings(ToggleSetting::FlipY), 175 | ), 176 | ( 177 | "Toggle Ambient Light", 178 | &TrayMessages::ToggleSettings(ToggleSetting::AmbientLight), 179 | ), 180 | ], 181 | )?; 182 | 183 | tray.add_label("Actions")?; 184 | add_all_tray_message_senders( 185 | &tray_state, 186 | &mut tray, 187 | vec![ 188 | ("Reload Screen", &TrayMessages::Reload), 189 | ("Recenter", &TrayMessages::Recenter(true)), 190 | ("Recenter w/ Pitch", &TrayMessages::Recenter(false)), 191 | ("Quit", &TrayMessages::Quit), 192 | ], 193 | )?; 194 | 195 | Ok((tray, tray_state)) 196 | } 197 | 198 | fn try_elevate_priority() { 199 | log::info!("Trying to elevate process priority"); 200 | if set_current_thread_priority(ThreadPriority::Max).is_err() { 201 | log::warn!("Failed to set thread priority to max!"); 202 | } 203 | 204 | #[cfg(target_os = "windows")] 205 | { 206 | if !unsafe { SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS) }.as_bool() { 207 | log::warn!("Failed to set process priority to max!"); 208 | } 209 | } 210 | } 211 | 212 | fn run( 213 | xr_context: &mut OpenXRContext, 214 | wgpu_context: &WgpuContext, 215 | tray_state: &Arc>, 216 | config: &mut Option, 217 | ) -> anyhow::Result<()> { 218 | // Load the shaders from disk 219 | let screen_shader = wgpu_context 220 | .device 221 | .create_shader_module(wgpu::include_wgsl!("shader.wgsl")); 222 | let blit_shader = wgpu_context 223 | .device 224 | .create_shader_module(wgpu::include_wgsl!("blit.wgsl")); 225 | 226 | // We don't need to configure the texture view much, so let's 227 | // let wgpu define it. 228 | 229 | let mut aspect_ratio = 1.0; 230 | let mut stereo_mode = StereoMode::Mono; 231 | let mut current_loader = None; 232 | 233 | //Load blank texture 234 | let blank_texture = Texture2D::::from_bytes( 235 | &wgpu_context.device, 236 | &wgpu_context.queue, 237 | include_bytes!("../assets/blank_grey.png"), 238 | "Blank", 239 | )?; 240 | 241 | let mut loaders: Vec> = vec![ 242 | #[cfg(target_os = "windows")] 243 | { 244 | Box::::default() 245 | }, 246 | ]; 247 | 248 | let texture_bind_group_layout = 249 | wgpu_context 250 | .device 251 | .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { 252 | entries: &[ 253 | wgpu::BindGroupLayoutEntry { 254 | binding: 0, 255 | visibility: wgpu::ShaderStages::FRAGMENT, 256 | ty: wgpu::BindingType::Texture { 257 | multisampled: false, 258 | view_dimension: wgpu::TextureViewDimension::D2, 259 | sample_type: wgpu::TextureSampleType::Float { filterable: true }, 260 | }, 261 | count: None, 262 | }, 263 | wgpu::BindGroupLayoutEntry { 264 | binding: 1, 265 | visibility: wgpu::ShaderStages::FRAGMENT, 266 | // This should match the filterable field of the 267 | // corresponding Texture entry above. 268 | ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), 269 | count: None, 270 | }, 271 | ], 272 | label: Some("texture_bind_group_layout"), 273 | }); 274 | 275 | let mut screen_texture = 276 | blank_texture.bind_to_context(wgpu_context, &texture_bind_group_layout); 277 | let mut ambient_texture = get_ambient_texture( 278 | &screen_texture, 279 | 1.0, 280 | &StereoMode::Mono, 281 | wgpu_context, 282 | &texture_bind_group_layout, 283 | )?; 284 | 285 | if let Some((texture, aspect, mode, loader)) = try_to_load_texture(&mut loaders, wgpu_context) { 286 | screen_texture = texture.bind_to_context(wgpu_context, &texture_bind_group_layout); 287 | ambient_texture = get_ambient_texture( 288 | &screen_texture, 289 | aspect, 290 | &mode, 291 | wgpu_context, 292 | &texture_bind_group_layout, 293 | )?; 294 | aspect_ratio = aspect; 295 | stereo_mode = mode; 296 | current_loader = Some(loader); 297 | } 298 | 299 | let fullscreen_triangle_index_buffer = 300 | wgpu_context 301 | .device 302 | .create_buffer_init(&wgpu::util::BufferInitDescriptor { 303 | label: Some("Fullscreen Tri Index Buffer"), 304 | contents: bytemuck::cast_slice(&[0, 1, 2]), 305 | usage: wgpu::BufferUsages::INDEX, 306 | }); 307 | 308 | let mut screen_params = match config { 309 | Some(ConfigContext { 310 | last_config: Some(config), 311 | .. 312 | }) => config.clone(), 313 | _ => AppConfig::parse(), 314 | }; 315 | 316 | let mut temporal_blur_params = TemporalBlurParams { 317 | jitter: [0.0, 0.0], 318 | scale: [1.1, 1.1], 319 | resolution: [ 320 | ambient_texture.current().texture.width() as f32, 321 | ambient_texture.current().texture.height() as f32, 322 | ], 323 | history_decay: 0.985, 324 | }; 325 | 326 | let mut screen = Screen::new( 327 | &wgpu_context.device, 328 | -screen_params.distance, 329 | screen_params.scale, 330 | aspect_ratio, 331 | screen_params.ambient, 332 | ); 333 | 334 | let screen_params_buffer = 335 | wgpu_context 336 | .device 337 | .create_buffer_init(&wgpu::util::BufferInitDescriptor { 338 | label: Some("Screen Params Buffer"), 339 | contents: bytemuck::cast_slice(&[screen_params.uniform(1.0, 1, 1)]), 340 | usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, 341 | }); 342 | 343 | let temporal_blur_params_buffer = 344 | wgpu_context 345 | .device 346 | .create_buffer_init(&wgpu::util::BufferInitDescriptor { 347 | label: Some("Temporal Blur Params Buffer"), 348 | contents: bytemuck::cast_slice(&[temporal_blur_params.uniform()]), 349 | usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, 350 | }); 351 | 352 | let screen_model_matrix_buffer = 353 | wgpu_context 354 | .device 355 | .create_buffer_init(&wgpu::util::BufferInitDescriptor { 356 | label: Some("Screen Model Matrix Buffer"), 357 | contents: bytemuck::cast_slice(&[screen.entity.uniform()]), 358 | usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, 359 | }); 360 | 361 | let mut cameras = vec![Camera::default(), Camera::default()]; 362 | let mut camera_uniform = vec![CameraUniform::new(), CameraUniform::new()]; 363 | 364 | let camera_buffer = wgpu_context 365 | .device 366 | .create_buffer_init(&wgpu::util::BufferInitDescriptor { 367 | label: Some("Camera Buffer"), 368 | contents: bytemuck::cast_slice(camera_uniform.as_slice()), 369 | usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST, 370 | }); 371 | 372 | let global_temporal_blur_uniform_layout = 373 | wgpu_context 374 | .device 375 | .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { 376 | entries: &[wgpu::BindGroupLayoutEntry { 377 | binding: 0, 378 | visibility: wgpu::ShaderStages::FRAGMENT, 379 | ty: wgpu::BindingType::Buffer { 380 | ty: wgpu::BufferBindingType::Uniform, 381 | has_dynamic_offset: false, 382 | min_binding_size: None, 383 | }, 384 | count: None, 385 | }], 386 | label: Some("global_temporal_blur_bind_group_layout"), 387 | }); 388 | 389 | let global_uniform_bind_group_layout = 390 | wgpu_context 391 | .device 392 | .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { 393 | entries: &[ 394 | wgpu::BindGroupLayoutEntry { 395 | binding: 0, 396 | visibility: wgpu::ShaderStages::VERTEX, 397 | ty: wgpu::BindingType::Buffer { 398 | ty: wgpu::BufferBindingType::Uniform, 399 | has_dynamic_offset: false, 400 | min_binding_size: None, 401 | }, 402 | count: None, 403 | }, 404 | wgpu::BindGroupLayoutEntry { 405 | binding: 1, 406 | visibility: wgpu::ShaderStages::VERTEX_FRAGMENT, 407 | ty: wgpu::BindingType::Buffer { 408 | ty: wgpu::BufferBindingType::Uniform, 409 | has_dynamic_offset: false, 410 | min_binding_size: None, 411 | }, 412 | count: None, 413 | }, 414 | wgpu::BindGroupLayoutEntry { 415 | binding: 2, 416 | visibility: wgpu::ShaderStages::VERTEX, 417 | ty: wgpu::BindingType::Buffer { 418 | ty: wgpu::BufferBindingType::Uniform, 419 | has_dynamic_offset: false, 420 | min_binding_size: None, 421 | }, 422 | count: None, 423 | }, 424 | ], 425 | label: Some("global_uniform_bind_group_layout"), 426 | }); 427 | 428 | let global_uniform_bind_group = 429 | wgpu_context 430 | .device 431 | .create_bind_group(&wgpu::BindGroupDescriptor { 432 | layout: &global_uniform_bind_group_layout, 433 | entries: &[ 434 | wgpu::BindGroupEntry { 435 | binding: 0, 436 | resource: camera_buffer.as_entire_binding(), 437 | }, 438 | wgpu::BindGroupEntry { 439 | binding: 1, 440 | resource: screen_params_buffer.as_entire_binding(), 441 | }, 442 | wgpu::BindGroupEntry { 443 | binding: 2, 444 | resource: screen_model_matrix_buffer.as_entire_binding(), 445 | }, 446 | ], 447 | label: Some("global_uniform_bind_group"), 448 | }); 449 | 450 | let global_temporal_blur_uniform_bind_group = 451 | wgpu_context 452 | .device 453 | .create_bind_group(&wgpu::BindGroupDescriptor { 454 | layout: &global_temporal_blur_uniform_layout, 455 | entries: &[wgpu::BindGroupEntry { 456 | binding: 0, 457 | resource: temporal_blur_params_buffer.as_entire_binding(), 458 | }], 459 | label: Some("global_temporal_blur_uniform_bind_group"), 460 | }); 461 | 462 | let render_pipeline_layout = 463 | wgpu_context 464 | .device 465 | .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { 466 | label: Some("Render Pipeline Layout"), 467 | bind_group_layouts: &[ 468 | &texture_bind_group_layout, 469 | &global_uniform_bind_group_layout, 470 | ], 471 | push_constant_ranges: &[], 472 | }); 473 | 474 | let blit_pipeline_layout = 475 | wgpu_context 476 | .device 477 | .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { 478 | label: Some("Blit Pipeline Layout"), 479 | bind_group_layouts: &[ 480 | &texture_bind_group_layout, 481 | &texture_bind_group_layout, 482 | &global_temporal_blur_uniform_layout, 483 | ], 484 | push_constant_ranges: &[], 485 | }); 486 | 487 | let screen_render_pipeline = 488 | wgpu_context 489 | .device 490 | .create_render_pipeline(&wgpu::RenderPipelineDescriptor { 491 | label: Some("Render Pipeline"), 492 | layout: Some(&render_pipeline_layout), 493 | vertex: wgpu::VertexState { 494 | module: &screen_shader, 495 | entry_point: "vs_main", 496 | buffers: &[ModelVertex::desc()], 497 | }, 498 | fragment: Some(wgpu::FragmentState { 499 | module: &screen_shader, 500 | entry_point: "fs_main", 501 | targets: &[Some(wgpu::ColorTargetState { 502 | format: SWAPCHAIN_COLOR_FORMAT, 503 | blend: Some(wgpu::BlendState::REPLACE), 504 | write_mask: wgpu::ColorWrites::ALL, 505 | })], 506 | }), 507 | primitive: wgpu::PrimitiveState::default(), 508 | depth_stencil: None, 509 | multisample: wgpu::MultisampleState::default(), 510 | multiview: NonZeroU32::new(VIEW_COUNT), 511 | }); 512 | 513 | let ambient_dome_pipeline = 514 | wgpu_context 515 | .device 516 | .create_render_pipeline(&wgpu::RenderPipelineDescriptor { 517 | label: Some("Ambient Dome Pipeline"), 518 | layout: Some(&render_pipeline_layout), 519 | vertex: wgpu::VertexState { 520 | module: &screen_shader, 521 | entry_point: "mv_vs_main", 522 | buffers: &[ModelVertex::desc()], 523 | }, 524 | fragment: Some(wgpu::FragmentState { 525 | module: &screen_shader, 526 | entry_point: "vignette_fs_main", 527 | targets: &[Some(wgpu::ColorTargetState { 528 | format: SWAPCHAIN_COLOR_FORMAT, 529 | blend: Some(wgpu::BlendState::REPLACE), 530 | write_mask: wgpu::ColorWrites::ALL, 531 | })], 532 | }), 533 | primitive: wgpu::PrimitiveState::default(), 534 | depth_stencil: None, 535 | multisample: wgpu::MultisampleState::default(), 536 | multiview: NonZeroU32::new(VIEW_COUNT), 537 | }); 538 | 539 | let temporal_blur_pipeline = 540 | wgpu_context 541 | .device 542 | .create_render_pipeline(&wgpu::RenderPipelineDescriptor { 543 | label: Some("Blit Pipeline"), 544 | layout: Some(&blit_pipeline_layout), 545 | vertex: wgpu::VertexState { 546 | module: &blit_shader, 547 | entry_point: "vs_main", 548 | buffers: &[], 549 | }, 550 | fragment: Some(wgpu::FragmentState { 551 | module: &blit_shader, 552 | entry_point: "temporal_fs_main", 553 | targets: &[ 554 | Some(wgpu::ColorTargetState { 555 | format: SWAPCHAIN_COLOR_FORMAT, 556 | blend: Some(wgpu::BlendState::REPLACE), 557 | write_mask: wgpu::ColorWrites::ALL, 558 | }), 559 | Some(wgpu::ColorTargetState { 560 | format: SWAPCHAIN_COLOR_FORMAT, 561 | blend: Some(wgpu::BlendState::REPLACE), 562 | write_mask: wgpu::ColorWrites::ALL, 563 | }), 564 | ], 565 | }), 566 | primitive: wgpu::PrimitiveState::default(), 567 | depth_stencil: None, 568 | multisample: wgpu::MultisampleState::default(), 569 | multiview: None, 570 | }); 571 | 572 | // Start the OpenXR session 573 | let (xr_session, mut frame_wait, mut frame_stream) = unsafe { 574 | xr_context.instance.create_session::( 575 | xr_context.system, 576 | &openxr::vulkan::SessionCreateInfo { 577 | instance: wgpu_context.vk_instance_ptr as _, 578 | physical_device: wgpu_context.vk_phys_device_ptr as _, 579 | device: wgpu_context.vk_device_ptr as _, 580 | queue_family_index: wgpu_context.queue_index, 581 | queue_index: 0, 582 | }, 583 | )? 584 | }; 585 | 586 | // Create a room-scale reference space 587 | let xr_reference_space = xr_session 588 | .create_reference_space(openxr::ReferenceSpaceType::LOCAL, openxr::Posef::IDENTITY)?; 589 | let xr_view_space = xr_session 590 | .create_reference_space(openxr::ReferenceSpaceType::VIEW, openxr::Posef::IDENTITY)?; 591 | let mut xr_space = xr_session 592 | .create_reference_space(openxr::ReferenceSpaceType::LOCAL, openxr::Posef::IDENTITY)?; 593 | 594 | let mut event_storage = openxr::EventDataBuffer::new(); 595 | let mut session_running = false; 596 | let mut swapchain = None; 597 | let mut screen_invalidated = false; 598 | let mut recenter_request = None; 599 | let mut last_invalidation_check = std::time::Instant::now(); 600 | let mut input_context = InputContext::init(&xr_context.instance) 601 | .map(Some) 602 | .unwrap_or(None); 603 | 604 | if input_context.is_some() { 605 | let mut attach_context = input_context 606 | .take() 607 | .context("Cannot attach input context to session")?; 608 | if attach_context.attach_to_session(&xr_session).is_ok() { 609 | input_context = Some(attach_context); 610 | } 611 | } 612 | 613 | let mut jitter_frame: u32 = 0; 614 | // Handle OpenXR events 615 | loop { 616 | #[cfg(feature = "profiling")] 617 | profiling::scope!("main loop"); 618 | 619 | let time = std::time::Instant::now(); 620 | 621 | if current_loader.is_some() || time.duration_since(last_invalidation_check).as_secs() > 10 { 622 | check_loader_invalidation(current_loader, &loaders, &mut screen_invalidated)?; 623 | last_invalidation_check = time; 624 | } 625 | 626 | if screen_invalidated { 627 | if let Some((texture, aspect, mode, loader)) = 628 | try_to_load_texture(&mut loaders, wgpu_context) 629 | { 630 | screen_texture = texture.bind_to_context(wgpu_context, &texture_bind_group_layout); 631 | ambient_texture = get_ambient_texture( 632 | &screen_texture, 633 | aspect, 634 | &mode, 635 | wgpu_context, 636 | &texture_bind_group_layout, 637 | )?; 638 | aspect_ratio = aspect; 639 | stereo_mode = mode; 640 | current_loader = Some(loader); 641 | screen.change_aspect_ratio(aspect_ratio); 642 | 643 | wgpu_context.queue.write_buffer( 644 | &screen_model_matrix_buffer, 645 | 0, 646 | bytemuck::cast_slice(&[screen.entity.uniform()]), 647 | ); 648 | 649 | let width_multiplier = match &stereo_mode { 650 | StereoMode::FullSbs => 2, 651 | _ => 1, 652 | }; 653 | 654 | wgpu_context.queue.write_buffer( 655 | &screen_params_buffer, 656 | 0, 657 | bytemuck::cast_slice(&[screen_params.uniform( 658 | aspect, 659 | screen_texture.texture.width() * width_multiplier, 660 | ambient_texture.current().texture.width() * width_multiplier, 661 | )]), 662 | ); 663 | } 664 | screen_invalidated = false; 665 | } 666 | 667 | let event = xr_context.instance.poll_event(&mut event_storage)?; 668 | match event { 669 | Some(openxr::Event::SessionStateChanged(e)) => { 670 | // Session state change is where we can begin and end sessions, as well as 671 | // find quit messages! 672 | log::info!("Entered state {:?}", e.state()); 673 | match e.state() { 674 | openxr::SessionState::READY => { 675 | xr_session.begin(VIEW_TYPE)?; 676 | session_running = true; 677 | } 678 | openxr::SessionState::STOPPING => { 679 | xr_session.end()?; 680 | session_running = false; 681 | } 682 | openxr::SessionState::EXITING => { 683 | break; 684 | } 685 | _ => {} 686 | } 687 | } 688 | Some(openxr::Event::InstanceLossPending(_)) => {} 689 | Some(openxr::Event::EventsLost(e)) => { 690 | log::error!("Lost {} OpenXR events", e.lost_event_count()); 691 | } 692 | Some(openxr::Event::ReferenceSpaceChangePending(_)) => { 693 | //Reset XR space to follow runtime 694 | xr_space = xr_session.create_reference_space( 695 | openxr::ReferenceSpaceType::LOCAL, 696 | openxr::Posef::IDENTITY, 697 | )?; 698 | } 699 | _ => { 700 | // Render to HMD only if we have an active session 701 | if session_running { 702 | // Block until the previous frame is finished displaying, and is ready for 703 | // another one. Also returns a prediction of when the next frame will be 704 | // displayed, for use with predicting locations of controllers, viewpoints, etc. 705 | #[cfg(feature = "profiling")] 706 | profiling::scope!("Wait for frame"); 707 | let xr_frame_state = frame_wait.wait()?; 708 | 709 | // Must be called before any rendering is done! 710 | frame_stream.begin()?; 711 | 712 | #[cfg(feature = "profiling")] 713 | profiling::scope!("FrameStream Recording"); 714 | 715 | // Only render if we should 716 | if !xr_frame_state.should_render { 717 | #[cfg(feature = "profiling")] 718 | { 719 | let predicted_display_time_nanos = 720 | xr_frame_state.predicted_display_time.as_nanos(); 721 | profiling::scope!( 722 | "Show Time Calculation", 723 | format!("{predicted_display_time_nanos}").as_str() 724 | ); 725 | } 726 | 727 | // Early bail 728 | if let Err(err) = frame_stream.end( 729 | xr_frame_state.predicted_display_time, 730 | xr_context.blend_mode, 731 | &[], 732 | ) { 733 | log::error!( 734 | "Failed to end frame stream when should_render is FALSE : {:?}", 735 | err 736 | ); 737 | }; 738 | continue; 739 | } 740 | 741 | #[cfg(feature = "profiling")] 742 | profiling::scope!("Swapchain Setup"); 743 | 744 | // If we do not have a swapchain yet, create it 745 | let (xr_swapchain, resolution, swapchain_textures) = match swapchain { 746 | Some(ref mut swapchain) => swapchain, 747 | None => { 748 | let new_swapchain = 749 | xr_context.create_swapchain(&xr_session, &wgpu_context.device)?; 750 | swapchain.get_or_insert(new_swapchain) 751 | } 752 | }; 753 | // Check which image we need to render to and wait until the compositor is 754 | // done with this image 755 | let image_index = xr_swapchain.acquire_image()?; 756 | xr_swapchain.wait_image(openxr::Duration::INFINITE)?; 757 | 758 | let swapchain_view = &swapchain_textures[image_index as usize].view; 759 | 760 | log::trace!("Encode render pass"); 761 | #[cfg(feature = "profiling")] 762 | profiling::scope!("Encode Render Pass"); 763 | // Render! 764 | let mut encoder = wgpu_context.device.create_command_encoder( 765 | &wgpu::CommandEncoderDescriptor { 766 | label: Some("Render Encorder"), 767 | }, 768 | ); 769 | if screen.ambient_enabled { 770 | ambient_texture.next(); 771 | let mut blit_pass = 772 | encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 773 | label: Some("Blit Pass"), 774 | color_attachments: &[ 775 | Some(wgpu::RenderPassColorAttachment { 776 | view: &ambient_texture.current().view, 777 | resolve_target: None, 778 | ops: wgpu::Operations { 779 | load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), 780 | store: true, 781 | }, 782 | }), 783 | Some(wgpu::RenderPassColorAttachment { 784 | view: &ambient_texture.previous(1).view, 785 | resolve_target: None, 786 | ops: wgpu::Operations { 787 | load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), 788 | store: true, 789 | }, 790 | }), 791 | ], 792 | depth_stencil_attachment: None, 793 | }); 794 | 795 | blit_pass.set_pipeline(&temporal_blur_pipeline); 796 | blit_pass.set_bind_group(0, screen_texture.bind_group(), &[]); 797 | blit_pass.set_bind_group(1, ambient_texture.previous(2).bind_group(), &[]); 798 | blit_pass.set_bind_group(2, &global_temporal_blur_uniform_bind_group, &[]); 799 | blit_pass.set_index_buffer( 800 | fullscreen_triangle_index_buffer.slice(..), 801 | wgpu::IndexFormat::Uint32, 802 | ); 803 | blit_pass.draw_indexed(0..3, 0, 0..1); 804 | } 805 | { 806 | let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { 807 | label: Some("Render Pass"), 808 | color_attachments: &[Some(wgpu::RenderPassColorAttachment { 809 | view: swapchain_view, 810 | resolve_target: None, 811 | ops: wgpu::Operations { 812 | load: wgpu::LoadOp::Clear(wgpu::Color::BLACK), 813 | store: true, 814 | }, 815 | })], 816 | depth_stencil_attachment: None, 817 | }); 818 | 819 | // Render the ambient dome 820 | if screen.ambient_enabled { 821 | let ambient_mesh = &screen.ambient_mesh; 822 | rpass.set_pipeline(&ambient_dome_pipeline); 823 | rpass.set_bind_group(0, ambient_texture.current().bind_group(), &[]); 824 | rpass.set_bind_group(1, &global_uniform_bind_group, &[]); 825 | rpass.set_vertex_buffer(0, ambient_mesh.vertex_buffer().slice(..)); 826 | rpass.set_index_buffer( 827 | ambient_mesh.index_buffer().slice(..), 828 | wgpu::IndexFormat::Uint32, 829 | ); 830 | rpass.draw_indexed(0..ambient_mesh.indices(), 0, 0..1); 831 | } 832 | 833 | // Render the screen 834 | rpass.set_pipeline(&screen_render_pipeline); 835 | rpass.set_bind_group(0, screen_texture.bind_group(), &[]); 836 | rpass.set_bind_group(1, &global_uniform_bind_group, &[]); 837 | rpass.set_vertex_buffer(0, screen.mesh.vertex_buffer().slice(..)); 838 | rpass.set_index_buffer( 839 | screen.mesh.index_buffer().slice(..), 840 | wgpu::IndexFormat::Uint32, 841 | ); 842 | rpass.draw_indexed(0..screen.mesh.indices(), 0, 0..1); 843 | } 844 | 845 | #[cfg(feature = "profiling")] 846 | profiling::scope!("Locate Views"); 847 | log::trace!("Locate views"); 848 | // Fetch the view transforms. To minimize latency, we intentionally do this 849 | // *after* recording commands to render the scene, i.e. at the last possible 850 | // moment before rendering begins in earnest on the GPU. Uniforms dependent on 851 | // this data can be sent to the GPU just-in-time by writing them to per-frame 852 | // host-visible memory which the GPU will only read once the command buffer is 853 | // submitted. 854 | let (_, views) = xr_session.locate_views( 855 | VIEW_TYPE, 856 | xr_frame_state.predicted_display_time, 857 | &xr_space, 858 | )?; 859 | 860 | for (view_idx, view) in views.iter().enumerate() { 861 | let mut eye = cameras 862 | .get_mut(view_idx) 863 | .context("Cannot borrow camera as mutable")?; 864 | eye.entity.position.x = view.pose.position.x; 865 | eye.entity.position.y = view.pose.position.y; 866 | eye.entity.position.z = view.pose.position.z; 867 | eye.entity.rotation.v.x = view.pose.orientation.x; 868 | eye.entity.rotation.v.y = view.pose.orientation.y; 869 | eye.entity.rotation.v.z = view.pose.orientation.z; 870 | eye.entity.rotation.s = view.pose.orientation.w; 871 | eye.entity.update_matrices(&[]); 872 | eye.update_projection_from_tangents(view.fov); 873 | let camera_uniform = camera_uniform 874 | .get_mut(view_idx) 875 | .context("Cannot borrow camera uniform buffer as mutable")?; 876 | camera_uniform.update_view_proj(eye)?; 877 | } 878 | 879 | log::trace!("Write views"); 880 | wgpu_context.queue.write_buffer( 881 | &camera_buffer, 882 | 0, 883 | bytemuck::cast_slice(camera_uniform.as_slice()), 884 | ); 885 | 886 | if screen.ambient_enabled { 887 | log::trace!("Writing temporal blur uniforms"); 888 | let ambient_texture = &ambient_texture.current().texture; 889 | let resolution = [ 890 | ambient_texture.width() as f32, 891 | ambient_texture.height() as f32, 892 | ]; 893 | jitter_frame = (jitter_frame + 1) % AMBIENT_BLUR_TEMPORAL_SAMPLES; 894 | temporal_blur_params.jitter = 895 | engine::jitter::get_jitter(jitter_frame, &resolution); 896 | temporal_blur_params.resolution = resolution; 897 | wgpu_context.queue.write_buffer( 898 | &temporal_blur_params_buffer, 899 | 0, 900 | bytemuck::cast_slice(&[temporal_blur_params.uniform()]), 901 | ); 902 | } 903 | 904 | #[cfg(feature = "profiling")] 905 | profiling::scope!("Encode Submit"); 906 | 907 | log::trace!("Submit command buffer"); 908 | wgpu_context.queue.submit(iter::once(encoder.finish())); 909 | 910 | #[cfg(feature = "profiling")] 911 | profiling::scope!("Release Swapchain"); 912 | log::trace!("Release swapchain image"); 913 | xr_swapchain.release_image()?; 914 | 915 | // End rendering and submit the images 916 | let rect = openxr::Rect2Di { 917 | offset: openxr::Offset2Di { x: 0, y: 0 }, 918 | extent: openxr::Extent2Di { 919 | width: resolution.width as _, 920 | height: resolution.height as _, 921 | }, 922 | }; 923 | 924 | log::trace!("End frame stream"); 925 | 926 | #[cfg(feature = "profiling")] 927 | { 928 | let predicted_display_time_nanos = 929 | xr_frame_state.predicted_display_time.as_nanos(); 930 | profiling::scope!( 931 | "Show Time Calculation", 932 | format!("{predicted_display_time_nanos}").as_str() 933 | ); 934 | } 935 | if let Err(err) = frame_stream.end( 936 | xr_frame_state.predicted_display_time, 937 | xr_context.blend_mode, 938 | &[&openxr::CompositionLayerProjection::new() 939 | .space(&xr_space) 940 | .views(&[ 941 | openxr::CompositionLayerProjectionView::new() 942 | .pose(views[0].pose) 943 | .fov(views[0].fov) 944 | .sub_image( 945 | openxr::SwapchainSubImage::new() 946 | .swapchain(xr_swapchain) 947 | .image_array_index(0) 948 | .image_rect(rect), 949 | ), 950 | openxr::CompositionLayerProjectionView::new() 951 | .pose(views[1].pose) 952 | .fov(views[1].fov) 953 | .sub_image( 954 | openxr::SwapchainSubImage::new() 955 | .swapchain(xr_swapchain) 956 | .image_array_index(1) 957 | .image_rect(rect), 958 | ), 959 | ])], 960 | ) { 961 | log::error!("Failed to end frame stream: {}", err); 962 | }; 963 | 964 | //XR Input processing 965 | if input_context.is_some() { 966 | #[cfg(feature = "profiling")] 967 | profiling::scope!("Process Inputs"); 968 | 969 | let input_context = input_context 970 | .as_mut() 971 | .context("Cannot borrow input context as mutable")?; 972 | 973 | if input_context 974 | .process_inputs( 975 | &xr_session, 976 | &xr_frame_state, 977 | &xr_reference_space, 978 | &xr_view_space, 979 | ) 980 | .is_ok() 981 | { 982 | if let Some(new_state) = &input_context.input_state { 983 | if new_state.hands_near_head > 0 984 | && new_state.near_start.elapsed().as_secs() > 3 985 | { 986 | let should_unlock_horizon = new_state.hands_near_head > 1 987 | || (new_state.hands_near_head == 1 988 | && new_state.count_change.elapsed().as_secs() < 1); 989 | 990 | if recenter_request.is_none() { 991 | recenter_request = Some(RecenterRequest { 992 | horizon_locked: !should_unlock_horizon, 993 | delay: 0, 994 | }); 995 | } 996 | } 997 | } 998 | } 999 | } 1000 | 1001 | if let Some(recenter_request) = recenter_request.take() { 1002 | if let Err(err) = recenter_scene( 1003 | &xr_session, 1004 | &xr_reference_space, 1005 | &xr_view_space, 1006 | xr_frame_state.predicted_display_time, 1007 | recenter_request.horizon_locked, 1008 | recenter_request.delay, 1009 | &mut xr_space, 1010 | ) { 1011 | log::error!("Failed to recenter scene: {}", err); 1012 | } 1013 | } 1014 | } 1015 | 1016 | #[cfg(feature = "profiling")] 1017 | profiling::finish_frame!(); 1018 | } 1019 | } 1020 | 1021 | // Non-XR Input processing 1022 | match tray_state 1023 | .lock() 1024 | .ok() 1025 | .context("Cannot get lock on icon tray state")? 1026 | .message 1027 | .take() 1028 | { 1029 | Some(TrayMessages::Quit) => { 1030 | log::info!("Qutting app manually..."); 1031 | break; 1032 | } 1033 | Some(TrayMessages::Reload) => { 1034 | check_loader_invalidation(current_loader, &loaders, &mut screen_invalidated)?; 1035 | } 1036 | Some(TrayMessages::Recenter(horizon_locked)) => { 1037 | recenter_request = Some(RecenterRequest { 1038 | horizon_locked: *horizon_locked, 1039 | delay: 0, 1040 | }); 1041 | } 1042 | Some(TrayMessages::ToggleSettings(setting)) => match setting { 1043 | ToggleSetting::SwapEyes => { 1044 | screen_params.swap_eyes = !screen_params.swap_eyes; 1045 | screen_invalidated = true; 1046 | } 1047 | ToggleSetting::FlipX => { 1048 | screen_params.flip_x = !screen_params.flip_x; 1049 | match stereo_mode { 1050 | StereoMode::Sbs | StereoMode::FullSbs => { 1051 | screen_params.swap_eyes = !screen_params.swap_eyes; 1052 | } 1053 | _ => {} 1054 | } 1055 | screen_invalidated = true; 1056 | } 1057 | ToggleSetting::FlipY => { 1058 | screen_params.flip_y = !screen_params.flip_y; 1059 | match stereo_mode { 1060 | StereoMode::Tab | StereoMode::FullTab => { 1061 | screen_params.swap_eyes = !screen_params.swap_eyes; 1062 | } 1063 | _ => {} 1064 | } 1065 | screen_invalidated = true; 1066 | } 1067 | ToggleSetting::AmbientLight => { 1068 | screen_params.ambient = !screen_params.ambient; 1069 | screen.change_ambient_mode(screen_params.ambient); 1070 | screen_invalidated = true; 1071 | } 1072 | }, 1073 | _ => {} 1074 | } 1075 | 1076 | if let Some(ConfigContext { 1077 | config_notifier: Some(config_receiver), 1078 | .. 1079 | }) = config 1080 | { 1081 | if config_receiver.try_recv().is_ok() { 1082 | let config = config 1083 | .as_mut() 1084 | .context("Cannot borrow configuration as mutable")?; 1085 | let config_changed = config.update_config().is_ok(); 1086 | 1087 | if config_changed { 1088 | if let Some(new_params) = config.last_config.clone() { 1089 | screen_params = new_params; 1090 | screen.change_scale(screen_params.scale); 1091 | screen.change_distance(-screen_params.distance); 1092 | screen.change_ambient_mode(screen_params.ambient); 1093 | screen_invalidated = true; 1094 | } 1095 | } 1096 | } 1097 | } 1098 | } 1099 | 1100 | Ok(()) 1101 | } 1102 | 1103 | fn get_ambient_texture( 1104 | screen_texture: &Texture2D, 1105 | aspect: f32, 1106 | stereo_mode: &StereoMode, 1107 | wgpu_context: &WgpuContext, 1108 | bind_group_layout: &BindGroupLayout, 1109 | ) -> anyhow::Result, 3>> { 1110 | let height_multiplier = match stereo_mode { 1111 | StereoMode::FullTab => 2, 1112 | _ => 1, 1113 | }; 1114 | 1115 | let width_multiplier = match stereo_mode { 1116 | StereoMode::FullSbs => 2, 1117 | _ => 1, 1118 | }; 1119 | 1120 | let buffer = RoundRobinTextureBuffer::new( 1121 | (0..3) 1122 | .map(|idx| { 1123 | screen_texture 1124 | .as_render_target_with_extent( 1125 | format!("Ambient Texture {idx}").as_str(), 1126 | wgpu::Extent3d { 1127 | width: AMBIENT_BLUR_BASE_RES * width_multiplier, 1128 | height: (AMBIENT_BLUR_BASE_RES as f32 / aspect) as u32 1129 | * height_multiplier, 1130 | depth_or_array_layers: screen_texture.texture.depth_or_array_layers(), 1131 | }, 1132 | SWAPCHAIN_COLOR_FORMAT, 1133 | &wgpu_context.device, 1134 | ) 1135 | .bind_to_context(wgpu_context, bind_group_layout) 1136 | }) 1137 | .collect::>() 1138 | .try_into() 1139 | .ok() 1140 | .context("Cannot create ambient texture buffer")?, 1141 | ); 1142 | 1143 | Ok(buffer) 1144 | } 1145 | 1146 | fn recenter_scene( 1147 | xr_session: &openxr::Session, 1148 | xr_reference_space: &openxr::Space, 1149 | xr_view_space: &openxr::Space, 1150 | last_predicted_frame_time: openxr::Time, 1151 | horizon_locked: bool, 1152 | delay: i64, 1153 | xr_space: &mut openxr::Space, 1154 | ) -> anyhow::Result<()> { 1155 | let mut view_location_pose = xr_view_space 1156 | .locate( 1157 | xr_reference_space, 1158 | openxr::Time::from_nanos(last_predicted_frame_time.as_nanos() - delay), 1159 | )? 1160 | .pose; 1161 | let quaternion = 1162 | cgmath::Quaternion::from(mint::Quaternion::from(view_location_pose.orientation)); 1163 | let forward = cgmath::Vector3::new(0.0, 0.0, 1.0); 1164 | let look_dir = quaternion * forward; 1165 | let yaw = cgmath::Rad(look_dir.x.atan2(look_dir.z)); 1166 | let clean_orientation = if horizon_locked { 1167 | cgmath::Quaternion::from_angle_y(yaw) 1168 | } else { 1169 | let padj = (look_dir.x * look_dir.x + look_dir.z * look_dir.z).sqrt(); 1170 | let pitch = -cgmath::Rad(look_dir.y.atan2(padj)); 1171 | cgmath::Quaternion::from_angle_y(yaw) * cgmath::Quaternion::from_angle_x(pitch) 1172 | }; 1173 | view_location_pose.orientation = openxr::Quaternionf { 1174 | x: clean_orientation.v.x, 1175 | y: clean_orientation.v.y, 1176 | z: clean_orientation.v.z, 1177 | w: clean_orientation.s, 1178 | }; 1179 | *xr_space = xr_session.create_reference_space(ReferenceSpaceType::LOCAL, view_location_pose)?; 1180 | 1181 | Ok(()) 1182 | } 1183 | 1184 | fn check_loader_invalidation( 1185 | current_loader: Option, 1186 | loaders: &[Box], 1187 | screen_invalidated: &mut bool, 1188 | ) -> anyhow::Result<()> { 1189 | if let Some(loader) = current_loader { 1190 | if loaders 1191 | .get(loader) 1192 | .context("Error getting loader")? 1193 | .is_invalid() 1194 | { 1195 | log::info!("Reloading app..."); 1196 | *screen_invalidated = true; 1197 | } 1198 | } else { 1199 | *screen_invalidated = true; 1200 | } 1201 | 1202 | Ok(()) 1203 | } 1204 | 1205 | fn try_to_load_texture( 1206 | loaders: &mut [Box], 1207 | wgpu_context: &WgpuContext, 1208 | ) -> Option<(Texture2D, f32, StereoMode, usize)> { 1209 | for (loader_idx, loader) in loaders.iter_mut().enumerate() { 1210 | if let Ok(tex_source) = loader.load(&wgpu_context.instance, &wgpu_context.device) { 1211 | return Some(( 1212 | tex_source.texture, 1213 | (tex_source.width as f32 / 2.0) / tex_source.height as f32, 1214 | tex_source.stereo_mode, 1215 | loader_idx, 1216 | )); 1217 | } 1218 | } 1219 | None 1220 | } 1221 | 1222 | #[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "full"))] 1223 | pub fn main() { 1224 | if let Err(err) = launch() { 1225 | log::error!("VRScreenCap closed unexpectedly with an error: {}", err); 1226 | } 1227 | } 1228 | -------------------------------------------------------------------------------- /src/loaders.rs: -------------------------------------------------------------------------------- 1 | use wgpu::{Device, Instance}; 2 | 3 | use crate::engine::texture::{Texture2D, Unbound}; 4 | 5 | #[cfg(target_os = "windows")] 6 | pub mod katanga_loader; 7 | 8 | pub struct TextureSource { 9 | pub texture: Texture2D, 10 | pub width: u32, 11 | pub height: u32, 12 | pub stereo_mode: StereoMode, 13 | } 14 | 15 | #[allow(unused)] 16 | pub enum StereoMode { 17 | Mono, 18 | Sbs, 19 | Tab, 20 | FullSbs, 21 | FullTab, 22 | } 23 | 24 | pub trait Loader { 25 | fn load(&mut self, instance: &Instance, device: &Device) -> anyhow::Result; 26 | 27 | fn is_invalid(&self) -> bool; 28 | } 29 | -------------------------------------------------------------------------------- /src/loaders/katanga_loader.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context}; 2 | use ash::vk::{self, ImageCreateInfo}; 3 | use wgpu::{Device, Instance, TextureFormat}; 4 | use wgpu_hal::{api::Vulkan, MemoryFlags, TextureDescriptor, TextureUses}; 5 | use windows::{ 6 | core::s, 7 | core::w, 8 | Win32::{ 9 | Foundation::{CloseHandle, HANDLE}, 10 | Graphics::{ 11 | Direct3D::D3D_DRIVER_TYPE_HARDWARE, 12 | Direct3D11::{ 13 | D3D11CreateDevice, ID3D11Texture2D, D3D11_CREATE_DEVICE_FLAG, D3D11_SDK_VERSION, 14 | D3D11_TEXTURE2D_DESC, 15 | }, 16 | Direct3D12::{D3D12CreateDevice, ID3D12Device, ID3D12Resource}, 17 | }, 18 | System::Memory::{ 19 | MapViewOfFile, OpenFileMappingA, UnmapViewOfFile, FILE_MAP_ALL_ACCESS, 20 | MEMORYMAPPEDVIEW_HANDLE, 21 | }, 22 | }, 23 | }; 24 | 25 | use crate::{ 26 | conversions::{map_texture_format, unmap_texture_format, vulkan_image_to_texture}, 27 | engine::texture::{Texture2D, Unbound}, 28 | }; 29 | 30 | use super::{Loader, TextureSource}; 31 | 32 | #[derive(Default)] 33 | pub struct KatangaLoaderContext { 34 | katanga_file_handle: HANDLE, 35 | katanga_file_mapping: MEMORYMAPPEDVIEW_HANDLE, 36 | current_address: usize, 37 | } 38 | 39 | impl Loader for KatangaLoaderContext { 40 | fn load(&mut self, _instance: &Instance, device: &Device) -> anyhow::Result { 41 | self.katanga_file_handle = unsafe { 42 | OpenFileMappingA(FILE_MAP_ALL_ACCESS.0, false, s!("Local\\KatangaMappedFile"))? 43 | }; 44 | log::info!("Handle: {:?}", self.katanga_file_handle); 45 | 46 | self.katanga_file_mapping = unsafe { 47 | MapViewOfFile( 48 | self.katanga_file_handle, 49 | FILE_MAP_ALL_ACCESS, 50 | 0, 51 | 0, 52 | std::mem::size_of::(), 53 | )? 54 | }; 55 | 56 | if self.katanga_file_mapping.is_invalid() { 57 | bail!("Cannot map file!"); 58 | } 59 | 60 | let address = unsafe { *(self.katanga_file_mapping.0 as *mut usize) }; 61 | self.current_address = address; 62 | let tex_handle = self.current_address as vk::HANDLE; 63 | log::info!("{:#01x}", tex_handle as usize); 64 | 65 | let tex_info = get_d3d11_texture_info(HANDLE(tex_handle as isize)).or_else(|err| { 66 | log::warn!("Not a D3D11Texture {}", err); 67 | get_d3d12_texture_info().map_err(|err| { 68 | log::warn!("Not a D3D12Texture {}", err); 69 | err 70 | }) 71 | })?; 72 | 73 | let tex_handle = tex_info.actual_handle as vk::HANDLE; 74 | if tex_info.actual_handle != self.current_address { 75 | log::info!("Actual Handle: {:?}", self.katanga_file_handle); 76 | } 77 | 78 | let vk_format = map_texture_format(tex_info.format); 79 | 80 | log::info!("Mapped DXGI format to {:?}", tex_info.format); 81 | log::info!("Mapped WGPU format to Vulkan {:?}", vk_format); 82 | 83 | let raw_image: Option> = unsafe { 84 | device.as_hal::(|device| { 85 | device.map(|device| { 86 | let raw_device = device.raw_device(); 87 | //let raw_phys_device = device.raw_physical_device(); 88 | let handle_type = match tex_info.external_api { 89 | ExternalApi::D3D11 => { 90 | vk::ExternalMemoryHandleTypeFlags::D3D11_TEXTURE_KMT_KHR 91 | } 92 | ExternalApi::D3D12 => vk::ExternalMemoryHandleTypeFlags::D3D12_RESOURCE_KHR, 93 | }; 94 | 95 | let mut import_memory_info = vk::ImportMemoryWin32HandleInfoKHR::builder() 96 | .handle_type(handle_type) 97 | .handle(tex_handle); 98 | 99 | let allocate_info = vk::MemoryAllocateInfo::builder() 100 | .push_next(&mut import_memory_info) 101 | .memory_type_index(0); 102 | 103 | let allocated_memory = raw_device.allocate_memory(&allocate_info, None)?; 104 | 105 | let mut ext_create_info = 106 | vk::ExternalMemoryImageCreateInfo::builder().handle_types(handle_type); 107 | 108 | let image_create_info = ImageCreateInfo::builder() 109 | .push_next(&mut ext_create_info) 110 | //.push_next(&mut dedicated_creation_info) 111 | .image_type(vk::ImageType::TYPE_2D) 112 | .format(vk_format) 113 | .extent(vk::Extent3D { 114 | width: tex_info.width, 115 | height: tex_info.height, 116 | depth: tex_info.array_size, 117 | }) 118 | .mip_levels(tex_info.mip_levels) 119 | .array_layers(tex_info.array_size) 120 | .samples(vk::SampleCountFlags::TYPE_1) 121 | .tiling(vk::ImageTiling::OPTIMAL) 122 | .usage(vk::ImageUsageFlags::TRANSFER_SRC | vk::ImageUsageFlags::SAMPLED) 123 | .sharing_mode(vk::SharingMode::CONCURRENT); 124 | 125 | let raw_image = raw_device.create_image(&image_create_info, None)?; 126 | 127 | raw_device.bind_image_memory(raw_image, allocated_memory, 0)?; 128 | 129 | Ok(raw_image) 130 | }) 131 | }) 132 | }; 133 | 134 | if let Some(Ok(raw_image)) = raw_image { 135 | let texture = vulkan_image_to_texture( 136 | device, 137 | raw_image, 138 | wgpu::TextureDescriptor { 139 | label: "KatangaStream".into(), 140 | size: wgpu::Extent3d { 141 | width: tex_info.width, 142 | height: tex_info.height, 143 | depth_or_array_layers: tex_info.array_size, 144 | }, 145 | mip_level_count: tex_info.mip_levels, 146 | sample_count: tex_info.sample_count, 147 | dimension: wgpu::TextureDimension::D2, 148 | format: tex_info.format, 149 | view_formats: &[], 150 | usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC, 151 | }, 152 | TextureDescriptor { 153 | label: "KatangaStream".into(), 154 | size: wgpu::Extent3d { 155 | width: tex_info.width, 156 | height: tex_info.height, 157 | depth_or_array_layers: tex_info.array_size, 158 | }, 159 | mip_level_count: tex_info.mip_levels, 160 | sample_count: tex_info.sample_count, 161 | dimension: wgpu::TextureDimension::D2, 162 | format: tex_info.format, 163 | view_formats: vec![], 164 | usage: TextureUses::RESOURCE | TextureUses::COPY_SRC, 165 | memory_flags: MemoryFlags::empty(), 166 | }, 167 | ); 168 | 169 | return Ok(TextureSource { 170 | texture: Texture2D::::from_wgpu(device, texture), 171 | width: tex_info.width, 172 | height: tex_info.height, 173 | stereo_mode: crate::loaders::StereoMode::FullSbs, 174 | }); 175 | } 176 | 177 | bail!("Cannot open shared texture!") 178 | } 179 | 180 | fn is_invalid(&self) -> bool { 181 | let address = unsafe { *(self.katanga_file_mapping.0 as *mut usize) }; 182 | self.current_address != address 183 | } 184 | } 185 | 186 | impl Drop for KatangaLoaderContext { 187 | fn drop(&mut self) { 188 | log::info!("Dropping KatangaLoaderContext"); 189 | 190 | if !self.katanga_file_mapping.is_invalid() 191 | && unsafe { bool::from(UnmapViewOfFile(self.katanga_file_mapping)) } 192 | { 193 | log::info!("Unmapped file!"); 194 | } 195 | 196 | if !self.katanga_file_handle.is_invalid() 197 | && unsafe { bool::from(CloseHandle(self.katanga_file_handle)) } 198 | { 199 | log::info!("Closed handle!"); 200 | } 201 | } 202 | } 203 | 204 | struct ExternalTextureInfo { 205 | external_api: ExternalApi, 206 | width: u32, 207 | height: u32, 208 | array_size: u32, 209 | sample_count: u32, 210 | mip_levels: u32, 211 | format: TextureFormat, 212 | actual_handle: usize, 213 | } 214 | 215 | enum ExternalApi { 216 | D3D11, 217 | D3D12, 218 | } 219 | 220 | fn get_d3d11_texture_info(handle: HANDLE) -> anyhow::Result { 221 | let mut d3d11_device = None; 222 | let mut d3d11_device_context = None; 223 | unsafe { 224 | D3D11CreateDevice( 225 | None, 226 | D3D_DRIVER_TYPE_HARDWARE, 227 | None, 228 | D3D11_CREATE_DEVICE_FLAG(0), 229 | None, 230 | D3D11_SDK_VERSION, 231 | Some(&mut d3d11_device), 232 | None, 233 | Some(&mut d3d11_device_context), 234 | )? 235 | }; 236 | let mut d3d11_texture: Option = None; 237 | unsafe { 238 | d3d11_device 239 | .as_ref() 240 | .context("DX11 device not initialized")? 241 | .OpenSharedResource(handle, &mut d3d11_texture) 242 | }?; 243 | let d3d11_texture = d3d11_texture.context("Failed to open shared DX11 texture")?; 244 | let mut texture_desc = D3D11_TEXTURE2D_DESC::default(); 245 | unsafe { d3d11_texture.GetDesc(&mut texture_desc) }; 246 | 247 | log::info!( 248 | "Got texture from DX11 with format {:?}", 249 | texture_desc.Format 250 | ); 251 | 252 | Ok(ExternalTextureInfo { 253 | external_api: ExternalApi::D3D11, 254 | width: texture_desc.Width, 255 | height: texture_desc.Height, 256 | array_size: texture_desc.ArraySize, 257 | sample_count: texture_desc.SampleDesc.Count, 258 | mip_levels: texture_desc.MipLevels, 259 | format: unmap_texture_format(texture_desc.Format), 260 | actual_handle: handle.0 as usize, 261 | }) 262 | } 263 | 264 | fn get_d3d12_texture_info() -> anyhow::Result { 265 | let mut d3d12_device: Option = None; 266 | unsafe { 267 | D3D12CreateDevice( 268 | None, 269 | windows::Win32::Graphics::Direct3D::D3D_FEATURE_LEVEL_12_0, 270 | &mut d3d12_device, 271 | ) 272 | }?; 273 | 274 | let d3d12_device = d3d12_device 275 | .as_ref() 276 | .context("DX12 device not initialized")?; 277 | 278 | let named_handle = unsafe { 279 | d3d12_device.OpenSharedHandleByName(w!("DX12VRStream"), 0x10000000) //GENERIC_ALL 280 | }?; 281 | 282 | let mut d3d12_texture: Option = None; 283 | unsafe { d3d12_device.OpenSharedHandle(named_handle, &mut d3d12_texture) }?; 284 | let d3d12_texture = d3d12_texture.context("Failed to open shared DX12 texture")?; 285 | 286 | let tex_info = unsafe { d3d12_texture.GetDesc() }; 287 | 288 | log::info!("Got texture from DX12 with format {:?}", tex_info.Format); 289 | 290 | Ok(ExternalTextureInfo { 291 | external_api: ExternalApi::D3D12, 292 | width: tex_info.Width as u32, 293 | height: tex_info.Height, 294 | array_size: tex_info.DepthOrArraySize as u32, 295 | sample_count: tex_info.SampleDesc.Count, 296 | mip_levels: tex_info.MipLevels as u32, 297 | format: unmap_texture_format(tex_info.Format), 298 | actual_handle: named_handle.0 as usize, 299 | }) 300 | } 301 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #![windows_subsystem = "windows"] 2 | use vr_screen_cap_core::launch; 3 | 4 | pub fn main() { 5 | if let Err(err) = launch() { 6 | log::error!("VRScreenCap closed unexpectedly with an error: {}", err); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/shader.wgsl: -------------------------------------------------------------------------------- 1 | // Vertex shader 2 | struct CameraUniform { 3 | view_proj: mat4x4, 4 | }; 5 | 6 | struct ModelUniform { 7 | model_matrix: mat4x4, 8 | }; 9 | 10 | struct ScreenParams { 11 | x_curvature: f32, 12 | y_curvature: f32, 13 | eye_offset: f32, 14 | y_offset: f32, 15 | x_offset: f32, 16 | aspect_ratio: f32, 17 | screen_width: u32, 18 | ambient_width: u32, 19 | }; 20 | 21 | @group(1) @binding(0) 22 | var camera: array; 23 | @group(1) @binding(1) 24 | var screen_params: ScreenParams; 25 | //Could be a push constant but we only have one entity 26 | @group(1) @binding(2) 27 | var model_uniform: ModelUniform; 28 | 29 | struct VertexInput { 30 | @location(0) position: vec3, 31 | @location(1) tex_coords: vec2 32 | }; 33 | 34 | struct VertexOutput { 35 | @builtin(position) clip_position: vec4, 36 | @location(0) tex_coords: vec2, 37 | }; 38 | 39 | @vertex 40 | fn vs_main( 41 | model: VertexInput, 42 | @builtin(view_index) view_index: i32 43 | ) -> VertexOutput { 44 | var out: VertexOutput; 45 | out.tex_coords = model.tex_coords; 46 | let x_diff = (model.tex_coords.x - 0.5) * 2.0; 47 | let y_diff = (model.tex_coords.y - 0.5) * 2.0; 48 | let z_x_curvature = (1.0 - x_diff * x_diff) * screen_params.x_curvature; 49 | let z_y_curvature = (1.0 - y_diff * y_diff) * screen_params.y_curvature; 50 | out.clip_position = camera[view_index].view_proj * model_uniform.model_matrix * vec4(model.position.xy, model.position.z - z_x_curvature - z_y_curvature, 1.0); 51 | return out; 52 | } 53 | 54 | @group(0) @binding(0) 55 | var t_diffuse: texture_2d; 56 | @group(0) @binding(1) 57 | var s_diffuse: sampler; 58 | 59 | fn uv_to_stereo_uv(view_index: i32, uv: vec2) -> vec2 { 60 | let x_offset = abs(f32(view_index) - screen_params.eye_offset) / 2.0; 61 | return vec2(abs(uv.x - screen_params.x_offset) / 2.0 + x_offset, abs(uv.y - screen_params.y_offset)); 62 | } 63 | 64 | @fragment 65 | fn fs_main(in: VertexOutput, @builtin(view_index) view_index: i32) -> @location(0) vec4 { 66 | return textureSample(t_diffuse, s_diffuse, uv_to_stereo_uv(view_index, in.tex_coords)); 67 | } 68 | 69 | @vertex 70 | fn mv_vs_main( 71 | model: VertexInput, 72 | @builtin(view_index) view_index: i32 73 | ) -> VertexOutput { 74 | var out: VertexOutput; 75 | out.tex_coords = model.tex_coords; 76 | out.clip_position = camera[view_index].view_proj * model_uniform.model_matrix * vec4(model.position.xyz, 1.0); 77 | return out; 78 | } 79 | 80 | fn weighted9_sample(vstep: f32, hstep: f32, 81 | w_ul: f32, w_u: f32, w_ur: f32, 82 | w_l: f32, w_c: f32, w_r: f32, 83 | w_dl: f32, w_d: f32, w_dr: f32, 84 | t_texture: texture_2d, s_texture: sampler, uv: vec2) -> vec4 { 85 | let center_color = textureSample(t_texture, s_texture, uv); 86 | let center_up_color = textureSample(t_texture, s_texture, uv + vec2(0.0, vstep)); 87 | let center_down_color = textureSample(t_texture, s_texture, uv - vec2(0.0, vstep)); 88 | let center_left_color = textureSample(t_texture, s_texture, uv + vec2(hstep, 0.0)); 89 | let center_right_color = textureSample(t_texture, s_texture, uv - vec2(hstep, 0.0)); 90 | let center_up_left_color = textureSample(t_texture, s_texture, uv + vec2(hstep, vstep)); 91 | let center_up_right_color = textureSample(t_texture, s_texture, uv + vec2(-hstep, vstep)); 92 | let center_down_right_color = textureSample(t_texture, s_texture, uv - vec2(hstep, vstep)); 93 | let center_down_left_color = textureSample(t_texture, s_texture, uv - vec2(-hstep, vstep)); 94 | return center_color * w_c 95 | + center_up_color * w_u 96 | + center_down_color * w_d 97 | + center_left_color * w_l 98 | + center_right_color * w_r 99 | + center_up_left_color * w_ul 100 | + center_up_right_color * w_ur 101 | + center_down_right_color * w_dr 102 | + center_down_left_color * w_dl; 103 | } 104 | 105 | @fragment 106 | fn vignette_fs_main(in: VertexOutput, @builtin(view_index) view_index: i32) -> @location(0) vec4 { 107 | let clamped_text_coords = clamp(in.tex_coords, vec2(0.0), vec2(1.0)) - vec2(0.5); 108 | let dist = length(clamped_text_coords); 109 | let vig = 1.0 - smoothstep(0.35, 0.5, dist); 110 | let hstep = 1.0 / f32(screen_params.ambient_width); 111 | let vstep = 1.0 / (f32(screen_params.ambient_width) * screen_params.aspect_ratio); 112 | return vec4(weighted9_sample(hstep, vstep, 113 | //0.0625, 0.125, 0.0625, 114 | //0.125, 0.25, 0.125, 115 | //0.0625, 0.125, 0.0625, 116 | 0.1111111111111111, 0.1111111111111111, 0.1111111111111111, 117 | 0.1111111111111111, 0.1111111111111111, 0.1111111111111111, 118 | 0.1111111111111111, 0.1111111111111111, 0.1111111111111111, 119 | t_diffuse, s_diffuse, uv_to_stereo_uv(view_index, in.tex_coords)).rgb * vig, 1.0); 120 | } --------------------------------------------------------------------------------