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