hitting a speed limit

Hello,

I asked about creating a 2-d model with only a few tiles. The Procedural Cube tutorial ended up being most helpful.

I’m hitting a speed limit, and I’m only at 10,000 cards on screen. The frame rate is only around 2 or 3 fps, and doesn’t get up to 30 or 40 until there are only several tiles visible. I’d like to know if there are a few simple flags I can flip without trifling.

Here’s the relevant excerpt from my program, CMIIW, correct me if I’m wrong.

xor= mydir + "500px-XOR_ANSI.png"
testTexture=loader.loadTexture(xor)

cm = CardMaker('card')
card = cm.generate()
square0path= NodePath( card )
square0path.setTexture(testTexture)
square0path.setPos( 0, 0, 0 )

placeholders= [ render.attachNewNode(
   "Tex-Placeholder %i"%i) for i in range( 10000 ) ]

for i,ph in enumerate(placeholders):
    ph.setPos(i%100, 0, i/100)
    square0path.instanceTo( ph )

alight = AmbientLight('alight')
alight.setColor(Vec4(1,1,1,1))
alnp = render.attachNewNode(alight)
render.setLight(alnp)

Minfilters didn’t really seem to have an effect.

You are hitting your cards batch count. You need to read up on GPU performance and how it works. You cant really render more then 300 batches on screen.

That’s what I wanted to hear (like you care what I wanted). There was a nice link posted on Ogre Forums about it.
http://www.nvidia.de/docs/IO/8230/BatchBatchBatch.pdf
I need these 10,000 cards all in the same batch, which really gets down to the nitty of the engine. Do discontiguous figures need different batches? Could I render the scene to one monstrosity of a texture or something? How would I do it in OpenGL directly, or would the same thing necessarily happen?

:smiley:

thats why i use c++ , there you have the possibility to render a texture as geometry without creating a card. and there you can render around 1800 visible ones with a framerate around 25 fps :slight_smile: (at my computer 2 cores with 2.2 ghz and a ati 4800 mobile) but in 2d. i will test if this works also in 3d. i dont think so.

All you need to do is combine all of your textures cards into a single batch, RigidBodyCombiner can do that for you.

Here’s the change:

rbc= RigidBodyCombiner( 'rbc' )
rbcnp = NodePath(rbc)
rbcnp.reparentTo(render)

placeholders= [ rbcnp.attachNewNode("Tex-Placeholder %i"%i) for i in range( 10000 ) ]
for i,ph in enumerate(placeholders):
	ph.setPos(i%100, 0, i/100)
	square0path.instanceTo( ph )
rbc.collect( )

But later, when I call

	placeholders[ 0 ].setColor( 1, .776, 0 )

no change happens.

When I tried drwr’s earlier suggestion of ‘flattenStrong’:

render.flattenStrong( )

Every tile changed when I tried to change one.

oh wow, my workstation(onboard graphics :smiley: ) seems to be double speed :slight_smile: there i can display without any troubles 1500 texture cards, which are fully interacting :slight_smile: at my laptop, was 750 nearly the maximum. :frowning: crap laptop, switching cpu multiplicator which i cant fix and it gets very often overheated…

at the moment im uploading this demo, i reduced there the count to 1000, for users on laptops.

here the link -> https://discourse.panda3d.org/viewtopic.php?p=54577#54577

not bad, at 5000 fully interactive particle particles i get 7 fps, im sure if im going to do my logic on the gpu too, i can reach 20000 particles with a framerate around 20.

When you use CardMaker to create 10,000 objects, you end up with 10,000 different copies of the same object being sent to the graphics card in 10,000 batches (even though they’re instanced).

When you call flattenStrong(), you combine all of the geometry into a single object, and they get sent as a single batch. This gives you optimal rendering speed, but it makes it difficult or impossible to control the tiles individually thereafter.

When you use the RigidBodyCombiner, the objects remain independent, but through some trickery inside Panda get sent to the graphics card as a single batch. This allows you to retain independent control over the pieces, but there is some CPU cost to do this trickery, so it’s not quite as optimal as flattenStrong(). The setColor() operation should work, but (a) you should avoid using instances, use copyTo() instead, and (b) you might need to apply an override, e.g. piece.setColor(r, g, b, a, 1).

A third option is to use hardware instancing, if your graphics card supports it. This is only a little more complicated to set up, and is described in rdb’s recent blog entry, and in a few recent forum posts.

David

just to have a working code snippset. i used the RBC for the very same thing. thought only for some 100 nodes. but i also individually change color and position.
from my code…

rigidNode = RigidBodyCombiner("LevelNode")
levelNode = NodePath(rigidNode)
inputData = inputData.replace("\n","").strip().replace(" ","").lstrip("<").rstrip(">").split("><")
        
for tileData in inputData:
    tile = self.loadTile(tileData)
    if tile != None:
        tile.reparentTo(levelNode)
        tile.setPos( self.getPosFromTile(tile) )
        tile.setZ(tile,0.00000001) #workaround for rigid body combiner so it does not assume the (0,0) tile as static
    else:
        print "ERROR, could not load tile with data: ",tileData
