BlitzMaxNG Garbage collector issues

Started by Ashmoor, June 14, 2020, 21:00:08

Previous topic - Next topic

Ashmoor

I have some code that updates an image pixel by pixel

Code (blitzmax) Select

For Local i:Int = 0 Until h
For Local j:Int = 0 Until w
pixel = ReadPixel(basePM, j, i)
'imgPixel = GetRGBvectorFromPixel(pixel)
GetRGBvectorFromPixel1(pixel, imgPixel)
imgPixel.DarkenByPercent(alpha)
pixel = GetPixelFromRGBvector(imgPixel)
WritePixel(finalPM, j, i, pixel)
Next
Next


After running it 3-4 times in a row, I get "exception access violation" unless I use GCSuspend() before and GCResume() after.

How exactly does the garbage collector work and how can I ensure it doesn't mess my color operations?

Derron

Run in debug mode to see which variable became null.

I assume your imgPixel somewhere becomes null...and you access its not existing properties.

Bye
Ron

Ashmoor

Debug shows nothing, I just get EAV and app closes. It happens in other areas of the program and I can't track what is causing it.

Here is a video of how it happens. I press W a few times to set the wallpaper. You can see my print checkpoints since debug shows nothing.
https://drive.google.com/file/d/16nOH3nKE1JwjF9ns4jEr9OviFI2gOsC4/view?usp=sharing

Derron

Recompile all modules for "debug" but enable "gdb" before. Then recompile your application with "debug + gdb" too. After compilation you execute your programme (it is there "MinGW's gcc" is installed):

path/to/gdb -r mybinary.exe

it starts, type in "r" to execute your binary.
When your binary is crashing ... type in "bt" to see where in the BlitzMax code it crashed.


A crash not catched by the BlitzMax debugger normally means it happens somewhere in the "C"-Codes.


bye
Ron

Ashmoor

QuoteRecompile all modules for "debug" but enable "gdb" before.

How do I do that? I checked "Program/developer options/GDB debug generation" and then "program/build modules". Is that how it should be?

Derron

Yes ... so it did rebuild all the modules ?

Then recompile your app (non quick) with the same settings - then run gdb -r your binaryname

bye
Ron

Ashmoor

this is what i got
Quote[New Thread 24932.0x1804]
[New Thread 24932.0x3f28]
[New Thread 24932.0x5a8]
[New Thread 24932.0x5124]
[New Thread 24932.0x6f18]
[New Thread 24932.0x3018]
[New Thread 24932.0x7c34]
[New Thread 24932.0x57d8]
[New Thread 24932.0x796c]
[New Thread 24932.0x4db8]
[New Thread 24932.0x734]
[New Thread 24932.0x3990]
[New Thread 24932.0x3924]
[Thread 24932.0x3924 exited with code 0]
[New Thread 24932.0xbf8]
[New Thread 24932.0x65d8]
[New Thread 24932.0x289c]
[New Thread 24932.0x7dfc]
warning: onecore\com\combase\objact\objact.cxx(836)\combase.dll!75E5C59C: (caller: 75E5B56B) ReturnHr(1) tid(1804) 800401F0 CoInitialize has not been called.
[New Thread 24932.0x7954]
[New Thread 24932.0x44b0]
[New Thread 24932.0x2cc4]
[New Thread 24932.0x7f6c]
[New Thread 24932.0x3a18]
[Thread 24932.0x7f6c exited with code 0]
[Thread 24932.0x3a18 exited with code 0]
[Thread 24932.0x2cc4 exited with code 0]
[New Thread 24932.0x7398]
[New Thread 24932.0x6d8c]
[New Thread 24932.0x67a8]
[New Thread 24932.0x68dc]
[New Thread 24932.0x22e4]
[New Thread 24932.0x3f14]
[New Thread 24932.0x62d0]
[New Thread 24932.0x7a08]

Thread 1 received signal SIGSEGV, Segmentation fault.
0x0061d434 in ?? ()
(gdb) bt
#0  0x0061d434 in ?? ()
#1  0xf8e483ec in ?? ()
#2  0x64084d8b in ?? ()
#3  0x0018158b in ?? ()
Backtrace stopped: Cannot access memory at address 0x8b55ff8f

I dont know what this means or where to look for in the code. My code came from Blitzmax Vanilla and should not be threaded. I don't know if the "new thread" messages mean that it's trying to be threaded.

Derron

hmm "normally" the "bt" command would contain elements with additional information (source code position). So ... maybe it is something in the threads.

"NG" is threaded by default ... so if your code somewhere has

?threaded
do something
?not threaded
do something else
?

Then this is now running "do something" while it runs "do something else" in vanilla.


More interesting is this:

warning: onecore\com\combase\objact\objact.cxx(836)\combase.dll!75E5C59C: (caller: 75E5B56B) ReturnHr(1) tid(1804) 800401F0 CoInitialize has not been called.


What happens if you do not use this thing / lib ?


bye
Ron

Ashmoor

My code has nothing ?threaded. Is there a way to set not threaded by default?

QuoteWhat happens if you do not use this thing / lib ?

I don't know who calls that lib. How can I find out?

Below is a sample code that crashes in a similar way to my main app. You can replace "p1_wallpaper.bmp" with any image you want.

Code (blitzmax) Select

SuperStrict



Global myImg:TImage=LoadImage("graphics/p1_wallpaper.bmp")

Const BI_RGB:Int = 0

Const SPI_SETDESKWALLPAPER:Int = 20
Const SPIF_UPDATEINIFILE:Int = 01


'-----------------------------------------
'
' save an image as a bmp file
'
'------------------------------------------
Global bfh_Type:Short
Global bfh_Size:Int
Global bfh_Reserved:Int = 0
Global bfh_OffBits:Int

Global bfh_header_size:Int = 2 + 4 + 4 + 4
Global cnt#=0

'----------------------------------------------------------------

'----------------------------------------------------------------


Global wallPath$=CurrentDir()+"/graphics/savedwallpp.bmp"
Print wallPath
Print "proc wp:"+(CurrentDir()+"/graphics/savedwallpp.bmp")
Global blackAlpha:Float=0.2

Graphics 800,600
SetBlend ALPHABLEND

Repeat
Cls
DrawImage (myImg,0,0)
SetAlpha (blackAlpha)
SetColor(0,0,0)
DrawRect(0,0,800,600)
SetColor(255,255,255)
SetAlpha(1.0)
Flip

If KeyHit(KEY_ESCAPE) Or AppTerminate() Then End
If KeyHit(KEY_W) Then
'GCSuspend()
SaveBitmapFromImage(AddBlackToImage(myImg,blackAlpha))
Wallpaper_Set(wallPath) 'CurrentDir()+"/graphics/savedwallpp.bmp"
cnt:+1
'GCResume()
EndIf

If KeyHit (KEY_P) Then blackAlpha:+0.1
If KeyHit(KEY_O) Then blackAlpha:-0.1



Forever


SaveBitmapFromImage(AddBlackToImage( myImg))
Wallpaper_Set(wallPath) 'CurrentDir()+"/graphics/savedwallpp.bmp"

Wallpaper_Set(wallPath) 'CurrentDir()+"/graphics/savedwallpp.bmp"

'----------------------------------------------------------------



Extern "Win32"
Function SystemParametersInfo:Int(action:Int, param:Int, vparam:Byte Ptr, winini:Int) "win32"="WINBOOL __stdcall  SystemParametersInfo(UINT ,UINT ,PVOID ,UINT )!" '"SystemParametersInfoA@16"
Function SystemParametersInfoW:Int(uiAction:UInt, uiParam:UInt, pvParam:Byte Ptr, fWinIni:UInt) "win32"="WINBOOL __stdcall  SystemParametersInfoW(UINT ,UINT ,PVOID ,UINT )!"
'Function GetLastError() = "GetLastError@0"
End Extern

Function Wallpaper_Set(filename:Byte Ptr)
'note: filename must be real path of the image, not truncated relative path.
Print "filename:"+filename[0]
Local res:Int 'this is to check if the wallpaper was set or an error happened
res=SystemParametersInfo(SPI_SETDESKWALLPAPER, Null, filename, SPIF_UPDATEINIFILE)
Print "res:"+res

EndFunction



Type BitmapInfoHeader
Field biSize:Int
Field biWidth:Int
Field biHeight:Int
Field biPlanes:Short
Field biBitCount:Short
Field biCompression:Int
Field biSizeImage:Int
Field biXPelsPerMeter:Int
Field biYPelsPerMeter:Int
Field biClrUsed:Int
Field biClrImportant:Int
EndType



'Summary: Writes an TImage to an open file stream as a BMP format.
Function Img_ToBmp:Int(img:TImage, file:TStream)
Local pxmp:TPixmap, x:Int, y:Int, ptr1:Byte Ptr
Local r:Int, g:Int, b:Int
Local bank1:TBank, size:Int
Local file_size:Int, data_size:Int
Local bih:BitmapInfoHeader=New BitmapInfoHeader

Print "ImgToBMP start"

pxmp = LockImage(img, , True, False)
If (pxmp = Null)
Return False
EndIf

