diff --git a/example.html b/example.html index 25f737c..a1317ae 100644 --- a/example.html +++ b/example.html @@ -16,8 +16,6 @@ document.body.appendChild(heatmap.canvas); var paintAtCoord = function(x, y){ - //heatmap.addPoint(x, y, 100, 10/255); - var count = 0; while(count < 200){ var xoff = Math.random()*2-1; @@ -30,7 +28,7 @@ xoff/=ls; yoff/=ls; xoff*=1-l; yoff*=1-l; count += 1; - heatmap.addPoint(x+xoff*50, y+yoff*50, 20, 1/300); + heatmap.addPoint(x+xoff*50, y+yoff*50, 30, 2/300); } } diff --git a/webgl-heatmap.coffee b/webgl-heatmap.coffee index 726b86b..13007a7 100644 --- a/webgl-heatmap.coffee +++ b/webgl-heatmap.coffee @@ -1,3 +1,459 @@ +nukeVendorPrefix = -> + if window.WebGLRenderingContext? + vendors = ['WEBKIT', 'MOZ', 'MS', 'O'] + vendorRe = /^WEBKIT_(.*)|MOZ_(.*)|MS_(.*)|O_(.*)/ + + getExtension = WebGLRenderingContext.prototype.getExtension + WebGLRenderingContext.prototype.getExtension = (name) -> + match = name.match vendorRe + if match != null + name = match[1] + + extobj = getExtension.call @, name + if extobj == null + for vendor in vendors + extobj = getExtension.call @, vendor + '_' + name + if extobj != null + return extobj + return null + else + return extobj + + getSupportedExtensions = WebGLRenderingContext.prototype.getSupportedExtensions + WebGLRenderingContext.prototype.getSupportedExtensions = -> + supported = getSupportedExtensions.call @ + result = [] + + for extension in supported + match = extension.match vendorRe + if match != null + extension = match[1] + + if extension not in result + result.push extension + + return result + +textureFloatShims = -> + createSourceCanvas = -> + canvas = document.createElement 'canvas' + canvas.width = 2 + canvas.height = 2 + ctx = canvas.getContext '2d' + imageData = ctx.getImageData(0, 0, 2, 2) + imageData.data.set(new Uint8ClampedArray([ + 0,0,0,0, + 255,255,255,255, + 0,0,0,0, + 255,255,255,255, + ])) + ctx.putImageData(imageData, 0, 0) + return canvas + + createSourceCanvas() + + checkFloatLinear = (gl, sourceType) -> + ## drawing program ## + program = gl.createProgram() + vertexShader = gl.createShader(gl.VERTEX_SHADER) + gl.attachShader(program, vertexShader) + gl.shaderSource(vertexShader, ''' + attribute vec2 position; + void main(){ + gl_Position = vec4(position, 0.0, 1.0); + } + ''') + + gl.compileShader(vertexShader) + if not gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS) + throw gl.getShaderInfoLog(vertexShader) + + fragmentShader = gl.createShader(gl.FRAGMENT_SHADER) + gl.attachShader(program, fragmentShader) + gl.shaderSource(fragmentShader, ''' + uniform sampler2D source; + void main(){ + gl_FragColor = texture2D(source, vec2(1.0, 1.0)); + } + ''') + gl.compileShader(fragmentShader) + if not gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS) + throw gl.getShaderInfoLog(fragmentShader) + + gl.linkProgram(program) + if not gl.getProgramParameter(program, gl.LINK_STATUS) + throw gl.getProgramInfoLog(program) + + gl.useProgram(program) + + cleanup = -> + gl.deleteShader(fragmentShader) + gl.deleteShader(vertexShader) + gl.deleteProgram(program) + gl.deleteBuffer(buffer) + gl.deleteTexture(source) + gl.deleteTexture(target) + gl.deleteFramebuffer(framebuffer) + + gl.bindBuffer(gl.ARRAY_BUFFER, null) + gl.useProgram(null) + gl.bindTexture(gl.TEXTURE_2D, null) + gl.bindFramebuffer(gl.FRAMEBUFFER, null) + + ## target FBO ## + target = gl.createTexture() + gl.bindTexture(gl.TEXTURE_2D, target) + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + 2, 2, + 0, + gl.RGBA, + gl.UNSIGNED_BYTE, + null, + ) + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) + + framebuffer = gl.createFramebuffer() + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer) + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + target, + 0 + ) + + ## source texture ## + sourceCanvas = createSourceCanvas() + source = gl.createTexture() + gl.bindTexture(gl.TEXTURE_2D, source) + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + gl.RGBA, + sourceType, + sourceCanvas, + ) + + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) + + ## create VBO ## + vertices = new Float32Array([ + 1, 1, + -1, 1, + -1, -1, + + 1, 1, + -1, -1, + 1, -1, + ]) + buffer = gl.createBuffer() + gl.bindBuffer(gl.ARRAY_BUFFER, buffer) + gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW) + positionLoc = gl.getAttribLocation(program, 'position') + sourceLoc = gl.getUniformLocation(program, 'source') + gl.enableVertexAttribArray(positionLoc) + gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0) + gl.uniform1i(sourceLoc, 0) + gl.drawArrays(gl.TRIANGLES, 0, 6) + + readBuffer = new Uint8Array(4*4) + gl.readPixels(0, 0, 2, 2, gl.RGBA, gl.UNSIGNED_BYTE, readBuffer) + + result = Math.abs(readBuffer[0] - 127) < 10 + + cleanup() + return result + + checkTexture = (gl, targetType) -> + target = gl.createTexture() + gl.bindTexture(gl.TEXTURE_2D, target) + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + 2, 2, + 0, + gl.RGBA, + targetType, + null, + ) + + if gl.getError() == 0 + gl.deleteTexture(target) + return true + else + gl.deleteTexture(target) + return false + + checkColorBuffer = (gl, targetType) -> + target = gl.createTexture() + gl.bindTexture(gl.TEXTURE_2D, target) + gl.texImage2D( + gl.TEXTURE_2D, + 0, + gl.RGBA, + 2, 2, + 0, + gl.RGBA, + targetType, + null, + ) + + framebuffer = gl.createFramebuffer() + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer) + gl.framebufferTexture2D( + gl.FRAMEBUFFER, + gl.COLOR_ATTACHMENT0, + gl.TEXTURE_2D, + target, + 0 + ) + + check = gl.checkFramebufferStatus(gl.FRAMEBUFFER) + + gl.deleteTexture(target) + gl.deleteFramebuffer(framebuffer) + gl.bindTexture(gl.TEXTURE_2D, null) + gl.bindFramebuffer(gl.FRAMEBUFFER, null) + + if check == gl.FRAMEBUFFER_COMPLETE + return true + else + return false + + shimExtensions = [] + shimLookup = {} + unshimExtensions = [] + + checkSupport = -> + canvas = document.createElement 'canvas' + gl = null + try + gl = canvas.getContext 'experimental-webgl' + if(gl == null) + gl = canvas.getContext 'webgl' + + if gl? + singleFloatExt = gl.getExtension 'OES_texture_float' + if singleFloatExt == null + if checkTexture gl, gl.FLOAT + singleFloatTexturing = true + shimExtensions.push 'OES_texture_float' + shimLookup.OES_texture_float = {shim:true} + else + singleFloatTexturing = false + unshimExtensions.push 'OES_texture_float' + else + if checkTexture gl, gl.FLOAT + singleFloatTexturing = true + shimExtensions.push 'OES_texture_float' + else + singleFloatTexturing = false + unshimExtensions.push 'OES_texture_float' + + if singleFloatTexturing + extobj = gl.getExtension 'WEBGL_color_buffer_float' + if extobj == null + if checkColorBuffer gl, gl.FLOAT + shimExtensions.push 'WEBGL_color_buffer_float' + shimLookup.WEBGL_color_buffer_float = { + shim: true + RGBA32F_EXT: 0x8814 + RGB32F_EXT: 0x8815 + FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT: 0x8211 + UNSIGNED_NORMALIZED_EXT: 0x8C17 + } + else + unshimExtensions.push 'WEBGL_color_buffer_float' + else + if checkColorBuffer gl, gl.FLOAT + shimExtensions.push 'WEBGL_color_buffer_float' + else + unshimExtensions.push 'WEBGL_color_buffer_float' + + extobj = gl.getExtension 'OES_texture_float_linear' + if extobj == null + if checkFloatLinear gl, gl.FLOAT + shimExtensions.push 'OES_texture_float_linear' + shimLookup.OES_texture_float_linear = {shim:true} + else + unshimExtensions.push 'OES_texture_float_linear' + else + if checkFloatLinear gl, gl.FLOAT + shimExtensions.push 'OES_texture_float_linear' + else + unshimExtensions.push 'OES_texture_float_linear' + + halfFloatExt = gl.getExtension 'OES_texture_half_float' + if halfFloatExt == null + if checkTexture(gl, 0x8D61) + halfFloatTexturing = true + shimExtensions.push 'OES_texture_half_float' + halfFloatExt = shimLookup.OES_texture_half_float = { + HALF_FLOAT_OES: 0x8D61 + shim:true + } + else + halfFloatTexturing = false + unshimExtensions.push 'OES_texture_half_float' + else + if checkTexture(gl, halfFloatExt.HALF_FLOAT_OES) + halfFloatTexturing = true + shimExtensions.push 'OES_texture_half_float' + else + halfFloatTexturing = false + unshimExtensions.push 'OES_texture_half_float' + + if halfFloatTexturing + extobj = gl.getExtension 'EXT_color_buffer_half_float' + if extobj == null + if checkColorBuffer gl, halfFloatExt.HALF_FLOAT_OES + shimExtensions.push 'EXT_color_buffer_half_float' + shimLookup.EXT_color_buffer_half_float = { + shim: true + RGBA16F_EXT: 0x881A + RGB16F_EXT: 0x881B + FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT: 0x8211 + UNSIGNED_NORMALIZED_EXT: 0x8C17 + } + else + unshimExtensions.push 'EXT_color_buffer_half_float' + else + if checkColorBuffer gl, halfFloatExt.HALF_FLOAT_OES + shimExtensions.push 'EXT_color_buffer_half_float' + else + unshimExtensions.push 'EXT_color_buffer_half_float' + + extobj = gl.getExtension 'OES_texture_half_float_linear' + if extobj == null + if checkFloatLinear gl, halfFloatExt.HALF_FLOAT_OES + shimExtensions.push 'OES_texture_half_float_linear' + shimLookup.OES_texture_half_float_linear = {shim:true} + else + unshimExtensions.push 'OES_texture_half_float_linear' + else + if checkFloatLinear gl, halfFloatExt.HALF_FLOAT_OES + shimExtensions.push 'OES_texture_half_float_linear' + else + unshimExtensions.push 'OES_texture_half_float_linear' + + if window.WebGLRenderingContext? + checkSupport() + + unshimLookup = {} + for name in unshimExtensions + unshimLookup[name] = true + + getExtension = WebGLRenderingContext.prototype.getExtension + WebGLRenderingContext.prototype.getExtension = (name) -> + extobj = shimLookup[name] + if extobj == undefined + if unshimLookup[name] + return null + else + return getExtension.call @, name + else + return extobj + + getSupportedExtensions = WebGLRenderingContext.prototype.getSupportedExtensions + WebGLRenderingContext.prototype.getSupportedExtensions = -> + supported = getSupportedExtensions.call(@) + result = [] + + for extension in supported + if unshimLookup[extension] == undefined + result.push(extension) + + for extension in shimExtensions + if extension not in result + result.push extension + + return result + + WebGLRenderingContext.prototype.getFloatExtension = (spec) -> + spec.prefer ?= ['half'] + spec.require ?= [] + spec.throws ?= true + + singleTexture = @getExtension 'OES_texture_float' + halfTexture = @getExtension 'OES_texture_half_float' + singleFramebuffer = @getExtension 'WEBGL_color_buffer_float' + halfFramebuffer = @getExtension 'EXT_color_buffer_half_float' + singleLinear = @getExtension 'OES_texture_float_linear' + halfLinear = @getExtension 'OES_texture_half_float_linear' + + single = { + texture: singleTexture != null + filterable: singleLinear != null + renderable: singleFramebuffer != null + score: 0 + precision: 'single' + half: false + single: true + type: @FLOAT + } + + half = { + texture: halfTexture != null + filterable: halfLinear != null + renderable: halfFramebuffer != null + score: 0 + precision: 'half' + half: true + single: false + type: halfTexture?.HALF_FLOAT_OES ? null + } + + candidates = [] + if single.texture + candidates.push(single) + if half.texture + candidates.push(half) + + result = [] + for candidate in candidates + use = true + for name in spec.require + if candidate[name] == false + use = false + if use + result.push candidate + + for candidate in result + for preference, i in spec.prefer + importance = Math.pow 2, spec.prefer.length - i - 1 + if candidate[preference] + candidate.score += importance + + result.sort (a, b) -> + if a.score == b.score then 0 + else if a.score < b.score then 1 + else if a.score > b.score then -1 + + if result.length == 0 + if throws + throw 'No floating point texture support that is ' + spec.require.join(', ') + else + return null + else + result = result[0] + return { + filterable: result.filterable + renderable: result.renderable + type: result.type + precision: result.precision + } + +nukeVendorPrefix() +textureFloatShims() + class Shader constructor: (@gl, {vertex, fragment}) -> @program = @gl.createProgram() @@ -106,8 +562,12 @@ class Framebuffer class Texture constructor: (@gl, params={}) -> - @channels = @gl[(params.channels ? 'rgb').toUpperCase()] - @type = @gl[(params.type ? 'unsigned_byte').toUpperCase()] + @channels = @gl[(params.channels ? 'rgba').toUpperCase()] + + if typeof(params.type) == 'number' + @type = params.type + else + @type = @gl[(params.type ? 'unsigned_byte').toUpperCase()] switch @channels when @gl.RGBA then @chancount = 4 @@ -163,11 +623,9 @@ class Texture class Node constructor: (@gl, @width, @height) -> - @texture = new Texture(@gl, type:'float').bind(0).setSize(@width, @height).nearest().clampToEdge() - try - @fbo = new Framebuffer(@gl).bind().color(@texture).unbind() - catch error - throw 'Floating point render target not supported' + floatExt = @gl.getFloatExtension require: ['renderable'] + @texture = new Texture(@gl, type:floatExt.type).bind(0).setSize(@width, @height).nearest().clampToEdge() + @fbo = new Framebuffer(@gl).bind().color(@texture).unbind() use: -> @fbo.bind() bind: (unit) -> @texture.bind(unit) @@ -395,9 +853,6 @@ class WebGLHeatmap @gl = WebGLDebugUtils.makeDebugContext @gl, (err, funcName, args) -> throw WebGLDebugUtils.glEnumToString(err) + " was caused by call to: " + funcName - if not @gl.getExtension('OES_texture_float') - throw 'No floating point texture support' - @gl.enableVertexAttribArray 0 @gl.blendFunc @gl.ONE, @gl.ONE diff --git a/webgl-heatmap.js b/webgl-heatmap.js index 8b3fab7..dcb4a74 100644 --- a/webgl-heatmap.js +++ b/webgl-heatmap.js @@ -1,6 +1,445 @@ // Generated by CoffeeScript 1.3.3 (function() { - var Framebuffer, Heights, Node, Shader, Texture, WebGLHeatmap, fragmentShaderBlit, vertexShaderBlit; + var Framebuffer, Heights, Node, Shader, Texture, WebGLHeatmap, fragmentShaderBlit, nukeVendorPrefix, textureFloatShims, vertexShaderBlit, + __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + nukeVendorPrefix = function() { + var getExtension, getSupportedExtensions, vendorRe, vendors; + if (window.WebGLRenderingContext != null) { + vendors = ['WEBKIT', 'MOZ', 'MS', 'O']; + vendorRe = /^WEBKIT_(.*)|MOZ_(.*)|MS_(.*)|O_(.*)/; + getExtension = WebGLRenderingContext.prototype.getExtension; + WebGLRenderingContext.prototype.getExtension = function(name) { + var extobj, match, vendor, _i, _len; + match = name.match(vendorRe); + if (match !== null) { + name = match[1]; + } + extobj = getExtension.call(this, name); + if (extobj === null) { + for (_i = 0, _len = vendors.length; _i < _len; _i++) { + vendor = vendors[_i]; + extobj = getExtension.call(this, vendor + '_' + name); + if (extobj !== null) { + return extobj; + } + } + return null; + } else { + return extobj; + } + }; + getSupportedExtensions = WebGLRenderingContext.prototype.getSupportedExtensions; + return WebGLRenderingContext.prototype.getSupportedExtensions = function() { + var extension, match, result, supported, _i, _len; + supported = getSupportedExtensions.call(this); + result = []; + for (_i = 0, _len = supported.length; _i < _len; _i++) { + extension = supported[_i]; + match = extension.match(vendorRe); + if (match !== null) { + extension = match[1]; + } + if (__indexOf.call(result, extension) < 0) { + result.push(extension); + } + } + return result; + }; + } + }; + + textureFloatShims = function() { + var checkColorBuffer, checkFloatLinear, checkSupport, checkTexture, createSourceCanvas, getExtension, getSupportedExtensions, name, shimExtensions, shimLookup, unshimExtensions, unshimLookup, _i, _len; + createSourceCanvas = function() { + var canvas, ctx, imageData; + canvas = document.createElement('canvas'); + canvas.width = 2; + canvas.height = 2; + ctx = canvas.getContext('2d'); + imageData = ctx.getImageData(0, 0, 2, 2); + imageData.data.set(new Uint8ClampedArray([0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 255, 255, 255, 255])); + ctx.putImageData(imageData, 0, 0); + return canvas; + }; + createSourceCanvas(); + checkFloatLinear = function(gl, sourceType) { + var buffer, cleanup, fragmentShader, framebuffer, positionLoc, program, readBuffer, result, source, sourceCanvas, sourceLoc, target, vertexShader, vertices; + program = gl.createProgram(); + vertexShader = gl.createShader(gl.VERTEX_SHADER); + gl.attachShader(program, vertexShader); + gl.shaderSource(vertexShader, 'attribute vec2 position;\nvoid main(){\n gl_Position = vec4(position, 0.0, 1.0);\n}'); + gl.compileShader(vertexShader); + if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) { + throw gl.getShaderInfoLog(vertexShader); + } + fragmentShader = gl.createShader(gl.FRAGMENT_SHADER); + gl.attachShader(program, fragmentShader); + gl.shaderSource(fragmentShader, 'uniform sampler2D source;\nvoid main(){\n gl_FragColor = texture2D(source, vec2(1.0, 1.0));\n}'); + gl.compileShader(fragmentShader); + if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) { + throw gl.getShaderInfoLog(fragmentShader); + } + gl.linkProgram(program); + if (!gl.getProgramParameter(program, gl.LINK_STATUS)) { + throw gl.getProgramInfoLog(program); + } + gl.useProgram(program); + cleanup = function() { + gl.deleteShader(fragmentShader); + gl.deleteShader(vertexShader); + gl.deleteProgram(program); + gl.deleteBuffer(buffer); + gl.deleteTexture(source); + gl.deleteTexture(target); + gl.deleteFramebuffer(framebuffer); + gl.bindBuffer(gl.ARRAY_BUFFER, null); + gl.useProgram(null); + gl.bindTexture(gl.TEXTURE_2D, null); + return gl.bindFramebuffer(gl.FRAMEBUFFER, null); + }; + target = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, target); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, gl.UNSIGNED_BYTE, null); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, target, 0); + sourceCanvas = createSourceCanvas(); + source = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, source); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, sourceType, sourceCanvas); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + vertices = new Float32Array([1, 1, -1, 1, -1, -1, 1, 1, -1, -1, 1, -1]); + buffer = gl.createBuffer(); + gl.bindBuffer(gl.ARRAY_BUFFER, buffer); + gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW); + positionLoc = gl.getAttribLocation(program, 'position'); + sourceLoc = gl.getUniformLocation(program, 'source'); + gl.enableVertexAttribArray(positionLoc); + gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0); + gl.uniform1i(sourceLoc, 0); + gl.drawArrays(gl.TRIANGLES, 0, 6); + readBuffer = new Uint8Array(4 * 4); + gl.readPixels(0, 0, 2, 2, gl.RGBA, gl.UNSIGNED_BYTE, readBuffer); + result = Math.abs(readBuffer[0] - 127) < 10; + cleanup(); + return result; + }; + checkTexture = function(gl, targetType) { + var target; + target = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, target); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, targetType, null); + if (gl.getError() === 0) { + gl.deleteTexture(target); + return true; + } else { + gl.deleteTexture(target); + return false; + } + }; + checkColorBuffer = function(gl, targetType) { + var check, framebuffer, target; + target = gl.createTexture(); + gl.bindTexture(gl.TEXTURE_2D, target); + gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 2, 2, 0, gl.RGBA, targetType, null); + framebuffer = gl.createFramebuffer(); + gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer); + gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, target, 0); + check = gl.checkFramebufferStatus(gl.FRAMEBUFFER); + gl.deleteTexture(target); + gl.deleteFramebuffer(framebuffer); + gl.bindTexture(gl.TEXTURE_2D, null); + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + if (check === gl.FRAMEBUFFER_COMPLETE) { + return true; + } else { + return false; + } + }; + shimExtensions = []; + shimLookup = {}; + unshimExtensions = []; + checkSupport = function() { + var canvas, extobj, gl, halfFloatExt, halfFloatTexturing, singleFloatExt, singleFloatTexturing; + canvas = document.createElement('canvas'); + gl = null; + try { + gl = canvas.getContext('experimental-webgl'); + if (gl === null) { + gl = canvas.getContext('webgl'); + } + } catch (_error) {} + if (gl != null) { + singleFloatExt = gl.getExtension('OES_texture_float'); + if (singleFloatExt === null) { + if (checkTexture(gl, gl.FLOAT)) { + singleFloatTexturing = true; + shimExtensions.push('OES_texture_float'); + shimLookup.OES_texture_float = { + shim: true + }; + } else { + singleFloatTexturing = false; + unshimExtensions.push('OES_texture_float'); + } + } else { + if (checkTexture(gl, gl.FLOAT)) { + singleFloatTexturing = true; + shimExtensions.push('OES_texture_float'); + } else { + singleFloatTexturing = false; + unshimExtensions.push('OES_texture_float'); + } + } + if (singleFloatTexturing) { + extobj = gl.getExtension('WEBGL_color_buffer_float'); + if (extobj === null) { + if (checkColorBuffer(gl, gl.FLOAT)) { + shimExtensions.push('WEBGL_color_buffer_float'); + shimLookup.WEBGL_color_buffer_float = { + shim: true, + RGBA32F_EXT: 0x8814, + RGB32F_EXT: 0x8815, + FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT: 0x8211, + UNSIGNED_NORMALIZED_EXT: 0x8C17 + }; + } else { + unshimExtensions.push('WEBGL_color_buffer_float'); + } + } else { + if (checkColorBuffer(gl, gl.FLOAT)) { + shimExtensions.push('WEBGL_color_buffer_float'); + } else { + unshimExtensions.push('WEBGL_color_buffer_float'); + } + } + extobj = gl.getExtension('OES_texture_float_linear'); + if (extobj === null) { + if (checkFloatLinear(gl, gl.FLOAT)) { + shimExtensions.push('OES_texture_float_linear'); + shimLookup.OES_texture_float_linear = { + shim: true + }; + } else { + unshimExtensions.push('OES_texture_float_linear'); + } + } else { + if (checkFloatLinear(gl, gl.FLOAT)) { + shimExtensions.push('OES_texture_float_linear'); + } else { + unshimExtensions.push('OES_texture_float_linear'); + } + } + } + halfFloatExt = gl.getExtension('OES_texture_half_float'); + if (halfFloatExt === null) { + if (checkTexture(gl, 0x8D61)) { + halfFloatTexturing = true; + shimExtensions.push('OES_texture_half_float'); + halfFloatExt = shimLookup.OES_texture_half_float = { + HALF_FLOAT_OES: 0x8D61, + shim: true + }; + } else { + halfFloatTexturing = false; + unshimExtensions.push('OES_texture_half_float'); + } + } else { + if (checkTexture(gl, halfFloatExt.HALF_FLOAT_OES)) { + halfFloatTexturing = true; + shimExtensions.push('OES_texture_half_float'); + } else { + halfFloatTexturing = false; + unshimExtensions.push('OES_texture_half_float'); + } + } + if (halfFloatTexturing) { + extobj = gl.getExtension('EXT_color_buffer_half_float'); + if (extobj === null) { + if (checkColorBuffer(gl, halfFloatExt.HALF_FLOAT_OES)) { + shimExtensions.push('EXT_color_buffer_half_float'); + shimLookup.EXT_color_buffer_half_float = { + shim: true, + RGBA16F_EXT: 0x881A, + RGB16F_EXT: 0x881B, + FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE_EXT: 0x8211, + UNSIGNED_NORMALIZED_EXT: 0x8C17 + }; + } else { + unshimExtensions.push('EXT_color_buffer_half_float'); + } + } else { + if (checkColorBuffer(gl, halfFloatExt.HALF_FLOAT_OES)) { + shimExtensions.push('EXT_color_buffer_half_float'); + } else { + unshimExtensions.push('EXT_color_buffer_half_float'); + } + } + extobj = gl.getExtension('OES_texture_half_float_linear'); + if (extobj === null) { + if (checkFloatLinear(gl, halfFloatExt.HALF_FLOAT_OES)) { + shimExtensions.push('OES_texture_half_float_linear'); + return shimLookup.OES_texture_half_float_linear = { + shim: true + }; + } else { + return unshimExtensions.push('OES_texture_half_float_linear'); + } + } else { + if (checkFloatLinear(gl, halfFloatExt.HALF_FLOAT_OES)) { + return shimExtensions.push('OES_texture_half_float_linear'); + } else { + return unshimExtensions.push('OES_texture_half_float_linear'); + } + } + } + } + }; + if (window.WebGLRenderingContext != null) { + checkSupport(); + unshimLookup = {}; + for (_i = 0, _len = unshimExtensions.length; _i < _len; _i++) { + name = unshimExtensions[_i]; + unshimLookup[name] = true; + } + getExtension = WebGLRenderingContext.prototype.getExtension; + WebGLRenderingContext.prototype.getExtension = function(name) { + var extobj; + extobj = shimLookup[name]; + if (extobj === void 0) { + if (unshimLookup[name]) { + return null; + } else { + return getExtension.call(this, name); + } + } else { + return extobj; + } + }; + getSupportedExtensions = WebGLRenderingContext.prototype.getSupportedExtensions; + WebGLRenderingContext.prototype.getSupportedExtensions = function() { + var extension, result, supported, _j, _k, _len1, _len2; + supported = getSupportedExtensions.call(this); + result = []; + for (_j = 0, _len1 = supported.length; _j < _len1; _j++) { + extension = supported[_j]; + if (unshimLookup[extension] === void 0) { + result.push(extension); + } + } + for (_k = 0, _len2 = shimExtensions.length; _k < _len2; _k++) { + extension = shimExtensions[_k]; + if (__indexOf.call(result, extension) < 0) { + result.push(extension); + } + } + return result; + }; + return WebGLRenderingContext.prototype.getFloatExtension = function(spec) { + var candidate, candidates, half, halfFramebuffer, halfLinear, halfTexture, i, importance, preference, result, single, singleFramebuffer, singleLinear, singleTexture, use, _j, _k, _l, _len1, _len2, _len3, _len4, _m, _ref, _ref1, _ref2, _ref3, _ref4, _ref5; + if ((_ref = spec.prefer) == null) { + spec.prefer = ['half']; + } + if ((_ref1 = spec.require) == null) { + spec.require = []; + } + if ((_ref2 = spec.throws) == null) { + spec.throws = true; + } + singleTexture = this.getExtension('OES_texture_float'); + halfTexture = this.getExtension('OES_texture_half_float'); + singleFramebuffer = this.getExtension('WEBGL_color_buffer_float'); + halfFramebuffer = this.getExtension('EXT_color_buffer_half_float'); + singleLinear = this.getExtension('OES_texture_float_linear'); + halfLinear = this.getExtension('OES_texture_half_float_linear'); + single = { + texture: singleTexture !== null, + filterable: singleLinear !== null, + renderable: singleFramebuffer !== null, + score: 0, + precision: 'single', + half: false, + single: true, + type: this.FLOAT + }; + half = { + texture: halfTexture !== null, + filterable: halfLinear !== null, + renderable: halfFramebuffer !== null, + score: 0, + precision: 'half', + half: true, + single: false, + type: (_ref3 = halfTexture != null ? halfTexture.HALF_FLOAT_OES : void 0) != null ? _ref3 : null + }; + candidates = []; + if (single.texture) { + candidates.push(single); + } + if (half.texture) { + candidates.push(half); + } + result = []; + for (_j = 0, _len1 = candidates.length; _j < _len1; _j++) { + candidate = candidates[_j]; + use = true; + _ref4 = spec.require; + for (_k = 0, _len2 = _ref4.length; _k < _len2; _k++) { + name = _ref4[_k]; + if (candidate[name] === false) { + use = false; + } + } + if (use) { + result.push(candidate); + } + } + for (_l = 0, _len3 = result.length; _l < _len3; _l++) { + candidate = result[_l]; + _ref5 = spec.prefer; + for (i = _m = 0, _len4 = _ref5.length; _m < _len4; i = ++_m) { + preference = _ref5[i]; + importance = Math.pow(2, spec.prefer.length - i - 1); + if (candidate[preference]) { + candidate.score += importance; + } + } + } + result.sort(function(a, b) { + if (a.score === b.score) { + return 0; + } else if (a.score < b.score) { + return 1; + } else if (a.score > b.score) { + return -1; + } + }); + if (result.length === 0) { + if (throws) { + throw 'No floating point texture support that is ' + spec.require.join(', '); + } else { + return null; + } + } else { + result = result[0]; + return { + filterable: result.filterable, + renderable: result.renderable, + type: result.type, + precision: result.precision + }; + } + }; + } + }; + + nukeVendorPrefix(); + + textureFloatShims(); Shader = (function() { @@ -166,8 +605,12 @@ if (params == null) { params = {}; } - this.channels = this.gl[((_ref = params.channels) != null ? _ref : 'rgb').toUpperCase()]; - this.type = this.gl[((_ref1 = params.type) != null ? _ref1 : 'unsigned_byte').toUpperCase()]; + this.channels = this.gl[((_ref = params.channels) != null ? _ref : 'rgba').toUpperCase()]; + if (typeof params.type === 'number') { + this.type = params.type; + } else { + this.type = this.gl[((_ref1 = params.type) != null ? _ref1 : 'unsigned_byte').toUpperCase()]; + } switch (this.channels) { case this.gl.RGBA: this.chancount = 4; @@ -246,17 +689,17 @@ Node = (function() { function Node(gl, width, height) { + var floatExt; this.gl = gl; this.width = width; this.height = height; + floatExt = this.gl.getFloatExtension({ + require: ['renderable'] + }); this.texture = new Texture(this.gl, { - type: 'float' + type: floatExt.type }).bind(0).setSize(this.width, this.height).nearest().clampToEdge(); - try { - this.fbo = new Framebuffer(this.gl).bind().color(this.texture).unbind(); - } catch (error) { - throw 'Floating point render target not supported'; - } + this.fbo = new Framebuffer(this.gl).bind().color(this.texture).unbind(); } Node.prototype.use = function() { @@ -467,9 +910,6 @@ throw WebGLDebugUtils.glEnumToString(err) + " was caused by call to: " + funcName; }); } - if (!this.gl.getExtension('OES_texture_float')) { - throw 'No floating point texture support'; - } this.gl.enableVertexAttribArray(0); this.gl.blendFunc(this.gl.ONE, this.gl.ONE); if (gradientTexture) {