| /* | |
| * Copyright 2026 Rive | |
| */ | |
| #if defined(RIVE_CANVAS) && defined(RIVE_ORE) | |
| #include "rive/renderer/cmd/deferred_host.hpp" | |
| #include "rive/renderer/render_canvas.hpp" | |
| #include "rive/renderer/render_context.hpp" | |
| #include "rive/renderer/render_context_impl.hpp" | |
| #include <assert.h> | |
| #include <stdio.h> | |
| #if defined(__ANDROID__) | |
| #include <android/log.h> | |
| // stderr goes nowhere on Android, route diagnostics to logcat. | |
| #define DEFERRED_WARN(...) \ | |
| __android_log_print(ANDROID_LOG_ERROR, "Rive", __VA_ARGS__) | |
| #else | |
| #define DEFERRED_WARN(...) fprintf(stderr, __VA_ARGS__) | |
| #endif | |
| namespace rive::cmd | |
| { | |
| rive::Factory* HostFrameSink::factory() { return renderContext(); } | |
| rive::ore::Context* HostFrameSink::oreContext() | |
| { | |
| return m_replayOre ? renderContext()->ore() : nullptr; | |
| } | |
| rive::Renderer* HostFrameSink::beginScreenFrame(uint64_t target) | |
| { | |
| if (target != m_target && !m_acceptAnyScreenTarget) | |
| { | |
| // Another texture's segments reached this sink, so the session frame | |
| // was taken while that texture was still recording. Drop them loudly | |
| // rather than draw them here; the replayer counts them as dropped. | |
| static int strays = 0; | |
| if (strays < 16) | |
| { | |
| strays++; | |
| DEFERRED_WARN("rive deferred: frame carried target %llu into the " | |
| "sink for target %llu, dropping its draws\n", | |
| (unsigned long long)target, | |
| (unsigned long long)m_target); | |
| } | |
| // Native delivery is one host per session frame: hosts must record | |
| // sequentially, so a foreign target here means overlapping opens and | |
| // its draws would silently blank. Loud in debug; the warning above | |
| // is all release gets. | |
| assert(false && "deferred hosts must open and close their screen " | |
| "target sequentially, a neighbor's frame reached " | |
| "this sink"); | |
| return nullptr; | |
| } | |
| if (m_screen == nullptr) | |
| { | |
| m_screen = beginScreen(target, m_clear, m_color); | |
| } | |
| return m_screen; | |
| } | |
| void HostFrameSink::beginOreFrame() | |
| { | |
| if (auto* ore = oreContext()) | |
| { | |
| ore->beginFrame({}); | |
| } | |
| } | |
| void HostFrameSink::endOreFrame() | |
| { | |
| if (auto* ore = oreContext()) | |
| { | |
| ore->endFrame(); | |
| } | |
| } | |
| void HostFrameSink::afterOreFrame() | |
| { | |
| renderContext()->impl()->scrubStateAfterOre(); | |
| } | |
| rive::Renderer* HostFrameSink::beginCanvasContent( | |
| rive::gpu::RenderCanvas* canvas, | |
| uint32_t clearColor) | |
| { | |
| m_activeCanvas = canvas; | |
| auto* rc = renderContext(); | |
| // A deferred canvas has no texture until the replaying context gives it | |
| // one, and this is the first thing that renders into it. | |
| rc->impl()->ensureCanvasBacking(canvas); | |
| rive::gpu::RenderContext::FrameDescriptor d{}; | |
| d.renderTargetWidth = canvas->width(); | |
| d.renderTargetHeight = canvas->height(); | |
| d.loadAction = rive::gpu::LoadAction::clear; | |
| d.clearColor = clearColor; | |
| d.disableRasterOrdering = m_disableRasterOrdering; | |
| rc->beginFrame(d); | |
| m_canvasRenderer = std::make_unique<rive::RiveRenderer>(rc); | |
| return m_canvasRenderer.get(); | |
| } | |
| void HostFrameSink::endCanvasContent() | |
| { | |
| if (m_activeCanvas == nullptr) | |
| { | |
| return; | |
| } | |
| auto* rc = renderContext(); | |
| rive::gpu::RenderContext::FlushResources fr{}; | |
| fr.renderTarget = m_activeCanvas->renderTarget(); | |
| if (m_useExternalCommandBuffer) | |
| { | |
| void* cb = rc->impl()->makeCommandBuffer(); | |
| fr.externalCommandBuffer = cb; | |
| rc->flush(fr); | |
| rc->impl()->commitCommandBuffer(cb); | |
| } | |
| else | |
| { | |
| rc->flush(fr); | |
| } | |
| m_canvasRenderer.reset(); | |
| m_activeCanvas = nullptr; | |
| } | |
| void DeferredInlineHost::beginRecord(bool clear, uint32_t color) | |
| { | |
| m_doClear = clear; | |
| m_color = color; | |
| m_session->recordOreReplayMarker(); | |
| } | |
| bool DeferredInlineHost::replayOre() const | |
| { | |
| return m_session->oreContext().stream().commandBytes().size() != 0; | |
| } | |
| static void warnDroppedDraws(uint32_t droppedDraws) | |
| { | |
| if (droppedDraws == 0) | |
| { | |
| return; | |
| } | |
| static int drops = 0; | |
| if (drops < 16) | |
| { | |
| drops++; | |
| DEFERRED_WARN("rive deferred: %u draws DROPPED this frame (mixed " | |
| "factories or replay bug)\n", | |
| droppedDraws); | |
| } | |
| } | |
| bool DeferredInlineHost::replayInline(HostFrameSink& sink, | |
| const std::function<void()>& present) | |
| { | |
| // Nothing recorded, keep showing the last presented frame. | |
| if (!m_session->recordedThisFrame()) | |
| { | |
| return false; | |
| } | |
| m_replayer.replayFrame(*m_session, sink); | |
| m_session->resetFrame(); | |
| if (sink.began()) | |
| { | |
| present(); | |
| } | |
| warnDroppedDraws(m_replayer.droppedDraws()); | |
| return true; | |
| } | |
| } // namespace rive::cmd | |
| #endif // RIVE_CANVAS && RIVE_ORE |