Game Development Community

Rendering/Drawing to a Texture

by Demolishun · in Torque 3D Professional · 01/14/2013 (8:42 am) · 7 replies

This has got me stumped. I cannot figure out the right combination to make this work. I found this article at MSDN: msdn.microsoft.com/en-us/library/windows/desktop/bb147263%28v=vs.85%29.aspx#Usin.... This helped me understand what needs to happen regarding dynamic textures. However, I am struggling to translate this to code in T3D. I have been using the resource: www.garagegames.com/community/forums/viewthread/109335/5. I am failing to extract what I actually need to make it work.

Here is my attempt.
.H
class AudioTextureMap;

// Object that can display sampled data on a texture
class AudioTextureObject : public SceneObject
{
private:
   typedef SceneObject Parent;

   enum MaskBits 
   {
      TransformMask = Parent::NextFreeMask << 0,
      UpdateMask    = Parent::NextFreeMask << 1,
      NextFreeMask  = Parent::NextFreeMask << 2
   };

   // buffers and pointers to texture
   //    texture writing is double buffered
   GBitmap* mBitmap;
   GFXTexHandle   mTextureBuffer1; 
   GFXTexHandle   mTextureBuffer2;
   GFXTexHandle   mWarningTexture;
   GFXTextureObject* mTexture; // 

   // The size in pixels of the backing texture
   S32 mTexSize;

   // texture reference name for use in materials
   //    eg: diffuseMap[0] = "#MyWebTexture";
   String mTextureName;  

   // texture target reg object
   NamedTexTarget* mTextureTarget; 

   // enable rendering texture onto an object in the scene
   //    this will default to false to prevent rendering when used as a texture source for materials
   //    the texture will be mapped to a simple quad
   bool mEnableRender;  

   // The name of the Material we will use for rendering
   //String            mMaterialName;
   // The actual Material instance
   //aseMatInstance*  mMaterialInst;

   // render variables
   //typedef GFXVertexPCN VertexType;
   typedef GFXVertexPNT VertexType;

   // The handles for our StateBlocks
   GFXStateBlockRef mNormalSB;
   GFXStateBlockRef mReflectSB;

   // The GFX vertex and primitive buffers
   GFXVertexBufferHandle< VertexType > mVertexBuffer;
   //GFXPrimitiveBufferHandle            mPrimitiveBuffer;

public:
   AudioTextureObject();
   virtual ~AudioTextureObject();
   
   // get the current texture handle
   GFXTextureObject* getTexture(){
      return mTexture;
   };

   // Allows the object to update its editable settings
   // from the server object to the client
   virtual void inspectPostApply();

   // Set up any fields that we want to be editable (like position or render enable)
   static void initPersistFields();

   // Handle when we are added to the scene and removed from the scene
   bool onAdd();
   void onRemove();

   // Override this so that we can dirty the network flag when it is called
   void setTransform( const MatrixF &mat );

   // networking
   U32 packUpdate( NetConnection *conn, U32 mask, BitStream *stream );      
   void unpackUpdate( NetConnection *conn, BitStream *stream );

   // Create the geometry for rendering
   //    would used for debug only
   inline void createGeometry();

   void updateMaterial();

   // This is the function that allows this object to submit itself for rendering
   void prepRenderImage( SceneRenderState *state );

   // render the bitmap
   void render( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat );   

   DECLARE_CONOBJECT(AudioTextureObject);
};

// displaying audio data
class AudioTextureMap : public NamedTexTarget
{
public:
   AudioTextureMap(AudioTextureObject* tobj) {mAudioTextureObject = tobj;}
   ~AudioTextureMap() {  }

   // MatTextureTarget
   virtual GFXTextureObject* getTexture( U32 mrtIndex ) const { return mAudioTextureObject->getTexture(); }
   virtual const RectI& getTargetViewport() const { return RectI::One; }
   virtual void setupSamplerState( GFXSamplerStateDesc *desc ) const { TORQUE_UNUSED( desc ); }
   virtual ConditionerFeature* getTargetConditioner() const { return NULL; }

private:
   AudioTextureObject* mAudioTextureObject;
};
.CPP
IMPLEMENT_CO_NETOBJECT_V1(AudioTextureObject);

