emscripten.c 8.5 KB

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