diff --git a/CMakeLists.txt b/CMakeLists.txt index 2b0c6d2..6bec80a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,13 +10,15 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Android") message(STATUS "Building for Android") include_directories( - + vulkan ) add_library(sample SHARED app/src/main/cpp/main.cpp app/src/main/cpp/AndroidOut.cpp + vulkan/AppBase.h vulkan/AppBase.cpp + vulkan/Application.h vulkan/Application.cpp ) @@ -53,7 +55,9 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows" OR WIN32) # 将源代码添加到此项目的可执行文件。 add_executable (sample "vulkan/main.cpp" + "vulkan/AppBase.h" "vulkan/AppBase.cpp" + "vulkan/Application.h" "vulkan/Application.cpp" ) endif() diff --git a/app/build.gradle b/app/build.gradle index 35296b5..21af407 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -2,6 +2,9 @@ plugins { alias(libs.plugins.android.application) } +ext.vvl_version='1.4.321.0' +apply from: "./download_vvl.gradle" + android { namespace 'com.hmwl.sample' compileSdk 35 diff --git a/app/download_vvl.gradle b/app/download_vvl.gradle new file mode 100644 index 0000000..bfa51f3 --- /dev/null +++ b/app/download_vvl.gradle @@ -0,0 +1,81 @@ +apply plugin: 'com.android.application' + +/* + * Copyright (c) 2022-2024, The Android Open Source Project. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Licensed under the Apache License, Version 2.0 the "License"; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +/* + * Download validation layer binary release zip file from Khronos github repo + * https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases + * + * To use this script, add the following to your module's build.gradle: + * ext.vvl_version='your-new-version' + * apply from: "${PATH-TO-THIS}/download_vvl.gradle" + * To update to a new version: + * - change the ext.vvl_version to a new version string. + * - delete directory pointed by ${VVL_JNILIB_DIR}. + * - sync gradle script in IDE and rebuild project. + * + * Note: binary release can also be manually downloaded and put it into + * the default jniLibs directory at app/src/main/jniLibs. + */ + +// get the validation layer version. +def VVL_VER = "1.4.321.0" +if (ext.has("vvl_version")) { + VVL_VER = ext.vvl_version +} + +// declare local variables shared between downloading and unzipping. +def VVL_SITE ="https://github.com/KhronosGroup/Vulkan-ValidationLayers" +def VVL_LIB_ROOT= rootDir.absolutePath.toString() + "/layerLib" +def VVL_JNILIB_DIR="${VVL_LIB_ROOT}/jniLibs" +def VVL_SO_NAME = "libVkLayer_khronos_validation.so" + +// download the release zip file to ${VVL_LIB_ROOT}/ +task download { + def VVL_ZIP_NAME = "releases/download/vulkan-sdk-${VVL_VER}/android-binaries-${VVL_VER}.zip" + mkdir "${VVL_LIB_ROOT}" + def f = new File("${VVL_LIB_ROOT}/android-binaries-${VVL_VER}.zip") + new URL("${VVL_SITE}/${VVL_ZIP_NAME}") + .withInputStream { i -> f.withOutputStream { it << i } } +} + +// unzip the downloaded VVL zip archive to the ${VVL_JNILIB_DIR} for APK packaging. +task unzip(dependsOn: download, type: Copy) { + from zipTree(file("${VVL_LIB_ROOT}/android-binaries-${VVL_VER}.zip")) + into file("${VVL_JNILIB_DIR}") +} +android.sourceSets.main.jniLibs { + srcDirs += ["${VVL_JNILIB_DIR}/android-binaries-${VVL_VER}"] +} + +// add vvl download as an application dependency. +dependencies { + def ARM64_VVL_FILE = "${VVL_JNILIB_DIR}/arm64-v8a/${VVL_SO_NAME}" + if(!file("${ARM64_VVL_FILE}").exists()) { + implementation files("${ARM64_VVL_FILE}") { + builtBy 'unzip' + } + } +} + +project.afterEvaluate{ + project.getTasks().getByName("mergeDebugJniLibFolders").dependsOn(unzip) + project.getTasks().getByName("mergeReleaseJniLibFolders").dependsOn(unzip) +} diff --git a/app/src/main/cpp/main.cpp b/app/src/main/cpp/main.cpp index a8032e5..4fab6a7 100644 --- a/app/src/main/cpp/main.cpp +++ b/app/src/main/cpp/main.cpp @@ -3,14 +3,21 @@ #include #include #include "AndroidOut.h" +#include "Application.h" +#include +#include - +using namespace std; extern "C" { +android_app* g_android_app = nullptr; +Application* g_Application = nullptr; + void handle_cmd(android_app *pApp, int32_t cmd) { switch (cmd) { case APP_CMD_INIT_WINDOW: aout << "APP_CMD_INIT_WINDOW" << std::endl; + g_Application->initVulkan(); break; case APP_CMD_TERM_WINDOW: aout << "APP_CMD_TERM_WINDOW" << std::endl; @@ -20,26 +27,24 @@ void handle_cmd(android_app *pApp, int32_t cmd) { } } -android_app* g_android_app = nullptr; - void android_main(struct android_app *pApp) { aout << "Welcome to android_main" << std::endl; - + g_android_app = pApp; + Application application; + g_Application = &application; pApp->onAppCmd = handle_cmd; android_app_set_motion_event_filter(pApp, nullptr); - // This sets up a typical game/event loop. It will run until the app is destroyed. do { + auto ms = chrono::duration_cast(chrono::system_clock::now().time_since_epoch()).count(); // Process all pending events before running game logic. bool done = false; while (!done) { - // 0 is non-blocking. int timeout = 0; int events; android_poll_source *pSource; - int result = ALooper_pollOnce(timeout, nullptr, &events, - reinterpret_cast(&pSource)); + int result = ALooper_pollOnce(timeout, nullptr, &events, reinterpret_cast(&pSource)); switch (result) { case ALOOPER_POLL_TIMEOUT: [[clang::fallthrough]]; @@ -58,8 +63,13 @@ void android_main(struct android_app *pApp) { } } } - - + if(application.isInited()) + { + application.drawFrame(); + } + auto end_ms = chrono::duration_cast(chrono::system_clock::now().time_since_epoch()).count(); + this_thread::sleep_for(chrono::milliseconds((end_ms-ms))); } while (!pApp->destroyRequested); + application.cleanup(); } } \ No newline at end of file diff --git a/vulkan/AppBase.cpp b/vulkan/AppBase.cpp index cf0f496..1aa2a6d 100644 --- a/vulkan/AppBase.cpp +++ b/vulkan/AppBase.cpp @@ -1,4 +1,11 @@ #include "AppBase.h" +#include + + +void VK_CHECK(VkResult ret) +{ + assert(ret == VK_SUCCESS); +} std::vector AppBase::readFile(const std::string& enginePath) { @@ -55,6 +62,18 @@ bool AppBase::checkValidationLayerSupport() { return true; } +void checkInstanceExtensions() { + uint32_t extensionCount = 0; + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); + + std::vector extensions(extensionCount); + vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, extensions.data()); + + logOut << "Android Available instance extensions:" << std::endl; + for (const auto& extension : extensions) { + logOut << "ExtensionName:" << extension.extensionName << " Version:" << extension.specVersion << std::endl; + } +} void AppBase::createInstance() { @@ -84,7 +103,14 @@ void AppBase::createInstance() for(int i = 0; i < glfwExtensionCount; i++) { extensions.push_back(glfwExtensions[i]); } +#else + checkInstanceExtensions(); + // Android: 手动添加必要的扩展 + extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + extensions.push_back(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME); + // 可选:添加这些扩展以获得更好的兼容性 + extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); #endif if (enableValidationLayers) { @@ -160,7 +186,7 @@ QueueFamilyIndices AppBase::findQueueFamilies(VkPhysicalDevice device, VkSurface indices.graphicsFamily = i; } VkBool32 presentSupport = false; - vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); + VK_CHECK(vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport)); if (presentSupport) { indices.presentFamily = i; } diff --git a/vulkan/AppBase.h b/vulkan/AppBase.h index 639fc43..12cfada 100644 --- a/vulkan/AppBase.h +++ b/vulkan/AppBase.h @@ -1,23 +1,28 @@ #ifndef VULKAN_UTILS_H #define VULKAN_UTILS_H -#define VK_CHECK(x) \ - do \ - { \ - VkResult err = x; \ - if (err) \ - { \ - assert(false); \ - } \ - } while (0) +//#define VK_CHECK(x) \ +// do \ +// { \ +// VkResult ret = x; \ +// if (ret != VK_SUCCESS) \ +// { \ +// assert(false); \ +// } \ +// } while (0) + #ifdef _WIN32 #define logOut std::cout #define GLFW_INCLUDE_VULKAN #include #else + #include "../app/src/main/cpp/AndroidOut.h" #define logOut aout + #define VK_USE_PLATFORM_ANDROID_KHR #include + #include + #include #endif @@ -29,7 +34,9 @@ #include #include #include -#include + +void VK_CHECK(VkResult ret); + struct QueueFamilyIndices { std::optional graphicsFamily; diff --git a/vulkan/Application.cpp b/vulkan/Application.cpp index d9ba85b..11c61a9 100644 --- a/vulkan/Application.cpp +++ b/vulkan/Application.cpp @@ -44,7 +44,7 @@ void Application::createSurface() { } #else - VkSurfaceKHR surface{}; + //VkSurfaceKHR surface{}; VkAndroidSurfaceCreateInfoKHR info{VK_STRUCTURE_TYPE_ANDROID_SURFACE_CREATE_INFO_KHR}; @@ -73,6 +73,7 @@ void Application::initVulkan() createCommandPool(); // 创建命令池 createCommandBuffer(); // 创建命令缓冲区 createSyncObjects(); // 创建同步对象 + inited = true; } void Application::createImageViews() { @@ -259,6 +260,7 @@ void Application::createSwapChain() { swapChainImageFormat = surfaceFormat.format; swapChainExtent = extent; + MAX_FRAMES_IN_FLIGHT = imageCount; } void Application::createPipelineLayout() { @@ -447,12 +449,11 @@ void Application::createCommandBuffer() void Application::createSyncObjects() { - int swapChainImageCount = swapChainImages.size(); - imageAvailableSemaphores.resize(swapChainImageCount); - renderFinishedSemaphores.resize(swapChainImageCount); + imageAvailableSemaphores.resize(MAX_FRAMES_IN_FLIGHT); + renderFinishedSemaphores.resize(MAX_FRAMES_IN_FLIGHT); inFlightFences.resize(MAX_FRAMES_IN_FLIGHT); - imagesInFlight.resize(swapChainImageCount, VK_NULL_HANDLE); + imagesInFlight.resize(MAX_FRAMES_IN_FLIGHT, VK_NULL_HANDLE); VkSemaphoreCreateInfo semaphoreInfo{}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; @@ -462,7 +463,7 @@ void Application::createSyncObjects() fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; // 为每个交换链图像创建信号量 - for (size_t i = 0; i < swapChainImageCount; i++) { + for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[i]) != VK_SUCCESS || vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create synchronization objects!"); @@ -516,15 +517,14 @@ void Application::recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t im } } -void Application::drawFrame() { +void Application::drawFrame() +{ // 1. 等待前一帧完成 vkWaitForFences(device, 1, &inFlightFences[currentFrame], VK_TRUE, UINT64_MAX); // 2. 获取交换链图像 - 使用当前帧的信号量 uint32_t imageIndex; - VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, - imageAvailableSemaphores[currentFrame], - VK_NULL_HANDLE, &imageIndex); + VkResult result = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX,imageAvailableSemaphores[currentFrame],VK_NULL_HANDLE, &imageIndex); if (result != VK_SUCCESS) { throw std::runtime_error("failed to acquire swap chain image!"); diff --git a/vulkan/Application.h b/vulkan/Application.h index 549922f..f56cde2 100644 --- a/vulkan/Application.h +++ b/vulkan/Application.h @@ -11,7 +11,6 @@ class Application : public AppBase public: void run(); -protected: void initVulkan(); void initWindow(); void createSurface(); @@ -31,6 +30,7 @@ protected: void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex); void createSyncObjects(); void drawFrame(); + bool isInited() { return inited; } private: VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; // 物理设备 VkDevice device; // 逻辑设备 @@ -55,4 +55,5 @@ private: std::vector imagesInFlight; // 跟踪每个图像的使用状态 int currentFrame = 0; int MAX_FRAMES_IN_FLIGHT = 2; + bool inited = false; }; \ No newline at end of file