diff --git a/src/geometries/path2d.js b/src/geometries/path2d.js index d9c86c2a13..fe4d2d561d 100644 --- a/src/geometries/path2d.js +++ b/src/geometries/path2d.js @@ -242,9 +242,6 @@ import earcut from "earcut"; points.push(pool.pull("Point", _x2, _y2)); angle += direction * dangle; } - //var x1 = radiusX * Math.cos(endAngle); - //var y1 = radiusY * Math.sin(endAngle); - //points.push(pool.pull("Point", x + x1 * cos_rotation - y1 * sin_rotation, y + x1 * sin_rotation + y1 * cos_rotation)); } /** @@ -257,8 +254,11 @@ import earcut from "earcut"; rect(x, y, width, height) { this.moveTo(x, y); this.lineTo(x + width, y); + this.moveTo(x + width, y); this.lineTo(x + width, y + height); + this.moveTo(x + width, y + height); this.lineTo(x, y + height); + this.moveTo(x, y + height); this.lineTo(x, y); } diff --git a/src/index.js b/src/index.js index 6a17f0208f..b4c69680aa 100644 --- a/src/index.js +++ b/src/index.js @@ -20,7 +20,9 @@ import Body from "./physics/body.js"; import Bounds from "./physics/bounds.js"; import Tween from "./tweens/tween.js"; import GLShader from "./video/webgl/glshader.js"; -import WebGLCompositor from "./video/webgl/compositors/webgl_compositor.js"; +import Compositor from "./video/webgl/compositors/compositor.js"; +import PrimitiveCompositor from "./video/webgl/compositors/primitive_compositor.js"; +import QuadCompositor from "./video/webgl/compositors/quad_compositor.js"; import Renderer from "./video/renderer.js"; import WebGLRenderer from "./video/webgl/webgl_renderer.js"; import CanvasRenderer from "./video/canvas/canvas_renderer.js"; @@ -127,7 +129,9 @@ export { Tween, QuadTree, GLShader, - WebGLCompositor, + Compositor, + PrimitiveCompositor, + QuadCompositor, Renderer, WebGLRenderer, CanvasRenderer, diff --git a/src/video/webgl/compositors/compositor.js b/src/video/webgl/compositors/compositor.js index a67a736179..87ddc752b9 100644 --- a/src/video/webgl/compositors/compositor.js +++ b/src/video/webgl/compositors/compositor.js @@ -97,6 +97,19 @@ import * as event from "../../../system/event.js"; this.clearColor(0.0, 0.0, 0.0, 0.0); } + /** + * @ignore + * called by the WebGL renderer when a compositor become the current one + */ + bind() { + if (this.activeShader !== null) { + this.activeShader.bind(); + this.activeShader.setUniform("uProjectionMatrix", this.renderer.projectionMatrix); + this.activeShader.setVertexAttributes(this.gl, this.attributes, this.vertexByteSize); + } + this.gl.bufferData(this.gl.ARRAY_BUFFER, this.vertexBuffer.buffer, this.gl.STREAM_DRAW); + } + /** * add vertex attribute property definition to the compositor * @param {string} name - name of the attribute in the vertex shader diff --git a/src/video/webgl/compositors/primitive_compositor.js b/src/video/webgl/compositors/primitive_compositor.js new file mode 100644 index 0000000000..829d88f190 --- /dev/null +++ b/src/video/webgl/compositors/primitive_compositor.js @@ -0,0 +1,86 @@ +import GLShader from "../glshader.js"; +import VertexArrayBuffer from "../buffer/vertex.js"; +import primitiveVertex from "./../shaders/primitive.vert"; +import primitiveFragment from "./../shaders/primitive.frag"; +import Compositor from "./compositor.js"; + +/** + * @classdesc + * A WebGL Compositor object. This class handles all of the WebGL state
+ * Pushes texture regions or shape geometry into WebGL buffers, automatically flushes to GPU + * @augments Compositor + */ + export default class PrimitiveCompositor extends Compositor { + + /** + * Initialize the compositor + * @ignore + */ + init (renderer) { + super.init(renderer); + + // Load and create shader programs + this.primitiveShader = new GLShader(this.gl, primitiveVertex, primitiveFragment); + + /// define all vertex attributes + this.addAttribute("aVertex", 2, this.gl.FLOAT, false, 0 * Float32Array.BYTES_PER_ELEMENT); // 0 + this.addAttribute("aColor", 4, this.gl.UNSIGNED_BYTE, true, 2 * Float32Array.BYTES_PER_ELEMENT); // 1 + + this.vertexBuffer = new VertexArrayBuffer(this.vertexSize, 6); + + // vertex buffer + this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.gl.createBuffer()); + this.gl.bufferData(this.gl.ARRAY_BUFFER, this.vertexBuffer.buffer, this.gl.STREAM_DRAW); + } + + /** + * Reset compositor internal state + * @ignore + */ + reset() { + super.reset(); + + // set the quad shader as the default program + this.useShader(this.primitiveShader); + } + + /** + * Draw an array of vertices + * @param {GLenum} mode - primitive type to render (gl.POINTS, gl.LINE_STRIP, gl.LINE_LOOP, gl.LINES, gl.TRIANGLE_STRIP, gl.TRIANGLE_FAN, gl.TRIANGLES) + * @param {Point[]} verts - an array of vertices + * @param {number} [vertexCount=verts.length] - amount of points defined in the points array + */ + drawVertices(mode, verts, vertexCount = verts.length) { + var viewMatrix = this.viewMatrix; + var vertexBuffer = this.vertexBuffer; + var color = this.renderer.currentColor; + var alpha = this.renderer.getGlobalAlpha(); + + if (vertexBuffer.isFull(vertexCount)) { + // is the vertex buffer full if we add more vertices + this.flush(); + } + + // flush if drawing vertices with a different drawing mode + if (mode !== this.mode) { + this.flush(this.mode); + this.mode = mode; + } + + if (!viewMatrix.isIdentity()) { + verts.forEach((vert) => { + viewMatrix.apply(vert); + vertexBuffer.push(vert.x, vert.y, undefined, undefined, color.toUint32(alpha)); + }); + } else { + verts.forEach((vert) => { + vertexBuffer.push(vert.x, vert.y, undefined, undefined, color.toUint32(alpha)); + }); + } + + // disable batching for primitive using LINE_STRIP or LINE_LOOP + if (this.mode === this.gl.LINE_STRIP || this.mode === this.gl.LINE_LOOP) { + this.flush(this.mode); + } + } +} diff --git a/src/video/webgl/compositors/webgl_compositor.js b/src/video/webgl/compositors/quad_compositor.js similarity index 78% rename from src/video/webgl/compositors/webgl_compositor.js rename to src/video/webgl/compositors/quad_compositor.js index d63c399324..f344ffe222 100644 --- a/src/video/webgl/compositors/webgl_compositor.js +++ b/src/video/webgl/compositors/quad_compositor.js @@ -2,8 +2,6 @@ import Vector2d from "../../../math/vector2.js"; import GLShader from "../glshader.js"; import VertexArrayBuffer from "../buffer/vertex.js"; import { isPowerOfTwo } from "../../../math/math.js"; -import primitiveVertex from "./../shaders/primitive.vert"; -import primitiveFragment from "./../shaders/primitive.frag"; import quadVertex from "./../shaders/quad.vert"; import quadFragment from "./../shaders/quad.frag"; import Compositor from "./compositor.js"; @@ -22,7 +20,7 @@ var V_ARRAY = [ * Pushes texture regions or shape geometry into WebGL buffers, automatically flushes to GPU * @augments Compositor */ - export default class WebGLCompositor extends Compositor { + export default class QuadCompositor extends Compositor { /** * Initialize the compositor @@ -36,7 +34,6 @@ var V_ARRAY = [ this.boundTextures = []; // Load and create shader programs - this.primitiveShader = new GLShader(this.gl, primitiveVertex, primitiveFragment); this.quadShader = new GLShader(this.gl, quadVertex, quadFragment); /// define all vertex attributes @@ -44,7 +41,7 @@ var V_ARRAY = [ this.addAttribute("aRegion", 2, this.gl.FLOAT, false, 2 * Float32Array.BYTES_PER_ELEMENT); // 1 this.addAttribute("aColor", 4, this.gl.UNSIGNED_BYTE, true, 4 * Float32Array.BYTES_PER_ELEMENT); // 2 - this.vertexBuffer = new VertexArrayBuffer(this.vertexSize, 6); // 6 vertices per quad + this.vertexBuffer = new VertexArrayBuffer(this.vertexSize, 6); // vertex buffer this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.gl.createBuffer()); @@ -201,22 +198,6 @@ var V_ARRAY = [ return this.currentTextureUnit; } - - /** - * Select the shader to use for compositing - * @see GLShader - * @param {GLShader} shader - a reference to a GLShader instance - */ - useShader(shader) { - if (this.activeShader !== shader) { - this.flush(); - this.activeShader = shader; - this.activeShader.bind(); - this.activeShader.setUniform("uProjectionMatrix", this.renderer.projectionMatrix); - this.activeShader.setVertexAttributes(this.gl, this.attributes, this.vertexByteSize); - } - } - /** * Add a textured quad * @param {TextureAtlas} texture - Source texture atlas @@ -231,15 +212,9 @@ var V_ARRAY = [ * @param {number} tint - tint color to be applied to the texture in UINT32 (argb) format */ addQuad(texture, x, y, w, h, u0, v0, u1, v1, tint) { + var vertexBuffer = this.vertexBuffer; - if (this.color.alpha < 1 / 255) { - // Fast path: don't send fully transparent quads - return; - } - - this.useShader(this.quadShader); - - if (this.vertexBuffer.isFull(6)) { + if (vertexBuffer.isFull(6)) { // is the vertex buffer full if we add 6 more vertices this.flush(); } @@ -263,38 +238,11 @@ var V_ARRAY = [ m.apply(vec3); } - this.vertexBuffer.push(vec0.x, vec0.y, u0, v0, tint); - this.vertexBuffer.push(vec1.x, vec1.y, u1, v0, tint); - this.vertexBuffer.push(vec2.x, vec2.y, u0, v1, tint); - this.vertexBuffer.push(vec2.x, vec2.y, u0, v1, tint); - this.vertexBuffer.push(vec1.x, vec1.y, u1, v0, tint); - this.vertexBuffer.push(vec3.x, vec3.y, u1, v1, tint); - } - - /** - * Draw an array of vertices - * @param {GLenum} mode - primitive type to render (gl.POINTS, gl.LINE_STRIP, gl.LINE_LOOP, gl.LINES, gl.TRIANGLE_STRIP, gl.TRIANGLE_FAN, gl.TRIANGLES) - * @param {Point[]} verts - an array of vertices - * @param {number} [vertexCount=verts.length] - amount of points defined in the points array - */ - drawVertices(mode, verts, vertexCount = verts.length) { - // use the primitive shader - this.useShader(this.primitiveShader); - // Set the line color - this.primitiveShader.setUniform("uColor", this.color); - - var m = this.viewMatrix; - var vertex = this.vertexBuffer; - var m_isIdentity = m.isIdentity(); - - for (var i = 0; i < vertexCount; i++) { - if (!m_isIdentity) { - m.apply(verts[i]); - } - vertex.push(verts[i].x, verts[i].y); - } - - // flush - this.flush(mode); + vertexBuffer.push(vec0.x, vec0.y, u0, v0, tint); + vertexBuffer.push(vec1.x, vec1.y, u1, v0, tint); + vertexBuffer.push(vec2.x, vec2.y, u0, v1, tint); + vertexBuffer.push(vec2.x, vec2.y, u0, v1, tint); + vertexBuffer.push(vec1.x, vec1.y, u1, v0, tint); + vertexBuffer.push(vec3.x, vec3.y, u1, v1, tint); } } diff --git a/src/video/webgl/shaders/primitive.vert b/src/video/webgl/shaders/primitive.vert index 72e21a5cbc..6e21cceb0b 100644 --- a/src/video/webgl/shaders/primitive.vert +++ b/src/video/webgl/shaders/primitive.vert @@ -1,18 +1,15 @@ // Current vertex point attribute vec2 aVertex; +attribute vec4 aColor; // Projection matrix uniform mat4 uProjectionMatrix; -// Vertex color -uniform vec4 uColor; - -// Fragment color varying vec4 vColor; void main(void) { // Transform the vertex position by the projection matrix gl_Position = uProjectionMatrix * vec4(aVertex, 0.0, 1.0); // Pass the remaining attributes to the fragment shader - vColor = vec4(uColor.rgb * uColor.a, uColor.a); + vColor = vec4(aColor.bgr * aColor.a, aColor.a); } diff --git a/src/video/webgl/shaders/quad.vert b/src/video/webgl/shaders/quad.vert index 3b6a751355..f6faf15d59 100644 --- a/src/video/webgl/shaders/quad.vert +++ b/src/video/webgl/shaders/quad.vert @@ -1,7 +1,9 @@ +// Current vertex point attribute vec2 aVertex; attribute vec2 aRegion; attribute vec4 aColor; +// Projection matrix uniform mat4 uProjectionMatrix; varying vec2 vRegion; @@ -9,7 +11,7 @@ varying vec4 vColor; void main(void) { // Transform the vertex position by the projection matrix - gl_Position = uProjectionMatrix * vec4(aVertex, 0.0, 1.0); + gl_Position = uProjectionMatrix * vec4(aVertex, 0.0, 1.0); // Pass the remaining attributes to the fragment shader vColor = vec4(aColor.bgr * aColor.a, aColor.a); vRegion = aRegion; diff --git a/src/video/webgl/webgl_renderer.js b/src/video/webgl/webgl_renderer.js index 0a3fedbf6c..ce0690d9b0 100644 --- a/src/video/webgl/webgl_renderer.js +++ b/src/video/webgl/webgl_renderer.js @@ -1,6 +1,7 @@ import Color from "./../../math/color.js"; import Matrix2d from "./../../math/matrix2.js"; -import WebGLCompositor from "./compositors/webgl_compositor.js"; +import QuadCompositor from "./compositors/quad_compositor"; +import PrimitiveCompositor from "./compositors/primitive_compositor"; import Renderer from "./../renderer.js"; import TextureCache from "./../texture/cache.js"; import { TextureAtlas, createAtlas } from "./../texture/atlas.js"; @@ -112,8 +113,9 @@ import { isPowerOfTwo, nextPowerOfTwo } from "./../../math/math.js"; */ this.compositors = new Map(); - // Create a default compositor - this.addCompositor(new (this.settings.compositor || WebGLCompositor)(this), "default", true); + // Create both quad and primitive compositor + this.addCompositor(new (this.settings.compositor || QuadCompositor)(this), "quad", true); + this.addCompositor(new (this.settings.compositor || PrimitiveCompositor)(this), "primitive"); // default WebGL state(s) @@ -215,6 +217,7 @@ import { isPowerOfTwo, nextPowerOfTwo } from "./../../math/math.js"; } // set given one as current this.currentCompositor = compositor; + this.currentCompositor.bind(); } return this.currentCompositor; @@ -231,6 +234,7 @@ import { isPowerOfTwo, nextPowerOfTwo } from "./../../math/math.js"; * @ignore */ createFontTexture(cache) { + this.setCompositor("quad"); if (typeof this.fontTexture === "undefined") { var canvas = this.getCanvas(); var width = canvas.width; @@ -278,6 +282,8 @@ import { isPowerOfTwo, nextPowerOfTwo } from "./../../math/math.js"; */ createPattern(image, repeat) { + this.setCompositor("quad"); + if (renderer.WebGLVersion === 1 && (!isPowerOfTwo(image.width) || !isPowerOfTwo(image.height))) { var src = typeof image.src !== "undefined" ? image.src : image; throw new Error( @@ -307,16 +313,15 @@ import { isPowerOfTwo, nextPowerOfTwo } from "./../../math/math.js"; */ setProjection(matrix) { super.setProjection(matrix); - this.compositors.forEach((compositor) => { - compositor.setProjection(matrix); - }); } /** * prepare the framebuffer for drawing a new frame */ clear() { - this.currentCompositor.clear(this.settings.transparent ? 0.0 : 1.0); + this.compositors.forEach((compositor) => { + compositor.clear(this.settings.transparent ? 0.0 : 1.0); + }); } /** @@ -359,6 +364,8 @@ import { isPowerOfTwo, nextPowerOfTwo } from "./../../math/math.js"; drawFont(bounds) { var fontContext = this.getFontContext(); + this.setCompositor("quad"); + // Force-upload the new texture this.currentCompositor.uploadTexture(this.fontTexture, 0, 0, 0, true); @@ -431,6 +438,8 @@ import { isPowerOfTwo, nextPowerOfTwo } from "./../../math/math.js"; dy |= 0; } + this.setCompositor("quad"); + var texture = this.cache.get(image); var uvs = texture.getUVs(sx + "," + sy + "," + sw + "," + sh); this.currentCompositor.addQuad(texture, dx, dy, dw, dh, uvs[0], uvs[1], uvs[2], uvs[3], this.currentTint.toUint32(this.getGlobalAlpha())); @@ -447,6 +456,7 @@ import { isPowerOfTwo, nextPowerOfTwo } from "./../../math/math.js"; */ drawPattern(pattern, x, y, width, height) { var uvs = pattern.getUVs("0,0," + width + "," + height); + this.setCompositor("quad"); this.currentCompositor.addQuad(pattern, x, y, width, height, uvs[0], uvs[1], uvs[2], uvs[3], this.currentTint.toUint32(this.getGlobalAlpha())); } @@ -687,10 +697,7 @@ import { isPowerOfTwo, nextPowerOfTwo } from "./../../math/math.js"; * @param {boolean} [fill=false] */ strokeArc(x, y, radius, start, end, antiClockwise = false, fill = false) { - if (this.getGlobalAlpha() < 1 / 255) { - // Fast path: don't draw fully transparent - return; - } + this.setCompositor("primitive"); this.path2D.beginPath(); this.path2D.arc(x, y, radius, start, end, antiClockwise); if (fill === false) { @@ -723,15 +730,12 @@ import { isPowerOfTwo, nextPowerOfTwo } from "./../../math/math.js"; * @param {boolean} [fill=false] - also fill the shape with the current color if true */ strokeEllipse(x, y, w, h, fill = false) { - if (this.getGlobalAlpha() < 1 / 255) { - // Fast path: don't draw fully transparent - return; - } + this.setCompositor("primitive"); this.path2D.beginPath(); this.path2D.ellipse(x, y, w, h, 0, 0, 360); this.path2D.closePath(); if (fill === false) { - this.currentCompositor.drawVertices(this.gl.LINE_LOOP, this.path2D.points); + this.currentCompositor.drawVertices(this.gl.LINE_STRIP, this.path2D.points); } else { this.currentCompositor.drawVertices(this.gl.TRIANGLES, this.path2D.triangulatePath()); } @@ -756,14 +760,11 @@ import { isPowerOfTwo, nextPowerOfTwo } from "./../../math/math.js"; * @param {number} endY - the end y coordinate */ strokeLine(startX, startY, endX, endY) { - if (this.getGlobalAlpha() < 1 / 255) { - // Fast path: don't draw fully transparent - return; - } + this.setCompositor("primitive"); this.path2D.beginPath(); this.path2D.moveTo(startX, startY); this.path2D.lineTo(endX, endY); - this.currentCompositor.drawVertices(this.gl.LINE_STRIP, this.path2D.points); + this.currentCompositor.drawVertices(this.gl.LINES, this.path2D.points); } @@ -784,22 +785,19 @@ import { isPowerOfTwo, nextPowerOfTwo } from "./../../math/math.js"; * @param {boolean} [fill=false] - also fill the shape with the current color if true */ strokePolygon(poly, fill = false) { - if (this.getGlobalAlpha() < 1 / 255) { - // Fast path: don't draw fully transparent - return; - } + this.setCompositor("primitive"); this.translate(poly.pos.x, poly.pos.y); this.path2D.beginPath(); - this.path2D.moveTo(poly.points[0].x, poly.points[0].y); - var point; - for (var i = 1; i < poly.points.length; i++) { - point = poly.points[i]; - this.path2D.lineTo(point.x, point.y); + + var points = poly.points; + for (var i = 1; i < points.length; i++) { + this.path2D.moveTo(points[i-1].x, points[i-1].y); + this.path2D.lineTo(points[i].x, points[i].y); } - this.path2D.lineTo(poly.points[0].x, poly.points[0].y); + this.path2D.lineTo(points[points.length - 1].x, points[points.length - 1].y); this.path2D.closePath(); if (fill === false) { - this.currentCompositor.drawVertices(this.gl.LINE_LOOP, this.path2D.points); + this.currentCompositor.drawVertices(this.gl.LINES, this.path2D.points); } else { // draw all triangles this.currentCompositor.drawVertices(this.gl.TRIANGLES, this.path2D.triangulatePath()); @@ -824,14 +822,11 @@ import { isPowerOfTwo, nextPowerOfTwo } from "./../../math/math.js"; * @param {boolean} [fill=false] - also fill the shape with the current color if true */ strokeRect(x, y, width, height, fill = false) { - if (this.getGlobalAlpha() < 1 / 255) { - // Fast path: don't draw fully transparent - return; - } + this.setCompositor("primitive"); this.path2D.beginPath(); this.path2D.rect(x, y, width, height); if (fill === false) { - this.currentCompositor.drawVertices(this.gl.LINE_LOOP, this.path2D.points); + this.currentCompositor.drawVertices(this.gl.LINES, this.path2D.points); } else { this.currentCompositor.drawVertices(this.gl.TRIANGLES, this.path2D.triangulatePath()); } @@ -858,14 +853,11 @@ import { isPowerOfTwo, nextPowerOfTwo } from "./../../math/math.js"; * @param {boolean} [fill=false] - also fill the shape with the current color if true */ strokeRoundRect(x, y, width, height, radius, fill = false) { - if (this.getGlobalAlpha() < 1 / 255) { - // Fast path: don't draw fully transparent - return; - } + this.setCompositor("primitive"); this.path2D.beginPath(); this.path2D.roundRect(x, y, width, height, radius); if (fill === false) { - this.currentCompositor.drawVertices(this.gl.LINE_LOOP, this.path2D.points); + this.currentCompositor.drawVertices(this.gl.LINE_STRIP, this.path2D.points); } else { this.path2D.closePath(); this.currentCompositor.drawVertices(this.gl.TRIANGLES, this.path2D.triangulatePath());