blob: 6bfde495c5cd5272a1e2a1d3a6fcddc89b60854d [file] [log] [blame]
/*
* Copyright 2023 Rive
*/
#include "rive/pls/gl/pls_render_context_gl_impl.hpp"
#include "gl/gl_utils.hpp"
#include "rive/math/simd.hpp"
#include "rive/pls/gl/pls_render_target_gl.hpp"
#include <vector>
#include "../out/obj/generated/glsl.exports.h"
#include "../out/obj/generated/pls_load_store_ext.glsl.hpp"
// We have to manually implement load/store operations from a shader when using
// EXT_shader_pixel_local_storage. These bits define specific operations that can be turned on or
// off in that shader.
namespace loadstoreops
{
constexpr static uint32_t kClearColor = 1;
constexpr static uint32_t kLoadColor = 2;
constexpr static uint32_t kStoreColor = 4;
constexpr static uint32_t kClearCoverage = 8;
constexpr static uint32_t kClearClip = 16;
}; // namespace loadstoreops
namespace rive::pls
{
// Wraps an EXT_shader_pixel_local_storage load/store program, described by a set of loadstoreops.
class PLSLoadStoreProgram
{
public:
PLSLoadStoreProgram(const PLSLoadStoreProgram&) = delete;
PLSLoadStoreProgram& operator=(const PLSLoadStoreProgram&) = delete;
PLSLoadStoreProgram(uint32_t ops, GLuint vertexShader, const GLExtensions& extensions)
{
std::vector<const char*> defines{GLSL_PLS_IMPL_EXT_NATIVE};
if (ops & loadstoreops::kClearColor)
{
defines.push_back(GLSL_CLEAR_COLOR);
}
if (ops & loadstoreops::kLoadColor)
{
defines.push_back(GLSL_LOAD_COLOR);
}
if (ops & loadstoreops::kStoreColor)
{
defines.push_back(GLSL_STORE_COLOR);
}
if (ops & loadstoreops::kClearCoverage)
{
defines.push_back(GLSL_CLEAR_COVERAGE);
}
if (ops & loadstoreops::kClearClip)
{
defines.push_back(GLSL_CLEAR_CLIP);
}
const char* source = glsl::pls_load_store_ext;
m_id = glCreateProgram();
glAttachShader(m_id, vertexShader);
glutils::CompileAndAttachShader(m_id,
GL_FRAGMENT_SHADER,
defines.data(),
defines.size(),
&source,
1,
extensions);
glutils::LinkProgram(m_id);
if (ops & loadstoreops::kClearColor)
{
m_clearColorUniLocation = glGetUniformLocation(m_id, GLSL_clearColor);
}
}
~PLSLoadStoreProgram() { glDeleteProgram(m_id); }
GLuint id() const { return m_id; }
GLint clearColorUniLocation() const { return m_clearColorUniLocation; }
private:
GLuint m_id;
GLint m_clearColorUniLocation = -1;
};
class PLSRenderContextGLImpl::PLSImplEXTNative : public PLSRenderContextGLImpl::PLSImpl
{
public:
PLSImplEXTNative(const GLExtensions& extensions) : m_extensions(extensions)
{
// We have to manually implement load/store operations from a shader when using
// EXT_shader_pixel_local_storage.
m_plsLoadStoreVertexShader =
glutils::CompileShader(GL_VERTEX_SHADER, glsl::pls_load_store_ext, extensions);
glGenVertexArrays(1, &m_plsLoadStoreVAO);
}
~PLSImplEXTNative()
{
glDeleteShader(m_plsLoadStoreVertexShader);
glDeleteVertexArrays(1, &m_plsLoadStoreVAO);
}
rcp<PLSRenderTargetGL> wrapGLRenderTarget(GLuint framebufferID,
size_t width,
size_t height,
const PlatformFeatures& platformFeatures) override
{
return rcp(new PLSRenderTargetGL(framebufferID, width, height, platformFeatures));
}
rcp<PLSRenderTargetGL> makeOffscreenRenderTarget(
size_t width,
size_t height,
const PlatformFeatures& platformFeatures) override
{
return rcp(new PLSRenderTargetGL(width, height, platformFeatures));
}
void activatePixelLocalStorage(PLSRenderContextGLImpl* context,
const PLSRenderContext::FlushDescriptor& desc) override
{
assert(context->m_extensions.EXT_shader_pixel_local_storage);
assert(context->m_extensions.EXT_shader_framebuffer_fetch ||
context->m_extensions.ARM_shader_framebuffer_fetch);
auto renderTarget = static_cast<const PLSRenderTargetGL*>(desc.renderTarget);
glBindFramebuffer(GL_FRAMEBUFFER, renderTarget->drawFramebufferID());
glEnable(GL_SHADER_PIXEL_LOCAL_STORAGE_EXT);
uint32_t ops = loadstoreops::kClearCoverage;
float clearColor4f[4];
if (desc.loadAction == LoadAction::clear)
{
UnpackColorToRGBA32F(desc.clearColor, clearColor4f);
ops |= loadstoreops::kClearColor;
}
else
{
ops |= loadstoreops::kLoadColor;
}
if (desc.needsClipBuffer)
{
ops |= loadstoreops::kClearClip;
}
if ((ops & loadstoreops::kClearColor) && simd::all(simd::load4f(clearColor4f) == 0))
{
// glClear() is only well-defined for EXT_shader_pixel_local_storage when the clear
// color is zero, and it zeros out every plane of pixel local storage.
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
}
else
{
// Otherwise we have to initialize pixel local storage with a fullscreen draw.
const PLSLoadStoreProgram& plsProgram =
m_plsLoadStorePrograms
.try_emplace(ops, ops, m_plsLoadStoreVertexShader, m_extensions)
.first->second;
context->bindProgram(plsProgram.id());
if (plsProgram.clearColorUniLocation() >= 0)
{
glUniform4fv(plsProgram.clearColorUniLocation(), 1, clearColor4f);
}
context->bindVAO(m_plsLoadStoreVAO);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
}
void deactivatePixelLocalStorage(PLSRenderContextGLImpl* context) override
{
// Issue a fullscreen draw that transfers the color information in pixel local storage to
// the main framebuffer.
uint32_t ops = loadstoreops::kStoreColor;
const PLSLoadStoreProgram& plsProgram =
m_plsLoadStorePrograms.try_emplace(ops, ops, m_plsLoadStoreVertexShader, m_extensions)
.first->second;
context->bindProgram(plsProgram.id());
context->bindVAO(m_plsLoadStoreVAO);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisable(GL_SHADER_PIXEL_LOCAL_STORAGE_EXT);
}
const char* shaderDefineName() const override { return GLSL_PLS_IMPL_EXT_NATIVE; }
private:
const GLExtensions m_extensions;
std::map<uint32_t, PLSLoadStoreProgram> m_plsLoadStorePrograms; // Keyed by loadstoreops.
GLuint m_plsLoadStoreVertexShader = 0;
GLuint m_plsLoadStoreVAO = 0;
};
std::unique_ptr<PLSRenderContextGLImpl::PLSImpl> PLSRenderContextGLImpl::MakePLSImplEXTNative(
const GLExtensions& extensions)
{
return std::make_unique<PLSImplEXTNative>(extensions);
}
} // namespace rive::pls