648db22b |
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 | } |