diff --git a/CMakeLists.txt b/CMakeLists.txt index 84890624c..77b711af0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -423,6 +423,7 @@ FIND_PACKAGE(Poppler-glib) FIND_PACKAGE(RSVG) FIND_PACKAGE(GtkGl) FIND_PACKAGE(DirectInput) +FIND_PACKAGE(NVTT) # Include macro utilities here INCLUDE(OsgMacroUtils) diff --git a/CMakeModules/FindNVTT.cmake b/CMakeModules/FindNVTT.cmake new file mode 100644 index 000000000..de1e6efeb --- /dev/null +++ b/CMakeModules/FindNVTT.cmake @@ -0,0 +1,61 @@ +# Locate nvidia-texture-tools +# This module defines +# NVTT_LIBRARY +# NVTT_FOUND, if false, do not try to link to nvtt +# NVTT_INCLUDE_DIR, where to find the headers +# + + +FIND_PATH(NVTT_INCLUDE_DIR nvtt/nvtt.h + PATHS + /usr/local + /usr + $ENV{NVTT_DIR} + ${3rdPartyRoot} + PATH_SUFFIXES include +) + +FIND_LIBRARY(NVTT_LIBRARY + NAMES nvtt + PATHS + /usr/local + /usr + $ENV{NVTT_DIR} + ${3rdPartyRoot} + PATH_SUFFIXES lib64 lib lib/shared lib/static lib64/static +) + +FIND_LIBRARY(NVIMAGE_LIBRARY + NAMES nvimage + PATHS + /usr/local + /usr + $ENV{NVTT_DIR} + ${3rdPartyRoot} + PATH_SUFFIXES lib64 lib lib/shared lib/static lib64/static +) + +FIND_LIBRARY(NVMATH_LIBRARY + NAMES nvmath + PATHS + /usr/local + /usr + $ENV{NVTT_DIR} + ${3rdPartyRoot} + PATH_SUFFIXES lib64 lib lib/shared lib/static lib64/static +) + +FIND_LIBRARY(NVCORE_LIBRARY + NAMES nvcore + PATHS + /usr/local + /usr + $ENV{NVTT_DIR} + ${3rdPartyRoot} + PATH_SUFFIXES lib64 lib lib/shared lib/static lib64/static +) + +SET(NVTT_FOUND "NO") +IF(NVTT_LIBRARY AND NVTT_INCLUDE_DIR) + SET(NVTT_FOUND "YES") +ENDIF(NVTT_LIBRARY AND NVTT_INCLUDE_DIR) diff --git a/include/osgDB/ImageProcessor b/include/osgDB/ImageProcessor new file mode 100644 index 000000000..480b45633 --- /dev/null +++ b/include/osgDB/ImageProcessor @@ -0,0 +1,54 @@ +/* -*-c++-*- OpenSceneGraph - Copyright (C) 1998-2006 Robert Osfield + * + * This library is open source and may be redistributed and/or modified under + * the terms of the OpenSceneGraph Public License (OSGPL) version 0.0 or + * (at your option) any later version. The full license is in LICENSE file + * included with this distribution, and on the openscenegraph.org website. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * OpenSceneGraph Public License for more details. +*/ + +#ifndef OSGDB_IMAGEPROCESSOR +#define OSGDB_IMAGEPROCESSOR 1 + +#include + +namespace osgDB { + +class ImageProcessor : public osg::Object +{ + public: + + ImageProcessor(): + osg::Object(true) {} + + ImageProcessor(const ImageProcessor& rw,const osg::CopyOp& copyop=osg::CopyOp::SHALLOW_COPY): + osg::Object(rw,copyop) {} + + virtual ~ImageProcessor() {} + + META_Object(osgDB,ImageProcessor); + + enum CompressionMethod + { + USE_CPU, /// Use CPU for compression even when GPU compression is available + USE_GPU /// Use GPU for compression when available (i.e CUDA), otherwise fallback to CPU + }; + + enum CompressionQuality + { + FASTEST, + NORMAL, + PRODUCTION, + HIGHEST + }; + + virtual void compress(osg::Image& image, osg::Texture::InternalFormatMode compressedFormat, bool generateMipMap, bool resizeToPowerOfTwo, CompressionMethod method, CompressionQuality quality) {} + virtual void generateMipMap(osg::Image& image, bool resizeToPowerOfTwo, CompressionMethod method) {} +}; + +} +#endif diff --git a/include/osgDB/Registry b/include/osgDB/Registry index 9f7b64587..2a0858ac9 100644 --- a/include/osgDB/Registry +++ b/include/osgDB/Registry @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -59,7 +60,6 @@ struct type_wrapper: basic_type_wrapper { } }; - /** Registry is a singleton factory which stores the reader/writers which are linked in @@ -101,6 +101,9 @@ class OSGDB_EXPORT Registry : public osg::Referenced void addReaderWriter(ReaderWriter* rw); void removeReaderWriter(ReaderWriter* rw); + void addImageProcessor(ImageProcessor* ip); + void removeImageProcessor(ImageProcessor* ip); + /** create the platform specific library name associated with file.*/ std::string createLibraryNameForFile(const std::string& fileName); @@ -142,6 +145,21 @@ class OSGDB_EXPORT Registry : public osg::Referenced const ReaderWriterList& getReaderWriterList() const { return _rwList; } + typedef std::vector< osg::ref_ptr > ImageProcessorList; + + /** get a image processor if available.*/ + ImageProcessor* getImageProcessor(); + + /** get a image processor which is associated specified extension.*/ + ImageProcessor* getImageProcessorForExtension(const std::string& ext); + + /** get list of all registered ImageProcessors.*/ + ImageProcessorList& getImageProcessorList() { return _ipList; } + + /** get const list of all registered ImageProcessors.*/ + const ImageProcessorList& getImageProcessorList() const { return _ipList; } + + typedef class osgDB::FindFileCallback FindFileCallback; typedef class osgDB::ReadFileCallback ReadFileCallback; typedef class osgDB::WriteFileCallback WriteFileCallback; @@ -555,6 +573,7 @@ class OSGDB_EXPORT Registry : public osg::Referenced OpenThreads::ReentrantMutex _pluginMutex; ReaderWriterList _rwList; + ImageProcessorList _ipList; DynamicLibraryList _dlList; bool _openingLibrary; @@ -624,6 +643,34 @@ class RegisterReaderWriterProxy }; +/** Proxy class for automatic registration of reader/writers with the Registry.*/ +template +class RegisterImageProcessorProxy +{ + public: + RegisterImageProcessorProxy() + { + if (Registry::instance()) + { + _rw = new T; + Registry::instance()->addImageProcessor(_rw.get()); + } + } + + ~RegisterImageProcessorProxy() + { + if (Registry::instance()) + { + Registry::instance()->removeImageProcessor(_rw.get()); + } + } + + T* get() { return _rw.get(); } + + protected: + osg::ref_ptr _rw; +}; + struct PluginFunctionProxy { PluginFunctionProxy(CPluginFunction function) { (function)(); } @@ -657,6 +704,9 @@ struct PluginFunctionProxy extern "C" void osgdb_##ext(void) {} \ static osgDB::RegisterReaderWriterProxy g_proxy_##classname; +#define REGISTER_OSGIMAGEPROCESSOR(ext, classname) \ + extern "C" void osgdb_##ext(void) {} \ + static osgDB::RegisterImageProcessorProxy g_proxy_##classname; } diff --git a/src/osgDB/Registry.cpp b/src/osgDB/Registry.cpp index 84ab3ea44..062f837a6 100644 --- a/src/osgDB/Registry.cpp +++ b/src/osgDB/Registry.cpp @@ -550,6 +550,71 @@ void Registry::removeReaderWriter(ReaderWriter* rw) } +ImageProcessor* Registry::getImageProcessor() +{ + { + OpenThreads::ScopedLock lock(_pluginMutex); + if (!_ipList.empty()) + { + return _ipList.front().get(); + } + } + return getImageProcessorForExtension("nvtt"); +} + +ImageProcessor* Registry::getImageProcessorForExtension(const std::string& ext) +{ + { + OpenThreads::ScopedLock lock(_pluginMutex); + if (!_ipList.empty()) + { + return _ipList.front().get(); + } + } + + std::string libraryName = createLibraryNameForExtension(ext); + OSG_NOTICE << "Now checking for plug-in "< lock(_pluginMutex); + if (!_ipList.empty()) + { + OSG_NOTICE << "Loaded plug-in "<className()<<")"<< std::endl; + + OpenThreads::ScopedLock lock(_pluginMutex); + + _ipList.push_back(ip); + +} + + +void Registry::removeImageProcessor(ImageProcessor* ip) +{ + if (ip==0L) return; + + OSG_NOTIFY(NOTICE) << "osg::Registry::removeImageProcessor();"<< std::endl; + + OpenThreads::ScopedLock lock(_pluginMutex); + + ImageProcessorList::iterator ipitr = std::find(_ipList.begin(),_ipList.end(),ip); + if (ipitr!=_ipList.end()) + { + _ipList.erase(ipitr); + } + +} + void Registry::addFileExtensionAlias(const std::string mapExt, const std::string toExt) { diff --git a/src/osgPlugins/CMakeLists.txt b/src/osgPlugins/CMakeLists.txt index 82640d161..a35533062 100644 --- a/src/osgPlugins/CMakeLists.txt +++ b/src/osgPlugins/CMakeLists.txt @@ -215,6 +215,9 @@ IF(QTKIT_FOUND) ADD_SUBDIRECTORY(QTKit) ENDIF() +IF(NVTT_FOUND) + ADD_SUBDIRECTORY(nvtt) +ENDIF() IF(FREETYPE_FOUND) diff --git a/src/osgPlugins/nvtt/CMakeLists.txt b/src/osgPlugins/nvtt/CMakeLists.txt new file mode 100644 index 000000000..4a91eadc8 --- /dev/null +++ b/src/osgPlugins/nvtt/CMakeLists.txt @@ -0,0 +1,11 @@ +INCLUDE_DIRECTORIES( ${NVTT_INCLUDE_DIR} ) + + +SET(TARGET_SRC + NVTTImageProcessor.cpp +) + +SET(TARGET_LIBRARIES_VARS NVTT_LIBRARY) + +#### end var setup ### +SETUP_PLUGIN(nvtt) diff --git a/src/osgPlugins/nvtt/NVTTImageProcessor.cpp b/src/osgPlugins/nvtt/NVTTImageProcessor.cpp new file mode 100644 index 000000000..dbf06d6a3 --- /dev/null +++ b/src/osgPlugins/nvtt/NVTTImageProcessor.cpp @@ -0,0 +1,329 @@ + +#include +#include + +#include +#include + +class NVTTProcessor : public osgDB::ImageProcessor +{ +public: + virtual void compress(osg::Image& image, osg::Texture::InternalFormatMode compressedFormat, bool generateMipMap, bool resizeToPowerOfTwo, CompressionMethod method, CompressionQuality quality); + virtual void generateMipMap(osg::Image& image, bool resizeToPowerOfTwo, CompressionMethod method); + +protected: + + void process( osg::Image& texture, nvtt::Format format, bool generateMipMap, bool resizeToPowerOfTwo, CompressionMethod method, CompressionQuality quality); + + struct VPBErrorHandler : public nvtt::ErrorHandler + { + virtual void error(nvtt::Error e); + }; + + struct OSGImageOutputHandler : public nvtt::OutputHandler + { + typedef std::vector MipMapData; + + std::vector _mipmaps; + int _width; + int _height; + int _currentMipLevel; + int _currentNumberOfWritenBytes; + nvtt::Format _format; + bool _discardAlpha; + + OSGImageOutputHandler(nvtt::Format format, bool discardAlpha); + virtual ~OSGImageOutputHandler(); + + // create the osg image from the given format + bool assignImage(osg::Image& image); + + /// Indicate the start of a new compressed image that's part of the final texture. + virtual void beginImage(int size, int width, int height, int depth, int face, int miplevel); + + /// Output data. Compressed data is output as soon as it's generated to minimize memory allocations. + virtual bool writeData(const void * data, int size); + }; + + // Convert RGBA to BGRA : nvtt only accepts BGRA pixel format + void convertRGBAToBGRA( std::vector& outputData, const unsigned char* inputData ); + + // Convert RGB to BGRA : nvtt only accepts BGRA pixel format + void convertRGBToBGRA( std::vector& outputData, const unsigned char* inputData ); + +}; + +/// Error handler. +void NVTTProcessor::VPBErrorHandler::error(nvtt::Error e) +{ + switch (e) + { + case nvtt::Error_Unknown: + OSG_WARN<<" NVTT : unknown error"<size(); + for (unsigned int n=1; n<_mipmaps.size(); n++) + { + mipmapOffsets[n-1] = totalSize; + totalSize += _mipmaps[n]->size(); + } + + // Allocate data and copy it + unsigned char* data = new unsigned char[ totalSize ]; + unsigned char* ptr = data; + for (unsigned int n=0; n<_mipmaps.size(); n++) + { + memcpy( ptr, &(*_mipmaps[n])[0], _mipmaps[n]->size() ); + ptr += _mipmaps[n]->size(); + } + + image.setImage(_width,_height,1,pixelFormat,pixelFormat,GL_UNSIGNED_BYTE,data,osg::Image::USE_NEW_DELETE); + image.setMipmapLevels(mipmapOffsets); + + return true; +} + +/// Indicate the start of a new compressed image that's part of the final texture. +void NVTTProcessor::OSGImageOutputHandler::beginImage(int size, int width, int height, int depth, int face, int miplevel) +{ + // store the new width/height of the texture + if (miplevel == 0) + { + _width = width; + _height = height; + } + // prepare to receive mipmap data + if (miplevel >= static_cast(_mipmaps.size())) + { + _mipmaps.resize(miplevel+1); + } + _mipmaps[miplevel] = new MipMapData(size); + _currentMipLevel = miplevel; + _currentNumberOfWritenBytes = 0; +} + +/// Output data. Compressed data is output as soon as it's generated to minimize memory allocations. +bool NVTTProcessor::OSGImageOutputHandler::writeData(const void * data, int size) +{ + // Copy mipmap data + std::vector& dstData = *_mipmaps[_currentMipLevel]; + memcpy( &dstData[_currentNumberOfWritenBytes], data, size ); + _currentNumberOfWritenBytes += size; + return true; +} + +// Convert RGBA to BGRA : nvtt only accepts BGRA pixel format +void NVTTProcessor::convertRGBAToBGRA( std::vector& outputData, const unsigned char* inputData ) +{ + for (unsigned n=0; n& outputData, const unsigned char* inputData ) +{ + unsigned int numberOfPixels = outputData.size()/4; + for (unsigned n=0; n imageData( image.s() * image.t() * 4 ); + if (image.getPixelFormat() == GL_RGB) + { + convertRGBToBGRA( imageData, image.data() ); + } + else + { + convertRGBAToBGRA( imageData, image.data() ); + } + inputOptions.setMipmapData(&imageData[0],image.s(),image.t()); + + // Fill compression options + nvtt::CompressionOptions compressionOptions; + switch(quality) + { + case FASTEST: + compressionOptions.setQuality( nvtt::Quality_Fastest ); + break; + case NORMAL: + compressionOptions.setQuality( nvtt::Quality_Normal ); + break; + case PRODUCTION: + compressionOptions.setQuality( nvtt::Quality_Production); + break; + case HIGHEST: + compressionOptions.setQuality( nvtt::Quality_Highest); + break; + } + compressionOptions.setFormat( format ); + //compressionOptions.setQuantization(false,false,false); + if (format == nvtt::Format_RGBA) + { + if (image.getPixelFormat() == GL_RGB) + { + compressionOptions.setPixelFormat(24,0xff,0xff00,0xff0000,0); + } + else + { + compressionOptions.setPixelFormat(32,0xff,0xff00,0xff0000,0xff000000); + } + } + + // Handler + OSGImageOutputHandler outputHandler(format,image.getPixelFormat() == GL_RGB); + VPBErrorHandler errorHandler; + + // Fill output options + nvtt::OutputOptions outputOptions; + outputOptions.setOutputHandler(&outputHandler); + outputOptions.setErrorHandler(&errorHandler); + outputOptions.setOutputHeader(false); + + // Process the compression now + nvtt::Compressor compressor; + if(method == USE_GPU) + { + compressor.enableCudaAcceleration(true); + if(!compressor.isCudaAccelerationEnabled()) + { + OSG_WARN<< "CUDA acceleration was enabled but it is not available. CPU will be used."<