1 //
2 //   Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2012
3 //   Free Software Foundation, Inc
4 //
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.
9 //
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.
14 //
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
18 
19 
20 #ifdef HAVE_CONFIG_H
21 #include "gnashconfig.h"
22 #endif
23 
24 #include "Renderer_ogl.h"
25 
26 #include <boost/utility.hpp>
27 #include <iterator>
28 #include <functional>
29 #include <list>
30 #include <cstring>
31 #include <cmath>
32 #include <memory>
33 
34 #include "swf/ShapeRecord.h"
35 #include "GnashEnums.h"
36 #include "RGBA.h"
37 #include "GnashImage.h"
38 #include "GnashTexture.h"
39 #include "GnashNumeric.h"
40 #include "log.h"
41 #include "utility.h"
42 #include "Range2d.h"
43 #include "SWFCxForm.h"
44 #include "FillStyle.h"
45 #include "Transform.h"
46 
47 #if defined(_WIN32) || defined(WIN32)
48 #  include <Windows.h>
49 #endif
50 
51 #ifdef HAVE_VA_VA_GLX_H
52 #  include "GnashVaapiImage.h"
53 #  include "GnashVaapiTexture.h"
54 #endif
55 
56 // Defined to 1 to disable (slow) anti-aliasing with the accumulation buffer
57 #define NO_ANTIALIASING 1
58 
59 /// \file Renderer_ogl.cpp
60 /// \brief The OpenGL renderer and related code.
61 ///
62 /// So how does this thing work?
63 ///
64 /// 1. Flash graphics are fundamentally incompatible with OpenGL. Flash shapes
65 ///    are defined by an arbitrary number of paths, which in turn are formed
66 ///    from an arbitrary number of edges. An edge describes a quadratic Bezier
67 ///    curve. A shape is defined by at least one path enclosing a space -- this
68 ///    space is the shape. Every path may have a left and/or right fill style,
69 ///    determining (if the path is thought of as a vector) which side(s) of
70 ///    the path is to be filled.
71 ///    OpenGL, on the other hand, understands only triangles, lines and points.
72 ///    We must break Flash graphics down into primitives that OpenGL can
73 ///    understand before we can render them.
74 ///
75 /// 2. First, we must ensure that OpenGL receives only closed shapes with a
76 ///    single fill style. Paths with two fill styles are duplicated. Then,
77 ///    shapes with
78 ///    a left fill style are reversed and the fill style is moved to the right.
79 ///    The shapes must be closed, so the tesselator can parse them; this
80 ///    involves a fun game of connect-the-dots. Fortunately, Flash guarantees
81 ///    that shapes are always closed and that they're never self-intersecting.
82 ///
83 /// 3. Now that we have a consistent set of shapes, we can interpolate the
84 ///    Bezier curves of which each path is made of. OpenGL can do this for us,
85 ///    using evaluators, but we currently do it ourselves.
86 ///
87 /// 4. Being the proud owners of a brand new set of interpolated coordinates,
88 ///    we can feed the coordinates into the GLU tesselator. The tesselator will
89 ///    break our complex (but never self-intersecting) polygon into OpenGL-
90 ///    grokkable primitives (say, a triangle fan or strip). We let the
91 ///    tesselator worry about that part. When the tesselator is finished, all
92 ///    we have to do is set up the fill style and draw the primitives given to
93 ///    us. The GLU tesselator will take care of shapes having inner boundaries
94 ///    (for example a donut shape). This makes life a LOT easier!
95 
96 // TODO:
97 // - Profiling!
98 // - Optimize code:
99 // * Use display lists
100 // * Use better suited standard containers
101 // * convert to double at a later stage (oglVertex)
102 // * keep data for less time
103 // * implement hardware accelerated gradients. Most likely this will require
104 //   the use of fragment shader language.
105 
106 // * The "Programming Tips" in the OpenGL "red book" discusses a coordinate system
107 // that would give "exact two-dimensional rasterization". AGG uses a similar
108 // system; consider the benefits and drawbacks of switching.
109 
110 namespace gnash {
111 
112 namespace renderer {
113 
114 namespace opengl {
115 
116 namespace {
117     const CachedBitmap* createGradientBitmap(const GradientFill& gf,
118             Renderer& renderer);
119 
120 class bitmap_info_ogl : public CachedBitmap
121 {
122 public:
123 
124     /// Set line and fill styles for mesh & line_strip rendering.
125     enum bitmap_wrap_mode
126     {
127       WRAP_REPEAT,
128       WRAP_CLAMP
129     };
130 
131     bitmap_info_ogl(std::unique_ptr<image::GnashImage> image, GLenum pixelformat,
132                     bool ogl_accessible);
133 
134     ~bitmap_info_ogl();
135 
136     /// TODO: implement this meaningfully.
dispose()137     virtual void dispose() {
138         _disposed = true;
139     }
140 
disposed() const141     virtual bool disposed() const {
142         return _disposed;
143     }
144 
image()145     virtual image::GnashImage& image() {
146         if (_cache.get()) return *_cache;
147         switch (_pixel_format) {
148             case GL_RGB:
149                 _cache.reset(new image::ImageRGB(_orig_width, _orig_height));
150                 break;
151             case GL_RGBA:
152                 _cache.reset(new image::ImageRGBA(_orig_width, _orig_height));
153                 break;
154             default:
155                 std::abort();
156         }
157         std::fill(_cache->begin(), _cache->end(), 0xff);
158         return *_cache;
159     }
160 
161     void apply(const gnash::SWFMatrix& bitmap_matrix,
162                bitmap_wrap_mode wrap_mode) const;
163 private:
164     inline bool ogl_accessible() const;
165     void setup() const;
166     void upload(std::uint8_t* data, size_t width, size_t height) const;
167 
168     mutable std::unique_ptr<image::GnashImage> _img;
169     mutable std::unique_ptr<image::GnashImage> _cache;
170     GLenum _pixel_format;
171     GLenum _ogl_img_type;
172     mutable bool _ogl_accessible;
173     mutable GLuint _texture_id;
174     size_t _orig_width;
175     size_t _orig_height;
176     bool _disposed;
177 };
178 
179 /// Style handler
180 //
181 /// Transfer FillStyles to the ogl renderer.
182 struct StyleHandler : boost::static_visitor<>
183 {
StyleHandlergnash::renderer::opengl::__anonb250f7cf0111::StyleHandler184     StyleHandler(const SWFCxForm& c, Renderer& r)
185         :
186         _cx(c),
187         _renderer(r)
188     {}
189 
operator ()gnash::renderer::opengl::__anonb250f7cf0111::StyleHandler190     void operator()(const GradientFill& f) const {
191 
192         const bitmap_info_ogl* binfo = static_cast<const bitmap_info_ogl*>(
193             createGradientBitmap(f, _renderer));
194 
195         SWFMatrix m = f.matrix();
196         binfo->apply(m, bitmap_info_ogl::WRAP_CLAMP);
197     }
198 
operator ()gnash::renderer::opengl::__anonb250f7cf0111::StyleHandler199     void operator()(const SolidFill& f) const {
200         const rgba c = _cx.transform(f.color());
201         glColor4ub(c.m_r, c.m_g, c.m_b, c.m_a);
202     }
203 
operator ()gnash::renderer::opengl::__anonb250f7cf0111::StyleHandler204     void operator()(const BitmapFill& f) const {
205         const bitmap_info_ogl* binfo = static_cast<const bitmap_info_ogl*>(
206                    f.bitmap());
207         binfo->apply(f.matrix(), f.type() == BitmapFill::TILED ?
208                 bitmap_info_ogl::WRAP_REPEAT : bitmap_info_ogl::WRAP_CLAMP);
209     }
210 
211 private:
212     const SWFCxForm& _cx;
213     Renderer& _renderer;
214 };
215 
216 }
217 
218 
219 #ifdef OSMESA_TESTING
220 
221 class OSRenderMesa : public boost::noncopyable
222 {
223 public:
OSRenderMesa(size_t width,size_t height)224   OSRenderMesa(size_t width, size_t height)
225     : _width(width),
226       _height(height),
227       _buffer(new std::uint8_t[width * height * 3]),
228 #if OSMESA_MAJOR_VERSION * 100 + OSMESA_MINOR_VERSION >= 305
229       _context(OSMesaCreateContextExt(OSMESA_RGB, 0, 2, 0, NULL))
230 #else
231       _context(OSMesaCreateContext(OSMESA_RGB, NULL))
232 #endif
233    {
234     if (!_context) {
235         log_error(_("OSMesaCreateContext failed!"));
236         return; // FIXME: throw an exception?
237     }
238 
239     if (!OSMesaMakeCurrent(_context, _buffer.get(), GL_UNSIGNED_BYTE, width,
240                            height)) {
241         log_error(_("OSMesaMakeCurrent failed!"));
242       return;
243     }
244 
245     // FIXME: is there any reason to do this?
246     OSMesaColorClamp(GL_TRUE);
247 
248     log_debug(_("OSMesa handle successfully created. with width %d and height %d."),
249               width, height);
250   }
251 
~OSRenderMesa()252   ~OSRenderMesa()
253   {
254     if (!_context) {
255       return;
256     }
257     OSMesaDestroyContext(_context);
258   }
259 
getPixel(rgba & color_out,int x,int y) const260   bool getPixel(rgba& color_out, int x, int y) const
261   {
262     glFinish(); // Force pending commands out (and wait until they're done).
263 
264     if (x > _width || y > _height) {
265       return false;
266     }
267 
268     ptrdiff_t offset = (_height - y) * (_width * 3) + x * 3;
269     color_out = rgba(_buffer[offset], _buffer[offset+1], _buffer[offset+2],
270             255);
271 
272     return true;
273   }
274 
getBitsPerPixel() const275   unsigned int getBitsPerPixel() const
276   {
277     return 24;
278   }
279 
280 private:
281   size_t _width;
282   size_t _height;
283   std::unique_ptr<std::uint8_t[]> _buffer;
284   OSMesaContext _context;
285 };
286 
287 #endif // OSMESA_TESTING
288 
289 
290 typedef std::vector<Path> PathVec;
291 
292 class oglScopeEnable : public boost::noncopyable
293 {
294 public:
oglScopeEnable(GLenum capability)295   oglScopeEnable(GLenum capability)
296     :_cap(capability)
297   {
298     glEnable(_cap);
299   }
300 
~oglScopeEnable()301   ~oglScopeEnable()
302   {
303     glDisable(_cap);
304   }
305 private:
306   GLenum _cap;
307 };
308 
309 class oglScopeMatrix : public boost::noncopyable
310 {
311 public:
oglScopeMatrix(const SWFMatrix & m)312   oglScopeMatrix(const SWFMatrix& m)
313   {
314     glPushMatrix();
315 
316     // Multiply (AKA "append") the new SWFMatrix with the current OpenGL one.
317     float mat[16];
318     memset(&mat[0], 0, sizeof(mat));
319     mat[0] = m.a() / 65536.0f;
320     mat[1] = m.b() / 65536.0f;
321     mat[4] = m.c() / 65536.0f;
322     mat[5] = m.d() / 65536.0f;
323     mat[10] = 1;
324     mat[12] = m.tx();
325     mat[13] = m.ty();
326     mat[15] = 1;
327     glMultMatrixf(mat);
328   }
329 
~oglScopeMatrix()330   ~oglScopeMatrix()
331   {
332     glPopMatrix();
333   }
334 };
335 
336 static void
check_error()337 check_error()
338 {
339   GLenum error = glGetError();
340 
341   if (error == GL_NO_ERROR) {
342     return;
343   }
344 
345   log_error(_("OpenGL: %s"), gluErrorString(error));
346 }
347 
348 /// @ret A point in the middle of points a and b, that is, the middle of a line
349 ///      drawn from a to b.
middle(const point & a,const point & b)350 point middle(const point& a, const point& b)
351 {
352   return point(0.5 * (a.x + b.x), 0.5 * (a.y + b.y));
353 }
354 
355 namespace {
356 
357 class
358 PointSerializer
359 {
360 public:
PointSerializer(std::vector<std::int16_t> & dest)361     PointSerializer(std::vector<std::int16_t>& dest)
362         :
363         _dest(dest)
364     {}
365 
operator ()(const point & p)366     void operator()(const point& p)
367     {
368         _dest.push_back(p.x);
369         _dest.push_back(p.y);
370     }
371 private:
372     std::vector<std::int16_t>& _dest;
373 };
374 
375 }
376 
377 // Unfortunately, we can't use OpenGL as-is to interpolate the curve for us. It
378 // is legal for Flash coordinates to be outside of the viewport, which will
379 // be ignored by OpenGL's feedback mode. Feedback mode
380 // will simply not return those coordinates, which will destroy a shape which
381 // is partly off-screen.
382 
383 // So, if we transform the coordinates to be always positive, it should be
384 // possible to use evaluators. This then presents another problem: what if
385 // one coordinate is negative and the other is not, and what if both of
386 // those are outside of the viewport?
387 
388 // one solution would be to use feedback mode unless one of the coordinates
389 // is outside of the viewport.
trace_curve(const point & startP,const point & controlP,const point & endP,std::vector<oglVertex> & coords)390 void trace_curve(const point& startP, const point& controlP,
391                   const point& endP, std::vector<oglVertex>& coords)
392 {
393   // Midpoint on line between two endpoints.
394   point mid = middle(startP, endP);
395 
396   // Midpoint on the curve.
397   point q = middle(mid, controlP);
398 
399   if (mid.distance(q) < 0.1 /*error tolerance*/) {
400     coords.push_back(oglVertex(endP));
401   } else {
402     // Error is too large; subdivide.
403     trace_curve(startP, middle(startP, controlP), q, coords);
404 
405     trace_curve(q, middle(controlP, endP), endP, coords);
406   }
407 }
408 
interpolate(const std::vector<Edge> & edges,const float & anchor_x,const float & anchor_y)409 std::vector<oglVertex> interpolate(const std::vector<Edge>& edges,
410         const float& anchor_x, const float& anchor_y)
411 {
412   point anchor(anchor_x, anchor_y);
413 
414   std::vector<oglVertex> shape_points;
415   shape_points.push_back(oglVertex(anchor));
416 
417   for (const Edge& the_edge : edges) {
418 
419       point target(the_edge.ap.x, the_edge.ap.y);
420 
421       if (the_edge.straight()) {
422         shape_points.push_back(oglVertex(target));
423       } else {
424         point control(the_edge.cp.x, the_edge.cp.y);
425 
426         trace_curve(anchor, control, target, shape_points);
427       }
428       anchor = target;
429   }
430 
431   return shape_points;
432 }
433 
434 // FIXME: OSX doesn't like void (*)().
Tesselator()435 Tesselator::Tesselator()
436 : _tessobj(gluNewTess())
437 {
438   gluTessCallback(_tessobj, GLU_TESS_ERROR,
439                   reinterpret_cast<GLUCALLBACKTYPE>(Tesselator::error));
440   gluTessCallback(_tessobj, GLU_TESS_COMBINE_DATA,
441                   reinterpret_cast<GLUCALLBACKTYPE>(Tesselator::combine));
442 
443   gluTessCallback(_tessobj, GLU_TESS_BEGIN,
444                   reinterpret_cast<GLUCALLBACKTYPE>(glBegin));
445   gluTessCallback(_tessobj, GLU_TESS_END,
446                   reinterpret_cast<GLUCALLBACKTYPE>(glEnd));
447 
448   gluTessCallback(_tessobj, GLU_TESS_VERTEX,
449                   reinterpret_cast<GLUCALLBACKTYPE>(glVertex3dv));
450 
451 #if 0
452   // for testing, draw only the outside of shapes.
453   gluTessProperty(_tessobj, GLU_TESS_BOUNDARY_ONLY, GL_TRUE);
454 #endif
455 
456   // all coordinates lie in the x-y plane
457   // this speeds up tesselation
458   gluTessNormal(_tessobj, 0.0, 0.0, 1.0);
459 }
460 
~Tesselator()461 Tesselator::~Tesselator()
462 {
463   gluDeleteTess(_tessobj);
464 }
465 
466 void
beginPolygon()467 Tesselator::beginPolygon()
468 {
469   gluTessBeginPolygon(_tessobj, this);
470 }
471 
beginContour()472 void Tesselator::beginContour()
473 {
474   gluTessBeginContour(_tessobj);
475 }
476 
477 void
feed(std::vector<oglVertex> & vertices)478 Tesselator::feed(std::vector<oglVertex>& vertices)
479 {
480   for (std::vector<oglVertex>::const_iterator it = vertices.begin(), end = vertices.end();
481         it != end; ++it) {
482     GLdouble* vertex = const_cast<GLdouble*>(&(*it)._x);
483     gluTessVertex(_tessobj, vertex, vertex);
484   }
485 }
486 
487 void
endContour()488 Tesselator::endContour()
489 {
490   gluTessEndContour(_tessobj);
491 }
492 
493 void
tesselate()494 Tesselator::tesselate()
495 {
496   gluTessEndPolygon(_tessobj);
497 
498   for (GLdouble* vertex: _vertices) {
499     delete [] vertex;
500   }
501 
502   _vertices.clear();
503 }
504 
505 void
rememberVertex(GLdouble * v)506 Tesselator::rememberVertex(GLdouble* v)
507 {
508   _vertices.push_back(v);
509 }
510 
511 // static
512 void
error(GLenum error)513 Tesselator::error(GLenum error)
514 {
515     log_error(_("GLU: %s"), gluErrorString(error));
516 }
517 
518 // static
519 void
combine(GLdouble coords[3],void **,GLfloat *,void ** outData,void * userdata)520 Tesselator::combine(GLdouble coords [3], void **/*vertex_data[4] */,
521 		    GLfloat * /* weight[4] */, void **outData, void* userdata)
522 {
523   Tesselator* tess = (Tesselator*)userdata;
524   assert(tess);
525 
526   GLdouble* v = new GLdouble[3];
527   v[0] = coords[0];
528   v[1] = coords[1];
529   v[2] = coords[2];
530 
531   *outData = v;
532 
533   tess->rememberVertex(v);
534 }
535 
isEven(const size_t & n)536 bool isEven(const size_t& n)
537 {
538   return n % 2 == 0;
539 }
540 
bitmap_info_ogl(std::unique_ptr<image::GnashImage> image,GLenum pixelformat,bool ogl_accessible)541 bitmap_info_ogl::bitmap_info_ogl(std::unique_ptr<image::GnashImage> image,
542         GLenum pixelformat, bool ogl_accessible)
543 :
544   _img(image.release()),
545   _pixel_format(pixelformat),
546   _ogl_img_type(_img->height() == 1 ? GL_TEXTURE_1D : GL_TEXTURE_2D),
547   _ogl_accessible(ogl_accessible),
548   _texture_id(0),
549   _orig_width(_img->width()),
550   _orig_height(_img->height()),
551   _disposed(false)
552 {
553   if (!_ogl_accessible) {
554     return;
555   }
556 
557   setup();
558 }
559 
~bitmap_info_ogl()560 bitmap_info_ogl::~bitmap_info_ogl()
561 {
562   glDeleteTextures(1, &_texture_id);
563   glDisable(_ogl_img_type);
564 }
565 
566 void
setup() const567 bitmap_info_ogl::setup() const
568 {
569     oglScopeEnable enabler(_ogl_img_type);
570 
571     glGenTextures(1, &_texture_id);
572     glBindTexture(_ogl_img_type, _texture_id);
573 
574     bool resize = false;
575     if (_img->height() == 1) {
576         if (!isEven(_img->width())) {
577             resize = true;
578         }
579     }
580     else {
581         if (!isEven(_img->width()) || !isEven(_img->height())) {
582             resize = true;
583         }
584     }
585 
586     if (!resize) {
587         upload(_img->begin(), _img->width(), _img->height());
588     }
589     else {
590 
591         size_t w = 1; while (w < _img->width()) { w <<= 1; }
592         size_t h = 1;
593         if (_img->height() != 1) {
594             while (h < _img->height()) { h <<= 1; }
595         }
596 
597         std::unique_ptr<std::uint8_t[]> resized_data(
598                 new std::uint8_t[w * h * _img->channels()]);
599         // Q: Would mipmapping these textures aid in performance?
600 
601         GLint rv = gluScaleImage(_pixel_format, _img->width(),
602                 _img->height(), GL_UNSIGNED_BYTE, _img->begin(), w, h,
603                 GL_UNSIGNED_BYTE, resized_data.get());
604         if (rv != 0) {
605             Tesselator::error(rv);
606             assert(0);
607         }
608 
609         upload(resized_data.get(), w, h);
610     }
611 
612     _img.reset();
613 }
614 
615 void
upload(std::uint8_t * data,size_t width,size_t height) const616 bitmap_info_ogl::upload(std::uint8_t* data, size_t width, size_t height) const
617 {
618   glTexParameteri(_ogl_img_type, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
619 
620   // FIXME: confirm that OpenGL can handle this image
621 
622   if (_ogl_img_type == GL_TEXTURE_1D) {
623     glTexImage1D(GL_TEXTURE_1D, 0, _pixel_format, width,
624                   0, _pixel_format, GL_UNSIGNED_BYTE, data);
625 
626   } else {
627     glTexImage2D(_ogl_img_type, 0, _pixel_format, width, height,
628                   0, _pixel_format, GL_UNSIGNED_BYTE, data);
629 
630   }
631 }
632 
633 void
apply(const gnash::SWFMatrix & bitmap_matrix,bitmap_wrap_mode wrap_mode) const634 bitmap_info_ogl::apply(const gnash::SWFMatrix& bitmap_matrix,
635                        bitmap_wrap_mode wrap_mode) const
636 {
637     // TODO: use the altered data. Currently it doesn't display anything,
638     // so I don't feel obliged to implement this!
639 
640   glEnable(_ogl_img_type);
641 
642   glEnable(GL_TEXTURE_GEN_S);
643   glEnable(GL_TEXTURE_GEN_T);
644 
645   if (!_ogl_accessible) {
646     // renderer context wasn't available when this class was instantiated.
647     _ogl_accessible=true;
648     setup();
649   }
650 
651   glEnable(_ogl_img_type);
652   glEnable(GL_TEXTURE_GEN_S);
653   glEnable(GL_TEXTURE_GEN_T);
654 
655   glBindTexture(_ogl_img_type, _texture_id);
656 
657   glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
658 
659   if (wrap_mode == WRAP_CLAMP) {
660     glTexParameteri(_ogl_img_type, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
661     glTexParameteri(_ogl_img_type, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
662   } else {
663     glTexParameteri(_ogl_img_type, GL_TEXTURE_WRAP_S, GL_REPEAT);
664     glTexParameteri(_ogl_img_type, GL_TEXTURE_WRAP_T, GL_REPEAT);
665   }
666 
667   // Set up the bitmap SWFMatrix for texgen.
668 
669   float inv_width = 1.0f / _orig_width;
670   float inv_height = 1.0f / _orig_height;
671 
672   const gnash::SWFMatrix& m = bitmap_matrix;
673   glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
674   float p[4] = { 0, 0, 0, 0 };
675   p[0] = m.a() / 65536.0f * inv_width;
676   p[1] = m.c() / 65536.0f * inv_width;
677   p[3] = m.tx() * inv_width;
678   glTexGenfv(GL_S, GL_OBJECT_PLANE, p);
679 
680   glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
681   p[0] = m.b() / 65536.0f * inv_height;
682   p[1] = m.c() / 65536.0f * inv_height;
683   p[3] = m.ty() * inv_height;
684   glTexGenfv(GL_T, GL_OBJECT_PLANE, p);
685 
686 }
687 
688 template<typename C, typename T, typename R, typename A>
689 void
for_each(C & container,R (T::* pmf)(const A &),const A & arg)690 for_each(C& container, R (T::*pmf)(const A&),const A& arg)
691 {
692     std::for_each(container.begin(), container.end(),
693                   std::bind(pmf, std::placeholders::_1, std::ref(arg)));
694 }
695 
696 
697 
698 class DSOEXPORT Renderer_ogl : public Renderer
699 {
700 public:
Renderer_ogl()701   Renderer_ogl()
702     : _xscale(1.0),
703       _yscale(1.0),
704       _width(0.0),
705       _height(0.0),
706       _drawing_mask(false)
707   {
708   }
709 
description() const710   std::string description() const { return "OpenGL"; }
711 
init()712   void init()
713   {
714     // Turn on alpha blending.
715     // FIXME: do this when it's actually used?
716     glEnable(GL_BLEND);
717     // This blend operation is best for rendering antialiased points and lines in
718     // no particular order.
719     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
720 
721     // Turn on line smoothing.  Antialiased lines can be used to
722     // smooth the outsides of shapes.
723     glEnable(GL_LINE_SMOOTH);
724 
725     // Use fastest line smoothing since additional anti-aliasing will happen later.
726     glHint(GL_LINE_SMOOTH_HINT, GL_FASTEST); // GL_NICEST, GL_FASTEST, GL_DONT_CARE
727 
728     glMatrixMode(GL_PROJECTION);
729 
730     float oversize = 1.0;
731 
732     // Flip the image, since (0,0) by default in OpenGL is the bottom left.
733     gluOrtho2D(-oversize, oversize, oversize, -oversize);
734     // Restore the SWFMatrix mode to the default.
735     glMatrixMode(GL_MODELVIEW);
736     glLoadIdentity();
737 
738     glShadeModel(GL_FLAT);
739 
740   }
741 
~Renderer_ogl()742   ~Renderer_ogl()
743   {
744   }
745 
746   inline bool
ogl_accessible() const747   ogl_accessible() const
748   {
749 #if defined(_WIN32) || defined(WIN32)
750     return wglGetCurrentContext();
751 #elif defined(__APPLE_CC__)
752     return aglGetCurrentContext();
753 #else
754 # ifdef OSMESA_TESTING
755     if (_offscreen.get()) {
756       return OSMesaGetCurrentContext();
757     }
758 # endif
759     return glXGetCurrentContext();
760 #endif
761   }
762 
createCachedBitmap(std::unique_ptr<image::GnashImage> im)763   virtual CachedBitmap* createCachedBitmap(std::unique_ptr<image::GnashImage> im)
764   {
765       switch (im->type()) {
766           case image::TYPE_RGB:
767           {
768               std::unique_ptr<image::GnashImage> rgba(
769                       new image::ImageRGBA(im->width(), im->height()));
770 
771               image::GnashImage::iterator it = rgba->begin();
772               for (size_t i = 0; i < im->size(); ++i) {
773                   *it++ = *(im->begin() + i);
774                   if (!(i % 3)) *it++ = 0xff;
775               }
776               im = std::move(rgba);
777           }
778           case image::TYPE_RGBA:
779                 return new bitmap_info_ogl(std::move(im), GL_RGBA, ogl_accessible());
780           default:
781                 std::abort();
782       }
783   }
784 
getCachedTexture(image::GnashImage * frame)785   std::shared_ptr<GnashTexture> getCachedTexture(image::GnashImage *frame)
786   {
787       std::shared_ptr<GnashTexture> texture;
788       GnashTextureFormat frameFormat(frame->type());
789       unsigned int frameFlags;
790 
791       switch (frame->location()) {
792       case image::GNASH_IMAGE_CPU:
793           frameFlags = 0;
794           break;
795 #ifdef HAVE_VA_VA_GLX_H
796       case image::GNASH_IMAGE_GPU:
797           frameFlags = GNASH_TEXTURE_VAAPI;
798           break;
799 #endif
800       default:
801           assert(0);
802           return texture;
803       }
804 
805       // Look for a texture with the same dimensions and type
806       std::list< std::shared_ptr<GnashTexture> >::iterator it;
807       for (it = _cached_textures.begin(); it != _cached_textures.end(); ++it) {
808           if ((*it)->width() == frame->width() &&
809               (*it)->height() == frame->height() &&
810               (*it)->internal_format() == frameFormat.internal_format() &&
811               (*it)->format() == frameFormat.format() &&
812               (*it)->flags() == frameFlags)
813               break;
814       }
815 
816       // Found texture and remove it from cache. It will be pushed
817       // back into the cache when rendering is done, in end_display()
818       if (it != _cached_textures.end()) {
819           texture = *it;
820           _cached_textures.erase(it);
821       }
822 
823       // Otherwise, create one and empty cache because they may no
824       // longer be referenced
825       else {
826           _cached_textures.clear();
827 
828           switch (frame->location()) {
829           case image::GNASH_IMAGE_CPU:
830               texture.reset(new GnashTexture(frame->width(),
831                                              frame->height(),
832                                              frame->type()));
833               break;
834           case image::GNASH_IMAGE_GPU:
835               // This case should never be reached if vaapi is not
836               // enabled; but has to be handled to keep the compiler
837               // happy.
838 #ifdef HAVE_VA_VA_GLX_H
839               texture.reset(new GnashVaapiTexture(frame->width(),
840                                                   frame->height(),
841                                                   frame->type()));
842 #endif
843               break;
844           }
845       }
846 
847       assert(texture->width() == frame->width());
848       assert(texture->height() == frame->height());
849       assert(texture->internal_format() == frameFormat.internal_format());
850       assert(texture->format() == frameFormat.format());
851       assert(texture->flags() == frameFlags);
852       return texture;
853   }
854 
855   // Since we store drawing operations in display lists, we take special care
856   // to store video frame operations in their own display list, lest they be
857   // anti-aliased with the rest of the drawing. Since display lists cannot be
858   // concatenated this means we'll add up with several display lists for normal
859   // drawing operations.
drawVideoFrame(image::GnashImage * frame,const Transform & xform,const SWFRect * bounds,bool)860   virtual void drawVideoFrame(image::GnashImage* frame, const Transform& xform,
861           const SWFRect* bounds, bool /*smooth*/)
862   {
863     GLint index;
864 
865     glGetIntegerv(GL_LIST_INDEX, &index);
866 
867     if (index >= 255) {
868         log_error(_("An insane number of video frames have been requested to be drawn. Further video frames will be ignored."));
869       return;
870     }
871 
872     glEndList();
873 
874     std::shared_ptr<GnashTexture> texture = getCachedTexture(frame);
875     if (!texture.get())
876         return;
877 
878     switch (frame->location()) {
879     case image::GNASH_IMAGE_CPU:
880         texture->update(frame->begin());
881         break;
882 #ifdef HAVE_VA_VA_GLX_H
883     case image::GNASH_IMAGE_GPU:
884         dynamic_cast<GnashVaapiTexture *>(texture.get())->update(dynamic_cast<GnashVaapiImage *>(frame)->surface());
885         break;
886 #endif
887     default:
888         assert(0);
889         return;
890     }
891     _render_textures.push_back(texture);
892 
893     glGenLists(2);
894 
895     ++index;
896 
897     glNewList(index, GL_COMPILE);
898     _render_indices.push_back(index);
899 
900     reallyDrawVideoFrame(texture, &xform.matrix, bounds);
901 
902     glEndList();
903 
904     ++index;
905 
906     glNewList(index, GL_COMPILE);
907     _render_indices.push_back(index);
908   }
909 
910 private:
reallyDrawVideoFrame(std::shared_ptr<GnashTexture> texture,const SWFMatrix * m,const SWFRect * bounds)911   void reallyDrawVideoFrame(std::shared_ptr<GnashTexture> texture, const SWFMatrix* m, const SWFRect* bounds)
912   {
913     glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT);
914     glPushMatrix();
915 
916     gnash::point l, u;
917     m->transform(&l, point(bounds->get_x_min(), bounds->get_y_min()));
918     m->transform(&u, point(bounds->get_x_max(), bounds->get_y_max()));
919     const unsigned int w = u.x - l.x;
920     const unsigned int h = u.y - l.y;
921 
922     texture->bind();
923     glTranslatef(l.x, l.y, 0.0f);
924     glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
925     glBegin(GL_QUADS);
926     {
927         glTexCoord2f(0.0f, 0.0f); glVertex2i(0, 0);
928         glTexCoord2f(0.0f, 1.0f); glVertex2i(0, h);
929         glTexCoord2f(1.0f, 1.0f); glVertex2i(w, h);
930         glTexCoord2f(1.0f, 0.0f); glVertex2i(w, 0);
931     }
932     glEnd();
933     texture->release();
934 
935     glPopMatrix();
936     glPopAttrib();
937   }
938 
939   // FIXME
940   geometry::Range2d<int>
world_to_pixel(const SWFRect & worldbounds) const941   world_to_pixel(const SWFRect& worldbounds) const
942   {
943     // TODO: verify this is correct
944     geometry::Range2d<int> ret(worldbounds.getRange());
945     ret.scale(1.0/20.0); // twips to pixels
946     return ret;
947   }
948 
949   // FIXME
950   point
pixel_to_world(int x,int y) const951   pixel_to_world(int x, int y) const
952   {
953     // TODO: verify this is correct
954     return point(pixelsToTwips(x), pixelsToTwips(y));
955   }
956 
957 public:
958 
startInternalRender(image::GnashImage &)959     virtual Renderer* startInternalRender(image::GnashImage& /*im*/) {
960         return nullptr;
961     }
962 
endInternalRender()963     virtual void endInternalRender() {}
964 
begin_display(const rgba & bg_color,int viewport_width,int viewport_height,float x0,float x1,float y0,float y1)965   virtual void  begin_display(
966     const rgba& bg_color,
967     int viewport_width, int viewport_height,
968     float x0, float x1, float y0, float y1)
969   {
970     glViewport(0, 0, viewport_width, viewport_height);
971     glLoadIdentity();
972 
973     gluOrtho2D(x0, x1, y0, y1);
974 
975     _width  = std::fabs(x1 - x0);
976     _height = std::fabs(y1 - y0);
977 
978     glScalef(static_cast<float>(twipsToPixels(_width)) /
979     static_cast<float>(viewport_width),
980     static_cast<float>(twipsToPixels(_height)) /
981     static_cast<float>(viewport_height), 1.0f);
982 
983     // Setup the clear color. The actual clearing will happen in end_display.
984     if (bg_color.m_a) {
985       glClearColor(bg_color.m_r / 255.0, bg_color.m_g / 255.0, bg_color.m_b / 255.0,
986                    bg_color.m_a / 255.0);
987     } else {
988       glClearColor(1.0, 1.0, 1.0, 1.0);
989     }
990 
991     glGenLists(1);
992 
993     // Start a new display list which will contain almost everything we draw.
994     glNewList(1, GL_COMPILE);
995     _render_indices.push_back(1);
996 
997   }
998 
999   virtual void
end_display()1000   end_display()
1001   {
1002     glEndList();
1003 
1004 #if NO_ANTIALIASING
1005     // Don't use accumulation buffer based anti-aliasing
1006     glClear(GL_COLOR_BUFFER_BIT);
1007     glCallLists(_render_indices.size(), GL_UNSIGNED_BYTE,
1008                 &_render_indices.front());
1009 #else
1010     // This is a table of randomly generated numbers between -0.5 and 0.5.
1011     struct {
1012       GLfloat x;
1013       GLfloat y;
1014     } points [] = {
1015       {      	-0.448823,  	 0.078771 },
1016       {     	-0.430852, 	0.240592 },
1017       {     	-0.208887, 	-0.492535 },
1018       {     	-0.061232, 	-1.109432 },
1019       {     	-0.034984, 	-0.247317 },
1020       {     	-0.367119, 	-0.440909 },
1021       {     	0.047688, 	0.315757 },
1022       {     	0.434600, 	-0.204068 }
1023     };
1024 
1025     int numPoints = 8;
1026 
1027     GLint viewport[4];
1028     glGetIntegerv (GL_VIEWPORT, viewport);
1029     const GLint& viewport_width = viewport[2],
1030                  viewport_height = viewport[3];
1031 
1032     glClearAccum(0.0, 0.0, 0.0, 0.0);
1033 
1034     glClear(GL_ACCUM_BUFFER_BIT);
1035 
1036     for (int i = 0; i < numPoints; ++i) {
1037 
1038       glClear(GL_COLOR_BUFFER_BIT);
1039       glPushMatrix ();
1040 
1041       glTranslatef (points[i].x * _width / viewport_width,
1042                     points[i].y * _height / viewport_height, 0.0);
1043 
1044       glCallLists(_render_indices.size(), GL_UNSIGNED_BYTE,
1045                   &_render_indices.front());
1046 
1047       glPopMatrix ();
1048 
1049       glAccum(GL_ACCUM, 1.0/numPoints);
1050     }
1051 
1052     glAccum (GL_RETURN, 1.0);
1053 #endif
1054 
1055   #if 0
1056     GLint box[4];
1057     glGetIntegerv(GL_SCISSOR_BOX, box);
1058 
1059     int x = pixelsToTwips(box[0]),
1060         y = pixelsToTwips(box[1]),
1061         w = pixelsToTwips(box[2]),
1062         h = pixelsToTwips(box[3]);
1063 
1064     glRectd(x, y - h, x + w, y + h);
1065   #endif
1066 
1067     glDeleteLists(1, _render_indices.size());
1068     _render_indices.clear();
1069 
1070     for (auto& texture : _render_textures)
1071         _cached_textures.push_front(texture);
1072     _render_textures.clear();
1073 
1074     check_error();
1075 
1076     glFlush(); // Make OpenGL execute all commands in the buffer.
1077   }
1078 
1079   /// Draw a line-strip directly, using a thin, solid line.
1080   //
1081   /// Can be used to draw empty boxes and cursors.
drawLine(const std::vector<point> & coords,const rgba & color,const SWFMatrix & mat)1082   virtual void drawLine(const std::vector<point>& coords, const rgba& color,
1083                   const SWFMatrix& mat)
1084   {
1085     oglScopeMatrix scope_mat(mat);
1086 
1087     const size_t numPoints = coords.size();
1088 
1089     glColor3ub(color.m_r, color.m_g, color.m_b);
1090 
1091     std::vector<std::int16_t> pointList;
1092     pointList.reserve(numPoints * 2);
1093     std::for_each(coords.begin(), coords.end(), PointSerializer(pointList));
1094 
1095     // Send the line-strip to OpenGL
1096     glEnableClientState(GL_VERTEX_ARRAY);
1097     glVertexPointer(2, GL_SHORT, 0 /* tight packing */, &pointList.front());
1098     glDrawArrays(GL_LINE_STRIP, 0, numPoints);
1099 
1100     // Draw a dot on the beginning and end coordinates to round lines.
1101     // glVertexPointer: skip all but the first and last coordinates in the line.
1102     glVertexPointer(2, GL_SHORT, (sizeof(std::int16_t) * 2) *
1103             (numPoints - 1), &pointList.front());
1104     glEnable(GL_POINT_SMOOTH); // Draw a round (antialiased) point.
1105     glDrawArrays(GL_POINTS, 0, 2);
1106     glDisable(GL_POINT_SMOOTH);
1107     glPointSize(1); // return to default
1108 
1109     glDisableClientState(GL_VERTEX_ARRAY);
1110   }
1111 
1112   // NOTE: this implementation can't handle complex polygons (such as concave
1113   // polygons.
draw_poly(const std::vector<point> & corners,const rgba & fill,const rgba & outline,const SWFMatrix & mat,bool)1114   virtual void draw_poly(const std::vector<point>& corners,
1115     const rgba& fill, const rgba& outline, const SWFMatrix& mat, bool /* masked */)
1116   {
1117     if (corners.empty()) return;
1118 
1119     oglScopeMatrix scope_mat(mat);
1120 
1121     glColor4ub(fill.m_r, fill.m_g, fill.m_b, fill.m_a);
1122 
1123     glEnableClientState(GL_VERTEX_ARRAY);
1124 
1125     // Draw simple polygon
1126     glVertexPointer(2, GL_FLOAT, 0 /* tight packing */, &corners.front());
1127     glDrawArrays(GL_POLYGON, 0, corners.size());
1128 
1129     // Draw outline
1130     glLineWidth(1.0);
1131     glColor4ub(outline.m_r, outline.m_g, outline.m_b, outline.m_a);
1132     glVertexPointer(2, GL_FLOAT, 0 /* tight packing */, &corners.front());
1133     glDrawArrays(GL_LINE_LOOP, 0, corners.size());
1134 
1135     glDisableClientState(GL_VERTEX_ARRAY);
1136 
1137     glPopMatrix();
1138   }
1139 
set_antialiased(bool)1140   virtual void  set_antialiased(bool /* enable */ )
1141   {
1142       log_unimpl(_("set_antialiased"));
1143   }
1144 
begin_submit_mask()1145   virtual void begin_submit_mask()
1146   {
1147     PathVec mask;
1148     _masks.push_back(mask);
1149 
1150     _drawing_mask = true;
1151   }
1152 
end_submit_mask()1153   virtual void end_submit_mask()
1154   {
1155     _drawing_mask = false;
1156 
1157     apply_mask();
1158   }
1159 
1160   /// Apply the current mask; nesting is supported.
1161   ///
1162   /// This method marks the stencil buffer by incrementing every stencil pixel
1163   /// by one every time a solid from one of the current masks is drawn. When
1164   /// all the mask solids are drawn, we change the stencil operation to permit
1165   /// only drawing where all masks have drawn, in other words, where all masks
1166   /// intersect, or in even other words, where the stencil pixel buffer equals
1167   /// the number of masks.
apply_mask()1168   void apply_mask()
1169   {
1170     if (_masks.empty()) return;
1171 
1172     glEnable(GL_STENCIL_TEST);
1173 
1174     glClearStencil(0x0); // FIXME: default is zero, methinks
1175     glClear(GL_STENCIL_BUFFER_BIT);
1176 
1177     // GL_NEVER means the stencil test will never succeed, so OpenGL wil never
1178     // continue to the drawing stage.
1179     glStencilFunc (GL_NEVER, 0x1, 0x1);
1180 
1181     glStencilOp (GL_INCR /* Stencil test fails */,
1182                  GL_KEEP /* ignored */,
1183                  GL_KEEP /* Stencil test passes; never happens */);
1184 
1185     // Call add_paths for each mask.
1186     std::for_each(_masks.begin(), _masks.end(),
1187       std::bind(&Renderer_ogl::add_paths, this, std::placeholders::_1));
1188 
1189     glStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
1190     glStencilFunc(GL_EQUAL, _masks.size(), _masks.size());
1191   }
1192 
1193   void
add_paths(const PathVec & path_vec)1194   add_paths(const PathVec& path_vec)
1195   {
1196     SWFCxForm dummy_cx;
1197     std::vector<FillStyle> dummy_fs;
1198 
1199     FillStyle coloring = FillStyle(SolidFill(rgba(0, 0, 0, 0)));
1200 
1201     dummy_fs.push_back(coloring);
1202 
1203     std::vector<LineStyle> dummy_ls;
1204 
1205     draw_subshape(path_vec, SWFMatrix(), dummy_cx, dummy_fs, dummy_ls);
1206   }
1207 
disable_mask()1208   virtual void disable_mask()
1209   {
1210     _masks.pop_back();
1211 
1212     if (_masks.empty()) {
1213       glDisable(GL_STENCIL_TEST);
1214     } else {
1215       apply_mask();
1216     }
1217   }
1218 
1219 #if 0
1220   void print_path(const Path& path)
1221   {
1222     std::cout << "Origin: ("
1223               << path.ap.x
1224               << ", "
1225               << path.ap.y
1226               << ") fill0: "
1227               << path.m_fill0
1228               << " fill1: "
1229               << path.m_fill1
1230               << " line: "
1231               << path.m_line
1232               << " new shape: "
1233               << path.m_new_shape
1234               << " number of edges: "
1235               << path.m_edges.size()
1236               << " edge endpoint: ("
1237               << path.m_edges.back().ap.x
1238               << ", "
1239               << path.m_edges.back().ap.y
1240               << " ) points:";
1241 
1242     for (std::vector<edge>::const_iterator it = path.m_edges.begin(), end = path.m_edges.end();
1243          it != end; ++it) {
1244       const edge& cur_edge = *it;
1245       std::cout << "( " << cur_edge.ap.x << ", " << cur_edge.ap.y << ") ";
1246     }
1247     std::cout << std::endl;
1248   }
1249 #endif
1250 
1251 
reverse_path(const Path & cur_path)1252   Path reverse_path(const Path& cur_path)
1253   {
1254     const Edge& cur_end = cur_path.m_edges.back();
1255 
1256     float prev_cx = cur_end.cp.x;
1257     float prev_cy = cur_end.cp.y;
1258 
1259     Path newpath(cur_end.ap.x, cur_end.ap.y, cur_path.m_fill1, cur_path.m_fill0, cur_path.m_line);
1260 
1261     float prev_ax = cur_end.ap.x;
1262     float prev_ay = cur_end.ap.y;
1263 
1264     for (std::vector<Edge>::const_reverse_iterator it = cur_path.m_edges.rbegin()+1, end = cur_path.m_edges.rend();
1265          it != end; ++it) {
1266       const Edge& cur_edge = *it;
1267 
1268       if (prev_ax == prev_cx && prev_ay == prev_cy) {
1269         prev_cx = cur_edge.ap.x;
1270         prev_cy = cur_edge.ap.y;
1271       }
1272 
1273       Edge newedge(prev_cx, prev_cy, cur_edge.ap.x, cur_edge.ap.y);
1274 
1275       newpath.m_edges.push_back(newedge);
1276 
1277       prev_cx = cur_edge.cp.x;
1278       prev_cy = cur_edge.cp.y;
1279       prev_ax = cur_edge.ap.x;
1280       prev_ay = cur_edge.ap.y;
1281 
1282     }
1283 
1284     Edge newlastedge(prev_cx, prev_cy, cur_path.ap.x, cur_path.ap.y);
1285     newpath.m_edges.push_back(newlastedge);
1286 
1287     return newpath;
1288   }
1289 
find_connecting_path(const Path & to_connect,std::list<const Path * > path_refs)1290   const Path* find_connecting_path(const Path& to_connect,
1291                                    std::list<const Path*> path_refs)
1292   {
1293 
1294     float target_x = to_connect.m_edges.back().ap.x;
1295     float target_y = to_connect.m_edges.back().ap.y;
1296 
1297     if (target_x == to_connect.ap.x &&
1298         target_y == to_connect.ap.y) {
1299       return nullptr;
1300     }
1301 
1302     for (std::list<const Path*>::const_iterator it = path_refs.begin(), end = path_refs.end();
1303          it != end; ++it) {
1304       const Path* cur_path = *it;
1305 
1306       if (cur_path == &to_connect) {
1307 
1308         continue;
1309 
1310       }
1311 
1312 
1313       if (cur_path->ap.x == target_x && cur_path->ap.y == target_y) {
1314 
1315         if (cur_path->m_fill1 != to_connect.m_fill1) {
1316           continue;
1317         }
1318         return cur_path;
1319       }
1320     }
1321 
1322 
1323     return nullptr;
1324   }
1325 
normalize_paths(const PathVec & paths)1326   PathVec normalize_paths(const PathVec &paths)
1327   {
1328     PathVec normalized;
1329 
1330     for (const Path& cur_path : paths) {
1331 
1332       if (cur_path.m_edges.empty()) {
1333         continue;
1334 
1335       } else if (cur_path.m_fill0 && cur_path.m_fill1) {
1336 
1337         // Two fill styles; duplicate and then reverse the left-filled one.
1338         normalized.push_back(cur_path);
1339         normalized.back().m_fill0 = 0;
1340 
1341         Path newpath = reverse_path(cur_path);
1342         newpath.m_fill0 = 0;
1343 
1344         normalized.push_back(newpath);
1345 
1346       } else if (cur_path.m_fill0) {
1347         // Left fill style.
1348         Path newpath = reverse_path(cur_path);
1349         newpath.m_fill0 = 0;
1350 
1351         normalized.push_back(newpath);
1352       } else if (cur_path.m_fill1) {
1353         // Right fill style.
1354 
1355         normalized.push_back(cur_path);
1356       } else {
1357         // No fill styles; copy without modifying.
1358         normalized.push_back(cur_path);
1359       }
1360 
1361     }
1362 
1363     return normalized;
1364   }
1365 
1366 
1367 
1368 
1369 
1370 
1371   /// Analyzes a set of paths to detect real presence of fills and/or outlines
1372   /// TODO: This should be something the character tells us and should be
1373   /// cached.
analyze_paths(const PathVec & paths,bool & have_shape,bool & have_outline)1374   void analyze_paths(const PathVec &paths, bool& have_shape,
1375     bool& have_outline) {
1376     //normalize_paths(paths);
1377     have_shape=false;
1378     have_outline=false;
1379 
1380     int pcount = paths.size();
1381 
1382     for (int pno=0; pno<pcount; pno++) {
1383 
1384       const Path &the_path = paths[pno];
1385 
1386       if ((the_path.m_fill0>0) || (the_path.m_fill1>0)) {
1387         have_shape=true;
1388         if (have_outline) return; // have both
1389       }
1390 
1391       if (the_path.m_line>0) {
1392         have_outline=true;
1393         if (have_shape) return; // have both
1394       }
1395 
1396     }
1397   }
1398 
apply_FillStyle(const FillStyle & style,const SWFMatrix &,const SWFCxForm & cx)1399   void apply_FillStyle(const FillStyle& style, const SWFMatrix& /* mat */, const SWFCxForm& cx)
1400   {
1401       const StyleHandler st(cx, *this);
1402       boost::apply_visitor(st, style.fill);
1403   }
1404 
apply_line_style(const LineStyle & style,const SWFCxForm & cx,const SWFMatrix & mat)1405   bool apply_line_style(const LineStyle& style, const SWFCxForm& cx, const SWFMatrix& mat)
1406   {
1407   //  GNASH_REPORT_FUNCTION;
1408 
1409     // In case GL_TEXTURE_2D was enabled by apply_FillStyle(), disable it now.
1410     // FIXME: this sucks
1411     glDisable(GL_TEXTURE_2D);
1412 
1413 
1414     bool rv = true;
1415 
1416     float width = style.getThickness();
1417     //    float pointSize;
1418 
1419     if (!width)
1420     {
1421       glLineWidth(1.0f);
1422       rv = false; // Don't draw rounded lines.
1423     }
1424     else if ( (!style.scaleThicknessVertically()) && (!style.scaleThicknessHorizontally()) )
1425     {
1426       float pxThickness = twipsToPixels(width);
1427       glLineWidth(pxThickness);
1428       glPointSize(pxThickness);
1429     }
1430     else
1431     {
1432       if ( (!style.scaleThicknessVertically()) || (!style.scaleThicknessHorizontally()) )
1433       {
1434          LOG_ONCE( log_unimpl(_("Unidirectionally scaled strokes in OGL renderer")) );
1435       }
1436 
1437       float stroke_scale = std::fabs(mat.get_x_scale()) + std::fabs(mat.get_y_scale());
1438       stroke_scale /= 2.0f;
1439       stroke_scale *= (std::fabs(_xscale) + std::fabs(_yscale)) / 2.0f;
1440       width *= stroke_scale;
1441       width = twipsToPixels(width);
1442 
1443       GLfloat width_info[2];
1444 
1445       glGetFloatv(GL_LINE_WIDTH_RANGE, width_info);
1446 
1447       if (width > width_info[1]) {
1448           LOG_ONCE(log_unimpl(_("Your OpenGL implementation does not support the line width requested. Lines will be drawn with reduced width.")));
1449         width = width_info[1];
1450       }
1451 
1452 
1453       glLineWidth(width);
1454       glPointSize(width);
1455 #if 0
1456       if (width >= 1.5) {
1457         glPointSize(width-1);
1458       } else {
1459         // Don't draw rounded lines.
1460         rv = false;
1461       }
1462 #endif
1463 
1464 
1465     }
1466 
1467     rgba c = cx.transform(style.get_color());
1468 
1469     glColor4ub(c.m_r, c.m_g, c.m_b, c.m_a);
1470 
1471     return rv;
1472   }
1473 
getPathPoints(const PathVec & path_vec)1474   PathPointMap getPathPoints(const PathVec& path_vec)
1475   {
1476 
1477     PathPointMap pathpoints;
1478 
1479     for (const Path& cur_path : path_vec) {
1480 
1481       if (!cur_path.m_edges.size()) {
1482         continue;
1483       }
1484 
1485       pathpoints[&cur_path] = interpolate(cur_path.m_edges, cur_path.ap.x,
1486                                                             cur_path.ap.y);
1487 
1488     }
1489 
1490     return pathpoints;
1491   }
1492 
1493   typedef std::vector<const Path*> PathPtrVec;
1494 
1495   static void
draw_point(const Edge & point_edge)1496   draw_point(const Edge& point_edge)
1497   {
1498     glVertex2d(point_edge.ap.x, point_edge.ap.y);
1499   }
1500 
1501   void
draw_outlines(const PathVec & path_vec,const PathPointMap & pathpoints,const SWFMatrix & mat,const SWFCxForm & cx,const std::vector<FillStyle> &,const std::vector<LineStyle> & line_styles)1502   draw_outlines(const PathVec& path_vec, const PathPointMap& pathpoints,
1503 		const SWFMatrix& mat, const SWFCxForm& cx,
1504 		const std::vector<FillStyle>& /* FillStyles */,
1505                 const std::vector<LineStyle>& line_styles)
1506   {
1507 
1508     for (const Path& cur_path : path_vec) {
1509 
1510       if (!cur_path.m_line) {
1511         continue;
1512       }
1513 
1514       bool draw_points = apply_line_style(line_styles[cur_path.m_line-1], cx, mat);
1515 
1516       assert(pathpoints.find(&cur_path) != pathpoints.end());
1517 
1518       const std::vector<oglVertex>& shape_points = (*pathpoints.find(&cur_path)).second;
1519 
1520       // Draw outlines.
1521       glEnableClientState(GL_VERTEX_ARRAY);
1522       glVertexPointer(3, GL_DOUBLE, 0 /* tight packing */, &shape_points.front());
1523       glDrawArrays(GL_LINE_STRIP, 0, shape_points.size());
1524       glDisableClientState(GL_VERTEX_ARRAY);
1525 
1526       if (!draw_points) {
1527         continue;
1528       }
1529 
1530       // Drawing points on the edges will allow us to simulate rounded lines.
1531 
1532       glEnable(GL_POINT_SMOOTH); // Draw round points.
1533 
1534       glBegin(GL_POINTS);
1535       {
1536         glVertex2d(cur_path.ap.x, cur_path.ap.y);
1537         std::for_each(cur_path.m_edges.begin(), cur_path.m_edges.end(),
1538                       draw_point);
1539       }
1540       glEnd();
1541     }
1542   }
1543 
get_contours(const PathPtrVec & paths)1544   std::list<PathPtrVec> get_contours(const PathPtrVec &paths)
1545   {
1546     std::list<const Path*> path_refs;
1547     std::list<PathPtrVec> contours;
1548 
1549 
1550     for (const Path* cur_path : paths) {
1551       path_refs.push_back(cur_path);
1552     }
1553 
1554     for (std::list<const Path*>::const_iterator it = path_refs.begin(), end = path_refs.end();
1555          it != end; ++it) {
1556       const Path* cur_path = *it;
1557 
1558       if (cur_path->m_edges.empty()) {
1559         continue;
1560       }
1561 
1562       if (!cur_path->m_fill0 && !cur_path->m_fill1) {
1563         continue;
1564       }
1565 
1566       PathPtrVec contour;
1567 
1568       contour.push_back(cur_path);
1569 
1570       const Path* connector = find_connecting_path(*cur_path, path_refs);
1571 
1572       while (connector) {
1573         contour.push_back(connector);
1574 
1575         const Path* tmp = connector;
1576         connector = find_connecting_path(*connector, std::list<const Path*>(std::next(it), end));
1577 
1578         // make sure we don't iterate over the connecting path in the for loop.
1579         path_refs.remove(tmp);
1580 
1581       }
1582 
1583       contours.push_back(contour);
1584     }
1585 
1586     return contours;
1587   }
1588 
1589 
draw_mask(const PathVec & path_vec)1590   void draw_mask(const PathVec& path_vec)
1591   {
1592     for (const Path& cur_path : path_vec) {
1593 
1594       if (cur_path.m_fill0 || cur_path.m_fill1) {
1595         _masks.back().push_back(cur_path);
1596         _masks.back().back().m_line = 0;
1597       }
1598     }
1599   }
1600 
1601   PathPtrVec
paths_by_style(const PathVec & path_vec,unsigned int style)1602   paths_by_style(const PathVec& path_vec, unsigned int style)
1603   {
1604     PathPtrVec paths;
1605     for (const Path& cur_path : path_vec) {
1606 
1607       if (cur_path.m_fill0 == style) {
1608         paths.push_back(&cur_path);
1609       }
1610 
1611       if (cur_path.m_fill1 == style) {
1612         paths.push_back(&cur_path);
1613       }
1614 
1615     }
1616     return paths;
1617   }
1618 
1619 
1620   /// Takes a path and translates it using the given SWFMatrix.
1621   void
apply_matrix_to_paths(std::vector<Path> & paths,const SWFMatrix & mat)1622   apply_matrix_to_paths(std::vector<Path>& paths, const SWFMatrix& mat)
1623   {
1624     std::for_each(paths.begin(), paths.end(),
1625                   std::bind(&Path::transform, std::placeholders::_1,
1626                   std::ref(mat)));
1627 
1628     //for_each(paths, &path::transform, mat);
1629   }
1630 
1631   void
draw_subshape(const PathVec & path_vec,const SWFMatrix & mat,const SWFCxForm & cx,const std::vector<FillStyle> & FillStyles,const std::vector<LineStyle> & line_styles)1632   draw_subshape(const PathVec& path_vec,
1633     const SWFMatrix& mat,
1634     const SWFCxForm& cx,
1635     const std::vector<FillStyle>& FillStyles,
1636     const std::vector<LineStyle>& line_styles)
1637   {
1638     PathVec normalized = normalize_paths(path_vec);
1639     PathPointMap pathpoints = getPathPoints(normalized);
1640 
1641     for (size_t i = 0; i < FillStyles.size(); ++i) {
1642       PathPtrVec paths = paths_by_style(normalized, i+1);
1643 
1644       if (!paths.size()) {
1645         continue;
1646       }
1647 
1648       std::list<PathPtrVec> contours = get_contours(paths);
1649 
1650       _tesselator.beginPolygon();
1651 
1652       for (std::list<PathPtrVec>::const_iterator iter = contours.begin(),
1653            final = contours.end(); iter != final; ++iter) {
1654         const PathPtrVec& refs = *iter;
1655 
1656         _tesselator.beginContour();
1657 
1658         for (const auto& ref : refs) {
1659           const Path& cur_path = *ref;
1660 
1661           assert(pathpoints.find(&cur_path) != pathpoints.end());
1662 
1663           _tesselator.feed(pathpoints[&cur_path]);
1664 
1665         }
1666 
1667         _tesselator.endContour();
1668       }
1669 
1670       apply_FillStyle(FillStyles[i], mat, cx);
1671 
1672       // This is terrible, but since the renderer is half dead I don't care.
1673       try {
1674           boost::get<SolidFill>(FillStyles[i].fill);
1675       }
1676       catch (const boost::bad_get&) {
1677           // For non solid fills...
1678           glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
1679       }
1680 
1681       _tesselator.tesselate();
1682 
1683       try {
1684           boost::get<SolidFill>(FillStyles[i].fill);
1685       }
1686       catch (const boost::bad_get&) {
1687           // Restore to original.
1688           glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1689       }
1690 
1691       glDisable(GL_TEXTURE_GEN_S);
1692       glDisable(GL_TEXTURE_GEN_T);
1693       glDisable(GL_TEXTURE_1D);
1694       glDisable(GL_TEXTURE_2D);
1695     }
1696 
1697     draw_outlines(normalized, pathpoints, mat, cx, FillStyles, line_styles);
1698   }
1699 
1700 // Drawing procedure:
1701 // 1. Separate paths by subshape.
1702 // 2. Separate subshapes by fill style.
1703 // 3. For every subshape/fill style combo:
1704 //  a. Separate contours: find closed shapes by connecting ends.
1705 //  b. Apply fill style.
1706 //  c. Feed the contours in the tesselator. (Render.)
1707 //  d. Draw outlines for every path in the subshape with a line style.
1708 //
1709 // 4. ...
1710 // 5. Profit!
1711 
drawShape(const SWF::ShapeRecord & shape,const Transform & xform)1712   virtual void drawShape(const SWF::ShapeRecord& shape, const Transform& xform)
1713   {
1714     if (shape.subshapes().empty()) {
1715         return;
1716     }
1717 
1718     oglScopeMatrix scope_mat(xform.matrix);
1719 
1720     for (const SWF::Subshape& subshape : shape.subshapes()) {
1721         const PathVec& path_vec = subshape.paths();
1722 
1723         if (!path_vec.size()) {
1724             // No paths. Nothing to draw...
1725             return;
1726         }
1727 
1728         if (_drawing_mask) {
1729             PathVec scaled_path_vec = path_vec;
1730 
1731             apply_matrix_to_paths(scaled_path_vec, xform.matrix);
1732             draw_mask(scaled_path_vec);
1733             continue;
1734         }
1735 
1736         bool have_shape, have_outline;
1737 
1738         analyze_paths(path_vec, have_shape, have_outline);
1739 
1740         if (!have_shape && !have_outline) {
1741             continue; // invisible character
1742         }
1743 
1744         draw_subshape(path_vec, xform.matrix, xform.colorTransform,
1745                       subshape.fillStyles(), subshape.lineStyles());
1746     }
1747   }
1748 
drawGlyph(const SWF::ShapeRecord & rec,const rgba & c,const SWFMatrix & mat)1749   virtual void drawGlyph(const SWF::ShapeRecord& rec, const rgba& c,
1750          const SWFMatrix& mat)
1751   {
1752     if (rec.subshapes().empty()) {
1753         return;
1754     }
1755     if (_drawing_mask) abort();
1756     SWFCxForm dummy_cx;
1757     std::vector<FillStyle> glyph_fs;
1758 
1759     FillStyle coloring = FillStyle(SolidFill(c));
1760 
1761     glyph_fs.push_back(coloring);
1762 
1763     std::vector<LineStyle> dummy_ls;
1764 
1765     oglScopeMatrix scope_mat(mat);
1766 
1767     draw_subshape(rec.subshapes().front().paths(), mat, dummy_cx, glyph_fs, dummy_ls);
1768   }
1769 
set_scale(float xscale,float yscale)1770   virtual void set_scale(float xscale, float yscale) {
1771     _xscale = xscale;
1772     _yscale = yscale;
1773   }
1774 
set_invalidated_regions(const InvalidatedRanges &)1775   virtual void set_invalidated_regions(const InvalidatedRanges& /* ranges */)
1776   {
1777 #if 0
1778     if (ranges.isWorld() || ranges.isNull()) {
1779       glDisable(GL_SCISSOR_TEST);
1780       return;
1781     }
1782 
1783     glEnable(GL_SCISSOR_TEST);
1784 
1785     geometry::Range2d<float> area = ranges.getFullArea;
1786 
1787     glScissor( (int)twipsToPixels(area.getMinX()), window_height-(int)twipsToPixels(area.getMaxY()),
1788                (int)twipsToPixels(area.width()), (int)twipsToPixels(area.height()));
1789 #endif
1790   }
1791 
1792 #ifdef OSMESA_TESTING
getPixel(rgba & color_out,int x,int y) const1793   bool getPixel(rgba& color_out, int x, int y) const
1794   {
1795     return _offscreen->getPixel(color_out, x, y);
1796   }
1797 
initTestBuffer(unsigned width,unsigned height)1798   bool initTestBuffer(unsigned width, unsigned height)
1799   {
1800     GNASH_REPORT_FUNCTION;
1801 
1802     _offscreen.reset(new OSRenderMesa(width, height));
1803 
1804     init();
1805 
1806     return true;
1807   }
1808 
getBitsPerPixel() const1809   unsigned int getBitsPerPixel() const
1810   {
1811     return _offscreen->getBitsPerPixel();
1812   }
1813 #endif // OSMESA_TESTING
1814 
1815 
1816 private:
1817 
1818   Tesselator _tesselator;
1819   float _xscale;
1820   float _yscale;
1821   float _width; // Width of the movie, in world coordinates.
1822   float _height;
1823 
1824   std::vector<PathVec> _masks;
1825   bool _drawing_mask;
1826 
1827   std::vector<std::uint8_t> _render_indices;
1828   std::vector< std::shared_ptr<GnashTexture> > _render_textures;
1829   std::list< std::shared_ptr<GnashTexture> > _cached_textures;
1830 
1831 #ifdef OSMESA_TESTING
1832   std::unique_ptr<OSRenderMesa> _offscreen;
1833 #endif
1834 }; // class Renderer_ogl
1835 
create_handler(bool init)1836 Renderer* create_handler(bool init)
1837 // Factory.
1838 {
1839   Renderer_ogl* renderer = new Renderer_ogl;
1840   if (init) {
1841     renderer->init();
1842   }
1843   return renderer;
1844 }
1845 
1846 namespace {
1847 
1848 // TODO: this function is rubbish and shouldn't survive a rewritten OGL
1849 // renderer.
1850 rgba
sampleGradient(const GradientFill & fill,std::uint8_t ratio)1851 sampleGradient(const GradientFill& fill, std::uint8_t ratio)
1852 {
1853 
1854     // By specs, first gradient should *always* be 0,
1855     // anyway a malformed SWF could break this,
1856     // so we cannot rely on that information...
1857     if (ratio < fill.record(0).ratio) {
1858         return fill.record(0).color;
1859     }
1860 
1861     if (ratio >= fill.record(fill.recordCount() - 1).ratio) {
1862         return fill.record(fill.recordCount() - 1).color;
1863     }
1864 
1865     for (size_t i = 1, n = fill.recordCount(); i < n; ++i) {
1866 
1867         const GradientRecord& gr1 = fill.record(i);
1868         if (gr1.ratio < ratio) continue;
1869 
1870         const GradientRecord& gr0 = fill.record(i - 1);
1871         if (gr0.ratio > ratio) continue;
1872 
1873         float f = 0.0f;
1874 
1875         if (gr0.ratio != gr1.ratio) {
1876             f = (ratio - gr0.ratio) / float(gr1.ratio - gr0.ratio);
1877         }
1878         else {
1879             // Ratios are equal IFF first and second GradientRecord
1880             // have the same ratio. This would be a malformed SWF.
1881             IF_VERBOSE_MALFORMED_SWF(
1882                 log_swferror(_("two gradients in a FillStyle "
1883                     "have the same position/ratio: %d"),
1884                     gr0.ratio);
1885             );
1886         }
1887 
1888         return lerp(gr0.color, gr1.color, f);
1889     }
1890 
1891     // Assuming gradients are ordered by ratio? see start comment
1892     return fill.record(fill.recordCount() - 1).color;
1893 }
1894 
1895 const CachedBitmap*
createGradientBitmap(const GradientFill & gf,Renderer & renderer)1896 createGradientBitmap(const GradientFill& gf, Renderer& renderer)
1897 {
1898     std::unique_ptr<image::ImageRGBA> im;
1899 
1900     switch (gf.type())
1901     {
1902         case GradientFill::LINEAR:
1903             // Linear gradient.
1904             im.reset(new image::ImageRGBA(256, 1));
1905 
1906             for (size_t i = 0; i < im->width(); i++) {
1907 
1908                 rgba sample = sampleGradient(gf, i);
1909                 im->setPixel(i, 0, sample.m_r, sample.m_g,
1910                         sample.m_b, sample.m_a);
1911             }
1912             break;
1913 
1914         case GradientFill::RADIAL:
1915             // Focal gradient.
1916             im.reset(new image::ImageRGBA(64, 64));
1917 
1918             for (size_t j = 0; j < im->height(); j++)
1919             {
1920                 for (size_t i = 0; i < im->width(); i++)
1921                 {
1922                     float radiusy = (im->height() - 1) / 2.0f;
1923                     float radiusx = radiusy + std::abs(radiusy * gf.focalPoint());
1924                     float y = (j - radiusy) / radiusy;
1925                     float x = (i - radiusx) / radiusx;
1926                     int ratio = std::floor(255.5f * std::sqrt(x*x + y*y));
1927 
1928                     if (ratio > 255) ratio = 255;
1929 
1930                     rgba sample = sampleGradient(gf, ratio);
1931                     im->setPixel(i, j, sample.m_r, sample.m_g,
1932                             sample.m_b, sample.m_a);
1933                 }
1934             }
1935             break;
1936         default:
1937             break;
1938     }
1939 
1940     const CachedBitmap* bi = renderer.createCachedBitmap(
1941                     static_cast<std::unique_ptr<image::GnashImage> >(std::move(im)));
1942 
1943     return bi;
1944 }
1945 
1946 }
1947 
1948 } // namespace gnash::renderer::opengl
1949 } // namespace gnash::renderer
1950 } // namespace gnash
1951 
1952 // local Variables:
1953 // mode: C++
1954 // indent-tabs-mode: nil
1955 // End:
1956