rigidNode.collect()
inode = rigidNode.getInternalScene().node() #workaround for a boundingvolume issue with rigidbodycombiner
inode.setBounds(OmniBoundingVolume())  #still workaround
inode.setFinal(True) #still workaround

if you have 10000 tiles you might want to create groups of 100 or 1000 tiles each. if you dont change colors each and every frame for all groups that should work out quite well.

drwr and ThomasEgi,

Bad news only I’m afraid. ‘copyTo’ had no effect. I don’t have geometry instancing (or a geometry shader), according to the Ozone3d GPU Caps Viewer. A setColor override had no effect. The rigid collector workarounds didn’t work either.

Here’s the change:

cm = CardMaker('card')
card = cm.generate()
square0path= NodePath( card )
square0path.setTexture(testTexture)
square0path.setPos( 0, 0, 0 )

rbc= RigidBodyCombiner( 'rbc' )
rbcnp = NodePath(rbc)
rbcnp.reparentTo(render)

placeholders= [ rbcnp.attachNewNode("Tex-Placeholder %i"%i) for i in range( 10000 ) ]
for i,ph in enumerate(placeholders):
	ph.setPos(i%100, 0, i/100)
	ph.setZ( ph, .0000001 )
	square0path.copyTo( ph )
rbc.collect( )

inode = rbc.getInternalScene().node() #workaround for a boundingvolume issue with rigidbodycombiner 
inode.setBounds(OmniBoundingVolume())  #still workaround 
inode.setFinal(True) #still workaround

and later:

		if self.col== 0:
			placeholders[ 0 ].setColor( 1, .776, 0, 1, 1 )
		else:
			placeholders[ 0 ].setColor( 0, .620, 1, 1, 1 )

Ah, you’re right, my apologies. The RigidBodyCombiner isn’t meant to handle live color changes. In order to support this, you’d have to call rbc.collect() after your setColor() call, to manifest any color changes on the geometry, which forces the geometry to be re-processed and can be quite an expensive call.

So, probably your best bet is something like ThomasEgi’s suggestion of a two-level RigidBodyCombiner (on which you can call collect() with much less penalty), or to go all the way to hardware instancing.

Or, you can go to the flattenStrong() model, and use direct writes into the vertex table to modify the color. It all depends on what your precise needs are.

David

Tell me more about direct writes.

Using the low-level GeomVertexWriter object, as described in the Panda3D manual under “Advanced operations with Panda3D’s internal structures”, you can directly manipulate any vertex values you like, to (for example) change the color of any vertex from red to blue.

The only trouble with using this technique to animate a single piece of a big mesh, though, is in finding the particular vertex that you want to manipulate. One easy way to solve that problem is to construct the mesh entirely yourself, rather than using the CardMaker and flattenStrong–just create a GeomVertexData and fill it up using GeomVertexWriters, as described in that section of the manual. Then you’ll have complete control over the placement of each vertex in the table, and you can easily modify the vertices you need to change the color of the square or squares you want to change.

This is a clumsy way to perform certain kinds of animations, but it’s ideal for certain other kinds (such as direct color writes to a grid). So it might be exactly what you’re looking for.

David

The API Reference says, “it is important not to keep a GeomVertexWriter object around over a long period of time”. I note it doesn’t have a setitem method. How am I supposed to find the right vertex? Should I be using ‘copyTo’, or adding a separate primitive for each pair of triangles?

It has a setRow() method. You remember which index number is associated with each vertex; if you are adding vertices in a grid, this is easy to do. Then you call gvw.setRow() to the appropriate index number.

Ideally, you will only have one GeomPrimitive for your entire mesh. The GeomPrimitive represents a single batch operation.

David

No dice. The textures are appearing fine, but no color change.

		color = GeomVertexWriter( vdata, 'color')
		# ...
		color.setRow( 0 )
		if self.col== 0:
			color.setData4f( 1, .776, 0, 1 )
		else:
			color.setData4f( 0, .620, 1, 1 )

That should do the trick, so there must be some other problem. Are you sure that vdata is the appropriate GeomVertexData object? Are you sure that the vertex at position 0 is actually used in your mesh? Are you sure that your vdata actually has a color column, and that you are respecting it? (This may require nodePath.setColorOff(1) on NodePath containing your grid. “setColorOff” nonintuitively means to respect the vertex color, rather than a color applied from the scene graph.)

David

Correction. This line had to be included before I called ‘setRow’:

		color = GeomVertexWriter( vdata, 'color')

The manual states, “… you should store just the GeomVertexData pointer (along with the current vertex index number if you require this), and construct a temporary GeomVertexReader/Writer each time you need to access it.”

Right, though the manual maybe overstates the case a bit: you may keep the GeomVertexWriter for duration of a processing loop, for instance, so that you don’t have to create a new one for each vertex. You just shouldn’t keep one for more than a frame.

Are you still having troubles with the color not appearing? If so, have you investigated my thoughts?

David