Drawing a second RenderTarget2D to the back buffer overwrites the first one - c#

My problem is that I'm trying to get two different RenderTarget2Ds drawn to the screen, but only the last-drawn one is shown. The code is similar to the following, and gets called twice, once by each instance of a class that needs to be drawn with the Draw method:
public override void Draw()
{
gfxDevice.SetRenderTarget(myRenderTarget);
gfxDevice.Clear(Thistle);
//pseudocode
foreach (something in a list)
{ spritebatch.begin();
spritebatch.DrawString(something);
spritebatch.end();
}
//reset to the backbuffer
gfxDevice.SetRenderTarget(null);
gfxDevice.Clear(backgroundColor) //Here is what I thought the offending line was
//draw the RenderTarget to backbuffer
spriteBatch.Begin();
spriteBatch.Draw(myRenderTarget, rect, this.backgroundColor);
spriteBatch.End();
What I thought would be the solution is to stop clearing the graphicsDevice each time the Draw() method gets called, but if I don't, everything but the freshly drawn rendertarget gets drawn an ugly purple. Like this:
The purple results from End()ing the spritebatch, I think, thanks to this question
What do I need to change in order for both RenderTargets get drawn properly, meaning both widgets get drawn, along with the proper Color.Thistle background?
Ideally, the screen would look like a combination of both: and

You're correct that you need to remove the gfxDevice.Clear(backgroundColor) call. If you draw to the backbuffer, and then clear the backbuffer, then obviously anything you drew before clearing isn't going to show up!
You want to clear the backbuffer before rendering anything, i.e. at the beginning of the frame.
EDIT
After taking a more thorough look at the source code provided, I think I know what's actually going on here--it's one of the tricky things about using render targets.
The weird purple you're seeing is not, in fact, caused by SpriteBatch.End(). That's the color that render targets are automatically cleared to when XNA resets their memory.
When does XNA clear out a render target's memory? Whenever that render target is set onto the graphics device as an active target. So whenever you call SetRenderTarget(null), XNA is obliterating the backbuffer's memory and resetting it to that lovely purple.
To avoid this, you need to draw all of your render targets before drawing anything to the backbuffer. Then, set the backbuffer as your active render target, and draw all of the render targets you updated previously in a single pass.

Related

C# Monogame Performance when Drawing Thousands of SpriteBatch.DrawString()

I'm currently creating a large map, that consists of a lot of rectangles (33,844), that all have a unique name (label), which I'm drawing on top of them using a SpriteFont.
Drawing all of the rectangles takes no performance hit at all. But, as soon as I try to write all of their labels with DrawString(), my performance goes into the dumps.
In my head, I would like to draw all my rectangles and text to one texture all at once, and only have to keep redrawing that entire finished texture. My issue is, this is an enormous map, and some of the coordinates for the rectangles are very high (example: one slot's x is 14869 and y is 23622), and they're far bigger than a Texture2D allows.
Since this is a map, I really only need to draw the entire thing once, and then allow the user to scroll/move around it. There's no need for me to continually redraw all of the individual rectangles and their labels.
Does anyone have experience with this type of situation?
Try to only render the labels that you can see on the screen and if you can zoom back far enough, just don't render them.
Textrendering is expensive, since it is basically creating a rectangle to draw on for every character in the font and then applying the same RGBA texture to it. So depending on the number of characters you write, the number of rectangles increases. This means four new vertices per character.
Depending on what you write you could simply create a texture with the text already on it and render that, but it won't be very dynamic.
EDIT: I need to clarify something.
There's no need for me to continually redraw all of the individual rectangles and their labels.
This is wrong. You have to draw the whole thing every frame. Sure, it doesn't increase memorywise, but it still is a lot to render and you will need to render it every frame.
But as I said: Try to only render the labels and the rectangles that collide with the screenboundaries, then you should be fine.
There are two ways to solve your problem.
You can either render your map to a RenderTarget(2D/3D) or you can cull the rectangles/text that are offscreen. However, I am not 100% sure that RenderTargets can go as large as you would need, but you could always segment your map into multiple smaller RenderTargets.
From more information on RenderTargets, you might want to check out RB Whitaker's article on them, http://rbwhitaker.wikidot.com/render-to-texture
Culling, in case you are familiar with the term when used in this context, means to only render what is visible to the end-user. There are various ways that culling can be implemented. This does however require you to have already implemented a camera (or some type of view region) and you perform a basic axis-aligned bounding box collision (AABB collision, which MonoGame's Rectangle supports out of the box) of the rectangles against the camera's viewport and only render it if there is a collision.
A basic implementation of culling would look something like this:
Rectangle myRect = new Rectangle(100, 100, 48, 32);
public void DrawMapItem(SpriteBatch batch, Rectangle viewRegion)
{
if (viewRegion.Contains(myRect))
{
//Render your object here with the SpriteBatch
}
}
Where 'viewRegion' is the area of you world that the camera/end-user can actually see.
You can also combine the two methods, and render the map to multiple render targets, and then cull the render targets.
Hope this helps!

RenderTarget2D not preserving transparent background

I'm working on a project. All 2D using spriteBatch.
I'm having things like explosions use custom effects that do not apply to the rest of the image. So here is my flow so far:
1)Clear the Background
2)Draw all the explosion sprites
3)Capture that image and implement my effects into a separate RenderTarget2D
4)Draw my Background
5)Draw the RenderTarget2D created in step 3
6) Draw everything else
The problem I'm running into is the RenderTarget2D created is not transparent in the areas not drawn on. As a result, the background drawn in step 4 is not shown.
I have tried GraphicsDevice.Clear(Color.Transparent) following any calls to SetRenderTarget(null). However, I am still getting that purple background.
Any ideas?
I'd post code, but there's too much for you all to have to parse through.
if you follow this flow it should work;
GraphicsDevice.SetRenderTarget(renderTarget);
GraphicsDevice.Clear(Color.Transparent);
// Draw stuff to texture
GraphicsDevice.SetRenderTarget(null);
GraphicsDevice.Clear(BackgroundColor); // Important to clear here
// Draw background
// Draw texture
// Draw stuff

