diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..93615d0 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,78 @@ +name: Unit tests + +on: + pull_request: + push: + paths-ignore: + - '.gitignore' + - 'LICENSE' + - 'README.md' + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: [ubuntu-24.04] + arch: [x86_64] + + runs-on: ${{ matrix.os }} + if: "!contains(github.event.head_commit.message, 'ci skip')" + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - uses: NcStudios/VulkanCI@v1.0 + + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get -y install mesa-common-dev clang libsdl2-2.0-0 libsdl2-dev build-essential libvulkan-dev + + # Build the lib + - name: Build MacroLibX + run: make -j DEBUG=true + + # Force xmake to a specific folder (for cache) + - name: Set xmake env + run: echo "XMAKE_GLOBALDIR=${{ runner.workspace }}/xmake-global" >> $GITHUB_ENV + + # Install xmake + - name: Setup xmake + if: ${{ matrix.confs.plat != 'mingw' }} + uses: xmake-io/github-action-setup-xmake@v1 + with: + xmake-version: latest + actions-cache-folder: .xmake-cache-W${{ steps.cache_key.outputs.key }} + + # Update xmake repository (in order to have the file that will be cached) + - name: Update xmake repository + run: xmake repo --update + + # Fetch xmake dephash + - name: Retrieve dependencies hash + id: dep_hash + run: echo "hash=$(xmake l utils.ci.packageskey)" >> $GITHUB_OUTPUT + + - name: Install unit tester + run: git clone https://github.com/seekrs/MacroUnitTest.git ../MacroUnitTest + + # Setup compilation mode and install project dependencies + - name: Configure xmake and install dependencies + run: | + cd ../MacroUnitTest + xmake config --toolchain=clang --ccache=n --yes + + # Save dependencies + - name: Save cached xmake dependencies + if: ${{ !steps.restore-depcache.outputs.cache-hit }} + uses: actions/cache/save@v4 + with: + path: ${{ env.XMAKE_GLOBALDIR }}/.xmake/packages + key: ${{ steps.restore-depcache.outputs.cache-primary-key }} + + - name: Build and run unit tester + run: | + cd ../MacroUnitTest + xmake run MacroUnitTest --headless --path="${{ runner.workspace }}/MacroLibX/libmlx.so" diff --git a/README.md b/README.md index d2a2111..63f0097 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,9 @@
+
+ +
###### MacroLibX, a rewrite of 42 School's MiniLibX using SDL2 and Vulkan. diff --git a/runtime/Includes/Core/UUID.h b/runtime/Includes/Core/UUID.h index 9911302..2caa3f7 100644 --- a/runtime/Includes/Core/UUID.h +++ b/runtime/Includes/Core/UUID.h @@ -7,9 +7,7 @@ namespace mlx { public: UUID(); - UUID(std::uint64_t uuid); - - inline operator std::uint64_t() const { return m_uuid; } + inline operator std::uint64_t() const noexcept { return m_uuid; } private: std::uint64_t m_uuid; diff --git a/runtime/Sources/Core/SDLManager.cpp b/runtime/Sources/Core/SDLManager.cpp index 5f3aa50..51aa85d 100644 --- a/runtime/Sources/Core/SDLManager.cpp +++ b/runtime/Sources/Core/SDLManager.cpp @@ -22,7 +22,7 @@ namespace mlx MLX_PROFILE_FUNCTION(); s_instance = this; - m_drop_sdl_responsability = SDL_WasInit(SDL_INIT_VIDEO); + m_drop_sdl_responsability = SDL_WasInit(SDL_INIT_VIDEO) || std::getenv("MLX_HEADLESS_MODE") != nullptr; if(m_drop_sdl_responsability) // is case the mlx is running in a sandbox like MacroUnitTester where SDL is already init return; SDL_SetMemoryFunctions(MemManager::Get().Malloc, MemManager::Get().Calloc, MemManager::Get().Realloc, MemManager::Get().Free); diff --git a/runtime/Sources/Core/UUID.cpp b/runtime/Sources/Core/UUID.cpp index cf2855a..5a370dd 100644 --- a/runtime/Sources/Core/UUID.cpp +++ b/runtime/Sources/Core/UUID.cpp @@ -7,7 +7,14 @@ namespace mlx static std::random_device random_device; static std::mt19937_64 engine(random_device()); static std::uniform_int_distribution uniform_distribution; + static std::unordered_set registry; - UUID::UUID() : m_uuid(uniform_distribution(engine)) {} - UUID::UUID(std::uint64_t uuid) : m_uuid(uuid) {} + UUID::UUID() + { + do + { + m_uuid = uniform_distribution(engine); + } while(registry.contains(m_uuid)); + registry.emplace(m_uuid); + } } diff --git a/runtime/Sources/Renderer/Buffer.cpp b/runtime/Sources/Renderer/Buffer.cpp index c34f965..f8ef1b9 100644 --- a/runtime/Sources/Renderer/Buffer.cpp +++ b/runtime/Sources/Renderer/Buffer.cpp @@ -81,7 +81,7 @@ namespace mlx VkCommandBuffer cmd = kvfCreateCommandBuffer(RenderCore::Get().GetDevice()); kvfBeginCommandBuffer(cmd, VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT); - kvfCopyBufferToBuffer(cmd, m_buffer, buffer.Get(), m_size); + kvfCopyBufferToBuffer(cmd, m_buffer, buffer.Get(), m_size, 0, 0); kvfEndCommandBuffer(cmd); VkFence fence = kvfCreateFence(RenderCore::Get().GetDevice()); kvfSubmitSingleTimeCommandBuffer(RenderCore::Get().GetDevice(), cmd, KVF_GRAPHICS_QUEUE, fence); diff --git a/runtime/Sources/Renderer/RenderCore.cpp b/runtime/Sources/Renderer/RenderCore.cpp index 4bddadc..1bbe5f6 100644 --- a/runtime/Sources/Renderer/RenderCore.cpp +++ b/runtime/Sources/Renderer/RenderCore.cpp @@ -87,15 +87,24 @@ namespace mlx kvfSetValidationErrorCallback(&ValidationErrorCallback); kvfSetValidationWarningCallback(&WarningCallback); - mlx_window_create_info info{}; - info.title = ""; - info.width = 1; - info.height = 1; - Window window(&info, true); - std::vector instance_extensions = window.GetRequiredVulkanInstanceExtentions(); - #ifdef MLX_PLAT_MACOS - instance_extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); - #endif + std::vector instance_extensions; + VkSurfaceKHR surface = VK_NULL_HANDLE; + std::unique_ptr window; + + bool is_headless = std::getenv("MLX_HEADLESS_MODE") != nullptr; + + if(!is_headless) + { + mlx_window_create_info info{}; + info.title = ""; + info.width = 1; + info.height = 1; + window = std::make_unique(&info, true); + instance_extensions = window->GetRequiredVulkanInstanceExtentions(); + #ifdef MLX_PLAT_MACOS + instance_extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + #endif + } m_instance = kvfCreateInstance(instance_extensions.data(), instance_extensions.size()); DebugLog("Vulkan: instance created"); @@ -103,25 +112,34 @@ namespace mlx loader->LoadInstance(m_instance); LoadKVFInstanceVulkanFunctionPointers(); - VkSurfaceKHR surface = window.CreateVulkanSurface(m_instance); + if(!is_headless) + { + surface = window->CreateVulkanSurface(m_instance); + m_physical_device = kvfPickGoodDefaultPhysicalDevice(m_instance, surface); + } + else + m_physical_device = kvfPickGoodPhysicalDevice(m_instance, VK_NULL_HANDLE, nullptr, 0); - m_physical_device = kvfPickGoodDefaultPhysicalDevice(m_instance, surface); + Verify(m_physical_device != VK_NULL_HANDLE, "Could not find a suitable physical device"); // just for style VkPhysicalDeviceProperties props; vkGetPhysicalDeviceProperties(m_physical_device, &props); DebugLog("Vulkan: physical device picked '%'", props.deviceName); - const char* device_extensions[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; + std::vector device_extensions; + if(!is_headless) + device_extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); VkPhysicalDeviceFeatures features{}; vkGetPhysicalDeviceFeatures(m_physical_device, &features); - m_device = kvfCreateDevice(m_physical_device, device_extensions, sizeof(device_extensions) / sizeof(device_extensions[0]), &features); + m_device = kvfCreateDevice(m_physical_device, device_extensions.data(), device_extensions.size(), &features); DebugLog("Vulkan: logical device created"); loader->LoadDevice(m_device); LoadKVFDeviceVulkanFunctionPointers(); - vkDestroySurfaceKHR(m_instance, surface, nullptr); + if(surface != VK_NULL_HANDLE) + vkDestroySurfaceKHR(m_instance, surface, nullptr); VkAllocationCallbacks callbacks; callbacks.pUserData = nullptr; diff --git a/runtime/Sources/Renderer/Vulkan/VulkanLoader.cpp b/runtime/Sources/Renderer/Vulkan/VulkanLoader.cpp index 4f63d54..7fc5483 100644 --- a/runtime/Sources/Renderer/Vulkan/VulkanLoader.cpp +++ b/runtime/Sources/Renderer/Vulkan/VulkanLoader.cpp @@ -28,6 +28,10 @@ namespace mlx { static inline PFN_vkVoidFunction vkGetInstanceProcAddrStub(Handle context, const char* name) { + bool is_headless = std::getenv("MLX_HEADLESS_MODE") != nullptr; + if(is_headless && std::string_view(name).find("KHR") != std::string_view::npos) + return nullptr; + PFN_vkVoidFunction function = RenderCore::Get().vkGetInstanceProcAddr(static_cast(context), name); if(!function) FatalError("Vulkan Loader: could not load '%'", name); @@ -37,6 +41,10 @@ namespace mlx static inline PFN_vkVoidFunction vkGetDeviceProcAddrStub(Handle context, const char* name) { + bool is_headless = std::getenv("MLX_HEADLESS_MODE") != nullptr; + if(is_headless && std::string_view(name).find("KHR") != std::string_view::npos) + return nullptr; + PFN_vkVoidFunction function = RenderCore::Get().vkGetDeviceProcAddr(static_cast(context), name); if(!function) FatalError("Vulkan Loader: could not load '%'", name); diff --git a/third_party/kvf.h b/third_party/kvf.h index 1308495..bda9a57 100755 --- a/third_party/kvf.h +++ b/third_party/kvf.h @@ -1475,9 +1475,6 @@ int32_t __kvfScorePhysicalDevice(VkPhysicalDevice device, VkSurfaceKHR surface, if(device_props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) score += 1000; - if(!device_features.geometryShader) - return -1; - score += device_props.limits.maxImageDimension2D; score += device_props.limits.maxBoundDescriptorSets; diff --git a/xmake.lua b/xmake.lua index 074e4ce..f7445a4 100644 --- a/xmake.lua +++ b/xmake.lua @@ -1,6 +1,6 @@ -- Global settings -add_requires("libsdl", { configs = { sdlmain = false } }) +add_requires("libsdl2", { configs = { sdlmain = false } }) add_rules("mode.debug", "mode.release", "mode.releasedbg") set_languages("cxx20", "c11") @@ -63,7 +63,7 @@ target("mlx") add_files("runtime/Sources/**.cpp") - add_packages("libsdl") + add_packages("libsdl2") if is_mode("debug") then add_defines("DEBUG") @@ -98,5 +98,5 @@ target("Test") add_defines("SDL_MAIN_HANDLED") - add_packages("libsdl") + add_packages("libsdl2") target_end()