file = LittleEndianStream(file)

If (pxmp.format <> PF_BGR888)
pxmp = pxmp.Convert(PF_BGR888)
EndIf

ptr1 = pxmp.pixels

data_size = pxmp.height * pxmp.pitch
file_size = data_size + bfh_header_size + SizeOf(bih)

'go at start of file
SeekStream(file, 0)

'write file header

bfh_Type = Asc("B") + ( Asc("M") Shl 8 )
WriteShort(file, bfh_Type)

bfh_Size = file_size
WriteInt(file, bfh_Size)

bfh_Reserved = 0
WriteInt(file, bfh_Reserved)

bfh_OffBits= bfh_header_size + SizeOf(bih)
WriteInt(file, bfh_OffBits)


'write info header
bih = New BitmapInfoHeader
bih.biSize = SizeOf(bih)
bih.biWidth = pxmp.width
bih.biHeight = pxmp.height
bih.biPlanes = 1
bih.biBitCount = 24
bih.biCompression = BI_RGB
bih.biSizeImage = data_size
bih.biXPelsPerMeter = 2835
bih.biYPelsPerMeter = 2835
bih.biClrUsed = 0
bih.biClrImportant = 0
bank1 = CreateStaticBank(bih, SizeOf(bih) )
WriteBank(bank1, file, 0, SizeOf(bih) )

'write it top-down
ptr1 :+ (data_size - pxmp.pitch)
For y = 0 Until pxmp.height

bank1 = CreateStaticBank(ptr1, pxmp.pitch )
WriteBank(bank1, file, 0, pxmp.pitch )

ptr1 :- pxmp.pitch
Next


UnlockImage(img)
Print "imgTOBMP done"
Return True
EndFunction


'Summary: Opens a stream and writes ssImg to it as BMP format.
Function SaveBitmapFromImage(ssImg:TImage)
'note: this will save a bmp to my roaming file.
Print "SBFI start"
Local path:String
Local file:TStream

path = "graphics/savedwallpp.bmp"
file = OpenFile(path)

'make sure there is no wallpaper of this name already
If file <> Null Then
CloseStream(file)
DeleteFile(path)
EndIf
CreateFile(path)
file = WriteFile(path)

Img_ToBmp(ssImg, file)

Print path + " image was created"

CloseStream(file)

End Function



Function AddBlackToImage:TImage(img:TImage, alpha:Float=0.3)
'alpha is in the transparency with witch the black box is drawn on top
Print "add black to img start"
Local w:Int = ImageWidth(img)
Local h:Int = ImageHeight(img)

Local r:Float, g:Float, b:Float
Local pixel:Int
Local imgPixel:TRGBvector= New TRGBvector

Local basePM:TPixmap = TPixmap.Create(w, h, PF_RGB888)
Local finalPM:TPixmap = TPixmap.Create(w, h, PF_RGB888)

Print "here1"

basePM = LockImage(img)
UnlockImage(img)

Print "here2"
For Local i:Int = 0 Until h
For Local j:Int = 0 Until w
pixel = ReadPixel(basePM, j, i)
'imgPixel = GetRGBvectorFromPixel(pixel)
GetRGBvectorFromPixel1(pixel, imgPixel)
imgPixel.DarkenByPercent(alpha)
pixel = GetPixelFromRGBvector(imgPixel)
WritePixel(finalPM, j, i, pixel)
Next
Next
Print "here3"
Local retImg:TImage = LoadImage(finalPM)

Print "add black to img done"

Return retImg

End Function


Type TRGBvector
Field r:Float
Field g:Float
Field b:Float

Method DrawSelf()
Local rStr:String
Local gStr:String
Local bStr:String

rStr = r
rStr = rStr[..5]
gStr = g
gStr = gStr[..5]
bStr = b
bStr = bStr[..5]

DrawText("r:" + rStr + " g:" + gStr + " b:" + bStr, 10, 30)

End Method

'Summary: Adds a little bit of black into
Method DarkenByPercent(alpha:Float)
'This is equivalent to drawing a black pixel on top of current one using "alpha" transparncy
r = Truncate255(r - (r * alpha))
g = Truncate255(g - (g * alpha))
b = Truncate255(b - (b * alpha))
EndMethod

'Summary: Tweaks brightness of all channels (-1 to 1)
Method AdjustBrightness(br:Float)
'This will add/remove a percent of each channel, acting somewhat like overlay layer.
'it is not perfect because on the negative side of the curve it should
'remove a percent of the missing value
'eg: 245 has 10 missing so at br=-1.0 it should be -235, that woud be similar to the br>0

