| 1 | /** |
| 2 | * \file emscripten.c |
| 3 | * Emscripten example of using the single-file \c zstddeclib. Draws a rotating |
| 4 | * textured quad with data from the in-line Zstd compressed DXT1 texture (DXT1 |
| 5 | * being hardware compression, further compressed with Zstd). |
| 6 | * \n |
| 7 | * Compile using: |
| 8 | * \code |
| 9 | * export CC_FLAGS="-Wall -Wextra -Werror -Os -g0 -flto --llvm-lto 3 -lGL -DNDEBUG=1" |
| 10 | * export EM_FLAGS="-s WASM=1 -s ENVIRONMENT=web --shell-file shell.html --closure 1" |
| 11 | * emcc $CC_FLAGS $EM_FLAGS -o out.html emscripten.c |
| 12 | * \endcode |
| 13 | * |
| 14 | * \author Carl Woffenden, Numfum GmbH (released under a CC0 license) |
| 15 | */ |
| 16 | |
| 17 | #include <stddef.h> |
| 18 | #include <stdio.h> |
| 19 | #include <stdlib.h> |
| 20 | #include <string.h> |
| 21 | |
| 22 | #include <emscripten/emscripten.h> |
| 23 | #include <emscripten/html5.h> |
| 24 | |
| 25 | #include <GLES2/gl2.h> |
| 26 | #include <GLES2/gl2ext.h> |
| 27 | |
| 28 | #include "../zstddeclib.c" |
| 29 | |
| 30 | //************************* Test Data (DXT texture) **************************/ |
| 31 | |
| 32 | /** |
| 33 | * Zstd compressed DXT1 256x256 texture source. |
| 34 | * \n |
| 35 | * See \c testcard.png for the original. |
| 36 | */ |
| 37 | static uint8_t const srcZstd[] = { |
| 38 | #include "testcard-zstd.inl" |
| 39 | }; |
| 40 | |
| 41 | /** |
| 42 | * Uncompressed size of \c #srcZstd. |
| 43 | */ |
| 44 | #define DXT1_256x256 32768 |
| 45 | |
| 46 | /** |
| 47 | * Destination for decoding \c #srcZstd. |
| 48 | */ |
| 49 | static uint8_t dstDxt1[DXT1_256x256] = {}; |
| 50 | |
| 51 | #ifndef ZSTD_VERSION_MAJOR |
| 52 | /** |
| 53 | * For the case where the decompression library hasn't been included we add a |
| 54 | * dummy function to fake the process and stop the buffers being optimised out. |
| 55 | */ |
| 56 | size_t ZSTD_decompress(void* dst, size_t dstLen, const void* src, size_t srcLen) { |
| 57 | return (memcmp(dst, src, (srcLen < dstLen) ? srcLen : dstLen)) ? dstLen : 0; |
| 58 | } |
| 59 | #endif |
| 60 | |
| 61 | //*************************** Program and Shaders ***************************/ |
| 62 | |
| 63 | /** |
| 64 | * Program object ID. |
| 65 | */ |
| 66 | static GLuint progId = 0; |
| 67 | |
| 68 | /** |
| 69 | * Vertex shader ID. |
| 70 | */ |
| 71 | static GLuint vertId = 0; |
| 72 | |
| 73 | /** |
| 74 | * Fragment shader ID. |
| 75 | */ |
| 76 | static GLuint fragId = 0; |
| 77 | |
| 78 | //********************************* Uniforms *********************************/ |
| 79 | |
| 80 | /** |
| 81 | * Quad rotation angle ID. |
| 82 | */ |
| 83 | static GLint uRotId = -1; |
| 84 | |
| 85 | /** |
| 86 | * Draw colour ID. |
| 87 | */ |
| 88 | static GLint uTx0Id = -1; |
| 89 | |
| 90 | //******************************* Shader Source ******************************/ |
| 91 | |
| 92 | /** |
| 93 | * Vertex shader to draw texture mapped polys with an applied rotation. |
| 94 | */ |
| 95 | static GLchar const vertShader2D[] = |
| 96 | #if GL_ES_VERSION_2_0 |
| 97 | "#version 100\n" |
| 98 | "precision mediump float;\n" |
| 99 | #else |
| 100 | "#version 120\n" |
| 101 | #endif |
| 102 | "uniform float uRot;" // rotation |
| 103 | "attribute vec2 aPos;" // vertex position coords |
| 104 | "attribute vec2 aUV0;" // vertex texture UV0 |
| 105 | "varying vec2 vUV0;" // (passed to fragment shader) |
| 106 | "void main() {" |
| 107 | " float cosA = cos(radians(uRot));" |
| 108 | " float sinA = sin(radians(uRot));" |
| 109 | " mat3 rot = mat3(cosA, -sinA, 0.0," |
| 110 | " sinA, cosA, 0.0," |
| 111 | " 0.0, 0.0, 1.0);" |
| 112 | " gl_Position = vec4(rot * vec3(aPos, 1.0), 1.0);" |
| 113 | " vUV0 = aUV0;" |
| 114 | "}"; |
| 115 | |
| 116 | /** |
| 117 | * Fragment shader for the above polys. |
| 118 | */ |
| 119 | static GLchar const fragShader2D[] = |
| 120 | #if GL_ES_VERSION_2_0 |
| 121 | "#version 100\n" |
| 122 | "precision mediump float;\n" |
| 123 | #else |
| 124 | "#version 120\n" |
| 125 | #endif |
| 126 | "uniform sampler2D uTx0;" |
| 127 | "varying vec2 vUV0;" // (passed from fragment shader) |
| 128 | "void main() {" |
| 129 | " gl_FragColor = texture2D(uTx0, vUV0);" |
| 130 | "}"; |
| 131 | |
| 132 | /** |
| 133 | * Helper to compile a shader. |
| 134 | * |
| 135 | * \param type shader type |
| 136 | * \param text shader source |
| 137 | * \return the shader ID (or zero if compilation failed) |
| 138 | */ |
| 139 | static GLuint compileShader(GLenum const type, const GLchar* text) { |
| 140 | GLuint shader = glCreateShader(type); |
| 141 | if (shader) { |
| 142 | glShaderSource (shader, 1, &text, NULL); |
| 143 | glCompileShader(shader); |
| 144 | GLint compiled; |
| 145 | glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); |
| 146 | if (compiled) { |
| 147 | return shader; |
| 148 | } else { |
| 149 | GLint logLen; |
| 150 | glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLen); |
| 151 | if (logLen > 1) { |
| 152 | GLchar* logStr = malloc(logLen); |
| 153 | glGetShaderInfoLog(shader, logLen, NULL, logStr); |
| 154 | #ifndef NDEBUG |
| 155 | printf("Shader compilation error: %s\n", logStr); |
| 156 | #endif |
| 157 | free(logStr); |
| 158 | } |
| 159 | glDeleteShader(shader); |
| 160 | } |
| 161 | } |
| 162 | return 0; |
| 163 | } |
| 164 | |
| 165 | //********************************** Helpers *********************************/ |
| 166 | |
| 167 | /** |
| 168 | * Vertex position index. |
| 169 | */ |
| 170 | #define GL_VERT_POSXY_ID 0 |
| 171 | |
| 172 | /** |
| 173 | * Vertex UV0 index. |
| 174 | */ |
| 175 | #define GL_VERT_TXUV0_ID 1 |
| 176 | |
| 177 | /** |
| 178 | * \c GL vec2 storage type. |
| 179 | */ |
| 180 | struct vec2 { |
| 181 | float x; |
| 182 | float y; |
| 183 | }; |
| 184 | |
| 185 | /** |
| 186 | * Combined 2D vertex and 2D texture coordinates. |
| 187 | */ |
| 188 | struct posTex2d { |
| 189 | struct vec2 pos; |
| 190 | struct vec2 uv0; |
| 191 | }; |
| 192 | |
| 193 | //****************************************************************************/ |
| 194 | |
| 195 | /** |
| 196 | * Current quad rotation angle (in degrees, updated per frame). |
| 197 | */ |
| 198 | static float rotDeg = 0.0f; |
| 199 | |
| 200 | /** |
| 201 | * Emscripten (single) GL context. |
| 202 | */ |
| 203 | static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE glCtx = 0; |
| 204 | |
| 205 | /** |
| 206 | * Emscripten resize handler. |
| 207 | */ |
| 208 | static EM_BOOL resize(int type, const EmscriptenUiEvent* e, void* data) { |
| 209 | double surfaceW; |
| 210 | double surfaceH; |
| 211 | if (emscripten_get_element_css_size ("#canvas", &surfaceW, &surfaceH) == EMSCRIPTEN_RESULT_SUCCESS) { |
| 212 | emscripten_set_canvas_element_size("#canvas", surfaceW, surfaceH); |
| 213 | if (glCtx) { |
| 214 | glViewport(0, 0, (int) surfaceW, (int) surfaceH); |
| 215 | } |
| 216 | } |
| 217 | (void) type; |
| 218 | (void) data; |
| 219 | (void) e; |
| 220 | return EM_FALSE; |
| 221 | } |
| 222 | |
| 223 | /** |
| 224 | * Boilerplate to create a WebGL context. |
| 225 | */ |
| 226 | static EM_BOOL initContext() { |
| 227 | // Default attributes |
| 228 | EmscriptenWebGLContextAttributes attr; |
| 229 | emscripten_webgl_init_context_attributes(&attr); |
| 230 | if ((glCtx = emscripten_webgl_create_context("#canvas", &attr))) { |
| 231 | // Bind the context and fire a resize to get the initial size |
| 232 | emscripten_webgl_make_context_current(glCtx); |
| 233 | emscripten_set_resize_callback(EMSCRIPTEN_EVENT_TARGET_DOCUMENT, NULL, EM_FALSE, resize); |
| 234 | resize(0, NULL, NULL); |
| 235 | return EM_TRUE; |
| 236 | } |
| 237 | return EM_FALSE; |
| 238 | } |
| 239 | |
| 240 | /** |
| 241 | * Called once per frame (clears the screen and draws the rotating quad). |
| 242 | */ |
| 243 | static void tick() { |
| 244 | glClearColor(1.0f, 0.0f, 1.0f, 1.0f); |
| 245 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); |
| 246 | |
| 247 | if (uRotId >= 0) { |
| 248 | glUniform1f(uRotId, rotDeg); |
| 249 | rotDeg += 0.1f; |
| 250 | if (rotDeg >= 360.0f) { |
| 251 | rotDeg -= 360.0f; |
| 252 | } |
| 253 | } |
| 254 | |
| 255 | glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, 0); |
| 256 | glFlush(); |
| 257 | } |
| 258 | |
| 259 | /** |
| 260 | * Creates the GL context, shaders and quad data, decompresses the Zstd data |
| 261 | * and 'uploads' the resulting texture. |
| 262 | * |
| 263 | * As a (naive) comparison, removing Zstd and building with "-Os -g0 s WASM=1 |
| 264 | * -lGL emscripten.c" results in a 15kB WebAssembly file; re-adding Zstd |
| 265 | * increases the Wasm by 26kB. |
| 266 | */ |
| 267 | int main() { |
| 268 | if (initContext()) { |
| 269 | // Compile shaders and set the initial GL state |
| 270 | if ((progId = glCreateProgram())) { |
| 271 | vertId = compileShader(GL_VERTEX_SHADER, vertShader2D); |
| 272 | fragId = compileShader(GL_FRAGMENT_SHADER, fragShader2D); |
| 273 | |
| 274 | glBindAttribLocation(progId, GL_VERT_POSXY_ID, "aPos"); |
| 275 | glBindAttribLocation(progId, GL_VERT_TXUV0_ID, "aUV0"); |
| 276 | |
| 277 | glAttachShader(progId, vertId); |
| 278 | glAttachShader(progId, fragId); |
| 279 | glLinkProgram (progId); |
| 280 | glUseProgram (progId); |
| 281 | uRotId = glGetUniformLocation(progId, "uRot"); |
| 282 | uTx0Id = glGetUniformLocation(progId, "uTx0"); |
| 283 | if (uTx0Id >= 0) { |
| 284 | glUniform1i(uTx0Id, 0); |
| 285 | } |
| 286 | |
| 287 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); |
| 288 | glEnable(GL_BLEND); |
| 289 | glDisable(GL_DITHER); |
| 290 | |
| 291 | glCullFace(GL_BACK); |
| 292 | glEnable(GL_CULL_FACE); |
| 293 | } |
| 294 | |
| 295 | GLuint vertsBuf = 0; |
| 296 | GLuint indexBuf = 0; |
| 297 | GLuint txName = 0; |
| 298 | // Create the textured quad (vert positions then UVs) |
| 299 | struct posTex2d verts2d[] = { |
| 300 | {{-0.85f, -0.85f}, {0.0f, 0.0f}}, // BL |
| 301 | {{ 0.85f, -0.85f}, {1.0f, 0.0f}}, // BR |
| 302 | {{-0.85f, 0.85f}, {0.0f, 1.0f}}, // TL |
| 303 | {{ 0.85f, 0.85f}, {1.0f, 1.0f}}, // TR |
| 304 | }; |
| 305 | uint16_t index2d[] = { |
| 306 | 0, 1, 2, |
| 307 | 2, 1, 3, |
| 308 | }; |
| 309 | glGenBuffers(1, &vertsBuf); |
| 310 | glBindBuffer(GL_ARRAY_BUFFER, vertsBuf); |
| 311 | glBufferData(GL_ARRAY_BUFFER, |
| 312 | sizeof(verts2d), verts2d, GL_STATIC_DRAW); |
| 313 | glVertexAttribPointer(GL_VERT_POSXY_ID, 2, |
| 314 | GL_FLOAT, GL_FALSE, sizeof(struct posTex2d), 0); |
| 315 | glVertexAttribPointer(GL_VERT_TXUV0_ID, 2, |
| 316 | GL_FLOAT, GL_FALSE, sizeof(struct posTex2d), |
| 317 | (void*) offsetof(struct posTex2d, uv0)); |
| 318 | glGenBuffers(1, &indexBuf); |
| 319 | glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuf); |
| 320 | glBufferData(GL_ELEMENT_ARRAY_BUFFER, |
| 321 | sizeof(index2d), index2d, GL_STATIC_DRAW); |
| 322 | glEnableVertexAttribArray(GL_VERT_POSXY_ID); |
| 323 | glEnableVertexAttribArray(GL_VERT_TXUV0_ID); |
| 324 | |
| 325 | // Decode the Zstd data and create the texture |
| 326 | if (ZSTD_decompress(dstDxt1, DXT1_256x256, srcZstd, sizeof srcZstd) == DXT1_256x256) { |
| 327 | glGenTextures(1, &txName); |
| 328 | glBindTexture(GL_TEXTURE_2D, txName); |
| 329 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| 330 | glCompressedTexImage2D(GL_TEXTURE_2D, 0, |
| 331 | GL_COMPRESSED_RGB_S3TC_DXT1_EXT, |
| 332 | 256, 256, 0, DXT1_256x256, dstDxt1); |
| 333 | } else { |
| 334 | printf("Failed to decode Zstd data\n"); |
| 335 | } |
| 336 | emscripten_set_main_loop(tick, 0, EM_FALSE); |
| 337 | emscripten_exit_with_live_runtime(); |
| 338 | } |
| 339 | return EXIT_FAILURE; |
| 340 | } |