But in real world, there are situations, when you simply need to use even the cutting edge features or features not commonly implemented by all hardware vendors. One of such a features is offscreen rendering, and this article will tell you for what it can be useful, what are the basic principles and it finally gives you sample implementation and script examples.
What is offscreen rendering?
When creating window in TBGL and drawing elements the common way, programmer renders the graphics to the window client area, so it is immediately visible after swapping the buffers using TBGL_DrawFrame. This approach is called on screen rendering.
The off screen rendering is slightly different approach, where you don't draw directly to screen, but to memory instead. The most straightforward approach to implement offscreen rendering is using Frame Buffer Objects, by friends and family members called simply FBO.
The advantages of this approach are:
- it is possible to render images at bigger resolution than display supports
- rendered image is immediately linked with texture
As everything, it has its disadvantages as well:
- it is avialable only on OpenGL implementations with GL_EXT_framebuffer_object extension support
- it can eat lot of graphic memory, depending on resolution
Birth and death of offscreen render surface
Similarly to other OpenGL objects, FBO must be created before its use, and released once you no longer need it. Unlike textures or display lists, there is not just single handle, but three of them. As this is getting complicated, it is good idea to hold all the information in single structure, like the following:
Type OSR_ImageBuffer8b buffer8bit_FBO As DWord buffer8bit_RBO As DWord buffer8bit_Texture As DWord buffer8bit_Width As Long buffer8bit_Height As Long End Type
The first member will hold the handle of frame buffer object itself, which takes care of storing the final image color information. The second member, render buffer object handle, holds information about depth in scene, it has the function of Z-Buffer. The last handle is the one of texture linked together with FBO.
The additional width and height information comes in handy later, as will be described in following text.
Before we start any further actions, we must ensure the feature is available. This can be done by checking the extension support. If it succeeds, we can retrieve function pointers to OpenGL extensions:
Function OSR_Init() If TBGL_oglExtensionSupport("GL_EXT_framebuffer_object") = FALSE Then MsgBox(0, "You need GL_EXT_framebuffer_object hardware support for offscreen rendering", %MB_OK | %MB_ICONINFORMATION, "unit_OffScreenRendering") Return FALSE End If Declare Set Address glGenRenderbuffersEXT, wglGetProcAddress("glGenRenderbuffersEXT") Declare Set Address glBindRenderbufferEXT, wglGetProcAddress("glBindRenderbufferEXT") Declare Set Address glRenderbufferStorageEXT, wglGetProcAddress("glRenderbufferStorageEXT") Declare Set Address glBindRenderbufferEXT, wglGetProcAddress("glBindRenderbufferEXT") Declare Set Address glGenFramebuffersEXT, wglGetProcAddress("glGenFramebuffersEXT") Declare Set Address glBindFramebufferEXT, wglGetProcAddress("glBindFramebufferEXT") Declare Set Address glFramebufferTexture2DEXT, wglGetProcAddress("glFramebufferTexture2DEXT") Declare Set Address glFramebufferRenderbufferEXT, wglGetProcAddress("glFramebufferRenderbufferEXT") Declare Set Address glCheckFramebufferStatusEXT, wglGetProcAddress("glCheckFramebufferStatusEXT") Declare Set Address glDeleteRenderBuffersEXT, wglGetProcAddress("glDeleteRenderbuffersEXT") Declare Set Address glDeleteFrameBuffersEXT, wglGetProcAddress("glDeleteFramebuffersEXT") 'Declare Set Address glGenerateMipmapEXT, wglGetProcAddress("glGenerateMipmapEXT") Return TRUE End Function
' -- Create image buffer with 8bits per channel precision and specified resolution Function OSR_CreateImageBuffer8B( nWidth As Long, nHeight As Long) As DWord Local pMem As DWord pMem = HEAP_Alloc(SizeOf(OSR_ImageBuffer8b)) Dim imageBuffer As OSR_ImageBuffer8b At pMem imageBuffer.buffer8bit_Width = nWidth imageBuffer.buffer8bit_Height= nHeight ' -- Frame buffer to hold color information glGenFramebuffersEXT(1, imageBuffer.buffer8bit_FBO) glBindFramebufferEXT(%GL_FRAMEBUFFER_EXT, imageBuffer.buffer8bit_FBO) ' -- Render buffer to hold depth information glGenRenderbuffersEXT(1, imageBuffer.buffer8bit_RBO) glBindRenderbufferEXT(%GL_RENDERBUFFER_EXT, imageBuffer.buffer8bit_RBO) glRenderbufferStorageEXT(%GL_RENDERBUFFER_EXT, %GL_DEPTH_COMPONENT, imageBuffer.buffer8bit_Width, imageBuffer.buffer8bit_Height) glFramebufferRenderbufferEXT(%GL_FRAMEBUFFER_EXT, %GL_DEPTH_ATTACHMENT_EXT, %GL_RENDERBUFFER_EXT, imageBuffer.buffer8bit_RBO) ' -- Texture mapping of the FBO glGenTextures(1, imageBuffer.buffer8bit_Texture) glBindTexture(%GL_TEXTURE_2D, imageBuffer.buffer8bit_Texture) glTexImage2D(%GL_TEXTURE_2D, 0, %GL_RGBA8, imageBuffer.buffer8bit_Width, imageBuffer.buffer8bit_Height, 0, %GL_RGBA, %GL_UNSIGNED_BYTE, 0) glTexParameterf(%GL_TEXTURE_2D, %GL_TEXTURE_WRAP_S, %GL_CLAMP_TO_EDGE) glTexParameterf(%GL_TEXTURE_2D, %GL_TEXTURE_WRAP_T, %GL_CLAMP_TO_EDGE) glTexParameteri(%GL_TEXTURE_2D, %GL_TEXTURE_MAG_FILTER, %GL_LINEAR) glTexParameteri(%GL_TEXTURE_2D, %GL_TEXTURE_MIN_FILTER, %GL_LINEAR) glFramebufferTexture2DEXT(%GL_FRAMEBUFFER_EXT, %GL_COLOR_ATTACHMENT0_EXT, %GL_TEXTURE_2D, imageBuffer.buffer8bit_Texture, 0) ' -- Did we succeeded Local glStatus As Long glStatus = glCheckFramebufferStatusEXT(%GL_FRAMEBUFFER_EXT) If(glStatus <> %GL_FRAMEBUFFER_COMPLETE_EXT) Then OSR_DestroyImageBuffer8B(pMem) Return 0 Else ' Go back To regular Frame buffer rendering glBindFramebufferEXT(%GL_FRAMEBUFFER_EXT, 0) Return pMem End If End Function
The destruction of the image buffer is more simple:
' -- Destroy the offscreen image buffer Function OSR_DestroyImageBuffer8B( pMem As DWord ) Dim imageBuffer As OSR_ImageBuffer8b At pMem ' -- Free the resources, important as it is quite memory intensive glDeleteTextures(1, imageBuffer.buffer8bit_Texture) glDeleteRenderBuffersEXT(1, imageBuffer.buffer8bit_RBO) glDeleteFrameBuffersEXT(1, imageBuffer.buffer8bit_FBO) HEAP_Free(pMem) End Function
The good news is that there is no difference between rendering on screen and offscreen, you simply have to clear the color and depth buffer, which is ensured via TBGL_ClearFrame and then draw what you need. Just before and right after the rendering code you need to do one step extra, which is activating and deactivating the image buffer. For this, the following functions can serve:
' -- Begins rendering Function OSR_BeginRenderToImageBuffer8B( pMem As DWord ) Dim imageBuffer As OSR_ImageBuffer8b At pMem glBindFrameBufferEXT(%GL_FRAMEBUFFER_EXT, imageBuffer.buffer8bit_FBO) ' -- Viewport must match the FBO size glPushAttrib(%GL_VIEWPORT_BIT) glViewport(0, 0, imageBuffer.buffer8bit_Width, imageBuffer.buffer8bit_Height) End Function ' -- Finishes rendering Function OSR_EndRenderToImageBuffer8B( pMem As DWord ) Dim imageBuffer As OSR_ImageBuffer8b At pMem glPopAttrib() glBindFrameBufferEXT(%GL_FRAMEBUFFER_EXT, 0) End Function
' -- If Off Screen Rendering initialization failed, it won't work and we might want to end the app If OSR_Init() = false Then TBGL_DestroyWindow Stop End If ' -- Creating HD image buffer DIM hImageBuffer AS DWORD = OSR_CreateImageBuffer8B(1920, 1080) ... ' -- Generating images OSR_BeginRenderToImageBuffer8B(hImageBuffer) TBGL_ClearFrame TBGL_Camera 15, 15, 15, 0, 0, 0 TBGL_Sphere 1 OSR_EndRenderToImageBuffer8B(hImageBuffer) ... ' -- When application ends, we need to release it OSR_DestroyImageBuffer8B(hImageBuffer)
Now we know how to create image buffer for offscreen rendering, how to draw to it. But what next? What are the possible uses of it? The evident one is replacement for classic render-to-texture approach, but there is one much more interesting:
Using TBGL script as generator of frames for video in HD resolution, for example. Normally you would have to own display with 1920x1080 resolution and use TBGL_SaveScreenshot to save images. But this has few problems - if the window is covered by some window or some happy message like "Windows has new updates for you" pops up over it, the image might get corrupted on some hardware.
With offscreen rendering, you can produce such a big images even on average laptop with much smaller display without such a negative side effects. To do so, you need to do one step more - download the imagebuffer contents from GPU memory and store it to file.
The following code does just that:
' -- Saves image as BMP Function OSR_SaveImageBuffer8bAsBMP( pMem As DWord, fName As String) Dim imageBuffer As OSR_ImageBuffer8b At pMem Dim sBuffer As String glBindTexture(%GL_TEXTURE_2D, imageBuffer.buffer8bit_Texture) sBuffer = String$(imageBuffer.buffer8bit_Width*imageBuffer.buffer8bit_Height*3, $SPC) glGetTexImage(%GL_TEXTURE_2D, 0, %GL_BGR, %GL_UNSIGNED_BYTE, StrPtr(sBuffer)) Local bmpFileHdr As OSR_BITMAPFILEHEADER Local bmpInfoHdr As OSR_BITMAPINFOHEADER bmpFileHdr.bfType = CVI( "BM" ) bmpFileHdr.bfSize = SizeOf( bmpFileHdr ) + SizeOf( bmpInfoHdr ) + Len(sBuffer) bmpFileHdr.bfOffBits = 54 bmpInfoHdr.biSize = 40 bmpInfoHdr.biWidth = imageBuffer.buffer8bit_Width bmpInfoHdr.biHeight = imageBuffer.buffer8bit_Height bmpInfoHdr.biPlanes = 1 bmpInfoHdr.biBitCount = 24 bmpInfoHdr.biSizeImage= 54 + Len(sBuffer) FILE_Save(fName, Peek$(VarPtr(bmpFileHdr), SizeOf(bmpFileHdr))+ Peek$(VarPtr(bmpInfoHdr), SizeOf(bmpInfoHdr))+sBuffer) End Function
Offscreen rendering can be found useful in cases, when you need to render bigger than window images, or simply perform safe rendering of images without worries about surprises the window subsystem might have for you. The downside of this approach is fact, that this feature is not implemented on every piece of GPU around, especially older Intel solutions are known to have problems with this approach, but this is getting better with latest models on which this technique was successfully tested.
The attached file contains simple example of rendering 512x512 image from 256x256 window, code also shows how to render contents of image buffer both to screen and to file. The ZIP also contains standalone unit_OffScreenRendering.tBasicU you can use in your projects.
The purpose of demo code is not to shock you with outstanding graphics (as it only draws a sphere), but to provide minimal code sample to make porting your existing code more easily.
thinBASIC 1.8.7.0 and newer is recommended.
Download becomes visible after logging in.
Compatibility report
Did the code run? Did the code failed? Let us know, so the following compatibility list can be updated to give more info about usability of the technique.
Not sure which GPU do you have? Then please try to check using the tool in topic:
TBGL Troubleshooting
GPUs which managed to run the demo app
Intel HD Graphics
Nvidia GeForce 7300GT
Nvidia GeForce 9500GT
Nvidia GeForce GT100
Nvidia GeForce G210M
Nvidia GeForce GT630
GPUs which do not support the necessary extensions
Intel Pineview Platform
Message