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,µ);
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