I managed to get the tessellation to work, which doesn’t mean I have anything cool to show, but it is working. You will just have to take my word for it. It turns out that you need to input a special type of primitive type for the shader to be able to treat the input data as control points. This seems unnecessary if you send a triangle, it should be able to interpret a triangle as three control points, but nope, it doesn’t. The funny thing is that the Nvidia GPUs allows you to run the shader like nothing is wrong, except for the fact that you wont see a result of course. That result would automatically send you on a quest to fix the shader, but the actual problem is in your application, and not your shader. The ATI GPUs at least have the decency to tell you that something is fatally wrong by, quite simply, restarting the graphics driver. But enough bashing, it works, I’m happy, and when I get the time, I will be implementing a complete render pass using tessellated objects (by which I mean a normal-depth pass and a color pass).
While I’m already on the subject of hull shaders and domain shaders, I thought I’d share the design for how Nody implements these magical tools of power. The hull shader, very much like the vertex shader, is usually a program which you don’t need to modify too much, and by that I mean there is no real need to have different nodes for different features. The hull shader is also very complex in the sense that it is essentially two threads, the main thread and the patch constant thread. Because of this fact, a hull shader would need to be split into two different nodes, one for the main thread and one for the patch constant thread, and since the program itself is supposed to be user-friendly to some extent, I thought up a different solution. The solution is to treat the hull shader very much like the vertex shader, using one node to do a specific mission. For example, I have a node named ‘static’, which is basically used to pass the position, view-space position and uv-coordinates to the pixel shader. Then I have another for doing this and skinning the object. Well the hull shader works the same way, currently there is one node called trianglesubdivision, which takes information from the vertex node, and performs a constant patch function and a main function described in the shader variation file. So if one is to implement another hull shader, one should consider making another version of trianglesubdivision.
The domain shader however, is oh so very different. This shader step is much like the pixel shader, divided into an arbitrary amount of nodes, where each node performs some sort of action. The only hitch here is that since the domain shader gets a patch in the format OutputPatch<DS_INPUT, POINT_COUNT> Patch, every node accessing a variable from the hull shader needs a Patch[x] in front of the actual variable name. This is also a part where something can go wrong. Let’s say that POINT_COUNT is 3, and a domain shader node will try to access a variable at Patch, which isn’t going to work. I have accepted this as a minor flaw, but there are several obvious ways to work around it. First of all, domain shader variations are categorized by what kind of geometry they are expecting. The hull shader denotes that we are using triangles if you are using trianglesubdivision (obvious fact is obvious), and the domain nodes to go with this is of course the ones in the triangle-category. If one chooses to pick from another category, well, then they’ll have to suit themselves when they get an error telling you that you are trying to grab more than you can handle (i.e. trying to get a point which is out of bounds). So for future expansions on this system, add a new hull shader, and a suiting new domain shader category.
So that’s that. I also want to squeeze in how to add new nodes to Nody using the .shv or shader node variation system. Basically, it’s like a scripting language, and it looks something like this:
float4 Position : POSITION
float4 WorldProjectedPosition : SV_POSITION
WorldProjectedPosition -> mul(ModelViewProjection, Position);
It’s basic, it’s simple, and it’s highly maintainable. If this variation is attached to a node, that node will automatically get the input Position, the output WorldProjectedPosition, and perform the ModelViewProjection multiplication to put the position in the viewport. The -> operator is something new, which is not a part of the HLSL or GLSL standard, and is solely used by Nody to allow the user to decide what sort of operator should be used. In the default case, the action of the node is Set, which means that WorldProjectedPosition will be set to mul(ModelViewProjection, Position). If the action is, for example Add, well then WorldProjectedPosition will have mul(ModelViewProjection, Position) added to itself. In this case, the choice of action is pretty obvious, seeing as doing anything else but setting the value would cause a catastrophic result, but let’s say its a color addition, or subtraction, or what have you. I’m guessing you get the point. Oh and by the way, the syntax is very inspired by the OpenGL glBegin() and glEnd() style.
The tags that are currently available are: BEGININPUTS, BEGINOUTPUTS, BEGINSOURCE, BEGINEXTERNALS, BEGININCLUDES, BEGINTEXTURES, BEGINCONSTTEXTURES, BEGINSAMPLERS, BEGINGLOBALS, BEGINCONSTANTS and BEGINCLASSINSTANCES. You’ve already seen inputs, outputs and source, and I’m guessing you can figure out what includes, textures and samplers are. The global is a way to denote variables that will be changeable by the runtime, and constants are variables that are not. The externals denote code that is supposed to be outside of the main loop, but not included in another file, so for example some simple function. Const textures are just a way to make Nody avoid defining a texture, which is useful if the texture is in an include-file. This will still add the texture to the node as an input-variable, but will avoid declaring it in the shader code. Class instances are to be used for dynamic linkage, which in turn will allow us to make small modifications to the shader program, which is useful for not bloating the shader system with tons of different shaders, but instead make small modifications to the existing ones.