r = Truncate255(r + (r * br))
g = Truncate255(g + (g * br))
b = Truncate255(b + (b * br))

EndMethod

'Summary: Sets RGB values
Method SetRGB:Int(re:Int, gr:Int, bl:Int)
r = re
g = gr
b = bl
End Method

EndType


'Summary: Returns RGBvector from int pixel value
Function GetRGBvectorFromPixel:TRGBvector(rgb:Int)
If cnt>=1 Then Print "here 4"
Local rgbV:TRGBvector= New TRGBvector
If cnt>=1 Then Print "here 5"
Local r:Float = ((rgb Shr 16) & $ff) / 255:Float
Local g:Float = ((rgb Shr 8) & $ff) / 255:Float
Local b:Float = ((rgb) & $ff) / 255:Float

r = r * 255; g = g * 255; b = b * 255

rgbV.r = r; rgbV.g = g; rgbV.b = b

Return rgbV

End Function

'Summary: Returns RGBvector from int pixel value
Function GetRGBvectorFromPixel1(rgb:Int, rgbV:TRGBvector Var)

Local r:Float = ((rgb Shr 16) & $ff) / 255:Float
Local g:Float = ((rgb Shr 8) & $ff) / 255:Float
Local b:Float = ((rgb) & $ff) / 255:Float

r = r * 255; g = g * 255; b = b * 255

rgbV.r = r; rgbV.g = g; rgbV.b = b

End Function


'Summary: truncates a value to the interval [0,255]
Function Truncate255:Float(val:Float)
If val < 0 Then val = 0
If val > 255 Then val = 255

Return val
End Function

Function GetPixelFromRGBvector:Int(vec:TRGBvector)
Local pixel:Int

Local r:Int = vec.r
Local g:Int = vec.g
Local b:Int = vec.b

pixel = $ff000000 | (r Shl 16) | (g Shl 8) | b


Return pixel

End Function

Derron

Will check on a Windows machine later - but for now little "hints":

Code (BlitzMax) Select

        'Summary: Sets RGB values
        Method SetRGB:Int(re:Int, gr:Int, bl:Int)
                r = re
                g = gr
                b = bl
        End Method


can become

Code (BlitzMax) Select

        'Summary: Sets RGB values
        Method SetRGB:Int(r:Int, g:Int, b:Int)
                self.r = r
                self.g = g
                self.b = b
        End Method



Code (BlitzMax) Select

        file = OpenFile(path)
       
        'make sure there is no wallpaper of this name already
        If file <> Null Then
                CloseStream(file)
                DeleteFile(path)
        EndIf
        CreateFile(path)
        file = WriteFile(path)


Why not simply try to "DeleteFile(path)" before "CreateFile(path)" or do a "If FileType(path) = FILETYPE_FILE Then DeleteFile(path)" ? Both variants (mine and yours) only check if "file" can be opened, not if writeable).
https://blitzmax.org/docs/en/api/brl/brl.filesystem/#function-writefiletstream-urlobject-
checkout the example there.

"WriteFile" (which is equal to "WriteStream") is already what you are looking for: open/creating a file (and result is "Null" if it fails to do so)
Code (BlitzMax) Select

SuperStrict

Framework Brl.StandardIO
Import Brl.FileSystem

For local i:int = 0 until 10
local f:TStream = WriteFile("hi.txt")
f.WriteString(i)
CloseFile(f)
Next

Local f:TStream = ReadFile("hi.txt")
While Not Eof(f)
    Print ReadLine(f)
Wend


Will output "9".


Code (BlitzMax) Select

'Summary: Opens a stream and writes ssImg to it as BMP format.
Function SaveBitmapFromImage(ssImg:TImage)
'note: this will save a bmp to my roaming file.
        Print "SBFI start"
        Local path:String
        Local file:TStream
       
        path = "graphics/savedwallpp.bmp"
        file = OpenFile(path)
       
        'make sure there is no wallpaper of this name already
        If file <> Null Then
                CloseStream(file)
                DeleteFile(path)
        EndIf
        CreateFile(path)
        file = WriteFile(path)
       
        Img_ToBmp(ssImg, file)
       
        Print path + " image was created"
       
        CloseStream(file)
       
End Function


Could then become:
Code (BlitzMax) Select

'Summary: Opens a stream and writes ssImg to it as BMP format.
Function SaveBitmapFromImage:Int(ssImg:TImage)
'note: this will save a bmp to my roaming file.
        Print "SBFI start"
        Local path:String = "graphics/savedwallpp.bmp"
        'create new or open to overwrite content
        Local file:TStream = WriteFile(path)
        If Not file
                Print "Failed to open existing or to create new file ~q" + path + "~q for writing."
                Return False
        Else
                If Img_ToBmp(ssImg, file)
                        Print path + " image was created"
                        CloseStream(file)
                        Return True
                EndIf
       EndIf
