├── .gitignore ├── .idea ├── .name ├── codeStyles │ ├── Project.xml │ └── codeStyleConfig.xml ├── compiler.xml ├── deploymentTargetDropDown.xml ├── inspectionProfiles │ └── Project_Default.xml ├── jarRepositories.xml └── misc.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── AndroidManifest.xml ├── CMakeLists.txt ├── build.gradle.kts ├── cpp │ ├── CMakeLists.txt │ ├── graphics_plugin.hpp │ ├── graphics_plugin_vulkan.cpp │ ├── main.cpp │ ├── math_utils.h │ ├── openxr-include.hpp │ ├── openxr_program.cpp │ ├── openxr_program.hpp │ ├── openxr_utils.cpp │ ├── openxr_utils.hpp │ ├── platform.hpp │ ├── platform_android.cpp │ ├── platform_data.hpp │ ├── shaders │ │ ├── CMakeLists.txt │ │ ├── frag.glsl │ │ └── vert.glsl │ ├── vulkan │ │ ├── CMakeLists.txt │ │ ├── data_type.cpp │ │ ├── data_type.hpp │ │ ├── redering_pipeline_config.hpp │ │ ├── vertex_buffer_layout.cpp │ │ ├── vertex_buffer_layout.hpp │ │ ├── vulkan_buffer.cpp │ │ ├── vulkan_buffer.hpp │ │ ├── vulkan_rendering_context.cpp │ │ ├── vulkan_rendering_context.hpp │ │ ├── vulkan_rendering_pipeline.cpp │ │ ├── vulkan_rendering_pipeline.hpp │ │ ├── vulkan_shader.cpp │ │ ├── vulkan_shader.hpp │ │ ├── vulkan_utils.cpp │ │ └── vulkan_utils.hpp │ ├── vulkan_swapchain_context.cpp │ └── vulkan_swapchain_context.hpp ├── libs │ ├── debug │ │ └── arm64-v8a │ │ │ ├── libVkLayer_khronos_validation.so │ │ │ └── libopenxr_loader.so │ └── release │ │ └── arm64-v8a │ │ └── libopenxr_loader.so └── meta_quest_openxr_loader │ └── CMakeLists.txt ├── build.gradle.kts ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio generated files and folders 37 | captures/ 38 | .externalNativeBuild/ 39 | .cxx/ 40 | output.json 41 | 42 | # IntelliJ 43 | *.iml 44 | .idea/appInsightsSettings.xml 45 | .idea/assetWizardSettings.xml 46 | .idea/dictionaries 47 | .idea/gradle.xml 48 | .idea/libraries 49 | .idea/migrations.xml 50 | .idea/tasks.xml 51 | .idea/workspace.xml 52 | # Android Studio 3 in .gitignore file. 53 | .idea/caches 54 | .idea/modules.xml 55 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 56 | .idea/navEditor.xml 57 | 58 | # Keystore files 59 | # Uncomment the following lines if you do not want to check your keystore files in. 60 | #*.jks 61 | #*.keystore 62 | 63 | # Google Services (e.g. APIs or Firebase) 64 | # google-services.json 65 | 66 | # Freeline 67 | freeline.py 68 | freeline/ 69 | freeline_project_description.json 70 | 71 | # fastlane 72 | fastlane/report.xml 73 | fastlane/Preview.html 74 | fastlane/screenshots 75 | fastlane/test_output 76 | fastlane/readme.md 77 | 78 | # Version control 79 | vcs.xml 80 | 81 | # lint 82 | lint/intermediates/ 83 | lint/generated/ 84 | lint/outputs/ 85 | lint/tmp/ 86 | # lint/reports/ 87 | 88 | #macos DS_Store 89 | .DS_Store 90 | 91 | #compiled spirv files 92 | *.spv 93 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Quest XR -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 74 | 75 | 189 | 190 | 192 | 193 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/deploymentTargetDropDown.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quest XR 2 | 3 | This is a sample project that is using only OpenXR and Vulkan on the android platform and targets Oculus Quest 2 device. This project demonstrates how to create a native c++ only project with Gradle and Cmake and utilize OpenXR runtime. The project was taken from the official [OpenXR SDK samples](https://github.com/KhronosGroup/OpenXR-SDK-Source). 4 | 5 | 6 | ### Building the project 7 | 8 | Only the following dependencies are required: 9 | 10 | - [Android SDK](https://developer.android.com/studio) 11 | 12 | - [Vulkan SDK](https://vulkan.lunarg.com/sdk/home) 13 | 14 | To build the project just run the following in the project directory: 15 | 16 | ```bash 17 | ./gradlew assembleDebug #for debug build 18 | ./gradlew assembleRelease # for release build 19 | ``` 20 | 21 | After that, apk can be found in `app/build/outputs/apk/` directory. 22 | 23 | ### Preview (Screenshot from Quest2) 24 | 25 | ![](https://user-images.githubusercontent.com/22776744/148455860-78d585cc-252c-481c-9fb3-a45999326977.jpg) 26 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 13 | 21 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22.1) 2 | include(FetchContent) 3 | 4 | project(quest-xr) 5 | 6 | set(CMAKE_CXX_STANDARD 20) 7 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 8 | set(CMAKE_POLICY_DEFAULT_CMP0077 NEW) 9 | 10 | add_subdirectory(meta_quest_openxr_loader) 11 | 12 | FetchContent_Declare(magic_enum 13 | GIT_REPOSITORY https://github.com/Neargye/magic_enum.git 14 | GIT_TAG v0.9.5 15 | GIT_SHALLOW TRUE 16 | GIT_PROGRESS TRUE 17 | ) 18 | FetchContent_MakeAvailable(magic_enum) 19 | 20 | 21 | FetchContent_Declare(spdlog 22 | GIT_REPOSITORY https://github.com/gabime/spdlog.git 23 | GIT_TAG v1.13.0 24 | GIT_SHALLOW TRUE 25 | GIT_PROGRESS TRUE 26 | ) 27 | FetchContent_MakeAvailable(spdlog) 28 | 29 | FetchContent_Declare(glm 30 | GIT_REPOSITORY https://github.com/g-truc/glm.git 31 | GIT_TAG 1.0.1 32 | GIT_SHALLOW TRUE 33 | GIT_PROGRESS TRUE 34 | ) 35 | FetchContent_MakeAvailable(glm) 36 | 37 | FetchContent_Declare(SPIRV-Reflect 38 | GIT_REPOSITORY https://github.com/KhronosGroup/SPIRV-Reflect.git 39 | GIT_TAG vulkan-sdk-1.3.280.0 40 | GIT_SHALLOW TRUE 41 | GIT_PROGRESS TRUE 42 | ) 43 | set(SPIRV_REFLECT_EXAMPLES OFF) 44 | set(SPIRV_REFLECT_EXECUTABLE OFF) 45 | set(SPIRV_REFLECT_STATIC_LIB ON) 46 | FetchContent_MakeAvailable(SPIRV-Reflect) 47 | 48 | FetchContent_Declare(OpenXR-SDK 49 | GIT_REPOSITORY https://github.com/KhronosGroup/OpenXR-SDK.git 50 | GIT_TAG release-1.0.33 #must match the meta quest loader OpenXR version 51 | #https://developer.oculus.com/downloads/package/oculus-openxr-mobile-sdk#current-openxr-version 52 | GIT_SHALLOW TRUE 53 | GIT_PROGRESS TRUE 54 | ) 55 | FetchContent_MakeAvailable(OpenXR-SDK) 56 | 57 | add_subdirectory(cpp) 58 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | } 4 | android { 5 | compileSdk = 32 6 | ndkVersion = "26.3.11579264" 7 | namespace = "app.artyomd.questxr" 8 | defaultConfig { 9 | minSdk = 32 10 | targetSdk = 32 11 | versionCode = 1 12 | versionName = "1.0" 13 | applicationId = "app.artyomd.questxr" 14 | externalNativeBuild { 15 | cmake { 16 | arguments.add("-DANDROID_STL=c++_shared") 17 | arguments.add("-DANDROID_USE_LEGACY_TOOLCHAIN_FILE=OFF") 18 | } 19 | ndk { 20 | abiFilters.add("arm64-v8a") 21 | } 22 | } 23 | } 24 | lint { 25 | disable.add("ExpiredTargetSdkVersion") 26 | } 27 | buildTypes { 28 | getByName("release") { 29 | isDebuggable = false 30 | isJniDebuggable = false 31 | } 32 | getByName("debug") { 33 | isDebuggable = true 34 | isJniDebuggable = true 35 | } 36 | } 37 | externalNativeBuild { 38 | cmake { 39 | version = "3.22.1" 40 | path("CMakeLists.txt") 41 | } 42 | } 43 | sourceSets { 44 | getByName("main") { 45 | manifest.srcFile("AndroidManifest.xml") 46 | } 47 | getByName("debug") { 48 | jniLibs { 49 | srcDir("libs/debug") 50 | } 51 | } 52 | getByName("release") { 53 | jniLibs.srcDir("libs/release") 54 | } 55 | } 56 | packaging { 57 | jniLibs { 58 | keepDebugSymbols.add("**.so") 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/cpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(vulkan) 2 | add_subdirectory(shaders) 3 | 4 | add_library(native_app_glue STATIC ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) 5 | target_include_directories(native_app_glue PUBLIC ${ANDROID_NDK}/sources/android/native_app_glue) 6 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") 7 | 8 | add_library(quest-xr SHARED 9 | graphics_plugin_vulkan.cpp 10 | main.cpp 11 | openxr_program.cpp 12 | openxr_utils.cpp 13 | platform_android.cpp 14 | vulkan_swapchain_context.cpp 15 | ) 16 | 17 | target_compile_definitions(quest-xr PRIVATE XR_USE_PLATFORM_ANDROID 18 | XR_USE_GRAPHICS_API_VULKAN 19 | VK_USE_PLATFORM_ANDROID_KHR) 20 | 21 | target_link_libraries( 22 | quest-xr 23 | android 24 | glm 25 | native_app_glue 26 | meta_quest_openxr_loader 27 | OpenXR::headers 28 | shaders 29 | magic_enum 30 | spdlog 31 | vulkan-wrapper 32 | ) 33 | -------------------------------------------------------------------------------- /app/cpp/graphics_plugin.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "openxr-include.hpp" 4 | #include "math_utils.h" 5 | 6 | #include 7 | #include 8 | 9 | class GraphicsPlugin { 10 | public: 11 | virtual std::vector GetOpenXrInstanceExtensions() const = 0; 12 | virtual void InitializeDevice(XrInstance instance, XrSystemId system_id) = 0; 13 | virtual const XrBaseInStructure *GetGraphicsBinding() const = 0; 14 | virtual int64_t SelectSwapchainFormat(const std::vector &runtime_formats) = 0; 15 | 16 | virtual XrSwapchainImageBaseHeader *AllocateSwapchainImageStructs(uint32_t capacity, 17 | const XrSwapchainCreateInfo &swapchain_create_info) = 0; 18 | 19 | virtual void SwapchainImageStructsReady(XrSwapchainImageBaseHeader *images) = 0; 20 | 21 | virtual void RenderView(const XrCompositionLayerProjectionView &layer_view, 22 | XrSwapchainImageBaseHeader *swapchain_images, 23 | const uint32_t image_index, 24 | const std::vector &cube_transforms) = 0; 25 | 26 | virtual void DeinitDevice() = 0; 27 | 28 | virtual ~GraphicsPlugin() = default; 29 | }; 30 | 31 | std::shared_ptr CreateGraphicsPlugin(); 32 | -------------------------------------------------------------------------------- /app/cpp/graphics_plugin_vulkan.cpp: -------------------------------------------------------------------------------- 1 | #include "graphics_plugin.hpp" 2 | 3 | #include "openxr_utils.hpp" 4 | 5 | #include "vulkan_swapchain_context.hpp" 6 | #include "vulkan/data_type.hpp" 7 | #include "vulkan/vulkan_rendering_context.hpp" 8 | #include "vulkan/vulkan_rendering_pipeline.hpp" 9 | #include "vulkan/vulkan_utils.hpp" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | namespace { 18 | const std::vector kCubePositions = { 19 | -0.5, -0.5, 0.5, 1.0, 0.0, 0.0, 20 | 0.5, -0.5, 0.5, 0.0, 1.0, 0.0, 21 | 0.5, 0.5, 0.5, 0.0, 0.0, 1.0, 22 | -0.5, 0.5, 0.5, 1.0, 1.0, 1.0, 23 | -0.5, -0.5, -0.5, 1.0, 0.0, 0.0, 24 | 0.5, -0.5, -0.5, 0.0, 1.0, 0.0, 25 | 0.5, 0.5, -0.5, 0.0, 0.0, 1.0, 26 | -0.5, 0.5, -0.5, 1.0, 1.0, 1.0 27 | }; 28 | const std::vector kCubeIndices{ 29 | 0, 1, 2, 30 | 2, 3, 0, 31 | 1, 5, 6, 32 | 6, 2, 1, 33 | 7, 6, 5, 34 | 5, 4, 7, 35 | 4, 0, 3, 36 | 3, 7, 4, 37 | 4, 5, 1, 38 | 1, 0, 4, 39 | 3, 2, 6, 40 | 6, 7, 3 41 | }; 42 | 43 | VkResult CreateDebugUtilsMessengerExt( 44 | VkInstance instance, 45 | const VkDebugUtilsMessengerCreateInfoEXT *p_create_info, 46 | const VkAllocationCallbacks *p_allocator, 47 | VkDebugUtilsMessengerEXT *p_debug_messenger 48 | ) { 49 | auto func = reinterpret_cast( 50 | vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT") 51 | ); 52 | if (func != nullptr) { 53 | return func(instance, p_create_info, p_allocator, p_debug_messenger); 54 | } else { 55 | return VK_ERROR_EXTENSION_NOT_PRESENT; 56 | } 57 | } 58 | 59 | void DestroyDebugUtilsMessengerExt( 60 | VkInstance instance, 61 | VkDebugUtilsMessengerEXT debug_messenger, 62 | const VkAllocationCallbacks *p_allocator 63 | ) { 64 | auto func = reinterpret_cast( 65 | vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT") 66 | ); 67 | if (func != nullptr) { 68 | func(instance, debug_messenger, p_allocator); 69 | } 70 | } 71 | 72 | class VulkanGraphicsPlugin : public GraphicsPlugin { 73 | [[nodiscard]] std::vector GetOpenXrInstanceExtensions() const override { 74 | return {XR_KHR_VULKAN_ENABLE2_EXTENSION_NAME}; 75 | } 76 | 77 | void InitializeDevice(XrInstance xr_instance, XrSystemId system_id) override { 78 | XrGraphicsRequirementsVulkan2KHR graphics_requirements{}; 79 | graphics_requirements.type = XR_TYPE_GRAPHICS_REQUIREMENTS_VULKAN2_KHR; 80 | 81 | PFN_xrGetVulkanGraphicsRequirements2KHR pfn_get_vulkan_graphics_requirements_khr = nullptr; 82 | CHECK_XRCMD(xrGetInstanceProcAddr(xr_instance, "xrGetVulkanGraphicsRequirements2KHR", 83 | reinterpret_cast(&pfn_get_vulkan_graphics_requirements_khr))); 84 | if (pfn_get_vulkan_graphics_requirements_khr == nullptr) { 85 | throw std::runtime_error("unable to obtain address of xrGetVulkanGraphicsRequirements2KHR"); 86 | } 87 | CHECK_XRCMD(pfn_get_vulkan_graphics_requirements_khr(xr_instance, 88 | system_id, 89 | &graphics_requirements)); 90 | 91 | PFN_xrCreateVulkanInstanceKHR pfn_xr_create_vulkan_instance_khr = nullptr; 92 | CHECK_XRCMD(xrGetInstanceProcAddr(xr_instance, "xrCreateVulkanInstanceKHR", 93 | reinterpret_cast(&pfn_xr_create_vulkan_instance_khr))); 94 | if (pfn_xr_create_vulkan_instance_khr == nullptr) { 95 | throw std::runtime_error("unable to obtain address of xrCreateVulkanInstanceKHR"); 96 | } 97 | VkApplicationInfo app_info = { 98 | .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, 99 | .pApplicationName= "quest-xr", 100 | .applicationVersion = 0u, 101 | .pEngineName = "No Engine", 102 | .engineVersion = 0u, 103 | .apiVersion = VK_MAKE_VERSION(XR_VERSION_MAJOR(graphics_requirements.maxApiVersionSupported), 104 | XR_VERSION_MINOR(graphics_requirements.maxApiVersionSupported), 105 | XR_VERSION_PATCH(graphics_requirements.maxApiVersionSupported)), 106 | }; 107 | 108 | std::vector layers{}; 109 | std::vector instance_extensions{}; 110 | 111 | bool debug_utils_enabled = false; 112 | bool validation_features_enabled = false; 113 | void *instance_create_info_next = nullptr; 114 | std::vector 115 | enabled_features = {VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_EXT, 116 | VK_VALIDATION_FEATURE_ENABLE_GPU_ASSISTED_RESERVE_BINDING_SLOT_EXT, 117 | VK_VALIDATION_FEATURE_ENABLE_BEST_PRACTICES_EXT, 118 | VK_VALIDATION_FEATURE_ENABLE_SYNCHRONIZATION_VALIDATION_EXT,}; 119 | VkValidationFeaturesEXT validation_features_ext = { 120 | .sType = VK_STRUCTURE_TYPE_VALIDATION_FEATURES_EXT, 121 | .enabledValidationFeatureCount = static_cast(enabled_features.size()), 122 | .pEnabledValidationFeatures = enabled_features.data(), 123 | .disabledValidationFeatureCount = 0, 124 | .pDisabledValidationFeatures = nullptr, 125 | }; 126 | 127 | #if !defined(NDEBUG) 128 | auto available_layers = vulkan::GetAvailableInstanceLayers(); 129 | for (const auto &kLayer: available_layers) { 130 | if (strcmp(kLayer.layerName, "VK_LAYER_KHRONOS_validation") == 0) { 131 | layers.emplace_back("VK_LAYER_KHRONOS_validation"); 132 | break; 133 | } 134 | } 135 | if (!layers.empty()) { 136 | auto available_extensions = 137 | vulkan::GetAvailableInstanceExtensions("VK_LAYER_KHRONOS_validation"); 138 | for (const auto &kExt: available_extensions) { 139 | if (strcmp(kExt.extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0) { 140 | instance_extensions.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); 141 | debug_utils_enabled = true; 142 | } else if (strcmp(kExt.extensionName, VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME) == 0) { 143 | instance_extensions.emplace_back(VK_EXT_VALIDATION_FEATURES_EXTENSION_NAME); 144 | validation_features_enabled = true; 145 | } 146 | } 147 | } 148 | if (validation_features_enabled) { 149 | instance_create_info_next = &validation_features_ext; 150 | } 151 | #endif 152 | 153 | VkInstanceCreateInfo instance_create_info{}; 154 | instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; 155 | instance_create_info.pApplicationInfo = &app_info; 156 | instance_create_info.enabledLayerCount = static_cast(layers.size()); 157 | instance_create_info.ppEnabledLayerNames = layers.empty() ? nullptr : layers.data(); 158 | instance_create_info.enabledExtensionCount = static_cast(instance_extensions.size()); 159 | instance_create_info.ppEnabledExtensionNames = 160 | instance_extensions.empty() ? nullptr : instance_extensions.data(); 161 | 162 | XrVulkanInstanceCreateInfoKHR vulkan_instance_create_info_khr{}; 163 | vulkan_instance_create_info_khr.type = XR_TYPE_VULKAN_INSTANCE_CREATE_INFO_KHR; 164 | vulkan_instance_create_info_khr.systemId = system_id; 165 | vulkan_instance_create_info_khr.createFlags = 0; 166 | vulkan_instance_create_info_khr.pfnGetInstanceProcAddr = &vkGetInstanceProcAddr; 167 | vulkan_instance_create_info_khr.vulkanCreateInfo = &instance_create_info; 168 | vulkan_instance_create_info_khr.vulkanAllocator = nullptr; 169 | 170 | VkResult instance_create_result = VK_SUCCESS; 171 | CHECK_XRCMD(pfn_xr_create_vulkan_instance_khr(xr_instance, 172 | &vulkan_instance_create_info_khr, 173 | &vulkan_instance_, 174 | &instance_create_result)); 175 | if (instance_create_result != VK_SUCCESS) { 176 | throw std::runtime_error("unable to create vulkan instance"); 177 | } 178 | 179 | if (debug_utils_enabled) { 180 | VkDebugUtilsMessengerCreateInfoEXT create_info = { 181 | .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, 182 | .pNext = nullptr, 183 | .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT 184 | | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT 185 | | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT 186 | | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, 187 | .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT 188 | | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT 189 | | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, 190 | .pfnUserCallback = []( 191 | VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, 192 | VkDebugUtilsMessageTypeFlagsEXT flags, 193 | const VkDebugUtilsMessengerCallbackDataEXT *p_callback_data, 194 | void *) -> VKAPI_ATTR VkBool32 VKAPI_CALL { 195 | auto flag_to_string = [](VkDebugUtilsMessageTypeFlagsEXT flag) { 196 | std::string flags; 197 | if (flag & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) { 198 | flags += "|GENERAL"; 199 | } 200 | if (flag & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT) { 201 | flags += "|VALIDATION"; 202 | } 203 | if (flag & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT) { 204 | flags += "|PERFORMANCE"; 205 | } 206 | flags += "|"; 207 | return flags; 208 | }; 209 | if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT) { 210 | spdlog::info("validation layer {}: {}", 211 | flag_to_string(flags), 212 | p_callback_data->pMessage); 213 | } else if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { 214 | spdlog::warn("validation layer {}: {}", 215 | flag_to_string(flags), 216 | p_callback_data->pMessage); 217 | } else if (message_severity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) { 218 | spdlog::error("validation layer {}: {}", 219 | flag_to_string(flags), 220 | p_callback_data->pMessage); 221 | } else { 222 | spdlog::debug("validation layer {}: {}", 223 | flag_to_string(flags), 224 | p_callback_data->pMessage); 225 | } 226 | return VK_FALSE; 227 | },}; 228 | CHECK_VKCMD(CreateDebugUtilsMessengerExt(vulkan_instance_, 229 | &create_info, 230 | nullptr, 231 | &debug_messenger_)); 232 | } 233 | 234 | PFN_xrGetVulkanGraphicsDevice2KHR pfn_get_vulkan_graphics_device_khr = nullptr; 235 | CHECK_XRCMD(xrGetInstanceProcAddr(xr_instance, "xrGetVulkanGraphicsDevice2KHR", 236 | reinterpret_cast(&pfn_get_vulkan_graphics_device_khr))); 237 | if (pfn_get_vulkan_graphics_device_khr == nullptr) { 238 | throw std::runtime_error("unable to obtain address of xrGetVulkanGraphicsDevice2KHR"); 239 | } 240 | XrVulkanGraphicsDeviceGetInfoKHR vulkan_graphics_device_get_info_khr{}; 241 | vulkan_graphics_device_get_info_khr.type = XR_TYPE_VULKAN_GRAPHICS_DEVICE_GET_INFO_KHR; 242 | vulkan_graphics_device_get_info_khr.systemId = system_id; 243 | vulkan_graphics_device_get_info_khr.vulkanInstance = vulkan_instance_; 244 | CHECK_XRCMD(pfn_get_vulkan_graphics_device_khr(xr_instance, 245 | &vulkan_graphics_device_get_info_khr, 246 | &physical_device_)); 247 | 248 | PFN_xrCreateVulkanDeviceKHR pfn_xr_create_vulkan_device_khr = nullptr; 249 | CHECK_XRCMD(xrGetInstanceProcAddr(xr_instance, "xrCreateVulkanDeviceKHR", 250 | reinterpret_cast(&pfn_xr_create_vulkan_device_khr))); 251 | if (pfn_xr_create_vulkan_device_khr == nullptr) { 252 | throw std::runtime_error("failed to get address of xrCreateVulkanDeviceKHR"); 253 | } 254 | 255 | float queue_priorities = 0; 256 | VkDeviceQueueCreateInfo queue_info{}; 257 | queue_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; 258 | queue_info.queueCount = 1; 259 | queue_info.pQueuePriorities = &queue_priorities; 260 | 261 | uint32_t queue_family_count = 0; 262 | vkGetPhysicalDeviceQueueFamilyProperties(physical_device_, &queue_family_count, 263 | nullptr); 264 | std::vector queue_family_properties(queue_family_count); 265 | vkGetPhysicalDeviceQueueFamilyProperties(physical_device_, 266 | &queue_family_count, 267 | &queue_family_properties[0]); 268 | for (uint32_t i = 0; i < queue_family_count; ++i) { 269 | if ((queue_family_properties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) != 0u) { 270 | graphics_queue_family_index_ = queue_info.queueFamilyIndex = i; 271 | break; 272 | } 273 | } 274 | 275 | VkPhysicalDeviceFeatures features{}; 276 | 277 | VkDeviceCreateInfo device_create_info{}; 278 | device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; 279 | device_create_info.queueCreateInfoCount = 1; 280 | device_create_info.pQueueCreateInfos = &queue_info; 281 | device_create_info.enabledLayerCount = 0; 282 | device_create_info.ppEnabledLayerNames = nullptr; 283 | device_create_info.enabledExtensionCount = 0; 284 | device_create_info.ppEnabledExtensionNames = nullptr; 285 | device_create_info.pEnabledFeatures = &features; 286 | 287 | XrVulkanDeviceCreateInfoKHR vulkan_device_create_info_khr{}; 288 | vulkan_device_create_info_khr.type = XR_TYPE_VULKAN_DEVICE_CREATE_INFO_KHR; 289 | vulkan_device_create_info_khr.systemId = system_id; 290 | vulkan_device_create_info_khr.createFlags = 0; 291 | vulkan_device_create_info_khr.pfnGetInstanceProcAddr = &vkGetInstanceProcAddr; 292 | vulkan_device_create_info_khr.vulkanPhysicalDevice = physical_device_; 293 | vulkan_device_create_info_khr.vulkanCreateInfo = &device_create_info; 294 | vulkan_device_create_info_khr.vulkanAllocator = nullptr; 295 | 296 | VkResult vulkan_device_create_result = VK_SUCCESS; 297 | CHECK_XRCMD(pfn_xr_create_vulkan_device_khr(xr_instance, 298 | &vulkan_device_create_info_khr, 299 | &logical_device_, 300 | &vulkan_device_create_result)); 301 | if (vulkan_device_create_result != VK_SUCCESS) { 302 | throw std::runtime_error("unable to create vulkan logical device"); 303 | } 304 | vkGetDeviceQueue(logical_device_, queue_info.queueFamilyIndex, 0, &graphic_queue_); 305 | 306 | VkCommandPoolCreateInfo pool_info = {}; 307 | pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; 308 | pool_info.queueFamilyIndex = graphics_queue_family_index_; 309 | pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; 310 | CHECK_VKCMD(vkCreateCommandPool(logical_device_, &pool_info, nullptr, &graphics_command_pool_)); 311 | 312 | graphics_binding_.type = XR_TYPE_GRAPHICS_BINDING_VULKAN2_KHR; 313 | graphics_binding_.instance = vulkan_instance_; 314 | graphics_binding_.physicalDevice = physical_device_; 315 | graphics_binding_.device = logical_device_; 316 | graphics_binding_.queueFamilyIndex = queue_info.queueFamilyIndex; 317 | graphics_binding_.queueIndex = 0; 318 | } 319 | 320 | void InitializeResources() { 321 | 322 | const std::vector kVertexShader = { 323 | #include "vert.spv" 324 | }; 325 | const std::vector kFragmentShader = { 326 | #include "frag.spv" 327 | }; 328 | 329 | auto vertex_shader = std::make_shared(rendering_context_, 330 | kVertexShader, 331 | "main"); 332 | auto fragment_shader = std::make_shared(rendering_context_, 333 | kFragmentShader, 334 | "main"); 335 | 336 | vulkan::VertexBufferLayout vertex_buffer_layout = vulkan::VertexBufferLayout(); 337 | vertex_buffer_layout.Push({0, vulkan::DataType::FLOAT, 3}); 338 | vertex_buffer_layout.Push({1, vulkan::DataType::FLOAT, 3}); 339 | 340 | auto pipeline_config = vulkan::RenderingPipelineConfig{ 341 | .draw_mode = vulkan::DrawMode::TRIANGLE_LIST, 342 | .cull_mode = vulkan::CullMode::BACK, 343 | .front_face = vulkan::FrontFace::CCW, 344 | .enable_depth_test = true, 345 | .depth_function = vulkan::CompareOp::LESS, 346 | }; 347 | pipeline_ = std::make_shared( 348 | rendering_context_, 349 | vertex_shader, 350 | fragment_shader, 351 | vertex_buffer_layout, 352 | pipeline_config 353 | ); 354 | auto vertex_buffer = std::make_shared( 355 | rendering_context_, 356 | sizeof(float) * kCubePositions.size(), 357 | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 358 | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); 359 | vertex_buffer->Update(kCubePositions.data()); 360 | pipeline_->SetVertexBuffer(vertex_buffer); 361 | 362 | auto index_buffer = std::make_shared( 363 | rendering_context_, 364 | sizeof(unsigned short) * kCubeIndices.size(), 365 | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, 366 | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); 367 | index_buffer->Update(kCubeIndices.data()); 368 | pipeline_->SetIndexBuffer(index_buffer, vulkan::DataType::UINT_16); 369 | } 370 | 371 | [[nodiscard]] int64_t SelectSwapchainFormat(const std::vector &runtime_formats) override { 372 | if (rendering_context_ != nullptr) { 373 | throw std::runtime_error("select swapchain format must be called only once"); 374 | } 375 | constexpr VkFormat kPreferredSwapchainFormats[] = { //4 channel formats 376 | VK_FORMAT_R8G8B8A8_SRGB, 377 | VK_FORMAT_R8G8B8A8_UNORM, 378 | VK_FORMAT_B8G8R8A8_SRGB, 379 | VK_FORMAT_B8G8R8A8_UNORM}; 380 | 381 | auto swapchain_format_it = std::find_first_of(runtime_formats.begin(), runtime_formats.end(), 382 | std::begin(kPreferredSwapchainFormats), 383 | std::end(kPreferredSwapchainFormats)); 384 | if (swapchain_format_it == runtime_formats.end()) { 385 | throw std::runtime_error("No runtime swapchain format supported for swapchain"); 386 | } 387 | rendering_context_ = std::make_shared( 388 | physical_device_, 389 | logical_device_, 390 | graphic_queue_, 391 | graphics_command_pool_, 392 | (VkFormat) (*swapchain_format_it)); 393 | InitializeResources(); 394 | return *swapchain_format_it; 395 | } 396 | 397 | [[nodiscard]] const XrBaseInStructure *GetGraphicsBinding() const override { 398 | return reinterpret_cast (&graphics_binding_); 399 | } 400 | 401 | XrSwapchainImageBaseHeader *AllocateSwapchainImageStructs(uint32_t capacity, 402 | const XrSwapchainCreateInfo &swapchain_create_info) override { 403 | auto swapchain_context = std::make_shared(rendering_context_, 404 | capacity, 405 | swapchain_create_info); 406 | auto images = swapchain_context->GetFirstImagePointer(); 407 | image_to_context_mapping_.insert(std::make_pair(images, swapchain_context)); 408 | return images; 409 | } 410 | 411 | void SwapchainImageStructsReady(XrSwapchainImageBaseHeader *images) override { 412 | auto context = this->image_to_context_mapping_[images]; 413 | if (context->IsInited()) { 414 | throw std::runtime_error("trying to init same image twice"); 415 | } 416 | context->InitSwapchainImageViews(); 417 | } 418 | void RenderView(const XrCompositionLayerProjectionView &layer_view, 419 | XrSwapchainImageBaseHeader *swapchain_images, 420 | const uint32_t image_index, 421 | const std::vector &cube_transforms) override { 422 | if (layer_view.subImage.imageArrayIndex != 0) { 423 | throw std::runtime_error("Texture arrays not supported"); 424 | } 425 | glm::mat4 proj = math::CreateProjectionFov(layer_view.fov, 0.05f, 100.0f); 426 | glm::mat4 view = math::InvertRigidBody( 427 | glm::translate(glm::identity(), math::XrVector3FToGlm(layer_view.pose.position)) 428 | * glm::mat4_cast(math::XrQuaternionFToGlm(layer_view.pose.orientation)) 429 | ); 430 | std::vector transforms{}; 431 | for (const math::Transform &cube: cube_transforms) { 432 | glm::mat4 model = glm::scale(glm::translate(glm::identity(), cube.position) 433 | * glm::mat4_cast(cube.orientation), cube.scale); 434 | transforms.emplace_back(proj * view * model); 435 | } 436 | auto swapchain_context = image_to_context_mapping_[swapchain_images]; 437 | 438 | swapchain_context->Draw(image_index, 439 | pipeline_, 440 | kCubeIndices.size(), 441 | transforms); 442 | } 443 | 444 | void DeinitDevice() override { 445 | image_to_context_mapping_.clear(); 446 | pipeline_ = nullptr; 447 | rendering_context_ = nullptr; 448 | vkDestroyCommandPool(logical_device_, graphics_command_pool_, nullptr); 449 | vkDestroyDevice(logical_device_, nullptr); 450 | if (debug_messenger_ != VK_NULL_HANDLE) { 451 | DestroyDebugUtilsMessengerExt(vulkan_instance_, debug_messenger_, nullptr); 452 | } 453 | vkDestroyInstance(vulkan_instance_, nullptr); 454 | } 455 | 456 | private: 457 | XrGraphicsBindingVulkan2KHR graphics_binding_{}; 458 | 459 | VkInstance vulkan_instance_ = VK_NULL_HANDLE; 460 | VkDebugUtilsMessengerEXT debug_messenger_ = VK_NULL_HANDLE; 461 | VkPhysicalDevice physical_device_ = VK_NULL_HANDLE; 462 | 463 | std::shared_ptr rendering_context_ = nullptr; 464 | std::shared_ptr pipeline_ = nullptr; 465 | 466 | VkDevice logical_device_ = VK_NULL_HANDLE; 467 | uint32_t graphics_queue_family_index_ = 0; 468 | VkQueue graphic_queue_ = VK_NULL_HANDLE; 469 | VkCommandPool graphics_command_pool_ = VK_NULL_HANDLE; 470 | 471 | std::map> 472 | image_to_context_mapping_{}; 473 | }; 474 | } // namespace 475 | 476 | std::shared_ptr CreateGraphicsPlugin() { 477 | return std::make_shared(); 478 | } 479 | -------------------------------------------------------------------------------- /app/cpp/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "platform_data.hpp" 4 | #include "platform.hpp" 5 | 6 | #include "openxr_program.hpp" 7 | 8 | #include 9 | #include 10 | 11 | struct AndroidAppState { 12 | bool resumed = false; 13 | }; 14 | 15 | static void AppHandleCmd(struct android_app *app, int32_t cmd) { 16 | auto *app_state = reinterpret_cast(app->userData); 17 | switch (cmd) { 18 | case APP_CMD_START: { 19 | spdlog::info("APP_CMD_START onStart()"); 20 | break; 21 | } 22 | case APP_CMD_RESUME: { 23 | spdlog::info("APP_CMD_RESUME onResume()"); 24 | app_state->resumed = true; 25 | break; 26 | } 27 | case APP_CMD_PAUSE: { 28 | spdlog::info("APP_CMD_PAUSE onPause()"); 29 | app_state->resumed = false; 30 | break; 31 | } 32 | case APP_CMD_STOP: { 33 | spdlog::info("APP_CMD_STOP onStop()"); 34 | break; 35 | } 36 | case APP_CMD_DESTROY: { 37 | spdlog::info("APP_CMD_DESTROY onDestroy()"); 38 | break; 39 | } 40 | case APP_CMD_INIT_WINDOW: { 41 | spdlog::info("APP_CMD_INIT_WINDOW surfaceCreated()"); 42 | break; 43 | } 44 | case APP_CMD_TERM_WINDOW: { 45 | spdlog::info("APP_CMD_TERM_WINDOW surfaceDestroyed()"); 46 | break; 47 | } 48 | } 49 | } 50 | 51 | void android_main(struct android_app *app) { 52 | try { 53 | auto android_logger = spdlog::android_logger_mt("android", "spdlog-android"); 54 | android_logger->set_level(spdlog::level::info); 55 | spdlog::set_default_logger(android_logger); 56 | 57 | JNIEnv *env; 58 | app->activity->vm->AttachCurrentThread(&env, nullptr); 59 | 60 | AndroidAppState app_state = {}; 61 | 62 | app->userData = &app_state; 63 | app->onAppCmd = AppHandleCmd; 64 | 65 | std::shared_ptr data = std::make_shared(); 66 | data->application_vm = app->activity->vm; 67 | data->application_activity = app->activity->clazz; 68 | 69 | std::shared_ptr program = CreateOpenXrProgram(CreatePlatform(data)); 70 | 71 | program->CreateInstance(); 72 | program->InitializeSystem(); 73 | program->InitializeSession(); 74 | program->CreateSwapchains(); 75 | while (app->destroyRequested == 0) { 76 | for (;;) { 77 | int events; 78 | struct android_poll_source *source; 79 | const int kTimeoutMilliseconds = 80 | (!app_state.resumed && !program->IsSessionRunning() && 81 | app->destroyRequested == 0) ? -1 : 0; 82 | if (ALooper_pollAll(kTimeoutMilliseconds, nullptr, &events, (void **) &source) < 0) { 83 | break; 84 | } 85 | if (source != nullptr) { 86 | source->process(app, source); 87 | } 88 | } 89 | 90 | program->PollEvents(); 91 | if (!program->IsSessionRunning()) { 92 | continue; 93 | } 94 | 95 | program->PollActions(); 96 | program->RenderFrame(); 97 | } 98 | 99 | app->activity->vm->DetachCurrentThread(); 100 | } catch (const std::exception &ex) { 101 | spdlog::error(ex.what()); 102 | } catch (...) { 103 | spdlog::error("Unknown Error"); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /app/cpp/math_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #define GLM_ENABLE_EXPERIMENTAL 5 | #include 6 | 7 | namespace math { 8 | struct Transform { 9 | glm::quat orientation; 10 | glm::vec3 position; 11 | glm::vec3 scale; 12 | }; 13 | 14 | // The projection matrix transforms -Z=forward, +Y=up, +X=right to the appropriate clip space for the graphics API. 15 | // The far plane is placed at infinity if far <= near. 16 | // An infinite projection matrix is preferred for rasterization because, except for 17 | // things *right* up against the near plane, it always provides better precision: 18 | // "Tightening the Precision of Perspective Rendering" 19 | // Paul Upchurch, Mathieu Desbrun 20 | // Journal of Graphics Tools, Volume 16, Issue 1, 2012 21 | inline static glm::mat4 CreateProjectionFov(const XrFovf fov, 22 | const float near, 23 | const float far) { 24 | const float kTanAngleLeft = tanf(fov.angleLeft); 25 | const float kTanAngleRight = tanf(fov.angleRight); 26 | 27 | const float kTanAngleUp = tanf(fov.angleUp); 28 | const float kTanAngleDown = tanf(fov.angleDown); 29 | 30 | const float kTanAngleWidth = kTanAngleRight - kTanAngleLeft; 31 | const float kTanAngleHeight = kTanAngleUp - kTanAngleDown; 32 | glm::mat4 result; 33 | auto result_ptr = &result[0][0]; 34 | if (far <= near) { 35 | // place the far plane at infinity 36 | result_ptr[0] = 2 / kTanAngleWidth; 37 | result_ptr[4] = 0; 38 | result_ptr[8] = (kTanAngleRight + kTanAngleLeft) / kTanAngleWidth; 39 | result_ptr[12] = 0; 40 | 41 | result_ptr[1] = 0; 42 | result_ptr[5] = 2 / kTanAngleHeight; 43 | result_ptr[9] = (kTanAngleUp + kTanAngleDown) / kTanAngleHeight; 44 | result_ptr[13] = 0; 45 | 46 | result_ptr[2] = 0; 47 | result_ptr[6] = 0; 48 | result_ptr[10] = -1; 49 | result_ptr[14] = -near; 50 | 51 | result_ptr[3] = 0; 52 | result_ptr[7] = 0; 53 | result_ptr[11] = -1; 54 | result_ptr[15] = 0; 55 | } else { 56 | // normal projection 57 | result_ptr[0] = 2 / kTanAngleWidth; 58 | result_ptr[4] = 0; 59 | result_ptr[8] = (kTanAngleRight + kTanAngleLeft) / kTanAngleWidth; 60 | result_ptr[12] = 0; 61 | 62 | result_ptr[1] = 0; 63 | result_ptr[5] = 2 / kTanAngleHeight; 64 | result_ptr[9] = (kTanAngleUp + kTanAngleDown) / kTanAngleHeight; 65 | result_ptr[13] = 0; 66 | 67 | result_ptr[2] = 0; 68 | result_ptr[6] = 0; 69 | result_ptr[10] = -far / (far - near); 70 | result_ptr[14] = -(far * near) / (far - near); 71 | 72 | result_ptr[3] = 0; 73 | result_ptr[7] = 0; 74 | result_ptr[11] = -1; 75 | result_ptr[15] = 0; 76 | } 77 | return result; 78 | } 79 | inline static glm::mat4 InvertRigidBody(const glm::mat4 src) { 80 | glm::mat4 result{}; 81 | auto result_ptr = &result[0][0]; 82 | auto src_ptr = &src[0][0]; 83 | result_ptr[0] = src_ptr[0]; 84 | result_ptr[1] = src_ptr[4]; 85 | result_ptr[2] = src_ptr[8]; 86 | result_ptr[3] = 0.0f; 87 | result_ptr[4] = src_ptr[1]; 88 | result_ptr[5] = src_ptr[5]; 89 | result_ptr[6] = src_ptr[9]; 90 | result_ptr[7] = 0.0f; 91 | result_ptr[8] = src_ptr[2]; 92 | result_ptr[9] = src_ptr[6]; 93 | result_ptr[10] = src_ptr[10]; 94 | result_ptr[11] = 0.0f; 95 | result_ptr[12] = 96 | -(src_ptr[0] * src_ptr[12] + src_ptr[1] * src_ptr[13] + src_ptr[2] * src_ptr[14]); 97 | result_ptr[13] = 98 | -(src_ptr[4] * src_ptr[12] + src_ptr[5] * src_ptr[13] + src_ptr[6] * src_ptr[14]); 99 | result_ptr[14] = 100 | -(src_ptr[8] * src_ptr[12] + src_ptr[9] * src_ptr[13] + src_ptr[10] * src_ptr[14]); 101 | result_ptr[15] = 1.0f; 102 | return result; 103 | } 104 | inline static glm::vec3 XrVector3FToGlm(XrVector3f xr_vector) { 105 | return {xr_vector.x, xr_vector.y, xr_vector.z}; 106 | } 107 | inline static glm::quat XrQuaternionFToGlm(XrQuaternionf quaternion) { 108 | return {quaternion.w, quaternion.x, quaternion.y, quaternion.z}; 109 | } 110 | } -------------------------------------------------------------------------------- /app/cpp/openxr-include.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | -------------------------------------------------------------------------------- /app/cpp/openxr_program.cpp: -------------------------------------------------------------------------------- 1 | #include "openxr_program.hpp" 2 | 3 | #include "platform.hpp" 4 | #include "graphics_plugin.hpp" 5 | #include "openxr_utils.hpp" 6 | #include "magic_enum.hpp" 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | static inline XrVector3f XrVector3f_Zero() { 16 | XrVector3f r; 17 | r.x = r.y = r.z = 0.0f; 18 | return r; 19 | } 20 | 21 | static inline XrQuaternionf XrQuaternionf_Identity() { 22 | XrQuaternionf r; 23 | r.x = r.y = r.z = 0.0; 24 | r.w = 1.0f; 25 | return r; 26 | } 27 | 28 | static inline XrPosef XrPosef_Identity() { 29 | XrPosef r; 30 | r.orientation = XrQuaternionf_Identity(); 31 | r.position = XrVector3f_Zero(); 32 | return r; 33 | } 34 | 35 | namespace Math::Pose { 36 | XrPosef Translation(const XrVector3f &translation) { 37 | XrPosef t = XrPosef_Identity(); 38 | t.position = translation; 39 | return t; 40 | } 41 | 42 | XrPosef RotateCCWAboutYAxis(float radians, XrVector3f translation) { 43 | XrPosef t = XrPosef_Identity(); 44 | t.orientation.x = 0.f; 45 | t.orientation.y = std::sin(radians * 0.5f); 46 | t.orientation.z = 0.f; 47 | t.orientation.w = std::cos(radians * 0.5f); 48 | t.position = translation; 49 | return t; 50 | } 51 | } // namespace Math::Pose 52 | 53 | namespace { 54 | bool EqualsIgnoreCase(const std::string &a, const std::string &b) { 55 | return std::equal(a.begin(), a.end(), 56 | b.begin(), b.end(), 57 | [](char a, char b) { 58 | return tolower(a) == tolower(b); 59 | }); 60 | } 61 | }; 62 | 63 | inline XrReferenceSpaceCreateInfo GetXrReferenceSpaceCreateInfo(const std::string &reference_space_type_str) { 64 | XrReferenceSpaceCreateInfo reference_space_create_info{}; 65 | reference_space_create_info.type = XR_TYPE_REFERENCE_SPACE_CREATE_INFO; 66 | reference_space_create_info.poseInReferenceSpace = XrPosef_Identity(); 67 | 68 | if (EqualsIgnoreCase(reference_space_type_str, "View")) { 69 | reference_space_create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW; 70 | } else if (EqualsIgnoreCase(reference_space_type_str, "ViewFront")) { 71 | // Render head-locked 2m in front of device. 72 | reference_space_create_info.poseInReferenceSpace = Math::Pose::Translation({0.f, 0.f, -2.f}); 73 | reference_space_create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_VIEW; 74 | } else if (EqualsIgnoreCase(reference_space_type_str, "Local")) { 75 | reference_space_create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_LOCAL; 76 | } else if (EqualsIgnoreCase(reference_space_type_str, "Stage")) { 77 | reference_space_create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE; 78 | } else if (EqualsIgnoreCase(reference_space_type_str, "StageLeft")) { 79 | reference_space_create_info.poseInReferenceSpace = 80 | Math::Pose::RotateCCWAboutYAxis(0.f, {-2.f, 0.f, -2.f}); 81 | reference_space_create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE; 82 | } else if (EqualsIgnoreCase(reference_space_type_str, "StageRight")) { 83 | reference_space_create_info.poseInReferenceSpace = 84 | Math::Pose::RotateCCWAboutYAxis(0.f, {2.f, 0.f, -2.f}); 85 | reference_space_create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE; 86 | } else if (EqualsIgnoreCase(reference_space_type_str, "StageLeftRotated")) { 87 | reference_space_create_info.poseInReferenceSpace = 88 | Math::Pose::RotateCCWAboutYAxis(3.14f / 3.f, {-2.f, 0.5f, -2.f}); 89 | reference_space_create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE; 90 | } else if (EqualsIgnoreCase(reference_space_type_str, "StageRightRotated")) { 91 | reference_space_create_info.poseInReferenceSpace = 92 | Math::Pose::RotateCCWAboutYAxis(-3.14f / 3.f, {2.f, 0.5f, -2.f}); 93 | reference_space_create_info.referenceSpaceType = XR_REFERENCE_SPACE_TYPE_STAGE; 94 | } else { 95 | throw std::invalid_argument(fmt::format("Unknown reference space type '{}'", 96 | reference_space_type_str)); 97 | } 98 | return reference_space_create_info; 99 | } 100 | 101 | OpenXrProgram::OpenXrProgram(std::shared_ptr platform) 102 | : platform_(platform), graphics_plugin_(CreateGraphicsPlugin()) {} 103 | 104 | void OpenXrProgram::CreateInstance() { 105 | LogLayersAndExtensions(); 106 | if (instance_ != XR_NULL_HANDLE) { 107 | throw std::runtime_error("xr instance must not have been inited"); 108 | } 109 | 110 | std::vector extensions{}; 111 | 112 | const std::vector kPlatformExtensions = platform_->GetInstanceExtensions(); 113 | std::transform(kPlatformExtensions.begin(), 114 | kPlatformExtensions.end(), 115 | std::back_inserter(extensions), 116 | [](const std::string &ext) { return ext.c_str(); }); 117 | 118 | const std::vector 119 | kGraphicsExtensions = graphics_plugin_->GetOpenXrInstanceExtensions(); 120 | std::transform(kGraphicsExtensions.begin(), 121 | kGraphicsExtensions.end(), 122 | std::back_inserter(extensions), 123 | [](const std::string &ext) { return ext.c_str(); }); 124 | 125 | XrInstanceCreateInfo create_info{}; 126 | create_info.type = XR_TYPE_INSTANCE_CREATE_INFO; 127 | create_info.next = platform_->GetInstanceCreateExtension(); 128 | create_info.enabledExtensionCount = static_cast(extensions.size()); 129 | create_info.enabledExtensionNames = extensions.data(); 130 | create_info.enabledApiLayerCount = 0; 131 | create_info.enabledApiLayerNames = nullptr; 132 | strcpy(create_info.applicationInfo.applicationName, "DEMO"); 133 | create_info.applicationInfo.apiVersion = XR_CURRENT_API_VERSION; 134 | 135 | CHECK_XRCMD(xrCreateInstance(&create_info, &instance_)); 136 | 137 | LogInstanceInfo(instance_); 138 | } 139 | 140 | void OpenXrProgram::InitializeSystem() { 141 | if (instance_ == XR_NULL_HANDLE) { 142 | throw std::runtime_error("instance is xr null handle"); 143 | } 144 | if (system_id_ != XR_NULL_SYSTEM_ID) { 145 | throw std::runtime_error("system id must be null system id"); 146 | } 147 | 148 | XrSystemGetInfo system_info{}; 149 | system_info.type = XR_TYPE_SYSTEM_GET_INFO; 150 | system_info.formFactor = XR_FORM_FACTOR_HEAD_MOUNTED_DISPLAY; 151 | CHECK_XRCMD(xrGetSystem(instance_, &system_info, &system_id_)); 152 | 153 | if (system_id_ == XR_NULL_SYSTEM_ID) { 154 | throw std::runtime_error("system id must not be null system id"); 155 | } 156 | 157 | LogViewConfigurations(instance_, system_id_); 158 | 159 | graphics_plugin_->InitializeDevice(instance_, system_id_); 160 | } 161 | 162 | void OpenXrProgram::InitializeSession() { 163 | if (instance_ == XR_NULL_HANDLE) { 164 | throw std::runtime_error("instance_ can not be xr null handle"); 165 | } 166 | if (session_ != XR_NULL_HANDLE) { 167 | throw std::runtime_error("session must be xr null handle"); 168 | } 169 | { 170 | spdlog::debug("Creating session..."); 171 | 172 | XrSessionCreateInfo create_info{}; 173 | create_info.type = XR_TYPE_SESSION_CREATE_INFO; 174 | create_info.next = graphics_plugin_->GetGraphicsBinding(); 175 | create_info.systemId = system_id_; 176 | CHECK_XRCMD(xrCreateSession(instance_, &create_info, &session_)); 177 | } 178 | LogReferenceSpaces(session_); 179 | InitializeActions(); 180 | CreateVisualizedSpaces(); 181 | 182 | { 183 | XrReferenceSpaceCreateInfo 184 | reference_space_create_info = GetXrReferenceSpaceCreateInfo("Local"); 185 | CHECK_XRCMD(xrCreateReferenceSpace(session_, &reference_space_create_info, &app_space_)); 186 | } 187 | } 188 | 189 | void OpenXrProgram::InitializeActions() { 190 | { 191 | XrActionSetCreateInfo action_set_info{}; 192 | action_set_info.type = XR_TYPE_ACTION_SET_CREATE_INFO; 193 | strncpy(action_set_info.actionSetName, "gameplay", sizeof(action_set_info.actionSetName)); 194 | strncpy(action_set_info.localizedActionSetName, 195 | "Gameplay", 196 | sizeof(action_set_info.localizedActionSetName)); 197 | action_set_info.priority = 0; 198 | CHECK_XRCMD(xrCreateActionSet(instance_, &action_set_info, &input_.action_set)); 199 | } 200 | 201 | CHECK_XRCMD(xrStringToPath(instance_, 202 | "/user/hand/left", 203 | &input_.hand_subaction_path[side::LEFT])); 204 | CHECK_XRCMD(xrStringToPath(instance_, 205 | "/user/hand/right", 206 | &input_.hand_subaction_path[side::RIGHT])); 207 | 208 | { 209 | XrActionCreateInfo action_info{}; 210 | action_info.type = XR_TYPE_ACTION_CREATE_INFO; 211 | action_info.actionType = XR_ACTION_TYPE_FLOAT_INPUT; 212 | strncpy(action_info.actionName, "grab_object", sizeof(action_info.actionName)); 213 | strncpy(action_info.localizedActionName, 214 | "Grab Object", 215 | sizeof(action_info.localizedActionName)); 216 | action_info.countSubactionPaths = uint32_t(input_.hand_subaction_path.size()); 217 | action_info.subactionPaths = input_.hand_subaction_path.data(); 218 | CHECK_XRCMD(xrCreateAction(input_.action_set, &action_info, &input_.grab_action)); 219 | 220 | action_info.actionType = XR_ACTION_TYPE_POSE_INPUT; 221 | strncpy(action_info.actionName, "hand_pose", sizeof(action_info.actionName)); 222 | strncpy(action_info.localizedActionName, "Hand Pose", sizeof(action_info.localizedActionName)); 223 | action_info.countSubactionPaths = uint32_t(input_.hand_subaction_path.size()); 224 | action_info.subactionPaths = input_.hand_subaction_path.data(); 225 | CHECK_XRCMD(xrCreateAction(input_.action_set, &action_info, &input_.pose_action)); 226 | 227 | action_info.actionType = XR_ACTION_TYPE_VIBRATION_OUTPUT; 228 | strncpy(action_info.actionName, "vibrate_hand", sizeof(action_info.actionName)); 229 | strncpy(action_info.localizedActionName, 230 | "Vibrate Hand", 231 | sizeof(action_info.localizedActionName)); 232 | action_info.countSubactionPaths = uint32_t(input_.hand_subaction_path.size()); 233 | action_info.subactionPaths = input_.hand_subaction_path.data(); 234 | CHECK_XRCMD(xrCreateAction(input_.action_set, &action_info, &input_.vibrate_action)); 235 | 236 | action_info.actionType = XR_ACTION_TYPE_BOOLEAN_INPUT; 237 | strncpy(action_info.actionName, "quit_session", sizeof(action_info.actionName)); 238 | strncpy(action_info.localizedActionName, 239 | "Quit Session", 240 | sizeof(action_info.localizedActionName)); 241 | action_info.countSubactionPaths = 0; 242 | action_info.subactionPaths = nullptr; 243 | CHECK_XRCMD(xrCreateAction(input_.action_set, &action_info, &input_.quit_action)); 244 | } 245 | 246 | std::array select_path{}; 247 | std::array squeeze_value_path{}; 248 | std::array squeeze_force_path{}; 249 | std::array squeeze_click_path{}; 250 | std::array pose_path{}; 251 | std::array haptic_path{}; 252 | std::array menu_click_path{}; 253 | std::array b_click_path{}; 254 | std::array trigger_value_path{}; 255 | CHECK_XRCMD(xrStringToPath(instance_, 256 | "/user/hand/left/input/select/click", 257 | &select_path[side::LEFT])); 258 | CHECK_XRCMD(xrStringToPath(instance_, 259 | "/user/hand/right/input/select/click", 260 | &select_path[side::RIGHT])); 261 | CHECK_XRCMD(xrStringToPath(instance_, 262 | "/user/hand/left/input/squeeze/value", 263 | &squeeze_value_path[side::LEFT])); 264 | CHECK_XRCMD(xrStringToPath(instance_, 265 | "/user/hand/right/input/squeeze/value", 266 | &squeeze_value_path[side::RIGHT])); 267 | CHECK_XRCMD(xrStringToPath(instance_, 268 | "/user/hand/left/input/squeeze/force", 269 | &squeeze_force_path[side::LEFT])); 270 | CHECK_XRCMD(xrStringToPath(instance_, 271 | "/user/hand/right/input/squeeze/force", 272 | &squeeze_force_path[side::RIGHT])); 273 | CHECK_XRCMD(xrStringToPath(instance_, 274 | "/user/hand/left/input/squeeze/click", 275 | &squeeze_click_path[side::LEFT])); 276 | CHECK_XRCMD(xrStringToPath(instance_, 277 | "/user/hand/right/input/squeeze/click", 278 | &squeeze_click_path[side::RIGHT])); 279 | CHECK_XRCMD(xrStringToPath(instance_, 280 | "/user/hand/left/input/grip/pose", 281 | &pose_path[side::LEFT])); 282 | CHECK_XRCMD(xrStringToPath(instance_, 283 | "/user/hand/right/input/grip/pose", 284 | &pose_path[side::RIGHT])); 285 | CHECK_XRCMD(xrStringToPath(instance_, 286 | "/user/hand/left/output/haptic", 287 | &haptic_path[side::LEFT])); 288 | CHECK_XRCMD(xrStringToPath(instance_, 289 | "/user/hand/right/output/haptic", 290 | &haptic_path[side::RIGHT])); 291 | CHECK_XRCMD(xrStringToPath(instance_, 292 | "/user/hand/left/input/menu/click", 293 | &menu_click_path[side::LEFT])); 294 | CHECK_XRCMD(xrStringToPath(instance_, 295 | "/user/hand/right/input/menu/click", 296 | &menu_click_path[side::RIGHT])); 297 | CHECK_XRCMD(xrStringToPath(instance_, 298 | "/user/hand/left/input/b/click", 299 | &b_click_path[side::LEFT])); 300 | CHECK_XRCMD(xrStringToPath(instance_, 301 | "/user/hand/right/input/b/click", 302 | &b_click_path[side::RIGHT])); 303 | CHECK_XRCMD(xrStringToPath(instance_, 304 | "/user/hand/left/input/trigger/value", 305 | &trigger_value_path[side::LEFT])); 306 | CHECK_XRCMD(xrStringToPath(instance_, 307 | "/user/hand/right/input/trigger/value", 308 | &trigger_value_path[side::RIGHT])); 309 | { 310 | XrPath oculus_touch_interaction_profile_path; 311 | CHECK_XRCMD( 312 | xrStringToPath(instance_, 313 | "/interaction_profiles/oculus/touch_controller", 314 | &oculus_touch_interaction_profile_path)); 315 | std::vector 316 | bindings{{{input_.grab_action, squeeze_value_path[side::LEFT]}, 317 | {input_.grab_action, squeeze_value_path[side::RIGHT]}, 318 | {input_.pose_action, pose_path[side::LEFT]}, 319 | {input_.pose_action, pose_path[side::RIGHT]}, 320 | {input_.quit_action, menu_click_path[side::LEFT]}, 321 | {input_.vibrate_action, haptic_path[side::LEFT]}, 322 | {input_.vibrate_action, haptic_path[side::RIGHT]}}}; 323 | XrInteractionProfileSuggestedBinding suggested_bindings{}; 324 | suggested_bindings.type = XR_TYPE_INTERACTION_PROFILE_SUGGESTED_BINDING; 325 | suggested_bindings.interactionProfile = oculus_touch_interaction_profile_path; 326 | suggested_bindings.suggestedBindings = bindings.data(); 327 | suggested_bindings.countSuggestedBindings = (uint32_t) bindings.size(); 328 | CHECK_XRCMD(xrSuggestInteractionProfileBindings(instance_, &suggested_bindings)); 329 | } 330 | XrActionSpaceCreateInfo action_space_info{}; 331 | action_space_info.type = XR_TYPE_ACTION_SPACE_CREATE_INFO; 332 | action_space_info.action = input_.pose_action; 333 | action_space_info.poseInActionSpace.orientation.w = 1.f; 334 | action_space_info.subactionPath = input_.hand_subaction_path[side::LEFT]; 335 | CHECK_XRCMD(xrCreateActionSpace(session_, &action_space_info, &input_.hand_space[side::LEFT])); 336 | action_space_info.subactionPath = input_.hand_subaction_path[side::RIGHT]; 337 | CHECK_XRCMD(xrCreateActionSpace(session_, &action_space_info, &input_.hand_space[side::RIGHT])); 338 | 339 | XrSessionActionSetsAttachInfo attach_info{}; 340 | attach_info.type = XR_TYPE_SESSION_ACTION_SETS_ATTACH_INFO; 341 | attach_info.countActionSets = 1; 342 | attach_info.actionSets = &input_.action_set; 343 | CHECK_XRCMD(xrAttachSessionActionSets(session_, &attach_info)); 344 | } 345 | 346 | void OpenXrProgram::CreateVisualizedSpaces() { 347 | if (session_ == XR_NULL_HANDLE) { 348 | throw std::runtime_error("session can not be xr null handle"); 349 | } 350 | 351 | std::string visualized_spaces[] = 352 | {"ViewFront", "Local", "Stage", "StageLeft", "StageRight", "StageLeftRotated", 353 | "StageRightRotated"}; 354 | 355 | for (const auto &visualized_space: visualized_spaces) { 356 | XrReferenceSpaceCreateInfo 357 | reference_space_create_info = GetXrReferenceSpaceCreateInfo(visualized_space); 358 | XrSpace space{}; 359 | XrResult res = xrCreateReferenceSpace(session_, &reference_space_create_info, &space); 360 | if (XR_SUCCEEDED(res)) { 361 | visualized_spaces_.push_back(space); 362 | } else { 363 | spdlog::warn("Failed to create reference space {} with error {}", 364 | visualized_space, 365 | magic_enum::enum_name(res)); 366 | } 367 | } 368 | } 369 | 370 | void OpenXrProgram::CreateSwapchains() { 371 | if (session_ == XR_NULL_HANDLE) { 372 | throw std::runtime_error("session is null"); 373 | } 374 | if (!swapchains_.empty()) { 375 | throw std::runtime_error("swapchains must be empty"); 376 | } 377 | if (!config_views_.empty()) { 378 | throw std::runtime_error("config views must be empty"); 379 | } 380 | 381 | LogSystemProperties(instance_, system_id_); 382 | 383 | uint32_t swapchain_format_count = 0; 384 | CHECK_XRCMD(xrEnumerateSwapchainFormats(session_, 0, &swapchain_format_count, nullptr)); 385 | std::vector swapchain_formats(swapchain_format_count); 386 | CHECK_XRCMD(xrEnumerateSwapchainFormats(session_, 387 | static_cast(swapchain_formats.size()), 388 | &swapchain_format_count, 389 | swapchain_formats.data())); 390 | uint32_t swapchain_color_format = graphics_plugin_->SelectSwapchainFormat(swapchain_formats); 391 | 392 | if (view_config_type_ != XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO) { 393 | throw std::runtime_error("only stereo is supported"); 394 | } 395 | 396 | uint32_t view_count = 0; 397 | CHECK_XRCMD(xrEnumerateViewConfigurationViews(instance_, 398 | system_id_, 399 | view_config_type_, 400 | 0, 401 | &view_count, 402 | nullptr)); 403 | config_views_.resize(view_count, {XR_TYPE_VIEW_CONFIGURATION_VIEW}); 404 | CHECK_XRCMD(xrEnumerateViewConfigurationViews(instance_, 405 | system_id_, 406 | view_config_type_, 407 | view_count, 408 | &view_count, 409 | config_views_.data())); 410 | 411 | views_.resize(view_count, {XR_TYPE_VIEW}); 412 | for (const auto &view_config_view: config_views_) { 413 | spdlog::info("Creating swapchain with dimensions Width={} Height={} SampleCount={}", 414 | view_config_view.recommendedImageRectWidth, 415 | view_config_view.recommendedImageRectHeight, 416 | view_config_view.recommendedSwapchainSampleCount); 417 | 418 | XrSwapchainCreateInfo swapchain_create_info{}; 419 | swapchain_create_info.type = XR_TYPE_SWAPCHAIN_CREATE_INFO; 420 | swapchain_create_info.arraySize = 1; 421 | swapchain_create_info.format = swapchain_color_format; 422 | swapchain_create_info.width = view_config_view.recommendedImageRectWidth; 423 | swapchain_create_info.height = view_config_view.recommendedImageRectHeight; 424 | swapchain_create_info.mipCount = 1; 425 | swapchain_create_info.faceCount = 1; 426 | swapchain_create_info.sampleCount = view_config_view.recommendedSwapchainSampleCount; 427 | swapchain_create_info.usageFlags = 428 | XR_SWAPCHAIN_USAGE_SAMPLED_BIT | XR_SWAPCHAIN_USAGE_COLOR_ATTACHMENT_BIT; 429 | Swapchain swapchain{}; 430 | swapchain.width = swapchain_create_info.width; 431 | swapchain.height = swapchain_create_info.height; 432 | CHECK_XRCMD(xrCreateSwapchain(session_, &swapchain_create_info, &swapchain.handle)); 433 | 434 | swapchains_.push_back(swapchain); 435 | 436 | uint32_t image_count; 437 | CHECK_XRCMD(xrEnumerateSwapchainImages(swapchain.handle, 0, &image_count, nullptr)); 438 | 439 | XrSwapchainImageBaseHeader *swapchain_images = 440 | graphics_plugin_->AllocateSwapchainImageStructs(image_count, swapchain_create_info); 441 | CHECK_XRCMD(xrEnumerateSwapchainImages(swapchain.handle, 442 | image_count, 443 | &image_count, 444 | swapchain_images)); 445 | graphics_plugin_->SwapchainImageStructsReady(swapchain_images); 446 | swapchain_images_.insert(std::make_pair(swapchain.handle, swapchain_images)); 447 | } 448 | } 449 | 450 | void OpenXrProgram::PollEvents() { 451 | while (const XrEventDataBaseHeader *event = TryReadNextEvent()) { 452 | switch (event->type) { 453 | case XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING: { 454 | const auto &instance_loss_pending = 455 | *reinterpret_cast(&event); 456 | spdlog::warn("XrEventDataInstanceLossPending by {}", instance_loss_pending.lossTime); 457 | return; 458 | } 459 | case XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: { 460 | auto session_state_changed_event = 461 | *reinterpret_cast(event); 462 | HandleSessionStateChangedEvent(session_state_changed_event); 463 | break; 464 | } 465 | case XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED: { 466 | LogActionSourceName(session_, input_.grab_action, "Grab"); 467 | LogActionSourceName(session_, input_.quit_action, "Quit"); 468 | LogActionSourceName(session_, input_.pose_action, "Pose"); 469 | LogActionSourceName(session_, input_.vibrate_action, "Vibrate"); 470 | } 471 | break; 472 | case XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING: 473 | default: { 474 | spdlog::debug("Ignoring event type {}", magic_enum::enum_name(event->type)); 475 | break; 476 | } 477 | } 478 | } 479 | } 480 | 481 | const XrEventDataBaseHeader *OpenXrProgram::TryReadNextEvent() { 482 | auto base_header = reinterpret_cast(&event_data_buffer_); 483 | base_header->type = XR_TYPE_EVENT_DATA_BUFFER; 484 | XrResult result = xrPollEvent(instance_, &event_data_buffer_); 485 | if (result == XR_SUCCESS) { 486 | if (base_header->type == XR_TYPE_EVENT_DATA_EVENTS_LOST) { 487 | auto events_lost = reinterpret_cast(base_header); 488 | spdlog::warn("{} events lost", events_lost->lostEventCount); 489 | } 490 | return base_header; 491 | } 492 | if (result != XR_EVENT_UNAVAILABLE) { 493 | spdlog::error("xr pull event unknown result: {}", magic_enum::enum_name(result)); 494 | } 495 | return nullptr; 496 | } 497 | 498 | void OpenXrProgram::HandleSessionStateChangedEvent(const XrEventDataSessionStateChanged &state_changed_event) { 499 | 500 | spdlog::info("XrEventDataSessionStateChanged: state {}->{} time={}", 501 | magic_enum::enum_name(session_state_), 502 | magic_enum::enum_name(state_changed_event.state), 503 | state_changed_event.time); 504 | 505 | if ((state_changed_event.session != XR_NULL_HANDLE) 506 | && (state_changed_event.session != session_)) { 507 | spdlog::error("XrEventDataSessionStateChanged for unknown session"); 508 | return; 509 | } 510 | session_state_ = state_changed_event.state; 511 | switch (session_state_) { 512 | case XR_SESSION_STATE_READY: { 513 | XrSessionBeginInfo session_begin_info{}; 514 | session_begin_info.type = XR_TYPE_SESSION_BEGIN_INFO; 515 | session_begin_info.primaryViewConfigurationType = view_config_type_; 516 | CHECK_XRCMD(xrBeginSession(session_, &session_begin_info)); 517 | session_running_ = true; 518 | break; 519 | } 520 | case XR_SESSION_STATE_STOPPING: { 521 | session_running_ = false; 522 | CHECK_XRCMD(xrEndSession(session_)); 523 | break; 524 | } 525 | default:break; 526 | } 527 | } 528 | 529 | bool OpenXrProgram::IsSessionRunning() const { 530 | return session_running_; 531 | } 532 | 533 | void OpenXrProgram::PollActions() { 534 | input_.hand_active = {XR_FALSE, XR_FALSE}; 535 | 536 | const XrActiveActionSet kActiveActionSet{input_.action_set, XR_NULL_PATH}; 537 | XrActionsSyncInfo sync_info{}; 538 | sync_info.type = XR_TYPE_ACTIONS_SYNC_INFO; 539 | sync_info.countActiveActionSets = 1; 540 | sync_info.activeActionSets = &kActiveActionSet; 541 | CHECK_XRCMD(xrSyncActions(session_, &sync_info)); 542 | 543 | for (auto hand: {side::LEFT, side::RIGHT}) { 544 | XrActionStateGetInfo get_info{}; 545 | get_info.type = XR_TYPE_ACTION_STATE_GET_INFO; 546 | get_info.action = input_.grab_action; 547 | get_info.subactionPath = input_.hand_subaction_path[hand]; 548 | 549 | XrActionStateFloat grab_value{}; 550 | grab_value.type = XR_TYPE_ACTION_STATE_FLOAT; 551 | CHECK_XRCMD(xrGetActionStateFloat(session_, &get_info, &grab_value)); 552 | if (grab_value.isActive == XR_TRUE) { 553 | input_.hand_scale[hand] = 1.0f - 0.5f * grab_value.currentState; 554 | if (grab_value.currentState > 0.9f) { 555 | XrHapticVibration vibration{}; 556 | vibration.type = XR_TYPE_HAPTIC_VIBRATION; 557 | vibration.amplitude = 0.5; 558 | vibration.duration = XR_MIN_HAPTIC_DURATION; 559 | vibration.frequency = XR_FREQUENCY_UNSPECIFIED; 560 | 561 | XrHapticActionInfo haptic_action_info{}; 562 | haptic_action_info.type = XR_TYPE_HAPTIC_ACTION_INFO; 563 | haptic_action_info.action = input_.vibrate_action; 564 | haptic_action_info.subactionPath = input_.hand_subaction_path[hand]; 565 | CHECK_XRCMD(xrApplyHapticFeedback(session_, &haptic_action_info, 566 | (XrHapticBaseHeader *) &vibration)); 567 | } 568 | } 569 | 570 | get_info.action = input_.pose_action; 571 | XrActionStatePose pose_state{}; 572 | pose_state.type = XR_TYPE_ACTION_STATE_POSE; 573 | CHECK_XRCMD(xrGetActionStatePose(session_, &get_info, &pose_state)); 574 | input_.hand_active[hand] = pose_state.isActive; 575 | } 576 | 577 | XrActionStateGetInfo get_info{}; 578 | get_info.type = XR_TYPE_ACTION_STATE_GET_INFO; 579 | get_info.next = nullptr; 580 | get_info.action = input_.quit_action; 581 | get_info.subactionPath = XR_NULL_PATH; 582 | XrActionStateBoolean quit_value{}; 583 | quit_value.type = XR_TYPE_ACTION_STATE_BOOLEAN; 584 | CHECK_XRCMD(xrGetActionStateBoolean(session_, &get_info, &quit_value)); 585 | if ((quit_value.isActive == XR_TRUE) && (quit_value.changedSinceLastSync == XR_TRUE) 586 | && (quit_value.currentState == XR_TRUE)) { 587 | CHECK_XRCMD(xrRequestExitSession(session_)); 588 | } 589 | } 590 | 591 | void OpenXrProgram::RenderFrame() { 592 | if (session_ == XR_NULL_HANDLE) { 593 | throw std::runtime_error("session can not be null"); 594 | } 595 | 596 | XrFrameWaitInfo frame_wait_info{ 597 | .type = XR_TYPE_FRAME_WAIT_INFO, 598 | }; 599 | XrFrameState frame_state{ 600 | .type = XR_TYPE_FRAME_STATE, 601 | }; 602 | CHECK_XRCMD(xrWaitFrame(session_, &frame_wait_info, &frame_state)); 603 | 604 | XrFrameBeginInfo frame_begin_info{ 605 | .type = XR_TYPE_FRAME_BEGIN_INFO, 606 | }; 607 | CHECK_XRCMD(xrBeginFrame(session_, &frame_begin_info)); 608 | 609 | std::vector layers{}; 610 | XrCompositionLayerProjection layer{ 611 | .type = XR_TYPE_COMPOSITION_LAYER_PROJECTION, 612 | }; 613 | std::vector projection_layer_views{}; 614 | if (frame_state.shouldRender == XR_TRUE) { 615 | if (RenderLayer(frame_state.predictedDisplayTime, projection_layer_views, layer)) { 616 | layers.push_back(reinterpret_cast(&layer)); 617 | } 618 | } 619 | 620 | XrFrameEndInfo frame_end_info{}; 621 | frame_end_info.type = XR_TYPE_FRAME_END_INFO; 622 | frame_end_info.displayTime = frame_state.predictedDisplayTime; 623 | frame_end_info.environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE; 624 | frame_end_info.layerCount = static_cast(layers.size()); 625 | frame_end_info.layers = layers.data(); 626 | CHECK_XRCMD(xrEndFrame(session_, &frame_end_info)); 627 | } 628 | 629 | bool OpenXrProgram::RenderLayer(XrTime predicted_display_time, 630 | std::vector &projection_layer_views, 631 | XrCompositionLayerProjection &layer) { 632 | XrViewState view_state{}; 633 | view_state.type = XR_TYPE_VIEW_STATE; 634 | 635 | XrViewLocateInfo view_locate_info{}; 636 | view_locate_info.type = XR_TYPE_VIEW_LOCATE_INFO; 637 | view_locate_info.viewConfigurationType = view_config_type_; 638 | view_locate_info.displayTime = predicted_display_time; 639 | view_locate_info.space = app_space_; 640 | 641 | uint32_t view_count_output = 0; 642 | CHECK_XRCMD(xrLocateViews(session_, 643 | &view_locate_info, 644 | &view_state, 645 | views_.size(), 646 | &view_count_output, 647 | views_.data())); 648 | if ((view_state.viewStateFlags & XR_VIEW_STATE_POSITION_VALID_BIT) == 0 || 649 | (view_state.viewStateFlags & XR_VIEW_STATE_ORIENTATION_VALID_BIT) == 0) { 650 | return false; // There is no valid tracking poses for the views. 651 | } 652 | 653 | projection_layer_views.resize(view_count_output); 654 | 655 | // For each locatable space that we want to visualize, render a 25cm cube. 656 | std::vector cubes{}; 657 | 658 | for (XrSpace visualized_space: visualized_spaces_) { 659 | XrSpaceLocation space_location{}; 660 | space_location.type = XR_TYPE_SPACE_LOCATION; 661 | auto res = xrLocateSpace(visualized_space, app_space_, predicted_display_time, &space_location); 662 | if (XR_UNQUALIFIED_SUCCESS(res)) { 663 | if ((space_location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0 && 664 | (space_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0) { 665 | cubes.push_back(math::Transform{ 666 | math::XrQuaternionFToGlm(space_location.pose.orientation), 667 | math::XrVector3FToGlm(space_location.pose.position), 668 | {0.25f, 0.25f, 0.25f}}); 669 | } 670 | } else { 671 | spdlog::debug("Unable to locate a visualized reference space in app space: {}", 672 | magic_enum::enum_name(res)); 673 | } 674 | } 675 | 676 | // Render a 10cm cube scaled by grab_action for each hand. Note renderHand will only be true when the application has focus. 677 | for (auto hand: {side::LEFT, side::RIGHT}) { 678 | XrSpaceLocation space_location{}; 679 | space_location.type = XR_TYPE_SPACE_LOCATION; 680 | auto res = 681 | xrLocateSpace(input_.hand_space[hand], app_space_, predicted_display_time, &space_location); 682 | if (XR_UNQUALIFIED_SUCCESS(res)) { 683 | if ((space_location.locationFlags & XR_SPACE_LOCATION_POSITION_VALID_BIT) != 0 && 684 | (space_location.locationFlags & XR_SPACE_LOCATION_ORIENTATION_VALID_BIT) != 0) { 685 | float scale = 0.1f * input_.hand_scale[hand]; 686 | cubes.push_back(math::Transform{ 687 | math::XrQuaternionFToGlm(space_location.pose.orientation), 688 | math::XrVector3FToGlm(space_location.pose.position), 689 | {scale, scale, scale}}); 690 | } 691 | } else { 692 | // Tracking loss is expected when the hand is not active so only log a message if the hand is active. 693 | if (input_.hand_active[hand] == XR_TRUE) { 694 | const char *hand_name[] = {"left", "right"}; 695 | spdlog::debug("Unable to locate {} hand action space in app space: {}", 696 | hand_name[hand], 697 | magic_enum::enum_name(res)); 698 | } 699 | } 700 | } 701 | 702 | // Render view to the appropriate part of the swapchain image. 703 | for (uint32_t i = 0; i < view_count_output; i++) { 704 | Swapchain view_swapchain = swapchains_[i]; 705 | 706 | XrSwapchainImageAcquireInfo acquire_info{}; 707 | acquire_info.type = XR_TYPE_SWAPCHAIN_IMAGE_ACQUIRE_INFO; 708 | 709 | uint32_t swapchain_image_index = 0; 710 | CHECK_XRCMD(xrAcquireSwapchainImage(view_swapchain.handle, 711 | &acquire_info, 712 | &swapchain_image_index)); 713 | 714 | XrSwapchainImageWaitInfo wait_info{}; 715 | wait_info.type = XR_TYPE_SWAPCHAIN_IMAGE_WAIT_INFO; 716 | wait_info.timeout = XR_INFINITE_DURATION; 717 | CHECK_XRCMD(xrWaitSwapchainImage(view_swapchain.handle, &wait_info)); 718 | 719 | projection_layer_views[i].type = XR_TYPE_COMPOSITION_LAYER_PROJECTION_VIEW; 720 | projection_layer_views[i].pose = views_[i].pose; 721 | projection_layer_views[i].fov = views_[i].fov; 722 | projection_layer_views[i].subImage.swapchain = view_swapchain.handle; 723 | projection_layer_views[i].subImage.imageRect.offset = {0, 0}; 724 | projection_layer_views[i].subImage.imageRect.extent = 725 | {view_swapchain.width, view_swapchain.height}; 726 | 727 | auto swapchain_image = swapchain_images_[view_swapchain.handle]; 728 | graphics_plugin_->RenderView(projection_layer_views[i], 729 | swapchain_image, 730 | swapchain_image_index, 731 | cubes); 732 | 733 | XrSwapchainImageReleaseInfo release_info{}; 734 | release_info.type = XR_TYPE_SWAPCHAIN_IMAGE_RELEASE_INFO; 735 | CHECK_XRCMD(xrReleaseSwapchainImage(view_swapchain.handle, &release_info)); 736 | } 737 | 738 | layer.space = app_space_; 739 | layer.viewCount = static_cast(projection_layer_views.size()); 740 | layer.views = projection_layer_views.data(); 741 | return true; 742 | } 743 | 744 | OpenXrProgram::~OpenXrProgram() { 745 | if (input_.action_set != XR_NULL_HANDLE) { 746 | for (auto hand: {side::LEFT, side::RIGHT}) { 747 | xrDestroySpace(input_.hand_space[hand]); 748 | } 749 | xrDestroyActionSet(input_.action_set); 750 | } 751 | for (Swapchain swapchain: swapchains_) { 752 | xrDestroySwapchain(swapchain.handle); 753 | } 754 | graphics_plugin_->DeinitDevice(); 755 | for (XrSpace visualized_space: visualized_spaces_) { 756 | xrDestroySpace(visualized_space); 757 | } 758 | if (app_space_ != XR_NULL_HANDLE) { 759 | xrDestroySpace(app_space_); 760 | } 761 | if (session_ != XR_NULL_HANDLE) { 762 | xrDestroySession(session_); 763 | } 764 | if (instance_ != XR_NULL_HANDLE) { 765 | xrDestroyInstance(instance_); 766 | } 767 | } 768 | 769 | std::shared_ptr CreateOpenXrProgram(std::shared_ptr platform) { 770 | return std::make_shared(platform); 771 | } 772 | -------------------------------------------------------------------------------- /app/cpp/openxr_program.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "platform.hpp" 4 | 5 | #include "graphics_plugin.hpp" 6 | 7 | #include 8 | #include 9 | 10 | namespace side { 11 | const int LEFT = 0; 12 | const int RIGHT = 1; 13 | const int COUNT = 2; 14 | } 15 | 16 | struct Swapchain { 17 | XrSwapchain handle; 18 | int32_t width; 19 | int32_t height; 20 | }; 21 | 22 | struct InputState { 23 | XrActionSet action_set = XR_NULL_HANDLE; 24 | XrAction grab_action = XR_NULL_HANDLE; 25 | XrAction pose_action = XR_NULL_HANDLE; 26 | XrAction vibrate_action = XR_NULL_HANDLE; 27 | XrAction quit_action = XR_NULL_HANDLE; 28 | std::array hand_subaction_path{}; 29 | std::array hand_space{}; 30 | std::array hand_scale = {{1.0f, 1.0f}}; 31 | std::array hand_active{}; 32 | }; 33 | 34 | class OpenXrProgram { 35 | public: 36 | OpenXrProgram(std::shared_ptr platform); 37 | 38 | void CreateInstance(); 39 | void InitializeSystem(); 40 | void InitializeSession(); 41 | void CreateSwapchains(); 42 | 43 | void PollEvents(); 44 | void PollActions(); 45 | void RenderFrame(); 46 | 47 | bool IsSessionRunning() const; 48 | 49 | ~OpenXrProgram(); 50 | private: 51 | void InitializeActions(); 52 | void CreateVisualizedSpaces(); 53 | 54 | const XrEventDataBaseHeader *TryReadNextEvent(); 55 | void HandleSessionStateChangedEvent(const XrEventDataSessionStateChanged &state_changed_event); 56 | bool RenderLayer(XrTime predicted_display_time, 57 | std::vector &projection_layer_views, 58 | XrCompositionLayerProjection &layer); 59 | private: 60 | std::shared_ptr platform_; 61 | std::shared_ptr graphics_plugin_; 62 | 63 | XrInstance instance_ = XR_NULL_HANDLE; 64 | XrSystemId system_id_ = XR_NULL_SYSTEM_ID; 65 | XrSession session_ = XR_NULL_HANDLE; 66 | 67 | InputState input_{}; 68 | 69 | std::vector visualized_spaces_{}; 70 | XrSpace app_space_ = XR_NULL_HANDLE; 71 | 72 | XrViewConfigurationType view_config_type_ = XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO; 73 | std::vector config_views_; 74 | std::vector views_; 75 | 76 | std::vector swapchains_; 77 | std::map swapchain_images_; 78 | 79 | XrEventDataBuffer event_data_buffer_{}; 80 | 81 | XrSessionState session_state_ = XR_SESSION_STATE_UNKNOWN; 82 | bool session_running_ = false; 83 | }; 84 | 85 | std::shared_ptr CreateOpenXrProgram(std::shared_ptr platform); 86 | -------------------------------------------------------------------------------- /app/cpp/openxr_utils.cpp: -------------------------------------------------------------------------------- 1 | #include "openxr_utils.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | void CheckResult(XrResult result, const std::string &file, uint32_t line) { 10 | if (XR_FAILED(result)) [[unlikely]] { 11 | throw std::runtime_error(fmt::format("call failed with error {} {}:{}\n", 12 | magic_enum::enum_name(result), 13 | file, 14 | line)); 15 | } 16 | } 17 | 18 | std::string GetXrVersionString(XrVersion ver) { 19 | return fmt::format("{}.{}.{}", 20 | XR_VERSION_MAJOR(ver), 21 | XR_VERSION_MINOR(ver), 22 | XR_VERSION_PATCH(ver)); 23 | } 24 | 25 | void LogLayersAndExtensions() { 26 | const auto log_extensions = [](const char *layer_name, int indent = 0) { 27 | uint32_t instance_extension_count; 28 | CHECK_XRCMD(xrEnumerateInstanceExtensionProperties(layer_name, 29 | 0, 30 | &instance_extension_count, 31 | nullptr)); 32 | 33 | std::vector extensions(instance_extension_count); 34 | for (XrExtensionProperties &extension: extensions) { 35 | extension.type = XR_TYPE_EXTENSION_PROPERTIES; 36 | } 37 | 38 | CHECK_XRCMD(xrEnumerateInstanceExtensionProperties(layer_name, 39 | extensions.size(), 40 | &instance_extension_count, 41 | extensions.data())); 42 | 43 | const std::string kIndentStr(indent, ' '); 44 | spdlog::debug("{} Available Extensions: ({})", 45 | kIndentStr.c_str(), 46 | instance_extension_count); 47 | for (const XrExtensionProperties &extension: extensions) { 48 | spdlog::debug("{} Name={} SpecVersion={}", 49 | kIndentStr.c_str(), 50 | extension.extensionName, 51 | extension.extensionVersion); 52 | } 53 | }; 54 | 55 | log_extensions(nullptr); 56 | 57 | { 58 | uint32_t layer_count; 59 | CHECK_XRCMD(xrEnumerateApiLayerProperties(0, &layer_count, nullptr)); 60 | 61 | std::vector layers(layer_count); 62 | for (XrApiLayerProperties &layer: layers) { 63 | layer.type = XR_TYPE_API_LAYER_PROPERTIES; 64 | } 65 | 66 | CHECK_XRCMD(xrEnumerateApiLayerProperties((uint32_t) layers.size(), 67 | &layer_count, 68 | layers.data())); 69 | 70 | spdlog::info("Available Layers: ({})", layer_count); 71 | for (const XrApiLayerProperties &layer: layers) { 72 | spdlog::debug(" Name={} SpecVersion={} LayerVersion={} Description={}", 73 | layer.layerName, 74 | GetXrVersionString(layer.specVersion).c_str(), 75 | layer.layerVersion, 76 | layer.description); 77 | log_extensions(layer.layerName, 4); 78 | } 79 | } 80 | } 81 | 82 | void LogInstanceInfo(XrInstance instance) { 83 | if (instance == XR_NULL_HANDLE) { 84 | throw std::runtime_error("instance is xr null handle"); 85 | } 86 | 87 | XrInstanceProperties instance_properties{}; 88 | instance_properties.type = XR_TYPE_INSTANCE_PROPERTIES; 89 | CHECK_XRCMD(xrGetInstanceProperties(instance, &instance_properties)); 90 | 91 | spdlog::info("Instance RuntimeName={} RuntimeVersion={}", 92 | instance_properties.runtimeName, 93 | GetXrVersionString(instance_properties.runtimeVersion).c_str()); 94 | } 95 | 96 | void LogViewConfigurations(XrInstance instance, XrSystemId system_id) { 97 | if (instance == XR_NULL_HANDLE) { 98 | throw std::runtime_error("instance is xr null handle"); 99 | } 100 | if (system_id == XR_NULL_SYSTEM_ID) { 101 | throw std::runtime_error("system id is xr null system id"); 102 | } 103 | 104 | uint32_t view_config_type_count; 105 | CHECK_XRCMD(xrEnumerateViewConfigurations(instance, 106 | system_id, 107 | 0, 108 | &view_config_type_count, 109 | nullptr)); 110 | std::vector view_config_types(view_config_type_count); 111 | CHECK_XRCMD(xrEnumerateViewConfigurations(instance, 112 | system_id, 113 | view_config_type_count, 114 | &view_config_type_count, 115 | view_config_types.data())); 116 | 117 | spdlog::info("Available View Configuration Types: ({})", view_config_type_count); 118 | for (XrViewConfigurationType view_config_type: view_config_types) { 119 | std::string view_config_type_name = "unknown"; 120 | switch (view_config_type) { 121 | case XR_VIEW_CONFIGURATION_TYPE_PRIMARY_MONO:view_config_type_name = "PRIMARY_MONO"; 122 | break; 123 | case XR_VIEW_CONFIGURATION_TYPE_PRIMARY_STEREO:view_config_type_name = "PRIMARY_STEREO"; 124 | break; 125 | case XR_VIEW_CONFIGURATION_TYPE_PRIMARY_QUAD_VARJO:view_config_type_name = "QUAD_VARJO"; 126 | break; 127 | case XR_VIEW_CONFIGURATION_TYPE_SECONDARY_MONO_FIRST_PERSON_OBSERVER_MSFT: 128 | view_config_type_name = "SECONDARY_MONO_FIRST_PERSON_OBSERVER_MSFT"; 129 | break; 130 | default:view_config_type_name = "unknown"; 131 | } 132 | spdlog::debug(" View Configuration Type: {}", view_config_type_name); 133 | 134 | XrViewConfigurationProperties view_config_properties{}; 135 | view_config_properties.type = XR_TYPE_VIEW_CONFIGURATION_PROPERTIES; 136 | CHECK_XRCMD(xrGetViewConfigurationProperties(instance, 137 | system_id, 138 | view_config_type, 139 | &view_config_properties)); 140 | 141 | spdlog::debug(" View configuration FovMutable={}", 142 | view_config_properties.fovMutable == XR_TRUE ? "True" 143 | : "False"); 144 | 145 | uint32_t view_count; 146 | CHECK_XRCMD(xrEnumerateViewConfigurationViews(instance, 147 | system_id, 148 | view_config_type, 149 | 0, 150 | &view_count, 151 | nullptr)); 152 | if (view_count > 0) { 153 | std::vector views(view_count); 154 | for (uint32_t i = 0; i < view_count; i++) { 155 | views[i].type = XR_TYPE_VIEW_CONFIGURATION_VIEW; 156 | views[i].next = nullptr; 157 | } 158 | CHECK_XRCMD( 159 | xrEnumerateViewConfigurationViews(instance, 160 | system_id, 161 | view_config_type, 162 | view_count, 163 | &view_count, 164 | views.data())); 165 | 166 | for (uint32_t i = 0; i < views.size(); i++) { 167 | const XrViewConfigurationView &view = views[i]; 168 | 169 | spdlog::debug(" View [{}]: Recommended Width={} Height={} SampleCount={}", 170 | i, 171 | view.recommendedImageRectWidth, 172 | view.recommendedImageRectHeight, 173 | view.recommendedSwapchainSampleCount); 174 | spdlog::debug(" View [{}]: Maximum Width={} Height={} SampleCount={}", 175 | i, 176 | view.maxImageRectWidth, 177 | view.maxImageRectHeight, 178 | view.maxSwapchainSampleCount); 179 | } 180 | } else { 181 | spdlog::error("Empty view configuration type"); 182 | } 183 | 184 | uint32_t count; 185 | CHECK_XRCMD(xrEnumerateEnvironmentBlendModes(instance, 186 | system_id, 187 | view_config_type, 188 | 0, 189 | &count, 190 | nullptr)); 191 | if (count < 0) { 192 | throw std::runtime_error("must have at least 1 env blend mode"); 193 | } 194 | 195 | spdlog::info("Available Environment Blend Mode count : {}", count); 196 | 197 | std::vector blend_modes(count); 198 | CHECK_XRCMD(xrEnumerateEnvironmentBlendModes(instance, 199 | system_id, 200 | view_config_type, 201 | count, 202 | &count, 203 | blend_modes.data())); 204 | 205 | for (XrEnvironmentBlendMode mode: blend_modes) { 206 | std::string blend_mode_name = "unknown"; 207 | switch (mode) { 208 | case XR_ENVIRONMENT_BLEND_MODE_OPAQUE:blend_mode_name = "opaque"; 209 | break; 210 | case XR_ENVIRONMENT_BLEND_MODE_ADDITIVE:blend_mode_name = "additive"; 211 | break; 212 | case XR_ENVIRONMENT_BLEND_MODE_ALPHA_BLEND:blend_mode_name = "alpha blend"; 213 | break; 214 | default:blend_mode_name = "unknown"; 215 | } 216 | spdlog::info("Environment Blend Mode: {}", blend_mode_name); 217 | } 218 | } 219 | } 220 | 221 | void LogReferenceSpaces(XrSession session) { 222 | uint32_t space_count; 223 | CHECK_XRCMD(xrEnumerateReferenceSpaces(session, 0, &space_count, nullptr)); 224 | std::vector spaces(space_count); 225 | CHECK_XRCMD(xrEnumerateReferenceSpaces(session, space_count, &space_count, spaces.data())); 226 | 227 | spdlog::info("Available reference spaces: {}", space_count); 228 | for (XrReferenceSpaceType space: spaces) { 229 | auto reference_space_name = ""; 230 | switch (space) { 231 | case XR_REFERENCE_SPACE_TYPE_VIEW: reference_space_name = "view"; 232 | break; 233 | case XR_REFERENCE_SPACE_TYPE_LOCAL:reference_space_name = "local"; 234 | break; 235 | case XR_REFERENCE_SPACE_TYPE_STAGE: reference_space_name = "stage"; 236 | break; 237 | case XR_REFERENCE_SPACE_TYPE_UNBOUNDED_MSFT: reference_space_name = "msft"; 238 | break; 239 | case XR_REFERENCE_SPACE_TYPE_COMBINED_EYE_VARJO: reference_space_name = "eye_varjo"; 240 | break; 241 | default:reference_space_name = "unknown"; 242 | } 243 | spdlog::debug(" space: {}", reference_space_name); 244 | } 245 | } 246 | 247 | void LogSystemProperties(XrInstance instance, XrSystemId system_id) { 248 | 249 | XrSystemProperties system_properties{}; 250 | system_properties.type = XR_TYPE_SYSTEM_PROPERTIES; 251 | CHECK_XRCMD(xrGetSystemProperties(instance, system_id, &system_properties)); 252 | 253 | spdlog::info("System Properties: Name={} VendorId={}", 254 | system_properties.systemName, 255 | system_properties.vendorId); 256 | spdlog::info("System Graphics Properties: MaxWidth={} MaxHeight={} MaxLayers={}", 257 | system_properties.graphicsProperties.maxSwapchainImageWidth, 258 | system_properties.graphicsProperties.maxSwapchainImageHeight, 259 | system_properties.graphicsProperties.maxLayerCount); 260 | spdlog::info( 261 | "System Tracking Properties: OrientationTracking={} PositionTracking={}", 262 | system_properties.trackingProperties.orientationTracking == XR_TRUE 263 | ? "True" 264 | : "False", 265 | system_properties.trackingProperties.positionTracking == XR_TRUE ? "True" 266 | : "False"); 267 | } 268 | 269 | void LogActionSourceName(XrSession session, XrAction action, const std::string &action_name) { 270 | XrBoundSourcesForActionEnumerateInfo get_info{}; 271 | get_info.type = XR_TYPE_BOUND_SOURCES_FOR_ACTION_ENUMERATE_INFO; 272 | get_info.action = action; 273 | uint32_t path_count = 0; 274 | CHECK_XRCMD(xrEnumerateBoundSourcesForAction(session, &get_info, 0, &path_count, nullptr)); 275 | std::vector paths(path_count); 276 | CHECK_XRCMD(xrEnumerateBoundSourcesForAction(session, 277 | &get_info, 278 | static_cast(paths.size()), 279 | &path_count, 280 | paths.data())); 281 | 282 | std::string source_name; 283 | for (uint32_t i = 0; i < path_count; ++i) { 284 | constexpr XrInputSourceLocalizedNameFlags kAll = XR_INPUT_SOURCE_LOCALIZED_NAME_USER_PATH_BIT | 285 | XR_INPUT_SOURCE_LOCALIZED_NAME_INTERACTION_PROFILE_BIT | 286 | XR_INPUT_SOURCE_LOCALIZED_NAME_COMPONENT_BIT; 287 | 288 | XrInputSourceLocalizedNameGetInfo name_info{}; 289 | name_info.type = XR_TYPE_INPUT_SOURCE_LOCALIZED_NAME_GET_INFO; 290 | name_info.sourcePath = paths[i]; 291 | name_info.whichComponents = kAll; 292 | 293 | uint32_t size = 0; 294 | CHECK_XRCMD(xrGetInputSourceLocalizedName(session, &name_info, 0, &size, nullptr)); 295 | if (size == 0) { 296 | continue; 297 | } 298 | std::vector grab_source(size); 299 | CHECK_XRCMD(xrGetInputSourceLocalizedName(session, 300 | &name_info, 301 | static_cast(grab_source.size()), 302 | &size, 303 | grab_source.data())); 304 | if (!source_name.empty()) { 305 | source_name += " and "; 306 | } 307 | source_name += "'"; 308 | source_name += std::string(grab_source.data(), size - 1); 309 | source_name += "'"; 310 | } 311 | 312 | spdlog::info("{} action is bound to {}", 313 | action_name.c_str(), 314 | !source_name.empty() ? source_name.c_str() : "nothing"); 315 | } -------------------------------------------------------------------------------- /app/cpp/openxr_utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "openxr-include.hpp" 4 | 5 | #include 6 | 7 | #define CHECK_XRCMD(cmd) \ 8 | CheckResult(cmd, __FILE__, __LINE__) 9 | 10 | void CheckResult(XrResult result, const std::string &file, uint32_t line); 11 | std::string GetXrVersionString(XrVersion ver); 12 | void LogLayersAndExtensions(); 13 | void LogInstanceInfo(XrInstance instance); 14 | void LogViewConfigurations(XrInstance instance, XrSystemId system_id); 15 | void LogReferenceSpaces(XrSession session); 16 | void LogSystemProperties(XrInstance instance, XrSystemId system_id); 17 | void LogActionSourceName(XrSession session, XrAction action, const std::string &action_name); -------------------------------------------------------------------------------- /app/cpp/platform.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "openxr-include.hpp" 4 | 5 | #include 6 | #include 7 | 8 | class Platform { 9 | public: 10 | virtual XrBaseInStructure *GetInstanceCreateExtension() const = 0; 11 | 12 | virtual std::vector GetInstanceExtensions() const = 0; 13 | 14 | virtual ~Platform() = default; 15 | }; 16 | 17 | std::shared_ptr CreatePlatform(const std::shared_ptr &data); 18 | -------------------------------------------------------------------------------- /app/cpp/platform_android.cpp: -------------------------------------------------------------------------------- 1 | #include "platform.hpp" 2 | #include "platform_data.hpp" 3 | 4 | #include 5 | 6 | class AndroidPlatform : public Platform { 7 | public: 8 | explicit AndroidPlatform(const std::shared_ptr &data) { 9 | PFN_xrInitializeLoaderKHR initialize_loader = nullptr; 10 | 11 | if (XR_SUCCEEDED(xrGetInstanceProcAddr(XR_NULL_HANDLE, "xrInitializeLoaderKHR", 12 | (PFN_xrVoidFunction *) (&initialize_loader)))) { 13 | XrLoaderInitInfoAndroidKHR loader_init_info_android; 14 | loader_init_info_android.type = XR_TYPE_LOADER_INIT_INFO_ANDROID_KHR; 15 | loader_init_info_android.next = nullptr; 16 | loader_init_info_android.applicationVM = data->application_vm; 17 | loader_init_info_android.applicationContext = data->application_activity; 18 | initialize_loader(reinterpret_cast(&loader_init_info_android)); 19 | } 20 | 21 | instance_create_info_android_ = {XR_TYPE_INSTANCE_CREATE_INFO_ANDROID_KHR}; 22 | instance_create_info_android_.applicationVM = data->application_vm; 23 | instance_create_info_android_.applicationActivity = data->application_activity; 24 | } 25 | 26 | [[nodiscard]] std::vector GetInstanceExtensions() const override { 27 | return {XR_KHR_ANDROID_CREATE_INSTANCE_EXTENSION_NAME}; 28 | } 29 | 30 | [[nodiscard]] XrBaseInStructure * 31 | GetInstanceCreateExtension() const override { return (XrBaseInStructure *) (&instance_create_info_android_); } 32 | 33 | private: 34 | XrInstanceCreateInfoAndroidKHR instance_create_info_android_{}; 35 | }; 36 | 37 | std::shared_ptr 38 | CreatePlatform(const std::shared_ptr &data) { 39 | return std::make_shared(data); 40 | } 41 | -------------------------------------------------------------------------------- /app/cpp/platform_data.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct PlatformData { 4 | void *application_vm; 5 | void *application_activity; 6 | }; 7 | -------------------------------------------------------------------------------- /app/cpp/shaders/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(glslc_exe "${CMAKE_ANDROID_NDK}/shader-tools/${CMAKE_ANDROID_NDK_TOOLCHAIN_HOST_TAG}/glslc${TOOL_OS_SUFFIX}") 2 | 3 | #add spirv library 4 | #LIBRARY_NAME - string, name of output library target 5 | #DEBUG - boolean, enable/disable, generate debuggable spir-v shaders 6 | #WERROR - boolean, enable/disable, treat all warnings as errors. 7 | #TARGET_SPV - string, target spv version 8 | #INCLUDE_PATH - string, include path for shaders 9 | #INPUT_GLSL_FILE - list, absolute path to source files 10 | FUNCTION(add_spirv_library) 11 | cmake_parse_arguments(PARAM "" "LIBRARY_NAME;DEBUG;WERROR;TARGET_SPV;INCLUDE_PATH" "INPUT_GLSL_FILE" ${ARGN}) 12 | 13 | set(EXTRA_FLAGS) 14 | if (${PARAM_DEBUG}) 15 | set(EXTRA_FLAGS "${EXTRA_FLAGS};-O0;-g") 16 | else () 17 | set(EXTRA_FLAGS "${EXTRA_FLAGS};-O") 18 | endif () 19 | 20 | if (${PARAM_WERROR}) 21 | set(EXTRA_FLAGS "${EXTRA_FLAGS};-Werror") 22 | endif () 23 | 24 | if (NOT ${TARGET_SPV} STREQUAL "") 25 | set(EXTRA_FLAGS "${EXTRA_FLAGS};--target-spv=${TARGET_SPV}") 26 | endif () 27 | 28 | if (NOT ${INCLUDE_PATH} STREQUAL "") 29 | set(EXTRA_FLAGS "${EXTRA_FLAGS};-I;${TARGET_SPV}") 30 | endif () 31 | 32 | set(SPIR_V_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/spv") 33 | 34 | add_custom_target(CREATE_SPIRV_BUILD_DIR 35 | COMMAND ${CMAKE_COMMAND} -E make_directory ${SPIR_V_DIRECTORY}) 36 | 37 | FOREACH (FILE ${PARAM_INPUT_GLSL_FILE}) 38 | cmake_path(GET FILE STEM LAST_ONLY FILE_NAME) 39 | set(OUTPUT_FILE "${SPIR_V_DIRECTORY}/${FILE_NAME}.spv") 40 | 41 | add_custom_command(OUTPUT ${OUTPUT_FILE} 42 | COMMAND ${glslc_exe} ${EXTRA_FLAGS} -mfmt=num -o ${OUTPUT_FILE} ${FILE} 43 | COMMENT "Building GLSL object ${OUTPUT_FILE}" 44 | DEPENDS ${glslc_exe} CREATE_SPIRV_BUILD_DIR ${FILE} 45 | ) 46 | 47 | list(APPEND OUTPUT_SPV_FILES ${OUTPUT_FILE}) 48 | ENDFOREACH (FILE) 49 | 50 | add_library(${PARAM_LIBRARY_NAME} INTERFACE ${OUTPUT_SPV_FILES}) 51 | target_include_directories(${PARAM_LIBRARY_NAME} INTERFACE 52 | ${SPIR_V_DIRECTORY}) 53 | 54 | ENDFUNCTION(add_spirv_library) 55 | 56 | set(GLSL_FILES 57 | frag.glsl 58 | vert.glsl) 59 | 60 | list(TRANSFORM GLSL_FILES PREPEND "${CMAKE_CURRENT_LIST_DIR}/") 61 | 62 | set(SHADERS_DEBUG OFF) 63 | if (CMAKE_BUILD_TYPE STREQUAL "Debug") 64 | set(SHADERS_DEBUG ON) 65 | elseif (CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") 66 | set(SHADERS_DEBUG ON) 67 | endif () 68 | 69 | add_spirv_library(LIBRARY_NAME shaders 70 | DEBUG ${SHADERS_DEBUG} 71 | WERROR ${SHADERS_DEBUG} 72 | INPUT_GLSL_FILE ${GLSL_FILES}) 73 | -------------------------------------------------------------------------------- /app/cpp/shaders/frag.glsl: -------------------------------------------------------------------------------- 1 | #version 460 2 | #pragma shader_stage(fragment) 3 | #extension GL_ARB_separate_shader_objects : enable 4 | 5 | layout(location = 0) in vec4 v_color; 6 | 7 | layout(location = 0) out vec4 color; 8 | 9 | void main(){ 10 | color = v_color; 11 | } -------------------------------------------------------------------------------- /app/cpp/shaders/vert.glsl: -------------------------------------------------------------------------------- 1 | #version 460 2 | #pragma shader_stage(vertex) 3 | #extension GL_ARB_separate_shader_objects : enable 4 | 5 | layout(location = 0) in vec4 position; 6 | layout(location = 1) in vec4 color; 7 | 8 | layout(push_constant, std140) uniform UniformBufferObject { 9 | mat4 mvp; 10 | }; 11 | 12 | layout(location = 0) out vec4 v_color; 13 | 14 | void main() { 15 | v_color = color; 16 | gl_Position = mvp * position; 17 | } 18 | -------------------------------------------------------------------------------- /app/cpp/vulkan/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(vulkan-wrapper STATIC 2 | data_type.cpp 3 | vertex_buffer_layout.cpp 4 | vulkan_buffer.cpp 5 | vulkan_rendering_context.cpp 6 | vulkan_rendering_pipeline.cpp 7 | vulkan_shader.cpp 8 | vulkan_utils.cpp 9 | ) 10 | 11 | target_link_libraries(vulkan-wrapper 12 | magic_enum 13 | spdlog 14 | spirv-reflect-static 15 | vulkan 16 | ) 17 | -------------------------------------------------------------------------------- /app/cpp/vulkan/data_type.cpp: -------------------------------------------------------------------------------- 1 | #include "data_type.hpp" 2 | 3 | #include 4 | 5 | size_t vulkan::GetDataTypeSizeInBytes(DataType type) { 6 | switch (type) { 7 | case DataType::BYTE:return 1; 8 | case DataType::UINT_16:return 2; 9 | case DataType::UINT_32: 10 | case DataType::FLOAT:return 4; 11 | default: throw std::runtime_error("unsupported enum"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/cpp/vulkan/data_type.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace vulkan { 7 | enum class DataType { 8 | BYTE, 9 | UINT_16,//SHORT 10 | UINT_32, 11 | FLOAT, 12 | }; 13 | 14 | typedef enum BufferUsage { 15 | TRANSFER_SRC = 1, 16 | TRANSFER_DST = 2, 17 | UNIFORM_BUFFER = 4, 18 | INDEX_BUFFER = 8, 19 | VERTEX_BUFFER = 16, 20 | } BufferUsage; 21 | 22 | enum class MemoryType { 23 | DEVICE_LOCAL, 24 | HOST_VISIBLE, 25 | }; 26 | 27 | enum class ShaderType { 28 | VERTEX, 29 | FRAGMENT, 30 | COUNT, 31 | }; 32 | 33 | size_t GetDataTypeSizeInBytes(DataType type); 34 | } 35 | -------------------------------------------------------------------------------- /app/cpp/vulkan/redering_pipeline_config.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace vulkan { 4 | enum class CompareOp { 5 | NEVER, 6 | LESS, 7 | EQUAL, 8 | LESS_OR_EQUAL, 9 | GREATER, 10 | NOT_EQUAL, 11 | GREATER_OR_EQUAL, 12 | ALWAYS, 13 | COUNT, 14 | }; 15 | enum class CullMode { 16 | NONE, 17 | FRONT, 18 | BACK, 19 | FRONT_AND_BACK, 20 | COUNT, 21 | }; 22 | enum class FrontFace { 23 | CW, 24 | CCW, 25 | COUNT, 26 | }; 27 | enum class DrawMode { 28 | POINT_LIST, 29 | LINE_LIST, 30 | LINE_STRIP, 31 | TRIANGLE_LIST, 32 | TRIANGLE_STRIP, 33 | TRIANGLE_FAN, 34 | COUNT, 35 | }; 36 | struct RenderingPipelineConfig { 37 | DrawMode draw_mode = DrawMode::TRIANGLE_STRIP; 38 | CullMode cull_mode = CullMode::NONE; 39 | FrontFace front_face = FrontFace::CW; 40 | bool enable_depth_test = false; 41 | CompareOp depth_function = CompareOp::LESS; 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /app/cpp/vulkan/vertex_buffer_layout.cpp: -------------------------------------------------------------------------------- 1 | #include "vertex_buffer_layout.hpp" 2 | 3 | const std::vector &vulkan::VertexBufferLayout::GetElements() const { 4 | return elements_; 5 | } 6 | 7 | void vulkan::VertexBufferLayout::Push(vulkan::VertexAttribute attribute) { 8 | elements_.emplace_back(attribute); 9 | } 10 | 11 | size_t vulkan::VertexBufferLayout::GetElementSize() const { 12 | size_t size = 0; 13 | for (auto elem: elements_) { 14 | size += elem.count * GetDataTypeSizeInBytes(elem.type); 15 | } 16 | return size; 17 | } 18 | -------------------------------------------------------------------------------- /app/cpp/vulkan/vertex_buffer_layout.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "data_type.hpp" 7 | 8 | namespace vulkan { 9 | struct VertexAttribute { 10 | unsigned int binding_index; 11 | DataType type; 12 | size_t count; 13 | }; 14 | 15 | class VertexBufferLayout { 16 | private: 17 | std::vector elements_{}; 18 | public: 19 | VertexBufferLayout() = default; 20 | 21 | void Push(VertexAttribute attribute); 22 | 23 | [[nodiscard]] size_t GetElementSize() const; 24 | 25 | [[nodiscard]] const std::vector &GetElements() const; 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /app/cpp/vulkan/vulkan_buffer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Artyom Dangizyan on 1/1/21. 3 | // 4 | 5 | #include "vulkan_buffer.hpp" 6 | 7 | #include 8 | 9 | #include "vulkan_utils.hpp" 10 | 11 | vulkan::VulkanBuffer::VulkanBuffer(const std::shared_ptr &context, 12 | const size_t &length, 13 | VkBufferUsageFlags usage, 14 | VkMemoryPropertyFlags properties) 15 | : context_(context), 16 | device_(context->GetDevice()), 17 | size_in_bytes_(length), 18 | host_visible_(!(properties & VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) { 19 | if (!host_visible_) { 20 | usage |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; 21 | } 22 | context->CreateBuffer(length, 23 | usage, 24 | properties, 25 | &buffer_, 26 | &memory_); 27 | } 28 | 29 | void vulkan::VulkanBuffer::Update(const void *data) { 30 | if (host_visible_) { 31 | void *mapped_data = nullptr; 32 | CHECK_VKCMD(vkMapMemory(device_, 33 | memory_, 34 | 0, 35 | size_in_bytes_, 36 | 0, 37 | &mapped_data)); 38 | memcpy(mapped_data, data, size_in_bytes_); 39 | vkUnmapMemory(device_, memory_); 40 | } else { 41 | VulkanBuffer tmp_buffer(this->context_, 42 | this->size_in_bytes_, 43 | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, 44 | GetVkMemoryType(MemoryType::HOST_VISIBLE)); 45 | tmp_buffer.Update(data); 46 | context_->CopyBuffer(tmp_buffer.GetBuffer(), 47 | buffer_, 48 | size_in_bytes_); 49 | } 50 | } 51 | 52 | void vulkan::VulkanBuffer::CopyFrom(std::shared_ptr src_buffer, 53 | size_t size, 54 | size_t src_offset, 55 | size_t dst_offset) { 56 | context_->CopyBuffer(std::dynamic_pointer_cast(src_buffer)->GetBuffer(), 57 | buffer_, 58 | size, 59 | src_offset, 60 | dst_offset); 61 | } 62 | 63 | VkBuffer vulkan::VulkanBuffer::GetBuffer() const { 64 | return buffer_; 65 | } 66 | 67 | vulkan::VulkanBuffer::~VulkanBuffer() { 68 | vkDestroyBuffer(device_, buffer_, nullptr); 69 | vkFreeMemory(device_, memory_, nullptr); 70 | } 71 | 72 | size_t vulkan::VulkanBuffer::GetSizeInBytes() const { 73 | return size_in_bytes_; 74 | } 75 | -------------------------------------------------------------------------------- /app/cpp/vulkan/vulkan_buffer.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Artyom Dangizyan on 1/1/21. 3 | // 4 | 5 | #pragma once 6 | 7 | #include 8 | #include "vulkan_rendering_context.hpp" 9 | 10 | namespace vulkan { 11 | class VulkanBuffer { 12 | public: 13 | VulkanBuffer() = delete; 14 | VulkanBuffer(const VulkanBuffer &) = delete; 15 | VulkanBuffer(const std::shared_ptr &context, const size_t &length, 16 | VkBufferUsageFlags usage, 17 | VkMemoryPropertyFlags properties); 18 | void Update(const void *data); 19 | void CopyFrom(std::shared_ptr src_buffer, 20 | size_t size, 21 | size_t src_offset, 22 | size_t dst_offset); 23 | [[nodiscard]] VkBuffer GetBuffer() const; 24 | [[nodiscard]] size_t GetSizeInBytes() const; 25 | virtual ~VulkanBuffer(); 26 | protected: 27 | std::shared_ptr context_; 28 | VkDevice device_; 29 | size_t size_in_bytes_; 30 | VkBuffer buffer_ = nullptr; 31 | VkDeviceMemory memory_ = nullptr; 32 | private: 33 | bool host_visible_; 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /app/cpp/vulkan/vulkan_rendering_context.cpp: -------------------------------------------------------------------------------- 1 | #include "vulkan_rendering_context.hpp" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | vulkan::VulkanRenderingContext::VulkanRenderingContext( 8 | VkPhysicalDevice physical_device, 9 | VkDevice device, 10 | VkQueue graphics_queue, 11 | VkCommandPool graphics_pool, 12 | VkFormat color_attachment_format) : 13 | color_attachment_format_(color_attachment_format), 14 | physical_device_(physical_device), 15 | device_(device), 16 | graphics_queue_(graphics_queue), 17 | graphics_pool_(graphics_pool), 18 | recommended_msaa_samples_(GetMaxUsableSampleCount()) { 19 | 20 | depth_attachment_format_ = FindSupportedFormat( 21 | {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}, 22 | VK_IMAGE_TILING_OPTIMAL, 23 | VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT 24 | ); 25 | 26 | ///render pass 27 | VkAttachmentDescription depth_attachment = {}; 28 | depth_attachment.format = depth_attachment_format_; 29 | depth_attachment.samples = recommended_msaa_samples_; 30 | depth_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; 31 | depth_attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; 32 | depth_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; 33 | depth_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; 34 | depth_attachment.initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; 35 | depth_attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; 36 | 37 | VkAttachmentReference depth_attachment_ref = {}; 38 | depth_attachment_ref.attachment = 1; 39 | depth_attachment_ref.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; 40 | 41 | VkAttachmentDescription color_attachment = {}; 42 | color_attachment.format = color_attachment_format_; 43 | color_attachment.samples = recommended_msaa_samples_; 44 | color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; 45 | color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; 46 | color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; 47 | color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; 48 | color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; 49 | color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; 50 | 51 | VkAttachmentReference color_attachment_ref = {}; 52 | color_attachment_ref.attachment = 0; 53 | color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; 54 | 55 | VkAttachmentDescription color_attachment_resolve = {}; 56 | color_attachment_resolve.format = color_attachment_format_; 57 | color_attachment_resolve.samples = VK_SAMPLE_COUNT_1_BIT; 58 | color_attachment_resolve.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; 59 | color_attachment_resolve.storeOp = VK_ATTACHMENT_STORE_OP_STORE; 60 | color_attachment_resolve.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; 61 | color_attachment_resolve.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; 62 | color_attachment_resolve.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; 63 | color_attachment_resolve.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; 64 | 65 | VkAttachmentReference color_attachment_resolve_ref = {}; 66 | color_attachment_resolve_ref.attachment = 2; 67 | color_attachment_resolve_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; 68 | 69 | VkSubpassDescription sub_pass = {}; 70 | sub_pass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; 71 | sub_pass.colorAttachmentCount = 1; 72 | sub_pass.pColorAttachments = &color_attachment_ref; 73 | sub_pass.pDepthStencilAttachment = &depth_attachment_ref; 74 | sub_pass.pResolveAttachments = &color_attachment_resolve_ref; 75 | 76 | VkSubpassDependency dependency = {}; 77 | dependency.srcSubpass = VK_SUBPASS_EXTERNAL; 78 | dependency.dstSubpass = 0; 79 | dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; 80 | dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; 81 | dependency.srcAccessMask = 0; 82 | dependency.dstAccessMask = 83 | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; 84 | dependency.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; 85 | 86 | std::array 87 | attachments = {color_attachment, depth_attachment, color_attachment_resolve}; 88 | 89 | VkRenderPassCreateInfo render_pass_info = {}; 90 | render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; 91 | render_pass_info.attachmentCount = static_cast(attachments.size()); 92 | render_pass_info.pAttachments = attachments.data(); 93 | render_pass_info.subpassCount = 1; 94 | render_pass_info.pSubpasses = &sub_pass; 95 | render_pass_info.dependencyCount = 1; 96 | render_pass_info.pDependencies = &dependency; 97 | 98 | if (vkCreateRenderPass(device_, &render_pass_info, nullptr, &render_pass_) != VK_SUCCESS) { 99 | throw std::runtime_error("failed to create render pass!"); 100 | } 101 | } 102 | 103 | VkSampleCountFlagBits vulkan::VulkanRenderingContext::GetMaxUsableSampleCount() { 104 | VkPhysicalDeviceProperties physical_device_properties; 105 | vkGetPhysicalDeviceProperties(physical_device_, 106 | &physical_device_properties); 107 | VkSampleCountFlags 108 | counts = std::min(physical_device_properties.limits.framebufferColorSampleCounts, 109 | physical_device_properties.limits.framebufferDepthSampleCounts); 110 | if (counts & VK_SAMPLE_COUNT_64_BIT) { 111 | return VK_SAMPLE_COUNT_64_BIT; 112 | } 113 | if (counts & VK_SAMPLE_COUNT_32_BIT) { 114 | return VK_SAMPLE_COUNT_32_BIT; 115 | } 116 | if (counts & VK_SAMPLE_COUNT_16_BIT) { 117 | return VK_SAMPLE_COUNT_16_BIT; 118 | } 119 | if (counts & VK_SAMPLE_COUNT_8_BIT) { 120 | return VK_SAMPLE_COUNT_8_BIT; 121 | } 122 | if (counts & VK_SAMPLE_COUNT_4_BIT) { 123 | return VK_SAMPLE_COUNT_4_BIT; 124 | } 125 | if (counts & VK_SAMPLE_COUNT_2_BIT) { 126 | return VK_SAMPLE_COUNT_2_BIT; 127 | } 128 | return VK_SAMPLE_COUNT_1_BIT; 129 | } 130 | 131 | VkDevice vulkan::VulkanRenderingContext::GetDevice() const { 132 | return device_; 133 | } 134 | 135 | void vulkan::VulkanRenderingContext::WaitForGpuIdle() const { 136 | vkDeviceWaitIdle(device_); 137 | } 138 | 139 | VkFormat vulkan::VulkanRenderingContext::FindSupportedFormat( 140 | const std::vector &candidates, 141 | VkImageTiling tiling, 142 | VkFormatFeatureFlags features) const { 143 | for (VkFormat format: candidates) { 144 | VkFormatProperties props; 145 | vkGetPhysicalDeviceFormatProperties(physical_device_, format, &props); 146 | if ((tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) 147 | || (tiling == VK_IMAGE_TILING_OPTIMAL 148 | && (props.optimalTilingFeatures & features) == features)) { 149 | return format; 150 | } 151 | } 152 | throw std::runtime_error("failed to find supported format!"); 153 | } 154 | void vulkan::VulkanRenderingContext::CreateImage(uint32_t width, 155 | uint32_t height, 156 | VkSampleCountFlagBits num_samples, 157 | VkFormat format, 158 | VkImageUsageFlags usage, 159 | VkMemoryPropertyFlags properties, 160 | VkImage *image, 161 | VkDeviceMemory *image_memory) const { 162 | VkImageCreateInfo image_info = {}; 163 | image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; 164 | image_info.imageType = VK_IMAGE_TYPE_2D; 165 | image_info.extent.width = width; 166 | image_info.extent.height = height; 167 | image_info.extent.depth = 1; 168 | image_info.mipLevels = 1; 169 | image_info.arrayLayers = 1; 170 | image_info.format = format; 171 | image_info.tiling = VK_IMAGE_TILING_OPTIMAL; 172 | image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; 173 | image_info.usage = usage; 174 | image_info.samples = num_samples; 175 | image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; 176 | 177 | if (vkCreateImage(device_, &image_info, nullptr, image) != VK_SUCCESS) { 178 | throw std::runtime_error("failed to create image!"); 179 | } 180 | 181 | VkMemoryRequirements mem_requirements; 182 | vkGetImageMemoryRequirements(device_, *image, &mem_requirements); 183 | 184 | VkMemoryAllocateInfo alloc_info = {}; 185 | alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; 186 | alloc_info.allocationSize = mem_requirements.size; 187 | alloc_info.memoryTypeIndex = FindMemoryType(mem_requirements.memoryTypeBits, properties); 188 | 189 | if (vkAllocateMemory((device_), &alloc_info, nullptr, image_memory) != VK_SUCCESS) { 190 | throw std::runtime_error("failed to allocate image memory!"); 191 | } 192 | 193 | vkBindImageMemory(device_, *image, *image_memory, 0); 194 | } 195 | 196 | VkRenderPass vulkan::VulkanRenderingContext::GetRenderPass() const { 197 | return render_pass_; 198 | } 199 | 200 | void vulkan::VulkanRenderingContext::TransitionImageLayout(VkImage image, 201 | VkImageLayout old_layout, 202 | VkImageLayout new_layout) { 203 | VkCommandBuffer command_buffer = BeginSingleTimeCommands(graphics_pool_); 204 | 205 | VkImageMemoryBarrier barrier = {}; 206 | barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; 207 | barrier.oldLayout = old_layout; 208 | barrier.newLayout = new_layout; 209 | barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; 210 | barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; 211 | barrier.image = image; 212 | barrier.subresourceRange.baseMipLevel = 0; 213 | barrier.subresourceRange.levelCount = 1; 214 | barrier.subresourceRange.baseArrayLayer = 0; 215 | barrier.subresourceRange.layerCount = 1; 216 | VkPipelineStageFlags source_stage; 217 | VkPipelineStageFlags destination_stage; 218 | if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED 219 | && new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { 220 | barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 221 | barrier.srcAccessMask = 0; 222 | barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; 223 | source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; 224 | destination_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; 225 | } else if (old_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && 226 | new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { 227 | barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 228 | barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; 229 | barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; 230 | source_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; 231 | destination_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; 232 | } else if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED && new_layout == 233 | VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { 234 | barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; 235 | barrier.srcAccessMask = 0; 236 | barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT 237 | | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; 238 | source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; 239 | destination_stage = 240 | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; 241 | } else if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED && new_layout == 242 | VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { 243 | barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; 244 | barrier.srcAccessMask = 0; 245 | barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT 246 | | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; 247 | source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; 248 | destination_stage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; 249 | } else { 250 | throw std::invalid_argument("unsupported layout transition!"); 251 | } 252 | vkCmdPipelineBarrier( 253 | command_buffer, 254 | source_stage, destination_stage, 255 | 0, 256 | 0, nullptr, 257 | 0, nullptr, 258 | 1, &barrier 259 | ); 260 | 261 | EndSingleTimeCommands(graphics_queue_, graphics_pool_, command_buffer); 262 | } 263 | 264 | void vulkan::VulkanRenderingContext::CreateBuffer(VkDeviceSize size, 265 | VkBufferUsageFlags usage, 266 | VkMemoryPropertyFlags properties, 267 | VkBuffer *buffer, 268 | VkDeviceMemory *buffer_memory) { 269 | VkBufferCreateInfo buffer_info = {}; 270 | buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; 271 | buffer_info.size = size; 272 | buffer_info.usage = usage; 273 | buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; 274 | if (vkCreateBuffer(device_, &buffer_info, nullptr, buffer) != VK_SUCCESS) { 275 | throw std::runtime_error("failed to create buffer!"); 276 | } 277 | VkMemoryRequirements mem_requirements; 278 | vkGetBufferMemoryRequirements(device_, *buffer, &mem_requirements); 279 | VkMemoryAllocateInfo alloc_info = {}; 280 | alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; 281 | alloc_info.allocationSize = mem_requirements.size; 282 | alloc_info.memoryTypeIndex = FindMemoryType(mem_requirements.memoryTypeBits, properties); 283 | if (vkAllocateMemory(device_, &alloc_info, nullptr, buffer_memory) != VK_SUCCESS) { 284 | throw std::runtime_error("failed to allocate buffer memory!"); 285 | } 286 | vkBindBufferMemory(device_, *buffer, *buffer_memory, 0); 287 | } 288 | 289 | void vulkan::VulkanRenderingContext::CopyBuffer(VkBuffer src_buffer, 290 | VkBuffer dst_buffer, 291 | VkDeviceSize size, 292 | VkDeviceSize src_offset, 293 | VkDeviceSize dst_offset) { 294 | VkCommandBuffer command_buffer = BeginSingleTimeCommands(graphics_pool_); 295 | VkBufferCopy copy_region = {}; 296 | copy_region.size = size; 297 | copy_region.srcOffset = src_offset; 298 | copy_region.dstOffset = dst_offset; 299 | vkCmdCopyBuffer(command_buffer, src_buffer, dst_buffer, 1, ©_region); 300 | EndSingleTimeCommands(graphics_queue_, graphics_pool_, command_buffer); 301 | } 302 | 303 | uint32_t vulkan::VulkanRenderingContext::FindMemoryType(uint32_t type_filter, 304 | VkMemoryPropertyFlags properties) const { 305 | VkPhysicalDeviceMemoryProperties mem_properties; 306 | vkGetPhysicalDeviceMemoryProperties(physical_device_, &mem_properties); 307 | 308 | for (uint32_t i = 0; i < mem_properties.memoryTypeCount; i++) { 309 | if (type_filter & (1u << i) 310 | && (mem_properties.memoryTypes[i].propertyFlags & properties) == properties) { 311 | return i; 312 | } 313 | } 314 | throw std::runtime_error("failed to find suitable memory type!"); 315 | } 316 | 317 | void vulkan::VulkanRenderingContext::CreateImageView(VkImage image, 318 | VkFormat format, 319 | VkImageAspectFlagBits aspect_mask, 320 | VkImageView *image_view) { 321 | VkImageViewCreateInfo view_info = {}; 322 | view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; 323 | view_info.image = image; 324 | view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; 325 | view_info.format = format; 326 | view_info.subresourceRange.aspectMask = aspect_mask; 327 | view_info.subresourceRange.baseMipLevel = 0; 328 | view_info.subresourceRange.levelCount = 1; 329 | view_info.subresourceRange.baseArrayLayer = 0; 330 | view_info.subresourceRange.layerCount = 1; 331 | if (vkCreateImageView(device_, &view_info, nullptr, image_view) != VK_SUCCESS) { 332 | throw std::runtime_error("failed to create texture image view!"); 333 | } 334 | } 335 | 336 | VkCommandBuffer vulkan::VulkanRenderingContext::BeginSingleTimeCommands(VkCommandPool command_pool) { 337 | VkCommandBufferAllocateInfo alloc_info = {}; 338 | alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; 339 | alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; 340 | alloc_info.commandPool = command_pool; 341 | alloc_info.commandBufferCount = 1; 342 | VkCommandBuffer command_buffer; 343 | vkAllocateCommandBuffers(device_, &alloc_info, &command_buffer); 344 | VkCommandBufferBeginInfo begin_info = {}; 345 | begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; 346 | begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; 347 | vkBeginCommandBuffer(command_buffer, &begin_info); 348 | return command_buffer; 349 | } 350 | 351 | void vulkan::VulkanRenderingContext::EndSingleTimeCommands(VkQueue queue, 352 | VkCommandPool pool, 353 | VkCommandBuffer command_buffer) { 354 | vkEndCommandBuffer(command_buffer); 355 | VkSubmitInfo submit_info = {}; 356 | submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; 357 | submit_info.commandBufferCount = 1; 358 | submit_info.pCommandBuffers = &command_buffer; 359 | vkQueueSubmit(queue, 1, &submit_info, VK_NULL_HANDLE); 360 | vkQueueWaitIdle(queue); 361 | vkFreeCommandBuffers(device_, pool, 1, &command_buffer); 362 | } 363 | 364 | VkSampleCountFlagBits vulkan::VulkanRenderingContext::GetRecommendedMsaaSamples() const { 365 | return recommended_msaa_samples_; 366 | } 367 | 368 | vulkan::VulkanRenderingContext::~VulkanRenderingContext() { 369 | vkDestroyRenderPass(device_, render_pass_, nullptr); 370 | } 371 | 372 | VkFormat vulkan::VulkanRenderingContext::GetDepthAttachmentFormat() const { 373 | return depth_attachment_format_; 374 | } 375 | VkCommandPool vulkan::VulkanRenderingContext::GetGraphicsPool() const { 376 | return graphics_pool_; 377 | } 378 | VkQueue vulkan::VulkanRenderingContext::GetGraphicsQueue() const { 379 | return graphics_queue_; 380 | } 381 | -------------------------------------------------------------------------------- /app/cpp/vulkan/vulkan_rendering_context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "data_type.hpp" 6 | 7 | #include 8 | 9 | namespace vulkan { 10 | class VulkanRenderingContext 11 | : public std::enable_shared_from_this { 12 | private: 13 | VkFormat color_attachment_format_ = VK_FORMAT_UNDEFINED; 14 | VkFormat depth_attachment_format_ = VK_FORMAT_UNDEFINED; 15 | 16 | VkPhysicalDevice physical_device_; 17 | VkDevice device_; 18 | VkQueue graphics_queue_; 19 | VkCommandPool graphics_pool_; 20 | VkSampleCountFlagBits recommended_msaa_samples_; 21 | VkRenderPass render_pass_ = VK_NULL_HANDLE; 22 | 23 | VkSampleCountFlagBits GetMaxUsableSampleCount(); 24 | public: 25 | VulkanRenderingContext(VkPhysicalDevice physical_device, 26 | VkDevice device, 27 | VkQueue graphics_queue, 28 | VkCommandPool graphics_pool, 29 | VkFormat color_attachment_format); 30 | 31 | [[nodiscard]] VkDevice GetDevice() const; 32 | 33 | VkFormat GetDepthAttachmentFormat() const; 34 | 35 | void WaitForGpuIdle() const; 36 | 37 | virtual ~VulkanRenderingContext(); 38 | 39 | void CreateBuffer(VkDeviceSize size, 40 | VkBufferUsageFlags usage, 41 | VkMemoryPropertyFlags properties, 42 | VkBuffer *buffer, 43 | VkDeviceMemory *buffer_memory); 44 | 45 | void CreateImage(uint32_t width, 46 | uint32_t height, 47 | VkSampleCountFlagBits num_samples, 48 | VkFormat format, 49 | VkImageUsageFlags usage, 50 | VkMemoryPropertyFlags properties, 51 | VkImage *image, 52 | VkDeviceMemory *image_memory) const; 53 | 54 | void CopyBuffer(VkBuffer src_buffer, 55 | VkBuffer dst_buffer, 56 | VkDeviceSize size, 57 | VkDeviceSize src_offset = 0, 58 | VkDeviceSize dst_offset = 0); 59 | 60 | void TransitionImageLayout(VkImage image, 61 | VkImageLayout old_layout, 62 | VkImageLayout new_layout); 63 | 64 | void CreateImageView(VkImage image, 65 | VkFormat format, 66 | VkImageAspectFlagBits aspect_mask, 67 | VkImageView *image_view); 68 | 69 | VkCommandBuffer BeginSingleTimeCommands(VkCommandPool command_pool); 70 | 71 | void EndSingleTimeCommands(VkQueue queue, VkCommandPool pool, VkCommandBuffer command_buffer); 72 | 73 | [[nodiscard]] uint32_t FindMemoryType(uint32_t type_filter, 74 | VkMemoryPropertyFlags properties) const; 75 | 76 | [[nodiscard]] VkRenderPass GetRenderPass() const; 77 | 78 | VkCommandPool GetGraphicsPool() const; 79 | 80 | VkQueue GetGraphicsQueue() const; 81 | 82 | [[nodiscard]] VkFormat FindSupportedFormat(const std::vector &candidates, 83 | VkImageTiling tiling, 84 | VkFormatFeatureFlags features) const; 85 | 86 | [[nodiscard]] VkSampleCountFlagBits GetRecommendedMsaaSamples() const; 87 | }; 88 | } 89 | -------------------------------------------------------------------------------- /app/cpp/vulkan/vulkan_rendering_pipeline.cpp: -------------------------------------------------------------------------------- 1 | #include "vulkan_rendering_pipeline.hpp" 2 | 3 | vulkan::VulkanRenderingPipeline::VulkanRenderingPipeline( 4 | std::shared_ptr context, 5 | std::shared_ptr vertex_shader, 6 | std::shared_ptr fragment_shader, 7 | const VertexBufferLayout &vbl, 8 | RenderingPipelineConfig config) : 9 | context_(context), 10 | device_(context_->GetDevice()), 11 | config_(config) { 12 | this->vertex_shader_ = std::dynamic_pointer_cast(vertex_shader); 13 | this->fragment_shader_ = std::dynamic_pointer_cast(fragment_shader); 14 | CreatePipeline(vbl); 15 | } 16 | 17 | void vulkan::VulkanRenderingPipeline::SetVertexBuffer(std::shared_ptr buffer) { 18 | this->vertex_buffer_ = std::dynamic_pointer_cast(buffer); 19 | } 20 | 21 | void vulkan::VulkanRenderingPipeline::SetIndexBuffer(std::shared_ptr buffer, 22 | DataType element_type) { 23 | this->index_buffer_ = std::dynamic_pointer_cast(buffer); 24 | this->index_type_ = GetVkType(element_type); 25 | } 26 | 27 | void vulkan::VulkanRenderingPipeline::CreatePipeline(const VertexBufferLayout &vbl) { 28 | VkPipelineShaderStageCreateInfo shader_stages[] = { 29 | vertex_shader_->GetShaderStageInfo(), 30 | fragment_shader_->GetShaderStageInfo() 31 | }; 32 | auto vertex_push_constants = vertex_shader_->GetPushConstants(); 33 | auto fragment_push_constants = fragment_shader_->GetPushConstants(); 34 | auto pipeline_push_constants = vertex_push_constants; 35 | pipeline_push_constants.reserve(pipeline_push_constants.size() + fragment_push_constants.size()); 36 | pipeline_push_constants.insert(pipeline_push_constants.end(), 37 | fragment_push_constants.begin(), 38 | fragment_push_constants.end()); 39 | 40 | VkPipelineInputAssemblyStateCreateInfo input_assembly = {}; 41 | input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; 42 | input_assembly.topology = GetVkDrawMode(config_.draw_mode); 43 | input_assembly.primitiveRestartEnable = VK_FALSE; 44 | 45 | VkPipelineViewportStateCreateInfo viewport_state = {}; 46 | viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; 47 | viewport_state.viewportCount = 1; 48 | viewport_state.pViewports = VK_NULL_HANDLE; 49 | viewport_state.scissorCount = 1; 50 | viewport_state.pScissors = VK_NULL_HANDLE; 51 | 52 | VkPipelineRasterizationStateCreateInfo rasterizer = {}; 53 | rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; 54 | rasterizer.depthClampEnable = VK_FALSE; 55 | rasterizer.rasterizerDiscardEnable = VK_FALSE; 56 | rasterizer.polygonMode = VK_POLYGON_MODE_FILL; 57 | rasterizer.lineWidth = 1.0F; 58 | rasterizer.cullMode = GetVkCullMode(config_.cull_mode); 59 | rasterizer.frontFace = GetVkFrontFace(config_.front_face); 60 | rasterizer.depthBiasEnable = VK_FALSE; 61 | 62 | VkPipelineMultisampleStateCreateInfo multisampling = {}; 63 | multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; 64 | multisampling.sampleShadingEnable = VK_FALSE; 65 | multisampling.alphaToCoverageEnable = VK_FALSE; 66 | multisampling.rasterizationSamples = context_->GetRecommendedMsaaSamples(); 67 | 68 | VkPipelineColorBlendAttachmentState color_blend_attachment = {}; 69 | color_blend_attachment.colorWriteMask = 70 | VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT 71 | | VK_COLOR_COMPONENT_A_BIT; 72 | color_blend_attachment.blendEnable = VK_FALSE; 73 | 74 | VkPipelineColorBlendStateCreateInfo color_blending = {}; 75 | color_blending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; 76 | color_blending.logicOpEnable = VK_FALSE; 77 | color_blending.logicOp = VK_LOGIC_OP_COPY; 78 | color_blending.attachmentCount = 1; 79 | color_blending.pAttachments = &color_blend_attachment; 80 | color_blending.blendConstants[0] = 0.0F; 81 | color_blending.blendConstants[1] = 0.0F; 82 | color_blending.blendConstants[2] = 0.0F; 83 | color_blending.blendConstants[3] = 0.0F; 84 | 85 | VkPipelineDepthStencilStateCreateInfo depth_stencil = {}; 86 | depth_stencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; 87 | depth_stencil.depthTestEnable = config_.enable_depth_test; 88 | depth_stencil.depthWriteEnable = VK_TRUE; 89 | depth_stencil.depthCompareOp = GetVkCompareOp(config_.depth_function); 90 | depth_stencil.depthBoundsTestEnable = VK_FALSE; 91 | depth_stencil.minDepthBounds = 0.0F; 92 | depth_stencil.maxDepthBounds = 1.0F; 93 | 94 | VkPipelineLayoutCreateInfo pipeline_layout_info = {}; 95 | pipeline_layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; 96 | pipeline_layout_info.setLayoutCount = 0; 97 | pipeline_layout_info.pSetLayouts = nullptr; 98 | pipeline_layout_info.pushConstantRangeCount = pipeline_push_constants.size(); 99 | pipeline_layout_info.pPushConstantRanges = pipeline_push_constants.data(); 100 | CHECK_VKCMD(vkCreatePipelineLayout(device_, &pipeline_layout_info, nullptr, &pipeline_layout_)); 101 | 102 | VkPipelineDynamicStateCreateInfo dynamic_state_create_info{}; 103 | dynamic_state_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; 104 | std::vector dynamic_states = {VkDynamicState::VK_DYNAMIC_STATE_VIEWPORT, 105 | VkDynamicState::VK_DYNAMIC_STATE_SCISSOR}; 106 | dynamic_state_create_info.dynamicStateCount = static_cast(dynamic_states.size()); 107 | dynamic_state_create_info.pDynamicStates = dynamic_states.data(); 108 | 109 | const auto &elements = vbl.GetElements(); 110 | auto stride = static_cast(vbl.GetElementSize()); 111 | size_t offset = 0; 112 | std::vector attribute_descriptions{}; 113 | for (auto element: elements) { 114 | VkVertexInputAttributeDescription description{ 115 | .location = element.binding_index, 116 | .binding = 0, 117 | .format = GetVkFormat(element.type, static_cast(element.count)), 118 | .offset = static_cast(offset), 119 | }; 120 | attribute_descriptions.push_back(description); 121 | offset += element.count * GetDataTypeSizeInBytes(element.type); 122 | } 123 | 124 | VkVertexInputBindingDescription vertex_input_binding_description{}; 125 | vertex_input_binding_description.binding = 0; 126 | vertex_input_binding_description.stride = stride; 127 | vertex_input_binding_description.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; 128 | 129 | VkPipelineVertexInputStateCreateInfo vertex_input_info = {}; 130 | vertex_input_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; 131 | vertex_input_info.vertexBindingDescriptionCount = 1; 132 | vertex_input_info.pVertexBindingDescriptions = &vertex_input_binding_description; 133 | vertex_input_info.vertexAttributeDescriptionCount = 134 | static_cast(attribute_descriptions.size()); 135 | vertex_input_info.pVertexAttributeDescriptions = attribute_descriptions.data(); 136 | 137 | VkGraphicsPipelineCreateInfo pipeline_info = {}; 138 | pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; 139 | pipeline_info.stageCount = 2; 140 | pipeline_info.pStages = shader_stages; 141 | pipeline_info.pVertexInputState = &vertex_input_info; 142 | pipeline_info.pInputAssemblyState = &input_assembly; 143 | pipeline_info.pTessellationState = VK_NULL_HANDLE; 144 | pipeline_info.pViewportState = &viewport_state; 145 | pipeline_info.pRasterizationState = &rasterizer; 146 | pipeline_info.pMultisampleState = &multisampling; 147 | pipeline_info.pDepthStencilState = &depth_stencil; 148 | pipeline_info.pColorBlendState = &color_blending; 149 | pipeline_info.pDynamicState = VK_NULL_HANDLE; 150 | pipeline_info.layout = pipeline_layout_; 151 | pipeline_info.renderPass = context_->GetRenderPass(); 152 | pipeline_info.subpass = 0; 153 | pipeline_info.basePipelineHandle = VK_NULL_HANDLE; 154 | pipeline_info.pDynamicState = &dynamic_state_create_info; 155 | 156 | CHECK_VKCMD(vkCreateGraphicsPipelines(device_, 157 | VK_NULL_HANDLE, 158 | 1, 159 | &pipeline_info, 160 | nullptr, 161 | &pipeline_)); 162 | } 163 | 164 | void vulkan::VulkanRenderingPipeline::BindPipeline(VkCommandBuffer command_buffer) { 165 | vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_); 166 | VkDeviceSize offsets[] = {0}; 167 | auto buffer = vertex_buffer_->GetBuffer(); 168 | vkCmdBindVertexBuffers(command_buffer, 0, 1, &buffer, offsets); 169 | vkCmdBindIndexBuffer(command_buffer, index_buffer_->GetBuffer(), 0, this->index_type_); 170 | } 171 | 172 | vulkan::VulkanRenderingPipeline::~VulkanRenderingPipeline() { 173 | context_->WaitForGpuIdle(); 174 | vkDestroyPipelineLayout(device_, pipeline_layout_, nullptr); 175 | vkDestroyPipeline(device_, pipeline_, nullptr); 176 | } 177 | VkPipelineLayout vulkan::VulkanRenderingPipeline::GetPipelineLayout() const { 178 | return pipeline_layout_; 179 | } 180 | -------------------------------------------------------------------------------- /app/cpp/vulkan/vulkan_rendering_pipeline.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include "vulkan_buffer.hpp" 7 | #include "vulkan_rendering_context.hpp" 8 | #include "vulkan_shader.hpp" 9 | #include "vertex_buffer_layout.hpp" 10 | 11 | namespace vulkan { 12 | class VulkanRenderingPipeline { 13 | private: 14 | std::shared_ptr context_; 15 | VkDevice device_; 16 | RenderingPipelineConfig config_; 17 | 18 | VkPipeline pipeline_{}; 19 | VkPipelineLayout pipeline_layout_ = nullptr; 20 | 21 | std::shared_ptr vertex_buffer_ = nullptr; 22 | 23 | std::shared_ptr index_buffer_ = nullptr; 24 | VkIndexType index_type_ = VkIndexType::VK_INDEX_TYPE_UINT16; 25 | 26 | std::shared_ptr vertex_shader_ = nullptr; 27 | std::shared_ptr fragment_shader_ = nullptr; 28 | 29 | void CreatePipeline(const VertexBufferLayout &vbl); 30 | 31 | public: 32 | VulkanRenderingPipeline() = delete; 33 | VulkanRenderingPipeline(const VulkanRenderingPipeline &) = delete; 34 | VulkanRenderingPipeline(std::shared_ptr context, 35 | std::shared_ptr vertex_shader, 36 | std::shared_ptr fragment_shader, 37 | const VertexBufferLayout &vbl, 38 | RenderingPipelineConfig config); 39 | 40 | void SetIndexBuffer(std::shared_ptr buffer, DataType element_type); 41 | void SetVertexBuffer(std::shared_ptr buffer); 42 | void BindPipeline(VkCommandBuffer command_buffer); 43 | VkPipelineLayout GetPipelineLayout() const; 44 | virtual ~VulkanRenderingPipeline(); 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /app/cpp/vulkan/vulkan_shader.cpp: -------------------------------------------------------------------------------- 1 | #include "vulkan_shader.hpp" 2 | 3 | #include 4 | #include 5 | 6 | vulkan::VulkanShader::VulkanShader(const std::shared_ptr &context, 7 | const std::vector &code, 8 | std::string entry_point_name) 9 | : code_(std::move(code)), 10 | entry_point_name_(std::move(entry_point_name)), 11 | device_(context->GetDevice()) { 12 | VkShaderModuleCreateInfo create_info = { 13 | .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, 14 | .codeSize = code_.size() * sizeof(uint32_t), 15 | .pCode = code_.data(), 16 | }; 17 | if (vkCreateShaderModule(device_, &create_info, nullptr, &shader_module_) != VK_SUCCESS) { 18 | throw std::runtime_error("failed to create shader module!"); 19 | } 20 | 21 | SpvReflectResult 22 | result = 23 | spvReflectCreateShaderModule(code_.size() * sizeof(uint32_t), 24 | code_.data(), 25 | &reflect_shader_module_); 26 | if (result != SPV_REFLECT_RESULT_SUCCESS) { 27 | throw std::runtime_error("spir-v reflection failed"); 28 | } 29 | switch (reflect_shader_module_.shader_stage) { 30 | case SPV_REFLECT_SHADER_STAGE_VERTEX_BIT: 31 | this->type_ = VkShaderStageFlagBits::VK_SHADER_STAGE_VERTEX_BIT; 32 | break; 33 | case SPV_REFLECT_SHADER_STAGE_FRAGMENT_BIT: 34 | this->type_ = VkShaderStageFlagBits::VK_SHADER_STAGE_FRAGMENT_BIT; 35 | break; 36 | default:throw std::runtime_error("unhandled shader stage"); 37 | } 38 | 39 | uint32_t count = 0; 40 | result = spvReflectEnumerateEntryPointPushConstantBlocks(&reflect_shader_module_, 41 | this->entry_point_name_.data(), 42 | &count, 43 | nullptr); 44 | 45 | if (result != SPV_REFLECT_RESULT_SUCCESS)[[unlikely]] { 46 | throw std::runtime_error(fmt::format("spirv reflect failed with error {}\n", 47 | magic_enum::enum_name(result))); 48 | } 49 | 50 | std::vector blocks(count); 51 | result = spvReflectEnumerateEntryPointPushConstantBlocks(&reflect_shader_module_, 52 | this->entry_point_name_.data(), 53 | &count, 54 | blocks.data()); 55 | 56 | if (result != SPV_REFLECT_RESULT_SUCCESS)[[unlikely]] { 57 | throw std::runtime_error(fmt::format("spirv reflect failed with error {}\n", 58 | magic_enum::enum_name(result))); 59 | } 60 | 61 | for (const auto &block: blocks) { 62 | VkPushConstantRange range{ 63 | .stageFlags = type_, 64 | .offset = block->offset, 65 | .size = block->size, 66 | }; 67 | push_constants_.emplace_back(range); 68 | } 69 | 70 | } 71 | 72 | VkPipelineShaderStageCreateInfo vulkan::VulkanShader::GetShaderStageInfo() const { 73 | return { 74 | .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, 75 | .stage = type_, 76 | .module = shader_module_, 77 | .pName = this->entry_point_name_.data(), 78 | .pSpecializationInfo = nullptr, 79 | }; 80 | } 81 | 82 | vulkan::VulkanShader::~VulkanShader() { 83 | spvReflectDestroyShaderModule(&reflect_shader_module_); 84 | vkDestroyShaderModule(device_, shader_module_, nullptr); 85 | } 86 | const std::vector &vulkan::VulkanShader::GetPushConstants() const { 87 | return push_constants_; 88 | } 89 | 90 | -------------------------------------------------------------------------------- /app/cpp/vulkan/vulkan_shader.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "vulkan_rendering_context.hpp" 6 | #include "vulkan_utils.hpp" 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace vulkan { 16 | class VulkanShader { 17 | private: 18 | std::vector code_{}; 19 | std::string entry_point_name_; 20 | VkShaderStageFlagBits type_; 21 | 22 | VkDevice device_; 23 | VkShaderModule shader_module_ = nullptr; 24 | SpvReflectShaderModule reflect_shader_module_{}; 25 | std::vector push_constants_{}; 26 | public: 27 | VulkanShader(const std::shared_ptr &context, 28 | const std::vector &code, 29 | std::string entry_point_name); 30 | 31 | [[nodiscard]] VkPipelineShaderStageCreateInfo GetShaderStageInfo() const; 32 | 33 | const std::vector &GetPushConstants() const; 34 | 35 | virtual ~VulkanShader(); 36 | }; 37 | } 38 | -------------------------------------------------------------------------------- /app/cpp/vulkan/vulkan_utils.cpp: -------------------------------------------------------------------------------- 1 | #include "vulkan_utils.hpp" 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | void vulkan::CheckResult(VkResult result, const std::string &file, uint32_t line) { 8 | if (result != VK_SUCCESS) [[unlikely]] { 9 | throw std::runtime_error(fmt::format("call failed with error {} {}:{}\n", 10 | magic_enum::enum_name(result), 11 | file, 12 | line)); 13 | } 14 | } 15 | 16 | std::vector vulkan::GetAvailableInstanceLayers() { 17 | uint32_t count = 0; 18 | CHECK_VKCMD(vkEnumerateInstanceLayerProperties(&count, nullptr)); 19 | std::vector available_layers(count); 20 | CHECK_VKCMD(vkEnumerateInstanceLayerProperties(&count, available_layers.data())); 21 | return available_layers; 22 | } 23 | 24 | std::vector vulkan::GetAvailableInstanceExtensions(std::string layer_name) { 25 | const char *p_layer_name = nullptr; 26 | if (!layer_name.empty()) { 27 | p_layer_name = layer_name.data(); 28 | } 29 | uint32_t count = 0; 30 | CHECK_VKCMD(vkEnumerateInstanceExtensionProperties(p_layer_name, &count, nullptr)); 31 | std::vector available_extensions(count); 32 | CHECK_VKCMD(vkEnumerateInstanceExtensionProperties(p_layer_name, 33 | &count, 34 | available_extensions.data())); 35 | return available_extensions; 36 | } 37 | 38 | VkFormat vulkan::GetVkFormat(DataType type, uint32_t count) { 39 | switch (type) { 40 | case DataType::BYTE: 41 | switch (count) { 42 | case 1:return VK_FORMAT_R8_UNORM; 43 | case 2:return VK_FORMAT_R8G8_UNORM; 44 | case 3:return VK_FORMAT_R8G8B8_UNORM; 45 | case 4:return VK_FORMAT_R8G8B8A8_UNORM; 46 | default:throw std::runtime_error("unsupported count"); 47 | } 48 | case DataType::UINT_16: 49 | switch (count) { 50 | case 1:return VK_FORMAT_R16_UINT; 51 | case 2:return VK_FORMAT_R16G16_UINT; 52 | case 3: return VK_FORMAT_R16G16B16_UINT; 53 | case 4: return VK_FORMAT_R16G16B16A16_UINT; 54 | default:throw std::runtime_error("unsupported count"); 55 | } 56 | case DataType::UINT_32: 57 | switch (count) { 58 | case 1:return VK_FORMAT_R32_UINT; 59 | case 2:return VK_FORMAT_R32G32_UINT; 60 | case 3: return VK_FORMAT_R32G32B32_UINT; 61 | case 4: return VK_FORMAT_R32G32B32A32_UINT; 62 | default:throw std::runtime_error("unsupported count"); 63 | } 64 | case DataType::FLOAT: 65 | switch (count) { 66 | case 1:return VK_FORMAT_R32_SFLOAT; 67 | case 2:return VK_FORMAT_R32G32_SFLOAT; 68 | case 3: return VK_FORMAT_R32G32B32_SFLOAT; 69 | case 4: return VK_FORMAT_R32G32B32A32_SFLOAT; 70 | default:throw std::runtime_error("unsupported count"); 71 | } 72 | default:throw std::runtime_error("unsupported enum"); 73 | } 74 | } 75 | 76 | VkIndexType vulkan::GetVkType(DataType type) { 77 | switch (type) { 78 | case DataType::UINT_16:return VK_INDEX_TYPE_UINT16; 79 | case DataType::UINT_32:return VK_INDEX_TYPE_UINT32; 80 | default:throw std::runtime_error("unsupported enum"); 81 | } 82 | } 83 | 84 | VkShaderStageFlagBits vulkan::GetVkShaderStageFlag(ShaderType shader_type) { 85 | switch (shader_type) { 86 | case ShaderType::VERTEX:return VK_SHADER_STAGE_VERTEX_BIT; 87 | case ShaderType::FRAGMENT:return VK_SHADER_STAGE_FRAGMENT_BIT; 88 | default: throw std::runtime_error("invalid shader type"); 89 | } 90 | } 91 | 92 | VkPrimitiveTopology vulkan::GetVkDrawMode(DrawMode draw_mode) { 93 | switch (draw_mode) { 94 | case DrawMode::POINT_LIST:return VK_PRIMITIVE_TOPOLOGY_POINT_LIST; 95 | case DrawMode::LINE_LIST: return VK_PRIMITIVE_TOPOLOGY_LINE_LIST; 96 | case DrawMode::LINE_STRIP: return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP; 97 | case DrawMode::TRIANGLE_LIST: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; 98 | case DrawMode::TRIANGLE_STRIP: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP; 99 | case DrawMode::TRIANGLE_FAN: return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN; 100 | default: throw std::runtime_error("unsupported draw mode"); 101 | } 102 | } 103 | 104 | VkCullModeFlags vulkan::GetVkCullMode(CullMode cull_mode) { 105 | switch (cull_mode) { 106 | case CullMode::NONE:return VK_CULL_MODE_NONE; 107 | case CullMode::FRONT: return VK_CULL_MODE_FRONT_BIT; 108 | case CullMode::BACK: return VK_CULL_MODE_BACK_BIT; 109 | case CullMode::FRONT_AND_BACK: return VK_CULL_MODE_FRONT_AND_BACK; 110 | default: throw std::runtime_error("unsupported cull mode"); 111 | } 112 | } 113 | VkFrontFace vulkan::GetVkFrontFace(FrontFace front_face) { 114 | switch (front_face) { 115 | case FrontFace::CW: return VK_FRONT_FACE_CLOCKWISE; 116 | case FrontFace::CCW: return VK_FRONT_FACE_COUNTER_CLOCKWISE; 117 | default: throw std::runtime_error("unsupported front face"); 118 | } 119 | } 120 | 121 | VkCompareOp vulkan::GetVkCompareOp(CompareOp compare_op) { 122 | switch (compare_op) { 123 | case CompareOp::NEVER: return VK_COMPARE_OP_NEVER; 124 | case CompareOp::LESS: return VK_COMPARE_OP_LESS; 125 | case CompareOp::EQUAL: return VK_COMPARE_OP_EQUAL; 126 | case CompareOp::LESS_OR_EQUAL: return VK_COMPARE_OP_LESS_OR_EQUAL; 127 | case CompareOp::GREATER: return VK_COMPARE_OP_GREATER; 128 | case CompareOp::NOT_EQUAL: return VK_COMPARE_OP_EQUAL; 129 | case CompareOp::GREATER_OR_EQUAL: return VK_COMPARE_OP_GREATER_OR_EQUAL; 130 | case CompareOp::ALWAYS: return VK_COMPARE_OP_ALWAYS; 131 | default: throw std::runtime_error("unsupported compare op");; 132 | } 133 | } 134 | 135 | VkBufferUsageFlags vulkan::GetVkBufferUsage(BufferUsage buffer_usage) { 136 | VkBufferUsageFlags vk_flag = 0; 137 | if (buffer_usage & BufferUsage::TRANSFER_SRC) { 138 | vk_flag |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT; 139 | } 140 | if (buffer_usage & BufferUsage::TRANSFER_DST) { 141 | vk_flag |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; 142 | } 143 | if (buffer_usage & BufferUsage::INDEX_BUFFER) { 144 | vk_flag |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT; 145 | } 146 | if (buffer_usage & BufferUsage::UNIFORM_BUFFER) { 147 | vk_flag |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; 148 | } 149 | if (buffer_usage & BufferUsage::VERTEX_BUFFER) { 150 | vk_flag |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; 151 | } 152 | return vk_flag; 153 | } 154 | 155 | VkMemoryPropertyFlags vulkan::GetVkMemoryType(MemoryType memory_property) { 156 | switch (memory_property) { 157 | case MemoryType::DEVICE_LOCAL:return VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; 158 | case MemoryType::HOST_VISIBLE: 159 | return VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; 160 | default:throw std::runtime_error("unknown memory type"); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /app/cpp/vulkan/vulkan_utils.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by artyomd on 10/8/20. 3 | // 4 | #pragma once 5 | 6 | #include 7 | #include 8 | 9 | #include "data_type.hpp" 10 | #include "redering_pipeline_config.hpp" 11 | 12 | #include 13 | 14 | #define CHECK_VKCMD(x) \ 15 | vulkan::CheckResult(x, __FILE__, __LINE__) 16 | 17 | namespace vulkan { 18 | 19 | void CheckResult(VkResult result, const std::string &file, uint32_t line); 20 | 21 | std::vector GetAvailableInstanceExtensions(std::string layer_name); 22 | 23 | std::vector GetAvailableInstanceLayers(); 24 | 25 | VkBufferUsageFlags GetVkBufferUsage(BufferUsage buffer_usage); 26 | 27 | VkMemoryPropertyFlags GetVkMemoryType(MemoryType memory_property); 28 | 29 | VkIndexType GetVkType(DataType type); 30 | 31 | VkFormat GetVkFormat(DataType type, uint32_t count); 32 | 33 | VkPrimitiveTopology GetVkDrawMode(DrawMode draw_mode); 34 | 35 | VkCullModeFlags GetVkCullMode(CullMode cull_mode); 36 | 37 | VkFrontFace GetVkFrontFace(FrontFace front_face); 38 | 39 | VkCompareOp GetVkCompareOp(CompareOp compare_op); 40 | 41 | VkShaderStageFlagBits GetVkShaderStageFlag(ShaderType shader_type); 42 | } 43 | -------------------------------------------------------------------------------- /app/cpp/vulkan_swapchain_context.cpp: -------------------------------------------------------------------------------- 1 | #include "vulkan_swapchain_context.hpp" 2 | 3 | VulkanSwapchainContext::VulkanSwapchainContext(std::shared_ptr 4 | vulkan_rendering_context, 5 | uint32_t capacity, 6 | const XrSwapchainCreateInfo &swapchain_create_info 7 | ) : 8 | rendering_context_(vulkan_rendering_context), 9 | swapchain_image_format_(static_cast(swapchain_create_info.format)), 10 | swapchain_extent_({swapchain_create_info.width, swapchain_create_info.height}) { 11 | swapchain_images_.resize(capacity); 12 | swapchain_image_views_.resize(capacity); 13 | swapchain_frame_buffers_.resize(capacity); 14 | 15 | viewport_ = { 16 | .x = 0.0F, 17 | .y = static_cast(swapchain_extent_.height), 18 | .width = static_cast(swapchain_extent_.width), 19 | .height = -static_cast(swapchain_extent_.height), 20 | .minDepth = 0.0, 21 | .maxDepth = 1.0, 22 | }; 23 | scissor_.extent = { 24 | .width = static_cast(swapchain_extent_.width), 25 | .height = static_cast(swapchain_extent_.height), 26 | }; 27 | 28 | for (auto &image: swapchain_images_) { 29 | image.type = XR_TYPE_SWAPCHAIN_IMAGE_VULKAN_KHR; 30 | } 31 | } 32 | 33 | XrSwapchainImageBaseHeader *VulkanSwapchainContext::GetFirstImagePointer() { 34 | return reinterpret_cast(&swapchain_images_[0]); 35 | } 36 | 37 | void VulkanSwapchainContext::InitSwapchainImageViews() { 38 | if (inited_) { 39 | throw std::runtime_error("cannot double init"); 40 | } 41 | for (size_t i = 0; i < swapchain_images_.size(); i++) { 42 | rendering_context_->CreateImageView( 43 | swapchain_images_[i].image, 44 | swapchain_image_format_, 45 | VK_IMAGE_ASPECT_COLOR_BIT, 46 | &swapchain_image_views_[i]); 47 | } 48 | CreateColorResources(); 49 | CreateDepthResources(); 50 | CreateFrameBuffers(); 51 | CreateCommandBuffers(); 52 | CreateSyncObjects(); 53 | 54 | inited_ = true; 55 | } 56 | 57 | void VulkanSwapchainContext::Draw(uint32_t image_index, 58 | std::shared_ptr pipeline, 59 | uint32_t index_count, 60 | std::vector transforms) { 61 | if (images_in_flight_[current_fame_] != VK_NULL_HANDLE) { 62 | vkWaitForFences(rendering_context_->GetDevice(), 63 | 1, 64 | &images_in_flight_[current_fame_], 65 | VK_TRUE, 66 | UINT64_MAX); 67 | } 68 | images_in_flight_[current_fame_] = in_flight_fences_[current_fame_]; 69 | 70 | VkCommandBufferBeginInfo begin_info = {}; 71 | begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; 72 | begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; 73 | vkBeginCommandBuffer(graphics_command_buffers_[current_fame_], &begin_info); 74 | 75 | VkRenderPassBeginInfo render_pass_info = {}; 76 | render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; 77 | render_pass_info.renderPass = rendering_context_->GetRenderPass(); 78 | render_pass_info.framebuffer = swapchain_frame_buffers_[image_index]; 79 | render_pass_info.renderArea.offset = {0, 0}; 80 | render_pass_info.renderArea.extent = swapchain_extent_; 81 | 82 | std::array clear_values = {}; 83 | clear_values[0].color = {{0.184313729f, 0.309803933f, 0.309803933f, 1.0f}}; 84 | clear_values[1].depthStencil = {1.0f, 0}; 85 | render_pass_info.clearValueCount = static_cast(clear_values.size()); 86 | render_pass_info.pClearValues = clear_values.data(); 87 | vkCmdBeginRenderPass(graphics_command_buffers_[current_fame_], 88 | &render_pass_info, 89 | VK_SUBPASS_CONTENTS_INLINE); 90 | ////render 91 | pipeline->BindPipeline(graphics_command_buffers_[current_fame_]); 92 | vkCmdSetViewport(graphics_command_buffers_[current_fame_], 0, 1, &viewport_); 93 | vkCmdSetScissor(graphics_command_buffers_[current_fame_], 0, 1, &scissor_); 94 | for (const auto &transform: transforms) { 95 | vkCmdPushConstants(graphics_command_buffers_[current_fame_], 96 | pipeline->GetPipelineLayout(), 97 | VK_SHADER_STAGE_VERTEX_BIT, 98 | 0, 99 | sizeof(transform), 100 | &transform); 101 | vkCmdDrawIndexed(graphics_command_buffers_[current_fame_], 102 | static_cast(index_count), 103 | 1, 104 | 0, 105 | 0, 106 | 0); 107 | } 108 | ////render 109 | vkCmdEndRenderPass(graphics_command_buffers_[current_fame_]); 110 | vkEndCommandBuffer(graphics_command_buffers_[current_fame_]); 111 | 112 | VkPipelineStageFlags wait_stages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; 113 | VkSubmitInfo submit_info = {}; 114 | submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; 115 | submit_info.waitSemaphoreCount = 0; 116 | submit_info.pWaitDstStageMask = wait_stages; 117 | submit_info.commandBufferCount = 1; 118 | submit_info.pCommandBuffers = &graphics_command_buffers_[current_fame_]; 119 | submit_info.signalSemaphoreCount = 0; 120 | 121 | vkResetFences(rendering_context_->GetDevice(), 1, &in_flight_fences_[current_fame_]); 122 | if (vkQueueSubmit(rendering_context_->GetGraphicsQueue(), 123 | 1, 124 | &submit_info, 125 | in_flight_fences_[current_fame_]) 126 | != VK_SUCCESS) { 127 | throw std::runtime_error("failed to submit draw command buffer!"); 128 | } 129 | current_fame_ = (current_fame_ + 1) % max_frames_in_flight_; 130 | } 131 | 132 | [[nodiscard]] bool VulkanSwapchainContext::IsInited() const { 133 | return inited_; 134 | } 135 | 136 | VulkanSwapchainContext::~VulkanSwapchainContext() { 137 | for (const auto &fence: in_flight_fences_) { 138 | vkDestroyFence(rendering_context_->GetDevice(), fence, nullptr); 139 | } 140 | vkFreeCommandBuffers(rendering_context_->GetDevice(), 141 | rendering_context_->GetGraphicsPool(), 142 | graphics_command_buffers_.size(), 143 | &graphics_command_buffers_[0]); 144 | for (const auto &framebuffer: swapchain_frame_buffers_) { 145 | vkDestroyFramebuffer(rendering_context_->GetDevice(), framebuffer, nullptr); 146 | } 147 | if (depth_image_view_ != VK_NULL_HANDLE) { 148 | vkDestroyImageView(rendering_context_->GetDevice(), depth_image_view_, nullptr); 149 | vkDestroyImage(rendering_context_->GetDevice(), depth_image_, nullptr); 150 | vkFreeMemory(rendering_context_->GetDevice(), depth_image_memory_, nullptr); 151 | } 152 | if (color_image_view_ != VK_NULL_HANDLE) { 153 | vkDestroyImageView(rendering_context_->GetDevice(), color_image_view_, nullptr); 154 | vkDestroyImage(rendering_context_->GetDevice(), color_image_, nullptr); 155 | vkFreeMemory(rendering_context_->GetDevice(), color_image_memory_, nullptr); 156 | } 157 | for (auto image_view: swapchain_image_views_) { 158 | vkDestroyImageView(rendering_context_->GetDevice(), image_view, nullptr); 159 | } 160 | } 161 | 162 | void VulkanSwapchainContext::CreateColorResources() { 163 | rendering_context_->CreateImage(swapchain_extent_.width, 164 | swapchain_extent_.height, 165 | rendering_context_->GetRecommendedMsaaSamples(), 166 | swapchain_image_format_, 167 | VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | 168 | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, 169 | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 170 | &color_image_, 171 | &color_image_memory_); 172 | rendering_context_->CreateImageView(color_image_, 173 | swapchain_image_format_, 174 | VK_IMAGE_ASPECT_COLOR_BIT, 175 | &color_image_view_); 176 | rendering_context_->TransitionImageLayout(color_image_, 177 | VK_IMAGE_LAYOUT_UNDEFINED, 178 | VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); 179 | } 180 | 181 | void VulkanSwapchainContext::CreateDepthResources() { 182 | VkFormat depth_format = rendering_context_->GetDepthAttachmentFormat(); 183 | rendering_context_->CreateImage(swapchain_extent_.width, swapchain_extent_.height, 184 | rendering_context_->GetRecommendedMsaaSamples(), 185 | depth_format, 186 | VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, 187 | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, 188 | &depth_image_, 189 | &depth_image_memory_); 190 | rendering_context_->CreateImageView(depth_image_, 191 | depth_format, 192 | VK_IMAGE_ASPECT_DEPTH_BIT, 193 | &depth_image_view_); 194 | rendering_context_->TransitionImageLayout(depth_image_, 195 | VK_IMAGE_LAYOUT_UNDEFINED, 196 | VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); 197 | } 198 | 199 | void VulkanSwapchainContext::CreateFrameBuffers() { 200 | for (size_t i = 0; i < swapchain_image_views_.size(); i++) { 201 | std::array attachments = { 202 | color_image_view_, 203 | depth_image_view_, 204 | swapchain_image_views_[i] 205 | }; 206 | 207 | VkFramebufferCreateInfo framebuffer_info = {}; 208 | framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; 209 | framebuffer_info.renderPass = rendering_context_->GetRenderPass(); 210 | framebuffer_info.attachmentCount = static_cast(attachments.size()); 211 | framebuffer_info.pAttachments = attachments.data(); 212 | framebuffer_info.width = swapchain_extent_.width; 213 | framebuffer_info.height = swapchain_extent_.height; 214 | framebuffer_info.layers = 1; 215 | CHECK_VKCMD(vkCreateFramebuffer(rendering_context_->GetDevice(), 216 | &framebuffer_info, 217 | nullptr, 218 | &swapchain_frame_buffers_[i])); 219 | } 220 | } 221 | 222 | void VulkanSwapchainContext::CreateCommandBuffers() { 223 | graphics_command_buffers_.resize(max_frames_in_flight_); 224 | VkCommandBufferAllocateInfo alloc_info = {}; 225 | alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; 226 | alloc_info.commandPool = rendering_context_->GetGraphicsPool(); 227 | alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; 228 | alloc_info.commandBufferCount = static_cast(graphics_command_buffers_.size()); 229 | CHECK_VKCMD(vkAllocateCommandBuffers(rendering_context_->GetDevice(), 230 | &alloc_info, 231 | graphics_command_buffers_.data())); 232 | } 233 | 234 | void VulkanSwapchainContext::CreateSyncObjects() { 235 | in_flight_fences_.resize(max_frames_in_flight_); 236 | images_in_flight_.resize(swapchain_images_.size(), VK_NULL_HANDLE); 237 | VkFenceCreateInfo fence_info = { 238 | .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, 239 | .flags = VK_FENCE_CREATE_SIGNALED_BIT, 240 | }; 241 | for (size_t i = 0; i < max_frames_in_flight_; i++) { 242 | if (vkCreateFence(rendering_context_->GetDevice(), 243 | &fence_info, 244 | nullptr, 245 | &in_flight_fences_[i]) != VK_SUCCESS) { 246 | throw std::runtime_error("failed to create sync objects for a frame!"); 247 | } 248 | } 249 | } -------------------------------------------------------------------------------- /app/cpp/vulkan_swapchain_context.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "openxr-include.hpp" 4 | #include 5 | 6 | #include "vulkan/vulkan_rendering_context.hpp" 7 | #include "vulkan/vulkan_rendering_pipeline.hpp" 8 | #include "vulkan/vulkan_utils.hpp" 9 | 10 | class VulkanSwapchainContext { 11 | private: 12 | std::shared_ptr rendering_context_; 13 | VkFormat swapchain_image_format_; 14 | VkExtent2D swapchain_extent_; 15 | std::vector swapchain_images_{}; 16 | std::vector swapchain_image_views_{}; 17 | 18 | std::vector swapchain_frame_buffers_{}; 19 | 20 | VkImage color_image_ = VK_NULL_HANDLE; 21 | VkDeviceMemory color_image_memory_ = VK_NULL_HANDLE; 22 | VkImageView color_image_view_ = VK_NULL_HANDLE; 23 | 24 | VkImage depth_image_ = VK_NULL_HANDLE; 25 | VkDeviceMemory depth_image_memory_ = VK_NULL_HANDLE; 26 | VkImageView depth_image_view_ = VK_NULL_HANDLE; 27 | 28 | std::vector graphics_command_buffers_{}; 29 | const uint32_t max_frames_in_flight_ = 2; 30 | 31 | std::vector in_flight_fences_; 32 | std::vector images_in_flight_; 33 | 34 | bool inited_ = false; 35 | 36 | uint32_t current_fame_ = 0; 37 | 38 | VkViewport viewport_ = {0, 0, 0, 0, 0, 1.0}; 39 | VkRect2D scissor_ = {{0, 0}, {0, 0}}; 40 | 41 | void CreateColorResources(); 42 | void CreateDepthResources(); 43 | void CreateFrameBuffers(); 44 | void CreateCommandBuffers(); 45 | void CreateSyncObjects(); 46 | public: 47 | VulkanSwapchainContext() = delete; 48 | VulkanSwapchainContext(std::shared_ptr vulkan_rendering_context, 49 | uint32_t capacity, 50 | const XrSwapchainCreateInfo &swapchain_create_info); 51 | 52 | XrSwapchainImageBaseHeader *GetFirstImagePointer(); 53 | 54 | void InitSwapchainImageViews(); 55 | 56 | void Draw(uint32_t image_index, 57 | std::shared_ptr pipeline, 58 | uint32_t index_count, 59 | std::vector transforms); 60 | 61 | [[nodiscard]] bool IsInited() const; 62 | 63 | virtual ~VulkanSwapchainContext(); 64 | }; -------------------------------------------------------------------------------- /app/libs/debug/arm64-v8a/libVkLayer_khronos_validation.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artyomd/Quest-XR/86313ef219f585769c8ba20b8789111d37f618bb/app/libs/debug/arm64-v8a/libVkLayer_khronos_validation.so -------------------------------------------------------------------------------- /app/libs/debug/arm64-v8a/libopenxr_loader.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artyomd/Quest-XR/86313ef219f585769c8ba20b8789111d37f618bb/app/libs/debug/arm64-v8a/libopenxr_loader.so -------------------------------------------------------------------------------- /app/libs/release/arm64-v8a/libopenxr_loader.so: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artyomd/Quest-XR/86313ef219f585769c8ba20b8789111d37f618bb/app/libs/release/arm64-v8a/libopenxr_loader.so -------------------------------------------------------------------------------- /app/meta_quest_openxr_loader/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(meta_quest_openxr_loader SHARED IMPORTED GLOBAL) 2 | if (${CMAKE_BUILD_TYPE} STREQUAL Debug) 3 | set_target_properties(meta_quest_openxr_loader PROPERTIES IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/libs/debug/arm64-v8a/libopenxr_loader.so") 4 | elseif (${CMAKE_BUILD_TYPE} STREQUAL RelWithDebInfo) #android always generates debug symbols and the gradle plugin does the stripping part 5 | set_target_properties(meta_quest_openxr_loader PROPERTIES IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/libs/release/arm64-v8a/libopenxr_loader.so") 6 | else () 7 | message(FATAL_ERROR "unknown build type") 8 | endif () 9 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") version "8.3.2" apply (false) 3 | } 4 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/artyomd/Quest-XR/86313ef219f585769c8ba20b8789111d37f618bb/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | include ':app' 16 | rootProject.name = "Quest XR" 17 | --------------------------------------------------------------------------------