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/BUILD b/build_tests/BUILD
index 07b84fe..bf6e9ee 100644
--- a/build_tests/BUILD
+++ b/build_tests/BUILD
@@ -15,12 +15,15 @@
emcc_binary(
name = "webgl2.html",
- srcs = ["webgl2_draw_packed_triangle.c"],
+ srcs = ["webgl2_benchmark.cc"],
+ html_shell = "minimal_shell.html",
# Enable WEBGL2 (-s is used by the emscripten
# compiler to specify sundry options).
linkopts = [
"-s",
"USE_WEBGL2=1",
+ "-s",
+ "TOTAL_MEMORY=" + repr(256 * 1024 * 1024),
],
)
diff --git a/build_tests/minimal_shell.html b/build_tests/minimal_shell.html
new file mode 100644
index 0000000..6158571
--- /dev/null
+++ b/build_tests/minimal_shell.html
@@ -0,0 +1,128 @@
+<!doctype html>
+<!--This file is adapted from https://github.com/emscripten-core/emscripten/blob/incoming/src/shell_minimal.html-->
+<html lang="en-us">
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Emscripten-Generated Code</title>
+ <style>
+ .emscripten { padding-right: 0; margin-left: auto; margin-right: auto; display: block; }
+ div.emscripten { text-align: center; }
+ /* the canvas *must not* have any border or padding, or mouse coords will be wrong */
+ canvas.emscripten {
+ border: 0px none;
+ background-color: black;
+ }
+
+ .spinner {
+ height: 50px;
+ width: 50px;
+ margin: 0px auto;
+ -webkit-animation: rotation .8s linear infinite;
+ -moz-animation: rotation .8s linear infinite;
+ -o-animation: rotation .8s linear infinite;
+ animation: rotation 0.8s linear infinite;
+ border-left: 10px solid rgb(0,150,240);
+ border-right: 10px solid rgb(0,150,240);
+ border-bottom: 10px solid rgb(0,150,240);
+ border-top: 10px solid rgb(100,0,200);
+ border-radius: 100%;
+ background-color: rgb(200,100,250);
+ }
+ @-webkit-keyframes rotation {
+ from {-webkit-transform: rotate(0deg);}
+ to {-webkit-transform: rotate(360deg);}
+ }
+ @-moz-keyframes rotation {
+ from {-moz-transform: rotate(0deg);}
+ to {-moz-transform: rotate(360deg);}
+ }
+ @-o-keyframes rotation {
+ from {-o-transform: rotate(0deg);}
+ to {-o-transform: rotate(360deg);}
+ }
+ @keyframes rotation {
+ from {transform: rotate(0deg);}
+ to {transform: rotate(360deg);}
+ }
+
+ </style>
+ </head>
+ <body>
+ <figure style="overflow:visible;" id="spinner"><div class="spinner"></div><center style="margin-top:0.5em"><strong>emscripten</strong></center></figure>
+ <div class="emscripten" id="status">Downloading...</div>
+ <div class="emscripten">
+ <progress value="0" max="100" id="progress" hidden=1></progress>
+ </div>
+ <!--The width and height values in the canvas specify the pixel width/height for the WebGL canvas.
+ The actual on-screen size is controlled by the stylesheet.-->
+ <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" width=3000 height=2000></canvas>
+
+ <script type='text/javascript'>
+ var statusElement = document.getElementById('status');
+ var progressElement = document.getElementById('progress');
+ var spinnerElement = document.getElementById('spinner');
+
+ var Module = {
+ preRun: [],
+ postRun: [],
+ print: (function() {
+ return function(text) {
+ if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
+ console.log(text);
+ };
+ })(),
+ printErr: function(text) {
+ if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' ');
+ console.error(text);
+ },
+ canvas: (function() {
+ var canvas = document.getElementById('canvas');
+
+ // As a default initial behavior, pop up an alert when webgl context is lost. To make your
+ // application robust, you may want to override this behavior before shipping!
+ // See http://www.khronos.org/registry/webgl/specs/latest/1.0/#5.15.2
+ canvas.addEventListener("webglcontextlost", function(e) { alert('WebGL context lost. You will need to reload the page.'); e.preventDefault(); }, false);
+
+ return canvas;
+ })(),
+ setStatus: function(text) {
+ if (!Module.setStatus.last) Module.setStatus.last = { time: Date.now(), text: '' };
+ if (text === Module.setStatus.last.text) return;
+ var m = text.match(/([^(]+)\((\d+(\.\d+)?)\/(\d+)\)/);
+ var now = Date.now();
+ if (m && now - Module.setStatus.last.time < 30) return; // if this is a progress update, skip it if too soon
+ Module.setStatus.last.time = now;
+ Module.setStatus.last.text = text;
+ if (m) {
+ text = m[1];
+ progressElement.value = parseInt(m[2])*100;
+ progressElement.max = parseInt(m[4])*100;
+ progressElement.hidden = false;
+ spinnerElement.hidden = false;
+ } else {
+ progressElement.value = null;
+ progressElement.max = null;
+ progressElement.hidden = true;
+ if (!text) spinnerElement.hidden = true;
+ }
+ statusElement.innerHTML = text;
+ },
+ totalDependencies: 0,
+ monitorRunDependencies: function(left) {
+ this.totalDependencies = Math.max(this.totalDependencies, left);
+ Module.setStatus(left ? 'Preparing... (' + (this.totalDependencies-left) + '/' + this.totalDependencies + ')' : 'All downloads complete.');
+ }
+ };
+ Module.setStatus('Downloading...');
+ window.onerror = function() {
+ Module.setStatus('Exception thrown, see JavaScript console');
+ spinnerElement.style.display = 'none';
+ Module.setStatus = function(text) {
+ if (text) Module.printErr('[post-exception status] ' + text);
+ };
+ };
+ </script>
+ {{{ SCRIPT }}}
+ </body>
+</html>
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);
+}
diff --git a/build_tests/webgl2_draw_packed_triangle.c b/build_tests/webgl2_draw_packed_triangle.c
deleted file mode 100644
index 5dd74d7..0000000
--- a/build_tests/webgl2_draw_packed_triangle.c
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright 2018 The Emscripten Authors. All rights reserved.
- * Emscripten is available under two separate licenses, the MIT license and the
- * University of Illinois/NCSA Open Source License. Both these licenses can be
- * found in the LICENSE file.
- */
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <assert.h>
-#include <emscripten/emscripten.h>
-#include <emscripten/html5.h>
-#include <GLES3/gl3.h>
-
-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;
-}
-
-int main()
-{
- EmscriptenWebGLContextAttributes attr;
- emscripten_webgl_init_context_attributes(&attr);
-#ifdef EXPLICIT_SWAP
- attr.explicitSwapControl = 1;
-#endif
- 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;"
- "void main() {"
- "color = acolor;"
- "gl_Position = apos;"
- "}";
- 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);
-
- static const uint32_t pos_and_color[] = {
- // 1,0,y,x, a,b,g,r
- 0x400b36cd, 0xc00003ff,
- 0x400b3533, 0xc00ffc00,
- 0x4004cc00, 0xfff00000,
- };
-
- 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);
-// printf("%d\n", glGetError());
-#if 0
- switch (glGetError()) {
- case GL_NO_ERROR:
- std::cout << "No error!";
- break;
- case GL_INVALID_ENUM:
- std::cout << "Invliad enum.";
- break;
- }
-#endif
- 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);
-
- glClearColor(0.3f,0.3f,0.3f,1);
- glClear(GL_COLOR_BUFFER_BIT);
- glDrawArrays(GL_TRIANGLES, 0, 3);
-
-#ifdef EXPLICIT_SWAP
- emscripten_webgl_commit_frame();
-#endif
-
-#ifdef REPORT_RESULT
- REPORT_RESULT(0);
-#endif
-}