RenderTargets draw order in XNA - c#

I think I am fundamentally misunderstanding the way render targets work. In my understanding RenderTargets are just Textures that the spritebatch draw calls draw to.
So I tried this code to render GUI windows in order to make sure they can only draw in their client area and its cropped outside that.
for (int i = Controls.Count - 1; i >= 0; i--)
{
RenderTarget2D oldTarget;
if (graphics.GetRenderTargets().Count() == 0) oldTarget = null;
else oldTarget = (RenderTarget2D)graphics.GetRenderTargets()[0].RenderTarget; // Get the old target being used.
graphics.SetRenderTarget(canvas); //set the target to a temporary RT
graphics.Clear(Color.Black); // Clear it
Control c = Controls[i]; // Get the current control (a form in this case)
c.Draw(spriteBatch, gameTime); // Draw it to the temp RT
graphics.SetRenderTarget(oldTarget); // Set the RT back to the main RT
Vector2 dest = c.DrawCoOrds(); // Gets the draw coordinates of the control
spriteBatch.Begin();
spriteBatch.Draw(canvas, new Rectangle((int)dest.X, (int)dest.Y, c.Bounds.Width, c.Bounds.Height), new Rectangle((int)dest.X, (int)dest.Y, c.Bounds.Width, c.Bounds.Height), Color.White);
// take the rect from the temp RT and draw it to the main RT.
spriteBatch.End();
}
However this code only draws the last form in the list which means it must be clearing the main RT somehow but i dont understand why. I only call clear when the RT is set to the temp canvas.

i think the best method to draw gui controls is with ScissorRectangle, because lets draw only inside that rectangle, that can be the client area of the gui control.
MSDN: GraphicsDevice.ScissorRectangle
You need to enable this funcionality through a RasterizerState.
RasterizerState ScissorState = new RasterizerState()
{
ScissorTestEnabled = true;
}
Before you draw, call SpriteBatch.Begin with this state.
A video of my own gui running in a xbox360 :)

How did you create your render target and back buffer? By default you can't write to a render target multiple times after changing to a different render target. This is why:
http://blogs.msdn.com/b/shawnhar/archive/2007/11/21/rendertarget-changes-in-xna-game-studio-2-0.aspx
You can change the default behavior by creating your render targets with RenderTargetUsage.PreserveContents., and the back buffer by overriding GraphicsDeviceManager.PrepareDeviceSettings., changing GraphicsDeviceInformation.PresentationParameters.RenderTargetUsage, as described in the link. Although I believe overriding those settings is done differently in XNA 4.
All that being said, changing away from the default behavior has performance considerations, and is not recommended. You should find a way to do this differently. One possibility would be to create a separate render target for each of your windows, draw all of them, switch to the back buffer and draw the render targets to it.
A better option would be to use the scissor rectangle rasterizer state as proposed by #Blau.

Related

XNA Disable Auto Fit to Screen

I'm using the XNA 4.0 framework to create a business intelligence screen that will be projected in a control room. The screen itself is designed to fit on two 1920 * 1080 projectors in series.
Currently I'm defining the resolution of the screen as follows:
graphics.PreferredBackBufferWidth = 3840;
graphics.PreferredBackBufferHeight = 1080;
However if I run the solution, XNA automatically 'squashes' the 2D graphics so that the entire screen fits on my primary 1920 * 1080 screen. How do you disable this 're-sizing' functionality in XNA? What I want to achieve is one big screen that I can show across two 1920 * 1080 monitors. Not a squashed screen that fits on one monitor.
Note that my XNA knowledge is very limited. I'm using SpriteFonts and Texture2D to create the graphic objects
Your solution should work if you are setting those values on the Game class constructor and have configured the screens to expand content between themselves.
Another, not really recommended, way to do it is to apply the changes on either the Initialize or LoadContent method.
In order to do this, just add the following line after setting the dimensions:
this.graphics.ApplyChanges();
So your whole thing would look like this:
protected virtual void Initialize()
{
this.graphics.PreferredBackBufferWidth = 1024;
this.graphics.PreferredBackBufferHeight = 600;
this.graphics.ApplyChanges();
}
If you do not want to use the ApplyChanges method, you can set the values on the class constructor without calling this method.
Also, be sure to check that the graphics.IsFullScreen property is not set to true.

