As mentioned above, in order to be able to build the example, there're are some packages to install, namely: Emscripten SDK and CMake. Python (2.x) is also required but you can use the one bundled with Emscripten, just make sure both CMake & Python are added into PATH (to make them accessible from the command-line). Please note that the whole set-up has been tested on a Windows machine only, but it should be a matter of minutes to tweak it for other platforms as well (OSX & Linux).
Download the source package: hello_triangle_emscripten.zip (~70 kB)
Check the resulting page: Hello Triangle!
The image below shows the expected result (a spinning triangle):
In order to build the demo application, please start a new command prompt using emcmdprompt.bat (typically located in c:\Program Files\Emscripten\), then cd into the hello_triangle_emscripten directory and type build.py. The image bellow shows the steps and the output:
Then check the build directory for hello_triangle.js/html
Basically, the whole project consists of these 3 files:
VIEW THE CODE BELOW IN FULL-SCREEN (main.cpp)
Code: Select all
/*
(c) 2014 +++ Filip Stoklas, aka FipS, http://www.4FipS.com +++
THIS CODE IS FREE - LICENSED UNDER THE MIT LICENSE
ARTICLE URL: http://forums.4fips.com/viewtopic.php?f=3&t=1201
*/
#define GL_GLEXT_PROTOTYPES
#include <cstdio>
#include <cassert>
#include <GL/glut.h>
struct Context
{
int width, height;
GLuint vert_id, frag_id;
GLuint prog_id, geom_id;
GLint u_time_loc;
enum { Position_loc, Color_loc };
Context():
width(400), height(300),
vert_id(0), frag_id(0),
prog_id(0), geom_id(0),
u_time_loc(-1)
{}
} g_context;
void init()
{
printf("init()\n");
glClearColor(.3f, .3f, .3f, 1.f);
auto load_shader = [](GLenum type, const char *src) -> GLuint
{
const GLuint id = glCreateShader(type);
assert(id);
glShaderSource(id, 1, &src, nullptr);
glCompileShader(id);
GLint compiled = 0;
glGetShaderiv(id, GL_COMPILE_STATUS, &compiled);
assert(compiled);
return id;
};
g_context.vert_id = load_shader(
GL_VERTEX_SHADER,
"attribute vec4 a_position; \n"
"attribute vec4 a_color; \n"
"uniform float u_time; \n"
"varying vec4 v_color; \n"
"void main() \n"
"{ \n"
" float sz = sin(u_time); \n"
" float cz = cos(u_time); \n"
" mat4 rot = mat4( \n"
" cz, -sz, 0, 0, \n"
" sz, cz, 0, 0, \n"
" 0, 0, 1, 0, \n"
" 0, 0, 0, 1 \n"
" ); \n"
" gl_Position = a_position * rot; \n"
" v_color = a_color; \n"
"} \n"
);
printf("- vertex shader loaded\n");
g_context.frag_id = load_shader(
GL_FRAGMENT_SHADER,
"precision mediump float; \n"
"varying vec4 v_color; \n"
"void main() \n"
"{ \n"
" gl_FragColor = v_color; \n"
"} \n"
);
printf("- fragment shader loaded\n");
g_context.prog_id = glCreateProgram();
assert(g_context.prog_id);
glAttachShader(g_context.prog_id, g_context.vert_id);
glAttachShader(g_context.prog_id, g_context.frag_id);
glBindAttribLocation(g_context.prog_id, Context::Position_loc, "a_position");
glBindAttribLocation(g_context.prog_id, Context::Color_loc, "a_color");
glLinkProgram(g_context.prog_id);
GLint linked = 0;
glGetProgramiv(g_context.prog_id, GL_LINK_STATUS, &linked);
assert(linked);
g_context.u_time_loc = glGetUniformLocation(g_context.prog_id, "u_time");
assert(g_context.u_time_loc >= 0);
glUseProgram(g_context.prog_id);
printf("- shader program linked & bound\n");
struct Vertex { float x, y, z; unsigned char r, g, b, a; };
const Vertex vtcs[] {
{ 0.f, .5f, 0.f, 255, 0, 0, 255 },
{ -.5f, -.5f, 0.f, 0, 255, 0, 255 },
{ .5f, -.5f, 0.f, 0, 0, 255, 255 }
};
glGenBuffers(1, &g_context.geom_id);
assert(g_context.geom_id);
glBindBuffer(GL_ARRAY_BUFFER, g_context.geom_id);
glBufferData(GL_ARRAY_BUFFER, sizeof(vtcs), vtcs, GL_STATIC_DRAW);
auto offset = [](size_t value) -> const GLvoid * { return reinterpret_cast<const GLvoid *>(value); };
glVertexAttribPointer(Context::Position_loc, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), offset(0));
glEnableVertexAttribArray(Context::Position_loc);
glVertexAttribPointer(Context::Color_loc, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), offset(3 * sizeof(float)));
glEnableVertexAttribArray(Context::Color_loc);
printf("- geometry created & bound\n");
}
void resize(int width, int height)
{
printf("resize(%d, %d)\n", width, height);
g_context.width = width;
g_context.height = height;
}
void draw()
{
glViewport(0, 0, g_context.width, g_context.height);
glClear(GL_COLOR_BUFFER_BIT);
glUniform1f(g_context.u_time_loc, glutGet(GLUT_ELAPSED_TIME) / 1000.f);
glDrawArrays(GL_TRIANGLES, 0, 3);
glutSwapBuffers();
}
void update()
{
glutPostRedisplay();
}
int main(int argc, char *argv[])
{
printf("main()\n");
glutInit(&argc, argv);
glutInitWindowSize(g_context.width, g_context.height);
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
glutCreateWindow("Hello Triangle! | 4FipS.com");
glutReshapeFunc(resize);
glutDisplayFunc(draw);
glutIdleFunc(update);
init();
glutMainLoop();
return 0;
}
Code: Select all
# /*
# (c) 2014 +++ Filip Stoklas, aka FipS, http://www.4FipS.com +++
# THIS CODE IS FREE - LICENSED UNDER THE MIT LICENSE
# ARTICLE URL: http://forums.4fips.com/viewtopic.php?f=3&t=1201
# */
cmake_minimum_required(VERSION 2.8)
add_definitions("-std=c++11")
project(hello_triangle)
file(GLOB sources *.cpp)
add_executable(hello_triangle.html ${sources})
Code: Select all
#!/usr/bin/env python
# /*
# (c) 2014 +++ Filip Stoklas, aka FipS, http://www.4FipS.com +++
# THIS CODE IS FREE - LICENSED UNDER THE MIT LICENSE
# ARTICLE URL: http://forums.4fips.com/viewtopic.php?f=3&t=1201
# */
import os
import stat
from shutil import rmtree
from subprocess import check_call
def resolve_path(rel_path):
return os.path.abspath(os.path.join(os.path.dirname(__file__), rel_path))
def rmtree_silent(root):
def remove_readonly_handler(fn, root, excinfo):
if fn is os.rmdir:
if os.path.isdir(root): # if exists
os.chmod(root, stat.S_IWRITE) # make writable
os.rmdir(root)
elif fn is os.remove:
if os.path.isfile(root): # if exists
os.chmod(root, stat.S_IWRITE) # make writable
os.remove(root)
rmtree(root, onerror=remove_readonly_handler)
def makedirs_silent(root):
try:
os.makedirs(root)
except OSError: # mute if exists
pass
if __name__ == "__main__":
build_dir = resolve_path("./build")
rmtree_silent(build_dir)
makedirs_silent(build_dir)
os.chdir(build_dir)
check_call([
"cmake",
os.path.expandvars("-DCMAKE_TOOLCHAIN_FILE=$EMSCRIPTEN/cmake/Platform/Emscripten.cmake"),
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_MAKE_PROGRAM=mingw32-make",
"-G", "Unix Makefiles",
".."
])
check_call(["mingw32-make"])