End Function



bye
Ron

Derron

Code (BlitzMax) Select


Function AddBlackToImage:TImage(img:TImage, alpha:Float=0.3)
'alpha is in the transparency with witch the black box is drawn on top
        Print "add black to img start"
        Local w:Int = ImageWidth(img)
        Local h:Int = ImageHeight(img)
       
        Local r:Float, g:Float, b:Float
        Local pixel:Int
        Local imgPixel:TRGBvector= New TRGBvector
       
        Local basePM:TPixmap = TPixmap.Create(w, h, PF_RGB888)
        Local finalPM:TPixmap = TPixmap.Create(w, h, PF_RGB888)
       
        Print "here1"
       
        basePM = LockImage(img)
        UnlockImage(img)
       
        Print "here2"
        For Local i:Int = 0 Until h
                For Local j:Int = 0 Until w
                        pixel = ReadPixel(basePM, j, i)
                        'imgPixel = GetRGBvectorFromPixel(pixel)
                        GetRGBvectorFromPixel1(pixel, imgPixel)
                        imgPixel.DarkenByPercent(alpha)
                        pixel = GetPixelFromRGBvector(imgPixel)
                        WritePixel(finalPM, j, i, pixel)
                Next
        Next
        Print "here3"
        Local retImg:TImage = LoadImage(finalPM)
       
        Print "add black to img done"
       
        Return retImg
       
End Function


Does something unnecessary: Creating "basePM" with then just overriding it some steps later. also "LockImage" for now just returns the pixmap and does nothing more - so "UnlockImage" is not really necessary. BUT ... if it somewhen does something and you plan to alter the pixmap you extracted, then "UnlockImage()" should be called _after_ you manipulated the content.


Code (Blitzmax) Select

        Local basePM:TPixmap = TPixmap.Create(w, h, PF_RGB888)
        Local finalPM:TPixmap = TPixmap.Create(w, h, PF_RGB888)
       
        Print "here1"
       
        basePM = LockImage(img)
        UnlockImage(img)

becomes
Code (Blitzmax) Select

        Local basePM:TPixmap = LockImage(img)
        Local finalPM:TPixmap = TPixmap.Create(w, h, PF_RGB888)
       
        Print "here1"
       
        UnlockImage(img)




But to your problem: what happens if you comment out "Wallpaper_Set()" in (When hitting "W") ... does it then run as it should?

If it works: then maybe this command runs "threaded" (from Windows then). This would mean:
- 00.00s: you write to a file ...
- 00.01s: you tell windows to set the wallpaper -> new thread
- 00.02s: command "fired"
- 00.09s: thread of windows plans to open file to set the wallpaper
- 00.10s: next "w" hit ... write to a file ....
- 00.11s: thread and your app want to modify the file - one fails as handle is not valid (you did not check for it)

In that case we might add some more "validity" checks


bye
Ron

Ashmoor

Thanks for the hints, it will take me a little bit to digest.

Ashmoor

QuoteBut to your problem: what happens if you comment out "Wallpaper_Set()" in (When hitting "W") ... does it then run as it should?

Nope, it still crashes. It does not crash if I uncomment GCSuspend()

Derron

NG's GC is a different one..... It is better in handling cyclic dependencies...and it cleans up a bit different to the original one.

Maybe you exposed a bug there...or your code is flawed  :-)

Will try to replicate it in a Windows VM when I find some sparetime.


Bye
Ron

Pingus

@Ahmoor,
I think that the issue comes from Writebank
If I change the function Img_ToBmp with that simple test:


Function Img_ToBmp:Int(img:TImage, file:TStream)

    Print "ImgToBMP start"
Local pxmp:TPixmap
Local y:Int
Local bank1:TBank

    pxmp = LockImage(img, , True, False)
If (pxmp = Null)
Return False
    EndIf
       
    file = TStream(file)
Local ptr1:Byte Ptr
ptr1 = pxmp.pixels       


bank1 = CreateStaticBank(ptr1, pxmp.pitch )

For y = 0 To 1000
        WriteBank(bank1, file, 0, pxmp.pitch )
Print " test loop "+y
Next
   
UnlockImage(img)
   
Print "imgTOBMP done "
    Return True

EndFunction


It crashes after few hit on "W" to run the function (the normal function crash after 12 or 13 tries on my PC).
The same code in Blitzmax vanilla works flawless...

It may be tricky to find a bug in Writebank... ;/