Game Development Community

ShapeBase::prepRenderImage renders things twice?

by Eric Hartman · in Torque Game Engine · 05/02/2005 (11:44 am) · 7 replies

In Shapebase::prepRenderImage() we have some code that looks like this
if (mCloakLevel == 0.0f && mShapeInstance->hasSolid() && mFadeVal == 1.0f)
{
	SceneRenderImage* image = new SceneRenderImage;
	image->obj = this;
	image->isTranslucent = false;
	image->textureSortKey = mSkinHash ^ (U32)(dsize_t)(mDataBlock);
	state->insertRenderImage(image);
}

if ((mCloakLevel != 0.0f || mFadeVal != 1.0f || mShapeInstance->hasTranslucency()) ||
  (mMount.object == NULL && mGenerateShadow == true))
{
	SceneRenderImage* image = new SceneRenderImage;
	image->obj = this;
	image->isTranslucent = true;
	image->sortType = SceneRenderImage::Point;
	image->textureSortKey = mSkinHash ^ (U32)(dsize_t)(mDataBlock);
	state->setImageRefPoint(this, image);
	state->insertRenderImage(image);
}

Now it seems to me that if we have an object that isn't faded or cloaked and has a shadow or is translucent, this code will cause the item to be rendered twice.
Maybe I've done something wrong in my game, but I changed Shapebase::prepRenderImage() and the performance nearly doubled in character-heavy scenes.

Continued in reply....

#1
05/02/2005 (11:44 am)
Here is what my new Shapebase::prepRenderImage() looks like (significant changes are in bold):
//Badspot: redid this function to ensure everything only gets rendered once
bool ShapeBase::prepRenderImage(SceneState* state, const U32 stateKey,
                                const U32 startZone, const bool modifyBaseState)
{
   AssertFatal(modifyBaseState == false, "Error, should never be called with this parameter set");
   AssertFatal(startZone == 0xFFFFFFFF, "Error, startZone should indicate -1");

   if (isLastState(state, stateKey))
      return false;
   setLastState(state, stateKey);

   if( ( getDamageState() == Destroyed ) && ( !mDataBlock->renderWhenDestroyed ) )
      return false;

   // Select detail levels on mounted items
   // but... always draw the control object's mounted images
   // in high detail (I can't believe I'm commenting this hack :)
   F32 saveError = TSShapeInstance::smScreenError;
   GameConnection *con = GameConnection::getServerConnection();
   bool fogExemption = false;
   ShapeBase *co = NULL;
   if(con && ( (co = con->getControlObject()) != NULL) )
   {
      if(co == this || co->getObjectMount() == this)
      {
         TSShapeInstance::smScreenError = 0.001;
         fogExemption = true;
      }
   }

   if (state->isObjectRendered(this))
   {
      mLastRenderFrame = sLastRenderFrame;
      // get shape detail and fog information...we might not even need to be drawn
      Point3F cameraOffset;
      getRenderTransform().getColumn(3,&cameraOffset);
      cameraOffset -= state->getCameraPosition();
      F32 dist = cameraOffset.len();
      if (dist < 0.01)
         dist = 0.01;
      F32 fogAmount = state->getHazeAndFog(dist,cameraOffset.z);
      F32 invScale = (1.0f/getMax(getMax(mObjScale.x,mObjScale.y),mObjScale.z));
      if (mShapeInstance)
         DetailManager::selectPotentialDetails(mShapeInstance,dist,invScale);

      if (mShapeInstance)
         mShapeInstance->animate();

      if ((fogAmount>0.99f && fogExemption == false) ||
          (mShapeInstance && mShapeInstance->getCurrentDetail()<0) ||
          (!mShapeInstance && !gShowBoundingBox)) {
         // no, don't draw anything
         return false;
      }


      for (U32 i = 0; i < MaxMountedImages; i++)
      {
         MountedImage& image = mMountedImageList[i];
         if (image.dataBlock && image.shapeInstance)
         {
            DetailManager::selectPotentialDetails(image.shapeInstance,dist,invScale);
[b]
			ShapeImageRenderImage* rimage = new ShapeImageRenderImage;
			rimage->obj = this;
			rimage->mSBase = this;
            rimage->mIndex = i;

			//are we transparent in some way?
			if (mFadeVal != 1.0f || mShapeInstance->hasTranslucency())
			{
				rimage->isTranslucent = true;
				rimage->sortType = SceneRenderImage::Point; 
				state->setImageRefPoint(this, rimage);
			}
			else
			{
				rimage->isTranslucent = false;
				rimage->sortType = SceneRenderImage::Normal; 
				state->setImageRefPoint(this, rimage);
			}

			rimage->textureSortKey = mSkinHash ^ (U32)(dsize_t)(mDataBlock);
			state->insertRenderImage(rimage);	[/b]	
         }
      }
      TSShapeInstance::smScreenError = saveError;
		[b]
         SceneRenderImage* image = new SceneRenderImage;
         image->obj = this;
		 	
		 //are we transparent in some way?
		 if (mFadeVal != 1.0f || mShapeInstance->hasTranslucency())
		 {
			image->isTranslucent = true;
			image->sortType = SceneRenderImage::Point; 
			state->setImageRefPoint(this, image);
		 }
		 else
		 {
			image->isTranslucent = false;
			image->sortType = SceneRenderImage::Normal; 
			state->setImageRefPoint(this, image);
		 }

		 image->textureSortKey = mSkinHash ^ (U32)(dsize_t)(mDataBlock);
         state->insertRenderImage(image);
[/b]
	  calcClassRenderData();
   }

   return false;
}
//<-badspot

