blob: 7f8e4b8ce73dbeb534ba2ae08df6e0d163465a79 [file]
/*
* Copyright 2022 Rive
*/
// Shared between goldens.cpp and the env gated diagnostics in
// goldens_bench.cpp.
#pragma once
// Don't compile the goldens tool as part of the "tests" project.
#ifndef TESTING
#include "goldens_arguments.hpp"
#include "common/test_harness.hpp"
#include "common/testing_window.hpp"
#include "rive/artboard.hpp"
#include "rive/renderer.hpp"
#include "rive/file.hpp"
#include "rive/refcnt.hpp"
#include "rive/animation/state_machine_instance.hpp"
#include "rive/static_scene.hpp"
#ifdef WITH_RIVE_SCRIPTING
#include "rive/lua/scripting_vm.hpp"
#include "rive/lua/rive_lua_libs.hpp"
#endif
#if defined(WITH_RIVE_SCRIPTING) && defined(RIVE_CANVAS)
// RIVE_GOLDENS_DEFER_ORE records through a DeferredOreContext and replays on
// the real context in the same frame, single threaded.
#include "rive/renderer/render_context.hpp"
#include "rive/renderer/render_context_impl.hpp"
#include "rive/renderer/render_canvas.hpp"
#include "rive/renderer/rive_renderer.hpp"
#include "rive/renderer/cmd/deferred_session.hpp"
#include "rive/renderer/cmd/deferred_replayer.hpp"
#endif
#include <cstdlib>
#include <memory>
#include <vector>
// Builds without scripting or canvas never include the deferred headers but
// the accessors below still name the type.
namespace rive::cmd
{
class DeferredSession;
}
extern GoldensArguments s_args;
// Consoles build without an env API, probes just come back unset there.
inline const char* goldens_getenv(const char* name)
{
#ifdef NO_GETENV
return nullptr;
#else
return getenv(name);
#endif
}
void dumpPixelsAsPng(const char* rivName,
int windowWidth,
int windowHeight,
std::vector<uint8_t> pixels);
#if defined(WITH_RIVE_SCRIPTING) && defined(RIVE_CANVAS)
// DeferredFrameSink over TestingWindow. Leaves the screen frame open for the
// caller to present via endFrame.
class GoldensFrameSink : public rive::cmd::DeferredFrameSink
{
public:
GoldensFrameSink(bool doClear = true) :
m_rc(TestingWindow::Get()->renderContext()), m_doClear(doClear)
{}
rive::Factory* factory() override
{
return TestingWindow::Get()->factory();
}
// Goldens render into the one TestingWindow, so there is a single target.
rive::Renderer* beginScreenFrame(uint64_t target) override
{
assert(target == 0);
m_screen = TestingWindow::Get()->beginFrame(
{.clearColor = 0xffffffff, .doClear = m_doClear});
return m_screen.get();
}
void beginOreFrame() override { TestingWindow::Get()->beginOreFrame(); }
void endOreFrame() override { TestingWindow::Get()->endOreFrame(); }
rive::Renderer* beginCanvasContent(rive::gpu::RenderCanvas* canvas,
uint32_t clearColor) override
{
m_activeCanvas = canvas;
rive::gpu::RenderContext::FrameDescriptor d{};
d.renderTargetWidth = canvas->width();
d.renderTargetHeight = canvas->height();
d.loadAction = rive::gpu::LoadAction::clear;
d.clearColor = clearColor;
m_rc->beginFrame(d);
m_canvasRenderer = new rive::RiveRenderer(m_rc);
return m_canvasRenderer;
}
void endCanvasContent() override
{
if (m_activeCanvas == nullptr)
return;
void* cb = m_rc->impl()->makeCommandBuffer();
rive::gpu::RenderContext::FlushResources fr{};
fr.renderTarget = m_activeCanvas->renderTarget();
fr.externalCommandBuffer = cb;
m_rc->flush(fr);
m_rc->impl()->commitCommandBuffer(cb);
delete m_canvasRenderer;
m_canvasRenderer = nullptr;
m_activeCanvas = nullptr;
}
private:
rive::gpu::RenderContext* m_rc;
bool m_doClear;
std::unique_ptr<rive::Renderer> m_screen;
rive::RiveRenderer* m_canvasRenderer = nullptr;
rive::gpu::RenderCanvas* m_activeCanvas = nullptr;
};
// RIVE_GOLDENS_BENCH=<iters> loads the same scene immediate and deferred and
// reports per frame record cost, replay cost, and stream size.
void run_benchmark(const std::vector<uint8_t>& bytes,
const char* artboardName,
const char* stateMachineName,
int iters);
#endif
class RIVLoader
{
public:
// Auto defers for --deferred, or RIVE_GOLDENS_DEFER_ORE with a single
// cell. The benchmark forces one or the other to load both side by side.
enum class DeferMode
{
Auto,
Immediate,
Deferred
};
RIVLoader(const std::vector<uint8_t>& rivBytes,
const char* artboardName,
const char* stateMachineName,
DeferMode mode = DeferMode::Auto)
{
rive::Factory* importFactory = TestingWindow::Get()->factory();
#if defined(WITH_RIVE_SCRIPTING) && defined(RIVE_CANVAS)
// Importing through the DeferredSession makes the artboard's own 2D
// resources deferred objects with ids so drawInternal can record.
bool wantDeferred =
mode == DeferMode::Deferred ||
(mode == DeferMode::Auto &&
(s_args.deferred() || (goldens_getenv("RIVE_GOLDENS_DEFER_ORE") &&
s_args.cols() * s_args.rows() == 1)));
if (wantDeferred)
{
if (auto* rc = TestingWindow::Get()->renderContext())
{
if (auto* ore = rc->getOreContext())
{
m_session =
std::make_unique<rive::cmd::DeferredSession>(ore);
importFactory = m_session.get();
}
}
}
#endif
m_file = rive::File::import(rivBytes, importFactory);
if (m_file == nullptr)
{
throw "Bad riv file";
}
#ifdef WITH_RIVE_SCRIPTING
// Without the RenderContext handed over like a real host does,
// gpuCanvas bails and no GPU work runs.
if (auto* vm = m_file->scriptingVM())
{
vm->context()->setRenderContext(
TestingWindow::Get()->renderContext());
#if defined(RIVE_CANVAS)
if (m_session)
{
vm->context()->setOreContext(&m_session->oreContext());
// Regular canvas 2D content records into the deferred stream.
vm->context()->setDeferredCanvasHost(m_session.get());
}
#endif
}
#endif
if (artboardName != nullptr && artboardName[0] != '\0')
{
m_artboard = m_file->artboardNamed(artboardName);
}
else
{
m_artboard = m_file->artboardDefault();
}
if (m_artboard == nullptr)
{
throw "Can't load artboard";
}
// Bind the default view model instance
m_viewModelInstance = m_file->createViewModelInstance(m_artboard.get());
m_artboard->bindViewModelInstance(m_viewModelInstance);
if (stateMachineName != nullptr && stateMachineName[0] != '\0')
{
m_scene = m_artboard->stateMachineNamed(stateMachineName);
}
else
{
m_scene = m_artboard->defaultStateMachine();
}
if (m_scene == nullptr)
{
// This is a riv without any state machines. Just draw the artboard.
m_scene = std::make_unique<rive::StaticScene>(m_artboard.get());
}
if (m_scene != nullptr && m_viewModelInstance != nullptr)
{
m_scene->bindViewModelInstance(m_viewModelInstance);
}
}
rive::Scene* stateMachine() const { return m_scene.get(); }
rive::Artboard* artboard() const { return m_artboard.get(); }
// Null when deferred mode is off.
rive::cmd::DeferredSession* deferredSession() const
{
#if defined(WITH_RIVE_SCRIPTING) && defined(RIVE_CANVAS)
return m_session.get();
#else
return nullptr;
#endif
}
private:
// Destroyed last since deferred resources held by the file record their
// destruction into the session, so it must outlive them.
#if defined(WITH_RIVE_SCRIPTING) && defined(RIVE_CANVAS)
std::unique_ptr<rive::cmd::DeferredSession> m_session;
#endif
rive::rcp<rive::File> m_file;
std::unique_ptr<rive::ArtboardInstance> m_artboard;
std::unique_ptr<rive::Scene> m_scene;
rive::rcp<rive::ViewModelInstance> m_viewModelInstance;
};
#endif // TESTING