A blog which discusses various GPU applications including visualization, GPGPU and games.

Sunday, January 3, 2010

Integer division

Beware integer division on the GPU.

In a certain compute shader I am working on, this code

if (tcoord.x < 0) tcoord.x += gridsize.x;
if (tcoord.y < 0) tcoord.y += gridsize.y;
if (tcoord.z < 0) tcoord.z += gridsize.z;

if (tcoord.x >= gridsize.x) tcoord.x -= gridsize.x;
if (tcoord.y >= gridsize.y) tcoord.y -= gridsize.y;
if (tcoord.z >= gridsize.z) tcoord.z -= gridsize.z;

runs much faster than using

tcoord = (tcoord + gridsize) % gridsize;

Sunday, November 15, 2009

C++0x random header

C++0x includes a number of new headers, one of which includes new random number facilities. The functionality is ingeniously separated into two major parts: engines and distributions.

Engines are responsible for generating uniformly distributed random numbers. One such provided engine is the Mersenne Twister engine. Distributions use the output of engines to mold the numbers to a specific distribution.

Consider the following example.

mt19937 engine(static_cast<unsigned long>(time(NULL)));
exponential_distribution<double> dist;
cout << dist(engine) << endl;

The output is a single random number, following the exponential distribution. Now let's say we want to use this in a call to generate_n with a lambda:

mt19937 engine(static_cast<unsigned long>(time(NULL)));
exponential_distribution<double> dist;
generate_n(ostream_iterator<double>(cout, "\n"), 20, [&dist,&engine]() -> double { return dist(engine); });

You're probably thinking that a simple for loop would be much cleaner here, and I don't disagree. However, there is one other thing we can do:

mt19937 engine(static_cast<unsigned long>(time(NULL)));
exponential_distribution<double> dist;
variate_generator<mt19937, exponential_distribution<double>> gen(engine, dist);
generate_n(ostream_iterator<double>(cout, "\n"), 20, gen);

That's right -- variate_generator is provided to us so that we can encapsulate an engine along with a distribution. That way, a simple gen() gets us a random number using the desired engine and distribution.

Saturday, September 19, 2009

Tessellator

I finally decided to dive into the new tessellation shaders, and I am quite pleased. Going into it I thought it would be very specific to gaming applications, but as I've found out it is surprisingly general.

New primitive topologies have been added for tessellation. Since the basic unit for the new shaders are patches and control points, the new types allow you to render anywhere from 1 to 32 control points per patch.

Say you're using 16 control point patches. The vertex shader is run per control point, and the output from this stage is passed into the hull shader. The hull shader is really described in two parts; one being a patch-constant function, and the other being the hull program.

The patch-constant function computes user-defined data and is run once per patch. This allows you to compute things that remain constant across the entire patch. The required output from the patch-constant function are tessellation factors: these tell the tessellation hardware how much to tessellate the patch. The hull program is run once per output control point, and both the patch-constant function and hull program can see all control points.

The next step is the actual tessellation, which is performed in a fixed-function, yet configurable stage. The tessellator ONLY looks at the tessellation factors output from your patch-constant function. The user-defined output from the patch-constant function and hull program are provided to the domain shader, which is run after tessellation.

The domain shader is run per-tessellated-vertex and is provided with the tessellated vertex location on the patch. To me it seems that the domain shader can be seen as a post-tessellation vertex shader; this is where you transform the tessellated vertices. The output from the domain shader is provided to the geometry shader (or pixel shader, if not using a geometry shader).

Here are some results from my initial experiments with the new stages:





The toughest part to me is computing the per-patch tessellation factors. But since this is completely programmable, it's a fun problem.

Saturday, August 29, 2009

D3D11 Types

I did some more fiddling with D3D11's new types and have learned new things about them.

First, it seems that it is not possible to read from multi-component RWTexture* and RWBuffer objects due to a hardware limitation. However, it is possible to read-write the 32-bit RGBA type thanks to the way D3D handles views.

Create the texture with a DXGI_FORMAT_R8G8B8A8_TYPELESS format, then for the unordered access view cast it to DXGI_FORMAT_R32_UINT. This allows for a common texture format to be read/written without ping ponging, which is great for in-place transformations.

