blob: fcea9b0c34712e64d229fd14be9eb2996ea9aab4 [file] [log] [blame]
/*
* Copyright 2022 Rive
*/
#include "rive/renderer/gl/gl_utils.hpp"
#include <stdio.h>
#include <sstream>
#include <vector>
#include "generated/shaders/glsl.glsl.hpp"
#ifdef BYPASS_EMSCRIPTEN_SHADER_PARSER
#include <emscripten/emscripten.h>
#include <emscripten/html5.h>
// Emscripten's shader preprocessor crashes on PLS shaders. This method allows
// us to bypass Emscripten and set a WebGL shader source directly.
EM_JS(void,
webgl_shader_source,
(EMSCRIPTEN_WEBGL_CONTEXT_HANDLE gl, GLuint shader, const char* source),
{
gl = GL.getContext(gl).GLctx;
shader = GL.shaders[shader];
source = UTF8ToString(source);
gl.shaderSource(shader, source);
});
#endif
namespace glutils
{
void CompileAndAttachShader(GLuint program,
GLenum type,
const char* source,
const GLCapabilities& capabilities)
{
CompileAndAttachShader(program, type, nullptr, 0, &source, 1, capabilities);
}
void CompileAndAttachShader(GLuint program,
GLenum type,
const char* defines[],
size_t numDefines,
const char* inputSources[],
size_t numInputSources,
const GLCapabilities& capabilities)
{
GLuint shader = CompileShader(type,
defines,
numDefines,
inputSources,
numInputSources,
capabilities);
glAttachShader(program, shader);
glDeleteShader(shader);
}
GLuint CompileShader(GLuint type,
const char* source,
const GLCapabilities& capabilities)
{
return CompileShader(type, nullptr, 0, &source, 1, capabilities);
}
GLuint CompileShader(GLuint type,
const char* defines[],
size_t numDefines,
const char* inputSources[],
size_t numInputSources,
const GLCapabilities& capabilities)
{
std::ostringstream shaderSource;
shaderSource << "#version " << capabilities.contextVersionMajor
<< capabilities.contextVersionMinor << '0';
if (capabilities.isGLES)
{
shaderSource << " es";
}
shaderSource << '\n';
// Create our own "GLSL_VERSION" macro. In "#version 320 es", Qualcomm
// incorrectly substitutes
// __VERSION__ to 300.
shaderSource << "#define " << GLSL_GLSL_VERSION << ' '
<< capabilities.contextVersionMajor
<< capabilities.contextVersionMinor << "0\n";
if (type == GL_VERTEX_SHADER)
{
shaderSource << "#define " << GLSL_VERTEX "\n";
}
else if (GL_FRAGMENT_SHADER)
{
shaderSource << "#define " << GLSL_FRAGMENT "\n";
}
for (size_t i = 0; i < numDefines; ++i)
{
shaderSource << "#define " << defines[i] << " true\n";
}
shaderSource << rive::gpu::glsl::glsl << "\n";
for (size_t i = 0; i < numInputSources; ++i)
{
shaderSource << inputSources[i] << "\n";
}
return CompileRawGLSL(type, shaderSource.str().c_str());
}
[[nodiscard]] GLuint CompileRawGLSL(GLuint shaderType, const char* rawGLSL)
{
GLuint shader = glCreateShader(shaderType);
#ifdef BYPASS_EMSCRIPTEN_SHADER_PARSER
// Emscripten's shader preprocessor crashes on PLS shaders. Feed Emscripten
// something very simple and then hop to WebGL to bypass it and set the real
// shader source.
const char* kMinimalShader =
shaderType == GL_VERTEX_SHADER
? "#version 300 es\nvoid main() { gl_Position = vec4(0); }"
: "#version 300 es\nvoid main() {}";
glShaderSource(shader, 1, &kMinimalShader, nullptr);
webgl_shader_source(emscripten_webgl_get_current_context(),
shader,
rawGLSL);
#else
glShaderSource(shader, 1, &rawGLSL, nullptr);
#endif
glCompileShader(shader);
#ifdef DEBUG
GLint isCompiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
if (isCompiled == GL_FALSE)
{
GLint maxLength = 0;
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
std::vector<GLchar> infoLog(maxLength);
glGetShaderInfoLog(shader, maxLength, &maxLength, &infoLog[0]);
fprintf(stderr, "Failed to compile shader\n");
int l = 1;
std::stringstream stream(rawGLSL);
std::string lineStr;
while (std::getline(stream, lineStr, '\n'))
{
fprintf(stderr, "%4i| %s\n", l++, lineStr.c_str());
}
fprintf(stderr, "%s\n", &infoLog[0]);
fflush(stderr);
glDeleteShader(shader);
abort();
}
#endif
return shader;
}
void LinkProgram(GLuint program)
{
glLinkProgram(program);
#ifdef DEBUG
GLint isLinked = 0;
glGetProgramiv(program, GL_LINK_STATUS, &isLinked);
if (isLinked == GL_FALSE)
{
GLint maxLength = 0;
glGetProgramiv(program, GL_INFO_LOG_LENGTH, &maxLength);
std::vector<GLchar> infoLog(maxLength);
glGetProgramInfoLog(program, maxLength, &maxLength, &infoLog[0]);
fprintf(stderr, "Failed to link program %s\n", &infoLog[0]);
fflush(stderr);
abort();
}
#endif
}
void Program::reset(GLuint adoptedProgramID)
{
if (m_fragmentShaderID != 0)
{
glDeleteShader(m_fragmentShaderID);
m_fragmentShaderID = 0;
}
if (m_vertexShaderID != 0)
{
glDeleteShader(m_vertexShaderID);
m_vertexShaderID = 0;
}
if (m_id != 0)
{
glDeleteProgram(m_id);
}
m_id = adoptedProgramID;
}
void Program::compileAndAttachShader(GLuint type,
const char* defines[],
size_t numDefines,
const char* sources[],
size_t numSources,
const GLCapabilities& capabilities)
{
assert(type == GL_VERTEX_SHADER || type == GL_FRAGMENT_SHADER);
GLuint& internalShaderID =
type == GL_VERTEX_SHADER ? m_vertexShaderID : m_fragmentShaderID;
if (internalShaderID != 0)
{
glDeleteShader(internalShaderID);
}
internalShaderID = CompileShader(type,
defines,
numDefines,
sources,
numSources,
capabilities);
glAttachShader(m_id, internalShaderID);
}
void SetTexture2DSamplingParams(GLenum minFilter, GLenum magFilter)
{
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
void BlitFramebuffer(rive::IAABB bounds,
uint32_t renderTargetHeight,
GLbitfield mask)
{
// glBlitFramebuffer is oriented bottom-up.
uint32_t l = bounds.left;
uint32_t b = renderTargetHeight - bounds.bottom;
uint32_t r = bounds.right;
uint32_t t = renderTargetHeight - bounds.top;
glBlitFramebuffer(l, b, r, t, l, b, r, t, mask, GL_NEAREST);
}
void Uniform1iByName(GLuint programID, const char* name, GLint value)
{
GLint location = glGetUniformLocation(programID, name);
// Don't allow non-existent uniforms. glUniform1i() is supposed to silently
// ignore -1, but Moto G7 Play throws an error. We also just shouldn't be
// querying uniform locations we know aren't going to exist anyway for
// performance reasons.
assert(location != -1);
glUniform1i(location, value);
}
} // namespace glutils