RenderToTexture to file

If you are a new Irrlicht Engine user, and have a newbie-question, this is the forum for you. You may also post general programming questions here.
Post Reply
aboeing
Posts: 16
Joined: Sat Oct 07, 2006 7:50 pm

RenderToTexture to file

Post by aboeing »

Hi,

I'm trying to get the output from the render to texture to write into a file.

I have modifed the RenderToTexture example, and it works for the ApfelBaum software renderer, but none of the others. Lock returns a valid pointer, but the data is empty. Can someone tell me what I'm doing wrong? (ie: why it will not work with DX8,9 & OpenGL)

Thank you!

Code: Select all

if (rt)
		{
			// draw scene into render target
			
			// set render target texture
			driver->setRenderTarget(rt, true, true, video::SColor(0,0,0,255));     

			// make cube invisible and set fixed camera as active camera
			test->setVisible(false);
			smgr->setActiveCamera(fixedCam);

			// draw whole scene into render buffer
			smgr->drawAll();                 

			
			//NEW CODE FOLLOWS::
			core::dimension2d<s32> rtsize;
			unsigned char *data = (unsigned char *)rt->lock();
			rtsize = rt->getSize();
			video::ECOLOR_FORMAT ecf = rt->getColorFormat();

			//assuming 32bit ARGB, writing out to a 24bit RGB
			FILE *fout = fopen("output.raw","wb");
			for (int j=0;j<rtsize.Height;j++)
			for (int i=0;i<rtsize.Width;i++) {
				fwrite(&data[i*4+rt->getPitch()*j],sizeof(char),3,fout);
			}
			fflush(fout);
			fclose(fout);
			
			rt->unlock();
			//END NEW CODE


			// set back old render target
			driver->setRenderTarget(0);  
    

			// make the cube visible and set the user controlled camera as active one
			test->setVisible(true);
			smgr->setActiveCamera(fpsCamera);
		}
misw
Posts: 1
Joined: Mon Oct 09, 2006 11:26 am

Post by misw »

same problem here ...

works for the burning's software renderer, but not with opengl (under linux).

i'm using the latest svn-version.
hey_i_am_real
Posts: 44
Joined: Thu Sep 28, 2006 2:27 pm
Location: Europe

Post by hey_i_am_real »

I think it's normal (at least, according to the current source-code, it's normal)


With the OpenGL driver, when a texture is created using createRenderTargetTexture(), lock() should returns NULL.

It's because there is no copy of the content of the texture into the computer's memory.




With the OpenGL driver, textures have to be copied into the video-card memory.

With usual textures, OpenGL makes a copy into the video-card memory, and IrrLicht keeps the original version into the computer's memory. (you then have two copy of the texture, one into the computer's memory, the other into the video-card memory)

When you call lock(), you get acces to the copy which is sotred into the computer's memory.
And when you unlock() it, IrrLicht's OpenGL driver send a new copy of the modified version of the texture into the video-card.


That means you can't have access to the copy of the texture which is stored into the video-card memory.



When a texture is created using createRenderTargetTexture(), this texture exists only into the video-card memory.

Then, when you call lock(), since there is no copy of the texture into the computer's memory, lock() should return 0.



With OpenGL, to get a copy of a texture stored into the video-card memory, we can use this opengl function : glGetTexImage().
But that would be a hack out of the current IrrLicht engine :)
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

Have you considered using the IVideoDriver::createScreenShot() method?

Travis
aboeing
Posts: 16
Joined: Sat Oct 07, 2006 7:50 pm

Post by aboeing »

Hi, Thanks for the advice hey_i_am_real.

I downloaded the irrlicht source from subversion and modified the lock operation to use glGetTexImage as you suggested:

Code: Select all

void* COpenGLTexture::lock()
{
	if(!ImageData) {
		ImageData =  new u8[256 * 256 * 4];
//hardcoded this for now
	}
	glBindTexture(GL_TEXTURE_2D, TextureName);
	glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_BYTE, ImageData);

	return ImageData;
}
but the image that comes back doesn't look the same as the rendered image - infact, its mostly random garbage.

Is there more to it than this?

vitek: The create screenshot method just calls glReadPixels which will only read from the framebuffer - I woud like to read from a render-target texture.

Thanks!
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

The image writer classes were created as part of the screen shot code. They are there to make it easy to write image files to disk. All you have to do is get the texture into an image and then use the image writers and you're all set. That should be pretty easy.

Code: Select all

  void* data = t->lock();
  if (data)
  {
    // this _should_ work, but requires some heap allocation
    IImage* image = driver->createImageFromData(t->getColorFormat(), t->getSize(), data, false);
    t->unlock();

    driver->writeImageToFile(image, "foo.bmp");
    image->drop();
  }
If you wanted to avoid the heap allocation, you could write an IImage derived class that just act as a proxy to the the data owned by the texture instead of copying it.
hey_i_am_real
Posts: 44
Joined: Thu Sep 28, 2006 2:27 pm
Location: Europe

Post by hey_i_am_real »

I looked deeper at the source code again (still studying it :P), and here are some more info that may be helpfull :

- despite i said "lock() should return 0 with a RTT", and like you experienced by yourself, it does not. It returns a valid pointer to a surface which seems to be "empty".
When you look to the source code, seems that this surface is created even for RTT, which means, unless i wrong, you could reuse it to copy the RTT from the video-card memory to the computer-memory. (no need to alloc memory)

- i found no mean to detect if a texture is a normal texture or a RTT. Which means that you may be unable to "activate" your lock() modifications only for RTT. Which means that will slowdown the engine twice more if you make use of lock() with normal textures too ...

- you can get the size / color-format of the RTT using ImageSize, ColorFormat and others (look at COpenGLTexture.h)

As an other advice, instead of modifying lock(), maybe you could add a new method called lockRTT(). This way, you will not slowdown normal texture lock(), and you'll use lockRTT() when required :)



PS : and yes, try to use what Vitek told you to write the result into an image file :)
I like working with rank-outsiders, as long as they give themselves the means to achieve their ambitions.
aboeing
Posts: 16
Joined: Sat Oct 07, 2006 7:50 pm

Post by aboeing »

vitek: Ah yes, thanks for that! The previous version of irrlicht didn't have those functions I don't think - but that all works fine.

hey_i_am_real: Thanks for the tips! Got a working version now! :)

I noticed that using the code for writing straight to a file seems to also work for DirectX 9.0c, so this problem only occurs for opengl.

I rewrote the code, it all appears to work now, but I hope this could be generalized enough to be included into irrlicht directly??

Code: Select all

void* COpenGLTexture::lock()
{
	int bpp;
	GLenum	format = GL_RGB;
	if (Name == "rt") { //this is a render target texture
		//get the bytes per pixel
		switch (ColorFormat) {
		case ECF_A1R5G5B5:
		case ECF_R5G6B5:
			bpp = 2;
			//? what should format be?
			break;
		case ECF_R8G8B8:
			bpp = 3;
			break;
		case ECF_A8R8G8B8:
			bpp = 4;
			//format = GL_RGBA; //?? perhaps under linux???
			format = GL_BGRA_EXT;
			break;
		}
		//build the image
		//?what happens if ImageData is not null -- when exactly DOES this happen ??
		if(!ImageData) {
			//?should this be pitch or ImageSize.Width ??
			ImageData =  new u8[ImageSize.Height * ImageSize.Width * bpp];
		}
		//bind the texture
		glBindTexture(GL_TEXTURE_2D, TextureName);
		//get the subimage
		glGetTexImage(GL_TEXTURE_2D, 0, format, GL_BYTE, ImageData);
	}

	return ImageData;
}
unanswered questions include:
-Is it okay just to determine if a texture is a render target via name==rt
-What is the format if its 15/16bit?
-When/why is ImageData sometimes not null? (this happens somewhat randomly!) - do we need to new it anyway then, or is it a valid pointer?
-Should the image data be in terms of width or pitch?
-Does linux support GL_BGRA_EXT? - what should it be here instead?

Thanks for your help guys!
aboeing
Posts: 16
Joined: Sat Oct 07, 2006 7:50 pm

Post by aboeing »

Oh yeah, I just noticed, the image is also upside down with OpenGL vs DirectX

Is this an issue to do with the API's/Platforms or something to do with bitmaps (.bmp) being writen upside down? Any ideas? (ie: should the openGL lock code flip the image?)
bitplane
Admin
Posts: 3204
Joined: Mon Mar 28, 2005 3:45 am
Location: England
Contact:

Post by bitplane »

I think OGL is the right way up, but bitmap files are bottom first, the image writers flip them around.. maybe render targets are upside down and get double flipped. it could be the other way around though. (can't test here in work)
I don't think lock() should bother flipping the image. Nice bit of code in here, thanks :) I think this should make it into irrlicht before 1.2.
Submit bugs/patches to the tracker!
Need help right now? Visit the chat room
vitek
Bug Slayer
Posts: 3919
Joined: Mon Jan 16, 2006 10:52 am
Location: Corvallis, OR

Post by vitek »

OpenGL is inverted [when compared to the other renderers]. Bitmaps are also inverted when compared to other image formats.

When the createScreenShot code runs, the image is flipped vertically [in that method] before returning it to the user. That way the user always gets an IImage that is oriented the same regardless of the driver that they are using.

Travis
Post Reply