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. |