Redraw panel contents after ClientSizeChanged

So my application runs in fixed size window and in full screen. The problem I'm facing is how to properly scale the current contents of the panel (which depend on the application use) when the window is resized. This is my current code:
private void Form1_ClientSizeChanged(object sender, EventArgs e)
{
System.Drawing.Drawing2D.Matrix transformMatrix = new System.Drawing.Drawing2D.Matrix();
float px = panel2.Width;
float py = panel2.Height;
panel2.Width = this.Width / 2;
panel2.Height = panel2.Width;
panel2.Location = new Point(this.Width - panel2.Width - 30, 30);
transformMatrix.Scale(panel2.Width / px, panel2.Height / py);
panel2.Region.Transform(transformMatrix);
//Rest of the code
}
But the drawn content doesn't scale, and if I use Invalidate() or Refresh() the drawn content gets cleared (the panel is redrawn empty). What am I missing?
.NET doesn't remember what's drawn on the panel, as simple as that. As soon as anything invalidates the windows bitmap buffer (causing a WM_PAINT), it's going to be repainted again. So, you have to draw what you want to draw using the Paint event (or overriding OnPaint).
However, there is another way that might be easier to implement - don't paint into a Panel. Instead, paint into a PictureBox (or rather, a Bitmap assigned to the Image property of the PictureBox). The Bitmap will be reused when invalidating (and redrawing) the picture box, so nothing will be lost. By using PictureBox.ScaleMode, you can define how you want the picture box to scale the bitmap, and it will do so as well as it can.
In any case, transforming the Region property doesn't do anything useful - you're simply changing the region, not doing anything to the drawing itself. To use 2D transformation matrices, you want to apply them on a Graphics object during the drawing (in Paint handler or OnPaint override) - drawing anything on the Graphics object will then transform everything you're trying to draw, which in your case means scaling the painting.
So you have to decide: do you want to just scale a stored bitmap with the painted image, or do you want to redraw it all from scratch (which also means you can pick any level of detail you can provide)?
I think that you're mistaking what the Region property is meant for. According to the MSDN docs (empasis mine, replace 'window' with 'control' when reading):
The window region is a collection of pixels within the window where the operating system permits drawing. The operating system does not display any portion of a window that lies outside of the window region. The coordinates of a control's region are relative to the upper-left corner of the control, not the client area of the control.
All that you're doing is changing the region that the OS will allow painting, which explains why you're not seeing anything. I think that you should be resizing the control when the form is resized, either through Anchor, or through my preference of Dock with several controls, or a panel like TableLayoutPanel where it will handle scaling and relative sizing for you.
Thank you for your answers, but I wrote my own function and logic that serves the purpose for this application. Basically the function checks for the state of the application variables, and calls the appropriate function that originally drew the content, and since those functions use the panel width and height as arguments they properly scale the drawn content and retain the drawing composition.
P.S. I'll accept Luaan's answers since it offers a valid alternative and is complete.

Render Large Canvases in UserControl

