11
\$\begingroup\$

I often tend to do a lot of premature optimazation when dealing with graphics. There are a few principles I always try to follow:

  • Keep the number of D3D components to a minimum. (Render states, buffers, shaders, etc.)
  • Only bind components if absolutely necessary. (Not bound already, etc.)
  • Specialize the components as much as possible. (Only set necessary BindFlags, etc.)

This lead me to building very elaborate wrappers for managing the created components and the current pipeling state. Not only does this consume a lot of my valuable development time, it also adds another big layer of complexity.

And worst of all: I don't even know if it is all worth the trouble.

Some of my optimization considerations may already be implemented on a lower level and I am just replicating them, additionally wasting time on the CPU. Other considerations may be completely unnecessary, since the effect on performance is negligible.

So my questions are:

  1. Which of the above guidelines are valid and to which extent should I follow them?
  2. How does the GPU handle state changes?
  3. What happens if I change a state that is never used? (No draw call being made while it is active.)
  4. What are the actual performance penalties for binding the various different components?
  5. What other performance considerations should be made?

Please don't just tell me, that I just should not care about performance until I hit actual limits. While that is obviously true from a practical point of view, I am mainly interested in the theory. I somehow need to combat the urge of building the optimal graphics framework and I don't think I can do that with the usualy "premature optimization lecture".

Managing components

I am currently writing DirectX 11 applications in C# using SlimDX as a managed wrapper. It is a very low level wrapper and my current abstraction is built on top of it.

There are some obvious advantages when using a Direct3D abstraction. Setting up the environment, loading shaders, setting constants and drawing a mesh is much simpler and uses a lot less code. Also, since it manages creation and disposing of most components they can be automatically reused everywhere and I almost completely avoid memory leaks.

  1. How do you usually manage all the graphics components and resources?
  2. Can you recommend any managed wrappers doing something similar to my example below?

Here is an example of my current implementation. I am quite happy with the interface. It has enough flexibility for my needs and is very simple to use and understand:

// Init D3D environment
var window = new RenderForm();
var d3d = new Direct3D(window, GraphicsSettings.Default);
var graphics = new GraphicsManager(d3d.Device);

// Load assets
var mesh = GeometryPackage.FromFile(d3d, "teapot.gp");
var texture = Texture.FromFile(d3d, "bricks.dds");

// Render states
graphics.SetViewports(new Viewport(0, 0, 800, 600);
graphics.SetRasterizer(wireFrame: false, culling: CullMode.Back);
graphics.SetDepthState(depthEnabled: true, depthWriteEnabled: true);
graphics.SetBlendState(BlendMethod.Transparency);

// Input layout
graphics.SetLayout("effect.fx", "VS", "vs_4_0",
    new InputElement("POSITION", 0, Format.R32G32B32_Float, 0),
    new InputElement("TEXCOORD", 0, Format.R32G32_Float, 0)
);

// Vertex shader
graphics.SetShader(Shader.Vertex, "effect.fx", "VS", "vs_4_0");
graphics.SetConstants(Shader.Vertex, 0, 4, stream => stream.Write(wvpMatrix));

// Pixel shader
graphics.SetShader(Shader.Pixel, "effect.fx", "PS", "ps_4_0");
graphics.SetTexture(Shader.Pixel, 0, texture);
graphics.SetSampler(Shader.Pixel, 0, Sampler.AnisotropicWrap);
graphics.SetConstants(Shader.Pixel, 0, 1, stream => stream.Write(new Color4(1, 0, 1, 0);

d3d.Run(() =>
{
    // Draw and present
    d3d.BackBuffer.Clear(new Color4(1, 0, 0.5f, 1));
    graphics.SetOutput(d3d.BackBuffer);
    graphics.Draw(mesh);
    d3d.Present();
}
\$\endgroup\$
3
  • 8
    \$\begingroup\$ For this kind of question I wouldn't give the "premature optimization" lecture, I'd give you the "profile changes so you can see for yourself" lecture. \$\endgroup\$
    – Tetrad
    Commented Mar 1, 2013 at 12:00
  • \$\begingroup\$ @Tetrad I am almost ashamed to admit that this is some pretty decent advice. I should definitely do more profiling. \$\endgroup\$
    – Lucius
    Commented Mar 1, 2013 at 12:05
  • 1
    \$\begingroup\$ Profiling numbers are the gamedev version of "pics or it didn't happen" for sure =) \$\endgroup\$ Commented Mar 1, 2013 at 15:45

1 Answer 1

3
\$\begingroup\$

I like the abstraction approach outlined by Hodgman in these threads on gamedev.net:

He describes a three-tier rendering system:

  1. Low-level rendering API that accepts "commands", abstracting no more than the differences across different graphics API's, such as Direct3D 9, Direct3D 11, and OpenGL. Each "command" maps to a different state or draw-call, such as binding a vertex stream or texture, or drawing the primitives.
  2. API that accepts "render items", which group together all the states and a single draw-call needed to render a certain object, and sorts and translates them into commands which are sent to the first level. A render state contains a draw-call and a stack of "state groups", which logically group state-changes. For example, you'd have a state group for the render pass, a state group for the material, a state group for the geometry, a state group for the instance, and so on. This level is responsible for sorting these render items to reduce redundant state changes and culling any state changes which are, in fact, redundant.
  3. High-level systems like a scene graph or GUI renderer which send off render items to the second level. The important thing to notice is that these systems don't know about either the state sorting algorithms or the specific rendering API, making them completely platform-agnostic. They're also easy to use once the lower-level API's have been implemented.

In conclusion, this model solves both of your problems at once.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .