c3c icon indicating copy to clipboard operation
c3c copied to clipboard

wasm and webGL

Open panjea opened this issue 1 year ago • 1 comments

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()

panjea avatar Sep 30 '24 14:09 panjea

Well, first of all you should be able to pass in arguments to wasm-ld using "-z ". Did you try this already?

lerno avatar Oct 03 '24 09:10 lerno

@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()

panjea avatar Oct 28 '24 16:10 panjea

Peek 2024-10-29 10-34

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

brentharts avatar Oct 29 '24 17:10 brentharts