| /* |
| * 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 |