Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Memory Leak related to unload and rendering #306

Open
ScheiklP opened this issue Oct 6, 2022 · 3 comments
Open

Memory Leak related to unload and rendering #306

ScheiklP opened this issue Oct 6, 2022 · 3 comments

Comments

@ScheiklP
Copy link
Collaborator

ScheiklP commented Oct 6, 2022

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

@fredroy
Copy link
Contributor

fredroy commented Oct 7, 2022

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.

@fredroy
Copy link
Contributor

fredroy commented Oct 7, 2022

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.

@ScheiklP
Copy link
Collaborator Author

ScheiklP commented Oct 7, 2022

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants