mirror of
https://github.com/rive-app/rive-cpp.git
synced 2026-01-18 21:21:17 +01:00
Diffs= f161bf4679 Tighter RHI integration, extra build options (#9149) Co-authored-by: Jonathon Copeland <jcopela4@gmail.com>
455 lines
14 KiB
C++
455 lines
14 KiB
C++
/*
|
|
* 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 forceFixedDeltaTime = false;
|
|
static bool quit = false;
|
|
static bool hotloadShaders = 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 'f':
|
|
forceFixedDeltaTime = !forceFixedDeltaTime;
|
|
break;
|
|
case 'q':
|
|
case '\x03': // ^C
|
|
quit = true;
|
|
break;
|
|
case '\x1b': // Esc
|
|
break;
|
|
case '`':
|
|
hotloadShaders = true;
|
|
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();
|
|
}
|
|
std::unique_ptr<rive::File> file =
|
|
rive::File::import(rivBytes, TestingWindow::Get()->factory());
|
|
assert(file);
|
|
std::unique_ptr<rive::ArtboardInstance> 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.
|
|
rive::rcp<rive::Font> roboto = HBFont::Decode(assets::roboto_flex_ttf());
|
|
rive::rcp<rive::RenderPaint> blackStroke =
|
|
TestingWindow::Get()->factory()->makeRenderPaint();
|
|
blackStroke->color(0xff000000);
|
|
blackStroke->style(rive::RenderPaintStyle::stroke);
|
|
blackStroke->thickness(4);
|
|
rive::rcp<rive::RenderPaint> whiteFill =
|
|
TestingWindow::Get()->factory()->makeRenderPaint();
|
|
whiteFill->color(0xffffffff);
|
|
std::unique_ptr<rive::RawText> fpsText;
|
|
int fpsFrames = 0;
|
|
std::chrono::time_point timeLastFPSUpdate =
|
|
std::chrono::high_resolution_clock::now();
|
|
std::chrono::time_point timestampPrevFrame =
|
|
std::chrono::high_resolution_clock::now();
|
|
|
|
while (!quit && !TestingWindow::Get()->shouldQuit())
|
|
{
|
|
std::chrono::time_point timeNow =
|
|
std::chrono::high_resolution_clock::now();
|
|
const double elapsedS =
|
|
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
|
timeNow - timestampPrevFrame)
|
|
.count() /
|
|
1e9; // convert to s
|
|
timestampPrevFrame = timeNow;
|
|
|
|
float advanceDeltaTime = static_cast<float>(elapsedS);
|
|
if (forceFixedDeltaTime)
|
|
{
|
|
advanceDeltaTime = 1.0f / 120;
|
|
}
|
|
|
|
scene->advanceAndApply(paused ? 0 : advanceDeltaTime);
|
|
|
|
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,
|
|
});
|
|
|
|
if (hotloadShaders)
|
|
{
|
|
hotloadShaders = false;
|
|
#ifndef RIVE_NO_STD_SYSTEM
|
|
std::system("sh rebuild_shaders.sh /tmp/rive");
|
|
TestingWindow::Get()->hotloadShaders();
|
|
#endif
|
|
}
|
|
|
|
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;
|
|
const double elapsedFPSUpdate =
|
|
std::chrono::duration_cast<std::chrono::nanoseconds>(
|
|
timeNow - timeLastFPSUpdate)
|
|
.count() /
|
|
1e9; // convert to s
|
|
if (elapsedFPSUpdate >= 2.0)
|
|
{
|
|
double fps = fpsFrames / elapsedFPSUpdate;
|
|
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;
|
|
timeLastFPSUpdate = timeNow;
|
|
}
|
|
|
|
const rive::Mat2D alignmentMat =
|
|
computeAlignment(rive::Fit::contain,
|
|
rive::Alignment::center,
|
|
rive::AABB(0, 0, width, height),
|
|
artboard->bounds());
|
|
|
|
// Consume all input events until none are left in the queue
|
|
TestingWindow::InputEventData inputEventData;
|
|
while (TestingWindow::Get()->consumeInputEvent(inputEventData))
|
|
{
|
|
const rive::Vec2D mousePosAligned =
|
|
alignmentMat.invertOrIdentity() *
|
|
rive::Vec2D(inputEventData.metadata.posX,
|
|
inputEventData.metadata.posY);
|
|
|
|
switch (inputEventData.eventType)
|
|
{
|
|
case TestingWindow::InputEvent::KeyPress:
|
|
key_pressed(inputEventData.metadata.key);
|
|
break;
|
|
|
|
case TestingWindow::InputEvent::MouseMove:
|
|
scene->pointerMove(mousePosAligned);
|
|
break;
|
|
|
|
case TestingWindow::InputEvent::MouseDown:
|
|
scene->pointerDown(mousePosAligned);
|
|
break;
|
|
|
|
case TestingWindow::InputEvent::MouseUp:
|
|
scene->pointerUp(mousePosAligned);
|
|
break;
|
|
}
|
|
}
|
|
|
|
char key;
|
|
while (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
|