shader like graphics programming - ugly

Started by
11 comments, last by Ravyne 10 years, 3 months ago

I would say that its not ugly, inconvenient, or constrained -- its just different than what you know, and so its way of solving problems are different than the way you think about solving the same problem. I made the comparisons between single-lane(CPU) / multi-lane (SIMD) programming earlier, and between procedural / functional programming. Both comparisons are apt -- changing your execution environment often means that you have to think about how to solve an old problem in a new way. Its not that the new way is any harder, its that you only have experience solving problems in the old way.

In most functional languages there's no loops, for example, iteration is accomplished by recursion, directly, or some other construct that hides the details but is implemented by recursion under the hood. If all you know are loops, functional programming looks like alien gobbledygook. But, when you stop thinking like a procedural (loops, side-effects) programmer, and start thinking like a functional (recursion, pattern-matching) programmer, then you start to see the elegant solutions that functional programming offers. Of course, functional programming is better suited to some problems than procedural programming, and the opposite can be said as well.

So, in summary, if the shader-based approach seems particularly hard for the problem at hand, one of two scenarios is likely; either you're trying to solve a problem that's not suited for a shader in the first place, or you're trying to solve a problem that *is* but you're not approaching the problem as a shader programmer should.

throw table_exception("(? ???)? ? ???");

Advertisement

I love the shader model of programming. It is a very nice way to structure highly parallel code, and exposes low-level benefits in a really manageable way. It also ensures the driver can do a lot of optimizations instead of those optimizations making my code ugly, hard to follow, and even harder to debug. Heck, shaders look better than SIMD code to me.

If you've only done the simple tutorial demos and stuff, shaders probably do look uglier. You basically have a bunch of setup and overhead stuff, but you don't have code complex and powerful enough to make it seem like the overhead is worthwhile. Think of it as the equivalent to a Photoshop tutorial that is teaching you to crop a picture. Photoshop seems pretty darn complicated, ugly, and inconvenient for just cropping a picture. But Photoshop can do a lot more than just cropping pictures once you go past that tutorial and learn more powerful features.

Just to further expand on what I mean a little -- lets say you wanted to use a shader to rasterize a line segment.

In the single-threaded CPU-based approach, the basic idea is to start at the top-most end of the line, calculate some information about the slope of that line, and then iterate over each pixel in the line accumulating slope counters that direct the pixel output left, right, or center until you reach the bottom. There are various optimizations for both performance and quality of output here, but that's the basic approach: start at one end and go to the other.

In a shader-based approach, the basic idea is to calculate the bounding box of the line segment, and then for each pixel inside the bounding box evaluate a function that describes the line by substituting in the pixel's coordinates. If the result indicates that the pixel is on the line--where 'on' means 'within a suitable margin of error'--then the pixel is lit; otherwise it is not. Again, there are ways to optimize both the performance (by finding and skipping large empty regions) and quality (anti-aliasing by setting the opacity of the pixel based on how close it is to the mathematically true line segment), but that's again the basic approach: determine the potential coverage (its bounding box) of the thing you want to draw (the line), and then evaluate a function for each pixel in the bounding box that tells you whether the pixel is inside or outside the thing.

Its worth noting two things about this scenario: The first is that if you are used to thinking procedural, the shader approach sounds horribly wasteful of computation resources, and in some ways the naive shader implementation is--however, this is why GPUs have thousands of simple, relatively slow processors, rather than just a few really complex and fast ones. The second is that rasterizing a line is actually a pathologically-bad case -- you have to do a lot of work to lite up very few pixels, and there's usually a ton of empty space in the bounding box; but for triangles or other full shapes, you typically get a lot more bang for your buck--there are a lot of pixels that are either fully-in or fully-out of the triangle, and you can determine groups of them very quickly.

Another thing worth mentioning is that achieving high-quality results from a shader-based approach is often a very straight-forward extrapolation of the simple algorithm, while achieving the same quality from a CPU-based approach is not -- for example, drawing an anti-aliased line on the CPU requires a quite different, slower, and much more complex algorithm than a line that doesn't eliminate aliasing. In the shader approach, all you have to change is varying the opacity of the pixel based on how close it is to the line -- this doesn't require a fundamentally different algorithm at all, it remains nearly as fast and is little-more complex than an aliased line. This also demonstrates one of the properties of massively-parallel approach, which is that once minimal-quality results are achieved, high-quality results are often close at hand.

throw table_exception("(? ???)? ? ???");

This topic is closed to new replies.

Advertisement