[Solved] fps problems

Hello, I’m new to these forums, and relatively new to Panda3d. I hope you can help me with my issue.

I’ve been writing some small programs, pretty much to get the hang of Panda, and my most recent one seems to run unnaturally slowly. I procedurally create a beveled cube, and more or less a floor of these (in a tile pattern).

The code:

from panda3d.core import loadPrcFileData

loadPrcFileData("", "show-frame-rate-meter True")

from direct.showbase.ShowBase import ShowBase

from panda3d.core import Geom, GeomNode
from panda3d.core import GeomTriangles, GeomTristrips
from panda3d.core import GeomVertexFormat, GeomVertexData, GeomVertexWriter

from panda3d.core import Vec3 as V3, Point3 as P3

from panda3d.core import NodePath


def make_tri_vertex(vertices, color=(0, 0, 0, 1)):

    vec0 = vertices[1] - vertices[0]
    vec1 = vertices[2] - vertices[0]
    normal = vec0.cross(vec1)
    normal.normalize()

    format = GeomVertexFormat.getV3n3c4()
    vdata  = GeomVertexData("triangle", format, Geom.UHStatic)
    
    vertex  = GeomVertexWriter(vdata, "vertex")
    vnormal = GeomVertexWriter(vdata, "normal")
    vcolor  = GeomVertexWriter(vdata, "color")

    for i in range(3):
        vertex.addData3f(vertices[i])
        vnormal.addData3f(normal)
        vcolor.addData4f(color)

    triangle = GeomTriangles(Geom.UHStatic)
    
    triangle.addVertices(0, 1, 2)

    tri = Geom(vdata)
    tri.addPrimitive(triangle)

    return tri


def make_square_vertex(vertices, color=(0, 0, 0, 1)):

    vec0 = vertices[1] - vertices[0]
    vec1 = vertices[2] - vertices[0]
    normal = vec0.cross(vec1)
    normal.normalize()

    format = GeomVertexFormat.getV3n3c4()
    vdata  = GeomVertexData("square", format, Geom.UHStatic)
    
    vertex  = GeomVertexWriter(vdata, "vertex")
    vnormal = GeomVertexWriter(vdata, "normal")
    vcolor  = GeomVertexWriter(vdata, "color")

    for i in range(4):
        vertex.addData3f(vertices[i])
        vnormal.addData3f(normal)
        vcolor.addData4f(color)

    tristrip = GeomTristrips(Geom.UHStatic)

    tristrip.addVertices(0, 1, 2, 3)
    tristrip.closePrimitive()
    
    quad = Geom(vdata)
    quad.addPrimitive(tristrip)

    return quad


def make_bevel_cube(name, center, size, bevel, color=(0, 0, 0, 1)):

    if type(size) not in (tuple, list):
        vec0 = V3(size / 2.0, 0, 0)
        vec1 = V3(0, size / 2.0, 0)
        vec2 = V3(0, 0, size / 2.0)

    else:
        vec0 = V3(size[0] / 2.0, 0, 0)
        vec1 = V3(0, size[1] / 2.0, 0)
        vec2 = V3(0, 0, size[2] / 2.0)

    vtxs = (center - vec0 - vec1 - vec2,
            center + vec0 - vec1 - vec2,
            center - vec0 + vec1 - vec2,
            center + vec0 + vec1 - vec2,
            center - vec0 - vec1 + vec2,
            center + vec0 - vec1 + vec2,
            center - vec0 + vec1 + vec2,
            center + vec0 + vec1 + vec2)

    bx = V3(bevel, 0, 0)
    by = V3(0, bevel, 0)
    bz = V3(0, 0, bevel)

    bvs = ((vtxs[0]+bx+by, vtxs[0]+bx+bz, vtxs[0]+by+bz),
           (vtxs[1]-bx+by, vtxs[1]-bx+bz, vtxs[1]+by+bz),
           (vtxs[2]+bx-by, vtxs[2]+bx+bz, vtxs[2]-by+bz),
           (vtxs[3]-bx-by, vtxs[3]-bx+bz, vtxs[3]-by+bz),
           (vtxs[4]+bx+by, vtxs[4]+bx-bz, vtxs[4]+by-bz),
           (vtxs[5]-bx+by, vtxs[5]-bx-bz, vtxs[5]+by-bz),
           (vtxs[6]+bx-by, vtxs[6]+bx-bz, vtxs[6]-by-bz),
           (vtxs[7]-bx-by, vtxs[7]-bx-bz, vtxs[7]-by-bz))

    cube = GeomNode(name)

    cube.addGeom(make_square_vertex((bvs[2][0], bvs[3][0], bvs[0][0], bvs[1][0]), color))
    cube.addGeom(make_square_vertex((bvs[4][0], bvs[5][0], bvs[6][0], bvs[7][0]), color))
    cube.addGeom(make_square_vertex((bvs[0][1], bvs[1][1], bvs[4][1], bvs[5][1]), color))
    cube.addGeom(make_square_vertex((bvs[1][2], bvs[3][2], bvs[5][2], bvs[7][2]), color))
    cube.addGeom(make_square_vertex((bvs[3][1], bvs[2][1], bvs[7][1], bvs[6][1]), color))
    cube.addGeom(make_square_vertex((bvs[2][2], bvs[0][2], bvs[6][2], bvs[4][2]), color))

    cube.addGeom(make_square_vertex((bvs[0][0], bvs[1][0], bvs[0][1], bvs[1][1]), color))
    cube.addGeom(make_square_vertex((bvs[1][0], bvs[3][0], bvs[1][2], bvs[3][2]), color))
    cube.addGeom(make_square_vertex((bvs[3][0], bvs[2][0], bvs[3][1], bvs[2][1]), color))
    cube.addGeom(make_square_vertex((bvs[2][0], bvs[0][0], bvs[2][2], bvs[0][2]), color))

    cube.addGeom(make_square_vertex((bvs[4][2], bvs[0][2], bvs[4][1], bvs[0][1]), color))
    cube.addGeom(make_square_vertex((bvs[5][1], bvs[1][1], bvs[5][2], bvs[1][2]), color))
    cube.addGeom(make_square_vertex((bvs[7][2], bvs[3][2], bvs[7][1], bvs[3][1]), color))
    cube.addGeom(make_square_vertex((bvs[6][1], bvs[2][1], bvs[6][2], bvs[2][2]), color))

    cube.addGeom(make_square_vertex((bvs[4][1], bvs[5][1], bvs[4][0], bvs[5][0]), color))
    cube.addGeom(make_square_vertex((bvs[5][2], bvs[7][2], bvs[5][0], bvs[7][0]), color))
    cube.addGeom(make_square_vertex((bvs[7][1], bvs[6][1], bvs[7][0], bvs[6][0]), color))
    cube.addGeom(make_square_vertex((bvs[6][2], bvs[4][2], bvs[6][0], bvs[4][0]), color))
    
    cube.addGeom(make_tri_vertex((bvs[0][0], bvs[0][1], bvs[0][2]), color))
    cube.addGeom(make_tri_vertex((bvs[1][0], bvs[1][2], bvs[1][1]), color))
    cube.addGeom(make_tri_vertex((bvs[2][0], bvs[2][2], bvs[2][1]), color))
    cube.addGeom(make_tri_vertex((bvs[3][0], bvs[3][1], bvs[3][2]), color))
    cube.addGeom(make_tri_vertex((bvs[4][0], bvs[4][2], bvs[4][1]), color))
    cube.addGeom(make_tri_vertex((bvs[5][0], bvs[5][1], bvs[5][2]), color))
    cube.addGeom(make_tri_vertex((bvs[6][0], bvs[6][1], bvs[6][2]), color))
    cube.addGeom(make_tri_vertex((bvs[7][0], bvs[7][2], bvs[7][1]), color))

    return cube


