In an earlier part of this chapter, we discussed making calls to the Emscripten WebAssembly app from a shell template. Now that you know how to make the interaction work between JavaScript and WebAssembly, we can add acanvas element back into the template and start to manipulate thatcanvasusing the WebAssembly module. We are going to create a new .c file that will call a JavaScript function passing it an x and y coordinate. The JavaScript function will manipulate a spaceship image, moving it around thecanvas. We will also create a brand new shell file called canvas_shell.html.
As we did for the previous version of our shell, we will start by breaking this file down into four sections to discuss it at a high level. We will then discuss the essential parts of this file a piece at a time.
The beginning of the HTML file starts with the opening HTML tag and the head element:
After the new JavaScript functions, we have the new definition of the Module object:
var Module = { preRun: [], postRun: [ModuleLoaded], print: (function() { var element = document.getElementById('output'); if (element) element.value = ''; // clear browser cache return function(text) { if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); // uncomment block below if you want to write to an html element /* text = text.replace(/&/g, "&"); text = text.replace(/</g, "<"); text = text.replace(/>/g, ">"); text = text.replace('\n', '<br>', 'g'); */ console.log(text); if (element) { element.value += text + "\n"; element.scrollTop = element.scrollHeight; // focus on bottom } }; })(), printErr: function(text) { if (arguments.length > 1) text = Array.prototype.slice.call(arguments).join(' '); console.error(text); }, canvas: (function() { var canvas = document.getElementById('canvas'); 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 this is a progress update, skip it if too soon if (m && now - Module.setStatus.last.time < 30) return; Module.setStatus.last.time = now; Module.setStatus.last.text = text; if (m) { text = m[1]; } console.log("status: " + 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'); Module.setStatus = function(text) { if (text) Module.printErr('[post-exception status] ' + text); }; };
The last few lines close out our tags and include the{{{ SCRIPT }}} Emscripten tag:
Now that we have looked at the code at a high level, we can look at the source in more detail. In the head section of the HTML, we are changing the title and the name of the CSS file that we are linking. Here is the change in the HTMLhead:
We do not need most of the elements that were in the previous<body>tag. We need acanvas, which we had removed from the shell_minimal.html file provided by Emscripten, but now we need to add it back in. We are keeping thetextareathat was initially in the minimal shell, and we are adding a newimgtag that has a spaceship image taken from a TypeScript canvas tutorial on theembed.comwebsite athttps://www.embed.com/typescript-games/draw-image.html. Here are the new HTML tags in thebodyelement:
Finally, we need to change the JavaScript code. The first thing we are going to do is add three variables at the beginning to hold a reference to thecanvaselement, the canvas context, and the new spaceshipimgelement:
var img = null; var canvas = null; var ctx = null;
The next thing we are adding to the JavaScript is a function that renders the spaceship image to the canvas at a given x and y coordinate:
This function first checks to see whether theimgvariable is a value other than null. That will let us know if the module has been loaded or not because theimgvariable starts set to null. The next thing we do is clear the canvas with the color black using thectx.fillStyle = “black”line to set the context fill style to the color black, before callingctx.fillRectto draw a rectangle that fills the entire canvas with a black rectangle. The next four lines save off the canvas context, translate the context position to the ship's x and y coordinate value, and then draw the ship image to the canvas. The last one of these four lines performs a context restore to set our translation back to (0,0) where it started.
After defining this function, the WebAssembly module can call it. We need to set up some initialization code to initialize those three variables when the module is loaded. Here is that code:
TheModuleLoadedfunction usesgetElementByIdto setimgandcanvasto the spaceship andcanvasHTML elements, respectively. We will then callcanvas.getContext(”2d”)to get the 2D canvas context and set thectxvariable to that context. All of this gets called when theModuleobject finishes loading because we added theModuleLoadedfunction to thepostRunarray.
We have also added back thecanvasfunction that was on theModuleobject in the minimum shell file, which we had removed along with the canvas in an earlier tutorial. That code watches the canvas context and alerts the user if that context is lost. Eventually, we will want this code to fix the problem, but, for now, it is good to know when it happens. Here is that code:
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; })(),
To go along with this new HTML shell file, we have created a new canvas.c file to compile into a WebAssembly module. Be aware that, in the long run, we will be doing a lot less in our JavaScript and a lot more inside our WebAssembly C/C++ code. Here is the new canvas.c file:
To start, we create a ship_x and ship_y variable to track the ship'sxandycoordinates. After that, we create a MoveShip function. This function increments the ship'sxposition by 2 and the ship'syposition by 1 each time it is called. It also checks to see whether the ship's x coordinates have left the canvas on the right side, which moves it back to the left side if it has, and does something similar if the ship has moved off the canvas on the bottom. The last thing this function does is call our JavaScript ShipPosition function, passing it the ship'sxandycoordinates. That final step is what will draw our spaceship to the new coordinates on the HTML5 canvas element.
In the new version of our main function, we have the following line:
emscripten_set_main_loop(MoveShip, 0, 0);
This line turns the function passed in as the first parameter into a game loop. We will go into more detail about howemscripten_set_main_loopworks in a later chapter, but for the moment, know that this causes the MoveShip function to be called every time a new frame is rendered to our canvas.
Finally, we will create a new canvas.css file that keeps the code for thebodyand#outputCSS and adds a new#canvasCSS class. Here are the contents of the canvas.css file:
After everything is complete, we will use emcc to compile the new canvas.html file as well as canvas.wasm and the canvas.js glue code. Here is what the call to emcc will look like:
Immediately afteremcc,we pass in the name of the .c file, canvas.c, which will be used to compile our WASM module. The-oflag tells our compiler that the next argument will be the output. Using an output file with a .html extension tells emcc to compile the WASM, JavaScript, and HTML files. The next flag passed in is--shell-file, which tells emcc that the argument to follow is the name of the HTML shell file, which will be used to create the HTML file of our final output.
It is important to remember that you must run WebAssembly apps using a web server, or with emrun. If you would like to run your WebAssembly app using emrun, you must compile it with the --emrun flag. The web browser requires a web server to stream the WebAssembly module. If you attempt to open an HTML page that uses WebAssembly in a browser directly from your hard drive, that WebAssembly module will not load.
The following is a screenshot of canvas.html:
Figure 2.3: Our first WebAssembly HTML5 canvas app