| /* | |
| * Copyright 2026 Rive | |
| */ | |
| #pragma once | |
| #ifdef RIVE_CANVAS | |
| #include "common/testing_window.hpp" | |
| #include "rive/renderer/render_canvas.hpp" | |
| #include "rive/renderer/render_context.hpp" | |
| #include "rive/renderer/rive_renderer.hpp" | |
| #include "rive/renderer/cmd/deferred_replayer.hpp" | |
| #include <cassert> | |
| #include <memory> | |
| namespace rive_tests | |
| { | |
| // A DeferredFrameSink that replays a recorded frame against a TestingWindow's | |
| // real render context, offscreen canvas frames included. This is what a test | |
| // needs in order to see the *pixels* a deferred recording produces: the | |
| // GPU-free sinks (deferred_test::TestSink, SerializingFactory) drop canvas | |
| // content instead of rasterizing it. | |
| // | |
| // The window's frame is expected to be already open when the first sink action | |
| // runs -- that frame is flushed and later ones resume with preserve, so the | |
| // caller still ends the frame through the window and reads pixels back the | |
| // usual way. | |
| class TestingWindowFrameSink : public rive::cmd::DeferredFrameSink | |
| { | |
| public: | |
| TestingWindowFrameSink( | |
| TestingWindow* window, | |
| rive::gpu::RenderContext* rc, | |
| const rive::gpu::RenderContext::FrameDescriptor& mainDesc) : | |
| m_window(window), m_rc(rc), m_mainDesc(mainDesc) | |
| {} | |
| rive::Factory* factory() override { return m_window->factory(); } | |
| rive::gpu::RenderContext* renderContext() override { return m_rc; } | |
| // The window owns one main render target. | |
| rive::Renderer* beginScreenFrame(uint64_t target) override | |
| { | |
| assert(target == 0); | |
| flushOpenFrame(); | |
| auto d = m_mainDesc; | |
| d.loadAction = rive::gpu::LoadAction::preserveRenderTarget; | |
| m_rc->beginFrame(std::move(d)); | |
| m_frameOpen = true; | |
| m_screen = std::make_unique<rive::RiveRenderer>(m_rc); | |
| return m_screen.get(); | |
| } | |
| rive::Renderer* beginCanvasContent(rive::gpu::RenderCanvas* canvas, | |
| uint32_t clearColor) override | |
| { | |
| flushOpenFrame(); | |
| m_activeCanvas = canvas; | |
| m_canvasFrames++; | |
| auto d = m_mainDesc; | |
| d.renderTargetWidth = canvas->width(); | |
| d.renderTargetHeight = canvas->height(); | |
| d.loadAction = rive::gpu::LoadAction::clear; | |
| d.clearColor = clearColor; | |
| m_rc->beginFrame(std::move(d)); | |
| m_frameOpen = true; | |
| m_canvasRenderer = std::make_unique<rive::RiveRenderer>(m_rc); | |
| return m_canvasRenderer.get(); | |
| } | |
| void endCanvasContent() override | |
| { | |
| if (m_activeCanvas == nullptr) | |
| { | |
| return; | |
| } | |
| m_window->flushPLSContext(m_activeCanvas->renderTarget()); | |
| m_frameOpen = false; | |
| m_canvasRenderer = nullptr; | |
| m_activeCanvas = nullptr; | |
| } | |
| // Offscreen frames opened while replaying, i.e. how many times the content | |
| // was actually rasterized into a canvas. | |
| size_t canvasFrames() const { return m_canvasFrames; } | |
| private: | |
| // The window (or the previous replay) leaves the main frame open. | |
| void flushOpenFrame() | |
| { | |
| if (!m_flushedWindowFrame || m_frameOpen) | |
| { | |
| m_window->flushPLSContext(); | |
| m_flushedWindowFrame = true; | |
| m_frameOpen = false; | |
| } | |
| } | |
| TestingWindow* m_window; | |
| rive::gpu::RenderContext* m_rc; | |
| rive::gpu::RenderContext::FrameDescriptor m_mainDesc; | |
| bool m_flushedWindowFrame = false; | |
| bool m_frameOpen = false; | |
| size_t m_canvasFrames = 0; | |
| std::unique_ptr<rive::RiveRenderer> m_screen; | |
| std::unique_ptr<rive::RiveRenderer> m_canvasRenderer; | |
| rive::gpu::RenderCanvas* m_activeCanvas = nullptr; | |
| }; | |
| } // namespace rive_tests | |
| #endif // RIVE_CANVAS |