blob: fba6979ba44158f5e8cfa5f81c7a17fe7105348d [file] [log] [blame]
/*
* Copyright 2022 Rive
*/
#include "rive/pls/gl/gles3.hpp"
#ifdef RIVE_WASM
#include <emscripten/emscripten.h>
#include <emscripten/html5.h>
#endif
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
#include <array>
#include <chrono>
#include <cmath>
#include <sstream>
#include <string>
#include <vector>
constexpr static char vs[] = R"(#version 300 es
precision highp float;
uniform vec2 window;
uniform float T;
layout(location=0) in vec3 bubble;
layout(location=1) in vec2 speed;
layout(location=2) in vec4 incolor;
out vec2 coord;
out vec4 color;
void main() {
vec2 offset = vec2((gl_VertexID & 1) == 0 ? -1.0 : 1.0, (gl_VertexID & 2) == 0 ? -1.0 : 1.0);
coord = offset;
color = incolor;
float r = bubble.z;
vec2 center = bubble.xy + speed * T;
vec2 span = window - 2.0 * r;
center = span - abs(span - mod(center - r, span * 2.0)) + r;
gl_Position.xy = (center + offset * r) * 2.0 / window - 1.0;
gl_Position.zw = vec2(0, 1);
})";
constexpr static char fs[] = R"(#version 300 es
#extension GL_ANGLE_shader_pixel_local_storage : require
precision mediump float;
in vec2 coord;
in vec4 color;
layout(binding=0, rgba8) uniform mediump pixelLocalANGLE framebuffer;
float color_burn_component(vec2 s, vec2 d) {
if (d.y == d.x) {
return s.y*d.y + s.x*(1.0 - d.y) + d.x*(1.0 - s.y);
} else if (s.x == 0.0) {
return d.x*(1.0 - s.y);
} else {
float delta = max(0.0, d.y - (d.y - d.x)*s.y / s.x);
return delta*s.y + s.x*(1.0 - d.y) + d.x*(1.0 - s.y);
}
}
vec4 blend(vec4 src, vec4 dst) {
return (dst.a == 0.0) ? src : vec4(color_burn_component(src.ra, dst.ra),
color_burn_component(src.ga, dst.ga),
color_burn_component(src.ba, dst.ba),
src.a + (1.0 - src.a)*dst.a);
}
void main() {
float f = coord.x * coord.x + coord.y * coord.y - 1.0;
float coverage = clamp(.5 - f/fwidth(f), 0.0, 1.0);
vec4 s = vec4(color.rgb, 1) * (color.a * mix(.25, 1.0, dot(coord, coord)) * coverage);
vec4 d = pixelLocalLoadANGLE(framebuffer);
pixelLocalStoreANGLE(framebuffer, blend(s, d));
})";
static bool compile_and_attach_shader(GLuint program, GLuint type, const char* source)
{
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &source, nullptr);
glCompileShader(shader);
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(source);
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);
return false;
}
glAttachShader(program, shader);
glDeleteShader(shader);
return true;
}
static bool link_program(GLuint program)
{
glLinkProgram(program);
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);
return false;
}
return true;
}
#ifdef RIVE_DESKTOP_GL
static void GLAPIENTRY err_msg_callback(GLenum source,
GLenum type,
GLuint id,
GLenum severity,
GLsizei length,
const GLchar* message,
const void* userParam)
{
if (type == GL_DEBUG_TYPE_ERROR)
{
printf("GL ERROR: %s\n", message);
}
else if (type == GL_DEBUG_TYPE_PERFORMANCE)
{
printf("GL PERF: %s\n", message);
}
}
#endif
static int W = 2048;
static int H = 2048;
struct Bubble
{
float x, y, r;
float dx, dy;
std::array<float, 4> color;
};
static float lerp(float a, float b, float t) { return a + (b - a) * t; }
static float frand() { return static_cast<float>(rand()) / static_cast<float>(RAND_MAX); }
static float frand(float lo, float hi) { return lerp(lo, hi, frand()); }
double now()
{
auto now = std::chrono::high_resolution_clock::now();
return std::chrono::time_point_cast<std::chrono::nanoseconds>(now).time_since_epoch().count() *
1e-9;
}
#ifdef RIVE_WASM
EM_JS(int, window_inner_width, (), { return window["innerWidth"]; });
EM_JS(int, window_inner_height, (), { return window["innerHeight"]; });
#endif
constexpr int n = 800;
GLFWwindow* window;
GLint uniformWindow;
GLint uniformT;
GLuint tex = 0;
GLuint blitFBO;
GLuint renderFBO;
GLenum clearOp = GL_LOAD_OP_CLEAR_WEBGL;
GLenum keepOp = GL_STORE_OP_STORE_WEBGL;
int totalFrames = 0;
int frames = 0;
double start = now();
int lastWidth = 0, lastHeight = 0;
static void mainLoop();
int main(int argc, const char* argv[])
{
#if RIVE_WASM
emscripten_set_main_loop(mainLoop, 0, false);
#endif
#ifdef RIVE_DESKTOP_GL
// Select the ANGLE backend.
for (int i = 1; i < argc; i++)
{
if (!strcmp(argv[i], "--gl"))
{
glfwInitHint(GLFW_ANGLE_PLATFORM_TYPE, GLFW_ANGLE_PLATFORM_TYPE_OPENGL);
}
else if (!strcmp(argv[i], "--gles"))
{
glfwInitHint(GLFW_ANGLE_PLATFORM_TYPE, GLFW_ANGLE_PLATFORM_TYPE_OPENGLES);
}
else if (!strcmp(argv[i], "--d3d"))
{
glfwInitHint(GLFW_ANGLE_PLATFORM_TYPE, GLFW_ANGLE_PLATFORM_TYPE_D3D11);
}
else if (!strcmp(argv[i], "--vk"))
{
glfwInitHint(GLFW_ANGLE_PLATFORM_TYPE, GLFW_ANGLE_PLATFORM_TYPE_VULKAN);
}
else if (!strcmp(argv[i], "--mtl"))
{
glfwInitHint(GLFW_ANGLE_PLATFORM_TYPE, GLFW_ANGLE_PLATFORM_TYPE_METAL);
}
}
#endif
if (!glfwInit())
{
fprintf(stderr, "Failed to initialize glfw.\n");
return 1;
}
glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_EGL_CONTEXT_API);
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_SAMPLES, 0);
window = glfwCreateWindow(W, H, "Rive Bubbles", nullptr, nullptr);
if (!window)
{
glfwTerminate();
fprintf(stderr, "Failed to create window.\n");
return -1;
}
glfwSetWindowTitle(window, "Rive Bubbles");
glfwMakeContextCurrent(window);
#ifdef RIVE_DESKTOP_GL
// Load the OpenGL API using glad.
if (!gladLoadCustomLoader((GLADloadproc)glfwGetProcAddress))
{
fprintf(stderr, "Failed to initialize glad.\n");
return -1;
}
glfwSwapInterval(0);
#endif
printf("GL_VENDOR: %s\n", glGetString(GL_VENDOR));
printf("GL_RENDERER: %s\n", glGetString(GL_RENDERER));
printf("GL_VERSION: %s\n", glGetString(GL_VERSION));
fflush(stdout);
#ifdef RIVE_DESKTOP_GL
if (!GLAD_GL_ANGLE_shader_pixel_local_storage_coherent)
{
printf("ANGLE_shader_pixel_local_storage_coherent not supported\n");
return -1;
}
if (GLAD_GL_KHR_debug)
{
glEnable(GL_DEBUG_OUTPUT);
glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE, 0, NULL, GL_TRUE);
glDebugMessageCallbackKHR(&err_msg_callback, nullptr);
}
#endif
#ifdef RIVE_WASM
if (!emscripten_webgl_enable_WEBGL_shader_pixel_local_storage(
emscripten_webgl_get_current_context()))
{
printf("WEBGL_shader_pixel_local_storage is not supported\n");
return -1;
}
if (!emscripten_webgl_shader_pixel_local_storage_is_coherent())
{
printf("WEBGL_shader_pixel_local_storage is not coherent\n");
return -1;
}
#endif
GLuint program = glCreateProgram();
if (!compile_and_attach_shader(program, GL_VERTEX_SHADER, vs) ||
!compile_and_attach_shader(program, GL_FRAGMENT_SHADER, fs) || !link_program(program))
{
return -1;
}
glUseProgram(program);
uniformWindow = glGetUniformLocation(program, "window");
uniformT = glGetUniformLocation(program, "T");
// Generate n bubbles.
Bubble bubbles[n];
for (Bubble& bubble : bubbles)
{
float r = lerp(.1f, .3f, powf(frand(), 4));
bubble.x = (frand(-1 + r, 1 - r) + 1) * 1024.f;
bubble.y = (frand(-1 + r, 1 - r) + 1) * 1024.f;
bubble.r = r * 1024.f;
bubble.dx = (frand() - .5) * .02 * 1024.f;
bubble.dy = (frand() - .5) * .02 * 1024.f;
// bubble.da = 0; //(frand() - .5) * .03;
bubble.color = {frand(.5, 1), frand(.5, 1), frand(.5, 1), frand(.75, 1)};
}
GLuint bubbleBuff;
glGenBuffers(1, &bubbleBuff);
glBindBuffer(GL_ARRAY_BUFFER, bubbleBuff);
glBufferData(GL_ARRAY_BUFFER, sizeof(bubbles), bubbles, GL_STATIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(Bubble), 0);
glVertexAttribDivisor(0, 1);
glEnableVertexAttribArray(1);
glVertexAttribPointer(1,
2,
GL_FLOAT,
GL_FALSE,
sizeof(Bubble),
reinterpret_cast<const void*>(offsetof(Bubble, dx)));
glVertexAttribDivisor(1, 1);
glEnableVertexAttribArray(2);
glVertexAttribPointer(2,
4,
GL_FLOAT,
GL_TRUE,
sizeof(Bubble),
reinterpret_cast<const void*>(offsetof(Bubble, color)));
glVertexAttribDivisor(2, 1);
glGenFramebuffers(1, &blitFBO);
glGenFramebuffers(1, &renderFBO);
glBindFramebuffer(GL_FRAMEBUFFER, renderFBO);
glDrawBuffers(0, nullptr);
glDisable(GL_DITHER);
float clearColor[] = {.1f, .1f, .1f, .1f};
glFramebufferPixelLocalClearValuefvWEBGL(0, clearColor);
#if RIVE_DESKTOP_GL
while (!glfwWindowShouldClose(window))
{
mainLoop();
glfwSwapBuffers(window);
}
glfwTerminate();
#endif
return 0;
}
static void mainLoop()
{
#ifdef RIVE_WASM
{
// Fit the canvas to the browser window size.
int windowWidth = window_inner_width();
int windowHeight = window_inner_height();
double devicePixelRatio = emscripten_get_device_pixel_ratio();
int canvasExpectedWidth = windowWidth * devicePixelRatio;
int canvasExpectedHeight = windowHeight * devicePixelRatio;
int canvasWidth, canvasHeight;
glfwGetFramebufferSize(window, &canvasWidth, &canvasHeight);
if (canvasWidth != canvasExpectedWidth || canvasHeight != canvasExpectedHeight)
{
glfwSetWindowSize(window, canvasExpectedWidth, canvasExpectedHeight);
emscripten_set_element_css_size("#canvas", windowWidth, windowHeight);
}
}
#endif
int width, height;
glfwGetFramebufferSize(window, &width, &height);
if (lastWidth != width || lastHeight != height)
{
printf("rendering %i bubbles at %i x %i\n", n, width, height);
glViewport(0, 0, width, height);
glUniform2f(uniformWindow, width, height);
glDeleteTextures(1, &tex);
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height);
glBindFramebuffer(GL_FRAMEBUFFER, blitFBO);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
glBindFramebuffer(GL_FRAMEBUFFER, renderFBO);
glFramebufferTexturePixelLocalStorageWEBGL(0, tex, 0, 0);
glDrawBuffers(0, nullptr);
lastWidth = width;
lastHeight = height;
}
glBindFramebuffer(GL_FRAMEBUFFER, renderFBO);
glBeginPixelLocalStorageWEBGL(1, &clearOp);
glUniform1f(uniformT, totalFrames++);
glDrawArraysInstanced(GL_TRIANGLE_STRIP, 0, 4, n);
glEndPixelLocalStorageWEBGL(1, &keepOp);
glBindFramebuffer(GL_READ_FRAMEBUFFER, blitFBO);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
++frames;
double end = now();
double seconds = end - start;
if (seconds >= 2)
{
printf("%f fps\n", frames / seconds);
fflush(stdout);
frames = 0;
start = end;
}
glfwPollEvents();
}