I've been having trouble trying to implement this for a couple of days now. I've searched extensively on similar questions in regards to what I'm trying to do but I haven't come across a question that helps my issues directly.
Basically I'm rendering tiles onto a grid on my UserControl class. This is for my Tile Engine based world editor I'm developing. Here is a screenshot of an open world document and some tiles brushed on.
Initially, I was going to use a Bitmap in my control that would be the world's preview canvas. Using a brush tool for example, when you move your mouse and have the left button down, it sets the nearest tile beneath your cursor to the brush's tile, and paints it on the layer bitmap. The control's OnPaint method is overridden to where the layer bitmap is draw with respect to the paint event's clipping rectangle.
The issue with this method is that when dealing with large worlds, the bitmap will be extremely large. I need this application to be versatile with world sizes, and it's quite obvious there are performance issues when rendering large bitmaps onto the control each time it's invalidated.
Currently, I'm drawing the tiles onto the control directly in my control's overridden OnPaint event. This is great because it doesn't require a lot of memory. For example, a (1000, 1000) world at (20, 20) per tile (total canvas size is (20000, 20000)) runs at about 18mb of memory for the whole application. While not memory intensive, it's pretty processor intensive because every time the control is invalidated it iterates through every tile in the viewport. This produces a very annoying flicker.
What I want to accomplish is a way to meet in the middle as far as memory usage and performance. Essentially double buffer the world so that there isn't flickering when the control is redrawn (form resize, focus and blur, scrolling, etc). Take Photoshop for example - how does it render the open document when it overflows the container viewport?
For reference, here's my control's OnPaint override that is using the direct draw method mentioned above.
getRenderBounds returns a rectangle relative to PaintEventArgs.ClipRectangle that is used to render visible tiles, instead of looping through all the tiles in the world and checking if it's visible.
protected override void OnPaint(PaintEventArgs e)
{
WorldSettings settings = worldSettings();
Rectangle bounds = getRenderBounds(e.ClipRectangle),
drawLocation = new Rectangle(Point.Empty, settings.TileSize);
e.Graphics.InterpolationMode =
System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
e.Graphics.SmoothingMode =
System.Drawing.Drawing2D.SmoothingMode.None;
e.Graphics.PixelOffsetMode =
System.Drawing.Drawing2D.PixelOffsetMode.None;
e.Graphics.CompositingQuality =
System.Drawing.Drawing2D.CompositingQuality.HighSpeed;
for (int x = bounds.X; x < bounds.Width; x++)
{
for (int y = bounds.Y; y < bounds.Height; y++)
{
if (!inWorld(x, y))
continue;
Tile tile = getTile(x, y);
if (tile == null)
continue;
drawLocation.X = x * settings.TileSize.Width;
drawLocation.Y = y * settings.TileSize.Height;
e.Graphics.DrawImage(img,
drawLocation,
tileRectangle,
GraphicsUnit.Pixel);
}
}
}
Just comment if you need some more context from my code.
The trick is to not use a big bitmap for this at all. You only need a bitmap covering the visible area. Then you draw whatever is visible.
To achieve this you will need to maintain the data separately from the bitmap. This can be a simple array or an array/list with a simple class holding information for each block such as world position.
When your block is within the visible area then you draw it. You may or may not have to iterate through the whole array, but that isn't really a problem (you can also calculate the visible array on a separate thread). You can also make the function more intelligent by creating region indexes so you don't iterate all blocks.
To add a new block to the array, calculate it's canvas position to world coordinates, add it and then render the array again (or the area where the block is drawn).
This is how controls with scrollable areas are drawn by the system too.
Enable double-buffering will keep it clear and flicker-less.
In this case I would also use a panel with separate scroll bars and calculate the scroll-bars' relative position.

Changing stroke color dynamically

I am using stylus input to draw lines in a canvas. I want to change the color of the stroke with the pen pressure. so I used:
DrawingAttributes dattribute = new DrawingAttributes();
inkcan.EditingMode = InkCanvasEditingMode.Ink;
if (stylusInput.pressureFactor < 0.5)
dattribute.Color = Colors.Red;
else
dattribute.Color = Colors.Blue;
inkcan.DefaultDrawingAttributes = dattribute;
but I have found that the color changes only when I lift off and retouch the pen to tablet surface. I am not sure how to fix that problem.
Any help would be greatly appreciated.
Look at this question: InkCanvas Eraser
In the MSDN it states:
If you change the EraserShape, the cursor rendered on the InkCanvas is
not updated until the next EditingMode change.
The effect you are experiencing might be caused by the EditingMode being changed when you pull the pen off the canvas and put it back down.
If so, you could toggle the EditingMode property as I suggested in the linked answer.
EDIT
Have a look at this a 3rd down it says:
Off course, life is never as simple as that, so there is one more
little issue to handle. Apparently, the InkCanvas uses different
renderers for the final result compared to while the strokes are being
drawn. To show a transparency based on the pressure while the
drawing action is still in progress, we need to use the protected
property called DyamicRenderer which gets/sets the object used to
render the strokes on a drawing context while the stroke is being
drawn. This rendering object must be a descendent of DynamicRenderer.
All you need to do here is override the OnDraw method and change the
brush that is used. When you assign a new value to this property, the
InkCanvas actually changes an internal 'PlugIn list' which is called
whenever data is entered using the stylus.
This might be it.
The if condition is evaluated only once, so there is no reason for the colour to change while you are drawing. Unfortunately, there seems to be no "onpressurechanged" event, so you would probably have to set up a loop that checks the pressure every x milliseconds and adjusts the colour accordingly.
Since you don't want to freeze the UI, you would either need to run it in another thread and delegate the colour change back to the UI thread, or you can queue the pressure checks onto the window dispatcher with "applicationIdle" priority. This would look something like:
void checkPressure(InkCanvas inkcan)
{
//return if touch is lifted code here
DrawingAttributes dattribute = new DrawingAttributes();
if (stylusInput.pressureFactor < 0.5)
dattribute.Color = Colors.Red;
else
dattribute.Color = Colors.Blue;
inkcan.DefaultDrawingAttributes = dattribute;
this.Dispatcher.BeginInvoke(new MyPressureDelegate(checkPressure), DispatcherPriority.ApplicationIdle, inkcan);
}
assuming your stylusInput shares scope with the function, of course. Otherwise you'd need to pass it in along with the canvas in an object array.

