Game Development Community

Is Resolving a RenderTarget to a GBitmap possible?

by John Kanalakis · in Torque 3D Professional · 05/09/2010 (12:13 pm) · 7 replies

Essentially, I'm trying to get the in-game screen capture function working on Mac OS. The path I'm exploring is grabbing the active render target, resolving to a bitmap, then writing that bitmap to a file.

I started by creating a profile for a PNG-friendly screen capture texture...
GFX_ImplementTextureProfile(  CaptureTextureProfile,
GFXTextureProfile::DiffuseMap, 
GFXTextureProfile::Static,
GFXTextureProfile::DXT1 );

Next, I modified GuiCanvas::renderFrame() in guiCanvas.cpp to look for the screen capture signal and then grab the active render target and dump it to an image file. Around line 1650, I added the following..
if( gScreenShot != NULL && gScreenShot->mPending )
   {
	   //JK gScreenShot->captureStandard();
 
	   const Point2I &targetSize = renderTarget->getSize();
	   GFXFormat targetFormat = renderTarget->getFormat();
 	   
	   GFXTexHandle textureObject;
	   textureObject.set( targetSize.x, targetSize.y, targetFormat, &CaptureTextureProfile, "textureObject" );    
 
	   renderTarget->resolveTo(textureObject);
	   GBitmap* bitmapRender = textureObject->getBitmap();
 
	   FileStream stream;
	   stream.open( gScreenShot->mFilename, Torque::FS::File::Write );
	   bitmapRender->writeBitmap( "png", stream );
	   stream.close();
 	   
	   delete bitmapRender;
 	   
	   gScreenShot->mPending = false;
   }
 
   PROFILE_END();

Everything compiles and links, but at run time, the following error is produced when it's time to take the screen capture.
"Engine/source/gfx/gfxTextureManager.cpp(744):Fatal"
"Anonymous texture didn't get the format it wanted."

I feel like I'm close and that there's something wrong with the profile definition at the beginning. Has anyone tried resolving a render target to a texture and then saved it out to a file?

Thank you,

John K.

EDIT:
Some additional comments... Changing the texture profile to the following results in a slightly different error message...
GFX_ImplementTextureProfile( CaptureTextureProfile2,
							GFXTextureProfile::DiffuseMap,
							GFXTextureProfile::PreserveSize | 
							GFXTextureProfile::RenderTarget |
							GFXTextureProfile::Pooled,
							GFXTextureProfile::None );

The error message reads, "Engine/source/gfx/gfxTextureObject.h(179) : Fatal"
"GFXTextureObject::getBitmap - Cannot access bitmap for a 'CaptureTextureProfile2' texture."

About the author

John Kanalakis is the owner of EnvyGames, an independent game development studio in Silicon Valley that produces games and tools for Xbox 360, Windows, and the Web.


#1
05/10/2010 (8:34 am)
I think you need a profile with the SystemMemory flag turned on. Resolve the render target to that, then save it as a bitmap.
#2
05/10/2010 (4:26 pm)
Thank you, Manoel, it looks like it's getting closer, but still produces an error. I made the following change:
GFX_ImplementTextureProfile(  CaptureTextureProfile,
								GFXTextureProfile::DiffuseMap, 
								GFXTextureProfile::SystemMemory, 
								GFXTextureProfile::DXT1 );

The error now reads, "Engine/source/gfx/gfxTextureManager.cpp(774) : Fatal"
"Anonymous texture didn't get the format it wanted."

I also tried setting the profile compression to None, but it has no effect.

John K.
#3
05/11/2010 (6:07 am)
John, I remembered that I had to solve something like this a while ago. Let me know if this code helps.

// this is using a rendertarget that has its separate bin
   // but I don't see why it shouldn't work with other render targets
   MatTextureTarget *texTarget = MatTextureTarget::findTargetByName( "portrait" );
   if (!texTarget)
      return;

   GFXTextureObject *texObject = texTarget->getTargetTexture(0);
   if (!texObject)
      return;

   // this is probably the more interesting part
   GBitmap bmp( texObject->getWidth(), texObject->getHeight(), false, GFXFormatR8G8B8A8 );
   // you might not need the fill - I had to render over 
   // a transparent bg
   bmp.fill(ColorI(0,0,0,0));
   texObject->copyToBmp( &bmp );

   // finally save the file where "filename" is a parameter
   // for the current function
   FileStream  stream;
   stream.open( filename, Torque::FS::File::Write );
   if ( stream.getStatus() == Stream::Ok )
      bmp.writeBitmap("png", stream);
