1 // gnash-canvas.cpp: Gtk canvas widget for gnash
2 //
3 //   Copyright (C) 2009, 2010, 2011 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 <string>
25 
26 #include "gtk_canvas.h"
27 #include "Renderer.h"
28 #include "rc.h"
29 #include "log.h"
30 #include "gtk_glue.h"
31 #include "GnashException.h"
32 
33 #ifdef HAVE_VA_VA_H
34 #include "vaapi_utils.h"
35 #endif
36 
37 // OpenGL support for rendering in the canvas. This also requires
38 // the GtkGL widget for GTK2.
39 #ifdef HAVE_GTK_GTKGL_H
40 #include "gtk_glue_gtkglext.h"
41 #endif
42 
43 // Cairo support for rendering in the canvas.
44 #ifdef RENDERER_CAIRO
45 #include "gtk_glue_cairo.h"
46 #endif
47 
48 // Cairo support for rendering in the canvas.
49 #ifdef RENDERER_OPENVG
50 #include "gtk_glue_ovg.h"
51 #endif
52 
53 // AGG support, which is the default, for rendering in the canvas.
54 #include "gtk_glue_agg.h"
55 
56 #ifdef HAVE_VA_VA_H
57 #include "gtk_glue_agg_vaapi.h"
58 #endif
59 
60 struct _GnashCanvas
61 {
62     GtkDrawingArea base_instance;
63     std::unique_ptr<gnash::GtkGlue> glue;
64     std::shared_ptr<gnash::Renderer> renderer;
65 };
66 
67 G_DEFINE_TYPE(GnashCanvas, gnash_canvas, GTK_TYPE_DRAWING_AREA)
68 
69 static GObjectClass *parent_class = nullptr;
70 
71 static void gnash_canvas_class_init(GnashCanvasClass *gnash_canvas_class);
72 static void gnash_canvas_init(GnashCanvas *canvas);
73 static void gnash_canvas_size_allocate(GtkWidget *widget, GtkAllocation *allocation);
74 static gboolean gnash_canvas_expose_event(GtkWidget *widget, GdkEventExpose *event);
75 static gboolean gnash_canvas_configure_event(GtkWidget *widget, GdkEventConfigure *event);
76 static void gnash_canvas_realize(GtkWidget *widget);
77 static void gnash_canvas_after_realize(GtkWidget *widget);
78 
79 GtkWidget *
gnash_canvas_new()80 gnash_canvas_new ()
81 {
82     return GTK_WIDGET(g_object_new (GNASH_TYPE_CANVAS, nullptr));
83 }
84 
85 static void
gnash_canvas_class_init(GnashCanvasClass * gnash_canvas_class)86 gnash_canvas_class_init(GnashCanvasClass *gnash_canvas_class)
87 {
88     GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(gnash_canvas_class);
89 
90     parent_class = (GObjectClass *)g_type_class_peek_parent(gnash_canvas_class);
91 
92     widget_class->size_allocate = gnash_canvas_size_allocate;
93     widget_class->expose_event = gnash_canvas_expose_event;
94     widget_class->configure_event = gnash_canvas_configure_event;
95     widget_class->realize = gnash_canvas_realize;
96 }
97 
98 static void
gnash_canvas_init(GnashCanvas * canvas)99 gnash_canvas_init(GnashCanvas *canvas)
100 {
101 
102     canvas->renderer.reset();
103 
104     gtk_widget_set_double_buffered(GTK_WIDGET(canvas), FALSE);
105 
106     g_signal_connect_after(G_OBJECT(canvas), "realize",
107                            G_CALLBACK(gnash_canvas_after_realize), NULL);
108 
109     // If we don't set this flag we won't be able to grab focus
110     // ( grabFocus() would be a no-op )
111     GTK_WIDGET_SET_FLAGS (GTK_WIDGET(canvas), GTK_CAN_FOCUS);
112 }
113 
114 static void
gnash_canvas_size_allocate(GtkWidget * widget,GtkAllocation * allocation)115 gnash_canvas_size_allocate(GtkWidget *widget, GtkAllocation *allocation)
116 {
117     GnashCanvas *canvas = GNASH_CANVAS(widget);
118 
119     gnash::log_debug("gnash_canvas_size_allocate %d %d", allocation->width,
120                 allocation->height);
121 
122     if (canvas->renderer.get()) {
123         canvas->glue->setRenderHandlerSize(allocation->width,
124                 allocation->height);
125     }
126 
127     GTK_WIDGET_CLASS(parent_class)->size_allocate (widget, allocation);
128 }
129 
130 static gboolean
gnash_canvas_expose_event(GtkWidget * widget,GdkEventExpose * event)131 gnash_canvas_expose_event(GtkWidget *widget, GdkEventExpose *event)
132 {
133     GnashCanvas *canvas = GNASH_CANVAS(widget);
134 
135     // In some versions of GTK this can't be const...
136     GdkRegion* nonconst_region = const_cast<GdkRegion*>(event->region);
137 
138     canvas->glue->render(nonconst_region);
139 
140     return TRUE;
141 }
142 
143 static gboolean
gnash_canvas_configure_event(GtkWidget * widget,GdkEventConfigure * event)144 gnash_canvas_configure_event(GtkWidget *widget, GdkEventConfigure *event)
145 {
146     GnashCanvas *canvas = GNASH_CANVAS(widget);
147 
148     canvas->glue->configure(widget, event);
149 
150     return FALSE;
151 }
152 
153 static void
gnash_canvas_realize(GtkWidget * widget)154 gnash_canvas_realize(GtkWidget *widget)
155 {
156     GTK_WIDGET_SET_FLAGS (widget, GTK_REALIZED);
157 
158     GdkWindowAttr attributes;
159     attributes.window_type = GDK_WINDOW_CHILD;
160     attributes.x = widget->allocation.x;
161     attributes.y = widget->allocation.y;
162     attributes.width = widget->allocation.width;
163     attributes.height = widget->allocation.height;
164     attributes.wclass = GDK_INPUT_OUTPUT;
165     attributes.visual = gtk_widget_get_visual (widget);
166     attributes.colormap = gtk_widget_get_colormap (widget);
167     attributes.event_mask = gtk_widget_get_events (widget) | GDK_EXPOSURE_MASK;
168 
169     gint attributes_mask =
170         GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL | GDK_WA_COLORMAP;
171 
172     widget->window = gdk_window_new (gtk_widget_get_parent_window (widget),
173                                     &attributes, attributes_mask);
174     gdk_window_set_user_data (widget->window, widget);
175 
176     widget->style = gtk_style_attach (widget->style, widget->window);
177     gtk_style_set_background (widget->style, widget->window, GTK_STATE_NORMAL);
178 
179 #if defined(RENDERER_CAIRO) || defined(RENDERER_AGG)
180     GnashCanvas *canvas = GNASH_CANVAS(widget);
181     // cairo needs the _drawingArea.window to prepare it ..
182     // TODO: find a way to make 'glue' use independent from actual
183     // renderer in use
184     canvas->glue->prepDrawingArea(GTK_WIDGET(canvas));
185 #endif
186 }
187 
188 static void
gnash_canvas_after_realize(GtkWidget * widget)189 gnash_canvas_after_realize(GtkWidget *widget)
190 {
191     GnashCanvas *canvas = GNASH_CANVAS(widget);
192 
193     canvas->renderer.reset(canvas->glue->createRenderHandler());
194 
195     canvas->glue->setRenderHandlerSize(widget->allocation.width,
196                                         widget->allocation.height);
197 }
198 
199 void
gnash_canvas_setup(GnashCanvas * canvas,std::string & hwaccel,std::string & renderer,int argc,char ** argv[])200 gnash_canvas_setup(GnashCanvas *canvas, std::string& hwaccel,
201         std::string& renderer, int argc, char **argv[])
202 {
203 
204     // Order should be VAAPI, X11
205     bool initialized_renderer = false;
206 
207     // If a renderer hasn't been defined in gnashrc, or on the command
208     // line, pick a sensible default.
209     if (renderer.empty()) {
210 #ifdef RENDERER_AGG
211         renderer = "agg";
212 #elif defined (RENDERER_CAIRO)
213         renderer = "cairo";
214 #elif defined (RENDERER_OPENGL)
215         renderer = "opengl";
216 #elif defined (RENDERER_OPENVG)
217         renderer = "openvg";
218 #endif
219     }
220 
221     // If the Hardware acceleration isn't defined in gnashrc, or on
222     // the command line, pick a sensible default.
223     if (hwaccel.empty()) {
224         hwaccel = "none";
225     }
226 
227 #ifdef HAVE_VA_VA_H
228     // Global enable VA-API, if requested
229     gnash::vaapi_set_is_enabled(hwaccel == "vaapi");
230 #endif
231 
232     // Use the Cairo renderer. Cairo is also used by GTK2, so using
233     // Cairo makes much sense. Unfortunately, our implementation seems
234     // to have serious performance issues, although it does work.
235     if (renderer == "cairo") {
236 #ifdef RENDERER_CAIRO
237         canvas->glue.reset(new gnash::GtkCairoGlue);
238 #else
239         boost::format fmt = boost::format("Support for renderer %1% "
240                 " was not built") % renderer;
241         throw gnash::GnashException(fmt.str());
242 #endif
243     }
244 
245     else if (renderer == "opengl") {
246 #ifdef RENDERER_OPENGL
247         canvas->glue.reset(new gnash::GtkGlExtGlue);
248 #else
249         boost::format fmt = boost::format("Support for renderer %1% "
250                 " was not built") % renderer;
251         throw gnash::GnashException(fmt.str());
252 #endif
253     }
254     else if ((renderer == "openvg") || (renderer == "ovg")) {
255 	renderer = "openvg";
256 #ifdef RENDERER_OPENVG
257         canvas->glue.reset(new gnash::gui::GtkOvgGlue);
258 #else
259         boost::format fmt = boost::format("Support for renderer %1% "
260                 " was not built") % renderer;
261         throw gnash::GnashException(fmt.str());
262 #endif
263     }
264     else if (renderer == "agg") {
265         // Use the AGG software library for rendering. While this runs
266         // on any hardware platform, it does have performance issues
267         // on low-end platforms without a GPU. So while AGG may render
268         // streaming video over a network connection just fine,
269         // anything below about 600Mhz CPU may have buffering and
270         // rendering performance issues.
271         // Use LibVva, which works on Nvidia, AT, or Intel 965 GPUs
272         // with AGG or OpenGL.
273 #ifdef RENDERER_AGG
274 #ifdef HAVE_VA_VA_H
275         if (hwaccel == "vaapi") {
276             canvas->glue.reset(new gnash::GtkAggVaapiGlue);
277             // Set the hardware acclerator to the next one to try
278             // if initializing fails.
279         } else
280 #endif
281         {
282             canvas->glue.reset(new gnash::GtkAggGlue);
283         }
284 #else // ndef RENDERER_AGG
285         boost::format fmt = boost::format("Support for renderer %1% "
286                 "was not built") % renderer;
287         throw gnash::GnashException(fmt.str());
288 #endif
289     }
290     else {
291         boost::format fmt = boost::format("Non-existent renderer %1% "
292             "specified") % renderer;
293         throw gnash::GnashException(fmt.str());
294     }
295 
296     // Initialize the canvas for rendering into
297     initialized_renderer = canvas->glue->init(argc, argv);
298     // If the renderer with the least dependencies fails, we can't
299     // proceed.
300     if (!initialized_renderer) {
301         boost::format fmt = boost::format("Requested renderer %1% (hwaccel: "
302                 "%2%) could not be initialized") % renderer % hwaccel;
303         throw gnash::GnashException(fmt.str());
304     }
305 
306     if (renderer == "opengl") {
307         // OpenGL glue needs to prepare the drawing area for OpenGL
308         // rendering before
309         // widgets are realized and before the configure event is fired.
310         // TODO: find a way to make '_glue' use independent from
311         // actual renderer in use
312         canvas->glue->prepDrawingArea(GTK_WIDGET(canvas));
313     }
314 }
315 
316 void
gnash_canvas_before_rendering(GnashCanvas * canvas,gnash::movie_root * stage)317 gnash_canvas_before_rendering(GnashCanvas *canvas, gnash::movie_root* stage)
318 {
319     canvas->glue->beforeRendering(stage);
320 }
321 
322 std::shared_ptr<gnash::Renderer>
gnash_canvas_get_renderer(GnashCanvas * canvas)323 gnash_canvas_get_renderer(GnashCanvas *canvas)
324 {
325     return canvas->renderer;
326 }
327 
328