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