memoryLeak in panda ?

Hi.
When trying to profiling a bit our program, I found that the memory used is in constant augmentation.

after few try, it seems to come from the rotating camera that we have.
there no ‘leak’ with a standing camera, but there is a small one if the camera rotate ( in a separate task).

I have try to isolate it :

if you add these line in the shadow tutorial, the same effect apply:

import math
from direct.task                  import Task

at the beginning

taskMgr.add(self.control_camera, "camera-task")

at the end of world.init

and

    def control_camera(self, task):
        #==
        angle_degrees = task.time * 30.0
        angle_radians = angle_degrees * (math.pi / 180.0)
        base.cam.setPos(30 * math.sin(angle_radians), 45 * math.cos(angle_radians), 26)
        base.cam.lookAt(0, 0, 0)
        return Task.cont

inside the ‘world’ class

then using the system monitor, I can see the increasing memory use ( which is very slow in the demo but much bigger in my program )

Am I doing something wrong ?

by the way: I’m running panda3D 1.5.4 on a ubuntu 8.10, 8800GT 180.something NVIDIA DRIVERS

EDIT: in pstats it seems that this come from: system_memory/mmap/neverfree/active/transform state for the most part

Are you sure it’s a leak, and not just a cache filling up? Each time you move the camera, or any node for that matter, Panda needs to recompute a bunch of transforms for the new world positions, and each of these is a TransformState. They get stored in a cache for rapid recomputation, and the cache will grow over time, but it shouldn’t grow without bounds.

You can disable the cache if you want. Put:

transform-cache 0

in your Config.prc file. This may or may not result in a performance penalty, depending on your usage pattern. (Turning off the cache may hurt performance by causing more transforms to have to be recomputed unnecessarily. Then again, it may help performance if you are filling up the cache with a lot of TransformStates that never get revisited.)

I’m surprised that it appears to be a leak, though. How fast does your memory usage grow? Does it never taper off?

David

ok.
It was the cache.
With the correct config file, the ‘leak’ disappear.

But I still think there a leak with the cache:

My program start with 640 mb used of ram ( OS + panda3D,…)

with only a rotating camera, that amount is constant with “transform-cache 0”
but without it, in 30 min it became1.8 gb of ram. ( I stopped there but it was still increasing )
I don’t think the cache supposed to be this big !

You’re right, it seems like something is wrong. Is your program simple enough for you to send me a copy of it?

David

Like I said, I can obtain the same ‘leak’ with only the small change described at the beginning of the topic.:

just replace the file Tut-Shadow-Mapping.py from the sampler Shadow of panda3D:
( or do the change directly inside the file )

 from pandac.PandaModules import *
import sys,os
loadPrcFileData("", "prefer-parasite-buffer #f")

import direct.directbase.DirectStart
from direct.interval.IntervalGlobal import *
from direct.gui.DirectGui import OnscreenText
from direct.showbase.DirectObject import DirectObject
from direct.actor import Actor
from random import *
import math
from direct.task                  import Task

# Function to put instructions on the screen.
def addInstructions(pos, msg):
    return OnscreenText(text=msg, style=1, fg=(1,1,1,1), mayChange=1,
                        pos=(-1.3, pos), align=TextNode.ALeft, scale = .05, shadow=(0,0,0,1), shadowOffset=(0.1,0.1))

# Function to put title on the screen.
def addTitle(text):
    return OnscreenText(text=text, style=1, fg=(1,1,1,1),
                        pos=(1.3,-0.95), align=TextNode.ARight, scale = .07)

