Dewey Anderson (dewey++at++evt.com)
Fri, 6 Jun 1997 18:54:30 -0600
I'm thinking I've got a problem with Texture memory not being freed when I
pfDelete objects.
The scene is very simple: Different textures mapped onto rectangles attached to
the scene through their own pfSCS and pfDCS:
scene
/ | \
/ | \
DCS DCS DCS
| | |
SCS SCS SCS
| | |
Geode Geode Geode
There is a function, get_texture_node(), that takes an image in and creates the
Geode and SCS. It creates fresh pfGeoStates for each. Nobody shares anything.
Each Geode consists of a rectangle with a 32-bit RGBA image texture mapped
onto it. The textures are mip-mapped. The texture dimensions are powers of
two and texture coordinates are used to just map a portion of that onto the
rectangle.
I have an example with 4 images and hence 4 such branches in the tree. I
animate motion of the 4 rectangles with their DCSs. The motion is just a
smooth vertical slide. The rectangles are nearly square and close to 200
pixels on a side. This means we create textures that are 256x256.
Our application is set up so that when you want to play that motion, it will
create the branches & attach them to the scene, animate it and then pfDelete
each branch. If you ask it to repeat the animation, it will do all of that all
over again.
When I play this animation, it will play 3 times smoothly. On the 4th play the
motion fails to make real time. Using DrawStats makes it look like the draw
time has gotten larger than 1 frame.
This makes it sound as if the texture memory has filled up and is now having to
swap data to/from regular memory. 12 256x256 mipmapped images should just take
up the full 4M texture memory. (Each 256 x 256 x 4 byte image is 1/4 Meg + an
additional 1/3 of that for mipmapping.)
If this is what's happening, the question is:"What am I NOT doing that's
keeping the texture memory from being freed up?"
An interesting side bar before I list a bunch of code to show you what I AM
doing: If I disable the code that makes my textures have dimensions that are
powers of 2, e.g. 190 x 200, the PROBLEM GOES AWAY. I can play these over and
over again without any problem. Unfortunately, the textures appear soft in
that case. I get the impression that what's happening is that Performer is
handed a non-power-of-2 texture, says "I can't pass that on to OpenGL" and so
creates a legit power-of-2 texture and scales the original image to fit that.
This scaling softens the image. This legit texture image is used in the
texture so we can run things. THEN, when the Geode gets pfDeleted, Performer
is very good about cleaning up after itself - perhaps even better than if it
had NOT created the extra image.
Now to the code. Here's the bulk of how I create things:
pfNode *
get_texture_node(RasterImg* rimg, float x, float y,
int rect_width, int rect_height )
{
... a slight simplification of getting w & h here
int w = rect_width;
int h = rect_height;
// define the rectangle to which the texure will be applied
// coordinates are just the width and height from above
pfVec3 *coords = (pfVec3 *)pfMalloc( 4*sizeof(pfVec3), pfGetSharedArena() );
coords[0].set( 0.0f, 0.0f, 0.0f );
coords[1].set( w , 0.0f, 0.0f );
coords[2].set( w , h , 0.0f );
coords[3].set( 0.0f, h , 0.0f );
// texture coords map texture to the corners of the rectangle
pfVec2 *tcoords = (pfVec2 *)pfMalloc( 4*sizeof(pfVec2), pfGetSharedArena() );
// tw & th are the fractions used to peg the useful part of the
// texture to the corners of the rectangle
float tw = (float)w / (float)rimg->Width();
float th = (float)h / (float)rimg->Height();
tcoords[0].set(0.0f, 0.0f);
tcoords[1].set( tw, 0.0f);
tcoords[2].set( tw, th);
tcoords[3].set(0.0f, th);
// normal is just +z
pfVec3 *normal = (pfVec3 *)pfMalloc( sizeof(pfVec3), pfGetSharedArena() );
normal->set(0.0f, 0.0f, 1.0f);
// NOTE: We do NOT set up the color of the rectangle here.
// These values are recomputed as part doing fade-in and fade-out effects.
// set up the geoset holding the rectangle
pfGeoSet *geoset = new pfGeoSet;
geoset->setAttr( PFGS_COORD3, PFGS_PER_VERTEX, coords, NULL );
geoset->setAttr( PFGS_NORMAL3, PFGS_PER_PRIM, normal, NULL );
geoset->setAttr( PFGS_TEXCOORD2, PFGS_PER_VERTEX, tcoords, NULL );
geoset->setPrimType(PFGS_QUADS );
geoset->setNumPrims( 1 );
// set up the geostate for the rectangle. Attach it to the geoset
pfGeoState *gstate = new pfGeoState;
gstate->setMode( PFSTATE_ENTEXTURE, 1 ); // enable texturing
gstate->setMode( PFSTATE_CULLFACE, PFCF_OFF ); // show both front & back
geoset->setGState( gstate );
// set up the texture for the geostate
pfTexture *tex = new pfTexture;
// define the texture image. params are: the texture, a pointer to
// the actual image data, the number of components per pixel,
// width, height and depth.
tex->setImage( (unsigned int *)rimg->ImgData(), 4,
rimg->Width(), rimg->Height(), 1 );
tex->setRepeat( PFTEX_WRAP_S, PFTEX_CLAMP ); // clamp edges so filtering
tex->setRepeat( PFTEX_WRAP_T, PFTEX_CLAMP ); // won't wrap around
tex->setFilter( PFTEX_MINFILTER, PFTEX_MIPMAP_TRILINEAR );
// to keep full color resolution in texture (32 bit texels)
tex->setFormat( PFTEX_INTERNAL_FORMAT, PFTEX_RGBA_8 );
gstate->setAttr( PFSTATE_TEXTURE, tex );
gstate->setMode( PFSTATE_TRANSPARENCY, PFTR_BLEND_ALPHA );
// Set alpha function to clip off alpha values less than 10.
// This is needed to make low alpha pixels actually NOT be drawn at all.
// THAT is necessary so that z-buffer isn't filled in by clear pixels.
gstate->setMode(PFSTATE_ALPHAFUNC, PFAF_GREATER);
gstate->setVal(PFSTATE_ALPHAREF, (10.0f/255.0f));
// set up the texture environment. Attach it to the geostate
pfTexEnv *tenv = new pfTexEnv;
tenv->setMode( PFTE_MODULATE );
gstate->setAttr( PFSTATE_TEXENV, tenv );
// make an SCS for placing text relative to rectangle
//To set up the SCS we must first set up a pfCoord
// containing the rotation and translation. We use this
// to define a matrix which we then use in defining the SCS.
pfCoord rotrans;
rotrans.hpr.set( 0.0f, 0.0f, 0.0f);
rotrans.xyz.set( x, y, 0.0f);
pfMatrix mat;
mat.makeCoord( &rotrans );
pfSCS *scs = new pfSCS(mat);
// make the actual geode and attach the geoset to it
pfGeode *text = new pfGeode;
text->addGSet( geoset );
// attach this geode to the dcs
scs->addChild( text );
return (pfNode *)scs;
} // get_texture_node
Now, when we get done playing the animation, we prune the scene tree by
removing each branch. Because of the nature of the data structures with the
DCS, I clip it off at the SCS (which was what was returned from the function
above).
void
remove_perf_effect_branch( Effect *effect )
{
if ( effect != NULL ) // only if there IS an effect
{
... snip.... details dealing with DCS
// Get the actual pfNode attached to the bottom of the branch
pfNode *leaf_node = dcs->getChild(0);
// remove that texture pfNode from the branch
dcs->removeChild( leaf_node );
// remove the branch from the rest of the tree
scene->removeChild( dcs );
// delete the texture pfNode
pfDelete( leaf_node );
}
}
It is my understanding that since the "leaf_node" (really the SCS and Geode)
was all created using pfMalloc, where appropriate, AND that since nothing is
being shared, so ref counts should all be 1, this pfDelete should be all I need
to delete it. THIS INCLUDES FREEING THE TEXTURE MEMORY. Am I wrong about
this?
Suspecting that I might BE wrong about this, I added some code just before the
pfDelete( leaf_node ) that I thought might explicitly free things up:
// free all texture images in this node
// get list of textures in this node
pfList *tex_list = pfuMakeTexList( leaf_node );
int n = tex_list->getNum(); // number of textures in list
// for each tex in list, get ptr to it and free it
for ( int i=0; i<=n ; i++ )
{
pfTexture *tex = (pfTexture *)tex_list->get(i);
if ( tex != 0 )
tex->freeImage();
}
As expected, n is always 1 and the for loop only executes once. But this had
no effect. We still miss real time after 3 plays.
Just in case I was having a problem with reference counts, I went a step
further and forced the ref count down. Just before the freeImage, I added:
tex->unref();
just before the freeImage. Still no diff.
Now the man on freeImage has me wondering one thing. It says
pfTexture::freeImage frees the texture image memory associated with the
pfTexture after the next pfTexture::apply is called if the image's
reference count is 0.
I don't ever explicitly call pfTexture::apply. I assume that's handled when
Performer goes to draw the scene. (Is this some step I need to add somewhere?)
Before I begin the play action, I do preload all the textures with:
pfuDownloadTexList( pfuMakeSceneTexList(scene), PFUTEX_APPLY );
I assume this will load the new textures into HW texture memory IN PLACE of the
old textures if they've been freed. (The fact that the textures LOOK alike is
irrelevant. The code has no way of knowing that.)
So.... Any thoughts? Is there some aspect of my use of textures that's not
"normal". Am I leaving out some critical step? Any ideas why NOT using a
power of 2 texture would avoid this problem? (It's not just a matter of the
smaller texture using less texture memory. That would simply DELAY this
problem until more plays had occurred. I've played the animation over 20 times
without stuttering. The problem appears to be GONE.)
If you've stuck with me this far, thanks. I'm open to any suggestions or
thoughts.
Dewey Anderson
dewey++at++evt.com
Evolving Video Technologies
=======================================================================
List Archives, FAQ, FTP: http://www.sgi.com/Technology/Performer/
Submissions: info-performer++at++sgi.com
Admin. requests: info-performer-request++at++sgi.com
SGI DevForum 97 info: http://www.sgi.com/Forum97/
This archive was generated by hypermail 2.0b2 on Mon Aug 10 1998 - 17:55:24 PDT