AudioTextureObject::AudioTextureObject(){
   // Flag this object so that it will always
   // be sent across the network to clients
   mNetFlags.set( Ghostable | ScopeAlways );

   mTypeMask |= StaticObjectType | StaticShapeObjectType;

   mTextureTarget = NULL;   
   mTexSize = 512;

   mBitmap = NULL;  
   mTexture = NULL; 
}
AudioTextureObject::~AudioTextureObject(){   
}

void AudioTextureObject::initPersistFields(){
   addGroup( "Rendering" );
   /*addField( "material",      TypeMaterialName, Offset( mMaterialName, AudioTextureObject ),
      "" );*/
   addField( "texture",      TypeRealString, Offset( mTextureName, AudioTextureObject ),
      "The target render texture." );
   endGroup( "Rendering" );

   // SceneObject already handles exposing the transform
   Parent::initPersistFields();
}

void AudioTextureObject::inspectPostApply()
{
   Parent::inspectPostApply();

   // Flag the network mask to send the updates
   // to the client object
   setMaskBits( UpdateMask );
}

bool AudioTextureObject::onAdd(){
   if ( !Parent::onAdd() )
      return false;

   // setup material
   if( isClientObject() ){
      updateMaterial();
   }

   // Set up a 1x1x1 bounding box
   mObjBox.set( Point3F( -0.5f, -0.5f, -0.5f ),
                Point3F(  0.5f,  0.5f,  0.5f ) );

   resetWorldBox();

   // Add this object to the scene
   addToScene();

   return true;
}
void AudioTextureObject::onRemove(){
   // Remove this object from the scene
   removeFromScene();

   if(mWarningTexture)
      SAFE_DELETE(mWarningTexture);
   if(mTextureTarget){      
      SAFE_DELETE(mTextureTarget);      
   }
   if(mBitmap){
      SAFE_DELETE(mBitmap);
   }  

   Parent::onRemove();
}

void AudioTextureObject::setTransform( const MatrixF &mat ){
   // Let SceneObject handle all of the matrix manipulation
   Parent::setTransform( mat );

   // Dirty our network mask so that the new transform gets
   // transmitted to the client object
   setMaskBits( TransformMask );
}

U32 AudioTextureObject::packUpdate( NetConnection *conn, U32 mask, BitStream *stream ){
   // Allow the Parent to get a crack at writing its info
   U32 retMask = Parent::packUpdate( conn, mask, stream );

   // Write our transform information
   if ( stream->writeFlag( mask & TransformMask ) )
   {
      mathWrite(*stream, getTransform());
      mathWrite(*stream, getScale());
   }

   // Write out any of the updated editable properties
   if ( stream->writeFlag( mask & UpdateMask ) )
      stream->write( mTextureName );

   return retMask;
}

void AudioTextureObject::unpackUpdate( NetConnection *conn, BitStream *stream ){
   // Let the Parent read any info it sent
   Parent::unpackUpdate(conn, stream);

   if ( stream->readFlag() )  // TransformMask
   {
      mathRead(*stream, &mObjToWorld);
      mathRead(*stream, &mObjScale);

      setTransform( mObjToWorld );
   }

   if ( stream->readFlag() )  // UpdateMask
   {
      stream->read( &mTextureName );

      if ( isProperlyAdded() )       
         updateMaterial();      
   }
}