is there any other way to avoid redrawing for the 2nd viewport?

I have to add a second viewport (= like splitscreen) to allow the player to see an event somewhere else on the current level.
Is there any possibility to draw the event area without redrawing every things already drew ?
[EDIT]
RenderTarget2D is the key. Thx User1459910 for all.
It almost worked.
New questions :
I've searched for a while and still don't find a tutorial about "Xna 2D camera with source and destination rectangle" if you have a link, i'd like to see it, please ♥
Currently, the drawing code looks like this :
protected override void Draw(GameTime gameTime)
{
/*
...
here is the code to "draw" in the renderTarget2D renderAllScene object
...
*/
//Let's draw into the 2 viewports
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.LinearClamp, null, null, null, camera1.transform);
spriteBatch.Draw(renderAllScene, viewport1.Bounds, Color.White);
spriteBatch.End();
if (EventIsRunning)
{
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, SamplerState.LinearClamp, null, null, null, camera2.transform);
spriteBatch.Draw(renderAllScene, viewport2.Bounds, Color.White);
spriteBatch.End();
}
*The Viewport 1 is great. The camera follows the character but after moving the camera for a short distance, the map is cutted at 1280pixels i think. so it drew only 1280pixels of all the map. I don't know why. Maybe because i failed when i created renderAllScene = new RenderTarget2D. :x
renderAllScene = new RenderTarget2D(GraphicsDevice, GraphicsDevice.PresentationParameters.BackBufferWidth, GraphicsDevice.PresentationParameters.BackBufferHeight);
*For the Viewport 2 : I need the source rectangle. I'll try it tomorrow.
I'll assume you are making a 2D game with NOTHING 3D at all.
Here is what you could do:
You need to render the whole map, and all game objects that appear on it, on a Texture. If you don't know how to render to a Texture, here is the procedure:
Create a RenderTarget2D object
On the Draw function, before you render anything, you must call the graphicsDevice.SetRenderTarget() method, and set the RenderTarget2D you created.
After you are done rendering, call graphicsDevice.SetRenderTarget(null) to reset the render target to the default one. You must do it or you'll have problems!
To render the RenderTarget2D, simply use SpriteBatch.Draw((Texture2D)renderTarget2D, position, color), being "renderTarget2D" of course the name of the RenderTarget2D you created.
Then, you use two 2D Cameras. One will display where the hero is, and the other one will display the event area.
A 2D camera is basically a trick with Source and Destination rectangles. The trick is to use a Source Rectangle to define the area that displays the hero and the area around it and use the main Viewport as the Destination Rectangle, and use another Source Rectangle to define the event area and another Destination Rectangle as the second Viewport.
If you have doubts, google about "XNA 2D Camera", and research about Source and Destination rectangles on the MSDN's article for SpriteBatch.Draw().

C# XNA 2D trail effect optimization

