Contents

[hide]

Concept

A new concept introduced to TGEA by GFX2 is Stateblocks. The purpose of GFXStateblocks is quite simple: an entire rendering state is contained in one object, and you are able to set the rendering state with one call. If you've written rendering code before, you may have written code similar to this before:

GFX->setBlendEnable(true);
GFX->setBlendDest(GFXInvSrcAlpha);
GFX->setBlendSrc(GFXSrcAlpha);


What if you need to setup that kind of rendering state more than just once? GFXStateblocks allow you to wrap that up into the following:

GFX->setStateBlock(myState);


There's a little more tech and setup involved, but the above example should be enough to encourage you to read on and use this new concept in your own project.

Technical Description

Using GFXStateblocks allows you to pre-generate your rendering states, which is going to save you time, clean up your code, apply modern rendering techniques, and give you more control over your game's rendering from the engine as well as script. When combined with CustomMaterials, the material definitions in script need less custom engine support.


Stateblocks are created by filling out a GFXStateBlockDesc structure and calling GFX->createStateBlock on it. This returns a GFXStateBlockRef, which is a reference-counted GFXStateBlock. This is essentially a GFXResource, just like a texture. Then you use a state block by calling GFX->setStateBlock and passing the state block in.


Stateblock creation should be done during initialization of your object, as the process is somewhat expensive. Each renderable object has a setupStateBlocks() function, which is then called during an objet's ::onAdd() class method. It is recommended you follow suit with this practice.


Previous versions of GFX and other Torque rendering systems worked under the concept of a canonical render state. Using a stateblock fully defines a render state, which completely does away with all concepts canonical. This is actually a good thing as this will reduce bugs introduced because a state was not properly cleaned up before exiting.

Source Code

It might help if we tour the engine code, so you can get some concrete code samples in front of your eyes. Start by opening gfxStateBlock.h and gfxStateBlock.cpp, both of which are found in engine/source/GFX. Let's browse the header (.h) first. All four major declarations are important here:

struct GFXSamplerStateDesc {...};

struct GFXStateBlockDesc {...};

class GFXStateBlock {...};

typedef StrongRefPtr<GFXStateBlock> GFXStateBlockRef;

GFXSamplerStateDesc

The GFXSamplerStateDesc struct contains variables and methods to identify the texture object used for each texture lookup. You'll find texture arguments for color, alpha, min/mag/mip filter, and address modes. A solid example of how this structure is used can be found in the GFXGLStateBlock::activate(...) function.


Inside this method, a GFXSamplerStateDesc variable (ssd) is set to one of the stateblocks internal samplers. The function performs a check to see how OpenGL handles the GL_TEXTURE_2D variable:


Abbreviated code from GFXGLStateBlock::activate(...), found in engine/source/gfx/gl/gfxGLStateBlock.cpp

const GFXSamplerStateDesc ssd = mDesc.samplers[i];

switch (ssd.textureColorOp)
{
case GFXTOPDisable :
   if(!tex)
      break;
   glDisable(GL_TEXTURE_2D);
   updateTexParam = false;
   break;
}

GFXStateBlockDesc

GFXStateBlockDesc defines a render state, which is then used to create a GFXStateBlock instance. If you look through the structure, you will read through some common render state ops and references: blending (source/destination/operation), depth buffer (z buffer enable/bias/deinition), color writes (write red/blue/green/alpha), and so on.


A nifty concept applied through GFXStateBlockDescr is the combining state block descriptions. This is done so through the ::addDesc(...) function:


/// Adds data from desc to this description, uses *defined parameters in desc to figure out what blocks of state to actually copy from desc.

   void addDesc(const GFXStateBlockDesc& desc);

GFXStateBlock

The base GFXStateBlock class looks very bare. It is derived from StrongRefBase and GFXResource. Being a StrongRefBase object, the stateblock will exist as long as the reference exists. When all of the strong references to the stateblock go away, it is permanently deleted. It is treated as a GFXResource since our GFX system needs to track its resources owned by a particular device.


There are only 4 functions in this base class, and they are all virtual. This is due to the fact that we have multiple graphical APIs to support, which means we are going to have GFXStateBlocks for DirectX and OpenGL:

  • GFXD3D8StateBlock
  • GFXD3D9StateBlock
  • GFXGLStateBlock


Of course, we have our null device stateblock (GFXNullStateBlock), but you will not actually need to use that for any rendering. The other GFX devices will have their own implementation of stateblocks. In fact, let's take a look at the GL implementation. Open engine/source/gfx/gl/gfxGLStateBlock.h. The class we are looking at is GFXGLStateBlock.


Within this function we have a working interface (constructor and destructor), a function called by OpenGL to activate the stateblock (::activate), and a tangible GFXStateBlockDesc member variable (mDesc). The activate function definition can be found in gfxGLStateBlock.cpp

GFXGLStateBlock::activate(const GFXGLStateBlock* oldState)
{
   // Internal code not shown
}


It is heavily commented and has a very important warning at the beginning. It is highly recommended you read through the comments and code to get a better understanding of how the stateblock is set up.

GFXStateBlockData