Continued in reply...
#2
05/02/2005 (11:44 am)
Some cleanup is needed to get shadows working (changes are in bold):

In Shapebase.cc
void ShapeBase::renderImage(SceneState* state, SceneRenderImage* image)
{

...

   // Shadow...
   if (mShapeInstance && mCloakLevel == 0.0 &&
       mMount.object == NULL && mGenerateShadow == true [b]/*&&
       image->isTranslucent == true*/[/b])
   { 
       renderShadow(dist,fogAmount);
   }
   PROFILE_END();
}

And in Player.cc
void Player::renderImage(SceneState* state, SceneRenderImage* image)
{

...

   if (mShapeInstance && renderPlayer && mCloakLevel == 0.0 &&
       mMount.object == NULL [b]/*&& image->isTranslucent == true*/[/b]) 
   {
       renderShadow(dist,fogAmount);
   }

   TSMesh::setOverrideFade( 1.0 );
   PROFILE_END();

...

}
#3
05/02/2005 (12:05 pm)
Hey Eric,

I think you have a misunderstanding as to how SceneRenderImages work - since translucent and non translucent "images" are rendered seperately, it's necessary to register TWO images in the case of a translucent object, one to render the opaque bits and another to render the translucent bits afterwards. The original code is actually correct, and does not result in any extra drawing.
#4
05/02/2005 (1:06 pm)
..oops. I had commented out some rather important code in shapebase::renderObject() and that causedeverything to be rendered on the translucent image instead of just the translucent stuff.
#5
05/10/2005 (10:03 am)
@ Ben (or anyone who knows the rendering code well)

I thought of this thread while I was looking at a profile dump and am slightly puzzled - I dont think I am understanding the rendering code correctly (after spending quite a while trying to trace it throgh the code) so would you be kind enough to (briefly) explain why the player (and mounted object and shadow) seem to be rendering twice per frame and does it result in more drawing??

If I am reading this right (and please correct me if I am not), 67505 frames were rendered with 67505 calls to InteriorRenderObject (1 interior in this) but PlayerRenderPrimary, PlayerRenderShadow and PlayerRenderMounted are all called 135758 times??

I get the same result (twice as many calls to player than frame) in the stock demo.

There is only one player in the mission (albeit fairly hefty on the triangle count) and it only has one level of detail.

Ordered by stack trace total time -
% Time  % NSTime  Invoke #  Name
  0.315 -99.685        0 ROOT
100.000   7.669   319264   MainLoop
 92.214   0.124    67505     ProcessTimeEvent
 85.635   0.056    67505       RenderFrame
 80.011   4.408    67505         CanvasRenderControls
 73.968   0.984    67505           GameRenderWorld
 72.984   0.224    67505             SceneGraphRender
 70.820   0.256    67505               TraverseScene
 70.490   3.266   764627                 SceneStateRenderImage
 38.284   2.259   271516                   ShapeBaseRenderObject
 18.062   0.204   135758                     PlayerRenderPrimary
 17.858  17.565   135758                       TSShapeInstanceRender
  0.286   0.286   135758                         TSShapeInstanceMaterials
  0.007   0.007   135758                         TSShapeInstanceRenderBillboards
 14.165  14.165   135758                     PlayerRenderShadow
  0.000   0.000       87                       MemoryFree
  0.000   0.000       96                       MemoryAlloc
  0.000   0.000       14                       MemoryRealloc
  0.000   0.000        8                         MemoryAlloc
  0.000   0.000        8                         MemoryFree
  3.753   0.261   135758                     PlayerRenderMounted
  3.492   3.137   135758                       TSShapeInstanceRender
  0.350   0.350   135758                         TSShapeInstanceMaterials
  0.004   0.004   135758                         TSShapeInstanceRenderBillboards
  0.045   0.027   271516                     GetLightingAmbientColor
  0.017   0.017     7109                       ContainerCastRay
 25.471   0.171    67505                   InteriorRenderObject
 23.616   0.126    67505                     IRO_RenderSolids
 23.490   0.019    67505                       IRO_RenderARB_FC
.
.
.
snip
#6
05/10/2005 (1:21 pm)
That is perfectly right. Each render call is made once for opaque stuff, and again for translucent stuff. This is necessary so that everything gets sorted out properly.