• entries
21
28
• views
34415

2145 views

Last entry:

After a long time of not being productive again, I've finally come around to fully implement the current-gen API features to my engines shading language, now called "ASL" (acclimate shading language).

One thing I noticed when learning geomentry, domain and hull shaders in DX11 was that those had quite some bulky syntax. IDK, its straightforward and not that overly complicated to understand, but there is quite a lot to remember about the exact syntactical structure of those things. So in my own implementation, I tried to keep the requirements for coding any of those shaders as simple as possible.

For the following shaders, I assume you already have at least a basic understanding of the concepts.

So lets have a look at how a geometry-shader looks like, shall we?geometryShader{ input Stage { matrix mViewProj; } primitives { in: point[1]; out: triangle[4]; } main { float3 vRight = float3(0.0f, 0.0f, 1.0f); float3 vUp = float3(1.0f, 0.0f, 0.0f); vRight *= 0.5f; vUp *= 0.5f; float3 vVerts[4]; vVerts[0] = in[0].vPosition.xyz - vRight - vUp; // Get bottom left vertex vVerts[1] = in[0].vPosition.xyz + vRight - vUp; // Get bottom right vertex vVerts[2] = in[0].vPosition.xyz - vRight + vUp; // Get top left vertex vVerts[3] = in[0].vPosition.xyz + vRight + vUp; // Get top right vertex for(int i = 0; i < 4; i++) { out.vPosition = mul(float4(vVerts, 1.0f), mViewProj); Append(); } }}
This is a simple geometry-shader, expanding a point to an floor-aligned quad. The input block, as known previously, offers cbuffer-variables supplied from the application side.

The "primitives" block is new and geometry-shader only, and is there for definition of what primitives the geometry-shader gets, and what it should output.

Inside the "main" block, you can access vertex shader output via "in[X]" (uses the vertexShader-blocks "out" definition not seen here), and write to "out", calling "Append()" after each vertex is written. "Restart()" can be called to emit the next (seperate) primitive.

The geometryShader can eigther output its own vertex structure to the pixel shader by specifying an "out" block, otherwise it will use the previous shaders output structure.

So in conclusion, geometry-shaders using is quite easy using the ASL. Note that this also parses to OpenGL4 (unfortunately I havn't implementated OpenGL-support for the upcoming shader-types, so I can't say for sure they will work there also).

Tessellation was one of the key-features for current-gen hardware. Especially those always deterred me from getting started with it, since there is really a whole lot of things to keep in mind for both Hull & Domain-Shader, at least in DX11. You have a constant hull function, the regular hull shader function, with inputs of multiple vertices as well as the constant data, with PointID/PatchID and UV-coordinates for the Domain-Shader... lets just say, its even more little things to keep in mind syntactically than the geometry-shader.

But lets have a look at how you write them with ASL:hullShader{ settings { domain: QUAD; partitioning: FRAC_EVEN; outputtopology: TRI_CW; outputcontrolpoints: 4; inputcontrolpoints: 4; } out { float3 vPos; } constOut { } constFunc { constOut.Edges[0] = 1.0f; constOut.Edges[1] = 1.0f; constOut.Edges[2] = 1.0f; constOut.Edges[3] = 1.0f; constOut.Inside[0] = 1.0f; constOut.Inside[1] = 1.0f; } main { out.vPos = in[PointID].vPos.xyz; }}
The "settings" block obviously is hull-shader specific, as you define the tessellation-paramters.

"constOut" and "constFunc" are the constFunction, which is a completely seperate function in DX11. There, you calculate the tessellation factors, and any other data that is shared between the entire patch. Note that the "Inside/Edges" are defined for you automatically based on what domain you specify.

in the main-function you have a few things defined for you. Obviously "in" is the output of the vertex shader, as an array with as many elements as you specified as "inputcontrolpoints". PointID is the ID of the current vertex, and there is also a PatchID for the ID of the entire patch.

And now for the domain-shader:domainShader{ input Instance { matrix mWorld; } input Stage { matrix mViewProj; } out { float4 vPos; } functions { float3 Bilerp(float3 v0, float3 v1, float3 v2, float3 v3, float2 i) { float3 bottom = lerp(v0, v3, i.x); float3 top = lerp(v1, v2, i.x); float3 result = lerp(bottom, top, i.y); return result; } } main { float3 vPos = Bilerp( in[0].vPos, in[1].vPos, in[2].vPos, in[3].vPos, UV ); out.vPos = mul(float4(vPos, 1.0f), mul(mWorld, mViewProj)); }}
So there is really little special about that here. You have an "in" block with as many controlpoints as you specified in "outputcontrolpoints", using the hull-shaders "output" for the vertex elements. "UV" is defined for you, depending on what you have got - for a quad its float2, for a triangle it would be float3. In the "functions"-block, I've specified a Bilerp-function, which could probably be somewhat pulled in the standard-repertoir of the shading-language, as it is probably needed extremely often.

And thats it! You can do all your displacement-mapping etc.. here, since the domain-shader also has access to a "textures"-block, and so on...

Conclusion:

So for those of you familiar with the Domain/Hull-Shader, I think it should be clear that my shading language is taking quite a bit of work. From what I've seen during building the parser for those, you normally have to specifiy lots of duplicated data, e.g. while "outputcontrolpoints" exists in DX11 also, you still have to manually write it as the array-size in the hull/domain-shader input signature. You also always have to figure out the correct combination of Inside/Edge-Tessellation-Factors yourself, as well as what UV you want as input in the domain-shader.

So thats it for now. Next time, I'll probably talk about some more specific block-types that are needed for developing shaders, like "constans", "extentions", and probably also more complicated stuff like templating shaders (which I partially still have to implement). Thanks for reading, and up until next time, I've attached a somewhat complete shader example of the tessellation terrain shader I'm currently working on, based on the Terrain Tessellation example from NVIDIAS DX11 SDK.

PS:

For those of you that reguarely work with plain HLSL/GLSL-shaders, I want to ask a question: Given what you have read so far, would you rather write your shaders in a language like mine (given that there is full documentation, etc...) or still rather write your shaders in a plain shader language, and why? Would be very interested to hear some different opinions.

Having developed a scripting language which I also use for shading purposes, I take the liberty to rephrase your question to:

I would use a standard language because of the tools. Note your language - just like mine - isn't providing any meaningful level of abstraction. So we don't save much and yet we get the problem of a custom language. We are not given extra flexibility. The amount of efforts saved could be debated.

My consideration over my own shading language changed across the months I've used it and let me tell you my workflow was slightly more flexible. By the time I stopped working on that project I was well aware I shouldn't have left the language live, for shading purposes, in its current form.

I remember one of the last notes I wrote about it. It was something like:

• current approach = no value
• underlying idea promising but is it worth?
• new approach - performance / flexibility
• perfomance workflow use native API language
• flexibility workflow with greatly updated execution environment

That is: performance users would get extreme control. Users needing flexibility would have access to a cost-reducing system (in the works for quite a while). Those were the two extremes I identified.

Shading languages such as mine - such as yours - end up in a middle zone. It is unclear to me who would ever be happy with those.

I think you would have to provide a big improvement in productivity to get people to utilize your language.  Perhaps you could try to use your language as a pre-processor to generate the HLSL files for people to use, but it is very hard for graphics programmers to give up low level access to their shaders.  Productivity is the only way that I could see that happening, so I would encourage you to look for features there.

Even so, I find this type of work quite interesting, so please keep posting your progress!

@Krohm:

Interesting to hear the opinion of someone who did something similar to me. At least personally, after having used this language for about a year and a half, I don't feel like you did after some time with your language. In my case/project, it seems to fit quite fine, and has taken considerable amounts of work (especially for DX9/DX11/OpenGL cross-support).

The idea behind my language is actually more in the terms of saving work, be it by removing syntactical overhead, or by unifying the shading-code generation for multiple APIs while keeping a decent amount of direct shader access. As to who would be happy with such a language - I have no complete idea of that eigther, but at least I am, I just hope thats not only positive self-bias...  Also, thanks for your input on the topic!

@Jason Z:

Productivity was actually on of the main reasons/aspects of to why I started developing this. It was quite unbearing, having to keep up with 3 different API-versions of my shaders, which pretty much did all the same at the time. The language actually does just that - it outputs a plain HLSL/GLSL-file, which can then be loaded normally, while offering some sort of more comfortable reflection system than using the DX11 variants. I'll look even further into the productivity line, I've already got a few things on my mind, I'll keep updating progress here as I go.

Create an account

Register a new account