b6a205ce56ed7845540c66abb017afdc146eebb8
[harmattan/cameraplus] / lib / qtcamviewfinderrenderermeego.cpp
1 /*!
2  * This file is part of CameraPlus.
3  *
4  * Copyright (C) 2012-2013 Mohammed Sameer <msameer@foolab.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
19  */
20
21 #include "qtcamviewfinderrenderermeego.h"
22 #include <QDebug>
23 #include <gst/video/video.h>
24 #include <QPainter>
25 #include "qtcamconfig.h"
26 #include <QX11Info>
27 #include <QGLContext>
28 #include <QGLShaderProgram>
29 #include <gst/interfaces/meegovideotexture.h>
30
31 QT_CAM_VIEWFINDER_RENDERER(RENDERER_TYPE_MEEGO, QtCamViewfinderRendererMeeGo);
32
33 #define GL_TEXTURE_EXTERNAL_OES                  0x8060
34
35 typedef void *EGLSyncKHR;
36 #define EGL_SYNC_FENCE_KHR                       0x30F9
37
38 typedef EGLSyncKHR(EGLAPIENTRYP _PFNEGLCREATESYNCKHRPROC)(EGLDisplay dpy, EGLenum type,
39                                                           const EGLint *attrib_list);
40
41 _PFNEGLCREATESYNCKHRPROC eglCreateSyncKHR = 0;
42
43 static const QString FRAGMENT_SHADER = ""
44     "#extension GL_OES_EGL_image_external: enable\n"
45     "uniform samplerExternalOES texture0;"
46     "varying lowp vec2 fragTexCoord;"
47     "void main() {"
48     "  gl_FragColor = texture2D(texture0, fragTexCoord);"
49     "}"
50   "";
51
52 static const QString VERTEX_SHADER = ""
53     "attribute highp vec4 inputVertex;"
54     "attribute lowp vec2 textureCoord;"
55     "uniform highp mat4 matrix;"
56     "uniform highp mat4 matrixWorld;"
57     "varying lowp vec2 fragTexCoord;"
58     ""
59     "void main() {"
60     "  gl_Position = matrix * matrixWorld * inputVertex;"
61     "  fragTexCoord = textureCoord;"
62     "}"
63   "";
64
65 QtCamViewfinderRendererMeeGo::QtCamViewfinderRendererMeeGo(QtCamConfig *config,
66                                                            QObject *parent) :
67   QtCamViewfinderRenderer(config, parent),
68   m_conf(config),
69   m_sink(0),
70   m_frame(-1),
71   m_id(0),
72   m_notify(0),
73   m_needsInit(true),
74   m_program(0) {
75
76   // Texture coordinates:
77   // Reversed because of Qt reversed coordinate system
78   m_texCoords[0] = 0;       m_texCoords[1] = 1;
79   m_texCoords[2] = 1;       m_texCoords[3] = 1;
80   m_texCoords[4] = 1;       m_texCoords[5] = 0;
81   m_texCoords[6] = 0;       m_texCoords[7] = 0;
82
83   bzero(&m_vertexCoords, 8);
84 }
85
86 QtCamViewfinderRendererMeeGo::~QtCamViewfinderRendererMeeGo() {
87   if (m_sink) {
88     g_signal_handler_disconnect(m_sink, m_id);
89     g_signal_handler_disconnect(m_sink, m_notify);
90     g_object_remove_toggle_ref(G_OBJECT(m_sink), (GToggleNotify)sink_notify, this);
91     m_sink = 0;
92   }
93 }
94
95 void QtCamViewfinderRendererMeeGo::paint(QPainter *painter) {
96   QMutexLocker locker(&m_frameMutex);
97   if (m_frame == -1) {
98     return;
99   }
100
101   painter->beginNativePainting();
102
103   if (m_needsInit) {
104     calculateProjectionMatrix(painter->viewport());
105
106     if (!eglCreateSyncKHR && m_conf->viewfinderUseFence()) {
107       eglCreateSyncKHR = (_PFNEGLCREATESYNCKHRPROC)eglGetProcAddress("eglCreateSyncKHR");
108
109       if (!eglCreateSyncKHR) {
110         qWarning() << "eglCreateSyncKHR not found. Fences disabled";
111       }
112     }
113
114     m_needsInit = false;
115   }
116
117   if (!m_program) {
118     // Program will be created if needed and will never be deleted even
119     // if attaching the shaders fail.
120     createProgram();
121   }
122
123   paintFrame(painter, m_frame);
124
125   painter->endNativePainting();
126 }
127
128 void QtCamViewfinderRendererMeeGo::resize(const QSizeF& size) {
129   if (size == m_size) {
130     return;
131   }
132
133   m_size = size;
134
135   m_renderArea = QRectF();
136
137   calculateCoords();
138
139   // This will destroy everything
140   // but we need a way to reset the viewport and the transformation matrix only.
141   m_needsInit = true;
142
143   emit renderAreaChanged();
144 }
145
146 void QtCamViewfinderRendererMeeGo::reset() {
147   QMutexLocker locker(&m_frameMutex);
148   m_frame = -1;
149 }
150
151 GstElement *QtCamViewfinderRendererMeeGo::sinkElement() {
152   if (!QGLContext::currentContext()) {
153     qCritical() << "Cannot create the GStreamer element without an OpenGL context.";
154     return 0;
155   }
156
157   if (!m_sink) {
158     m_sink = gst_element_factory_make(m_conf->viewfinderSink().toLatin1().data(),
159                                       "QtCamViewfinderRendererMeeGoSink");
160     if (!m_sink) {
161       qCritical() << "Failed to create" << m_conf->viewfinderSink();
162       return 0;
163     }
164
165     g_object_add_toggle_ref(G_OBJECT(m_sink), (GToggleNotify)sink_notify, this);
166   }
167
168   // Displa can be replaced with a null pointer.
169   // We all know that the sink used for Harmattan ignores the x-display property ;-)
170
171   Display *d = QX11Info::display();
172   g_object_set(G_OBJECT(m_sink), "x-display", d, "use-framebuffer-memory", TRUE, NULL);
173
174   m_dpy = eglGetDisplay((EGLNativeDisplayType)d);
175   if (m_dpy == EGL_NO_DISPLAY) {
176     qCritical() << "Failed to obtain EGL Display";
177   }
178
179   EGLContext context = eglGetCurrentContext();
180   if (context == EGL_NO_CONTEXT) {
181     qCritical() << "Failed to obtain EGL context";
182   }
183
184   g_object_set(G_OBJECT(m_sink), "egl-display", m_dpy, "egl-context", context, NULL);
185
186   m_id = g_signal_connect(G_OBJECT(m_sink), "frame-ready", G_CALLBACK(frame_ready), this);
187
188   GstPad *pad = gst_element_get_static_pad(m_sink, "sink");
189   m_notify = g_signal_connect(G_OBJECT(pad), "notify::caps",
190                               G_CALLBACK(sink_caps_changed), this);
191   gst_object_unref(pad);
192
193   return m_sink;
194 }
195
196 void QtCamViewfinderRendererMeeGo::frame_ready(GstElement *sink, int frame,
197                                                QtCamViewfinderRendererMeeGo *r) {
198   Q_UNUSED(sink);
199   Q_UNUSED(frame);
200
201   r->m_frameMutex.lock();
202   r->m_frame = frame;
203   r->m_frameMutex.unlock();
204
205   QMetaObject::invokeMethod(r, "updateRequested", Qt::QueuedConnection);
206 }
207
208 void QtCamViewfinderRendererMeeGo::sink_notify(QtCamViewfinderRendererMeeGo *q,
209                                                GObject *object, gboolean is_last_ref) {
210
211   Q_UNUSED(object);
212
213   if (is_last_ref) {
214     g_signal_handler_disconnect(q->m_sink, q->m_id);
215     g_object_remove_toggle_ref(G_OBJECT(q->m_sink), (GToggleNotify)sink_notify, q);
216     q->m_sink = 0;
217   }
218 }
219
220 void QtCamViewfinderRendererMeeGo::sink_caps_changed(GObject *obj, GParamSpec *pspec,
221                                                      QtCamViewfinderRendererMeeGo *q) {
222   Q_UNUSED(obj);
223   Q_UNUSED(pspec);
224
225   int width, height;
226   if (obj && gst_video_get_size(GST_PAD(obj), &width, &height)) {
227     QMetaObject::invokeMethod(q, "setVideoSize", Qt::QueuedConnection,
228                               Q_ARG(QSizeF, QSizeF(width, height)));
229   }
230 }
231
232 void QtCamViewfinderRendererMeeGo::calculateProjectionMatrix(const QRectF& rect) {
233   m_projectionMatrix = QMatrix4x4();
234   m_projectionMatrix.ortho(rect);
235 }
236
237 void QtCamViewfinderRendererMeeGo::createProgram() {
238   if (m_program) {
239     delete m_program;
240   }
241
242   m_program = new QGLShaderProgram(this);
243
244   if (!m_program->addShaderFromSourceCode(QGLShader::Vertex, VERTEX_SHADER)) {
245     qCritical() << "Failed to add vertex shader";
246     return;
247   }
248
249   if (!m_program->addShaderFromSourceCode(QGLShader::Fragment, FRAGMENT_SHADER)) {
250     qCritical() << "Failed to add fragment shader";
251     return;
252   }
253
254   m_program->bindAttributeLocation("inputVertex", 0);
255   m_program->bindAttributeLocation("textureCoord", 1);
256
257   if (!m_program->link()) {
258     qCritical() << "Failed to link program!";
259     return;
260   }
261
262   if (!m_program->bind()) {
263     qCritical() << "Failed to bind program";
264     return;
265   }
266
267   m_program->setUniformValue("texture0", 0); // texture UNIT 0
268   m_program->release();
269 }
270
271 void QtCamViewfinderRendererMeeGo::paintFrame(QPainter *painter, int frame) {
272   EGLSyncKHR sync = 0;
273
274   if (frame == -1) {
275     return;
276   }
277
278   MeegoGstVideoTexture *sink = MEEGO_GST_VIDEO_TEXTURE(m_sink);
279   if (!meego_gst_video_texture_acquire_frame(sink, frame)) {
280     qDebug() << "Failed to acquire frame";
281     return;
282   }
283
284   m_program->bind();
285
286   m_program->setUniformValue("matrix", m_projectionMatrix);
287   m_program->setUniformValue("matrixWorld", QMatrix4x4(painter->combinedTransform()));
288
289   if (!meego_gst_video_texture_bind_frame(sink, GL_TEXTURE_EXTERNAL_OES, frame)) {
290     qDebug() << "Failed to bind frame";
291     m_program->release();
292     return;
293   }
294
295   glEnableVertexAttribArray(0);
296   glEnableVertexAttribArray(1);
297
298   glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, &m_vertexCoords);
299   glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, &m_texCoords);
300
301   glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
302
303   if (!meego_gst_video_texture_bind_frame(sink, GL_TEXTURE_EXTERNAL_OES, -1)) {
304     qDebug() << "Failed to unbind frame";
305   }
306
307   glDisableVertexAttribArray(1);
308   glDisableVertexAttribArray(0);
309
310   m_program->release();
311
312   if (eglCreateSyncKHR) {
313     sync = eglCreateSyncKHR(m_dpy, EGL_SYNC_FENCE_KHR, NULL);
314   }
315
316   meego_gst_video_texture_release_frame(sink, frame, sync);
317 }
318
319 void QtCamViewfinderRendererMeeGo::calculateCoords() {
320   if (!m_size.isValid() || !m_videoSize.isValid()) {
321     return;
322   }
323
324   QRectF area = renderArea();
325
326   qreal leftMargin = area.x();
327   qreal topMargin = area.y();
328   QSizeF renderSize = area.size();
329
330   m_vertexCoords[0] = leftMargin;
331   m_vertexCoords[1] = topMargin + renderSize.height();
332
333   m_vertexCoords[2] = renderSize.width() + leftMargin;
334   m_vertexCoords[3] = topMargin + renderSize.height();
335
336   m_vertexCoords[4] = renderSize.width() + leftMargin;
337   m_vertexCoords[5] = topMargin;
338
339   m_vertexCoords[6] = leftMargin;
340   m_vertexCoords[7] = topMargin;
341 }
342
343 QRectF QtCamViewfinderRendererMeeGo::renderArea() {
344   if (!m_renderArea.isNull()) {
345     return m_renderArea;
346   }
347
348   QSizeF renderSize = m_videoSize;
349   renderSize.scale(m_size, Qt::KeepAspectRatio);
350
351   qreal leftMargin = (m_size.width() - renderSize.width())/2.0;
352   qreal topMargin = (m_size.height() - renderSize.height())/2.0;
353
354   m_renderArea = QRectF(QPointF(leftMargin, topMargin), renderSize);
355
356   return m_renderArea;
357 }
358
359 QSizeF QtCamViewfinderRendererMeeGo::videoResolution() {
360   return m_videoSize;
361 }
362
363 void QtCamViewfinderRendererMeeGo::setVideoSize(const QSizeF& size) {
364   if (size == m_videoSize) {
365     return;
366   }
367
368   m_videoSize = size;
369
370   m_renderArea = QRectF();
371
372   calculateCoords();
373
374   m_needsInit = true;
375
376   emit renderAreaChanged();
377   emit videoResolutionChanged();
378 }