class World(DirectObject):
    def __init__(self):
        # Preliminary capabilities check.
    
        if (base.win.getGsg().getSupportsBasicShaders()==0):
            self.t=addTitle("Shadow Demo: Video driver reports that shaders are not supported.")
            return
        if (base.win.getGsg().getSupportsDepthTexture()==0):
            self.t=addTitle("Shadow Demo: Video driver reports that depth textures are not supported.")
            return
        
        # creating the offscreen buffer.
    
        winprops = WindowProperties.size(512,512)
        props = FrameBufferProperties()
        props.setRgbColor(1)
        props.setAlphaBits(1)
        props.setDepthBits(1)
        LBuffer = base.graphicsEngine.makeOutput(
                 base.pipe, "offscreen buffer", -2,
                 props, winprops,
                 GraphicsPipe.BFRefuseWindow,
                 base.win.getGsg(), base.win)
    
        if (LBuffer == None):
           self.t=addTitle("Shadow Demo: Video driver cannot create an offscreen buffer.")
           return

        Ldepthmap = Texture()
        LBuffer.addRenderTexture(Ldepthmap, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPDepth)
        if (base.win.getGsg().getSupportsShadowFilter()):
            Ldepthmap.setMinfilter(Texture.FTShadow)
            Ldepthmap.setMagfilter(Texture.FTShadow) 

	# Adding a color texture is totally unnecessary, but it helps with debugging.
        Lcolormap = Texture()
        LBuffer.addRenderTexture(Lcolormap, GraphicsOutput.RTMBindOrCopy, GraphicsOutput.RTPColor)
    
        self.inst_p = addInstructions(0.95, 'P : stop/start the Panda Rotation')
        self.inst_w = addInstructions(0.90, 'W : stop/start the Walk Cycle')
        self.inst_t = addInstructions(0.85, 'T : stop/start the Teapot')
        self.inst_l = addInstructions(0.80, 'L : move light source far or close')
        self.inst_v = addInstructions(0.75, 'V: View the Depth-Texture results')
        self.inst_x = addInstructions(0.70, 'Left/Right Arrow : switch camera angles')
        self.inst_a = addInstructions(0.65, 'Something about A/Z and push bias')
    
        base.setBackgroundColor(0,0,0.2,1)
    
        base.camLens.setNearFar(1.0,10000)
        base.camLens.setFov(75)
        base.disableMouse()

        
        # Load the scene.
    
        floorTex=loader.loadTexture('maps/envir-ground.jpg')
        cm=CardMaker('')
        cm.setFrame(-2,2,-2,2)
        floor = render.attachNewNode(PandaNode("floor"))
        for y in range(12):
            for x in range(12):
                nn = floor.attachNewNode(cm.generate())
                nn.setP(-90)
                nn.setPos((x-6)*4, (y-6)*4, 0)
        floor.setTexture(floorTex)
        floor.flattenStrong()
    
        self.pandaAxis=render.attachNewNode('panda axis')
        self.pandaModel=Actor.Actor('panda-model',{'walk':'panda-walk4'})
        self.pandaModel.reparentTo(self.pandaAxis)
        self.pandaModel.setPos(9,0,0)
        self.pandaModel.setShaderInput("scale",0.01,0.01,0.01,1.0)
        self.pandaWalk = self.pandaModel.actorInterval('walk',playRate=1.8)
        self.pandaWalk.loop()
        self.pandaMovement = self.pandaAxis.hprInterval(20.0,Point3(-360,0,0),startHpr=Point3(0,0,0))
        self.pandaMovement.loop()
    
        self.teapot=loader.loadModel('teapot')
        self.teapot.reparentTo(render)
        self.teapot.setPos(0,-20,10)
        self.teapot.setShaderInput("texDisable",1,1,1,1)
        self.teapotMovement = self.teapot.hprInterval(50,Point3(0,360,360))
        self.teapotMovement.loop()
    
        self.accept('escape',sys.exit)
    
        self.accept("arrow_left", self.incrementCameraPosition, [-1])
        self.accept("arrow_right", self.incrementCameraPosition, [1])
        self.accept("p", self.toggleInterval, [self.pandaMovement])
        self.accept("P", self.toggleInterval, [self.pandaMovement])
        self.accept("t", self.toggleInterval, [self.teapotMovement])
        self.accept("T", self.toggleInterval, [self.teapotMovement])
        self.accept("w", self.toggleInterval, [self.pandaWalk])
        self.accept("W", self.toggleInterval, [self.pandaWalk])
        self.accept("v", base.bufferViewer.toggleEnable)
        self.accept("V", base.bufferViewer.toggleEnable)
        self.accept("l", self.incrementLightPosition, [1])
        self.accept("L", self.incrementLightPosition, [1])
        self.accept("o", base.oobe)
        self.accept('a',self.adjustPushBias,[1.1])
        self.accept('A',self.adjustPushBias,[1.1])
        self.accept('z',self.adjustPushBias,[0.9])
        self.accept('Z',self.adjustPushBias,[0.9])
    
        self.LCam=base.makeCamera(LBuffer)
        self.LCam.node().setScene(render)
        self.LCam.node().getLens().setFov(40)
        self.LCam.node().getLens().setNearFar(10,100)

        # default values
        self.pushBias=0.04
        self.ambient=0.2
        self.cameraSelection = 0
        self.lightSelection = 0
    
        # setting up shader
        render.setShaderInput('light',self.LCam)
        render.setShaderInput('Ldepthmap',Ldepthmap)
        render.setShaderInput('ambient',self.ambient,0,0,1.0)
        render.setShaderInput('texDisable',0,0,0,0)
        render.setShaderInput('scale',1,1,1,1)
    
        # Put a shader on the Light camera.
        lci = NodePath(PandaNode("Light Camera Initializer"))
        lci.setShader(Shader.load('caster.sha'))
        self.LCam.node().setInitialState(lci.getState())
    
        # Put a shader on the Main camera.
        # Some video cards have special hardware for shadow maps.
        # If the card has that, use it.  If not, use a different
        # shader that does not require hardware support.

        mci = NodePath(PandaNode("Main Camera Initializer"))
        if (base.win.getGsg().getSupportsShadowFilter()):
            mci.setShader(Shader.load('shadow.sha'))
        else:
            mci.setShader(Shader.load('shadow-nosupport.sha'))
        base.cam.node().setInitialState(mci.getState())
    
        self.incrementCameraPosition(0)
        self.incrementLightPosition(0)
        self.adjustPushBias(1.0)
        taskMgr.add(self.control_camera, "camera-task")
    
# end of __init__

    def toggleInterval(self, ival):
        if (ival.isPlaying()):
            ival.pause()
        else:
            ival.resume()

    def control_camera(self, task):
        #==
        angle_degrees = task.time * 30.0
        angle_radians = angle_degrees * (math.pi / 180.0)
        base.cam.setPos(30 * math.sin(angle_radians), 45 * math.cos(angle_radians), 26)
        base.cam.lookAt(0, 0, 0)
        return Task.cont

    def incrementCameraPosition(self,n):
        self.cameraSelection = (self.cameraSelection + n) % 6
        if (self.cameraSelection == 0):
            base.cam.reparentTo(render)
            base.cam.setPos(30,-45,26)
            base.cam.lookAt(0,0,0)
            self.LCam.node().hideFrustum()
        if (self.cameraSelection == 1):
            base.cam.reparentTo(self.pandaModel)
            base.cam.setPos(7,-3,9)
            base.cam.lookAt(0,0,0)
            self.LCam.node().hideFrustum()
        if (self.cameraSelection == 2):
            base.cam.reparentTo(self.pandaModel)
            base.cam.setPos(-7,-3,9)
            base.cam.lookAt(0,0,0)
            self.LCam.node().hideFrustum()
        if (self.cameraSelection == 3):
            base.cam.reparentTo(render)
            base.cam.setPos(7,-23,12)
            base.cam.lookAt(self.teapot)
            self.LCam.node().hideFrustum()
        if (self.cameraSelection == 4):
            base.cam.reparentTo(render)
            base.cam.setPos(-7,-23,12)
            base.cam.lookAt(self.teapot)
            self.LCam.node().hideFrustum()
        if (self.cameraSelection == 5):
            base.cam.reparentTo(render)
            base.cam.setPos(1000,0,195)
            base.cam.lookAt(0,0,0)
            self.LCam.node().showFrustum()
  
    def incrementLightPosition(self,n):
        self.lightSelection = (self.lightSelection + n) % 2
        if (self.lightSelection == 0):
            self.LCam.setPos(0,-40,25)
            self.LCam.lookAt(0,-10,0)
            self.LCam.node().getLens().setNearFar(10,100)
        if (self.lightSelection == 1):
            self.LCam.setPos(0,-600,200)
            self.LCam.lookAt(0,-10,0)
            self.LCam.node().getLens().setNearFar(10,1000)
  
    def shaderSupported(self):
        return base.win.getGsg().getSupportsBasicShaders() and \
               base.win.getGsg().getSupportsDepthTexture() and \
               base.win.getGsg().getSupportsShadowFilter()
  
    def adjustPushBias(self,inc):
        self.pushBias *= inc
        self.inst_a.setText('A/Z: Increase/Decrease the Push-Bias [%F]' % self.pushBias)
        render.setShaderInput('push',self.pushBias,self.pushBias,self.pushBias,0)
  

World()
run()

Update: there does appear to be a genuine leak in there. Strangely, it only happens in certain specific circumstances. I am actively researching it.

David

thank david.
I’ve try with the no transform-cache in the config file, but then the program is far too slow to be usable, so we’re stuck with this for a while.

I’ve found the problem, and will check in a fix. In the meantime, though, I also found a config variable that should work around the problem for you, without causing the performance hit of disabling the cache altogether. Try:

uniquify-transforms 0

David

thank a lot, I try that immediately