Skip to content

Instantly share code, notes, and snippets.

@kripken
Last active September 12, 2024 14:01
Show Gist options
  • Save kripken/58c0e640227fe5bac9e7b30100a2a1d3 to your computer and use it in GitHub Desktop.
Save kripken/58c0e640227fe5bac9e7b30100a2a1d3 to your computer and use it in GitHub Desktop.

Emscripten as a linker for Zig and C

This shows how to build a nontrivial program using Zig+Emscripten or C+Emscripten. In both cases Emscripten is only used as a linker, that is the frontend is either zig or clang.

"Nontrivial" here means the program uses interesting Emscripten features:

  • Asyncify
  • Full GLES3 support
  • GLFW3 support

Zig

The Zig version can be built with something like:

zig build-obj -isystem …/emsdk/upstream/emscripten/cache/sysroot/include/ triangle.zig -target wasm32-wasi

emcc triangle.o -o zig.html -s FULL_ES3=1 -s USE_GLFW=3 -s ASYNCIFY -s STANDALONE_WASM -s EXPORTED_FUNCTIONS=_run_zig --no-entry -O3

(Note: the target is wasi and not emscripten, as it looks like the Zig frontend doesn’t have that as an option atm.)

You also need to write _run_zig() in the dev console to start up the application, as the Zig startup code is not integrated with Emscripten yet and we need to do it manually. (You can also add that in the HTML, but it needs to run after the wasm is all ready, which is async.)

C

The C version can be compiled and linked with:

clang triangle.c -isystem …/emsdk/upstream/emscripten/cache/sysroot/include/ -target wasm32-unknown-emscripten -c

