diff --git a/source/glest_game/game/game.cpp b/source/glest_game/game/game.cpp index a6007ceb0..c91ba6332 100644 --- a/source/glest_game/game/game.cpp +++ b/source/glest_game/game/game.cpp @@ -1197,13 +1197,15 @@ void Game::keyDown(char key) { string path = GameConstants::folder_path_screenshots; if(isdir(path.c_str()) == true) { Config &config= Config::getInstance(); - string fileFormat = config.getString("ScreenShotFileType","bmp"); + string fileFormat = config.getString("ScreenShotFileType","png"); - for(int i=0; i<250; ++i){ + unsigned int queueSize = Renderer::getInstance().getSaveScreenQueueSize(); + + for(int i=0; i < 250; ++i) { path = GameConstants::folder_path_screenshots; - path += "screen" + intToStr(i) + "." + fileFormat; + path += "screen" + intToStr(i + queueSize) + "." + fileFormat; FILE *f= fopen(path.c_str(), "rb"); - if(f==NULL) { + if(f == NULL) { Renderer::getInstance().saveScreen(path); break; } diff --git a/source/glest_game/graphics/renderer.cpp b/source/glest_game/graphics/renderer.cpp index 579a33c03..00339b7e3 100644 --- a/source/glest_game/graphics/renderer.cpp +++ b/source/glest_game/graphics/renderer.cpp @@ -152,12 +152,11 @@ Renderer::Renderer() { modelRenderer = NULL; textRenderer = NULL; particleRenderer = NULL; + saveScreenShotThread = NULL; lastRenderFps=MIN_FPS_NORMAL_RENDERING; shadowsOffDueToMinRender=false; - pixmapScreenShot = NULL;; - //resources for(int i=0; i < rsCount; ++i) { modelManager[i] = NULL; @@ -189,9 +188,13 @@ Renderer::Renderer() { particleManager[i]= graphicsFactory->newParticleManager(); fontManager[i]= graphicsFactory->newFontManager(); } + + saveScreenShotThread = new SimpleTaskThread(this,0,25); + saveScreenShotThread->setUniqueID(__FILE__); + saveScreenShotThread->start(); } -Renderer::~Renderer(){ +Renderer::~Renderer() { delete modelRenderer; modelRenderer = NULL; delete textRenderer; @@ -211,12 +214,50 @@ Renderer::~Renderer(){ fontManager[i] = NULL; } - delete pixmapScreenShot; + // Wait for the queue to become empty or timeout the thread at 7 seconds + for(time_t elapsed = time(NULL); + getSaveScreenQueueSize() > 0 && difftime(time(NULL),elapsed) <= 7;) { + sleep(10); + } + delete saveScreenShotThread; + saveScreenShotThread = NULL; + + if(getSaveScreenQueueSize() > 0) { + SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s Line %d] FORCING MEMORY CLEANUP and NOT SAVING screenshots, saveScreenQueue.size() = %d\n",__FILE__,__FUNCTION__,__LINE__,saveScreenQueue.size()); + + for(std::list >::iterator iter = saveScreenQueue.begin(); + iter != saveScreenQueue.end(); ++iter) { + delete iter->second; + } + } this->menu = NULL; this->game = NULL; } +void Renderer::simpleTask() { + // This code reads pixmaps from a queue and saves them to disk + Pixmap2D *savePixMapBuffer=NULL; + string path=""; + MutexSafeWrapper safeMutex(&saveScreenShotThreadAccessor); + if(saveScreenQueue.size() > 0) { + SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s Line %d] saveScreenQueue.size() = %d\n",__FILE__,__FUNCTION__,__LINE__,saveScreenQueue.size()); + + savePixMapBuffer = saveScreenQueue.front().second; + path = saveScreenQueue.front().first; + + saveScreenQueue.pop_front(); + } + safeMutex.ReleaseLock(); + + if(savePixMapBuffer != NULL) { + SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s Line %d] about to save [%s]\n",__FILE__,__FUNCTION__,__LINE__,path.c_str()); + + savePixMapBuffer->save(path); + delete savePixMapBuffer; + } +} + Renderer &Renderer::getInstance(){ static Renderer renderer; return renderer; @@ -2945,21 +2986,13 @@ void Renderer::loadConfig(){ } } -void Renderer::saveScreen(const string &path){ - +void Renderer::saveScreen(const string &path) { const Metrics &sm= Metrics::getInstance(); SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s Line: %d]\n",__FILE__,__FUNCTION__,__LINE__); //Pixmap2D pixmap(sm.getScreenW(), sm.getScreenH(), 3); - if( pixmapScreenShot == NULL || - pixmapScreenShot->getW() != sm.getScreenW() || - pixmapScreenShot->getH() != sm.getScreenH()) { - SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s Line: %d]\n",__FILE__,__FUNCTION__,__LINE__); - - delete pixmapScreenShot; - pixmapScreenShot = new Pixmap2D(sm.getScreenW(), sm.getScreenH(), 3); - } + Pixmap2D *pixmapScreenShot = new Pixmap2D(sm.getScreenW(), sm.getScreenH(), 3); SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s Line: %d]\n",__FILE__,__FUNCTION__,__LINE__); glFinish(); @@ -2969,11 +3002,23 @@ void Renderer::saveScreen(const string &path){ GL_RGB, GL_UNSIGNED_BYTE, pixmapScreenShot->getPixels()); SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s Line: %d]\n",__FILE__,__FUNCTION__,__LINE__); - pixmapScreenShot->save(path); + + // Signal the threads queue to add a screenshot save request + MutexSafeWrapper safeMutex(&saveScreenShotThreadAccessor); + saveScreenQueue.push_back(make_pair(path,pixmapScreenShot)); + safeMutex.ReleaseLock(); SystemFlags::OutputDebug(SystemFlags::debugSystem,"In [%s::%s Line: %d]\n",__FILE__,__FUNCTION__,__LINE__); } +unsigned int Renderer::getSaveScreenQueueSize() { + MutexSafeWrapper safeMutex(&saveScreenShotThreadAccessor); + int queueSize = saveScreenQueue.size(); + safeMutex.ReleaseLock(); + + return queueSize; +} + // ==================== PRIVATE ==================== float Renderer::computeSunAngle(float time) { diff --git a/source/glest_game/graphics/renderer.h b/source/glest_game/graphics/renderer.h index 50a2a4e76..cdad27dee 100644 --- a/source/glest_game/graphics/renderer.h +++ b/source/glest_game/graphics/renderer.h @@ -31,6 +31,7 @@ #include "model.h" #include "graphics_interface.h" #include "base_renderer.h" +#include "simple_threads.h" #ifdef DEBUG_RENDERING_ENABLED # define IF_DEBUG_EDITION(x) x @@ -44,6 +45,7 @@ namespace Glest{ namespace Game{ using namespace Shared::Graphics; +using namespace Shared::PlatformCommon; // ===================================================== // class MeshCallbackTeamColor @@ -131,7 +133,7 @@ public: }; -class Renderer : public RendererInterface, public BaseRenderer { +class Renderer : public RendererInterface, public BaseRenderer, public SimpleTaskCallbackInterface { public: //progress bar static const int maxProgressBar; @@ -242,7 +244,9 @@ private: bool useQuadCache; - Pixmap2D *pixmapScreenShot; + SimpleTaskThread *saveScreenShotThread; + Mutex saveScreenShotThreadAccessor; + std::list > saveScreenQueue; private: Renderer(); @@ -386,6 +390,7 @@ public: void removeUnitFromQuadCache(const Unit *unit); uint64 getCurrentPixelByteCount(ResourceScope rs=rsGame) const; + unsigned int getSaveScreenQueueSize(); private: //private misc @@ -421,6 +426,8 @@ private: void renderTile(const Vec2i &pos); void renderQuad(int x, int y, int w, int h, const Texture2D *texture); + void simpleTask(); + //static static Texture2D::Filter strToTextureFilter(const string &s); }; diff --git a/source/shared_lib/include/graphics/pixmap.h b/source/shared_lib/include/graphics/pixmap.h index 4069d0e2c..ee29385ac 100644 --- a/source/shared_lib/include/graphics/pixmap.h +++ b/source/shared_lib/include/graphics/pixmap.h @@ -33,7 +33,7 @@ namespace Shared{ namespace Graphics{ // class PixmapIo // ===================================================== -class PixmapIo{ +class PixmapIo { protected: int w; int h; @@ -58,7 +58,7 @@ public: // class PixmapIoTga // ===================================================== -class PixmapIoTga: public PixmapIo{ +class PixmapIoTga: public PixmapIo { private: FILE *file; @@ -78,7 +78,7 @@ public: // class PixmapIoBmp // ===================================================== -class PixmapIoBmp: public PixmapIo{ +class PixmapIoBmp: public PixmapIo { private: FILE *file; @@ -94,6 +94,27 @@ public: virtual void write(uint8 *pixels); }; +// ===================================================== +// class PixmapIoBmp +// ===================================================== + +class PixmapIoPng: public PixmapIo { +private: + FILE *file; + string path; + +public: + PixmapIoPng(); + virtual ~PixmapIoPng(); + + virtual void openRead(const string &path); + virtual void read(uint8 *pixels); + virtual void read(uint8 *pixels, int components); + + virtual void openWrite(const string &path, int w, int h, int components); + virtual void write(uint8 *pixels); +}; + // ===================================================== // class Pixmap1D // ===================================================== @@ -157,7 +178,7 @@ public: void save(const string &path); void saveBmp(const string &path); void saveTga(const string &path); - + void savePng(const string &path); //get int getW() const {return w;} diff --git a/source/shared_lib/sources/graphics/pixmap.cpp b/source/shared_lib/sources/graphics/pixmap.cpp index cb59b70c1..901ba5c9c 100644 --- a/source/shared_lib/sources/graphics/pixmap.cpp +++ b/source/shared_lib/sources/graphics/pixmap.cpp @@ -21,6 +21,8 @@ #include "randomgen.h" #include "FileReader.h" #include "ImageReaders.h" +#include +#include #include "leak_dumper.h" @@ -285,7 +287,7 @@ void PixmapIoBmp::openWrite(const string &path, int w, int h, int components) { file= fopen(path.c_str(),"wb"); if (file == NULL) { - throw runtime_error("Can't open BMP file for writting: "+ path); + throw runtime_error("Can't open BMP file for writing: "+ path); } BitmapFileHeader fileHeader; @@ -321,6 +323,210 @@ void PixmapIoBmp::write(uint8 *pixels) { } } +// ===================================================== +// class PixmapIoPng +// ===================================================== + +PixmapIoPng::PixmapIoPng() { + file= NULL; +} + +PixmapIoPng::~PixmapIoPng() { + if(file!=NULL){ + fclose(file); + file=NULL; + } +} + +void PixmapIoPng::openRead(const string &path) { + + throw runtime_error("PixmapIoPng::openRead not implemented!"); + +/* + file= fopen(path.c_str(),"rb"); + if (file==NULL){ + throw runtime_error("Can't open BMP file: "+ path); + } + + //read file header + BitmapFileHeader fileHeader; + size_t readBytes = fread(&fileHeader, sizeof(BitmapFileHeader), 1, file); + if(fileHeader.type1!='B' || fileHeader.type2!='M'){ + throw runtime_error(path +" is not a bitmap"); + } + + //read info header + BitmapInfoHeader infoHeader; + readBytes = fread(&infoHeader, sizeof(BitmapInfoHeader), 1, file); + if(infoHeader.bitCount!=24){ + throw runtime_error(path+" is not a 24 bit bitmap"); + } + + h= infoHeader.height; + w= infoHeader.width; + components= 3; +*/ +} + +void PixmapIoPng::read(uint8 *pixels) { + throw runtime_error("PixmapIoPng::read not implemented!"); + //read(pixels, 3); +} + +void PixmapIoPng::read(uint8 *pixels, int components) { + + throw runtime_error("PixmapIoPng::read #2 not implemented!"); + +/* + for(int i=0; ipath = path; + this->w= w; + this->h= h; + this->components= components; + + file= fopen(path.c_str(),"wb"); + if (file == NULL) { + throw runtime_error("Can't open PNG file for writing: "+ path); + } +} + +void PixmapIoPng::write(uint8 *pixels) { + // Allocate write & info structures + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + if(!png_ptr) { + fclose(file); + throw runtime_error("OpenGlDevice::saveImageAsPNG() - out of memory creating write structure"); + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if(!info_ptr) { + png_destroy_write_struct(&png_ptr, + (png_infopp)NULL); + fclose(file); + throw runtime_error("OpenGlDevice::saveImageAsPNG() - out of memery creating info structure"); + } + + // setjmp() must be called in every function that calls a PNG-writing + // libpng function, unless an alternate error handler was installed-- + // but compatible error handlers must either use longjmp() themselves + // (as in this program) or exit immediately, so here we go: */ + + if(setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(file); + throw runtime_error("OpenGlDevice::saveImageAsPNG() - setjmp problem"); + } + + // make sure outfile is (re)opened in BINARY mode + png_init_io(png_ptr, file); + + // set the compression levels--in general, always want to leave filtering + // turned on (except for palette images) and allow all of the filters, + // which is the default; want 32K zlib window, unless entire image buffer + // is 16K or smaller (unknown here)--also the default; usually want max + // compression (NOT the default); and remaining compression flags should + // be left alone + + //png_set_compression_level(png_ptr, Z_BEST_COMPRESSION); + png_set_compression_level(png_ptr, Z_DEFAULT_COMPRESSION); + + // + // this is default for no filtering; Z_FILTERED is default otherwise: + // png_set_compression_strategy(png_ptr, Z_DEFAULT_STRATEGY); + // these are all defaults: + // png_set_compression_mem_level(png_ptr, 8); + // png_set_compression_window_bits(png_ptr, 15); + // png_set_compression_method(png_ptr, 8); + + + // Set some options: color_type, interlace_type + int color_type=0, interlace_type=0, numChannels=0; + + // color_type = PNG_COLOR_TYPE_GRAY; + // color_type = PNG_COLOR_TYPE_GRAY_ALPHA; + color_type = PNG_COLOR_TYPE_RGB; + numChannels = 3; + // color_type = PNG_COLOR_TYPE_RGB_ALPHA; + + interlace_type = PNG_INTERLACE_NONE; + // interlace_type = PNG_INTERLACE_ADAM7; + + int bit_depth = 8; + png_set_IHDR(png_ptr, info_ptr, this->w, this->h, bit_depth, + color_type, + interlace_type, + PNG_COMPRESSION_TYPE_BASE, + PNG_FILTER_TYPE_BASE); + + // Optional gamma chunk is strongly suggested if you have any guess + // as to the correct gamma of the image. (we don't have a guess) + // + // png_set_gAMA(png_ptr, info_ptr, image_gamma); + + // write all chunks up to (but not including) first IDAT + png_write_info(png_ptr, info_ptr); + + // set up the row pointers for the image so we can use png_write_image + + png_bytep* row_pointers = new png_bytep[this->h]; + if (row_pointers == 0) { + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(file); + throw runtime_error("OpenGlDevice::failed to allocate memory for row pointers"); + } + + unsigned int row_stride = this->w * numChannels; + unsigned char *rowptr = (unsigned char*) pixels; + for (int row = this->h-1; row >=0 ; row--) { + row_pointers[row] = rowptr; + rowptr += row_stride; + } + + // now we just write the whole image; libpng takes care of interlacing for us + png_write_image(png_ptr, row_pointers); + + // since that's it, we also close out the end of the PNG file now--if we + // had any text or time info to write after the IDATs, second argument + // would be info_ptr, but we optimize slightly by sending NULL pointer: */ + + png_write_end(png_ptr, info_ptr); + + // + // clean up after the write + // free any memory allocated & close the file + // + png_destroy_write_struct(&png_ptr, &info_ptr); + + delete [] row_pointers; + //fclose(file); +} + // ===================================================== // class Pixmap1D // ===================================================== @@ -512,6 +718,9 @@ void Pixmap2D::save(const string &path) { else if(toLower(extension) == "tga") { saveTga(path); } + else if(toLower(extension) == "png") { + savePng(path); + } else { throw runtime_error("Unknown pixmap extension: " + extension); } @@ -528,7 +737,12 @@ void Pixmap2D::saveTga(const string &path) { pst.openWrite(path, w, h, components); pst.write(pixels); } +void Pixmap2D::savePng(const string &path) { + PixmapIoPng pst; + pst.openWrite(path, w, h, components); + pst.write(pixels); +} void Pixmap2D::getPixel(int x, int y, uint8 *value) const { for(int i=0; i