Freezed graphics when moving a windowed game
BlitzMax Forums/BlitzMax Programming/Freezed graphics when moving a windowed game
| ||
Hi, After a long time of coding on a game a tester mentioned my game to freeze the graphical output when he drags the window of the game and tries to move it around. As long as a user is moving the window around, there is no redraw of the surface and also no processing of code. In Singleplayergames this would not disturb but on networkgames problems occour with receivepuffers getting too much input. So my question is, how I could force my game to process messages and redraw graphics (and so on) while a user is moving the windowed game around the desktop. I already tried some lines of code with event hooks but EVENT_WINDOWMOVE isn't recognized (only Appterminate, Appresume...). If I'm forced to forbid windowmovement for the users, how would I achieve this? bye MB |
| ||
You should use hooks, and a graphic canvas to solve this particular issues. |
| ||
I doubt there's a solution, cause every game I know halt graphics while dragging the window. |
| ||
EVENT_WINDOWMOVE isn't recognized? BMax v1.24 does. Also processing is not suspended using hooks.Strict Global Window:TGadget = CreateWindow("Test",10,10,320,240) Global Canvas:TGadget = CreateCanvas(0,0,320,240,Window) Global Timer:TTimer = CreateTimer(30) Global x:Int = 0 AddHook EmitEventHook, MyHook Repeat WaitEvent() Forever Function MyHook:Object(id:Int,data:Object,context:Object) Local Event:TEvent = TEvent(data) Print Event.ToString() Select Event.id Case EVENT_WINDOWCLOSE End Case EVENT_GADGETPAINT SetGraphics CanvasGraphics(Canvas) Cls DrawOval x-5,115,10,10 Flip Return Null Case EVENT_TIMERTICK x :+ 2 If x >= 320 Then x = 0 RedrawGadget Canvas Return Null End Select Return data End Function |
| ||
I did find, at least on earlier versions (untested recently) that, on the Mac, when you drag the window title bar, all other events stop being transmitted to the event hooks until you let go of the mouse button. For an event-based app this basically means the whole refresh from the timertick event halts completely until the drag is stopped and the mouse is let go. The only way I found to get around this is to do away with the system title bar entirely, implement my own `drag zone` and process the individual mouse-down and mouse-up events which moves the window if initially clicked within the title bar zone. The same I found to be true of the window resize, that it was not letting all the events through until the window stopped being resized (ie mouse stopped moving). To get around that I implemented my own resize gadget. Obviously it won't look the same as the native system gadget unless you somehow grab that into an image to use in the corner of your canvas. I don't know if this has been fixed or changed since, this was a MaxGUI issue. |
| ||
I get a pause on the PC with this. If you click on the title bar the Gadget paint does not happen for a short while (data 452 - 466) . Works fine on the mac though. Anyone else get this?TimerTick: data=449, mods=0, x=0, y=0, extra="" GadgetPaint: data=0, mods=0, x=0, y=0, extra="" TimerTick: data=450, mods=0, x=0, y=0, extra="" GadgetPaint: data=0, mods=0, x=0, y=0, extra="" TimerTick: data=451, mods=0, x=0, y=0, extra="" GadgetPaint: data=0, mods=0, x=0, y=0, extra="" TimerTick: data=452, mods=0, x=0, y=0, extra="" TimerTick: data=453, mods=0, x=0, y=0, extra="" TimerTick: data=454, mods=0, x=0, y=0, extra="" TimerTick: data=455, mods=0, x=0, y=0, extra="" TimerTick: data=456, mods=0, x=0, y=0, extra="" TimerTick: data=457, mods=0, x=0, y=0, extra="" TimerTick: data=458, mods=0, x=0, y=0, extra="" TimerTick: data=459, mods=0, x=0, y=0, extra="" TimerTick: data=460, mods=0, x=0, y=0, extra="" TimerTick: data=461, mods=0, x=0, y=0, extra="" TimerTick: data=462, mods=0, x=0, y=0, extra="" TimerTick: data=463, mods=0, x=0, y=0, extra="" TimerTick: data=464, mods=0, x=0, y=0, extra="" TimerTick: data=465, mods=0, x=0, y=0, extra="" TimerTick: data=466, mods=0, x=0, y=0, extra="" GadgetPaint: data=0, mods=0, x=0, y=0, extra="" TimerTick: data=467, mods=0, x=0, y=0, extra="" GadgetPaint: data=0, mods=0, x=0, y=0, extra="" TimerTick: data=468, mods=0, x=0, y=0, extra="" GadgetPaint: data=0, mods=0, x=0, y=0, extra="" TimerTick: data=469, mods=0, x=0, y=0, extra="" GadgetPaint: data=0, mods=0, x=0, y=0, extra="" TimerTick: data=470, mods=0, x=0, y=0, extra="" GadgetPaint: data=0, mods=0, x=0, y=0, extra="" |
| ||
I tried it with the update from 1.22 to 1.24 (inclusive syncmods and recompiling them) - no change to see. I just want an as simple as possible solution to force a "processmessages" when the window is being moved around. @TomToad - I'm not using maxgui @ziggy - could you please be so kind and give me an working example of your suggestion? If not possible and before I forget - thanks for your nice IDE (0.8.08 gave an big speed improvement although the old engine loaded a lot faster) @AngelDaniel - On Linux there isn't such an odd behaviour, if the window is moved around the content (graphics) are updated with a small delay - but processed is processed. bye MB |
| ||
I looked into this for my framework but there is no obvious solution without a lot of jiggery pokery. |
| ||
EVENT_WINDOWMOVE is working in 1.24 here. If I replace EVENT_TIMERTICK in the above example with EVENT_WINDOWMOVE. The ball only moves if I move the window. |
| ||
@Mystik - I'm not using Maxgui (so no access to "TGadget") - and wether used PollEvent or WaitEvent, I never got the Event for Event_Windowmove, just AppSuspend, AppResume and so on are working. And like I wrote: I updated to 1.24 Edit: Okay, the thing with the canvas wont bring me success in this (I think its an maxgui thingy). So other solutions? bye MB |
| ||
Strict Framework brl.blitz Import brl.d3d7max2d Import brl.glmax2d Import brl.timer Global Ending Global Timer:TTimer = CreateTimer ( 60 ) AddHook EmitEventHook , Hook AppTitle = "Rotating Rectangle" Graphics 400 , 300 While Not Ending WaitSystem Wend Function Hook:Object ( id , data:Object , context:Object ) Local Event:TEvent = TEvent ( data ) Select Event.source Case Null If Event.id = EVENT_APPTERMINATE Ending = True EndIf If Event.id = EVENT_KEYDOWN And Event.data = KEY_ESCAPE Ending = True EndIf Case Timer If Event.id = EVENT_TIMERTICK Cls SetRotation Event.data DrawRect 200 , 150 , 100 , 100 Flip 0 EndIf EndSelect Return data EndFunctionI hope I could help you! |
| ||
When playing a multiplayer game, make the window unmoverable. |
| ||
I get a pause on the PC with this. If you click on the title bar the Gadget paint does not happen for a short while (data 452 - 466) . Works fine on the mac though. Anyone else get this? Yes, I tried this on windows with some applications. It doesn't seem to be a BMX specific behaviour. Other applications, for example windows media player show the same behaviour. The problem is surely caused by the operating system. |
| ||
If I remember, window move and resize return those events only when you stop the resizing movement (ie mouse comes to a rest) or you let go of the button. I also recall that on a slower computer the o/s doesn't give enough remaining cpu time to the app for it to update the game very often while draggin the window, so it may appear to not be updating at all when it's really down to not enough cpu time to share. I found that on a faster computer the display actually would update while dragging. |
| ||
I don't have a solution to offer you but just an observation. I've always known this to be standard behaviour on Windows systems going all the way back to Win 3.1 so is it really a problem to be solved? Anyone who's used Windows for any amount of time will now about and expect the action to freeze when dragging a window about. In fact, just positioning the mouse cursor over the minimize or close button will cause the app to freeze for a second as the balloon tip pops up. So maybe you really don't need to fix this. |
| ||
I agree with CGV, Just make the window boaderless in multiplayer games |
| ||
If I remember, window move and resize return those events only when you stop the resizing movement (ie mouse comes to a rest) or you let go of the button. I've always known this to be standard behaviour on Windows systems going all the way back to Win 3.1 so is it really a problem to be solved? In fact, just positioning the mouse cursor over the minimize or close button will cause the app to freeze for a second as the balloon tip pops up. Sorry, but I'm getting different results here, for all the three situations. Regardless whether I use a window messages spy app or I write a MaxGUI app dumping out events, I always receive window move messages/events, also if the mouse doesn't come to rest.I couldn't find any windows application, which doesn't repaint the window's contents while dragging (except some BlitzMax and BlitzBasic applications). And on my windows, the tool tip also comes up, without causing any applications to freeze. The only situation where I can confirm the freezing is when you click on the window's title bar and hold down the mouse button for a while, but don't move the mouse in this tme. I could test only on one machine, WinXP Home with Sp2, but I supposed this was the default for all windows systems. Personally I would avoid to make the window borderless and unmovable, since it's not that user friendly. p.s.: doesn't the code above solve the problem? |
| ||
@fabian - I tried your example code - works fine. I tried to implement it in my code and from then I wasn't even able to move the mouse around - neither in the window nor in the title-area. Also strokes on the keyboard aren't recognized. So I tried to add some events for keydown events - no change. Function Hook:Object ( id , data:Object , context:Object ) Local Event:TEvent = TEvent ( data ) Select Event.source Case Null If Event.id = EVENT_MOUSEMOVE Print "mousemove" MOUSEMANAGER.changeStatus() EndIf If Event.id = EVENT_APPTERMINATE Ending = True EndIf If Event.id = EVENT_KEYDOWN Print "keydown" KEYMANAGER.changeStatus() End If If Event.id = EVENT_KEYDOWN And Event.data = KEY_ESCAPE Print "esc" Ending = True EndIf Case mytimer If Event.id = EVENT_TIMERTICK DrawMain() EndIf EndSelect Return data EndFunction Because of not recognizing keystrokes or klicks on [x] I also tried to close it by the contextmenu - but in this case its an empty one (no items to click on) which forces me to kill the program using the task manager. EDIT: I removed some lines of code (something which is now commented out makes it stop working - but this will be another subject which does not fit here - and which I will find out myself - I hope ;D) and I think your source is working pretty well (ok it gets kind of slower while being dragged - but its a start) EDIT2: I uncommented my first commented lines of code - and now it works (no changes made) - very strange. But as long as it works. Big bags of thanks to fabian. bye MB |
| ||
(sorry for double post but if one of you read my last post already - I don't know if "editing" refreshs the last post-date). Fabian's Code is working pretty well on newer graphiccards. Some of my users wrote about not moving mousecursors and so on - after he posted he uses an old sis 650 onboard graphic card I tried on an old pc with the same sis-card. The result was the same: the whole game doesn't respond - even if I switched a section with much less graphics. On parts of the Code where I'm not using Fabians Code (parts where I'm not able to move the window ;D) everything works well on my 4 test pcs and the computers of some testers. So I tried and tried and finally lowered the timer-intervall to 30 ... it's just the graphic card not being able to do it as fast as I want (60 fps). The most common graphics I use have an maximum Pixelcount of 80000 (eg 800x100) but most graphics are 40x60 or so. So if one uses Fabians code and does some fancy graphical things on screen - remember to decrease framerate if the pc seems locked down although the cpu-usage is lower than 20% (I know it's just an average value). EDIT: it's just the choppy graphic card ... even the old waittimer-method shows a slight slowdown every X frames... it doesn't bother if using dx or ogl. bye MB |
| ||
Hi, excuse the late answer, I tested the code on another machine and saw the problem, it seems to be caused by the application's timer. The timer posts events regardless whether the event processing code is fast enough to process these events. So in the example above the drawing code seems to take more than 1000/60=17 milliseconds to be executed. Therefore the timer posts the next event before the previous one is processed, the result is that the application's message queue overflows and the gui messages, which are needed to react the close button, open the context menu, setting the cursor and so on, can't get processed. To solve this problem it would need another timer in the application, since the BlitzMax timers can't change the frequency while running. This timer posts the next timer event not until the previous was processed. Therefore the message queue doesn't contain timer events while the application draws the window's contents, and in this time the gui events can be processed. Sadly I've no access to a linux or macos, therefore I could write this timer only for windows: Strict Framework brl.blitz Import brl.d3d7max2d Import brl.glmax2d Global Ending Global TimerHertz = 60 Setup SetTimer Max ( 1000 / TimerHertz , 1 ) AddHook EmitEventHook , Hook AppTitle = "Rotating Rectangle" Graphics 400 , 300 While Not Ending WaitSystem Wend Function Hook:Object ( id , data:Object , context:Object ) Local Event:TEvent = TEvent ( data ) Select Event.source Case Null If Event.id = EVENT_APPTERMINATE Ending = True EndIf If Event.id = EVENT_KEYDOWN And Event.data = KEY_ESCAPE Ending = True EndIf If Event.id = EVENT_TIMERTICK Local DrawStart = MilliSecs ( ) Cls SetRotation Event.data DrawRect 200 , 150 , 100 , 100 Flip 0 Local DrawStop = MilliSecs ( ) Local DrawTime = DrawStop - DrawStart Local TimeLeft = Max ( 1000 / TimerHertz - DrawTime , 1 ) SetTimer TimeLeft EndIf EndSelect Return data EndFunction Global Wnd Global Ticks Type TWinClass Field Style Field Proc:Byte Ptr Field ClsExtra Field WndExtra Field Instance Field Icon Field Cursor Field Background Field MenuName:Short Ptr Field ClassName:Short Ptr EndType Function Setup ( ) Local Class:TWinClass = New TWinClass Class.Proc = Proc Class.ClassName = ( "CLASS#" + Int Byte Ptr Proc ).ToWString ( ) RegisterClassW Class Wnd = CreateWindowExW ( 0 , Class.ClassName , Null , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ) MemFree Class.ClassName EndFunction Function SetTimer ( millis ) Ticks :+ 1 timeSetEvent millis , 0 , TimeFunc , Ticks , 0 EndFunction Function TimeFunc ( Timer , Msg , User , DW1 , DW2 ) "Win32" NoDebug PostMessageW Wnd , $400 , User , 0 EndFunction Function Proc ( Win , Msg , WP , LP ) If Win = Wnd And Msg = $400 TEvent.Create ( EVENT_TIMERTICK , Null , WP ).Emit Return EndIf Return DefWindowProcW ( Win , Msg , Int Byte Ptr WP , LP ) EndFunction Extern "Win32" Function DefWindowProcW ( Win , Msg , WP , LP ) Function RegisterClassW ( Class:Byte Ptr ) Function CreateWindowExW ( ExS , CN:Short Ptr , WN:Short Ptr , S , X , Y , W , H , P , M , I , LP ) Function timeSetEvent ( D , R , P:Byte Ptr , U , E ) EndExternIf it is important for your project to have a cross-plattform solution, I sadly can't help you. In this case you could make a borderless window, or you could make the game full-screen only. |
| ||
Thanks - I'll have a look at the code now. How would I achieve a borderless window (Linux and Win have to be supported - so windows would get a switch)? I looked through the help some days ago and have not found such an command. Another very uhm... confusing thing is the fact that not all users with this problem are owning old graphic cards...and some with old PCs are not having problems... So one uses the fairly "ok" graphic card "NVidia GeForce 7600 Go" - and has big slowdowns and "mouse does not respond" - others, with an 5 year old pc with cheap components are just saying "running smooth as cream". I also reduced my loop-times - but although the whole loop is processed in about 7ms, it has problems when run with 60fps (420ms - smaller then the needed 1s). While using the directx-mode the flip when big images are used (and capped by setviewport) is slightly faster than in maxgl-mode - using smaller images (other sections) the tide is turning and maxgl wins the race. Sometimes my code uses DrawRect (on the slow pc 12x the times of an image drawn with the same size) but finally I cut down the loop to use just the mentioned 7ms (on my pc it's lower than 1ms) with the flip using 5ms of those 7ms. uhm... before I'm writing too much I will better finish this posting. Thanks Fabian, --- for Fabian also in German (so that I would not run into problems for just translated my thoughts the false way) --- Ich werde mir den Code mal anschauen Wie ist der Befehl (ohne Maxgui) zum entfernen des Rahmens, als Alternative hatte ich mir das schon ueberlegt, da sowohl Linux als auch Windows unterstuetzt werden sollen und dann per Schalter entschieden wird, ob bestimmte Routinen anlaufen. Ich habe zwar vor ein paar Tagen mal durch die Hilfe geschaut aber einen solchen Befehl nicht entdecken koennen. Was aber irgendwie doch... verstoerend wirkt, ist die Tatsache, dass nicht alle User mit diesem Problem auch gleichzeitig einen alten PC haben - und andersherum haben User mit alten PCs nicht immer obige Probleme. Einer hat beispielsweise die halbwegs brauchbare (Notebook-)Grafikkarte "NVidia GeForce 7600 Go" und er hat grosse Ruckler und das "Maus reagiert nicht"-Problem. Andere hingegen haben einen 5 Jahre alten PC mit Uralt-Komponenten und schreiben als Kommentar nur, dass es fluessig laeuft und keine Probleme auftreten. Ich habe auch die Zeiten reduziert, die fuer die jeweiligen Prozeduren anfielen - und obwohl die Gesamtschleife in etwa 7ms abgearbeitet wird, gibt es Probleme, wenn mit 60fps durchgerattert werden soll (und 60*7ms = 420ms was weit weniger als die notwendige 1s ist). Waehrend im DirectX-Modus der Flip bei der Nutzung von grossen Bildern (die per SetViewPort abgeschnitten werden) schneller als im MaxGL-Modus ist, ist es bei kleineren Bildern (also anderen Bereichen des Spieles) genau andersherum und MaxGL ist der Sieger. Ebenfalls ist mir aufgefallen, dass DrawRect auf meinem langsameren PC (der mit der Sis 651) etwa 12x so langsam wie das Zeichnen eines gleichgrossen Bildes ist. Mittlerweile habe ich den Gesamtdurchlauf auf unter 7ms gedrueckt (auf meinem unter 1ms) aber der Flip benoetigt immernoch 5 der 7ms. So, bevor ich noch mehr Senf von mir gebe, beende ich lieber diesen Beitrag. Nochmals Dank an Fabian, bye MB |
| ||
I wonder if you really don't need a second timer, you could implement your own queuing system, use the hooked event code to deal with receiving new events and putting them in an internal queue and when extra time is available allocate it to processing them? |
| ||
Sure I could try to do this - but this seems not to be one of the (simple) solutions I thought to find in all your answers. The solution of Fabian may work but as long it's just a thing working for windows I can't use it. Another problem was that some users experienced the "blocked mouse movement" when using the normal waittimer-method (not the hookevent-method) - so vice-versa I run into trouble which may fall into off-topic. Additional I wonder why brl doesn't implement it in max2d - I think it's more than in humans nature to move a windowed application sometimes (eg a window of an instant messager pops up and has to stay next to the games window) - and to stop a networkgame each time isn't the user friendly way. Thanks to all - nice to see many people willed to help bye MB |
| ||
Why don't you just disable the timer while processing the timer event, and enable it again when the event has been procesed? |
| ||
Redrawing window content while moving the window only happens if the user enables this. By default is it disabled and the users would have to manually enable it. (this is for XP, don't now if the abandoned windows versions support it at all) So what you could do is check at the timertick if a gadgetpaint has happened, if not, delay for the rest of the timer ticktime and recheck again (-> implement "pause on window move") |
| ||
Why don't you just disable the timer while processing the timer event, and enable it again when the event has been procesed? I also thought about this, it would solve the problem, but I was too scared that it could cause too much system overhead to create and delete the timer permanently. The code I wrote above is just such a timer which fires only once, till you recreate it. But as you said this I tried to do so and it works! Thanks, it looks like the problem is solved now, at least my tests showed me this, but let's hear what Michael says.Strict Framework brl.blitz Import brl.d3d7max2d Import brl.glmax2d Import brl.timer Const HERTZ# = 60 Global Ending Global Timer:TTimer = CreateTimer ( HERTZ ) Global Rotation AddHook EmitEventHook , Hook AppTitle = "Rotating Rectangle" Graphics 400 , 300 While Not Ending WaitSystem Wend Function Hook:Object ( id , data:Object , context:Object ) Local Event:TEvent = TEvent ( data ) Select Event.source Case Null If Event.id = EVENT_APPTERMINATE Ending = True EndIf If Event.id = EVENT_KEYDOWN And Event.data = KEY_ESCAPE Ending = True EndIf Case Timer If Event.id = EVENT_TIMERTICK StopTimer Timer Cls SetRotation Rotation Rotation :+ 1 DrawRect 200 , 150 , 100 , 100 Flip 0 Timer = CreateTimer ( HERTZ ) EndIf EndSelect Return data EndFunction |
| ||
@dreamora - gadgetpaint sounds like maxgui (like I mentioned I'm not using it). If you think of the "repaint"-appeareance instead of just an dotted-rectangle on the old position of the window - it just repaints the image it has stored when dragging started @Fabian - I'll have a look at your code tomorrow. At the moment I'm optimizing the maximum framerates (drawtext can be very time consuming and so on). After finishing those parts I'll try your code. Thanks for your engagement also on weekends. bye MB |
| ||
' redrawgadget.bmx Strict Type TApplet Method OnEvent(Event:TEvent) Abstract Method New() AddHook EmitEventHook,eventhook,Self End Method Function eventhook:Object(id,data:Object,context:Object) Local event:TEvent Local app:TApplet event=TEvent(data) app=TApplet(context) app.OnEvent event End Function End Type Type TSpinningApplet Extends TApplet Field window:TGadget Field canvas:TGadget Field timer:TTimer Field image:TImage Method Draw() SetGraphics CanvasGraphics(canvas) SetViewport 0,0,GraphicsWidth(),GraphicsHeight() SetBlend ALPHABLEND SetRotation MilliSecs()*.1 SetClsColor 255,0,0 Cls DrawImage image,GraphicsWidth()/2,GraphicsHeight()/2 Flip End Method Method OnEvent(Event:TEvent) Select event.id Case EVENT_WINDOWCLOSE End Case EVENT_TIMERTICK RedrawGadget canvas Case EVENT_GADGETPAINT draw End Select End Method Method Create:TSpinningApplet(name$) Local a:TApplet Local w,h image=LoadImage("fltkwindow.png") window=CreateWindow(name,20,20,512,512) w=ClientWidth(window) h=ClientHeight(window) canvas=CreateCanvas(0,0,w,h,window) canvas.SetLayout 1,1,1,1 timer=CreateTimer(100) Return Self End Method End Type AutoMidHandle True Local spinner:TSpinningApplet spinner=New TSpinningApplet.Create("Spinning Applet") While True WaitEvent Wend There's no freezing for me when I use the example code that comes with maxgui. |
| ||
Read your last line - remember my phrase "not using maxgui", so the code won't work. bye MB |
| ||
Ah, I assumed it was for MaxGui. I remember seeing a blitzmax example were it used plain out DirectX. |