emcc triangle.o -o c.html -s FULL_ES3=1 -s USE_GLFW=3 -s ASYNCIFY -O3
/*
* Copyright 2018 The Emscripten Authors. All rights reserved.
* Emscripten is available under two separate licenses, the MIT license and the
* University of Illinois/NCSA Open Source License. Both these licenses can be
* found in the LICENSE file.
*/
#include <GLFW/glfw3.h>
#include <GLES3/gl3.h>
#include <math.h>
#include <stdlib.h>
#include <emscripten.h>
GLuint program;
size_t counter = 0;
const float PI = 3.14159f;
void onDraw(void* arg)
{
float t = ((float)counter) / 60.0f;
float r = 0.5f + sin(t / 8) / 4;
float s = t / 4;
const float position[] = {
r * sinf(s), r * cosf(s),
r * sinf(s + 2 * PI / 3), r * cosf(s + 2 * PI / 3),
r * sinf(s + 4 * PI / 3), r * cosf(s + 4 * PI / 3)
};
GLint location;
location = glGetAttribLocation(program, "aPosition");
glVertexAttribPointer(location, 2, GL_FLOAT, GL_FALSE, 0, position);
glEnableVertexAttribArray(location);
glClear(GL_COLOR_BUFFER_BIT);
glDrawArrays(GL_TRIANGLES, 0, 3);
counter++;
}
int main()
{
if (!glfwInit()) {
return EXIT_FAILURE;
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
GLFWwindow* window = glfwCreateWindow(500, 500, "Client-side Vertex Arrays", NULL, NULL);
if (!window) {
glfwTerminate();
return EXIT_FAILURE;
}
glfwMakeContextCurrent(window);
GLuint vertex_shader;
GLuint fragment_shader;
{
const GLchar* source = "#version 300 es \n"
" \n"
"in vec2 aPosition; \n"
"in uvec3 aColor; \n"
"out vec4 color; \n"
" \n"
"void main() \n"
"{ \n"
" gl_Position = vec4(aPosition, 0.f, 1.f); \n"
" color = vec4(vec3(aColor) / 255.f, 1.f); \n"
"} \n";
vertex_shader = glCreateShader(GL_VERTEX_SHADER);
glShaderSource(vertex_shader, 1, &source, NULL);
glCompileShader(vertex_shader);
}
{
const GLchar* source = "#version 300 es \n"
" \n"
"precision mediump float; \n"
" \n"
"in vec4 color; \n"
"out vec4 FragColor; \n"
" \n"
"void main() \n"
"{ \n"
" FragColor = color; \n"
"} \n";
fragment_shader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragment_shader, 1, &source, NULL);
glCompileShader(fragment_shader);
}
program = glCreateProgram();
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
glLinkProgram(program);
glUseProgram(program);
{
static const unsigned int color[] = {
255, 0, 0,
0, 255, 0,
0, 0, 255,
};
GLint location;
location = glGetAttribLocation(program, "aColor");
glVertexAttribIPointer(location, 3, GL_UNSIGNED_INT, 0, color);
glEnableVertexAttribArray(location);
}
while (1) {
onDraw(&window);
emscripten_sleep(0);
}
}
const c = @cImport({
@cInclude("GLFW/glfw3.h");
@cInclude("GLES3/gl3.h");
@cInclude("math.h");
@cInclude("stdlib.h");
@cInclude("emscripten.h");
});
const assert = @import("std").debug.assert;
var program: ?c_uint = null;
var counter: usize = 0;
const PI = 3.14159;
fn onDraw(arg: *c_void) void {
_ = arg;
const color = [_]c_uint{
255, 0, 0,
0, 255, 0,
0, 0, 255
};
var location = @intCast(c_uint, c.glGetAttribLocation(program.?, "aColor"));
c.glVertexAttribIPointer(location, 3, c.GL_UNSIGNED_INT, 0, &color);
c.glEnableVertexAttribArray(location);
const t: f32 = @intToFloat(f32, counter) / 60.0;
const r: f32 = 0.5 + c.sinf(t / 8) / 4;
const s: f32 = t / 4;
const position = [_]f32{
r * c.sinf(s), r * c.cosf(s),
r * c.sinf(s + 2.0 * PI / 3.0), r * c.cosf(s + 2.0 * PI / 3.0),
r * c.sinf(s + 4.0 * PI / 3.0), r * c.cosf(s + 4.0 * PI / 3.0)
};
location = @intCast(c_uint, c.glGetAttribLocation(program.?, "aPosition"));
c.glVertexAttribPointer(location, 2, c.GL_FLOAT, c.GL_FALSE, 0, &position);
c.glEnableVertexAttribArray(location);
c.glClear(c.GL_COLOR_BUFFER_BIT);
c.glDrawArrays(c.GL_TRIANGLES, 0, 3);
counter += 1;
}
export fn run_zig() void {
if (c.glfwInit() == 0) {
return;
}
c.glfwWindowHint(c.GLFW_CLIENT_API, c.GLFW_OPENGL_ES_API);
c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MAJOR, 3);
c.glfwWindowHint(c.GLFW_CONTEXT_VERSION_MINOR, 0);
var window = c.glfwCreateWindow(500, 500, "Client-side Vertex Arrays", null, null);
if (window == null) {
c.glfwTerminate();
return;
}
c.glfwMakeContextCurrent(window);
const vertex_source: [*]const u8 = // waka
\\#version 300 es
\\
\\in vec2 aPosition;
\\in uvec3 aColor;
\\out vec4 color;
\\
\\void main() {
\\ gl_Position = vec4(aPosition, 0.f, 1.f);
\\ color = vec4(vec3(aColor) / 255.f, 1.f);
\\}
;
const vertex_shader = c.glCreateShader(c.GL_VERTEX_SHADER);
c.glShaderSource(vertex_shader, 1, &vertex_source, null);
c.glCompileShader(vertex_shader);
const fragment_source: [*]const c.GLchar =
\\#version 300 es
\\
\\precision mediump float;
\\
\\in vec4 color;
\\out vec4 FragColor;
\\
\\void main() {
\\ FragColor = color;
\\}
;
const fragment_shader = c.glCreateShader(c.GL_FRAGMENT_SHADER);
c.glShaderSource(fragment_shader, 1, &fragment_source, null);
c.glCompileShader(fragment_shader);
program = c.glCreateProgram();
c.glAttachShader(program.?, vertex_shader);
c.glAttachShader(program.?, fragment_shader);
c.glLinkProgram(program.?);
c.glUseProgram(program.?);
while (true) {
onDraw(&window);
c.emscripten_sleep(0);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment