Skip to content

Commit

Permalink
add a hacky browserify transform to remove runtime checks
Browse files Browse the repository at this point in the history
  • Loading branch information
mikolalysenko committed May 6, 2016
1 parent 288b11e commit a2549e6
Show file tree
Hide file tree
Showing 8 changed files with 6,719 additions and 142 deletions.
9 changes: 5 additions & 4 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,11 @@
## Planned

* Improve readability of generated code
* Use numeric ids instead of strings for shader sources
* Better error messages for shader compilation failure
* Change buffer and texture APIs to separate data from rest of options
* Add in place update methods to buffers and textures
* Add support for polling buffers and animated GIFs (useful for web audio)
* Support more DDS texture formats (HDR, PVRTC, etc.)
* Cubic framebuffer objects
* Browserify transform to remove all runtime checks
* Benchmark suite
* More unit tests
* Fix CI problems
Expand All @@ -29,12 +26,16 @@

## Next

* Use numeric ids instead of strings for shader sources
* shader error messages are better
* Browserify transform to remove all runtime checks

## 0.3.0

* added renderbuffers (via regl.renderbuffer)
* added framebuffer objects (via regl.framebuffer)
* regl.buffer and regl.elements can now take ndarray-like inputs
* Switch to using Google closure compiler for minified builds
* witch to using Google closure compiler for minified builds

## 0.2.0

