
CutJS – Fast 2D HTML5 rendering engine for game development - shakiba
http://cutjs.org/
======
illicium
>new concept called pinning introduced instead of styling

It's just a scene graph.

~~~
shakiba
Pinning is about positioning variables, not graph structure.

~~~
aviraldg
A scene graph is a scene graph is a scene graph. Whatever you call it.

Also, it looks like the pinwheel demo has an error. I suggest you add one of
the more advanced demos along with source code on the intro page. Just looking
at the first few demos I felt it reimplemented stuff that's already possible
with CSS, but it looks like it's capable of much more (the last few demos)

~~~
shakiba
Yes, a scene graph is a scene graph but by pinning I meant "how" the
positioning is "described", not the fact that it is positioned.

The game O! is open source. Everything is almost possible with HTML/CSS too,
the point is performance across different devices.

Thanks, found and corrected the error.

------
jablan
I know nothing about game development nor 2D/3D graphics, but why does an HTML
animation of an airplane flying has to pin my i5 core to 100%?

~~~
martinml
You probably don't have GPU acceleration enabled/supported in your browser.
For example, if you use Chrome, check _about:gpu_.

~~~
jablan
That's true (thanks), but anyways, it seems to me that a simple animation
should take only a fraction of a modern CPU's (or GPU) power. Is the engine
calculating more often than what's needed for 60fps or whatever refresh rate
our eyes need to be satisfied?

~~~
shakiba
The issue is not with this engine, no other HTML5 game/engine can do better in
your browser. CPU is not optimized to do what takes only a fraction of GPU.

~~~
kevingadd
This is false. Properly optimized rasterizers (like Adobe Flash's, for
example) can deliver vastly superior performance for simple animated scenes
like that, and have been doing so for over a decade.

Cut's renderer would be much faster if it were designed in a GPU and CPU-
friendly manner instead of the naive graph traversal it does right now (I'll
post about this in detail outside this thread)

~~~
shakiba
CutJS is already GPU and CPU-friendly, the problem is that his browser is not
GPU-enabled and uses CPU for GPU tasks. It is not about CutJS, Canvas or
Flash. When you use CutJS with a GPU enabled browser only a very small portion
of CPU is used which shows that is CPU-friendly.

~~~
kevingadd
As I mention in my top level post, this is false. The most obvious example is
that you do all your rendering and updates in graph traversal order, even
though this is one of the worst traversal orders to use because it minimizes
the effectiveness of the processor's caches and produces the maximum number of
GPU state changes. GPU state changes increase _both_ CPU and GPU overhead, and
in a software rasterizer they also prohibit efficient caching (for example, in
Cairo).

You can say it's efficient as much as you like, but you clearly haven't done
much research if you think it's fast just because the CPU usage is low in your
trivial demos - demos that cause GC pauses on a regular basis, I should note.
If you want to claim that it's fast and GPU/CPU friendly, you should be
profiling it in a GPU profiler like PIX to ensure that it produces few GPU
draw calls and state changes, and profiling it in a CPU profiler to ensure
that virtually all the time spent is spent inside of the Canvas implementation
- which, sadly, is not true for your example game 'O!' in the Chrome Profiler.

