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