[HN Gopher] Vector graphics on GPU
___________________________________________________________________
Vector graphics on GPU
Author : gsf_emergency_6
Score : 148 points
Date : 2026-01-03 06:08 UTC (4 days ago)
(HTM) web link (gasiulis.name)
(TXT) w3m dump (gasiulis.name)
| larodi wrote:
| Really, inst there anything which comes Slug-level of
| capabilities and is not super expensive?
| coffeeaddict1 wrote:
| Vello [0] might suit you although it's not production grade
| yet.
|
| [0] https://github.com/linebender/vello
| miguel_martin wrote:
| Just use blend2d - it is CPU only but it is plenty fast enough.
| Cache the rasterization to a texture if needed. Alternatively,
| see blaze by the same author as this article:
| https://gasiulis.name/parallel-rasterization-on-cpu/
| badlibrarian wrote:
| Author uses a lot of odd, confusing terminology and brings CPU
| baggage to the GPU creating the worst of both worlds. Shader
| hacks and CPU-bound partitioning and choosing the Greek letter
| alpha to be your accumulator in a graphics article? Oh my.
|
| NV_path_rendering solved this in 2011.
| https://developer.nvidia.com/nv-path-rendering
|
| It never became a standard but was a compile-time option in Skia
| for a long time. Skia of course solved this the right way.
|
| https://skia.org/
| bsder wrote:
| While the author doesn't seem to be aware of state of the art
| in the field, vector rendering is absolute _NOT_ a solved
| problem whether on CPU or GPU.
|
| Vello by Raph Levien seems to be a nice combination of what is
| required to pull this off on GPUs.
| https://www.youtube.com/watch?v=_sv8K190Zps
| lukan wrote:
| Yeah, I have high hopes for Vello to take off. I could throw
| away lots of hacks and caching and whatnot if I could do fast
| vector rendering reliable on the GPU.
|
| I think Rive also does vector rendering on the GPU
|
| https://rive.app/renderer
|
| But it is not really meant (yet?) as a general graphics
| libary, but just a renderer for the rive design tools.
| pier25 wrote:
| AFAIK you can use the Rive renderer in your C++ app.
|
| http://github.com/rive-app/rive-runtime
| bean469 wrote:
| > While the author doesn't seem to be aware of state of the
| art in the field
|
| The blog post is from 2022, though
| sirwhinesalot wrote:
| So what is the right way that Skia uses? Why is there still
| discussion on how to do vector graphics on the GPU right if
| Skia's approach is good enough?
|
| Not being sarcastic, genuinely curious.
| cyberax wrote:
| The major unsolved problem is real-time high-quality text
| rendering on GPU. Skia just renders fonts on the CPU with all
| kinds of hacks (
| https://skia.org/docs/dev/design/raster_tragedy/ ). It then
| renders them as textures.
|
| Ideally, we want to have as much stuff rendered on the GPU as
| possible. Ideally with support for glyph layout. This is not
| at all trivial, especially for complex languages like
| Devanagari.
|
| In the perfect world, we want to be able to create a 3D cube
| and just have the renderer put the text on one of its facets.
| And have it rendered perfectly as you rotate the cube.
| exDM69 wrote:
| > NV_path_rendering solved this in 2011.
|
| By no means is this a solved problem.
|
| NV_path_rendering is an implementation of "stencil then cover"
| method with a lot of CPU preprocessing.
|
| It's also only available on OpenGL, not on any other graphics
| API.
|
| The STC method scales very badly with increasing resolutions as
| it is using a lot of fill rate and memory bandwidth.
|
| It's mostly using GPU fixed function units (rasterizer and
| stencil test), leaving the "shader cores" practically idle.
|
| There's a lot of room for improvement to get more performance
| and better GPU utilization.
| Asm2D wrote:
| You know nothing.
|
| Skia is definitely not a good example at all. Skia started as a
| CPU renderer, and added GPU rendering later, which heavily
| relies on caching. Vello, for example, takes a completely
| different approach compared to Skia.
|
| NV path rendering is a joke. nVidia though that ALL graphics
| would be rendered on GPU within 2 years after making the
| presentation, and it took 2 decades and 2D CPU renderers still
| shine.
| nicoburns wrote:
| I believe Skia's new Graphite architecture is much more
| similar to Vello
| badlibrarian wrote:
| Right. The question is does Skia grows its broad and useful
| toolkit with an eye toward further GPU optimization? Or
| does Vello (broadened and perhaps burdened by Rust and the
| shader-obsessive crowd) grow a broad and useful API?
|
| There's also the issue of just how many billions of line
| segments you really need to draw every 1/120th of a second
| at 8K resolution, but I'll leave those discussions to dark-
| gray Discord forums rendered by Skia in a browser.
| coffeeaddict1 wrote:
| > There's also the issue of just how many billions of
| line segments you really need to draw every 1/120th of a
| second at 8K resolution
|
| IMO, one of biggest benefit of a high performance
| renderer would be power savings (very important for
| laptops and phones). If I can run the same work but use
| half the power, then by all means I'd be happy to deal
| with the complications that the GPU brings. AFAIK though,
| no one really cares about that and even efforts like
| Vello are just targeting fps gains, which do correlate
| with reduced power consumption but only indirectly.
| badlibrarian wrote:
| It's an argument you can make in any performance effort.
| But I think the "let's save power using GPUs" ship sailed
| even before Microsoft started buying nuclear reactors to
| power them.
| Asm2D wrote:
| Adding a power draw into the mix is pretty interesting.
| Just because a GPU can render something 2x faster in a
| particular test doesn't mean you have consumed 50% less
| power, especially when we talk about dedicated GPUs that
| can have power draw in hundreds of watts.
|
| Historically 2D rendering on CPU was pretty much single-
| threaded. Skia is single-threaded, Cairo too, Qt mostly
| (they offload gradient rendering to threads, but it's
| painfully slow for small gradients, worse than single-
| threaded), AGG is single-threaded, etc...
|
| In the end only Blend2D, Blaze, and now Vello can use
| multiple threads on CPU, so finally CPU vs GPU
| comparisons can be made more fairy - and power draw is
| definitely a nice property of a benchmark. BTW Blend2D
| was probably the first library to offer multi-threaded
| rendering on CPU (just an option to pass to the rendering
| context, same API).
|
| As far as I know - nobody did a good benchmarking between
| CPU and GPU 2D renderers - it's very hard to do
| completely unbiased comparison, and you would be
| surprised how good the CPU is in this mix. Modern CPU
| cores consume maybe few watts and you can render to a 4K
| framebuffer with that single CPU core. Put rendering text
| to the mix and the numbers would start to be very
| interesting. Also GPU memory allocation should be
| included, because rendering fonts on GPU means to pre-
| process them as well, etc...
|
| 2D is just very hard, on both CPU and GPU you would be
| solving a little bit different problems, but doing it
| right is insane amount of work, research, and
| experimentation.
| nicoburns wrote:
| It's not a formal benchmark, but my Browser Engine /
| Webview (https://github.com/DioxusLabs/blitz/) has
| pluggable rendering backends (via
| https://github.com/DioxusLabs/anyrender) with Vello
| (GPU), Vello CPU, Skia (various backends incl. Vulkan,
| Metal, OpenGL, and CPU) currently implemented
|
| On my Apple M1 Pro, the Vello CPU renderer is competitive
| with the GPU renderers on simple scenes, but falls behind
| on more complex ones. And especially seems to struggle
| with large raster images. This is also without a glyph
| cache (so re-rasterizing every glyph every time, although
| there is a hinting cache) which isn't implemented yet.
| This is dependent on multi-threading being enabled and
| can consume largish portions of all-core CPU while it
| runs. Skia raster (CPU) gets similarish numbers, which is
| quite impressive if that is single-threaded.
| virtualritz wrote:
| Unless I miss something I think that this describes box
| filtering.
|
| It should probably mention that that this is only sufficient for
| some use cases but not for high quality ones.
|
| E.g. if you were to use this e.g. for rendering font glyphs into
| something like a static image (or a slow rolling title/credits)
| you probably want a higher quality filter.
| jstimpfle wrote:
| What type of filter do you mean? Unless I'm
| misunderstanding/missing something, the approach described
| doesn't go into the details of how coverage is computed. If the
| input image is only simple lines whose coverage can be
| correctly computed (don't know how to do this for curves?) then
| what's missing?
|
| I'd be interested how feasible complete 2D UIs using
| dynamically GPU rendered vector graphics are. I've played with
| vector rendering in the past, using a pixel shader that more or
| less implemented the method described in the OP. Could render
| the ghost script tiger at good speeds (like 1-digit
| milliseconds at 4K IIRC), but there is always an overhead to
| generating vector paths, sampling them into line segments,
| dispatching them etc... Building a 2D UI based on optimized
| primitives instead, like axis-aligned rects and rounded rects,
| mostly will always be faster, obviously.
|
| Text rendering typically adds pixel snapping, possibly using
| byte code interpreter, and often adds sub-pixel rendering.
| jlokier wrote:
| > If the input image is only simple lines whose coverage can
| be correctly computed (don't know how to do this for curves?)
| then what's missing?
|
| Computing pixel coverage accurately isn't enough for the best
| results. Using it as the alpha channel for blending forground
| over background colour is the same thing as sampling a box
| filter applied to the underlying continuous vector image.
|
| But often a box filter isn't ideal.
|
| Pixels on the physical screen have a shape and non-uniform
| intensity across their surface.
|
| RGB sub-pixels (or other colour basis) are often at different
| positions, and the perceptual luminance differs between sub-
| pixels in addition to the non-uniform intensity.
|
| If you don't want to tune rendering for a particular display,
| there are sometimes still improvements from using a non-box
| filter
|
| An alternative is to compute the 2D integral of a filter
| kernel over the coverage area for each pixel. If the kernel
| has separate R, G, B components, to account for sub-pixel
| geometry, then you may require another function to optimise
| perceptual luminance while minimising colour fringing on
| detailed geometries.
|
| Gamma correction helps, and fortunately that's easily
| combined with coverage. For example, slow rolling
| tile/credits will shimmer less at the edges if gamme is
| applied correctly.
|
| However, these days with Retina/HiDPI-style displays, these
| issues are reduced.
|
| For example, MacOS removed sub-pixel anti-aliasing from text
| rendering in recent years, because they expect you to use a
| Retina display, and they've decided regular whole-pixel
| coverage anti-aliasing is good enough on those.
| dahart wrote:
| > What type of filter do you mean? [...] the approach
| described doesn't go into the details of how coverage is
| computed
|
| This article does clip against a square pixel's edges, and
| sums the area of what's inside without weighting, which is
| equivalent to a box filter. (A box filter is also what you
| get if you super-sample the pixel with an infinite number of
| samples and then use the average value of all the samples.)
| The problem is that there are cases where this approach can
| result in visible aliasing, even though it's an analytic
| method.
|
| When you want high quality anti-aliasing, you need to model
| pixels as soft leaky overlapping blobs, not little squares.
| Instead of clipping at the pixel edges, you need to clip
| further away, and weight the middle of the region more than
| the outer edges. There's no analytic method and no perfect
| filter, there are just tradeoffs that you have to balance.
| Often people use filters like Triangle, Lanczos, Mitchell,
| Gaussian, etc.. These all provide better anti-aliasing
| properties than clipping against a square.
| masswerk wrote:
| May require "(2022)" in the title.
| xattt wrote:
| Tangential, but was this not the goal of Quartz 2D? The idea of
| everyday things running on the GPU seemed very attractive.
|
| There is some context in this 13-year-old discussion:
| https://news.ycombinator.com/item?id=5345905#5346541
|
| I am curious if the equation of CPU-determined graphics being
| faster than being done on the GPU has changed in the last decade.
|
| Did Quartz 2D ever become enabled on macOS?
| kllrnohj wrote:
| When things like this (or Vello or piet-gpu or etc...) talk
| about "vector graphics on GPU" they are near exclusively
| talking only about essentially a full solve solution. A generic
| solution that handles fonts and svgs and arbitrarily complex
| paths with strokes and fills and the whole shebang.
|
| These are great goals, but also largely inconsequential with
| nearly all UI designs. The majority of systems today (like
| skia) are hybrids. Things like simple shapes (eg, round rects)
| have analytical shaders on the GPU and complex paths (like
| fronts) are just done on the CPU once and cached on the GPU in
| a texture. It's a very robust, fast approach to the wholistic
| problem, at the cost of not being as "clean" of a solution like
| a pure GPU renderer would be.
| jacobp100 wrote:
| > I am curious if the equation of CPU-determined graphics being
| faster than being done on the GPU has changed in the last
| decade
|
| If you look at Blend2D (a CPU rasterizer), they seem to
| outperform every other rasterizer including GPU-based ones -
| according to their own benchmarks at least
| Asm2D wrote:
| Blend2D doesn't benchmark against GPU renderers - the
| benchmarking page compares CPU renderers. I have seen
| comparisons in the past, but it's pretty difficult to do a
| good CPU vs GPU benchmarking.
| miguel_martin wrote:
| Blaze outperforms Blend2D - by the same author as the
| article: https://gasiulis.name/parallel-rasterization-on-cpu/
| - but to be fair, Blend2D is really fast.
| Asm2D wrote:
| You need to rerun the benchmarks if you want fresh numbers.
| The post was written when Blend2D didn't have JIT for
| AArch64, which penalized it a bit. Also on X86_64 the
| numbers are really good for Blend2D, which beats Blaze in
| some tests. So it's not black&white.
|
| And please keep in mind that Blend2D is not really in
| development anymore - it has no funding so the project is
| basically done.
| coffeeaddict1 wrote:
| > And please keep in mind that Blend2D is not really in
| development anymore - it has no funding so the project is
| basically done.
|
| That's such a shame. Thanks a lot for Blend2D! I wish
| companies were less greedy and would fund amazing
| projects like yours. Unfortunately, I do think that
| everyone is a bit obsessed with GPUs nowadays. For 2D
| rendering the CPU is great, especially if you want
| predictable results and avoid having to deal with the
| countless driver bugs that plague every GPU vendor.
| samiv wrote:
| The issue is not performance the issue is that pixel precise
| operations are difficult on the GPU using graphics features
| such as shaders.
|
| You don't normally work with pixels but you work with polygonal
| geometry (triangles) and the GPU does the pixel (fragment)
| rasterization.
| pjmlp wrote:
| Not sure what you mean, it can make use of accelerated
| graphics,
|
| https://developer.apple.com/library/archive/documentation/Gr...
| willtemperley wrote:
| Quartz 2D is now CoreGraphics. It's hard to find information
| about the backend, presumably for commercial reasons. I do know
| it uses the GPU for some operations like magnifyEffect.
|
| Today I was smoothly panning and zooming 30K vertex polygons
| with SwiftUI Canvas and it was barely touching the CPU so I
| suspect it uses the GPU heavily. Either way it's getting very
| good. There's barely any need to use render caches.
| nubskr wrote:
| Turns out the best GPU optimization is just being too scared of
| graphics drivers to do the fancy stuff, 10-15x faster and you can
| actually debug it.
| jayd16 wrote:
| So without blowing up the traditional shader pipeline, why is it
| not trivial to add a path stage as an alternative to the vertex
| stage? It seems like GPUs and shader language could implement a
| standard way to turn vector paths into fragments and keep the
| rest of the pipeline.
|
| In fact, you could likely use the geometry stage to create
| arbitrarily dense vertices based on path data passed to the
| shader without needing any new GPU features.
|
| Why is this not done? Is the CPU render still faster than these
| options?
| exDM69 wrote:
| > why is it not trivial to add a path stage as an alternative
| to the vertex stage?
|
| Because paths, unlike triangles are not fixed size or have
| screen space locality. Paths consist of multiple contours of
| segments, typically cubic bezier curves and a winding rule.
|
| You can't draw one segment out of a contour on the screen and
| continue to the next one, let alone do them in parallel. A
| vertical line segment on the left hand side going bottom to top
| of your screen will make every pixel to the right of it
| "inside" the path, but if there's another line segment going
| top to bottom somewhere the pixel and it's outside again.
|
| You need to evaluate the winding rule for every curve segment
| on every pixel and sum it up.
|
| By contrast, all the pixels inside the triangle are also inside
| the bounding box of the triangle and the inside/outside test
| for a pixel is trivially simple.
|
| There are at least four popular approaches to GPU vector
| graphics:
|
| 1) Loop-Blinn: Use CPU to tessellate the path to triangles on
| the inside and on the edges of the paths. Use a special shader
| with some tricks to evaluate a bezier curve for the triangles
| on the edges.
|
| 2) Stencil then cover: For each line segment in a tessellated
| curve, draw a rectangle that extends to the left edge of the
| contour and use two sided stencil function to add +1 or -1 to
| the stencil buffer. Draw another rectangle on top of the whole
| path and set the stencil test to draw only where the stencil
| buffer is non-zero (or even/odd) according to the winding rule.
|
| 3) Draw a rectangle with a special shader that evaluates all
| the curves in a path, and use a spatial data structure to skip
| some. Useful for fonts and quadratic bezier curves, not full
| vector graphics. Much faster than the other methods for simple
| and small (pixel size) filled paths. Example: Lengyel's method
| / Slug library.
|
| 4) Compute based methods such as the one in this article or
| Raph Levien's work: use a grid based system with tessellated
| line segments to limit the number of curves that have to be
| evaluated per pixel.
|
| Now this is only filling paths, which is the easy part.
| Stroking paths is much more difficult. Full SVG support has
| both and much more.
|
| > In fact, you could likely use the geometry stage to create
| arbitrarily dense vertices based on path data passed to the
| shader without needing any new GPU features.
|
| Geometry shaders are commonly used with stencil-then-cover to
| avoid a CPU preprocessing step.
|
| But none of the GPU geometry stages (geometry, tessellation or
| mesh shaders) are powerful enough to deal with all the corner
| cases of tessellating vector graphics paths, self
| intersections, cusps, holes, degenerate curves etc. It's not a
| very parallel friendly problem.
|
| > Why is this not done?
|
| As I've described here: all of these ideas have been done with
| varying degrees of success.
|
| > Is the CPU render still faster than these options?
|
| No, the fastest methods are a combination of CPU preprocessing
| for the difficult geometry problems and GPU for blasting out
| the pixels.
| Dwedit wrote:
| According to the page here:
| https://www.humus.name/index.php?page=News&ID=228
|
| The best way to draw a circle on a GPU is to start with a large
| triangle, and keep adding additional triangles on the edges until
| you've reached the point where you do not need to add any more
| triangles (smaller than a pixel)
| jesse__ wrote:
| I'd put money on that the best way is actually to draw a quad,
| or single triangle, and draw the circle as a SDF in the
| fragment shader
| Lichtso wrote:
| > but [analytic anti-aliasing (aaa)] also has much better quality
| than what can be practically achieved with supersampling
|
| What this statement is missing is that aaa coverage is
| immediately resolved, while msaa coverage is resolved later in a
| separate step with extra data being buffered in between. This is
| important because msaa is unbiased while aaa is biased towards
| too much coverage once two paths partially cover the same pixel.
| In other words aaa becomes incorrect once you draw overlapping or
| self-intersecting paths.
|
| Think about drawing the same path over and over at the same
| place: aaa will become darker with every iteration, msaa is
| idempotent and will not change further after the first iteration.
|
| Unfortunately, this is a little known fact even in the exquisite
| circles of 2D vector graphics people, often presenting aaa as the
| silver bullet, which it is not.
| jesse__ wrote:
| Interestingly they do not cite calculating a signed distance to
| the surface of the shape as an approach to doing AA, as described
| in the Valve paper [1]. I suppose this is more targeted at
| offline baking, but given they're suggesting iterating every
| curve at every pixel, I'm not sure why you wouldn't.
|
| [1]
| https://steamcdn-a.akamaihd.net/apps/valve/2007/SIGGRAPH2007...
___________________________________________________________________
(page generated 2026-01-07 23:01 UTC)