// search for TSLastDetail::render to find info on possible engine support of billboards
inline void AudioTextureObject::createGeometry(){

   U32 numPoints = 12;
   
   static const Point3F cubePoints[4] = 
   {
      Point3F( -1.0f, 0.0f, -1.0f), Point3F( -1.0f, 0.0f,  1.0f),
      Point3F( 1.0f,  0.0f, 1.0f), Point3F( 1.0f,  0.0f,  -1.0f)      
   };

   static const Point3F cubeNormals[2] = 
   {
      Point3F( 0.0f, 1.0f, 0.0f), Point3F(0.0f, -1.0f, 0.0f)
   };

   static const ColorI cubeColors[4] = 
   {
      ColorI( 128,   128,   128, 255 ),
      ColorI( 255,   128,   128, 255 ),     
      ColorI( 128,   255,   128, 255 ),
      ColorI( 128,   128,   255, 255 ),
   };

   static const Point2F cubeTexCoords[4] = 
   {
      Point2F( 0,  0), Point2F( 0, 1),
      Point2F( 1,  0), Point2F( 1, 1)
   };

   // for color
   /*
   static const U32 cubeFaces[12][3] = 
   {
      { 0, 0, 0 }, { 1, 0, 0 }, { 2, 0, 0 },
      { 2, 0, 1 }, { 3, 0, 1 }, { 0, 0, 1 },
      { 2, 1, 2 }, { 1, 1, 2 }, { 0, 1, 2 },
      { 2, 1, 3 }, { 0, 1, 3 }, { 3, 1, 3 },
   };
   */  
   // for texture
   static const U32 cubeFaces[12][3] = 
   {
      { 0, 0, 1 }, { 1, 0, 0 }, { 2, 0, 2 },
      { 2, 0, 2 }, { 3, 0, 3 }, { 0, 0, 1 },
      { 3, 1, 1 }, { 2, 1, 0 }, { 1, 1, 2 },
      { 1, 1, 2 }, { 0, 1, 3 }, { 3, 1, 1 },
   }; 

   // Fill the vertex buffer
   VertexType *pVert = NULL;
   
   //mVertexBuffer.set( GFX, numPoints, GFXBufferTypeStatic );
   mVertexBuffer.set( GFX, numPoints, GFXBufferTypeDynamic );
   pVert = mVertexBuffer.lock();

   Point3F halfSize = getObjBox().getExtents() * 0.5f;
   
   for (U32 i = 0; i < numPoints; i++)
   {
      const U32& vdx = cubeFaces[i][0];
      const U32& ndx = cubeFaces[i][1];
      //const U32& cdx = cubeFaces[i][2];
      const U32& tdx = cubeFaces[i][2];

      pVert[i].point  = cubePoints[vdx] * halfSize;
      pVert[i].normal = cubeNormals[ndx];
      //pVert[i].color  = cubeColors[cdx];
      pVert[i].texCoord  = cubeTexCoords[tdx];
   }

   mVertexBuffer.unlock();

   // Set up our normal and reflection StateBlocks   
   GFXStateBlockDesc desc;
   /*
   desc.setCullMode( GFXCullCCW );
   desc.setBlend( true );
   desc.setZReadWrite( false, false );
   desc.samplersDefined = true;
   desc.samplers[0].addressModeU = GFXAddressWrap;
   desc.samplers[0].addressModeV = GFXAddressWrap;
   desc.samplers[0].addressModeW = GFXAddressWrap;
   desc.samplers[0].magFilter = GFXTextureFilterLinear;
   desc.samplers[0].minFilter = GFXTextureFilterLinear;
   desc.samplers[0].mipFilter = GFXTextureFilterLinear;
   desc.samplers[0].textureColorOp = GFXTOPModulate;
   //desc.samplers[0].textureColorOp = GFXTOPAdd;
   */

   //desc.samplers[0].textureColorOp = GFXTOPModulate;  

   // DrawBitmapStretchSR   
   desc.setCullMode(GFXCullCCW);
   desc.setZReadWrite(false);
   desc.setBlend(true, GFXBlendSrcAlpha, GFXBlendInvSrcAlpha);
   desc.samplersDefined = true;

   // Linear: Create wrap SB
   desc.samplers[0] = GFXSamplerStateDesc::getWrapLinear();
   //mBitmapStretchWrapLinearSB = mDevice->createStateBlock(bitmapStretchSR);

   // Linear: Create clamp SB
   desc.samplers[0] = GFXSamplerStateDesc::getClampLinear();
   //mBitmapStretchLinearSB = mDevice->createStateBlock(bitmapStretchSR);

   // Point:
   desc.samplers[0].minFilter = GFXTextureFilterPoint;
   desc.samplers[0].mipFilter = GFXTextureFilterPoint;
   desc.samplers[0].magFilter = GFXTextureFilterPoint;

   // Point: Create clamp SB, last created clamped so no work required here
   //mBitmapStretchSB = mDevice->createStateBlock(bitmapStretchSR);    

   // The normal StateBlock only needs a default StateBlock
   mNormalSB = GFX->createStateBlock( desc );

   // The reflection needs its culling reversed
   desc.cullDefined = true;
   desc.cullMode = GFXCullCW;
   mReflectSB = GFX->createStateBlock( desc );
}

