87bcc9d358545ca72eea859b93798a9d63edd127
[harmattan/cameraplus] / lib / qtcamviewfinderrenderermeego.cpp
1 /*!
2  * This file is part of CameraPlus.
3  *
4  * Copyright (C) 2012 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 <EGL/egl.h>
29 #include <QGLShaderProgram>
30 #include <gst/interfaces/meegovideotexture.h>
31
32 QT_CAM_VIEWFINDER_RENDERER(RENDERER_TYPE_MEEGO, QtCamViewfinderRendererMeeGo);
33
34 #define GL_TEXTURE_EXTERNAL_OES                  0x8060
35
36 static const QString FRAGMENT_SHADER = ""
37     "#extension GL_OES_EGL_image_external: enable\n"
38     "uniform samplerExternalOES texture0;"
39     "varying lowp vec2 fragTexCoord;"
40     "void main() {"
41     "  gl_FragColor = texture2D(texture0, fragTexCoord);"
42     "}"
43   "";
44
45 static const QString VERTEX_SHADER = ""
46     "attribute highp vec4 inputVertex;"
47     "attribute lowp vec2 textureCoord;"
48     "uniform highp mat4 matrix;"
49     "uniform highp mat4 matrixWorld;"
50     "varying lowp vec2 fragTexCoord;"
51     ""
52     "void main() {"
53     "  gl_Position = matrix * matrixWorld * inputVertex;"
54     "  fragTexCoord = textureCoord;"
55     "}"
56   "";
57
58 QtCamViewfinderRendererMeeGo::QtCamViewfinderRendererMeeGo(QtCamConfig *config,
59                                                                QObject *parent) :
60   QtCamViewfinderRenderer(config, parent),
61   m_conf(config),
62   m_sink(0),
63   m_frame(-1),
64   m_id(0),
65   m_notify(0),
66   m_needsInit(true),
67   m_program(0) {
68
69   // Texture coordinates:
70   // Reversed because of Qt reversed coordinate system
71   m_texCoords[0] = 0;       m_texCoords[1] = 1;
72   m_texCoords[2] = 1;       m_texCoords[3] = 1;
73   m_texCoords[4] = 1;       m_texCoords[5] = 0;
74   m_texCoords[6] = 0;       m_texCoords[7] = 0;
75
76   bzero(&m_vertexCoords, 8);
77 }
78
79 QtCamViewfinderRendererMeeGo::~QtCamViewfinderRendererMeeGo() {
80   if (m_sink) {
81     g_signal_handler_disconnect(m_sink, m_id);
82     g_signal_handler_disconnect(m_sink, m_notify);
83     g_object_remove_toggle_ref(G_OBJECT(m_sink), (GToggleNotify)sink_notify, this);
84     m_sink = 0;
85   }
86 }
87
88 void QtCamViewfinderRendererMeeGo::paint(QPainter *painter) {
89   QMutexLocker locker(&m_frameMutex);
90   if (m_frame == -1) {
91     return;
92   }
93
94   painter->beginNativePainting();
95
96   if (m_needsInit) {
97     calculateProjectionMatrix(painter->viewport());
98     m_needsInit = false;
99   }
100
101   if (!m_program) {
102     // Program will be created if needed and will never be deleted even
103     // if attaching the shaders fail.
104     createProgram();
105   }
106
107   paintFrame(painter, m_frame);
108
109   painter->endNativePainting();
110 }
111
112 void QtCamViewfinderRendererMeeGo::resize(const QSizeF& size) {
113   if (size == m_size) {
114     return;
115   }
116
117   m_size = size;
118
119   m_renderArea = QRectF();
120
121   calculateCoords();
122
123   // TODO: this will destroy everything
124   // but we need a way to reset the viewport and the transformation matrix only.
125   m_needsInit = true;
126
127   emit renderAreaChanged();
128 }
129
130 void QtCamViewfinderRendererMeeGo::reset() {
131   QMutexLocker locker(&m_frameMutex);
132   m_frame = -1;
133 }
134
135 GstElement *QtCamViewfinderRendererMeeGo::sinkElement() {
136   if (!QGLContext::currentContext()) {
137     qCritical() << "Cannot create the GStreamer element without an OpenGL context.";
138     return 0;
139   }
140
141   if (!m_sink) {
142     m_sink = gst_element_factory_make(m_conf->viewfinderSink().toAscii().data(),
143                                       "QtCamViewfinderRendererMeeGoSink");
144     if (!m_sink) {
145       qCritical() << "Failed to create" << m_conf->viewfinderSink();
146       return 0;
147     }
148
149     g_object_add_toggle_ref(G_OBJECT(m_sink), (GToggleNotify)sink_notify, this);
150   }
151
152   // Displa can be replaced with a null pointer.
153   // We all know that the sink used for Harmattan ignores the x-display property ;-)
154
155   Display *d = QX11Info::display();
156   g_object_set(G_OBJECT(m_sink), "x-display", d, "use-framebuffer-memory", TRUE, NULL);
157
158   EGLDisplay dpy = eglGetDisplay((EGLNativeDisplayType)d);
159   EGLContext context = eglGetCurrentContext();
160
161   g_object_set(G_OBJECT(m_sink), "egl-display", dpy, "egl-context", context, NULL);
162
163   m_id = g_signal_connect(G_OBJECT(m_sink), "frame-ready", G_CALLBACK(frame_ready), this);
164
165   GstPad *pad = gst_element_get_static_pad(m_sink, "sink");
166   m_notify = g_signal_connect(G_OBJECT(pad), "notify::caps",
167                               G_CALLBACK(sink_caps_changed), this);
168   gst_object_unref(pad);
169
170   return m_sink;
171 }
172
173 void QtCamViewfinderRendererMeeGo::frame_ready(GstElement *sink, int frame,
174                                                QtCamViewfinderRendererMeeGo *r) {
175   Q_UNUSED(sink);
176   Q_UNUSED(frame);
177
178   r->m_frameMutex.lock();
179   r->m_frame = frame;
180   r->m_frameMutex.unlock();
181
182   QMetaObject::invokeMethod(r, "updateRequested", Qt::QueuedConnection);
183 }
184
185 void QtCamViewfinderRendererMeeGo::sink_notify(QtCamViewfinderRendererMeeGo *q,
186                                                GObject *object, gboolean is_last_ref) {
187
188   Q_UNUSED(object);
189
190   if (is_last_ref) {
191     g_signal_handler_disconnect(q->m_sink, q->m_id);
192     g_object_remove_toggle_ref(G_OBJECT(q->m_sink), (GToggleNotify)sink_notify, q);
193     q->m_sink = 0;
194   }
195 }
196
197 void QtCamViewfinderRendererMeeGo::sink_caps_changed(GObject *obj, GParamSpec *pspec,
198                                                      QtCamViewfinderRendererMeeGo *q) {
199   Q_UNUSED(obj);
200   Q_UNUSED(pspec);
201
202   int width, height;
203   if (obj && gst_video_get_size  (GST_PAD(obj), &width, &height)) {
204     QMetaObject::invokeMethod(q, "setVideoSize", Qt::QueuedConnection,
205                               Q_ARG(QSizeF, QSizeF(width, height)));
206   }
207 }
208
209 void QtCamViewfinderRendererMeeGo::calculateProjectionMatrix(const QRectF& rect) {
210   m_projectionMatrix = QMatrix4x4();
211   m_projectionMatrix.ortho(rect);
212 }
213
214 void QtCamViewfinderRendererMeeGo::createProgram() {
215   if (m_program) {
216     delete m_program;
217   }
218
219   m_program = new QGLShaderProgram(this);
220
221   if (!m_program->addShaderFromSourceCode(QGLShader::Vertex, VERTEX_SHADER)) {
222     qCritical() << "Failed to add vertex shader";
223     return;
224   }
225
226   if (!m_program->addShaderFromSourceCode(QGLShader::Fragment, FRAGMENT_SHADER)) {
227     qCritical() << "Failed to add fragment shader";
228     return;
229   }
230
231   m_program->bindAttributeLocation("inputVertex", 0);
232   m_program->bindAttributeLocation("textureCoord", 1);
233
234   if (!m_program->link()) {
235     qCritical() << "Failed to link program!";
236     return;
237   }
238
239   if (!m_program->bind()) {
240     qCritical() << "Failed to bind program";
241     return;
242   }
243
244   m_program->setUniformValue("texture0", 0); // texture UNIT 0
245   m_program->release();
246 }
247
248 void QtCamViewfinderRendererMeeGo::paintFrame(QPainter *painter, int frame) {
249   if (frame == -1) {
250     return;
251   }
252
253   // TODO: sometimes there is an assertion.
254
255   MeegoGstVideoTexture *sink = MEEGO_GST_VIDEO_TEXTURE(m_sink);
256   if (!meego_gst_video_texture_acquire_frame(sink, frame)) {
257     qDebug() << "Failed to acquire frame";
258     return;
259   }
260
261   m_program->bind();
262
263   m_program->setUniformValue("matrix", m_projectionMatrix);
264   m_program->setUniformValue("matrixWorld", QMatrix4x4(painter->combinedTransform()));
265
266   if (!meego_gst_video_texture_bind_frame(sink, GL_TEXTURE_EXTERNAL_OES, frame)) {
267     qDebug() << "Failed to bind frame";
268     m_program->release();
269     return;
270   }
271
272   glEnableVertexAttribArray(0);
273   glEnableVertexAttribArray(1);
274
275   glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, &m_vertexCoords);
276   glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 0, &m_texCoords);
277
278   glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
279
280   if (!meego_gst_video_texture_bind_frame(sink, GL_TEXTURE_EXTERNAL_OES, -1)) {
281     qDebug() << "Failed to unbind frame";
282   }
283
284   glDisableVertexAttribArray(1);
285   glDisableVertexAttribArray(0);
286
287   m_program->release();
288
289   // We are not using fences.
290   // TODO: make it configurable.
291   meego_gst_video_texture_release_frame(sink, frame, 0);
292 }
293
294 void QtCamViewfinderRendererMeeGo::calculateCoords() {
295   if (!m_size.isValid() || !m_videoSize.isValid()) {
296     return;
297   }
298
299   QRectF area = renderArea();
300
301   qreal leftMargin = area.x();
302   qreal topMargin = area.y();
303   QSizeF renderSize = area.size();
304
305   m_vertexCoords[0] = leftMargin;
306   m_vertexCoords[1] = topMargin + renderSize.height();
307
308   m_vertexCoords[2] = renderSize.width() + leftMargin;
309   m_vertexCoords[3] = topMargin + renderSize.height();
310
311   m_vertexCoords[4] = renderSize.width() + leftMargin;
312   m_vertexCoords[5] = topMargin;
313
314   m_vertexCoords[6] = leftMargin;
315   m_vertexCoords[7] = topMargin;
316 }
317
318 QRectF QtCamViewfinderRendererMeeGo::renderArea() {
319   if (!m_renderArea.isNull()) {
320     return m_renderArea;
321   }
322
323   QSizeF renderSize = m_videoSize;
324   renderSize.scale(m_size, Qt::KeepAspectRatio);
325
326   qreal leftMargin = (m_size.width() - renderSize.width())/2.0;
327   qreal topMargin = (m_size.height() - renderSize.height())/2.0;
328
329   m_renderArea = QRectF(QPointF(leftMargin, topMargin), renderSize);
330
331   return m_renderArea;
332 }
333
334 void QtCamViewfinderRendererMeeGo::setVideoSize(const QSizeF& size) {
335   if (size == m_videoSize) {
336     return;
337   }
338
339   m_videoSize = size;
340
341   m_renderArea = QRectF();
342
343   calculateCoords();
344
345   m_needsInit = true;
346
347   emit renderAreaChanged();
348 }