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