void AudioTextureObject::updateMaterial()
{   
   //BaseMatInstance* tmpMat = MATMGR->createMatInstance( tmpMatName, getGFXVertexFormat< VertexType >() );   
   if(mWarningTexture.isNull()){
      BaseMatInstance* tmpMat = MATMGR->createWarningMatInstance();
      if(tmpMat){
         const char* tmpTexName = tmpMat->getMaterial()->getDataField(StringTable->insert("diffuseMap"),"0");
         if(tmpTexName){
            mWarningTexture.set(String(tmpTexName),&GFXDefaultStaticDiffuseProfile,"");                                              
            //Con::warnf("AudioTextureObject::updateMaterial - setting WarningMaterial texture: %s",tmpTexName);
         }  
    
         // get rid of temp material instance
         SAFE_DELETE( tmpMat );
      }
   } 

   if(mTextureName.isNotEmpty()){
      if(!mTextureTarget){
         // create texture target object with the pointer to this object
         mTextureTarget = new NamedTexTarget();
         if(!mTextureTarget)
            Con::errorf("AudioTextureObject::updateMaterial - could not allocate memory for new texture target.");
      }      
      // if we have a texture target and the name is not the same as what is already set on the target
      if(mTextureTarget && !mTextureName.equal( mTextureTarget->getName(), String::NoCase )){         
         // check to see if the name is already registered         
         if(!mTextureTarget->registerWithName(mTextureName)){
            Con::errorf("AudioTextureObject::updateMaterial - could not register texture target name (%s).  The target name may already be in use.",mTextureName.c_str());
            SAFE_DELETE(mTextureTarget);
         }else{
            // allocate space for texture
            //mTextureBuffer.set(mTexSize, mTexSize, GFXFormatR8G8B8X8, &GFXDefaultStaticDiffuseProfile, "", 0);                        
            //mTextureBuffer.set(mTexSize, mTexSize, GFXFormatR8G8B8X8, &GFXDefaultPersistentProfile, "", 0); 
            if(mBitmap){
               SAFE_DELETE(mBitmap);
            }
            mBitmap = new GBitmap(mTexSize, mTexSize, false, GFXFormatR8G8B8X8); 
            mBitmap->fill(ColorI(0,0,255));          
            mTextureBuffer1.set(mBitmap, &GFXDefaultPersistentProfile, false, String(""));            
            mTextureBuffer2.set(mBitmap, &GFXDefaultPersistentProfile, false, String(""));

            // set the texture 
            mTextureTarget->setTexture(mTextureBuffer1.getPointer());
            Con::warnf("AudioTextureObject::updateMaterial - setting texture to texture target: %s",mTextureName.c_str());

            // 
            mTexture = mTextureBuffer1.getPointer();
         }         
      }
   }else{
      if(mTextureTarget)
         SAFE_DELETE(mTextureTarget);
   }       

   /*
   if( mMaterialName.isEmpty() )
      return;

   // If the material name matches then don't bother updating it.
   if ( mMaterialInst && mMaterialName.equal( mMaterialInst->getMaterial()->getName(), String::NoCase ) )
      return;

   SAFE_DELETE( mMaterialInst );

   mMaterialInst = MATMGR->createMatInstance( mMaterialName, getGFXVertexFormat< VertexType >() );
   if ( !mMaterialInst )
      Con::errorf( "AudioTextureObject::updateMaterial - no Material called '%s'", mMaterialName.c_str() );   

   mTextureName = mMaterialInst->getMaterial()->getDataField(StringTable->insert("diffuseMap"),"0");   
   //Con::warnf("AudioTextureObject::updateMaterial - Texture name '%s'",mTextureName.c_str());
   */
}

