blob: 5b8ef22db953f6dc330fe7c7c6095baf8aeb5a7c [file] [log] [blame] [edit]
/*
* Copyright 2022 Rive
*/
#include "rive/renderer/gl/gl_utils.hpp"
#include <stdio.h>
#include <sstream>
#include <thread>
#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";
}
if (!capabilities.ANGLE_base_vertex_base_instance_shader_builtin)
{
shaderSource << "#define " << GLSL_BASE_INSTANCE_UNIFORM_NAME << ' '
<< BASE_INSTANCE_UNIFORM_NAME << '\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(GLenum 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");
// Print the error message *before* the shader in case stderr hasn't
// finished flushing when we call abort() further on.
fprintf(stderr, "%s\n", &infoLog[0]);
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());
}
// Print the error message, again, *after* the shader where it's easier
// to find in the console.
fprintf(stderr, "%s\n", &infoLog[0]);
fflush(stderr);
glDeleteShader(shader);
// Give stderr another second to finish flushing before we abort.
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
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)
{
m_fragmentShader.reset();
m_vertexShader.reset();
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);
glutils::Shader& internalShader =
type == GL_VERTEX_SHADER ? m_vertexShader : m_fragmentShader;
internalShader
.compile(type, defines, numDefines, sources, numSources, capabilities);
glAttachShader(m_id, internalShader);
}
void Shader::compile(GLenum type,
const char* defines[],
size_t numDefines,
const char* sources[],
size_t numSources,
const GLCapabilities& capabilities)
{
reset(CompileShader(type,
defines,
numDefines,
sources,
numSources,
capabilities));
}
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);
}
// Setup a small test to verify that GL_PIXEL_LOCAL_FORMAT_ANGLE has the correct
// value.
bool validate_pixel_local_storage_angle()
{
#if defined(RIVE_DESKTOP_GL) || defined(RIVE_WEBGL)
glutils::Texture tex;
glBindTexture(GL_TEXTURE_2D, tex);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_R32UI, 1, 1);
glutils::Framebuffer fbo;
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexturePixelLocalStorageANGLE(0, tex, 0, 0);
// Clear the error queue. (Should already be empty.)
while (GLenum err = glGetError())
{
fprintf(stderr, "WARNING: unhandled GL error 0x%x\n", err);
}
GLint format = GL_NONE;
glGetFramebufferPixelLocalStorageParameterivANGLE(
0,
GL_PIXEL_LOCAL_FORMAT_ANGLE,
&format);
return glGetError() == GL_NONE && format == GL_R32UI;
#else
return false;
#endif
}
} // namespace glutils