Metaprogramming GPUs with Sh
Martin Ecker writes "With the advent of powerful, programmable GPUs in consumer graphics hardware, an increasing number of shading languages to program these GPUs has become available. One quite interesting language that - in many ways - has a very different approach than other mainstream shading languages (such as Cg or the OpenGL Shading Language) is Sh. The recently released book "Metaprogramming GPUs with Sh" by Michael McCool and Stefanus Du Toit, both major contributors to the Sh project, explains the basics of the Sh high-level shading language and the corresponding API and also goes into some of the details of the implementation. The book is intended for an audience that is already familiar with traditional shader development for programmable GPUs. Also, a firm background in 3D graphics programming and C++ is a must for the interested reader." Read on for the rest.
Metaprogramming GPUs with Sh
author
Michael McCool, Stefanus Du Toit
pages
308
publisher
A K Peters
rating
7/10
reviewer
Martin Ecker
ISBN
0321197895
summary
A book that describes an interesting shading language and accompanying API to program GPUs.
Before discussing the book in more detail, I will try to give a basic overview of Sh, since most readers will not be familiar with it. For a more in-depth look at Sh, I recommend taking a look at a recently posted Gamasutra article by Michael McCool (http://www.gamasutra.com/features/20040716/mccool _01.shtml), the paper on Sh from the authors presented at the recently held SIGGRAPH 2004 conference (http://www.cgl.uwaterloo.ca/Projects/rendering/Pa pers/#algebra), and of course the Sh homepage at http://www.libsh.org.
Sh started out as a research project at the University of Waterloo (http://www.cgl.uwaterloo.ca), and it is both a shading language and a runtime API to use the Sh shaders. As a shading language Sh is embedded into C++ as a domain-specific language, which is made possible by using C++ operator overloading and by defining special tuple and matrix types that are used extensively in shader code. So instead of defining its own language that requires a full compiler, like other shading languages do, Sh uses regular C++ syntax to describe shader code, which is then dynamically (at runtime) compiled to a specific backend, such as a GPU or possibly even the CPU. In addition to compiling to a specific GPU or CPU target, Sh can also be used in a special stream mode where a shader is applied to a stream of input tuples. This is very useful for general purpose GPU programming where the GPU is basically used as an additional processor to the host CPU (see http://www.gpgpu.org for more information on the subject). Finally, Sh code can also be executed in an immediate mode where every Sh statement is directly executed on the host CPU (without being compiled into a shader program), which makes it very easy to debug shaders with any host debugger running on the CPU.
Due to the way Sh is embedded into C++, the full range of abstraction mechanisms offered by C++ can be used to structure and modularize shader code. Abstract base classes, regular functions, templates, and any other features offered by C++ can be used to develop shaders. This is an interesting consequence of the metaprogramming approach of Sh that also allows the use of software engineering principles in shader development, such as object orientation, that other shading languages currently cannot offer.
This kind of metaprogramming in C++ is used by an increasing number of libraries. For example, the Spirit parser framework (see http://spirit.sourceforge.net) uses a similar approach to describe and generate parsers directly in C++ instead of using traditional external tools, such as yacc or bison.
One of the most fascinating features of the Sh toolkit is the possibility to combine and connect shader programs to form new shader programs, which allows one to easily build complex shaders out of simple shader fragments. In a more general sense, Sh provides what can be called a shader algebra (see also the aforementioned SIGGRAPH 2004 paper), where shader programs are the objects on which special operators to combine and connect them are defined. An interesting application of this shader algebra is to specifically bind certain varying shader inputs to uniform variables and the other way around (this is what functional programming languages usually call currying). Also combining a matrix palette skinning shader with any light model shader (or any shaders that perform specific tasks, for that matter) is easily possible.
After this short introduction to the Sh toolkit, we shall now take a closer look at the book "Metaprogramming GPUs with Sh".
The book is split into three parts, an introduction, a reference, and an engineering overview.
The introduction consists of the first five chapters and discusses the basics of the Sh shading language and the API. In particular, the tuple and matrix types and the operators defined on them are presented. The way shader programs are defined and how parameters and attributes are handled is discussed, followed by the way textures are represented. Finally, the stream and channel concept used to feed data into shader programs is discussed. These introductory chapters contain a number of examples that demonstrate the presented concepts. Chapter three contains a quite interesting sample shader that uses constructive solid geometry techniques and metaprogramming in Sh to render text. While not the most useful use case, the shader shows some interesting capabilities of Sh, in particular the shader algebra operators. Chapter four on textures has some more nice sample shaders for doing shiny bump mapping, rendering wood and marble, and using Worley noise.
The second part of the book is a reference on Sh. Unlike references in many other computer books, this is not just a technical listing of the available features of Sh but is written in regular prose (with the occasional reference-like table here and there). The six chapters of the reference section describe how to setup and use the Sh library, and then discuss the available types, operators, and standard library functions more thoroughly than in the introduction. Additionally, the available backends are mentioned in the last chapter of this part of the book. A draft of the reference manual can also be found online at http://www.libsh.org/ref/online.
The final part of the book deals with engineering aspects of Sh. These final five chapters of the book discuss the details of the current implementation. The intermediate representation for shaders that is used by Sh is presented as well as how streams and textures are managed and stored internally. The interface between the Sh frontend and the various specific backends is discussed, as well as the current state of the optimizer including some further improvements that are planned in the future.
The images in the book are all in black and white except for 14 color plates in the middle of the book. The color plates and other images usually show teapots or animals, so they aren't all that exciting, but do demonstrate what the sample shaders presented in the book look like.
The book does not come with a CD-ROM, but with such a young library that is still under heavy development, putting a snapshot of the library's source code base on a CD-ROM would be a waste of resources. Sh itself as well as all sample shaders presented in the book can be downloaded from the Sh homepage at http://www.libsh.org. This website also has additional documentation, including some papers and the API reference documentation generated with Doxygen from the sources. Sh is distributed under a very liberal open source license (based on the zlib/libpng license) that also allows commercial use.
For the reader with enough expertise in 3D and shader programming, this book provides a concise and well-written introduction to Sh. The book will definitely contribute to enlarging the currently relative small user base of Sh and hopefully help the library grow and get more refined in the near future. Everyone familiar with "regular" high-level shading languages, such as Cg or the OpenGL Shading Language, should take a look at this book to see a new and interesting way of programming GPUs that the aforementioned languages do not offer.
About the review author:
The author has been involved in real-time graphics programming for more than 9 years and works as a games developer for arcade games. In his rare spare time he works on a graphics-related open source project called XEngine http://xengine.sourceforge.net.
Before discussing the book in more detail, I will try to give a basic overview of Sh, since most readers will not be familiar with it. For a more in-depth look at Sh, I recommend taking a look at a recently posted Gamasutra article by Michael McCool (http://www.gamasutra.com/features/20040716/mccool _01.shtml), the paper on Sh from the authors presented at the recently held SIGGRAPH 2004 conference (http://www.cgl.uwaterloo.ca/Projects/rendering/Pa pers/#algebra), and of course the Sh homepage at http://www.libsh.org.
Sh started out as a research project at the University of Waterloo (http://www.cgl.uwaterloo.ca), and it is both a shading language and a runtime API to use the Sh shaders. As a shading language Sh is embedded into C++ as a domain-specific language, which is made possible by using C++ operator overloading and by defining special tuple and matrix types that are used extensively in shader code. So instead of defining its own language that requires a full compiler, like other shading languages do, Sh uses regular C++ syntax to describe shader code, which is then dynamically (at runtime) compiled to a specific backend, such as a GPU or possibly even the CPU. In addition to compiling to a specific GPU or CPU target, Sh can also be used in a special stream mode where a shader is applied to a stream of input tuples. This is very useful for general purpose GPU programming where the GPU is basically used as an additional processor to the host CPU (see http://www.gpgpu.org for more information on the subject). Finally, Sh code can also be executed in an immediate mode where every Sh statement is directly executed on the host CPU (without being compiled into a shader program), which makes it very easy to debug shaders with any host debugger running on the CPU.
Due to the way Sh is embedded into C++, the full range of abstraction mechanisms offered by C++ can be used to structure and modularize shader code. Abstract base classes, regular functions, templates, and any other features offered by C++ can be used to develop shaders. This is an interesting consequence of the metaprogramming approach of Sh that also allows the use of software engineering principles in shader development, such as object orientation, that other shading languages currently cannot offer.
This kind of metaprogramming in C++ is used by an increasing number of libraries. For example, the Spirit parser framework (see http://spirit.sourceforge.net) uses a similar approach to describe and generate parsers directly in C++ instead of using traditional external tools, such as yacc or bison.
One of the most fascinating features of the Sh toolkit is the possibility to combine and connect shader programs to form new shader programs, which allows one to easily build complex shaders out of simple shader fragments. In a more general sense, Sh provides what can be called a shader algebra (see also the aforementioned SIGGRAPH 2004 paper), where shader programs are the objects on which special operators to combine and connect them are defined. An interesting application of this shader algebra is to specifically bind certain varying shader inputs to uniform variables and the other way around (this is what functional programming languages usually call currying). Also combining a matrix palette skinning shader with any light model shader (or any shaders that perform specific tasks, for that matter) is easily possible.
After this short introduction to the Sh toolkit, we shall now take a closer look at the book "Metaprogramming GPUs with Sh".
The book is split into three parts, an introduction, a reference, and an engineering overview.
The introduction consists of the first five chapters and discusses the basics of the Sh shading language and the API. In particular, the tuple and matrix types and the operators defined on them are presented. The way shader programs are defined and how parameters and attributes are handled is discussed, followed by the way textures are represented. Finally, the stream and channel concept used to feed data into shader programs is discussed. These introductory chapters contain a number of examples that demonstrate the presented concepts. Chapter three contains a quite interesting sample shader that uses constructive solid geometry techniques and metaprogramming in Sh to render text. While not the most useful use case, the shader shows some interesting capabilities of Sh, in particular the shader algebra operators. Chapter four on textures has some more nice sample shaders for doing shiny bump mapping, rendering wood and marble, and using Worley noise.
The second part of the book is a reference on Sh. Unlike references in many other computer books, this is not just a technical listing of the available features of Sh but is written in regular prose (with the occasional reference-like table here and there). The six chapters of the reference section describe how to setup and use the Sh library, and then discuss the available types, operators, and standard library functions more thoroughly than in the introduction. Additionally, the available backends are mentioned in the last chapter of this part of the book. A draft of the reference manual can also be found online at http://www.libsh.org/ref/online.
The final part of the book deals with engineering aspects of Sh. These final five chapters of the book discuss the details of the current implementation. The intermediate representation for shaders that is used by Sh is presented as well as how streams and textures are managed and stored internally. The interface between the Sh frontend and the various specific backends is discussed, as well as the current state of the optimizer including some further improvements that are planned in the future.
The images in the book are all in black and white except for 14 color plates in the middle of the book. The color plates and other images usually show teapots or animals, so they aren't all that exciting, but do demonstrate what the sample shaders presented in the book look like.
The book does not come with a CD-ROM, but with such a young library that is still under heavy development, putting a snapshot of the library's source code base on a CD-ROM would be a waste of resources. Sh itself as well as all sample shaders presented in the book can be downloaded from the Sh homepage at http://www.libsh.org. This website also has additional documentation, including some papers and the API reference documentation generated with Doxygen from the sources. Sh is distributed under a very liberal open source license (based on the zlib/libpng license) that also allows commercial use.
For the reader with enough expertise in 3D and shader programming, this book provides a concise and well-written introduction to Sh. The book will definitely contribute to enlarging the currently relative small user base of Sh and hopefully help the library grow and get more refined in the near future. Everyone familiar with "regular" high-level shading languages, such as Cg or the OpenGL Shading Language, should take a look at this book to see a new and interesting way of programming GPUs that the aforementioned languages do not offer.
About the review author:
The author has been involved in real-time graphics programming for more than 9 years and works as a games developer for arcade games. In his rare spare time he works on a graphics-related open source project called XEngine http://xengine.sourceforge.net.
You can purchase Metaprogramming GPUs with Sh from bn.com. Slashdot welcomes readers' book reviews -- to see your own review here, read the book review guidelines, then visit the submission page.
Or, buy it from Amazon
This space for rent
The GPGPU course was at the top of my list for SIGGRAPH 2004 this year, and I was impressed with all of the presenters' material. However, in my estimation, Sh is built to more closely resemble the existing HLSL for DX and similar GLSLang from the ARB.
Brook, on the other hand, was written from a more C-like perspective, and approaches the GPU as a massively data-parallel stream processor (well, Sh does as well, but IMHO Brook achieves that aim more directly as is evidenced by things like iterator streams and similar kernels).
I'm a graphics and systems programmer at Midway Games.
I think it will be a while before sh / GPU metaprogramming will be commonly used for "real" games programming. For example, their paper on Worley shaders claimed interactive rates of 14 FPS on a very simple single model for stone shading on the fastest video hardware (6800GT) currently available.
The benefit of HLSL and Cg are that they achieve performance close to or better than the PS/VS-Asembler implementations and are orders of magnitude easier to program. This allows them to be used on hundreds of objects per frame at video game "interactive" framerates (which usually starts at 30 FPS, with 60 FPS as a gold standard, and 300 FPS or other crazy numbers as the Doom/Quake standard for some reason).
Still it's good to see that there are languages evolving that will handle more complex shading algorithms and as the hardware becomes faster in 2 or 3 years from now, this may be practical for real-time use in video games.
From the review, it sounds like SH is basically a library, and that library invocations are dressed up through the use of operator overloading. Is this "metaprogramming"?
Yes, because rather than just executing the the operations you specify, Sh has a special "retained mode", which instead collects these operations, builds an intermediate representation of them, runs an optimizer and passes them on to a backend compiler. For example:
So, the bits that are written inside the declaration of "prg" (the above is valid C++/Sh), are actually collected into prg, instead of being executed. prg can then be compiled to a GPU (or CPU) backend and sent to the GPU to run. Thus, your C++ program is used to write Sh programs at runtime. This allows all sorts of metaprogramming techniques.
We also provide further levels of metaprogramming with the shader algebra operations, but that's at an even higher level. These let you take previously written Sh programs and combine them in various ways, at run time. See our SIGGRAPH paper for details.
The About Page on our homepage tries to explain this in more detail.
Unfortunately, the developers of Sh chose to publish the book which details features which are not yet supported in the library.
Yes, this is true. If we had written the book based entirely on what we had implemented at the time, it would however have become out of date completely very quickly. Instead we chose to write the book as a specification. Too many programming language books, and books describing systems become out of date as soon as they are published, so it's a tough decision to make.
I should point out that we are getting closer to full support of everything in the book every day. Most of the missing features are very simple things like missing library functions. Sh works, right now, and can be used to write real shaders.
With the next release I will post a fixed version of the glut example. As unfortunately happens so often, the example got broken during book writing stage...
You're welcome to browse our Issue Tracker. Progress has been slow of late because I've been working hard on the optimizer, and we've been busy with non-Sh-related things. However, note that most of the issues in the track are either "easy" or "bitesized", and once development gets on track again (in a week or so) I expect most of them to be resolved quite quickly.
Get over it. The name comes from the "Sh" prefix used on all the types, and (originally) was short for "shader".
Another thing to emphasize: yes, Sh does compile to the *CPU* as well as the GPU. So you can use it for dynamic code generation.
And finally: our long term goal is to make sh suitable for all kinds of scientfic programming on all kinds of data-parallel machines, not just GPUs.
However, people DO like to think of shaders as "data" to be managed like "assets", just like textures. Also, there are contexts like web browsers where downloading and running an arbitrary C++ program or loading and running a DLL is not a good idea, and situations like limited-memory game platforms where having a full JIT compiler is too expensive (also a problem with the OpenGL SL, BTW; the OpenGL ES spec called for a precompiled mode for this reason).
Well, both those problems can be solved. Basically, the idea is to externalize shaders (after using a host C++ app to "build" them) and then load them using a lightweight load-and-run engine. Of course such a lightweight runtime engine would not be able to use metaprogramming techniques, and would require an API similar to other shading languages to manage parameters and so forth, but you could use metaprogramming to BUILD the the shaders. It turns out that this is not all THAT easy as you also have to externalize all the RELATIONSHIPS between shaders, textures, and parameters. On the other hand, other shading languages force you to manage these relationships yourself...
Another alternative would be to bind all the types, operators, and functions of Sh to a suitable (sandboxed) scripting language. This would preserve the metaprogramming capabilities, and permit applications in things like web browsers.
Both of these solutions would also make it easier for artists to use Sh. In the first case, the application that defines an Sh shader might in fact be a content creation tool that uses, for instance, a visual language. If you can write an interpretor for a language, it's easy to use Sh to build a compiler for it. In the second case, a scripting language is probably a better tool for artists to use (if they must) to write shaders themselves. And there are lots of good scripting languages out there with good modularity constructs that we can hijack with the same approach we used with C++.
These are things we want very much to do... once we tackle a few other issues, like completing our GPGPU stream programming model. For the moment, we want to focus on the C++ binding of the library until it is completely polished before moving onto these extensions.
If you want to be picky, what Sh supports is "generative metaprogramming", i.e. the ability of one program to build another. This is sometimes called "code generation", i.e. see Herrington's book by that title (but that's confusing terminology too!). There are other meanings to metaprogramming, like self-reflection, that we don't support, although the host application *can* manipulate and analyse program objects (i.e. the C++ app can look at program objects from "the outside" but shader programs themselves have more limited semantics and can't look inside or modify their *own* implementations).
Also, it is possible to use C++ control constructs in the definition of Sh programs to swap chunks in and out or to repeat chunks (unrolled loops). This is handy for creating variants of shaders, i.e. for different levels of detail. Basically, C++ is used as a "macro" language for the definition of Sh program objects.
And yes... it is true that most Sh syntax is checked statically by C++ at C++ compile time. We consider this a feature. Why "most"? Well, we support SH_IF/SH_END_IF and like constructs for data-dependent control flow, and these must be parsed at runtime, although we've set up the library so that simple syntax checking (looking for matching begin/end keyword pairs) is done at C++ compile time.
We *were* planning to get closer to the spec for the release of the book, but we got distracted trying to support some of the fun new features in the newest graphics cards. So in fact the implementation contains things the book *doesn't* specify, too. Of course we want the spec and the implementation to converge as soon as possible.