------
nacs
One of the best 2d rendering engines I've found is Pixi.js (
[http://www.pixijs.com/](http://www.pixijs.com/) ) which uses WebGL with a
canvas fallback. Also works great on mobile.

Theres another framework that uses pixi for the rendering but adds physics,
sprite support and sound/input managers called Phaser (
[http://phaser.io/](http://phaser.io/) ), also open source.

~~~
shakiba
If you like Pixi/Phaser you will love CutJS! Using CutJS is far simpler and
you can use whatever physics and sound library you like. There are P2.js,
PhysicsJS and SAT.js examples in repo. For sound you can use Howler.js,
SoundManager, etc.

------
throwaway15213
>A CutJS app consists of a tree of nodes, each node is pinned (transformed)
against its parent and have zero, one or more image cutouts. Image cutouts
comes from either image textures or Canvas drawing.

Won't SVG achieve the same thing?

~~~
shakiba
Yes, it has inspired me, but CutJS renders to HTML5 Canvas to be usable in
game development.

------
zerr
Nice, but "Fast" \- compared to what?

~~~
shakiba
Thanks! Fast enough for game development >= 60fps

Check out [http://piqnt.com/o/](http://piqnt.com/o/)

~~~
camus2
so you claim your engine maintains 60fps whatever the code is thrown at it
right?

~~~
shakiba
Not whatever, but it makes game dev as easy as web dev while still almost as
fast as native development. See your CPU usage while playing.

~~~
camus2
so you mean fast like RAD , not fast like SPEED,right ? because >=60fps highly
depends on the machine one is running , and the browser one is using. Saying
something is fast without benchmarks is stupid. speed is something relative.
Nothing is fast or slow, but faster or slower than something else. So what is
your framework faster than?

~~~
seszett
Faster than SVG or DOM under certain conditions, obviously.

Nobody is claiming that CutJS is "fast" in a somehow absolute way, I think
most HN visitors are intelligent enough to understand that performance depends
on the machine running the application and also on the complexity of the tasks
that are running.

Speed aside, CutJS seems like a nice canvas manipulation library, I'll have to
give it a try.

~~~
_random_
So fast as in turtle vs snail. Got it.

------
kevingadd
'Fast' is a poor descriptive choice for a library that was clearly written
without attention paid to CPU performance characteristics, GPU performance
characteristics, or the nature of modern Canvas implementations.

EDIT: A quick glance at the sample game 'O!' in the chrome profiler shows that
it is triggering a garbage collection roughly every second. Pretty bad for a
game with such low scene complexity and rendering complexity, and those pauses
may be long on a mobile device.

Based on a casual review of the source code (which I'll note is quite readable
- good job!), here are some obvious problems that probably need to be
addressed, both in terms of actual performance and code quality.

Don't render the scene in graph traversal order; doing so ensures that the
number of graphics state changes (shader, transform, texture, etc) is as high
as possible. Most game engines render using a more sophisticated algorithm
where your drawing operations are ordered (as possible - obviously layering
has to be preserved) by graphics state in order to minimize overhead.

For some documentation on this approach, see articles like
[http://realtimecollisiondetection.net/blog/?p=86](http://realtimecollisiondetection.net/blog/?p=86)
I've been using this approach in my games for years and it produces a _huge_
measurable performance improvement on virtually all modern architectures (even
canvas, though it's less significant there)

The tickBefore/tick/tickAfter architecture is particularly suspicious, as is
its use of poorly-scaling algorithms/primitives like Array.splice. Usually
this sort of thing is more clearly and efficiently handled by having an
ordered set of operations (typically represented by a linked list, a
heapqueue, or an array of priority-value tuples that you sort once per frame).

I see frequent references to Cut.Pin.prototype.absoluteMatrix and .matrix. I
will note that these don't appear to create garbage - which is great! - but
they silently mutate a shared Matrix instance which means that it is very easy
for a library consumer to end up with their state corrupted - if you store the
result of absoluteMatrix, then modify the state of a Pin, that matrix will
suddenly become corrupted. Sadly the code appears to make no effort to warn
users about this.

You're not using any type hinting (i.e. | 0, +) which means that JS JITs will
have to work harder to figure out whether given values are integers, floats,
or something else. This results in JIT recompilations, less efficient in-
memory representation, and in some cases results in floating point/integer
values being boxed instead of stored efficiently on the stack/heap.

You mix different types in some of your slots (for one example, Anim._time is
both 'object' and 'integer') which prevents optimization in most modern JS
runtimes. This can have a catastrophic impact on performance in some
scenarios, so you should avoid it if possible. A good way to replace a
null/integer pattern is to have an integer that's always an integer, along
with a hasValue boolean - this is how nullable primitives work in C#, for
example.

Some of your non-initialization code uses temporary object literals -
this.pin({ ... }), this._next = { ... }, etc - which is a recipe for bad
performance and GC pauses if you're doing it during updates or rendering
operations. It's not immediately obvious to me whether you could optimize this
out easily, but you should make sure these temporary literals are not being
created in large quantities.

You have many functions containing throw statements and a few containing catch
statements. In most JS runtimes these both prevent optimization of the
function body. You can solve this by hoisting the throw into a utility
function (In JSIL I have a 'JSIL.RuntimeError' function that just does
'throw') - this allows the function to be optimized even if the throw-function
is not optimized or inlined. When you have a single throw statement in a
complex high-traffic function this can matter a lot.

You appear to not be validating or properly type-casting the inputs you pass
to canvas.drawImage, which will impair performance and result in runtime
errors. I can tell you've already run into problems with this since your
drawImage call has a try/catch wrapped around it (YUCK) - the right solution
here is to read the spec (that more or less clearly specifies the expected
types and constraints for drawImage), and then make sure your code satisfies
those constraints. It will be much faster and more reliable if you do so.

It appears that you may have many scenarios where you call drawImage with a
source rectangle. This tends to be poorly-performing in canvas implementations
(due to deficiencies in the spec), so if you want that to be fast for source
rectangles that don't change, you should cache the source rectangle in a
unique canvas so you can draw without source parameters. Typically source-rect
blits can't be batched at all (i.e. one hardware draw call per blit) so this
makes a big difference.

I'm sure there's more, this is just the result of a quick skim of the code.
Feel free to contact me if you have more questions. You can identify many of
these performance problems by reading specs and using things like Chrome's
profiler and Firefox's SPS and JIT Inspector profiling tools.

I'm a huge fan of people sharing their libraries and engines with the world,
but if you're going to spout superlatives you need to be prepared to back them
up. Otherwise, people who don't know better will be misled by your dishonesty
and end up having their efforts at using your tech go to waste when they have
to port to something better.

~~~
shakiba
Regarding you technical points:

Yes, there is an issue with O!, it makes too much garbage, it should be
corrected.

I guess your point is to store depth of a drawable instead of traversing the
tree? I'm not sure how faster it can make the entire rendering process.

tick(fn) to register fn is only called during initializations (not every
frame), it doesn't matter how it is implemented.

Instead of pin({}) you can use pin('name', value) or simply reuse your {}
object. I don't see any problem with "_next = {}" where it is used for
creating tweening.

Without type hinting it is still fast enough, but good idea.

Thanks for your point about "throw", however I'm not sure functions having
"throw" really need to be optimized, but I will consider that.

absoluteMatrix is only for internal use, and I have not documented it yet, but
it is what it is and is only updated on ticks for performance reasons.

Thanks for your point about drawImage try/catch.

I'm not sure how drawImage with source params is optimized across different
platforms so for now I have let it be as it is. But I agree with your point.

There is obviously a lot to make it run faster but the point is to find
bottlenecks which really matter. For example in most case the bottleneck is
the physics engine not rendering engine at all.

Also if you notice you can see I have done a lot to make it fast, for example
using lots of timestamps (ts) and monitors (mo) to avoid unnecessary
calculations.

Anyway feel free to fork it and change anything to prove that I'm wrong! I
will appreciate that!

~~~
shakiba
I compared O! GC with few other HTML5 games, it seems that GC frequency is
normal with high number of game objects.

------
polskibus
The approach reminds me of React in terms of keeping entire state aside from
the DOM.

Of course it is much older than React, but you can see it performs very well
and is suited for things other than HTML elements.

------
olegkikin
Why not WebGL-based?

~~~
pjmlp
Most likely because they also want to target mobile and system browsers don't
allow for it.

~~~
kevingadd
Note that most canvas implementations on mobile are not hardware accelerated,
so you're not getting all that much for your trouble. ;/ WebGL on mobile
performs much better than Canvas on mobile for configurations that support
both, just like on desktops.

~~~
pjmlp
Which configurations?

WebGL is forbiden on iOS other than iAds.

Most Android devices don't support WebGL on their system browsers, requiring
the users to install Chrome or FireFox. Even then, some devices have their
drivers blacklisted.

WP only supports partially hardware accelerated WebGL in some devices with IE
11 mobile.

------
dikei
Yet another canvas manipulation library, just like fabricjs, kineticjs and
easeljs

~~~
Kiro
What's your point? Also, this looks like a new approach.

~~~
dikei
I don't think this approach is new. Scene graph structure has been used for a
long time, and those libraries also make use of scene graph or allow you to
use it.

------
varg
Very nice! ... was looking for something like this for some experiments,
thanks.

~~~
shakiba
Thanks!

------
mycounty
Does it support high DPI (retina) resolutions?

~~~
shakiba
Yes, just use a high res texture and set imageRatio with texture definition.

See [http://piqnt.com/o/](http://piqnt.com/o/) It is open source and available
for iOS.