Graphics are too slow in my project

I am developing zoo simulator project. It contains three thing types to draw: a map, animal environments and the animals themselves. The map is too big to fit on screen, player needs to move screen to see other parts of it. I am using a timer to draw. On its tick, it calls Invalidate() for the form being drawing on. In ZooForm_Paint method, I first draw every thing in the map on mapBuffer Bitmap. Since mapBuffer is too big to fit on screen, I draw (on screen) the part of mapBuffer the player is where.
Unfortunately, it seems that drawing everything in the map (although it may not be viewed) on mapBuffer slows the game. Can I draw my evironments and animals without need to draw entire map first?
How?
My code:
public void DrawGame(Graphics g, ref Point locationOnMap)
{
this.drawBufferMap();
this.drawMapLocation(g, ref locationOnMap);
}
private void drawBufferMap()
{
Bitmap buffer = new Bitmap(this.map.Size.Width, this.map.Size.Height);
using (Graphics graphics = Graphics.FromImage(buffer))
{
graphics.DrawImageUnscaled(this.map.Picture, new Point()); // draw entire map
foreach (var item in this.zoo.Environments) // draw all env.
{
graphics.DrawImageUnscaled(item.Picture, item.Bounds.Location);
}
foreach (var item in this.zoo.ILocatables) // draw all ILocatables
{
graphics.DrawImageUnscaled(item.Picture, item.Location);
}
}
if (this.mapBuffer != null)
{
this.mapBuffer.Dispose();
}
this.mapBuffer = buffer;
}
private void drawMapLocation(Graphics g, ref Point location)
{
g.DrawImage(this.mapBuffer, new Rectangle(0, 0, viewSize.Width, viewSize.Height),
new Rectangle(location.X, location.Y, viewSize.Width, viewSize.Height), GraphicsUnit.Pixel);
}
I don't think you are going to get any easy solutions. I can offer a few tips and opinions:
You seem to be creating a new BitMap every time you paint the screen. This is definitely not a good idea, as large bitmaps are absolutely huge in terms of memory. What you probably want to do is create one when your game loads, and then simply clear it and repaint it at every frame. I think this is probably one of the bigger performance issues you have.
There are a number of optimisations you could make afterwards. E.g. you are "rendering" the image that you will end up painting to the screen on the user interface thread. If the rendering process takes long, this will be noticeable. Typically this work happens on a background thread, and then the UI thread just checks if it can repaint using the new image. (I am simplifying things greatly here).
For graphics intensive applications, WinForms is not a particularly good environment, as others have pointed out. You will not get any hardware acceleration at all. Moving to XNA is one option, but if your application is also quite rich in terms of standard WinForms screens and controls, this is probably not an easy option. Another suggested alternative might be WPF, where you might be able to get away with using transformations to move things around, which are hardware accelerated, and are not too dissimilar to a WinForms application (well, you don't need to implement your own buttons, etc).
Hope this helps a bit.
As Daniel pointed out: creating a new bitmap each time you need to draw your map will decrease performance. Reuse the same bitmap over and over instead.
Creating a bitmap larger that you need is also very bad for performance. If you need it to scroll around, then it's fine. But if you paint a new image each time anyway, then you should just create it exactly the same size you need. Then you can call Graphics.TranslateTransform to compensate for the new coordinates so you can leave your existion code unchanged.
This will make it possible for GDI+ to clip your graphics and simply just don't draw things outside your map bitmap - which will speed things up.

Categories