diff --git a/README.md b/README.md index 06538bc..c28cefb 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,6 @@ WebGL-2D is a proof of concept and attempts to ascertain performance improvement It should allow _most_ Canvas2D applications to be switched to a WebGL context. -Check out a [LIVE DEMO!](http://weare.buildingsky.net/webgl-2d/example.html) - ![20110304-qfkhgf9hjin5uk3we9rmgpwtf8.jpg](https://img.skitch.com/20110304-qfkhgf9hjin5uk3we9rmgpwtf8.jpg) diff --git a/bower.json b/bower.json new file mode 100644 index 0000000..cd6c077 --- /dev/null +++ b/bower.json @@ -0,0 +1,24 @@ +{ + "name": "webgl-2d", + "main": "webgl-2d.js", + "homepage": "https://github.com/lekzd/webgl-2d", + "authors": [ + "gameclosure" + ], + "description": "Use WebGl features as HTML5 Canvas api", + "moduleType": [], + "keywords": [ + "WebGl" + ], + "license": "MIT", + "ignore": [ + "**/.*", + "benchmarks", + "examples", + "support", + "node_modules", + "bower_components", + "test", + "tests" + ] +} diff --git a/examples/animation.html b/examples/animation.html new file mode 100644 index 0000000..306b0bd --- /dev/null +++ b/examples/animation.html @@ -0,0 +1,59 @@ +

Canvas2D

+ +

Webgl-2d

+ + + diff --git a/examples/animationTransparency.html b/examples/animationTransparency.html new file mode 100644 index 0000000..1e434c1 --- /dev/null +++ b/examples/animationTransparency.html @@ -0,0 +1,61 @@ +

Canvas2D

+ +

Webgl-2d

+ + + diff --git a/examples/createPattern.html b/examples/createPattern.html new file mode 100644 index 0000000..6683741 --- /dev/null +++ b/examples/createPattern.html @@ -0,0 +1,63 @@ + + + + WebGL-2D Example + + + + + + + + + + +

Example: WebGL-2D Comparison

+

drawImage

+
+

WebGL-2D

+ +
+
+

Canvas2D

+ +
+ + diff --git a/examples/hslPalette.html b/examples/hslPalette.html new file mode 100644 index 0000000..0db45b9 --- /dev/null +++ b/examples/hslPalette.html @@ -0,0 +1,35 @@ +

Canvas2D

+ +

Webgl-2d

+ + + diff --git a/examples/hslaPalette.html b/examples/hslaPalette.html new file mode 100644 index 0000000..6e15f95 --- /dev/null +++ b/examples/hslaPalette.html @@ -0,0 +1,43 @@ +

Canvas2D

+ +

Webgl-2d

+ + + diff --git a/examples/rgbaPalette.html b/examples/rgbaPalette.html new file mode 100644 index 0000000..0a2eb1d --- /dev/null +++ b/examples/rgbaPalette.html @@ -0,0 +1,35 @@ +

Canvas2D

+ +

Webgl-2d

+ + + diff --git a/webgl-2d.js b/webgl-2d.js index 3979214..320d3c5 100644 --- a/webgl-2d.js +++ b/webgl-2d.js @@ -38,7 +38,9 @@ * * WebGL2D.enable(cvs); // adds "webgl-2d" to cvs * - * cvs.getContext("webgl-2d"); + * var ctx = cvs.getContext("webgl-2d"); // Attempt to get webgl context + * + * ctx.isWebGL // Detect whether WebGL-2D is active on this context. * */ @@ -280,28 +282,28 @@ var gl = gl2d.gl = gl2d.canvas.$getContext("experimental-webgl"); + // If we failed to get a WebGL context, return a normal 2D context instead. + if ((typeof (gl) === "undefined") || (gl === null)) { + return gl2d.canvas.$getContext("2d"); + } + gl2d.initShaders(); gl2d.initBuffers(); // Append Canvas2D API features to the WebGL context gl2d.initCanvas2DAPI(); - gl.viewport(0, 0, gl2d.canvas.width, gl2d.canvas.height); - - // Default white background - gl.clearColor(1, 1, 1, 1); - gl.clear(gl.COLOR_BUFFER_BIT); // | gl.DEPTH_BUFFER_BIT); - // Disables writing to dest-alpha - gl.colorMask(1,1,1,0); + // gl.colorMask(1, 1, 1, 0); // Depth options - //gl.enable(gl.DEPTH_TEST); - //gl.depthFunc(gl.LEQUAL); + // gl.enable(gl.DEPTH_TEST); + // gl.depthFunc(gl.LEQUAL); // Blending options + // See http://stackoverflow.com/questions/11521035/blending-with-html-background-in-webgl gl.enable(gl.BLEND); - gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA); gl2d.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE); @@ -336,8 +338,8 @@ "precision highp float;", "#endif", - "#define hasTexture " + ((sMask&shaderMask.texture) ? "1" : "0"), - "#define hasCrop " + ((sMask&shaderMask.crop) ? "1" : "0"), + "#define hasTexture " + ((sMask == shaderMask.texture) ? "1" : "0"), + "#define hasCrop " + ((sMask == shaderMask.crop) ? "1" : "0"), "varying vec4 vColor;", @@ -365,13 +367,11 @@ return fsSource; }; - WebGL2D.prototype.getVertexShaderSource = function getVertexShaderSource(stackDepth,sMask) { - var w = 2 / this.canvas.width, h = -2 / this.canvas.height; - + WebGL2D.prototype.getVertexShaderSource = function getVertexShaderSource(stackDepth, sMask) { stackDepth = stackDepth || 1; var vsSource = [ - "#define hasTexture " + ((sMask&shaderMask.texture) ? "1" : "0"), + "#define hasTexture " + ((sMask == shaderMask.texture) ? "1" : "0"), "attribute vec4 aVertexPosition;", "#if hasTexture", @@ -383,7 +383,7 @@ "varying vec4 vColor;", - "const mat4 pMatrix = mat4(" + w + ",0,0,0, 0," + h + ",0,0, 0,0,1.0,1.0, -1.0,1.0,0,0);", + "uniform mat4 pMatrix;", "mat3 crunchStack(void) {", "mat3 result = uTransforms[0];", @@ -407,7 +407,7 @@ // Initialize fragment and vertex shaders - WebGL2D.prototype.initShaders = function initShaders(transformStackDepth,sMask) { + WebGL2D.prototype.initShaders = function initShaders(transformStackDepth, sMask) { var gl = this.gl; transformStackDepth = transformStackDepth || 1; @@ -420,25 +420,23 @@ if (storedShader) { gl.useProgram(storedShader); this.shaderProgram = storedShader; - return storedShader; } else { var fs = this.fs = gl.createShader(gl.FRAGMENT_SHADER); gl.shaderSource(this.fs, this.getFragmentShaderSource(sMask)); gl.compileShader(this.fs); if (!gl.getShaderParameter(this.fs, gl.COMPILE_STATUS)) { - throw "fragment shader error: "+gl.getShaderInfoLog(this.fs); + throw "fragment shader error: " + gl.getShaderInfoLog(this.fs); } var vs = this.vs = gl.createShader(gl.VERTEX_SHADER); - gl.shaderSource(this.vs, this.getVertexShaderSource(transformStackDepth,sMask)); + gl.shaderSource(this.vs, this.getVertexShaderSource(transformStackDepth, sMask)); gl.compileShader(this.vs); if (!gl.getShaderParameter(this.vs, gl.COMPILE_STATUS)) { - throw "vertex shader error: "+gl.getShaderInfoLog(this.vs); + throw "vertex shader error: " + gl.getShaderInfoLog(this.vs); } - var shaderProgram = this.shaderProgram = gl.createProgram(); shaderProgram.stackDepth = transformStackDepth; gl.attachShader(shaderProgram, fs); @@ -458,20 +456,24 @@ shaderProgram.uSampler = gl.getUniformLocation(shaderProgram, 'uSampler'); shaderProgram.uCropSource = gl.getUniformLocation(shaderProgram, 'uCropSource'); + shaderProgram.pMatrix = gl.getUniformLocation(shaderProgram, 'pMatrix'); + shaderProgram.uTransforms = []; for (var i=0; i 100 ? 1 : l / 100; l = l < 0 ? 0 : l; - m2 = l <= 0.5 ? l * (s + 1) : l + s - l * s; - m1 = l * 2 - m2; + if(s == 0) { + r = g = b = l; // achromatic + } else { + function hue2rgb(p, q, t){ + if(t < 0) t += 1; + if(t > 1) t -= 1; + if(t < 1/6) return p + (q - p) * 6 * t; + if(t < 1/2) return q; + if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; + return p; + } - function getHue(value) { - var hue; + q = l < 0.5 ? l * (1 + s) : l + s - l * s; + p = 2 * l - q; - if (value * 6 < 1) { - hue = m1 + (m2 - m1) * value * 6; - } else if (value * 2 < 1) { - hue = m2; - } else if (value * 3 < 2) { - hue = m1 + (m2 - m1) * (2/3 - value) * 6; - } else { - hue = m1; - } - - return hue; + r = hue2rgb(p, q, h + 1/3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1/3); } - r = getHue(h + 1/3); - g = getHue(h); - b = getHue(h - 1/3); - return [r, g, b, a]; } @@ -806,7 +812,11 @@ Object.defineProperty(gl, "fillStyle", { get: function() { return colorVecToString(drawState.fillStyle); }, set: function(value) { - drawState.fillStyle = colorStringToVec4(value) || drawState.fillStyle; + if (value instanceof GlPattern) { + drawState.fillStyle = value; + } else { + drawState.fillStyle = colorStringToVec4(value) || drawState.fillStyle; + } } }); @@ -1035,31 +1045,104 @@ }; gl.fillRect = function fillRect(x, y, width, height) { - var transform = gl2d.transform; - var shaderProgram = gl2d.initShaders(transform.c_stack+2,0); + if (drawState.fillStyle instanceof GlPattern) { + fillRectPattern.apply(this, arguments); + } else { + fillRectColor.apply(this, arguments); + } + }; - gl.bindBuffer(gl.ARRAY_BUFFER, rectVertexPositionBuffer); - gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 4, gl.FLOAT, false, 0, 0); + function fillRectColor(x, y, width, height) { + var transform = gl2d.transform; + var shaderProgram = gl2d.initShaders(transform.c_stack + 2, 0); transform.pushMatrix(); - transform.translate(x, y); + + gl.bindBuffer(gl.ARRAY_BUFFER, gl.rectVertexPositionBuffer); + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 4, gl.FLOAT, false, 0, 0); + + var r = drawState.fillStyle[0], + g = drawState.fillStyle[1], + b = drawState.fillStyle[2], + a = drawState.fillStyle[3]; transform.scale(width, height); + gl.uniform4f(shaderProgram.uColor, r, g, b, a); sendTransformStack(shaderProgram); + gl.drawArrays(gl.TRIANGLE_FAN, 0, 4); + + transform.popMatrix(); + }; + + function getRectVertices(x, y) { + x2 = x + 1; + y2 = y + 1; + return [ + x,y, x,y, + x,y2, x,y2, + x2,y2, x2,y2, + x2,y, x2,y + ]; + }; - gl.uniform4f(shaderProgram.uColor, drawState.fillStyle[0], drawState.fillStyle[1], drawState.fillStyle[2], drawState.fillStyle[3]); + function fillRectPattern(x, y, width, height) { + var transform = gl2d.transform; + var shaderProgram = gl2d.initShaders(transform.c_stack, shaderMask.texture); + var textureWidth = drawState.fillStyle.width; + var textureHeight = drawState.fillStyle.height; - gl.drawArrays(gl.TRIANGLE_FAN, 0, 4); + transform.pushMatrix(); + + transform.translate(0, 0); + + var horisontalRepeats = 1; + if (~['repeat', 'repeat-x'].indexOf(drawState.fillStyle.direction)) { + horisontalRepeats = Math.ceil((x + width) / textureWidth); + } + var verticalRepeats = 1; + if (~['repeat', 'repeat-y'].indexOf(drawState.fillStyle.direction)) { + verticalRepeats = Math.ceil((y + height) / textureHeight); + } + var verts = []; + for (vX = 0; vX < horisontalRepeats; vX++) { + for (vY = 0; vY < verticalRepeats; vY++) { + verts = verts.concat(getRectVertices(vX, vY)); + } + } + + gl.bindBuffer(gl.ARRAY_BUFFER, gl.pathVertexPositionBuffer); + gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW); + + gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 4, gl.FLOAT, false, 0, 0); + + var texture = drawState.fillStyle.texture; + + transform.scale(textureWidth, textureHeight); + gl.bindTexture(gl.TEXTURE_2D, texture.obj); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT); + gl.activeTexture(gl.TEXTURE0); + + gl.uniform1i(shaderProgram.uSampler, 0); + + gl.enable(gl.SCISSOR_TEST); + var verticalOffset = (height - (verticalRepeats * textureHeight)) / 2; + gl.scissor(x, gl2d.canvas.height - height - y, width, height); + + sendTransformStack(shaderProgram); + gl.drawArrays(gl.TRIANGLE_FAN, 0, verts.length / 4); + + gl.disable(gl.SCISSOR_TEST); transform.popMatrix(); }; gl.strokeRect = function strokeRect(x, y, width, height) { var transform = gl2d.transform; - var shaderProgram = gl2d.initShaders(transform.c_stack + 2,0); + var shaderProgram = gl2d.initShaders(transform.c_stack + 2, 0); - gl.bindBuffer(gl.ARRAY_BUFFER, rectVertexPositionBuffer); + gl.bindBuffer(gl.ARRAY_BUFFER, gl.rectVertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 4, gl.FLOAT, false, 0, 0); transform.pushMatrix(); @@ -1141,7 +1224,7 @@ var subPath = subPaths[index]; var verts = subPath.verts; - gl.bindBuffer(gl.ARRAY_BUFFER, pathVertexPositionBuffer); + gl.bindBuffer(gl.ARRAY_BUFFER, gl.pathVertexPositionBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 4, gl.FLOAT, false, 0, 0); @@ -1150,9 +1233,12 @@ sendTransformStack(shaderProgram); - gl.uniform4f(shaderProgram.uColor, drawState.fillStyle[0], drawState.fillStyle[1], drawState.fillStyle[2], drawState.fillStyle[3]); - - gl.drawArrays(gl.TRIANGLE_FAN, 0, verts.length/4); + var r = drawState.fillStyle[0], + g = drawState.fillStyle[1], + b = drawState.fillStyle[2], + a = drawState.fillStyle[3]; + gl.uniform4f(shaderProgram.uColor, r, g, b, a); + gl.drawArrays(gl.TRIANGLE_FAN, 0, verts.length / 4); transform.popMatrix(); } @@ -1170,7 +1256,7 @@ var subPath = subPaths[index]; var verts = subPath.verts; - gl.bindBuffer(gl.ARRAY_BUFFER, pathVertexPositionBuffer); + gl.bindBuffer(gl.ARRAY_BUFFER, gl.pathVertexPositionBuffer); gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 4, gl.FLOAT, false, 0, 0); @@ -1245,6 +1331,17 @@ gl.bindTexture(gl.TEXTURE_2D, null); } + var getTextureFromImage = function(image) { + var texture, cacheIndex = imageCache.indexOf(image); + + if (cacheIndex !== -1) { + texture = textureCache[cacheIndex]; + } else { + texture = new Texture(image); + } + return texture; + }; + gl.drawImage = function drawImage(image, a, b, c, d, e, f, g, h) { var transform = gl2d.transform; @@ -1274,25 +1371,18 @@ } var shaderProgram = gl2d.initShaders(transform.c_stack, sMask); - - var texture, cacheIndex = imageCache.indexOf(image); - - if (cacheIndex !== -1) { - texture = textureCache[cacheIndex]; - } else { - texture = new Texture(image); - } + var texture = getTextureFromImage(image); if (doCrop) { gl.uniform4f(shaderProgram.uCropSource, a/image.width, b/image.height, c/image.width, d/image.height); } - gl.bindBuffer(gl.ARRAY_BUFFER, rectVertexPositionBuffer); + gl.bindBuffer(gl.ARRAY_BUFFER, gl.rectVertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, 4, gl.FLOAT, false, 0, 0); gl.bindTexture(gl.TEXTURE_2D, texture.obj); gl.activeTexture(gl.TEXTURE0); - + gl.colorMask(1, 1, 1, 1); gl.uniform1i(shaderProgram.uSampler, 0); sendTransformStack(shaderProgram); @@ -1300,6 +1390,34 @@ transform.popMatrix(); }; + + var GlPattern = function(image, direction) { + var texture = getTextureFromImage(image); + var REPEAT_DIRECTIONS = [ + 'repeat', 'repeat-x', 'repeat-y', 'no-repeat' + ]; + direction = String(direction).toLowerCase(); + if (!~REPEAT_DIRECTIONS.indexOf(direction)) { + direction = REPEAT_DIRECTIONS[0]; + } + + this.direction = direction; + this.texture = texture; + this.width = image.width; + this.height = image.height; + }; + + gl.createPattern = function createPattern(image, direction) { + return new GlPattern(image, direction); + }; + + // This enables the user to detect whether they got a webgl-2d context or a 2d context. + Object.defineProperty(gl, "isWebGL", { + configurable: false, + enumerable: true, + writable: false, + value: true + }); }; }(Math));