 Thank you. So hello everyone, my name is Arjen Hinstra. I have been involved with KDE for a pretty long time. Currently I'm working at Blue Systems and one of my interest areas is graphics related, graphics programming mostly. Well, that's actually what today's talk is about. I've been working on some very much graphics related problems for the past two years and have found some interesting developments there. So this talk is about distance fields, sign distance fields specifically. But first I would like to explain some concepts behind, well everything that is being or everything that underpins these distance fields. So for starters, the current major APIs that use for 2D rendering on well everything basically are based on a model that's called the PostScript model. This model has been developed in the end of the 1980s and essentially it models a 2D plotter and it has a list of commands where you go, okay, move from point A to point B, then draw a line, then do something else. And that's how you end up rendering various shapes using this PostScript model. And as I mentioned, it's been the major API or the major model used by systems such as Q-Painter also, Canvas on the web is using this, and in fact, SCG model is based on this, which is all nice and everything. But then we have a different thing these days, which is called the GPU, which is so ubiquitous these days that everything, even mobile phones and a whole bunch of embedded systems even now feature this GPU. And it's a chip that has been designed for something completely different because it was originally meant to accelerate 3D rendering. And because of that, it's designed to be massively parallel. So, it's designed to process individual points and everything in a parallel way, which is completely different from how your CPU handles things, actually. And a development that has been around for a long time now was to be able to execute different small programs on this CPU to do various tasks to render things on screen. These programs are called programmable shaders, or shaders usually, which are these days written in some kind of high-level language. For example, and what I will be using in the rest of this talk, GLSL, which is the main shading language of used by OpenGL. These shaders will be executed during different stages of your rendering pipeline. So, for example, in this example, I have a Vertex shader, which converts points that you provide to the API to something that can then later on be resturized and rendered using the fragment shader, which I've also illustrated here. Which is a different way of handling things, and it comes with its own complexities. So, sign distance fields. As I said, we have this GPU, but we have a problem here, because it would be really nice if we can use this GPU for rendering much more complex 2D items so that we don't stress our CPU and leave that up to do other tasks, or because we simply have a GPU that's more capable of doing this. However, the PostScript model, as I'd illustrated before, is very much a serial process. It's executing tasks that go, OK, move, line, all that stuff. And every previous step actually depends on the previous step. So, this is a problem for a GPU, because the GPU is actually really, really bad at doing serial tasks. It's way better at doing massive parallel tasks, because that's how it was designed. Which means that we kind of need a new rendering model if we're going to render 2D on the GPU. And this is where distance fields come in. So, sign distance fields are at their most basic. They are maps that describes the distance from a point to a shape. In this example, I'm using a circle, which is the most basic distance field that you can get. And the distance field for a circle is basically the length of the point to the center of the circle minus the radius of the circle. Now, this is a bit complex if you're looking at it from the point of math. However, it does provide us with some properties, because what we can do is we can sample that function, that mathematical function at various points, get a distance out of that. And then, with that distance, we can calculate other effects. So, for example, here, if we have three different points in this distance field, where we have one point, A, which is on the edge, where the distance is zero, and then we have B, which is outside of the shape, which has a different distance, and then we have C, which is inside the shape, which has also a distance value. And you'll notice here that this is where the sign comes in, because B is outside the shape, so it has a positive value, whereas C is inside the shape, and it has a negative value. So, this sampling is very useful because we can determine where things lie. However, and this is actually where the GPU comes in, because a fragment shader is effectively capable of sampling this distance function for each pixel on the screen, which is something that basically comes for free if you're using a GPU, because that's what your GPU does anyway for the final rendering step in your pipeline. It needs to convert your points into something that's rendered on screen, and there's a step called restoration, which turns these points into a grid of individual pixels, and then for each pixel, it will execute your fragment shader to determine the final color. So, if you have a fragment shader that contains a sign distance field, you will get a sample for each pixel, or you can sample this distance field for each pixel. So, in this case, continuing with the circle example, I have converted the math expression to something in GLSL, which is, in fact, fairly simple to do, at least for a circle, because you can, GLSL simply has a length function, so you can get the length of the point of the sample, and then you subtract the radius, and you get the distance to the shape. In this case, I'm rendering directly the distance to this point, so you get a nice black blob. Of course, that's not very useful, so we need to do some extra operations with this distance field, with this distance value. And so, as said, distance fields, sign distance fields specifically, are signed for, which means that we can make use of this property to determine whether we're inside or outside of a shape. In this case, if we check, hey, is my distance bigger than zero, then I know we're outside of the shape, so we don't need to render anything, and we can just return a transparent color, whereas if we're inside the shape, we can actually return the color that we want the shape to have. Well, that said, this test is very binary, so it's not, I don't know if it's clear in the picture, but it leads to some artifact that's called aliasing, because right now we're saying either the pixel is inside the shape or it's outside the shape, and there's no, since we're rendering in a grid, we get a grid as an output, which doesn't lead to very nice shapes. However, since we have a distance, we can make use of that distance to improve this result, because rather than saying, just saying binary, hey, inside or outside, we can actually use the gradient from the distance to perform anti-aliasing almost for free. As an example here, where we previously simply checked the distance, now we are doing a mix operation using the distance with a multiplier because we want a very short curve, and then based on the, in this case, a linear interpolation between transparent and our shape color, we render the shape, which means that at some point close to the edge, rather than having a fully transparent or a fully colored pixel, we get a pixel that's only like half of the color, which is what effectively anti-aliasing is. So that's all nice and everything, but there, of course, rendering just circles isn't very useful unless you really, really would like to render circles. So luckily, there are many shapes that can be expressed as a distance field, and next to that, there are also many operations that can be done on these distance fields to achieve different results. As an example here, there's a, we have the basic transformations, translate, rotate, scale, which are already helpful, but then there are three operations which are similar to a concept called constructive solid geometry, which is something that CAD uses a lot, which allows us to combine these shapes into different results. So listen here, these examples are, so union which combines both shapes, we have a subtract where we subtract one shape from another leading to a new result, and then we have intersect, which provides an intersection between two shapes. And finally, we have a few operations that are unique to sign distance fields, which is annular in this case, which is best described as using our outlining the current distance field. We can easily round shapes by, and finally, we can just take the outline because wherever the distance is zero, we know that we're exactly on the edge of the shape. In this example, outline and annular don't really provide different results, but outline is useful for expanding, for creating different shapes. We have these, this technique called distance fields for, and this is all nice from a theory point of view, but what practical use does it have? Well, I started with this entire thing about two years ago because I had a specific problem to solve in a traditional GPU rendered way, you end up rendering a circle is actually somewhat tricky because your GPU works with points and lines between them. And this means that it doesn't know anything like curves or anything. So this means that if you want to render a circle, you need to approximate this circle. You need to come up with some way of effectively faking the circle. And this is, for example, what QtQuick currently does when you have a rectangle with rounded corners, it will use geometry to approximate the circle. However, if you can actually test this out, if you have a QtQuick rectangle at a very large size, you will actually end up seeing the result of this approximation. And I don't know if it's visible in the image, but you end up with straight edges that approximate this circle instead of an actual circle. So for my problem, or for me, this was a problem because I wanted to render something even a bit more complex, which is pie charts and a geometric approximation of a circle is easy enough to do for an actual circle. However, if you're rendering a pie chart, you have multiple segments of circles or not even full circles necessarily, you could actually have segments of a torus. And that makes the geometric approach way more complex, whereas if we use design distance fields, we can suddenly render actual circles, actual shapes without needing any trickery for approximations. Because another problem with the geometric approach is actually that those lines are by default, you will have artifacts, anti-aliasing artifacts. So you need to add additional approximations, additional tricks on top of that geometry to actually render a nice circle. Whereas our SDF has seen it provides a very natural way of rendering circles. So I discovered this while researching this topic and then realized, wait, this is great for doing pie charts because we can actually have a sign distance field where we have each segment as its own distance field. We render that segment and then we need no approximations because we can feed the shape, the data to the GPU. The GPU will go, okay, for each pixel, is this within the segment? Yes, no, using all the gradients and everything, and then we can get very nice looking pie charts fully accelerated on your GPU. So this proved, I experimented with this and this proved quite successful. My initial implementation of this actually used a number of, actually used a bunch of the CSG operations that I mentioned before because it used a circle and then cut away the parts that are not, so it ended up with a segment of a circle instead of using full circle. Later on, I found a distance field operation, a distance field function for a part of an arc like this. So that became unnecessary and the result was better that way but even the initial implementation was already better than an approximation in geometry would have been. So using that information from the pie chart, I went on and figured, well, what if we do the same with line charts? Line charts have similar problems. You have lines that will have aliasing problems if you use geometry for them. You want to be able to vary their thickness so you can't just render points or render line segments easily because you need to be able to control what these line segments look. So you would need to generate geometry for the entire line segments which becomes complex rather quickly. That said, the distance field behind the line chart implementation turned out to be a bit more complex as well because it's tricky to make a set of line segments this way. So what I ended up doing instead is that rather than trying to chain a bunch of line segments because then you lose information about what is above or below the line, this actually renders using a polygon. Effectively what it does is there is a polygon that is rendered using about this shape. And then if we need to fill it, we can just fill that entire shape and using some of the other operations like the annular and outline, we can get just the line on the side and render that as well. So this is all been implemented in the KQuickCharts framework and there's a bunch of other features there that I'm not going to into right now because those are related to distance fields. The distance fields do power two of the main chart types though. Another use case for distance fields came later. Kirigami has a concept called cards. They used to look approximately like this, which is okay visually, but there's actually a bit of an element missing here because these use sharp corners. Whereas a lot of elements within the breeze style and within our general visual teaming have rounded corners. But there was another problem with these because their implementation was rather suboptimal. You can't see it from the visual result, but in the background, what this did was actually create separate items for each shadowed edge. So there is an item here for the left edge, one for the bottom edge, one for the left bottom corner and all, et cetera. And that makes the implementation of this rather expensive. So I spent some time thinking, what if we could replace this entire background thing with all the items with just a single item to render this background. And that's how I created an item that's now called shadowed rectangle, which is a distance field of a rectangle with rounded corners that also includes a shadow, which meant that I had one item that would allow me to render this entire background, including a shadow without needing any other items and with a fairly simple implementation in the shader, actually, because you need the distance field for the rectangle, but after that, most of the other effects are simple operations on the distance field or on the distance that you get from the distance field. So this also allows extra features that current quick rectangle doesn't expose, like having different radiuses for different corners. Well, with this, I could replace the background of the cards. However, there was a remaining problem because some of the cards have an image in the top and if we render the background with rounded corners, that's all fine, but if we just place an image on top of it, either we lose the rounded corners on top or we end up with some rather expensive, needing to write some rather expensive code to cut off these corners of this image. Luckily, again, the distance field implementation provides a fairly simple solution here because what we can do is rather than fill the distance field with a solid color, we can query a texture, get a color from the texture and use that as the color of the shape if we're inside the distance, which means that rather than needing to do a whole complex set of operations to render a shape and query, use that as an opacity mask to cut off these corners, we can just use almost the same logic as the rectangle previously used and then use that to render a texture instead. So with that, I managed, I was able to recreate or replace the cards with a new background that used the shadow rectangle and use the shadow texture implementation to get rounded corners and also a new updated design for the cards and just generally looked a lot nicer than what we had before that. And well, both of these elements were very important because eventually what I did or what I was working on was a new version of our new system monitor for Plasma. And this system monitor is very heavy on charts because almost all the data sources you want to render as a chart. So you don't want to have to constantly fall back on CPU rendering. You instead want to make sure that your charts can update at a smooth frame rate, ideally whatever your refresh rate of your monitor is. So that's what I did to make this all possible. The pie charts are provided by Quick Charts. The line charts are provided by Quick Charts and both use the sign distance fields for rendering. And then the cards are provided by Kirigami now, but also use distance fields for rendering. So approximately 80% of what's being rendered by Plasma system monitor here is being rendered through distance fields. Well, and that's where I want to end this talk. I want to say a special thanks to, I'm going to fill on the pronunciation, but in Igo Quires, who has a whole set of articles about distance fields. So I'm going to go ahead and do a little bit of that. About distance fields on his website, and which is what most of this work has been based on. Any questions? Well, yes, thank you so much for your presentation. We do have a few questions. The first being, it might be misspelling, but is this shader approach fully portable to Qt's new RHI API? Or where do we have to be careful? So Qt RHI in Qt 6 requires using Qt Shader Tool, which wasn't available until Qt 6 was released. I haven't done actual porting yet, but the shaders, all that's needed is porting the shaders to Vulkan DLSL instead of Open DLSL, and then building this with Qt Shader Tools. And then we should, as far as I know, we should be able to build using Qt 6. So as far as I know, it's fully portable and there isn't any real limitations there. In fact, Qt 6 RHI actually makes things easier because right now we have to maintain a separate set of shaders for core profiles and a whole bunch of other tweaks just to make sure that we can support a various set of Open DLSL APIs, whereas in Qt 6, Shader Tools will take care of all that for us. All right. The next question, can we do visa curves with signed distance fields? Bezier curves, yes, there is a distance field for Bezier curves. That said, those implementations get expensive rather quickly, so it depends a bit on how many of these Bezier curves you want to render, whether that's feasible or not. Alternatively, what I do in Quick Charts for the line chart smoothing is that actually the processing of the smoothing is done on the CPU, and then this is rendered on the GPU as individual line segments. One final question for you. One of Qt Quick's text renderers is also SDF-based inspired by Valve SIG Graph Paper that popularized the technique. Is this usage broadly similar or has the SDF state of the art evolved? So I do know that the distance field rendering of Qt's text is indeed also based on this. The main difference there is that the text rendering uses pre-calculated values for the distance. It will generate a texture that encodes the distance for each point in the texture. Which is mostly because the actual text shapes would be way too complex to render directly on the GPU because you have all these features of fonts that you need to take care of. And basically, there are a large collection of very complex Bezier parts, which as I said previously, you can do, but it gets expensive. So they're mostly similar with the side note that distance field text is using pre-computer distance fields. Whereas what we're doing here is using the actual distance field function for rendering, which allows us to scale things much more easily.