Add WebGl2 benchmarking example
This makes it so that we can specify a custom HTML shell file for the
emcc_binary rule.
Change-Id: I919e917d6fd92fed6ab77775c5373d2886edacca
diff --git a/build_tests/webgl2_benchmark.cc b/build_tests/webgl2_benchmark.cc
new file mode 100644
index 0000000..f9f15fc
--- /dev/null
+++ b/build_tests/webgl2_benchmark.cc
@@ -0,0 +1,161 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <cmath>
+#include <emscripten/emscripten.h>
+#include <emscripten/html5.h>
+#include <iostream>
+#include <unistd.h>
+#include <GLES3/gl3.h>
+
+namespace {
+constexpr int kNPoints = 10 * 1000 * 1000;
+} // namespace
+
+// Shader and program construction taken from examples at
+// https://github.com/emscripten-core/emscripten/blob/incoming/tests/webgl2_draw_packed_triangle.c
+GLuint compile_shader(GLenum shaderType, const char *src) {
+ GLuint shader = glCreateShader(shaderType);
+ glShaderSource(shader, 1, &src, NULL);
+ glCompileShader(shader);
+
+ GLint isCompiled = 0;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &isCompiled);
+ if (!isCompiled) {
+ GLint maxLength = 0;
+ glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
+ char *buf = (char *)malloc(maxLength + 1);
+ glGetShaderInfoLog(shader, maxLength, &maxLength, buf);
+ printf("%s\n", buf);
+ free(buf);
+ return 0;
+ }
+
+ return shader;
+}
+
+GLuint create_program(GLuint vertexShader, GLuint fragmentShader) {
+ GLuint program = glCreateProgram();
+ glAttachShader(program, vertexShader);
+ glAttachShader(program, fragmentShader);
+ glBindAttribLocation(program, 0, "apos");
+ glBindAttribLocation(program, 1, "acolor");
+ glLinkProgram(program);
+ return program;
+}
+
+struct Vector {
+ int x;
+ int y;
+ int z;
+ int w;
+};
+
+// Packs a vector for use with GL_INT_2_10_10_10_REV.
+uint32_t PackVector(const Vector &vec) {
+ uint32_t retval = 0;
+ retval = vec.w;
+ retval <<= 10;
+ retval |= vec.z & 0x3FF;
+ retval <<= 10;
+ retval |= vec.y & 0x3FF;
+ retval <<= 10;
+ retval |= vec.x & 0x3FF;
+ return retval;
+}
+
+struct AnimationState {
+ // The time, in seconds, at which the last animation frame occurred.
+ double last_animation_time = 0.0;
+ // The location for the "scale" uniform to modify on each animation call.
+ GLint scale_uniform_location;
+};
+
+// This function modifies the "scale" uniform to vary from 0.5->1.0->0.5
+// in a cycle, and redraws the points on each iteration.
+int Redraw(double time, void *data) {
+ AnimationState *state = reinterpret_cast<AnimationState*>(data);
+ time /= 1000.0;
+ const double difftime = time - state->last_animation_time;
+ const double wrap_time = std::fmod(time, 1.0);
+ const double offset = wrap_time > 0.5 ? 1.0 - wrap_time : wrap_time;
+ glUniform1f(state->scale_uniform_location, std::min(0.5 + offset, 100.0));
+ std::cout << 1.0 / difftime << "fps\n";
+ glDrawArrays(GL_POINTS, 0, kNPoints);
+ state->last_animation_time = time;
+ assert(glGetError() == GL_NO_ERROR && "glDrawArray failed");
+ return 1;
+}
+
+int main() {
+ EmscriptenWebGLContextAttributes attr;
+ emscripten_webgl_init_context_attributes(&attr);
+ attr.majorVersion = 2;
+ EMSCRIPTEN_WEBGL_CONTEXT_HANDLE ctx = emscripten_webgl_create_context("#canvas", &attr);
+ assert(ctx && "Failed to create WebGL2 context");
+ emscripten_webgl_make_context_current(ctx);
+
+ static const char vertex_shader[] =
+ "#version 100\n"
+ "attribute vec4 apos;"
+ "attribute vec4 acolor;"
+ "varying vec4 color;"
+ "uniform float scale;"
+ "void main() {"
+ "color = acolor;"
+ "gl_Position = apos;"
+ "gl_Position.x = apos.x * scale;"
+ "gl_Position.y = apos.y * 0.5 / scale;"
+ "gl_PointSize = 1.0;"
+ "}";
+ GLuint vs = compile_shader(GL_VERTEX_SHADER, vertex_shader);
+
+ static const char fragment_shader[] =
+ "#version 100\n"
+ "precision lowp float;"
+ "varying vec4 color;"
+ "void main() {"
+ "gl_FragColor = color;"
+ "}";
+ GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fragment_shader);
+
+ GLuint program = create_program(vs, fs);
+ glUseProgram(program);
+
+ // Go through and generate randomly located and colored points.
+ static uint32_t pos_and_color[kNPoints * 2];
+ for (int ii = 0; ii < kNPoints; ++ii) {
+ uint16_t w = 1;
+ uint16_t z = 0;
+ uint16_t y = rand() % 1024;
+ uint16_t x = rand() % 1024;
+ uint32_t wzyx = PackVector({x, y, z, w});
+ uint16_t a = 3;
+ uint16_t b = rand() % 1024;
+ uint16_t g = rand() % 1024;
+ uint16_t r = rand() % 1024;
+ uint32_t argb = PackVector({r, g, b, a});
+ pos_and_color[2 * ii] = wzyx;
+ pos_and_color[2 * ii + 1] = argb;
+ }
+
+ GLuint vbo;
+ glGenBuffers(1, &vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, vbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(pos_and_color), pos_and_color, GL_STATIC_DRAW);
+ glVertexAttribPointer(0, 4, GL_INT_2_10_10_10_REV, GL_TRUE, 8, 0);
+ assert(glGetError() == GL_NO_ERROR && "glVertexAttribPointer with GL_INT_2_10_10_10_REV failed");
+ glVertexAttribPointer(1, 4, GL_UNSIGNED_INT_2_10_10_10_REV, GL_TRUE, 8, (void*)4);
+ assert(glGetError() == GL_NO_ERROR && "glVertexAttribPointer with GL_UNSIGNED_INT_2_10_10_10_REV failed");
+
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+
+ // Note that the animation_state must last until Redraw stops being called,
+ // which we cannot provide any bound on. As such, we don't currently destroy
+ // the memory until the webpage is closed.
+ AnimationState *animation_state = new AnimationState();
+ animation_state->scale_uniform_location =
+ glGetUniformLocation(program, "scale");
+ emscripten_request_animation_frame_loop(&Redraw, animation_state);
+}