Skip to content

Instantly share code, notes, and snippets.

@robert-nix
Last active October 7, 2015 08:55
Show Gist options
  • Select an option

  • Save robert-nix/93c1b09f4fbdeb7faad4 to your computer and use it in GitHub Desktop.

Select an option

Save robert-nix/93c1b09f4fbdeb7faad4 to your computer and use it in GitHub Desktop.

Interacting with an emscripten-compiled module from javascript usually takes the form of writing a simple wrapper in C to return a string-serialized version of the data you need that is then parsed in javascript. This function is called using Module.ccall, and its result is converted out of the heap using Module.*ToString(result) and then (hopefully) freed.

However, this is annoying, because you have to recompile the module any time you want to use the library in a different way. Here is an example of interacting with the emscripten heap directly, using the typed ArrayViews provided and my library for defining C structs over ArrayBuffers.

The library shown is mojoshader, and the use case implemented is translating HLSL assembly like that compiled in to Unity3d shaders to GLSL shader source code which can then be manipulated into a usable form of GLSL ES shader source code (the GLSL code isn't directly usable because of the legacy support that GLSL ES strips away).

do -> # cheap setup
MojoError = cheap.struct [
cheap.ptr 'char', 'error'
cheap.ptr 'char', 'filename'
cheap.int 'error_position'
]
cheap.typedef 'MojoError', MojoError
MojoUniform = cheap.struct [
cheap.int 'type'
cheap.int 'index'
cheap.int 'array_count'
cheap.int 'constant'
cheap.ptr 'char', 'Name'
]
cheap.typedef 'MojoUniform', MojoUniform
MojoConstant = cheap.struct
type: cheap.int 0
index: cheap.int 4
f: cheap.float 8, 4
i: cheap.int 8, 4
b: cheap.int 8
cheap.typedef 'MojoConstant', MojoConstant
MojoSampler = cheap.struct [
cheap.int 'type'
cheap.int 'index'
cheap.ptr 'char', 'Name'
cheap.int 'texbem'
]
cheap.typedef 'MojoSampler', MojoSampler
MojoAttribute = cheap.struct [
cheap.int 'usage'
cheap.int 'index'
cheap.ptr 'char', 'Name'
]
cheap.typedef 'MojoAttribute', MojoAttribute
MojoSwizzle = cheap.struct [
cheap.int 'usage'
cheap.int 'index'
cheap.uchar 'swizzles', 4
]
cheap.typedef 'MojoSwizzle', MojoSwizzle
MojoParseData = cheap.struct [
cheap.int 'error_count'
cheap.ptr 'MojoError', 'errors'
cheap.ptr 'char', 'profile'
cheap.ptr 'char', 'output'
cheap.int 'output_len'
cheap.int 'instruction_count'
cheap.int 'shader_type'
cheap.int 'major_ver'
cheap.int 'minor_ver'
cheap.int 'uniform_count'
cheap.ptr 'MojoUniform', 'uniforms'
cheap.int 'constant_count'
cheap.ptr 'MojoConstant', 'constants'
cheap.int 'sampler_count'
cheap.ptr 'MojoSampler', 'samplers'
cheap.int 'attribute_count'
cheap.ptr 'MojoAttribute', 'attributes'
cheap.int 'output_count'
cheap.ptr 'MojoAttribute', 'outputs'
cheap.int 'swizzle_count'
cheap.ptr 'MojoSwizzle', 'swizzles'
cheap.int 'symbol_count'
cheap.ptr 'void', 'symbols'
cheap.ptr 'void', 'preshader'
cheap.ptr 'void', 'malloc'
cheap.ptr 'void', 'free'
cheap.ptr 'void', 'malloc_data'
]
cheap.typedef 'MojoParseData', MojoParseData
# make emscripten's heap visible to cheap:
buf = Module.HEAP8.buffer
cheap._heapLast =
a: 0
l: buf.byteLength
e: buf.byteLength
prev: null
next: null
buf: buf
ui32: Module.HEAPU32
i32: Module.HEAP32
ui16: Module.HEAPU16
i16: Module.HEAP16
ui8: Module.HEAPU8
i8: Module.HEAP8
f32: Module.HEAPF32
f64: Module.HEAPF64
null
emblock = cheap._heapLast
emptr = (t, n) ->
p = cheap.ref emblock
p = p.add n
p.cast t + '*'
mojoshader =
# enum usageType
usage: ['position', 'blendweight', 'blendindices', 'normal',
'pointsize', 'texcoord', 'tangent', 'binormal',
'tessfactor', 'positiont', 'color', 'fog', 'depth', 'sample']
# given a string of hlsl assembly, will output an object with glsl shader
# source and metadata or parse errors if an error occurred.
hlslAsmToGlslRaw: (hlsl) ->
res = {}
bytecode = @assemble hlsl
if bytecode.error_count > 0
res.errors = @listErrors bytecode
@free bytecode
return res
parsed = @parseGlsl bytecode
@free bytecode
if parsed.error_count > 0
res.errors = @listErrors parsed
else
res.output = Module.UTF8ToString parsed.output.a
res.slots = parsed.instruction_count
res.type = if parsed.shader_type == 2 then 'vert' else 'frag'
res.version = [parsed.major_ver, parsed.minor_ver]
res.uniforms = for i in [0..parsed.uniform_count-1] by 1
u = parsed.uniforms.deref i
type: ['float', 'int', 'bool'][u.type]
index: u.index
size: u.array_count
constant: u.constant != 0
name: Module.UTF8ToString u.Name.a
res.samplers = for i in [0..parsed.sampler_count-1] by 1
s = parsed.samplers.deref i
type: ['2d', 'cube', 'volume'][s.type]
index: s.index
name: Module.UTF8ToString s.Name.a
res.attributes = for i in [0..parsed.attribute_count-1] by 1
a = parsed.attributes.deref i
usage: @usage[a.usage]
index: a.index
name: Module.UTF8ToString a.Name.a
res.outputs = for i in [0..parsed.output_count-1] by 1
a = parsed.outputs.deref i
usage: @usage[a.usage]
index: a.index
name: Module.UTF8ToString a.Name.a
@free parsed
return res
listErrors: (data) ->
for i in [0..data.error_count-1] by 1
err = data.errors.deref i
error: Module.UTF8ToString err.error.a
pos: err.error_position
# see mojoshader.h for the function signatures
assemble: (asm) ->
ptr = Module.ccall('MOJOSHADER_assemble', 'number',
['string',
'string', 'number',
'number', 'number',
'number', 'number',
'number', 'number',
'number', 'number',
'number', 'number', 'number'],
['input', asm, asm.length, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0])
emptr('MojoParseData', ptr).deref()
parseGlsl: (data) ->
ptr = Module.ccall('MOJOSHADER_parse', 'number', [
'string',
'number', 'number',
'number', 'number',
'number', 'number',
'number', 'number', 'number'],
['glsl120', data.output.a, data.output_len, 0, 0, 0, 0, 0, 0, 0])
emptr('MojoParseData', ptr).deref()
free: (data) ->
Module.ccall('MOJOSHADER_freeParseData', null, ['number'], [data.a])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment