blob: 3aaf8e72c8db0e974bdbca242f7873c547af8110 [file] [log] [blame] [edit]
/*
* Copyright 2024 Rive
*/
// Don't compile this file as part of the "tests" project.
#ifndef TESTING
#include "common/test_harness.hpp"
#include "common/testing_window.hpp"
#include "rive/artboard.hpp"
#include "rive/animation/state_machine_instance.hpp"
#include "rive/file.hpp"
#include "rive/text/font_hb.hpp"
#include "rive/text/raw_text.hpp"
#include "assets/roboto_flex.ttf.hpp"
#include <stdio.h>
#include <fstream>
#ifdef RIVE_ANDROID
#include "common/rive_android_app.hpp"
#endif
static void update_parameter(int& val, int multiplier, char key, bool seenBang)
{
if (seenBang)
val = multiplier;
else if (key >= 'a')
val += multiplier;
else
val -= multiplier;
}
static int copiesLeft = 0;
static int copiesAbove = 0;
static int copiesRight = 0;
static int copiesBelow = 0;
static int rotations90 = 0;
static int zoomLevel = 0;
static int spacing = 0;
static int monitorIdx = 0;
static bool wireframe = false;
static bool paused = false;
static bool quit = false;
static void key_pressed(char key)
{
static int multiplier = 0;
static bool seenDigit = false;
static bool seenBang = false;
if (key >= '0' && key <= '9')
{
multiplier = multiplier * 10 + (key - '0');
seenDigit = true;
return;
}
if (key == '!')
{
seenBang = true;
return;
}
if (!seenDigit)
{
multiplier = seenBang ? 0 : 1;
}
switch (key)
{
case 'h':
case 'H':
update_parameter(copiesLeft, multiplier, key, seenBang);
break;
case 'k':
case 'K':
update_parameter(copiesAbove, multiplier, key, seenBang);
break;
case 'l':
case 'L':
update_parameter(copiesRight, multiplier, key, seenBang);
break;
case 'j':
case 'J':
update_parameter(copiesBelow, multiplier, key, seenBang);
break;
case 'x':
case 'X':
update_parameter(copiesLeft, multiplier, key, seenBang);
update_parameter(copiesRight, multiplier, key, seenBang);
break;
case 'y':
case 'Y':
update_parameter(copiesAbove, multiplier, key, seenBang);
update_parameter(copiesBelow, multiplier, key, seenBang);
break;
case 'r':
case 'R':
update_parameter(rotations90, multiplier, key, seenBang);
break;
case 'z':
case 'Z':
update_parameter(zoomLevel, multiplier, key, seenBang);
break;
case 's':
case 'S':
update_parameter(spacing, multiplier, key, seenBang);
break;
case 'm':
monitorIdx += multiplier;
break;
case 'w':
wireframe = !wireframe;
break;
case 'p':
paused = !paused;
break;
case 'q':
case '\x03': // ^C
quit = true;
break;
case '\x1b': // Esc
break;
default:
// fprintf(stderr, "invalid option: %c\n", key);
// abort();
break;
}
multiplier = 0;
seenDigit = false;
seenBang = false;
}
#if defined(RIVE_IOS) || defined(RIVE_IOS_SIMULATOR)
int player_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
std::string rivName;
std::vector<uint8_t> rivBytes;
auto backend =
#ifdef __APPLE__
TestingWindow::Backend::metal;
#else
TestingWindow::Backend::vk;
#endif
auto visibility = TestingWindow::Visibility::fullscreen;
std::string gpuNameFilter;
for (int i = 0; i < argc; ++i)
{
if (strcmp(argv[i], "--test_harness") == 0)
{
TestHarness::Instance().init(TCPClient::Connect(argv[++i]), 0);
if (!TestHarness::Instance().fetchRivFile(rivName, rivBytes))
{
fprintf(stderr, "failed to fetch a riv file.");
abort();
}
}
else if (strcmp(argv[i], "--backend") == 0 ||
strcmp(argv[i], "-b") == 0)
{
backend = TestingWindow::ParseBackend(argv[++i], &gpuNameFilter);
}
else if (argv[i][0] == '-' &&
argv[i][1] == 'b') // "-bvk" without a space.
{
backend = TestingWindow::ParseBackend(argv[i] + 2, &gpuNameFilter);
}
else if (strcmp(argv[i], "--options") == 0 ||
strcmp(argv[i], "-k") == 0)
{
for (const char* k = argv[++i]; *k; ++k)
{
key_pressed(*k);
}
}
else if (argv[i][0] == '-' &&
argv[i][1] == 'k') // "-k1234asdf" without a space.
{
for (const char* k = argv[i] + 2; *k; ++k)
{
key_pressed(*k);
}
}
else if (strcmp(argv[i], "--window") == 0 || strcmp(argv[i], "-w") == 0)
{
visibility = TestingWindow::Visibility::window;
}
else
{
// No argument name defaults to the source riv.
if (strcmp(argv[i], "--src") == 0 || strcmp(argv[i], "-s") == 0)
{
++i;
}
rivName = argv[i];
std::ifstream rivStream(rivName, std::ios::binary);
rivBytes =
std::vector<uint8_t>(std::istreambuf_iterator<char>(rivStream),
{});
}
}
TestingWindow::Init(backend,
visibility,
gpuNameFilter,
#ifdef RIVE_ANDROID
rive_android_app_wait_for_window()
#else
reinterpret_cast<void*>(
static_cast<intptr_t>(monitorIdx))
#endif
);
// Load the riv file.
if (rivBytes.empty())
{
fprintf(stderr, "no .riv file specified");
abort();
}
auto file = rive::File::import(rivBytes, TestingWindow::Get()->factory());
assert(file);
auto artboard = file->artboardDefault();
assert(artboard);
std::unique_ptr<rive::Scene> scene = artboard->defaultStateMachine();
if (!scene)
{
scene = artboard->animationAt(0);
}
assert(scene);
int lastReportedCopyCount = 0;
bool lastReportedPauseState = paused;
// Setup FPS.
auto roboto = HBFont::Decode(assets::roboto_flex_ttf());
auto blackStroke = TestingWindow::Get()->factory()->makeRenderPaint();
blackStroke->color(0xff000000);
blackStroke->style(rive::RenderPaintStyle::stroke);
blackStroke->thickness(4);
auto whiteFill = TestingWindow::Get()->factory()->makeRenderPaint();
whiteFill->color(0xffffffff);
std::unique_ptr<rive::RawText> fpsText;
int fpsFrames = 0;
std::chrono::time_point fpsLastTime =
std::chrono::high_resolution_clock::now();
while (!quit && !TestingWindow::Get()->shouldQuit())
{
scene->advanceAndApply(paused ? 0 : 1.0 / 120);
copiesLeft = std::max(copiesLeft, 0);
copiesAbove = std::max(copiesAbove, 0);
copiesRight = std::max(copiesRight, 0);
copiesBelow = std::max(copiesBelow, 0);
int copyCount =
(copiesLeft + 1 + copiesRight) * (copiesAbove + 1 + copiesBelow);
if (copyCount != lastReportedCopyCount ||
paused != lastReportedPauseState)
{
printf("Drawing %i copies of %s%s at %u x %u\n",
copyCount,
rivName.c_str(),
paused ? " (paused)" : "",
TestingWindow::Get()->width(),
TestingWindow::Get()->height());
lastReportedCopyCount = copyCount;
lastReportedPauseState = paused;
}
auto renderer = TestingWindow::Get()->beginFrame({
.clearColor = 0xff303030,
.doClear = true,
.wireframe = wireframe,
});
renderer->save();
uint32_t width = TestingWindow::Get()->width();
uint32_t height = TestingWindow::Get()->height();
for (int i = rotations90; (i & 3) != 0; --i)
{
renderer->transform(rive::Mat2D(0, 1, -1, 0, width, 0));
std::swap(height, width);
}
if (zoomLevel != 0)
{
float scale = powf(1.25f, zoomLevel);
renderer->translate(width / 2.f, height / 2.f);
renderer->scale(scale, scale);
renderer->translate(width / -2.f, height / -2.f);
}
// Draw the .riv.
renderer->save();
renderer->align(rive::Fit::contain,
rive::Alignment::center,
rive::AABB(0, 0, width, height),
artboard->bounds());
float spacingPx = spacing * 5 + 150;
renderer->translate(-spacingPx * copiesLeft, -spacingPx * copiesAbove);
for (int y = -copiesAbove; y <= copiesBelow; ++y)
{
renderer->save();
for (int x = -copiesLeft; x <= copiesRight; ++x)
{
artboard->draw(renderer.get());
renderer->translate(spacingPx, 0);
}
renderer->restore();
renderer->translate(0, spacingPx);
}
renderer->restore();
if (fpsText != nullptr)
{
// Draw FPS.
renderer->save();
renderer->translate(0, 20);
fpsText->render(renderer.get(), blackStroke);
fpsText->render(renderer.get(), whiteFill);
renderer->restore();
}
renderer->restore();
TestingWindow::Get()->endFrame();
// Count FPS.
++fpsFrames;
std::chrono::time_point now = std::chrono::high_resolution_clock::now();
auto fpsElapsedMS =
std::chrono::duration_cast<std::chrono::milliseconds>(now -
fpsLastTime)
.count();
if (fpsElapsedMS > 2000)
{
double fps = 1000.0 * fpsFrames / fpsElapsedMS;
printf("[%.3f FPS]\n", fps);
char fpsRawText[32];
snprintf(fpsRawText, sizeof(fpsRawText), " %.1f FPS ", fps);
fpsText = std::make_unique<rive::RawText>(
TestingWindow::Get()->factory());
fpsText->maxWidth(width);
#ifdef RIVE_ANDROID
fpsText->align(rive::TextAlign::center);
#else
fpsText->align(rive::TextAlign::right);
#endif
fpsText->sizing(rive::TextSizing::fixed);
fpsText->append(fpsRawText, nullptr, roboto, 50.f);
fpsFrames = 0;
fpsLastTime = now;
}
char key;
while (TestingWindow::Get()->peekKey(key) ||
TestHarness::Instance().peekChar(key))
{
key_pressed(key);
}
#ifdef RIVE_ANDROID
if (!rive_android_app_poll_once())
{
break;
}
#endif
}
printf("\nShutting down\n");
TestingWindow::Destroy(); // Exercise our PLS teardown process now that
// we're done.
TestHarness::Instance().shutdown();
return 0;
}
#endif