blob: 709cfdd448fea9bcaff3602a97d8c16e4ce621a2 [file] [log] [blame]
/*
* Copyright 2024 Rive
*/
#include "gm.hpp"
#include "gmutils.hpp"
#include "common/testing_window.hpp"
#include "rive/renderer/render_context.hpp"
#include "rive/renderer/rive_renderer.hpp"
#ifdef RIVE_TOOLS_NO_GL
namespace rive::gpu
{
class TextureRenderTargetGL;
};
using GLuint = uint32_t;
#else
#include "rive/renderer/gl/render_context_gl_impl.hpp"
#include "rive/renderer/gl/render_target_gl.hpp"
#endif
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 TextureTargetGL : public GM
{
public:
TextureTargetGL(const char* name) : GM(256, 256, name) {}
TextureTargetGL() : TextureTargetGL("texture_target_gl") {}
ColorInt clearColor() const override { return 0xffff0000; }
void onDraw(rive::Renderer* originalRenderer) override
{
#ifndef RIVE_TOOLS_NO_GL
if (auto plsImplGL = TestingWindow::Get()->renderContextGLImpl())
{
// Intercept the current frameDescriptor and end the PLS frame.
auto renderContext = TestingWindow::Get()->renderContext();
auto originalFrameDescriptor = renderContext->frameDescriptor();
auto originalRenderTarget = static_cast<RenderTargetGL*>(
TestingWindow::Get()->renderTarget());
TestingWindow::Get()->flushPLSContext();
plsImplGL->unbindGLInternalResources();
// Create an offscreen texture.
if (m_offscreenTex == 0)
{
glGenTextures(1, &m_offscreenTex);
glBindTexture(GL_TEXTURE_2D, m_offscreenTex);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, 256, 256);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MIN_FILTER,
GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MAG_FILTER,
GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_WRAP_S,
GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_WRAP_T,
GL_CLAMP_TO_EDGE);
}
auto textureTargetGL = make_rcp<TextureRenderTargetGL>(256, 256);
textureTargetGL->setTargetTexture(m_offscreenTex);
plsImplGL->invalidateGLState();
// Draw to the offscreen texture.
auto textureFrameDescriptor = originalFrameDescriptor;
textureFrameDescriptor.clearColor = 0xffff00ff;
renderContext->beginFrame(std::move(textureFrameDescriptor));
RiveRenderer renderer(renderContext);
drawInternal(&renderer, textureTargetGL.get());
renderContext->flush({.renderTarget = textureTargetGL.get()});
// Copy the offscreen texture back to the destination framebuffer.
auto copyFrameDescriptor = originalFrameDescriptor;
copyFrameDescriptor.loadAction =
gpu::LoadAction::preserveRenderTarget;
copyFrameDescriptor.clearColor = 0xffff0000;
renderContext->beginFrame(std::move(copyFrameDescriptor));
originalRenderTarget->bindDestinationFramebuffer(GL_FRAMEBUFFER);
if (plsImplGL->capabilities().isAndroidANGLE)
{
// Some android devices shipping with ANGLE have a
// syncronization issue drawing this texture into the main
// framebuffer. glFinish is a very heavy handed workaround, but
// it's the only one so far to work.
// Sadly, we can't incorporate a workaround this heavy into the
// main GL runtime. Apps that run into it will have to come up
// with the best workaround for them.
glFinish();
}
plsImplGL->blitTextureToFramebufferAsDraw(m_offscreenTex,
{0, 0, 256, 256},
256);
}
else
#endif
{
// We aren't PLS/GL, but still draw with a red background, just for
// fun.
drawInternal(originalRenderer, nullptr);
}
}
virtual void drawInternal(Renderer* renderer, TextureRenderTargetGL*)
{
drawStar5(renderer, Paint(0x8000ffff));
drawStar13(renderer, Paint(0x80ffff00));
}
void drawStar5(Renderer* renderer, RenderPaint* paint)
{
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));
}
renderer->save();
renderer->translate(100, 100);
renderer->scale(80, 80);
renderer->drawPath(builder.detach(), paint);
renderer->restore();
}
void drawStar13(Renderer* renderer, RenderPaint* paint)
{
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::evenOdd);
renderer->save();
renderer->translate(256 - 100, 256 - 100);
renderer->scale(80, 80);
renderer->drawPath(builder.detach(), paint);
renderer->restore();
}
~TextureTargetGL()
{
#ifdef RIVE_TOOLS_NO_GL
assert(m_offscreenTex == 0);
#else
if (m_offscreenTex != 0)
{
glDeleteTextures(1, &m_offscreenTex);
}
#endif
}
private:
GLuint m_offscreenTex = 0;
};
GMREGISTER(return new TextureTargetGL)
// This GM checks that texture targets (including MSAA targets) work with
// LoadAction::preserveRenderTarget.
class TextureTargetGLPreserve : public TextureTargetGL
{
public:
TextureTargetGLPreserve(const char* name, BlendMode blendMode) :
TextureTargetGL(name), m_blendMode(blendMode)
{}
TextureTargetGLPreserve() :
TextureTargetGLPreserve("texture_target_gl_preserve",
BlendMode::srcOver)
{}
virtual void drawInternal(Renderer* renderer,
TextureRenderTargetGL* renderTextureTargetGL)
{
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(renderTextureTargetGL);
renderContext->flush({.renderTarget = renderTextureTargetGL});
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)
{
renderTextureTargetGL->bindMSAAFramebuffer(plsImplGL,
sampleCount,
nullptr,
nullptr);
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
}
}
}
else
#endif
{
TestingWindow::Get()->flushPLSContext();
}
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(renderTextureTargetGL);
renderContext->flush({.renderTarget = renderTextureTargetGL});
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)
{
renderTextureTargetGL->bindMSAAFramebuffer(plsImplGL,
sampleCount,
nullptr,
nullptr);
glClearColor(1, 0, 0, 1);
glClear(GL_COLOR_BUFFER_BIT);
}
}
}
else
#endif
{
TestingWindow::Get()->flushPLSContext();
}
frameDescriptor.loadAction = gpu::LoadAction::preserveRenderTarget;
renderContext->beginFrame(std::move(frameDescriptor));
}
Paint paint13(0x80ffff00);
paint13->blendMode(m_blendMode);
drawStar13(renderer, paint13);
}
private:
BlendMode m_blendMode;
};
GMREGISTER(return new TextureTargetGLPreserve)
// ...And verify that blend modes work on a texture target.
class TextureTargetGLPreserveLum : public TextureTargetGLPreserve
{
public:
TextureTargetGLPreserveLum() :
TextureTargetGLPreserve("texture_target_gl_preserve_lum",
BlendMode::luminosity)
{}
};
GMREGISTER(return new TextureTargetGLPreserveLum)