Skip to main content
gamedev.net
🔒 Locked

Off-screen OpenGL rendering...

Started by Yansen Jun 15, 2004 at 5:58 AM 12 replies 12.6k views
Original Post
Yansen
Yansen
Hi. I need render OpenGL to off-screnn buffer and write them as BMP image into the disk. I need very large image (like 2500x2000), I know how to write to disk image that fit in to the screen but don't know how to do this which such a large image. Where to read about that problem ("feedback" will be good solutioin as I suspose....)???? PS.Sorry for my poor English language skill. PSS.I'am Physicist so I need some very simple example to understand how to do this. I use Borland Builder 6 + OpenGLAP 1.0.8...
Brother Bob
Brother Bob
You can use the offscreen renderer in Mesa3D; OSMesa. It's a software implementation of OpenGL 1.5, and includes lots of extensions. Unless rendering speed is of great importance, Mesa is, in my oppinion, an extremely good solution, especially for offscereen rendering.
Yansen
Yansen
Is this realy so complicated that I need use so big library? I already have whole scene import to POV-Ray then ray-trace them in resolution I want but this is a lot of work. I prefer use only naked OpenGL. Is this possible?
Brother Bob
Brother Bob
Well, Mesa IS "naked" OpenGL*. There's no difference between Mesa and the implementation that ships by default with Windows or whatever platform you use (other that different feature sets, like version, extensions, ability for hardware acceleration, and so on). Mesa is not an add-on library or so, it is a stand-alone library you use instead of some other implementation. Just download it, compile it (unless there are some precompiled binaries), and link against it instead of your other OpenGL library (opengl32.lib on Windows).

* It is OpenGL in the sense that it fullt implements the specification. For legal reasons it can't be called an implementaiton since it doesn't have a license to do so.
_the_phantom_
_the_phantom_
the other option would be to draw a section of it at a time, copying it back to system memory after each frame is generated and assembling it in a block of memory on your own.
There was an artical recently about saving screen shots and videos, i'm pretty sure it had some code to allow you to save 'poster quality' shots which rendered the scene multiple times to generate a large image from lots of smaller ones.

The other option is that you could make a pBuffer of 2500*2000 and render directly to it and then readback from it using glReadPixels, however there is a thread on this page detailing the problem someone has had getting a pbuffer to work at large sizes on a GeForce card (although, iirc it was working fine at around 3000*3000 so even if a bug does exist w.r.t. the size it should be fine for your purposes), but assuming it works this would probably be the better choice.
As long as you dont try and do render to texture with it then you can have a viewport size of 4096*4096 even on an ATI card (max texture size is only 2048*2048 you see)
python_regious
python_regious
For reference, the OpenGL Superbible explains how to render to a bitmap (under windows).

In short, you need to create a DIB section, and bind it to the DC. In the pixel format selection stage, use PFD_DRAW_TO_BITMAP instead of PFD_DRAW_TO_WINDOW, and use the DC with the bitmap bound to it to create the GLRC. Then you just render as normal, and save the DIB.
If at first you don't succeed, redefine success.
_the_phantom_
_the_phantom_
does that allow you to use a hardware accelerated context as well?
python_regious
python_regious
I've no idea, try it out.
If at first you don't succeed, redefine success.
s_p_oneil
s_p_oneil
Try this class, and tweak the ChoosePixelFormat() method to get the pixel format you want. In my case, I chose to force it to use the software renderer because performance wasn't critical, and I needed something that would never mess up due to a bug in some vendor's OpenGL driver.


