Godot: GetSurfaceMaterial() causes critical VRAM Memory Leak

Created on 21 Oct 2020  路  4Comments  路  Source: godotengine/godot

Godot version:
Last stable Godot 3.2.3, tested in C# Build

OS/device including version:
Windows 10

Issue description:
If GetSurfaceMaterial() is called anytime it causes a VRAM memory leak when the object is freed. It happens in all free calls like QueueFree(), QueueFree() of parents and even scene changes.

To get the material of objects that have large textures is currently impossible without cause huge memory leaks (based on textures used by it). It's a critical bug and i could not find any workaround to fix it.

Steps to reproduce:

Do in any object:
GetNode<MeshInstance>("MeshInstance").QueueFree();
Check the VRAM Profiler. it released the VRAM.

Now do it:

SpatialMaterial mat = GetNode<MeshInstance>("MeshInstance").GetSurfaceMaterial(0) as SpatialMaterial;
GetNode<MeshInstance>("MeshInstance").QueueFree();

Check the Memory Graph. It do not free anything. VRAM is yet been used.

Or just use the sample project below to test.

Minimal reproduction project:
BugMemory.zip

bug discussion mono

Most helpful comment

"The MeshInstance is freed, but the material is not freed because you still have a reference to it in mat. The material can't be freed until there are no references to it."

Tested without envolve any local variable only a print and the result is the same. My suspect is that GetSurfaceMaterial creates a reference count that is never released. No variable need to be envolved. This also causes the same bug:

GD.Print(GetNode<MeshInstance>("MeshInstance").GetSurfaceMaterial(0) as SpatialMaterial);

It works in gdscript. I haven't tested the c#, but assuming the report is correct this could be a bug in the godot c# side.
This was unexpected. You're right. I tested it now and works as should in GDScript. This is my code

extends Node
export var getMat = true;
func _input(event):
    if event is InputEventKey and event.pressed:
        if event.scancode == KEY_SPACE:
            if getMat:
                var a = get_node("MeshInstance").get_surface_material(0)
                print(a);
            get_node("MeshInstance").queue_free()

Any possible workaround in C# until a proper fix?

All 4 comments

Based on the description here, I am not sure there is a bug.

SpatialMaterial mat = GetNode("MeshInstance").GetSurfaceMaterial(0) as SpatialMaterial;

creates a reference to the material so when you call:

GetNode("MeshInstance").QueueFree();

The MeshInstance is freed, but the material is not freed because you still have a reference to it in mat. The material can't be freed until there are no references to it.

Looking at the example project:

public class Teste : Node
{
    [Export] public bool getMaterial;

    public override void _Input(InputEvent inputEvent)
    {
        if (inputEvent is InputEventKey keyEvent && keyEvent.Pressed)
        {
            if ((KeyList)keyEvent.Scancode == KeyList.Space)
            {
                if(getMaterial){
                    SpatialMaterial mat = GetNode<MeshInstance>("MeshInstance").GetSurfaceMaterial(0) as SpatialMaterial;
                }
                GetNode<MeshInstance>("MeshInstance").QueueFree();
            }
        }
    }
}

My interpretation is that although mat should have a reference, afaik that reference should be released at the end of the function, it mat is on the stack, so that when the deferred QueueFree calls the ref count should be able to reach zero.

Does this also occur in gdscript? Could this be a c# specific issue?

It works in gdscript. I haven't tested the c#, but assuming the report is correct this could be a bug in the godot c# side.

gdscript project, press return to delete the object:
BugMemory2.zip

"The MeshInstance is freed, but the material is not freed because you still have a reference to it in mat. The material can't be freed until there are no references to it."

Tested without envolve any local variable only a print and the result is the same. My suspect is that GetSurfaceMaterial creates a reference count that is never released. No variable need to be envolved. This also causes the same bug:

GD.Print(GetNode<MeshInstance>("MeshInstance").GetSurfaceMaterial(0) as SpatialMaterial);

It works in gdscript. I haven't tested the c#, but assuming the report is correct this could be a bug in the godot c# side.
This was unexpected. You're right. I tested it now and works as should in GDScript. This is my code

extends Node
export var getMat = true;
func _input(event):
    if event is InputEventKey and event.pressed:
        if event.scancode == KEY_SPACE:
            if getMat:
                var a = get_node("MeshInstance").get_surface_material(0)
                print(a);
            get_node("MeshInstance").queue_free()

Any possible workaround in C# until a proper fix?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mefihl picture mefihl  路  3Comments

timoschwarzer picture timoschwarzer  路  3Comments

testman42 picture testman42  路  3Comments

RebelliousX picture RebelliousX  路  3Comments

ndee85 picture ndee85  路  3Comments