blob: 089a34cef4265d898777c4c593a7853baa876b9a [file] [log] [blame]
/*
* Copyright 2023 Rive
*/
#include "rive/pls/gl/pls_render_context_gl_impl.hpp"
#include "rive/pls/gl/load_store_actions_ext.hpp"
#include "rive/pls/gl/gl_utils.hpp"
#include "rive/math/simd.hpp"
#include "rive/pls/gl/pls_render_target_gl.hpp"
#include <sstream>
#include "generated/shaders/pls_load_store_ext.exports.h"
namespace rive::pls
{
// Wraps an EXT_shader_pixel_local_storage load/store program, described by a set of
// LoadStoreActions.
class PLSLoadStoreProgram
{
public:
PLSLoadStoreProgram(const PLSLoadStoreProgram&) = delete;
PLSLoadStoreProgram& operator=(const PLSLoadStoreProgram&) = delete;
PLSLoadStoreProgram(LoadStoreActionsEXT actions,
GLuint vertexShader,
pls::ShaderFeatures combinedShaderFeatures,
rcp<GLState> state) :
m_state(std::move(state))
{
m_id = glCreateProgram();
glAttachShader(m_id, vertexShader);
std::ostringstream glsl;
glsl << "#version 300 es\n";
glsl << "#define " GLSL_FRAGMENT "\n";
if (combinedShaderFeatures & pls::ShaderFeatures::ENABLE_CLIPPING)
{
glsl << "#define " GLSL_ENABLE_CLIPPING "\n";
}
BuildLoadStoreEXTGLSL(glsl, actions);
GLuint fragmentShader = glutils::CompileRawGLSL(GL_FRAGMENT_SHADER, glsl.str().c_str());
glAttachShader(m_id, fragmentShader);
glDeleteShader(fragmentShader);
glutils::LinkProgram(m_id);
if (actions & LoadStoreActionsEXT::clearColor)
{
m_colorClearUniLocation = glGetUniformLocation(m_id, GLSL_clearColor);
}
}
~PLSLoadStoreProgram() { m_state->deleteProgram(m_id); }
GLuint id() const { return m_id; }
GLint clearColorUniLocation() const { return m_colorClearUniLocation; }
private:
GLuint m_id;
GLint m_colorClearUniLocation = -1;
const rcp<GLState> m_state;
};
class PLSRenderContextGLImpl::PLSImplEXTNative : public PLSRenderContextGLImpl::PLSImpl
{
public:
PLSImplEXTNative(const GLCapabilities& capabilities) : m_capabilities(capabilities) {}
~PLSImplEXTNative()
{
for (GLuint shader : m_plsLoadStoreVertexShaders)
{
if (shader != 0)
{
glDeleteShader(shader);
}
}
m_state->deleteVAO(m_plsLoadStoreVAO);
}
void init(rcp<GLState> state) override { m_state = std::move(state); }
bool supportsRasterOrdering(const GLCapabilities&) const override { return true; }
void activatePixelLocalStorage(PLSRenderContextGLImpl* impl,
const FlushDescriptor& desc) override
{
assert(impl->m_capabilities.EXT_shader_pixel_local_storage);
assert(impl->m_capabilities.EXT_shader_framebuffer_fetch ||
impl->m_capabilities.ARM_shader_framebuffer_fetch);
auto renderTarget = static_cast<PLSRenderTargetGL*>(desc.renderTarget);
renderTarget->bindDestinationFramebuffer(GL_FRAMEBUFFER);
glEnable(GL_SHADER_PIXEL_LOCAL_STORAGE_EXT);
std::array<float, 4> clearColor4f;
LoadStoreActionsEXT actions = BuildLoadActionsEXT(desc, &clearColor4f);
if ((actions & LoadStoreActionsEXT::clearColor) &&
simd::all(simd::load4f(clearColor4f.data()) == 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 =
findLoadStoreProgram(actions, desc.combinedShaderFeatures);
m_state->bindProgram(plsProgram.id());
if (plsProgram.clearColorUniLocation() >= 0)
{
glUniform4fv(plsProgram.clearColorUniLocation(), 1, clearColor4f.data());
}
m_state->bindVAO(m_plsLoadStoreVAO);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
}
void deactivatePixelLocalStorage(PLSRenderContextGLImpl* impl,
const FlushDescriptor& desc) override
{
// Issue a fullscreen draw that transfers the color information in pixel local storage to
// the main framebuffer.
LoadStoreActionsEXT actions = LoadStoreActionsEXT::storeColor;
m_state->bindProgram(findLoadStoreProgram(actions, desc.combinedShaderFeatures).id());
m_state->bindVAO(m_plsLoadStoreVAO);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
glDisable(GL_SHADER_PIXEL_LOCAL_STORAGE_EXT);
}
void pushShaderDefines(pls::InterlockMode, std::vector<const char*>* defines) const override
{
defines->push_back(GLSL_PLS_IMPL_EXT_NATIVE);
}
private:
const PLSLoadStoreProgram& findLoadStoreProgram(LoadStoreActionsEXT actions,
pls::ShaderFeatures combinedShaderFeatures)
{
bool hasClipping = combinedShaderFeatures & pls::ShaderFeatures::ENABLE_CLIPPING;
uint32_t programKey =
(static_cast<uint32_t>(actions) << 1) | static_cast<uint32_t>(hasClipping);
if (m_plsLoadStoreVertexShaders[hasClipping] == 0)
{
std::ostringstream glsl;
glsl << "#version 300 es\n";
glsl << "#define " GLSL_VERTEX "\n";
if (hasClipping)
{
glsl << "#define " GLSL_ENABLE_CLIPPING "\n";
}
BuildLoadStoreEXTGLSL(glsl, LoadStoreActionsEXT::none);
m_plsLoadStoreVertexShaders[hasClipping] =
glutils::CompileRawGLSL(GL_VERTEX_SHADER, glsl.str().c_str());
glGenVertexArrays(1, &m_plsLoadStoreVAO);
}
return m_plsLoadStorePrograms
.try_emplace(programKey,
actions,
m_plsLoadStoreVertexShaders[hasClipping],
combinedShaderFeatures,
m_state)
.first->second;
}
const GLCapabilities m_capabilities;
std::map<uint32_t, PLSLoadStoreProgram> m_plsLoadStorePrograms;
GLuint m_plsLoadStoreVertexShaders[2] = {0}; // One with a clip plane and one without.
GLuint m_plsLoadStoreVAO = 0;
rcp<GLState> m_state;
};
std::unique_ptr<PLSRenderContextGLImpl::PLSImpl> PLSRenderContextGLImpl::MakePLSImplEXTNative(
const GLCapabilities& capabilities)
{
return std::make_unique<PLSImplEXTNative>(capabilities);
}
} // namespace rive::pls