I have problem of memory leak when using materials. I read some thread about this but I didn’t found the answer i was looking for.
So I need to change all the materials on a gameobject each second and the materials I need are kept in an array. The problem is that some copies of the materials are kept in memory every time I change the materials.
Here is the code I use:
foreach (GameObject g in Rooms)
{
if (g != null)
{
int index = a number computed;
Material[] mats = g.GetComponent<MeshRenderer>().materials;
for (int j = 0; j < g.GetComponent<MeshRenderer>().materials.Length; j++)
{
mats[j] = StoredMaterial[index];
}
g.GetComponent<MeshRenderer>().materials = mats;
}
}
I tried to found a work around but I found none that resolve all my problem.
If i use the following code, I do note have the leaks anymore but only the first material is changed and if the gameobjects have more than one materials it did not work well.
foreach (GameObject g in Rooms)
{
if (g != null)
{
int index = a number previously computed;
g.GetComponent<MeshRenderer>().material = StoredMaterial[index];
}
}
StoredMaterial is an Array of Materials where are stored the different materials that could be used at every iteration of my application to colorize the differents gameobjects with the good color. And the goal is to change the color of some part of a big object at runtime using external data to pick the correct color in the StoredMaterial array. And as I build on a low performance device I must take care of the perf of my application.
Since all answers are either wrong or provide bad (almost horrible) solutions I’ll post an answer.
Many people seem to not understand or are not aware of the difference between the material / materials property compared to the sharedMaterial / sharedMaterials property. Whenever you use the material or materials property (use means either reading or writing it) Unity will automatically instantiate the material for this renderer unless it already has its own instance. This has many severe consequences.
First of all as observed the instantiated materials are not automatically destroyed when you replace the material. This is actually true for all objects that are derived from UnityEngine.Object. So this holds true for Mesh, Material, GameObject, Component, ScriptableObject, … For GameObjects and its components there’s a slight difference. When destroying a gameobject all components and sub gameobjects are destroyed as well. However destroying a gameobject or the renderer component does not destroy an instantiated Material or Mesh. The material is not “owned” by the renderer and could potentially be used by other objects as well. This is not possible with components.
Besides the duty of cleaning up the instantiated Materials when using those properties is that instantiated materials are seperate materials. That means they will always cause to be rendered with its own drawcall, even when all parameters are the same. So looping through 10 objects and assigning the same material to the material / materials property will actually create 10 seperate materials. Such materials will not be batched. So by using those properties you essentially loose any batching support for those objects.
The properties sharedMaterial and sharedMaterials on the other hand do not automatically instantiate the Material. That means if you have 10 objects and you assigned the same material to all 10 sharedMaterial properties, they will all use the same material. Such objects can actually be batched (statically or dynamically). Of course that also means you can not change any material attributes only for one object. So when changing the shared material all objects that are using that material will be affected.
Unfortunately at the moment we can not check / test if a renderer actually has an instantiated material or not because any access to material / materials would immediately instantiate the material(s). So like mentioned in the documentation of the material property you are responsible for keeping track of where you actually created instances of Materials.
About the code snippet in the question another warning when using the materials or sharedMaterials property. They return an array of materials. However each time you read this property a new array will be created. Since the for loop uses .materials.Length in the loop condition the code would create a new array for every loop iteration. Of course those arrays will be garbage collected but it’s unnecessary created garbage. The code in the OP’s answer is even worse. Instead of using materials and causing an instantiation of all those materials he should just use sharedMaterials without destroying anything.
So the proper answer would be:
It appears that the garbage collector do not collect unused materials and as I do not destroy the materials I replace, it creates a memory leaks.
I just had to destroy the Materials in g.GetComponent().materials before assigning them again.
In order to change all the materials on one gameobject without creating a leak, I first create a array with the good size, then for each materials inside, I destroy the current one and add the new one in the array. Then replace the old array with the new one.