bah.cairo advice needed

BlitzMax Forums/Brucey's Modules/bah.cairo advice needed

wmaass(Posted 2009) [#1]
I have this bit of code that draws 2 paths that represent a golf shot's top view trajectory (a main path and a simulated drop shadow). I've got it running in a loop at the moment to test it out with some random control points. The problem I am having is that it is chewing up memory big time.

FYI my app is a MaxGui App running at 2160x1920, fullscreen over 2 1920x1080 monitors in Nvidia DualView, rotated 90 degrees. Whew, got that? In this case, the paths are being drawn on the second monitor.

relavant code:

Cls
					
Local cairo:TCairo = TCairo.Create(TCairoImageSurface.CreateForPixmap(1080,1920))

Local normalizeMat:TCairoMatrix = TCairoMatrix.CreateScale(1080.0,1920.0)
cairo.SetMatrix(normalizeMat)
cairo.SetLineWidth(0.04)
				
Local offx2:Double = Rnd(0.8)
Local offx3:Double = Rnd(0.8)

cairo.SetSourceRGBA(0.7,0.7,0.7, 0.6)
cairo.CurveTo(0.49, 0.89, offx2, 0.56, offx3, 0.35)
cairo.Stroke()

cairo.SetSourceRGB(1.0,1.0,1.0)
cairo.CurveTo(0.50, 0.89, offx2 + 0.01, 0.56, offx3 + 0.01, 0.35)

cairo.Stroke()
cairo.Destroy()

Local image:TImage = LoadImage(TCairoImageSurface(cairo.getTarget()).pixmap())
DrawImage image,1080,0

flip


As far as I can tell, the LoadImage command is the issue. Any idea on how to keep this from turning my PC to goo?


wmaass(Posted 2009) [#2]
I wonder if it could be related to this:
http://www.blitzbasic.com/Community/posts.php?topic=86916#985353


xlsior(Posted 2009) [#3]
since your doing a LoadImage every single loop, the problem likely is that you collect a ton of data before the garbage collector gets around to running. Once blitzmax has allocated memory, it will never release it back to the system until the application is ended - the GC will only mark it as available for blitzmax to re-use, but never shrink.

You may want to disable the automatic GC, and manually run it each loop, at least that way you may catch things before the memory usage spirals out of control.

Alternatively, you could try to explicitly release the image:timage before loading another image into it and see if that makes any difference... Or see if the behaviour is any different if you compile your application with the threaded mode enabled, to force the use of the alternate garbage collector, just in case that makes a difference.


wmaass(Posted 2009) [#4]
Thanks xlsior. What is the recomended way to release a timage resource?


wmaass(Posted 2009) [#5]
I tried the things mentioned above with no luck - it still gobbles up memory. I can get around the problem by doing this:

DrawPixmap(TCairoImageSurface(cairo.getTarget()).pixmap(),1080,0)


instead of:
Local image:TImage = LoadImage(TCairoImageSurface(cairo.getTarget()).pixmap())
DrawImage image,1080,0


No more memory problem but the pixmap ends up being the paths draws on a black background, thus covering up the background image shown below it. Is there a way for the pixmap background to be transparent so that it only shows the paths?


Brucey(Posted 2009) [#6]
Ouch. DrawPixmap is quite inefficient.

As for releasing memory, you can always call GCCollect(), which should force BlitzMax to free any memory for unreferenced objects.

Is there a way for the pixmap background to be transparent so that it only shows the paths?

Hmmm. Possibly. I'll need to look into it.


Brucey(Posted 2009) [#7]
Seems it already works that way.
Try this modified arc example (which simply has some text drawn before the image) :


Notice how the image is transparent ;-)

What if you don't fill the background rectangle?


wmaass(Posted 2009) [#8]
I tried GCCollect(), did not work. Then again, I might have implemented it wrong. I did this after Flip.

image = null
GCCollect()


wmaass(Posted 2009) [#9]
That example does work. Maybe mine does not because I am drawing on a MaxGui canvas? SetBlend ALPHABLEND crashes my app if I stick it in there.


Brucey(Posted 2009) [#10]
Maybe mine does not because I am drawing on a MaxGui canvas?

Ah.
Any graphics related commands can only be run once you have a valid context. So you might want to run some initialisation stuff against the canvas graphics context the first time you make it active.

But otherwise, it shouldn't crash.

I would have expected GCCollect to work... Maybe something else holds a reference to the TImage (and its embedded TPixmap object).


wmaass(Posted 2009) [#11]
Hmm, I see that SetBlend isn't the issue. How do you not fill the background rectangle?

EDIT: I should clarify - if I use LoadImage, the background is transparent, works fine - but I get the memory problem.

I have to use LoadImage in the main loop like this because I need to calculate and draw a new path while the program is running.


Brucey(Posted 2009) [#12]
I assume your original code snippet has the issue you describe?


wmaass(Posted 2009) [#13]
The memory issue, yes.


wmaass(Posted 2009) [#14]
Here is the entire code.

Import maxgui.Drivers
Import blide.fontmachine
Import BaH.Cairo


Local Window1:TGadget = CreateWindow:TGadget("Window1",10,20,2160,1920,Null,Null)
	'MaximizeWindow( Window1:TGadget )
	
SetWindowFullscreen(Window1)

Local canvas:TGadget=CreateCanvas(0,0,2160,1920,Window1)
SetGadgetSensitivity(Window1, SENSITIZE_KEYS)

Local fnt1:TBitmapFont

'We create the object, loading the font samplefont.fmf
fnt1 = LoadBitmapFont("38pt.fmf")

'background
Global background:TImage = LoadImage("back.jpg")

CreateTimer(60)
Local oldTime:Int = MilliSecs()
Local newTime:Int

While True
        WaitEvent()
ActivateGadget(canvas)

	Select EventID()
		Case EVENT_TIMERTICK
			RedrawGadget canvas
			
			
		Case EVENT_GADGETPAINT
		
		
		
		SetGraphics CanvasGraphics(canvas)
		
		
			newTime = MilliSecs()
			If newTime > oldTime + 1000
			
								
					Local yds1:Int = Rnd(0,300)
					Local yds2:Int = Rnd(0,300)
					Local yds3:Int = Rnd(0,300)
					Local yds4:Int = Rnd(0,300)
					Local yds5:Int = Rnd(0,300)
					Local yds6:Int = Rnd(0,300)
					Local yds7:Int = Rnd(0,300)
					Local yds8:Int = Rnd(0,300)
					Local yds9:Int = Rnd(0,300)
				
					Cls
					
					DrawSomeStuff()
					
					
					Local cairo:TCairo = TCairo.Create(TCairoImageSurface.CreateForPixmap(1080,1920))

					Local normalizeMat:TCairoMatrix = TCairoMatrix.CreateScale(1080.0,1920.0)
					cairo.SetMatrix(normalizeMat)
					cairo.SetLineWidth(0.04)
				
					Local offx1:Double = Rnd(0.8)
					Local offx2:Double = Rnd(0.8)
					Local offx3:Double = Rnd(0.8)

					cairo.SetSourceRGBA(0.7,0.7,0.7, 0.6)
					cairo.CurveTo(0.49, 0.89, offx2, 0.56, offx3, 0.35)
					cairo.Stroke()

					cairo.SetSourceRGB(1.0,1.0,1.0)
					cairo.CurveTo(0.50, 0.89, offx2 + 0.01, 0.56, offx3 + 0.01, 0.35)

					cairo.Stroke()
					cairo.Destroy()
				
					Local image:TImage = LoadImage(TCairoImageSurface(cairo.getTarget()).pixmap())
					
					DrawImage Image,1080,0
						
					Local data1:String = yds1 + " mph"
					Local data2:String = yds2 + " mph"
					Local data3:String = yds3 + " °"
					Local data4:String = yds4 + " null"
					Local data5:String = yds5 + " rpm"
					Local data6:String = yds6 + " °"
					Local data7:String = yds7 + " null"
					Local data8:String = yds8 + " yds"
					Local data9:String = yds9 + " sec"
					
					fnt1.DrawText(data1, 874 - (fnt1.GetTxtWidth(data1)/2), 460)
					fnt1.DrawText(data2, 874 - (fnt1.GetTxtWidth(data2)/2), 610)
					fnt1.DrawText(data3, 874 - (fnt1.GetTxtWidth(data3)/2), 770)
					fnt1.DrawText(data4, 874 - (fnt1.GetTxtWidth(data4)/2), 932)
					fnt1.DrawText(data5, 874 - (fnt1.GetTxtWidth(data5)/2), 1076)
					fnt1.DrawText(data6, 874 - (fnt1.GetTxtWidth(data6)/2), 1236)
					fnt1.DrawText(data7, 874 - (fnt1.GetTxtWidth(data7)/2), 1400)
					fnt1.DrawText(data8, 874 - (fnt1.GetTxtWidth(data8)/2), 1550)
					fnt1.DrawText(data9, 874 - (fnt1.GetTxtWidth(data9)/2), 1712)
											
					Flip
					
					image = Null
					
					
		
			oldTime = MilliSecs()
			EndIf
	

		Case EVENT_WINDOWCLOSE
			FreeGadget canvas
			End

		Case EVENT_APPTERMINATE
			End

	End Select
Wend


Function SetWindowFullscreen:Int(Window:TGadget)

	Extern "Win32"
		Function SetWindowLongA:Int(hWnd:Int, nIndex:Int, dwNewLong:Int)="SetWindowLongA@12"
		Function SetLayeredWindowAttributes(hWnd:Int, temp:Int, alpha:Int, buh:Int)="SetLayeredWindowAttributes@16"
		Function GetWindowLongA:Int(hWnd:Int, nIndex:Int)="GetWindowLongA@8"
		Function MoveWindow:Int(hwnd:Int, x:Int, y:Int, nWidth:Int, nHeight:Int, bRepaint:Int)="MoveWindow@24"
		Function GetSystemMetrics:Int (nIndex:Int) = "GetSystemMetrics@4"
		Function SetWindowPos:Int (hwnd:Int, hWndInsertAfter:Int, x:Int, y:Int, cx:Int, cy:Int, wFlags:Int) = "SetWindowPos@28"
	End Extern

	Local hWnd:Int
	Local Style:Int
   
	hWnd = QueryGadget(Window, QUERY_HWND)
   
	If Not hWnd
		Return False
	EndIf
   
   'Style = GetWindowLongA(hWnd, GWL_STYLE)
   'Style:|WS_EX_LAYERED
   
	Const SM_XVIRTUALSCREEN:Int       =76
	Const SM_YVIRTUALSCREEN:Int       =77
	Const SM_CXVIRTUALSCREEN:Int      =78
	Const SM_CYVIRTUALSCREEN:Int      =79
	Const SM_CMONITORS:Int            =80
	Const SM_SAMEDISPLAYFORMAT:Int    =81
	
	Style = WS_VISIBLE
	
	Local window_w:Int = GetSystemMetrics(SM_CXVIRTUALSCREEN)
	Local window_h:Int = GetSystemMetrics(SM_CYVIRTUALSCREEN)
	ReleaseCapture()
	SetWindowLongA(hWnd, GWL_STYLE, Style)

	MoveWindow(hWnd, (GetSystemMetrics(SM_CXVIRTUALSCREEN) - window_w) / 2, (GetSystemMetrics(SM_CYVIRTUALSCREEN) - window_h) / 2, window_w, window_h, 1)
	Return True

End Function


Function DrawSomeStuff()

	DrawImage(background,0,0)
	
End Function




Brucey(Posted 2009) [#15]
Well, running this code, you can see that Cairo is not the problem :

:-)


Brucey(Posted 2009) [#16]
After your "SetGraphics CanvasGraphics(canvas)", try adding
SetBlend ALPHABLEND



wmaass(Posted 2009) [#17]
Your Cairo Mod is definitely not the issue. I just need a way to draw a new path in my main loop, often, with alpha, without blitzing the memory.

I put in SetBlend ALPHABLEND as you suggested but no dice. Doesn't work if I draw the Pixmap as opposed to using LoadImage,then drawing the image.


wmaass(Posted 2009) [#18]
Brucey,

I have a work around in mind and have a question.

I want to use DrawPixmap because it does not chew up the memory (note the cairo clock demo has the memory issue by the way though on a smaller scale than mine). Since I can't get the background of the pixmap to be transparent for some reason, I thought I might do this:

create the cairo context
first load the background into a pixmap and draw it with cairo
then draw the paths
then draw the pixmap

So:

Local cairo:TCairo = TCairo.Create(TCairoImageSurface.CreateForPixmap(1080,1920))

					Local normalizeMat:TCairoMatrix = TCairoMatrix.CreateScale(1080.0,1920.0)
					cairo.SetMatrix(normalizeMat)
					cairo.SetLineWidth(0.04)
					
					Local photo:TPixmap = LoadPixmap("rt_back.jpg")
					Local photosurf:TCairoSurface = TCairoImageSurface.CreateFromPixmap(photo)
					cairo.Scale(1.0/PixmapWidth(photo), 1.0/PixmapHeight(photo))
					cairo.SetSourceSurface(photosurf, 0, 0);
					cairo.Paint()
				
					
					Local offx1:Double = Rnd(0.8)
					Local offx2:Double = Rnd(0.8)
					Local offx3:Double = Rnd(0.8)
					
					cairo.SetSourceRGBA(0.7,0.7,0.7, 0.6)
					cairo.CurveTo(0.49, 0.89, offx2, 0.56, offx3, 0.35)
					cairo.Stroke()

					cairo.SetSourceRGB(1.0,1.0,1.0)
					cairo.CurveTo(0.50, 0.89, offx2 + 0.01, 0.56, offx3 + 0.01, 0.35)

					cairo.Stroke()

					cairo.Destroy()
					photosurf.Destroy()
					
					DrawPixmap(TCairoImageSurface(cairo.getTarget()).pixmap(),1080,0)



It almost works but the paths do not show up. Any idea? I'm thinking I need to create something to draw the paths on after the photo but I am not sure.


wmaass(Posted 2009) [#19]
Just wanted to update my progress - It's working now. I had to switch to 1.34 (was using 1.35). Then I needed to add GCCollect() after setting the image to NULL. Lastly, I had to use the OpenGL driver because 1.34 was giving me an unknown DXError. I am running a stress test on the app now but so far it all looks good. If all goes well then the app will be part of the set for a TV show coming next year!


Brucey(Posted 2009) [#20]
I wonder what's changed between 1.34 and 1.35 on Windows....


wmaass(Posted 2009) [#21]
I'm looking at this: "Major news is the addition of an official d3d9 max2d driver."


Brucey(Posted 2009) [#22]
I suppose that's a possibility, assuming that the dx9 driver is the default?

Have you tried OpenGL on 1.35 ?


wmaass(Posted 2009) [#23]
I don't think I did. I can try it a bit later. Would be interesting to know if it works on 1.35 fine by using OpenGL.


wmaass(Posted 2009) [#24]
Interesting! 1.35 works as long as I use the OpenGL driver and as long as I collect the garbage after I release the image.


Brucey(Posted 2009) [#25]
So... a possible bug in whatever is the default dx driver, perhaps?


wmaass(Posted 2009) [#26]
Looks that way.

The DX7 driver in 1.34 works fine with LoadImage() in your main loop with GCCollect() as well. I had a different problem related to my app being displayed over 2 monitors on 1.34, which is what prompted me to try OpenGL. But it looks like:

1.35 + DX + LoadImage() in main loop = bad mojo