Graphics are rendered outside the form with GDI+ - c#

I am currently making a game with GDI+, I know it is not the optimal solution for developing a game, but since it is a school project I have no choice.
About every tenth time I run my game, the graphics gets rendered outside the form in the top left corner of the screen.
I'm using double buffering if that helps to narrow the problem down.
The rendering code looks like this:
while (true)
{
// Create buffer if it don't exist already
if (context == null)
{
context = BufferedGraphicsManager.Current;
this.buffer = context.Allocate(CreateGraphics(), this.DisplayRectangle);
}
// Clear the screen with the forms back color
this.buffer.Graphics.Clear(this.BackColor);
// Stuff is written to the buffer here, example of drawing a game object:
this.buffer.Graphics.DrawImage(
image: SpriteSheet,
destRect: new Rectangle(
this.Position.X
this.Position.Y
this.SpriteSheetSource.Width,
this.SpriteSheetSource.Height),
srcX: this.SpriteSheetSource.X,
srcY: this.SpriteSheetSource.Y,
srcWidth: this.SpriteSheetSource.Width,
srcHeight: this.SpriteSheetSource.Height,
srcUnit: GraphicsUnit.Pixel);
// Transfer buffer to display - aka back/front buffer swapping
this.buffer.Render();
}
It's easier to explain with a screenshot:

