XNA: Rendering stuff to a Texture2D, then rendering that Texture2D to the screen

Started by
8 comments, last by Billr17 15 years, 4 months ago
I have a class in my 2D game which currently draws some stuff to the screen. What I'd like to do is have it instead draw some stuff to a Texture2D object, then draw that Texture2D to the screen based off of some scale/rotation/position variables. Suppose my game's Draw function looks somewhat like this:

screen.Begin(); //"screen" is a SpriteBatch
foreach(Widget item in AllMyWidgets)
{
     //each widget has its own draw function, which takes 
     //the spritebatch as its argument
     item.Draw(screen);
}
screen.End();
Now for one specific type of Widget, there are multiple things to draw, and I want to draw them all to a simple Texture2D, then draw that Texture2D using screen.Draw() and passing it my scale, rotation, position, origin variables. It would greatly simplify things by eliminating all the transformations of individual "thingies" I would have to otherwise calculate. How would I go about doing this? [Edited by - BTownTKD on April 22, 2009 2:45:45 PM]
Deep Blue Wave - Brian's Dev Blog.
Advertisement
This is pretty simple. What you want to do, is make a RenderTarget2D. Specify the size you want (you can make it the same size as your screen, if you want), and then specify that you only want one 1 surface level and that you want it to be SurfaceFormat.Color. Then you can render to this surface by using GraphicsDevice.SetRenderTarget and specifying 0 as the renderTargetIndex, and then rendering with SpriteBatch as usual. Once you're done with all of that, call SetRenderTarget again and pass "null" as the RenderTarget to specify that you want to render to the backbuffer again. At this point you can access your RenderTarget as a texture by calling GetTexture, which you can then pass to your SpriteBatch.
Thanks for the help - sounds simple enough. This would all occur within that specific Widget's Draw() function, and I would have already called screen.Begin(); will that effect it in any way? I.e. will I need to call screen.End(), set the new target, call Begin() again, change the target back to the back buffer, call End(), Begin(), etc? Or do I completely ignore the Begin() and End() functions through this whole ordeal? <--My hopeful answer :)

Thanks again.

[Edited by - BTownTKD on April 22, 2009 2:06:14 PM]
Deep Blue Wave - Brian's Dev Blog.
You need to have the correct RenderTarget set when you call SpriteBatch.End(). See when you call End, that's when the SpriteBatch does all of the actual drawing. So you'll have to do things like this:
// We're going to render all of the widgets to a texturegraphicsDevice.SetRenderTarget(0, widgetTexture);screen.Begin(); //"screen" is a SpriteBatchforeach(Widget item in AllMyWidgets){     //each widget has its own draw function, which takes      //the spritebatch as its argument     item.Draw(screen);}// Draw all the sprites we've queued upscreen.End();// Now draw the texture to the backbuffergraphicsDevice.SetRenderTarget(0, null);screen.Begin();screen.Draw(widgetTexture.GetTexture(), ...);screen.Begin();

Ah. I see what you're saying. My problem now is my own framework architecture. Continuing with my example so far (I've changed my class names to simplify things), each Widget object assumes, in its Draw() function, that the encompassing scope calls screen.Begin() at the beginning and screen.End() at the end.

for example:
screen.Begin(); //"screen" is a SpriteBatchforeach(Widget item in AllMyWidgets){     //each widget has its own draw function, which takes      //the spritebatch as its argument.     //Also, each item REQUIRES that screen.Begin() was already called.          item.Draw(screen); //RenderTarget gets changed within this function                        //for one specific type of Widget}screen.End();


So my problem is the fact that I want to render the contents of ONE particular type of widget to a Texture2D and then render the result to the screen (the other widgets just draw to the screen), but I've already called screen.Begin() in the enclosing scope, and some other Widget objects have already been drawn to that SpriteBatch. When I try to change the RenderTarget during mid-draw like this, I get some very amusing - but very wrong - results. And I can't imagine that calling "screen.End()" as the first line of a widget's draw() function would be in good form.

The solution, then, seems to be that I need to call screen.Begin() and screen.End() inside each widget in order to give them control over custom RenderTargets, instead of relying on the parent scope to do this job. Does this cause a loss in performance, since screen.Begin() and screen.End() could be called hundreds of times in a single Draw() loop? Also, how does this effect depth sorting? I have some Widgets which I like to draw in the background, and some I like to draw in the forground, and I rely on the "float depth" variable to keep track of this all.

