Memory Leak related to unload and rendering
Hi,
there is another memory leak, this time related to rendering in combination with simulation.unload.
As a testing scene, I took the pygame example from SofaPython3 and added a reload to every tenth step.
import Sofa
import Sofa.Core
import Sofa.Simulation
import Sofa.SofaGL
import SofaRuntime
import os
sofa_directory = os.environ["SOFA_ROOT"]
import pygame
from OpenGL.GL import *
from OpenGL.GLU import *
display_size = (800, 600)
def init_display(node):
pygame.display.init()
pygame.display.set_mode(display_size, pygame.DOUBLEBUF | pygame.OPENGL)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glEnable(GL_LIGHTING)
glEnable(GL_DEPTH_TEST)
Sofa.SofaGL.glewInit()
Sofa.Simulation.initVisual(node)
Sofa.Simulation.initTextures(node)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(45, (display_size[0] / display_size[1]), 0.1, 50.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
def simple_render(rootNode):
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glEnable(GL_LIGHTING)
glEnable(GL_DEPTH_TEST)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(45, (display_size[0] / display_size[1]), 0.1, 50.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
cameraMVM = rootNode.camera.getOpenGLModelViewMatrix()
glMultMatrixd(cameraMVM)
Sofa.SofaGL.draw(rootNode)
pygame.display.get_surface().fill((0, 0, 0))
pygame.display.flip()
def createScene(root):
# Register all the common component in the factory.
SofaRuntime.PluginRepository.addFirstPath(os.path.join(sofa_directory, "bin"))
root.addObject("RequiredPlugin", name="SofaOpenglVisual") # visual stuff
root.addObject("RequiredPlugin", name="SofaLoader") # geometry loaders
root.addObject("RequiredPlugin", name="SofaSimpleFem") # diffusion fem
root.addObject("RequiredPlugin", name="SofaBoundaryCondition") # constraints
root.addObject("RequiredPlugin", name="SofaEngine") # Box Roi
root.addObject("RequiredPlugin", name="SofaImplicitOdeSolver") # implicit solver
root.addObject("RequiredPlugin", name="SofaMiscForceField") # meshmatrix
root.addObject("RequiredPlugin", name="SofaGeneralEngine") # TextureInterpolation
root.addObject("RequiredPlugin", name="CImgPlugin") # for loading a bmp image for texture
root.addObject("RequiredPlugin", name="SofaBaseLinearSolver")
root.addObject("RequiredPlugin", name="SofaGeneralVisual")
root.addObject("RequiredPlugin", name="SofaTopologyMapping")
root.addObject("RequiredPlugin", name="SofaGeneralTopology")
root.addObject("RequiredPlugin", name="SofaGeneralLoader")
### these are just some things that stay still and move around
# so you know the animation is actually happening
root.gravity = [0, -1.0, 0]
root.addObject("VisualStyle", displayFlags="showAll")
root.addObject("MeshGmshLoader", name="meshLoaderCoarse", filename="mesh/liver.msh")
root.addObject("MeshObjLoader", name="meshLoaderFine", filename="mesh/liver-smooth.obj")
root.addObject("EulerImplicitSolver")
root.addObject("CGLinearSolver", iterations="200", tolerance="1e-09", threshold="1e-09")
liver = root.addChild("liver")
liver.addObject("TetrahedronSetTopologyContainer", name="topo", src="@../meshLoaderCoarse")
liver.addObject("TetrahedronSetGeometryAlgorithms", template="Vec3d", name="GeomAlgo")
liver.addObject("MechanicalObject", template="Vec3d", name="MechanicalModel", showObject="1", showObjectScale="3")
liver.addObject("TetrahedronFEMForceField", name="fem", youngModulus="1000", poissonRatio="0.4", method="large")
liver.addObject("MeshMatrixMass", massDensity="1")
liver.addObject("FixedConstraint", indices="2 3 50")
visual = liver.addChild("visual")
visual.addObject("MeshObjLoader", name="meshLoader_0", filename="mesh/liver-smooth.obj", handleSeams="1")
visual.addObject("OglModel", name="VisualModel", src="@meshLoader_0", color="red")
visual.addObject("BarycentricMapping", input="@..", output="@VisualModel", name="visual mapping")
# place light and a camera
root.addObject("LightManager")
root.addObject("DirectionalLight", direction=[0, 1, 0])
root.addObject("InteractiveCamera", name="camera", position=[0, 15, 0], lookAt=[0, 0, 0], distance=37, fieldOfView=45, zNear=0.63, zFar=55.69)
if __name__ == "__main__":
root = Sofa.Core.Node("myroot")
createScene(root)
Sofa.Simulation.init(root)
with_window = True
if with_window:
init_display(root)
for i in range(300):
Sofa.Simulation.animate(root, root.getDt())
Sofa.Simulation.updateVisual(root)
if with_window:
simple_render(root)
if i % 10 == 0:
if with_window:
pygame.display.quit()
Sofa.Simulation.unload(root)
createScene(root)
Sofa.Simulation.init(root)
if with_window:
init_display(root)
The leak seems to be related to the OpenGL context, because for 31 init_display calls, I get and AddressSanitizer output of
Indirect leak of 59521240 byte(s) in 31 object(s) allocated from:
#0 0x7f8a1c4cf808 in __interceptor_malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cc:144
#1 0x7f8a0be337c3 in SDL_malloc_REAL /sdl_build/SDL2-2.0.16/src/stdlib/SDL_malloc.c:5388
that matches the lost amount of memory. And it also veeeery closely matches the amount of memory required to store 31 RGBA buffers of the set image size. :D
>>> (600*800*4*31)/59521240
0.9999791671006854
If no display is created, there is no leak.
@fredroy Do you maybe know what could be the issue here?
Cheers, Paul
Hello,
As you say, it seems there is a leak of a copy of the framebuffer or something like that.
if you are using the built-in GUIs (qt, etc), I would have not been surprised (as the gui code is not really.... clean lets say)
But here in your case, you invoke your own GUI (pygame).
I would first try to try to locate where it could come from so I would just enable showVisualModel in the VisualStyle component. This will only call the draw() from Visual components (and thus limit the number of guys to investigate...)
One thing I see in your scene which could create/store framebuffers would be the light, which can comnpute depth buffers to compute (optionnally) shadows. You can try to just disable the LightManager/DirectionalLight to see if this is the culprit.
Just one thing for your sceme : if you are using the latest version, you dont need anymore the CImgPlugin to load bmp, as it is now handled natively in the core by the STB utility header (Sofa.Helper) CImgPlugin is only useful to load TIFF files now.
Hi @fredroy Thanks for the tip!
Sadly it even leaks, when there are absolutely no components but the root node.
import Sofa
from tqdm import tqdm
import Sofa.Core
import Sofa.Simulation
import Sofa.SofaGL
# import SofaRuntime
import os
import time
import numpy as np
sofa_directory = os.environ["SOFA_ROOT"]
import pygame
from OpenGL.GL import *
from OpenGL.GLU import *
display_size = (600, 600)
def init_display(node):
pygame.display.init()
pygame.display.set_mode(display_size, pygame.DOUBLEBUF | pygame.OPENGL)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glEnable(GL_LIGHTING)
glEnable(GL_DEPTH_TEST)
Sofa.SofaGL.glewInit()
Sofa.Simulation.initVisual(node)
Sofa.Simulation.initTextures(node)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(45, (display_size[0] / display_size[1]), 0.1, 50.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
def simple_render(rootNode):
"""
Get the OpenGL Context to render an image (snapshot) of the simulation state
"""
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glEnable(GL_LIGHTING)
glEnable(GL_DEPTH_TEST)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(45, (display_size[0] / display_size[1]), 0.1, 50.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
# cameraMVM = rootNode.InteractiveCamera.getOpenGLModelViewMatrix()
cameraMVM = np.identity(4)
glMultMatrixd(cameraMVM)
Sofa.SofaGL.draw(rootNode)
pygame.display.get_surface().fill((0, 0, 0))
pygame.display.flip()
def createScene(root):
pass
# root.addObject("InteractiveCamera", widthViewport=display_size[0], heightViewport=display_size[1])
if __name__ == "__main__":
root = Sofa.Core.Node("myroot")
createScene(root)
Sofa.Simulation.init(root)
with_window = True
if with_window:
init_display(root)
for i in tqdm(range(300)):
Sofa.Simulation.animate(root, root.getDt())
Sofa.Simulation.updateVisual(root)
if with_window:
simple_render(root)
if i % 10 == 0:
if with_window:
pygame.display.quit()
Sofa.Simulation.unload(root)
createScene(root)
Sofa.Simulation.init(root)
if with_window:
init_display(root)
else:
time.sleep(0.001)