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 #ifdef HAVE_CONFIG_H
34 #include <config.h>
35 #endif // HAVE_CONFIG_H
36 
37 #ifdef HAVE_VRML97
38 
39 /*!
40   \class SoVRMLImageTexture SoVRMLImageTexture.h Inventor/VRMLnodes/SoVRMLImageTexture.h
41   \brief The SoVRMLImageTexture class is used for mapping a texture file onto geometry.
42 
43   \ingroup VRMLnodes
44 
45   \WEB3DCOPYRIGHT
46 
47   \verbatim
48   ImageTexture {
49     exposedField MFString url     []
50     field        SFBool   repeatS TRUE
51     field        SFBool   repeatT TRUE
52   }
53   \endverbatim
54 
55   The ImageTexture node defines a texture map by specifying an image
56   file and general parameters for mapping to geometry. Texture maps
57   are defined in a 2D coordinate system (s, t) that ranges from [0.0,
58   1.0] in both directions. The bottom edge of the image corresponds to
59   the S-axis of the texture map, and left edge of the image
60   corresponds to the T-axis of the texture map. The lower-left pixel
61   of the image corresponds to s=0, t=0, and the top-right pixel of the
62   image corresponds to s=1, t=1. These relationships are depicted in
63   Figure 6.9.
64 
65   <center>
66   <img src="http://www.web3d.org/documents/specifications/14772/V2.0/Images/ImageTexture.gif">
67   Figure 6.9
68   </center>
69 
70   The texture is read from the URL specified by the url field. When
71   the url field contains no values ([]), texturing is
72   disabled. Browsers shall support the JPEG and PNG image file
73   formats. In addition, browsers may support other image formats
74   (e.g. CGM) which can be rendered into a 2D image. Support for the
75   GIF format is also recommended (including transparency).
76 
77   Details on the url field can be found in 4.5, VRML and the World
78   Wide Web.
79 
80   See 4.6.11, Texture maps
81   (<http://www.web3d.org/documents/specifications/14772/V2.0/part1/concepts.html#4.6.11>),
82   for a general description of texture maps.
83 
84   See 4.14, Lighting model
85   (<http://www.web3d.org/documents/specifications/14772/V2.0/part1/concepts.html#4.14>),
86   for a description of lighting equations and the interaction between
87   textures, materials, and geometry appearance.
88 
89   The repeatS and repeatT fields specify how the texture wraps in the
90   S and T directions. If repeatS is TRUE (the default), the texture
91   map is repeated outside the [0.0, 1.0] texture coordinate range in
92   the S direction so that it fills the shape. If repeatS is FALSE, the
93   texture coordinates are clamped in the S direction to lie within the
94   [0.0, 1.0] range. The repeatT field is analogous to the repeatS
95   field.
96 
97 
98   \ENDWEB3D
99 
100   One common flaw with many programs that has support for exporting
101   VRML or Inventor files, is that the same texture file is exported
102   several times, but as different nodes. This can cause excessive
103   texture memory usage and slow rendering. Below is an example program
104   that fixes this by replacing all instances of the same texture with
105   a pointer to the first node:
106 
107   \code
108 
109   #include <Inventor/actions/SoSearchAction.h>
110   #include <Inventor/actions/SoWriteAction.h>
111   #include <Inventor/VRMLnodes/SoVRMLGroup.h>
112   #include <Inventor/VRMLnodes/SoVRMLImageTexture.h>
113   #include <Inventor/VRMLnodes/SoVRMLAppearance.h>
114   #include <Inventor/SoDB.h>
115   #include <Inventor/SoInput.h>
116   #include <Inventor/SoOutput.h>
117   #include <cassert>
118 
119   int main(int argc, char ** argv)
120   {
121     if (argc < 2) return -1;
122     SoDB::init();
123 
124     SoInput in;
125     if (!in.openFile(argv[1])) return -1;
126 
127     if (!in.isFileVRML2()) return -1; // file is not a vrml2 file
128 
129     SoVRMLGroup * root = SoDB::readAllVRML(&in);
130 
131     if (!root) return -1;
132     root->ref();
133 
134     SoSearchAction sa;
135     sa.setType(SoVRMLImageTexture::getClassTypeId());
136     sa.setInterest(SoSearchAction::ALL);
137     sa.setSearchingAll(TRUE);
138     sa.apply(root);
139     SoPathList & pl = sa.getPaths();
140     SbDict namedict;
141 
142     for (int i = 0; i < pl.getLength(); i++) {
143       SoFullPath * p = (SoFullPath*) pl[i];
144       if (p->getTail()->isOfType(SoVRMLImageTexture::getClassTypeId())) {
145         SoVRMLImageTexture * tex = (SoVRMLImageTexture*) p->getTail();
146         if (tex->url.getNum()) {
147           // FIXME: we only check the first name here. Should really check all of them
148           SbName name = tex->url[0].getString();
149           unsigned long key = (unsigned long) ((void*) name.getString());
150           void * tmp;
151           if (!namedict.find(key, tmp)) {
152             (void) namedict.enter(key, tex);
153           }
154           else if (tmp != (void*) tex) {
155             SoNode * parent = p->getNodeFromTail(1);
156             if (parent->isOfType(SoVRMLAppearance::getClassTypeId())) {
157               ((SoVRMLAppearance*)parent)->texture = (SoNode*) tmp;
158             }
159             else {
160               // not a valid VRML2 file. Print a warning or something.
161             }
162           }
163         }
164       }
165     }
166     sa.reset();
167     SoOutput out;
168     out.setHeaderString("#VRML V2.0 utf8");
169     SoWriteAction wa(&out);
170     wa.apply(root);
171     root->unref();
172   }
173   \endcode
174 */
175 
176 // *************************************************************************
177 
178 /*!
179   SoMFString SoVRMLImageTexture::url
180   The texture file URL.
181 */
182 
183 // *************************************************************************
184 
185 /*! \file SoVRMLImageTexture.h */
186 #include <Inventor/VRMLnodes/SoVRMLImageTexture.h>
187 #include "coindefs.h"
188 
189 #include <cassert>
190 
191 #include <Inventor/SbImage.h>
192 #include <Inventor/SoInput.h>
193 #include <Inventor/VRMLnodes/SoVRMLMacros.h>
194 #include <Inventor/actions/SoCallbackAction.h>
195 #include <Inventor/actions/SoGLRenderAction.h>
196 #include <Inventor/actions/SoRayPickAction.h>
197 #include <Inventor/elements/SoCacheElement.h>
198 #include <Inventor/elements/SoGLMultiTextureEnabledElement.h>
199 #include <Inventor/elements/SoGLMultiTextureImageElement.h>
200 #include <Inventor/elements/SoTextureOverrideElement.h>
201 #include <Inventor/elements/SoTextureQualityElement.h>
202 #include <Inventor/elements/SoTextureUnitElement.h>
203 #include <Inventor/errors/SoDebugError.h>
204 #include <Inventor/errors/SoReadError.h>
205 #include <Inventor/lists/SbStringList.h>
206 #include <Inventor/misc/SoGLBigImage.h>
207 #include <Inventor/sensors/SoFieldSensor.h>
208 #include <Inventor/sensors/SoOneShotSensor.h>
209 #include <Inventor/sensors/SoTimerSensor.h>
210 #include <Inventor/C/threads/sched.h>
211 // FIXME: should be able to include file without
212 // #ifdef-wrapper. 20051202 mortene.
213 #ifdef HAVE_THREADS
214 #include <Inventor/threads/SbMutex.h>
215 #endif // HAVE_THREADS
216 
217 #include "tidbitsp.h"
218 #include "nodes/SoSubNodeP.h"
219 #include "glue/simage_wrapper.h"
220 #include "elements/SoTextureScalePolicyElement.h"
221 
222 // *************************************************************************
223 
224 static int imagedata_maxage = 0;
225 static VRMLPrequalifyFileCallback * imagetexture_prequalify_cb = NULL;
226 static void * imagetexture_prequalify_closure = NULL;
227 static SbBool imagetexture_delay_fetch = TRUE;
228 
229 // *************************************************************************
230 
231 class SoVRMLImageTextureP {
232 public:
SoVRMLImageTextureP(SoVRMLImageTexture * master)233   SoVRMLImageTextureP(SoVRMLImageTexture * master) : master(master) { }
234   SoVRMLImageTexture * master;
235 
236   int readstatus;
237   class SoGLImage * glimage;
238   // we don't want to delete or update the glimage in the scheduler thread, so use this
239   // member to store whether we need to recreate the glimage in the next GLRender() pass
240   bool glimagevalid;
241   SbImage image;
242   SoFieldSensor * urlsensor;
243   SbBool allowprequalifycb;
244 
245   SoTimerSensor * timersensor;
246   SbBool finishedloading;
247   static void timersensor_cb(void * data, SoSensor * sensor);
248 
249   void readimage_cleanup(void);
250   SbBool isdestructing;
251 
252   SbStringList searchdirs;
253 
clearSearchDirs(void)254   void clearSearchDirs(void) {
255     int n = this->searchdirs.getLength();
256     for (int i = 0; i < n; i++) {
257       delete this->searchdirs[i];
258     }
259     this->searchdirs.truncate(0);
260   }
setSearchDirs(const SbStringList & sl)261   void setSearchDirs(const SbStringList & sl) {
262     this->clearSearchDirs();
263     int n = sl.getLength();
264     for (int i = 0; i < n; i++) {
265       this->searchdirs.append(new SbString(*sl[i]));
266     }
267   }
268 
269   static SbBool is_exiting;
270   static cc_sched * scheduler;
271 
272 #ifdef COIN_THREADSAFE
273   static SbMutex * glimagemutex;
lock_glimage(void)274   void lock_glimage(void) { this->glimagemutex->lock(); }
unlock_glimage(void)275   void unlock_glimage(void) { this->glimagemutex->unlock(); }
276 #else // !COIN_THREADSAFE
lock_glimage(void)277   void lock_glimage(void) { }
unlock_glimage(void)278   void unlock_glimage(void) { }
279 #endif // !COIN_THREADSAFE
280 
cleanup(void)281   static void cleanup(void)
282   {
283     is_exiting = TRUE;
284 
285 #ifdef COIN_THREADSAFE
286     delete glimagemutex;
287     glimagemutex = NULL;
288 #endif // COIN_THREADSAFE
289 
290     if (scheduler) {
291       cc_sched_destruct(scheduler);
292       scheduler = NULL;
293     }
294 
295     imagetexture_delay_fetch = TRUE;
296     imagetexture_prequalify_cb = NULL;
297     imagetexture_prequalify_closure = NULL;
298   }
299 };
300 
301 #ifdef COIN_THREADSAFE
302 SbMutex * SoVRMLImageTextureP::glimagemutex = NULL;
303 #endif // COIN_THREADSAFE
304 
305 cc_sched * SoVRMLImageTextureP::scheduler = NULL;
306 SbBool SoVRMLImageTextureP::is_exiting = FALSE;
307 
308 // *************************************************************************
309 
310 SO_NODE_SOURCE(SoVRMLImageTexture);
311 
312 // *************************************************************************
313 
314 // Doc in parent
315 void
initClass(void)316 SoVRMLImageTexture::initClass(void) // static
317 {
318   SoVRMLImageTextureP::is_exiting = FALSE;
319   SO_NODE_INTERNAL_INIT_CLASS(SoVRMLImageTexture, SO_VRML97_NODE_TYPE);
320   imagedata_maxage = 500;
321 
322   SoType type = SoVRMLImageTexture::getClassTypeId();
323   SoRayPickAction::addMethod(type, SoNode::rayPickS);
324 
325   // only use/create scheduler if COIN_THREADSAFE is defined, since we need
326   // the mutex below for this to be safely used
327 #ifdef COIN_THREADSAFE
328   if (cc_thread_implementation() != CC_NO_THREADS) {
329     SoVRMLImageTextureP::scheduler = cc_sched_construct(1);
330   }
331 #endif
332 
333 #ifdef COIN_THREADSAFE
334   SoVRMLImageTextureP::glimagemutex = new SbMutex;
335 #endif // COIN_THREADSAFE
336 
337   coin_atexit((coin_atexit_f *)SoVRMLImageTextureP::cleanup, CC_ATEXIT_NORMAL);
338 }
339 
340 // *************************************************************************
341 
342 #define PRIVATE(x) (x)->pimpl
343 
344 // *************************************************************************
345 
346 /*!
347   Constructor.
348 */
SoVRMLImageTexture(void)349 SoVRMLImageTexture::SoVRMLImageTexture(void)
350 {
351   PRIVATE(this) = new SoVRMLImageTextureP(this);
352 
353   SO_VRMLNODE_INTERNAL_CONSTRUCTOR(SoVRMLImageTexture);
354   SO_VRMLNODE_ADD_EMPTY_EXPOSED_MFIELD(url);
355 
356   PRIVATE(this)->glimage = NULL;
357   PRIVATE(this)->glimagevalid = false;
358   PRIVATE(this)->readstatus = 1;
359   PRIVATE(this)->allowprequalifycb = TRUE;
360   PRIVATE(this)->timersensor =
361     new SoTimerSensor(SoVRMLImageTextureP::timersensor_cb, PRIVATE(this));
362   PRIVATE(this)->timersensor->setInterval(SbTime(0.5));
363 
364   // use field sensor for url since we will load an image if
365   // filename changes. This is a time-consuming task which should
366   // not be done in notify().
367   PRIVATE(this)->urlsensor = new SoFieldSensor(urlSensorCB, this);
368   PRIVATE(this)->urlsensor->setPriority(0);
369   PRIVATE(this)->urlsensor->attach(&this->url);
370   PRIVATE(this)->isdestructing = FALSE;
371 }
372 
373 /*!
374   Destructor.
375 */
~SoVRMLImageTexture()376 SoVRMLImageTexture::~SoVRMLImageTexture()
377 {
378   delete PRIVATE(this)->timersensor;
379 
380   // just wait for all threads to finish reading
381   if (SoVRMLImageTextureP::scheduler) {
382     PRIVATE(this)->isdestructing = TRUE; // signal thread that we are destructing
383     cc_sched_wait_all(SoVRMLImageTextureP::scheduler);
384   }
385 
386   if (PRIVATE(this)->glimage) PRIVATE(this)->glimage->unref(NULL);
387   PRIVATE(this)->clearSearchDirs();
388   delete PRIVATE(this)->urlsensor;
389   delete PRIVATE(this);
390 }
391 
392 // *************************************************************************
393 
394 /*!
395   Sets the prequalify callback for ImageTexture nodes. This is a callback
396   that will be called when an image is about to be read.
397 */
398 void
setPrequalifyFileCallBack(VRMLPrequalifyFileCallback * cb,void * closure)399 SoVRMLImageTexture::setPrequalifyFileCallBack(VRMLPrequalifyFileCallback * cb,
400                                               void * closure)
401 {
402   imagetexture_prequalify_cb = cb;
403   imagetexture_prequalify_closure = closure;
404 }
405 
406 /*!
407   Sets whether the image loading is delayed until the first time the
408   image is needed, or if the image is loaded immediately when the
409   url field is changed/set. Default value is \e TRUE.
410 */
411 void
setDelayFetchURL(const SbBool onoff)412 SoVRMLImageTexture::setDelayFetchURL(const SbBool onoff)
413 {
414   imagetexture_delay_fetch = onoff;
415 }
416 
417 /*!
418   Enable prequalify file loading.
419 */
420 void
allowPrequalifyFile(SbBool enable)421 SoVRMLImageTexture::allowPrequalifyFile(SbBool enable)
422 {
423   PRIVATE(this)->allowprequalifycb = enable;
424 }
425 
426 static SoGLImage::Wrap
imagetexture_translate_wrap(const SbBool repeat)427 imagetexture_translate_wrap(const SbBool repeat)
428 {
429   if (repeat) return SoGLImage::REPEAT;
430   return SoGLImage::CLAMP_TO_EDGE;
431 }
432 
433 // Doc in parent
434 void
doAction(SoAction * action)435 SoVRMLImageTexture::doAction(SoAction * action)
436 {
437   SoState * state = action->getState();
438   int unit = SoTextureUnitElement::get(state);
439 
440   if ((unit == 0) && SoTextureOverrideElement::getImageOverride(state))
441     return;
442 
443   int nc;
444   SbVec2s size;
445   const unsigned char * bytes = PRIVATE(this)->image.getValue(size, nc);
446 
447   if (!PRIVATE(this)->image.hasData()) {
448     if (this->url.getNum()) {
449       // texture has not been loaded yet. Supply a dummy image and
450       // enable SoTextureImageElement so that texture coordinates are
451       // generated in generatePrimitives()
452       static unsigned char dummydata[] = { 0xff, 0xff, 0xff, 0xff };
453       bytes = dummydata;
454       size = SbVec2s(2,2);
455       nc = 1;
456     }
457   }
458 
459   if (size == SbVec2s(0,0)) {
460     SoMultiTextureEnabledElement::set(state, this, unit, FALSE);
461   }
462   else {
463     SoMultiTextureEnabledElement::set(state, this, unit, TRUE);
464     SoMultiTextureImageElement::set(state, this, unit,
465                                     size, nc, bytes,
466                                     (this->repeatS.getValue() ?
467                                      SoMultiTextureImageElement::REPEAT :
468                                      SoMultiTextureImageElement::CLAMP_TO_BORDER),
469                                     (this->repeatT.getValue() ?
470                                      SoMultiTextureImageElement::REPEAT :
471                                      SoMultiTextureImageElement::CLAMP_TO_BORDER),
472                                     SoMultiTextureImageElement::MODULATE,
473                                     SbColor(1.0f, 1.0f, 1.0f));
474   }
475 }
476 
477 void
rayPick(SoRayPickAction * action)478 SoVRMLImageTexture::rayPick(SoRayPickAction * action)
479 {
480   SoVRMLImageTexture::doAction(action);
481 }
482 
483 // Doc in parent
484 void
GLRender(SoGLRenderAction * action)485 SoVRMLImageTexture::GLRender(SoGLRenderAction * action)
486 {
487   SoState * state = action->getState();
488 
489   int unit = SoTextureUnitElement::get(state);
490   if ((unit == 0) && SoTextureOverrideElement::getImageOverride(state))
491     return;
492 
493   float quality = SoTextureQualityElement::get(state);
494 
495   PRIVATE(this)->lock_glimage();
496 
497   SoTextureScalePolicyElement::Policy scalepolicy =
498     SoTextureScalePolicyElement::get(state);
499   SbBool needbig = (scalepolicy == SoTextureScalePolicyElement::FRACTURE);
500   SbBool isbig =
501     PRIVATE(this)->glimage &&
502     PRIVATE(this)->glimage->getTypeId() == SoGLBigImage::getClassTypeId();
503 
504   if (!PRIVATE(this)->glimagevalid ||
505       (!PRIVATE(this)->glimage || (needbig != isbig))) {
506     if (PRIVATE(this)->glimage) {
507       PRIVATE(this)->glimage->unref(state);
508     }
509     if (needbig) {
510       PRIVATE(this)->glimage = new SoGLBigImage();
511     }
512     else {
513       PRIVATE(this)->glimage = new SoGLImage();
514     }
515     PRIVATE(this)->glimagevalid = true;
516     if (scalepolicy == SoTextureScalePolicyElement::SCALE_DOWN) {
517       PRIVATE(this)->glimage->setFlags(PRIVATE(this)->glimage->getFlags()|SoGLImage::SCALE_DOWN);
518     }
519 
520     PRIVATE(this)->glimage->setData(&PRIVATE(this)->image,
521                                     imagetexture_translate_wrap(this->repeatS.getValue()),
522                                     imagetexture_translate_wrap(this->repeatT.getValue()),
523                                     quality);
524     PRIVATE(this)->glimage->setEndFrameCallback(glimage_callback, this);
525 
526     // don't cache while creating a texture object
527     SoCacheElement::setInvalid(TRUE);
528     if (state->isCacheOpen()) {
529       SoCacheElement::invalidate(state);
530     }
531   }
532 
533   if (PRIVATE(this)->glimage && PRIVATE(this)->glimage->getTypeId() == SoGLBigImage::getClassTypeId()) {
534     SoCacheElement::invalidate(state);
535   }
536 
537   PRIVATE(this)->unlock_glimage();
538 
539   SoGLMultiTextureImageElement::set(state, this, unit,
540                                     PRIVATE(this)->glimage,
541                                     SoMultiTextureImageElement::MODULATE,
542                                     SbColor(1.0f, 1.0f, 1.0f));
543 
544   SbBool enable = PRIVATE(this)->glimage &&
545     quality > 0.0f &&
546     PRIVATE(this)->glimage->getImage() &&
547     PRIVATE(this)->glimage->getImage()->hasData();
548 
549   SoMultiTextureEnabledElement::set(state,
550                                     this,
551                                     unit,
552                                     enable);
553 
554   if (this->isOverride() && (unit == 0)) {
555     SoTextureOverrideElement::setImageOverride(state, TRUE);
556   }
557 }
558 
559 // Doc in parent
560 void
callback(SoCallbackAction * action)561 SoVRMLImageTexture::callback(SoCallbackAction * action)
562 {
563   SoVRMLImageTexture::doAction(action);
564 }
565 
566 // Doc in parent
567 SbBool
readInstance(SoInput * in,unsigned short flags)568 SoVRMLImageTexture::readInstance(SoInput * in,
569                                  unsigned short flags)
570 {
571   PRIVATE(this)->urlsensor->detach();
572   SbBool ret = inherited::readInstance(in, flags);
573   this->setReadStatus((int) ret);
574   if (ret) {
575     // need to copy the SoInput directories, so that the texture is
576     // found again if it's thrown out of memory (can happen when it's
577     // a long time since it has been used)
578     PRIVATE(this)->setSearchDirs(SoInput::getDirectories());
579     if (!this->loadUrl()) {
580       SoReadError::post(in, "Could not read texture file: %s",
581                         url[0].getString());
582       this->setReadStatus(FALSE);
583     }
584   }
585   PRIVATE(this)->urlsensor->attach(&this->url);
586   return ret;
587 }
588 
589 /*!
590   Returns the read status.
591 */
592 int
getReadStatus(void) const593 SoVRMLImageTexture::getReadStatus(void) const
594 {
595   return PRIVATE(this)->readstatus;
596 }
597 
598 /*!
599   Sets the read status.
600 */
601 void
setReadStatus(int status)602 SoVRMLImageTexture::setReadStatus(int status)
603 {
604   PRIVATE(this)->readstatus = status;
605 }
606 
607 //
608 // Called from readInstance() or when user changes the
609 // filename field.
610 //
611 SbBool
loadUrl(void)612 SoVRMLImageTexture::loadUrl(void)
613 {
614   PRIVATE(this)->lock_glimage();
615   PRIVATE(this)->glimagevalid = false;
616   PRIVATE(this)->unlock_glimage();
617 
618   SbBool retval = TRUE;
619   if (this->url.getNum() && this->url[0].getLength()) {
620     const SbStringList & sl = PRIVATE(this)->searchdirs;
621     if (sl.getLength() == 0) { // will be empty if the node isn't read but created in C++
622       PRIVATE(this)->setSearchDirs(SoInput::getDirectories());
623     }
624     if (imagetexture_delay_fetch) {
625       // instruct SbImage to call image_read_cb the first time the image
626       // data is requested (typically when some shape using the texture
627       // is inside the view frustum).
628       retval = PRIVATE(this)->image.scheduleReadFile(image_read_cb, this,
629                                                      this->url[0],
630                                                      sl.getArrayPtr(), sl.getLength());
631 
632     }
633     else {
634       retval = this->readImage(this->url[0]);
635     }
636   }
637   else {
638     retval = TRUE;
639   }
640   return retval;
641 }
642 
643 // sensor callback used for deleting old GLImage instances
644 static void
imagetexture_glimage_delete(void * closure,SoSensor * s)645 imagetexture_glimage_delete(void * closure, SoSensor * s)
646 {
647   SoGLImage * img = (SoGLImage*) closure;
648   img->unref(NULL);
649   delete s;
650 }
651 
652 //
653 // used for checking if this texture should be purged from memory
654 //
655 void
glimage_callback(void * closure)656 SoVRMLImageTexture::glimage_callback(void * closure)
657 {
658   SoVRMLImageTexture * thisp = (SoVRMLImageTexture*) closure;
659   PRIVATE(thisp)->lock_glimage();
660   if (PRIVATE(thisp)->glimage) {
661     int age = PRIVATE(thisp)->glimage->getNumFramesSinceUsed();
662     if (age > imagedata_maxage) {
663       // we can't delete the glimage here, since it's locked by
664       // SoGLImage. Use a sensor to delete it the next time the
665       // delayqueue sensors are processed.
666       if (PRIVATE(thisp)->glimage) {
667         PRIVATE(thisp)->glimage->setEndFrameCallback(NULL, NULL);
668         // allocate new sensor. It will be deleted in the sensor
669         // callback. We do this here since this node might be outside
670         // the view frustum, and GLRender() may not be called anytime
671         // soon.
672         SoOneShotSensor * s = new SoOneShotSensor(imagetexture_glimage_delete, PRIVATE(thisp)->glimage);
673         s->schedule();
674         // clear the GLImage in this node. The sensor has a pointer to it and will delete it
675         PRIVATE(thisp)->glimage = NULL;
676         PRIVATE(thisp)->glimagevalid = false;
677       }
678       PRIVATE(thisp)->unlock_glimage();
679       PRIVATE(thisp)->image.setValue(SbVec2s(0,0), 0, NULL);
680       (void) thisp->loadUrl();
681       return;
682     }
683   }
684   PRIVATE(thisp)->unlock_glimage();
685 }
686 
687 SbBool
default_prequalify_cb(const SbString & url,void * COIN_UNUSED_ARG (closure),SoVRMLImageTexture * thisp)688 SoVRMLImageTexture::default_prequalify_cb(const SbString & url,  void * COIN_UNUSED_ARG(closure),
689                                           SoVRMLImageTexture * thisp)
690 {
691   SbBool ret = TRUE;
692   if (!SoVRMLImageTextureP::is_exiting && !PRIVATE(thisp)->isdestructing) {
693     const SbStringList & sl = SoInput::getDirectories();
694     ret = PRIVATE(thisp)->image.readFile(url, sl.getArrayPtr(), sl.getLength());
695   }
696   return ret;
697 }
698 
699 // needed to pass data to a new thread
700 class imagetexture_thread_data {
701 public:
702   SoVRMLImageTexture * thisp;
703   SbString filename;
704 };
705 
706 //
707 // multithread loading thread.
708 //
709 void
read_thread(void * closure)710 SoVRMLImageTexture::read_thread(void * closure)
711 {
712   imagetexture_thread_data * data = (imagetexture_thread_data*) closure;
713   data->thisp->readImage(data->filename);
714   // we allocated this before staring the thread
715   delete data;
716 }
717 
718 // callback for SoOneShotSensor which is used to read image when
719 // Coin is compiled without the threads module.
720 void
oneshot_readimage_cb(void * closure,SoSensor * sensor)721 SoVRMLImageTexture::oneshot_readimage_cb(void * closure, SoSensor * sensor)
722 {
723   imagetexture_thread_data * data = (imagetexture_thread_data*) closure;
724   data->thisp->readImage(data->filename);
725   // delete both the sensor and the data
726   delete sensor;
727   delete data;
728 }
729 
730 //
731 // called (from SbImage) when image data is needed.
732 //
733 SbBool
image_read_cb(const SbString & filename,SbImage * image,void * closure)734 SoVRMLImageTexture::image_read_cb(const SbString & filename, SbImage * image, void * closure)
735 {
736   SoVRMLImageTexture * thisp = (SoVRMLImageTexture*) closure;
737   assert(&PRIVATE(thisp)->image == image);
738 
739   // start a timer sensor which polls the thread that loads images, to
740   // detect when it's done:
741   PRIVATE(thisp)->finishedloading = FALSE; // this will be TRUE when finished
742   PRIVATE(thisp)->timersensor->schedule();
743 
744   imagetexture_thread_data * data = new imagetexture_thread_data;
745   data->thisp = thisp;
746   data->filename = filename;
747 
748   if (SoVRMLImageTextureP::scheduler) {
749     // use a separate thread to load the image
750     cc_sched_schedule(SoVRMLImageTextureP::scheduler,
751                       read_thread, data, 0);
752   }
753   else {
754     // schedule a sensor to read the image as soon as the delay sensor
755     // queue is processed (typically when the run-time system is idle)
756     SoOneShotSensor * sensor = new SoOneShotSensor(oneshot_readimage_cb, data);
757     sensor->schedule();
758   }
759 
760   return TRUE;
761 }
762 
763 //
764 // called when filename changes
765 //
766 void
urlSensorCB(void * data,SoSensor *)767 SoVRMLImageTexture::urlSensorCB(void * data, SoSensor *)
768 {
769   SoVRMLImageTexture * thisp = (SoVRMLImageTexture*) data;
770 
771   PRIVATE(thisp)->lock_glimage();
772   PRIVATE(thisp)->glimagevalid = false;
773   PRIVATE(thisp)->unlock_glimage();
774 
775   thisp->setReadStatus(1);
776   if (thisp->url.getNum() && thisp->url[0].getLength() &&
777       !thisp->loadUrl()) {
778     SoDebugError::postWarning("SoVRMLImageTexture::urlSensorCB",
779                               "Image file could not be read: %s",
780                               thisp->url[0].getString());
781     thisp->setReadStatus(0);
782   }
783   else { // empty image?
784     if (thisp->url.getNum() == 0 || thisp->url[0].getLength() == 0) {
785       // wait for threads to finish in case a new thread is used to
786       // load the previous image, and the thread has not finished yet.
787       if (SoVRMLImageTextureP::scheduler) {
788         cc_sched_wait_all(SoVRMLImageTextureP::scheduler);
789       }
790 
791       thisp->pimpl->image.setValue(SbVec2s(0,0), 0, NULL);
792     }
793   }
794 }
795 
796 // helper function that either loads the image using the default
797 // loader, or calls the prequalify callback
798 SbBool
readImage(const SbString & filename)799 SoVRMLImageTexture::readImage(const SbString & filename)
800 {
801   SbBool retval = TRUE;
802   if (PRIVATE(this)->allowprequalifycb && imagetexture_prequalify_cb) {
803     retval = imagetexture_prequalify_cb(filename, imagetexture_prequalify_closure,
804                                         this);
805   }
806   else {
807     retval = default_prequalify_cb(filename, NULL, this);
808   }
809   PRIVATE(this)->lock_glimage();
810   PRIVATE(this)->glimagevalid = false;
811   PRIVATE(this)->unlock_glimage();
812 
813   // set flag that timer sensor will test.
814   PRIVATE(this)->finishedloading = TRUE;
815   return retval;
816 }
817 
818 /*!
819   Set the image data for this node. Can be used by the prequalify
820   callback to set the data in the node.
821 */
822 void
setImage(const SbImage & image)823 SoVRMLImageTexture::setImage(const SbImage & image)
824 {
825   PRIVATE(this)->image = image;
826   PRIVATE(this)->lock_glimage();
827   PRIVATE(this)->glimagevalid = false;
828   PRIVATE(this)->unlock_glimage();
829   this->touch(); // destroy caches using this node
830 }
831 
832 /*!
833   Returns the texture image.
834 */
835 const SbImage *
getImage(void) const836 SoVRMLImageTexture::getImage(void) const
837 {
838   return &PRIVATE(this)->image;
839 }
840 
841 /*!
842   \COININTERNAL
843 */
844 void
setImageDataMaxAge(const uint32_t maxage)845 SoVRMLImageTexture::setImageDataMaxAge(const uint32_t maxage)
846 {
847   imagedata_maxage = maxage;
848 }
849 
850 void
timersensor_cb(void * data,SoSensor * COIN_UNUSED_ARG (sensor))851 SoVRMLImageTextureP::timersensor_cb(void * data, SoSensor * COIN_UNUSED_ARG(sensor))
852 {
853   SoVRMLImageTextureP * thisp = (SoVRMLImageTextureP*) data;
854 
855   if (thisp->finishedloading) {
856     thisp->master->touch(); // trigger redraw
857     thisp->timersensor->unschedule();
858   }
859 }
860 
861 #undef PRIVATE
862 
863 #endif // HAVE_VRML97
864