void AudioTextureObject::prepRenderImage( SceneRenderState *state ){
   //Con::warnf("AudioTextureObject::prepRenderImage - called");

   // Do a little prep work if needed
   //if ( mVertexBuffer.isNull() )
      createGeometry();

   // Perform drawing on texture here
   if(mTextureTarget){
      /*
      GFXTextureObject* tmpTexture = mTextureTarget->getTexture();
      tmpTexture->lock();
      GBitmap* tmpBitmap = tmpTexture->getBitmap();
      U32 w = tmpBitmap->getWidth();
      U32 h = tmpBitmap->getHeight();
      
      tmpBitmap->fill(ColorI(0,255,0));
      tmpBitmap->setColor(w/2, h/2, ColorI(255,128,128));      
      
      tmpTexture->unlock();
      */
      mTexture = mTextureTarget->getTexture();      
      mTexture->lock();
      mBitmap->fill(ColorI(0,255,0));
      mTexture->unlock();
      
      //mTexture->

      // swap textures
      /*
      if(mTextureTarget->getTexture() == mTextureBuffer1.getPointer()){
         mTextureTarget->setTexture(mTextureBuffer2.getPointer());  
      }else{
         mTextureTarget->setTexture(mTextureBuffer1.getPointer());
      }
      */
   }

   // only display textured object when in the editor
   //    if the render request is not submitted to the render pass then ::render is not called
   if(!gEditingMission)
      return; 

   // Allocate an ObjectRenderInst so that we can submit it to the RenderPassManager
   ObjectRenderInst *ri = state->getRenderPass()->allocInst<ObjectRenderInst>();

   // Now bind our rendering function so that it will get called
   ri->renderDelegate.bind( this, &AudioTextureObject::render );

   // Set our RenderInst as a standard object render
   ri->type = RenderPassManager::RIT_Object;

   // Set our sorting keys to a default value
   ri->defaultKey = 0;
   ri->defaultKey2 = 0;

   // Submit our RenderInst to the RenderPassManager
   state->getRenderPass()->addInst( ri );   
}

void AudioTextureObject::render( ObjectRenderInst *ri, SceneRenderState *state, BaseMatInstance *overrideMat ){
   //Con::warnf("AudioTextureObject::render - called");

   if ( overrideMat )
      return;

   if ( mVertexBuffer.isNull() )
      return;     

   PROFILE_SCOPE(AudioTextureObject_Render);

   // Set up a GFX debug event (this helps with debugging rendering events in external tools)
   GFXDEBUGEVENT_SCOPE( AudioTextureObject_Render, ColorI::RED );

   // GFXTransformSaver is a handy helper class that restores
   // the current GFX matrices to their original values when
   // it goes out of scope at the end of the function
   GFXTransformSaver saver;

   // Calculate our object to world transform matrix
   MatrixF objectToWorld = getRenderTransform();
   objectToWorld.scale( getScale() );

   // Apply our object transform
   GFX->multWorld( objectToWorld );

   // Deal with reflect pass otherwise
   // set the normal StateBlock
   if ( state->isReflectPass() )
      GFX->setStateBlock( mReflectSB );      
   else
      GFX->setStateBlock( mNormalSB );   

   // Set up the "generic" shaders
   // These handle rendering on GFX layers that don't support
   // fixed function. Otherwise they disable shaders.
   //GFX->setupGenericShaders( GFXDevice::GSModColorTexture );
   //GFX->setupGenericShaders( GFXDevice::GSTexture );

   // Set the vertex buffer
   GFX->setVertexBuffer( mVertexBuffer );

   // set texture 
   if (!mTextureTarget){      
      GFX->setTexture(0, mWarningTexture);      
   }else{
      GFX->setTexture(0, mTexture);
   }

   // define shader type
   GFX->setupGenericShaders( GFXDevice::GSModColorTexture );

   // Draw our triangles
   GFX->drawPrimitive( GFXTriangleList, 0, 4 );   
}

