How to safely unload meshes and textures?
How to safely unload meshes and textures?
I am using irrlicht for my game engine and when I remove meshes from the cache, same as textures, to free up memory, the next frame the scene manager crashes because I’m assuming it tries to find the memory for these assets and it finds something unrelated. How do I safely unload assets?
Re: How to safely unload meshes and textures?
Your assumption why it crashes is probably wrong. You can for example add the following line in example 09 in loadModel after creating the models:
Device->getSceneManager()->getMeshCache()->removeMesh(mesh);
You'll see it still works. Only difference is that it'll load the mesh again next time you open it.
A bit more tricky tend to be removing nodes. Which you either should do outside the render-loop or you have to use ISceneManager::addToDeletionQueue to do that safely.
So... first step is to use a debugger and check where it crashes. Hard to make any guesses without that. Thought if I had to guess - probably you mess up reference counting somewhere. See https://irrlicht.sourceforge.io/docu/cl ... unted.html for when you are allowed to drop or delete meshes. If that happens once too often then you mess up the reference counter and you can get crashes.
Device->getSceneManager()->getMeshCache()->removeMesh(mesh);
You'll see it still works. Only difference is that it'll load the mesh again next time you open it.
A bit more tricky tend to be removing nodes. Which you either should do outside the render-loop or you have to use ISceneManager::addToDeletionQueue to do that safely.
So... first step is to use a debugger and check where it crashes. Hard to make any guesses without that. Thought if I had to guess - probably you mess up reference counting somewhere. See https://irrlicht.sourceforge.io/docu/cl ... unted.html for when you are allowed to drop or delete meshes. If that happens once too often then you mess up the reference counter and you can get crashes.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
Re: How to safely unload meshes and textures?
I just did the whole remove via getMeshCache, checked that the reference counts are 1, and when I run my unload my program crashes and points to a completely unrelated part of my project. (and if I comment out the line it claims is causing it, it'll say it's the next line, but these are 100% unrelated) I do this all prior to the render loop...
Re: How to safely unload meshes and textures?
Ok, it seems I've done it but my memory usage is still the same, even though the mesh cache size is 0. Any idea?
Re: How to safely unload meshes and textures?
The mesh could be referenced somewhere else. Try checking the reference count before removing it.
Something like this:
Something like this:
Code: Select all
if(mesh->getReferenceCount() > 0)
{
SceneManager->getMeshCache()->removeMesh(mesh);
}Re: How to safely unload meshes and textures?
You should look at CMeshCache.cpp to get a good idea of what it is doing. If the mesh is in the mesh cache, then it will drop the reference to the array and remove it from the cache.dart_theg wrote: Sat Oct 25, 2025 6:37 pm Ok, it seems I've done it but my memory usage is still the same, even though the mesh cache size is 0. Any idea?
From there, open IReferenceCounted.cpp (its in the include folder). Look at drop and you see these lines:
Code: Select all
--ReferenceCounter;
if (!ReferenceCounter)
{
delete this;
return true;
}Basically, removing it from the cache won't automatically set the reference count to 0. Any function that called grab() on the mesh still has an active reference to it until it has been dropped.
However, I am no expert so CuteAlien might have something to correct me on this.
Re: How to safely unload meshes and textures?
Nope, nothing to say - just like that ;-)
Other possibility is that you still have a pointer to the mesh somewhere but didn't grab - then the pointer is now pointing to bad memory.
Really hard to find that stuff without debugging.
Other possibility is that you still have a pointer to the mesh somewhere but didn't grab - then the pointer is now pointing to bad memory.
Really hard to find that stuff without debugging.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
Re: How to safely unload meshes and textures?
In fact, the memory will not decrease, but it will not increase a second time either (it may allocate a little more memory the second time). If you delete and clear the model from memory, Irrlicht will continue to reuse the allocated memory. I can confirm this in an example on Linux: the memory does not decrease because it is then reused or more memory is allocated if necessary. in other words, the memory space that did not decrease is free to load another model in its place, for example... or so I believe...
In other words, you won't see the memory decrease, but consider it as if it had decreased and is technically free to load another model by reusing the resources.
...In other words, you don't have to worry that memory will grow indefinitely even if you clear the model; free memory will simply remain available to assign values.
In other words, you won't see the memory decrease, but consider it as if it had decreased and is technically free to load another model by reusing the resources.
...In other words, you don't have to worry that memory will grow indefinitely even if you clear the model; free memory will simply remain available to assign values.
Code: Select all
// toggle_sydney_memory_stable.cpp
// C++98 - Memory stability test for Irrlicht SDK
// Tests that memory stabilizes after initial allocations (no leaks)
// SPACE toggles load/unload, ESC quits, 'R' resets memory baseline
#include <irrlicht.h>
#include <cstdio>
#include <vector>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <psapi.h>
#else
#include <stdio.h>
#include <unistd.h>
#endif
#ifdef _IRR_WINDOWS_
#pragma comment(lib, "Irrlicht.lib")
#endif*/
using namespace irr;
using namespace core;
using namespace scene;
using namespace video;
using namespace gui;
// Cross-platform RSS monitoring
static long getProcessRSS_kB()
{
#ifdef _WIN32
PROCESS_MEMORY_COUNTERS pmc;
if (GetProcessMemoryInfo(GetCurrentProcess(), &pmc, sizeof(pmc))) {
return (long)(pmc.WorkingSetSize / 1024);
}
return -1;
#else
FILE* f = fopen("/proc/self/status","r");
if (!f) return -1;
char line[256];
long result = -1;
while (fgets(line,sizeof(line),f)) {
if (strncmp(line,"VmRSS:",6)==0) {
char *p = line;
while (*p && (*p < '0' || *p > '9')) ++p;
if (*p) result = atol(p);
break;
}
}
fclose(f);
return result;
#endif
}
class SimpleReceiver : public IEventReceiver {
public:
SimpleReceiver(): tog(false), quit(false), reset_base(false) {}
virtual bool OnEvent(const SEvent& e) {
if (e.EventType==EET_KEY_INPUT_EVENT && !e.KeyInput.PressedDown) {
if (e.KeyInput.Key==KEY_SPACE) { tog = true; return true; }
if (e.KeyInput.Key==KEY_ESCAPE) { quit = true; return true; }
if (e.KeyInput.Key==KEY_KEY_R) { reset_base = true; return true; }
}
return false;
}
bool consumeToggle() { if (tog) { tog=false; return true; } return false; }
bool consumeReset() { if (reset_base) { reset_base=false; return true; } return false; }
bool shouldQuit() const { return quit; }
private:
bool tog, quit, reset_base;
};
// Memory monitoring with baseline
class MemoryMonitor {
private:
long baseline_rss;
std::vector<long> history;
int cycle_count;
public:
MemoryMonitor() : baseline_rss(-1), cycle_count(0) {}
void setBaseline() {
baseline_rss = getProcessRSS_kB();
history.clear();
history.push_back(baseline_rss);
cycle_count = 0;
std::printf("[MEMORY] Baseline set: %ld kB\n", baseline_rss);
}
void record(const char* context) {
long current_rss = getProcessRSS_kB();
history.push_back(current_rss);
cycle_count++;
std::printf("[MEMORY %d] %s - RSS: %ld kB", cycle_count, context, current_rss);
if (baseline_rss != -1) {
long diff = current_rss - baseline_rss;
std::printf(" (delta from baseline: %+ld kB)", diff);
}
if (history.size() > 1) {
long prev_rss = history[history.size()-2];
long instant_diff = current_rss - prev_rss;
std::printf(" (instant: %+ld kB)", instant_diff);
}
std::printf("\n");
}
void analyzeStability() {
if (history.size() < 4) return;
// Check if memory has stabilized
long recent_avg = 0;
int count = history.size() > 10 ? 10 : history.size();
for (int i = history.size() - count; i < history.size(); i++) {
recent_avg += history[i];
}
recent_avg /= count;
long max_deviation = 0;
for (int i = history.size() - count; i < history.size(); i++) {
long dev = std::abs(history[i] - recent_avg);
if (dev > max_deviation) max_deviation = dev;
}
std::printf("[STABILITY] Last %d samples: avg=%ld kB, max deviation=%ld kB (%s)\n",
count, recent_avg, max_deviation,
max_deviation < 1024 ? "STABLE" : "UNSTABLE");
}
};
// Camera positioning
static void positionCameraForNode(ISceneManager* smgr, ISceneNode* node)
{
if (!node) {
smgr->addCameraSceneNode(0, vector3df(0,20,-60), vector3df(0,0,0));
return;
}
core::aabbox3d<f32> box = node->getBoundingBox();
core::matrix4 absMat = node->getAbsoluteTransformation();
core::vector3d<f32> corners[8];
corners[0] = core::vector3d<f32>(box.MinEdge.X, box.MinEdge.Y, box.MinEdge.Z);
corners[1] = core::vector3d<f32>(box.MaxEdge.X, box.MinEdge.Y, box.MinEdge.Z);
corners[2] = core::vector3d<f32>(box.MinEdge.X, box.MaxEdge.Y, box.MinEdge.Z);
corners[3] = core::vector3d<f32>(box.MaxEdge.X, box.MaxEdge.Y, box.MinEdge.Z);
corners[4] = core::vector3d<f32>(box.MinEdge.X, box.MinEdge.Y, box.MaxEdge.Z);
corners[5] = core::vector3d<f32>(box.MaxEdge.X, box.MinEdge.Y, box.MaxEdge.Z);
corners[6] = core::vector3d<f32>(box.MinEdge.X, box.MaxEdge.Y, box.MaxEdge.Z);
corners[7] = core::vector3d<f32>(box.MaxEdge.X, box.MaxEdge.Y, box.MaxEdge.Z);
core::aabbox3d<f32> wb;
bool first=true;
for (int i=0;i<8;++i) {
absMat.transformVect(corners[i]);
if (first) { wb.reset(corners[i]); first=false; }
else wb.addInternalPoint(corners[i]);
}
core::vector3d<f32> center = wb.getCenter();
core::vector3d<f32> extent = wb.getExtent();
f32 r = core::max_<f32>(extent.getLength(), 1.0f);
core::vector3d<f32> camPos = center + core::vector3df(0.0f, r*0.6f, r*2.5f);
smgr->addCameraSceneNode(0, camPos, center);
}
// Resource loading with cache monitoring
static void loadResources(ISceneManager* smgr, IVideoDriver* driver,
const char* modelPath, const char* texPath,
IAnimatedMesh*& outMesh, ISceneNode*& outNode, ITexture*& outTex)
{
outMesh = 0; outNode = 0; outTex = 0;
IMeshCache* meshCache = smgr->getMeshCache();
std::printf("[LOAD] Mesh cache size before: %d\n", (int)meshCache->getMeshCount());
IAnimatedMesh* mesh = smgr->getMesh(modelPath);
if (!mesh) {
char alt[512]; std::sprintf(alt, "media/%s", modelPath);
mesh = smgr->getMesh(alt);
}
if (!mesh) {
std::printf("[LOAD] Failed to load mesh '%s'\n", modelPath);
return;
}
outMesh = mesh;
IAnimatedMeshSceneNode* node = smgr->addAnimatedMeshSceneNode(mesh);
if (!node) {
std::printf("[LOAD] Failed to create mesh node\n");
return;
}
node->setMaterialFlag(EMF_LIGHTING, false);
s32 frames = mesh->getFrameCount();
if (frames > 1) node->setFrameLoop(0, frames-1);
node->setAnimationSpeed(15.0f);
outNode = node;
ITexture* tex = driver->getTexture(texPath);
if (!tex) {
char alt[512]; std::sprintf(alt, "media/%s", texPath);
tex = driver->getTexture(alt);
}
if (tex) {
outTex = tex;
for (u32 i=0;i<node->getMaterialCount();++i) {
node->setMaterialTexture(i, tex);
}
}
std::printf("[LOAD] Mesh cache size after: %d\n", (int)meshCache->getMeshCount());
std::printf("[LOAD] Loaded '%s' (%d frames)\n", modelPath, (int)frames);
}
// Cleanup that focuses on reference counting
static void unloadResources(ISceneManager* smgr, IVideoDriver* driver,
IAnimatedMesh*& mesh, ISceneNode*& node, ITexture*& tex)
{
std::printf("[UNLOAD] Starting cleanup\n");
IMeshCache* meshCache = smgr->getMeshCache();
if (node) {
// Report node state before cleanup
std::printf("[UNLOAD] Node children: %d\n", (int)node->getChildren().size());
// Clear materials
for (u32 i = 0; i < node->getMaterialCount(); ++i) {
node->setMaterialTexture(i, NULL);
}
// Remove mesh reference from animated node
IAnimatedMeshSceneNode* animNode = (IAnimatedMeshSceneNode*)node;
if (animNode) {
animNode->setMesh(NULL);
}
node->remove();
node = NULL;
}
smgr->clear();
if (tex) {
driver->removeTexture(tex);
tex = NULL;
}
if (mesh) {
int refs_before = mesh->getReferenceCount();
std::printf("[UNLOAD] Mesh references: %d\n", refs_before);
meshCache->removeMesh(mesh);
mesh = NULL;
}
// Don't clear entire cache - let Irrlicht manage its pools
// meshCache->clear();
std::printf("[UNLOAD] Mesh cache size after cleanup: %d\n", (int)meshCache->getMeshCount());
std::printf("[UNLOAD] Cleanup completed\n");
}
int main()
{
IrrlichtDevice* device = createDevice(EDT_OPENGL, dimension2d<u32>(1024,768), 16, false, false, false, 0);
if (!device) {
std::printf("Failed to create Irrlicht device\n");
return 1;
}
SimpleReceiver recv;
device->setEventReceiver(&recv);
IVideoDriver* driver = device->getVideoDriver();
ISceneManager* smgr = device->getSceneManager();
IGUIEnvironment* guienv = device->getGUIEnvironment();
IGUIFont* font = guienv->getBuiltInFont();
const char* model = "../../media/sydney.md2";
const char* texture = "../../media/sydney.bmp";
IAnimatedMesh* mesh = 0;
ISceneNode* node = 0;
ITexture* tex = 0;
bool loaded = false;
int cycle = 0;
MemoryMonitor memMonitor;
memMonitor.setBaseline();
std::printf("=== MEMORY STABILITY TEST ===\n");
std::printf("Initial state - no model loaded\n");
memMonitor.record("Initial state");
// Main loop
while (device->run()) {
if (recv.shouldQuit()) break;
if (recv.consumeReset()) {
std::printf("\n=== RESETTING MEMORY BASELINE ===\n");
memMonitor.setBaseline();
}
if (recv.consumeToggle()) {
if (loaded) {
std::printf("\n--- UNLOAD CYCLE %d ---\n", cycle);
unloadResources(smgr, driver, mesh, node, tex);
loaded = false;
memMonitor.record("After unload");
} else {
std::printf("\n--- LOAD CYCLE %d ---\n", cycle);
loadResources(smgr, driver, model, texture, mesh, node, tex);
if (node) positionCameraForNode(smgr, node);
loaded = (node != 0);
memMonitor.record("After load");
cycle++;
// Analyze stability every few cycles
if (cycle % 5 == 0) {
memMonitor.analyzeStability();
}
}
}
// Rendering
driver->beginScene(true, true, SColor(255,100,101,140));
smgr->drawAll();
guienv->drawAll();
// Display information
char buf[512];
long rss = getProcessRSS_kB();
std::sprintf(buf, "Cycle: %d Loaded: %s RSS: %ld kB",
cycle, loaded ? "YES" : "NO", rss);
if (font) font->draw(buf, core::rect<s32>(10,10,900,30), SColor(255,255,255,255));
std::sprintf(buf, "Mesh: %s Node: %s Texture: %s",
mesh ? "present" : "null",
node ? "present" : "null",
tex ? "present" : "null");
if (font) font->draw(buf, core::rect<s32>(10,34,900,54), SColor(255,255,255,255));
if (mesh) {
int refs = mesh->getReferenceCount();
int meshCacheSize = smgr->getMeshCache()->getMeshCount();
std::sprintf(buf, "Mesh refs: %d Mesh cache: %d", refs, meshCacheSize);
if (font) font->draw(buf, core::rect<s32>(10,58,900,78), SColor(255,255,255,255));
}
// Instructions
if (font) {
font->draw(L"SPACE: Toggle load/unload", core::rect<s32>(10,82,900,102), SColor(255,255,255,255));
font->draw(L"R: Reset memory baseline", core::rect<s32>(10,106,900,126), SColor(255,255,255,255));
font->draw(L"ESC: Quit", core::rect<s32>(10,130,900,150), SColor(255,255,255,255));
}
driver->endScene();
}
// Final cleanup
if (loaded) {
unloadResources(smgr, driver, mesh, node, tex);
}
memMonitor.record("Before shutdown");
memMonitor.analyzeStability();
device->drop();
std::printf("=== TEST COMPLETE ===\n");
return 0;
}
**
If you are looking for people with whom to develop your game, even to try functionalities, I can help you, free. CC0 man.

**
If you are looking for people with whom to develop your game, even to try functionalities, I can help you, free. CC0 man.

**