| /* |
| * Copyright 2022 Rive |
| */ |
| |
| // Don't compile this file as part of the "tests" project. |
| #ifndef TESTING |
| |
| #include "gm.hpp" |
| #include "gmutils.hpp" |
| #include "utils/no_op_renderer.hpp" |
| #include "common/testing_window.hpp" |
| #include "common/test_harness.hpp" |
| |
| #ifdef RIVE_ANDROID |
| #include "common/rive_android_app.hpp" |
| #include <sys/system_properties.h> |
| #endif |
| |
| using namespace rivegm; |
| |
| static bool verbose = false; |
| |
| static void dump_gm(GM* gm) |
| { |
| uint32_t width = gm->width(); |
| uint32_t height = gm->height(); |
| TestingWindow::Get()->resize(width, height); |
| std::vector<uint8_t> pixels; |
| if (verbose) |
| { |
| printf("[gms] Running %s...\n", gm->name().c_str()); |
| } |
| gm->run(&pixels); |
| assert(pixels.size() == height * width * 4); |
| if (TestHarness::Instance().initialized()) |
| { |
| TestHarness::Instance().savePNG({ |
| .name = gm->name(), |
| .width = width, |
| .height = height, |
| .pixels = std::move(pixels), |
| }); |
| } |
| if (verbose) |
| { |
| printf("[gms] Sent %s.png\n", gm->name().c_str()); |
| } |
| } |
| |
| static bool contains(const std::string& str, const std::string& substr) |
| { |
| auto pos = str.find(substr, 0); |
| return pos < str.size(); |
| } |
| |
| static void dumpGMs(const std::string& match, bool interactive) |
| { |
| for (auto head = rivegm::GMRegistry::head(); head; head = head->next()) |
| { |
| auto gm = head->get()(); |
| if (!gm) |
| { |
| continue; |
| } |
| if (match.size() && !contains(gm->name(), match)) |
| { |
| continue; // This gm got filtered out by the '--match' argument. |
| } |
| if (!TestHarness::Instance().claimGMTest(gm->name())) |
| { |
| continue; // A different process already drew this gm. |
| } |
| #ifdef RIVE_ANDROID |
| if (gm->name().find("feather") != std::string::npos) |
| { |
| // Don't support or test feathering on Android until MSAA is |
| // implemented and device-specific crashes are resolved. |
| continue; |
| } |
| #endif |
| gm->onceBeforeDraw(); |
| |
| dump_gm(gm.get()); |
| if (interactive) |
| { |
| // Wait for any key if in interactive mode. |
| TestingWindow::Get()->getKey(); |
| } |
| #ifdef RIVE_ANDROID |
| if (!rive_android_app_poll_once()) |
| { |
| return; |
| } |
| #endif |
| } |
| } |
| |
| static bool is_arg(const char arg[], |
| const char target[], |
| const char alt[] = nullptr) |
| { |
| return !strcmp(arg, target) || (arg && !strcmp(arg, alt)); |
| } |
| |
| #if defined(RIVE_UNREAL) |
| |
| typedef const void* REGISTRY_HANDLE; |
| |
| REGISTRY_HANDLE gms_get_registry_head() { return rivegm::GMRegistry::head(); } |
| |
| REGISTRY_HANDLE gms_registry_get_next(REGISTRY_HANDLE position_handle) |
| { |
| const GMRegistry* position = |
| reinterpret_cast<const GMRegistry*>(position_handle); |
| if (position == nullptr) |
| return nullptr; |
| return position->next(); |
| } |
| |
| bool gms_run_gm(REGISTRY_HANDLE gm_handle) |
| { |
| const GMRegistry* position = reinterpret_cast<const GMRegistry*>(gm_handle); |
| if (position == nullptr) |
| return false; |
| |
| auto gm = position->get()(); |
| if (!gm) |
| { |
| return false; |
| } |
| |
| gm->onceBeforeDraw(); |
| |
| uint32_t width = gm->width(); |
| uint32_t height = gm->height(); |
| TestingWindow::Get()->resize(width, height); |
| gm->run(nullptr); |
| |
| return true; |
| } |
| |
| bool gms_registry_get_name(REGISTRY_HANDLE position_handle, std::string& name) |
| { |
| const GMRegistry* position = |
| reinterpret_cast<const GMRegistry*>(position_handle); |
| if (position == nullptr) |
| return false; |
| |
| auto gm = position->get()(); |
| if (!gm) |
| { |
| return false; |
| } |
| |
| name = gm->name(); |
| return true; |
| } |
| |
| bool gms_registry_get_size(REGISTRY_HANDLE position_handle, |
| size_t& width, |
| size_t& height) |
| { |
| const GMRegistry* position = |
| reinterpret_cast<const GMRegistry*>(position_handle); |
| if (position == nullptr) |
| return false; |
| |
| width = 0; |
| height = 0; |
| |
| auto gm = position->get()(); |
| if (!gm) |
| { |
| return false; |
| } |
| |
| width = gm->width(); |
| height = gm->height(); |
| |
| return true; |
| } |
| int gms_main(int argc, const char* argv[]) |
| #elif defined(RIVE_IOS) || defined(RIVE_IOS_SIMULATOR) |
| int gms_ios_main(int argc, const char* argv[]) |
| #elif defined(RIVE_ANDROID) |
| int rive_android_main(int argc, const char* const* argv) |
| #else |
| int main(int argc, const char* argv[]) |
| #endif |
| { |
| #ifdef _WIN32 |
| // Cause stdout and stderr to print immediately without buffering. |
| setvbuf(stdout, NULL, _IONBF, 0); |
| setvbuf(stderr, NULL, _IONBF, 0); |
| #endif |
| |
| const char* match = ""; |
| bool interactive = false; |
| auto backend = TestingWindow::Backend::gl; |
| std::string gpuNameFilter; |
| auto visibility = TestingWindow::Visibility::window; |
| int pngThreads = 2; |
| |
| for (int i = 1; i < argc; ++i) |
| { |
| if (strcmp(argv[i], "--test_harness") == 0) |
| { |
| TestHarness::Instance().init(TCPClient::Connect(argv[++i]), |
| pngThreads); |
| continue; |
| } |
| if (is_arg(argv[i], "--output", "-o")) |
| { |
| TestHarness::Instance().init(std::filesystem::path(argv[++i]), |
| pngThreads); |
| continue; |
| } |
| if (is_arg(argv[i], "--match", "-m")) |
| { |
| match = argv[++i]; |
| continue; |
| } |
| if (is_arg(argv[i], "--fast-png", "-f")) |
| { |
| TestHarness::Instance().setPNGCompression(PNGCompression::fast_rle); |
| continue; |
| } |
| if (is_arg(argv[i], "--interactive", "-i")) |
| { |
| interactive = true; |
| continue; |
| } |
| if (is_arg(argv[i], "--backend", "-b")) |
| { |
| backend = TestingWindow::ParseBackend(argv[++i], &gpuNameFilter); |
| continue; |
| } |
| if (is_arg(argv[i], "--headless", "-d")) |
| { |
| visibility = TestingWindow::Visibility::headless; |
| continue; |
| } |
| if (is_arg(argv[i], "--verbose", "-v")) |
| { |
| verbose = true; |
| continue; |
| } |
| if (sscanf(argv[i], "-p%d", &pngThreads) == 1) |
| { |
| pngThreads = std::max(pngThreads, 1); |
| continue; |
| } |
| printf("Unrecognized argument %s\n", argv[i]); |
| return 1; |
| } |
| |
| void* platformWindow = nullptr; |
| #ifdef RIVE_ANDROID |
| // Make sure the testing harness always gets initialized on Android so we |
| // pipe stdout & stderr to the android log always get pngs. |
| if (!TestHarness::Instance().initialized()) |
| { |
| // Android introduced a lot of changes to external storage at v11. We |
| // need to dump the pngs to different locations pre and post 11. |
| char androidOSVersion[PROP_VALUE_MAX + 1] = {0}; |
| __system_property_get("ro.build.version.release", androidOSVersion); |
| int androidOSVersionMajor = atoi(androidOSVersion); |
| const char* pngLocation = |
| androidOSVersionMajor >= 11 |
| ? "/sdcard/Pictures/rive_gms" |
| : "/sdcard/Android/data/app.rive.android_tests/files/data/gms"; |
| TestHarness::Instance().init(std::filesystem::path(pngLocation), 4); |
| // When the app is launched with no test harness, presumably via tap or |
| // some other automation process, always do verbose output. |
| verbose = true; |
| } |
| if (TestingWindow::IsGL(backend)) |
| { |
| // Android can render directly to the main window in GL. |
| // TOOD: add this support to TestingWindowAndroidVulkan as well. |
| platformWindow = rive_android_app_wait_for_window(); |
| if (platformWindow != nullptr) |
| { |
| visibility = TestingWindow::Visibility::fullscreen; |
| } |
| } |
| #endif |
| TestingWindow::Init(backend, visibility, gpuNameFilter, platformWindow); |
| |
| dumpGMs(std::string(match), interactive); |
| |
| TestingWindow::Destroy(); // Exercise our PLS teardown process now that |
| // we're done. |
| |
| TestHarness::Instance().shutdown(); |
| return 0; |
| } |
| |
| #endif |