def basic_bevel_cube(parent, color, pos, name="cube", unit=0.5):
    
    cube = make_bevel_cube(name, P3(0, 0, unit*0.5), (unit,)*3, unit*0.05)
    cube = parent.attachNewNode(cube)
    cube.setColor(color)
    cube.setPos(pos[0], pos[1], pos[2])
    
    return cube


def bevel_floor(parent, color, pos, size, name="floor", unit=0.5):
    
    halfunit = unit*0.5
    
    size_x, size_y = size

    offset_x, offset_y = -size_x*0.5*unit, -size_y*0.5*unit

    node = NodePath(name)
    holder = NodePath("holder")

    cube = basic_bevel_cube(holder, color, (0, 0, -unit), name="bcube", unit=unit)

    for x in range(size_x):
        for y in range(size_y):

            unit_name = str(x) + "," + str(y)
            
            inst = node.attachNewNode(unit_name)
            inst.setPos(offset_x + x*unit, offset_y + y*unit, 0)
            cube.instanceTo(inst)

    node.reparentTo(parent)
    node.setPos(pos[0], pos[1], pos[2])


class App(ShowBase):
    
    def __init__(self):

        self.setup()
        self.add_models()

    def setup(self):
        
        ShowBase.__init__(self)

        self.disableMouse()
        self.camera.setPos(0, -20, 20)
        self.camera.setP(-45)

    def add_models(self):

        self.ground = bevel_floor(render, (0, 0.5, 0, 1), (0, 0, 0), (24, 24))

App().run()

Now, this tends to run at just under 3 fps on my computer, just marginally faster than it was before I used instancing. As it’s ‘only’ 25k tris in the scene, I would expect this to run a lot faster. I probably have a bottleneck somewhere, but I can’t find it.

Any tips on why it’s running so slowly, and how I can get it running faster would be very much appreciated.

(P.S. computer specs, for completeness:
OS: Windows 7
CPU: i7 @ 1.6GHz
RAM: 8GB
GPU: ATI Mobility Radeon HD 5800 series (not entirely sure which at the moment…)
Python: 2.6.5 (from Panda)
Panda3d: 1.7.2)

Your script is creating too many Geom nodes. While it may not be a lot of triangles, having so many Geoms is very taxing on performance.

For example, you are making each cube out of many Geoms. Just by making each cube one Geom you will see a huge performance benefit.

Basically you want to aim for about 200 Geoms visible to the camera at any one time. In your scene you have 24 cubes times 24 cubes times 26 Geoms per cube, equaling potentially 14976. With one Geom per cube you are looking at 576, still too high but it might be OK with simple geometry.

Another option is to use the flatten commands, like nodepath.flattenStrong() on the parent node once you are done creating all of your Geoms. This will combine all of your objects into as few as possible, but it will not be possible to individually manipulate the cubes afterwards.

Ah, thank you, I hadn’t considered the Geom aspect. With one Geom per cube it’s running quite smoothly now, though I’ll probably end up using flattenStrong on the whole floor.