Hacker News new | ask | show | jobs
by ninetenfour 1746 days ago
I think you can tessellation on the CPU in an adaptive fashion (e.g. based on curvature or similar) and update that on a per frame (add, remove tessellation points) basis rather than re-tesselating from scratch each frame. The tessellation would just have weights from each control point.

You can modify the vertices on the CPU, update the tessellation points and update those to the GPU. Get 60fps should be possible even when updating 1Ks of vertices (probably more) per second. Do this for whatever changed using dirty lists or something so you do not update everything to the GPU on each frame. This should be relatively fast if you using ArrayBuffers.

100% this can be done in JavaScript in a very fast fashion. It may only seem like it can not be done if you are doing JS poorly. Maybe people make the mistake of not using ArrayBuffers for everything and that makes it slow. The main limitation in JS is the lack of really great multithreading, but I do not think you need it here.

If you want ultimate speed use WASM for the CPU-based updating of your geometry structures.

Anyhow, you are mistaken in your current beliefs and it will hurt you going forward.

2 comments

Thanks for all the advice, I know it is well-intended, but unfortunately this late in the project there is nothing that could change my mind anyway. It's too late.

But even if I had to start from scratch, I would still use C++. It is the most-used language in this field for rational reasons, not just for mistaken beliefs. Multi-threading is important, supporting custom pen tablets hardware is important, accessing Metal and Direct3D graphics API is important, being able to use the core as a shared library for command-line tools is important, being able to not crash when another tab makes the browser crash is important (okay, for this I could use electron). Being able to embed a Python interpreter is important (yes, it has to be Python it's a design decision). And we're not talking 1Ks vertices, but millions.

Again, AAA games are not developed in Javascript for a reason. Professional productivity graphics application have the same requirements than AAA games.

Tablets are important, so native apps for tablets makes sense if you think this is the main device people will use. ProCreate, Autodesk Sketchbook, etc. live on the tablet and native makes sense for that experience.

But then do not target desktop. You should target tablets and the pen-based interface on them.

I will also target iPads and Android tablets as native apps, yes. But most professional users use a powerful desktop computer with a Wacom Intuos or Cintiq. Some professional work requires hundreds of layers, and in the context of animation, you're not going to make the next Studio Ghibli animation on an iPad. You need your 64GBs or RAM.
> I think you can tessellation on the CPU in an adaptive fashion (e.g. based on curvature or similar) and update that on a per frame (add, remove tessellation points) basis rather than re-tesselating from scratch each frame.

The GPU’s hardware-implemented tessellation is (a) not compatible enough. It’s OK on Windows because Microsoft requires them for GPU vendors to declare the support of Direct3D 11. On the rest of the platforms, support across GPU vendors varies. And (b) doesn’t help much for 2D vector graphics. The hardware tessellation can be good for terrain, trees, or other triangle meshes in 3D space. Doesn’t help much for these Bezier curves/elliptical arcs for 2D shapes. Especially so for stroked paths.

Counter-intuitively, stroked paths are harder to render than filled ones. The offset of Bezier spline is not representable as another Bezier spline. Also, strokes have more parameters on input: line caps, join types, dashes, miter limit, etc.

> The main limitation in JS is the lack of really great multithreading

Also lack of SIMD. Also, the code in general is slow compared to C++, C# and many other statically typed languages. It’s incredibly hard to generate fast code from very dynamic languages like JS or Python, where everything is a hash map.

> I do not think you need it here.

Here’s my code which offloads CPU-bound pieces of 2D rendering to other CPU cores: https://github.com/Const-me/Vrmac/tree/master/Vrmac/Draw/Tes... Multithreading helped a lot.

> The GPU’s hardware-implemented tessellation is (a) not compatible enough.

I am unsure why we are talking about GPU-based tesselation again. I said do it on the CPU in my comment you are referencing.

> Counter-intuitively, stroked paths are harder to render than filled ones.

I would if it could be rendered via a 2D SDF? Similar to how you can do resolution independent fonts/decals via SDFs... basically use a cutoff distance from stroke midline or similar.

> Also, the code in general is slow compared to C++, C# and many other statically typed languages. It’s incredibly hard to generate fast code from very dynamic languages like JS or Python, where everything is a hash map.

The above is 100% incorrect. The trick is to not use slow hashmaps and then your code is near the speed of C++. Use ArrayBuffers instead for everything. I said this in my earlier comment.

> if it could be rendered via a 2D SDF?

I don’t think so. For one, computing SDF is a computationally hard problem.

Another thing, SDF will result in round joins, look at these pictures for available options: https://docs.microsoft.com/en-us/windows/win32/api/d2d1/ne-d... That particular spec is for MS Direct2D, but the rest of them offer the same features. Vector graphic formats are old and very stable. Similar story with line caps, SDF will get you round caps but sometimes you need different ones: https://docs.microsoft.com/en-us/windows/win32/api/d2d1/ne-d...

> The trick is to not use slow hashmaps

Every JavaScript object has such a slow hashmap, for the properties of that object.

> and then your code is near the speed of C++

JavaScript can only approach the speed of C++ for I/O bound things. For crunching numbers on CPU, the difference is rather large. Here’s some benchmark across many programming languages: https://benchmarksgame-team.pages.debian.net/benchmarksgame/ The average difference between C++ and JavaScript is 4x, for some problems it’s almost an order of magnitude.

Two reasons.

1. It’s hard to produce fast code from dynamically typed languages. CPU instructions are typed, different data types need different instructions.

2. C++ compilers work offline. In release builds, C++ optimizer can spend more time optimizing code. JavaScript JIT simply doesn’t have time to do these optimizations.

Thanks a lot Const-me for all your very accurate responses! I basically have nothing more to add.

Oh, and indeed, SDF is just completely irrelevant to the problem at hand here.

I would kindly suggest ninetenfour to avoid making bold statements without more domain experience. JS is great for many things. It's just not the tool for the job here.

SDF 2D fields are fun though.

Give you can create them from any cubic curve and it gives you offsets from it.

https://www.iquilezles.org/www/articles/distfunctions2d/dist...

If you offset in both direction from a curve, you can pick a start and end threshold to fill an outline. This only gives you rounded outline strokes. If you had separable tangential versus normal SDFs you could vary your end cap types.

Now how do you handle an arbitrary set of knots/control points in one shader? That is a separate problems for which there are a bunch of solutions.