1 /**************************************************************************\
2  * Copyright (c) Kongsberg Oil & Gas Technologies AS
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  * Redistributions of source code must retain the above copyright notice,
10  * this list of conditions and the following disclaimer.
11  *
12  * Redistributions in binary form must reproduce the above copyright
13  * notice, this list of conditions and the following disclaimer in the
14  * documentation and/or other materials provided with the distribution.
15  *
16  * Neither the name of the copyright holder nor the names of its
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 \**************************************************************************/
32 
33 /*!
34   \class SoOffscreenRenderer SoOffscreenRenderer.h Inventor/SoOffscreenRenderer.h
35   \brief The SoOffscreenRenderer class is used for rendering scenes in offscreen buffers.
36 
37   \ingroup general
38 
39   If you want to render to a memory buffer instead of an on-screen
40   OpenGL context, use this class. Rendering to a memory buffer can be
41   used to generate texture maps on-the-fly, or for saving snapshots of
42   the scene to disk files (as pixel bitmaps or as Postscript files for
43   sending to a Postscript-capable printer).
44 
45   Here's a dead simple usage example, just the code directly related
46   to the SoOffscreenRenderer:
47 
48   \code
49   SoOffscreenRenderer myRenderer(vpregion);
50   SoNode * root = myViewer->getSceneManager()->getSceneGraph();
51   SbBool ok = myRenderer.render(root);
52   unsigned char * imgbuffer = myRenderer.getBuffer();
53   // [then use image buffer in a texture, or write it to file, or whatever]
54   \endcode
55 
56   And here a complete stand-alone example with a moving camera saving multiple
57   frames to disk as JPGs:
58 
59   \code
60   #include <Inventor/SoDB.h>
61   #include <Inventor/SoOffscreenRenderer.h>
62   #include <Inventor/engines/SoInterpolateVec3f.h>
63   #include <Inventor/nodes/SoCube.h>
64   #include <Inventor/nodes/SoDirectionalLight.h>
65   #include <Inventor/nodes/SoPerspectiveCamera.h>
66   #include <Inventor/nodes/SoSeparator.h>
67 
68   #include <iostream>
69 
70   int main()
71   {
72     // Init Coin
73     SoDB::init();
74 
75     // The root node
76     SoSeparator * root = new SoSeparator;
77     root->ref();
78 
79     // It is mandatory to have at least one light for the offscreen renderer
80     SoDirectionalLight * light = new SoDirectionalLight;
81     root->addChild(light);
82 
83     // It is mandatory to have at least one camera for the offscreen renderer
84     SoPerspectiveCamera * camera = new SoPerspectiveCamera;
85     SbRotation cameraRotation = SbRotation::identity();
86     cameraRotation *= SbRotation(SbVec3f(1, 0, 0), -0.4f);
87     cameraRotation *= SbRotation(SbVec3f(0, 1, 0), 0.4f);
88     camera->orientation = cameraRotation;
89     root->addChild(camera);
90 
91     // Something to show... A box
92     SoCube * cube = new SoCube;
93     root->addChild(cube);
94 
95     // Set up the two camera positions we want to move the camera between
96     SoInterpolateVec3f * interpolate = new SoInterpolateVec3f;
97     interpolate->input0 = SbVec3f(2, 2, 9);
98     interpolate->input1 = SbVec3f(2, 2, 5);
99     camera->position.connectFrom(&interpolate->output);
100 
101     // Set up the offscreen renderer
102     SbViewportRegion vpRegion(400, 300);
103     SoOffscreenRenderer offscreenRenderer(vpRegion);
104 
105     // How many frames to render for the video
106     int frames = 5;
107     std::cout << "Writing " << frames << " frames..." << std::endl;
108 
109     for (int i = 0; i < frames; i++) {
110       // Update the camera position
111       interpolate->alpha = float(i) / (frames - 1);
112 
113       // Render the scene
114       SbBool ok = offscreenRenderer.render(root);
115 
116       // Save the image to disk
117       SbString filename = SbString("coinvideo-") + (i + 1) + ".jpg";
118       if (ok) {
119         offscreenRenderer.writeToFile(filename.getString(), "jpg");
120       } else {
121         std::cout << "Error saving image: " << filename.getString() << std::endl;
122         break;
123       }
124     }
125 
126     std::cout << "Done!" << std::endl;
127 
128     root->unref();
129     return 0;
130   }
131   \endcode
132 
133 
134   Note that the SoOffscreenRenderer potentially allocates a fairly
135   large amount of resources, both OpenGL and general system resources,
136   for each instance. You will therefore be well adviced to try to
137   reuse SoOffscreenRenderer instances, instead of constructing and
138   destructing a new instance e.g. for each frame when generating
139   pictures for video.
140 
141   Offscreen rendering is internally done through either a GLX
142   offscreen context (i.e. OpenGL on X11), WGL (i.e. OpenGL on
143   Win32), AGL (old-style OpenGL on the Mac OS X) or CGL (new-style Mac OS X).
144 
145   If the OpenGL driver supports the pbuffer extension, it is detected
146   and used to provide hardware-accelerated offscreen rendering.
147 
148   The pixeldata is fetched from the OpenGL buffer with glReadPixels(),
149   with the format and type arguments set to GL_RGBA and
150   GL_UNSIGNED_BYTE, respectively. This means that the maximum
151   resolution is 32 bits, 8 bits for each of the R/G/B/A components.
152 
153 
154   One particular usage of the SoOffscreenRenderer is to make it render
155   frames to be used for the construction of movies. The general
156   technique for doing this is to iterate over the following actions:
157 
158   <ul>
159   <li>move camera to correct position for frame</li>
160   <li>update the \c realTime global field (see explanation below)</li>
161   <li>invoke the SoOffscreenRenderer</li>
162   <li>dump rendered scene to file</li>
163   </ul>
164 
165   ..then you use some external tool or library to construct the movie
166   file, for instance in MPEG format, from the set of files dumped to
167   disk from the iterative process above.
168 
169   The code would go something like the following (pseudo-code
170   style). First we need to stop the Coin library itself from doing any
171   automatic updating of the \c realTime field, so your application
172   initialization for Coin should look something like:
173 
174 
175   \code
176    [...] = SoQt::init([...]); // or SoWin::init() or SoDB::init()
177    // ..and then immediately:
178 
179    // Control realTime field ourselves, so animations within the scene
180    // follows "movie-time" and not "wallclock-time".
181    SoDB::enableRealTimeSensor(FALSE);
182    SoSceneManager::enableRealTimeUpdate(FALSE);
183    SoSFTime * realtime = SoDB::getGlobalField("realTime");
184    realtime->setValue(0.0);
185   \endcode
186 
187   Note that it is important that the \c realTime field is initialized
188   to \e your start-time \e before setting up any engines or other
189   entities in the system that uses the \c realTime field.
190 
191   Then for the rendering loop, something like:
192 
193   \code
194    for (int i=0; i < NRFRAMES; i++) {
195      // [...reposition camera here, if necessary...]
196 
197      // render
198      offscreenrend->render(root);
199 
200      // dump to file
201      SbString framefile;
202      framefile.sprintf("frame%06d.rgb", i);
203      offscreenrend->writeToRGB(framefile.getString());
204 
205      // advance "current time" by the frames-per-second value, which
206      // is 24 fps in this example
207      realtime->setValue(realtime.getValue() + 1/24.0);
208    }
209   \endcode
210 
211   When making movies you need to write your application control code
212   to take care of moving the camera along the correct trajectory
213   yourself, and to explicitly control the global \c realTime field.
214   The latter is so you're able to "step" with appropriate time units
215   for each render operation (e.g. if you want a movie that has a 24
216   FPS refresh rate, first render with \c realTime=0.0, then add 1/24s
217   to the \c realTime field, render again to a new frame, add another
218   1/24s to the \c realTime field, render, and so on).
219 
220   For further information about how to control the \c realTime field,
221   see documentation of SoDB::getGlobalField(),
222   SoDB::enableRealTimeSensor(), and
223   SoSceneManager::enableRealTimeUpdate().
224 
225   If you want to use this class to create snapshots of your current
226   viewer's view, but want to control the size of the snapshot, you
227   need to modify the camera a bit while rendering to be sure that
228   everything you see in the current view is visible in the snapshot.
229 
230   Below you'll find some pseude-code that does this. There are
231   probably other ways to do this as well.
232 
233   \code
234   void render_offscreen(const SbVec2s size)
235   {
236     SbVec2s glsize = this->getGLSize(); // size of your normal viewer
237     float glar = float(glsize[0] / float(glsize[1]));
238     float ar = float(size[0]) / float(size[1]);
239     SoCamera * camera = this->getCamera(); // the camera you're using
240     SoCamera::ViewportMapping oldmap = (SoCamera::ViewportMapping)
241       camera->viewportMapping.getValue();
242     float oldar = camera->aspectRatio.getValue();
243 
244     camera->viewportMapping = SoCamera::LEAVE_ALONE;
245     camera->aspectRatio = ar;
246 
247     float scaleheight = 1.0f;
248     if (glar > ar) {
249       scaleheight = glar / ar;
250       camera->scaleHeight(scaleheight);
251     }
252     else {
253       scaleheight = ar / glar;
254       camera->scaleHeight(scaleheight);
255     }
256     SoOffscreenRenderer * renderer = new SoOffscreenRenderer(size);
257     renderer->render(root);
258 
259     // ... save image
260 
261     // restore camera
262     camera->viewportMapping = oldmap;
263     camera->aspectRatio = oldar;
264 
265     if (scaleheight != 1.0f) {
266       camera->scaleHeight(1.0f / scaleheight);
267     }
268   }
269   \endcode
270 
271 */
272 
273 // As first mentioned to me by kyrah, the functionality of this class
274 // should really have been outside the core Coin library, seeing how
275 // it makes heavy use of window-system specifics. To be SGI Inventor
276 // compatible we need it to be part of the Coin API, though.
277 //
278 // mortene.
279 
280 // *************************************************************************
281 
282 // FIXME: we don't set up and render to RGBA-capable OpenGL-contexts,
283 // even when the requested format from the app-programmer is
284 // RGBA.
285 //
286 // I think this is what we should do:
287 //
288 //        1) first, try to get hold of a p-buffer with destination
289 //        alpha (p-buffers are faster to render into, as they can take
290 //        advantage of hardware acceleration)
291 //
292 //        2) failing that, try to make WGL/GLX/AGL/CGL set up a buffer
293 //        with destination alpha
294 //
295 //        3) failing that, get hold of either a p-buffer or a straight
296 //        WGL buffer with only RGB (no destination alpha -- this
297 //        should never fail), and do post-processing on the rendered
298 //        scene pixel-by-pixel to convert it into an RGBA texture
299 //
300 // 20020604 mortene.
301 //
302 // UPDATE 20041111 mortene: TGS Inventor has a new set of classes,
303 // e.g. "SoGLGraphicConfigTemplate", which makes it possible to set up
304 // wanted attributes with GL contexts. Audit their interface and
305 // implement, if well designed.
306 
307 // *************************************************************************
308 
309 #include <Inventor/SoOffscreenRenderer.h>
310 
311 #ifdef HAVE_CONFIG_H
312 #include "config.h"
313 #endif // HAVE_CONFIG_H
314 
315 #include <cassert>
316 #include <cstring> // memset(), memcpy()
317 #include <cmath> // for ceil()
318 #include <climits> // SHRT_MAX
319 
320 #include <Inventor/C/glue/gl.h>
321 #include <Inventor/C/tidbits.h>
322 #include <Inventor/SbMatrix.h>
323 #include <Inventor/SbVec2f.h>
324 #include <Inventor/SbViewportRegion.h>
325 #include <Inventor/SoPath.h>
326 #include <Inventor/actions/SoGLRenderAction.h>
327 #include <Inventor/elements/SoCullElement.h>
328 #include <Inventor/elements/SoGLCacheContextElement.h>
329 #include <Inventor/elements/SoModelMatrixElement.h>
330 #include <Inventor/elements/SoProjectionMatrixElement.h>
331 #include <Inventor/elements/SoViewVolumeElement.h>
332 #include <Inventor/elements/SoViewingMatrixElement.h>
333 #include <Inventor/elements/SoCacheElement.h>
334 #include <Inventor/errors/SoDebugError.h>
335 #include <Inventor/misc/SoContextHandler.h>
336 #include <Inventor/misc/SoGLBigImage.h>
337 #include <Inventor/nodes/SoCallback.h>
338 #include <Inventor/nodes/SoCamera.h>
339 #include <Inventor/nodes/SoNode.h>
340 #include <Inventor/system/gl.h>
341 #include <Inventor/SbTime.h>
342 
343 #include "glue/simage_wrapper.h"
344 #include "tidbitsp.h"
345 #include "coindefs.h" // COIN_STUB()
346 
347 #include <boost/current_function.hpp>
348 
349 // *************************************************************************
350 
351 #include "CoinOffscreenGLCanvas.h"
352 
353 #ifdef HAVE_GLX
354 #include "SoOffscreenGLXData.h"
355 #endif // HAVE_GLX
356 
357 #ifdef COIN_MACOS_10
358 #include "SoOffscreenCGData.h"
359 #endif // COIN_MACOS_10
360 
361 #ifdef HAVE_WGL
362 #include "SoOffscreenWGLData.h"
363 #endif // HAVE_WGL
364 
365 // *************************************************************************
366 
367 /*!
368   \enum SoOffscreenRenderer::Components
369 
370   Enumerated values for the available image formats.
371 
372   \sa setComponents()
373 */
374 
375 // *************************************************************************
376 
377 class SoOffscreenRendererP {
378 public:
SoOffscreenRendererP(SoOffscreenRenderer * masterptr,const SbViewportRegion & vpr,SoGLRenderAction * glrenderaction=NULL)379   SoOffscreenRendererP(SoOffscreenRenderer * masterptr,
380                        const SbViewportRegion & vpr,
381                        SoGLRenderAction * glrenderaction = NULL)
382   {
383     this->master = masterptr;
384     this->didreadbuffer = TRUE;
385 
386     this->backgroundcolor.setValue(0,0,0);
387     this->components = SoOffscreenRenderer::RGB;
388     this->buffer = NULL;
389     this->bufferbytesize = 0;
390     this->lastnodewasacamera = FALSE;
391 
392     if (glrenderaction) {
393       this->renderaction = glrenderaction;
394     }
395     else {
396       this->renderaction = new SoGLRenderAction(vpr);
397       this->renderaction->setCacheContext(SoGLCacheContextElement::getUniqueCacheContext());
398       this->renderaction->setTransparencyType(SoGLRenderAction::SORTED_OBJECT_BLEND);
399     }
400 
401     this->didallocation = glrenderaction ? FALSE : TRUE;
402     this->viewport = vpr;
403 	this->useDC = false;
404   }
405 
~SoOffscreenRendererP()406   ~SoOffscreenRendererP()
407   {
408     if (this->didallocation) { delete this->renderaction; }
409   }
410 
411   static SbBool offscreenContextsNotSupported(void);
412 
413   static const char * debugTileOutputPrefix(void);
414 
415   static SoGLRenderAction::AbortCode GLRenderAbortCallback(void *userData);
416   SbBool renderFromBase(SoBase * base);
417 
418   void setCameraViewvolForTile(SoCamera * cam);
419 
420   static SbBool writeToRGB(FILE * fp, unsigned int w, unsigned int h,
421                            unsigned int nrcomponents, const uint8_t * imgbuf);
422 
423   SbViewportRegion viewport;
424   SbColor backgroundcolor;
425   SoOffscreenRenderer::Components components;
426   SoGLRenderAction * renderaction;
427   SbBool didallocation;
428 
429   void updateDCBitmap();
430   SbBool useDC;
431 
432   unsigned char * buffer;
433   size_t bufferbytesize;
434 
435   CoinOffscreenGLCanvas glcanvas;
436   int glcanvassize[2];
437 
438   int numsubscreens[2];
439   // The subscreen size of the current tile. (Less than max if it's a
440   // right- or bottom-border tile.)
441   unsigned int subsize[2];
442   // Keeps track of the current tile to be rendered.
443   SbVec2s currenttile;
444 
445   SbBool lastnodewasacamera;
446   SoCamera * visitedcamera;
447 
448   // used for lazy readPixels()
449   SbBool didreadbuffer;
450 private:
451   SoOffscreenRenderer * master;
452 };
453 
454 #define PRIVATE(p) (p->pimpl)
455 #define PUBLIC(p) (p->master)
456 
457 // *************************************************************************
458 
459 // Set the environment variable below to get the individual tiles
460 // written out for debugging purposes. E.g.
461 //
462 //   $ export COIN_DEBUG_SOOFFSCREENRENDERER_TILEPREFIX="/tmp/offscreentile_"
463 //
464 // Tile X and Y position, plus the ".rgb" suffix, will be added when
465 // writing.
466 const char *
debugTileOutputPrefix(void)467 SoOffscreenRendererP::debugTileOutputPrefix(void)
468 {
469   return coin_getenv("COIN_DEBUG_SOOFFSCREENRENDERER_TILEPREFIX");
470 }
471 
472 // *************************************************************************
473 
474 /*!
475   Constructor. Argument is the \a viewportregion we should use when
476   rendering. An internal SoGLRenderAction will be constructed.
477 */
SoOffscreenRenderer(const SbViewportRegion & viewportregion)478 SoOffscreenRenderer::SoOffscreenRenderer(const SbViewportRegion & viewportregion)
479 {
480   PRIVATE(this) = new SoOffscreenRendererP(this, viewportregion);
481 }
482 
483 /*!
484   Constructor. Argument is the \a action we should apply to the
485   scene graph when rendering the scene. Information about the
486   viewport is extracted from the \a action.
487 */
SoOffscreenRenderer(SoGLRenderAction * action)488 SoOffscreenRenderer::SoOffscreenRenderer(SoGLRenderAction * action)
489 {
490   PRIVATE(this) = new SoOffscreenRendererP(this, action->getViewportRegion(),
491                                            action);
492 }
493 
494 /*!
495   Destructor.
496 */
~SoOffscreenRenderer()497 SoOffscreenRenderer::~SoOffscreenRenderer()
498 {
499   delete[] PRIVATE(this)->buffer;
500   delete PRIVATE(this);
501 }
502 
503 /*!
504   Returns the screen pixels per inch resolution of your monitor.
505 */
506 float
getScreenPixelsPerInch(void)507 SoOffscreenRenderer::getScreenPixelsPerInch(void)
508 {
509   SbVec2f pixmmres(72.0f / 25.4f, 72.0f / 25.4f);
510 #ifdef HAVE_GLX
511   pixmmres = SoOffscreenGLXData::getResolution();
512 #elif defined(HAVE_WGL)
513   pixmmres = SoOffscreenWGLData::getResolution();
514 #elif defined(COIN_MACOS_10)
515   pixmmres = SoOffscreenCGData::getResolution();
516 #endif // COIN_MACOS_10
517 
518   // The API-signature of this method is not what it should be: it
519   // assumes the same resolution in the vertical and horizontal
520   // directions.
521   float pixprmm = (pixmmres[0] + pixmmres[1]) / 2.0f; // find average
522 
523   return pixprmm * 25.4f; // an inch is 25.4 mm.
524 }
525 
526 /*!
527   Get maximum dimensions (width, height) of the offscreen buffer.
528 
529   Note that from Coin version 2 onwards, the returned value will
530   always be (\c SHRT_MAX, \c SHRT_MAX), where \c SHRT_MAX on most
531   systems is equal to 32767.
532 
533   This because the SoOffscreenRenderer can in principle generate
534   unlimited size offscreen canvases by tiling together multiple
535   renderings of the same scene.
536 */
537 SbVec2s
getMaximumResolution(void)538 SoOffscreenRenderer::getMaximumResolution(void)
539 {
540   return SbVec2s(SHRT_MAX, SHRT_MAX);
541 }
542 
543 /*!
544   Sets the component format of the offscreen buffer.
545 
546   If set to \c LUMINANCE, a grayscale image is rendered, \c
547   LUMINANCE_TRANSPARENCY gives us a grayscale image with transparency,
548   \c RGB will give us a 24-bit image with 8 bits each for the red,
549   green and blue component, and \c RGB_TRANSPARENCY yields a 32-bit
550   image (\c RGB plus transparency).
551 
552   The default format to render to is \c RGB.
553 
554   This will invalidate the current buffer, if any. The buffer will not
555   contain valid data until another call to
556   SoOffscreenRenderer::render() happens.
557 */
558 void
setComponents(const Components components)559 SoOffscreenRenderer::setComponents(const Components components)
560 {
561   PRIVATE(this)->components = components;
562 }
563 
564 /*!
565   Returns the component format of the offscreen buffer.
566 
567   \sa setComponents()
568  */
569 SoOffscreenRenderer::Components
getComponents(void) const570 SoOffscreenRenderer::getComponents(void) const
571 {
572   return PRIVATE(this)->components;
573 
574 }
575 
576 /*!
577   Sets the viewport region.
578 
579   This will invalidate the current buffer, if any. The buffer will not
580   contain valid data until another call to
581   SoOffscreenRenderer::render() happens.
582 */
583 void
setViewportRegion(const SbViewportRegion & region)584 SoOffscreenRenderer::setViewportRegion(const SbViewportRegion & region)
585 {
586   PRIVATE(this)->viewport = region;
587 }
588 
589 /*!
590   Returns the viewerport region.
591 */
592 const SbViewportRegion &
getViewportRegion(void) const593 SoOffscreenRenderer::getViewportRegion(void) const
594 {
595   return PRIVATE(this)->viewport;
596 }
597 
598 /*!
599   Sets the background color. The buffer is cleared to this color
600   before rendering.
601 */
602 void
setBackgroundColor(const SbColor & color)603 SoOffscreenRenderer::setBackgroundColor(const SbColor & color)
604 {
605   PRIVATE(this)->backgroundcolor = color;
606 }
607 
608 /*!
609   Returns the background color.
610 */
611 const SbColor &
getBackgroundColor(void) const612 SoOffscreenRenderer::getBackgroundColor(void) const
613 {
614   return PRIVATE(this)->backgroundcolor;
615 }
616 
617 /*!
618   Sets the render action. Use this if you have special rendering needs.
619 */
620 void
setGLRenderAction(SoGLRenderAction * action)621 SoOffscreenRenderer::setGLRenderAction(SoGLRenderAction * action)
622 {
623   if (action == PRIVATE(this)->renderaction) { return; }
624 
625   if (PRIVATE(this)->didallocation) { delete PRIVATE(this)->renderaction; }
626   PRIVATE(this)->renderaction = action;
627   PRIVATE(this)->didallocation = FALSE;
628 }
629 
630 /*!
631   Returns the rendering action currently used.
632 */
633 SoGLRenderAction *
getGLRenderAction(void) const634 SoOffscreenRenderer::getGLRenderAction(void) const
635 {
636   return PRIVATE(this)->renderaction;
637 }
638 
639 // *************************************************************************
640 
641 static void
pre_render_cb(void * COIN_UNUSED_ARG (userdata),SoGLRenderAction * action)642 pre_render_cb(void * COIN_UNUSED_ARG(userdata), SoGLRenderAction * action)
643 {
644   glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
645   action->setRenderingIsRemote(FALSE);
646 }
647 
648 // *************************************************************************
649 
650 // Callback when rendering scenegraph to subscreens. Detects when a
651 // camera has just been traversed, and then invokes the method which
652 // narrows the camera viewport according to the current tile we're
653 // rendering to.
654 //
655 // FIXME: if possible, it would be better to pick up from the state
656 // whatever data we're now grabbing directly from the SoCamera nodes.
657 // It'd be more robust, I believe, as the elements set by SoCamera can
658 // in principle also be set from other code. 20041006 mortene.
659 //
660 // UPDATE 20050711 mortene: on how to fix this properly, see item #121
661 // in Coin/BUGS.txt.
662 SoGLRenderAction::AbortCode
GLRenderAbortCallback(void * userData)663 SoOffscreenRendererP::GLRenderAbortCallback(void *userData)
664 {
665   SoOffscreenRendererP * thisp = (SoOffscreenRendererP *) userData;
666   const SoFullPath * path = (const SoFullPath*) thisp->renderaction->getCurPath();
667   SoNode * node = path->getTail();
668   assert(node);
669 
670   if (thisp->lastnodewasacamera) {
671     thisp->setCameraViewvolForTile(thisp->visitedcamera);
672     thisp->lastnodewasacamera = FALSE;
673   }
674 
675   if (node->isOfType(SoCamera::getClassTypeId())) {
676     thisp->visitedcamera = (SoCamera *) node;
677     thisp->lastnodewasacamera = TRUE;
678 
679     // FIXME: this is not really entirely sufficient. If a camera is
680     // already within a cached list upon the first invocation of a
681     // render pass, we'll never get a callback upon encountering it.
682     //
683     // This would be a fairly obscure case, though, as the glcache
684     // would have to be set up in another context, compatible for
685     // sharing GL data with the one set up internally by the
686     // SoOffscreenRenderer -- which is very unlikely.
687     //
688     // 20050512 mortene.
689     //
690     // UPDATE 20050711 mortene: on how to fix this properly, see item
691     // #121 in Coin/BUGS.txt. (The tile number should be in an
692     // element, which the SoCamera would query (and thereby also make
693     // the cache dependent on)).
694     SoCacheElement::invalidate(thisp->renderaction->getState());
695   }
696 
697   return SoGLRenderAction::CONTINUE;
698 }
699 
700 // Collects common code from the two render() functions.
701 SbBool
renderFromBase(SoBase * base)702 SoOffscreenRendererP::renderFromBase(SoBase * base)
703 {
704   if (SoOffscreenRendererP::offscreenContextsNotSupported()) {
705     static SbBool first = TRUE;
706     if (first) {
707       SoDebugError::post("SoOffscreenRenderer::renderFromBase",
708                          "SoOffscreenRenderer not compiled against any "
709                          "window-system binding, it is defunct for this build.");
710       first = FALSE;
711     }
712     return FALSE;
713   }
714 
715   const SbVec2s fullsize = this->viewport.getViewportSizePixels();
716   this->glcanvas.setWantedSize(fullsize);
717 
718   // check if no possible canvas size was found
719   if (this->glcanvas.getActualSize() == SbVec2s(0, 0)) { return FALSE; }
720 
721   const uint32_t newcontext = this->glcanvas.activateGLContext();
722   if (newcontext == 0) {
723     SoDebugError::postWarning("SoOffscreenRenderer::renderFromBase",
724                               "Could not set up an offscreen OpenGL context.");
725     return FALSE;
726   }
727 
728   const SbVec2s glsize = this->glcanvas.getActualSize();
729 
730   // We need to know the actual GL viewport size for tiled rendering,
731   // in calculations when narrowing the camera view volume -- so we
732   // store away this value for the "found a camera"-callback.
733   //
734   // FIXME: seems unnecessary now, should be able to just query
735   // glcanvas.getActualSize() XXX
736   this->glcanvassize[0] = glsize[0];
737   this->glcanvassize[1] = glsize[1];
738 
739   if (CoinOffscreenGLCanvas::debug()) {
740     SoDebugError::postInfo("SoOffscreenRendererP::renderFromBase",
741                            "fullsize==<%d, %d>, glsize==<%d, %d>",
742                            fullsize[0], fullsize[1], glsize[0], glsize[1]);
743   }
744 
745   // oldcontext is used to restore the previous context id, in case
746   // the render action is not allocated by us.
747   const uint32_t oldcontext = this->renderaction->getCacheContext();
748   this->renderaction->setCacheContext(newcontext);
749 
750   if (CoinOffscreenGLCanvas::debug()) {
751     GLint colbits[4];
752     glGetIntegerv(GL_RED_BITS, &colbits[0]);
753     glGetIntegerv(GL_GREEN_BITS, &colbits[1]);
754     glGetIntegerv(GL_BLUE_BITS, &colbits[2]);
755     glGetIntegerv(GL_ALPHA_BITS, &colbits[3]);
756     SoDebugError::postInfo("SoOffscreenRenderer::renderFromBase",
757                            "GL context GL_[RED|GREEN|BLUE|ALPHA]_BITS=="
758                            "[%d, %d, %d, %d]",
759                            colbits[0], colbits[1], colbits[2], colbits[3]);
760   }
761 
762   glEnable(GL_DEPTH_TEST);
763   glClearColor(this->backgroundcolor[0],
764                this->backgroundcolor[1],
765                this->backgroundcolor[2],
766                0.0f);
767 
768   // Make this large to get best possible quality on any "big-image"
769   // textures (from using SoTextureScalePolicy).
770   //
771   // FIXME: this doesn't seem to be working, according to a report by
772   // Colin Dunlop. See bug item #108. 20050509 mortene.
773   //
774   // UPDATE 20050711 mortene: the bug report referred to above may not
775   // be correct. We should anyway fix this in a more appropriate
776   // manner, for instance by setting up a new element with a boolean
777   // value to indicate whether or not stuff should be rendered in
778   // maximum quality. That would be generally useful for having better
779   // control from the offscreenrenderer.
780   const int bigimagechangelimit = SoGLBigImage::setChangeLimit(INT_MAX);
781 
782   // Deallocate old and allocate new target buffer, if necessary.
783   //
784   // If we need more space:
785   const size_t bufsize =
786     fullsize[0] * fullsize[1] * PUBLIC(this)->getComponents();
787   SbBool alloc = (bufsize > this->bufferbytesize);
788   // or if old buffer was much larger, free up the memory by fitting
789   // to smaller size:
790   alloc = alloc || (bufsize <= (this->bufferbytesize / 8));
791 
792   if (alloc) {
793     delete[] this->buffer;
794     this->buffer = new unsigned char[bufsize];
795     this->bufferbytesize = bufsize;
796   }
797 
798   if (SoOffscreenRendererP::debugTileOutputPrefix()) {
799     (void)memset(this->buffer, 0x00, bufsize);
800   }
801 
802   // needed to clear viewport after glViewport() is called from
803   // SoGLRenderAction
804   this->renderaction->addPreRenderCallback(pre_render_cb, NULL);
805 
806   // For debugging purposes, it has been made possible to use an
807   // envvar to *force* tiled rendering even when it can be done in a
808   // single chunk.
809   //
810   // (Note: don't use this envvar when using SoExtSelection nodes, for
811   // the reason noted below.)
812   static int forcetiled = -1;
813   if (forcetiled == -1) {
814     const char * env = coin_getenv("COIN_FORCE_TILED_OFFSCREENRENDERING");
815     forcetiled = (env && (atoi(env) > 0)) ? 1 : 0;
816     if (forcetiled) {
817       SoDebugError::postInfo("SoOffscreenRendererP::renderFromBase",
818                              "Forcing tiled rendering.");
819     }
820   }
821 
822   // FIXME: tiled rendering should be decided on the exact same
823   // criteria as is used in SoExtSelection to decide which size to use
824   // for its offscreen-buffer, as that node fails in VISIBLE_SHAPE
825   // mode with tiled rendering. This is a weakness with SoExtSelection
826   // which should be improved upon, if possible (i.e. fix
827   // SoExtSelection, rather than adding some kind of "semi-private"
828   // API to let SoExtSelection find out whether or not tiled rendering
829   // is used). 20041028 mortene.
830   const SbBool tiledrendering =
831     forcetiled || (fullsize[0] > glsize[0]) || (fullsize[1] > glsize[1]);
832 
833   // Shall we use subscreen rendering or regular one-screen renderer?
834   if (tiledrendering) {
835     // we need to copy from GL to system memory if we're doing tiled rendering
836     this->didreadbuffer = TRUE;
837 
838     for (int i=0; i < 2; i++) {
839       this->numsubscreens[i] = (fullsize[i] + (glsize[i] - 1)) / glsize[i];
840     }
841 
842     // We have to grab cameras using this callback during rendering
843     this->visitedcamera = NULL;
844     this->renderaction->setAbortCallback(SoOffscreenRendererP::GLRenderAbortCallback, this);
845 
846     // Render entire scenegraph for each subscreen.
847     for (int y=0; y < this->numsubscreens[1]; y++) {
848       for (int x=0; x < this->numsubscreens[0]; x++) {
849         this->currenttile = SbVec2s(x, y);
850 
851         // Find current "active" tilesize.
852         this->subsize[0] = glsize[0];
853         this->subsize[1] = glsize[1];
854         if (x == (this->numsubscreens[0] - 1)) {
855           this->subsize[0] = fullsize[0] % glsize[0];
856           if (this->subsize[0] == 0) { this->subsize[0] = glsize[0]; }
857         }
858         if (y == (this->numsubscreens[1] - 1)) {
859           this->subsize[1] = fullsize[1] % glsize[1];
860           if (this->subsize[1] == 0) { this->subsize[1] = glsize[1]; }
861         }
862 
863         SbViewportRegion subviewport = SbViewportRegion(SbVec2s(this->subsize[0], this->subsize[1]));
864         this->renderaction->setViewportRegion(subviewport);
865 
866         if (base->isOfType(SoNode::getClassTypeId()))
867           this->renderaction->apply((SoNode *)base);
868         else if (base->isOfType(SoPath::getClassTypeId()))
869           this->renderaction->apply((SoPath *)base);
870         else {
871           assert(FALSE && "Cannot apply to anything else than an SoNode or an SoPath");
872         }
873 
874         const unsigned int nrcomp = PUBLIC(this)->getComponents();
875 
876         const int MAINBUF_OFFSET =
877           (glsize[1] * y * fullsize[0] + glsize[0] * x) * nrcomp;
878 
879         const SbVec2s vpsize = subviewport.getViewportSizePixels();
880         this->glcanvas.readPixels(this->buffer + MAINBUF_OFFSET,
881                                   vpsize, fullsize[0], nrcomp);
882 
883         // Debug option to dump the (full) buffer after each
884         // iteration.
885         if (SoOffscreenRendererP::debugTileOutputPrefix()) {
886           SbString s;
887           s.sprintf("%s_%03d_%03d.rgb",
888                     SoOffscreenRendererP::debugTileOutputPrefix(), x, y);
889 
890           FILE * f = fopen(s.getString(), "wb");
891 		  if (f) {
892             SbBool w = SoOffscreenRendererP::writeToRGB(f, fullsize[0], fullsize[1],
893                                                         nrcomp, this->buffer);
894             assert(w);
895             const int r = fclose(f);
896             assert(r == 0);
897 		  }
898 
899           // This is sometimes useful to enable during debugging to
900           // see the exact order and position of the tiles. Not
901           // enabled by default because it makes the final buffer
902           // completely blank.
903 #if 0 // debug
904           (void)memset(this->buffer, 0x00, bufsize);
905 #endif // debug
906         }
907       }
908     }
909 
910     this->renderaction->setAbortCallback(NULL, this);
911 
912     if (!this->visitedcamera) {
913       SoDebugError::postWarning("SoOffscreenRenderer::renderFromBase",
914                                 "No camera node found in scenegraph while rendering offscreen image. "
915                                 "The result will most likely be incorrect.");
916     }
917 
918   }
919   // Regular, non-tiled rendering.
920   else {
921     // do lazy buffer read (GL context is read in getBuffer())
922     this->didreadbuffer = FALSE;
923 
924 	SbViewportRegion region;
925 
926 	region.setViewportPixels(0,0,fullsize[0],fullsize[1]);
927 
928     this->renderaction->setViewportRegion(region);
929 
930     SbTime t = SbTime::getTimeOfDay(); // for profiling
931 
932     if (base->isOfType(SoNode::getClassTypeId()))
933       this->renderaction->apply((SoNode *)base);
934     else if (base->isOfType(SoPath::getClassTypeId()))
935       this->renderaction->apply((SoPath *)base);
936     else  {
937       assert(FALSE && "Cannot apply to anything else than an SoNode or an SoPath");
938     }
939 
940     if (CoinOffscreenGLCanvas::debug()) {
941       SoDebugError::postInfo("SoOffscreenRendererP::renderFromBase",
942                              "*TIMING* SoGLRenderAction::apply() took %f msecs",
943                              (SbTime::getTimeOfDay() - t).getValue() * 1000);
944       t = SbTime::getTimeOfDay();
945     }
946 
947     if (CoinOffscreenGLCanvas::debug()) {
948       SoDebugError::postInfo("SoOffscreenRendererP::renderFromBase",
949                              "*TIMING* glcanvas.readPixels() took %f msecs",
950                              (SbTime::getTimeOfDay() - t).getValue() * 1000);
951     }
952   }
953 
954   this->renderaction->removePreRenderCallback(pre_render_cb, NULL);
955 
956   // Restore old value.
957   (void)SoGLBigImage::setChangeLimit(bigimagechangelimit);
958 
959   this->glcanvas.deactivateGLContext();
960   this->renderaction->setCacheContext(oldcontext); // restore old
961 
962   if(this->useDC)
963 	this->updateDCBitmap();
964 
965   return TRUE;
966 }
967 
968 /*!
969   Render the scenegraph rooted at \a scene into our internal pixel
970   buffer.
971 
972 
973   Important note: make sure you pass in a \a scene node pointer which
974   has both a camera and at least one lightsource below it -- otherwise
975   you are likely to end up with just a blank or black image buffer.
976 
977   This mistake is easily made if you use an SoOffscreenRenderer on a
978   scenegraph from one of the standard viewer components, as you will
979   often just leave the addition of a camera and a headlight
980   lightsource to the viewer to set up. This camera and lightsource are
981   then part of the viewer's private "super-graph" outside of the scope
982   of the scenegraph passed in by the application programmer. To make
983   sure the complete scenegraph (including the viewer's "private parts"
984   (*snicker*)) are passed to this method, you can get the scenegraph
985   root from the viewer's internal SoSceneManager instance instead of
986   from the viewer's own getSceneGraph() method, like this:
987 
988   \code
989   SoOffscreenRenderer * myRenderer = new SoOffscreenRenderer(vpregion);
990   SoNode * root = myViewer->getSceneManager()->getSceneGraph();
991   SbBool ok = myRenderer->render(root);
992   // [then use image buffer in a texture, or write it to file, or whatever]
993   \endcode
994 
995   If you do this and still get a blank buffer, another common problem
996   is to have a camera which is not actually pointing at the scene
997   geometry you want a snapshot of. If you suspect that could be the
998   cause of problems on your end, take a look at SoCamera::pointAt()
999   and SoCamera::viewAll() to see how you can make a camera node
1000   guaranteed to be directed at the scene geometry.
1001 
1002   Yet another common mistake when setting up the camera is to specify
1003   values for the SoCamera::nearDistance and SoCamera::farDistance
1004   fields which doesn't not enclose the full scene. This will result in
1005   either just the background color, or that parts at the front or the
1006   back of the scene will not be visible in the rendering.
1007 
1008   \sa writeToRGB()
1009 */
1010 SbBool
render(SoNode * scene)1011 SoOffscreenRenderer::render(SoNode * scene)
1012 {
1013   return PRIVATE(this)->renderFromBase(scene);
1014 }
1015 
1016 /*!
1017   Render the \a scene path into our internal memory buffer.
1018 */
1019 SbBool
render(SoPath * scene)1020 SoOffscreenRenderer::render(SoPath * scene)
1021 {
1022   return PRIVATE(this)->renderFromBase(scene);
1023 }
1024 
1025 // *************************************************************************
1026 
1027 /*!
1028   Returns the offscreen memory buffer.
1029 */
1030 unsigned char *
getBuffer(void) const1031 SoOffscreenRenderer::getBuffer(void) const
1032 {
1033   if (!PRIVATE(this)->didreadbuffer) {
1034     const SbVec2s dims = this->getViewportRegion().getViewportSizePixels();
1035     //fprintf(stderr,"reading pixels: %d %d\n", dims[0], dims[1]);
1036 
1037     PRIVATE(this)->glcanvas.activateGLContext();
1038     PRIVATE(this)->glcanvas.readPixels(PRIVATE(this)->buffer, dims, dims[0],
1039                                        (unsigned int) this->getComponents());
1040     PRIVATE(this)->glcanvas.deactivateGLContext();
1041     PRIVATE(this)->didreadbuffer = TRUE;
1042   }
1043   return PRIVATE(this)->buffer;
1044 }
1045 
1046 /*!
1047   Win32 only:
1048 
1049   returns a direct handle to the internal DC of the offscreen
1050   context.
1051 
1052   Useful for efficient access to the raw image under certain special
1053   circumstances. getBuffer() might be too slow, for instance due to
1054   pixel format conversion (Windows DCs are usually BGRA, while the
1055   32-bit buffers returned from getBuffer() are RGBA).
1056 
1057   Notes:
1058 
1059   The return value is a reference to a HDC. The HDC typedef has been
1060   unwound to a native C++ type for multiplatform compatibility
1061   reasons.
1062 
1063   Returned reference will contain a NULL value on other platforms.
1064 
1065   Important limitation: if the current dimensions of the
1066   SoOffscreenRenderer instance are larger than what can be rendered
1067   with a single offscreen buffer, tiling will be used by the
1068   SoOffscreenRenderer, and the returned HDC will contain only part of
1069   the full rendered image.
1070 
1071   \sa getBuffer()
1072   \since Coin 3.1
1073 */
1074 const void * const &
getDC(void) const1075 SoOffscreenRenderer::getDC(void) const
1076 {
1077   if(!PRIVATE(this)->useDC)
1078   {
1079 	PRIVATE(this)->useDC = true;
1080 	PRIVATE(this)->updateDCBitmap();
1081   }
1082 
1083   return PRIVATE(this)->glcanvas.getHDC();
1084 }
1085 
updateDCBitmap()1086 void SoOffscreenRendererP::updateDCBitmap()
1087 {
1088   this->glcanvas.updateDCBitmap();
1089 }
1090 // *************************************************************************
1091 
1092 //
1093 // avoid endian problems (little endian sucks, right? :)
1094 //
1095 static size_t
write_short(FILE * fp,unsigned short val)1096 write_short(FILE * fp, unsigned short val)
1097 {
1098   unsigned char tmp[2];
1099   tmp[0] = (unsigned char)(val >> 8);
1100   tmp[1] = (unsigned char)(val & 0xff);
1101   return fwrite(&tmp, 2, 1, fp);
1102 }
1103 
1104 SbBool
writeToRGB(FILE * fp,unsigned int w,unsigned int h,unsigned int nrcomponents,const uint8_t * imgbuf)1105 SoOffscreenRendererP::writeToRGB(FILE * fp, unsigned int w, unsigned int h,
1106                                  unsigned int nrcomponents,
1107                                  const uint8_t * imgbuf)
1108 {
1109   // FIXME: add code to rle rows, pederb 2000-01-10
1110 
1111   (void)write_short(fp, 0x01da); // imagic
1112   (void)write_short(fp, 0x0001); // raw (no rle yet)
1113 
1114   if (nrcomponents == 1)
1115     (void)write_short(fp, 0x0002); // 2 dimensions (heightmap)
1116   else
1117     (void)write_short(fp, 0x0003); // 3 dimensions
1118 
1119   (void)write_short(fp, (unsigned short) w);
1120   (void)write_short(fp, (unsigned short) h);
1121   (void)write_short(fp, (unsigned short) nrcomponents);
1122 
1123   const size_t BUFSIZE = 500;
1124   unsigned char buf[BUFSIZE];
1125   (void)memset(buf, 0, BUFSIZE);
1126   buf[7] = 255; // set maximum pixel value to 255
1127   strcpy((char *)buf+8, "https://bitbucket.org/Coin3D/");
1128   const size_t wrote = fwrite(buf, 1, BUFSIZE, fp);
1129   assert(wrote == BUFSIZE);
1130 
1131   unsigned char * tmpbuf = new unsigned char[w];
1132 
1133   SbBool writeok = TRUE;
1134   for (unsigned int c = 0; c < nrcomponents; c++) {
1135     for (unsigned int y = 0; y < h; y++) {
1136       for (unsigned int x = 0; x < w; x++) {
1137         tmpbuf[x] = imgbuf[(x + y * w) * nrcomponents + c];
1138       }
1139       writeok = writeok && (fwrite(tmpbuf, 1, w, fp) == w);
1140     }
1141   }
1142 
1143   if (!writeok) {
1144     SoDebugError::postWarning("SoOffscreenRendererP::writeToRGB",
1145                               "error when writing RGB file");
1146   }
1147 
1148   delete [] tmpbuf;
1149   return writeok;
1150 }
1151 
1152 
1153 /*!
1154   Writes the buffer in SGI RGB format by appending it to the already
1155   open file. Returns \c FALSE if writing fails.
1156 
1157   Important note: do \e not use this method when the Coin library has
1158   been compiled as an MSWindows DLL, as passing FILE* instances back
1159   or forth to DLLs is dangerous and will most likely cause a
1160   crash. This is an intrinsic limitation for MSWindows DLLs.
1161 */
1162 SbBool
writeToRGB(FILE * fp) const1163 SoOffscreenRenderer::writeToRGB(FILE * fp) const
1164 {
1165   if (SoOffscreenRendererP::offscreenContextsNotSupported()) { return FALSE; }
1166 
1167   SbVec2s size = PRIVATE(this)->viewport.getViewportSizePixels();
1168 
1169   return SoOffscreenRendererP::writeToRGB(fp, size[0], size[1],
1170                                           this->getComponents(),
1171                                           this->getBuffer());
1172 }
1173 
1174 /*!
1175   Opens a file with the given name and writes the offscreen buffer in
1176   SGI RGB format to the new file. If the file already exists, it will
1177   be overwritten (if permitted by the filesystem).
1178 
1179   Returns \c TRUE if all went ok, otherwise \c FALSE.
1180 */
1181 SbBool
writeToRGB(const char * filename) const1182 SoOffscreenRenderer::writeToRGB(const char * filename) const
1183 {
1184   FILE * rgbfp = fopen(filename, "wb");
1185   if (!rgbfp) {
1186     SoDebugError::postWarning("SoOffscreenRenderer::writeToRGB",
1187                               "couldn't open file '%s'", filename);
1188     return FALSE;
1189   }
1190   SbBool result = this->writeToRGB(rgbfp);
1191   (void)fclose(rgbfp);
1192   return result;
1193 }
1194 
1195 /*!
1196   Writes the buffer in Postscript format by appending it to the
1197   already open file. Returns \c FALSE if writing fails.
1198 
1199   Important note: do \e not use this method when the Coin library has
1200   been compiled as an MSWindows DLL, as passing FILE* instances back
1201   or forth to DLLs is dangerous and will most likely cause a
1202   crash. This is an intrinsic limitation for MSWindows DLLs.
1203 */
1204 SbBool
writeToPostScript(FILE * fp) const1205 SoOffscreenRenderer::writeToPostScript(FILE * fp) const
1206 {
1207   // just choose a page size of 8.5 x 11 inches (A4)
1208   return this->writeToPostScript(fp, SbVec2f(8.5f, 11.0f));
1209 }
1210 
1211 /*!
1212   Opens a file with the given name and writes the offscreen buffer in
1213   Postscript format to the new file. If the file already exists, it
1214   will be overwritten (if permitted by the filesystem).
1215 
1216   Returns \c TRUE if all went ok, otherwise \c FALSE.
1217 */
1218 SbBool
writeToPostScript(const char * filename) const1219 SoOffscreenRenderer::writeToPostScript(const char * filename) const
1220 {
1221   FILE * psfp = fopen(filename, "wb");
1222   if (!psfp) {
1223     SoDebugError::postWarning("SoOffscreenRenderer::writeToPostScript",
1224                               "couldn't open file '%s'", filename);
1225     return FALSE;
1226   }
1227   SbBool result = this->writeToPostScript(psfp);
1228   (void)fclose(psfp);
1229   return result;
1230 }
1231 
1232 /*!
1233   Writes the buffer to a file in Postscript format, with \a printsize
1234   dimensions.
1235 
1236   Important note: do \e not use this method when the Coin library has
1237   been compiled as an MSWindows DLL, as passing FILE* instances back
1238   or forth to DLLs is dangerous and will most likely cause a
1239   crash. This is an intrinsic limitation for MSWindows DLLs.
1240 */
1241 SbBool
writeToPostScript(FILE * fp,const SbVec2f & printsize) const1242 SoOffscreenRenderer::writeToPostScript(FILE * fp,
1243                                        const SbVec2f & printsize) const
1244 {
1245   if (SoOffscreenRendererP::offscreenContextsNotSupported()) { return FALSE;}
1246 
1247   const SbVec2s size = PRIVATE(this)->viewport.getViewportSizePixels();
1248   const int nc = this->getComponents();
1249   const float defaultdpi = 72.0f; // we scale against this value
1250   const float dpi = this->getScreenPixelsPerInch();
1251   const SbVec2s pixelsize((short)(printsize[0]*defaultdpi),
1252                           (short)(printsize[1]*defaultdpi));
1253 
1254   const unsigned char * src = this->getBuffer();
1255   const int chan = nc <= 2 ? 1 : 3;
1256   const SbVec2s scaledsize((short) ceil(size[0]*defaultdpi/dpi),
1257                            (short) ceil(size[1]*defaultdpi/dpi));
1258 
1259   cc_string storedlocale;
1260   SbBool changed = coin_locale_set_portable(&storedlocale);
1261 
1262   fprintf(fp, "%%!PS-Adobe-2.0 EPSF-1.2\n");
1263   fprintf(fp, "%%%%BoundingBox: 0 %d %d %d\n",
1264           pixelsize[1]-scaledsize[1],
1265           scaledsize[0],
1266           pixelsize[1]);
1267   fprintf(fp, "%%%%Creator: Coin <https://bitbucket.org/Coin3D/>\n");
1268   fprintf(fp, "%%%%EndComments\n");
1269 
1270   fprintf(fp, "\n");
1271   fprintf(fp, "/origstate save def\n");
1272   fprintf(fp, "\n");
1273   fprintf(fp, "%% workaround for bug in some PS interpreters\n");
1274   fprintf(fp, "%% which doesn't skip the ASCII85 EOD marker.\n");
1275   fprintf(fp, "/~ {currentfile read pop pop} def\n\n");
1276   fprintf(fp, "/image_wd %d def\n", size[0]);
1277   fprintf(fp, "/image_ht %d def\n", size[1]);
1278   fprintf(fp, "/pos_wd %d def\n", size[0]);
1279   fprintf(fp, "/pos_ht %d def\n", size[1]);
1280   fprintf(fp, "/image_dpi %g def\n", dpi);
1281   fprintf(fp, "/image_scale %g image_dpi div def\n", defaultdpi);
1282   fprintf(fp, "/image_chan %d def\n", chan);
1283   fprintf(fp, "/xpos_offset 0 image_scale mul def\n");
1284   fprintf(fp, "/ypos_offset 0 image_scale mul def\n");
1285   fprintf(fp, "/pix_buf_size %d def\n\n", size[0]*chan);
1286   fprintf(fp, "/page_ht %g %g mul def\n", printsize[1], defaultdpi);
1287   fprintf(fp, "/page_wd %g %g mul def\n", printsize[0], defaultdpi);
1288   fprintf(fp, "/image_xpos 0 def\n");
1289   fprintf(fp, "/image_ypos page_ht pos_ht image_scale mul sub def\n");
1290   fprintf(fp, "image_xpos xpos_offset add image_ypos ypos_offset add translate\n");
1291   fprintf(fp, "\n");
1292   fprintf(fp, "/pix pix_buf_size string def\n");
1293   fprintf(fp, "image_wd image_scale mul image_ht image_scale mul scale\n");
1294   fprintf(fp, "\n");
1295   fprintf(fp, "image_wd image_ht 8\n");
1296   fprintf(fp, "[image_wd 0 0 image_ht 0 0]\n");
1297   fprintf(fp, "currentfile\n");
1298   fprintf(fp, "/ASCII85Decode filter\n");
1299   // fprintf(fp, "/RunLengthDecode filter\n"); // FIXME: add later. 2003???? pederb.
1300   if (chan == 3) fprintf(fp, "false 3\ncolorimage\n");
1301   else fprintf(fp,"image\n");
1302 
1303   const int rowlen = 72;
1304   int num = size[0] * size[1];
1305   unsigned char tuple[4];
1306   unsigned char linebuf[rowlen+5];
1307   int tuplecnt = 0;
1308   int linecnt = 0;
1309   int cnt = 0;
1310   while (cnt < num) {
1311     switch (nc) {
1312     default: // avoid warning
1313     case 1:
1314       coin_output_ascii85(fp, src[cnt], tuple, linebuf, &tuplecnt, &linecnt, rowlen, FALSE);
1315       break;
1316     case 2:
1317       coin_output_ascii85(fp, src[cnt*2], tuple, linebuf, &tuplecnt, &linecnt, rowlen, FALSE);
1318       break;
1319     case 3:
1320       coin_output_ascii85(fp, src[cnt*3], tuple, linebuf, &tuplecnt, &linecnt, rowlen, FALSE);
1321       coin_output_ascii85(fp, src[cnt*3+1], tuple, linebuf, &tuplecnt, &linecnt, rowlen, FALSE);
1322       coin_output_ascii85(fp, src[cnt*3+2], tuple, linebuf, &tuplecnt, &linecnt, rowlen, FALSE);
1323       break;
1324     case 4:
1325       coin_output_ascii85(fp, src[cnt*4], tuple, linebuf, &tuplecnt, &linecnt, rowlen, FALSE);
1326       coin_output_ascii85(fp, src[cnt*4+1], tuple, linebuf, &tuplecnt, &linecnt,rowlen, FALSE);
1327       coin_output_ascii85(fp, src[cnt*4+2], tuple, linebuf, &tuplecnt, &linecnt, rowlen, FALSE);
1328       break;
1329     }
1330     cnt++;
1331   }
1332 
1333   // flush data in ascii85 encoder
1334   coin_flush_ascii85(fp, tuple, linebuf, &tuplecnt, &linecnt, rowlen);
1335 
1336   fprintf(fp, "~>\n\n"); // ASCII85 EOD marker
1337   fprintf(fp, "origstate restore\n");
1338   fprintf(fp, "\n");
1339   fprintf(fp, "%%%%Trailer\n");
1340   fprintf(fp, "\n");
1341   fprintf(fp, "%%%%EOF\n");
1342 
1343   if (changed) { coin_locale_reset(&storedlocale); }
1344 
1345   return (SbBool) (ferror(fp) == 0);
1346 }
1347 
1348 /*!
1349   Opens a file with the given name and writes the offscreen buffer in
1350   Postscript format with \a printsize dimensions to the new file. If
1351   the file already exists, it will be overwritten (if permitted by the
1352   filesystem).
1353 
1354   Returns \c TRUE if all went ok, otherwise \c FALSE.
1355 */
1356 SbBool
writeToPostScript(const char * filename,const SbVec2f & printsize) const1357 SoOffscreenRenderer::writeToPostScript(const char * filename,
1358                                        const SbVec2f & printsize) const
1359 {
1360   FILE * psfp = fopen(filename, "wb");
1361   if (!psfp) {
1362     SoDebugError::postWarning("SoOffscreenRenderer::writeToPostScript",
1363                               "couldn't open file '%s'", filename);
1364     return FALSE;
1365   }
1366   SbBool result = this->writeToPostScript(psfp, printsize);
1367   (void)fclose(psfp);
1368   return result;
1369 }
1370 
1371 // FIXME: the file format support checking could have been done
1372 // better, for instance by using MIME types. Consider fixing the API
1373 // for later major releases. 20020206 mortene.
1374 //
1375 // UPDATE 20050711 mortene: it seems like TGS has extended their API
1376 // in an even worse way; by adding separate writeToJPEG(),
1377 // writeToPNG(), etc etc functions.
1378 
1379 /*!
1380   Returns \c TRUE if the buffer can be saved as a file of type \a
1381   filetypeextension, using SoOffscreenRenderer::writeToFile().  This
1382   function needs simage v1.1 or newer.
1383 
1384   Examples of possibly supported extensions are: "jpg", "png", "tiff",
1385   "gif", "bmp", etc. The extension match is not case sensitive.
1386 
1387   Which formats are \e actually supported depends on the capabilities
1388   of Coin's support library for handling import and export of
1389   pixel-data files: the simage library. If the simage library is not
1390   installed on your system, no extension output formats will be
1391   supported.
1392 
1393   Also, note that it is possible to build and install a simage library
1394   that lacks support for most or all of the file formats it is \e
1395   capable of supporting. This is so because the simage library depends
1396   on other, external 3rd party libraries -- in the same manner as Coin
1397   depends on the simage library for added file format support.
1398 
1399   The two built-in formats that are supported through the
1400   SoOffscreenRenderer::writeToRGB() and
1401   SoOffscreenRenderer::writeToPostScript() methods (for SGI RGB format
1402   and for Adobe Postscript files, respectively) are \e not considered
1403   by this method, as those two formats are guaranteed to \e always be
1404   supported through those functions.
1405 
1406   So if you want to be guaranteed to be able to export a screenshot in
1407   your wanted format, you will have to use either one of the above
1408   mentioned method for writing SGI RGB or Adobe Postscript directly,
1409   or make sure the Coin library has been built and is running on top
1410   of a version of the simage library (that you have preferably built
1411   yourself) with the file format(s) you want support for.
1412 
1413 
1414   This method is an extension versus the original SGI Open Inventor
1415   API.
1416 
1417   \sa  getNumWriteFiletypes(), getWriteFiletypeInfo(), writeToFile()
1418 */
1419 SbBool
isWriteSupported(const SbName & filetypeextension) const1420 SoOffscreenRenderer::isWriteSupported(const SbName & filetypeextension) const
1421 {
1422   if (!simage_wrapper()->versionMatchesAtLeast(1,1,0)) {
1423 
1424     if (CoinOffscreenGLCanvas::debug()) {
1425       if (!simage_wrapper()->available) {
1426         SoDebugError::postInfo("SoOffscreenRenderer::isWriteSupported",
1427                                "simage library not available.");
1428       } else {
1429         SoDebugError::postInfo("SoOffscreenRenderer::isWriteSupported",
1430                                "You need simage v1.1 for this functionality.");
1431       }
1432     }
1433     return FALSE;
1434   }
1435   int ret = simage_wrapper()->simage_check_save_supported(filetypeextension.getString());
1436   return ret ? TRUE : FALSE;
1437 }
1438 
1439 /*!
1440   Returns the number of available exporters. Detailed information
1441   about the exporters can then be found using getWriteFiletypeInfo().
1442 
1443   See SoOffscreenRenderer::isWriteSupported() for information about
1444   which file formats you can expect to be present.
1445 
1446   Note that the two built-in export formats, SGI RGB and Adobe
1447   Postscript, are not counted.
1448 
1449   This method is an extension versus the original SGI Open Inventor
1450   API.
1451 
1452   \sa getWriteFiletypeInfo()
1453 */
1454 int
getNumWriteFiletypes(void) const1455 SoOffscreenRenderer::getNumWriteFiletypes(void) const
1456 {
1457   if (!simage_wrapper()->versionMatchesAtLeast(1,1,0)) {
1458 #if COIN_DEBUG
1459     SoDebugError::postInfo("SoOffscreenRenderer::getNumWriteFiletypes",
1460                            "You need simage v1.1 for this functionality.");
1461 #endif // COIN_DEBUG
1462     return 0;
1463   }
1464   return simage_wrapper()->simage_get_num_savers();
1465 }
1466 
1467 /*!
1468   Returns information about an image exporter. \a extlist is a list
1469   of filename extensions for a file format. E.g. for JPEG it is legal
1470   to use both jpg and jpeg. Extlist will contain const char * pointers
1471   (you need to cast the void * pointers to const char * before using
1472   them).
1473 
1474   \a fullname is the full name of the image format. \a description is
1475   an optional string with more information about the file format.
1476 
1477   See SoOffscreenRenderer::isWriteSupported() for information about
1478   which file formats you can expect to be present.
1479 
1480   This method is an extension versus the original SGI Open Inventor
1481   API.
1482 
1483   Here is a stand-alone, complete code example that shows how you can
1484   check exactly which output formats are supported:
1485 
1486   \code
1487   #include <Inventor/SoDB.h>
1488   #include <Inventor/SoOffscreenRenderer.h>
1489 
1490   int
1491   main(int argc, char **argv)
1492   {
1493     SoDB::init();
1494     SoOffscreenRenderer * r = new SoOffscreenRenderer(*(new SbViewportRegion));
1495     int num = r->getNumWriteFiletypes();
1496 
1497     if (num == 0) {
1498       (void)fprintf(stdout,
1499                     "No image formats supported by the "
1500                     "SoOffscreenRenderer except SGI RGB and Postscript.\n");
1501     }
1502     else {
1503       for (int i=0; i < num; i++) {
1504         SbPList extlist;
1505         SbString fullname, description;
1506         r->getWriteFiletypeInfo(i, extlist, fullname, description);
1507         (void)fprintf(stdout, "%s: %s (extension%s: ",
1508                       fullname.getString(), description.getString(),
1509                       extlist.getLength() > 1 ? "s" : "");
1510         for (int j=0; j < extlist.getLength(); j++) {
1511           (void)fprintf(stdout, "%s%s", j>0 ? ", " : "", (const char*) extlist[j]);
1512         }
1513         (void)fprintf(stdout, ")\n");
1514       }
1515     }
1516 
1517     delete r;
1518     return 0;
1519   }
1520   \endcode
1521 
1522   \sa getNumWriteFiletypes(), writeToFile()
1523 
1524   \since Coin 2.3
1525 */
1526 void
getWriteFiletypeInfo(const int idx,SbPList & extlist,SbString & fullname,SbString & description)1527 SoOffscreenRenderer::getWriteFiletypeInfo(const int idx,
1528                                           SbPList & extlist,
1529                                           SbString & fullname,
1530                                           SbString & description)
1531 {
1532   if (!simage_wrapper()->versionMatchesAtLeast(1,1,0)) {
1533 #if COIN_DEBUG
1534     SoDebugError::postInfo("SoOffscreenRenderer::getNumWriteFiletypes",
1535                            "You need simage v1.1 for this functionality.");
1536 #endif // COIN_DEBUG
1537     return;
1538   }
1539   extlist.truncate(0);
1540   assert(idx >= 0 && idx < this->getNumWriteFiletypes());
1541   void * saver = simage_wrapper()->simage_get_saver_handle(idx);
1542   SbString allext(simage_wrapper()->simage_get_saver_extensions(saver));
1543   const char * start = allext.getString();
1544   const char * curr = start;
1545   const char * end = strchr(curr, ',');
1546   while (end) {
1547     const ptrdiff_t offset_start = curr - start;
1548     const ptrdiff_t offset_end = end - start - 1;
1549     SbString ext = allext.getSubString((int)offset_start, (int)offset_end);
1550     SbName extname(ext.getString());
1551     extlist.append((void*)extname.getString());
1552     curr = end+1;
1553     end = strchr(curr, ',');
1554   }
1555   const ptrdiff_t offset = curr - start;
1556   SbString ext = allext.getSubString((int)offset);
1557   SbName extname(ext.getString());
1558   extlist.append((void*)extname.getString());
1559   const char * fullname_s = simage_wrapper()->simage_get_saver_fullname(saver);
1560   const char * desc_s = simage_wrapper()->simage_get_saver_description(saver);
1561   fullname = fullname_s ? SbString(fullname_s) : SbString("");
1562   description = desc_s ? SbString(desc_s) : SbString("");
1563 }
1564 
1565 /*!
1566   Saves the buffer to \a filename, in the filetype specified by \a
1567   filetypeextensions.
1568 
1569   Note that you must still specify the \e full \a filename for the
1570   first argument, i.e. the second argument will not automatically be
1571   attached to the filename -- it is only used to decide the filetype.
1572 
1573   This method is an extension versus the orignal SGI Open Inventor
1574   API.
1575 
1576   \sa isWriteSupported()
1577 */
1578 SbBool
writeToFile(const SbString & filename,const SbName & filetypeextension) const1579 SoOffscreenRenderer::writeToFile(const SbString & filename, const SbName & filetypeextension) const
1580 {
1581   if (!simage_wrapper()->versionMatchesAtLeast(1,1,0)) {
1582     //FIXME: Shouldn't use BOOST_CURRENT_FUNCTION here, the
1583     //HAVE_CPP_COMPILER_FUNCTION_NAME_VAR should be massaged correctly
1584     //to fit here. BFG 20090917
1585     if (!simage_wrapper()->available) {
1586       SoDebugError::post(BOOST_CURRENT_FUNCTION,
1587                              "simage library not available.");
1588     }
1589     else {
1590       int major, minor, micro;
1591       simage_wrapper()->simage_version(&major,&minor,&micro);
1592       SoDebugError::post(BOOST_CURRENT_FUNCTION,
1593                          "simage version is older than 1.1.0, available version is %d.%d.%d", major,minor,micro);
1594     }
1595     return FALSE;
1596   }
1597   if (SoOffscreenRendererP::offscreenContextsNotSupported()) {
1598     SoDebugError::post(BOOST_CURRENT_FUNCTION,
1599                        "Offscreen contexts not supported.");
1600     return FALSE;
1601   }
1602 
1603   SbVec2s size = PRIVATE(this)->viewport.getViewportSizePixels();
1604   int comp = (int) this->getComponents();
1605   unsigned char * bytes = this->getBuffer();
1606   int ret = simage_wrapper()->simage_save_image(filename.getString(),
1607                                                 bytes,
1608                                                 int(size[0]), int(size[1]), comp,
1609                                                 filetypeextension.getString());
1610   return ret ? TRUE : FALSE;
1611 }
1612 
1613 // *************************************************************************
1614 
1615 /*!
1616   Control whether or not SoOffscreenRenderer can use the "pbuffer"
1617   feature of OpenGL to render the scenes with hardware acceleration.
1618 
1619   This is a dummy function in Coin, provided for API compatibility
1620   reasons, as it is really superfluous:
1621 
1622   Coin has internal heuristics to figure out if pbuffers are available
1623   and can be allocated and used for the SoOffscreenRenderer.  The
1624   SoOffscreenRenderer will also automatically fall back on "soft"
1625   buffers if it can not use pbuffers (or any other hardware
1626   accelerated rendering technique).
1627 
1628   \since Coin 3.1
1629 */
1630 void
setPbufferEnable(SbBool COIN_UNUSED_ARG (enable))1631 SoOffscreenRenderer::setPbufferEnable(SbBool COIN_UNUSED_ARG(enable))
1632 {
1633   // FIXME: change the semantics of this function from just ignoring
1634   // the input argument, to using it for shutting off pbuffers if
1635   // FALSE?
1636   //
1637   // not sure there's really any good reason to do that, however.
1638   //
1639   // mortene.
1640 }
1641 
1642 /*!
1643   See SoOffscreenRenderer::setPbufferEnable().
1644 
1645   \since Coin 3.1
1646 */
1647 SbBool
getPbufferEnable(void) const1648 SoOffscreenRenderer::getPbufferEnable(void) const
1649 {
1650   // FIXME: should perhaps return a flag indicating whether or not the
1651   // system can use pbuffers. this depends on the GL context, however,
1652   // so the design of this Mercury Inventor API function is inherently
1653   // flawed.
1654   //
1655   // hardly any GL driver these days does *not* provide pbuffers,
1656   // though, so this is unlikely to be an important issue.
1657   //
1658   // mortene.
1659 
1660   return TRUE;
1661 }
1662 
1663 // *************************************************************************
1664 
1665 // FIXME: this should really be done by SoCamera, on the basis of data
1666 // from an "SoTileRenderingElement". See BUGS.txt, item #121. 20050712 mortene.
1667 void
setCameraViewvolForTile(SoCamera * cam)1668 SoOffscreenRendererP::setCameraViewvolForTile(SoCamera * cam)
1669 {
1670   SoState * state = (PUBLIC(this)->getGLRenderAction())->getState();
1671 
1672   // A small trick to change the aspect ratio without changing the
1673   // scenegraph camera.
1674   SbViewVolume vv;
1675   const float aspectratio = this->viewport.getViewportAspectRatio();
1676   const SbVec2s vporigin = this->viewport.getViewportOriginPixels();
1677 
1678   switch(cam->viewportMapping.getValue()) {
1679   case SoCamera::CROP_VIEWPORT_FILL_FRAME:
1680   case SoCamera::CROP_VIEWPORT_LINE_FRAME:
1681   case SoCamera::CROP_VIEWPORT_NO_FRAME:
1682     vv = cam->getViewVolume(0.0f);
1683 
1684     { // FIXME: should really fix this bug, not just warn that it is
1685       // there. See item #191 in Coin/BUGS.txt for more information.
1686       // 20050714 mortene.
1687       static SbBool first = TRUE;
1688       if (first) {
1689         SbString s;
1690         cam->viewportMapping.get(s);
1691         SoDebugError::postWarning("SoOffscreenRendererP::setCameraViewvolForTile",
1692                                   "The SoOffscreenRenderer does not yet work "
1693                                   "properly with the SoCamera::viewportMapping "
1694                                   "field set to '%s'", s.getString());
1695         first = FALSE;
1696       }
1697     }
1698     break;
1699   case SoCamera::ADJUST_CAMERA:
1700     vv = cam->getViewVolume(aspectratio);
1701     if (aspectratio < 1.0f) vv.scale(1.0f / aspectratio);
1702     break;
1703   case SoCamera::LEAVE_ALONE:
1704     vv = cam->getViewVolume(0.0f);
1705     break;
1706   default:
1707     assert(0 && "unknown viewport mapping");
1708     break;
1709   }
1710 
1711   const int LEFTINTPOS = (this->currenttile[0] * this->glcanvassize[0]) - vporigin[0];
1712   const int RIGHTINTPOS = LEFTINTPOS + this->subsize[0];
1713   const int TOPINTPOS = (this->currenttile[1] * this->glcanvassize[1]) - vporigin[1];
1714   const int BOTTOMINTPOS = TOPINTPOS + this->subsize[1];
1715 
1716   const SbVec2s fullsize = this->viewport.getViewportSizePixels();
1717   const float left = float(LEFTINTPOS) / float(fullsize[0]);
1718   const float right = float(RIGHTINTPOS) / float(fullsize[0]);
1719   // Swap top / bottom, to flip the coordinate system for the Y axis
1720   // the way we want it.
1721   const float top = float(BOTTOMINTPOS) / float(fullsize[1]);
1722   const float bottom = float(TOPINTPOS) / float(fullsize[1]);
1723 
1724   if (CoinOffscreenGLCanvas::debug()) {
1725     SoDebugError::postInfo("SoOffscreenRendererP::setCameraViewvolForTile",
1726                            "narrowing for tile <%d, %d>: <%f, %f> - <%f, %f>",
1727                            this->currenttile[0], this->currenttile[1],
1728                            left, bottom, right, top);
1729   }
1730 
1731   // Reshape view volume
1732   vv = vv.narrow(left, bottom, right, top);
1733 
1734   SbMatrix proj, affine;
1735   vv.getMatrices(affine, proj);
1736 
1737   // Support antialiasing if renderpasses > 1
1738   if (renderaction->getNumPasses() > 1) {
1739     SbVec3f jittervec;
1740     SbMatrix m;
1741     coin_viewvolume_jitter(renderaction->getNumPasses(), renderaction->getCurPass(),
1742                            this->glcanvassize, (float *)jittervec.getValue());
1743     m.setTranslate(jittervec);
1744     proj.multRight(m);
1745   }
1746 
1747   SoCullElement::setViewVolume(state, vv);
1748   SoViewVolumeElement::set(state, cam, vv);
1749   SoProjectionMatrixElement::set(state, cam, proj);
1750   SoViewingMatrixElement::set(state, cam, affine);
1751 }
1752 
1753 // *************************************************************************
1754 
1755 SbBool
offscreenContextsNotSupported(void)1756 SoOffscreenRendererP::offscreenContextsNotSupported(void)
1757 {
1758   // Returning FALSE means that offscreen rendering seems to be
1759   // generally supported on the system.
1760   //
1761   // (It is however important to be robust and handle cases where it
1762   // still fails, as this can happen due to e.g. lack of resources or
1763   // other causes that may change during run-time.)
1764 
1765 #ifdef HAVE_GLX
1766   return FALSE;
1767 #elif defined(HAVE_WGL)
1768   return FALSE;
1769 #elif defined(COIN_MACOS_10)
1770   return FALSE;
1771 #endif
1772 
1773   // No win-system GL binding was found, so we're sure that offscreen
1774   // rendering can *not* be done.
1775   return TRUE;
1776 }
1777 
1778 // *************************************************************************
1779 
1780 #undef PRIVATE
1781 #undef PUBLIC
1782