/**************************************************************************\ * * 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 * \**************************************************************************/ #include "CoinOffscreenGLCanvas.h" #include #include #include #include #include #include #include // ************************************************************************* unsigned int CoinOffscreenGLCanvas::tilesizeroof = UINT_MAX; // ************************************************************************* CoinOffscreenGLCanvas::CoinOffscreenGLCanvas(void) { this->size = SbVec2s(0, 0); this->context = NULL; } CoinOffscreenGLCanvas::~CoinOffscreenGLCanvas() { if (this->context) { this->destructContext(); } } // ************************************************************************* SbBool CoinOffscreenGLCanvas::clampSize(SbVec2s & reqsize) { // getMaxTileSize() returns the theoretical maximum gathered from // various GL driver information. We're not guaranteed that we'll be // able to allocate a buffer of this size -- e.g. due to memory // constraints on the gfx card. const SbVec2s maxsize = CoinOffscreenGLCanvas::getMaxTileSize(); if (maxsize == SbVec2s(0, 0)) { return FALSE; } reqsize[0] = SbMin(reqsize[0], maxsize[0]); reqsize[1] = SbMin(reqsize[1], maxsize[1]); // Fit the attempted allocation size to be less than the largest // tile size we know have failed allocation. We do this to avoid // trying to set the tilesize to dimensions which will very likely // fail -- as attempting to find a workable tilesize is an expensive // operation when the SoOffscreenRenderer instance already has a GL // context set up (destruction and creation of a new one will take // time, and it will also kill all GL resources tied to the // context). while ((((unsigned int)reqsize[0]) * ((unsigned int)reqsize[1])) > CoinOffscreenGLCanvas::tilesizeroof) { // shrink by halving the largest dimension: if (reqsize[0] > reqsize[1]) { reqsize[0] /= 2; } else { reqsize[1] /= 2; } } if ((reqsize[0] == 0) || (reqsize[1] == 0)) { return FALSE; } return TRUE; } void CoinOffscreenGLCanvas::setWantedSize(SbVec2s reqsize) { assert((reqsize[0] > 0) && (reqsize[1] > 0) && "invalid dimensions attempted set"); const SbBool ok = CoinOffscreenGLCanvas::clampSize(reqsize); if (!ok) { if (this->context) { this->destructContext(); } this->size = SbVec2s(0, 0); return; } // We check if the current GL canvas is much larger than what is // requested, as to then free up potentially large memory resources, // even if we already have a large enough canvas. size_t oldres = (size_t)this->size[0] * (size_t)this->size[1]; size_t newres = (size_t)reqsize[0] * (size_t)reqsize[1]; const SbBool resourcehog = (oldres > (newres * 16)); // Since the operation of context destruction and reconstruction has // the potential to be such a costly operation (because GL caches // are smashed, among other things), we try hard to avoid it. // // So avoid it if not really necessary, by checking that if we // already have a working GL context with size equal or larger to // the requested, don't destruct (and reconstruct). // // We can have a different sized internal GL canvas as to what // SoOffscreenRenderer wants, because glViewport() is used from // SoOffscreenRenderer to render to the correct viewport dimensions. if (this->context && (this->size[0] >= reqsize[0]) && (this->size[1] >= reqsize[1]) && !resourcehog) { return; } // Ok, there's no way around it, we need to destruct the GL context: if (CoinOffscreenGLCanvas::debug()) { SoDebugError::postInfo("CoinOffscreenGLCanvas::setWantedSize", "killing current context, (clamped) reqsize==[%d, %d]," " previous size==[%d, %d], resourcehog==%s", reqsize[0], reqsize[1], this->size[0], this->size[1], resourcehog ? "TRUE" : "FALSE"); } if (resourcehog) { // If we were hogging too much memory for the offscreen context, // simply go back to the requested size, to free up all that we // can. this->size = reqsize; } else { // To avoid costly reconstruction on "flutter", by one or two // dimensions going a little bit up and down from frame to frame, // we try to expand the GL canvas up-front to what perhaps would // be sufficient to avoid further GL canvas destruct- / // reconstruct-operations. this->size[0] = SbMax(reqsize[0], this->size[0]); this->size[1] = SbMax(reqsize[1], this->size[1]); } if (this->context) { this->destructContext(); } } const SbVec2s & CoinOffscreenGLCanvas::getActualSize(void) const { return this->size; } // ************************************************************************* uint32_t CoinOffscreenGLCanvas::tryActivateGLContext(void) { if (this->size == SbVec2s(0, 0)) { return 0; } if (this->context == NULL) { this->context = cc_glglue_context_create_offscreen(this->size[0], this->size[1]); if (CoinOffscreenGLCanvas::debug()) { SoDebugError::postInfo("CoinOffscreenGLCanvas::tryActivateGLContext", "Tried to create offscreen context of dimensions " "<%d, %d> -- %s", this->size[0], this->size[1], this->context == NULL ? "failed" : "succeeded"); } if (this->context == NULL) { return 0; } // Set up mapping from GL context to SoGLRenderAction context id. this->renderid = SoGLCacheContextElement::getUniqueCacheContext(); } if (cc_glglue_context_make_current(this->context) == FALSE) { SoDebugError::post("CoinOffscreenGLCanvas::tryActivateGLContext", "Couldn't make context current."); return 0; } return this->renderid; } void CoinOffscreenGLCanvas::clampToPixelSizeRoof(SbVec2s & s) { unsigned int pixelsize; do { pixelsize = (unsigned int)s[0] * (unsigned int)s[1]; if (pixelsize >= CoinOffscreenGLCanvas::tilesizeroof) { // halve the largest dimension, and try again: if (s[0] > s[1]) { s[0] /= 2; } else { s[1] /= 2; } } } while (pixelsize >= CoinOffscreenGLCanvas::tilesizeroof); } // Activates an offscreen GL context, and returns a guaranteed unique // id to use with SoGLRenderAction::setCacheContext(). // // If the given context can not be made current (due to e.g. any error // condition resulting from the attempt at setting up the offscreen GL // context), 0 is returned. uint32_t CoinOffscreenGLCanvas::activateGLContext(void) { // We try to allocate the wanted size, and then if we fail, // successively try with smaller sizes (alternating between halving // width and height) until either a workable offscreen buffer was // found, or no buffer could be made. uint32_t ctx; do { CoinOffscreenGLCanvas::clampToPixelSizeRoof(this->size); ctx = this->tryActivateGLContext(); if (ctx != 0) { break; } // if we've allocated a context, but couldn't make it current if (this->context) { this->destructContext(); } // we failed with this size, so make sure we only try with smaller // tile sizes later const unsigned int failedsize = (unsigned int)this->size[0] * (unsigned int)this->size[1]; assert(failedsize < CoinOffscreenGLCanvas::tilesizeroof); CoinOffscreenGLCanvas::tilesizeroof = failedsize; // keep trying until 32x32 -- if even those dimensions doesn't // work, give up, as too small tiles will cause the processing // time to go through the roof due to the huge number of passes: if ((this->size[0] <= 32) && (this->size[1] <= 32)) { break; } } while (TRUE); return ctx; } void CoinOffscreenGLCanvas::deactivateGLContext(void) { assert(this->context); cc_glglue_context_reinstate_previous(this->context); } // ************************************************************************* void CoinOffscreenGLCanvas::destructContext(void) { assert(this->context); if (cc_glglue_context_make_current(this->context)) { SoContextHandler::destructingContext(this->renderid); this->deactivateGLContext(); } else { SoDebugError::post("CoinOffscreenGLCanvas::destructContext", "Couldn't activate context -- resource clean-up " "not complete."); } cc_glglue_context_destruct(this->context); this->context = NULL; this->renderid = 0; } // ************************************************************************* // Pushes the rendered pixels into the internal memory array. void CoinOffscreenGLCanvas::readPixels(uint8_t * dst, const SbVec2s & vpdims, unsigned int dstrowsize, unsigned int nrcomponents) const { glPushAttrib(GL_ALL_ATTRIB_BITS); // First reset all settings that can influence the result of a // glReadPixels() call, to make sure we get the actual contents of // the buffer, unmodified. // // The values set up below matches the default settings of an // OpenGL driver. glPixelStorei(GL_PACK_SWAP_BYTES, 0); glPixelStorei(GL_PACK_LSB_FIRST, 0); glPixelStorei(GL_PACK_ROW_LENGTH, (GLint)dstrowsize); glPixelStorei(GL_PACK_SKIP_ROWS, 0); glPixelStorei(GL_PACK_SKIP_PIXELS, 0); // FIXME: should use best possible alignment, for speediest // operation. 20050617 mortene. // glPixelStorei(GL_PACK_ALIGNMENT, 4); glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelTransferi(GL_MAP_COLOR, 0); glPixelTransferi(GL_MAP_STENCIL, 0); glPixelTransferi(GL_INDEX_SHIFT, 0); glPixelTransferi(GL_INDEX_OFFSET, 0); glPixelTransferf(GL_RED_SCALE, 1); glPixelTransferf(GL_RED_BIAS, 0); glPixelTransferf(GL_GREEN_SCALE, 1); glPixelTransferf(GL_GREEN_BIAS, 0); glPixelTransferf(GL_BLUE_SCALE, 1); glPixelTransferf(GL_BLUE_BIAS, 0); glPixelTransferf(GL_ALPHA_SCALE, 1); glPixelTransferf(GL_ALPHA_BIAS, 0); glPixelTransferf(GL_DEPTH_SCALE, 1); glPixelTransferf(GL_DEPTH_BIAS, 0); GLuint i = 0; GLfloat f = 0.0f; glPixelMapfv(GL_PIXEL_MAP_I_TO_I, 1, &f); glPixelMapuiv(GL_PIXEL_MAP_S_TO_S, 1, &i); glPixelMapfv(GL_PIXEL_MAP_I_TO_R, 1, &f); glPixelMapfv(GL_PIXEL_MAP_I_TO_G, 1, &f); glPixelMapfv(GL_PIXEL_MAP_I_TO_B, 1, &f); glPixelMapfv(GL_PIXEL_MAP_I_TO_A, 1, &f); glPixelMapfv(GL_PIXEL_MAP_R_TO_R, 1, &f); glPixelMapfv(GL_PIXEL_MAP_G_TO_G, 1, &f); glPixelMapfv(GL_PIXEL_MAP_B_TO_B, 1, &f); glPixelMapfv(GL_PIXEL_MAP_A_TO_A, 1, &f); // The flushing of the OpenGL pipeline before and after the // glReadPixels() call is done as a work-around for a reported // OpenGL driver bug: on a Win2000 system with ATI Radeon graphics // card, the system would hang hard if the flushing was not done. // // This is obviously an OpenGL driver bug, but the workaround of // doing excessive flushing has no real ill effects, so we just do // it unconditionally for all drivers. Note that it might not be // necessary to flush both before and after glReadPixels() to work // around the bug (this was not established with the external // reporter), but again it shouldn't matter if we do. // // For reference, the specific driver which was reported to fail has // the following characteristics: // // GL_VENDOR="ATI Technologies Inc." // GL_RENDERER="Radeon 9000 DDR x86/SSE2" // GL_VERSION="1.3.3446 Win2000 Release" // // mortene. glFlush(); glFinish(); assert((nrcomponents >= 1) && (nrcomponents <= 4)); static const GLenum formats[] = { GL_LUMINANCE, GL_LUMINANCE_ALPHA, GL_RGB, GL_RGBA }; glReadPixels(0, 0, vpdims[0], vpdims[1], formats[nrcomponents - 1], GL_UNSIGNED_BYTE, dst); glFlush(); glFinish(); glPopAttrib(); } // ************************************************************************* static SbBool tilesize_cached = FALSE; static unsigned int maxtile[2] = { 0, 0 }; static void tilesize_cleanup(void) { tilesize_cached = FALSE; maxtile[0] = maxtile[1] = 0; } // Return largest size of offscreen canvas system can handle. Will // cache result, so only the first look-up is expensive. SbVec2s CoinOffscreenGLCanvas::getMaxTileSize(void) { // cache the values in static variables so that a new context is not // created every time render() is called in SoOffscreenRenderer if (tilesize_cached) return SbVec2s((short)maxtile[0], (short)maxtile[1]); tilesize_cached = TRUE; // Flip on first run. coin_atexit((coin_atexit_f*) tilesize_cleanup, CC_ATEXIT_NORMAL); unsigned int width, height; cc_glglue_context_max_dimensions(&width, &height); if (CoinOffscreenGLCanvas::debug()) { SoDebugError::postInfo("CoinOffscreenGLCanvas::getMaxTileSize", "cc_glglue_context_max_dimensions()==[%u, %u]", width, height); } // Makes it possible to override the default tilesizes. Should prove // useful for debugging problems on remote sites. const char * env = coin_getenv("COIN_OFFSCREENRENDERER_TILEWIDTH"); const unsigned int forcedtilewidth = env ? atoi(env) : 0; env = coin_getenv("COIN_OFFSCREENRENDERER_TILEHEIGHT"); const unsigned int forcedtileheight = env ? atoi(env) : 0; if (forcedtilewidth != 0) { width = forcedtilewidth; } if (forcedtileheight != 0) { height = forcedtileheight; } // Also make it possible to force a maximum tilesize. env = coin_getenv("COIN_OFFSCREENRENDERER_MAX_TILESIZE"); const unsigned int maxtilesize = env ? atoi(env) : 0; if (maxtilesize != 0) { width = SbMin(width, maxtilesize); height = SbMin(height, maxtilesize); } // cache result for later calls, and clamp to fit within a short // integer type maxtile[0] = SbMin(width, (unsigned int)SHRT_MAX); maxtile[1] = SbMin(height, (unsigned int)SHRT_MAX); return SbVec2s((short)maxtile[0], (short)maxtile[1]); } // ************************************************************************* SbBool CoinOffscreenGLCanvas::debug(void) { static int flag = -1; // -1 means "not initialized" in this context if (flag == -1) { const char * env = coin_getenv("COIN_DEBUG_SOOFFSCREENRENDERER"); flag = env && (atoi(env) > 0); } return flag; } // *************************************************************************