[Mono-winforms-list] Master's Thesis

Jonathan Gilbert 2a5gjx302 at sneakemail.com
Fri Aug 26 12:22:20 EDT 2005


At 02:34 PM 26/08/2005 +0300, you wrote:
>Hi!
>
>I composed a reply message to you earlier but I guess our mail system
blocked it
>because it contained .exe files as attachment (two versions of the game I am
>making). I just wanted to thank you for your good advice!!! I wrote one
version
>of the game just the way you described. And it works really good! Only
problem
>is that it is slow compared to "real" double buffer.

It shouldn't be slow. The only reason I can think is that it must still be
doing some sort of translation of the buffer. I assume you went with the
second approach, with the Bitmap that you LockBits, draw to, UnlockBits,
and then Graphics.DrawImageUnscaled to the screen. You can find the code
for Bitmap.LockBits inside libgdiplus's src/bitmap.c file (the source code
is in Mono's SVN). I actually wrote parts of it myself, but I was forced to
work with the existing infrastructure which, for simplicity, only has one
"real" non-indexed pixel format, that being CAIRO_FORMAT_ARGB32. This
corresponds, I believe, to PixelFormat.Format32bppArgb.

I'm not sure exactly how mono implements its double-buffer, but at least
when I wrote my library (mode13 -- http://www.deltaq.org/mode13/), it was
crucial that repainting the form not hold up the message queue, and also
that it not be delayed by WM_* messages. I actually came up with a rather
elaborate scheme involving two Bitmaps that get swapped back and forth,
making an "active" plane that isn't visible, and a "visible" plane that
isn't being drawn to. However, I never leave the game's thread, and I never
use Window Messages, in order to paint the surface of the form.

One other minor point of note for performance is that Mono is in the
process of switching from Cairo 0.3 to Cairo 1.0. The current SVN head does
not reflect this, afaik; it's still sitting on a branch. However, it will
be switched over "soon", after which many drawing operations should see a
notable improvement in speed. Depending on exactly what you're doing when
drawing, this may affect you more or less. :-)

>I wrote a second version of the game that uses overridden OnPaint()-method
for
>drawing AND thread for game loop. This way I could use the doublebuffer
setting
>and get about two times faster framerate compared to what you suggested. Only
>problem is that it doesn't work with Mono :( I call Refresh()-method in
>thread's while loop to get form redrawn. This works just fine with .NET
but not
>with Mono. Could it be that Mono's Refresh()-method is not implemented at
all?
>It should force invalidate and update of the form immediately after calling,
>and that way it may not append any messages to message queue?

Check out the following MSDN page:

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpref/html/
frlrfsystemwindowsformscontrolclassinvokerequiredtopic.asp

(try http://lnks.us/WCTG09 if that wraps too badly; it should go to the
same place)

In particular, it says, quote:

   There are four methods on a control that are safe to call from any
thread: Invoke, BeginInvoke, EndInvoke and CreateGraphics.

The Refresh method isn't one of these, I'm afraid, and that could be the
source of your problems. Even if it were (or if you used Control.Invoke),
it would still require marshalling the repaint operation to a different
thread, which is something you really want to avoid! It should be possible
to get directly drawing to the form from a background thread to perform
*better* than drawing from the UI thread, since you can do essentially
exactly what the framework does on the UI thread without all the extra
baggage of passing messages around, waiting for other messages to be
handled, etc.

If you really want the standard double-buffering behaviour, you might be
able to replicate it within your own code; just search
System.Windows.Forms/Control.cs and any other applicable files for places
where it checks whether the DoubleBuffer style is set.

>Would you like to see the sourcecode of my game too? I can try to attach it
>again without any binaries (so our mail-blocker shouldn't block it). It's
very
>nice to have someone that can understand my point of view there :)
>
>BTW. You were the only one on your post list who replyed something useful
to me!

Sure, and thanks :-) Many people see programming as a job -- something
tedious which must be done but at least they get paid for it -- but I have
a true passion for it :-)

Jonathan Gilbert

