├── .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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
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 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 | xmlns:android
88 |
89 | ^$
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 | xmlns:.*
99 |
100 | ^$
101 |
102 |
103 | BY_NAME
104 |
105 |
106 |
107 |
108 |
109 |
110 | .*:id
111 |
112 | http://schemas.android.com/apk/res/android
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 | .*:name
122 |
123 | http://schemas.android.com/apk/res/android
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 | name
133 |
134 | ^$
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 | style
144 |
145 | ^$
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 | .*
155 |
156 | ^$
157 |
158 |
159 | BY_NAME
160 |
161 |
162 |
163 |
164 |
165 |
166 | .*
167 |
168 | http://schemas.android.com/apk/res/android
169 |
170 |
171 | ANDROID_ATTRIBUTE_ORDER
172 |
173 |
174 |
175 |
176 |
177 |
178 | .*
179 |
180 | .*
181 |
182 |
183 | BY_NAME
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
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 | 
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 |
--------------------------------------------------------------------------------