From 730ad0fbeaa2dba6f0056d4b992b0c976c2f8433 Mon Sep 17 00:00:00 2001 From: wmayer Date: Mon, 10 Nov 2014 14:10:38 +0100 Subject: [PATCH] + support anti-aliasing in offscreen renderer --- src/Gui/CommandView.cpp | 2 +- src/Gui/SoFCOffscreenRenderer.cpp | 366 ++++++++++++++++++++++++++++++ src/Gui/SoFCOffscreenRenderer.h | 49 ++++ src/Gui/View3DInventorViewer.cpp | 10 +- src/Gui/View3DPy.cpp | 4 + 5 files changed, 425 insertions(+), 6 deletions(-) diff --git a/src/Gui/CommandView.cpp b/src/Gui/CommandView.cpp index 1ab7988d1b32..254261028f13 100644 --- a/src/Gui/CommandView.cpp +++ b/src/Gui/CommandView.cpp @@ -1463,7 +1463,7 @@ void StdViewScreenShot::activated(int iMsg) QStringList formats; SbViewportRegion vp(view->getViewer()->getSoRenderManager()->getViewportRegion()); { - SoFCOffscreenRenderer& rd = SoFCOffscreenRenderer::instance(); + SoQtOffscreenRenderer rd(vp); formats = rd.getWriteImageFiletypeInfo(); } diff --git a/src/Gui/SoFCOffscreenRenderer.cpp b/src/Gui/SoFCOffscreenRenderer.cpp index 0cef2ab2a54e..537500bdf471 100644 --- a/src/Gui/SoFCOffscreenRenderer.cpp +++ b/src/Gui/SoFCOffscreenRenderer.cpp @@ -22,10 +22,17 @@ #include "PreCompiled.h" #ifndef _PreComp_ +# include +# include # include +# include # include # include # include +# include +# include +# include +# include # include #endif @@ -365,3 +372,362 @@ void writeJPEGComment(const std::string& comment, QByteArray& ba) } } } + +// --------------------------------------------------------------- + +#define PRIVATE(p) p +#define PUBLIC(p) p + +void SoQtOffscreenRenderer::init(const SbViewportRegion & vpr, + SoGLRenderAction * glrenderaction) +{ + this->backgroundcolor.setValue(0,0,0); + + if (glrenderaction) { + this->renderaction = glrenderaction; + } + else { + this->renderaction = new SoGLRenderAction(vpr); + this->renderaction->setCacheContext(SoGLCacheContextElement::getUniqueCacheContext()); + this->renderaction->setTransparencyType(SoGLRenderAction::SORTED_OBJECT_BLEND); + } + + this->didallocation = glrenderaction ? FALSE : TRUE; + this->viewport = vpr; + + this->pixelbuffer = NULL; // constructed later + this->framebuffer = NULL; + this->numSamples = -1; + this->pbuffer = QGLPixelBuffer::hasOpenGLPbuffers(); +} + +/*! + Constructor. Argument is the \a viewportregion we should use when + rendering. An internal SoGLRenderAction will be constructed. +*/ +SoQtOffscreenRenderer::SoQtOffscreenRenderer(const SbViewportRegion & viewportregion) +{ + init(viewportregion); +} + +/*! + Constructor. Argument is the \a action we should apply to the + scene graph when rendering the scene. Information about the + viewport is extracted from the \a action. +*/ +SoQtOffscreenRenderer::SoQtOffscreenRenderer(SoGLRenderAction * action) +{ + init(action->getViewportRegion(), action); +} + +/*! + Destructor. +*/ +SoQtOffscreenRenderer::~SoQtOffscreenRenderer() +{ + delete pixelbuffer; + delete framebuffer; + + if (this->didallocation) { + delete this->renderaction; + } +} + +/*! + Sets the viewport region. + + This will invalidate the current buffer, if any. The buffer will not + contain valid data until another call to + SoOffscreenRendererQt::render() happens. +*/ +void +SoQtOffscreenRenderer::setViewportRegion(const SbViewportRegion & region) +{ + PRIVATE(this)->viewport = region; +} + +/*! + Returns the viewerport region. +*/ +const SbViewportRegion & +SoQtOffscreenRenderer::getViewportRegion(void) const +{ + return PRIVATE(this)->viewport; +} + +/*! + Sets the background color. The buffer is cleared to this color + before rendering. +*/ +void +SoQtOffscreenRenderer::setBackgroundColor(const SbColor & color) +{ + PRIVATE(this)->backgroundcolor = color; +} + +/*! + Returns the background color. +*/ +const SbColor & +SoQtOffscreenRenderer::getBackgroundColor(void) const +{ + return PRIVATE(this)->backgroundcolor; +} + +/*! + Sets the render action. Use this if you have special rendering needs. +*/ +void +SoQtOffscreenRenderer::setGLRenderAction(SoGLRenderAction * action) +{ + if (action == PRIVATE(this)->renderaction) { return; } + + if (PRIVATE(this)->didallocation) { delete PRIVATE(this)->renderaction; } + PRIVATE(this)->renderaction = action; + PRIVATE(this)->didallocation = FALSE; +} + +/*! + Returns the rendering action currently used. +*/ +SoGLRenderAction * +SoQtOffscreenRenderer::getGLRenderAction(void) const +{ + return PRIVATE(this)->renderaction; +} + +void +SoQtOffscreenRenderer::setNumPasses(const int num) +{ + PRIVATE(this)->numSamples = num; +} + +int +SoQtOffscreenRenderer::getNumPasses(void) const +{ + return PRIVATE(this)->numSamples; +} + +void +SoQtOffscreenRenderer::setPbufferEnable(SbBool enable) +{ + PRIVATE(this)->pbuffer = enable; +} + +SbBool +SoQtOffscreenRenderer::getPbufferEnable(void) const +{ + return PRIVATE(this)->pbuffer; +} + +// ************************************************************************* + +void +SoQtOffscreenRenderer::pre_render_cb(void * userdata, SoGLRenderAction * action) +{ + glClear(GL_DEPTH_BUFFER_BIT|GL_COLOR_BUFFER_BIT); + action->setRenderingIsRemote(FALSE); +} + +void +SoQtOffscreenRenderer::makePixelBuffer(int width, int height, int samples) +{ + if (pixelbuffer) { + delete pixelbuffer; + pixelbuffer = NULL; + } + + viewport.setWindowSize(width, height); + + QGLFormat fmt; + if (samples > 0) { + fmt.setSampleBuffers(true); + fmt.setSamples(samples); + } + else { + fmt.setSampleBuffers(false); + } + + pixelbuffer = new QGLPixelBuffer(width, height, fmt); + cache_context = SoGLCacheContextElement::getUniqueCacheContext(); // unique per pixel buffer object, just to be sure +} + +void +SoQtOffscreenRenderer::makeFrameBuffer(int width, int height, int samples) +{ + if (framebuffer) { + delete framebuffer; + framebuffer = NULL; + } + + viewport.setWindowSize(width, height); + +#if QT_VERSION >= 0x040600 + QGLFramebufferObjectFormat fmt; + fmt.setSamples(samples); + fmt.setAttachment(QGLFramebufferObject::Depth); +#else + QGLFramebufferObject::Attachment fmt; + fmt = QGLFramebufferObject::Depth; +#endif + + framebuffer = new QGLFramebufferObject(width, height, fmt); + cache_context = SoGLCacheContextElement::getUniqueCacheContext(); // unique per pixel buffer object, just to be sure +} + +SbBool +SoQtOffscreenRenderer::renderFromBase(SoBase * base) +{ + const SbVec2s fullsize = this->viewport.getViewportSizePixels(); + + if (PRIVATE(this)->pbuffer) { + if (!pixelbuffer) { + makePixelBuffer(fullsize[0], fullsize[1], PRIVATE(this)->numSamples); + } + else if (pixelbuffer->width() != fullsize[0] || pixelbuffer->height() != fullsize[1]) { + // get the size right! + makePixelBuffer(fullsize[0], fullsize[1], PRIVATE(this)->numSamples); + } + + pixelbuffer->makeCurrent(); // activate us! + } + else { + if (!framebuffer) { + makeFrameBuffer(fullsize[0], fullsize[1], PRIVATE(this)->numSamples); + } + else if (framebuffer->width() != fullsize[0] || framebuffer->height() != fullsize[1]) { + // get the size right! + makeFrameBuffer(fullsize[0], fullsize[1], PRIVATE(this)->numSamples); + } + + framebuffer->bind(); // activate us! + } + + // oldcontext is used to restore the previous context id, in case + // the render action is not allocated by us. + const uint32_t oldcontext = this->renderaction->getCacheContext(); + this->renderaction->setCacheContext(cache_context); + + glEnable(GL_DEPTH_TEST); + glClearColor(this->backgroundcolor[0], + this->backgroundcolor[1], + this->backgroundcolor[2], + 0.0f); + + // needed to clear viewport after glViewport() is called from + // SoGLRenderAction + this->renderaction->addPreRenderCallback(pre_render_cb, NULL); + this->renderaction->setViewportRegion(this->viewport); + + if (base->isOfType(SoNode::getClassTypeId())) + this->renderaction->apply((SoNode *)base); + else if (base->isOfType(SoPath::getClassTypeId())) + this->renderaction->apply((SoPath *)base); + else { + assert(FALSE && "Cannot apply to anything else than an SoNode or an SoPath"); + } + + this->renderaction->removePreRenderCallback(pre_render_cb, NULL); + + if (PRIVATE(this)->pbuffer) { + pixelbuffer->doneCurrent(); + } + else { + framebuffer->release(); + } + + this->renderaction->setCacheContext(oldcontext); // restore old + + return TRUE; +} + +/*! + Render the scenegraph rooted at \a scene into our internal pixel + buffer. + + Important note: make sure you pass in a \a scene node pointer which + has both a camera and at least one lightsource below it -- otherwise + you are likely to end up with just a blank or black image buffer. + + This mistake is easily made if you use an SoQtOffscreenRenderer on a + scenegraph from one of the standard viewer components, as you will + often just leave the addition of a camera and a headlight + lightsource to the viewer to set up. This camera and lightsource are + then part of the viewer's private "super-graph" outside of the scope + of the scenegraph passed in by the application programmer. To make + sure the complete scenegraph (including the viewer's "private parts" + (*snicker*)) are passed to this method, you can get the scenegraph + root from the viewer's internal SoSceneManager instance instead of + from the viewer's own getSceneGraph() method, like this: + + \code + SoQtOffscreenRenderer * myRenderer = new SoQtOffscreenRenderer(vpregion); + SoNode * root = myViewer->getSceneManager()->getSceneGraph(); + SbBool ok = myRenderer->render(root); + // [then use image buffer in a texture, or write it to file, or whatever] + \endcode + + If you do this and still get a blank buffer, another common problem + is to have a camera which is not actually pointing at the scene + geometry you want a snapshot of. If you suspect that could be the + cause of problems on your end, take a look at SoCamera::pointAt() + and SoCamera::viewAll() to see how you can make a camera node + guaranteed to be directed at the scene geometry. + + Yet another common mistake when setting up the camera is to specify + values for the SoCamera::nearDistance and SoCamera::farDistance + fields which doesn't not enclose the full scene. This will result in + either just the background color, or that parts at the front or the + back of the scene will not be visible in the rendering. + + \sa writeToImage() +*/ +SbBool +SoQtOffscreenRenderer::render(SoNode * scene) +{ + return PRIVATE(this)->renderFromBase(scene); +} + +/*! + Render the \a scene path into our internal memory buffer. +*/ +SbBool +SoQtOffscreenRenderer::render(SoPath * scene) +{ + return PRIVATE(this)->renderFromBase(scene); +} + +/*! + Writes the rendered image buffer directly into a QImage object. +*/ +void +SoQtOffscreenRenderer::writeToImage (QImage& img) const +{ + if (PRIVATE(this)->pbuffer) { + if (pixelbuffer) + img = pixelbuffer->toImage(); + } + else { + if (framebuffer) + img = framebuffer->toImage(); + } +} + +/*! + This method returns all image file formats supported by Coin3D (see getWriteFiletypeInfo()) with all QImage file formats that are + not directly supported by Coin3D, if so. +*/ +QStringList SoQtOffscreenRenderer::getWriteImageFiletypeInfo() const +{ + QList qtformats = QImageWriter::supportedImageFormats(); + + QStringList formats; + for (QList::Iterator it = qtformats.begin(); it != qtformats.end(); ++it) { + formats << QLatin1String(*it); + } + formats.sort(); + return formats; +} + +#undef PRIVATE +#undef PUBLIC diff --git a/src/Gui/SoFCOffscreenRenderer.h b/src/Gui/SoFCOffscreenRenderer.h index 7a8cc71f463a..c3d363b1c3a7 100644 --- a/src/Gui/SoFCOffscreenRenderer.h +++ b/src/Gui/SoFCOffscreenRenderer.h @@ -29,6 +29,8 @@ #include class QImage; +class QGLFramebufferObject; +class QGLPixelBuffer; namespace Gui { @@ -97,6 +99,53 @@ class GuiExport SoFCOffscreenRenderer : public SoOffscreenRenderer std::string createMIBA(const SbMatrix& mat) const; }; +class GuiExport SoQtOffscreenRenderer +{ +public: + SoQtOffscreenRenderer(const SbViewportRegion & viewportregion); + SoQtOffscreenRenderer(SoGLRenderAction * action); + ~SoQtOffscreenRenderer(); + + void setViewportRegion(const SbViewportRegion & region); + const SbViewportRegion & getViewportRegion(void) const; + + void setBackgroundColor(const SbColor & color); + const SbColor & getBackgroundColor(void) const; + + void setGLRenderAction(SoGLRenderAction * action); + SoGLRenderAction * getGLRenderAction(void) const; + + void setNumPasses(const int num); + int getNumPasses(void) const; + + void setPbufferEnable(SbBool enable); + SbBool getPbufferEnable(void) const; + + SbBool render(SoNode * scene); + SbBool render(SoPath * scene); + + void writeToImage (QImage&) const; + QStringList getWriteImageFiletypeInfo() const; + +private: + void init(const SbViewportRegion & vpr, SoGLRenderAction * glrenderaction = NULL); + static void pre_render_cb(void * userdata, SoGLRenderAction * action); + SbBool renderFromBase(SoBase * base); + void makePixelBuffer(int width, int height, int samples); + void makeFrameBuffer(int width, int height, int samples); + + QGLPixelBuffer* pixelbuffer; // the offscreen rendering supported by Qt + QGLFramebufferObject* framebuffer; + uint32_t cache_context; // our unique context id + + SbViewportRegion viewport; + SbColor backgroundcolor; + SoGLRenderAction * renderaction; + SbBool didallocation; + SbBool pbuffer; + int numSamples; +}; + } // namespace Gui diff --git a/src/Gui/View3DInventorViewer.cpp b/src/Gui/View3DInventorViewer.cpp index eb71a731c788..bd067273af46 100644 --- a/src/Gui/View3DInventorViewer.cpp +++ b/src/Gui/View3DInventorViewer.cpp @@ -809,8 +809,8 @@ void View3DInventorViewer::savePicture(int w, int h, const QColor& bg, QImage& i //The default value is 72.0. //If we need to support grayscale images with must either use SoOffscreenRenderer::LUMINANCE or //SoOffscreenRenderer::LUMINANCE_TRANSPARENCY. - SoFCOffscreenRenderer& renderer = SoFCOffscreenRenderer::instance(); - renderer.setViewportRegion(vp); + SoQtOffscreenRenderer renderer(vp); + renderer.setNumPasses(4); SoCallback* cb = 0; // for an invalid color use the viewer's current background color @@ -836,9 +836,9 @@ void View3DInventorViewer::savePicture(int w, int h, const QColor& bg, QImage& i // The behaviour in Coin4 has changed so that when using the same instance of 'SoFCOffscreenRenderer' // multiple times internally the biggest viewport size is stored and set to the SoGLRenderAction. // The trick is to add a callback node and override the viewport size with wath we want. - SoCallback* cbvp = new SoCallback; - cbvp->setCallback(setViewportCB); - root->addChild(cbvp); + //SoCallback* cbvp = new SoCallback; + //cbvp->setCallback(setViewportCB); + //root->addChild(cbvp); #endif SoCamera* camera = getSoRenderManager()->getCamera(); diff --git a/src/Gui/View3DPy.cpp b/src/Gui/View3DPy.cpp index 63d71438545a..ca234172e714 100644 --- a/src/Gui/View3DPy.cpp +++ b/src/Gui/View3DPy.cpp @@ -658,10 +658,14 @@ Py::Object View3DInventorPy::isAnimationEnabled(const Py::Tuple& args) void View3DInventorPy::createImageFromFramebuffer(int width, int height, const QColor& bgcolor, QImage& img) { +#if QT_VERSION >= 0x040600 QGLFramebufferObjectFormat format; format.setSamples(8); format.setAttachment(QGLFramebufferObject::Depth); QGLFramebufferObject fbo(width, height, format); +#else + QGLFramebufferObject fbo(width, height, QGLFramebufferObject::Depth); +#endif const QColor col = _view->getViewer()->backgroundColor(); bool on = _view->getViewer()->hasGradientBackground();