class CGLBitmapContext
{
protected:
BITMAPINFO m_bmi;
HBITMAP m_hBitmap;
void *m_pBits;
HDC m_hDC, m_hOldDC;
HGLRC m_hGLRC, m_hOldGLRC;
CGLFont m_font;

public:
CGLBitmapContext()
{
}

int GetWidth() { return m_bmi.bmiHeader.biWidth; }
int GetHeight() { return m_bmi.bmiHeader.biHeight; }
CGLFont &GetFont() { return m_font; }

bool ChoosePixelFormat()
{
int nMaxWeight = 0;
int nBestFormat = 0;
int nPixelFormat = 1;
PIXELFORMATDESCRIPTOR pfdDesc;
DWORD dwRequiredFlags = PFD_DRAW_TO_BITMAP | PFD_GENERIC_FORMAT | PFD_SUPPORT_OPENGL | PFD_SUPPORT_GDI;
DWORD dwRejectedFlags = PFD_GENERIC_ACCELERATED;
while(DescribePixelFormat(m_hDC, nPixelFormat, sizeof(PIXELFORMATDESCRIPTOR), &pfdDesc))
{
if(pfdDesc.cColorBits == 24 && pfdDesc.iLayerType == PFD_TYPE_RGBA &&
(pfdDesc.dwFlags & dwRequiredFlags) == dwRequiredFlags &&
(pfdDesc.dwFlags & dwRejectedFlags) == 0)
{
int nWeight = pfdDesc.cColorBits * 8 + pfdDesc.cDepthBits * 4 + pfdDesc.cStencilBits + pfdDesc.cAccumBits;
if(nWeight > nMaxWeight)
{
nMaxWeight = nWeight;
nBestFormat = nPixelFormat;
}
}
nPixelFormat++;
}
if(nBestFormat && ::SetPixelFormat(m_hDC, nBestFormat, &pfdDesc))
return true;
return false;
}

bool Init(HDC hWindowDC, int nWidth, int nHeight)
{
// Create the bitmap for this OpenGL context
memset(&m_bmi, 0, sizeof(BITMAPINFO));
m_bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
m_bmi.bmiHeader.biWidth = nWidth;
m_bmi.bmiHeader.biHeight = nHeight;
m_bmi.bmiHeader.biPlanes = 1;
m_bmi.bmiHeader.biBitCount = 24;
m_bmi.bmiHeader.biCompression = BI_RGB;
m_bmi.bmiHeader.biSizeImage = nWidth * nHeight * 3;
m_hBitmap = ::CreateDIBSection(hWindowDC, &m_bmi, DIB_RGB_COLORS, &m_pBits, NULL, (DWORD)0);

// Create the memory DC for the OpenGL context
m_hDC = ::CreateCompatibleDC(NULL);
::SelectObject(m_hDC, ::GetCurrentObject(hWindowDC, OBJ_FONT));
::SelectObject(m_hDC, m_hBitmap);

// Select a software-rendering pixel format that supports drawing to a bitmap
// (Must be 24-bit color because the bitmap is 24-bit color)
if(!ChoosePixelFormat())
return false;

// Initialize the OpenGL context
m_hGLRC = ::wglCreateContext(m_hDC);
m_hOldDC = ::wglGetCurrentDC();
m_hOldGLRC = ::wglGetCurrentContext();
::wglMakeCurrent(m_hDC, m_hGLRC);

// Initialize the CGLFont object and set up the viewport
m_font.Init(m_hDC, 16);
glViewport(0, 0, nWidth, nHeight);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluOrtho2D(0, nWidth, 0, nHeight);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();

return true;
}

void Cleanup()
{
m_font.Cleanup();
::wglMakeCurrent(NULL, NULL);
::wglDeleteContext(m_hGLRC);
::wglMakeCurrent(m_hOldDC, m_hOldGLRC);
::DeleteObject(m_hBitmap);
::DeleteDC(m_hDC);
}
};
Yansen
Yansen
THX. I don't care about hardware acceleration. (This is program that draw a crystal structure with magnetic moment on an atom's - no texturing, 1 light source, no AA or other gadget's). I'll try Your's sugestions. One more big thx.
outRider
outRider
Quote:
Original post by _the_phantom_

does that allow you to use a hardware accelerated context as well?


No, drawing falls back on the software implementation because the bitmap is in system memory. That's why pbuffers exist for hardware accelerated drawing offscreen drawing.
s_p_oneil
s_p_oneil
Are you certain that hardware acceleration is not supported? Unless it's a limitation enforced by Microsoft's OpenGL implementation, the decision should be up to the driver. It's not difficult to imagine a back buffer being created, and a glReadPixels call being made after a hardware-accelerated render (it's how impostors used to be done).

The only reason I bring this up is that when I've used the class I posted earlier, hardware-accelerated contexts were listed. When I selected those contexts, they produced visual artifacts that didn't show up when I selected a software-only context. (It's an old chipset with crappy drivers.)

Sean
cnstrnd
cnstrnd
Two things :

- Even if you don't care about the eye candy, I think you'd better draw to a hidden window (simply do not call ShowWindow after you create it) and blit back the color buffer to a DIB with glReadPixels : believe me, it will be waysss faster than the DRAW_TO_BITMAP thingy.

- One last thing : when dealing with huge bitmaps (DIB), it may help to use a file mapping instead of normal memory.

int createTarget( const wchar_t* filename ){	BITMAPFILEHEADER bfh;	BITMAPINFOHEADER bih;	DWORD            written;	HDC              context;	int              result = 0;	// Create target file	file = CreateFile( pfilename, GENERIC_ALL, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL );	if( file != NULL )	{		// Setup headers		memset( &bih, 0, sizeof(BITMAPINFOHEADER) );		bih.biSize        = sizeof(BITMAPINFOHEADER);		bih.biWidth       = w;		bih.biHeight      = h;		bih.biPlanes      = 1;		bih.biBitCount    = 24;		bih.biCompression = BI_RGB;		bfh.bfType      = 'MB';		bfh.bfOffBits   = 64; //sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);		bfh.bfSize      = bfh.bfOffBits + bih.biWidth * bih.biHeight * bih.biBitCount / 8;		bfh.bfReserved1 = 0;		bfh.bfReserved2 = 0;		// Write headers		if( WriteFile( file, &bfh, sizeof(BITMAPFILEHEADER), &written, NULL ) &&			WriteFile( file, &bih, sizeof(BITMAPINFOHEADER), &written, NULL ) )		{			// Create file mapping			mapping = CreateFileMapping( file, NULL, PAGE_READWRITE, 0, bfh.bfSize, NULL );			if( mapping != NULL )			{				context = CreateCompatibleDC( NULL );				target = CreateDIBSection( context, (BITMAPINFO*)&bih, DIB_RGB_COLORS, &bits, mapping, bfh.bfOffBits );				DeleteDC( context );				if( target != NULL )				{					return 1;				}				CloseHandle( mapping );			}		}		CloseHandle( file );		DeleteFile( filename );	}	return 0;}

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.