What would you suggest, as far as restructuring this drawing sequence?

P.s. If you're wondering what the hell I'm trying to accomplish; one of my Widgets is basically an entirely separate, self-contained scene, and I want to render a preview thumbnail to the current scene. As an additional possible expansion, I'd like to add a "RenderTexture2D()" method to my base Widget class, which basically renders the entire widget to a Texture2D. Could make for some cool possibilities.

[Edited by - BTownTKD on April 22, 2009 2:54:20 PM]
Deep Blue Wave - Brian's Dev Blog.
One of the XNA Developers (Shawn Hargreaves) has an excellant blog that he often details stuff like this. If your targeting XBox 360 with this, make sure to read the blog post I list below. The way render targets work on the 360 is different then Windows.

Render Target Changes in GSE 2.0

What you will want to do is draw each of your render targets first. After each render target is filled with the data you want, you will want to call GetTexture() to save off the data as an Texture2D. This is the only safe way to guarentee the data will remain once you start to fill you next render target (on the 360). Technically you can do this in XNA 2.0 and above. However, it is a very expensive operation to perform. Finally you can proceed to render everything to the back buffer.

To summarize in sudo code, it may look like:
widgets = QueryForOffScreenWidgets()foreach widget in widgets    tex2d = DrawWidget(widget)   RememberTex2DReferenceSomewhere(tex2d) DrawFrame()
I hesitate to leap at that implementation, as I was really hoping to keep all drawing within each single Widget.Draw() function. I really like the simplicity, and don't want to add extra steps to the enclosing component's drawing loop. What about my proposed solution of putting begin() and end() within each Widget's draw function? What happens as far as extra overhead, depth-sorting, etc?

If I were to go with your suggestion, I suppose I could add a function to each widget called "PreDraw(screen)" or something, which would resolve the Texture2D for the widgets that need it, before I call screen.Begin() and all the Widget.Draw()'s...

something for me to think about.

Edit: After some extended reading on SpriteBatches and reading some other posts, I see that many Begin() and End() pairs would be a bad idea, and I'd lose the spritebatch's internal depth sorting. Thanks to both of you guys for your help.

[Edited by - BTownTKD on April 22, 2009 2:36:54 PM]
Deep Blue Wave - Brian's Dev Blog.
Let me put it this way. Are you planning to target the XBox 360? How many off-screen surfaces do you need to generate with a render target?

If you are just drawing a single surface, you might be able to get away with what you want. In the RenderTarget2D constructor simply specify RenderTargetUsage.PreserveContents for the RenerTargetUsage. But be warned that this operation is fairly expensive on the 360. If you are going to be drawing many off-screen surfaces, I would highly suggest doing something similar to what I previously outlined.
BTownTKD:

Calling SpriteBatch.Begin and SpriteBatch.End multiple times will result in each sprite getting drawn with its own DrawPrimitive call, rather than them being batched up into a single call (which causes an extra performance drain on the CPU). Actually...sprites will only get batched when they use the same texture. So if all your widgets are using different textures, there wouldn't really be any difference.

Also if you want you can just specify SpriteSortMode.Immediate when you call Begin, and this will make the SpriteBatch draw the sprite immediately when you call Draw, rather than waiting until you call End. That's probably exactly what you want. In fact...I should have mentioned that before. :P


Billr17:

You don't need to call RenderTarget2D.GetTexture to make the 360 resolve the contents of the eDRAM to a texture. This happens automatically whenever you switch to a different RenderTarget. Shawn even says this right in that blog entry:

In 2.0, you no longer need to call the GraphicsDevice.ResolveRenderTarget method. In fact you can't, because we removed it. We now do this automatically when you switch away from the rendertarget.

The big restrictions with RenderTargets on the 360 (or on the PC when you don't specify RenderTargetUsage.PreserveContents) are that you can't render to a RenderTarget, render to a different RenderTarget, then render to the first RenderTarget again and expect all the stuff you originally rendered to still be there. (This applies to DepthStencilBuffers as well, since those also reside in eDRAM).
Quote:Original post by MJP
You don't need to call RenderTarget2D.GetTexture to make the 360 resolve the contents of the eDRAM to a texture.


opps, your right... Thanks for the correcton!

This topic is closed to new replies.

Advertisement