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