Difference between garbage collectors?

BlitzMax Forums/BlitzMax Programming/Difference between garbage collectors?

LT(Posted 2017) [#1]
I recently decided to do a threaded build of my engine and found that it crashes when the garbage collector collects. Since threaded builds use a different gc, I'm wondering if anyone knows of any specific differences between them that I should be looking out for..?


col(Posted 2017) [#2]
That shouldn't happen, especially if all you've done is flick a compiler switch.
If you've done more then it could be your multi-thread code that's the issue.

Can you reproduce it with a smaller 'postable' example ?


LT(Posted 2017) [#3]
I'm not using any multi-thread code. There is clearly a bug in my code somewhere, but memory issues can be hard to pin down. The disparity is definitely between the threaded and non-threaded gc, but I don't know what they do differently (other than one handles circular references). :/


LT(Posted 2017) [#4]
Can you reproduce it with a smaller 'postable' example ?
Certainly, that is the next step, but not so easy to do. At one point, my engine was working with no noticeable issues in an older NG, which (I think) uses the same garbage collector. Had trouble getting the most recent version of NG to compile, though. :(


GW(Posted 2017) [#5]
The vanilla bmax GC for threaded apps is really bad, don't even bother using it unless you can guarantee that you wont be creating or destroying objects in a thread. It's very slow and unstable.

However, the threaded gc in bMax-NG is very stable and fast. I recomend that.


LT(Posted 2017) [#6]
Hmm, I thought they were the same (Boehm), but I guess that changed.


Brucey(Posted 2017) [#7]
BlitzMax used Boehm GC for a few builds when Mark introduced thread support (around version 1.37 iirc), but later swapped it out for his own.


LT(Posted 2017) [#8]
Ah, I see.

This is a shot in the dark, but...

On another note that may or may not be related to the gc, I have one issue that's bitten me before and I've never understood. The (vanilla) behavior of...

Int Ptr( Byte Ptr o )

...seems different than...

Float Ptr( Byte Ptr o )

...where o is just an Object. The volatility only happens in release and debug seems okay. Any idea why this might be?


col(Posted 2017) [#9]
Side-stepping why the code behaves differently between Int Ptr/Float Ptr and more importantly, depending on why you have that code then you may need to manually add/remove a reference to the Object that the Byte Ptr points to - is it interacting with c/c++/asm code? If yes or no then that can also cause a problem.

What's the reason you're using a Byte Ptr for an Object?


col(Posted 2017) [#10]
Any idea why this might be?

The debugger code is keeping a reference to the Object so that the gc doesn't reclaim it. In release builds there is plenty of code stripped out which will allow the gc to release the Object at a more opportune time.


LT(Posted 2017) [#11]
No, it's not interacting with another language. I am storing offsets to the fields in an object and modifying those fields. Yes, I am aware that there are other ways to do it using reflection, but they are slow.

Yes, I know this way of doing it is potentially volatile. Had this discussion before, you see... :D

I have noticed that Int Ptr seems to be slightly different than Float Ptr, but I'm not sure how or why. Had the same issue with passing an Int Ptr into a shader and found I had to do it as a Float Ptr to get it to work. On the GLSL side, it still sees it as Int.

Anyway, 'wish I understood the GC better. Everyone says that GC collects "when it wants to," but what does that mean? Is it running in a separate thread?


LT(Posted 2017) [#12]
The debugger code is keeping a reference to the Object so that the gc doesn't reclaim it.
Ah, that's helpful...but doesn't that imply the GC could try to reclaim a local in the middle of a method? Having trouble making sense of that, but then I haven't given the full context.

I have a method that basically calls something like this...

Int Ptr( Byte Ptr o )[ offset ] = NewIntValue

Why would the reference to o get reclaimed (in the middle of executing the method)? And why would this NOT happen if it's Float Ptr?


Brucey(Posted 2017) [#13]
Yes, you need to be very careful about keeping pointer references to objects. If the object is not referenced as an object anywhere else, it will be flagged for collection.
As col notes, debug mode may increase the likelihood of a reference being maintained for longer, but should not be relied upon.

Otherwise, see blitz_gc.h with regards manually handling the reference count of objects (BBRETAIN/BBRELEASE) that are to be used outside of GC scope.
Once you head down this path, you need to be sure you are correctly tracking your own usage of the objects, and retaining/releasing as required.
wxMax uses this feature heavily in order to attach BlitzMax objects to wxWidgets objects.

Have fun! :-)


LT(Posted 2017) [#14]
Aaaah, yes. The cloud is starting to dissipate! Thanks for the lesson, guys!


Brucey(Posted 2017) [#15]
Int Ptr( Byte Ptr o )[ offset ] = NewIntValue

Which is all well and good if you intend to stick to 32-bit "vanilla" land forever. But I guess you are happy with that.

And why would this NOT happen if it's Float Ptr

Could be coincidence/luck/misfortune?
Have a look at the generated assembly and see if it does something wildly different?

Everyone says that GC collects "when it wants to," but what does that mean? Is it running in a separate thread?

Everything is done in a single thread (NG+Boehm has parallel collection on supported platforms)


When the GC attempts to create a new object, it checks to see if it wants to free up some space before allocating new blocks of memory for objects. If it does, it will run a collection, which may result in unused resources being freed/collected.
The GC is written in C. See blitz.mod/blitz_gc_ms.c or blitz_gc_rc.c for the implementations. See bbGCAllocObject(), and follow the logic.

Have more fun! ;-)


LT(Posted 2017) [#16]
Everything is done in a single thread
Hrrrmmm, so here's a method...

Method NewInt( o:Object, offset:Int, NewIntValue:Int )
Int Ptr( Byte Ptr o )[offset] = NewIntValue
EndMethod

Why would o not have a reference? Surely, being an argument to the function counts for something? If not, then why would this not resolve it?

Local oc:Object = o
Local bp:Byte Ptr = Byte Ptr o
Local ip:Int Ptr = Int Ptr bp
ip[offset] = NewIntValue

Incidentally, I have externed versions of BBRETAIN/BBRELEASE. Tried those, but it surprisingly didn't help. Seems I'm still missing something.

Using the far less volatile reflection (FindField) works. I've probably been overly concerned about performance and should just do it that way now. Its actual use is mostly cloning and loading/saving.

But I guess you are happy with that.
Yes and no. Apologies, Brucey, but the explanation is long and involved. The short version is that I've been at this for a long time, having left a prosperous game dev job, and now the pressure is on to make something of it. I won't be selling my engine.


col(Posted 2017) [#17]
The code above looks as though it *should* be ok.

Assuming that the parameters are all valid? You can verify with something old school such as...
Method NewInt( o:Object, offset:Int, NewIntValue:Int )
	WriteStdOut("Entered NewInt: o: " + Hex(Int Byte Ptr(o)) + " offset: " + offset + "~n")
	Int Ptr( Byte Ptr o )[offset] = NewIntValue
	WriteStdOut("Leaving NewInt~n")
EndMethod


You can do the same for the code that's calling that function to make sure that the values are the same.

Does it crash at the same place every time? Or in code that looks similar to other code that's also causing a problem?


col(Posted 2017) [#18]
Had the same issue with passing an Int Ptr into a shader and found I had to do it as a Float Ptr to get it to work. On the GLSL side, it still sees it as Int.


I did read that somewhere else too - not here but in the specs. If I recall correctly it had something to do compression within GLSL which also tied in with how GLSL interprets the data - normalized and/or un-normalised values spring to mind. Depending on which GL version you're using are you using the correct API function as GLSL has various *I* versions of it's shader functions, with the 'I' meaning that you're passing in an integer value as opposed to a float value.


LT(Posted 2017) [#19]
The code above looks as though it *should* be ok.
Hrmm, then maybe it is and I'm barking up the wrong tree. :(

I should add that this issue is not exactly the same as my OP. This is another memory issue that seems related to the GC, but I can't say where it crashes precisely because it works fine in Debug mode. If I replace that function with an equivalent use of reflection, the crash either stops or happens at a different time or place. The engine runs, but crashes while I'm doing something else, but consistently from the same action.*

At the moment, this issue happens only in VR, unless I'm in threaded mode. I was hoping that resolving the threaded issue would be a catch all...

* I have an in world gui and use my Vive controller as a laser pointer to select, etc. That's where it's been failing lately. I'll "laserover" something and...crashola! Where that happens seems to change based on which functions I've replaced. I'm convinced that it's happening in the GC somewhere.

GLSL has various *I* versions
But not the one I'm using, which should be able to take GL_INT instead of GL_FLOAT.


col(Posted 2017) [#20]
Out of curiosity which version are you using?

As to the issue... I'm sure you'll find the problem eventually. I've been in a similar situation ( c++ ) with bugs of this kind. My scenario was with over 70 threads all interacting and pushing gigs of data all over the place. Extensive logs were the only way to catch the bugger - you can never have too much data when trying to debug at the point of crashing.


LT(Posted 2017) [#21]
which version are you using?
My BlitzMax change log says 1.52.

EDIT: Heh, you probably meant GL... It's just an extension of Blitz' GL module. I'm sticking to functions that work with 2.0 ES. Nothing fancy, but the shaders look nice.

70 threads all interacting
Sounds scary to me!

Thanks, I will keep looking...


LT(Posted 2017) [#22]
Found an error in an initialization of one of my types. Had changed a field and neglected to update the initialization of the offsets. I had gone over them three times, but still missed one. It *seems* to be working now.

OP is still valid, though. Does not collect garbage properly in threaded mode.

Soooo...decided to give NG a try again. I'd tried it a few days ago, but had an error building modules and didn't want to commit more time to it. Now the modules are completely built with no errors, but the engine won't even start.

I get a pop-up that says, "Fatal Error in GC - Too many heap sections." What the...?


Brucey(Posted 2017) [#23]
(according to the internet) The GC thinks there no more space to allocate anything else.
Perhaps your objects aren't being collected or something - maybe you have references to them all ?


LT(Posted 2017) [#24]
maybe you have references to them all
That's generally how it works, but I don't think that's 100% true. Even if it was, why would that cause it to fail? Shouldn't it just happily wait until something does come along to collect?


LT(Posted 2017) [#25]
Well, I rewrote the type to use reflection and still no dice. Threaded mode lasts a lot longer before it fails, but it still does. And NG still doesn't work at all.

The only non-standard thing I'm doing that I can think of is that I convert Byte Ptr to Object frequently in my glue code (almost exclusively Lua FFI calls). Works fine in vanilla non-threaded. :/

Perhaps I'm having a misunderstanding regarding RETAIN and RELEASE. If I do this...

Local mo:MyType = MyType( GetBMaxObject( bp ) )

...where bp is a Byte Ptr to an object, shouldn't Local mo increase the ref count?


grable(Posted 2017) [#26]
If it really is a GC issue, it might surface if you force a collection (after each new allocation for example).
Also, you could instrument the GC itself and log some stats on allocated/freed pointers and various totals.
But i would use GDB instead, that will at least give you the location of the crash and you can wind your way out to something relevant.


LT(Posted 2017) [#27]
you could instrument the GC itself
I could if I had the slightest clue how to do that...

i would use GDB instead
Never used it before, but may be worth looking into, thanks.


Brucey(Posted 2017) [#28]
A lot depends on where you are getting "bp" from.
If you are storing a pointer to an Object somewhere, that doesn't count as a reference to the object. In this case, either you need to hold a reference to the Object in BlitzMax, or manually handle retain/release of the object while you hold a copy of it outside of the GC.
For each retain, you must release when you are no longer holding a copy of the reference - otherwise the Object will never be collected.

If you are having issues in the threaded build (and in NG), then chances are your code has bugs that happen not to show up when you are doing a standard build.
Of course, using offsets as you are to reference things, may not work in the same way on NG - so YMMV there.


LT(Posted 2017) [#29]
using offsets as you are to reference things, may not work in the same way on NG
Changed that class so that I'm no longer using offsets and using reflection, instead. However, the same system was in place last April and it worked with that version of NG.

/shrug


LT(Posted 2017) [#30]
Your explanation fits my understanding. I don't think I'm storing a Byte Ptr to an object anywhere. It's just how objects get passed into the FFI functions and they always have references, since they are in object pools.

The only place I'm using RETAIN/RELEASE is when pushing objects to the Lua stack. I might've just learned that RELEASE isn't getting called, but even so, I wouldn't expect that to cause a failure - just a slow memory leak.

EDIT: Nevermind, RELEASE is getting called. 'Been a long time since I looked at that code. ;)


LT(Posted 2017) [#31]
then chances are your code has bugs that happen not to show up when you are doing a standard build
In which case, it would be helpful to know how they differ. :)


col(Posted 2017) [#32]
In which case, it would be helpful to know how they differ.


You said that you were warned in the past about using Byte Ptrs so maybe you know this and I'm repeating but anyway... :p

Imagine this scenario
The GC allocates from the system a pool of memory for itself - ie for Max to use. You create a ton of Objects using up some of the allocated memory. When you use Objects then the GC can track the usage of those Objects and their memory locations within the pool. If you start using BPs however then the GC doesn't know that you actually want to keep an Object instance alive. Because the GC isn't aware of this, then there is now the possibility of 0 references to some Objects and those objects will be marked for collection. What the collection process means is that the memory slot for the Object is allowed to be used again for another Object. No memory is returned to the system, its just returned to the pool ( the GC ). This means that the memory can still be safely accessed via a BP and cast to an Object and can still be used as an Object! although you *should* get BadRef warnings in the console window or editor output pane, point being is that the system won't complain of an Exception Access Violation because the address within the 'still allocated pool' is still valid to be used by the GC. But say you keep allocating Objects... eventually using up all of the memory in the pool. The GC will then do an automatic collection which means that it will run the Delete method of the Objects that were marked for collection ( if there is a Delete method ) and it will start to allow allocation at the now free memory address of the now freed Object. Over time, but still pretty quickly, some Objects that were working and safely being accessed via a Byte Ptr will be overwritten by newly allocated Objects - and bam all of a sudden you're getting crashes as the data at the memory address doesn't make sense for the purpose that your using it. Even an input event generates a new TEvent object which means a new allocation from the GC. So the GC is in constant use, allocating and deallocating, and also collecting when it feel that it needs to.

I hope that makes sense :p

Of course the above assumes that you are using Byte Ptr to hand objects to outside code/libs/dlls - anything not in YOUR 'Max source code and if you're not holding the Object in say a List or Array or something similar.
You say that youre not doing that anymore in which case the above is just useful information only and may not help solve the problem :p


LT(Posted 2017) [#33]
Thanks for the detailed explanation and it does make sense. :)

But yes, I understand that much about how it works. And I'm sure there are objects being generated (like TEvent) that I'm not fully controlling and perhaps that's a problem, but it seems unlikely. All of the objects that I'm passing as Byte Ptr are in an array somewhere. All objects are part of a virtual directory and have a "path." In other words, every object (that gets passed) has a parent and is part of its parent's child or member array. I'm pretty confident they're not getting collected prematurely unless the GC is doing something unexpected. What is the threaded mode GC doing differently from the standard? That's what I'd like to understand better.

What I'm not doing anymore is using offsets to access data in a Blitz type. Basically, I had an init function for each type that would put all of its field offsets into a Global that could be used to both clone the object and read/write its data to disk. I've replaced that with a new version that uses reflection to store TField objects instead. It works well enough, but is probably slower and I have some concerns about how the values are cast to/from Object.

It seemed to help a little in that threaded mode lasts longer before crashing, but it's hard to say. My reason for assuming it was the GC is that threaded mode was not crashing if I set GCSuspend.

Since the change, that is no longer true. :(

EDIT: Heh, but now threaded debug mode works.

EDIT2: Narrowed it down a bit. Hopefully, the answer will come tomorrow.


LT(Posted 2017) [#34]
So, I found a problem. All of my math functions take Float Ptr arguments and I negligently used one that was too narrow (10 floats) for a shadow matrix calculation (needing 16, of course). Silly error that, and there may be more, but...

Now it's working in Debug and Release, Threaded and Non-Threaded. So it probably had nothing to do with the Byte Ptr to Object stuff we've been discussing. Still, I suppose it's better to use reflection. I am concerned that converting Float to String and back could cause accumulative precision errors... :/

Even after the changes, it's still not working in NG. I thought it might uncover more bugs to look into why, so I've been using the age-old method of removing things until it works as expected and then adding them back one-by-one until it doesn't. The Lua script line that broke is a function that loads a font for my gui system.

<sarcasm>I thought, great! That uses the two, and only two, NG modules in which I made slight changes (pub.freetype and brl.freetypefont).</sarcasm>

Unfortunately, I can't get the debugger to help me out because any DebugStop I put into the relevant parts causes a Debugger: Invalid Scope Kind error. Incidentally, a Print function in the same place prints, but causes the same error. I reread an old discussion we had about this error, but never really got an understanding of what it means.


Brucey(Posted 2017) [#35]
Silly error that, and there may be more

As I mentioned, you probably have bugs in your code which are causing all these issues.
If you use straight BlitzMax code - arrays, strings, math, etc - everything should be pretty solid. When you start bypassing BlitzMax's checks and balances (say, by passing arrays as ptrs) then you need to be extra sure your data is right - in debug, add your own boundary checks etc.


Invalid Scope Kind

Most likely an error in your code is throwing off the debug scope stack, although of course I can't rule out an issue with NG itself.


LT(Posted 2017) [#36]
Most likely an error in your code
Last time, you fixed two issues in NG. Both times, you fixed them without saying anything. Perhaps I should not admit I make silly mistakes? :P

add your own boundary checks
Not sure how you'd do that in a case like this. It was basically a clerical error.


col(Posted 2017) [#37]
Glad that you're on the right track to resolving it :O)


LT(Posted 2017) [#38]
Thanks, Dave.


LT(Posted 2017) [#39]
Sadly, it does appear the threaded mode GC is probably not fast enough for my purposes. :(


LT(Posted 2017) [#40]
There seems to be a compatibility issue between NG and vanilla that I'm not aware of. Any time I put a DebugStop in, I'm getting a Debugger Error:Invalid decl kind. Only way I can debug is with Print statements.


Derron(Posted 2017) [#41]
You could debug bmk.

So you compile bmk.bmx with the params to build and execute your app in debug mode (there is a "command line" option in MaxIDE).


Of course Brucey will be able to tell you more about potential differences regarding debugging stuff in vanilla<->NG.


bye
Ron


LT(Posted 2017) [#42]
I'm not following, Derron. My app is executing in Debug mode, but spitting out a message I don't understand. Are you suggesting I look at the bmk source..?

Is there a master list of differences somewhere?


Derron(Posted 2017) [#43]
or bcc ... or whatever you think has a problem.

When you compile via MaxIDE it calls BMK with some params (eg. "bmk makeapp -x -d bla.bmx"). If you now put the bmk-sources in your blitzmaxng/bin-dir, you should be able to compile bmk in debug mode, and pss "makeapp -x -d bla.bmx" as commandline-param (via MaxIDE). It then compiles bmk in debug mode, executes bmk with the given command line (so bmk starts compiling/linking/... bla.bmx into a bla.debug - and then executes it).
If then the debugger is called from "bla.debug", bmk will handle it - and if it throws an error itself, then MaxIDE will highlight the corresponding things the bmk-sources.
(above might not be explained perfectly - think of it as a rough sketch).

The difference might be in the "layout" of objects - it is different in NG and vanilla (brucey added something - I believe at least - or he did use previously "empty" properties). So if you manipulate "blitzmax objects" this might be the reason.


bye
Ron


LT(Posted 2017) [#44]
Ah, I see. I'm actually not manipulating bmx objects now, but that still might turn something else up. Thanks for the suggestion. :)


col(Posted 2017) [#45]
Another way if you want to work on the debugger then the source for it is in brl.mod/appstub.mod/debugger....

You can throw in some print ( or writestdout ) statements to get it output values with any code that you want to debug. I'd advise against trying to put DebugStop in the debugger itself though :p

Or you could drum up a code sample that reproduces the failure, post here and/or also at the github repo, the repo is a more 'official' place for bugs.


LT(Posted 2017) [#46]
After commenting a few things, I can get the render view to open, but glClear is not clearing. Some rendering is happening, but not all.

On a hunch, I tried the same files with NG from April 2016 and it renders fine. Hrmm.


Brucey(Posted 2017) [#47]
glClear is not clearing

Make sure everything is up-to-date. There was an OpenGL bug introduced last week that was also then fixed.


LT(Posted 2017) [#48]
Yup, that was it. Woohoo! And the threaded GC does seem to be a lot faster. :)

EDIT: Hmm, maybe not. My fps drops significantly... :/

EDIT 2: Manual control drops significantly, leaving it be is fast, but results in hiccups. Hopefully, that can be fine tuned. Didn't think I was creating many objects, though.

EDIT 3: Threaded/non-threaded both have hiccups, so I guess that's just how Boehm GC works.


LT(Posted 2017) [#49]
Not the right place for this, I suppose, but I found an NG bug. References to arrays, when passed as fields, don't get their assignments.
Local sa:String[]
GetStringArray( sa )					'<<< This works.

Local c:TContainer = New TContainer
GetStringArray( c._sa )					'<<< This doesn't.

Function GetStringArray( _sa:String[] Var )

	Local sa:String[] = [ "one", "two", "three" ]
	_sa = sa

EndFunction

Type TContainer
	Field _sa:String[]
EndType



Derron(Posted 2017) [#50]
A similar issue was fixed some weeks ago (I also had trouble with params). Having a look at the generated C-code might already expose what is missing.


@ GC
Couldn't you use some kind of "Pool" to reuse objects (handles or whatever)?

bye
Ron


LT(Posted 2017) [#51]
Yes, I'm using pools, which is why it's strange. Vanilla BMX is smooth, as long as it's not threaded. I'm able to exercise more control over vanilla, though. My render loop looks something like this (pseudo code)...

GCSetMode( 2 )
RenderStuff()
If Clock.TimesPerSecond( 1 ) Then GCCollect()
GCSetMode( 1 )

In NG, this slows things down a lot. Like I said, there may be things I can do about that. Perhaps there are objects getting created somewhere that I've forgotten about, etc. But I do know that vanilla is performing better and that's to be expected.


FireballStarfish(Posted 2017) [#52]
GCSetMode doesn't work on NG. If you want to temporarily stop the GC, you'll have to use GCSuspend/GCResume instead.


Brucey(Posted 2017) [#53]
You must be creating and throwing away a *lot* of objects?


LT(Posted 2017) [#54]
GCSetMode doesn't work on NG
Hmm, then why does it slow things down dramatically? The GCCollect?

You must be creating and throwing away a *lot* of objects?
Don't think so. As I've said, all my objects are in pools (arrays). But out of curiosity, what would you consider a lot?


I assume that this shouldn't cause any issues inside of a function in a (render) loop..?
Local this:TMyType = ArrayOfMyTypes[index]
this.DoSomething()
I would expect that 'this' is incrementing a reference count and isn't throwing anything away until its slot in the array or the array itself are nullified...


Brucey(Posted 2017) [#55]
But out of curiosity, what would you consider a lot?

I dunno... hundreds of thousands a second?

Have you profiled your app to get an idea of where your bottlenecks are?

I did some profiling of Derron's TVTower game and re-engineered it in places to do things more efficiently, especially with regards the GC. It made it a few % more CPU friendly.

Keeping hold of a reference is just what you think it is. As long as you have a reference to an Object somewhere, the GC won't touch it. But as soon as your Object is not referenced by anything, it becomes available for collection. When it gets collected depends on the GC and other factors, but it's likely to be collected at some point in the future.


LT(Posted 2017) [#56]
hundreds of thousands a second?
That's what I would think. Pretty sure I'm not doing that, but maybe I'm overlooking something.

The most likely candidates would be my (very inefficient) particle engine, Bullet physics, or skeletal animation. I turn all of those off when I test, though.

The way Mana (engine's name) works is something I'm pretty proud of. It's a system of nodes with update functions and ALL of them have a reference, so it seems unlikely to be causing this problem. Some of the nodes run LuaJIT scripts, most of which access C functions using the FFI, which is fast. However, I don't think LuaJIT is the problem, since it uses its own GC, and vanilla/NG shouldn't make a difference.

I haven't done extensive profiling, yet, so I'm not too worried. I still have one crash bug for one of the scenes I'm trying to load.

re-engineered it in places to do things more efficiently
Any tips?


Derron(Posted 2017) [#57]
String concatenation could use Brucey's stringBuffer. Maybe you create wrapper classes a dozen times per object (vec2 instead of x,y).

That lua scripts might call i to blitzmax and access things in a wrapping way (GetVec2 instead of GetX and GetY)... Such things.



Bye
Ron


LT(Posted 2017) [#58]
Good suggestions, Derron. String concatenation *might* be a factor. Don't think I'm doing much of the other things.


LT(Posted 2017) [#59]
My crash bug seems to be related to BRL.TGALoader. On a larger scene with more textures, it kept having exceptions (in NG). So, I converted them to BMP (ick) and switched to BRL.BMPLoader and everything now works. BAH.FreeImage also seems fine, but I'd prefer to keep the .exe smaller (FreeImage doubles the size), for now.


Derron(Posted 2017) [#60]
Maybe the TGALoader is not threadsafe?
Or there are other issues with the module when using NG.


bye
Ron


LT(Posted 2017) [#61]
Was happening in non-threaded NG, too. Works in vanilla, but maybe the bug is just not showing up, for some reason. Or, maybe something was missed in the conversion. It does work, but crashes if too many pixmaps are loaded.

Suppose I can't know for sure it's the cause. But all I did was:

1. Convert TGA files to BMP and update filenames.
2. Switch BRL.TGALoader for BRL.BMPLoader.
3. Add support for BGRA8888 in my texture upload, because that's the default pixmap format for BRL.BMPLoader and BAH.FreeImage.

And now it works in NG, both threaded and non-threaded.


LT(Posted 2017) [#62]
I know this is off-topic, but...

Now BMPLoader and TGALoader are deprecated, which is disappointing for two reasons. One, FreeImage makes my .exe more than twice as large. Two, whenever I've tried to import it, I get crash bugs. Not every time, but enough for me to question its stability.

Has anyone used it to make a full game, with lots of texture loading?


Derron(Posted 2017) [#63]
Brucey uploaded a stbimage loader... You could try this out.


Bye
Ron


Brucey(Posted 2017) [#64]
You should still be able to import either of those, which are now just stubs, and fall through to BRL.STBImageLoader instead - which uses the stb_image lib ( https://github.com/nothings/stb/blob/master/stb_image.h ). It's about 260KB here on 64-bit OSX.

I couldn't see any obvious problems with the Max source for TGALoader, and I really don't want to be maintaining it - i.e. having to understand the TGA format, etc.
So the best thing I felt was to utilise another well-used and well-tested loader.


LT(Posted 2017) [#65]
Well, that's interesting. Is STBImageLoader NG only?


Brucey(Posted 2017) [#66]
Well, that's interesting. Is STBImageLoader NG only?


So far, but it shouldn't matter, as you simply import as you did before : Import BRL.TGALoader
Since it falls through to the other loader, you don't need to import the STB module directly, and therefore not change your code.

(i.e. TGALoader simply imports STBImageLoader now)

The image loader modules are just factory classes anyway - you never use them directly. By importing the ones you want image support for, when you call LoadPixmap() it cycles through each loader module until it is able to finally load the file - or eventually returns Null (having failed to do so).


LT(Posted 2017) [#67]
Right, I just realized that was kind of a dumb question. Thanks for the response, though. :)


LT(Posted 2017) [#68]
.