There is another reason why this is not a major limitation. Consider applications whose requirements are reading and writing a texture, but also use shared memory to reduce texture fetching. This most likely means that there is overlapped texture fetching going on (e.g., for an image convolution), and so ping ponging two textures is necessary here anyway to prevent clobbering of data between shaders.

I have found the new structured buffer types to be much more flexible since they are independent of the texture subsystem. It is possible to read/write any structure and any element of a RWStructuredBuffer. Any shader can read from structured buffers, and the compute and pixel shaders can write to them. According to John Rapp on the DX11 forum, this type also has beneficial performance characteristics.

It should be noted that a structured buffer cannot be bound to the input assembler (not that you'd want to since you can just read from it in a random access manner), and cannot be the output of a stream-out operation. I consider these limitations minimal, since really, the input assembler probably should be going away sometime soon. As for stream out, one can just stream out to a regular Buffer and read from that.

The March 2009 SDK Direct3D 11 documentation mentions that the AppendStructuredBuffer and ConsumeStructuredBuffer types operate similar to a stack in that items are appended and consumed from the end of the buffer. If this is true, this is a very nice property to have. This means it is possible to append to a structured buffer in one pass, and bind it as a plain old StructuredBuffer in another pass (for example, indexed by SV_InstanceID in the vertex shader). Or, filling up a RWStructuredBuffer in one pass, then consuming from it in another pass.

I haven't played around too much with the ByteAddressBuffer type. From my experiments, it seems that StructuredBuffer is the way to go for most things. It seems that these replace Buffer for me in most of my applications.

Monday, August 10, 2009

New DirectWrite Samples

I am excited to see that there are new sample applications that show off the capabilities of DirectWrite.

For anyone who is thinking about porting over older GDI code to D2D/DWrite, have a look at this sample. It's a word processor that renders using DWrite. This should give you a better understanding of what all DWrite can do. :)

Thursday, July 30, 2009

C++0x auto

Consider the following iterator example:

map<string, pair<int, float>> data;

for (map<string, pair<int, float>>::iterator i = data.begin(); i != data.end(); i++)
{
// ...
}

We can clean this up a bit by using the auto keyword:

map<string, pair<int, float>> data;

for (auto i = data.begin(); i != data.end(); i++)
{
// ...
}

Sunday, July 26, 2009

Limitations

Having used Direct2D, DirectWrite and the Direct3D 11 previews, I would like to discuss some of the limitations I have run into.

Direct2D has the ability to render into Direct3D textures. However, D2D does not deal with resource views directly; it uses DXGI's facilities to access surfaces. The problem comes when trying to obtain the DXGI surface representation of a 2D texture that has more than one mip in its mipmap chain and/or more than one layer. Unless I am missing something, this is simply not possible. This means that it is not possible to use D2D to render directly into a Direct3D multilayer texture (or mipmapped texture).

Admittedly, I have not found myself needing to do this very often. Indeed, the most useful application of D2D/D3D interop to me has proved to be rendering to the backbuffer, which is neither mipmapped nor multilayered. In one scenario, however, I needed to render some numbers into a texture array. I had to create a temporary texture without any mipmap/layers, render to that using D2D, then perform an on-device copy to get it into my texture array.

This copy could be eliminated in two ways. One way involves adding a D3D dependency to D2D, which is not the best route. The second way involves a modification to DXGI to enable the casting of multilayer/mipmapped 2D textures to surfaces; it would be nice to be able to pass in a subresource number and get a surface representing a particular subresource of a 2D texture.

The second limitation I have run into is in the compute shader. I dislike how the number of threads per group is declared in the shader, and cannot be changed during runtime without a shader recompile. I really do not see the need for this limitation, as both OpenCL and CUDA allow the number of threads per group to be specified at runtime. That aside, I still prefer Microsoft's approach to computation on the GPU. I like that it is integrated into the Direct3D API and uses a language similar to the other shaders.

Aside from these minor limitations, my expectations are definitely surpassed with regard to Direct2D and DirectWrite. I think these APIs fill in a large gap in the Windows graphics API collection.

Followers

Contributors