Currently as a trail effect in my game I have for every 5 frames a translucent texture copy of a sprite is added to a List<> of trails.
The alpha values of these trails is decremented every frame and a draw function iterates through the list and draws each texture. Once they hit 0 alpha they are removed from the List<>.
The result is a nice little trail effect behind moving entities. The problem is for about 100+ entities, the frame rate begins to drop drastically.
All trail textures come from the same sprite sheet so i dont think it's batching issue. I profiled the code and the CPU intensity is lower during the FPS drop spikes then it is at normal FPS so I assume that means its a GPU limitation?
Is there any way to achieve this effect more efficiently?
Heres the general code im using:
// fade alpha
m_alpha -= (int)(gameTime.ElapsedGameTime.TotalMilliseconds / 10.0f);
// draw
if (m_alpha > 0) {
// p is used to alter RGB of the trails color (m_tint) depending on alpha value
float p = (float)m_alpha/255.0f;
Color blend = new Color((int)(m_tint.R*p), (int)(m_tint.G*p), (int)(m_tint.B*p), m_alpha);
// draw texture to sprite batch
Globals.spriteBatch.Draw(m_texture, getOrigin(), m_rectangle, blend, getAngle(), new Vector2(m_rectangle.Width/2, m_rectangle.Height/2), m_scale, SpriteEffects.None, 0.0f);
} else {
// flag to remove from List<>
m_isDone = true;
}
I guess i should note, the m_texture given to the trail class is a reference to a global texture shared by all trails. Im note creating a hard copy for each trail.
EDIT: If I simply comment out the SpriteBatch.Draw call, even when im allocating a new trail every single frame for hundreds of objects there is no drop in frames... there has got to be a better way to do this.
Usually for trails, instead of clearing the screen on every frame, you simply draw a transparent screen-sized rectangle before drawing the current frame. Thus the previous frame is "dimmed" or "color blurred" while the newer frame is fully "clear" and "bright". As this is repeated, a trail is generated from all the previous frames, which are never cleared but rather "dimmed".
This technique is VERY efficient and it is used in the famous Flurry screensaver (www.youtube.com/watch?v=pKPEivA8x4g).
In order to make the trails longer, you simply increase the transparency of the rectangle that you use to clear the screen. Otherwise, you make it more opaque to make the trail shorter. Note, however, that if you make the trails too long by making the rectangle too transparent, you risk leaving some light traces of the trail that due to alpha blending, might not completely erase even after a long time. The Flurry screensaver suffers from this kind of artifact, but there are ways to compensate for it.
Depending on your situation, you might have to adapt the technique. For instance, you might want to have several drawing layers that allow certain objects to leave a trail while others don't generate trails.
This technique is more efficient for long trails than trying to redraw a sprite thousands of times as your current approach.
On the other hand, I think the bottleneck in your code is the following line:
Globals.spriteBatch.Draw(m_texture, getOrigin(), m_rectangle, blend, getAngle(), new Vector2(m_rectangle.Width/2, m_rectangle.Height/2), m_scale, SpriteEffects.None, 0.0f);
It is inefficient to have thousands of GPU calls like Draw(). It would be more efficient if you had a list of polygons in a buffer, where each polygon is located in the correct position and it has transparency information stored with it. Then, with a SINGLE call to Draw(), you can then render all polygons with the correct texture and transparency. Sorry I cannot provide you with code for this, but if you want to continue with your approach, this might be the direction you are headed. In short, your GPU can certainly draw millions of polygons at a time, but it can't call Draw() that many times...

Changing RenderTarget Results in Purple Screen?

I'm attempting to change RenderTargets at runtime, so I can draw some elements at runtime, manipulate them and then finally draw the texture to the screen. Problem is, the screen turns purple if I change the RenderTarget at runtime. Here's the code I've got in Draw:
RenderTarget2D tempTarget = new RenderTarget2D(GraphicsDevice, 128, 128, 1,
GraphicsDevice.DisplayMode.Format, GraphicsDevice.PresentationParameters.MultiSampleType,
GraphicsDevice.PresentationParameters.MultiSampleQuality, RenderTargetUsage.PreserveContents);
GraphicsDevice.SetRenderTarget(0, tempTarget);
GraphicsDevice.Clear(ClearOptions.Target, Color.SpringGreen, 0, 0);
GraphicsDevice.SetRenderTarget(0, null);
It doesn't seem to matter how I create the RenderTarget, if I do it at runtime (and I do need to create in-memory textures at runtime and draw on them with SpriteBatch) it results in an entirely purple screen. What can I do to fix this?
It looks like the best option is to create the RenderTarget somewhere other than Draw, draw to it during Update, save the resulting texture (and manipulate as necessary) then draw that texture during Draw.
I know this is late, but the solution is to write to the RenderTarget BEFORE you clear the screen and beginning drawing your other items.
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.SetRenderTarget(_renderTarget);
//...
//Perform Rendering to the specified target
//...
GraphicsDevice.SetRenderTarget(null);
GraphicsDevice.Clear(Color.CornflowerBlue);
//...
//Code that draws to the users screen goes here
//...
}
This should prevent you from rendering in the Update method as suggested by others, which is counter-intuitive in many aspects.
When spritebatch.End() is called objects are written to the backbuffer or in your case to tempTarget. To make the texture,
change the target
call begin
call all of the draws
end the spritebatch
set target back to null
then use the render2d

Categories