Skip to content

Instantly share code, notes, and snippets.

@zz85
Last active February 6, 2016 14:40
Show Gist options
  • Save zz85/606666529d38370ccee9 to your computer and use it in GitHub Desktop.
Save zz85/606666529d38370ccee9 to your computer and use it in GitHub Desktop.
Drawing / Painting as Many Rects as Possible (Still WIP, feel free to comments or contribute!)

Moved here

Fastest Rectangles

Some thoughts:

  • How many ways can you draw rectangles in a browser (eg. dom, css, 2d canvas, svg, webgl)
  • How is the fastest (most number of) rectangles can you draw with each approach?

Task:

  • Paint random rectangles for 5 seconds, find out how fast each approach takes.

Rational:

  • For various reasons, you may want to find the most convinent approach to draw rectangles. In Space Radar, I find myself having > 100K rectanges to paint at times, which is why I'm exploring and testing which ways rectangles can be painted.

Tests Cases:

  • DOM, CSS, Canvas, or WebGL (three.js, stackgl or other libraries) may be used
  • Fill them
  • Stroke them
  • Rectangle should be updatable by JS
  • Rectangles are defined by (x, y, w, h)
  • Canvas dimension is 1024x768.
  • Random rects dimensions is width/4, height/4
  • They are in units as fraction of the screen size (0..1)
  • Each draw frame should be within 16.67s (60fps)
  • Goal is to see compare the different approaches

Current Results

These numbers are estimated number of triangles that can be drawn in a single 60fps frame (which has only 16.67ms rendering time). As with all benchmarks, these numbers should be taken with a grain of salt. There are many factors depending on different scenarios so milleage always vary.

  1. Headless - (overheads of Math.random) 76K (boilerplate.html)
  2. Canvas - 2.7K rectangles (canvas.html)
  3. PixiJS - 3.1K Canvas Renderer, 2.3K WebGL Renderer (pixi.html)
  4. PixiJS - Single graphics. 1.8K WebGL, 2.4K Canvas (pixi-single.html)
  5. Three.js (Rect as mesh) (three-simple.html)
  • each mesh as individual geometry (WebGl Renderer)
    • no shared geometry & no shared material - 1K
      • shared geometry - 1.8K
      • shared material - 3.7K (1.1K for canvas renderer)
      • Add rect strokes (fill only, no lines) - 1.7K
      • Almost similar using buffergeometry + line buffers (1.7K)
  1. Single Buffer Geometry (three-buffer.html)

    • 5K
  2. Instance Geometry (three-instance.html)

    • 20K rectangles (Fill + Stroke)

To come

Others

  • Indexed / Interved Buffers?
  • Using point sprites
