From a6fdb5b0cd0c3bc8ea52cffac7cdb4e7faa14a64 Mon Sep 17 00:00:00 2001 From: komaruworld Date: Fri, 4 Sep 2026 18:08:36 +0300 Subject: [PATCH] Fix Vulkan icd selection #105 burmalda ili govnolda --- include/runtime/graphics_launch_policy.h | 10 +++ src/runtime/graphics_launch_policy.cc | 80 ++++++++++++++---- tests/CMakeLists.txt | 7 ++ tests/vulkan_icd_selection_test.cc | 102 +++++++++++++++++++++++ 4 files changed, 182 insertions(+), 17 deletions(-) create mode 100644 tests/vulkan_icd_selection_test.cc diff --git a/include/runtime/graphics_launch_policy.h b/include/runtime/graphics_launch_policy.h index f8ed857..ef492dc 100644 --- a/include/runtime/graphics_launch_policy.h +++ b/include/runtime/graphics_launch_policy.h @@ -1,13 +1,23 @@ #ifndef MOCKTAIL_RUNTIME_GRAPHICS_LAUNCH_POLICY_H_ #define MOCKTAIL_RUNTIME_GRAPHICS_LAUNCH_POLICY_H_ +#include #include +#include +#include #include "runtime/runtime_config.h" namespace mocktail { namespace runtime { +// Readable ICD manifest for `vendor` that this build can actually load, or an +// empty path. Directories are searched in order; a manifest built for another +// architecture is never selected. +std::string SelectVulkanIcdManifest( + const std::vector& directories, + std::string_view vendor); + // Publishes the resolved graphics backend before the managed payload updater // starts. OpenGL is a strict system EGL/GLES path; it never silently retries // through ANGLE/Vulkan or accepts a window without a real graphics context. diff --git a/src/runtime/graphics_launch_policy.cc b/src/runtime/graphics_launch_policy.cc index 1a3f934..df1ae0a 100644 --- a/src/runtime/graphics_launch_policy.cc +++ b/src/runtime/graphics_launch_policy.cc @@ -2,12 +2,14 @@ #include "runtime/frame_rate_policy.h" +#include #include #include #include #include #include #include +#include #include #include #include @@ -28,12 +30,28 @@ constexpr const char* kIcdDirectories[] = { "/usr/local/share/vulkan/icd.d", }; +constexpr char kNativeArchitecture[] = "x86_64"; + +constexpr const char* kForeignArchitectures[] = { + "i686", "i586", "i486", "i386", "x86.", "aarch64", + "arm64", "armhf", "armv7", "ppc64", "riscv64", "s390x", +}; + struct HostGpus { bool intel = false; bool nvidia = false; bool amd = false; }; +bool ForeignArchitecture(const std::string& name) { + for (const char* architecture : kForeignArchitectures) { + if (name.find(architecture) != std::string::npos) { + return true; + } + } + return false; +} + bool SetValue(const char* name, const std::string& value, std::string* error) { if (setenv(name, value.c_str(), 1) == 0) { return true; @@ -110,25 +128,11 @@ std::string FindIcdFile(const char* filename_needle) { if (filename_needle == nullptr || filename_needle[0] == '\0') { return {}; } + std::vector directories; for (const char* directory : kIcdDirectories) { - std::error_code error; - for (std::filesystem::directory_iterator iterator(directory, error), end; - !error && iterator != end; iterator.increment(error)) { - if (!iterator->is_regular_file(error)) { - continue; - } - const std::string name = iterator->path().filename().string(); - if (name.find(".json") == std::string::npos || - name.find(filename_needle) == std::string::npos) { - continue; - } - const std::string path = iterator->path().string(); - if (access(path.c_str(), R_OK) == 0) { - return path; - } - } + directories.emplace_back(directory); } - return {}; + return SelectVulkanIcdManifest(directories, filename_needle); } std::string SelectHardwareIcd(const HostGpus& gpus) { @@ -209,6 +213,48 @@ bool ApplyVulkanIcdPolicy(const HostGpus& gpus, std::string* error) { } // namespace +std::string SelectVulkanIcdManifest( + const std::vector& directories, + std::string_view vendor) { + if (vendor.empty()) { + return {}; + } + for (const std::filesystem::path& directory : directories) { + std::error_code error; + std::vector names; + for (std::filesystem::directory_iterator iterator(directory, error), end; + !error && iterator != end; iterator.increment(error)) { + if (!iterator->is_regular_file(error)) { + continue; + } + const std::string name = iterator->path().filename().string(); + if (name.find(".json") == std::string::npos || + name.find(vendor) == std::string::npos || ForeignArchitecture(name)) { + continue; + } + names.push_back(name); + } + std::sort(names.begin(), names.end()); + std::string generic; + for (const std::string& name : names) { + const std::string path = (directory / name).string(); + if (access(path.c_str(), R_OK) != 0) { + continue; + } + if (name.find(kNativeArchitecture) != std::string::npos) { + return path; + } + if (generic.empty()) { + generic = path; + } + } + if (!generic.empty()) { + return generic; + } + } + return {}; +} + bool ApplyGraphicsLaunchPolicy(const RuntimeConfig& config, std::string* error) { if (config.graphics_backend() == GraphicsBackend::kUnknown) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 4237d5f..5fad435 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -71,6 +71,12 @@ target_link_libraries(support_bundle_runtime_test PRIVATE GTest::gtest_main ) +add_executable(vulkan_icd_selection_test vulkan_icd_selection_test.cc) +target_link_libraries(vulkan_icd_selection_test PRIVATE + Mocktail::Runtime + GTest::gtest_main +) + add_executable(session_log_test session_log_test.cc) target_link_libraries(session_log_test PRIVATE Mocktail::Runtime @@ -844,6 +850,7 @@ gtest_discover_tests(payload_update_preflight_test) gtest_discover_tests(apk_provider_test) gtest_discover_tests(native_update_test) gtest_discover_tests(support_bundle_runtime_test) +gtest_discover_tests(vulkan_icd_selection_test) gtest_discover_tests(session_log_test) gtest_discover_tests(failure_dialog_test) gtest_discover_tests(frame_rate_policy_test) diff --git a/tests/vulkan_icd_selection_test.cc b/tests/vulkan_icd_selection_test.cc new file mode 100644 index 0000000..c268db1 --- /dev/null +++ b/tests/vulkan_icd_selection_test.cc @@ -0,0 +1,102 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include "runtime/graphics_launch_policy.h" + +namespace mocktail { +namespace runtime { +namespace { + +class TemporaryDirectory final { + public: + TemporaryDirectory() { + char pattern[] = "/tmp/mocktail_icd_XXXXXX"; + const char* created = mkdtemp(pattern); + if (created != nullptr) root_ = created; + } + + ~TemporaryDirectory() { + std::error_code error; + std::filesystem::remove_all(root_, error); + } + + const std::filesystem::path& root() const { return root_; } + + private: + std::filesystem::path root_; +}; + +void WriteManifest(const std::filesystem::path& path) { + std::filesystem::create_directories(path.parent_path()); + std::ofstream(path) << R"({"ICD":{"library_path":"libGLX_nvidia.so.0"}})"; +} + +TEST(VulkanIcdSelectionTest, PrefersTheNativeArchitectureManifest) { + TemporaryDirectory temporary; + WriteManifest(temporary.root() / "nvidia_icd.i686.json"); + WriteManifest(temporary.root() / "nvidia_icd.x86_64.json"); + + EXPECT_EQ(SelectVulkanIcdManifest({temporary.root()}, "nvidia_icd"), + (temporary.root() / "nvidia_icd.x86_64.json").string()); +} + +TEST(VulkanIcdSelectionTest, NeverSelectsAForeignArchitectureManifest) { + TemporaryDirectory temporary; + WriteManifest(temporary.root() / "nvidia_icd.i686.json"); + WriteManifest(temporary.root() / "radeon_icd.aarch64.json"); + + EXPECT_TRUE( + SelectVulkanIcdManifest({temporary.root()}, "nvidia_icd").empty()); + EXPECT_TRUE( + SelectVulkanIcdManifest({temporary.root()}, "radeon_icd").empty()); +} + +TEST(VulkanIcdSelectionTest, AcceptsAnUnqualifiedManifest) { + TemporaryDirectory temporary; + WriteManifest(temporary.root() / "nvidia_icd.json"); + + EXPECT_EQ(SelectVulkanIcdManifest({temporary.root()}, "nvidia_icd"), + (temporary.root() / "nvidia_icd.json").string()); +} + +TEST(VulkanIcdSelectionTest, KeepsDirectoryPrecedenceOverName) { + TemporaryDirectory temporary; + const std::filesystem::path first = temporary.root() / "first"; + const std::filesystem::path second = temporary.root() / "second"; + WriteManifest(first / "nvidia_icd.json"); + WriteManifest(second / "nvidia_icd.x86_64.json"); + + EXPECT_EQ(SelectVulkanIcdManifest({first, second}, "nvidia_icd"), + (first / "nvidia_icd.json").string()); +} + +TEST(VulkanIcdSelectionTest, IsStableWhateverTheDirectoryOrderIs) { + TemporaryDirectory temporary; + WriteManifest(temporary.root() / "nvidia_icd.x86_64.json"); + WriteManifest(temporary.root() / "nvidia_icd.json"); + WriteManifest(temporary.root() / "nvidia_icd.i686.json"); + + const std::string selected = + SelectVulkanIcdManifest({temporary.root()}, "nvidia_icd"); + EXPECT_EQ(selected, (temporary.root() / "nvidia_icd.x86_64.json").string()); + EXPECT_EQ(selected, SelectVulkanIcdManifest({temporary.root()}, + "nvidia_icd")); +} + +TEST(VulkanIcdSelectionTest, IgnoresAMissingDirectory) { + TemporaryDirectory temporary; + EXPECT_TRUE(SelectVulkanIcdManifest({temporary.root() / "absent"}, + "nvidia_icd") + .empty()); + EXPECT_TRUE(SelectVulkanIcdManifest({temporary.root()}, "").empty()); +} + +} // namespace +} // namespace runtime +} // namespace mocktail