Massive multiple-canvas performance hit

BlitzMax Forums/MaxGUI Module/Massive multiple-canvas performance hit

SpaceAce(Posted 2008) [#1]
I've noticed that, with one canvas, I can draw a rect and flip in 0-1ms. When I add a second canvas, the time taken to draw and flip (really, the draw is negligible, it's all in the flip) jumps to 18-25ms. There seems to be little or no difference between debug and release, both show pretty much identical slowdown.

Is this just inherent in trying to use multiple canvases, or is there some secret I am not aware of? The draw function looks like this:

Function TemporaryDraw(Canvas:TGadget)
	SetGraphics CanvasGraphics ( Canvas )
	SetViewport 0, 0, GadgetWidth(Canvas), GadgetHeight(Canvas)
        Cls
        DrawRect(0, 0, 200, 200)
	Flip 0
End Function


I've tested each piece individually, and, not surprisingly, the time is all lost in the flip. Why do two flips take 20x longer than one?


Grisu(Posted 2008) [#2]
You should get rid of the "viewport" command.
At least try to avoid "math" inside the drawing routine:
=>e.g. GadgetHeight(Canvas)and GadgetWidth(Canvas)

That should speed things up a bit.


tonyg(Posted 2008) [#3]
Is there a simple example we can run that will show the performance issue?


SpaceAce(Posted 2008) [#4]
Hi, Grisu. Like I said, I tried each bit individually. All of the time is being lost to the Flip command. As to what you said about keeping math out of the function, I agree with you. The above function was generated by a tool I was using.

tonyg, I'll add some test code to this post. In the meantime, I've narrowed down the problem.

I am using event hooks and a single timer to refresh the canvases. Everything seems OK at 10 or 20, but as I creep the refresh timer up, the problem starts to appear. Around 65-70, the window becomes unresponsive to any events and the time taken to draw the canvases starts leaping up.

My first thought was that the timer was just firing too often and the drawing was hogging all the resources. I thought maybe the draw operation were getting stacked on top of one another - a second one starting before the first finished - but I added a "lock" to my logic loop, so the drawing function would not run again if it hadn't completed the last time, and it had no effect.

I have a sneaking suspicion I am doing something really obvious and really stupid, and that this is all refresh-rate related, somehow. Especially since my desktop refresh rate is at 75 and the draw time in the code below settles on 13-14ms as the timer Hz are increased, which seems like a bit much for coincidence.

Here's some hastily thrown together source code:
SuperStrict

Import maxgui.drivers

SetGraphicsDriver(GLMax2DDriver())

Global MainWindow:TGadget
MainWindow = CreateWindow:TGadget("Logic_Gui", 220, 155, 830, 629, Null,
    WINDOW_TITLEBAR | WINDOW_RESIZABLE | WINDOW_MENU | WINDOW_STATUS | WINDOW_CLIENTCOORDS)
Global Canvas1:TGadget
Canvas1 = CreateCanvas:TGadget(2, 2, 300, 216, MainWindow, Null)
Global Canvas2:TGadget
Canvas2 = CreateCanvas:TGadget(518, 2, 300, 216, MainWindow, Null)

AddHook(EmitEventHook, HookHandler)
Global Which:Byte = 0
Global OneCanvas:Int = 0
Global TimeTakenSingle:Int = 0
Global TimeTakenDouble:Int = 0

Global DrawTimer:TTimer = CreateTimer(100)

While (WaitEvent() <> EVENT_WINDOWCLOSE)
Wend


Function HookHandler:Object(iId:Int, tData:Object, tContext:Object)
	Local Event:TEvent = TEvent(TData)
	
	Select Event.id
		Case EVENT_TIMERTICK
		  Select Event.source
			Case DrawTimer
			  Select Which & 1
			    Case True
			      Local s:Int = MilliSecs()
		    	      SetGraphics(CanvasGraphics(Canvas1))
		  	      Flip 0
	  		      SetGraphics(CanvasGraphics(Canvas2))
  		              Cls
			      DrawText("Both canvases drawn in " + TimeTakenDouble + "ms.", 10, 60)
			      Flip 0
			      TimeTakenDouble = MilliSecs() - s
			      Which:~ 1
			      Return Null
			    Case False
			      Local s:Int = MilliSecs()
  			      SetGraphics(CanvasGraphics(Canvas1))
  			      Cls
			      DrawText("One canvas drawn in " + TimeTakenSingle + "ms.", 10, 60)
			      Flip 0
			      TimeTakenSingle = MilliSecs() - s
			      Which:~ 1
			      Return Null
			    End Select
			End Select
	End Select
	Return TData
End Function


Two things to note:
1) The timer refresh rate is really half of what it says, since every other loop draws a single canvas instead of both canvases.
2) There is a point between working smoothly and failing entirely as you push up the timer refresh rate where the draw time is high for the first 2-6 seconds of program execution, then settles down to a more reasonable rate. I tried throwing a "sleep" in before starting the timer, but it did nothing.

One other odd symptom: clicking (and/or holding) down a mouse button on the title bar causes a big spike in draw times when drawing two canvases, but not when only drawing one.

On my computer, total meltdown is achieved somewhere around 140 ticks/second. I need to be able to reliably refresh at around 60Hz for the application I have in mind. I could probably get things semi-working by only redrawing the canvases when they are "dirty", but a timer simplifies things for me, especially since the dirty canvases can come in big groups.


REDi(Posted 2008) [#5]
I'll second this bug, I've just spent ages trying to figure out why an old app of mine has started freezing, it came down to the same problem, multiple canvases on a timer. This never used to happen so it must be something that's been changed quite recently.

I'm using the SVN version BTW.


skidracer(Posted 2008) [#6]
Please see the RedrawGadget example for best practise implementation. It uses a timer to tell the OS the canvas needs redrawing and redraws the canvas when the OS tells it to (EVENT_GADGETPAINT).


REDi(Posted 2008) [#7]
My app already uses RedrawGadget


skidracer(Posted 2008) [#8]
hmph, if possible can you try changing win32maxguiex.bmx[835] to:
				InvalidateRect _hwnd,Null,0



REDi(Posted 2008) [#9]
That's much better skid. (line 840)


SebHoll(Posted 2008) [#10]
Eeekkk... I'm not sure that we could use that as a permanent solution as:

a) Iirc, InvalidateRect doesn't redraw the window's children.
b) InvalideRect only invalidates a control's client area, i.e. the gadget's frame is not invalidated.

Quite honestly, I'm finding it hard to reproduce the problem (maybe it's because of Vista's Desktop Composition), but I'm curious as to whether replacing the same line (which on my PC is 841) with...

Return RedrawWindow( _hwnd, Null, Null, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN )
...speeds thing up too. If so, then we should probably use this instead for the reasons outlined above. If not, then we may have to be a bit more creative.


REDi(Posted 2008) [#11]
Just tried that out Seb, still freezes with it :(

(freezes with 6 canvases at 20 hertz)


skidracer(Posted 2008) [#12]
Don't fret Seb, it was just a test to verify the problem, a canvas specific patch I think will be most optimal, still looking into original problem also.


SpaceAce(Posted 2008) [#13]
Hi, skidracer. I will update my code to reflect the proper practice shown in the RedrawGadget example, thanks for the heads-up. In the meantime, I gather there actually is something wrong besides my being a lousy programmer, so I'll keep an eye out for a fix.

SpaceAce