├── .clang-format ├── .gitignore ├── .gitmodules ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── app ├── CMakeLists.txt ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── assets │ ├── sample_tex.png │ ├── texture.frag │ ├── texture.frag.spv │ ├── texture.vert │ └── texture.vert.spv │ ├── cpp │ ├── Engine.cpp │ ├── Engine.h │ ├── Renderer.cpp │ ├── Renderer.h │ ├── Utils.h │ ├── VkHelper.cpp │ ├── VkHelper.h │ └── main.cpp │ └── res │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ └── values │ └── strings.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── third_party └── CMakeLists.txt /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | 3 | AccessModifierOffset: -4 4 | AlignOperands: false 5 | AllowShortFunctionsOnASingleLine: Inline 6 | AlwaysBreakBeforeMultilineStrings: false 7 | ColumnLimit: 100 8 | CommentPragmas: NOLINT:.* 9 | ConstructorInitializerIndentWidth: 6 10 | ContinuationIndentWidth: 8 11 | IndentWidth: 4 12 | PenaltyBreakBeforeFirstCallParameter: 100000 13 | SpacesBeforeTrailingComments: 1 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | .cxx 5 | .externalNativeBuild 6 | .DS_Store 7 | /build 8 | /app/build 9 | /local.properties 10 | /captures 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "third_party/glm"] 2 | path = third_party/glm 3 | url = https://github.com/g-truc/glm 4 | [submodule "third_party/stb"] 5 | path = third_party/stb 6 | url = https://github.com/nothings/stb 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google/conduct/). 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vulkan Pre-rotation Demo 2 | 3 | ## After git clone 4 | 5 | 1. git submodule init 6 | 2. git submodule update 7 | 8 | ## What's covered? 9 | 10 | 1. Detect all surface rotations in Android 10+(easier if landscape only without resizing). 11 | 2. Handle swapchain recreation. 12 | 3. Fix the shaders in clipping space with a simple 2x2 matrix. 13 | 4. NativityActivity, AChoreographer, etc. 14 | 15 | ## What's not covered? 16 | 17 | 1. Detect surface rotation in Android Pie and below, which can be fixed by either calling vkGetPhysicalDeviceSurfaceCapabilitiesKHR or the jni Display.getRotation() once a while. 18 | 2. Fix advanced shader features like dfdx and dfdy, which can be fixed by mapping the intended derivative to +-dfdx or +-dfdy according to preTransform pushed to the shader. 19 | 3. Other miscellaneous. 20 | 21 | -------------------------------------------------------------------------------- /app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.1) 2 | 3 | 4 | add_library(vkdemo SHARED 5 | src/main/cpp/main.cpp 6 | src/main/cpp/Engine.cpp 7 | src/main/cpp/Renderer.cpp 8 | src/main/cpp/VkHelper.cpp) 9 | 10 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") 11 | 12 | add_library(native_app_glue STATIC 13 | ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) 14 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") 15 | target_include_directories(vkdemo PRIVATE ${ANDROID_NDK}/sources/android/native_app_glue) 16 | 17 | add_subdirectory(../third_party third_party) 18 | 19 | add_definitions("-DVK_USE_PLATFORM_ANDROID_KHR") 20 | 21 | target_link_libraries(vkdemo android native_app_glue vulkan glm stb log) 22 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 29 5 | defaultConfig { 6 | applicationId "com.google.vkdemo" 7 | minSdkVersion 29 8 | targetSdkVersion 29 9 | externalNativeBuild { 10 | cmake { 11 | arguments '-DANDROID_STL=c++_static' 12 | } 13 | } 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | externalNativeBuild { 22 | cmake { 23 | path "CMakeLists.txt" 24 | } 25 | } 26 | } 27 | 28 | dependencies { 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 14 | 15 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/assets/sample_tex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/vulkan-pre-rotation-demo/b3f836dee9d9925820e7631dc9f4b3fe77cfa56b/app/src/main/assets/sample_tex.png -------------------------------------------------------------------------------- /app/src/main/assets/texture.frag: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #version 450 18 | 19 | layout (binding = 0) uniform sampler2D tex; 20 | layout (location = 0) in vec2 inTexPos; 21 | layout (location = 0) out vec4 outFragColor; 22 | 23 | void main() { 24 | outFragColor = texture(tex, inTexPos); 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/assets/texture.frag.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/vulkan-pre-rotation-demo/b3f836dee9d9925820e7631dc9f4b3fe77cfa56b/app/src/main/assets/texture.frag.spv -------------------------------------------------------------------------------- /app/src/main/assets/texture.vert: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #version 450 18 | 19 | layout (push_constant) uniform PushConstants { 20 | mat4 mvp; 21 | mat2 preRotate; 22 | } pushConstants; 23 | layout (location = 0) in vec2 inVertPos; 24 | layout (location = 1) in vec2 inTexPos; 25 | layout (location = 0) out vec2 outTexPos; 26 | 27 | void main() { 28 | outTexPos = inTexPos; 29 | vec4 clip = pushConstants.mvp * vec4(inVertPos, 0.0, 1.0); 30 | gl_Position = vec4(pushConstants.preRotate * vec2(clip.x, clip.y), clip.z, clip.w); 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/assets/texture.vert.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/vulkan-pre-rotation-demo/b3f836dee9d9925820e7631dc9f4b3fe77cfa56b/app/src/main/assets/texture.vert.spv -------------------------------------------------------------------------------- /app/src/main/cpp/Engine.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "Engine.h" 18 | 19 | #include "Utils.h" 20 | 21 | bool Engine::isReady() { 22 | std::lock_guard lock(mLock); 23 | return mIsRendererReady; 24 | } 25 | 26 | void Engine::drawFrame() { 27 | std::lock_guard lock(mLock); 28 | if (mIsRendererReady) { 29 | mRenderer.drawFrame(); 30 | } 31 | } 32 | 33 | void Engine::onInitWindow(ANativeWindow* window, AAssetManager* assetManager) { 34 | ALOGD("%s", __FUNCTION__); 35 | std::lock_guard lock(mLock); 36 | mRenderer.initialize(window, assetManager); 37 | mIsRendererReady = true; 38 | } 39 | 40 | void Engine::onWindowResized(uint32_t width, uint32_t height) { 41 | ALOGD("%s", __FUNCTION__); 42 | std::lock_guard lock(mLock); 43 | if (mIsRendererReady) { 44 | mRenderer.updateSurface(width, height); 45 | } 46 | } 47 | 48 | void Engine::onTermWindow() { 49 | ALOGD("%s", __FUNCTION__); 50 | std::lock_guard lock(mLock); 51 | if (mIsRendererReady) { 52 | mRenderer.destroy(); 53 | mIsRendererReady = false; 54 | } 55 | } 56 | 57 | uint32_t Engine::getDelayMillis(int64_t /*frameTimeNanos*/) { 58 | // we can play around with frameTimeNanos to add more dynamic callback delay control 59 | return kDelayMillis; 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/cpp/Engine.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | #include 22 | 23 | #include "Renderer.h" 24 | 25 | class Engine { 26 | public: 27 | explicit Engine() : mIsRendererReady(false) {} 28 | bool isReady(); 29 | void drawFrame(); 30 | void onInitWindow(ANativeWindow* window, AAssetManager* assetManager); 31 | void onWindowResized(uint32_t width, uint32_t height); 32 | void onTermWindow(); 33 | uint32_t getDelayMillis(int64_t frameTimeNanos); 34 | 35 | private: 36 | // mLock protects all members below 37 | std::mutex mLock; 38 | Renderer mRenderer; 39 | bool mIsRendererReady; 40 | 41 | // defer 13ms to target 60Hz on a 60Hz display or 45Hz on a 90Hz display 42 | static constexpr const uint32_t kDelayMillis = 13; 43 | }; 44 | -------------------------------------------------------------------------------- /app/src/main/cpp/Renderer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "Renderer.h" 18 | 19 | #include 20 | #include 21 | 22 | #define STB_IMAGE_IMPLEMENTATION 23 | #include 24 | 25 | #include 26 | 27 | #include "Utils.h" 28 | 29 | struct PushConstantBlock { 30 | glm::mat4 mvp; 31 | glm::mat2 preRotate; 32 | }; 33 | 34 | /* Public APIs start here */ 35 | void Renderer::initialize(ANativeWindow* window, AAssetManager* assetManager) { 36 | ASSERT(assetManager); 37 | mAssetManager = assetManager; 38 | 39 | createInstance(); 40 | createDevice(); 41 | createSurface(window); 42 | createSwapchain(VK_NULL_HANDLE); 43 | createTextures(); 44 | createDescriptorSet(); 45 | createRenderPass(); 46 | createGraphicsPipeline(); 47 | createVertexBuffer(); 48 | createCommandBuffers(); 49 | createSemaphores(); 50 | createFences(); 51 | } 52 | 53 | void Renderer::drawFrame() { 54 | // mInflightFences are created in the signaled state, so we can wait here from the beginning. 55 | const uint32_t frameIndex = mFrameCount % kInflight; 56 | ASSERT(mVk.WaitForFences(mDevice, 1, &mInflightFences[frameIndex], VK_TRUE, kTimeout30Sec) == 57 | VK_SUCCESS); 58 | 59 | // Need to reset fences to unsignaled state for vkQueueSubmit 60 | ASSERT(mVk.ResetFences(mDevice, 1, &mInflightFences[frameIndex]) == VK_SUCCESS); 61 | 62 | uint32_t imageIndex; 63 | ASSERT(mVk.AcquireNextImageKHR(mDevice, mSwapchain, UINT64_MAX, mAcquireSemaphores[frameIndex], 64 | VK_NULL_HANDLE, &imageIndex) == VK_SUCCESS); 65 | 66 | // Lazy allocate VkImageView and VkFramebuffer only when needed, and reuse later 67 | if (mFramebuffers[imageIndex] == VK_NULL_HANDLE) { 68 | createFramebuffer(imageIndex); 69 | } 70 | 71 | recordCommandBuffer(frameIndex, imageIndex); 72 | 73 | const VkPipelineStageFlags waitStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; 74 | const VkSubmitInfo submitInfo = { 75 | .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, 76 | .pNext = nullptr, 77 | .waitSemaphoreCount = 1, 78 | .pWaitSemaphores = &mAcquireSemaphores[frameIndex], 79 | .pWaitDstStageMask = &waitStageMask, 80 | .commandBufferCount = 1, 81 | .pCommandBuffers = &mCommandBuffers[frameIndex], 82 | .signalSemaphoreCount = 1, 83 | .pSignalSemaphores = &mRenderSemaphores[frameIndex], 84 | }; 85 | ASSERT(mVk.QueueSubmit(mQueue, 1, &submitInfo, mInflightFences[frameIndex]) == VK_SUCCESS); 86 | 87 | const VkPresentInfoKHR presentInfo = { 88 | .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, 89 | .pNext = nullptr, 90 | .waitSemaphoreCount = 1, 91 | .pWaitSemaphores = &mRenderSemaphores[frameIndex], 92 | .swapchainCount = 1, 93 | .pSwapchains = &mSwapchain, 94 | .pImageIndices = &imageIndex, 95 | .pResults = nullptr, 96 | }; 97 | VkResult ret = mVk.QueuePresentKHR(mQueue, &presentInfo); 98 | 99 | // If there's old swapchain to be destroyed, check if we have reached the retire frame count 100 | if (mOldSwapchain != VK_NULL_HANDLE && mRetireFrame == mFrameCount) { 101 | destroyOldSwapchain(); 102 | } 103 | 104 | // VK_SUBOPTIMAL_KHR shouldn't occur again within kInflight frames in the real world. If that 105 | // happens, will switch to an array later to save the old swapchain stuff 106 | if (ret == VK_SUBOPTIMAL_KHR || mFireRecreateSwapchain) { 107 | // mFireRecreateSwapchain usually comes 3 to 4 frames later after 90 degree rotation, but we 108 | // set the countdown latency to 30 to play safe 109 | if (is180Rotation() || mFireRecreateSwapchain || !(--mPreRotationLatency)) { 110 | mPreRotationLatency = kPreRotationLatency; 111 | mFireRecreateSwapchain = false; 112 | ALOGD("%s[%u][%d] - recreate swapchain", __FUNCTION__, mFrameCount, ret); 113 | std::swap(mSwapchain, mOldSwapchain); 114 | std::swap(mImages, mOldImages); 115 | std::swap(mImageViews, mOldImageViews); 116 | std::swap(mFramebuffers, mOldFramebuffers); 117 | 118 | mRetireFrame = mFrameCount + kInflight; 119 | 120 | // Recreate the new swapchain with the latest preTransform. Numbers of swapchain images, 121 | // image views and framebuffers are also allowed to change. Even the aspect ratio of the 122 | // swapchain can change, which requires us to use dynamic viewport and scissor 123 | createSwapchain(mOldSwapchain); 124 | } 125 | } else { 126 | ASSERT(ret == VK_SUCCESS); 127 | } 128 | 129 | // Increase the frame count here and log at a frame interval 130 | if (++mFrameCount % kLogInterval == 0) { 131 | ALOGD("%s[%u][%d]", __FUNCTION__, mFrameCount, ret); 132 | } 133 | } 134 | 135 | void Renderer::updateSurface(uint32_t width, uint32_t height) { 136 | if (mSurfaceWidth != width || mSurfaceHeight != height) { 137 | mFireRecreateSwapchain = true; 138 | } 139 | } 140 | 141 | void Renderer::destroy() { 142 | if (mDevice != VK_NULL_HANDLE) { 143 | mVk.DeviceWaitIdle(mDevice); 144 | 145 | // Destroy sync objects 146 | for (auto& fence : mInflightFences) { 147 | mVk.DestroyFence(mDevice, fence, nullptr); 148 | } 149 | mInflightFences.clear(); 150 | for (auto& semaphore : mAcquireSemaphores) { 151 | mVk.DestroySemaphore(mDevice, semaphore, nullptr); 152 | } 153 | mAcquireSemaphores.clear(); 154 | for (auto& semaphore : mRenderSemaphores) { 155 | mVk.DestroySemaphore(mDevice, semaphore, nullptr); 156 | } 157 | mRenderSemaphores.clear(); 158 | 159 | // Destroy command buffers 160 | if (!mCommandBuffers.empty()) { 161 | mVk.FreeCommandBuffers(mDevice, mCommandPool, mCommandBuffers.size(), 162 | mCommandBuffers.data()); 163 | } 164 | mCommandBuffers.clear(); 165 | mVk.DestroyCommandPool(mDevice, mCommandPool, nullptr); 166 | mCommandPool = VK_NULL_HANDLE; 167 | 168 | // Destroy vertex buffer 169 | mVk.DestroyBuffer(mDevice, mVertexBuffer, nullptr); 170 | mVertexBuffer = VK_NULL_HANDLE; 171 | mVk.FreeMemory(mDevice, mVertexMemory, nullptr); 172 | mVertexMemory = VK_NULL_HANDLE; 173 | 174 | // Destroy graphics pipeline 175 | mVk.DestroyPipeline(mDevice, mPipeline, nullptr); 176 | mPipeline = VK_NULL_HANDLE; 177 | mVk.DestroyPipelineLayout(mDevice, mPipelineLayout, nullptr); 178 | mPipelineLayout = VK_NULL_HANDLE; 179 | 180 | // Destroy render pass 181 | mVk.DestroyRenderPass(mDevice, mRenderPass, nullptr); 182 | mRenderPass = VK_NULL_HANDLE; 183 | 184 | // Destroy descriptor sets 185 | mVk.DestroyDescriptorPool(mDevice, mDescriptorPool, nullptr); 186 | mVk.DestroyDescriptorSetLayout(mDevice, mDescriptorSetLayout, nullptr); 187 | 188 | // Destroy textures 189 | for (auto& texture : mTextures) { 190 | mVk.DestroyImageView(mDevice, texture.view, nullptr); 191 | mVk.DestroySampler(mDevice, texture.sampler, nullptr); 192 | mVk.DestroyImage(mDevice, texture.image, nullptr); 193 | mVk.FreeMemory(mDevice, texture.memory, nullptr); 194 | } 195 | mTextures.clear(); 196 | 197 | // Destroy old swapchain 198 | destroyOldSwapchain(); 199 | 200 | // Destroy current swapchain 201 | for (auto& imageView : mImageViews) { 202 | mVk.DestroyImageView(mDevice, imageView, nullptr); 203 | } 204 | mImageViews.clear(); 205 | for (auto& framebuffer : mFramebuffers) { 206 | mVk.DestroyFramebuffer(mDevice, framebuffer, nullptr); 207 | } 208 | mFramebuffers.clear(); 209 | mImages.clear(); 210 | mVk.DestroySwapchainKHR(mDevice, mSwapchain, nullptr); 211 | 212 | // Destroy device 213 | mVk.DestroyDevice(mDevice, nullptr); 214 | mDevice = VK_NULL_HANDLE; 215 | } 216 | 217 | if (mInstance) { 218 | // Destroy surface 219 | mVk.DestroySurfaceKHR(mInstance, mSurface, nullptr); 220 | mSurface = VK_NULL_HANDLE; 221 | 222 | // Destroy instance 223 | mVk.DestroyInstance(mInstance, nullptr); 224 | mInstance = VK_NULL_HANDLE; 225 | } 226 | 227 | ALOGD("Successfully destroyed Vulkan renderer"); 228 | } 229 | 230 | /* Private APIs start here */ 231 | static bool hasExtension(const char* extension_name, 232 | const std::vector& extensions) { 233 | return std::find_if(extensions.cbegin(), extensions.cend(), 234 | [extension_name](const VkExtensionProperties& extension) { 235 | return strcmp(extension.extensionName, extension_name) == 0; 236 | }) != extensions.cend(); 237 | } 238 | 239 | void Renderer::createInstance() { 240 | mVk.initializeGlobalApi(); 241 | 242 | uint32_t instanceVersion = 0; 243 | ASSERT(mVk.EnumerateInstanceVersion(&instanceVersion) == VK_SUCCESS); 244 | ASSERT(instanceVersion >= VK_MAKE_VERSION(1, 1, 0)); 245 | 246 | uint32_t extensionCount = 0; 247 | ASSERT(mVk.EnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr) == 248 | VK_SUCCESS); 249 | std::vector supportedInstanceExtensions(extensionCount); 250 | ASSERT(mVk.EnumerateInstanceExtensionProperties(nullptr, &extensionCount, 251 | supportedInstanceExtensions.data()) == 252 | VK_SUCCESS); 253 | 254 | std::vector enabledInstanceExtensions; 255 | for (const auto extension : kRequiredInstanceExtensions) { 256 | ASSERT(hasExtension(extension, supportedInstanceExtensions)); 257 | enabledInstanceExtensions.push_back(extension); 258 | } 259 | 260 | const VkApplicationInfo applicationInfo = { 261 | .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, 262 | .pNext = nullptr, 263 | .pApplicationName = LOG_TAG, 264 | .applicationVersion = 0, 265 | .pEngineName = nullptr, 266 | .engineVersion = 0, 267 | .apiVersion = VK_MAKE_VERSION(1, 1, 0), 268 | }; 269 | const VkInstanceCreateInfo instanceInfo = { 270 | .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, 271 | .pNext = nullptr, 272 | .flags = 0, 273 | .pApplicationInfo = &applicationInfo, 274 | .enabledLayerCount = 0, 275 | .ppEnabledLayerNames = nullptr, 276 | .enabledExtensionCount = static_cast(enabledInstanceExtensions.size()), 277 | .ppEnabledExtensionNames = enabledInstanceExtensions.data(), 278 | }; 279 | 280 | ASSERT(mVk.CreateInstance(&instanceInfo, nullptr, &mInstance) == VK_SUCCESS); 281 | mVk.initializeInstanceApi(mInstance); 282 | 283 | ALOGD("Successfully created instance"); 284 | } 285 | 286 | void Renderer::createDevice() { 287 | uint32_t gpuCount = 0; 288 | ASSERT(mVk.EnumeratePhysicalDevices(mInstance, &gpuCount, nullptr) == VK_SUCCESS); 289 | ASSERT(gpuCount); 290 | ALOGD("gpuCount = %u", gpuCount); 291 | std::vector gpus(gpuCount, VK_NULL_HANDLE); 292 | ASSERT(mVk.EnumeratePhysicalDevices(mInstance, &gpuCount, gpus.data()) == VK_SUCCESS); 293 | mGpu = gpus[0]; 294 | 295 | uint32_t extensionCount = 0; 296 | ASSERT(mVk.EnumerateDeviceExtensionProperties(mGpu, nullptr, &extensionCount, nullptr) == 297 | VK_SUCCESS); 298 | std::vector supportedDeviceExtensions(extensionCount); 299 | ASSERT(mVk.EnumerateDeviceExtensionProperties(mGpu, nullptr, &extensionCount, 300 | supportedDeviceExtensions.data()) == VK_SUCCESS); 301 | 302 | std::vector enabledDeviceExtensions; 303 | for (const auto extension : kRequiredDeviceExtensions) { 304 | ASSERT(hasExtension(extension, supportedDeviceExtensions)); 305 | enabledDeviceExtensions.push_back(extension); 306 | } 307 | 308 | uint32_t queueFamilyCount = 0; 309 | mVk.GetPhysicalDeviceQueueFamilyProperties(mGpu, &queueFamilyCount, nullptr); 310 | ASSERT(queueFamilyCount); 311 | ALOGD("queueFamilyCount = %u", queueFamilyCount); 312 | std::vector queueFamilyProperties(queueFamilyCount); 313 | mVk.GetPhysicalDeviceQueueFamilyProperties(mGpu, &queueFamilyCount, 314 | queueFamilyProperties.data()); 315 | 316 | uint32_t queueFamilyIndex; 317 | for (queueFamilyIndex = 0; queueFamilyIndex < queueFamilyCount; ++queueFamilyIndex) { 318 | if (queueFamilyProperties[queueFamilyIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT) { 319 | break; 320 | } 321 | } 322 | ASSERT(queueFamilyIndex < queueFamilyCount); 323 | mQueueFamilyIndex = queueFamilyIndex; 324 | ALOGD("queueFamilyIndex = %u", queueFamilyIndex); 325 | 326 | const float priority = 1.0F; 327 | const VkDeviceQueueCreateInfo queueCreateInfo = { 328 | .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, 329 | .pNext = nullptr, 330 | .flags = 0, 331 | .queueFamilyIndex = mQueueFamilyIndex, 332 | .queueCount = 1, 333 | .pQueuePriorities = &priority, 334 | }; 335 | const VkDeviceCreateInfo deviceCreateInfo = { 336 | .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, 337 | .pNext = nullptr, 338 | .queueCreateInfoCount = 1, 339 | .pQueueCreateInfos = &queueCreateInfo, 340 | .enabledLayerCount = 0, 341 | .ppEnabledLayerNames = nullptr, 342 | .enabledExtensionCount = static_cast(enabledDeviceExtensions.size()), 343 | .ppEnabledExtensionNames = enabledDeviceExtensions.data(), 344 | .pEnabledFeatures = nullptr, 345 | }; 346 | ASSERT(mVk.CreateDevice(mGpu, &deviceCreateInfo, nullptr, &mDevice) == VK_SUCCESS); 347 | mVk.initializeDeviceApi(mDevice); 348 | 349 | mVk.GetDeviceQueue(mDevice, mQueueFamilyIndex, 0, &mQueue); 350 | 351 | ALOGD("Successfully created device"); 352 | } 353 | 354 | void Renderer::createSurface(ANativeWindow* window) { 355 | ASSERT(window); 356 | 357 | const VkAndroidSurfaceCreateInfoKHR surfaceInfo = { 358 | .sType = VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR, 359 | .pNext = nullptr, 360 | .flags = 0, 361 | .window = window, 362 | }; 363 | ASSERT(mVk.CreateAndroidSurfaceKHR(mInstance, &surfaceInfo, nullptr, &mSurface) == VK_SUCCESS); 364 | 365 | VkBool32 surfaceSupported = VK_FALSE; 366 | ASSERT(mVk.GetPhysicalDeviceSurfaceSupportKHR(mGpu, mQueueFamilyIndex, mSurface, 367 | &surfaceSupported) == VK_SUCCESS); 368 | ASSERT(surfaceSupported == VK_TRUE); 369 | 370 | uint32_t formatCount = 0; 371 | ASSERT(mVk.GetPhysicalDeviceSurfaceFormatsKHR(mGpu, mSurface, &formatCount, nullptr) == 372 | VK_SUCCESS); 373 | std::vector formats(formatCount); 374 | ASSERT(mVk.GetPhysicalDeviceSurfaceFormatsKHR(mGpu, mSurface, &formatCount, formats.data()) == 375 | VK_SUCCESS); 376 | 377 | uint32_t formatIndex; 378 | for (formatIndex = 0; formatIndex < formatCount; ++formatIndex) { 379 | if (formats[formatIndex].format == VK_FORMAT_R8G8B8A8_UNORM) { 380 | break; 381 | } 382 | } 383 | ASSERT(formatIndex < formatCount); 384 | mFormat = formats[formatIndex].format; 385 | mColorSpace = formats[formatIndex].colorSpace; 386 | 387 | ALOGD("Successfully created surface"); 388 | } 389 | 390 | void Renderer::createSwapchain(VkSwapchainKHR oldSwapchain) { 391 | VkSurfaceCapabilitiesKHR surfaceCapabilities; 392 | ASSERT(mVk.GetPhysicalDeviceSurfaceCapabilitiesKHR(mGpu, mSurface, &surfaceCapabilities) == 393 | VK_SUCCESS); 394 | ALOGD("Current surface size: %dx%d\n", surfaceCapabilities.currentExtent.width, 395 | surfaceCapabilities.currentExtent.height); 396 | ALOGD("Current transform: 0x%x\n", surfaceCapabilities.currentTransform); 397 | 398 | mSurfaceWidth = mImageWidth = surfaceCapabilities.currentExtent.width; 399 | mSurfaceHeight = mImageHeight = surfaceCapabilities.currentExtent.height; 400 | mPreTransform = surfaceCapabilities.currentTransform; 401 | 402 | if (mPreTransform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR || 403 | mPreTransform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR) { 404 | std::swap(mImageWidth, mImageHeight); 405 | } 406 | 407 | const VkSwapchainCreateInfoKHR swapchainCreateInfo = { 408 | .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, 409 | .pNext = nullptr, 410 | .flags = 0, 411 | .surface = mSurface, 412 | .minImageCount = kReqImageCount, 413 | .imageFormat = mFormat, 414 | .imageColorSpace = mColorSpace, 415 | .imageExtent = 416 | { 417 | .width = mImageWidth, 418 | .height = mImageHeight, 419 | }, 420 | .imageArrayLayers = 1, 421 | .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, 422 | .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, 423 | .queueFamilyIndexCount = 1, 424 | .pQueueFamilyIndices = &mQueueFamilyIndex, 425 | .preTransform = mPreTransform, 426 | .compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, 427 | .presentMode = VK_PRESENT_MODE_FIFO_KHR, 428 | .clipped = VK_FALSE, 429 | .oldSwapchain = oldSwapchain, 430 | }; 431 | ASSERT(mVk.CreateSwapchainKHR(mDevice, &swapchainCreateInfo, nullptr, &mSwapchain) == 432 | VK_SUCCESS); 433 | 434 | uint32_t imageCount = 0; 435 | ASSERT(mVk.GetSwapchainImagesKHR(mDevice, mSwapchain, &imageCount, nullptr) == VK_SUCCESS); 436 | ALOGD("Swapchain image count = %u", imageCount); 437 | 438 | mImages.resize(imageCount, VK_NULL_HANDLE); 439 | ASSERT(mVk.GetSwapchainImagesKHR(mDevice, mSwapchain, &imageCount, mImages.data()) == 440 | VK_SUCCESS); 441 | 442 | mImageViews.resize(imageCount, VK_NULL_HANDLE); 443 | mFramebuffers.resize(imageCount, VK_NULL_HANDLE); 444 | 445 | ALOGD("Successfully created swapchain"); 446 | } 447 | 448 | static std::vector readFileFromAsset(AAssetManager* assetManager, const char* filePath, 449 | int mode) { 450 | ASSERT(filePath); 451 | 452 | AAsset* file = AAssetManager_open(assetManager, filePath, mode); 453 | ASSERT(file); 454 | 455 | auto fileLength = (size_t)AAsset_getLength(file); 456 | std::vector fileContent(fileLength); 457 | AAsset_read(file, fileContent.data(), fileLength); 458 | AAsset_close(file); 459 | 460 | return fileContent; 461 | } 462 | 463 | uint32_t Renderer::getMemoryTypeIndex(uint32_t typeBits, VkFlags mask) { 464 | VkPhysicalDeviceMemoryProperties memoryProperties; 465 | mVk.GetPhysicalDeviceMemoryProperties(mGpu, &memoryProperties); 466 | 467 | for (uint32_t typeIndex = 0; typeIndex < std::numeric_limits::digits; typeIndex++) { 468 | if ((typeBits & 1U) == 1 && 469 | (memoryProperties.memoryTypes[typeIndex].propertyFlags & mask) == mask) { 470 | return typeIndex; 471 | } 472 | typeBits >>= 1U; 473 | } 474 | ASSERT(false); 475 | } 476 | 477 | void Renderer::setImageLayout(VkCommandBuffer commandBuffer, VkImage image, 478 | VkImageLayout oldImageLayout, VkImageLayout newImageLayout, 479 | VkPipelineStageFlags srcStageMask, 480 | VkPipelineStageFlags dstStageMask) { 481 | VkImageMemoryBarrier imageMemoryBarrier = { 482 | .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, 483 | .pNext = nullptr, 484 | .srcAccessMask = 0, 485 | .dstAccessMask = 0, 486 | .oldLayout = oldImageLayout, 487 | .newLayout = newImageLayout, 488 | .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, 489 | .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, 490 | .image = image, 491 | .subresourceRange = 492 | { 493 | .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, 494 | .baseMipLevel = 0, 495 | .levelCount = 1, 496 | .baseArrayLayer = 0, 497 | .layerCount = 1, 498 | }, 499 | }; 500 | 501 | switch (oldImageLayout) { 502 | case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: 503 | imageMemoryBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; 504 | break; 505 | 506 | case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: 507 | imageMemoryBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; 508 | break; 509 | 510 | case VK_IMAGE_LAYOUT_PREINITIALIZED: 511 | imageMemoryBarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; 512 | break; 513 | 514 | default: 515 | break; 516 | } 517 | 518 | switch (newImageLayout) { 519 | case VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL: 520 | imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; 521 | break; 522 | 523 | case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: 524 | imageMemoryBarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; 525 | break; 526 | 527 | case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: 528 | imageMemoryBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; 529 | break; 530 | 531 | case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: 532 | imageMemoryBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; 533 | break; 534 | 535 | case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL: 536 | imageMemoryBarrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; 537 | break; 538 | 539 | default: 540 | break; 541 | } 542 | 543 | mVk.CmdPipelineBarrier(commandBuffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1, 544 | &imageMemoryBarrier); 545 | 546 | ALOGD("Successfully transferred image layout from %u to %u", oldImageLayout, newImageLayout); 547 | } 548 | 549 | void Renderer::loadTextureFromFile(const char* filePath, Texture* outTexture) { 550 | std::vector file = readFileFromAsset(mAssetManager, filePath, AASSET_MODE_BUFFER); 551 | ASSERT(!file.empty()); 552 | 553 | VkFormatProperties formatProperties; 554 | mVk.GetPhysicalDeviceFormatProperties(mGpu, VK_FORMAT_R8G8B8A8_UNORM, &formatProperties); 555 | ASSERT(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT); 556 | 557 | uint32_t imageWidth = 0; 558 | uint32_t imageHeight = 0; 559 | uint32_t channel = 0; 560 | uint8_t* imageData = 561 | stbi_load_from_memory((const stbi_uc*)file.data(), file.size(), 562 | reinterpret_cast(&imageWidth), 563 | reinterpret_cast(&imageHeight), 564 | reinterpret_cast(&channel), 4 /*desired_channels*/); 565 | ASSERT(imageData); 566 | ASSERT(imageWidth); 567 | ASSERT(imageHeight); 568 | ASSERT(channel == 4); 569 | 570 | // Create a stageImage and stageMemory for the original texture uploading 571 | VkImage stageImage = VK_NULL_HANDLE; 572 | VkDeviceMemory stageMemory = VK_NULL_HANDLE; 573 | VkImageCreateInfo imageCreateInfo = { 574 | .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, 575 | .pNext = nullptr, 576 | .flags = 0, 577 | .imageType = VK_IMAGE_TYPE_2D, 578 | .format = VK_FORMAT_R8G8B8A8_UNORM, 579 | .extent = 580 | { 581 | .width = imageWidth, 582 | .height = imageHeight, 583 | .depth = 1, 584 | }, 585 | .mipLevels = 1, 586 | .arrayLayers = 1, 587 | .samples = VK_SAMPLE_COUNT_1_BIT, 588 | .tiling = VK_IMAGE_TILING_LINEAR, 589 | .usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT, 590 | .sharingMode = VK_SHARING_MODE_EXCLUSIVE, 591 | .queueFamilyIndexCount = 1, 592 | .pQueueFamilyIndices = &mQueueFamilyIndex, 593 | .initialLayout = VK_IMAGE_LAYOUT_PREINITIALIZED, 594 | }; 595 | ASSERT(mVk.CreateImage(mDevice, &imageCreateInfo, nullptr, &stageImage) == VK_SUCCESS); 596 | 597 | VkMemoryRequirements memoryRequirements; 598 | mVk.GetImageMemoryRequirements(mDevice, stageImage, &memoryRequirements); 599 | 600 | const uint32_t typeIndex = getMemoryTypeIndex(memoryRequirements.memoryTypeBits, 601 | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); 602 | VkMemoryAllocateInfo memoryAllocateInfo = { 603 | .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, 604 | .pNext = nullptr, 605 | .allocationSize = memoryRequirements.size, 606 | .memoryTypeIndex = typeIndex, 607 | }; 608 | ASSERT(mVk.AllocateMemory(mDevice, &memoryAllocateInfo, nullptr, &stageMemory) == VK_SUCCESS); 609 | ASSERT(mVk.BindImageMemory(mDevice, stageImage, stageMemory, 0) == VK_SUCCESS); 610 | 611 | const VkImageSubresource imageSubresource = { 612 | .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, 613 | .mipLevel = 0, 614 | .arrayLayer = 0, 615 | }; 616 | VkSubresourceLayout subresourceLayout; 617 | mVk.GetImageSubresourceLayout(mDevice, stageImage, &imageSubresource, &subresourceLayout); 618 | 619 | void* textureData; 620 | ASSERT(mVk.MapMemory(mDevice, stageMemory, 0, memoryAllocateInfo.allocationSize, 0, 621 | &textureData) == VK_SUCCESS); 622 | 623 | for (uint32_t row = 0, srcPos = 0, cols = 4 * imageWidth; row < imageHeight; row++) { 624 | for (uint32_t col = 0; col < cols; col++) { 625 | ((uint8_t*)textureData)[col] = imageData[srcPos++]; 626 | } 627 | textureData = (uint8_t*)textureData + subresourceLayout.rowPitch; 628 | } 629 | 630 | mVk.UnmapMemory(mDevice, stageMemory); 631 | stbi_image_free(imageData); 632 | file.clear(); 633 | 634 | // Create a tile texture to blit into 635 | imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL; 636 | imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; 637 | imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; 638 | ASSERT(mVk.CreateImage(mDevice, &imageCreateInfo, nullptr, &outTexture->image) == VK_SUCCESS); 639 | 640 | mVk.GetImageMemoryRequirements(mDevice, outTexture->image, &memoryRequirements); 641 | 642 | memoryAllocateInfo.allocationSize = memoryRequirements.size; 643 | memoryAllocateInfo.memoryTypeIndex = getMemoryTypeIndex(memoryRequirements.memoryTypeBits, 644 | VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); 645 | 646 | ASSERT(mVk.AllocateMemory(mDevice, &memoryAllocateInfo, nullptr, &outTexture->memory) == 647 | VK_SUCCESS); 648 | ASSERT(mVk.BindImageMemory(mDevice, outTexture->image, outTexture->memory, 0) == VK_SUCCESS); 649 | 650 | VkCommandPoolCreateInfo commandPoolCreateInfo = { 651 | .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 652 | .pNext = nullptr, 653 | .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, 654 | .queueFamilyIndex = mQueueFamilyIndex, 655 | }; 656 | VkCommandPool commandPool; 657 | ASSERT(mVk.CreateCommandPool(mDevice, &commandPoolCreateInfo, nullptr, &commandPool) == 658 | VK_SUCCESS); 659 | 660 | VkCommandBuffer commandBuffer; 661 | const VkCommandBufferAllocateInfo commandBufferAllocateInfo = { 662 | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 663 | .pNext = nullptr, 664 | .commandPool = commandPool, 665 | .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, 666 | .commandBufferCount = 1, 667 | }; 668 | 669 | ASSERT(mVk.AllocateCommandBuffers(mDevice, &commandBufferAllocateInfo, &commandBuffer) == 670 | VK_SUCCESS); 671 | VkCommandBufferBeginInfo commandBufferBeginInfo = { 672 | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 673 | .pNext = nullptr, 674 | .flags = 0, 675 | .pInheritanceInfo = nullptr, 676 | }; 677 | ASSERT(mVk.BeginCommandBuffer(commandBuffer, &commandBufferBeginInfo) == VK_SUCCESS); 678 | 679 | setImageLayout(commandBuffer, stageImage, VK_IMAGE_LAYOUT_PREINITIALIZED, 680 | VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_PIPELINE_STAGE_HOST_BIT, 681 | VK_PIPELINE_STAGE_TRANSFER_BIT); 682 | 683 | // Transitions image out of UNDEFINED type 684 | setImageLayout(commandBuffer, outTexture->image, VK_IMAGE_LAYOUT_UNDEFINED, 685 | VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_PIPELINE_STAGE_HOST_BIT, 686 | VK_PIPELINE_STAGE_TRANSFER_BIT); 687 | 688 | const VkImageCopy blitInfo = { 689 | .srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, 690 | .srcSubresource.mipLevel = 0, 691 | .srcSubresource.baseArrayLayer = 0, 692 | .srcSubresource.layerCount = 1, 693 | .srcOffset.x = 0, 694 | .srcOffset.y = 0, 695 | .srcOffset.z = 0, 696 | .dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, 697 | .dstSubresource.mipLevel = 0, 698 | .dstSubresource.baseArrayLayer = 0, 699 | .dstSubresource.layerCount = 1, 700 | .dstOffset.x = 0, 701 | .dstOffset.y = 0, 702 | .dstOffset.z = 0, 703 | .extent.width = imageWidth, 704 | .extent.height = imageHeight, 705 | .extent.depth = 1, 706 | }; 707 | mVk.CmdCopyImage(commandBuffer, stageImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, 708 | outTexture->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blitInfo); 709 | 710 | setImageLayout(commandBuffer, outTexture->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 711 | VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, 712 | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); 713 | 714 | ASSERT(mVk.EndCommandBuffer(commandBuffer) == VK_SUCCESS); 715 | 716 | const VkFenceCreateInfo fenceCreateInfo = { 717 | .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, 718 | .pNext = nullptr, 719 | .flags = 0, 720 | }; 721 | VkFence fence; 722 | ASSERT(mVk.CreateFence(mDevice, &fenceCreateInfo, nullptr, &fence) == VK_SUCCESS); 723 | 724 | const VkSubmitInfo submitInfo = { 725 | .pNext = nullptr, 726 | .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, 727 | .waitSemaphoreCount = 0, 728 | .pWaitSemaphores = nullptr, 729 | .pWaitDstStageMask = nullptr, 730 | .commandBufferCount = 1, 731 | .pCommandBuffers = &commandBuffer, 732 | .signalSemaphoreCount = 0, 733 | .pSignalSemaphores = nullptr, 734 | }; 735 | ASSERT(mVk.QueueSubmit(mQueue, 1, &submitInfo, fence) == VK_SUCCESS); 736 | ASSERT(mVk.WaitForFences(mDevice, 1, &fence, VK_TRUE, kTimeout30Sec) == VK_SUCCESS); 737 | mVk.DestroyFence(mDevice, fence, nullptr); 738 | 739 | mVk.FreeCommandBuffers(mDevice, commandPool, 1, &commandBuffer); 740 | mVk.DestroyCommandPool(mDevice, commandPool, nullptr); 741 | mVk.DestroyImage(mDevice, stageImage, nullptr); 742 | mVk.FreeMemory(mDevice, stageMemory, nullptr); 743 | 744 | // Record the image's original dimensions so we can respect it later 745 | outTexture->width = imageWidth; 746 | outTexture->height = imageHeight; 747 | 748 | ALOGD("Successfully loaded texture from %s", filePath); 749 | } 750 | 751 | void Renderer::createTextures() { 752 | mTextures.resize(kTextureCount); 753 | for (uint32_t i = 0; i < kTextureCount; i++) { 754 | loadTextureFromFile(kTextureFiles[i], &mTextures[i]); 755 | 756 | const VkSamplerCreateInfo samplerCreateInfo = { 757 | .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, 758 | .pNext = nullptr, 759 | .flags = 0, 760 | .magFilter = VK_FILTER_NEAREST, 761 | .minFilter = VK_FILTER_NEAREST, 762 | .mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST, 763 | .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT, 764 | .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, 765 | .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, 766 | .mipLodBias = 0.0F, 767 | .anisotropyEnable = VK_FALSE, 768 | .maxAnisotropy = 1, 769 | .compareEnable = VK_FALSE, 770 | .compareOp = VK_COMPARE_OP_NEVER, 771 | .minLod = 0.0F, 772 | .maxLod = 0.0F, 773 | .borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE, 774 | .unnormalizedCoordinates = VK_FALSE, 775 | }; 776 | ASSERT(mVk.CreateSampler(mDevice, &samplerCreateInfo, nullptr, &mTextures[i].sampler) == 777 | VK_SUCCESS); 778 | 779 | const VkImageViewCreateInfo viewCreateInfo = { 780 | .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 781 | .pNext = nullptr, 782 | .flags = 0, 783 | .image = mTextures[i].image, 784 | .viewType = VK_IMAGE_VIEW_TYPE_2D, 785 | .format = VK_FORMAT_R8G8B8A8_UNORM, 786 | .components = 787 | { 788 | VK_COMPONENT_SWIZZLE_R, 789 | VK_COMPONENT_SWIZZLE_G, 790 | VK_COMPONENT_SWIZZLE_B, 791 | VK_COMPONENT_SWIZZLE_A, 792 | }, 793 | .subresourceRange = 794 | { 795 | VK_IMAGE_ASPECT_COLOR_BIT, 796 | 0, 797 | 1, 798 | 0, 799 | 1, 800 | }, 801 | }; 802 | ASSERT(mVk.CreateImageView(mDevice, &viewCreateInfo, nullptr, &mTextures[i].view) == 803 | VK_SUCCESS); 804 | } 805 | 806 | ALOGD("Successfully created textures"); 807 | } 808 | 809 | void Renderer::createDescriptorSet() { 810 | const VkDescriptorSetLayoutBinding descriptorSetLayoutBinding = { 811 | .binding = 0, 812 | .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 813 | .descriptorCount = kTextureCount, 814 | .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, 815 | .pImmutableSamplers = nullptr, 816 | }; 817 | const VkDescriptorSetLayoutCreateInfo descriptorSetLayoutCreateInfo = { 818 | .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, 819 | .pNext = nullptr, 820 | .bindingCount = 1, 821 | .pBindings = &descriptorSetLayoutBinding, 822 | }; 823 | ASSERT(mVk.CreateDescriptorSetLayout(mDevice, &descriptorSetLayoutCreateInfo, nullptr, 824 | &mDescriptorSetLayout) == VK_SUCCESS); 825 | 826 | const VkDescriptorPoolSize descriptorPoolSize = { 827 | .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 828 | .descriptorCount = kTextureCount, 829 | }; 830 | const VkDescriptorPoolCreateInfo descriptor_pool = { 831 | .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, 832 | .pNext = nullptr, 833 | .flags = 0, 834 | .maxSets = 1, 835 | .poolSizeCount = 1, 836 | .pPoolSizes = &descriptorPoolSize, 837 | }; 838 | 839 | ASSERT(mVk.CreateDescriptorPool(mDevice, &descriptor_pool, nullptr, &mDescriptorPool) == 840 | VK_SUCCESS); 841 | 842 | VkDescriptorSetAllocateInfo descriptorSetAllocateInfo = { 843 | .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, 844 | .pNext = nullptr, 845 | .descriptorPool = mDescriptorPool, 846 | .descriptorSetCount = 1, 847 | .pSetLayouts = &mDescriptorSetLayout, 848 | }; 849 | ASSERT(mVk.AllocateDescriptorSets(mDevice, &descriptorSetAllocateInfo, &mDescriptorSet) == 850 | VK_SUCCESS); 851 | 852 | VkDescriptorImageInfo descriptorImageInfo[kTextureCount]; 853 | for (uint32_t i = 0; i < kTextureCount; i++) { 854 | descriptorImageInfo[i].sampler = mTextures[i].sampler; 855 | descriptorImageInfo[i].imageView = mTextures[i].view; 856 | descriptorImageInfo[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; 857 | } 858 | 859 | VkWriteDescriptorSet writeDescriptorSet = { 860 | .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, 861 | .pNext = nullptr, 862 | .dstSet = mDescriptorSet, 863 | .dstBinding = 0, 864 | .dstArrayElement = 0, 865 | .descriptorCount = kTextureCount, 866 | .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 867 | .pImageInfo = descriptorImageInfo, 868 | .pBufferInfo = nullptr, 869 | .pTexelBufferView = nullptr, 870 | }; 871 | mVk.UpdateDescriptorSets(mDevice, 1, &writeDescriptorSet, 0, nullptr); 872 | 873 | ALOGD("Successfully created descriptor set"); 874 | } 875 | 876 | void Renderer::loadShaderFromFile(const char* filePath, VkShaderModule* outShader) { 877 | ASSERT(filePath); 878 | 879 | std::vector file = readFileFromAsset(mAssetManager, filePath, AASSET_MODE_BUFFER); 880 | 881 | const VkShaderModuleCreateInfo shaderModuleCreateInfo = { 882 | .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, 883 | .pNext = nullptr, 884 | .flags = 0, 885 | .codeSize = file.size(), 886 | .pCode = (const uint32_t*)(file.data()), 887 | }; 888 | ASSERT(mVk.CreateShaderModule(mDevice, &shaderModuleCreateInfo, nullptr, outShader) == 889 | VK_SUCCESS); 890 | 891 | ALOGD("Successfully created shader module from %s", filePath); 892 | } 893 | 894 | void Renderer::createRenderPass() { 895 | const VkAttachmentDescription attachmentDescription = { 896 | .flags = 0, 897 | .format = mFormat, 898 | .samples = VK_SAMPLE_COUNT_1_BIT, 899 | .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, 900 | .storeOp = VK_ATTACHMENT_STORE_OP_STORE, 901 | .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, 902 | .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, 903 | .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, 904 | .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, 905 | }; 906 | const VkAttachmentReference attachmentReference = { 907 | .attachment = 0, 908 | .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, 909 | }; 910 | const VkSubpassDescription subpassDescription = { 911 | .flags = 0, 912 | .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, 913 | .inputAttachmentCount = 0, 914 | .pInputAttachments = nullptr, 915 | .colorAttachmentCount = 1, 916 | .pColorAttachments = &attachmentReference, 917 | .pResolveAttachments = nullptr, 918 | .pDepthStencilAttachment = nullptr, 919 | .preserveAttachmentCount = 0, 920 | .pPreserveAttachments = nullptr, 921 | }; 922 | const VkRenderPassCreateInfo renderPassCreateInfo = { 923 | .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, 924 | .pNext = nullptr, 925 | .flags = 0, 926 | .attachmentCount = 1, 927 | .pAttachments = &attachmentDescription, 928 | .subpassCount = 1, 929 | .pSubpasses = &subpassDescription, 930 | .dependencyCount = 0, 931 | .pDependencies = nullptr, 932 | }; 933 | ASSERT(mVk.CreateRenderPass(mDevice, &renderPassCreateInfo, nullptr, &mRenderPass) == 934 | VK_SUCCESS); 935 | 936 | ALOGD("Successfully created render pass"); 937 | } 938 | 939 | void Renderer::createGraphicsPipeline() { 940 | const VkPushConstantRange pushConstantRange = { 941 | .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, 942 | .offset = 0, 943 | .size = sizeof(PushConstantBlock), 944 | }; 945 | const VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = { 946 | .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, 947 | .pNext = nullptr, 948 | .flags = 0, 949 | .setLayoutCount = 1, 950 | .pSetLayouts = &mDescriptorSetLayout, 951 | .pushConstantRangeCount = 1, 952 | .pPushConstantRanges = &pushConstantRange, 953 | }; 954 | ASSERT(mVk.CreatePipelineLayout(mDevice, &pipelineLayoutCreateInfo, nullptr, 955 | &mPipelineLayout) == VK_SUCCESS); 956 | 957 | VkShaderModule vertexShader = VK_NULL_HANDLE; 958 | VkShaderModule fragmentShader = VK_NULL_HANDLE; 959 | loadShaderFromFile(kVertexShaderFile, &vertexShader); 960 | loadShaderFromFile(kFragmentShaderFile, &fragmentShader); 961 | 962 | const VkPipelineShaderStageCreateInfo shaderStages[2] = { 963 | { 964 | .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, 965 | .pNext = nullptr, 966 | .flags = 0, 967 | .stage = VK_SHADER_STAGE_VERTEX_BIT, 968 | .module = vertexShader, 969 | .pName = "main", 970 | .pSpecializationInfo = nullptr, 971 | }, 972 | { 973 | .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, 974 | .pNext = nullptr, 975 | .flags = 0, 976 | .stage = VK_SHADER_STAGE_FRAGMENT_BIT, 977 | .module = fragmentShader, 978 | .pName = "main", 979 | .pSpecializationInfo = nullptr, 980 | }, 981 | }; 982 | const VkVertexInputBindingDescription vertexInputBindingDescription = { 983 | .binding = 0, 984 | .stride = 4 * sizeof(float), 985 | .inputRate = VK_VERTEX_INPUT_RATE_VERTEX, 986 | }; 987 | const VkVertexInputAttributeDescription vertexInputAttributeDescriptions[2] = { 988 | { 989 | .location = 0, 990 | .binding = 0, 991 | .format = VK_FORMAT_R32G32_SFLOAT, 992 | .offset = 0, 993 | }, 994 | { 995 | 996 | .location = 1, 997 | .binding = 0, 998 | .format = VK_FORMAT_R32G32_SFLOAT, 999 | .offset = sizeof(float) * 2, 1000 | }, 1001 | }; 1002 | const VkPipelineVertexInputStateCreateInfo vertexInputInfo = { 1003 | .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, 1004 | .pNext = nullptr, 1005 | .flags = 0, 1006 | .vertexBindingDescriptionCount = 1, 1007 | .pVertexBindingDescriptions = &vertexInputBindingDescription, 1008 | .vertexAttributeDescriptionCount = 2, 1009 | .pVertexAttributeDescriptions = vertexInputAttributeDescriptions, 1010 | }; 1011 | const VkPipelineInputAssemblyStateCreateInfo inputAssemblyInfo = { 1012 | .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, 1013 | .pNext = nullptr, 1014 | .flags = 0, 1015 | .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, 1016 | .primitiveRestartEnable = VK_FALSE, 1017 | }; 1018 | const VkPipelineViewportStateCreateInfo viewportInfo = { 1019 | .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, 1020 | .pNext = nullptr, 1021 | .flags = 0, 1022 | .viewportCount = 1, 1023 | .pViewports = nullptr, 1024 | .scissorCount = 1, 1025 | .pScissors = nullptr, 1026 | }; 1027 | const VkPipelineRasterizationStateCreateInfo rasterInfo = { 1028 | .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, 1029 | .pNext = nullptr, 1030 | .flags = 0, 1031 | .depthClampEnable = VK_FALSE, 1032 | .rasterizerDiscardEnable = VK_FALSE, 1033 | .polygonMode = VK_POLYGON_MODE_FILL, 1034 | .cullMode = VK_CULL_MODE_NONE, 1035 | .frontFace = VK_FRONT_FACE_CLOCKWISE, 1036 | .depthBiasEnable = VK_FALSE, 1037 | .depthBiasConstantFactor = 0, 1038 | .depthBiasClamp = 0, 1039 | .depthBiasSlopeFactor = 0, 1040 | .lineWidth = 1, 1041 | }; 1042 | const VkSampleMask sampleMask = ~0U; 1043 | const VkPipelineMultisampleStateCreateInfo multisampleInfo = { 1044 | .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, 1045 | .pNext = nullptr, 1046 | .flags = 0, 1047 | .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, 1048 | .sampleShadingEnable = VK_FALSE, 1049 | .minSampleShading = 0, 1050 | .pSampleMask = &sampleMask, 1051 | .alphaToCoverageEnable = VK_FALSE, 1052 | .alphaToOneEnable = VK_FALSE, 1053 | }; 1054 | const VkPipelineColorBlendAttachmentState attachmentStates = { 1055 | .blendEnable = VK_FALSE, 1056 | .srcColorBlendFactor = VK_BLEND_FACTOR_ZERO, 1057 | .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, 1058 | .colorBlendOp = VK_BLEND_OP_ADD, 1059 | .srcAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, 1060 | .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, 1061 | .alphaBlendOp = VK_BLEND_OP_ADD, 1062 | .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | 1063 | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, 1064 | }; 1065 | const VkPipelineColorBlendStateCreateInfo colorBlendInfo = { 1066 | .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, 1067 | .pNext = nullptr, 1068 | .flags = 0, 1069 | .logicOpEnable = VK_FALSE, 1070 | .logicOp = VK_LOGIC_OP_COPY, 1071 | .attachmentCount = 1, 1072 | .pAttachments = &attachmentStates, 1073 | .blendConstants = {0.0F, 0.0F, 0.0F, 0.0F}, 1074 | }; 1075 | const VkDynamicState dynamicStates[2] = { 1076 | VK_DYNAMIC_STATE_VIEWPORT, 1077 | VK_DYNAMIC_STATE_SCISSOR, 1078 | }; 1079 | const VkPipelineDynamicStateCreateInfo dynamicInfo = { 1080 | .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, 1081 | .pNext = nullptr, 1082 | .flags = 0, 1083 | .dynamicStateCount = 2, 1084 | .pDynamicStates = dynamicStates, 1085 | }; 1086 | const VkGraphicsPipelineCreateInfo pipelineCreateInfo = { 1087 | .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, 1088 | .pNext = nullptr, 1089 | .flags = 0, 1090 | .stageCount = 2, 1091 | .pStages = shaderStages, 1092 | .pVertexInputState = &vertexInputInfo, 1093 | .pInputAssemblyState = &inputAssemblyInfo, 1094 | .pTessellationState = nullptr, 1095 | .pViewportState = &viewportInfo, 1096 | .pRasterizationState = &rasterInfo, 1097 | .pMultisampleState = &multisampleInfo, 1098 | .pDepthStencilState = nullptr, 1099 | .pColorBlendState = &colorBlendInfo, 1100 | .pDynamicState = &dynamicInfo, 1101 | .layout = mPipelineLayout, 1102 | .renderPass = mRenderPass, 1103 | .subpass = 0, 1104 | .basePipelineHandle = VK_NULL_HANDLE, 1105 | .basePipelineIndex = 0, 1106 | }; 1107 | ASSERT(mVk.CreateGraphicsPipelines(mDevice, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, 1108 | &mPipeline) == VK_SUCCESS); 1109 | 1110 | mVk.DestroyShaderModule(mDevice, vertexShader, nullptr); 1111 | mVk.DestroyShaderModule(mDevice, fragmentShader, nullptr); 1112 | 1113 | ALOGD("Successfully created graphics pipeline"); 1114 | } 1115 | 1116 | void Renderer::createVertexBuffer() { 1117 | const float vertexData[16] = { 1118 | -1.0F, -1.0F, 0.0F, 0.0F, // LT 1119 | -1.0F, 1.0F, 0.0F, 1.0F, // LB 1120 | 1.0F, -1.0F, 1.0F, 0.0F, // RT 1121 | 1.0F, 1.0F, 1.0F, 1.0F, // RB 1122 | }; 1123 | 1124 | const uint32_t queueFamilyIndex = mQueueFamilyIndex; 1125 | const VkBufferCreateInfo bufferCreateInfo = { 1126 | .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 1127 | .pNext = nullptr, 1128 | .flags = 0, 1129 | .size = sizeof(vertexData), 1130 | .usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 1131 | .sharingMode = VK_SHARING_MODE_EXCLUSIVE, 1132 | .queueFamilyIndexCount = 1, 1133 | .pQueueFamilyIndices = &queueFamilyIndex, 1134 | }; 1135 | ASSERT(mVk.CreateBuffer(mDevice, &bufferCreateInfo, nullptr, &mVertexBuffer) == VK_SUCCESS); 1136 | 1137 | VkMemoryRequirements memoryRequirements; 1138 | mVk.GetBufferMemoryRequirements(mDevice, mVertexBuffer, &memoryRequirements); 1139 | 1140 | uint32_t typeIndex = getMemoryTypeIndex(memoryRequirements.memoryTypeBits, 1141 | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); 1142 | VkMemoryAllocateInfo memoryAllocateInfo = { 1143 | .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, 1144 | .pNext = nullptr, 1145 | .allocationSize = memoryRequirements.size, 1146 | .memoryTypeIndex = typeIndex, 1147 | }; 1148 | ASSERT(mVk.AllocateMemory(mDevice, &memoryAllocateInfo, nullptr, &mVertexMemory) == VK_SUCCESS); 1149 | 1150 | void* data; 1151 | ASSERT(mVk.MapMemory(mDevice, mVertexMemory, 0, sizeof(vertexData), 0, &data) == VK_SUCCESS); 1152 | 1153 | memcpy(data, vertexData, sizeof(vertexData)); 1154 | mVk.UnmapMemory(mDevice, mVertexMemory); 1155 | 1156 | ASSERT(mVk.BindBufferMemory(mDevice, mVertexBuffer, mVertexMemory, 0) == VK_SUCCESS); 1157 | 1158 | ALOGD("Successfully created vertex buffer"); 1159 | } 1160 | 1161 | void Renderer::createCommandBuffers() { 1162 | const VkCommandPoolCreateInfo commandPoolCreateInfo = { 1163 | .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, 1164 | .pNext = nullptr, 1165 | .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, 1166 | .queueFamilyIndex = mQueueFamilyIndex, 1167 | }; 1168 | ASSERT(mVk.CreateCommandPool(mDevice, &commandPoolCreateInfo, nullptr, &mCommandPool) == 1169 | VK_SUCCESS); 1170 | 1171 | mCommandBuffers.resize(kInflight, VK_NULL_HANDLE); 1172 | const VkCommandBufferAllocateInfo commandBufferAllocateInfo = { 1173 | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, 1174 | .pNext = nullptr, 1175 | .commandPool = mCommandPool, 1176 | .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1177 | .commandBufferCount = kInflight, 1178 | }; 1179 | ASSERT(mVk.AllocateCommandBuffers(mDevice, &commandBufferAllocateInfo, 1180 | mCommandBuffers.data()) == VK_SUCCESS); 1181 | 1182 | ALOGD("Successfully created command buffers"); 1183 | } 1184 | 1185 | void Renderer::createSemaphore(VkSemaphore* outSemaphore) { 1186 | const VkSemaphoreCreateInfo semaphoreCreateInfo = { 1187 | .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, 1188 | .pNext = nullptr, 1189 | .flags = 0, 1190 | }; 1191 | ASSERT(mVk.CreateSemaphore(mDevice, &semaphoreCreateInfo, nullptr, outSemaphore) == VK_SUCCESS); 1192 | } 1193 | 1194 | void Renderer::createSemaphores() { 1195 | mAcquireSemaphores.resize(kInflight, VK_NULL_HANDLE); 1196 | mRenderSemaphores.resize(kInflight, VK_NULL_HANDLE); 1197 | for (int i = 0; i < kInflight; i++) { 1198 | createSemaphore(&mAcquireSemaphores[i]); 1199 | createSemaphore(&mRenderSemaphores[i]); 1200 | } 1201 | 1202 | ALOGD("Successfully created semaphores"); 1203 | } 1204 | 1205 | void Renderer::createFences() { 1206 | mInflightFences.resize(kInflight, VK_NULL_HANDLE); 1207 | const VkFenceCreateInfo fenceCreateInfo = { 1208 | .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, 1209 | .pNext = nullptr, 1210 | .flags = VK_FENCE_CREATE_SIGNALED_BIT, 1211 | }; 1212 | for (int i = 0; i < kInflight; i++) { 1213 | ASSERT(mVk.CreateFence(mDevice, &fenceCreateInfo, nullptr, &mInflightFences[i]) == 1214 | VK_SUCCESS); 1215 | } 1216 | 1217 | ALOGD("Successfully created fences"); 1218 | } 1219 | 1220 | void Renderer::createFramebuffer(uint32_t index) { 1221 | const VkImageViewCreateInfo imageViewCreateInfo = { 1222 | .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 1223 | .pNext = nullptr, 1224 | .flags = 0, 1225 | .image = mImages[index], 1226 | .viewType = VK_IMAGE_VIEW_TYPE_2D, 1227 | .format = mFormat, 1228 | .components = 1229 | { 1230 | .r = VK_COMPONENT_SWIZZLE_R, 1231 | .g = VK_COMPONENT_SWIZZLE_G, 1232 | .b = VK_COMPONENT_SWIZZLE_B, 1233 | .a = VK_COMPONENT_SWIZZLE_A, 1234 | }, 1235 | .subresourceRange = 1236 | { 1237 | .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, 1238 | .baseMipLevel = 0, 1239 | .levelCount = 1, 1240 | .baseArrayLayer = 0, 1241 | .layerCount = 1, 1242 | }, 1243 | }; 1244 | ASSERT(mVk.CreateImageView(mDevice, &imageViewCreateInfo, nullptr, &mImageViews[index]) == 1245 | VK_SUCCESS); 1246 | 1247 | const VkFramebufferCreateInfo framebufferCreateInfo = { 1248 | .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, 1249 | .pNext = nullptr, 1250 | .flags = 0, 1251 | .renderPass = mRenderPass, 1252 | .attachmentCount = 1, 1253 | .pAttachments = &mImageViews[index], 1254 | .width = mImageWidth, 1255 | .height = mImageHeight, 1256 | .layers = 1, 1257 | }; 1258 | ASSERT(mVk.CreateFramebuffer(mDevice, &framebufferCreateInfo, nullptr, &mFramebuffers[index]) == 1259 | VK_SUCCESS); 1260 | 1261 | ALOGD("Successfully created framebuffer[%u]", index); 1262 | } 1263 | 1264 | void Renderer::recordCommandBuffer(uint32_t frameIndex, uint32_t imageIndex) { 1265 | const VkCommandBufferBeginInfo commandBufferBeginInfo = { 1266 | .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 1267 | .pNext = nullptr, 1268 | .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, 1269 | .pInheritanceInfo = nullptr, 1270 | }; 1271 | ASSERT(mVk.BeginCommandBuffer(mCommandBuffers[frameIndex], &commandBufferBeginInfo) == 1272 | VK_SUCCESS); 1273 | 1274 | const VkClearValue clearVals = { 1275 | .color.float32[0] = 0.5F, 1276 | .color.float32[1] = 0.5F, 1277 | .color.float32[2] = 0.5F, 1278 | .color.float32[3] = 1.0F, 1279 | }; 1280 | const VkRenderPassBeginInfo renderPassBeginInfo = { 1281 | .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, 1282 | .pNext = nullptr, 1283 | .renderPass = mRenderPass, 1284 | .framebuffer = mFramebuffers[imageIndex], 1285 | .renderArea = 1286 | { 1287 | .offset = 1288 | { 1289 | .x = 0, 1290 | .y = 0, 1291 | }, 1292 | .extent = 1293 | { 1294 | .width = mImageWidth, 1295 | .height = mImageHeight, 1296 | }, 1297 | }, 1298 | .clearValueCount = 1, 1299 | .pClearValues = &clearVals, 1300 | }; 1301 | mVk.CmdBeginRenderPass(mCommandBuffers[frameIndex], &renderPassBeginInfo, 1302 | VK_SUBPASS_CONTENTS_INLINE); 1303 | 1304 | const VkViewport viewport = { 1305 | .x = 0.0F, 1306 | .y = 0.0F, 1307 | .width = (float)mImageWidth, 1308 | .height = (float)mImageHeight, 1309 | .minDepth = 0.0F, 1310 | .maxDepth = 1.0F, 1311 | }; 1312 | mVk.CmdSetViewport(mCommandBuffers[frameIndex], 0, 1, &viewport); 1313 | 1314 | const VkRect2D scissor = { 1315 | .offset = 1316 | { 1317 | .x = 0, 1318 | .y = 0, 1319 | }, 1320 | .extent = 1321 | { 1322 | .width = mImageWidth, 1323 | .height = mImageHeight, 1324 | }, 1325 | }; 1326 | mVk.CmdSetScissor(mCommandBuffers[frameIndex], 0, 1, &scissor); 1327 | 1328 | // Calculate the simple mvp for this demo 1329 | const float scaleW = mSurfaceWidth / (float)mTextures[0].width; 1330 | const float scaleH = mSurfaceHeight / (float)mTextures[0].height; 1331 | const float minimalScale = scaleW < scaleH ? scaleW : scaleH; 1332 | const float scaleX = minimalScale / scaleW; 1333 | const float scaleY = minimalScale / scaleH; 1334 | const glm::mat4 mvp = glm::scale(glm::mat4(1.0F), glm::vec3(scaleX, scaleY, 1.0F)); 1335 | 1336 | // Generate the simple 2x2 rotation matrix for fixing pre-rotation in the clipping space 1337 | float radians = 0.0F; 1338 | switch (mPreTransform) { 1339 | case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: 1340 | radians = glm::radians(90.0F); 1341 | break; 1342 | case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR: 1343 | radians = glm::radians(180.0F); 1344 | break; 1345 | case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR: 1346 | radians = glm::radians(270.0F); 1347 | break; 1348 | default: 1349 | break; 1350 | } 1351 | const glm::mat2 preRotate = glm::rotate(glm::mat4(1.0), radians, glm::vec3(0.0F, 0.0F, 1.0F)); 1352 | 1353 | // We can do the preRotate * mvp here, but separate to be explicit in this demo 1354 | const PushConstantBlock pushConstantBlock = { 1355 | .mvp = mvp, 1356 | .preRotate = preRotate, 1357 | }; 1358 | mVk.CmdPushConstants(mCommandBuffers[frameIndex], mPipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 1359 | 0, sizeof(PushConstantBlock), &pushConstantBlock); 1360 | 1361 | mVk.CmdBindPipeline(mCommandBuffers[frameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, mPipeline); 1362 | 1363 | mVk.CmdBindDescriptorSets(mCommandBuffers[frameIndex], VK_PIPELINE_BIND_POINT_GRAPHICS, 1364 | mPipelineLayout, 0, 1, &mDescriptorSet, 0, nullptr); 1365 | 1366 | const VkDeviceSize offset = 0; 1367 | mVk.CmdBindVertexBuffers(mCommandBuffers[frameIndex], 0, 1, &mVertexBuffer, &offset); 1368 | 1369 | mVk.CmdDraw(mCommandBuffers[frameIndex], 4, 1, 0, 0); 1370 | 1371 | mVk.CmdEndRenderPass(mCommandBuffers[frameIndex]); 1372 | 1373 | ASSERT(mVk.EndCommandBuffer(mCommandBuffers[frameIndex]) == VK_SUCCESS); 1374 | } 1375 | 1376 | void Renderer::destroyOldSwapchain() { 1377 | for (auto& framebuffer : mOldFramebuffers) { 1378 | mVk.DestroyFramebuffer(mDevice, framebuffer, nullptr); 1379 | } 1380 | mOldFramebuffers.clear(); 1381 | 1382 | for (auto& imageView : mOldImageViews) { 1383 | mVk.DestroyImageView(mDevice, imageView, nullptr); 1384 | } 1385 | mOldImageViews.clear(); 1386 | 1387 | mOldImages.clear(); 1388 | 1389 | mVk.DestroySwapchainKHR(mDevice, mOldSwapchain, nullptr); 1390 | mOldSwapchain = VK_NULL_HANDLE; 1391 | 1392 | ALOGD("Successfully destroyed old swapchain"); 1393 | } 1394 | 1395 | bool Renderer::is180Rotation() { 1396 | VkSurfaceCapabilitiesKHR surfaceCapabilities; 1397 | ASSERT(mVk.GetPhysicalDeviceSurfaceCapabilitiesKHR(mGpu, mSurface, &surfaceCapabilities) == 1398 | VK_SUCCESS); 1399 | VkSurfaceTransformFlagsKHR currentTransform = surfaceCapabilities.currentTransform; 1400 | switch (currentTransform) { 1401 | case VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR: 1402 | return mPreTransform == VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR; 1403 | case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR: 1404 | return mPreTransform == VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR; 1405 | case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR: 1406 | return mPreTransform == VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; 1407 | case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR: 1408 | return mPreTransform == VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR; 1409 | default: 1410 | break; 1411 | } 1412 | return false; 1413 | } 1414 | -------------------------------------------------------------------------------- /app/src/main/cpp/Renderer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | #include 22 | 23 | #include "VkHelper.h" 24 | 25 | class Renderer { 26 | private: 27 | struct Texture { 28 | VkSampler sampler; 29 | VkImage image; 30 | VkDeviceMemory memory; 31 | VkImageView view; 32 | uint32_t width; 33 | uint32_t height; 34 | 35 | Texture() 36 | : sampler(VK_NULL_HANDLE), 37 | image(VK_NULL_HANDLE), 38 | memory(VK_NULL_HANDLE), 39 | view(VK_NULL_HANDLE), 40 | width(0), 41 | height(0) {} 42 | }; 43 | 44 | public: 45 | explicit Renderer() {} 46 | void initialize(ANativeWindow* window, AAssetManager* assetManager); 47 | void drawFrame(); 48 | void updateSurface(uint32_t width, uint32_t height); 49 | void destroy(); 50 | 51 | private: 52 | void createInstance(); 53 | void createDevice(); 54 | void createSurface(ANativeWindow* window); 55 | void createSwapchain(VkSwapchainKHR oldSwapchain); 56 | uint32_t getMemoryTypeIndex(uint32_t typeBits, VkFlags mask); 57 | void setImageLayout(VkCommandBuffer commandBuffer, VkImage image, VkImageLayout oldImageLayout, 58 | VkImageLayout newImageLayout, VkPipelineStageFlags srcStages, 59 | VkPipelineStageFlags dstStages); 60 | void loadTextureFromFile(const char* filePath, Texture* outTexture); 61 | void createTextures(); 62 | void createDescriptorSet(); 63 | void createRenderPass(); 64 | void loadShaderFromFile(const char* filePath, VkShaderModule* outShader); 65 | void createGraphicsPipeline(); 66 | void createVertexBuffer(); 67 | void createCommandBuffers(); 68 | void createSemaphore(VkSemaphore* outSemaphore); 69 | void createSemaphores(); 70 | void createFences(); 71 | void createFramebuffer(uint32_t index); 72 | void recordCommandBuffer(uint32_t frameIndex, uint32_t imageIndex); 73 | void destroyOldSwapchain(); 74 | bool is180Rotation(); 75 | 76 | // Helper member for Vulkan entry points 77 | VkHelper mVk; 78 | // A pointer to cache AAssetManager 79 | AAssetManager* mAssetManager = nullptr; 80 | 81 | // Stable baseline members 82 | VkInstance mInstance = VK_NULL_HANDLE; 83 | VkPhysicalDevice mGpu = VK_NULL_HANDLE; 84 | VkDevice mDevice = VK_NULL_HANDLE; 85 | uint32_t mQueueFamilyIndex = 0; 86 | VkQueue mQueue = VK_NULL_HANDLE; 87 | 88 | // Swapchain related members 89 | VkSurfaceKHR mSurface = VK_NULL_HANDLE; 90 | VkFormat mFormat = VK_FORMAT_UNDEFINED; 91 | VkColorSpaceKHR mColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; 92 | uint32_t mSurfaceWidth = 0; 93 | uint32_t mSurfaceHeight = 0; 94 | uint32_t mImageWidth = 0; 95 | uint32_t mImageHeight = 0; 96 | VkSurfaceTransformFlagBitsKHR mPreTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; 97 | uint32_t mFrameCount = 0; 98 | VkSwapchainKHR mSwapchain = VK_NULL_HANDLE; 99 | std::vector mImages; 100 | std::vector mImageViews; 101 | std::vector mFramebuffers; 102 | // For swapchain recreation, old stuff can be refactored to kInflight buffered 103 | bool mFireRecreateSwapchain = false; 104 | uint32_t mPreRotationLatency = kPreRotationLatency; 105 | uint32_t mRetireFrame = 0; 106 | VkSwapchainKHR mOldSwapchain = VK_NULL_HANDLE; 107 | std::vector mOldImages; 108 | std::vector mOldImageViews; 109 | std::vector mOldFramebuffers; 110 | 111 | // Graphics pipeline related members 112 | VkRenderPass mRenderPass = VK_NULL_HANDLE; 113 | VkPipelineLayout mPipelineLayout = VK_NULL_HANDLE; 114 | VkPipeline mPipeline = VK_NULL_HANDLE; 115 | 116 | // Descriptor related members 117 | std::vector mTextures; 118 | VkDescriptorSetLayout mDescriptorSetLayout = VK_NULL_HANDLE; 119 | VkDescriptorPool mDescriptorPool = VK_NULL_HANDLE; 120 | VkDescriptorSet mDescriptorSet = VK_NULL_HANDLE; 121 | 122 | // Vertex buffer related members 123 | VkBuffer mVertexBuffer = VK_NULL_HANDLE; 124 | VkDeviceMemory mVertexMemory = VK_NULL_HANDLE; 125 | 126 | // Command buffer related members 127 | VkCommandPool mCommandPool = VK_NULL_HANDLE; 128 | std::vector mCommandBuffers; 129 | 130 | // Semaphores for synchronization 131 | std::vector mAcquireSemaphores; 132 | std::vector mRenderSemaphores; 133 | 134 | // Fences for latency control 135 | std::vector mInflightFences; 136 | 137 | // App specific constants 138 | static constexpr const char* kRequiredInstanceExtensions[2] = { 139 | "VK_KHR_surface", 140 | "VK_KHR_android_surface", 141 | }; 142 | static constexpr const char* kRequiredDeviceExtensions[1] = { 143 | "VK_KHR_swapchain", 144 | }; 145 | static constexpr const uint32_t kReqImageCount = 3; 146 | static constexpr const uint32_t kInflight = 2; 147 | static constexpr const uint32_t kTextureCount = 1; 148 | static constexpr const char* kTextureFiles[kTextureCount] = { 149 | "sample_tex.png", 150 | }; 151 | static constexpr const char* kVertexShaderFile = "texture.vert.spv"; 152 | static constexpr const char* kFragmentShaderFile = "texture.frag.spv"; 153 | static constexpr const uint32_t kLogInterval = 100; 154 | static constexpr const uint64_t kTimeout30Sec = 30000000000; 155 | static constexpr const uint32_t kPreRotationLatency = 30; 156 | }; 157 | -------------------------------------------------------------------------------- /app/src/main/cpp/Utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | #define LOG_TAG "VKDEMO" 22 | #define ALOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) 23 | #define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, LOG_TAG, __VA_ARGS__) 24 | #define ASSERT(cond) \ 25 | if (!(cond)) { \ 26 | __android_log_assert(#cond, LOG_TAG, "Error: " #cond " at " __FILE__ ":%d", __LINE__); \ 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/cpp/VkHelper.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "VkHelper.h" 18 | 19 | #define GET_PROC(F) F = reinterpret_cast(vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk" #F)) 20 | #define GET_INST_PROC(F) F = reinterpret_cast(vkGetInstanceProcAddr(instance, "vk" #F)) 21 | #define GET_DEV_PROC(F) F = reinterpret_cast(GetDeviceProcAddr(device, "vk" #F)) 22 | 23 | void VkHelper::initializeGlobalApi() { 24 | GET_PROC(CreateInstance); 25 | GET_PROC(EnumerateInstanceExtensionProperties); 26 | GET_PROC(EnumerateInstanceVersion); 27 | } 28 | 29 | void VkHelper::initializeInstanceApi(VkInstance instance) { 30 | GET_INST_PROC(CreateAndroidSurfaceKHR); 31 | GET_INST_PROC(CreateDevice); 32 | GET_INST_PROC(DestroyInstance); 33 | GET_INST_PROC(DestroySurfaceKHR); 34 | GET_INST_PROC(EnumerateDeviceExtensionProperties); 35 | GET_INST_PROC(EnumeratePhysicalDevices); 36 | GET_INST_PROC(GetDeviceProcAddr); 37 | GET_INST_PROC(GetPhysicalDeviceMemoryProperties); 38 | GET_INST_PROC(GetPhysicalDeviceFormatProperties); 39 | GET_INST_PROC(GetPhysicalDeviceQueueFamilyProperties); 40 | GET_INST_PROC(GetPhysicalDeviceSurfaceFormatsKHR); 41 | GET_INST_PROC(GetPhysicalDeviceSurfaceSupportKHR); 42 | GET_INST_PROC(GetPhysicalDeviceSurfaceCapabilitiesKHR); 43 | } 44 | 45 | void VkHelper::initializeDeviceApi(VkDevice device) { 46 | GET_DEV_PROC(AcquireNextImageKHR); 47 | GET_DEV_PROC(AllocateCommandBuffers); 48 | GET_DEV_PROC(AllocateDescriptorSets); 49 | GET_DEV_PROC(AllocateMemory); 50 | GET_DEV_PROC(BeginCommandBuffer); 51 | GET_DEV_PROC(BindBufferMemory); 52 | GET_DEV_PROC(BindImageMemory); 53 | GET_DEV_PROC(CmdBeginRenderPass); 54 | GET_DEV_PROC(CmdBindDescriptorSets); 55 | GET_DEV_PROC(CmdBindPipeline); 56 | GET_DEV_PROC(CmdBindVertexBuffers); 57 | GET_DEV_PROC(CmdCopyImage); 58 | GET_DEV_PROC(CmdDraw); 59 | GET_DEV_PROC(CmdEndRenderPass); 60 | GET_DEV_PROC(CmdPipelineBarrier); 61 | GET_DEV_PROC(CmdPushConstants); 62 | GET_DEV_PROC(CmdSetScissor); 63 | GET_DEV_PROC(CmdSetViewport); 64 | GET_DEV_PROC(CreateBuffer); 65 | GET_DEV_PROC(CreateCommandPool); 66 | GET_DEV_PROC(CreateDescriptorPool); 67 | GET_DEV_PROC(CreateDescriptorSetLayout); 68 | GET_DEV_PROC(CreateFence); 69 | GET_DEV_PROC(CreateFramebuffer); 70 | GET_DEV_PROC(CreateGraphicsPipelines); 71 | GET_DEV_PROC(CreateImage); 72 | GET_DEV_PROC(CreateImageView); 73 | GET_DEV_PROC(CreatePipelineLayout); 74 | GET_DEV_PROC(CreateRenderPass); 75 | GET_DEV_PROC(CreateSampler); 76 | GET_DEV_PROC(CreateSemaphore); 77 | GET_DEV_PROC(CreateShaderModule); 78 | GET_DEV_PROC(CreateSwapchainKHR); 79 | GET_DEV_PROC(DestroyBuffer); 80 | GET_DEV_PROC(DestroyCommandPool); 81 | GET_DEV_PROC(DestroyDescriptorPool); 82 | GET_DEV_PROC(DestroyDescriptorSetLayout); 83 | GET_DEV_PROC(DestroyDevice); 84 | GET_DEV_PROC(DestroyFence); 85 | GET_DEV_PROC(DestroyFramebuffer); 86 | GET_DEV_PROC(DestroyImage); 87 | GET_DEV_PROC(DestroyImageView); 88 | GET_DEV_PROC(DestroyPipeline); 89 | GET_DEV_PROC(DestroyPipelineLayout); 90 | GET_DEV_PROC(DestroyRenderPass); 91 | GET_DEV_PROC(DestroySampler); 92 | GET_DEV_PROC(DestroySemaphore); 93 | GET_DEV_PROC(DestroyShaderModule); 94 | GET_DEV_PROC(DestroySwapchainKHR); 95 | GET_DEV_PROC(DeviceWaitIdle); 96 | GET_DEV_PROC(EndCommandBuffer); 97 | GET_DEV_PROC(FreeCommandBuffers); 98 | GET_DEV_PROC(FreeMemory); 99 | GET_DEV_PROC(GetBufferMemoryRequirements); 100 | GET_DEV_PROC(GetDeviceQueue); 101 | GET_DEV_PROC(GetImageMemoryRequirements); 102 | GET_DEV_PROC(GetImageSubresourceLayout); 103 | GET_DEV_PROC(GetSwapchainImagesKHR); 104 | GET_DEV_PROC(MapMemory); 105 | GET_DEV_PROC(QueuePresentKHR); 106 | GET_DEV_PROC(QueueSubmit); 107 | GET_DEV_PROC(ResetFences); 108 | GET_DEV_PROC(UnmapMemory); 109 | GET_DEV_PROC(UpdateDescriptorSets); 110 | GET_DEV_PROC(WaitForFences); 111 | } 112 | -------------------------------------------------------------------------------- /app/src/main/cpp/VkHelper.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | class VkHelper { 22 | public: 23 | explicit VkHelper() {} 24 | void initializeGlobalApi(); 25 | void initializeInstanceApi(VkInstance instance); 26 | void initializeDeviceApi(VkDevice device); 27 | 28 | // GET_PROC functions 29 | PFN_vkCreateInstance CreateInstance = nullptr; 30 | PFN_vkEnumerateInstanceExtensionProperties EnumerateInstanceExtensionProperties = nullptr; 31 | PFN_vkEnumerateInstanceVersion EnumerateInstanceVersion = nullptr; 32 | 33 | // GET_INSTANCE_PROC functions 34 | PFN_vkCreateAndroidSurfaceKHR CreateAndroidSurfaceKHR = nullptr; 35 | PFN_vkCreateDevice CreateDevice = nullptr; 36 | PFN_vkDestroyInstance DestroyInstance = nullptr; 37 | PFN_vkDestroySurfaceKHR DestroySurfaceKHR = nullptr; 38 | PFN_vkEnumerateDeviceExtensionProperties EnumerateDeviceExtensionProperties = nullptr; 39 | PFN_vkEnumeratePhysicalDevices EnumeratePhysicalDevices = nullptr; 40 | PFN_vkGetDeviceProcAddr GetDeviceProcAddr = nullptr; 41 | PFN_vkGetPhysicalDeviceFormatProperties GetPhysicalDeviceFormatProperties = nullptr; 42 | PFN_vkGetPhysicalDeviceMemoryProperties GetPhysicalDeviceMemoryProperties = nullptr; 43 | PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR GetPhysicalDeviceSurfaceCapabilitiesKHR = nullptr; 44 | PFN_vkGetPhysicalDeviceSurfaceFormatsKHR GetPhysicalDeviceSurfaceFormatsKHR = nullptr; 45 | PFN_vkGetPhysicalDeviceSurfaceSupportKHR GetPhysicalDeviceSurfaceSupportKHR = nullptr; 46 | PFN_vkGetPhysicalDeviceQueueFamilyProperties GetPhysicalDeviceQueueFamilyProperties = nullptr; 47 | 48 | // GET_DEVICE_PROC functions 49 | PFN_vkAcquireNextImageKHR AcquireNextImageKHR = nullptr; 50 | PFN_vkAllocateCommandBuffers AllocateCommandBuffers = nullptr; 51 | PFN_vkAllocateDescriptorSets AllocateDescriptorSets = nullptr; 52 | PFN_vkAllocateMemory AllocateMemory = nullptr; 53 | PFN_vkBeginCommandBuffer BeginCommandBuffer = nullptr; 54 | PFN_vkBindBufferMemory BindBufferMemory = nullptr; 55 | PFN_vkBindImageMemory BindImageMemory = nullptr; 56 | PFN_vkCmdBeginRenderPass CmdBeginRenderPass = nullptr; 57 | PFN_vkCmdBindDescriptorSets CmdBindDescriptorSets = nullptr; 58 | PFN_vkCmdBindPipeline CmdBindPipeline = nullptr; 59 | PFN_vkCmdBindVertexBuffers CmdBindVertexBuffers = nullptr; 60 | PFN_vkCmdCopyImage CmdCopyImage = nullptr; 61 | PFN_vkCmdDraw CmdDraw = nullptr; 62 | PFN_vkCmdEndRenderPass CmdEndRenderPass = nullptr; 63 | PFN_vkCmdPipelineBarrier CmdPipelineBarrier = nullptr; 64 | PFN_vkCmdPushConstants CmdPushConstants = nullptr; 65 | PFN_vkCmdSetScissor CmdSetScissor = nullptr; 66 | PFN_vkCmdSetViewport CmdSetViewport = nullptr; 67 | PFN_vkCreateBuffer CreateBuffer = nullptr; 68 | PFN_vkCreateCommandPool CreateCommandPool = nullptr; 69 | PFN_vkCreateDescriptorPool CreateDescriptorPool = nullptr; 70 | PFN_vkCreateDescriptorSetLayout CreateDescriptorSetLayout = nullptr; 71 | PFN_vkCreateFence CreateFence = nullptr; 72 | PFN_vkCreateFramebuffer CreateFramebuffer = nullptr; 73 | PFN_vkCreateGraphicsPipelines CreateGraphicsPipelines = nullptr; 74 | PFN_vkCreateImage CreateImage = nullptr; 75 | PFN_vkCreateImageView CreateImageView = nullptr; 76 | PFN_vkCreatePipelineLayout CreatePipelineLayout = nullptr; 77 | PFN_vkCreateRenderPass CreateRenderPass = nullptr; 78 | PFN_vkCreateSampler CreateSampler = nullptr; 79 | PFN_vkCreateSemaphore CreateSemaphore = nullptr; 80 | PFN_vkCreateShaderModule CreateShaderModule = nullptr; 81 | PFN_vkCreateSwapchainKHR CreateSwapchainKHR = nullptr; 82 | PFN_vkDestroyBuffer DestroyBuffer = nullptr; 83 | PFN_vkDestroyCommandPool DestroyCommandPool = nullptr; 84 | PFN_vkDestroyDescriptorPool DestroyDescriptorPool = nullptr; 85 | PFN_vkDestroyDescriptorSetLayout DestroyDescriptorSetLayout = nullptr; 86 | PFN_vkDestroyDevice DestroyDevice = nullptr; 87 | PFN_vkDestroyFence DestroyFence = nullptr; 88 | PFN_vkDestroyFramebuffer DestroyFramebuffer = nullptr; 89 | PFN_vkDestroyImage DestroyImage = nullptr; 90 | PFN_vkDestroyImageView DestroyImageView = nullptr; 91 | PFN_vkDestroyPipeline DestroyPipeline = nullptr; 92 | PFN_vkDestroyPipelineLayout DestroyPipelineLayout = nullptr; 93 | PFN_vkDestroyRenderPass DestroyRenderPass = nullptr; 94 | PFN_vkDestroySampler DestroySampler = nullptr; 95 | PFN_vkDestroySemaphore DestroySemaphore = nullptr; 96 | PFN_vkDestroyShaderModule DestroyShaderModule = nullptr; 97 | PFN_vkDestroySwapchainKHR DestroySwapchainKHR = nullptr; 98 | PFN_vkDeviceWaitIdle DeviceWaitIdle = nullptr; 99 | PFN_vkEndCommandBuffer EndCommandBuffer = nullptr; 100 | PFN_vkFreeCommandBuffers FreeCommandBuffers = nullptr; 101 | PFN_vkFreeMemory FreeMemory = nullptr; 102 | PFN_vkGetBufferMemoryRequirements GetBufferMemoryRequirements = nullptr; 103 | PFN_vkGetDeviceQueue GetDeviceQueue = nullptr; 104 | PFN_vkGetImageMemoryRequirements GetImageMemoryRequirements = nullptr; 105 | PFN_vkGetImageSubresourceLayout GetImageSubresourceLayout = nullptr; 106 | PFN_vkGetSwapchainImagesKHR GetSwapchainImagesKHR = nullptr; 107 | PFN_vkMapMemory MapMemory = nullptr; 108 | PFN_vkQueuePresentKHR QueuePresentKHR = nullptr; 109 | PFN_vkQueueSubmit QueueSubmit = nullptr; 110 | PFN_vkResetFences ResetFences = nullptr; 111 | PFN_vkUnmapMemory UnmapMemory = nullptr; 112 | PFN_vkUpdateDescriptorSets UpdateDescriptorSets = nullptr; 113 | PFN_vkWaitForFences WaitForFences = nullptr; 114 | }; 115 | -------------------------------------------------------------------------------- /app/src/main/cpp/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include 18 | #include 19 | 20 | #include "Engine.h" 21 | #include "Utils.h" 22 | 23 | static void onChoreographer(int64_t frameTimeNanos, void* data) { 24 | if (!data) { 25 | return; 26 | } 27 | 28 | auto engine = static_cast(data); 29 | if (!engine->isReady()) { 30 | return; 31 | } 32 | 33 | AChoreographer_postFrameCallbackDelayed64(AChoreographer_getInstance(), onChoreographer, engine, 34 | engine->getDelayMillis(frameTimeNanos)); 35 | 36 | engine->drawFrame(); 37 | } 38 | 39 | static void handleAppCmd(android_app* app, int32_t cmd) { 40 | auto engine = static_cast(app->userData); 41 | switch (cmd) { 42 | case APP_CMD_INIT_WINDOW: 43 | engine->onInitWindow(app->window, app->activity->assetManager); 44 | AChoreographer_postFrameCallback64(AChoreographer_getInstance(), onChoreographer, 45 | engine); 46 | break; 47 | case APP_CMD_TERM_WINDOW: 48 | engine->onTermWindow(); 49 | break; 50 | default: 51 | break; 52 | } 53 | } 54 | 55 | static void handleNativeWindowResized(ANativeActivity* activity, ANativeWindow* window) { 56 | auto engine = static_cast(static_cast(activity->instance)->userData); 57 | const int32_t width = ANativeWindow_getWidth(window); 58 | const int32_t height = ANativeWindow_getHeight(window); 59 | ALOGD("%s: W[%d], H[%d]", __FUNCTION__, width, height); 60 | 61 | if (width < 0 || height < 0) { 62 | return; 63 | } 64 | engine->onWindowResized((uint32_t)width, (uint32_t)height); 65 | } 66 | 67 | void android_main(android_app* app) { 68 | Engine engine; 69 | 70 | app->userData = &engine; 71 | app->onAppCmd = handleAppCmd; 72 | app->activity->callbacks->onNativeWindowResized = handleNativeWindowResized; 73 | 74 | if (AChoreographer_getInstance() == nullptr) { 75 | return; 76 | } 77 | 78 | while (true) { 79 | int events; 80 | android_poll_source* source; 81 | 82 | while (ALooper_pollAll(-1, nullptr, &events, (void**)&source) >= 0) { 83 | if (source != nullptr) { 84 | source->process(app, source); 85 | } 86 | 87 | if (app->destroyRequested != 0) { 88 | ALOGD("Destroy requested"); 89 | engine.onTermWindow(); 90 | return; 91 | } 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/vulkan-pre-rotation-demo/b3f836dee9d9925820e7631dc9f4b3fe77cfa56b/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/vulkan-pre-rotation-demo/b3f836dee9d9925820e7631dc9f4b3fe77cfa56b/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/vulkan-pre-rotation-demo/b3f836dee9d9925820e7631dc9f4b3fe77cfa56b/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/vulkan-pre-rotation-demo/b3f836dee9d9925820e7631dc9f4b3fe77cfa56b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | vkdemo 3 | 4 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:4.0.0' 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/vulkan-pre-rotation-demo/b3f836dee9d9925820e7631dc9f4b3fe77cfa56b/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Aug 17 23:33:32 PDT 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /third_party/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.4.1) 2 | 3 | # glm 4 | add_library(glm INTERFACE) 5 | target_sources(glm INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/glm/glm/glm.hpp) 6 | target_include_directories(glm INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/glm) 7 | target_compile_definitions(glm INTERFACE GLM_FORCE_SIZE_T_LENGTH GLM_FORCE_RADIANS) 8 | 9 | # stb 10 | add_library(stb INTERFACE) 11 | target_include_directories(stb INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/stb") 12 | --------------------------------------------------------------------------------