blob: c5a638763e5dfc3e10155f414f4458356d66cabe [file] [log] [blame] [edit]
#include <emscripten.h> // For emscripten_get_device_pixel_ratio()
#include <emscripten/html5.h> // For Emscripten HTML5 WebGL context creation API
#include <webgl/webgl1.h> // For Emscripten WebGL API headers (see also webgl/webgl1_ext.h and webgl/webgl2.h)
#include <string.h> // For NULL and strcmp()
#include <assert.h> // For assert()
void upload_unicode_char_to_texture(int unicodeChar, int charSize, int applyShadow);
void load_texture_from_url(GLuint texture, const char *url, int *outWidth, int *outHeight);
static EMSCRIPTEN_WEBGL_CONTEXT_HANDLE glContext;
static GLuint quad, colorPos, matPos, solidColor;
static float pixelWidth, pixelHeight;
static GLuint compile_shader(GLenum shaderType, const char *src)
{
GLuint shader = glCreateShader(shaderType);
glShaderSource(shader, 1, &src, NULL);
glCompileShader(shader);
return shader;
}
static GLuint create_program(GLuint vertexShader, GLuint fragmentShader)
{
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
glBindAttribLocation(program, 0, "pos");
glLinkProgram(program);
glUseProgram(program);
return program;
}
static GLuint create_texture()
{
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
return texture;
}
void init_webgl(int width, int height)
{
double dpr = emscripten_get_device_pixel_ratio();
emscripten_set_element_css_size("canvas", width / dpr, height / dpr);
emscripten_set_canvas_element_size("canvas", width, height);
EmscriptenWebGLContextAttributes attrs;
emscripten_webgl_init_context_attributes(&attrs);
attrs.alpha = 0;
#if MAX_WEBGL_VERSION >= 2
attrs.majorVersion = 2;
#endif
glContext = emscripten_webgl_create_context("canvas", &attrs);
assert(glContext);
emscripten_webgl_make_context_current(glContext);
pixelWidth = 2.f / width;
pixelHeight = 2.f / height;
static const char vertex_shader[] =
"attribute vec4 pos;"
"varying vec2 uv;"
"uniform mat4 mat;"
"void main(){"
"uv=pos.xy;"
"gl_Position=mat*pos;"
"}";
GLuint vs = compile_shader(GL_VERTEX_SHADER, vertex_shader);
static const char fragment_shader[] =
"precision lowp float;"
"uniform sampler2D tex;"
"varying vec2 uv;"
"uniform vec4 color;"
"void main(){"
"gl_FragColor=color*texture2D(tex,uv);"
"}";
GLuint fs = compile_shader(GL_FRAGMENT_SHADER, fragment_shader);
GLuint program = create_program(vs, fs);
colorPos = glGetUniformLocation(program, "color");
matPos = glGetUniformLocation(program, "mat");
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glGenBuffers(1, &quad);
glBindBuffer(GL_ARRAY_BUFFER, quad);
const float pos[] = { 0, 0, 1, 0, 0, 1, 1, 1 };
glBufferData(GL_ARRAY_BUFFER, sizeof(pos), pos, GL_STATIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
glEnableVertexAttribArray(0);
solidColor = create_texture();
unsigned int whitePixel = 0xFFFFFFFFu;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &whitePixel);
}
typedef void (*tick_func)(double t, double dt);
static EM_BOOL tick(double time, void *userData)
{
static double t0;
double dt = time - t0;
t0 = time;
tick_func f = (tick_func)(userData);
f(time, dt);
return EM_TRUE;
}
void clear_screen(float r, float g, float b, float a)
{
glClearColor(r, g, b, a);
glClear(GL_COLOR_BUFFER_BIT);
}
static void fill_textured_rectangle(float x0, float y0, float x1, float y1, float r, float g, float b, float a, GLuint texture)
{
float mat[16] = { (x1-x0)*pixelWidth, 0, 0, 0, 0, (y1-y0)*pixelHeight, 0, 0, 0, 0, 1, 0, x0*pixelWidth-1.f, y0*pixelHeight-1.f, 0, 1};
glUniformMatrix4fv(matPos, 1, 0, mat);
glUniform4f(colorPos, r, g, b, a);
glBindTexture(GL_TEXTURE_2D, texture);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
void fill_solid_rectangle(float x0, float y0, float x1, float y1, float r, float g, float b, float a)
{
fill_textured_rectangle(x0, y0, x1, y1, r, g, b, a, solidColor);
}
typedef struct Texture
{
// Image
char *url;
int w, h;
GLuint texture;
} Texture;
#define MAX_TEXTURES 256
static Texture textures[MAX_TEXTURES] = {};
static Texture *find_or_cache_url(const char *url)
{
for(int i = 0; i < MAX_TEXTURES; ++i) // Naive O(n) lookup for tiny code size
if (!strcmp(textures[i].url, url))
return textures+i;
else if (!textures[i].url)
{
textures[i].url = strdup(url);
textures[i].texture = create_texture();
load_texture_from_url(textures[i].texture, url, &textures[i].w, &textures[i].h);
return textures+i;
}
return 0; // fail
}
void fill_image(float x0, float y0, float scale, float r, float g, float b, float a, const char *url)
{
Texture *t = find_or_cache_url(url);
fill_textured_rectangle(x0, y0, x0 + t->w * scale, y0 + t->h * scale, r, g, b, a, t->texture);
}
typedef struct Glyph
{
// Font
unsigned int ch;
int charSize;
int shadow;
GLuint texture;
} Glyph;
#define MAX_GLYPHS 256
static Glyph glyphs[MAX_GLYPHS] = {};
static Glyph *find_or_cache_character(unsigned int ch, int charSize, int shadow)
{
for(int i = 0; i < MAX_TEXTURES; ++i) // Naive O(n) lookup for tiny code size
if (glyphs[i].ch == ch && glyphs[i].charSize == charSize && glyphs[i].shadow == shadow)
return glyphs+i;
else if (!glyphs[i].ch)
{
glyphs[i].ch = ch;
glyphs[i].charSize = charSize;
glyphs[i].shadow = shadow;
glyphs[i].texture = create_texture();
upload_unicode_char_to_texture(ch, charSize, shadow);
return glyphs+i;
}
return 0; // fail
}
void fill_char(float x0, float y0, float r, float g, float b, float a, unsigned int ch, int charSize, int shadow)
{
fill_textured_rectangle(x0, y0, x0+charSize, y0+charSize, r, g, b, a, find_or_cache_character(ch, charSize, shadow)->texture);
}
void fill_text(float x0, float y0, float r, float g, float b, float a, const char *str, float spacing, int charSize, int shadow)
{
while(*str)
{
fill_char(x0, y0, r, g, b, a, *str++, charSize, shadow);
x0 += spacing;
}
}