blob: 9d59cabbd200a46b3fc142c2989cd5b0a5a24a4e [file] [log] [blame] [edit]
/*
* Copyright 2022 Rive
*/
#include "rive/renderer/gl/gl_utils.hpp"
#include "rive/shapes/paint/image_sampler.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,
DebugPrintErrorAndAbort debugPrintErrornAndAbort)
{
CompileAndAttachShader(program,
type,
nullptr,
0,
&source,
1,
capabilities,
debugPrintErrornAndAbort);
}
void CompileAndAttachShader(GLuint program,
GLenum type,
const char* defines[],
size_t numDefines,
const char* inputSources[],
size_t numInputSources,
const GLCapabilities& capabilities,
DebugPrintErrorAndAbort debugPrintErrornAndAbort)
{
GLuint shader = CompileShader(type,
defines,
numDefines,
inputSources,
numInputSources,
capabilities,
debugPrintErrornAndAbort);
glAttachShader(program, shader);
glDeleteShader(shader);
}
GLuint CompileShader(GLuint type,
const char* source,
const GLCapabilities& capabilities,
DebugPrintErrorAndAbort debugPrintErrornAndAbort)
{
return CompileShader(type,
nullptr,
0,
&source,
1,
capabilities,
debugPrintErrornAndAbort);
}
GLuint CompileShader(GLuint type,
const char* defines[],
size_t numDefines,
const char* inputSources[],
size_t numInputSources,
const GLCapabilities& capabilities,
DebugPrintErrorAndAbort debugPrintErrornAndAbort)
{
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';
}
if (capabilities.needsFloatingPointTessellationTexture)
{
shaderSource << "#define " << GLSL_TESS_TEXTURE_FLOATING_POINT << '\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(),
debugPrintErrornAndAbort);
}
#ifdef DEBUG
void PrintShaderCompilationErrors(GLuint shader)
{
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]);
GLint sourceLength = 0;
glGetShaderiv(shader, GL_SHADER_SOURCE_LENGTH, &sourceLength);
std::vector<GLchar> shaderSource(sourceLength);
glGetShaderSource(shader, sourceLength, nullptr, shaderSource.data());
int l = 1;
std::stringstream stream(shaderSource.data());
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);
}
#endif
[[nodiscard]] GLuint CompileRawGLSL(
GLenum shaderType,
const char* rawGLSL,
DebugPrintErrorAndAbort debugPrintErrornAndAbort)
{
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
if (debugPrintErrornAndAbort == DebugPrintErrorAndAbort::yes)
{
GLint isCompiled = 0;
glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
if (isCompiled == GL_FALSE)
{
PrintShaderCompilationErrors(shader);
glDeleteShader(shader);
// Give stderr another second to finish flushing before we abort.
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
abort();
}
}
#else
std::ignore = debugPrintErrornAndAbort;
#endif
return shader;
}
#ifdef DEBUG
void PrintLinkProgramErrors(GLuint program)
{
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);
}
#endif
void LinkProgram(GLuint program,
DebugPrintErrorAndAbort debugPrintErrornAndAbort)
{
glLinkProgram(program);
#ifdef DEBUG
if (debugPrintErrornAndAbort == DebugPrintErrorAndAbort::yes)
{
GLint isLinked = 0;
glGetProgramiv(program, GL_LINK_STATUS, &isLinked);
if (isLinked == GL_FALSE)
{
PrintLinkProgramErrors(program);
abort();
}
}
#else
std::ignore = debugPrintErrornAndAbort;
#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);
}
GLint gl_wrap_from_image_wrap(rive::ImageWrap wrap)
{
switch (wrap)
{
case rive::ImageWrap::clamp:
return GL_CLAMP_TO_EDGE;
case rive::ImageWrap::repeat:
return GL_REPEAT;
case rive::ImageWrap::mirror:
return GL_MIRRORED_REPEAT;
}
RIVE_UNREACHABLE();
}
GLint gl_min_filter_for_image_filter(rive::ImageFilter filter)
{
switch (filter)
{
case rive::ImageFilter::bilinear:
return GL_LINEAR_MIPMAP_NEAREST;
case rive::ImageFilter::nearest:
return GL_NEAREST;
}
RIVE_UNREACHABLE();
}
GLint gl_mag_filter_for_image_filter(rive::ImageFilter filter)
{
switch (filter)
{
case rive::ImageFilter::nearest:
return GL_NEAREST;
case rive::ImageFilter::bilinear:
return GL_LINEAR;
}
RIVE_UNREACHABLE();
}
void SetTexture2DSamplingParams(rive::ImageSampler samplingParams)
{
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MIN_FILTER,
gl_min_filter_for_image_filter(samplingParams.filter));
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_MAG_FILTER,
gl_mag_filter_for_image_filter(samplingParams.filter));
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_WRAP_S,
gl_wrap_from_image_wrap(samplingParams.wrapX));
glTexParameteri(GL_TEXTURE_2D,
GL_TEXTURE_WRAP_T,
gl_wrap_from_image_wrap(samplingParams.wrapY));
}
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