2 * Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
3 * Free Software Foundation, Inc.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22 #include "MovieTester.h"
23 #include "GnashException.h"
25 #include "noseek_fd_adapter.h"
26 #include "movie_definition.h"
28 #include "movie_root.h"
29 #include "MovieClip.h"
30 #include "MovieFactory.h"
31 #include "sound_handler.h" // for creating the "test" sound handlers
32 #include "NullSoundHandler.h"
33 #include "RGBA.h" // for rgba class (pixel checking)
34 #include "FuzzyPixel.h" // for pixel checking
36 #include "ManualClock.h" // for use by advance
37 #include "StreamProvider.h" // for passing to RunResources
38 #include "IOChannel.h"
39 #include "swf/TagLoadersTable.h"
40 #include "swf/DefaultTagLoaders.h"
41 #include "GnashFactory.h"
44 # include "Renderer_cairo.h"
46 #ifdef RENDERER_OPENGL
47 # include "Renderer_ogl.h"
50 # include "Renderer_agg.h"
53 #include "MediaHandler.h"
57 #include <memory> // for unique_ptr
58 #include <cmath> // for ceil
61 //#define SHOW_INVALIDATED_BOUNDS_ON_ADVANCE 1
63 #ifdef SHOW_INVALIDATED_BOUNDS_ON_ADVANCE
74 // exp2 isn't part of standard C++, so is defined here in case the compiler
75 // doesn't supply it (e.g. in BSD)
76 inline double exp2(double x
) { return std::pow(2.0, x
); }
78 bool getAveragePixel(const Renderer
& r
, rgba
& color_return
, int x
, int y
,
81 std::string
toShortString(const rgba
& r
) {
83 ss
<< +r
.m_r
<< "," << +r
.m_g
<< "," << +r
.m_b
<< "," << +r
.m_a
;
88 MovieTester::MovieTester(const std::string
& url
)
97 // Initialize the testing media handlers
98 initTestingMediaHandlers();
102 // Initialize the sound handler(s)
103 initTestingSoundHandlers();
104 _runResources
.setSoundHandler(_sound_handler
);
107 _runResources
.setMediaHandler(_mediaHandler
);
110 std::shared_ptr
<SWF::TagLoadersTable
> loaders(new SWF::TagLoadersTable());
111 addDefaultLoaders(*loaders
);
113 _runResources
.setTagLoaders(loaders
);
115 std::shared_ptr
<StreamProvider
> sp(new StreamProvider(url
, url
));
117 _runResources
.setStreamProvider(sp
);
120 std::unique_ptr
<IOChannel
> in (
121 noseek_fd_adapter::make_stream(fileno(stdin
))
123 _movie_def
= MovieFactory::makeMovie(std::move(in
), url
, _runResources
, false);
126 if ( urlObj
.protocol() == "file" ) {
127 RcInitFile
& rcfile
= RcInitFile::getDefaultInstance();
128 const std::string
& path
= urlObj
.path();
129 #if 1 // add the *directory* the movie was loaded from to the local sandbox path
130 size_t lastSlash
= path
.find_last_of('/');
131 std::string dir
= path
.substr(0, lastSlash
+1);
132 rcfile
.addLocalSandboxPath(dir
);
133 log_debug(_("%s appended to local sandboxes"), dir
.c_str());
134 #else // add the *file* to be loaded to the local sandbox path
135 rcfile
.addLocalSandboxPath(path
);
136 log_debug(_("%s appended to local sandboxes"), path
.c_str());
139 // _url should be always set at this point...
140 _movie_def
= MovieFactory::makeMovie(urlObj
, _runResources
,
144 if ( ! _movie_def
) {
145 throw GnashException("Could not load movie from "+url
);
148 _movie_root
.reset(new movie_root(_clock
, _runResources
));
150 // Initialize viewport size with the one advertised in the header
151 _width
= unsigned(_movie_def
->get_width_pixels());
152 _height
= unsigned(_movie_def
->get_height_pixels());
154 // Initialize the testing renderers
155 initTestingRenderers();
157 // Now complete load of the movie
158 _movie_def
->completeLoad();
159 _movie_def
->ensure_frame_loaded(_movie_def
->get_frame_count());
161 // Activate verbosity so that self-contained testcases are
163 gnash::LogFile
& dbglogfile
= gnash::LogFile::getDefaultInstance();
164 dbglogfile
.setVerbosity(1);
166 // Finally, place the root movie on the stage ...
167 MovieClip::MovieVariables v
;
168 _movie_root
->init(_movie_def
.get(), v
);
174 MovieTester::~MovieTester()
176 MovieFactory::clear();
180 MovieTester::render(std::shared_ptr
<Renderer
> h
,
181 InvalidatedRanges
& invalidated_regions
)
184 // This is a bit dangerous, as there isn't really support for swapping
185 // renderers during runtime; though the only problem is likely to be
186 // that CachedBitmaps are missing.
187 _runResources
.setRenderer(h
);
189 h
->set_invalidated_regions(invalidated_regions
);
191 // We call display here to simulate effect of a real run.
193 // What we're particularly interested about is
194 // proper computation of invalidated bounds, which
195 // needs clear_invalidated() to be called.
196 // display() will call clear_invalidated() on DisplayObjects
197 // actually modified so we're fine with that.
199 // Directly calling _movie->clear_invalidated() here
200 // also work currently, as invalidating the topmost
201 // movie will force recomputation of all invalidated
202 // bounds. Still, possible future changes might
203 // introduce differences, so better to reproduce
204 // real runs as close as possible, by calling display().
206 _movie_root
->display();
210 MovieTester::redraw()
217 MovieTester::render()
219 // Get invalidated ranges and cache them
220 _invalidatedBounds
.setNull();
222 _movie_root
->add_invalidated_bounds(_invalidatedBounds
, false);
224 #ifdef SHOW_INVALIDATED_BOUNDS_ON_ADVANCE
225 const MovieClip
* r
= getRootMovie();
226 std::cout
<< "frame " << r
->get_current_frame() << ") Invalidated bounds " << _invalidatedBounds
<< std::endl
;
229 // Force full redraw by using a WORLD invalidated ranges
230 InvalidatedRanges ranges
= _invalidatedBounds
;
231 if ( _forceRedraw
) {
232 ranges
.setWorld(); // set to world if asked a full redraw
233 _forceRedraw
= false; // reset to no forced redraw
236 for (TestingRenderers::const_iterator it
=_testingRenderers
.begin(),
237 itE
=_testingRenderers
.end(); it
!= itE
; ++it
) {
238 const TestingRenderer
& rend
= *it
;
239 render(rend
.getRenderer(), ranges
);
242 if ( _testingRenderers
.empty() ) {
243 // Make sure display is called in any case
245 // What we're particularly interested about is
246 // proper computation of invalidated bounds, which
247 // needs clear_invalidated() to be called.
248 // display() will call clear_invalidated() on DisplayObjects
249 // actually modified so we're fine with that.
251 // Directly calling _movie->clear_invalidated() here
252 // also work currently, as invalidating the topmost
253 // movie will force recomputation of all invalidated
254 // bounds. Still, possible future changes might
255 // introduce differences, so better to reproduce
256 // real runs as close as possible, by calling display().
258 _movie_root
->display();
263 MovieTester::advanceClock(unsigned long ms_current
)
265 _clock
.advance(ms_current
);
267 if ( _sound_handler
) {
269 unsigned int ms
= _clock
.elapsed();
271 // We need to fetch as many samples
272 // as needed for a theoretical 44100hz loop.
273 // That is 44100 samples each second.
275 // x = (44100*ms) / 1000
276 unsigned int nSamples
= (441*ms
) / 10;
278 // We double because sound_handler interface takes
279 // "mono" samples... (eh.. would be wise to change)
280 unsigned int toFetch
= nSamples
*2;
282 // Now substract what we fetched already
283 toFetch
-= _samplesFetched
;
285 // And update _samplesFetched..
286 _samplesFetched
+= toFetch
;
288 log_debug("advanceClock(%d) needs to fetch %d samples", ms
, toFetch
);
290 std::int16_t samples
[1024];
292 unsigned int n
= std::min(toFetch
, 1024u);
293 _sound_handler
->fetchSamples((std::int16_t*)&samples
, n
);
300 MovieTester::advance(bool updateClock
)
303 // TODO: cache 'clockAdvance'
304 float fps
= _movie_def
->get_frame_rate();
305 unsigned long clockAdvance
= long(1000/fps
);
306 advanceClock(clockAdvance
);
309 if (_movie_root
->advance()) render();
314 MovieTester::resizeStage(int x
, int y
)
316 _movie_root
->setDimensions(x
, y
);
318 if (_movie_root
->getStageScaleMode() != movie_root::SCALEMODE_NOSCALE
) {
319 // TODO: fix to deal with all scale modes
322 // set new scale value
323 float xscale
= x
/ _movie_def
->get_width_pixels();
324 float yscale
= y
/ _movie_def
->get_height_pixels();
326 if (xscale
< yscale
) yscale
= xscale
;
327 if (yscale
< xscale
) xscale
= yscale
;
329 // Scale for all renderers.
330 for (TestingRenderers::iterator it
=_testingRenderers
.begin(),
331 itE
=_testingRenderers
.end(); it
!= itE
; ++it
) {
332 TestingRenderer
& rend
= *it
;
333 Renderer
* h
= rend
.getRenderer().get();
334 h
->set_scale(xscale
, yscale
);
340 MovieTester::findDisplayItemByName(const MovieClip
& mc
,
341 const std::string
& name
)
343 const DisplayList
& dlist
= mc
.getDisplayList();
344 string_table
& st
= getStringTable(*getObject(&mc
));
345 VM
& vm
= getVM(*getObject(&mc
));
346 return dlist
.getDisplayObjectByName(st
, getURI(vm
, name
), false);
350 MovieTester::findDisplayItemByTarget(const std::string
& tgt
)
352 return _movie_root
->findCharacterByTarget(tgt
);
356 MovieTester::findDisplayItemByDepth(const MovieClip
& mc
,
359 const DisplayList
& dlist
= mc
.getDisplayList();
360 return dlist
.getDisplayObjectAtDepth(depth
);
364 MovieTester::movePointerTo(int x
, int y
)
368 if ( _movie_root
->mouseMoved(x
, y
) ) render();
372 MovieTester::checkPixel(int x
, int y
, unsigned radius
, const rgba
& color
,
373 short unsigned tolerance
, const std::string
& label
, bool expectFailure
) const
375 if ( ! canTestRendering() ) {
376 std::stringstream ss
;
377 ss
<< "exp:" << toShortString(color
) << " ";
378 cout
<< "UNTESTED: NORENDERER: pix:" << x
<< "," << y
<< " exp:" <<
379 toShortString(color
) << " " << label
<< endl
;
382 FuzzyPixel
exp(color
, tolerance
);
384 if ( expectFailure
) X
="X";
386 //std::cout <<"chekPixel(" << color << ") called" << std::endl;
388 for (TestingRenderers::const_iterator it
=_testingRenderers
.begin(),
389 itE
=_testingRenderers
.end(); it
!= itE
; ++it
) {
390 const TestingRenderer
& rend
= *it
;
392 std::stringstream ss
;
393 ss
<< rend
.getName() <<" ";
394 ss
<< "pix:" << x
<< "," << y
<<" ";
398 const Renderer
& handler
= *rend
.getRenderer();
400 if (!getAveragePixel(handler
, obt_col
, x
, y
, radius
) ) {
401 ss
<< " is out of rendering buffer";
402 cout
<< X
<< "FAILED: " << ss
.str() << " (" << label
<< ")" << endl
;
406 // Find minimum tolerance as a function of BPP
408 unsigned short minRendererTolerance
= 1;
409 unsigned int bpp
= handler
.getBitsPerPixel();
411 // UdoG: check_pixel should *always* tolerate at least 2 ^ (8 - bpp/3)
412 minRendererTolerance
= int(std::ceil(exp2(8 - bpp
/3)));
415 //unsigned short tol = std::max(tolerance, minRendererTolerance);
416 unsigned short tol
= tolerance
*minRendererTolerance
;
418 ss
<< "exp:" << toShortString(color
) << " ";
419 ss
<< "obt:" << toShortString(obt_col
) << " ";
422 FuzzyPixel
obt(obt_col
, tol
);
423 // equality operator would use tolerance of most tolerating FuzzyPixel
425 cout
<< X
<< "PASSED: " << ss
.str() << " (" << label
<< ")" << endl
;
427 cout
<< X
<< "FAILED: " << ss
.str() << " (" << label
<< ")" << endl
;
433 MovieTester::pressMouseButton()
435 if ( _movie_root
->mouseClick(true) ) {
441 MovieTester::depressMouseButton()
443 if ( _movie_root
->mouseClick(false) ) {
452 if ( _movie_root
->mouseClick(true) ) ++wantRedraw
;
453 if ( _movie_root
->mouseClick(false) ) ++wantRedraw
;
455 if ( wantRedraw
) render();
459 MovieTester::scrollMouse(int delta
)
461 if (_movie_root
->mouseWheel(delta
)) render();
465 MovieTester::pressKey(key::code code
)
467 if ( _movie_root
->keyEvent(code
, true) ) {
473 MovieTester::releaseKey(key::code code
)
475 if ( _movie_root
->keyEvent(code
, false) ) {
481 MovieTester::isMouseOverMouseEntity()
483 return (_movie_root
->getActiveEntityUnderPointer());
487 MovieTester::usingHandCursor()
489 DisplayObject
* activeEntity
= _movie_root
->getActiveEntityUnderPointer();
490 if ( ! activeEntity
) return false;
492 if ( activeEntity
->isSelectableTextField() ) {
493 return false; // setCursor(CURSOR_INPUT);
494 } else if ( activeEntity
->allowHandCursor() ) {
495 return true; // setCursor(CURSOR_HAND);
497 return false; // setCursor(CURSOR_NORMAL);
501 geometry::SnappingRanges2d
<int>
502 MovieTester::getInvalidatedRanges() const
504 using namespace gnash::geometry
;
506 SnappingRanges2d
<float> ranges
= _invalidatedBounds
;
508 // scale by 1/20 (twips to pixels)
509 ranges
.scale(1.0/20);
511 // Convert to integer range.
512 SnappingRanges2d
<int> pixranges(ranges
);
519 MovieTester::streamingSound() const
521 if (!_sound_handler
.get()) return false;
523 return _sound_handler
->streamingSound();
528 MovieTester::soundsStarted()
530 if ( ! _sound_handler
.get() ) return 0;
532 return _sound_handler
->numSoundsStarted();
537 MovieTester::soundsStopped()
539 if ( ! _sound_handler
.get() ) return 0;
540 return _sound_handler
->numSoundsStopped();
544 MovieTester::initTestingRenderers()
546 std::shared_ptr
<Renderer
> handler
;
548 // TODO: add support for testing multiple renderers
549 // This is tricky as requires changes in the core lib
553 static const char* aggPixelFormats
[] = {
554 "RGB555", "RGB565", "RGBA16",
555 "RGB24", "BGR24", "RGBA32", "BGRA32",
559 for (unsigned i
=0; i
<sizeof(aggPixelFormats
)/sizeof(*aggPixelFormats
); ++i
) {
560 const char* pixelFormat
= aggPixelFormats
[i
];
561 std::string name
= "AGG_" + std::string(pixelFormat
);
563 handler
.reset( create_Renderer_agg(pixelFormat
) );
564 if ( handler
.get() ) {
565 //log_debug("Renderer %s initialized", name.c_str());
566 std::cout
<< "Renderer " << name
<< " initialized" << std::endl
;
567 addTestingRenderer(handler
, name
);
569 std::cout
<< "Renderer " << name
<< " not supported" << std::endl
;
572 #endif // RENDERER_AGG
574 #ifdef RENDERER_CAIRO
576 handler
.reset(renderer::cairo::create_handler());
578 addTestingRenderer(handler
, "Cairo");
581 #ifdef RENDERER_OPENGL
582 // Initialize opengl renderer
583 handler
.reset(renderer::opengl::create_handler(false));
584 addTestingRenderer(handler
, "OpenGL");
589 MovieTester::addTestingRenderer(std::shared_ptr
<Renderer
> h
,
590 const std::string
& name
)
592 if ( ! h
->initTestBuffer(_width
, _height
) ) {
593 std::cout
<< "UNTESTED: render handler " << name
594 << " doesn't support in-memory rendering "
599 // TODO: make the core lib support this
600 if ( ! _testingRenderers
.empty() ) {
601 std::cout
<< "UNTESTED: can't test render handler " << name
602 << " because gnash core lib is unable to support testing of "
603 << "multiple renderers from a single process "
604 << "and we're already testing render handler "
605 << _testingRenderers
.front().getName()
610 _testingRenderers
.push_back(TestingRenderer(h
, name
));
612 // this will be needed till we allow run-time swapping of renderers,
613 // see above UNTESTED message...
614 _runResources
.setRenderer(_testingRenderers
.back().getRenderer());
618 MovieTester::canTestVideo() const
620 if ( ! canTestSound() ) return false;
626 MovieTester::initTestingSoundHandlers()
629 // Currently, SoundHandler can't be constructed
630 // w/out a registered MediaHandler .
631 // Should be fixed though...
632 if (_mediaHandler
.get()) {
633 _sound_handler
.reset(new sound::NullSoundHandler(_mediaHandler
.get()));
635 log_error("No media handler available, "
636 "could not construct sound handler");
642 MovieTester::initTestingMediaHandlers()
645 // TODO: allow selection.
646 _mediaHandler
.reset(media::MediaFactory::instance().get(""));
651 MovieTester::restart()
653 _movie_root
->reset();
654 MovieClip::MovieVariables v
;
655 _movie_root
->init(_movie_def
.get(), v
);
657 // Set _movie before calling ::render
662 MovieTester::getFrameRate() const
664 return _movie_def
->get_frame_rate();
669 /// Returns the average RGB color for a square block on the stage. The
670 /// width and height of the block is defined by "radius" and x/y refer
671 /// to the center of the block. radius==1 equals getPixel() and radius==0
672 /// is illegal. For even "radius" values, the center point is not exactly
674 /// The function returns false when at least one pixel of the block was
675 /// outside the main frame buffer. In that case the value in color_return
677 bool getAveragePixel(const Renderer
& rh
, rgba
& color_return
, int x
, int y
,
683 if (radius
==1) return rh
.getPixel(color_return
, x
, y
);
685 unsigned int r
=0, g
=0, b
=0, a
=0;
695 for (int yp
=y
; yp
<ye
; yp
++)
696 for (int xp
=x
; xp
<xe
; xp
++) {
697 if (!rh
.getPixel(pixel
, xp
, yp
))
706 int pcount
= radius
*radius
;
707 color_return
.m_r
= r
/ pcount
;
708 color_return
.m_g
= g
/ pcount
;
709 color_return
.m_b
= b
/ pcount
;
710 color_return
.m_a
= a
/ pcount
;