Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I don't want to be the guy that doesn't read the entire article, but the first sentence surprised me quite a bit:

> Despite vector graphics being used in every computer with a screen connected to it, rendering of vector shapes and text is still mostly a task for the CPU.

Do modern vector libraries really not use the GPU? One of the very first things I did when learning Vulkan was to use a fragment shader to draw a circle inside a square polygon. I always assumed that we've been using the GPU for pretty much any sort of vector rasterization, whether it was bezier curves or font rendering.



SVG paths can be arbitrarily complex. This article really doesn't discuss any of the actual hard cases. For example, imagine the character S rotated 1 degree and added to the path on top of itself in a full rotation. This is one path composed of 360 shapes. These begin and end fill sections (winding order changes) coincide in the same pixels at arbitrary angles (and the order of the hits is not automatically sorted!) but the final color cannot be arrived at correctly if you do not process all of the shapes at the same time. If you do them one at a time, you'll blend tiny (perhaps rounded to zero) bits of color and end up with a mess that looks nothing like what it should. These are often called conflation artifacts IIRC.

There's way more to this than drawing circles and rectangles, and these hard cases are why much of path / vector graphics filling still ends up being better on CPU where you can accumulate, sort, etc which takes a lot of the work away. CPU does basically per-Y whereas this is GPU per-pixel so perhaps they're almost equal if the GPU has the square of a CPU power. Obv this isn't quite right but gives you an idea.

Video discussing path filling on CPU (super sampling and trapezoid): https://youtu.be/Did21OYIrGI?t=318 We don't talk about the complex cases but this at least may help explain the simple stuff on CPU for those curious.


Is is it practical to ignore / approximate / offload the complex edge cases?


I want to say yes, but it depends on what you're actually doing as a final goal.

Detecting when a path is antagonistic to most GPU approaches takes time, as does preparing the data however it needs to be prepared on the CPU before being uploaded to the GPU. If you can just fill the whole thing on CPU in that time, you wasted your time even thinking about the GPU.

If you can identify a simple case quickly, it's probably totally a good idea to get the path done on the GPU unless you need to bring the pixels back to the CPU, maybe for writing to disk. The upload and then download can be way slower than just, again, filling on CPU.

If you're filling on GPU and then using on GPU (maybe as a web renderer or something), GPU is probably a big win. Except, this may not actually matter. If there is no need to re-render the path after the first time, it would be dumb to keep re-rendering on the GPU each frame / screen paint. Instead you'd want to put it into a texture. Well.... if you're only rendering once and putting into a texture, this whole conversation is maybe pointless? Then what is simple is probably the best idea. Anyway lots to 2d graphics that goes underappreciated!


No, mostly it is not practical to offload the edge cases.

The reason for this is that the single 2D application that people most want to speed up is font rendering. And font rendering is also the place where the edge cases are really common.

Rendering everything else (geometric shapes) is trivial by comparison.


Why is that? Glyphs can be cached in a texture. That's what nanovg does and it works quite well. That's what my little library does too (https://github.com/audulus/vger)


Doesn't work in the face of real-time dynamic transforms; 3-d, smooth zoom, etc. Atlases are also a bit heavy, so now you need a cache replacement policy, and you have annoying worst-case performance...


Only if you give up doing any script-based languages even remotely properly. And, it really doesn't even work in character-based languages with heavy kerning.

Text rendering is really complicated. There is a reason why we have so few text shaping engines.


Skia mostly uses the CPU -- it can draw some very basic stuff on the GPU, but text and curves are a CPU fallback. Quartz 2D is full CPU. cairo never got an acceptable GPU path. Direct2D is the tessellate-to-triangle approach. If you name a random vector graphics library, chances are 99% of the time it will be using the CPU.


Skia can tessellate curved paths.


Skia has code paths for everything: CPU path drawing, CPU tessellation followed by GPU rasterization with special paths for convex vs. concave paths, NV_path_rendering, Spinel/Skia Compute... It's actually hard to figure out what it's doing because it depends so much on the particular configuration.


3D vector graphics are not as full featured as 2d vector graphics.

2d vector graphics include things like "bones" and "tweening", which are CPU algorithms. (Much like how bone processing in 3d world is also CPU-side processing).

---------

Consider the creation of a Beizer curve, in 2d or 3d. Do you expect this to be a CPU algorithm, or GPU algorithm? Answer: clearly a CPU algorithm.

GPU algorithms generally are triangle-only, or close to it (ex: quads) as far as geometry. Sure, there are geometry shaders, but I don't think its common practice to take a Beizer Curve definition and write a Tesselator-shader for it and output (in parallel) a set of verticies. (And if someone is doing that, I'm interested in heading / learning more about it. It seems like a parallelizable algorithm to me but the devil is always in the details...).


GPUs have evolved away from being strictly triangle rasterizers. There are compute shaders that can do general purpose computing. The approach described here could in theory be set up by "drawing" a single quad - the whole screen, and it doesn't even need compute shaders but can be implemented using conventional vertex/fragment shaders with global buffer access (in OpenGL, UBOs or SSBOs).

There is a well-known paper that describes an approach how to draw bezier curves by "drawing" a single triangle. Checkout Loop-Blinn from 2005.