DefineEngineMethod( AudioTextureObject, postApply, void, (),,
   "A utility method for forcing a network update.n")
{
	object->inspectPostApply();
}
Note: I am not actually using the AudioTextureMap class so ignore. I am using the NamedTexTarget instead.

About the author

I love programming, I love programming things that go click, whirr, boom. For organized T3D Links visit: http://demolishun.com/?page_id=67


#1
01/14/2013 (11:37 am)
This tells me I am doing this wrong:
www.two-kings.de/tutorials/dxgraphics/dxgraphics16.html

I will explore this later today. I don't think I can update a bitmap and get what I want. So I will do the real RTT methods described in the new link. It also talks about point sprites. This sounds very interesting especially when rendering a lot of points to a texture. This might make what I am doing a lot more efficient.
#2
01/14/2013 (8:35 pm)
This is a feature I could make alot of use out of, many thanks to you if you figure out how to make it work.
#3
01/15/2013 (7:56 am)
I have successfully rendered to a texture:
// render to texture
   if(mTextureTarget){      
      mGFXTextureTarget = GFX->allocRenderToTextureTarget();        
      mGFXTextureTarget->attachTexture(GFXTextureTarget::RenderSlot::Color0,mTextureTarget->getTexture());      

      GFX->pushActiveRenderTarget();      
      GFX->setActiveRenderTarget(mGFXTextureTarget);

      GFX->clear(GFXClearTarget,ColorI(0,0,0),1.0f,0);
      GFX->setStateBlock( mNormalSB );
      GFX->setVertexBuffer( mVertexBuffer );
      GFX->setTexture(0, mWarningTexture);
      GFX->setupGenericShaders( GFXDevice::GSModColorTexture );
      GFX->drawPrimitive( GFXTriangleList, 0, 4 );

      mGFXTextureTarget->resolve();
            
      GFX->popActiveRenderTarget();
   }
Obviously there is a lot of detail missing, but it gives you an idea of how simple this is to do.

Take a look at imposterCapture.cpp. It does a render to texture. I will post more later.
#4
01/15/2013 (8:30 am)
Sorry Frank - I should have thought of imposters when you first posted this, but I'm a little slow. I knew they did this but for some reason never put the two together.... duh.
#5
01/15/2013 (8:38 am)
@Richard,
I needed to learn how the rendering pipeline worked a little better. When I started I had 0 (zero) knowledge about DirectX. Now I know I don't like DirectX, but hey I figured out how to mess with some things. I really like the abstraction layer T3D has. I mostly am not directly exposed to DirectX. This is a good thing.

I do need to try and understand how to render memory to texture as well. That is what the webctrl is doing. I sort of can do this, but it is not working right.

As for going forward I will gin up some simple render to texture examples that people can use to understand this process. The only reason I am even using a rendered object is to get the preprender function call to render to the texture.
#6
01/15/2013 (9:12 am)
@Frank: You may find this info helpful in some way. If you
go to www.gameinstitute.com and register an account ( NO COST)
you can then access the "Community Downloads" section. There
you will find a wide range of demonstration projects showing
different aspects of using DirectX. They are written for the
most part in C++, and cover items such as Point sprites,render to
texture, visual effects, etc. Bear in mind that not all are
in production quality code, rather for learning tools. As well
you will find some tutorials covering various related topics.
I have always found that it is easier for me to create a small
proof of concept project when trying to add features to an engine
or project.If you do not find what you seek, post here as I may
have something in archive that is not listed in the downloads.
#7
01/15/2013 (1:37 pm)
@Dale,
Thanks. The problem has not so much been finding examples in DirectX, as much as it has been understanding which calls in T3D perform which DirectX calls.

I did find some simple examples:
www.two-kings.de/tutorials/dxgraphics/dxgraphics16.html
or
www.codesampler.com/dx9src/dx9src_7.htm#dx9_offscreen_rendering