/**************************************************************************\
*
* This file is part of the Coin 3D visualization library.
* Copyright (C) 1998-2007 by Systems in Motion. All rights reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* ("GPL") version 2 as published by the Free Software Foundation.
* See the file LICENSE.GPL at the root directory of this source
* distribution for additional information about the GNU GPL.
*
* For using Coin with software that can not be combined with the GNU
* GPL, and for taking advantage of the additional benefits of our
* support services, please contact Systems in Motion about acquiring
* a Coin Professional Edition License.
*
* See http://www.coin3d.org/ for more information.
*
* Systems in Motion, Postboks 1283, Pirsenteret, 7462 Trondheim, NORWAY.
* http://www.sim.no/ sales@sim.no coin-support@coin3d.org
*
\**************************************************************************/
/*!
\class SoSceneTexture2 SoSceneTexture2.h Inventor/nodes/SoSceneTexture2.h
\brief The SoSceneTexture2 class is used to create a 2D texture from a Coin scene graph.
\ingroup nodes
Lets the rendering of a scene graph be specified as a texture image
to be used in another scene graph. Set up the scene graph used for a
texture in the SoSceneTexture2::scene field.
This node behaves exactly like SoTexture2 when it comes mapping the
actual texture onto subsequent geometry. Please read the SoTexture2
documentation for more information about how textures are mapped
onto shapes.
A notable feature of this node is that it will use offscreen
pbuffers for hardware accelerated rendering, if they are available
from the OpenGL driver. WGL, GLX and AGL, for OpenGL drivers on
Microsoft Windows, X11 and Mac OS X, respectively, all support the
OpenGL Architecture Review Board (ARB) pbuffer extension in later
incarnations from most OpenGL vendors.
Note also that the offscreen pbuffer will be used directly on the
card as a texture, with no costly round trip back and forth from CPU
memory, if the OpenGL driver supports the recent ARB_render_texture
extension.
An important limitation is that textures should have dimensions that
are equal to a whole power-of-two, see documentation for
SoSceneTexture::size.
FILE FORMAT/DEFAULTS:
\code
SceneTexture2 {
size 256 256
scene NULL
backgroundColor 0 0 0 0
transparencyFunction NONE
wrapS REPEAT
wrapT REPEAT
model MODULATE
blendColor 0 0 0
}
\endcode
\since Coin 2.2
*/
// *************************************************************************
/*!
\enum SoSceneTexture2::Model
Texture mapping model, for deciding how to "merge" the texturemap
with the object it is mapped unto.
*/
/*!
\var SoSceneTexture2::Model SoSceneTexture2::MODULATE
Texture color is multiplied by the polygon color. The result will
be Phong shaded (if light model is PHONG).
*/
/*!
\var SoSceneTexture2::Model SoSceneTexture2::DECAL
Texture image overwrites polygon shading. Textured pixels will
not be Phong shaded. Has undefined behaviour for grayscale and
grayscale-alpha textures.
*/
/*!
\var SoSceneTexture2::Model SoSceneTexture2::BLEND
This model is normally used with monochrome textures (i.e. textures
with one or two components). The first component, the intensity, is
then used to blend between the shaded color of the polygon and the
SoSceneTexture2::blendColor.
*/
/*!
\var SoSceneTexture2::Model SoSceneTexture2::REPLACE
Texture image overwrites polygon shading. Textured pixels will not
be Phong shaded. Supports grayscale and grayscale alpha
textures. This feature requires OpenGL 1.1. MODULATE will be used if
OpenGL version < 1.1 is detected.
*/
/*!
\enum SoSceneTexture2::Wrap
Enumeration of wrapping strategies which can be used when the
texturemap doesn't cover the full extent of the geometry.
*/
/*!
\var SoSceneTexture2::Wrap SoSceneTexture2::REPEAT
Repeat texture when coordinate is not between 0 and 1.
*/
/*!
\var SoSceneTexture2::Wrap SoSceneTexture2::CLAMP
Clamp coordinate between 0 and 1.
*/
/*!
\enum SoSceneTexture2::TransparencyFunction
For deciding how the texture's alpha channel is handled. It's not
possible to automatically detect this, since the texture is stored
only on the graphics card's memory, and it'd be too slow to fetch
the image to test the alpha channel like Coin does for regular
textures.
*/
/*!
\var SoSceneTexture2::Transparency SoSceneTexture2::NONE
The alpha channel is ignored.
*/
/*!
\var SoSceneTexture2::Transparency SoSceneTexture2::ALPHA_TEST
An alpha test function is used.
*/
/*!
\var SoSceneTexture2::Transparency SoSceneTexture2::ALPHA_BLEND
Alpha blending is used.
*/
/*!
\var SoSFEnum SoSceneTexture2::wrapS
Wrapping strategy for the S coordinate when the texturemap is
narrower than the object to map unto.
Default value is SoSceneTexture2::REPEAT.
*/
/*!
\var SoSFEnum SoSceneTexture2::wrapT
Wrapping strategy for the T coordinate when the texturemap is
shorter than the object to map unto.
Default value is SoSceneTexture2::REPEAT.
*/
/*!
\var SoSFEnum SoSceneTexture2::model
Texturemapping model for how the texturemap is "merged" with the
polygon primitives it is applied to. Default value is
SoSceneTexture2::MODULATE.
*/
/*!
\var SoSFColor SoSceneTexture2::blendColor
Blend color. Used when SoSceneTexture2::model is SoSceneTexture2::BLEND.
Default color value is [0, 0, 0], black, which means no contribution
to the blending is made.
*/
/*!
\var SoSFVec2s SoSceneTexture2::size
The size of the texture.
This node currently only supports power of two textures. If the
size is not a power of two, the value will be rounded upwards to the
next power of two.
*/
/*!
\var SoSFNode SoSceneTexture2::scene
The scene graph that is rendered into the texture.
*/
/*!
\var SoSFVec4f SoSceneTexture2::backgroundColor
The color the color buffer is cleared to before rendering the scene.
Default value is (0.0f, 0.0f, 0.0f, 0.0f).
*/
/*!
\var SoSFEnum SoSceneTexture2::transparencyFunction
The transparency function used. Default value is NONE.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#ifdef HAVE_CONFIG_H
#include
#endif // HAVE_CONFIG_H
#ifdef COIN_THREADSAFE
#include
#endif // COIN_THREADSAFE
// *************************************************************************
class SoSceneTexture2P {
public:
SoSceneTexture2P(SoSceneTexture2 * api);
~SoSceneTexture2P();
SoSceneTexture2 * api;
void * glcontext;
SbVec2s glcontextsize;
SoGLImage * glimage;
SbBool pbuffervalid;
SbBool glimagevalid;
SbBool glrectangle;
void updatePBuffer(SoState * state, const float quality);
SoGLRenderAction * glaction;
static void prerendercb(void * userdata, SoGLRenderAction * action);
#ifdef COIN_THREADSAFE
SbMutex mutex;
#endif // COIN_THREADSAFE
SbBool canrendertotexture;
unsigned char * offscreenbuffer;
int offscreenbuffersize;
};
// *************************************************************************
#undef PRIVATE
#define PRIVATE(obj) obj->pimpl
#ifdef COIN_THREADSAFE
#define LOCK_GLIMAGE(_thisp_) (PRIVATE(_thisp_)->mutex.lock())
#define UNLOCK_GLIMAGE(_thisp_) (PRIVATE(_thisp_)->mutex.unlock())
#else // COIN_THREADSAFE
#define LOCK_GLIMAGE(_thisp_)
#define UNLOCK_GLIMAGE(_thisp_)
#endif // COIN_THREADSAFE
SO_NODE_SOURCE(SoSceneTexture2);
// Documented in superclass.
void
SoSceneTexture2::initClass(void)
{
SO_NODE_INTERNAL_INIT_CLASS(SoSceneTexture2, SO_FROM_COIN_2_2);
SO_ENABLE(SoGLRenderAction, SoGLTextureImageElement);
SO_ENABLE(SoGLRenderAction, SoGLTextureEnabledElement);
SO_ENABLE(SoGLRenderAction, SoGLTexture3EnabledElement);
SO_ENABLE(SoCallbackAction, SoTextureImageElement);
SO_ENABLE(SoCallbackAction, SoTextureEnabledElement);
SO_ENABLE(SoCallbackAction, SoTexture3EnabledElement);
SO_ENABLE(SoRayPickAction, SoTextureImageElement);
SO_ENABLE(SoRayPickAction, SoTextureEnabledElement);
SO_ENABLE(SoRayPickAction, SoTexture3EnabledElement);
}
static SoGLImage::Wrap
translateWrap(const SoSceneTexture2::Wrap wrap)
{
if (wrap == SoSceneTexture2::REPEAT) return SoGLImage::REPEAT;
return SoGLImage::CLAMP;
}
SoSceneTexture2::SoSceneTexture2(void)
{
this->pimpl = new SoSceneTexture2P(this);
SO_NODE_INTERNAL_CONSTRUCTOR(SoSceneTexture2);
SO_NODE_ADD_FIELD(size, (256, 256));
SO_NODE_ADD_FIELD(scene, (NULL));
SO_NODE_ADD_FIELD(backgroundColor, (0.0f, 0.0f, 0.0f, 0.0f));
SO_NODE_ADD_FIELD(transparencyFunction, (NONE));
SO_NODE_ADD_FIELD(wrapS, (REPEAT));
SO_NODE_ADD_FIELD(wrapT, (REPEAT));
SO_NODE_ADD_FIELD(model, (MODULATE));
SO_NODE_ADD_FIELD(blendColor, (0.0f, 0.0f, 0.0f));
SO_NODE_DEFINE_ENUM_VALUE(Model, MODULATE);
SO_NODE_DEFINE_ENUM_VALUE(Model, DECAL);
SO_NODE_DEFINE_ENUM_VALUE(Model, BLEND);
SO_NODE_DEFINE_ENUM_VALUE(Model, REPLACE);
SO_NODE_DEFINE_ENUM_VALUE(Wrap, REPEAT);
SO_NODE_DEFINE_ENUM_VALUE(Wrap, CLAMP);
SO_NODE_DEFINE_ENUM_VALUE(TransparencyFunction, NONE);
SO_NODE_DEFINE_ENUM_VALUE(TransparencyFunction, ALPHA_BLEND);
SO_NODE_DEFINE_ENUM_VALUE(TransparencyFunction, ALPHA_TEST);
SO_NODE_SET_SF_ENUM_TYPE(wrapS, Wrap);
SO_NODE_SET_SF_ENUM_TYPE(wrapT, Wrap);
SO_NODE_SET_SF_ENUM_TYPE(model, Model);
SO_NODE_SET_SF_ENUM_TYPE(transparencyFunction, TransparencyFunction);
}
SoSceneTexture2::~SoSceneTexture2(void)
{
delete PRIVATE(this);
}
// Documented in superclass.
void
SoSceneTexture2::GLRender(SoGLRenderAction * action)
{
SoState * state = action->getState();
if (SoTextureOverrideElement::getImageOverride(state))
return;
float quality = SoTextureQualityElement::get(state);
const cc_glglue * glue = cc_glglue_instance(SoGLCacheContextElement::get(state));
SoNode * root = this->scene.getValue();
LOCK_GLIMAGE(this);
if (root && (!PRIVATE(this)->pbuffervalid || !PRIVATE(this)->glimagevalid)) {
PRIVATE(this)->updatePBuffer(state, quality);
// don't cache when we change the glimage
SoCacheElement::setInvalid(TRUE);
if (state->isCacheOpen()) {
SoCacheElement::invalidate(state);
}
}
UNLOCK_GLIMAGE(this);
SoTextureImageElement::Model glmodel = (SoTextureImageElement::Model)
this->model.getValue();
if (glmodel == SoTextureImageElement::REPLACE) {
if (!cc_glglue_glversion_matches_at_least(glue, 1, 1, 0)) {
static int didwarn = 0;
if (!didwarn) {
SoDebugError::postWarning("SoSceneTexture2::GLRender",
"Unable to use the GL_REPLACE texture model. "
"Your OpenGL version is < 1.1. "
"Using GL_MODULATE instead.");
didwarn = 1;
}
// use MODULATE and not DECAL, since DECAL only works for RGB
// and RGBA textures
glmodel = SoTextureImageElement::MODULATE;
}
}
int unit = SoTextureUnitElement::get(state);
int maxunits = cc_glglue_max_texture_units(glue);
if (unit == 0) {
SoGLTextureImageElement::set(state, this,
PRIVATE(this)->glimage,
glmodel,
this->blendColor.getValue());
SoGLTexture3EnabledElement::set(state, this, FALSE);
if (PRIVATE(this)->glimage && PRIVATE(this)->glrectangle) {
SoGLTextureEnabledElement::enableRectangle(state, this);
}
else {
SoGLTextureEnabledElement::set(state,
PRIVATE(this)->glimage != NULL &&
quality > 0.0f);
}
if (this->isOverride()) {
SoTextureOverrideElement::setImageOverride(state, TRUE);
}
}
else if (unit < maxunits) {
SoGLMultiTextureImageElement::set(state, this, unit,
PRIVATE(this)->glimage,
glmodel,
this->blendColor.getValue());
SoGLMultiTextureEnabledElement::set(state, this, unit,
PRIVATE(this)->glimage != NULL &&
quality > 0.0f);
}
else {
// we already warned in SoTextureUnit. I think it's best to just
// ignore the texture here so that all texture for non-supported
// units will be ignored. pederb, 2003-11-04
}
}
// Documented in superclass.
void
SoSceneTexture2::doAction(SoAction * action)
{
#if 0 // disabled until we figure out what to do here, pederb 2003-11-27
SoState * state = action->getState();
if (SoTextureOverrideElement::getImageOverride(state))
return;
SoTexture3EnabledElement::set(state, this, FALSE);
if (size != SbVec2s(0,0)) {
SoTextureImageElement::set(state, this,
size, nc, bytes,
(int)this->wrapT.getValue(),
(int)this->wrapS.getValue(),
(SoTextureImageElement::Model) model.getValue(),
this->blendColor.getValue());
SoTextureEnabledElement::set(state, this, TRUE);
}
else {
SoTextureImageElement::setDefault(state, this);
SoTextureEnabledElement::set(state, this, FALSE);
}
if (this->isOverride()) {
SoTextureOverrideElement::setImageOverride(state, TRUE);
}
#endif // disabled
}
// Documented in superclass.
void
SoSceneTexture2::callback(SoCallbackAction * action)
{
SoSceneTexture2::doAction(action);
}
// Documented in superclass.
void
SoSceneTexture2::rayPick(SoRayPickAction * action)
{
SoSceneTexture2::doAction(action);
}
// Documented in superclass.
void
SoSceneTexture2::notify(SoNotList * list)
{
SoField * f = list->getLastField();
if (f == &this->scene ||
f == &this->size) {
// rerender scene
PRIVATE(this)->pbuffervalid = FALSE;
}
else if (f == &this->wrapS ||
f == &this->wrapT ||
f == &this->model ||
f == &this->transparencyFunction) {
// no need to render scene again, but update the texture object
PRIVATE(this)->glimagevalid = FALSE;
}
inherited::notify(list);
}
// Documented in superclass.
void
SoSceneTexture2::write(SoWriteAction * action)
{
inherited::write(action);
}
#undef PRIVATE
// *************************************************************************
#undef PUBLIC
#define PUBLIC(obj) obj->api
SoSceneTexture2P::SoSceneTexture2P(SoSceneTexture2 * apiptr)
{
this->api = apiptr;
this->glcontext = NULL;
this->pbuffervalid = FALSE;
this->glimagevalid = FALSE;
this->glimage = NULL;
this->glaction = NULL;
this->glcontextsize.setValue(-1,-1);
this->glrectangle = FALSE;
this->offscreenbuffer = NULL;
this->offscreenbuffersize = 0;
this->canrendertotexture = FALSE;
}
SoSceneTexture2P::~SoSceneTexture2P()
{
if (this->glimage) this->glimage->unref(NULL);
if (this->glcontext != NULL) {
cc_glglue_context_destruct(this->glcontext);
}
delete[] this->offscreenbuffer;
delete this->glaction;
}
void
SoSceneTexture2P::updatePBuffer(SoState * state, const float quality)
{
SbVec2s size = PUBLIC(this)->size.getValue();
SoNode * scene = PUBLIC(this)->scene.getValue();
assert(scene);
if ((this->glcontext && this->glcontextsize != size) || (size == SbVec2s(0,0))) {
if (this->glimage) {
this->glimage->unref(state);
this->glimage = NULL;
}
if (this->glcontext) {
cc_glglue_context_destruct(this->glcontext);
this->glcontextsize.setValue(-1,-1);
this->glcontext = NULL;
}
delete this->glaction;
this->glaction = NULL;
this->glimagevalid = FALSE;
}
if (size == SbVec2s(0,0)) return;
// FIXME: temporary until non power of two textures are supported,
// pederb 2003-12-05
size[0] = (short) coin_geq_power_of_two(size[0]);
size[1] = (short) coin_geq_power_of_two(size[1]);
if (this->glcontext == NULL) {
this->glcontextsize = size;
const cc_glglue * glue = cc_glglue_instance(SoGLCacheContextElement::get(state));
// disabled until an pbuffer extension is available to create a
// render-to-texture pbuffer that has a non power of two size.
// pederb, 2003-12-05
#if 0
if (!glue->has_ext_texture_rectangle) {
#else
if (1) {
#endif
this->glcontextsize[0] = (short) coin_geq_power_of_two(size[0]);
this->glcontextsize[1] = (short) coin_geq_power_of_two(size[1]);
if (this->glcontextsize != size) {
static int didwarn = 0;
if (!didwarn) {
SoDebugError::postWarning("SoSceneTexture2P::updatePBuffer",
"Requested non power of two size, but your OpenGL "
"driver lacks support for such pbuffer textures.");
didwarn = 1;
}
}
}
this->glrectangle = FALSE;
if (!coin_is_power_of_two(this->glcontextsize[0]) ||
!coin_is_power_of_two(this->glcontextsize[1])) {
// we only get here if the OpenGL driver can handle non power of
// two textures/pbuffers.
this->glrectangle = TRUE;
}
// FIXME: make it possible to specify what kind of context you want
// (RGB or RGBA, I guess). pederb, 2003-11-27
this->glcontext = cc_glglue_context_create_offscreen(this->glcontextsize[0],
this->glcontextsize[1]);
this->canrendertotexture = cc_glglue_context_can_render_to_texture(this->glcontext);
if (this->glaction) delete this->glaction;
this->glaction = new SoGLRenderAction(SbViewportRegion(this->glcontextsize));
this->glaction->setCacheContext((int) SoGLCacheContextElement::getUniqueCacheContext());
this->glaction->addPreRenderCallback(SoSceneTexture2P::prerendercb,
(void*) PUBLIC(this));
this->glimagevalid = FALSE;
}
if (!this->pbuffervalid) {
assert(this->glaction != NULL);
assert(this->glcontext != NULL);
this->glaction->setTransparencyType((SoGLRenderAction::TransparencyType)
SoShapeStyleElement::getTransparencyType(state));
cc_glglue_context_make_current(this->glcontext);
glEnable(GL_DEPTH_TEST);
this->glaction->apply(scene);
// Make sure that rendering to pBuffer is completed to avoid
// flickering. DON'T REMOVE THIS. You have been warned.
glFlush();
if (!this->canrendertotexture) {
SbVec2s size = this->glcontextsize;
int reqbytes = size[0]*size[1]*4;
if (reqbytes > this->offscreenbuffersize) {
delete[] this->offscreenbuffer;
this->offscreenbuffer = new unsigned char[reqbytes];
this->offscreenbuffersize = reqbytes;
}
glPixelStorei(GL_PACK_ALIGNMENT, 1);
glReadPixels(0, 0, size[0], size[1], GL_RGBA, GL_UNSIGNED_BYTE,
this->offscreenbuffer);
glPixelStorei(GL_PACK_ALIGNMENT, 4);
}
cc_glglue_context_reinstate_previous(this->glcontext);
}
if (!this->glimagevalid || (this->glimage == NULL)) {
// just delete old glimage
if (this->glimage) {
this->glimage->unref(state);
this->glimage = NULL;
}
this->glimage = new SoGLImage;
uint32_t flags = this->glimage->getFlags();
if (this->glrectangle) {
flags |= SoGLImage::RECTANGLE;
}
switch ((SoSceneTexture2::TransparencyFunction) (PUBLIC(this)->transparencyFunction.getValue())) {
case SoSceneTexture2::NONE:
flags |= SoGLImage::FORCE_TRANSPARENCY_FALSE|SoGLImage::FORCE_ALPHA_TEST_FALSE;
break;
case SoSceneTexture2::ALPHA_TEST:
flags |= SoGLImage::FORCE_TRANSPARENCY_TRUE|SoGLImage::FORCE_ALPHA_TEST_TRUE;
break;
case SoSceneTexture2::ALPHA_BLEND:
flags |= SoGLImage::FORCE_TRANSPARENCY_TRUE|SoGLImage::FORCE_ALPHA_TEST_FALSE;
break;
default:
assert(0 && "should not get here");
break;
}
if (this->canrendertotexture) {
// bind texture to pbuffer
this->glimage->setPBuffer(state, this->glcontext,
translateWrap((SoSceneTexture2::Wrap)PUBLIC(this)->wrapS.getValue()),
translateWrap((SoSceneTexture2::Wrap)PUBLIC(this)->wrapT.getValue()),
quality);
}
this->glimage->setFlags(flags);
}
if (!this->canrendertotexture) {
assert(this->glimage);
assert(this->offscreenbuffer);
this->glimage->setData(this->offscreenbuffer,
this->glcontextsize,
4,
translateWrap((SoSceneTexture2::Wrap)PUBLIC(this)->wrapS.getValue()),
translateWrap((SoSceneTexture2::Wrap)PUBLIC(this)->wrapT.getValue()),
quality);
}
this->glimagevalid = TRUE;
this->pbuffervalid = TRUE;
}
void
SoSceneTexture2P::prerendercb(void * userdata, SoGLRenderAction * action)
{
SoSceneTexture2 * thisp = (SoSceneTexture2*) userdata;
SbVec4f col = thisp->backgroundColor.getValue();
glClearColor(col[0], col[1], col[2], col[3]);
glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT);
}
#undef PUBLIC
#undef LOCK_GLIMAGE
#undef UNLOCK_GLIMAGE
// **************************************************************