-
|
|
Custom blend modes with SpriteBatch (immediate mode) - RenderState overlapping?
|
Hi
(First post, a bit long, skip to code at bottom for pseudo-code of the problem I'm having!)
I'm writing a simple game engine in C# with the help of XNA. I have a sprite engine and custom effects engine. Within it Sprites are either drawn with AlphaBlend or Additive blend modes.
Because I want to use Pixel Shader effects I have to call Effect.Begin etc after a call to SpriteBatch.Begin with SpriteSortMode.Immediate set (right?).
However, to keep the code clean, I want to keep the effects-handling code out of my Sprite class Draw() method. So, I figured the only way to do this is use one SpriteBatch - set the mode to Immediate, set up any effects, then loop through and draw all of my sprites.
This worked fine HOWEVER, some of the sprites in the list want to be drawn with Additive blend mode. I figured that for these sprites I could set the DestinationBlend render state manually like this:
engine.GraphicsDevice.RenderState.DestinationBlend = Blend.One;
The problem is, even though I'm using SpriteSortMode.Immediate, there is some kind of 'lag' between setting the blend mode and drawing sprites. For example:
// // PROBLEM: with this code, BOTH sprites are Additive blended (Blend.One) // batch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None); DrawFirstSprite(); engine.GraphicsDevice.RenderState.DestinationBlend = Blend.One; // set additive mode DrawSecondSprite(); batch.End();
And another example:
//
// PROBLEM: with this code, the FIRST sprite is Additive blended (Blend.One) - the other two are AlphaBlend blended
//
batch.Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None);
DrawFirstSprite();
engine.GraphicsDevice.RenderState.DestinationBlend = Blend.One; // set additive mode
DrawSecondSprite();
engine.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha; // set AlphaBlend mode again DrawThirdSprite();
batch.End();
Any ideas at all? Any help would be really really appreciated. Thanks!
|
|
-
|
|
Re: Custom blend modes with SpriteBatch (immediate mode) - RenderState overlapping?
|
Also having the same problem with effects and effect parameters.
Changing a parameter (Effect.Parameters["param1"].SetValue(x);) even followed by a call to Effect.CommitChanges then rendering a sprite has the same delay / overlapping effect.
My guess is even with SpriteSortMode.Immediate, batch.Draw() calls don't use the current RenderState and complete before the method returns?
Is there no Flush or Commit method for synchronising this stuff?
|
|
-
-
- (12853)
-
Team XNA
-
Posts
8,531
|
Re: Custom blend modes with SpriteBatch (immediate mode) - RenderState overlapping?
|
This should clarify how the immediate sort mode works. To flush a SpriteBatch, you simply call End, then Begin another one if you want to continue drawing more stuff. You cannot change any states within a single batch. You must draw all the stuff you want using a given set of states, then End the batch, before you can change states.
XNA Framework Developer -
blog - homepage
|
|
-
|
|
Re: Custom blend modes with SpriteBatch (immediate mode) - RenderState overlapping?
|
Thanks, that post was the one that gave me the idea to try this :)
This section made me think: Another
more interesting implication is that because SpriteSortMode.Immediate
sets its renderstates inside the Begin call, if you were to change
these renderstates immediately after calling Begin, the sprites will be
drawn using your custom state
I was hoping that using Immediate rather than Deferred would mean the sprites are drawn immediately rather than having their rendering Deferred..... guess not ?
I thought it would save some overhead to only use one SpriteBatch.Begin(), keep most Effects code out of the Sprite class, and just vary a parameter to the shader as necessary. Should I just write my own textured-quad sprite engine?
(edit: I realise there's some key piece of knowledge I must be missing, in my mind the batch.Draw() method is just drawing a textured quad when in Immediate mode - this can't be right?)
(and another edit: Reflector rocks. I can see exactly what's going on. It still defers drawing of sprites until the sprite texture changes, right? Dare I invoke your secret Flush method? :))
|
|
-
|
|
Re: Custom blend modes with SpriteBatch (immediate mode) - RenderState overlapping?
|
Kierenj: I was hoping that using Immediate rather than Deferred would mean the sprites are drawn immediately rather than having their rendering Deferred..... guess not ?
I think that would ruin the whole "batch" idea of the "SpriteBatch". ;) Immediate just means that if you give it a sprite that can not be attached to the previous queue, it will draw it out the old queue immediately and create a new queue instead of holding onto it and hoping you give it more sprites that can fit into that batch. Also, Flush() is private so I don't believe theres any way to call it (unless theres some magical way to invoke private methods from an external project that I don't know about, and even doing so probably would not be a wise idea).
|
|
-
|
|
Re: Custom blend modes with SpriteBatch (immediate mode) - RenderState overlapping?
|
Yeah you can call any method, private or not, using runtime reflection. Going to have a think today to see if its worth it.
Thanks all
|
|
-
-
- (12853)
-
Team XNA
-
Posts
8,531
|
Re: Custom blend modes with SpriteBatch (immediate mode) - RenderState overlapping?
|
Kierenj: Should I just write my own textured-quad sprite engine?
I very much hope you wouldn't need to do that... If you do find a reason why that might become necessary, please let us know about it. We want to make SpriteBatch as widely useful as possible, so would love to hear about any important scenarios that it doesn't properly address. Kierenj:It still defers drawing of sprites until the sprite texture changes, right?
Yep. If you draw several sprites using the same texture in a row, even in immediate mode it will attempt to batch them. That's pretty important for performance: the difference between batched and non-batched draw calls is HUGE. Kierenj:Dare I invoke your secret Flush method? :))
I guess you could if you wanted, but I wouldn't myself. For one thing, calling methods through reflection is slow. For another, directly calling that internal code is not going to be significantly faster than just ending and then beginning a new batch (which is why we didn't make Flush public in the first place). There is a huge performance difference between drawing lots of sprites in a single batch, or splitting them over more than one batch. Once you've decided to issue more than one draw call, though, it really doesn't make that much difference how much extra code you call at this point, since you've already paid the big cost of losing your all-in-one-batch performance. How many sprites are we talking about here? If it's just a few dozen, I wouldn't worry about this: the time spent changing states and starting/ending the batch is going to be pretty much insignificant. If we are talking about thousands of sprites, I would reconsider your design. No matter how you handle the details of changing the states, drawing many sprites with a separate call for each is never going to be fast. To get good performance in that scenario, you really need to find some way to draw them all in a single batch, which usually boils down to using the per-sprite color parameter to encode whatever custom values you need to pass into your pixel shader.
XNA Framework Developer -
blog - homepage
|
|
-
|
|
Re: Custom blend modes with SpriteBatch (immediate mode) - RenderState overlapping?
|
Ok, I'll give some context - I have a directional (motion) blur pixel shader. Each frame, I get the movement 'delta' vector, and set it as the blur direction on the shader. Of course, it's different for each sprite.
In the sprite code I have something like (pseudo-code):
void Draw() { SpriteBatch batch = MyEngine.GetSuitableSpriteBatch(SpriteBlendMode.Whatever); MyEngine.SetMotionBlurEffectVector( ... ); batch.Draw( ... ); MyEngine.FinishedWithSpriteBatch(batch); }
Originally, GetSuitableSpriteBatch would create and Begin a new batch every time. In the current non-working state it just uses one batch per frame, checks to see if the renderstate needs to change for the blend mode, and updates as required.
I figured that with different Effect parameter values and possibly different blend modes for each sprite, it would be better to skip the overhead of creating a new batch each time? Since changing state and rendering one item buffer is less work than stopping a batch (includes rendering one item), starting a new batch (queue setup, just a few calls), as well as changing state.
It looks like my option is to do this?:
void Draw() { SpriteBatch batch = MyEngine.GetSpriteBatchWithMotionBlur(SpriteBlendMode.Whatever, motionBlurVector); batch.Draw( ... ); MyEngine.FinishedWithSpriteBatch(batch); }
..and I suppose GetSpriteBatchWithMotionBlur() would create a new batch, set the effect and parameter, call Effect.Begin(), EffectPass.Begin(), then return the batch for use.
(Kind of an aside point:) Thing is, I wanted to keep it generic AND keep Effects calls out of the Sprite class. I'm thinking future-proofing here as you might be able to tell: what happens if I have a multi-pass pixel shader or want to do some other kind of effect?
I'm fairly sure I could handle it with Delegates (I forget the correct syntax but here goes):
MyEngine.DrawSprite(delegate(SpriteBatch batch) { batch.Draw(...); })
i.e. DrawSprite would run through (begin, call the delegate param, end) for all passes as appropriate. This keeps the sprite render code in the Sprite class, and effect-dependant code in the MyEngine class. But it really doesn't look performance-friendly.
Maybe I'm barking up the wrong tree, after all with the mention of Motion Blur in the sprite class it's not really effects-independant. (end aside point)
Anyhoo, it just seems that if each sprite requires state changes, it's more effort to recreate a batch each time, than manually set the state each time and force a flush?
|
|
-
-
- (12853)
-
Team XNA
-
Posts
8,531
|
Re: Custom blend modes with SpriteBatch (immediate mode) - RenderState overlapping?
|
If you use a separate draw call per sprite, your performance will be bad. It makes no difference whether you end the batch or just change some effect parameters: the problem is that this requires you to issue a separate draw call per sprite, with only two triangles per draw call. That is going to perform poorly regardless of the details of how you implement it.
To make this fast, your only option is to draw all the sprites (or at least many sprites) in a single batch. That means you must pass all per-sprite data into your shader using the color parameter to SpriteBatch.Draw, which gets encoded directly into the vertex buffer and so need not interrupt the batch each time the data changes. In this case it seems like you could quite easily encode your blur vector into the r and g channels of the color parameter...
XNA Framework Developer -
blog - homepage
|
|
-
|
|
Re: Custom blend modes with SpriteBatch (immediate mode) - RenderState overlapping?
|
What an excellent idea. Thanks :)
Now I'm wondering about putting blending information in the vertex buffer (additive/alpha blended flag) - although the actual RenderState flags would have to be set one way or the other, the pixel shader 'adjusting' output colour as required. Doesn't seem possible without sampling the screen, and wouldn't work with overlapping additive-blended sprites without sampling after each draw. Sorry I'm waffling, its late here :) I don't suppose there is an easy way of doing this.
|
|
-
-
- (12853)
-
Team XNA
-
Posts
8,531
|
Re: Custom blend modes with SpriteBatch (immediate mode) - RenderState overlapping?
|
If you use premultiplied alpha (source blend = one, dest blend = inverse source alpha) it is actually possible to lerp between additive or translucent blending (or any point in between) just by altering your pixel shader outputs. Tom Forsyth has a good blog post on how this works.
XNA Framework Developer -
blog - homepage
|
|
-
-
- (10)
-
premium membership
-
Posts
162
|
Re: Custom blend modes with SpriteBatch (immediate mode) - RenderState overlapping?
|
I've been busy coming up with a good way to use Additive and Alpha blending together in a game. Up to now, thanks to the tips of Shawn, I've always used two Deferred SpriteBatches, one Alpha and one Additive. The disadvantage was that all additive sprites would be drawn either on top or below the alpha ones (depending on the End() order), and pixel shaders required to first use a rendertarget in between. As I'm writing a 2D game engine, I came up with a better solution, with those features: - Layer system, to be able to draw additive or alpha just where I want them
- Each layer has 5 sub layers: background additive, background subtractive, alpha, subtractive and additive
- Immediate SpriteBatches only, to allow pixel shaders effects
I was able to do with a batch of SpriteBatches. Basically, I use a struct called "DrawRequest" that contains all the necessary info for a Draw command (texure, position, source rect, color, stretch ration, pivot point, layer, blending mode, etc.), and when I want to "Draw", I register a new DrawRequest object in my engine inside the desired layer's collection of draw requests. Then when it's time to finally render the whole scene, I loop through each layer, starting with the bottom one, as well as between each sub layer, and I render everything using a single SpriteBatch ressource. If the blending mode needs to be changed (ex: I was drawing additive sprites and now I need to render alpha sprites), then I .End() the batch and restart it with the new blending mode, altering the RenderState if needed. This way I get best of both world, and if I really need optimization I just have to make sure that any layer with identical blending modes are drawn succesively, to avoid restarting the batch too much. I hope that this might help you. If you want to check the source, you can download my engine here (check the particle editor to see all the blending modes put together in action and use a few thousands particles to see that it's robust). Keep in mind that it's nowhere near usable though :) I just use codeplex to store my sources for now...
epsicode.netLittle Gamers: Teh Game IceCreamFree 2D XNA engine and authoring tool
|
|
|