wasm and webGL
I can locally run C3 with openGL and GLFW with no problems. I am stuck on how to compile for wasm32 target.
I saw the SDL2 bindings: https://github.com/c3lang/vendor/blob/main/libraries/sdl2.c3l/sdl2.c3i
I do not mind switching from GLFW to SDL. But I also have a simple C example that I compile with Emscripten and use -s GLFW=3 and that works, is there some way i can call wasm-ld directly so i can link with GLFW?
Below is my build test script:
#!/usr/bin/python3
# install ubuntu and debian: sudo apt-get install libglfw3-dev
import os, sys, subprocess
if not os.path.isfile('c3-ubuntu-20.tar.gz'):
cmd = 'wget -c https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz'
print(cmd)
subprocess.check_call(cmd.split())
if not os.path.isdir('c3'):
cmd = 'tar -xvf c3-ubuntu-20.tar.gz'
print(cmd)
subprocess.check_call(cmd.split())
C3 = os.path.abspath('./c3/c3c')
assert os.path.isfile(C3)
#if not os.path.isdir('opengl-examples'):
# cmd = 'git clone --depth 1 https://github.com/tonis2/opengl-examples.git'
# print(cmd)
# subprocess.check_call(cmd.split())
if not os.path.isdir('c3-opengl-examples'):
cmd = 'git clone --depth 1 https://github.com/brentharts/c3-opengl-examples.git'
print(cmd)
subprocess.check_call(cmd.split())
## https://github.com/tonis2/opengl-c3.git
if not os.path.isdir('opengl-c3'):
cmd = 'git clone --depth 1 https://github.com/tonis2/opengl-c3.git'
print(cmd)
subprocess.check_call(cmd.split())
def test_triangle():
cmd = [C3, 'run', 'triangle']
print(cmd)
subprocess.check_call(cmd, cwd='c3-opengl-examples')
def test_c3(output='test-c3-glfw.bin', opt='--opt' in sys.argv, wasm='--wasm' in sys.argv):
if opt:
output = output.replace('.bin', '.opt.bin')
if wasm:
output = output.replace('.bin', '.wasm.bin')
cmd = [C3]
if wasm:
cmd += [
'--target', 'wasm32',
'--linker=custom', './emsdk/upstream/bin/wasm-ld'
]
else:
cmd += ['--target', 'linux-x64']
mode = 'compile'
cmd += [
'--output-dir', '/tmp',
'--obj-out', '/tmp',
'--build-dir', '/tmp',
'--print-output',
'-o', output,
]
if wasm:
cmd += [#'--link-libc=no', '--use-stdlib=no',
'--no-entry', '--reloc=none']
pass
else:
cmd += ['-l', 'glfw']
if opt:
cmd.append('-Oz')
cmd += [
mode,
'./c3-opengl-examples/examples/tri.c3',
'./opengl-c3/build/gl.c3',
'./c3-opengl-examples/dependencies/glfw.c3',
'./c3-opengl-examples/dependencies/helpers.c3',
]
print(cmd)
res = subprocess.check_output(cmd).decode('utf-8')
ofiles = []
for ln in res.splitlines():
if ln.endswith('.o'):
ofiles.append(ln.strip())
print(ofiles)
os.system('ls -lh /tmp/*.bin')
os.system('ls -lh /tmp/*.o')
if '--run' in sys.argv:
subprocess.check_call(['/tmp/'+output])
if wasm:
cmd = ['./emsdk/upstream/bin/llvm-objdump', '--syms', '/tmp/'+output+'.wasm']
print(cmd)
subprocess.check_call(cmd)
if __name__=='__main__':
if '--simple' in sys.argv:
test_triangle()
else:
test_c3()
Well, first of all you should be able to pass in arguments to wasm-ld using "-z
@lerno , sorry for the late reply.
I did not give -z much of a try, because my end goal was to have something small enough for js13kgames.com, and GLFW or SDL were going to be too big for that.
Now I understand how to make my own direct wrappers for C3, for others wanting to learn how to wrangle the whole process into a single script, see below.
#!/usr/bin/python3
import os, sys, subprocess, base64, webbrowser
_thisdir = os.path.split(os.path.abspath(__file__))[0]
C3 = '/usr/local/bin/c3c'
if not os.path.isfile(C3):
C3 = '/opt/c3/c3c'
if not os.path.isfile(C3):
if not os.path.isdir('./c3'):
if not os.path.isfile('c3-ubuntu-20.tar.gz'):
cmd = 'wget -c https://github.com/c3lang/c3c/releases/download/latest/c3-ubuntu-20.tar.gz'
print(cmd)
subprocess.check_call(cmd.split())
cmd = 'tar -xvf c3-ubuntu-20.tar.gz'
print(cmd)
subprocess.check_call(cmd.split())
C3 = os.path.abspath('./c3/c3c')
JS_DECOMP = '''
var $d=async(u,t)=>{
var d=new DecompressionStream('gzip')
var r=await fetch('data:application/octet-stream;base64,'+u)
var b=await r.blob()
var s=b.stream().pipeThrough(d)
var o=await new Response(s).blob()
if(t) return await o.text()
else return await o.arrayBuffer()
}
$d($0,1).then((j)=>{
$=eval(j)
$d($1).then((r)=>{
WebAssembly.instantiate(r,{env:$.proxy()}).then((c)=>{$.reset(c,"$",r)});
});
});
'''
JS_API_HEADER = '''
function make_environment(e){
return new Proxy(e,{
get(t,p,r) {
if(e[p]!==undefined){return e[p].bind(e)}
return(...args)=>{throw p}
}
});
}
function cstrlen(m,p){
var l=0;
while(m[p]!=0){l++;p++}
return l;
}
function cstr_by_ptr(m,p){
const l=cstrlen(new Uint8Array(m),p);
const b=new Uint8Array(m,p,l);
return new TextDecoder().decode(b)
}
'''
JS_API_PROXY = '''
proxy(){
return make_environment(this)
}
'''
WASM_TEST = '''
const char* VERTEX_SHADER = `
attribute vec3 position;
uniform mat4 Pmat;
uniform mat4 Vmat;
uniform mat4 Mmat;
attribute vec3 color;
varying vec3 vColor;
void main(void){
gl_Position = Pmat*Vmat*Mmat*vec4(position, 1.);
vColor = color;
}
`;
const char* FRAGMENT_SHADER = `
precision mediump float;
varying vec3 vColor;
void main(void) {
gl_FragColor = vec4(vColor, 1.0);
}
`;
float[] cube_data = {
-1,-1,-1, 1,-1,-1, 1, 1,-1, -1, 1,-1,
-1,-1, 1, 1,-1, 1, 1, 1, 1, -1, 1, 1,
-1,-1,-1, -1, 1,-1, -1, 1, 1, -1,-1, 1,
1,-1,-1, 1, 1,-1, 1, 1, 1, 1,-1, 1,
-1,-1,-1, -1,-1, 1, 1,-1, 1, 1,-1,-1,
-1, 1,-1, -1, 1, 1, 1, 1, 1, 1, 1,-1,
};
ushort[] indices = {
0,1,2, 0,2,3, 4,5,6, 4,6,7,
8,9,10, 8,10,11, 12,13,14, 12,14,15,
16,17,18, 16,18,19, 20,21,22, 20,22,23
};
float[] colors = {
0.5,0.3,0.7, 0.5,0.3,0.7, 0.5,0.3,0.7, 0.5,0.3,0.7,
1,1,0.3, 1,1,0.3, 1,1,0.3, 1,1,0.3,
0,0,1, 0,0,1, 0,0,1, 0,0,1,
1,0,0, 1,0,0, 1,0,0, 1,0,0,
1,1,0, 1,1,0, 1,1,0, 1,1,0,
0,1,0, 0,1,0, 0,1,0, 0,1,0
};
float[] proj_matrix = {1.3737387097273113,0,0,0,0,1.8316516129697482,0,0,0,0,-1.02020202020202,-1,0,0,-2.0202020202020203,0};
float[] mov_matrix = {1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1};
float[] view_matrix = {1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1};
fn void main() @extern("main") @wasm {
view_matrix[14] = view_matrix[14] -6.0; //zoom
gl_init(800, 600);
int vbuff = gl_new_buffer();
gl_bind_buffer(vbuff);
gl_buffer_data(vbuff, cube_data.len, cube_data);
int cbuff = gl_new_buffer();
gl_bind_buffer(cbuff);
gl_buffer_data(cbuff, colors.len, colors);
int ibuff = gl_new_buffer();
gl_bind_buffer_element(ibuff);
gl_buffer_element(ibuff, indices.len, indices);
int vs = gl_new_vshader(VERTEX_SHADER);
int fs = gl_new_fshader(FRAGMENT_SHADER);
int prog = gl_new_program();
gl_attach_vshader(prog, vs);
gl_attach_fshader(prog, fs);
gl_link_program( prog );
int ploc = gl_get_uniform_location(prog, "Pmat");
int vloc = gl_get_uniform_location(prog, "Vmat");
int mloc = gl_get_uniform_location(prog, "Mmat");
gl_bind_buffer(vbuff);
int posloc = gl_get_attr_location(prog, "position");
gl_vertex_attr_pointer(posloc, 3);
gl_enable_vertex_attr_array(posloc);
gl_bind_buffer(cbuff);
int clrloc = gl_get_attr_location(prog, "color");
gl_vertex_attr_pointer(clrloc, 3);
gl_enable_vertex_attr_array(clrloc);
gl_use_program(prog);
gl_enable("DEPTH_TEST");
gl_depth_func("LEQUAL");
gl_viewport(0,0,800,600);
gl_clear(0.5,0.5,0.5, 1.0, 1.0);
gl_uniform_mat4fv(ploc, proj_matrix);
gl_uniform_mat4fv(vloc, view_matrix);
gl_uniform_mat4fv(mloc, mov_matrix);
gl_bind_buffer_element(ibuff);
gl_draw_triangles( indices.len );
}
'''
WASM_MINI_GL = '''
extern fn void gl_init(int w, int h);
extern fn void gl_enable(char *ptr);
extern fn void gl_depth_func(char *ptr);
extern fn int gl_new_buffer();
extern fn void gl_bind_buffer(int idx);
extern fn void gl_bind_buffer_element(int idx);
extern fn void gl_buffer_data(int idx, int sz, float *ptr);
extern fn void gl_buffer_u16(int idx, int sz, ushort *ptr);
extern fn void gl_buffer_element(int idx, int sz, ushort *ptr);
extern fn int gl_new_program();
extern fn void gl_link_program(int prog);
extern fn void gl_attach_vshader(int prog, int s);
extern fn void gl_attach_fshader(int prog, int s);
extern fn int gl_new_vshader(char *c);
extern fn int gl_new_fshader(char *c);
extern fn int gl_get_uniform_location(int a, char *ptr);
extern fn int gl_get_attr_location(int a, char *ptr);
extern fn void gl_enable_vertex_attr_array(int loc);
extern fn void gl_vertex_attr_pointer(int loc, int n);
extern fn void gl_use_program(int prog);
extern fn void gl_clear(float r, float g, float b, float a, float z);
extern fn void gl_viewport(int x, int y, int w, int h);
extern fn void gl_uniform_mat4fv(int loc, float *mat);
extern fn void gl_draw_triangles(int len);
'''
JS_MINI_GL = 'class api {' + JS_API_PROXY + '''
reset(wasm,id,bytes){
this.wasm=wasm;
this.canvas=document.getElementById(id);
this.gl=this.canvas.getContext('webgl');
this.bufs=[];
this.vs=[];
this.fs=[];
this.progs=[];
this.locs=[];
this.wasm.instance.exports.main();
}
gl_init(w,h) {
this.canvas.width=w;
this.canvas.height=h;
this.gl.clearColor(1,0,0, 1);
this.gl.clear(this.gl.COLOR_BUFFER_BIT)
}
gl_enable(ptr){
const key = cstr_by_ptr(this.wasm.instance.exports.memory.buffer,ptr);
console.log("gl enable:", key);
this.gl.enable(this.gl[key])
}
gl_depth_func(ptr){
const key = cstr_by_ptr(this.wasm.instance.exports.memory.buffer,ptr);
console.log("gl depthFunc:", key);
this.gl.depthFunc(this.gl[key])
}
gl_new_buffer(){
return this.bufs.push(this.gl.createBuffer())-1
}
gl_bind_buffer(i){
const b=this.bufs[i];
console.log("bind buffer:", b);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER,b)
}
gl_bind_buffer_element(i){
const b=this.bufs[i];
console.log("bind buffer element:", b);
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER,b)
}
gl_buffer_data(i, sz, ptr){
const b=this.bufs[i];
console.log("buffer data:", b);
const arr = new Float32Array(this.wasm.instance.exports.memory.buffer,ptr,sz);
this.gl.bufferData(this.gl.ARRAY_BUFFER, arr, this.gl.STATIC_DRAW)
}
gl_buffer_u16(i, sz, ptr){
const b=this.bufs[i];
console.log("buffer data:", b);
const arr = new Uint16Array(this.wasm.instance.exports.memory.buffer,ptr,sz);
this.gl.bufferData(this.gl.ARRAY_BUFFER, arr, this.gl.STATIC_DRAW)
}
gl_buffer_element(i, sz, ptr){
const b=this.bufs[i];
console.log("element buffer data:", b);
const arr = new Uint16Array(this.wasm.instance.exports.memory.buffer,ptr,sz);
console.log(arr);
this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, arr, this.gl.STATIC_DRAW)
}
gl_new_vshader(ptr){
const c = cstr_by_ptr(this.wasm.instance.exports.memory.buffer,ptr);
console.log('new vertex shader', c);
const s = this.gl.createShader(this.gl.VERTEX_SHADER);
this.gl.shaderSource(s,c);
this.gl.compileShader(s);
return this.vs.push(s)-1
}
gl_new_fshader(ptr){
const c = cstr_by_ptr(this.wasm.instance.exports.memory.buffer,ptr);
console.log('new fragment shader', c);
const s = this.gl.createShader(this.gl.FRAGMENT_SHADER);
this.gl.shaderSource(s,c);
this.gl.compileShader(s);
return this.fs.push(s)-1
}
gl_new_program(){
return this.progs.push(this.gl.createProgram())-1
}
gl_attach_vshader(a,b){
const prog = this.progs[a];
this.gl.attachShader(prog, this.vs[b])
}
gl_attach_fshader(a,b){
const prog = this.progs[a];
this.gl.attachShader(prog, this.fs[b])
}
gl_link_program(a){
this.gl.linkProgram(this.progs[a])
}
gl_get_uniform_location(a,b){
const c = cstr_by_ptr(this.wasm.instance.exports.memory.buffer,b);
var loc = this.gl.getUniformLocation(this.progs[a], c);
console.log('get uniform:', loc);
return this.locs.push(loc)-1
}
gl_get_attr_location(a,b){
const c = cstr_by_ptr(this.wasm.instance.exports.memory.buffer,b);
var loc = this.gl.getAttribLocation(this.progs[a], c);
console.log('get attribute:', loc);
return loc
}
gl_enable_vertex_attr_array(a){
this.gl.enableVertexAttribArray(a)
}
gl_vertex_attr_pointer(a,n){
this.gl.vertexAttribPointer(a, n, this.gl.FLOAT, false,0,0)
}
gl_use_program(a){
this.gl.useProgram(this.progs[a])
}
gl_clear(r,g,b,a,z){
this.gl.clearColor(r,g,b,a);
this.gl.clearDepth(z);
this.gl.clear(this.gl.COLOR_BUFFER_BIT|this.gl.DEPTH_BUFFER_BIT)
}
gl_viewport(x,y,w,h){
this.gl.viewport(x,y,w,h)
}
gl_uniform_mat4fv(a,ptr){
const mat = new Float32Array(this.wasm.instance.exports.memory.buffer,ptr,16);
this.gl.uniformMatrix4fv(this.locs[a], false, mat)
}
gl_draw_triangles(n){
console.log('draw triangles:', n);
this.gl.drawElements(this.gl.TRIANGLES, n, this.gl.UNSIGNED_SHORT, 0)
}
}
new api();
'''
def c3_compile(c3, name='test-c3'):
tmp = '/tmp/%s.c3' % name
open(tmp,'w').write(c3)
cmd = [
C3, '--target', 'wasm32', 'compile',
'--output-dir', '/tmp',
'--obj-out', '/tmp',
'--build-dir', '/tmp',
#'--print-output',
'--link-libc=no', '--use-stdlib=no', '--no-entry', '--reloc=none', '-z', '--export-table',
'-Oz',
'-o', name,
tmp
]
print(cmd)
subprocess.check_call(cmd)
wasm = '/tmp/%s.wasm' % name
return wasm
def test_wasm():
wasm = c3_compile(WASM_MINI_GL + WASM_TEST)
cmd = ['gzip', '--keep', '--force', '--verbose', '--best', wasm]
print(cmd)
subprocess.check_call(cmd)
wa = open(wasm,'rb').read()
w = open(wasm+'.gz','rb').read()
b = base64.b64encode(w).decode('utf-8')
jtmp = '/tmp/c3api.js'
open(jtmp,'w').write(JS_API_HEADER + JS_MINI_GL)
cmd = ['gzip', '--keep', '--force', '--verbose', '--best', jtmp]
print(cmd)
subprocess.check_call(cmd)
js = open(jtmp+'.gz','rb').read()
jsb = base64.b64encode(js).decode('utf-8')
o = [
'<html>',
'<body>',
'<canvas id="$"></canvas>',
'<script>',
'var $0="%s"' % jsb,
'var $1="%s"' % b,
JS_DECOMP,
'</script>',
]
out = 'c3zag-preview.html'
open(out,'w').write('\n'.join(o))
webbrowser.open(out)
if __name__=='__main__':
test_wasm()
you can now attach C3 scripts to blender objects from the UI, or from Python like this:
MY_C3_SCRIPT = '''
rotateY( self.matrix, self.my_prop );
'''
a = bpy.data.texts.new(name='example1.c3')
a.from_string(MY_C3_SCRIPT)
bpy.ops.mesh.primitive_monkey_add()
ob = bpy.context.active_object
ob.c3_script0 = a
ob['my_prop'] = 0.01