<html>
<body>
<script src="boilerplate.js"></script>
<script>
// Experiment 0. Boilerplate, headless rendering
var BATCH = 100000;
function draw() {
rects += BATCH;
var haha = 0;
for (var i = 0; i < BATCH; i++) {
var x = Math.random() * WIDTH;
var y = Math.random() * HEIGHT;
var w = Math.random() * WIDTH / 4;
var h = Math.random() * HEIGHT / 4;
haha += x + y - w - h; // just so JS doesn't skip this unused block smartly
}
}
setTimeout(start, 500);
</script>
</body>
</html>
/* Globals */
var WIDTH = 1024;
var HEIGHT = 768;
var timeStart;
var total = 0;
var rafs = 0;
var rects = 0;
/* Implement your draw function !! */
function run() {
rafs++
var then = performance.now();
draw();
var now = performance.now();
var lapsed = now - timeStart;
total += now - then;
if (lapsed < 5000) {
requestAnimationFrame(run);
} else {
console.timeEnd('run');
console.log('Number of Rafs', rafs);
console.log('Time per Rafs', (total / rafs).toFixed(2), 'ms');
console.log('Total Rects', rects);
console.log('Rects / second', (rects / total * 1000).toFixed(2));
console.log('Rects / 16.67ms', (rects * 1000 / total / 60).toFixed(2));
console.log('Done!!');
}
}
function start() {
console.log('Test start!');
timeStart = performance.now();
console.time('run');
run();
}
<html>
<body>
<script src="boilerplate.js"></script>
<script>
// Experiment 1. Using Canvas 2D
var canvas = document.createElement('canvas');
canvas.width = 1024; // window.innerWidth;
canvas.height = 768; // window.innerHeight;
var ctx = canvas.getContext('2d');
document.body.appendChild(canvas);
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = 'black';
ctx.fillStyle = 'white';
for (var i = 0; i < 2500; i++) {
ctx.beginPath()
var x = Math.random() * canvas.width;
var y = Math.random() * canvas.height;
var w = Math.random() * canvas.width / 4;
var h = Math.random() * canvas.height / 4;
ctx.rect(x, y, w, h);
ctx.stroke();
ctx.fill();
rects++;
}
}
setTimeout(start, 500);
</script>
</body>
</html>
<html>
<body>
<script src="../node_modules/pixi.js/bin/pixi.js"></script>
<script src="boilerplate.js"></script>
<script>
// Experiment 2b. Using pixi library with WebGL + single sprite
var renderer = new PIXI.CanvasRenderer(WIDTH, HEIGHT);
// var renderer = new PIXI.WebGLRenderer(WIDTH, HEIGHT);
document.body.appendChild(renderer.view);
var stage = new PIXI.Container();
var BATCH = 2000;
var sprites = [];
var graphics = new PIXI.Graphics();
stage.addChild(graphics);
function draw() {
rects += BATCH;
graphics.clear();
graphics.beginFill(0xffeeff);
graphics.lineStyle(1, 0x000000, 1);
for (var i = 0; i < BATCH; i++) {
var x = Math.random() * WIDTH;
var y = Math.random() * HEIGHT;
var w = Math.random() * WIDTH / 4;
var h = Math.random() * HEIGHT / 4;
graphics.drawRect(x, y, w, h);
// This is slower
// graphics.moveTo(x, y);
// graphics.lineTo(x + w, y);
// graphics.lineTo(x + w, y + h);
// graphics.lineTo(x, y + h);
// graphics.lineTo(x, y);
// graphics.endFill();
}
renderer.render(stage);
}
setTimeout(start, 500);
</script>
</body>
</html>
<html>
<body>
<script src="../node_modules/pixi.js/bin/pixi.js"></script>
<script src="boilerplate.js"></script>
<script>
// Experiment 2. Using pixi library
var renderer = new PIXI.CanvasRenderer(WIDTH, HEIGHT);
// var renderer = new PIXI.WebGLRenderer(WIDTH, HEIGHT);
document.body.appendChild(renderer.view);
var stage = new PIXI.Container();
var BATCH = 2700;
var sprites = [];
for (var i = 0; i < BATCH; i++) {
var x = Math.random() * WIDTH;
var y = Math.random() * HEIGHT;
var w = Math.random() * WIDTH / 4;
var h = Math.random() * HEIGHT / 4;
// Not sure how to use this...
// var graphics = new PIXI.Rectangle(x, y, w, h)
/**/
var graphics = new PIXI.Graphics();
graphics.beginFill(0xffeeff);
graphics.lineStyle(1, 0x000000, 1);
// draw a rectangle
graphics.drawRect(x, y, w, h);
// This is slower
// graphics.moveTo(x, y);
// graphics.lineTo(x + w, y);
// graphics.lineTo(x + w, y + h);
// graphics.lineTo(x, y + h);
// graphics.lineTo(x, y);
// graphics.endFill();
stage.addChild(graphics);
sprites.push(graphics);
}
function draw() {
rects += BATCH;
for (var i = 0; i < BATCH; i++) {
var x = Math.random() * WIDTH;
var y = Math.random() * HEIGHT;
var w = Math.random() * WIDTH / 4;
var h = Math.random() * HEIGHT / 4;
// sprites[i].drawRect(x, y, w, h);
// sprites[i].scale.x = Math.random();
// sprites[i].scale.y = Math.random();
var sprite = sprites[i];
sprite.position.x = x;
sprite.position.y = y;
sprite.width = w;
sprite.height = h;
}
renderer.render(stage);
}
setTimeout(start, 500);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js canvas - camera - orthographic</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
font-family: Monospace;
background-color: #f0f0f0;
margin: 0px;
overflow: hidden;
}
</style>
</head>
<body>
<script src="/gits/three.js/build/three.min.js"></script>
<script src="boilerplate.js"></script>
<script>
var camera, scene, renderer;
var RECTS = 5000;
var positions, lineBuffer;
function setRect(x, y, w, h, index, buffer, lines) {
// a.setXYZ(0, -0.5, 0.5, 0); // a
// a.setXYZ(1, 0.5, 0.5, 0); // b
// a.setXYZ(2, 0.5, -0.5, 0); // d
// a.setXYZ(3, -0.5, -0.5, 0); // c
var i = index * 6;
var ax = -0.5 + x;
var ay = -0.5 + y;
var bx = 0.5 + x + w;
var by = -0.5 + y;
var dx = 0.5 + x + w;
var dy = 0.5 + y + h;
var cx = -0.5 + x;
var cy = 0.5 + y + h;
var z = 0 + index * 0.01;
buffer.setXYZ(i + 0, ax, ay, z); // a
buffer.setXYZ(i + 1, bx, by, z); // b
buffer.setXYZ(i + 2, dx, dy, z); // d
buffer.setXYZ(i + 3, dx, dy, z); // d
buffer.setXYZ(i + 4, cx, cy, z); // c
buffer.setXYZ(i + 5, ax, ay, z); // a
i = index * 8;
z = 0.001 + index * 0.01;
lines.setXYZ(i + 0, ax, ay, z); // a
lines.setXYZ(i + 1, bx, by, z); // b
lines.setXYZ(i + 2, bx, by, z); // b
lines.setXYZ(i + 3, dx, dy, z); // d
lines.setXYZ(i + 4, dx, dy, z); // d
lines.setXYZ(i + 5, cx, cy, z); // c
lines.setXYZ(i + 6, cx, cy, z); // c
lines.setXYZ(i + 7, ax, ay, z); // a
}
function init() {
camera = new THREE.OrthographicCamera( WIDTH / - 2, WIDTH / 2, HEIGHT / 2, HEIGHT / - 2, - 500, 1000 );
// camera.position.x = 0;
// camera.position.y = 100;
camera.position.z = 100;
scene = new THREE.Scene();
lineBuffer = new THREE.BufferAttribute( new Float32Array( RECTS * 8 * 3 ), 3 );
positions = new THREE.BufferAttribute( new Float32Array( RECTS * 6 * 3 ), 3 )
var rectLineGeometry = new THREE.BufferGeometry();
rectLineGeometry.addAttribute( 'position', lineBuffer, 3 );
// var colors = new Float32Array( 5 * 3 ).fill(0);
// rectLineGeometry.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) );
// rectLineGeometry.computeBoundingSphere();
var geometry = new THREE.BufferGeometry();
geometry.addAttribute('position', positions);
var lineMaterial = new THREE.
LineBasicMaterial
( {
// vertexColors: THREE.VertexColors,
color: 0x000000,
// opacity: 0.2
} );
var lines = new THREE.LineSegments( rectLineGeometry, lineMaterial );
scene.add(lines);
var material;
var color = new THREE.Color(1, 1, 1);
material = new THREE.MeshBasicMaterial({
color: color,
side: THREE.DoubleSide,
wireframe: !true
})
var rectMesh = new THREE.Mesh(geometry, material);
for ( var i = 0; i < RECTS; i ++ ) {
var x, y, w, h;
x = Math.random() * WIDTH - WIDTH / 2;
y = Math.random() * HEIGHT - HEIGHT / 2;
w = Math.random() * WIDTH / 4;
h = Math.random() * HEIGHT / 4;
setRect(x, y, w, h, i, positions, lineBuffer);
}
scene.add( rectMesh );
renderer = new THREE.WebGLRenderer();
renderer.setClearColor( 0xf0f0f0 );
// renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( WIDTH, HEIGHT );
document.body.appendChild( renderer.domElement );
//
// window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.left = WIDTH / - 2;
camera.right = WIDTH / 2;
camera.top = HEIGHT / 2;
camera.bottom = HEIGHT / - 2;
camera.updateProjectionMatrix();
renderer.setSize( WIDTH, HEIGHT );
}
//
function draw() {
camera.lookAt( scene.position );
for ( var i = 0; i < RECTS; i ++ ) {
var x, y, w, h;
x = Math.random() * WIDTH - WIDTH / 2;
y = Math.random() * HEIGHT - HEIGHT / 2;
w = Math.random() * WIDTH / 4;
h = Math.random() * HEIGHT / 4;
setRect(x, y, w, h, i, positions, lineBuffer);
}
positions.needsUpdate = true;
lineBuffer.needsUpdate = true;
renderer.render( scene, camera );
rects += RECTS;
}
init();
setTimeout(start, 500);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgl - instanced particles - billboards - colors</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
color: #ffffff;
font-family: Monospace;
font-size: 13px;
text-align: center;
font-weight: bold;
background-color: #000000;
margin: 0px;
overflow: hidden;
}
#info {
position: absolute;
top: 0px;
width: 100%;
padding: 5px;
}
a {
color: #ffffff;
}
#oldie a {
color: #da0;
}
#notSupported {
width: 50%;
margin: auto;
border: 2px red solid;
margin-top: 20px;
padding: 10px;
}
</style>
</head>
<body>
<div id="info">
<div id="notSupported" style="display:none">Sorry your graphics card + browser does not support hardware instancing</div>
</div>
<script src="boilerplate.js"></script>
<script src="/gits/three.js/build/three.min.js"></script>
<script src="/gits/three.js/examples/js/Detector.js"></script>
<script id="vshader" type="x-shader/x-vertex">
precision highp float;
uniform mat4 modelViewMatrix;
uniform mat4 projectionMatrix;
attribute vec3 position;
attribute vec2 uv;
attribute vec3 normal;
attribute vec3 translate;
attribute vec2 scale;
attribute vec3 color;
varying vec3 vColor;
void main() {
vec3 p = position;
// p.xy *= scale.xy;
p.x *= scale.x;
p.y *= scale.y;
vec4 mvPosition = modelViewMatrix * vec4( p + translate, 1.0 );
// mvPosition.xyz += p;
// * scale
vColor = color;
gl_Position = projectionMatrix * mvPosition;
}
</script>
<script id="fshader" type="x-shader/x-fragment">
precision highp float;
varying vec3 vColor;
void main() {
// vec4 diffuseColor = texture2D( map, vUv );
// vec3 vColor = vec3(0.4, 0.5, 0.4);
// vec4 diffuseColor = vec4( 0.8, .8, .4, 1.);
gl_FragColor = vec4( vColor, 1. );
// if ( diffuseColor.w < 0.5 ) discard;
}
</script>
<script>
var container;
var camera, scene, renderer;
var geometry, material, mesh;
var particleCount = 20000;
var translateArray = new Float32Array( particleCount * 3 );
var scaleArray = new Float32Array( particleCount * 2 );
var colorsArray = new Float32Array( particleCount * 3 );
function init() {
if ( !Detector.webgl ) {
Detector.addGetWebGLMessage();
return false;
}
renderer = new THREE.WebGLRenderer();
if ( renderer.extensions.get( 'ANGLE_instanced_arrays' ) === false ) {
document.getElementById( "notSupported" ).style.display = "";
return false;
}
container = document.createElement( 'div' );
document.body.appendChild( container );
var color = new THREE.Color( 0xffffff );
camera = new THREE.OrthographicCamera( WIDTH / - 2, WIDTH / 2, HEIGHT / 2, HEIGHT / - 2, - 500, 1000 );
camera.position.z = 100;
scene = new THREE.Scene();
geometry = new THREE.InstancedBufferGeometry();
geometry.copy( new THREE.PlaneBufferGeometry( 1, 1, 1, 1 ) );
lineGeometry = new THREE.InstancedBufferGeometry();
lineBuffer = new THREE.BufferAttribute( new Float32Array( 8 * 3 ), 3 );
function setRect(lines) {
var ax = -0.5;
var ay = -0.5;
var bx = 0.5;
var by = -0.5;
var dx = 0.5;
var dy = 0.5;
var cx = -0.5;
var cy = 0.5;
var z = 0;
lines.setXYZ(0, ax, ay, z); // a
lines.setXYZ(1, bx, by, z); // b
lines.setXYZ(2, bx, by, z); // b
lines.setXYZ(3, dx, dy, z); // d
lines.setXYZ(4, dx, dy, z); // d
lines.setXYZ(5, cx, cy, z); // c
lines.setXYZ(6, cx, cy, z); // c
lines.setXYZ(7, ax, ay, z); // a
}
setRect(lineBuffer);
lineGeometry.addAttribute( 'position', lineBuffer );
translateArray = new Float32Array( particleCount * 3 );
scaleArray = new Float32Array( particleCount * 2 );
colorsArray = new Float32Array( particleCount * 3 );
var x, y, w, h;
for ( var i = 0, i3 = 0, l = particleCount; i < l; i ++, i3 += 3 ) {
x = Math.random() * WIDTH - WIDTH / 2;
y = Math.random() * HEIGHT - HEIGHT / 2;
translateArray[ i3 + 0 ] = x;
translateArray[ i3 + 1 ] = y;
translateArray[ i3 + 2 ] = i * 0.1;
}
for ( var i = 0, i2 = 0, l = particleCount; i < l; i ++, i2 += 2 ) {
w = Math.random() * WIDTH / 4;
h = Math.random() * HEIGHT / 4;
scaleArray[ i2 + 0 ] = w;
scaleArray[ i2 + 1 ] = h;
}
for ( var i = 0, i3 = 0, l = particleCount; i < l; i ++, i3 += 3 ) {
// color.setHSL( Math.random(), 1 - i / l * 0.3, 0.5 );
colorsArray[ i3 + 0 ] = color.r;
colorsArray[ i3 + 1 ] = color.g;
colorsArray[ i3 + 2 ] = color.b;
}
geometry.addAttribute( "translate", new THREE.InstancedBufferAttribute( translateArray, 3, 1 ).setDynamic( true ) );
geometry.addAttribute( "scale", new THREE.InstancedBufferAttribute( scaleArray, 2, 1 ).setDynamic( true ) );
geometry.addAttribute( "color", new THREE.InstancedBufferAttribute( colorsArray, 3, 1 ).setDynamic( true ) );
// Line Geometry
lineColors = new Float32Array( particleCount * 3 ).fill(0);
lineGeometry.addAttribute( "translate", new THREE.InstancedBufferAttribute( translateArray, 3, 1 ).setDynamic( true ) );
lineGeometry.addAttribute( "scale", new THREE.InstancedBufferAttribute( scaleArray, 2, 1 ).setDynamic( true ) );
lineGeometry.addAttribute( "color", new THREE.InstancedBufferAttribute( lineColors, 3, 1 ).setDynamic( true ) );
material = new THREE.RawShaderMaterial( {
uniforms: {
// map: { type: "t", value: THREE.ImageUtils.loadTexture( "textures/sprites/circle.png" ) }
},
vertexShader: document.getElementById( 'vshader' ).textContent,
fragmentShader: document.getElementById( 'fshader' ).textContent,
depthTest: true,
depthWrite: true
} );
mesh = new THREE.Mesh( geometry, material );
lineMesh = new THREE.LineSegments( lineGeometry, material );
lineMesh.position.z = 0.0001;
mesh.scale.multiplyScalar(1);
scene.add( mesh );
scene.add( lineMesh )
// renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( WIDTH, HEIGHT );
container.appendChild( renderer.domElement );
//
// window.addEventListener( 'resize', onWindowResize, false );
return true;
}
function onWindowResize( event ) {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function draw() {
var time = performance.now() * 0.0005;
camera.lookAt( scene.position );
var translates = geometry.getAttribute( 'translate' );
var scales = geometry.getAttribute( 'scale' );
var colors = geometry.getAttribute( 'color' );
for ( var i = 0, i2 = 0, i3 = 0, l = particleCount;
i < l;
i ++, i2 += 2, i3 += 3 ) {
var x = Math.random() * WIDTH - WIDTH / 2;
var y = Math.random() * HEIGHT - HEIGHT / 2;
var w = Math.random() * WIDTH / 4;
var h = Math.random() * HEIGHT / 4;
translateArray[ i3 + 0 ] = x;
translateArray[ i3 + 1 ] = y;
translateArray[ i3 + 2 ] = -i * 0.01;
scaleArray[ i2 + 0 ] = w;
scaleArray[ i2 + 1 ] = h;
}
renderer.setClearColor( 0xf0f0f0 );
translates.needsUpdate = true;
scales.needsUpdate = true;
// colors.needsUpdate = true;
lineGeometry.getAttribute( 'translate' ).needsUpdate = true;
lineGeometry.getAttribute( 'scale' ).needsUpdate = true;
// renderer.clear();
renderer.render( scene, camera );
rects += particleCount;
}
init();
setTimeout(start, 500);
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js canvas - camera - orthographic</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<style>
body {
font-family: Monospace;
background-color: #f0f0f0;
margin: 0px;
overflow: hidden;
}
</style>
</head>
<body>
<script src="/gits/three.js/build/three.min.js"></script>
<script src="/gits/three.js/examples/js/renderers/Projector.js"></script>
<script src="/gits/three.js/examples/js/renderers/CanvasRenderer.js"></script>
<script src="boilerplate.js"></script>
<script>
var camera, scene, renderer;
var RECTS = 5000;
function init() {
camera = new THREE.OrthographicCamera( WIDTH / - 2, WIDTH / 2, HEIGHT / 2, HEIGHT / - 2, - 500, 1000 );
camera.position.x = 0;
// camera.position.y = 100;
camera.position.z = 100;
scene = new THREE.Scene();
// Grid
var size = 500, step = 50;
var rectLineGeometry = new THREE.Geometry();
var geometry = new THREE.PlaneGeometry(1, 1, 1, 1);
rectLineGeometry.vertices[0] = geometry.vertices[0];
rectLineGeometry.vertices[1] = geometry.vertices[1];
rectLineGeometry.vertices[2] = geometry.vertices[3];
rectLineGeometry.vertices[3] = geometry.vertices[2];
rectLineGeometry.vertices[4] = geometry.vertices[0];
var lineMaterial = new THREE.LineBasicMaterial( { color: 0x000000, opacity: 0.2 } );
var material = new THREE.MeshBasicMaterial( { color: 0x330033, shading: THREE.FlatShading, overdraw: 0.5 } );
var geometry = new THREE.PlaneGeometry(1, 1, 1, 1);
var color = new THREE.Color(1, 1, 1);
material = new THREE.MeshBasicMaterial({
color: color,
side: THREE.DoubleSide,
wireframe: !true
})
function RectangleMesh() {
THREE.Mesh.call(this, geometry, material);
var line = new THREE.Line( rectLineGeometry, lineMaterial );
line.position.z = 0.0001;
this.add( line );
}
RectangleMesh.prototype = Object.create(THREE.Mesh.prototype);
RectangleMesh.prototype.setRect = function(x, y, w, h) {
this.position.x = x + w * .5;
this.position.y = y + h * .5;
this.scale.x = w;
this.scale.y = h;
}
for ( var i = 0; i < RECTS; i ++ ) {
var rect = new RectangleMesh( material );
rect.position.z = 0.01 * i;
scene.add( rect );
}
// renderer = new THREE.CanvasRenderer();
renderer = new THREE.WebGLRenderer();
renderer.setClearColor( 0xf0f0f0 );
// renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( WIDTH, HEIGHT );
document.body.appendChild( renderer.domElement );
//
// window.addEventListener( 'resize', onWindowResize, false );
}
function onWindowResize() {
camera.left = WIDTH / - 2;
camera.right = WIDTH / 2;
camera.top = HEIGHT / 2;
camera.bottom = HEIGHT / - 2;
camera.updateProjectionMatrix();
renderer.setSize( WIDTH, HEIGHT );
}
//
function draw() {
for ( var i = 0; i < RECTS; i ++ ) {
var rect = scene.children[i];
var x, y, w, h;
x = Math.random() * WIDTH - WIDTH / 2;
y = Math.random() * HEIGHT - HEIGHT / 2;
w = Math.random() * WIDTH / 4;
h = Math.random() * HEIGHT / 4;
rect.setRect(x, y, w, h);
}
camera.lookAt( scene.position );
renderer.render( scene, camera );
rects += RECTS;
}
init();
setTimeout(start, 500);
</script>
</body>
</html>
@zz85
Copy link
Author

zz85 commented Jan 15, 2016

Seems like it wasn't clear what I was trying to do. Maybe this screenshot would help.

image

@zz85
Copy link
Author

zz85 commented Jan 15, 2016

Updated canvas to be of 1024x768 so the tests would be slightly fairer.

@mattdesl
Copy link

If you are using PIXI:

  • Use WebGLRenderer
  • Use a single PIXI.Graphics object and clear() it before rendering. Then populate it with all rectangles using drawRectangle

With raw WebGL you could optimize it a lot further; e.g. packing all vertices into a static buffer and drawing with gl.LINES. Or, render the lines with triangles (see @spite's MeshLines), if you want more control over joins/edges/anti-aliasing/etc.

@zz85
Copy link
Author

zz85 commented Jan 16, 2016

@mattdesl, thanks for pointing that out. I was pretty sure I was using Pixi wrongly, I'll try the more efficient approach there and see...

@zz85
Copy link
Author

zz85 commented Jan 16, 2016

@mattdesl, I just tried that. It seems to have a lower performance though WebGL - 6.7K and Canvas - ~8K.

Take a look at pixijs-webgl.html

@zz85
Copy link
Author

zz85 commented Jan 16, 2016

Profiling pixi shows this... looks like it's spending some time in JS land...
image

@vorg
Copy link

vorg commented Jan 16, 2016

@zz85 what are your systems specs (GPU, CPU, OS, browser?). How do you come up with the batch size (e.g. 5000)? Is it done iteratively? E.g. increase it as long as you are at 60fps and then wait 5s to get precise average?

@zz85
Copy link
Author

zz85 commented Jan 17, 2016

@vorg I'm current on a 2013 MacBook Air with specs running it on Chrome.

image

The "test suite" kinda works like this. It runs using a sequence of requestAnimationFrames until 5 seconds is up. It then takes the total number of rectangles drawn (specified with rects += BATCH) over the rendering time the RAFs has happened. I typically tune the number so the batch number would run within 16.67ms, (which is also estimated in one of the console.logs). I've also made a calculate mistakes getting the optimal frames in a RAF, so I'm running the tests and updating the numbers again.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment