// Ryzom - MMORPG Framework // Copyright (C) 2010 Winch Gate Property Limited // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program 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 // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . ///////////// // INCLUDE // ///////////// #include "stdpch.h" #include "movie_shooter.h" #include "time_client.h" #include "nel/misc/bitmap.h" #include "nel/misc/path.h" #include "nel/misc/fast_mem.h" #include "nel/misc/common.h" #include "nel/misc/file.h" #include "nel/misc/debug.h" #include "nel/misc/system_info.h" /////////// // USING // /////////// using namespace NLMISC; using namespace std; // *************************************************************************** CMovieShooter MovieShooter; // *************************************************************************** CMovieShooter::CMovieShooter() { _MemoryBlock= NULL; _MemorySize= 0; _CurrentIndex= 0; _NumFrames= 0; _FirstFrame= NULL; _LastFrame= NULL; _FrameSkip = _CurrentFrameSkip = 0; } // *************************************************************************** CMovieShooter::~CMovieShooter() { clearMemory(); } // *************************************************************************** bool CMovieShooter::init(uint maxMemory) { clearMemory(); if(maxMemory==0) return false; _MemoryBlock= new uint8[maxMemory]; if(!_MemoryBlock) { nlwarning("Failed to Allocate %d bytes for MovieShooter", maxMemory); return false; } // Must fill memory with 0, just to ensure the system allocate the virtual pages. memset(_MemoryBlock, 0, maxMemory); _MemorySize= maxMemory; _CurrentIndex= 0; _NumFrames= 0; _FirstFrame= NULL; _LastFrame= NULL; return true; } // *************************************************************************** void CMovieShooter::clearMemory() { if(_MemoryBlock) delete [] (_MemoryBlock); _MemoryBlock= NULL; _MemorySize= 0; _CurrentIndex= 0; _NumFrames= 0; _FirstFrame= NULL; _LastFrame= NULL; } // *************************************************************************** bool CMovieShooter::addFrame(double time, UDriver *pDriver) { if(!enabled()) return false; // Get the buffer from driver. static to avoid reallocation static CBitmap bitmap; pDriver->getBuffer(bitmap); nlassert(bitmap.getPixelFormat()==CBitmap::RGBA); // add the frame. if(bitmap.getPixels().size()==0) return false; return addFrame(time, (CRGBA*)(&bitmap.getPixels()[0]), bitmap.getWidth(), bitmap.getHeight()); } // *************************************************************************** bool CMovieShooter::addFrame(double time, CRGBA *pImage, uint w, uint h) { if(!enabled() || w==0 || h==0) return false; ++_CurrentFrameSkip; if (_CurrentFrameSkip <= _FrameSkip) return true; _CurrentFrameSkip = 0; // _MemorySize must contain at least ONE frame. uint dataSize= w*h*sizeof(uint16); uint totalSize= dataSize+sizeof(CFrameHeader); nlassert(totalSize<=_MemorySize); // If too big to fit in memory malloc, loop if( totalSize + _CurrentIndex > _MemorySize) { _CurrentIndex= 0; } // prepare a new frame. CFrameHeader *newFrame= (CFrameHeader*)(_MemoryBlock+_CurrentIndex); // while this frame erase first one. while( _FirstFrame!=NULL && (uint8*)newFrame<=(uint8*)_FirstFrame && ((uint8*)newFrame+totalSize)>(uint8*)_FirstFrame ) { // skip to the next frame. _FirstFrame= _FirstFrame->Next; _NumFrames--; // if empty, clean all. if(_FirstFrame==NULL) { nlassert(_NumFrames==0); _LastFrame=NULL; _CurrentIndex= 0; newFrame= (CFrameHeader*)_MemoryBlock; } } // Build the frame. newFrame->Time= time; newFrame->Width= w; newFrame->Height= h; newFrame->Data= _MemoryBlock+_CurrentIndex+sizeof(CFrameHeader); newFrame->Next= NULL; // Compress and Fill Data. As fast as possible uint16 *dst= (uint16*)newFrame->Data; CRGBA *src= pImage; for(uint y=h;y>0;y--) { // Precache all the line. NB: correct for 800, since 800*4==3200, ie under the 4K cache size. CFastMem::precache(src, w*sizeof(CRGBA)); // For all pixels; compress, and store. #if defined(NL_OS_WINDOWS) && !defined(NL_NO_ASM) __asm { mov ecx, w mov esi, src mov edi, dst myLoop2: mov eax, [esi] mov ebx, eax mov edx, eax and eax, 0x000000F8 and ebx, 0x0000FC00 shl eax, 8 shr ebx, 5 or eax, ebx and edx, 0x00F80000 shr edx, 19 add esi, 4 or eax, edx mov [edi],eax add edi, 2 dec ecx jnz myLoop2 } src+= w; dst+= w; #else for(uint x=w;x>0;x--, src++, dst++) { *dst= src->get565(); } #endif } // inc index. _CurrentIndex+= totalSize; // Link to the list. if(_FirstFrame==NULL) { _FirstFrame= _LastFrame= newFrame; } else { _LastFrame->Next= newFrame; _LastFrame= newFrame; } // Ok! _NumFrames++; return true; } // *************************************************************************** uint CMovieShooter::getNumFrames() { return _NumFrames; } // *************************************************************************** void CMovieShooter::saveMovie(UDriver *drv, UTextContext *textContext, const char *path, float framePeriod, bool allowLinearInterp, const char *savePrefix /*= "shot_"*/) { if(!CFile::isDirectory(path)) throw Exception("SaveMovie: %s is not a directory", path); if(getNumFrames()<2) throw Exception("SaveMovie: no Frames to save"); if(framePeriod<=0) throw Exception("SaveMovie: bad Frame Period"); // Initialize Pen textContext->setFontSize(10); textContext->setColor(CRGBA(255,255,255)); textContext->setHotSpot(UTextContext::TopLeft); // prepape save string fileName= string(path) + "/" + string(savePrefix); // first frame CFrameHeader *precFrame= _FirstFrame; double time= precFrame->Time; // Save all frames uint fileIndex= 0; sint n= getNumFrames(); // Write interpolation of frames => must have 2 frames. while(n>=2) { // Grab Inputs. drv->EventServer.pump(true); // Stop to save ?? if(drv->AsyncListener.isKeyDown(KeyESCAPE)) { break; } // nextFrame is... CFrameHeader *nextFrame= precFrame->Next; // Skip any frame. Get first end frame with Time>time while(n>=2 && nextFrame->Time<=time) { // skip the Frame precFrame= nextFrame; nextFrame= nextFrame->Next; n--; } // If a frame exist, get it. if(n>=2) { // get the interpolation factor double interpValue= (time-precFrame->Time)/(nextFrame->Time-precFrame->Time); // interpolation is possible only if 2 frames are of same size, and if wanted bool interpOk; uint w, h; w= precFrame->Width; h= precFrame->Height; interpOk= allowLinearInterp && (w==nextFrame->Width && h==nextFrame->Height); // get the first frame static CBitmap bmp0; getFrameData(precFrame, bmp0); // if interp ok if(interpOk) { static CBitmap bmp1; getFrameData(nextFrame, bmp1); uint coef= (uint)floor(256*interpValue+0.5f); coef= min(256U, coef); // blend CRGBA *dst= (CRGBA*)&bmp0.getPixels()[0]; CRGBA *src= (CRGBA*)&bmp1.getPixels()[0]; for(uint nPix= w*h;nPix>0;nPix--, src++, dst++) { dst->blendFromuiRGBOnly(*dst, *src, coef); } } // write the bitmap char fname[500]; smprintf(fname, 500, "%s%05d.tga", fileName.c_str(), fileIndex); COFile file(fname); bmp0.writeTGA(file,24,false); // Copy frame to buffer, and swap drv->fillBuffer(bmp0); textContext->printfAt(0.05f,0.80f, "Movie Saving: %d%%", 100-n*100/getNumFrames()); drv->swapBuffers(); } // next frame to write. fileIndex++; time+= framePeriod; } } // *************************************************************************** void CMovieShooter::replayMovie(UDriver *drv, UTextContext *textContext) { nlassert(drv); if(getNumFrames()<2) throw Exception("ReplayMovie: no Frames to save"); // Initialize Pen textContext->setFontSize(10); textContext->setColor(CRGBA(255,255,255)); textContext->setHotSpot(UTextContext::TopLeft); // first frame CFrameHeader *frame= _FirstFrame; uint n= getNumFrames(); // replay all frames double tPrec= ryzomGetLocalTime ()*0.001; double lastFrameTime= frame->Time; while(frame) { // Grab Inputs. drv->EventServer.pump(true); // Stop to save ?? if(drv->AsyncListener.isKeyDown(KeyESCAPE)) { break; } // get the frame static CBitmap bmp0; getFrameData(frame, bmp0); // Copy frame to buffer drv->fillBuffer(bmp0); textContext->printfAt(0.05f,0.80f, "Movie Replay: %d%%", 100-n*100/getNumFrames()); // Wait frame double tNext; do { tNext= ryzomGetLocalTime ()*0.001; } while( tNext-tPrec < frame->Time-lastFrameTime ); lastFrameTime= frame->Time; tPrec= tNext; // swap. drv->swapBuffers(); // nextFrame frame= frame->Next; n--; } } // *************************************************************************** void CMovieShooter::getFrameData(CFrameHeader *frame, NLMISC::CBitmap &bmp) { uint w= frame->Width; uint h= frame->Height; // resize if(bmp.getWidth()!=w || bmp.getHeight()!=h) bmp.resize(w, h); // unpack. uint16 *src= (uint16*)frame->Data; CRGBA *dst= (CRGBA*)&bmp.getPixels()[0]; for(uint npix= w*h; npix>0; npix--, src++, dst++) { dst->set565(*src); } } // *************************************************************************** void CMovieShooter::resetMovie() { _CurrentIndex= 0; _NumFrames= 0; _FirstFrame= NULL; _LastFrame= NULL; } // *************************************************************************** void CMovieShooter::setFrameSkip (uint32 nNbFrameToSkip) { _FrameSkip = nNbFrameToSkip; _CurrentFrameSkip = 0; }