/
redgpu
/
Vulkore
Обзор
Документация
Войти
/
redgpu
/
Vulkore
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
tests/context_test.cpp
278 строк
10 KB
Nikhil Ludder
Mali/UMA portability: fix non-coherent transfers + expand test suite 52->82 (#1)
18 июл 2026, 08:31
18 июл 2026, 08:31
e905f6c
Код
Авторство
О чём код?
#include <vulkore/vulkore.hpp> #include <gtest/gtest.h> #include <cstdlib> #include <optional> #include <string> #include <vector> namespace { // RAII save/restore for VULKORE_DEVICE so selection tests do not disturb the // value the outer test-matrix runner exported. class ScopedEnv { public: ScopedEnv(const char* name, const char* value) : name_(name) { if (const char* old = std::getenv(name)) saved_ = old; if (value) { ::setenv(name, value, /*overwrite=*/1); } else { ::unsetenv(name); } } ~ScopedEnv() { if (saved_) { ::setenv(name_.c_str(), saved_->c_str(), 1); } else { ::unsetenv(name_.c_str()); } } private: std::string name_; std::optional<std::string> saved_; }; bool contains_ci(const std::string& haystack, const std::string& needle) { auto lower = [](std::string s) { for (char& c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c))); return s; }; return lower(haystack).find(lower(needle)) != std::string::npos; } // The set of physical devices visible to this machine, probed ONCE with // VULKORE_DEVICE unset so device enumeration can never throw on a value that // matches nothing (Context enumerates all_devices_ before pick_device runs, so // a default Context always succeeds where >=1 device exists). Lets the // environment-specific selection tests below GTEST_SKIP cleanly on a single-GPU // machine (e.g. the Redmi/Mali phone) instead of falsely failing. const std::vector<vulkore::DeviceInfo>& visible_devices() { static const std::vector<vulkore::DeviceInfo> devices = [] { ScopedEnv env("VULKORE_DEVICE", nullptr); vulkore::Context ctx; return ctx.all_devices(); // copied out; probe Context destroyed here }(); return devices; } bool has_device_named(const std::string& needle) { for (const auto& d : visible_devices()) { if (contains_ci(d.name, needle)) return true; } return false; } size_t device_count() { return visible_devices().size(); } TEST(ContextTest, CreatesOnDefaultDevice) { vulkore::Context ctx; EXPECT_FALSE(ctx.device_name().empty()); EXPECT_NE(ctx.device(), VK_NULL_HANDLE); EXPECT_NE(ctx.queue(), VK_NULL_HANDLE); EXPECT_NE(ctx.allocator(), VK_NULL_HANDLE); EXPECT_NE(ctx.table().vkQueueSubmit, nullptr); } TEST(ContextTest, EnumeratesAtLeastThreeDevices) { vulkore::Context ctx; const auto& devices = ctx.all_devices(); // Portable invariant: a Context always sees at least its own device // (context.cpp:100-111). The dev machine exposes three drivers (RADV RENOIR + // NVIDIA RTX 2050 + llvmpipe); a single-GPU phone legitimately exposes one, // so the historical >=3 expectation is only asserted when a multi-device // setup is actually present — this must NOT fail on the Redmi/Mali phone. ASSERT_GE(devices.size(), 1u) << "a Context must see at least its own device"; if (devices.size() >= 3) { EXPECT_GE(devices.size(), 3u) << "multi-driver dev box should keep exposing >=3 devices"; } // Field validation runs on every visible device regardless of count. for (const auto& d : devices) { EXPECT_FALSE(d.name.empty()); EXPECT_GE(d.api_version, VK_API_VERSION_1_1); } } TEST(ContextTest, EnvVarSelectsLlvmpipe) { // Constructing a Context with an unmatched VULKORE_DEVICE THROWS // (context.cpp:186-189); skip on machines with no llvmpipe (e.g. the phone). if (!has_device_named("llvmpipe")) { GTEST_SKIP() << "no llvmpipe device visible on this machine"; } ScopedEnv env("VULKORE_DEVICE", "llvmpipe"); vulkore::Context ctx; EXPECT_TRUE(contains_ci(ctx.device_name(), "llvmpipe")) << "got: " << ctx.device_name(); EXPECT_EQ(ctx.device_properties().deviceType, VK_PHYSICAL_DEVICE_TYPE_CPU); } TEST(ContextTest, EnvVarMatchIsCaseInsensitive) { if (!has_device_named("llvmpipe")) { GTEST_SKIP() << "no llvmpipe device visible on this machine"; } ScopedEnv env("VULKORE_DEVICE", "LLVMPIPE"); vulkore::Context ctx; EXPECT_TRUE(contains_ci(ctx.device_name(), "llvmpipe")); } TEST(ContextTest, EnvVarNoMatchThrows) { ScopedEnv env("VULKORE_DEVICE", "definitely-not-a-real-gpu-9000"); EXPECT_THROW({ vulkore::Context ctx; }, vulkore::Error); } TEST(ContextTest, ExplicitIndexSelectsThatDevice) { // Unset VULKORE_DEVICE to prove the explicit index needs no env cooperation, // then check the index-0 context reports the enumerated index-0 name. ScopedEnv env("VULKORE_DEVICE", nullptr); vulkore::Context probe; const auto& devices = probe.all_devices(); ASSERT_FALSE(devices.empty()); vulkore::Context ctx(0u); EXPECT_EQ(ctx.device_index(), 0u); EXPECT_EQ(ctx.device_name(), devices[0].name); } TEST(ContextTest, ExplicitIndexBeatsEnvVar) { vulkore::Context probe; const auto& devices = probe.all_devices(); // Env var points somewhere else; explicit index must still win. ScopedEnv env("VULKORE_DEVICE", "llvmpipe"); const uint32_t last = static_cast<uint32_t>(devices.size() - 1); vulkore::Context ctx(0u); EXPECT_EQ(ctx.device_index(), 0u); (void)last; } TEST(ContextTest, ExplicitIndexOutOfRangeThrows) { EXPECT_THROW({ vulkore::Context ctx(1000u); }, vulkore::Error); } TEST(ContextTest, DefaultPolicyPrefersNonCpuDevice) { // With three devices including hardware GPUs, default must not pick the // CPU device (llvmpipe). Only meaningful when VULKORE_DEVICE is unset. ScopedEnv env("VULKORE_DEVICE", nullptr); vulkore::Context ctx; if (ctx.all_devices().size() >= 2) { bool has_gpu = false; for (const auto& d : ctx.all_devices()) { if (d.type == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU || d.type == VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU) { has_gpu = true; } } if (has_gpu) { EXPECT_NE(ctx.device_properties().deviceType, VK_PHYSICAL_DEVICE_TYPE_CPU) << "default policy picked " << ctx.device_name(); } } } TEST(ContextTest, TwoContextsOnDifferentDevicesCoexist) { // Per-context VolkDeviceTable: two live devices must not clobber each other. // Needs >=2 devices AND an llvmpipe to force the second context onto (the // env forces VULKORE_DEVICE=llvmpipe for b, which throws without one) — so skip // on a single-GPU machine instead of failing. if (device_count() < 2 || !has_device_named("llvmpipe")) { GTEST_SKIP() << "need >=2 devices incl. llvmpipe to pair two drivers"; } ScopedEnv env("VULKORE_DEVICE", nullptr); vulkore::Context a; // default policy -> a hardware GPU when present if (contains_ci(a.device_name(), "llvmpipe")) { // Default already picked the CPU device; no distinct second driver to pair. GTEST_SKIP() << "default device is llvmpipe; no distinct second driver"; } ScopedEnv env2("VULKORE_DEVICE", "llvmpipe"); vulkore::Context b; EXPECT_NE(a.device(), b.device()); a.wait_idle(); b.wait_idle(); } TEST(ContextTest, ErrorMessagesAreDescriptive) { // Unmatched VULKORE_DEVICE: message says "available:" and lists real device // name(s) so the user can see what to pick (context.cpp:186-189). { ScopedEnv env("VULKORE_DEVICE", "definitely-not-a-real-gpu-9000"); try { vulkore::Context ctx; FAIL() << "expected Context to throw on unmatched VULKORE_DEVICE"; } catch (const vulkore::Error& e) { const std::string msg = e.what(); EXPECT_NE(msg.find("available:"), std::string::npos) << msg; ASSERT_FALSE(visible_devices().empty()); EXPECT_NE(msg.find(visible_devices().front().name), std::string::npos) << "message should list the available device names: " << msg; } } // Out-of-range explicit index: message says "out of range" (context.cpp:175-177). { ScopedEnv env("VULKORE_DEVICE", nullptr); try { vulkore::Context ctx(1000u); FAIL() << "expected Context(1000) to throw"; } catch (const vulkore::Error& e) { EXPECT_NE(std::string(e.what()).find("out of range"), std::string::npos) << e.what(); } } } // Portable smoke test locking the physical-device assumptions the library // relies on. Every real device satisfies these (Mali-G57 measured: 1.1 / atom // 64 / push 256 / has HOST_VISIBLE / maxBoundDescriptorSets 4). Queries via the // core-1.1 Properties2 path — note the vendored headers spell the struct type // with an UNDERSCORE, VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 (the KHR // ...PROPERTIES2 form does not exist here). vkGetPhysicalDeviceProperties2 is // core in the Vulkan 1.1 instance Context creates. TEST(ContextTest, DeviceProfileInvariants) { vulkore::Context ctx; ASSERT_NE(vkGetPhysicalDeviceProperties2, nullptr) << "core-1.1 vkGetPhysicalDeviceProperties2 must be loaded on a 1.1 instance"; VkPhysicalDeviceProperties2 props2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2}; vkGetPhysicalDeviceProperties2(ctx.physical_device(), &props2); const VkPhysicalDeviceProperties& p = props2.properties; const VkPhysicalDeviceLimits& lim = p.limits; VkPhysicalDeviceMemoryProperties mem{}; vkGetPhysicalDeviceMemoryProperties(ctx.physical_device(), &mem); // apiVersion >= 1.1 — the Context requests 1.1; Mali-G57 is exactly 1.1, so // any 1.2/1.3 assumption the library made would break there. EXPECT_GE(p.apiVersion, VK_API_VERSION_1_1) << "device '" << ctx.device_name() << "' apiVersion below the 1.1 floor"; // nonCoherentAtomSize is the flush/invalidate rounding granularity on // non-coherent memory; it must be a power of two and > 0 (VMA rounds ranges // to it — see the Mali coherency fix). const VkDeviceSize atom = lim.nonCoherentAtomSize; EXPECT_GT(atom, 0u) << "nonCoherentAtomSize must be > 0"; EXPECT_EQ(atom & (atom - 1), 0u) << "nonCoherentAtomSize " << atom << " is not a power of two"; // maxPushConstantsSize >= 128 — the compile-time POD cap in launch.hpp // (make_launch_arg's static_assert sizeof(U) <= 128). A device offering less // could reject a POD the cap accepts. EXPECT_GE(lim.maxPushConstantsSize, 128u) << "maxPushConstantsSize below the launch.hpp 128-byte POD cap"; // At least one HOST_VISIBLE memory type — the direct (mappable) transfer path // and every host<->device copy depend on one existing. bool has_host_visible = false; for (uint32_t i = 0; i < mem.memoryTypeCount; ++i) { if (mem.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) { has_host_visible = true; break; } } EXPECT_TRUE(has_host_visible) << "no HOST_VISIBLE memory type on '" << ctx.device_name() << "'"; // maxBoundDescriptorSets >= 1 — kernels bind set 0 (Mali measured 4). EXPECT_GE(lim.maxBoundDescriptorSets, 1u); } } // namespace