blob: 5d5015ade318d8b3087cd453fc01e08d99cfd064 [file] [log] [blame]
/*
* Copyright 2024 Rive
*/
#include "gm.hpp"
#include "gmutils.hpp"
#include "common/testing_window.hpp"
#include "common/offscreen_render_target.hpp"
#include "rive/renderer/render_context.hpp"
#include "rive/renderer/rive_renderer.hpp"
#ifdef RIVE_TOOLS_NO_GL
using GLuint = uint32_t;
#else
#include "rive/renderer/gl/render_context_gl_impl.hpp"
#include "rive/renderer/gl/render_target_gl.hpp"
#endif
#include "assets/nomoon.png.hpp"
using namespace rivegm;
using namespace rive;
using namespace rive::gpu;
// Most gms render directly to the framebuffer. This GM checks that texture
// targets work in GL.
class OffscreenRenderTarget : public GM
{
public:
OffscreenRenderTarget(BlendMode blendMode,
bool riveRenderable,
ColorInt clearColor = 0xffff00ff) :
GM(256, 256),
m_blendMode(blendMode),
m_riveRenderable(riveRenderable),
m_clearColor(clearColor)
{}
ColorInt clearColor() const override { return 0xffff0000; }
void onDraw(rive::Renderer* originalRenderer) override
{
if (rcp<rive_tests::OffscreenRenderTarget> renderImageTarget =
TestingWindow::Get()
->makeOffscreenRenderTarget(256, 256, m_riveRenderable))
{
// Intercept the current frameDescriptor and end the PLS frame.
auto renderContext = TestingWindow::Get()->renderContext();
auto originalFrameDescriptor = renderContext->frameDescriptor();
TestingWindow::Get()->flushPLSContext();
// Draw to the offscreen texture.
auto textureFrameDescriptor = originalFrameDescriptor;
textureFrameDescriptor.clearColor = m_clearColor;
renderContext->beginFrame(std::move(textureFrameDescriptor));
RiveRenderer renderer(renderContext);
drawInternal(&renderer, renderImageTarget->asRenderTarget());
TestingWindow::Get()->flushPLSContext(
renderImageTarget->asRenderTarget());
// Copy the offscreen texture back to the destination framebuffer.
RenderImage* renderImage = renderImageTarget->asRenderImage();
auto copyFrameDescriptor = originalFrameDescriptor;
copyFrameDescriptor.loadAction =
gpu::LoadAction::preserveRenderTarget;
copyFrameDescriptor.clearColor = 0xffff0000;
renderContext->beginFrame(std::move(copyFrameDescriptor));
renderer.save();
if (renderContext->platformFeatures().framebufferBottomUp)
{
renderer.translate(0, 256);
renderer.scale(1, -1);
}
renderer.drawImage(renderImage,
{.filter = ImageFilter::nearest},
BlendMode::srcOver,
1);
renderer.restore();
}
else
{
drawInternal(originalRenderer, nullptr);
}
}
virtual void drawInternal(Renderer* renderer, RenderTarget*)
{
Paint paint5(0x8000ffff);
paint5->blendMode(m_blendMode);
drawStar5(renderer, paint5);
Paint paint13(0x80ffff00);
paint13->blendMode(m_blendMode);
drawStar13(renderer, paint13);
}
void drawStar5(Renderer* renderer,
RenderPaint* paint,
FillRule fillRule = FillRule::nonZero)
{
PathBuilder builder;
float theta = -math::PI / 7;
builder.moveTo(cosf(theta), sinf(theta));
for (int i = 0; i <= 7; ++i)
{
theta += 2 * math::PI * 2 / 7;
builder.lineTo(cosf(theta), sinf(theta));
}
builder.fillRule(fillRule);
renderer->save();
renderer->translate(100, 100);
renderer->scale(80, 80);
renderer->drawPath(builder.detach(), paint);
renderer->restore();
}
void drawStar13(Renderer* renderer,
RenderPaint* paint,
FillRule fillRule = FillRule::evenOdd)
{
PathBuilder builder;
float theta = 0;
for (int i = 0; i <= 13; ++i)
{
theta += 2 * math::PI * 3 / 13;
builder.lineTo(cosf(theta), sinf(theta));
}
builder.fillRule(fillRule);
renderer->save();
renderer->translate(256 - 100, 256 - 100);
renderer->scale(80, 80);
renderer->drawPath(builder.detach(), paint);
renderer->restore();
}
protected:
const BlendMode m_blendMode;
const bool m_riveRenderable;
const ColorInt m_clearColor;
};
GMREGISTER(offscreen_render_target,
return new OffscreenRenderTarget(BlendMode::srcOver, true))
GMREGISTER(offscreen_render_target_nonrenderable,
return new OffscreenRenderTarget(BlendMode::srcOver, false))
GMREGISTER(offscreen_render_target_lum,
return new OffscreenRenderTarget(BlendMode::luminosity, true))
GMREGISTER(offscreen_render_target_lum_nonrenderable,
return new OffscreenRenderTarget(BlendMode::luminosity, false))
GMREGISTER(offscreen_render_target_transparent_clear,
return new OffscreenRenderTarget(BlendMode::srcOver,
true,
0x7fff00ff))
GMREGISTER(offscreen_render_target_nonrenderable_transparent_clear,
return new OffscreenRenderTarget(BlendMode::srcOver,
false,
0x7fff00ff))
GMREGISTER(offscreen_render_target_lum_transparent_clear,
return new OffscreenRenderTarget(BlendMode::luminosity,
true,
0x7fff00ff))
GMREGISTER(offscreen_render_target_lum_nonrenderable_transparent_clear,
return new OffscreenRenderTarget(BlendMode::luminosity,
false,
0x7fff00ff))
// This GM checks that texture targets (including MSAA targets) work with
// LoadAction::preserveRenderTarget.
class OffscreenRenderTargetPreserve : public OffscreenRenderTarget
{
public:
OffscreenRenderTargetPreserve(BlendMode blendMode, bool riveRenderable) :
OffscreenRenderTarget(blendMode, riveRenderable)
{}
OffscreenRenderTargetPreserve(bool riveRenderable) :
OffscreenRenderTargetPreserve(BlendMode::srcOver, riveRenderable)
{}
virtual void drawInternal(Renderer* renderer, RenderTarget* renderTarget)
{
ColorInt colors[2];
float stops[2];
colors[0] = 0xff000000;
stops[0] = 0;
colors[1] = 0xffff00ff;
stops[1] = 1;
Paint paint;
paint->shader(TestingWindow::Get()
->factory()
->makeLinearGradient(0, 0, 250, 0, colors, stops, 2));
renderer->drawPath(PathBuilder::Rect({0, 0, 256, 256}), paint);
colors[0] = 0x80000000;
stops[0] = 0;
colors[1] = 0x8000ffff;
stops[1] = 1;
Paint paint2;
paint2->shader(
TestingWindow::Get()
->factory()
->makeLinearGradient(0, 0, 0, 250, colors, stops, 2));
renderer->drawPath(PathBuilder::Rect({0, 0, 256, 256}), paint2);
if (auto renderContext = TestingWindow::Get()->renderContext())
{
auto frameDescriptor = renderContext->frameDescriptor();
#ifndef RIVE_TOOLS_NO_GL
if (auto plsImplGL = TestingWindow::Get()->renderContextGLImpl())
{
assert(renderTarget);
TestingWindow::Get()->flushPLSContext(renderTarget);
if (int sampleCount = frameDescriptor.msaaSampleCount)
{
// If the MSAA framebuffer target is not the target texture,
// wipe it to red behind the scenes in order to make sure
// our preservation codepath works. (It shouldn't appear red
// in the end -- this should get preserved instead.)
if (!plsImplGL->capabilities()
.EXT_multisampled_render_to_texture)
{
auto renderTextureTargetGL =
static_cast<RenderTargetGL*>(renderTarget);
renderTextureTargetGL->bindMSAAFramebuffer(plsImplGL,
sampleCount,
nullptr,
nullptr);
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
}
}
}
else
#endif
{
TestingWindow::Get()->flushPLSContext(renderTarget);
}
frameDescriptor.loadAction = gpu::LoadAction::preserveRenderTarget;
renderContext->beginFrame(std::move(frameDescriptor));
}
Paint paint5(0x8000ffff);
paint5->blendMode(m_blendMode);
drawStar5(renderer, paint5);
if (auto renderContext = TestingWindow::Get()->renderContext())
{
auto frameDescriptor = renderContext->frameDescriptor();
#ifndef RIVE_TOOLS_NO_GL
if (auto plsImplGL = TestingWindow::Get()->renderContextGLImpl())
{
assert(renderTarget);
TestingWindow::Get()->flushPLSContext(renderTarget);
if (int sampleCount = frameDescriptor.msaaSampleCount)
{
// If the MSAA framebuffer target is not the target texture,
// wipe it to red behind the scenes in order to make sure
// our preservation codepath works. (It shouldn't appear red
// in the end -- this should get preserved instead.)
if (!plsImplGL->capabilities()
.EXT_multisampled_render_to_texture)
{
auto renderTextureTargetGL =
static_cast<RenderTargetGL*>(renderTarget);
renderTextureTargetGL->bindMSAAFramebuffer(plsImplGL,
sampleCount,
nullptr,
nullptr);
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
}
}
}
else
#endif
{
TestingWindow::Get()->flushPLSContext(renderTarget);
}
frameDescriptor.loadAction = gpu::LoadAction::preserveRenderTarget;
renderContext->beginFrame(std::move(frameDescriptor));
}
Paint paint13(0x80ffff00);
paint13->blendMode(m_blendMode);
drawStar13(renderer, paint13);
}
};
GMREGISTER(offscreen_render_target_preserve,
return new OffscreenRenderTargetPreserve(true))
GMREGISTER(offscreen_render_target_preserve_nonrenderable,
return new OffscreenRenderTargetPreserve(false))
// ...And verify that blend modes work on a texture target.
class OffscreenRenderTargetPreserveLum : public OffscreenRenderTargetPreserve
{
public:
OffscreenRenderTargetPreserveLum(bool riveRenderable) :
OffscreenRenderTargetPreserve(BlendMode::luminosity, riveRenderable)
{}
};
GMREGISTER(offscreen_render_target_preserve_lum,
return new OffscreenRenderTargetPreserveLum(true))
GMREGISTER(offscreen_render_target_preserve_lum_nonrenderable,
return new OffscreenRenderTargetPreserveLum(false))
// This GM checks that virtual tiles work with various types of render targets.
class OffscreenVirtualTiles : public OffscreenRenderTarget
{
public:
OffscreenVirtualTiles(gpu::LoadAction loadAction, BlendMode blendMode) :
OffscreenRenderTarget(blendMode, /*riveRenderable=*/false),
m_loadAction(loadAction)
{}
virtual void drawInternal(Renderer* renderer, RenderTarget* renderTarget)
{
ColorInt colors[2];
float stops[2];
colors[0] = 0xff000000;
stops[0] = 0;
colors[1] = 0xffff00ff;
stops[1] = 1;
Paint paint;
paint->shader(TestingWindow::Get()
->factory()
->makeLinearGradient(0, 0, 250, 0, colors, stops, 2));
renderer->drawPath(PathBuilder::Rect({0, 0, 256, 256}), paint);
colors[0] = 0x80000000;
stops[0] = 0;
colors[1] = 0x8000ffff;
stops[1] = 1;
Paint paint2;
paint2->shader(
TestingWindow::Get()
->factory()
->makeLinearGradient(0, 0, 0, 250, colors, stops, 2));
renderer->drawPath(PathBuilder::Rect({0, 0, 256, 256}), paint2);
if (auto renderContext = TestingWindow::Get()->renderContext())
{
auto frameDescriptor = renderContext->frameDescriptor();
TestingWindow::Get()->flushPLSContext(renderTarget);
frameDescriptor.loadAction = m_loadAction;
frameDescriptor.virtualTileWidth = 37;
frameDescriptor.virtualTileHeight = 23;
renderContext->beginFrame(std::move(frameDescriptor));
}
Path largePathThatUsesInteriorTriangles;
Paint transWhite;
transWhite->color(0x80ffffff);
transWhite->blendMode(m_blendMode);
largePathThatUsesInteriorTriangles->addRect(0, 0, 1000, 1000);
renderer->drawPath(largePathThatUsesInteriorTriangles.get(),
transWhite.get());
Paint paint5(0x8000ffff);
paint5->blendMode(m_blendMode);
drawStar5(renderer, paint5, FillRule::clockwise);
auto img = LoadImage(assets::nomoon_png());
{
AutoRestore ar(renderer, true);
renderer->translate(100, 30);
renderer->scale(.4f, .4f);
renderer->drawImage(img.get(),
ImageSampler::LinearClamp(),
m_blendMode,
.5f);
}
{
Factory* factory = TestingWindow::Get()->factory();
auto pts = factory->makeRenderBuffer(
RenderBufferType::vertex,
RenderBufferFlags::mappedOnceAtInitialization,
4 * sizeof(Vec2D));
memcpy(pts->map(),
std::array<Vec2D, 4>{Vec2D{110, 110},
Vec2D{200, 110},
Vec2D{10, 200},
Vec2D{270, 270}}
.data(),
pts->sizeInBytes());
pts->unmap();
auto uvs = factory->makeRenderBuffer(
RenderBufferType::vertex,
RenderBufferFlags::mappedOnceAtInitialization,
4 * sizeof(Vec2D));
memcpy(uvs->map(),
std::array<Vec2D, 4>{Vec2D{0, 0},
Vec2D{1, 0},
Vec2D{0, 1},
Vec2D{1, 1}}
.data(),
uvs->sizeInBytes());
uvs->unmap();
auto indices = factory->makeRenderBuffer(
RenderBufferType::index,
RenderBufferFlags::mappedOnceAtInitialization,
6 * sizeof(uint16_t));
memcpy(indices->map(),
std::array<uint16_t, 6>{0, 1, 2, 1, 2, 3}.data(),
indices->sizeInBytes());
indices->unmap();
renderer->drawImageMesh(img.get(),
ImageSampler::LinearClamp(),
pts,
uvs,
indices,
4,
6,
m_blendMode,
.7f);
}
Paint paint13(0x80ffff00);
paint13->blendMode(m_blendMode);
paint13->feather(1);
drawStar13(renderer, paint13, FillRule::clockwise);
Paint white(0xffffffff);
paint13->blendMode(m_blendMode);
uint64_t lcg = 12789;
for (size_t i = 0; i < 2000; ++i)
{
AutoRestore ar(renderer, true);
renderer->translate(lcg & 0xff, (lcg >> 8) & 0xff);
renderer->scale(.01f, .01f);
drawStar5(renderer, white, FillRule::clockwise);
lcg = lcg * 6364136223846793005ULL + 1;
}
}
private:
const gpu::LoadAction m_loadAction;
};
GMREGISTER(offscreen_virtual_tiles_nonrenderable,
return new OffscreenVirtualTiles(gpu::LoadAction::clear,
BlendMode::srcOver))
GMREGISTER(
offscreen_virtual_tiles_preserve_nonrenderable,
return new OffscreenVirtualTiles(gpu::LoadAction::preserveRenderTarget,
BlendMode::srcOver))
GMREGISTER(offscreen_virtual_tiles_lum_nonrenderable,
return new OffscreenVirtualTiles(gpu::LoadAction::clear,
BlendMode::luminosity))
GMREGISTER(
offscreen_virtual_tiles_preserve_lum_nonrenderable,
return new OffscreenVirtualTiles(gpu::LoadAction::preserveRenderTarget,
BlendMode::luminosity))