>> At 11:10 AM 10/08/2005 +0300, Alvi Happonen wrote:
>> [snip]
>> >This is easy to do with .NET by overriding main forms "OnPaint"-method and
>> >calling "this.Invalidate()" at the end of it. This is very simple and
>> >effective but only seems to work with .NET. Mono doesn't start looping
this
>> >way. It seems that Mono always needs somekind of event (from Timer for
>> >example) to call "OnPaint"-method.
>>
>> While the GUI widgets and such all require marshalling calls to a specific
>> UI thread, System.Drawing is completely threadsafe and can be used from
>> multiple threads at the same time with only a few minor locking issues (a
>> Bitmap can only have its bits locked once, though those locked bits can
>> then be accessed from multiple threads).
>>
>> What you want to do is draw directly to the screen each time you want to
>> put a frame onto the screen. Since you want to run at the maximum possible
>> framerate, you don't want to introduce anything like a timer or a delay, so
>> what you want is a control with a background thread that does basically
this:
>>
>> class GameViewport : Control
>> {
>>   public GameViewport()
>>   {
>>     SetStyle(ControlStyles.AllPaintingInWmPaint, true);
>>     SetStyle(ControlStyles.UserPaint, true);
>>     SetStyle(ControlStyles.Opaque, true);
>>     // could also set UserMouse, but DoubleBuffer is not necessary
>>     // here since we're not painting through the OnPaint event
>>   }
>>
>>   public void StartGameThread()
>>   {
>>     Thread game_thread = new Thread(new ThreadStart(game_thread));
>>
>>     // optional, but makes the runtime kill the game thread when
>>     // the UI thread dies (means you don't have to manually kill
>>     // it when the user exits abruptly)
>>     game_thread.IsBackground = true;
>>
>>     game_thread.Start();
>>   }
>>
>>   void game_thread()
>>   {
>>     while (true)
>>     {
>>       update_game_world();
>>       if (is_time_to_quit())
>>         break;
>>       draw_next_frame();
>>     }
>>   }
>>
>>   void draw_next_frame()
>>   {
>>     using (Graphics g = base.CreateGraphics())
>>     {
>>       // insert the code to draw a frame here
>>     }
>>   }
>> }
>>
>> If your drawing code doesn't use the System.Drawing.Graphics primitives
>> much and you would prefer to draw directly to a framebuffer of sorts, then
>> you can do this:
>>
>> class GameViewport : Control
>> {
>>   [...]
>>   Bitmap frame_bitmap = new Bitmap(640, 480, PixelFormat.Format32bppRgb);
>>
>>   unsafe void draw_next_frame()
>>   {
>>     BitmapData data = null;
>>
>>     try
>>     {
>>       data = frame_bitmap.LockBits(new Rectangle(0, 0, 640, 480),
>> ImageLockMode.WriteOnly, frame_bitmap.PixelFormat);
>>
>>       byte *data_ptr = (byte *)data.Scan0.ToPointer();
>>
>>       // insert the code to draw the frame to the raw bytes at
>>       // data_ptr here. each scan consists of 640 * 4 = 2560 bytes,
>>       // but the offset of the next scan should be determined using
>>       // the data.Stride property. when using pointers, .NET does
>>       // *not* do bounds-checking for you, so you need to make
>>       // absolutely sure you don't write outside of the bitmap's
>>       // bits.
>>     }
>>     finally
>>     {
>>       if (data != null)
>>         frame_bitmap.UnlockBits(data);
>>     }
>>
>>     using (Graphics g = base.CreateGraphics())
>>       g.DrawImageUnscaled(frame_bitmap, 0, 0);
>>   }
>> }
>>
>> Because of the current design of libgdiplus, using a 32-bit pixel format is
>> necessary if you want your code to run quickly; 24-bit pixel formats get
>> stored as 32-bit behind the scenes, and when you lock the bits, it has to
>> blit from 32-bit to 24-bit and back (with a dynamic memory allocation to
>> boot!). This probably doesn't apply under Windows, but using a 32-bit pixel
>> format won't make your code run more slowly with GDI+.
>>
>> Using Invalidate() the way you described seems easy, but it is actually a
>> very bad design choice. At least in Windows, calling Invalidate() appends a
>> WM_PAINT message to the end of the message queue. This same queue contains,
>> for instance, keyboard & mouse messages, so if the user is flinging the
>> mouse about wildly, the WM_PAINT message will be delayed. There is some
>> issue of the thread running essentially in a busy loop. On processors with
>> hyperthreading, and on multiprocessor systems, this is essentially a
>> non-issue, since the UI thread can actually run concurrently to the game
>> loop thread, but on older uniprocessor systems, it might be a good idea to
>> have one of these somewhere in the game thread's loop:
>>
>>   Thread.Sleep(0);
>>
>> This call indicates that the thread should simply yield to other waiting
>> threads, but not actually delay its next scheduled timeslice.
>>
>> To the best of my knowledge, this approach should work just fine under
>> Mono, but I haven't actually tried precisely this combination :-) I'd be
>> interested to hear if it works for you.
>>
>> Good luck with your game :-)
>>
>> Jonathan Gilbert
>>
>
>
>
>


More information about the Mono-winforms-list mailing list