#4
05/11/2010 (3:54 pm)
Very interesting, thank you Konrad. I'll need to look into this approach of registering specific render targets more closely, as you're doing in line 3. The variation of your example that I tried (using a GFXTextureObject*) looks like this...
GFXTextureObject* texObject;
	   renderTarget->resolveTo(texObject);
	   
	   if (!texObject)
		   return;
	   
	   // this is probably the more interesting part
	   GBitmap bmp( texObject->getWidth(), texObject->getHeight(), false, GFXFormatR8G8B8A8 );
	   
	   // you might not need the fill - I had to render over a transparent bg
	   bmp.fill(ColorI(0,0,0,0));
	   texObject->copyToBmp( &bmp );
	   
	   // finally save the file where "filename" is a parameter for the current function
	   FileStream  stream;
	   stream.open( gScreenShot->mFilename, Torque::FS::File::Write );
	   
	   if ( stream.getStatus() == Stream::Ok )
		   bmp.writeBitmap("png", stream);
It requires changing the texObject definition from GFXTarget* renderTarget into a GFXGLTextureTarget*

It gets a little closer, but now the error generated indicates the following:
"Profiler::hashPop - didn't get expected ProfilerRoot!"

At least it's something new for me to look into. Thank you again for your advice.

John K.
#5
05/16/2010 (9:11 pm)
Here's an update. Thanks to Konrad's pointer, I'm pretty close. The screen capture is saved to disk, but there is a strange behavior that I'm still trying to shake. The GBitmap is always 1024x1024. Even though I set an explicitly different size, it's always 1024x1024.

Here's the texture profile...
GFX_ImplementTextureProfile(  CaptureTextureProfile,
								GFXTextureProfile::DiffuseMap, 
								GFXTextureProfile::SystemMemory,
							GFXTextureProfile::None );

Here's the capture code...
if( gScreenShot != NULL && gScreenShot->mPending )
	{
		//JK gScreenShot->captureStandard();

		GFXTarget* renderTarget = mPlatformWindow->getGFXTarget();
		const Point2I& renderTargetSize = renderTarget->getSize();
		GFXFormat renderTargetFormat = renderTarget->getFormat();
 
		GFXTexHandle* hTexture = new GFXTexHandle( renderTargetSize.x, 
												 renderTargetSize.y, 
												 renderTargetFormat, 
												 &CaptureTextureProfile, 
												 "textureObject");

		renderTarget->resolveTo( *hTexture );

		Con::printf("****** renderTargetSize=%dx%d, hTexture.Size=%dx%d", renderTargetSize.x, renderTargetSize.y, hTexture->getWidth(), hTexture->getHeight() );
		
		if (!hTexture)
			return;
		
		GBitmap bitmap( hTexture->getWidth(), hTexture->getHeight(), false, GFXFormatR8G8B8A8 );
		
		bitmap.fill(ColorI(0,0,0,0));
		hTexture->copyToBmp( &bitmap );
		
		FileStream  stream;
		stream.open( gScreenShot->mFilename, Torque::FS::File::Write );
		
		if ( stream.getStatus() == Stream::Ok )
			bitmap.writeBitmap("png", stream);	   
		
		gScreenShot->mPending = false;
	}

The console output that reports the dimensions returns this...
****** renderTargetSize=1000x550, hTexture.Size=1024x1024
The images that are created are also stretched to 1024x1024.

I'm just a little puzzled since I'm creating the GFXTexHandle with the dimensions of the render target, which is 1000x50 (the size of the entire renderable window). It seems that the resolveTo() method is changing the GFXTexHandle to 1024x1024 for some reason.

Anyone have any ideas?

John K.
#6
05/17/2010 (3:00 am)
I think it is because textures' dimension need to be a power of 2.

So 1000 becomes 1024 and 550 becomes 1024.

After that, does your image fills all the texture surface, or only the 1000x550 one.

Perhaps you have to copy only the region you need, or you need to resize your texture when you copy it in your bitmap?

Nicolas Buquet
www.buquet-net.com/cv/
#7
05/17/2010 (3:56 am)
Perhaps a bloated solution in your case, but if you made a named texture target - just like renderGlowManager does - then you could get the texture object like I suggested. You'd only have to render everything into it - so addElement would add everything.

An added bonus would be that you would have a firm control over what would be elected onto that texture target. Also, in this case you would not have to worry about the stretching or the pow 2 dimensions afaik.