Thursday, April 11, 2013

DirectX takes shape

While it might not be the most efficient way to create to parallel libraries, the "Big Bang" technique sometimes has its merits. Here I describe the first steps of porting the GR Graphics library to DirectX.




I will admit that it took me some time to draw a single pixel - except for clear - with DirectX. Of course, this was the accepted danger of going Big - Bang.

I did not develop the two api implementations in parallel. Rather, I created grGraphicsGl, and then tried to port it, (almost) in whole, as grGraphicsDX11, and expected it to work.

What? It might not be the most efficient, but I'm not being paid for it, and I wanted to see if I could pull it off.

I could.



It took some doing, and some refactoring, and some studying, but in an average of 4 evenings working on the engine after a mind-wrenching 9 hour day at work, plus a weekend, I could run the first HLSL - ported - from - GLSL shaders on the engine.


Making sure my grGraphics library was accomodating to both frameworks really was quite interesting. Especially around buffer  management, and memory in general.

The porting of the shaders, not so much - Suffice it to say, that the API differences between DirectX and OpenGL all but disappear as soon as you enter the HLSL/GLSL arena. The difference are 95% syntactic sugar, 5% real difference.

Enlightening, I would say.

The truth is, the only real difference I found between the two was samplers. In DirectX, a sampler is just what the word implies: The state and configuration needed to sample from a texture. Any compatible texture. And a texture is just a N-dimensional piece of memory. You can use a sampler with more than one texture, and you can sample a texture with more than one samplers.

In OpenGl, a sampler is bound to a texture. Before OpenGl 4(3?), a sampler was actually part and parcel to a texture. Now, you can at least sample a texture with more than one sampler. But not vice versa.


The other difference that is bothering me a little, although it is not a "real" difference per se, is the way that the registers are used - suffice it to say that I prefer the explicit register declaration of constant buffers in HLSL than the "uniform blocks" that have to be addressed by name in GLSL.
On the other hand, as far as vertex attributes go, GLSL allows register declaration of vertex attributes, while DirectX 11 uses the "Semantics", which boil down to the same thing as OpenGL's glGetAttribLocation .

Now, note that in the HLSL shader I am doing something that many of you will find strange: Pre-multiplying the vertices with the matrix. Most DirectX code that I have seen seems more comfortable with mul(vector, matrix) in DirectX, not mul(matrix, vector).


Be carefult. It does not matter - in this day and age of programmable shaders, it is a convention and it is up to you to decide.
A matrix multiplication is a matrix multiplication, be it DirectX, or OpenGL. My library creates matrices with the typical "API" layout (it is the same for the client-side of DirectX and OpenGL!), that is, stored in such a way that the translation is stored in positions 13, 14 and 15 of a classic float[16], or, put in another way, the columns of a matrix would be the rows of a float[4][4].

I don't transpose the matrices before uploading them, as was customary in the fixed DirectX pipeline.
Hence, my vectors are Column vectors(as per OpenGL and Math semantics), not Row vectors(as per DirectX semantics), hence I need to Pre-Multiply them with my matrices.
At this level, API choice doesn't even factor into it the discussion - the API conventions are needed for the fixed pipeline only. As soon as you program your own shaders, matrices and vectors mean whatever you decide them to mean. And you really really should, for your own piece of mind, pick a convention and stick with it.
Just be careful on matrix creation, because while multiplication order does not matter, the actual contents of the matrices DO matter, especially the Projection matrix. More on that later:

For now, observe the differences of the HLSL to GLSL comparison. Take a magnifying glass...

HLSL

#include "_CommonCbuffers.hlsl"

struct VSin {
    float4 position: POSITION;
    float3 normal: NORMAL;
    float2 texUv: TEXUV;
};

struct VSout {
    float3 eye_position: VSOUT_POS;
    float3 eye_normal: VSOUT_NORM;
    float2 texCoords: VSOUT_TEXUV;
    float4 clip_position: SV_POSITION;
};

VSout main(VSin inp)
{
    VSout vsout;

    float4 eyePosition4 = mul(modelToEye, inp.position);

    vsout.eye_position = eyePosition4.xyz / eyePosition4.w;
    vsout.eye_normal = mul(modelToEyeNorm, inp.normal);
    vsout.texCoords = inp.texUv;
    vsout.clip_position = mul(eyeToClip, eyePosition4);

    return vsout;
}



GLSL
#version 420

layout(std140) uniform;


#pragma include lightuniform.glsl
#pragma include materialarray.glsl
#pragma include matricesuniform.glsl


in vec4 position;
in vec3 normal;
in vec2 in_texUv;

out vec3 eye_position;
out vec3 eye_normal;
out vec2 texUv;

void main()
{
    vec4 eyePosition4 = modelToEye*position;

    //OUT
    eye_position= eyePosition4.xyz/eyePosition4.w;
    eye_normal= modelToEye_norm*normal;
    texUv=in_texUv;


    //SYSTEM
    gl_Position = eyeToClip*eyePosition4;
}

No comments:

Post a Comment