Hoeloe
Hoeloe

Reputation: 650

Combining multiple pixel shaders efficiently

So I'm making a thing with XNA 3.1, and I have a lot of separate effects that are applied via pixel shaders. These come from all sorts of sources, such as special attacks, environment, and so forth. The issue I'm having is that I'm noticing a significant reduction in frame rate.

At the moment, I'm drawing the entire scene to a RenderTarget2D, which I'm then applying all the effects to. I store a SortedDictionary containing the effects and their IDs (the IDs are used to change parameters at runtime), and I'm iterating over it and applying each effect one after the other:

foreach(KeyValuePair<Ref<int>,Effect> p in renderEffects)
{
    Effect r = p.Value;
    g.SetRenderTarget(0, MainGame.MainRenderTarget);
    //Change RenderTarget to allow code to grab existing texture in the same draw area.
    levelDraw = MainGame.LevelRenderTarget.GetTexture();
    //Change back to draw back to this texture, allowing render effects to be layered.
    g.SetRenderTarget(0, MainGame.LevelRenderTarget);

    MainGame.StartDraw(MainGame.GameBatch);
    //Starts the sprite batch and sets some parameters
    r.Begin();
    r.CurrentTechnique.Passes[0].Begin();
    MainGame.GameBatch.Draw(levelDraw, new Rectangle(0, 0, levelDraw.Width, levelDraw.Height), Color.White);
    r.CurrentTechnique.Passes[0].End();
    r.End();
    MainGame.GameBatch.End();
}

Now, this produces noticeable frame drops when layering just 3 effects, and when applying 10, it drops from 60FPS to 16FPS, which is of course unacceptable. I'm wondering if there is a more efficient way to do this. Considering I only have one texture, I considered that I may be able to combine the effects into one file and execute multiple passes without grabbing the texture back. I'm not sure if this is possible, however.

I'm not really sure exactly how the best way to do this is, though I imagine there must be a better way than the way I'm doing it.

Upvotes: 14

Views: 2035

Answers (2)

theodox
theodox

Reputation: 12208

The method in the snippet is likely to be very slow, because you're doing a texture grab and a full screen draw for every effect, which stresses the memory bandwidth between the CPU and GPU on top of whatever is going on inside the shaders. You probably need, as you suggested in your post, to create a set of shaders which each contain multiple operations rather than running the read-write loop over and over again: one expensive shader will usually still be faster than many read-write-repeats of simple shaders.

You might want to look at Shawn Hargreaves article on shader fragments in HLSL and Tim Jones's code for doing this in XNA

Upvotes: 2

doug65536
doug65536

Reputation: 6772

Are you fully shading everything as you draw? If your shaders are computation heavy, you should do a "depth pass" first, only ztesting/writing the Z buffer (color buffer writes are off). Also, use trivially simple shader to "depth fill" the screen.

In other words, render all opaque objects updating only the depth buffer on the first pass.

On the second pass, you turn on the shaders (and turn off depth writes, no need to use bandwidth to write back the same value that's already there). This will mask off all the unnecessary work of any overdrawn pixels, since they will fail the depth test immediately.

EDIT: now I realize OP is doing fullscreen effects, not the scene render.

Upvotes: 1

Related Questions