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?
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.
No, I didn't mean RAD by fast, I meant it makes development easier (than not using it) while it is still almost as "fast" as directly using Canvas and "fast" enough for game dev.
That game judders and skips at times, I'm concerned if current "fast" HTML5 games are simple 2d games that perform badly. Even the asteroids game has perceptible little jumps. It's asteroids! It's one of the simplest to render games there is.
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)
I couldn't understand exactly how it is supposed to work. Is it about defining anchor points in both parent and child (what you call "align" and "handle"), and then an offset from that?
>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.
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.
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.
When I started it 2D was enough for what I was trying to achieve, it is also both easier to use and more supported. However WebGL may be added in future, but currently it is a costly development effort for me.
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?
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.
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)
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.
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.
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.
One of the best 2d rendering engines I've found is Pixi.js ( 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/ ), also open source.
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.
'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
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 optim...
Feel free to ignore the unsolicited advice on how to improve your software from someone who's been working on games and game engines for 10 years.
I ran your sample under a profiler and saw obvious problems with CPU and memory performance, and there are obvious problems with GPU efficiency based on your rendering model. This is not subjective; profiling is objective.
P.S. JSIL doesn't compete with your library, it's a cross-compiler. Are you angry because I gave a specific example of how I used one of the optimizations I suggested, or just because I'm pointing out that your website is dishonest?
EDIT: I shouldn't have to point this out either, but saying 'this text you wrote is dishonest' is not a personal attack. It's an objective criticism of your writing. Saying 'it's dishonest to call this library fast because x' is also not a personal attack, it is an objective criticism of your code.
If I called you a liar or a dishonest person, that would be a personal attack. I have not done that. I could certainly make that claim based on your argumentative style in this thread, but I shall not.
'dishonesty' describes a dishonest or deceitful act, and I was referring to the act of writing 'fast' on your website. I don't believe this was an inappropriate use of the term.
I apologize if for whatever reason you believed that to be a personal attack - I did not intend to hurt you and if I did, I am sorry. But I will not retract what I wrote, because I believe it is accurate.
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!
53 comments
[ 2.9 ms ] story [ 82.4 ms ] threadOf course it is much older than React, but you can see it performs very well and is suited for things other than HTML elements.
Check out http://piqnt.com/o/
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.
It's just a scene graph.
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)
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.
Won't SVG achieve the same thing?
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.
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)
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.
See http://piqnt.com/o/ It is open source and available for iOS.
Theres another framework that uses pixi for the rendering but adds physics, sprite support and sound/input managers called Phaser ( http://phaser.io/ ), also open source.
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 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 optim...
You have also made personal attack ("your dishonesty"), probably because of your own competing library (jsil.org).
I ran your sample under a profiler and saw obvious problems with CPU and memory performance, and there are obvious problems with GPU efficiency based on your rendering model. This is not subjective; profiling is objective.
P.S. JSIL doesn't compete with your library, it's a cross-compiler. Are you angry because I gave a specific example of how I used one of the optimizations I suggested, or just because I'm pointing out that your website is dishonest?
EDIT: I shouldn't have to point this out either, but saying 'this text you wrote is dishonest' is not a personal attack. It's an objective criticism of your writing. Saying 'it's dishonest to call this library fast because x' is also not a personal attack, it is an objective criticism of your code.
If I called you a liar or a dishonest person, that would be a personal attack. I have not done that. I could certainly make that claim based on your argumentative style in this thread, but I shall not.
Your exact phrase is "your dishonestly", you could have described it as "inaccurate description" instead.
I apologize if for whatever reason you believed that to be a personal attack - I did not intend to hurt you and if I did, I am sorry. But I will not retract what I wrote, because I believe it is accurate.
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!