domenica 18 ottobre 2009

Working with D3D10: fullscreen quads inside.

As I already pointed out, the framework has D3D9 and D3D10 support.

The D3D10 Rendering Subsystem has been implemented from scratch this summer in two sessions, 4-5 hours each. While I have been working with D3D9 for a long time, that was the first (and until today, the only) time I wrote D3D10 code.

Here's the story in brief.

I had a running D3D9 rendering subsystem class, derived from a base class. I decided to give a try to D3D10, so I looked around for hints and tutorials.

I started with DirectX10 documentation and this. IMHO tutorials often aren't the best way to learn something (expecially when it comes down to properly detect errors, allocate/release resources, cleverly store objects, etc.), but they are great if you use them as reference (working) code.

I adapted my D3D10 code so that it could fit the rendering subsystem requirements, keeping YAGNI in mind.

The next day I had almost everything I needed already working. Shaders, textures (materials), shader parameters, vertex and index buffers, render targets. The reference sample code at that time was simple but feature rich: a 3d quad with a texture coloured and zoomed with shader parameters rendered to a render target, that is used as texture for the same quad rendered in another (rendering) pass.

Now, three months later, I'm working on the D3D10 rendering subsystem again.

What's good about it is since then I had to add very little functionalities in my D3D10 rendering subsystem. Despite my little knowledge and the fact I don't take advantage of useful features like constant buffers, the subsystem is fast.

What's bad is I "discovered" today something very important is missing: rendering a full-screen quad. Hey, after all that's what YAGNI is about: I need it now, so I'm going to implement it.

The rendering subsystem provides the user the chance to draw a rectangle, via this method:

NxBool DrawRect( NxFloat i_fX0, NxFloat i_fY0, NxFloat i_fX1, NxFloat i_fY1 ) = 0;

In D3D9 the implementation is straightforward, I have a local array with per-vertex data initialized according to the parameters submitted, I set a proper vertex format with position and UVs and I use DrawPrimitiveUP.

In D3D10, without such a mechanism, there are two options:
- use a common VB/IB pair and apply a proper transformation
- don't use buffers at all

As for the second solution, DirectX documentation covers this subject.

I decided to write something like that, here are a few hints:
1- don't use a triangle list, use a triangle strip (4 vertices instead of 6). Be careful with the vertex order. When I have to render an ABCD quad (A=(0,0) B=(0,1) C=(1,1) D=(0,1))*, the order I'm using is "BCAD". The reason is since the strip is built from the last two vertices of the first primitive, they need to be the vertices of the shared edge.
2- I didn't give a try to this idea, but if you need a simple fullscreen quad you could try rendering a single triangle of size ((0,0) (2,0) (0,2))* and scale your vertex data by a factor of two. Clipping should avoid extra calculations and in theory you'll get your good ol' fullscreen quad. Since this solution doesn't work with rects, I didn't implement this idea.

What's great is I don't have to worry about pixel-texel centers alignment. If you don't know what I'm talking about and you are using D3D9 you definitely need to have a look here before starting develop any postprocessing shader.

When using the framework, the main difference between D3D9 and D3D10 implementations is the amount of constraints in postprocessing shader. In D3D9 user has to write a simple passthrough vertex shader and provide two variables, one for half pixel width and another one for half pixel height. In D3D10, the vertex shader is more complex and the required parameters are the quad coordinates X0,Y0 X1,Y1.

The next step has been to create a specific class for screen rects. I wanted to be able to push screen rects inside a scene, so that building interfaces or postprocessors should be easy.

When testing compatibility I tried multithreaded and singlethreaded pipelines, they worked. I decided to check my D3D9 rendering subsystem and nothing is shown on screen.

It seems the subsystem doesn't correctly expose shader parameters. Yes, I haven't implemented it yet because of YAGNI!

Now I know what I'm going to implement today... ;)

* To make the post easier to understand I'm assuming the reference coordinate system is (0,0) at the top left corner and (1,1) at the bottom right. Which of course is NOT the case of D3D.

Nessun commento: