1 /*
2 SPDX-FileCopyrightText: 2020 Mladen Milinkovic <max@smoothware.net>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "glrenderer.h"
8
9 #include <QOpenGLShader>
10 #include <QMutexLocker>
11 #include <QStringBuilder>
12
13 #include "videoplayer/backend/ffplayer.h"
14 #include "videoplayer/videoplayer.h"
15 #include "videoplayer/subtitletextoverlay.h"
16 #include "videoplayer/backend/glcolorspace.h"
17
18 extern "C" {
19 #include "libavutil/pixdesc.h"
20 }
21
22 #define DEBUG_GL 0
23 #define OPENGL_CORE 0
24 #define OPENGL_VER 2,0
25
26 #if DEBUG_GL
27 #define asGL(glCall) glCall; {\
28 GLenum __glErr__ = glGetError(); \
29 if(__glErr__ != GL_NO_ERROR) \
30 qCritical("GL error 0x%04x during %s line %d in @%s", __glErr__, #glCall, __LINE__, __PRETTY_FUNCTION__); \
31 }
32 #else
33 #define asGL(glCall) glCall
34 #endif
35
36 using namespace SubtitleComposer;
37
38 enum { ID_Y, ID_U, ID_V, ID_OVR, ID_SIZE };
39 enum { AV_POS, AV_VIDTEX, AV_OVRTEX, A_SIZE };
40
GLRenderer(QWidget * parent)41 GLRenderer::GLRenderer(QWidget *parent)
42 : QOpenGLWidget(parent),
43 m_overlay(nullptr),
44 m_mmOvr(nullptr),
45 m_bufYUV(nullptr),
46 m_mmYUV(nullptr),
47 m_bufSize(0),
48 m_bufWidth(0),
49 m_bufHeight(0),
50 m_crWidth(0),
51 m_crHeight(0),
52 m_csNeedInit(true),
53 m_vertShader(nullptr),
54 m_fragShader(nullptr),
55 m_shaderProg(nullptr),
56 m_texNeedInit(true),
57 m_idTex(nullptr),
58 m_vaBuf(nullptr)
59 {
60 setUpdateBehavior(NoPartialUpdate);
61 }
62
~GLRenderer()63 GLRenderer::~GLRenderer()
64 {
65 makeCurrent();
66 if(m_idTex) {
67 asGL(glDeleteTextures(ID_SIZE, m_idTex));
68 delete[] m_idTex;
69 }
70 if(m_vaBuf) {
71 asGL(glDeleteBuffers(A_SIZE, m_vaBuf));
72 delete[] m_vaBuf;
73 }
74 m_vao.destroy();
75 doneCurrent();
76 delete[] m_bufYUV;
77 delete[] m_mmYUV;
78 delete[] m_mmOvr;
79 }
80
81 void
setupProfile()82 GLRenderer::setupProfile()
83 {
84 QSurfaceFormat format(QSurfaceFormat::defaultFormat());
85 format.setVersion(OPENGL_VER);
86 #if DEBUG_GL
87 format.setOption(QSurfaceFormat::DebugContext);
88 #endif
89 #if OPENGL_CORE
90 format.setProfile(QSurfaceFormat::CoreProfile);
91 #endif
92 QSurfaceFormat::setDefaultFormat(format);
93 }
94
95 void
setOverlay(SubtitleTextOverlay * overlay)96 GLRenderer::setOverlay(SubtitleTextOverlay *overlay)
97 {
98 if(m_overlay)
99 disconnect(m_overlay, nullptr, this, nullptr);
100 m_overlay = overlay;
101 connect(m_overlay, &SubtitleTextOverlay::repaintNeeded, this, QOverload<>::of(&GLRenderer::update));
102 }
103
104 void
reset()105 GLRenderer::reset()
106 {
107 m_csNeedInit = true;
108 m_texNeedInit = true;
109 }
110
111 void
setFrameFormat(int width,int height,int compBits,int crWidthShift,int crHeightShift)112 GLRenderer::setFrameFormat(int width, int height, int compBits, int crWidthShift, int crHeightShift)
113 {
114 const quint8 compBytes = compBits > 8 ? 2 : 1;
115 const GLsizei crWidth = AV_CEIL_RSHIFT(width, crWidthShift);
116 const GLsizei crHeight = AV_CEIL_RSHIFT(height, crHeightShift);
117 const quint32 bufSize = width * height * compBytes + 2 * crWidth * crHeight * compBytes;
118
119 if(m_bufWidth == width && m_bufHeight == height
120 && m_bufSize == bufSize && m_crWidth == crWidth && m_crHeight == crHeight)
121 return;
122
123 m_bufWidth = width;
124 m_bufHeight = height;
125 m_crWidth = crWidth;
126 m_crHeight = crHeight;
127
128 m_glType = compBytes == 1 ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT;
129 m_glFormat = compBytes == 1 ? GL_R8 : GL_R16;
130
131 delete[] m_bufYUV;
132 m_bufSize = bufSize;
133 m_bufYUV = new quint8[m_bufSize];
134
135 delete[] m_mmYUV;
136 m_mmYUV = new quint8[(m_bufWidth >> 1) * (m_bufHeight >> 1) * compBytes];
137
138 m_overlay->setImageSize(width, height);
139 delete[] m_mmOvr;
140 m_mmOvr = new quint8[(m_overlay->width() >> 1) * (m_overlay->height() >> 1) * 4];
141
142 m_pitch[0] = m_bufWidth * compBytes;
143 m_pitch[1] = m_pitch[2] = m_crWidth * compBytes;
144
145 m_pixels[0] = m_bufYUV;
146 m_pixels[1] = m_pixels[0] + m_pitch[0] * m_bufHeight;
147 m_pixels[2] = m_pixels[1] + m_pitch[1] * m_crHeight;
148
149 m_texNeedInit = true;
150 m_csNeedInit = true;
151
152 emit resolutionChanged();
153 }
154
155 void
setColorspace(const AVFrame * frame)156 GLRenderer::setColorspace(const AVFrame *frame)
157 {
158 if(!m_csNeedInit)
159 return;
160
161 const AVPixFmtDescriptor *fd = av_pix_fmt_desc_get(AVPixelFormat(frame->format));
162 const quint8 compBits = fd->comp[0].depth;
163 const quint8 compBytes = compBits > 8 ? 2 : 1;
164 const bool isYUV = ~fd->flags & AV_PIX_FMT_FLAG_RGB;
165
166 qDebug("Color range: %s(%d); primaries: %s(%d); xfer: %s(%d); space: %s(%d); depth: %d",
167 av_color_range_name(frame->color_range), frame->color_range,
168 av_color_primaries_name(frame->color_primaries), frame->color_primaries,
169 av_color_transfer_name(frame->color_trc), frame->color_trc,
170 av_color_space_name(frame->colorspace), frame->colorspace,
171 compBits);
172
173 // gamma/transfer function
174 auto ctfit = _ctfi.constFind(frame->color_trc);
175 if(ctfit == _ctfi.constEnd())
176 ctfit = _ctfi.constFind(AVCOL_TRC_BT709);
177 Q_ASSERT(ctfit != _ctfi.constEnd());
178 m_ctfIn = ctfit.value();
179
180 ctfit = _ctf.constFind(AVCOL_TRC_IEC61966_2_1); // sRGB
181 Q_ASSERT(ctfit != _ctf.constEnd());
182 m_ctfOut = ctfit.value();
183
184 // colorspace conversion
185 QMap<int, QVector<GLfloat>>::const_iterator cs;
186 if((cs = _csm.constFind(frame->color_primaries)) != _csm.constEnd()) {
187 m_csCM = QMatrix4x4(cs.value().constData(), 3, 3);
188 } else if((cs = _csc.constFind(frame->colorspace)) != _csc.constEnd()) {
189 m_csCM = QMatrix4x4(cs.value().constData(), 3, 3);
190 } else if(isYUV) {
191 cs = _csm.constFind(AVCOL_PRI_BT709);
192 m_csCM = QMatrix4x4(cs.value().constData(), 3, 3);
193 } else {
194 m_csCM = QMatrix4x4();
195 }
196
197 if(isYUV) {
198 if(frame->color_range == AVCOL_RANGE_MPEG || frame->color_range == AVCOL_RANGE_UNSPECIFIED) {
199 // convert to full range and offset UV channels
200 m_csCM.scale(255.0f / (219.0f + 16.0f), 255.0f / 224.0f, 255.0f / 224.0f);
201 m_csCM.translate(-16.0f / 255.0f, -128.0f / 255.0f, -128.0f / 255.0f);
202 } else {
203 // offset signed UV channels
204 m_csCM.translate(0.0f, -128.0f / 255.0f, -128.0f / 255.0f);
205 }
206 }
207
208 // scale to full range when some bits are unused
209 const float pixMult = double(1 << (compBytes << 3)) / double(1 << compBits);
210 m_csCM.scale(pixMult, pixMult, pixMult);
211 }
212
213 void
setFrameY(quint8 * buf,quint32 pitch)214 GLRenderer::setFrameY(quint8 *buf, quint32 pitch)
215 {
216 if(pitch == m_pitch[0]) {
217 memcpy(m_pixels[0], buf, pitch * m_bufHeight);
218 } else {
219 quint8 *dbuf = m_pixels[0];
220 for(int i = 0; i < m_bufHeight; i++) {
221 memcpy(dbuf, buf, m_pitch[0]);
222 dbuf += m_pitch[0];
223 buf += pitch;
224 }
225 }
226 }
227
228 void
setFrameU(quint8 * buf,quint32 pitch)229 GLRenderer::setFrameU(quint8 *buf, quint32 pitch)
230 {
231 if(pitch == m_pitch[1]) {
232 memcpy(m_pixels[1], buf, pitch * m_crHeight);
233 } else {
234 quint8 *dbuf = m_pixels[1];
235 for(int i = 0; i < m_crHeight; i++) {
236 memcpy(dbuf, buf, m_pitch[1]);
237 dbuf += m_pitch[1];
238 buf += pitch;
239 }
240 }
241 }
242
243 void
setFrameV(quint8 * buf,quint32 pitch)244 GLRenderer::setFrameV(quint8 *buf, quint32 pitch)
245 {
246 if(pitch == m_pitch[2]) {
247 memcpy(m_pixels[2], buf, pitch * m_crHeight);
248 } else {
249 quint8 *dbuf = m_pixels[2];
250 for(int i = 0; i < m_crHeight; i++) {
251 memcpy(dbuf, buf, m_pitch[2]);
252 dbuf += m_pitch[2];
253 buf += pitch;
254 }
255 }
256 }
257
258 void
initShader()259 GLRenderer::initShader()
260 {
261 delete m_vertShader;
262 m_vertShader = new QOpenGLShader(QOpenGLShader::Vertex, this);
263 bool success = m_vertShader->compileSourceCode(
264 "#version 120\n"
265 "attribute vec4 vPos;"
266 "attribute vec2 vVidTex;"
267 "attribute vec2 vOvrTex;"
268 "varying vec2 vfVidTex;"
269 "varying vec2 vfOvrTex;"
270 "void main(void) {"
271 "gl_Position = vPos;"
272 "vfVidTex = vVidTex;"
273 "vfOvrTex = vOvrTex;"
274 "}");
275 if(!success) {
276 qCritical() << "GLRenderer: vertex shader compilation failed";
277 return;
278 }
279
280 delete m_fragShader;
281 m_fragShader = new QOpenGLShader(QOpenGLShader::Fragment, this);
282 // asGL(glUniformMatrix4fv(m_texCSLoc, 1, GL_FALSE, m_csCM.constData()));
283
284 const float *csm = m_csCM.constData();
285 QString csms;
286 for(int i = 0; i < 16; i++) {
287 if(i) csms.append(QLatin1Char(','));
288 csms.append(QString::number(csm[i], 'g', 10));
289 }
290
291 success = m_fragShader->compileSourceCode(QStringLiteral("#version 120\n"
292 "varying vec2 vfVidTex;"
293 "varying vec2 vfOvrTex;"
294 "uniform sampler2D texY;"
295 "uniform sampler2D texU;"
296 "uniform sampler2D texV;"
297 "uniform sampler2D texOvr;"
298 "float toLinear(float vExp) {") % m_ctfIn % QStringLiteral("}"
299 "float toDisplay(float vLin) {") % m_ctfOut % QStringLiteral("}"
300 "void main(void) {"
301 "vec3 yuv;"
302 "yuv.x = texture2D(texY, vfVidTex).r;"
303 "yuv.y = texture2D(texU, vfVidTex).r;"
304 "yuv.z = texture2D(texV, vfVidTex).r;"
305 "mat4 texCS = mat4(") % csms % QStringLiteral(");"
306 "vec3 rgb = (texCS * vec4(yuv, 1)).xyz;"
307 // ideally gamma would be applied to Y, but video signals apply to RGB so we do the same
308 "rgb.r = toLinear(rgb.r);"
309 "rgb.g = toLinear(rgb.g);"
310 "rgb.b = toLinear(rgb.b);"
311 // apply display (sRGB) gamma transfer
312 "rgb.r = toDisplay(rgb.r);"
313 "rgb.g = toDisplay(rgb.g);"
314 "rgb.b = toDisplay(rgb.b);"
315 "vec4 o = texture2D(texOvr, vfOvrTex);"
316 "gl_FragColor = vec4(mix(rgb, o.rgb, o.w), 1);"
317 "}"));
318 if(!success) {
319 qCritical() << "GLRenderer: fragment shader compilation failed";
320 return;
321 }
322
323 delete m_shaderProg;
324 m_shaderProg = new QOpenGLShaderProgram(this);
325 m_shaderProg->addShader(m_fragShader);
326 m_shaderProg->addShader(m_vertShader);
327
328 m_shaderProg->bindAttributeLocation("vPos", AV_POS);
329 m_shaderProg->bindAttributeLocation("vVidTex", AV_VIDTEX);
330 m_shaderProg->bindAttributeLocation("vOvrTex", AV_OVRTEX);
331 if(!m_shaderProg->link()) {
332 qCritical() << "GLRenderer: shader linking failed:" << m_shaderProg->log();
333 return;
334 }
335 m_shaderProg->bind();
336
337 m_texY = m_shaderProg->uniformLocation("texY");
338 m_texU = m_shaderProg->uniformLocation("texU");
339 m_texV = m_shaderProg->uniformLocation("texV");
340 m_texOvr = m_shaderProg->uniformLocation("texOvr");
341
342 m_csNeedInit = true;
343 }
344
345 void
initializeGL()346 GLRenderer::initializeGL()
347 {
348 QMutexLocker l(&m_texMutex);
349
350 initializeOpenGLFunctions();
351 qDebug() << "OpenGL version: " << reinterpret_cast<const char *>(glGetString(GL_VERSION));
352 qDebug() << "GLSL version: " << reinterpret_cast<const char *>(glGetString(GL_SHADING_LANGUAGE_VERSION));
353
354 if(m_vao.create())
355 m_vao.bind();
356
357 if(m_vaBuf) {
358 asGL(glDeleteBuffers(A_SIZE, m_vaBuf));
359 } else {
360 delete[] m_vaBuf;
361 }
362 m_vaBuf = new GLuint[A_SIZE];
363 asGL(glGenBuffers(A_SIZE, m_vaBuf));
364
365 {
366 static const GLfloat v[] = {
367 -1.0f, 1.0f,
368 1.0f, 1.0f,
369 -1.0f, -1.0f,
370 1.0f, -1.0f,
371 };
372 asGL(glBindBuffer(GL_ARRAY_BUFFER, m_vaBuf[AV_POS]));
373 asGL(glBufferData(GL_ARRAY_BUFFER, sizeof(v), v, GL_STATIC_DRAW));
374 asGL(glVertexAttribPointer(AV_POS, 2, GL_FLOAT, GL_FALSE, 0, nullptr));
375 asGL(glEnableVertexAttribArray(AV_POS));
376 }
377
378 {
379 static const GLfloat v[] = {
380 0.0f, 0.0f,
381 1.0f, 0.0f,
382 0.0f, 1.0f,
383 1.0f, 1.0f,
384 };
385 asGL(glBindBuffer(GL_ARRAY_BUFFER, m_vaBuf[AV_VIDTEX]));
386 asGL(glBufferData(GL_ARRAY_BUFFER, sizeof(v), v, GL_STATIC_DRAW));
387 asGL(glVertexAttribPointer(AV_VIDTEX, 2, GL_FLOAT, GL_FALSE, 0, nullptr));
388 asGL(glEnableVertexAttribArray(AV_VIDTEX));
389 }
390
391 asGL(glBindBuffer(GL_ARRAY_BUFFER, m_vaBuf[AV_OVRTEX]));
392 asGL(glVertexAttribPointer(AV_OVRTEX, 2, GL_FLOAT, GL_FALSE, 0, nullptr));
393 asGL(glEnableVertexAttribArray(AV_OVRTEX));
394
395 if(m_idTex) {
396 asGL(glDeleteTextures(ID_SIZE, m_idTex));
397 } else {
398 m_idTex = new GLuint[ID_SIZE];
399 }
400 asGL(glGenTextures(ID_SIZE, m_idTex));
401
402 asGL(glClearColor(.0f, .0f, .0f, .0f)); // background color
403
404 asGL(glDisable(GL_DEPTH_TEST));
405
406 m_texNeedInit = true;
407 m_csNeedInit = true;
408 }
409
410 void
resizeGL(int width,int height)411 GLRenderer::resizeGL(int width, int height)
412 {
413 QMutexLocker l(&m_texMutex);
414 asGL(glViewport(0, 0, width, height));
415 m_vpWidth = width;
416 m_vpHeight = height;
417 m_texNeedInit = true;
418 update();
419 }
420
421 void
paintGL()422 GLRenderer::paintGL()
423 {
424 QMutexLocker l(&m_texMutex);
425
426 glClear(GL_COLOR_BUFFER_BIT);
427
428 if(!m_bufYUV)
429 return;
430
431 if(m_texNeedInit) {
432 asGL(glPixelStorei(GL_UNPACK_ALIGNMENT, m_bufWidth % 4 == 0 ? 4 : 1));
433 }
434 if(m_csNeedInit)
435 initShader();
436
437 uploadYUV();
438 uploadSubtitle();
439
440 asGL(glDrawArrays(GL_TRIANGLE_STRIP, 0, 4));
441
442 m_texNeedInit = false;
443 m_csNeedInit = false;
444 }
445
446 template<class T, int D>
447 void
uploadMM(int texWidth,int texHeight,T * texBuf,const T * texSrc)448 GLRenderer::uploadMM(int texWidth, int texHeight, T *texBuf, const T *texSrc)
449 {
450 int level = 0;
451 for(;;) {
452 if(m_texNeedInit) {
453 if(D == 1) {
454 asGL(glTexImage2D(GL_TEXTURE_2D, level, m_glFormat, texWidth, texHeight, 0, GL_RED, m_glType, texSrc));
455 } else { // D == 4
456 asGL(glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, texWidth, texHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, texSrc));
457 }
458 } else {
459 if(D == 1) {
460 asGL(glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, texWidth, texHeight, GL_RED, m_glType, texSrc));
461 } else { // D == 4
462 asGL(glTexSubImage2D(GL_TEXTURE_2D, level, 0, 0, texWidth, texHeight, GL_BGRA, GL_UNSIGNED_BYTE, texSrc));
463 }
464 }
465
466 const int srcStride = texWidth * D;
467 const int srcStridePD = srcStride + D;
468 texWidth >>= 1;
469 texHeight >>= 1;
470 if(texWidth < m_vpWidth && texHeight < m_vpHeight) {
471 if(m_texNeedInit) {
472 asGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR));
473 asGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
474 asGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0));
475 asGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, level));
476 }
477 break;
478 }
479 level++;
480
481 T *dst = texBuf;
482 const int texStride = texWidth * D;
483 for(int y = 0; y < texHeight; y++) {
484 const T *dstEnd = dst + texStride;
485 while(dst != dstEnd) {
486 if(D == 1) { // if should get optimized away
487 *dst++ = (texSrc[0] + texSrc[D] + texSrc[srcStride] + texSrc[srcStridePD]) >> 2;
488 texSrc += 2;
489 } else {
490 *dst++ = (texSrc[0] + texSrc[D] + texSrc[srcStride] + texSrc[srcStridePD]) >> 2;
491 texSrc++;
492 *dst++ = (texSrc[0] + texSrc[D] + texSrc[srcStride] + texSrc[srcStridePD]) >> 2;
493 texSrc++;
494 *dst++ = (texSrc[0] + texSrc[D] + texSrc[srcStride] + texSrc[srcStridePD]) >> 2;
495 texSrc++;
496 *dst++ = (texSrc[0] + texSrc[D] + texSrc[srcStride] + texSrc[srcStridePD]) >> 2;
497 texSrc += D + 1;
498 }
499 }
500 texSrc += srcStride;
501 }
502 texSrc = texBuf;
503 }
504 }
505
506 void
uploadYUV()507 GLRenderer::uploadYUV()
508 {
509 // load Y data
510 asGL(glActiveTexture(GL_TEXTURE0 + ID_Y));
511 asGL(glBindTexture(GL_TEXTURE_2D, m_idTex[ID_Y]));
512 if(m_glType == GL_UNSIGNED_BYTE)
513 uploadMM<quint8, 1>(m_bufWidth, m_bufHeight, m_mmYUV, m_pixels[0]);
514 else
515 uploadMM<quint16, 1>(m_bufWidth, m_bufHeight, reinterpret_cast<quint16 *>(m_mmYUV), reinterpret_cast<quint16 *>(m_pixels[0]));
516 if(m_texNeedInit) {
517 asGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
518 asGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
519 asGL(glUniform1i(m_texY, ID_Y));
520 }
521
522 // load U data
523 asGL(glActiveTexture(GL_TEXTURE0 + ID_U));
524 asGL(glBindTexture(GL_TEXTURE_2D, m_idTex[ID_U]));
525 if(m_glType == GL_UNSIGNED_BYTE)
526 uploadMM<quint8, 1>(m_crWidth, m_crHeight, m_mmYUV, m_pixels[1]);
527 else
528 uploadMM<quint16, 1>(m_crWidth, m_crHeight, reinterpret_cast<quint16 *>(m_mmYUV), reinterpret_cast<quint16 *>(m_pixels[1]));
529 if(m_texNeedInit) {
530 asGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
531 asGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
532 asGL(glUniform1i(m_texU, ID_U));
533 }
534
535 // load V data
536 asGL(glActiveTexture(GL_TEXTURE0 + ID_V));
537 asGL(glBindTexture(GL_TEXTURE_2D, m_idTex[ID_V]));
538 if(m_glType == GL_UNSIGNED_BYTE)
539 uploadMM<quint8, 1>(m_crWidth, m_crHeight, m_mmYUV, m_pixels[2]);
540 else
541 uploadMM<quint16, 1>(m_crWidth, m_crHeight, reinterpret_cast<quint16 *>(m_mmYUV), reinterpret_cast<quint16 *>(m_pixels[2]));
542 if(m_texNeedInit) {
543 asGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
544 asGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
545 asGL(glUniform1i(m_texV, ID_V));
546 }
547 }
548
549 void
uploadSubtitle()550 GLRenderer::uploadSubtitle()
551 {
552 if(!m_texNeedInit && !m_overlay->isDirty())
553 return;
554
555 const QImage &img = m_overlay->image();
556
557 const float ratio = float(img.height()) / float(m_bufHeight);
558 const float scaleV = m_overlay->renderScale() - 1.0f;
559 const float scaleH = scaleV * ratio / 2.0f;
560
561 // fix subtitle aspect ratio as image heights can differ
562 const float ratioDiff = (ratio - 1.) / 2.0f;
563 m_overlayPos[0] = m_overlayPos[4] = 0.0f - ratioDiff + scaleH;
564 m_overlayPos[2] = m_overlayPos[6] = 1.0f + ratioDiff - scaleH;
565
566 // subtitle offset + margin
567 const float hr = float(m_overlay->textSize().height()) / float(img.height()) + .03f;
568 m_overlayPos[1] = m_overlayPos[3] = -1.0f + hr + scaleV;
569 m_overlayPos[5] = m_overlayPos[7] = hr;
570
571 asGL(glBindBuffer(GL_ARRAY_BUFFER, m_vaBuf[AV_OVRTEX]));
572 asGL(glBufferData(GL_ARRAY_BUFFER, sizeof(m_overlayPos), m_overlayPos, GL_DYNAMIC_DRAW));
573 asGL(glBindBuffer(GL_ARRAY_BUFFER, 0));
574
575 // overlay
576 asGL(glActiveTexture(GL_TEXTURE0 + ID_OVR));
577 asGL(glBindTexture(GL_TEXTURE_2D, m_idTex[ID_OVR]));
578 uploadMM<quint8, 4>(img.width(), img.height(), m_mmOvr, img.bits());
579 if(m_texNeedInit) {
580 asGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER));
581 asGL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER));
582 static const float borderColor[] = { .0f, .0f, .0f, .0f };
583 asGL(glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor));
584 asGL(glUniform1i(m_texOvr, ID_OVR));
585 }
586 }
587