| /* | |
| * 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(); | |
| } |