Two very important classes we need to take a look at is the GFXStateBlockData class, found in engine/source/gfx/sim/gfxStateBlockData.h/.cpp. The class definitions and their comments are quite descriptive:

/// Allows definition of render state via script, basically wraps a GFXStateBlockDesc
class GFXStateBlockData : public SimObject

/// Allows definition of sampler state via script, basically wraps a GFXSamplerStateDesc
class GFXSamplerStateData : public SimObject


As you read, these classes allow us to create GFXStateBlock and GFXSamplerState descriptions in TorqueScript. This means you are able to a lot more custom shader and rendering work without being as reliant on custom engine code. You should definitely look through the ::initPersistFields() functions for each class. You will notice the various references and ops that make up a render state are exposed.

Engine Example

We are going to use a very simple engine example for showing how GFXStateBlocks are created and used. Start by opening engine/source/interior/interior.h/.cpp. When we use debug rendering, we are able to see certain aspects of an interior that are normally invisible to a player. One such aspect would be the rendering of portals.


In the Interior class, we have defined multiple stateblock references. Let's look at one that affects our portal rendering:

GFXStateBlockRef mInteriorDebugPortalSB;


Ok! We have our stateblock reference, but it still needs some information. Go into Interior.cpp, and scroll down to the prepForRendering function around line 428:

bool Interior::prepForRendering(const char* path)
{
     // Previous and remaining code not shown

     mInteriorDebugPortalSB = GFX->createStateBlock(sh);

}


This line of code shows we have initialized our stateblock reference, but wait a minute...what device does so? We haven't seen any OpenGL or DirectX code anywhere, so how can we know if we are using a GFXGLStateBlock or not? This is where GFX2 takes control. GFX->createStateBlock(...) takes in a fully initialized GFXStateBlockDesc reference.


GFX then proceeds to set up the hash value and search for existing stateblocks. If the stateblock we need does not exist, it calls createStateBlockInternal. This function drills into our platform's device, so we have a GFXGLDevice::createStateBlockInternal, a GFXD3D8Device::createStateBlockInternal, and a GFXD3D9Device::createStateBlockInternal.


When creating a GFXStateBlock in script, quite a few things happen for you automatically. However, when you are in the engine there are a few "Gotchas" you have to remember. When you create a GFXStateBlock in the engine, you must call setStateBlock before you use it!


Called in Interior::debugRenderPortals():

GFX->setStateBlock(mInteriorDebugPortalSB);


A simple, more generic engine example would be this:


Instead of:

  GFX->setBlendEnable(true);
  GFX->setBlendDest(GFXInvSrcAlpha);
  GFX->setBlendSrc(GFXSrcAlpha);


Use this:

  // Setup code, done once
  GFXStateBlockDesc desc;
  desc.setBlend(true, GFXSrcAlpha, GFXInvSrcAlpha);
  GFXStateBlockRef myState = GFX->createStateBlock(desc);

  // Render time code
  GFX->setStateBlock(myState);

Script Example

Earlier, I mentioned using CustomMaterials and GFXStateBlocks together in script. We have provided you with a couple of examples. Open gameExamples/T3D/game/scriptsAndAssets/data/terrains/materials.cs. This file contains quite a few stateblocks and custom materials. Let's focus on one:

new GFXStateBlockData(TerrainLightingState)
{   
   blendDefined = true;
   blendEnable = true;
   blendSrc = GFXBlendOne;
   blendDest = GFXBlendOne;
 
   samplersDefined = true;
   samplerStates[0] = SamplerWrapLinear;
   samplerStates[1] = SamplerBlendTextureAlpha;   
   samplerStates[2] = SamplerClampLinear;   
};


The above code demonstrates how to declare a GFXStateBlock in Torque Script using the exposed Console Object, GFXStateBlocKData. In this example, we set a render state to enable bledning with a source being BlendOne, and our destination being BlendOne.


We also define our samplers as WrapLinear, BlendTextureAlpha, and ClampLinear. We can then use this stateblock in one of our CustomMaterial definitions:

new CustomMaterial(TerrainMaterialDynamicLightingMask)
{
   texture[1] = "$blackfog";
   texture[2] = "$dynamiclight";
   texture[3] = "$dynamiclightmask";
   
   shader = TerrDynamicLightingMaskShader;
   version = 1.1;
   preload = true;
   stateBlock = TerrainLightingState;
   fallback = TerrainMaterialDynamicLightingFF;
};


Not only that, but we can use the same GFXStateBlock for other CustomMaterials:

new CustomMaterial(TerrainMaterialDynamicLighting)
{
   texture[1] = "$blackfog";
   texture[2] = "$dynamiclight";

   shader = TerrDynamicLightingShader;
   version = 1.1;
   preload = true;
   stateBlock = TerrainLightingState;
   fallback = TerrainMaterialDynamicLightingFF;
};


Essentially, we wanted the exact same rendering state for our two custom materials. Instead of having to modify our engine code (twice), we can define our stateblock in script (once) and use it multiple times.

Conclusion

There is a lot to learn about GFXStateBlocks. The intent of this doc was to give you a strong introduction on the purpose, engine structure, and examples of how to use this new system. As you develop new shaders and CustomMaterials, look for ways you can save yourself time and headaches by using GFXStateBlocks.