Linux multi-monitor fullscreen support
[ryzomcore.git] / ryzom / client / src / movie_shooter.cpp
blob1358a20c4d05bdf39e3d03bc31ee154b03d1ea70
1 // Ryzom - MMORPG Framework <http://dev.ryzom.com/projects/ryzom/>
2 // Copyright (C) 2010 Winch Gate Property Limited
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU Affero General Public License as
6 // published by the Free Software Foundation, either version 3 of the
7 // License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU Affero General Public License for more details.
14 // You should have received a copy of the GNU Affero General Public License
15 // along with this program. If not, see <http://www.gnu.org/licenses/>.
20 /////////////
21 // INCLUDE //
22 /////////////
23 #include "stdpch.h"
24 #include "movie_shooter.h"
25 #include "time_client.h"
26 #include "nel/misc/bitmap.h"
27 #include "nel/misc/path.h"
28 #include "nel/misc/fast_mem.h"
29 #include "nel/misc/common.h"
30 #include "nel/misc/file.h"
31 #include "nel/misc/debug.h"
32 #include "nel/misc/system_info.h"
34 #ifdef DEBUG_NEW
35 #define new DEBUG_NEW
36 #endif
38 ///////////
39 // USING //
40 ///////////
41 using namespace NLMISC;
42 using namespace std;
45 // ***************************************************************************
46 CMovieShooter MovieShooter;
49 // ***************************************************************************
50 CMovieShooter::CMovieShooter()
52 _MemoryBlock= NULL;
53 _MemorySize= 0;
54 _CurrentIndex= 0;
55 _NumFrames= 0;
56 _FirstFrame= NULL;
57 _LastFrame= NULL;
58 _FrameSkip = _CurrentFrameSkip = 0;
61 // ***************************************************************************
62 CMovieShooter::~CMovieShooter()
64 clearMemory();
67 // ***************************************************************************
68 bool CMovieShooter::init(uint maxMemory)
70 clearMemory();
72 if(maxMemory==0)
73 return false;
75 _MemoryBlock= new uint8[maxMemory];
76 if(!_MemoryBlock)
78 nlwarning("Failed to Allocate %d bytes for MovieShooter", maxMemory);
79 return false;
81 // Must fill memory with 0, just to ensure the system allocate the virtual pages.
82 memset(_MemoryBlock, 0, maxMemory);
83 _MemorySize= maxMemory;
84 _CurrentIndex= 0;
85 _NumFrames= 0;
86 _FirstFrame= NULL;
87 _LastFrame= NULL;
89 return true;
92 // ***************************************************************************
93 void CMovieShooter::clearMemory()
95 if(_MemoryBlock)
96 delete [] (_MemoryBlock);
97 _MemoryBlock= NULL;
98 _MemorySize= 0;
99 _CurrentIndex= 0;
100 _NumFrames= 0;
101 _FirstFrame= NULL;
102 _LastFrame= NULL;
105 // ***************************************************************************
106 bool CMovieShooter::addFrame(double time, UDriver *pDriver)
108 if(!enabled())
109 return false;
111 // Get the buffer from driver. static to avoid reallocation
112 static CBitmap bitmap;
113 pDriver->getBuffer(bitmap);
114 nlassert(bitmap.getPixelFormat()==CBitmap::RGBA);
116 // add the frame.
117 if(bitmap.getPixels().empty())
118 return false;
119 return addFrame(time, (CRGBA*)(&bitmap.getPixels()[0]), bitmap.getWidth(), bitmap.getHeight());
122 // ***************************************************************************
123 bool CMovieShooter::addFrame(double time, CRGBA *pImage, uint w, uint h)
125 if(!enabled() || w==0 || h==0)
126 return false;
128 ++_CurrentFrameSkip;
129 if (_CurrentFrameSkip <= _FrameSkip)
130 return true;
131 _CurrentFrameSkip = 0;
133 // _MemorySize must contain at least ONE frame.
134 uint dataSize= w*h*sizeof(uint16);
135 uint totalSize= dataSize+sizeof(CFrameHeader);
136 nlassert(totalSize<=_MemorySize);
138 // If too big to fit in memory malloc, loop
139 if( totalSize + _CurrentIndex > _MemorySize)
141 _CurrentIndex= 0;
144 // prepare a new frame.
145 CFrameHeader *newFrame= (CFrameHeader*)(_MemoryBlock+_CurrentIndex);
147 // while this frame erase first one.
148 while( _FirstFrame!=NULL && (uint8*)newFrame<=(uint8*)_FirstFrame && ((uint8*)newFrame+totalSize)>(uint8*)_FirstFrame )
150 // skip to the next frame.
151 _FirstFrame= _FirstFrame->Next;
152 _NumFrames--;
153 // if empty, clean all.
154 if(_FirstFrame==NULL)
156 nlassert(_NumFrames==0);
157 _LastFrame=NULL;
158 _CurrentIndex= 0;
159 newFrame= (CFrameHeader*)_MemoryBlock;
163 // Build the frame.
164 newFrame->Time= time;
165 newFrame->Width= w;
166 newFrame->Height= h;
167 newFrame->Data= _MemoryBlock+_CurrentIndex+sizeof(CFrameHeader);
168 newFrame->Next= NULL;
170 // Compress and Fill Data. As fast as possible
171 uint16 *dst= (uint16*)newFrame->Data;
172 CRGBA *src= pImage;
173 for(uint y=h;y>0;y--)
175 // Precache all the line. NB: correct for 800, since 800*4==3200, ie under the 4K cache size.
176 CFastMem::precache(src, w*sizeof(CRGBA));
177 // For all pixels; compress, and store.
178 #if defined(NL_OS_WINDOWS) && !defined(NL_NO_ASM)
179 __asm
181 mov ecx, w
182 mov esi, src
183 mov edi, dst
184 myLoop2:
185 mov eax, [esi]
187 mov ebx, eax
188 mov edx, eax
190 and eax, 0x000000F8
191 and ebx, 0x0000FC00
192 shl eax, 8
193 shr ebx, 5
195 or eax, ebx
196 and edx, 0x00F80000
198 shr edx, 19
199 add esi, 4
201 or eax, edx
203 mov [edi],eax
205 add edi, 2
207 dec ecx
208 jnz myLoop2
210 src+= w;
211 dst+= w;
212 #else
213 for(uint x=w;x>0;x--, src++, dst++)
215 *dst= src->get565();
217 #endif
220 // inc index.
221 _CurrentIndex+= totalSize;
223 // Link to the list.
224 if(_FirstFrame==NULL)
226 _FirstFrame= _LastFrame= newFrame;
228 else
230 _LastFrame->Next= newFrame;
231 _LastFrame= newFrame;
234 // Ok!
235 _NumFrames++;
236 return true;
239 // ***************************************************************************
240 uint CMovieShooter::getNumFrames()
242 return _NumFrames;
245 // ***************************************************************************
246 void CMovieShooter::saveMovie(UDriver *drv, UTextContext *textContext, const char *path, float framePeriod, bool allowLinearInterp, const char *savePrefix /*= "shot_"*/)
248 if(!CFile::isDirectory(path))
249 throw Exception("SaveMovie: %s is not a directory", path);
251 if(getNumFrames()<2)
252 throw Exception("SaveMovie: no Frames to save");
254 if(framePeriod<=0)
255 throw Exception("SaveMovie: bad Frame Period");
258 // Initialize Pen
259 textContext->setFontSize(10);
260 textContext->setColor(CRGBA(255,255,255));
261 textContext->setHotSpot(UTextContext::TopLeft);
264 // prepape save
265 string fileName= string(path) + "/" + string(savePrefix);
267 // first frame
268 CFrameHeader *precFrame= _FirstFrame;
269 double time= precFrame->Time;
271 // Save all frames
272 uint fileIndex= 0;
273 sint n= getNumFrames();
274 // Write interpolation of frames => must have 2 frames.
275 while(n>=2)
277 // Grab Inputs.
278 drv->EventServer.pump(true);
279 // Stop to save ??
280 if(drv->AsyncListener.isKeyDown(KeyESCAPE))
282 break;
285 // nextFrame is...
286 CFrameHeader *nextFrame= precFrame->Next;
288 // Skip any frame. Get first end frame with Time>time
289 while(n>=2 && nextFrame->Time<=time)
291 // skip the Frame
292 precFrame= nextFrame;
293 nextFrame= nextFrame->Next;
294 n--;
297 // If a frame exist, get it.
298 if(n>=2)
300 // get the interpolation factor
301 double interpValue= (time-precFrame->Time)/(nextFrame->Time-precFrame->Time);
303 // interpolation is possible only if 2 frames are of same size, and if wanted
304 bool interpOk;
305 uint w, h;
306 w= precFrame->Width;
307 h= precFrame->Height;
308 interpOk= allowLinearInterp && (w==nextFrame->Width && h==nextFrame->Height);
310 // get the first frame
311 static CBitmap bmp0;
312 getFrameData(precFrame, bmp0);
314 // if interp ok
315 if(interpOk)
317 static CBitmap bmp1;
318 getFrameData(nextFrame, bmp1);
321 uint coef= (uint)floor(256*interpValue+0.5f);
322 coef= min(256U, coef);
324 // blend
325 CRGBA *dst= (CRGBA*)&bmp0.getPixels()[0];
326 CRGBA *src= (CRGBA*)&bmp1.getPixels()[0];
327 for(uint nPix= w*h;nPix>0;nPix--, src++, dst++)
329 dst->blendFromuiRGBOnly(*dst, *src, coef);
333 // write the bitmap
334 char fname[500];
335 smprintf(fname, 500, "%s%05d.tga", fileName.c_str(), fileIndex);
336 COFile file(fname);
337 bmp0.writeTGA(file,24,false);
339 // Copy frame to buffer, and swap
340 drv->fillBuffer(bmp0);
341 textContext->printfAt(0.05f,0.80f, "Movie Saving: %d%%", 100-n*100/getNumFrames());
342 drv->swapBuffers();
345 // next frame to write.
346 fileIndex++;
347 time+= framePeriod;
351 // ***************************************************************************
352 void CMovieShooter::replayMovie(UDriver *drv, UTextContext *textContext)
354 nlassert(drv);
356 if(getNumFrames()<2)
357 throw Exception("ReplayMovie: no Frames to save");
359 // Initialize Pen
360 textContext->setFontSize(10);
361 textContext->setColor(CRGBA(255,255,255));
362 textContext->setHotSpot(UTextContext::TopLeft);
364 // first frame
365 CFrameHeader *frame= _FirstFrame;
366 uint n= getNumFrames();
368 // replay all frames
369 double tPrec= ryzomGetLocalTime ()*0.001;
370 double lastFrameTime= frame->Time;
371 while(frame)
373 // Grab Inputs.
374 drv->EventServer.pump(true);
375 // Stop to save ??
376 if(drv->AsyncListener.isKeyDown(KeyESCAPE))
378 break;
381 // get the frame
382 static CBitmap bmp0;
383 getFrameData(frame, bmp0);
385 // Copy frame to buffer
386 drv->fillBuffer(bmp0);
387 textContext->printfAt(0.05f,0.80f, "Movie Replay: %d%%", 100-n*100/getNumFrames());
389 // Wait frame
390 double tNext;
393 tNext= ryzomGetLocalTime ()*0.001;
395 while( tNext-tPrec < frame->Time-lastFrameTime );
397 lastFrameTime= frame->Time;
398 tPrec= tNext;
400 // swap.
401 drv->swapBuffers();
403 // nextFrame
404 frame= frame->Next;
405 n--;
410 // ***************************************************************************
411 void CMovieShooter::getFrameData(CFrameHeader *frame, NLMISC::CBitmap &bmp)
413 uint w= frame->Width;
414 uint h= frame->Height;
415 // resize
416 if(bmp.getWidth()!=w || bmp.getHeight()!=h)
417 bmp.resize(w, h);
418 // unpack.
419 uint16 *src= (uint16*)frame->Data;
420 CRGBA *dst= (CRGBA*)&bmp.getPixels()[0];
421 for(uint npix= w*h; npix>0; npix--, src++, dst++)
423 dst->set565(*src);
427 // ***************************************************************************
428 void CMovieShooter::resetMovie()
430 _CurrentIndex= 0;
431 _NumFrames= 0;
432 _FirstFrame= NULL;
433 _LastFrame= NULL;
436 // ***************************************************************************
437 void CMovieShooter::setFrameSkip (uint32 nNbFrameToSkip)
439 _FrameSkip = nNbFrameToSkip;
440 _CurrentFrameSkip = 0;