blob: be618d5671d1a0c60e1b1021be483138794d63d8 [file] [log] [blame]
/*
* Copyright 2025 Rive
*/
#include "gm.hpp"
#include "gmutils.hpp"
#include "common/testing_window.hpp"
#include "rive/renderer/render_canvas.hpp"
#include "rive/renderer/render_context.hpp"
#include "rive/renderer/rive_renderer.hpp"
using namespace rivegm;
using namespace rive;
using namespace rive::gpu;
// Renders Rive 2D content into a RenderCanvas and composites the result into
// the main framebuffer. Verifies the basic render-to-texture lifecycle:
// makeRenderCanvas() -> renderTarget() -> beginFrame/flush -> renderImage() ->
// drawImage.
class RenderCanvasBasic : public GM
{
public:
RenderCanvasBasic() : GM(256, 256) {}
ColorInt clearColor() const override { return 0xffff0000; } // red
void onDraw(rive::Renderer* originalRenderer) override
{
auto renderContext = TestingWindow::Get()->renderContext();
if (!renderContext)
return;
auto canvas = renderContext->makeRenderCanvas(256, 256);
if (!canvas)
return;
// Intercept the current frame and flush.
auto originalFrameDescriptor = renderContext->frameDescriptor();
TestingWindow::Get()->flushPLSContext();
// Begin a new frame targeting the canvas.
auto canvasFrameDescriptor = originalFrameDescriptor;
canvasFrameDescriptor.clearColor = 0xff0000ff; // blue
renderContext->beginFrame(std::move(canvasFrameDescriptor));
// Draw a green circle into the canvas.
RiveRenderer renderer(renderContext);
Paint green(0xff00ff00);
PathBuilder builder;
int nsegs = 40;
float r = 80;
float cx = 128, cy = 128;
for (int i = 0; i <= nsegs; ++i)
{
float theta = 2 * math::PI * i / nsegs;
float x = cx + r * cosf(theta);
float y = cy + r * sinf(theta);
if (i == 0)
builder.moveTo(x, y);
else
builder.lineTo(x, y);
}
renderer.drawPath(builder.detach(), green);
// Flush to the canvas's render target.
TestingWindow::Get()->flushPLSContext(canvas->renderTarget());
// Resume the main frame and composite the canvas.
auto mainFrameDescriptor = originalFrameDescriptor;
mainFrameDescriptor.loadAction = gpu::LoadAction::preserveRenderTarget;
renderContext->beginFrame(std::move(mainFrameDescriptor));
renderer.save();
if (renderContext->platformFeatures().framebufferBottomUp)
{
renderer.translate(0, 256);
renderer.scale(1, -1);
}
renderer.drawImage(canvas->renderImage(),
{.filter = ImageFilter::nearest},
BlendMode::srcOver,
1);
renderer.restore();
}
};
GMREGISTER(render_canvas_basic, return new RenderCanvasBasic())
// Verifies that RenderCanvas content persists across frames. Renders a green
// circle into the canvas on the first frame, then on the second frame only
// composites the canvas (without re-rendering into it).
class RenderCanvasPersistence : public GM
{
public:
RenderCanvasPersistence() : GM(256, 256) {}
ColorInt clearColor() const override { return 0xffff0000; } // red
void onDraw(rive::Renderer* originalRenderer) override
{
auto renderContext = TestingWindow::Get()->renderContext();
if (!renderContext)
return;
if (!m_canvas)
{
m_canvas = renderContext->makeRenderCanvas(256, 256);
if (!m_canvas)
return;
// Render green circle into the canvas on first call.
auto originalFrameDescriptor = renderContext->frameDescriptor();
TestingWindow::Get()->flushPLSContext();
auto canvasFD = originalFrameDescriptor;
canvasFD.clearColor = 0xff0000ff; // blue
renderContext->beginFrame(std::move(canvasFD));
RiveRenderer renderer(renderContext);
Paint green(0xff00ff00);
PathBuilder builder;
int nsegs = 40;
float r = 80;
float cx = 128, cy = 128;
for (int i = 0; i <= nsegs; ++i)
{
float theta = 2 * math::PI * i / nsegs;
float x = cx + r * cosf(theta);
float y = cy + r * sinf(theta);
if (i == 0)
builder.moveTo(x, y);
else
builder.lineTo(x, y);
}
renderer.drawPath(builder.detach(), green);
TestingWindow::Get()->flushPLSContext(m_canvas->renderTarget());
// Resume main frame.
auto mainFD = originalFrameDescriptor;
mainFD.loadAction = gpu::LoadAction::preserveRenderTarget;
renderContext->beginFrame(std::move(mainFD));
}
// Composite the canvas (rendered on first frame, persisted on later
// frames).
RiveRenderer renderer(renderContext);
renderer.save();
if (renderContext->platformFeatures().framebufferBottomUp)
{
renderer.translate(0, 256);
renderer.scale(1, -1);
}
renderer.drawImage(m_canvas->renderImage(),
{.filter = ImageFilter::nearest},
BlendMode::srcOver,
1);
renderer.restore();
}
private:
rcp<RenderCanvas> m_canvas;
};
GMREGISTER(render_canvas_persistence, return new RenderCanvasPersistence())