It was a bit of a design mistake in Winforms to make the BufferedGraphicsXxx classes public. They are an implementation detail of double-buffering support in Winforms and they are not terribly resilient to using them wrong.
You are definitely using the BufferedGraphics you get back from Allocate() wrong. You create buffers at a high rate, inside the game loop. But you forget to dispose the buffer you used at the end of the loop. This will consume device contexts (HDC's) at a high rate. That doesn't go on forever, if your program doesn't otherwise get the garbage collector running then Windows pulls the plug and will not let you create a new device context. The internal CreateCompatibleDC() call will fail and returns NULL. The BufferedGraphicsContext class otherwise misses the code to check for this error and plows on with the NULL handle. And starts painting to the desktop window instead of the form.
A fix will be to move the Allocate() call outside of the loop so you do it just once. But now you'll have a new problem when the user changes the window size, the buffer is no longer the correct size.
The better mousetrap is to just not use the BufferedGraphics class but leave it up to Winforms to get it right. There are several ways to get a gameloop in Winforms, but the simplest one is to just use the OnPaint() method to render the scene and immediately ask for another paint so it keeps getting called over and over again. Similar to this:
public partial class Form1 : Form {
public Form1() {
InitializeComponent();
this.DoubleBuffered = true;
this.ResizeRedraw = true;
}
protected override void OnPaint(PaintEventArgs e) {
RenderScene(e.Graphics);
this.Invalidate();
}
}
Where RenderScene() should draw the game objects, using the passed Graphics instance. Note that you no longer need to use Clear(), that was already done.

About every tenth time I run my game, the graphics gets rendered
outside the form in the top left corner of the screen.
From the screen shot and your description, you are occasionally drawing to the Window's Desktop device context (DC); Which is the effect of using a window handle of zero (IntPtr.Zero) when getting the DC.
This lead me to believe you could be starting the game loop before the form window has been created resulting in the graphics context to point to a zero'd window handle.
As confirmed in the commentary you are using a separate thread for your game loop resulting in the random behavior of this happening. Once dealing with threads, you don't always get the same result twice when it comes to timing of start up and completion of threads (especially when threads can run parallel, via a multi-core/cpu computer). Each time the game application is ran, there is a chance the game loop thread can start-up and execute before the form window on the UI thread can be created and shown.

Related

How to redraw quickly on a canvas. C# WINFORM

For my software, I am using a Timer from Systems.timer library, every time my timer ticks, it calls a method for repainting my screen. I do not want to clear the screen, then to repaint on it. I just want to paint the new areas on it directly.
At the beginning, I did this:
Constructor{
...
this.timer = new Timer
{
Interval = 10,
};
this.timer.Elapsed += OnPaint;
this.timer.start();
}
public void OnPaint(Object sender, EventArgs e)
{
This.Parent.OnPaintLoadingCircle();
This.Parent.OnPaintReadyToBePaintedAreas();
}
Then I noticed it was much faster for painting when the OnPaint method contains this:
public void OnPaint(Object sender, EventArgs e)
{
This.Parent.Invalidate();
}
So I have two questions:
QUESTION 1 :
Why is it faster???
Because when I call invalidate():
The UI thread clears the screen.
Then UI thread redraws the old areas
Then UI thread draws the loading circle
Then UI thread draws the new areas.
And when I call my two methods OnPaintLoadingCircle() and OnPaintReadyToBePaintedArea():
The timer thread draws the loading circle
Then the timer thread draws the new areas
QUESTION 2 :
I would like to know if it exists a way for asking a controller to draw it surface without clearing it. ( I tried this.Parent.Update(), this.Parent.Refresh(), both of them first clear the screen as well).
Thank you very much for helping me.
Why is it faster???
For the simplest of reasons: because when you call Invalidate() in the OnPaint() method, it forces re-painting of the window immediately, which is much more quickly than a timer could.
The timers in .NET are not suited for high-frequency operations. They only guarantee the time between intervals will be at least what you specify. The actual interval can and often is longer than what you specify, especially if you are using a very short interval (e.g. on the order of less than 10-20ms). This necessarily limits how often you can re-paint the window when using a timer, to a much greater degree than just re-painting the window as fast as you can.
I would like to know if it exists a way for asking a controller to draw it surface without clearing it.
Not easily, no. At the most basic level, you can override OnPaintBackground() and not call the base implementation. But this approach only works if you are prepared to redraw everything, because the system counts on you covering up stale pixels with the correct pixels when you draw.
In fact, a much more common approach is to use double-buffering. The most basic form is to just set the DoubleBuffered property in the control constructor. But you can also combine not clearing the window with maintaining your own offscreen Bitmap object into which you draw your content. Then when a Paint event happens, you just copy the Bitmap to the window.
A much more complicated approach involves hosting a Direct2D surface in your window. Not for the faint of heart, but should offer the best possible performance in a Winforms program.

How to prevent GraphicsDevice from being disposed when applying new settings?

My game window has manual resizing allowed, which means it can be resized like any other normal window, by dragging its edges. The game also makes use of a RenderTarget2D rt2d, to which the main Render Target is set in the main Draw method: GraphicsDevice.SetRenderTarget(rt2d), but it is reset back to null (the default render target) in the end of the main Draw method, which makes it a little bit confusing: is this really the source of the problem, resizing the game window right between the moment Render Target is set to rt2d, and not reset back to default? Right now it looks like it.
The code within the main Draw method is supposed to always reset the main Render Target back to null, so there are no expected cases when this normally wouldn't happen.
Still, the result of resizing the game window sometimes causes GraphicsDevice.isDisposed return true, and then the game throws System.ObjectDisposedException at the first SpriteBatch.End(). I've found posts about this error going back to the first days of XNA, but without a good explanation (and also without mentioning changing Render Target, so it may have been the source of the problem for those posters too).
Right now I'm able to trigger this error by calling this method a few times:
graphics.PreferredBackBufferWidth = graphics.PreferredBackBufferWidth;
graphics.PreferredBackBufferHeight = graphics.PreferredBackBufferHeight;
graphics.ApplyChanges();
…With the following lines in the main Draw method:
RenderTarget2D rt2d = new RenderTarget2D(GraphicsDevice,
graphics.PreferredBackBufferWidth,
graphics.PreferredBackBufferHeight);
GraphicsDevice.SetRenderTarget(rt2d);
sb.Begin();
// main draw method here, it's pretty big, so it might be taking long
// enough to process to actually resize before resetting render target
sb.End();
GraphicsDevice.SetRenderTarget(null);
sb.Begin();
// draw the whole rt2d to the screen
sb.End();
My guess is that I should be aborting the frame draw and resetting the render target if the resizing happens before the Render Target is reset, but I'm still not sure this is exactly what is causing this.
UPD: There are Window.ClientSizeChanged and graphics.PreparingDeviceSettings events, but even when they fire, defaulting the render target doesn't seem to help.
I guess this is not "timeout between resizing the client area and new graphics settings applying" or whatever. This is most likely caused by non-default render target.
And it's probably not that the render target size becomes different from new screen size, because this also throws exception when changing graphics device dimensions to the exact same values.
UPD2: I just tried making fullscreen toggling a pending operation, making the F11 set isFullscreenTogglePending to true and checking it in the beginning of the main Update method, and it didn't help at all. Then I figured out that previously fullscreen mode was also being toggled from the main Update method, only not at the very beginning, but halfway through the input update method, so it doesn't really matter where in the main Update method it is run, it still causes this error. Interestingly, the GraphicsDevice.isDisposed is false when the exception is thrown.
This is the exception message:
System.ObjectDisposedException occurred
Message=Cannot access a disposed object.
Object name: 'GraphicsDevice'.
Source=Microsoft.Xna.Framework
ObjectName=GraphicsDevice
StackTrace:
at Microsoft.Xna.Framework.Helpers.CheckDisposed(Object obj, IntPtr pComPtr)
at Microsoft.Xna.Framework.Graphics.BlendState.Apply(GraphicsDevice device)
at Microsoft.Xna.Framework.Graphics.GraphicsDevice.set_BlendState(BlendState value)
at Microsoft.Xna.Framework.Graphics.SpriteBatch.SetRenderState()
at Microsoft.Xna.Framework.Graphics.SpriteBatch.End()
at secret_project.Game1.Draw(GameTime gameTime) in P:\msvs projects\secret_project\Game1.cs:line 3310
InnerException:
It's at the spriteBatch.End() in the main Draw call.
How do I prevent this error?
Possibly related questions:
When I change vertical size of XNA game window to minimum, it throws ObjectDisposedException for spritebatch, why?
Is it possible to restore a GraphicsDevice if something goes wrong with it?
Two things:
1.
I'm not familiar with render targets... but maybe this will help? From MSDN:
"Render targets represent a linear area of display memory and usually reside in the display memory of the display card. Because of this, RenderTarget objects must be recreated when the device is reset."
2.
Besides that, I had a similar problem at one point. I was disposing of a texture at the end of the draw call. This would work fine, unless I tried to move the window around. Every once in a while, an ObjectDisposed exception would occur (for the texture) when ever I tried to move the game window. My best guess at the reasoning was that the update thread and draw thread would get misaligned, if only for a brief moment, and the texture would be called again before it had a chance to reset. I never found a way to stop the effect, other than just making sure the object was not disposed before attempting to draw.
Of course, our situations might be completely unrelated, but as a possible fix, just add a flag that will stop any draw calls when the window has been recently re-sized.
And if that does not solve it, hopefully it will help narrow down what the problem is not.
You should not create any graphics resources inside Draw call, like you did with RenderTarget2D. First of all creating of such resources is slow and should be done only once for a GraphicsDevice. Only Set calls should be inside the Draw method, as setting already created resource is much faster since they are already inside graphics device memory.
What should you do - is to move all graphics resources creation (including RenderTarget2D) inside LoadContent call and leave only Set methods inside Draw. The LoadContent method is called whenever the GraphicsDevice is recreated (for example, when you resizing the viewport). So all previously created resources will be recreated too.
I think your exception comes because you are recreating the graphics device while the draw method is active. You should only change the device settings in the update method once your game is running. Set some variable like a bool to true if the resolution should be changed, check that value in the update method and apply the new resolution there.
public class Game1 : Microsoft.Xna.Framework.Game
{
protected override void Update(GameTime gameTime)
{
if(resolutionChanged)
{
graphics.PreferredBackBufferHeight = userRequestedHeight;
graphics.PreferredBackBufferWidth = userRequestedWidth;
graphics.ApplyChanges();
}
// ...
}
// ...
}
Also I aggree with OpenMinded: Never create a resource on a per frame basis. Use the GraphicsDevicerManagers PreparingDeviceSettings event. It will be fired when the graphicsdevice is reset or recreated.
I know a lot of time passed but i was hitting this error consistently on our 8 years old game after the user resized the window 2 times in a row, after a gamecomponent providing Bloom effect was disabled. Turned out the Bloom component at the end of the Draw was setting a GraphicsDevice.Texture[1] and it wasn't resetting it to null at the end of the draw. This caused after the first resize to have the GraphicsDevice.Texture[1] disposed, but still set on the device. On the second resize the disposed texture was causing the GraphicsDevice to fail the reset. Setting Texture[1] to null before the reset, or after drawing the bloom-component fixed the problem.

Translate and rotate image outside of glControl1_Paint using OpenTK?

I am making CAD type software in VS2010 Pro using a C# Windows Form Application and OpenTK. Nothing fancy; I just want to be able to read in some basic shapes and draw them. I'm not sure if this makes a difference to the answer, but I am drawing in 2D space using GL.Ortho();
To get familiar with graphics I've done a few OpenTK examples straight from the OpenTK documentation and have a basic understanding of it. From what I've learned so far I cannot move/rotate my primitives unless they were created within this event:
private void glControl1_Paint(object sender, PaintEventArgs e)
{
}
My program launches and waits for the user to select the CAD file to read in. After I read the file and break it down into primitives I draw it to the glControl1 form. So far it works as expected. However, I do not draw it in the "glControl1_Paint" event. Thus I have no control to translate/rotate it by using keyboard/mouse inputs.
I have read answers to other questions where the asker was directed to draw in the "glControl1_Paint" event. I would love to because it would solve my problem, but I am not sure how to do that since I don't have the primitives upon launch of the application, I wait for the user to provide the data.
I suppose I have a few questions that I would like to know the answers to:
1) When does the "glControl1_Paint" event happen in the program? I assumed it was part of initializing the glControl1 window and fired upon startup. Can I control when this happens so that I can draw my primitives here? If so, how do I control when this happens and how do I pass my geometry into this?
2) Is there a way to translate/rotate the my primitives outside of the "glControl1_Paint" event?
No you can not know when paint event will trigger. But you can manually trigger it via Invalidate() function.
The flow should be like this.
You should do the all the drawing in your paint event.
If something happened that effects the drawing, you should call Invalidate()
Keyboard events that moves objects or mouse events that rotates camera etc. all of them should call Invalidate()
If you like maximum frame rate. you should override application main loop and make it call Invalidate() if there are no other windows messages to process.
here is my programming loop
static void Main()
{
...
MainForm mainFrom = new MainForm();
mainFrom.FormClosed += QuitLoop;
mainFrom.Show();
do
{
Application.DoEvents();
mainFrom.glControl1.Invalidate(true); //actually may program is a lot more complex than this
if (mainFrom.IsRunning)
System.Threading.Thread.Sleep(0);
else
System.Threading.Thread.Sleep(1);
} while (!mQuit);
...

Running a Winform and XNA game in parallel

Attention: this question is not about embedding one into the other!
I was thinking of a way to make XNA game window stop pausing its execution while it's being dragged or resized, because it disrupts network connection in most cases and causes desynchronization with game servers. Having a borderless game window and a winform as visual container could do the trick. The thing is, when a user resizes the fake game window border (winform actually) the game window checks for that and adjusts its bounds to fit inside winform's client area. Sounds simple, but I've been having trouble making that work.
Both game window and winform should be aware of each other's existence, so that if the focus is on winform, it immediately transfers to game window, and the game window resizes itself to fit the winform, polling for size changes, or maybe waiting for an event to fire up. I guess that involves exchanging window handles.
There is this very recent question, asked a few hours ago about making two WinForms running together. Hope it can help you help me, and thus help us all :)
also on this problem:
XNA How to Render and Update while resizing
XNA Is Running Slow when focus is removed
Turns out it isn't that hard, though it may bring undesirable side effects, such as stealing resources from the game, causing spontaneous lags (slightly noticeable), and to make it work perfectly, it will take some time.
What I did is created a new Form and assigned an event handler to its ResizeEnd event. The event handler sets the public static Rectangle fakeWindowRect to new rectangle of the fake window client area, which is calculated with help of Form.RectangleToScreen() method.
Here are some code pieces:
static void Main(string[] args)
{
System.Windows.Forms.Form f = new System.Windows.Forms.Form();
f.ClientSize = new System.Drawing.Size(800, 600);
f.TransparencyKey = f.BackColor;
((Action)(() => System.Windows.Forms.Application.Run(f))).BeginInvoke(
null, null);
using (Game1 game = new Game1())
{
f.ResizeEnd += new EventHandler(game.f_LocationChanged);
game.Run();
}
}
public class Game1 : Microsoft.Xna.Framework.Game
{
public static Rectangle windowRect;
/* ... */
protected override void Update(GameTime gameTime)
{
if (windowRect.X != this.Window.ClientBounds.X ||
windowRect.Y != this.Window.ClientBounds.Y ||
windowRect.Width != this.Window.ClientBounds.Width ||
windowRect.Height != this.Window.ClientBounds.Height)
{
// this method sets the game window size, but not location
InitGraphicsMode(windowRect.Width, windowRect.Height,
this.graphics.IsFullScreen);
var win = System.Windows.Forms.Control.FromHandle(
this.Window.Handle) as
System.Windows.Forms.Form;
win.SetBounds(windowRect.X,
windowRect.Y,
windowRect.Width,
windowRect.Height);
win.Activate();
}
}
public void f_LocationChanged(object sender, EventArgs e)
{
var FakeWindow = sender as System.Windows.Forms.Form;
var drawClientArea = FakeWindow.RectangleToScreen(
FakeWindow.ClientRectangle);
windowRect = new Rectangle(
drawClientArea.X,
drawClientArea.Y,
drawClientArea.Width,
drawClientArea.Height);
}
}
Implementation might be terrible and wrong all around, but it works without stealing all resources from the game, even when resizing the fake form the game drops some frames, but doesn't pause completely.
So I tested it, it works, but it was mainly for science, and I'm not planning on using this approach anytime soon. Maybe when I really, really need it.

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