diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9737e5b..2d7478f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,14 +2,21 @@ name: Build and Release on: push: - branches: [ master, main ] + branches: + - master + - main + - 'releases/**' tags: - - 'v*' + - '*' pull_request: - branches: [ master, main ] + branches: + - master + - main + workflow_dispatch: jobs: build-windows: + name: Build Windows runs-on: windows-latest env: VULKAN_SDK: C:\VulkanSDK\1.3.290.0 @@ -71,21 +78,115 @@ jobs: name: windows-bin path: RacingEngine-Windows.zip + build-linux: + name: Build Linux + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Dependencies + run: | + sudo apt-get update + sudo apt-get install -y libvulkan-dev libglfw3-dev libglm-dev build-essential cmake libxkbcommon-dev libxcursor-dev libxi-dev libxinerama-dev vulkan-tools glslang-tools + + - name: Clone dependencies + run: | + mkdir -p external + git clone --depth 1 https://github.com/glfw/glfw.git external/glfw + git clone --depth 1 https://github.com/g-truc/glm.git external/glm + + - name: Compile Shaders + run: | + mkdir -p shaders/compiled + glslangValidator -V shaders/raygen.rgen -o shaders/compiled/raygen.rgen.spv --target-env vulkan1.3 + glslangValidator -V shaders/miss.rmiss -o shaders/compiled/miss.rmiss.spv --target-env vulkan1.3 + glslangValidator -V shaders/closesthit.rchit -o shaders/compiled/closesthit.rchit.spv --target-env vulkan1.3 + glslangValidator -V shaders/shadow.rmiss -o shaders/compiled/shadow.rmiss.spv --target-env vulkan1.3 + glslangValidator -V shaders/tonemap.comp -o shaders/compiled/tonemap.comp.spv --target-env vulkan1.3 + + - name: Configure CMake + run: cmake -B build -S . -DCMAKE_BUILD_TYPE=Release + + - name: Build + run: cmake --build build + + - name: Package + run: | + mkdir release + cp build/RacingEngine release/ + mkdir -p release/shaders/compiled + cp shaders/compiled/*.spv release/shaders/compiled/ + if [ -d "assets" ]; then + cp -r assets release/ + fi + tar -czvf RacingEngine-Linux.tar.gz -C release . + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: linux-bin + path: RacingEngine-Linux.tar.gz + + build-android: + name: Build Android + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up NDK + run: echo "Using pre-installed NDK at $ANDROID_NDK_LATEST_HOME" + + - name: Clone dependencies + run: | + mkdir -p external + git clone --depth 1 https://github.com/g-truc/glm.git external/glm + + - name: Configure and Build + run: | + cmake -B build-android -S . \ + -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK_LATEST_HOME/build/cmake/android.toolchain.cmake \ + -DANDROID_ABI=arm64-v8a \ + -DANDROID_PLATFORM=android-29 \ + -DANDROID_NATIVE_API_LEVEL=29 \ + -DCMAKE_BUILD_TYPE=Release + cmake --build build-android + + - name: Package + run: cp build-android/libRacingEngine.so . + + - name: Upload Artifact + uses: actions/upload-artifact@v4 + with: + name: android-bin + path: libRacingEngine.so + release: - needs: [build-windows] - if: startsWith(github.ref, 'refs/tags/') + name: Create Release + needs: [build-windows, build-linux, build-android] + if: github.event_name != 'pull_request' runs-on: ubuntu-latest permissions: contents: write steps: + - name: Checkout code + uses: actions/checkout@v4 + - name: Download all artifacts uses: actions/download-artifact@v4 + with: + path: artifacts - name: List artifacts - run: ls -R + run: ls -R artifacts - - name: Create Release + - name: Publish Release uses: softprops/action-gh-release@v2 with: + tag_name: ${{ github.ref_type == 'tag' && github.ref_name || 'latest' }} files: | - windows-bin/RacingEngine-Windows.zip + artifacts/windows-bin/RacingEngine-Windows.zip + artifacts/linux-bin/RacingEngine-Linux.tar.gz + artifacts/android-bin/libRacingEngine.so + generate_release_notes: true + prerelease: ${{ github.ref_type != 'tag' }} + fail_on_unmatched_files: false diff --git a/CMakeLists.txt b/CMakeLists.txt index d2ae20f..c8e26af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,15 +65,17 @@ else() endif() # GLFW -# Check if external/glfw exists, otherwise try system package -if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/glfw/CMakeLists.txt") - set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) - set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) - set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) - set(GLFW_USE_MSVC_RUNTIME_LIBRARY_DLL ON CACHE BOOL "" FORCE) - add_subdirectory(external/glfw) -else() - find_package(glfw3 REQUIRED) +if(NOT ANDROID) + # Check if external/glfw exists, otherwise try system package + if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/external/glfw/CMakeLists.txt") + set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) + set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) + set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) + set(GLFW_USE_MSVC_RUNTIME_LIBRARY_DLL ON CACHE BOOL "" FORCE) + add_subdirectory(external/glfw) + else() + find_package(glfw3 REQUIRED) + endif() endif() # Add GLM (header-only) @@ -97,15 +99,22 @@ endif() file(GLOB_RECURSE CPP_SOURCES "src/*.cpp") file(GLOB_RECURSE HEADERS "src/*.h") -# Create executable -add_executable(${PROJECT_NAME} - ${CPP_SOURCES} - ${HEADERS} -) +# Create target +if(ANDROID) + add_library(${PROJECT_NAME} SHARED + ${CPP_SOURCES} + ${HEADERS} + ) +else() + add_executable(${PROJECT_NAME} + ${CPP_SOURCES} + ${HEADERS} + ) +endif() # Link libraries -if(TARGET glfw) - target_link_libraries(${PROJECT_NAME} Vulkan::Vulkan glfw) +if(ANDROID) + target_link_libraries(${PROJECT_NAME} Vulkan::Vulkan android log) else() target_link_libraries(${PROJECT_NAME} Vulkan::Vulkan glfw) endif() diff --git a/src/Camera.cpp b/src/Camera.cpp index b3bba55..56c70a9 100644 --- a/src/Camera.cpp +++ b/src/Camera.cpp @@ -50,6 +50,7 @@ void Camera::update(float deltaTime) { // For now, all updates happen in processKeyboard } +#ifndef ANDROID void Camera::processKeyboard(GLFWwindow* window, float deltaTime) { float velocity = movementSpeed * deltaTime; @@ -75,6 +76,7 @@ void Camera::processKeyboard(GLFWwindow* window, float deltaTime) { else movementSpeed = 5.0f; } +#endif void Camera::processMouseMovement(float xOffset, float yOffset) { xOffset *= mouseSensitivity; diff --git a/src/Camera.h b/src/Camera.h index 35841c5..6510580 100644 --- a/src/Camera.h +++ b/src/Camera.h @@ -2,7 +2,9 @@ #include #include +#ifndef ANDROID #include +#endif class Camera { public: @@ -21,7 +23,9 @@ class Camera { void reset(); // Input handling +#ifndef ANDROID void processKeyboard(GLFWwindow* window, float deltaTime); +#endif void processMouseMovement(float xOffset, float yOffset); void processMouseScroll(float yOffset); diff --git a/src/VulkanRTPipeline.cpp b/src/VulkanRTPipeline.cpp index 7afd082..4aad84e 100644 --- a/src/VulkanRTPipeline.cpp +++ b/src/VulkanRTPipeline.cpp @@ -4,7 +4,7 @@ #include #include -void VulkanRTPipeline::loadRTPipelineFunctions(VkDevice device) { +void VulkanRTPipeline::loadRTPipelineFunctions(VkInstance instance, VkDevice device) { vkGetRayTracingShaderGroupHandlesKHR = (PFN_vkGetRayTracingShaderGroupHandlesKHR) vkGetDeviceProcAddr(device, "vkGetRayTracingShaderGroupHandlesKHR"); vkCreateRayTracingPipelinesKHR = (PFN_vkCreateRayTracingPipelinesKHR) @@ -12,6 +12,13 @@ void VulkanRTPipeline::loadRTPipelineFunctions(VkDevice device) { vkCmdTraceRaysKHR = (PFN_vkCmdTraceRaysKHR) vkGetDeviceProcAddr(device, "vkCmdTraceRaysKHR"); + vkGetPhysicalDeviceProperties2KHR = (PFN_vkGetPhysicalDeviceProperties2KHR) + vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2KHR"); + if (!vkGetPhysicalDeviceProperties2KHR) { + vkGetPhysicalDeviceProperties2KHR = (PFN_vkGetPhysicalDeviceProperties2KHR) + vkGetInstanceProcAddr(instance, "vkGetPhysicalDeviceProperties2"); + } + std::cout << "RT pipeline function pointers loaded\n"; } @@ -374,7 +381,15 @@ void VulkanRTPipeline::createShaderBindingTable(VkPhysicalDevice physicalDevice, VkPhysicalDeviceProperties2 deviceProps{}; deviceProps.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; deviceProps.pNext = &rtProperties; - vkGetPhysicalDeviceProperties2(physicalDevice, &deviceProps); + + if (vkGetPhysicalDeviceProperties2KHR) { + vkGetPhysicalDeviceProperties2KHR(physicalDevice, &deviceProps); + } else { + std::cerr << "Warning: vkGetPhysicalDeviceProperties2 not available, RT properties may be invalid\n"; + // Fallback to basic properties, though rtProperties will remain uninitialized by the call + VkPhysicalDeviceProperties basicProps; + vkGetPhysicalDeviceProperties(physicalDevice, &basicProps); + } handleSize = rtProperties.shaderGroupHandleSize; diff --git a/src/VulkanRTPipeline.h b/src/VulkanRTPipeline.h index eaa5eea..d403516 100644 --- a/src/VulkanRTPipeline.h +++ b/src/VulkanRTPipeline.h @@ -51,8 +51,9 @@ class VulkanRTPipeline { PFN_vkGetRayTracingShaderGroupHandlesKHR vkGetRayTracingShaderGroupHandlesKHR; PFN_vkCreateRayTracingPipelinesKHR vkCreateRayTracingPipelinesKHR; PFN_vkCmdTraceRaysKHR vkCmdTraceRaysKHR; + PFN_vkGetPhysicalDeviceProperties2KHR vkGetPhysicalDeviceProperties2KHR; - void loadRTPipelineFunctions(VkDevice device); + void loadRTPipelineFunctions(VkInstance instance, VkDevice device); void createDescriptorSetLayout(VkDevice device); void createDescriptorPool(VkDevice device); void createDescriptorSet(VkDevice device, VkAccelerationStructureKHR tlas, diff --git a/src/VulkanRayTracing.cpp b/src/VulkanRayTracing.cpp index 329f7ef..efdf6d5 100644 --- a/src/VulkanRayTracing.cpp +++ b/src/VulkanRayTracing.cpp @@ -86,7 +86,11 @@ void VulkanRayTracing::createAccumulationImage(VkPhysicalDevice physicalDevice, // Prepare for external memory export (for CUDA interop) VkExportMemoryAllocateInfo exportAllocInfo{}; exportAllocInfo.sType = VK_STRUCTURE_TYPE_EXPORT_MEMORY_ALLOCATE_INFO; +#ifdef _WIN32 exportAllocInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_WIN32_BIT; +#else + exportAllocInfo.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_OPAQUE_FD_BIT; +#endif VkMemoryAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; diff --git a/src/main.cpp b/src/main.cpp index 4eebb1f..37657c3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,5 +1,7 @@ +#ifndef ANDROID #define GLFW_INCLUDE_VULKAN #include +#endif #include "VulkanSwapchain.h" #include "VulkanRayTracing.h" @@ -72,6 +74,9 @@ const std::vector deviceExtensions = { #ifdef _WIN32 "VK_KHR_external_memory_win32", "VK_KHR_external_semaphore_win32", +#else + "VK_KHR_external_memory_fd", + "VK_KHR_external_semaphore_fd", #endif }; @@ -120,7 +125,9 @@ class RacingEngine { } private: +#ifndef ANDROID GLFWwindow* window; +#endif VkInstance instance; VkDebugUtilsMessengerEXT debugMessenger = VK_NULL_HANDLE; VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; @@ -260,6 +267,7 @@ class RacingEngine { // UX State Tracking void updateWindowTitle() { +#ifndef ANDROID glm::vec3 pos = camera.getPosition(); char title[512]; uint32_t totalSamples = accumulationFrames * 8; // 8 SPP per frame @@ -271,9 +279,11 @@ class RacingEngine { "IZTAPALAPA PATH TRACER | FPS: %.0f | %.2fms | %u samples | Pos: (%.1f, %.1f, %.1f) | AI: %s | Cursor: %s", currentFPS, frameTimeMs, totalSamples, pos.x, pos.y, pos.z, denoiserStatus, cursorStatus); glfwSetWindowTitle(window, title); +#endif } void initWindow() { +#ifndef ANDROID glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); @@ -286,16 +296,20 @@ class RacingEngine { glfwSetKeyCallback(window, keyCallback); glfwSetScrollCallback(window, scrollCallback); glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_DISABLED); +#endif // Initialize camera with correct aspect ratio camera = Camera(45.0f, (float)WIDTH / (float)HEIGHT, 0.1f, 1000.0f); +#ifndef ANDROID std::cout << "GLFW window created successfully!\n"; std::cout << "Camera controls: WASD - move, QE - up/down, Mouse - look, Shift - faster\n"; std::cout << " TAB - toggle cursor lock, Scroll - zoom (FOV)\n"; std::cout << "Press D to toggle AI Denoiser (Tensor Cores)\n"; +#endif } +#ifndef ANDROID static void scrollCallback(GLFWwindow* window, double xoffset, double yoffset) { auto engine = reinterpret_cast(glfwGetWindowUserPointer(window)); if (engine->cursorLocked) { @@ -303,7 +317,9 @@ class RacingEngine { engine->updateWindowTitle(); } } +#endif +#ifndef ANDROID static void mouseCallback(GLFWwindow* window, double xposIn, double yposIn) { auto engine = reinterpret_cast(glfwGetWindowUserPointer(window)); @@ -375,6 +391,7 @@ class RacingEngine { engine->updateWindowTitle(); } } +#endif void initVulkan() { createInstance(); @@ -449,7 +466,7 @@ class RacingEngine { std::cout << "=======================================\n\n"; // Create RT pipeline - rtPipeline.loadRTPipelineFunctions(device); + rtPipeline.loadRTPipelineFunctions(instance, device); rtPipeline.createCameraBuffer(physicalDevice, device); rtPipeline.createDescriptorSetLayout(device); rtPipeline.createDescriptorPool(device); @@ -517,10 +534,16 @@ class RacingEngine { } void createSurface() { +#ifndef ANDROID if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("Failed to create window surface!"); } std::cout << "Window surface created!\n"; +#else + // Surface creation on Android is handled differently (e.g. via ANativeWindow) + // For now we just initialize the member to VK_NULL_HANDLE + surface = VK_NULL_HANDLE; +#endif } void pickPhysicalDevice() { @@ -718,11 +741,19 @@ class RacingEngine { } std::vector getRequiredExtensions() { + std::vector extensions; +#ifndef ANDROID uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); - std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); + for (uint32_t i = 0; i < glfwExtensionCount; i++) { + extensions.push_back(glfwExtensions[i]); + } +#else + extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME); + extensions.push_back("VK_KHR_android_surface"); +#endif if (enableValidationLayers) { extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); @@ -898,7 +929,11 @@ class RacingEngine { std::cout << " DEJANDO EN RIDICULO A LOS AAA\n"; std::cout << "==========================================\n\n"; +#ifdef ANDROID + while (true) { // TODO: Proper Android event loop +#else while (!glfwWindowShouldClose(window)) { +#endif // Calculate delta time and frame time auto currentTime = std::chrono::high_resolution_clock::now(); deltaTime = std::chrono::duration(currentTime - lastFrameTime).count(); @@ -915,8 +950,10 @@ class RacingEngine { } // Process input +#ifndef ANDROID glfwPollEvents(); camera.processKeyboard(window, deltaTime); +#endif camera.update(deltaTime); // Check if camera moved - reset accumulation if so @@ -1439,13 +1476,16 @@ class RacingEngine { vkDestroySurfaceKHR(instance, surface, nullptr); vkDestroyInstance(instance, nullptr); +#ifndef ANDROID glfwDestroyWindow(window); glfwTerminate(); +#endif std::cout << "Cleanup complete.\n"; } }; +#ifndef ANDROID int main() { RacingEngine engine; @@ -1458,3 +1498,4 @@ int main() { return EXIT_SUCCESS; } +#endif