Expand Down
2 changes: 2 additions & 0 deletions bin/build-gallery.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var fs = require('fs')
var glob = require('glob')
var browserify = require('browserify')
var removeCheck = require('./remove-check')
var ncp = require('ncp')
var mkdirp = require('mkdirp')
var ClosureCompiler = require('google-closure-compiler').compiler
Expand Down Expand Up @@ -52,6 +53,7 @@ mkdirp('www/gallery', function (err) {
debug: true
})
b.add(file)
b.transform(removeCheck)
b.bundle(function (err, bundle) {
if (err) {
throw err
Expand Down
41 changes: 30 additions & 11 deletions bin/build-min.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,32 @@
var path = require('path')
var fs = require('fs')
var browserify = require('browserify')
var ClosureCompiler = require('google-closure-compiler').compiler

var closureCompiler = new ClosureCompiler({
js: 'dist/regl.js',
compilation_level: 'ADVANCED',
js_output_file: 'dist/regl.min.js'
})

closureCompiler.run(function (exitCode, stdOut, stdErr) {
console.log('closure stdout:', stdOut)
console.log('closure stderr:', stdErr)
process.exit(exitCode)
})
var INPUT_FILE = path.join(__dirname, '../regl.js')
var UNCHECKED_FILE = path.join(__dirname, '../dist/regl.unchecked.js')
var OUTPUT_FILE = path.join(__dirname, '../dist/regl.min.js')

console.log('removing checks from ', INPUT_FILE)
console.log('writing to ', UNCHECKED_FILE)

browserify(INPUT_FILE, { standalone: 'initREGL' })
.transform(require('./remove-check'))
.bundle()
.pipe(fs.createWriteStream(UNCHECKED_FILE))
.on('close', function () {

console.log('minifying script: ', UNCHECKED_FILE)

var closureCompiler = new ClosureCompiler({
js: UNCHECKED_FILE,
compilation_level: 'SIMPLE',
js_output_file: OUTPUT_FILE
})

closureCompiler.run(function (exitCode, stdOut, stdErr) {
console.log('closure stdout:', stdOut)
console.log('closure stderr:', stdErr)
process.exit(exitCode)
})
})
52 changes: 52 additions & 0 deletions bin/remove-check.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
var through2 = require('through2')
var falafel = require('falafel')

function isCheckCall (node) {
if (node.type === 'Identifier' && node.name === 'check') {
return true
}
return (
node.type === 'MemberExpression' &&
node.object.type === 'Identifier' &&
node.object.name === 'check')
}

function isCheckRequire (node) {
return node.id.name === 'check'
}

module.exports = function () {
var data = ''
return through2(write, end)

function write (chunk, enc, done) {
data += chunk
done()
}

function end (done) {
try {
var result = falafel(data, function (node) {
switch (node.type) {
case 'CallExpression':
if (isCheckCall(node.callee)) {
node.update('')
return
}
break
case 'VariableDeclaration':
if (node.declarations.length === 1 &&
isCheckRequire(node.declarations[0])) {
node.update('')
return
}
break
}
})
this.push(result.toString())
} catch(e) {
this.push(data)
}
done()
}
}
192 changes: 188 additions & 4 deletions dist/regl.js
Original file line number Diff line number Diff line change
Expand Up @@ -468,6 +468,8 @@ module.exports = function wrapBufferState (gl) {
var isTypedArray = require('./is-typed-array')
var extend = require('./extend')

var GL_COMPILE_STATUS

function raise (message) {
var error = new Error('(regl) ' + message)
console.error(error)
Expand Down Expand Up @@ -524,17 +526,195 @@ function checkOneOf (value, list, message) {
}
}

function leftPad (str, n) {
str = str + ''
while (str.length < n) {
str = ' ' + str
}
return str
}

function ShaderFile () {
this.name = 'unknown'
this.lines = []
this.index = {}
this.hasErrors = false
}

function ShaderLine (number, line) {
this.number = number
this.line = line
this.errors = []
}

function ShaderError (fileNumber, lineNumber, message) {
this.file = fileNumber
this.line = lineNumber
this.message = message
}

function parseSource (source) {
var lines = source.split('\n')
var annotated = []
var lineNumber = 1
var fileNumber = 0
var files = {
unknown: new ShaderFile(),
0: new ShaderFile()
}
files.unknown.lines.push(new ShaderLine(0, ''))
for (var i = 0; i < lines.length; ++i) {
var line = lines[i]
var parts = /^\s*\#\s*(\w+)\s+(.+)\s*$/.exec(line)
if (parts) {
switch(parts[1]) {
case 'line':
var lineNumberInfo = /(\d+)(\s+\d+)?/.exec(parts[2])
if (lineNumberInfo) {
lineNumber = lineNumberInfo[1] | 0
if (lineNumberInfo[2]) {
fileNumber = lineNumberInfo[2] | 0
if (!(fileNumber in files)) {
files[fileNumber] = new ShaderFile()
}
}
}
break
case 'define':
var nameInfo = /SHADER_NAME(_B64)?\s+(.*)$/.exec(parts[2])
if (nameInfo) {
files[fileNumber].name = (nameInfo[1]
? btoa(nameInfo[2])
: nameInfo[2])
}
break
}
}
files[fileNumber].lines.push(new ShaderLine(lineNumber++, line))
}
Object.keys(files).forEach(function (fileNumber) {
var file = files[fileNumber]
file.lines.forEach(function (line) {
file.index[line.number] = line
})
})
return files
}

function parseErrorLog (errLog) {
var result = []
errLog.split('\n').forEach(function (errMsg) {
var parts = /^ERROR\:\s+(\d+)\:(\d+)\:\s*(.*)$/.exec(errMsg)
if (parts) {
result.push(new ShaderError(
parts[1] | 0,
parts[2] | 0,
parts[3].trim()))
} else if (errMsg.length > 0) {
result.push(new ShaderError('unknown', 0, errMsg))
}
})
return result
}

function annotateFiles (files, errors) {
errors.forEach(function (error) {
var file = files[error.file]
if (file) {
var line = file.index[error.line]
if (line) {
line.errors.push(error)
file.hasErrors = true
return
}
}
files.unknown.hasErrors = true
files.unknown.lines[0].errors.push(error)
})
}

function checkShaderError (gl, shader, source, type) {
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
var errLog = gl.getShaderInfoLog(shader)
check.raise('Error compiling shader:\n' + errLog)
var typeName = type === gl.FRAGMENT_SHADER ? 'fragment' : 'vertex'
var files = parseSource(source)
var errors = parseErrorLog(errLog)
annotateFiles(files, errors)

Object.keys(files).forEach(function (fileNumber) {
var file = files[fileNumber]
if (!file.hasErrors) {
return
}

var strings = ['']
var styles = ['']

function push (str, style) {
strings.push(str)
styles.push(style || '')
}

push('file number ' + fileNumber + ': ' + file.name + '\n', 'color:red;text-decoration:underline;font-weight:bold')

file.lines.forEach(function (line) {
if (line.errors.length > 0) {
push(leftPad(line.number, 4) + '| ', 'background-color:yellow; font-weight:bold')
push(line.line + '\n', 'color:red; background-color:yellow; font-weight:bold')

// try to guess token
var offset = 0
line.errors.forEach(function (error) {

var message = error.message
var token = /^\s*\'(.*)\'\s*\:\s*(.*)$/.exec(message)
if (token) {
var tokenPat = token[1]
message = token[2]
switch (tokenPat) {
case 'assign':
tokenPat = '='
break
}
offset = Math.max(line.line.indexOf(tokenPat, offset), 0)
} else {
offset = 0
}

push(leftPad('| ', 6))
push(leftPad('^^^', offset + 3) + '\n', 'font-weight:bold')
push(leftPad('| ', 6))
push(message + '\n', 'font-weight:bold')
})
push(leftPad('| ', 6) + '\n')
} else {
push(leftPad(line.number, 4) + '| ')
push(line.line + '\n', 'color:red')
}
})

styles[0] = strings.join('%c')

console.log.apply(console, styles)
})

check.raise('Error compiling ' + typeName + ' shader, ' + files[0].name)
}
}

function checkLinkError (gl, program) {
function checkLinkError (gl, program, fragShader, vertShader) {
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
var errLog = gl.getProgramInfoLog(program)
check.raise('Error linking program:\n' + errLog)
var fragParse = parseSource(fragShader)
var vertParse = parseSource(vertShader)

var header = 'Error linking program, vertex shader: ' +
vertParse[0].name + ', fragment shader: ' + fragParse[0].name

console.log('%c' + header + '\n%c' + errLog,
'color:red;text-decoration:underline;font-weight:bold',
'color:red')
check.raise(header)
}
}

Expand Down Expand Up @@ -4176,7 +4356,11 @@ module.exports = function wrapShaderState (
gl.attachShader(program, fragShader)
gl.attachShader(program, vertShader)
gl.linkProgram(program)
check.linkError(gl, program)
check.linkError(
gl,
program,
stringStore.str(desc.fragId),
stringStore.str(desc.vertId))

// -------------------------------
// grab uniforms
Expand Down
Loading

0 comments on commit a2549e6

Please sign in to comment.