blob: 64e36c13f45d77ccf8e0dc12ae4dbdb12f46503e [file] [log] [blame] [edit]
/*
* 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