Ah yes, the https://en.wikipedia.org/wiki/Implicit_curve approach to filling curves. I have implemented a GPU vector renderer using that: https://github.com/Lichtso/contrast_renderer Here the implicit curve filling extracted as a shader toy: https://www.shadertoy.com/view/WlcBRn

It can even do cubic rational bezier curves, resolution independently. And to my knowledge it is the only renderer capable of that so far.


Your project sounds very impressive. I would like to try it out, unfortunately I'm unlikely to be able to get it to run by building Rust. If I understand correctly it should be able to run it on the Web, you have a demo somewhere? Or a video?


There is a WASM based web demo on GitHub: https://lichtso.github.io/contrast_renderer/

You will need to try different nightly browsers (I think Chrome works ATM), because the WebGPU API changes and breaks all the time. Also don't forget to enable WebGPU, you can check that here: https://wgpu.rs/examples-gpu/

The WASM port is highly experimental: It currently does not use interval handlers. So for animations to run you need to constantly move the mouse to provide frame triggering events. In WebGPU MSAA is limited to 4 samples ATM, so anti aliasing will look kind of bad in browsers. And the keyboard mapping is not configured, so typing in text fields produces gibberish.


That's a bummer, I tried with Chrome and Firefox but no luck. Can't be arsed to try different versions or obscure settings right now.

2 years ago I had a similar experience with WASM/WebGL, I tried to make use of emscripten in a sane way but it was painful to get things like event handling, file I/O and quality graphics to work. Results weren't great. When using specific libraries and coding the app in the right way from the start, porting GPU applications to the Web is allegedly easier.

If you could provide a fool-proof description how to build and set the project up in a few minutes, I would very much be willing to try your project out, it still sounds great. Or provide a few screenshots/videos just to get the idea across how it looks.


Seeing that this is from Microsoft research, and that Microsoft's font renderer has always looked nicer (and is known to be GPU-rendered to boot) makes a lot of sense.

Still, my point stands that this is relatively uncommon even in the realm of 3d programmers. Unity / Unreal engine doesn't seem to do GPU-side Beizer curve processing, even if the algorithm was researched by Microsoft from 2005.


What font renderer do you mean? I don't know about the internals of Microsoft renderers but vector graphics and font rasterization generally are distinct disciplines. This has started to change with higher-resolution displays, but font rasterization traditionally has been a black art involving things like grid snapping, stem darkening etc. Probably (but can't back this up) the most used font rasterization technologies are still Microsoft ClearType (are there implementations that use GPU??) and Freetype (strictly a CPU rasterizer). Don't know about MacOS, but I heard they don't do any of the advanced stuff and have less sharp fonts on low-dpi displays.

I would also like to know where Loop-Blinn is used in practice? I once did an implementation of quadratic Beziers using it, but I'm not up to doing the cubic version, it's very complex.


Microsoft's DirectWrite font renderer, which has been the default since Windows 7 IIRC: https://docs.microsoft.com/en-us/windows/win32/directwrite/d...

Its a blackbox. But Microsoft is very clear that its "hardware accelerated", whatever that means (IE: I think it means they got GPU-shaders handling a lot of details).

GDI / etc. etc. are legacy. You were supposed to start migrating towards Direct2D and DirectWrite decades ago. Cleartype itself moved to DirectWrite (though it still has GDI renderer for legacy purposes).

https://docs.microsoft.com/en-us/windows/win32/directwrite/i...


I'm not really experienced when it comes to GPU programming, so forgive me if I'm wrong with this, but some of the things you say don't make a lot of sense to me:

> 2d vector graphics include things like "bones" and "tweening", which are CPU algorithms. (Much like how bone processing in 3d world is also CPU-side processing).

Changing the position of bones does seem like something you would do on a CPU (or at least setting the indices of bone positions in a pre-loaded animation), but as far as I'm aware, 99% of the work for this sort of thing is done in a vertex shader as it's just matrix math to change vertex positions.

> Consider the creation of a Beizer curve, in 2d or 3d. Do you expect this to be a CPU algorithm, or GPU algorithm? Answer: clearly a CPU algorithm.

Why is it clearly a CPU algorithm? If you throw the bezier data into a uniform buffer, you can use a compute shader that writes to an image to just check if each pixel falls into the bounds of the curve. You don't need to use the graphics pipeline at all if you're not using vertices. Or even just throw a quad on the screen and jump straight to the fragment shader like I did with my circle vector.


> creation of a Beizer curve: ... clearly a CPU algorithm.

isn't this just a tessellation basically? GPU-based tessellation is very common, mostly for meshes but can be used for line-like figure too.


A few of the previous approaches are mentioned in Other work near the end. And from reading a few articles on the topic I got the impression that, yes, drawing a single shape in a shader seems almost trivial, vector graphics in general means mostly what PostScript/PDF/SVG are capable of these days. This means you don't just need filled shapes, but also strokes (and stroking in itself is a quite complicated problem), including dashed lines, line caps, etc. Gradients, image fills, blending modes are probably on the more trivial end, since I think those can all be easily solved in shaders.


In SVG, strokes has a separated specification only for them [1].

The specification has images that highlights some of the challenges.

[1] https://svgwg.org/specs/strokes/


There's definitely a lot of code out there that still does this only on the CPU, but the optimized implementations used in modern OSes, browsers and games won't.


Yes, it's like the article is trying to ignore why GPUs were invented in the first place.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: