blob: bd2aa59c9092d9a5017b9101ce914e1a7bce19eb [file]
/*
* Copyright 2026 Rive
*
* Ore clears Rive's own render target and draws a triangle into it, then Rive
* draws a path on top, immediately and through a DeferredSession whose target
* view is wrapped at replay. The goldens must be byte identical and show both.
*/
#include "gm.hpp"
#include "gmutils.hpp"
#include "ore_gm_sink.hpp"
#if ORE_GM_HAS_BACKEND
#include "rive/renderer/ore/ore_buffer.hpp"
#include "rive/renderer/ore/ore_pipeline.hpp"
#include "rive/renderer/ore/ore_render_pass.hpp"
#include "rive/renderer/cmd/deferred_session.hpp"
#endif
using namespace rivegm;
using namespace rive;
using namespace rive::gpu;
class OreDeferredTargetGM : public GM
{
public:
OreDeferredTargetGM(bool deferred) : GM(256, 256), m_deferred(deferred) {}
// The first Ore pass covers the target, so Rive must not clear over it.
void updateFrameOptions(TestingWindow::FrameOptions* options) const override
{
options->doClear = false;
}
void onDraw(rive::Renderer* originalRenderer) override
{
auto renderContext = TestingWindow::Get()->renderContext();
TestingWindow* window = TestingWindow::Get();
bool exposed = renderContext != nullptr &&
m_ore.ensureContext(renderContext) &&
window->oreTarget().exposed;
#if ORE_GM_HAS_BACKEND
if (exposed)
{
if (m_deferred)
{
drawDeferred(renderContext, originalRenderer);
}
else
{
drawImmediate(renderContext, originalRenderer);
}
return;
}
#endif
// Keep the frame defined where no target is exposed.
drawBackground(*window->factory(), originalRenderer);
drawPath(*window->factory(), originalRenderer);
}
private:
static void drawBackground(Factory& factory, Renderer* renderer)
{
auto paint = factory.makeRenderPaint();
paint->color(0xff1ab38c);
// Overshoots the frame since Rive loads it, and a rasterizer that
// leaves the edge short of full coverage would blend the last frame.
auto path = factory.makeEmptyRenderPath();
path->moveTo(-1, -1);
path->lineTo(257, -1);
path->lineTo(257, 257);
path->lineTo(-1, 257);
path->close();
renderer->drawPath(path.get(), paint.get());
}
static void drawPath(Factory& factory, Renderer* renderer)
{
auto paint = factory.makeRenderPaint();
paint->color(0xc0e02040);
auto path = factory.makeEmptyRenderPath();
path->moveTo(128, 96);
path->lineTo(224, 160);
path->lineTo(128, 224);
path->lineTo(32, 160);
path->close();
renderer->drawPath(path.get(), paint.get());
}
#if ORE_GM_HAS_BACKEND
// Clears the whole target, as the contract asks of the first pass.
void recordTriangle(ore::Context& ctx, ore::TextureView* target)
{
auto shader = ore_gm::loadShader(ctx, ore_gm::kTriangle);
if (!shader.vsModule)
{
return;
}
ore::BufferDesc bd{};
bd.usage = ore::BufferUsage::vertex;
bd.size = sizeof(ore_gm::kTriVertices);
bd.data = ore_gm::kTriVertices;
bd.label = "ore_deferred_target_vb";
auto vb = ctx.makeBuffer(bd);
ore_gm::TrianglePipeline tri(
shader,
target->texture()->format(),
"ore_deferred_target_pipeline");
auto pipeline = ctx.makePipeline(tri.desc);
if (!vb || !pipeline)
{
return;
}
ore::ColorAttachment ca{};
ca.view = target;
ca.loadOp = ore::LoadOp::clear;
ca.storeOp = ore::StoreOp::store;
ca.clearColor = {0.10f, 0.70f, 0.55f, 1.0f};
ore::RenderPassDesc rp{};
rp.colorAttachments[0] = ca;
rp.colorCount = 1;
rp.label = "ore_deferred_target_pass";
auto pass = ctx.beginRenderPass(rp);
pass->setPipeline(pipeline.get());
pass->setVertexBuffer(0, vb.get());
pass->setViewport(0, 0, 256, 256);
pass->draw(3);
pass->finish();
}
void drawImmediate(RenderContext* renderContext, Renderer* renderer)
{
auto& realCtx = *renderContext->getOreContext();
m_ore.beginFrame(renderContext);
auto target = realCtx.wrapRenderTarget(
TestingWindow::Get()->renderTarget());
if (target != nullptr)
{
recordTriangle(realCtx, target.get());
}
m_ore.endFrame(renderContext);
ore_gm::invalidateGLStateAfterOre(renderContext);
Factory& factory = *TestingWindow::Get()->factory();
if (target == nullptr)
{
// Rive loads the target, so an unwrapped one must still be covered.
drawBackground(factory, renderer);
}
drawPath(factory, renderer);
}
void drawDeferred(RenderContext* renderContext, Renderer* renderer)
{
auto& realCtx = *renderContext->getOreContext();
TestingWindow* window = TestingWindow::Get();
rive::cmd::DeferredSession session(ore::ReplayCaps::from(realCtx));
session.oreContext().setTarget(ore::Context::TargetDesc::color8(
window->width(),
window->height(),
window->oreTarget().bgra));
auto target = session.oreContext().targetView();
recordTriangle(session.oreContext(), target.get());
session.recordOreReplayMarker();
auto screen = session.makeScreenRenderer();
drawPath(session, screen.get());
rive::cmd::DeferredFrame frame = rive::cmd::snapshotFrame(session);
ore_gm::GMFrameSink sink(
renderContext,
renderer,
&m_ore,
window->renderTarget());
rive::cmd::DeferredReplayer replayer;
replayer.replayFrame(frame, sink);
}
#endif
bool m_deferred;
ore_gm::OreGMContext m_ore;
};
GMREGISTER(ore_deferred_target_immediate, return new OreDeferredTargetGM(false))
GMREGISTER(ore_deferred_target, return new OreDeferredTargetGM(true))