Skip to content

Commit

Permalink
[#1172] enable batch drawing for primitive shapes
Browse files Browse the repository at this point in the history
Changes:
- use g.LINES when possible/applicable
- split the WebGLCompositor into two classes, `QuadCompositor` for Quad (Sprite) drawing, and `PrimitiveCompositor` to draw vertices
- fixed binding of active shader when switching
- color for drawing primitive is now a vertex attributes (as opposed to an uniform previously)

TODO :
- Optimize the `arcTo` function to use gl.LINES and enable batching for circle/ellipse and rounded rectangles
- Cache of optimize triangulation for filling operations. currently the bottleneck when drawing large amount  (> 1000) of shapes
  • Loading branch information
obiot committed Feb 19, 2023
1 parent 63fad7e commit 2ac9884
Show file tree
Hide file tree
Showing 8 changed files with 157 additions and 115 deletions.
6 changes: 3 additions & 3 deletions src/geometries/path2d.js
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}

/**
Expand All @@ -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);
}

Expand Down
8 changes: 6 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -127,7 +129,9 @@ export {
Tween,
QuadTree,
GLShader,
WebGLCompositor,
Compositor,
PrimitiveCompositor,
QuadCompositor,
Renderer,
WebGLRenderer,
CanvasRenderer,
Expand Down
13 changes: 13 additions & 0 deletions src/video/webgl/compositors/compositor.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
86 changes: 86 additions & 0 deletions src/video/webgl/compositors/primitive_compositor.js
Original file line number Diff line number Diff line change
@@ -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<br>
* 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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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
Expand All @@ -36,15 +34,14 @@ 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
this.addAttribute("aVertex", 2, this.gl.FLOAT, false, 0 * Float32Array.BYTES_PER_ELEMENT); // 0
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());
Expand Down Expand Up @@ -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
Expand All @@ -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();
}
Expand All @@ -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);
}
}
7 changes: 2 additions & 5 deletions src/video/webgl/shaders/primitive.vert
Original file line number Diff line number Diff line change
@@ -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);
}
4 changes: 3 additions & 1 deletion src/video/webgl/shaders/quad.vert
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
// Current vertex point
attribute vec2 aVertex;
attribute vec2 aRegion;
attribute vec4 aColor;

// Projection matrix
uniform mat4 uProjectionMatrix;

varying vec2 vRegion;
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;
Expand Down
Loading

0 comments on commit 2ac9884

Please sign in to comment.