/**************************************************************************\ * * This file is part of the Coin 3D visualization library. * Copyright (C) 1998-2007 by Systems in Motion. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * ("GPL") version 2 as published by the Free Software Foundation. * See the file LICENSE.GPL at the root directory of this source * distribution for additional information about the GNU GPL. * * For using Coin with software that can not be combined with the GNU * GPL, and for taking advantage of the additional benefits of our * support services, please contact Systems in Motion about acquiring * a Coin Professional Edition License. * * See http://www.coin3d.org/ for more information. * * Systems in Motion, Postboks 1283, Pirsenteret, 7462 Trondheim, NORWAY. * http://www.sim.no/ sales@sim.no coin-support@coin3d.org * \**************************************************************************/ /*! \class SoOffscreenRenderer SoOffscreenRenderer.h Inventor/SoOffscreenRenderer.h \brief The SoOffscreenRenderer class is used for rendering scenes in offscreen buffers. \ingroup general If you want to render to a memory buffer instead of an on-screen OpenGL context, use this class. Rendering to a memory buffer can be used to generate texture maps on-the-fly, or for saving snapshots of the scene to disk files (as pixel bitmaps or as Postscript files for sending to a Postscript-capable printer). Here's a dead simple usage example, just the code directly related to the SoOffscreenRenderer: \code SoOffscreenRenderer myRenderer(vpregion); SoNode * root = myViewer->getSceneManager()->getSceneGraph(); SbBool ok = myRenderer.render(root); unsigned char * imgbuffer = myRenderer.getBuffer(); // [then use image buffer in a texture, or write it to file, or whatever] \endcode And a complete, simple, stand-alone example: \code #include #include #include #include #include #include int main(int argc, char ** argv) { SoDB::init(); SoPerspectiveCamera * camera = new SoPerspectiveCamera; SoDirectionalLight * light = new SoDirectionalLight; SoCube * cube = new SoCube; SoSeparator * root = new SoSeparator; root->addChild(camera); root->addChild(light); root->addChild(cube); root->ref(); SbViewportRegion vpr; vpr.setWindowSize(400, 400); light->direction.setValue(1, -1.2, -0.5); camera->position.setValue(-2, 2, 2); camera->pointAt(SbVec3f(0, 0, 0)); camera->viewAll(cube, vpr); SoOffscreenRenderer osr(vpr); SbBool ok = osr.render(root); if (!ok) { exit(1); } ok = osr.writeToRGB("test-image.rgb"); if (!ok) { exit(1); } (void)puts("Image written successfully."); root->unref(); return 0; } \endcode Note that the SoOffscreenRenderer potentially allocates a fairly large amount of resources, both OpenGL and general system resources, for each instance. You will therefore be well adviced to try to reuse SoOffscreenRenderer instances, instead of constructing and destructing a new instance e.g. for each frame when generating pictures for video. Offscreen rendering is internally done through either a GLX offscreen context (i.e. OpenGL on X11) or a WGL (i.e. OpenGL on Win32) or AGL (i.e. OpenGL on the Mac OS) ditto. If the OpenGL driver supports the pbuffer extension, it is detected and used to provide hardware-accelerated offscreen rendering. The pixeldata is fetched from the OpenGL buffer with glReadPixels(), with the format and type arguments set to GL_RGBA and GL_UNSIGNED_BYTE, respectively. This means that the maximum resolution is 32 bits, 8 bits for each of the R/G/B/A components. One particular usage of the SoOffscreenRenderer is to make it render frames to be used for the construction of movies. The general technique for doing this is to iterate over the following actions:
  • move camera to correct position for frame
  • update the \c realTime global field (see explanation below)
  • invoke the SoOffscreenRenderer
  • dump rendered scene to file
..then you use some external tool or library to construct the movie file, for instance in MPEG format, from the set of files dumped to disk from the iterative process above. The code would go something like the following (pseudo-code style). First we need to stop the Coin library itself from doing any automatic updating of the \c realTime field, so your application initialization for Coin should look something like: \code [...] = SoQt::init([...]); // or SoWin::init() or SoDB::init() // ..and then immediately: // Control realTime field ourselves, so animations within the scene // follows "movie-time" and not "wallclock-time". SoDB::enableRealTimeSensor(FALSE); SoSceneManager::enableRealTimeUpdate(FALSE); SoSFTime * realtime = SoDB::getGlobalField("realTime"); realtime->setValue(0.0); \endcode Note that it is important that the \c realTime field is initialized to \e your start-time \e before setting up any engines or other entities in the system that uses the \c realTime field. Then for the rendering loop, something like: \code for (int i=0; i < NRFRAMES; i++) { // [...reposition camera here, if necessary...] // render offscreenrend->render(root); // dump to file SbString framefile; framefile.sprintf("frame%06d.rgb", i); offscreenrend->writeToRGB(framefile.getString()); // advance "current time" by the frames-per-second value, which // is 24 fps in this example realtime->setValue(realtime.getValue() + 1/24.0); } \endcode When making movies you need to write your application control code to take care of moving the camera along the correct trajectory yourself, and to explicitly control the global \c realTime field. The latter is so you're able to "step" with appropriate time units for each render operation (e.g. if you want a movie that has a 24 FPS refresh rate, first render with \c realTime=0.0, then add 1/24s to the \c realTime field, render again to a new frame, add another 1/24s to the \c realTime field, render, and so on). For further information about how to control the \c realTime field, see documentation of SoDB::getGlobalField(), SoDB::enableRealTimeSensor(), and SoSceneManager::enableRealTimeUpdate(). */ // As first mentioned to me by kyrah, the functionality of this class // should really have been outside the core Coin library, seeing how // it makes heavy use of window-system specifics. To be SGI Inventor // compatible we need it to be part of the Coin API, though. // // mortene. // ************************************************************************* // FIXME: we don't set up and render to RGBA-capable OpenGL-contexts, // even when the requested format from the app-programmer is // RGBA. // // I think this is what we should do: // // 1) first, try to get hold of a p-buffer with destination // alpha (p-buffers are faster to render into, as they can take // advantage of hardware acceleration) // // 2) failing that, try to make WGL (or GLX or AGL on // non-MSWindows platforms) set up a buffer with destination // alpha // // 3) failing that, get hold of either a p-buffer or a straight // WGL buffer with only RGB (no destination alpha -- this // should never fail), and do post-processing on the rendered // scene pixel-by-pixel to convert it into an RGBA texture // // 20020604 mortene. // // UPDATE 20041111 mortene: TGS Inventor has a new set of classes, // e.g. "SoGLGraphicConfigTemplate", which makes it possible to set up // wanted attributes with GL contexts. Audit their interface and // implement, if well designed. // ************************************************************************* #ifdef HAVE_CONFIG_H #include #endif // HAVE_CONFIG_H #include #include // memset(), memcpy() #include // for ceil() #include // SHRT_MAX #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // COIN_STUB() // ************************************************************************* #include "CoinOffscreenGLCanvas.h" #ifdef HAVE_GLX #include "SoOffscreenGLXData.h" #endif // HAVE_GLX #ifdef HAVE_AGL #include "SoOffscreenAGLData.h" #endif // HAVE_AGL #ifdef HAVE_WGL #include "SoOffscreenWGLData.h" #endif // HAVE_WGL // ************************************************************************* /*! \enum SoOffscreenRenderer::Components Enumerated values for the available image formats. \sa setComponents() */ // ************************************************************************* class SoOffscreenRendererP { public: SoOffscreenRendererP(SoOffscreenRenderer * masterptr, const SbViewportRegion & vpr, SoGLRenderAction * glrenderaction = NULL) { this->master = masterptr; this->backgroundcolor.setValue(0,0,0); this->components = SoOffscreenRenderer::RGB; this->buffer = NULL; this->bufferbytesize = 0; this->lastnodewasacamera = FALSE; if (glrenderaction) { this->renderaction = glrenderaction; } else { this->renderaction = new SoGLRenderAction(vpr); this->renderaction->setCacheContext(SoGLCacheContextElement::getUniqueCacheContext()); this->renderaction->setTransparencyType(SoGLRenderAction::SORTED_OBJECT_BLEND); } this->didallocation = glrenderaction ? FALSE : TRUE; this->viewport = vpr; } ~SoOffscreenRendererP() { if (this->didallocation) { delete this->renderaction; } } static SbBool offscreenContextsNotSupported(void); static const char * debugTileOutputPrefix(void); static SoGLRenderAction::AbortCode GLRenderAbortCallback(void *userData); SbBool renderFromBase(SoBase * base); void setCameraViewvolForTile(SoCamera * cam); static SbBool writeToRGB(FILE * fp, unsigned int w, unsigned int h, unsigned int nrcomponents, const uint8_t * imgbuf); SbViewportRegion viewport; SbColor backgroundcolor; SoOffscreenRenderer::Components components; SoGLRenderAction * renderaction; SbBool didallocation; unsigned char * buffer; size_t bufferbytesize; CoinOffscreenGLCanvas glcanvas; unsigned int glcanvassize[2]; int numsubscreens[2]; // The subscreen size of the current tile. (Less than max if it's a // right- or bottom-border tile.) unsigned int subsize[2]; // Keeps track of the current tile to be rendered. SbVec2s currenttile; SbBool lastnodewasacamera; SoCamera * visitedcamera; private: SoOffscreenRenderer * master; }; #define PRIVATE(p) (p->pimpl) #define PUBLIC(p) (p->master) // ************************************************************************* // Set the environment variable below to get the individual tiles // written out for debugging purposes. E.g. // // $ export COIN_DEBUG_SOOFFSCREENRENDERER_TILEPREFIX="/tmp/offscreentile_" // // Tile X and Y position, plus the ".rgb" suffix, will be added when // writing. const char * SoOffscreenRendererP::debugTileOutputPrefix(void) { return coin_getenv("COIN_DEBUG_SOOFFSCREENRENDERER_TILEPREFIX"); } // ************************************************************************* /*! Constructor. Argument is the \a viewportregion we should use when rendering. An internal SoGLRenderAction will be constructed. */ SoOffscreenRenderer::SoOffscreenRenderer(const SbViewportRegion & viewportregion) { PRIVATE(this) = new SoOffscreenRendererP(this, viewportregion); } /*! Constructor. Argument is the \a action we should apply to the scene graph when rendering the scene. Information about the viewport is extracted from the \a action. */ SoOffscreenRenderer::SoOffscreenRenderer(SoGLRenderAction * action) { PRIVATE(this) = new SoOffscreenRendererP(this, action->getViewportRegion(), action); } /*! Destructor. */ SoOffscreenRenderer::~SoOffscreenRenderer() { delete[] PRIVATE(this)->buffer; delete PRIVATE(this); } /*! Returns the screen pixels per inch resolution of your monitor. */ float SoOffscreenRenderer::getScreenPixelsPerInch(void) { SbVec2f pixmmres(72.0f / 25.4f, 72.0f / 25.4f); #ifdef HAVE_GLX pixmmres = SoOffscreenGLXData::getResolution(); #elif defined(HAVE_WGL) pixmmres = SoOffscreenWGLData::getResolution(); #elif defined(HAVE_AGL) pixmmres = SoOffscreenAGLData::getResolution(); #endif // HAVE_AGL // The API-signature of this method is not what it should be: it // assumes the same resolution in the vertical and horizontal // directions. float pixprmm = (pixmmres[0] + pixmmres[1]) / 2.0f; // find average return pixprmm * 25.4f; // an inch is 25.4 mm. } /*! Get maximum dimensions (width, height) of the offscreen buffer. Note that from Coin version 2 onwards, the returned value will always be (\c SHRT_MAX, \c SHRT_MAX), where \c SHRT_MAX on most systems is equal to 32767. This because the SoOffscreenRenderer can in principle generate unlimited size offscreen canvases by tiling together multiple renderings of the same scene. */ SbVec2s SoOffscreenRenderer::getMaximumResolution(void) { return SbVec2s(SHRT_MAX, SHRT_MAX); } /*! Sets the component format of the offscreen buffer. If set to \c LUMINANCE, a grayscale image is rendered, \c LUMINANCE_TRANSPARENCY gives us a grayscale image with transparency, \c RGB will give us a 24-bit image with 8 bits each for the red, green and blue component, and \c RGB_TRANSPARENCY yields a 32-bit image (\c RGB plus transparency). The default format to render to is \c RGB. This will invalidate the current buffer, if any. The buffer will not contain valid data until another call to SoOffscreenRenderer::render() happens. */ void SoOffscreenRenderer::setComponents(const Components components) { PRIVATE(this)->components = components; } /*! Returns the component format of the offscreen buffer. \sa setComponents() */ SoOffscreenRenderer::Components SoOffscreenRenderer::getComponents(void) const { return PRIVATE(this)->components; } /*! Sets the viewport region. This will invalidate the current buffer, if any. The buffer will not contain valid data until another call to SoOffscreenRenderer::render() happens. */ void SoOffscreenRenderer::setViewportRegion(const SbViewportRegion & region) { PRIVATE(this)->viewport = region; } /*! Returns the viewerport region. */ const SbViewportRegion & SoOffscreenRenderer::getViewportRegion(void) const { return PRIVATE(this)->viewport; } /*! Sets the background color. The buffer is cleared to this color before rendering. */ void SoOffscreenRenderer::setBackgroundColor(const SbColor & color) { PRIVATE(this)->backgroundcolor = color; } /*! Returns the background color. */ const SbColor & SoOffscreenRenderer::getBackgroundColor(void) const { return PRIVATE(this)->backgroundcolor; } /*! Sets the render action. Use this if you have special rendering needs. */ void SoOffscreenRenderer::setGLRenderAction(SoGLRenderAction * action) { if (action == PRIVATE(this)->renderaction) { return; } if (PRIVATE(this)->didallocation) { delete PRIVATE(this)->renderaction; } PRIVATE(this)->renderaction = action; PRIVATE(this)->didallocation = FALSE; } /*! Returns the rendering action currently used. */ SoGLRenderAction * SoOffscreenRenderer::getGLRenderAction(void) const { return PRIVATE(this)->renderaction; } // ************************************************************************* static void pre_render_cb(void * userdata, SoGLRenderAction * action) { glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT); action->setRenderingIsRemote(FALSE); } // ************************************************************************* // Callback when rendering scenegraph to subscreens. Detects when a // camera has just been traversed, and then invokes the method which // narrows the camera viewport according to the current tile we're // rendering to. // // FIXME: if possible, it would be better to pick up from the state // whatever data we're now grabbing directly from the SoCamera nodes. // It'd be more robust, I believe, as the elements set by SoCamera can // in principle also be set from other code. 20041006 mortene. // // UPDATE 20050711 mortene: on how to fix this properly, see item #121 // in Coin/BUGS.txt. SoGLRenderAction::AbortCode SoOffscreenRendererP::GLRenderAbortCallback(void *userData) { SoOffscreenRendererP * thisp = (SoOffscreenRendererP *) userData; const SoFullPath * path = (const SoFullPath*) thisp->renderaction->getCurPath(); SoNode * node = path->getTail(); assert(node); if (thisp->lastnodewasacamera) { thisp->setCameraViewvolForTile(thisp->visitedcamera); thisp->lastnodewasacamera = FALSE; } if (node->isOfType(SoCamera::getClassTypeId())) { thisp->visitedcamera = (SoCamera *) node; thisp->lastnodewasacamera = TRUE; // FIXME: this is not really entirely sufficient. If a camera is // already within a cached list upon the first invocation of a // render pass, we'll never get a callback upon encountering it. // // This would be a fairly obscure case, though, as the glcache // would have to be set up in another context, compatible for // sharing GL data with the one set up internally by the // SoOffscreenRenderer -- which is very unlikely. // // 20050512 mortene. // // UPDATE 20050711 mortene: on how to fix this properly, see item // #121 in Coin/BUGS.txt. (The tile number should be in an // element, which the SoCamera would query (and thereby also make // the cache dependent on)). SoCacheElement::invalidate(thisp->renderaction->getState()); } return SoGLRenderAction::CONTINUE; } // Collects common code from the two render() functions. SbBool SoOffscreenRendererP::renderFromBase(SoBase * base) { if (SoOffscreenRendererP::offscreenContextsNotSupported()) { static SbBool first = TRUE; if (first) { SoDebugError::post("SoOffscreenRenderer::renderFromBase", "SoOffscreenRenderer not compiled against any " "window-system binding, it is defunct for this build."); first = FALSE; } return FALSE; } const SbVec2s fullsize = this->viewport.getViewportSizePixels(); this->glcanvas.setWantedSize(fullsize); const uint32_t newcontext = this->glcanvas.activateGLContext(); if (newcontext == 0) { SoDebugError::postWarning("SoOffscreenRenderer::renderFromBase", "Could not set up an offscreen OpenGL context."); return FALSE; } const SbVec2s glsize = this->glcanvas.getActualSize(); // We need to know the actual GL viewport size for tiled rendering, // in calculations when narrowing the camera view volume -- so we // store away this value for the "found a camera"-callback. // // FIXME: seems unnecessary now, should be able to just query // glcanvas.getActualSize() XXX this->glcanvassize[0] = glsize[0]; this->glcanvassize[1] = glsize[1]; if (CoinOffscreenGLCanvas::debug()) { SoDebugError::postInfo("SoOffscreenRendererP::renderFromBase", "fullsize==<%d, %d>, glsize==<%d, %d>", fullsize[0], fullsize[1], glsize[0], glsize[1]); } // oldcontext is used to restore the previous context id, in case // the render action is not allocated by us. const uint32_t oldcontext = this->renderaction->getCacheContext(); this->renderaction->setCacheContext(newcontext); if (CoinOffscreenGLCanvas::debug()) { GLint colbits[4]; glGetIntegerv(GL_RED_BITS, &colbits[0]); glGetIntegerv(GL_GREEN_BITS, &colbits[1]); glGetIntegerv(GL_BLUE_BITS, &colbits[2]); glGetIntegerv(GL_ALPHA_BITS, &colbits[3]); SoDebugError::postInfo("SoOffscreenRenderer::renderFromBase", "GL context GL_[RED|GREEN|BLUE|ALPHA]_BITS==" "[%d, %d, %d, %d]", colbits[0], colbits[1], colbits[2], colbits[3]); } glEnable(GL_DEPTH_TEST); glClearColor(this->backgroundcolor[0], this->backgroundcolor[1], this->backgroundcolor[2], 0.0f); // Make this large to get best possible quality on any "big-image" // textures (from using SoTextureScalePolicy). // // FIXME: this doesn't seem to be working, according to a report by // Colin Dunlop. See bug item #108. 20050509 mortene. // // UPDATE 20050711 mortene: the bug report referred to above may not // be correct. We should anyway fix this in a more appropriate // manner, for instance by setting up a new element with a boolean // value to indicate whether or not stuff should be rendered in // maximum quality. That would be generally useful for having better // control from the offscreenrenderer. const int bigimagechangelimit = SoGLBigImage::setChangeLimit(INT_MAX); // Deallocate old and allocate new target buffer, if necessary. // // If we need more space: const size_t bufsize = fullsize[0] * fullsize[1] * PUBLIC(this)->getComponents(); SbBool alloc = (bufsize > this->bufferbytesize); // or if old buffer was much larger, free up the memory by fitting // to smaller size: alloc = alloc || (bufsize <= (this->bufferbytesize / 8)); if (alloc) { delete[] this->buffer; this->buffer = new unsigned char[bufsize]; this->bufferbytesize = bufsize; } if (SoOffscreenRendererP::debugTileOutputPrefix()) { (void)memset(this->buffer, 0x00, bufsize); } // needed to clear viewport after glViewport() is called from // SoGLRenderAction this->renderaction->addPreRenderCallback(pre_render_cb, NULL); // For debugging purposes, it has been made possible to use an // envvar to *force* tiled rendering even when it can be done in a // single chunk. // // (Note: don't use this envvar when using SoExtSelection nodes, for // the reason noted below.) static int forcetiled = -1; if (forcetiled == -1) { const char * env = coin_getenv("COIN_FORCE_TILED_OFFSCREENRENDERING"); forcetiled = (env && (atoi(env) > 0)) ? 1 : 0; if (forcetiled) { SoDebugError::postInfo("SoOffscreenRendererP::renderFromBase", "Forcing tiled rendering."); } } // FIXME: tiled rendering should be decided on the exact same // criteria as is used in SoExtSelection to decide which size to use // for its offscreen-buffer, as that node fails in VISIBLE_SHAPE // mode with tiled rendering. This is a weakness with SoExtSelection // which should be improved upon, if possible (i.e. fix // SoExtSelection, rather than adding some kind of "semi-private" // API to let SoExtSelection find out whether or not tiled rendering // is used). 20041028 mortene. const SbBool tiledrendering = forcetiled || (fullsize[0] > glsize[0]) || (fullsize[1] > glsize[1]); // Shall we use subscreen rendering or regular one-screen renderer? if (tiledrendering) { for (int i=0; i < 2; i++) { this->numsubscreens[i] = (fullsize[i] + (glsize[i] - 1)) / glsize[i]; } // We have to grab cameras using this callback during rendering this->visitedcamera = NULL; this->renderaction->setAbortCallback(SoOffscreenRendererP::GLRenderAbortCallback, this); // Render entire scenegraph for each subscreen. for (int y=0; y < this->numsubscreens[1]; y++) { for (int x=0; x < this->numsubscreens[0]; x++) { this->currenttile = SbVec2s(x, y); // Find current "active" tilesize. this->subsize[0] = glsize[0]; this->subsize[1] = glsize[1]; if (x == (this->numsubscreens[0] - 1)) { this->subsize[0] = fullsize[0] % glsize[0]; if (this->subsize[0] == 0) { this->subsize[0] = glsize[0]; } } if (y == (this->numsubscreens[1] - 1)) { this->subsize[1] = fullsize[1] % glsize[1]; if (this->subsize[1] == 0) { this->subsize[1] = glsize[1]; } } SbViewportRegion subviewport = SbViewportRegion(SbVec2s(this->subsize[0], this->subsize[1])); this->renderaction->setViewportRegion(subviewport); if (base->isOfType(SoNode::getClassTypeId())) this->renderaction->apply((SoNode *)base); else if (base->isOfType(SoPath::getClassTypeId())) this->renderaction->apply((SoPath *)base); else { assert(FALSE && "Cannot apply to anything else than an SoNode or an SoPath"); } const unsigned int nrcomp = PUBLIC(this)->getComponents(); const int MAINBUF_OFFSET = (glsize[1] * y * fullsize[0] + glsize[0] * x) * nrcomp; const SbVec2s vpsize = subviewport.getViewportSizePixels(); this->glcanvas.readPixels(this->buffer + MAINBUF_OFFSET, vpsize, fullsize[0], nrcomp); // Debug option to dump the (full) buffer after each // iteration. if (SoOffscreenRendererP::debugTileOutputPrefix()) { SbString s; s.sprintf("%s_%03d_%03d.rgb", SoOffscreenRendererP::debugTileOutputPrefix(), x, y); FILE * f = fopen(s.getString(), "wb"); assert(f); SbBool w = SoOffscreenRendererP::writeToRGB(f, fullsize[0], fullsize[1], nrcomp, this->buffer); assert(w); const int r = fclose(f); assert(r == 0); // This is sometimes useful to enable during debugging to // see the exact order and position of the tiles. Not // enabled by default because it makes the final buffer // completely blank. #if 0 // debug (void)memset(this->buffer, 0x00, bufsize); #endif // debug } } } this->renderaction->setAbortCallback(NULL, this); if (!this->visitedcamera) { SoDebugError::postWarning("SoOffscreenRenderer::renderFromBase", "No camera node found in scenegraph while rendering offscreen image. " "The result will most likely be incorrect."); } } // Regular, non-tiled rendering. else { this->renderaction->setViewportRegion(this->viewport); SbTime t = SbTime::getTimeOfDay(); // for profiling if (base->isOfType(SoNode::getClassTypeId())) this->renderaction->apply((SoNode *)base); else if (base->isOfType(SoPath::getClassTypeId())) this->renderaction->apply((SoPath *)base); else { assert(FALSE && "Cannot apply to anything else than an SoNode or an SoPath"); } if (CoinOffscreenGLCanvas::debug()) { SoDebugError::postInfo("SoOffscreenRendererP::renderFromBase", "*TIMING* SoGLRenderAction::apply() took %f msecs", (SbTime::getTimeOfDay() - t).getValue() * 1000); t = SbTime::getTimeOfDay(); } const SbVec2s dims = PUBLIC(this)->getViewportRegion().getViewportSizePixels(); this->glcanvas.readPixels(this->buffer, dims, dims[0], (unsigned int)PUBLIC(this)->getComponents()); if (CoinOffscreenGLCanvas::debug()) { SoDebugError::postInfo("SoOffscreenRendererP::renderFromBase", "*TIMING* glcanvas.readPixels() took %f msecs", (SbTime::getTimeOfDay() - t).getValue() * 1000); } } this->renderaction->removePreRenderCallback(pre_render_cb, NULL); // Restore old value. (void)SoGLBigImage::setChangeLimit(bigimagechangelimit); this->glcanvas.deactivateGLContext(); this->renderaction->setCacheContext(oldcontext); // restore old return TRUE; } /*! Render the scenegraph rooted at \a scene into our internal pixel buffer. Important note: make sure you pass in a \a scene node pointer which has both a camera and at least one lightsource below it -- otherwise you are likely to end up with just a blank or black image buffer. This mistake is easily made if you use an SoOffscreenRenderer on a scenegraph from one of the standard viewer components, as you will often just leave the addition of a camera and a headlight lightsource to the viewer to set up. This camera and lightsource are then part of the viewer's private "super-graph" outside of the scope of the scenegraph passed in by the application programmer. To make sure the complete scenegraph (including the viewer's "private parts" (*snicker*)) are passed to this method, you can get the scenegraph root from the viewer's internal SoSceneManager instance instead of from the viewer's own getSceneGraph() method, like this: \code SoOffscreenRenderer * myRenderer = new SoOffscreenRenderer(vpregion); SoNode * root = myViewer->getSceneManager()->getSceneGraph(); SbBool ok = myRenderer->render(root); // [then use image buffer in a texture, or write it to file, or whatever] \endcode If you do this and still get a blank buffer, another common problem is to have a camera which is not actually pointing at the scene geometry you want a snapshot of. If you suspect that could be the cause of problems on your end, take a look at SoCamera::pointAt() and SoCamera::viewAll() to see how you can make a camera node guaranteed to be directed at the scene geometry. Yet another common mistake when setting up the camera is to specify values for the SoCamera::nearDistance and SoCamera::farDistance fields which doesn't not enclose the full scene. This will result in either just the background color, or that parts at the front or the back of the scene will not be visible in the rendering. \sa writeToRGB() */ SbBool SoOffscreenRenderer::render(SoNode * scene) { return PRIVATE(this)->renderFromBase(scene); } /*! Render the \a scene path into our internal memory buffer. */ SbBool SoOffscreenRenderer::render(SoPath * scene) { return PRIVATE(this)->renderFromBase(scene); } /*! Returns the offscreen memory buffer. */ unsigned char * SoOffscreenRenderer::getBuffer(void) const { return PRIVATE(this)->buffer; } // // avoid endian problems (little endian sucks, right? :) // static size_t write_short(FILE * fp, unsigned short val) { unsigned char tmp[2]; tmp[0] = (unsigned char)(val >> 8); tmp[1] = (unsigned char)(val & 0xff); return fwrite(&tmp, 2, 1, fp); } SbBool SoOffscreenRendererP::writeToRGB(FILE * fp, unsigned int w, unsigned int h, unsigned int nrcomponents, const uint8_t * imgbuf) { // FIXME: add code to rle rows, pederb 2000-01-10 (void)write_short(fp, 0x01da); // imagic (void)write_short(fp, 0x0001); // raw (no rle yet) if (nrcomponents == 1) (void)write_short(fp, 0x0002); // 2 dimensions (heightmap) else (void)write_short(fp, 0x0003); // 3 dimensions (void)write_short(fp, (unsigned short) w); (void)write_short(fp, (unsigned short) h); (void)write_short(fp, (unsigned short) nrcomponents); unsigned char buf[500]; (void)memset(buf, 0, 500); buf[7] = 255; // set maximum pixel value to 255 strcpy((char *)buf+8, "http://www.coin3d.org"); fwrite(buf, 1, 500, fp); unsigned char * tmpbuf = new unsigned char[w]; SbBool writeok = TRUE; for (unsigned int c = 0; c < nrcomponents; c++) { for (unsigned int y = 0; y < h; y++) { for (unsigned int x = 0; x < w; x++) { tmpbuf[x] = imgbuf[(x + y * w) * nrcomponents + c]; } writeok = writeok && (fwrite(tmpbuf, 1, w, fp) == w); } } if (!writeok) { SoDebugError::postWarning("SoOffscreenRendererP::writeToRGB", "error when writing RGB file"); } delete [] tmpbuf; return writeok; } /*! Writes the buffer in SGI RGB format by appending it to the already open file. Returns \c FALSE if writing fails. Important note: do \e not use this method when the Coin library has been compiled as an MSWindows DLL, as passing FILE* instances back or forth to DLLs is dangerous and will most likely cause a crash. This is an intrinsic limitation for MSWindows DLLs. */ SbBool SoOffscreenRenderer::writeToRGB(FILE * fp) const { if (SoOffscreenRendererP::offscreenContextsNotSupported()) { return FALSE; } SbVec2s size = PRIVATE(this)->viewport.getViewportSizePixels(); return SoOffscreenRendererP::writeToRGB(fp, size[0], size[1], this->getComponents(), PRIVATE(this)->buffer); } /*! Opens a file with the given name and writes the offscreen buffer in SGI RGB format to the new file. If the file already exists, it will be overwritten (if permitted by the filesystem). Returns \c TRUE if all went ok, otherwise \c FALSE. */ SbBool SoOffscreenRenderer::writeToRGB(const char * filename) const { FILE * rgbfp = fopen(filename, "wb"); if (!rgbfp) { SoDebugError::postWarning("SoOffscreenRenderer::writeToRGB", "couldn't open file '%s'", filename); return FALSE; } SbBool result = this->writeToRGB(rgbfp); (void)fclose(rgbfp); return result; } /*! Writes the buffer in Postscript format by appending it to the already open file. Returns \c FALSE if writing fails. Important note: do \e not use this method when the Coin library has been compiled as an MSWindows DLL, as passing FILE* instances back or forth to DLLs is dangerous and will most likely cause a crash. This is an intrinsic limitation for MSWindows DLLs. */ SbBool SoOffscreenRenderer::writeToPostScript(FILE * fp) const { // just choose a page size of 8.5 x 11 inches (A4) return this->writeToPostScript(fp, SbVec2f(8.5f, 11.0f)); } /*! Opens a file with the given name and writes the offscreen buffer in Postscript format to the new file. If the file already exists, it will be overwritten (if permitted by the filesystem). Returns \c TRUE if all went ok, otherwise \c FALSE. */ SbBool SoOffscreenRenderer::writeToPostScript(const char * filename) const { FILE * psfp = fopen(filename, "wb"); if (!psfp) { SoDebugError::postWarning("SoOffscreenRenderer::writeToPostScript", "couldn't open file '%s'", filename); return FALSE; } SbBool result = this->writeToPostScript(psfp); (void)fclose(psfp); return result; } /*! Writes the buffer to a file in Postscript format, with \a printsize dimensions. Important note: do \e not use this method when the Coin library has been compiled as an MSWindows DLL, as passing FILE* instances back or forth to DLLs is dangerous and will most likely cause a crash. This is an intrinsic limitation for MSWindows DLLs. */ SbBool SoOffscreenRenderer::writeToPostScript(FILE * fp, const SbVec2f & printsize) const { if (SoOffscreenRendererP::offscreenContextsNotSupported()) { return FALSE;} const SbVec2s size = PRIVATE(this)->viewport.getViewportSizePixels(); const int nc = this->getComponents(); const float defaultdpi = 72.0f; // we scale against this value const float dpi = this->getScreenPixelsPerInch(); const SbVec2s pixelsize((short)(printsize[0]*defaultdpi), (short)(printsize[1]*defaultdpi)); const unsigned char * src = PRIVATE(this)->buffer; const int chan = nc <= 2 ? 1 : 3; const SbVec2s scaledsize((short) ceil(size[0]*defaultdpi/dpi), (short) ceil(size[1]*defaultdpi/dpi)); cc_string storedlocale; SbBool changed = coin_locale_set_portable(&storedlocale); fprintf(fp, "%%!PS-Adobe-2.0 EPSF-1.2\n"); fprintf(fp, "%%%%BoundingBox: 0 %d %d %d\n", pixelsize[1]-scaledsize[1], scaledsize[0], pixelsize[1]); fprintf(fp, "%%%%Creator: Coin \n"); fprintf(fp, "%%%%EndComments\n"); fprintf(fp, "\n"); fprintf(fp, "/origstate save def\n"); fprintf(fp, "\n"); fprintf(fp, "%% workaround for bug in some PS interpreters\n"); fprintf(fp, "%% which doesn't skip the ASCII85 EOD marker.\n"); fprintf(fp, "/~ {currentfile read pop pop} def\n\n"); fprintf(fp, "/image_wd %d def\n", size[0]); fprintf(fp, "/image_ht %d def\n", size[1]); fprintf(fp, "/pos_wd %d def\n", size[0]); fprintf(fp, "/pos_ht %d def\n", size[1]); fprintf(fp, "/image_dpi %g def\n", dpi); fprintf(fp, "/image_scale %g image_dpi div def\n", defaultdpi); fprintf(fp, "/image_chan %d def\n", chan); fprintf(fp, "/xpos_offset 0 image_scale mul def\n"); fprintf(fp, "/ypos_offset 0 image_scale mul def\n"); fprintf(fp, "/pix_buf_size %d def\n\n", size[0]*chan); fprintf(fp, "/page_ht %g %g mul def\n", printsize[1], defaultdpi); fprintf(fp, "/page_wd %g %g mul def\n", printsize[0], defaultdpi); fprintf(fp, "/image_xpos 0 def\n"); fprintf(fp, "/image_ypos page_ht pos_ht image_scale mul sub def\n"); fprintf(fp, "image_xpos xpos_offset add image_ypos ypos_offset add translate\n"); fprintf(fp, "\n"); fprintf(fp, "/pix pix_buf_size string def\n"); fprintf(fp, "image_wd image_scale mul image_ht image_scale mul scale\n"); fprintf(fp, "\n"); fprintf(fp, "image_wd image_ht 8\n"); fprintf(fp, "[image_wd 0 0 image_ht 0 0]\n"); fprintf(fp, "currentfile\n"); fprintf(fp, "/ASCII85Decode filter\n"); // fprintf(fp, "/RunLengthDecode filter\n"); // FIXME: add later. 2003???? pederb. if (chan == 3) fprintf(fp, "false 3\ncolorimage\n"); else fprintf(fp,"image\n"); const int rowlen = 72; int num = size[0] * size[1]; unsigned char tuple[4]; unsigned char linebuf[rowlen+5]; int tuplecnt = 0; int linecnt = 0; int cnt = 0; while (cnt < num) { switch (nc) { default: // avoid warning case 1: coin_output_ascii85(fp, src[cnt], tuple, linebuf, &tuplecnt, &linecnt, rowlen, FALSE); break; case 2: coin_output_ascii85(fp, src[cnt*2], tuple, linebuf, &tuplecnt, &linecnt, rowlen, FALSE); break; case 3: coin_output_ascii85(fp, src[cnt*3], tuple, linebuf, &tuplecnt, &linecnt, rowlen, FALSE); coin_output_ascii85(fp, src[cnt*3+1], tuple, linebuf, &tuplecnt, &linecnt, rowlen, FALSE); coin_output_ascii85(fp, src[cnt*3+2], tuple, linebuf, &tuplecnt, &linecnt, rowlen, FALSE); break; case 4: coin_output_ascii85(fp, src[cnt*4], tuple, linebuf, &tuplecnt, &linecnt, rowlen, FALSE); coin_output_ascii85(fp, src[cnt*4+1], tuple, linebuf, &tuplecnt, &linecnt,rowlen, FALSE); coin_output_ascii85(fp, src[cnt*4+2], tuple, linebuf, &tuplecnt, &linecnt, rowlen, FALSE); break; } cnt++; } // flush data in ascii85 encoder coin_flush_ascii85(fp, tuple, linebuf, &tuplecnt, &linecnt, rowlen); fprintf(fp, "~>\n\n"); // ASCII85 EOD marker fprintf(fp, "origstate restore\n"); fprintf(fp, "\n"); fprintf(fp, "%%%%Trailer\n"); fprintf(fp, "\n"); fprintf(fp, "%%%%EOF\n"); if (changed) { coin_locale_reset(&storedlocale); } return (SbBool) (ferror(fp) == 0); } /*! Opens a file with the given name and writes the offscreen buffer in Postscript format with \a printsize dimensions to the new file. If the file already exists, it will be overwritten (if permitted by the filesystem). Returns \c TRUE if all went ok, otherwise \c FALSE. */ SbBool SoOffscreenRenderer::writeToPostScript(const char * filename, const SbVec2f & printsize) const { FILE * psfp = fopen(filename, "wb"); if (!psfp) { SoDebugError::postWarning("SoOffscreenRenderer::writeToPostScript", "couldn't open file '%s'", filename); return FALSE; } SbBool result = this->writeToPostScript(psfp, printsize); (void)fclose(psfp); return result; } // FIXME: the file format support checking could have been done // better, for instance by using MIME types. Consider fixing the API // for later major releases. 20020206 mortene. // // UPDATE 20050711 mortene: it seems like TGS has extended their API // in an even worse way; by adding separate writeToJPEG(), // writeToPNG(), etc etc functions. /*! Returns \c TRUE if the buffer can be saved as a file of type \a filetypeextension, using SoOffscreenRenderer::writeToFile(). This function needs simage v1.1 or newer. Examples of possibly supported extensions are: "jpg", "png", "tiff", "gif", "bmp", etc. The extension match is not case sensitive. Which formats are \e actually supported depends on the capabilities of Coin's support library for handling import and export of pixel-data files: the simage library. If the simage library is not installed on your system, no extension output formats will be supported. Also, note that it is possible to build and install a simage library that lacks support for most or all of the file formats it is \e capable of supporting. This is so because the simage library depends on other, external 3rd party libraries -- in the same manner as Coin depends on the simage library for added file format support. The two built-in formats that are supported through the SoOffscreenRenderer::writeToRGB() and SoOffscreenRenderer::writeToPostScript() methods (for SGI RGB format and for Adobe Postscript files, respectively) are \e not considered by this method, as those two formats are guaranteed to \e always be supported through those functions. So if you want to be guaranteed to be able to export a screenshot in your wanted format, you will have to use either one of the above mentioned method for writing SGI RGB or Adobe Postscript directly, or make sure the Coin library has been built and is running on top of a version of the simage library (that you have preferably built yourself) with the file format(s) you want support for. This method is an extension versus the original SGI Open Inventor API. \sa getNumWriteFiletypes(), getWriteFiletypeInfo(), writeToFile() */ SbBool SoOffscreenRenderer::isWriteSupported(const SbName & filetypeextension) const { if (!simage_wrapper()->versionMatchesAtLeast(1,1,0)) { if (CoinOffscreenGLCanvas::debug()) { if (!simage_wrapper()->available) { SoDebugError::postInfo("SoOffscreenRenderer::isWriteSupported", "simage library not available."); } else { SoDebugError::postInfo("SoOffscreenRenderer::isWriteSupported", "You need simage v1.1 for this functionality."); } } return FALSE; } int ret = simage_wrapper()->simage_check_save_supported(filetypeextension.getString()); return ret ? TRUE : FALSE; } /*! Returns the number of available exporters. Detailed information about the exporters can then be found using getWriteFiletypeInfo(). See SoOffscreenRenderer::isWriteSupported() for information about which file formats you can expect to be present. Note that the two built-in export formats, SGI RGB and Adobe Postscript, are not counted. This method is an extension versus the original SGI Open Inventor API. \sa getWriteFiletypeInfo() */ int SoOffscreenRenderer::getNumWriteFiletypes(void) const { if (!simage_wrapper()->versionMatchesAtLeast(1,1,0)) { #if COIN_DEBUG SoDebugError::postInfo("SoOffscreenRenderer::getNumWriteFiletypes", "You need simage v1.1 for this functionality."); #endif // COIN_DEBUG return 0; } return simage_wrapper()->simage_get_num_savers(); } /*! Returns information about an image exporter. \a extlist is a list of filename extensions for a file format. E.g. for JPEG it is legal to use both jpg and jpeg. Extlist will contain const char * pointers (you need to cast the void * pointers to const char * before using them). \a fullname is the full name of the image format. \a description is an optional string with more information about the file format. See SoOffscreenRenderer::isWriteSupported() for information about which file formats you can expect to be present. This method is an extension versus the original SGI Open Inventor API. Here is a stand-alone, complete code example that shows how you can check exactly which output formats are supported: \code #include #include int main(int argc, char **argv) { SoDB::init(); SoOffscreenRenderer * r = new SoOffscreenRenderer(*(new SbViewportRegion)); int num = r->getNumWriteFiletypes(); if (num == 0) { (void)fprintf(stdout, "No image formats supported by the " "SoOffscreenRenderer except SGI RGB and Postscript.\n"); } else { for (int i=0; i < num; i++) { SbPList extlist; SbString fullname, description; r->getWriteFiletypeInfo(i, extlist, fullname, description); (void)fprintf(stdout, "%s: %s (extension%s: ", fullname.getString(), description.getString(), extlist.getLength() > 1 ? "s" : ""); for (int j=0; j < extlist.getLength(); j++) { (void)fprintf(stdout, "%s%s", j>0 ? ", " : "", (const char*) extlist[j]); } (void)fprintf(stdout, ")\n"); } } delete r; return 0; } \endcode \sa getNumWriteFiletypes(), writeToFile() \since Coin 2.3 */ void SoOffscreenRenderer::getWriteFiletypeInfo(const int idx, SbPList & extlist, SbString & fullname, SbString & description) { if (!simage_wrapper()->versionMatchesAtLeast(1,1,0)) { #if COIN_DEBUG SoDebugError::postInfo("SoOffscreenRenderer::getNumWriteFiletypes", "You need simage v1.1 for this functionality."); #endif // COIN_DEBUG return; } extlist.truncate(0); assert(idx >= 0 && idx < this->getNumWriteFiletypes()); void * saver = simage_wrapper()->simage_get_saver_handle(idx); SbString allext(simage_wrapper()->simage_get_saver_extensions(saver)); const char * start = allext.getString(); const char * curr = start; const char * end = strchr(curr, ','); while (end) { const ptrdiff_t offset_start = curr - start; const ptrdiff_t offset_end = end - start - 1; SbString ext = allext.getSubString((int)offset_start, (int)offset_end); SbName extname(ext.getString()); extlist.append((void*)extname.getString()); curr = end+1; end = strchr(curr, ','); } const ptrdiff_t offset = curr - start; SbString ext = allext.getSubString((int)offset); SbName extname(ext.getString()); extlist.append((void*)extname.getString()); const char * fullname_s = simage_wrapper()->simage_get_saver_fullname(saver); const char * desc_s = simage_wrapper()->simage_get_saver_description(saver); fullname = fullname_s ? SbString(fullname_s) : SbString(""); description = desc_s ? SbString(desc_s) : SbString(""); } /*! Saves the buffer to \a filename, in the filetype specified by \a filetypeextensions. Note that you must still specify the \e full \a filename for the first argument, i.e. the second argument will not automatically be attached to the filename -- it is only used to decide the filetype. This method is an extension versus the orignal SGI Open Inventor API. \sa isWriteSupported() */ SbBool SoOffscreenRenderer::writeToFile(const SbString & filename, const SbName & filetypeextension) const { // FIXME: shouldn't there be warnings on these two? 20050510 mortene. if (!simage_wrapper()->versionMatchesAtLeast(1,1,0)) { return FALSE; } if (SoOffscreenRendererP::offscreenContextsNotSupported()) { return FALSE; } SbVec2s size = PRIVATE(this)->viewport.getViewportSizePixels(); int comp = (int) this->getComponents(); unsigned char * bytes = PRIVATE(this)->buffer; int ret = simage_wrapper()->simage_save_image(filename.getString(), bytes, int(size[0]), int(size[1]), comp, filetypeextension.getString()); return ret ? TRUE : FALSE; } // FIXME: this should really be done by SoCamera, on the basis of data // from an "SoTileRenderingElement". See BUGS.txt, item #121. 20050712 mortene. void SoOffscreenRendererP::setCameraViewvolForTile(SoCamera * cam) { SoState * state = (PUBLIC(this)->getGLRenderAction())->getState(); // A small trick to change the aspect ratio without changing the // scenegraph camera. SbViewVolume vv; const float aspectratio = this->viewport.getViewportAspectRatio(); const SbVec2s vporigin = this->viewport.getViewportOriginPixels(); switch(cam->viewportMapping.getValue()) { case SoCamera::CROP_VIEWPORT_FILL_FRAME: case SoCamera::CROP_VIEWPORT_LINE_FRAME: case SoCamera::CROP_VIEWPORT_NO_FRAME: vv = cam->getViewVolume(0.0f); { // FIXME: should really fix this bug, not just warn that it is // there. See item #191 in Coin/BUGS.txt for more information. // 20050714 mortene. static SbBool first = TRUE; if (first) { SbString s; cam->viewportMapping.get(s); SoDebugError::postWarning("SoOffscreenRendererP::setCameraViewvolForTile", "The SoOffscreenRenderer does not yet work " "properly with the SoCamera::viewportMapping " "field set to '%s'", s.getString()); first = FALSE; } } break; case SoCamera::ADJUST_CAMERA: vv = cam->getViewVolume(aspectratio); if (aspectratio < 1.0f) vv.scale(1.0f / aspectratio); break; case SoCamera::LEAVE_ALONE: vv = cam->getViewVolume(0.0f); break; default: assert(0 && "unknown viewport mapping"); break; } const int LEFTINTPOS = (this->currenttile[0] * this->glcanvassize[0]) - vporigin[0]; const int RIGHTINTPOS = LEFTINTPOS + this->subsize[0]; const int TOPINTPOS = (this->currenttile[1] * this->glcanvassize[1]) - vporigin[1]; const int BOTTOMINTPOS = TOPINTPOS + this->subsize[1]; const SbVec2s fullsize = this->viewport.getViewportSizePixels(); const float left = float(LEFTINTPOS) / float(fullsize[0]); const float right = float(RIGHTINTPOS) / float(fullsize[0]); // Swap top / bottom, to flip the coordinate system for the Y axis // the way we want it. const float top = float(BOTTOMINTPOS) / float(fullsize[1]); const float bottom = float(TOPINTPOS) / float(fullsize[1]); if (CoinOffscreenGLCanvas::debug()) { SoDebugError::postInfo("SoOffscreenRendererP::setCameraViewvolForTile", "narrowing for tile <%d, %d>: <%f, %f> - <%f, %f>", this->currenttile[0], this->currenttile[1], left, bottom, right, top); } // Reshape view volume vv = vv.narrow(left, bottom, right, top); SbMatrix proj, affine; vv.getMatrices(affine, proj); // Support antialiasing if renderpasses > 1 if (renderaction->getNumPasses() > 1) { SbVec3f jittervec; SbMatrix m; const int vpsize[2] = { this->glcanvassize[0], this->glcanvassize[1] }; coin_viewvolume_jitter(renderaction->getNumPasses(), renderaction->getCurPass(), vpsize, (float *)jittervec.getValue()); m.setTranslate(jittervec); proj.multRight(m); } SoCullElement::setViewVolume(state, vv); SoViewVolumeElement::set(state, cam, vv); SoProjectionMatrixElement::set(state, cam, proj); SoViewingMatrixElement::set(state, cam, affine); } /*! \DANGEROUS_ALLOC_RETURN To avoid this potential problem, use the overloaded getWriteFiletypeInfo() function with an SbPList for the second argument instead. */ void SoOffscreenRenderer::getWriteFiletypeInfo(const int idx, SbList & extlist, SbString & fullname, SbString & description) { SoDebugError::postWarning("SoOffscreenRenderer::getWriteFiletypeInfo", "Obsoleted function. Use instead the overloaded " "method with an SbPList for the second argument."); if (!simage_wrapper()->versionMatchesAtLeast(1,1,0)) { #if COIN_DEBUG SoDebugError::postInfo("SoOffscreenRenderer::getWriteFiletypeInfo", "You need simage v1.1 for this functionality."); #endif // COIN_DEBUG return; } extlist.truncate(0); assert(idx >= 0 && idx < this->getNumWriteFiletypes()); void * saver = simage_wrapper()->simage_get_saver_handle(idx); SbString allext(simage_wrapper()->simage_get_saver_extensions(saver)); const char * start = allext.getString(); const char * curr = start; const char * end = strchr(curr, ','); while (end) { const ptrdiff_t offset_start = curr - start; const ptrdiff_t offset_end = end - start - 1; SbString ext = allext.getSubString((int)offset_start, (int)offset_end); extlist.append(SbName(ext.getString())); curr = end+1; end = strchr(curr, ','); } const ptrdiff_t offset = curr - start; SbString ext = allext.getSubString((int)offset); extlist.append(SbName(ext.getString())); const char * fullname_s = simage_wrapper()->simage_get_saver_fullname(saver); const char * desc_s = simage_wrapper()->simage_get_saver_description(saver); fullname = fullname_s ? SbString(fullname_s) : SbString(""); description = desc_s ? SbString(desc_s) : SbString(""); } // ************************************************************************* SbBool SoOffscreenRendererP::offscreenContextsNotSupported(void) { // Returning FALSE means that offscreen rendering seems to be // generally supported on the system. // // (It is however important to be robust and handle cases where it // still fails, as this can happen due to e.g. lack of resources or // other causes that may change during run-time.) #ifdef HAVE_GLX return FALSE; #elif defined(HAVE_WGL) return FALSE; #elif defined(HAVE_AGL) return FALSE; #endif // HAVE_AGL // No win-system GL binding was found, so we're sure that offscreen // rendering can *not* be done. return TRUE; } // ************************************************************************* #undef PRIVATE #undef PUBLIC