MythTV master
mythrenderopengl.cpp
Go to the documentation of this file.
1// Std
2#include <algorithm>
3#include <cmath>
4
5// Qt
6#include <QtGlobal>
7#if QT_VERSION >= QT_VERSION_CHECK(6,5,0)
8#include <QtEnvironmentVariables>
9#endif
10#include <QLibrary>
11#include <QPainter>
12#include <QWindow>
13#include <QWidget>
14#include <QGuiApplication>
15
16// MythTV
17#include "libmythbase/mythconfig.h"
20
21#include "mythmainwindow.h"
22#include "mythrenderopengl.h"
24#include "mythuitype.h"
25#if CONFIG_X11
27#endif
28
29#define LOC QString("OpenGL: ")
30
31static constexpr GLuint VERTEX_INDEX { 0 };
32static constexpr GLuint COLOR_INDEX { 1 };
33static constexpr GLuint TEXTURE_INDEX { 2 };
34static constexpr GLint VERTEX_SIZE { 2 };
35static constexpr GLint TEXTURE_SIZE { 2 };
36
37static constexpr GLuint kVertexOffset { 0 };
38static constexpr GLuint kTextureOffset { 8 * sizeof(GLfloat) };
39
40static constexpr int MAX_VERTEX_CACHE { 500 };
41
42MythGLTexture::MythGLTexture(QOpenGLTexture *Texture)
43 : m_texture(Texture)
44{
45}
46
48 : m_textureId(Texture)
49{
50}
51
53 : m_render(Render)
54{
55 if (m_render)
57}
58
60{
61 if (m_render)
63}
64
66{
67 // Don't try and create the window
68 if (!HasMythMainWindow())
69 return nullptr;
70
72 if (!window)
73 return nullptr;
74
75 auto* result = dynamic_cast<MythRenderOpenGL*>(window->GetRenderDevice());
76 if (result)
77 return result;
78 return nullptr;
79}
80
82{
83 if (!Widget)
84 return nullptr;
85
86#if CONFIG_X11
88 {
89 LOG(VB_GENERAL, LOG_WARNING, LOC + "OpenGL is disabled for Remote X Session");
90 return nullptr;
91 }
92#endif
93
94 // N.B the core profiles below are designed to target compute shader availability
95 bool opengles = !qEnvironmentVariableIsEmpty("MYTHTV_OPENGL_ES");
96 bool core = !qEnvironmentVariableIsEmpty("MYTHTV_OPENGL_CORE");
97 QSurfaceFormat format = QSurfaceFormat::defaultFormat();
98 if (core)
99 {
100 format.setProfile(QSurfaceFormat::CoreProfile);
101 format.setMajorVersion(4);
102 format.setMinorVersion(3);
103 }
104
105 if (opengles)
106 {
107 if (core)
108 {
109 format.setProfile(QSurfaceFormat::CoreProfile);
110 format.setMajorVersion(3);
111 format.setMinorVersion(1);
112 }
113 format.setRenderableType(QSurfaceFormat::OpenGLES);
114 }
115
116 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
117 format.setOption(QSurfaceFormat::DebugContext);
118
119 return new MythRenderOpenGL(format, Widget);
120}
121
122MythRenderOpenGL::MythRenderOpenGL(const QSurfaceFormat& Format, QWidget *Widget)
123 : MythEGL(this),
125 m_fullRange(gCoreContext->GetBoolSetting("GUIRGBLevels", true))
126{
127 m_projection.fill(0);
128 m_parameters.fill(0);
129 m_transforms.push(QMatrix4x4());
130 setFormat(Format);
131 connect(this, &QOpenGLContext::aboutToBeDestroyed, this, &MythRenderOpenGL::contextToBeDestroyed);
132 SetWidget(Widget);
133}
134
136{
137 LOG(VB_GENERAL, LOG_INFO, LOC + "MythRenderOpenGL closing");
138 if (!isValid())
139 return;
140 disconnect(this, &QOpenGLContext::aboutToBeDestroyed, this, &MythRenderOpenGL::contextToBeDestroyed);
141 if (m_ready)
143}
144
145void MythRenderOpenGL::MessageLogged(const QOpenGLDebugMessage &Message)
146{
147 // filter unwanted messages
148 if ((m_openGLDebuggerFilter & Message.type()) != 0U)
149 return;
150
151 QString source("Unknown");
152 QString type("Unknown");
153
154 switch (Message.source())
155 {
156 case QOpenGLDebugMessage::ApplicationSource: return; // filter out our own messages
157 case QOpenGLDebugMessage::APISource: source = "API"; break;
158 case QOpenGLDebugMessage::WindowSystemSource: source = "WinSys"; break;
159 case QOpenGLDebugMessage::ShaderCompilerSource: source = "ShaderComp"; break;
160 case QOpenGLDebugMessage::ThirdPartySource: source = "3rdParty"; break;
161 case QOpenGLDebugMessage::OtherSource: source = "Other"; break;
162 default: break;
163 }
164
165 // N.B. each break is on a separate line to allow setting individual break points
166 // when using synchronous logging
167 switch (Message.type())
168 {
169 case QOpenGLDebugMessage::ErrorType:
170 type = "Error"; break;
171 case QOpenGLDebugMessage::DeprecatedBehaviorType:
172 type = "Deprecated"; break;
173 case QOpenGLDebugMessage::UndefinedBehaviorType:
174 type = "Undef behaviour"; break;
175 case QOpenGLDebugMessage::PortabilityType:
176 type = "Portability"; break;
177 case QOpenGLDebugMessage::PerformanceType:
178 type = "Performance"; break;
179 case QOpenGLDebugMessage::OtherType:
180 type = "Other"; break;
181 case QOpenGLDebugMessage::MarkerType:
182 type = "Marker"; break;
183 case QOpenGLDebugMessage::GroupPushType:
184 type = "GroupPush"; break;
185 case QOpenGLDebugMessage::GroupPopType:
186 type = "GroupPop"; break;
187 default: break;
188 }
189 LOG(VB_GPU, LOG_INFO, LOC + QString("Src: %1 Type: %2 Msg: %3")
190 .arg(source, type, Message.message()));
191}
192
193void MythRenderOpenGL:: logDebugMarker(const QString &Message)
194{
196 {
197 QOpenGLDebugMessage message = QOpenGLDebugMessage::createApplicationMessage(
198 Message, 0, QOpenGLDebugMessage::NotificationSeverity, QOpenGLDebugMessage::MarkerType);
199 m_openglDebugger->logMessage(message);
200 }
201}
202
203// Can't be static because its connected to a signal and passed "this".
204// NOLINTNEXTLINE(readability-convert-member-functions-to-static)
206{
207 LOG(VB_GENERAL, LOG_WARNING, LOC + "Context about to be destroyed");
208}
209
211{
212 if (!isValid())
213 {
214 LOG(VB_GENERAL, LOG_ERR, LOC + "MythRenderOpenGL is not a valid OpenGL rendering context");
215 return false;
216 }
217
218 OpenGLLocker locker(this);
219 initializeOpenGLFunctions();
220 m_ready = true;
221 m_features = openGLFeatures();
222
223 // don't enable this by default - it can generate a lot of detail
224 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
225 {
226 m_openglDebugger = new QOpenGLDebugLogger();
227 if (m_openglDebugger->initialize())
228 {
229 connect(m_openglDebugger, &QOpenGLDebugLogger::messageLogged, this, &MythRenderOpenGL::MessageLogged);
230 QOpenGLDebugLogger::LoggingMode mode = QOpenGLDebugLogger::AsynchronousLogging;
231
232 // this will impact performance but can be very useful
233 if (!qEnvironmentVariableIsEmpty("MYTHTV_OPENGL_SYNCHRONOUS"))
234 mode = QOpenGLDebugLogger::SynchronousLogging;
235
236 m_openglDebugger->startLogging(mode);
237 if (mode == QOpenGLDebugLogger::AsynchronousLogging)
238 LOG(VB_GENERAL, LOG_INFO, LOC + "GPU debug logging started (async)");
239 else
240 LOG(VB_GENERAL, LOG_INFO, LOC + "Started synchronous GPU debug logging (will hurt performance)");
241
242 // filter messages. Some drivers can be extremely verbose for certain issues.
243 QStringList debug;
244 QString filter = qgetenv("MYTHTV_OPENGL_LOGFILTER");
245 if (filter.contains("other", Qt::CaseInsensitive))
246 {
247 m_openGLDebuggerFilter |= QOpenGLDebugMessage::OtherType;
248 debug << "Other";
249 }
250 if (filter.contains("error", Qt::CaseInsensitive))
251 {
252 m_openGLDebuggerFilter |= QOpenGLDebugMessage::ErrorType;
253 debug << "Error";
254 }
255 if (filter.contains("deprecated", Qt::CaseInsensitive))
256 {
257 m_openGLDebuggerFilter |= QOpenGLDebugMessage::DeprecatedBehaviorType;
258 debug << "Deprecated";
259 }
260 if (filter.contains("undefined", Qt::CaseInsensitive))
261 {
262 m_openGLDebuggerFilter |= QOpenGLDebugMessage::UndefinedBehaviorType;
263 debug << "Undefined";
264 }
265 if (filter.contains("portability", Qt::CaseInsensitive))
266 {
267 m_openGLDebuggerFilter |= QOpenGLDebugMessage::PortabilityType;
268 debug << "Portability";
269 }
270 if (filter.contains("performance", Qt::CaseInsensitive))
271 {
272 m_openGLDebuggerFilter |= QOpenGLDebugMessage::PerformanceType;
273 debug << "Performance";
274 }
275 if (filter.contains("grouppush", Qt::CaseInsensitive))
276 {
277 m_openGLDebuggerFilter |= QOpenGLDebugMessage::GroupPushType;
278 debug << "GroupPush";
279 }
280 if (filter.contains("grouppop", Qt::CaseInsensitive))
281 {
282 m_openGLDebuggerFilter |= QOpenGLDebugMessage::GroupPopType;
283 debug << "GroupPop";
284 }
285
286 if (!debug.isEmpty())
287 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Filtering out GPU messages for: %1")
288 .arg(debug.join(", ")));
289 }
290 else
291 {
292 LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to initialise OpenGL logging");
293 delete m_openglDebugger;
294 m_openglDebugger = nullptr;
295 }
296 }
297
298 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
299 logDebugMarker("RENDER_INIT_START");
300
301 Init2DState();
302
303 // basic features
304 GLint maxtexsz = 0;
305 GLint maxunits = 0;
306 glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxtexsz);
307 glGetIntegerv(GL_MAX_COMBINED_TEXTURE_IMAGE_UNITS, &maxunits);
308 m_maxTextureUnits = maxunits;
309 m_maxTextureSize = maxtexsz ? maxtexsz : 512;
310 QSurfaceFormat fmt = format();
311
312 // Pixel buffer objects
313 bool buffer_procs = reinterpret_cast<MYTH_GLMAPBUFFERPROC>(GetProcAddress("glMapBuffer")) &&
314 reinterpret_cast<MYTH_GLUNMAPBUFFERPROC>(GetProcAddress("glUnmapBuffer"));
315
316 // Buffers are available by default (GL and GLES).
317 // Buffer mapping is available by extension
318 if ((isOpenGLES() && hasExtension("GL_OES_mapbuffer") && buffer_procs) ||
319 (hasExtension("GL_ARB_vertex_buffer_object") && buffer_procs))
321
322 // Rectangular textures
323 if (!isOpenGLES() && (hasExtension("GL_NV_texture_rectangle") ||
324 hasExtension("GL_ARB_texture_rectangle") ||
325 hasExtension("GL_EXT_texture_rectangle")))
327
328 // GL_RED etc texure formats. Not available on GLES2.0 or GL < 2
329 if ((isOpenGLES() && format().majorVersion() < 3) ||
330 (!isOpenGLES() && format().majorVersion() < 2))
332
333 // GL_UNPACK_ROW_LENGTH - for uploading video textures
334 // Note: Should also be available on GL1.4 per specification
335 if (!isOpenGLES() || (isOpenGLES() && ((fmt.majorVersion() >= 3) || hasExtension("GL_EXT_unpack_subimage"))))
337
338 // check for core profile N.B. not OpenGL ES
339 if (fmt.profile() == QSurfaceFormat::OpenGLContextProfile::CoreProfile)
340 {
341 // if we have a core profile then we need a VAO bound - this is just a
342 // workaround for the time being
343 extraFunctions()->glGenVertexArrays(1, &m_vao);
344 extraFunctions()->glBindVertexArray(m_vao);
345 }
346
347 // For (embedded) GPUs that use tile based rendering, it is faster to use
348 // glClear e.g. on the Pi3 it improves video frame rate significantly. Using
349 // glClear tells the GPU it doesn't have to retrieve the old framebuffer and will
350 // also clear existing draw calls.
351 // For now this just includes Broadcom VideoCoreIV.
352 // Other Tile Based Deferred Rendering GPUS - PowerVR5/6/7, Apple (PowerVR as well?)
353 // Other Tile Based Immediate Mode Rendering GPUS - ARM Mali, Qualcomm Adreno
354 static const std::array<const QByteArray,3> kTiled { "videocore", "vc4", "v3d" };
355 auto renderer = QByteArray(reinterpret_cast<const char*>(glGetString(GL_RENDERER))).toLower();
356 for (const auto & name : kTiled)
357 {
358 if (renderer.contains(name))
359 {
361 break;
362 }
363 }
364
365 // Check for memory extensions
366 if (hasExtension("GL_NVX_gpu_memory_info"))
368
369 // Check 16 bit FBOs
371
372 // Check for compute and geometry shaders
373 if (QOpenGLShader::hasOpenGLShaders(QOpenGLShader::Compute))
375 if (QOpenGLShader::hasOpenGLShaders(QOpenGLShader::Geometry))
377
379
382 {
383 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create default shaders");
384 return false;
385 }
386
387 LOG(VB_GENERAL, LOG_INFO, LOC + "Initialised MythRenderOpenGL");
388 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Using %1 range output").arg(m_fullRange ? "full" : "limited"));
389 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
390 logDebugMarker("RENDER_INIT_END");
391 return true;
392}
393
394static constexpr QLatin1String GLYesNo (bool v)
395{
396 return v ? QLatin1String("Yes") : QLatin1String("No");
397}
398
400{
401 QSurfaceFormat fmt = format();
402 QString qtglversion = QString("OpenGL%1 %2.%3")
403 .arg(fmt.renderableType() == QSurfaceFormat::OpenGLES ? "ES" : "")
404 .arg(fmt.majorVersion()).arg(fmt.minorVersion());
405 QString qtglsurface = QString("RGBA: %1:%2:%3:%4 Depth: %5 Stencil: %6")
406 .arg(fmt.redBufferSize()).arg(fmt.greenBufferSize())
407 .arg(fmt.blueBufferSize()).arg(fmt.alphaBufferSize())
408 .arg(fmt.depthBufferSize()).arg(fmt.stencilBufferSize());
409 QStringList shaders {"None"};
410 if (m_features & Shaders)
411 {
412 shaders = QStringList { "Vertex", "Fragment" };
414 shaders << "Geometry";
416 shaders << "Compute";
417 }
418 const QString module = QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL ? "OpenGL (not ES)" : "OpenGL ES";
419 LOG(VB_GENERAL, LOG_INFO, LOC + QString("OpenGL vendor : %1").arg(reinterpret_cast<const char*>(glGetString(GL_VENDOR))));
420 LOG(VB_GENERAL, LOG_INFO, LOC + QString("OpenGL renderer : %1").arg(reinterpret_cast<const char*>(glGetString(GL_RENDERER))));
421 LOG(VB_GENERAL, LOG_INFO, LOC + QString("OpenGL version : %1").arg(reinterpret_cast<const char*>(glGetString(GL_VERSION))));
422 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Qt platform : %1").arg(QGuiApplication::platformName()));
423#if CONFIG_EGL
424 bool eglfuncs = IsEGL();
425 LOG(VB_GENERAL, LOG_INFO, LOC + QString("EGL display : %1").arg(GLYesNo(GetEGLDisplay() != nullptr)));
426 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("EGL images : %1").arg(GLYesNo(eglfuncs)));
427#endif
428 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Qt OpenGL module : %1").arg(module));
429 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Qt OpenGL format : %1").arg(qtglversion));
430 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Qt OpenGL surface : %1").arg(qtglsurface));
431 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Max texture size : %1").arg(m_maxTextureSize));
432 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Shaders : %1").arg(shaders.join(",")));
433 LOG(VB_GENERAL, LOG_INFO, LOC + QString("16bit framebuffers : %1").arg(GLYesNo(m_extraFeatures & kGL16BitFBO)));
434 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Unpack Subimage : %1").arg(GLYesNo(m_extraFeatures & kGLExtSubimage)));
435 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Buffer mapping : %1").arg(GLYesNo(m_extraFeatures & kGLBufferMap)));
436 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Rectangular textures : %1").arg(GLYesNo(m_extraFeatures & kGLExtRects)));
437 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("NPOT textures : %1").arg(GLYesNo(m_features & NPOTTextures)));
438 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Max texture units : %1").arg(m_maxTextureUnits));
439 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("GL_RED/GL_R8 : %1").arg(GLYesNo(!(m_extraFeatures & kGLLegacyTextures))));
440 // warnings
441 if (m_maxTextureUnits < 3)
442 LOG(VB_GENERAL, LOG_WARNING, LOC + "Warning: Insufficient texture units for some features.");
443}
444
446{
447 return m_maxTextureSize;
448}
449
451{
452 return m_maxTextureUnits;
453}
454
456{
457 return m_extraFeaturesUsed;
458}
459
460QOpenGLFunctions::OpenGLFeatures MythRenderOpenGL::GetFeatures(void) const
461{
462 return m_features;
463}
464
466{
467 if (!IsReady())
468 return false;
469
470 bool recommended = true;
471 OpenGLLocker locker(this);
472 QString renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
473
474 if (!(openGLFeatures() & Shaders))
475 {
476 LOG(VB_GENERAL, LOG_WARNING, LOC + "OpenGL has no shader support");
477 recommended = false;
478 }
479 else if (!(openGLFeatures() & Framebuffers))
480 {
481 LOG(VB_GENERAL, LOG_WARNING, LOC + "OpenGL has no framebuffer support");
482 recommended = false;
483 }
484 else if (renderer.contains("Software Rasterizer", Qt::CaseInsensitive))
485 {
486 LOG(VB_GENERAL, LOG_WARNING, LOC + "OpenGL is using software rasterizer.");
487 recommended = false;
488 }
489 else if (renderer.contains("softpipe", Qt::CaseInsensitive))
490 {
491 LOG(VB_GENERAL, LOG_WARNING, LOC + "OpenGL seems to be using software "
492 "fallback. Please check your OpenGL driver installation, "
493 "configuration, and device permissions.");
494 recommended = false;
495 }
496
497 if (!recommended)
498 {
499 LOG(VB_GENERAL, LOG_INFO, LOC +
500 "OpenGL not recommended with this system's hardware/drivers.");
501 }
502
503 return recommended;
504}
505
507{
508 return isValid() && m_ready;
509}
510
512{
513 QOpenGLContext::swapBuffers(m_window);
514 m_swapCount++;
515}
516
518{
519 auto result = m_swapCount;
520 m_swapCount = 0;
521 return result;
522}
523
524void MythRenderOpenGL::SetWidget(QWidget *Widget)
525{
526 if (!Widget)
527 {
528 LOG(VB_GENERAL, LOG_CRIT, LOC + "No widget!");
529 return;
530 }
531
532 // We must have a window/surface.
533 m_window = Widget->windowHandle();
534 QWidget* native = Widget->nativeParentWidget();
535 if (!m_window && native)
536 m_window = native->windowHandle();
537
538 if (!m_window)
539 {
540 LOG(VB_GENERAL, LOG_CRIT, LOC + "No window surface!");
541 return;
542 }
543
544#ifdef CONFIG_QTWEBENGINE
545 auto * globalcontext = QOpenGLContext::globalShareContext();
546 if (globalcontext)
547 {
548 LOG(VB_GENERAL, LOG_INFO, LOC + "Using global shared OpenGL context");
549 setShareContext(globalcontext);
550 }
551#endif
552
553 if (!create())
554 {
555 LOG(VB_GENERAL, LOG_CRIT, LOC + "Failed to create OpenGLContext!");
556 }
557 else
558 {
559 Widget->setAttribute(Qt::WA_PaintOnScreen);
560 }
561}
562
564{
565 m_lock.lock();
566 if (!m_lockLevel++)
567 if (!QOpenGLContext::makeCurrent(m_window))
568 LOG(VB_GENERAL, LOG_ERR, LOC + "makeCurrent failed");
569}
570
572{
573 // TODO add back QOpenGLContext::doneCurrent call
574 // once calls are better pipelined
575 m_lockLevel--;
576 if (m_lockLevel < 0)
577 LOG(VB_GENERAL, LOG_ERR, LOC + "Mis-matched calls to makeCurrent()");
578 m_lock.unlock();
579}
580
581void MythRenderOpenGL::SetViewPort(QRect Rect, bool ViewportOnly)
582{
583 if (Rect == m_viewport)
584 return;
585 makeCurrent();
586 m_viewport = Rect;
587 glViewport(m_viewport.left(), m_viewport.top(),
588 m_viewport.width(), m_viewport.height());
589 if (!ViewportOnly)
591 doneCurrent();
592}
593
595{
596 if (!m_flushEnabled)
597 return;
598
599 makeCurrent();
600 glFlush();
601 doneCurrent();
602}
603
605{
606 makeCurrent();
607 if (Enable && !m_blend)
608 glEnable(GL_BLEND);
609 else if (!Enable && m_blend)
610 glDisable(GL_BLEND);
611 m_blend = Enable;
612 doneCurrent();
613}
614
615void MythRenderOpenGL::SetBackground(uint8_t Red, uint8_t Green, uint8_t Blue, uint8_t Alpha)
616{
617 int32_t tmp = (Red << 24) + (Green << 16) + (Blue << 8) + Alpha;
618 if (tmp == m_background)
619 return;
620
621 m_background = tmp;
622 makeCurrent();
623 glClearColor(Red / 255.0F, Green / 255.0F, Blue / 255.0F, Alpha / 255.0F);
624 doneCurrent();
625}
626
628{
629 if (!Image)
630 return nullptr;
631
632 OpenGLLocker locker(this);
633 auto *texture = new QOpenGLTexture(*Image, QOpenGLTexture::DontGenerateMipMaps);
634 if (!texture->textureId())
635 {
636 LOG(VB_GENERAL, LOG_INFO, LOC + "Failed to create texure");
637 delete texture;
638 return nullptr;
639 }
640 texture->setMinMagFilters(QOpenGLTexture::Linear, QOpenGLTexture::Linear);
641 texture->setWrapMode(QOpenGLTexture::ClampToEdge);
642 auto *result = new MythGLTexture(texture);
643 result->m_texture = texture;
644 result->m_vbo = CreateVBO(kVertexSize);
645 result->m_totalSize = GetTextureSize(Image->size(), result->m_target != QOpenGLTexture::TargetRectangle);
646 // N.B. Format and type per qopengltexure.cpp
647 result->m_pixelFormat = QOpenGLTexture::RGBA;
648 result->m_pixelType = QOpenGLTexture::UInt8;
649 result->m_bufferSize = GetBufferSize(result->m_totalSize, QOpenGLTexture::RGBA, QOpenGLTexture::UInt8);
650 result->m_size = Image->size();
651 result->m_crop = true;
652 return result;
653}
654
655QSize MythRenderOpenGL::GetTextureSize(const QSize Size, bool Normalised)
656{
657 if (((m_features & NPOTTextures) != 0U) || !Normalised)
658 return Size;
659
660 int w = 64;
661 int h = 64;
662 while (w < Size.width())
663 w *= 2;
664 while (h < Size.height())
665 h *= 2;
666 return {w, h};
667}
668
670{
671 if (Texture)
672 return Texture->m_bufferSize;
673 return 0;
674}
675
676void MythRenderOpenGL::SetTextureFilters(MythGLTexture *Texture, QOpenGLTexture::Filter Filter, QOpenGLTexture::WrapMode Wrap)
677{
678 if (!Texture || !(Texture->m_texture || Texture->m_textureId))
679 return;
680
681 makeCurrent();
682 if (Texture->m_texture)
683 {
684 Texture->m_texture->bind();
685 Texture->m_texture->setWrapMode(Wrap);
686 Texture->m_texture->setMinMagFilters(Filter, Filter);
687 }
688 else
689 {
690 glBindTexture(Texture->m_target, Texture->m_textureId);
691 glTexParameteri(Texture->m_target, GL_TEXTURE_MIN_FILTER, Filter);
692 glTexParameteri(Texture->m_target, GL_TEXTURE_MAG_FILTER, Filter);
693 glTexParameteri(Texture->m_target, GL_TEXTURE_WRAP_S, Wrap);
694 glTexParameteri(Texture->m_target, GL_TEXTURE_WRAP_T, Wrap);
695 }
696 doneCurrent();
697}
698
700{
701 if (!(m_features & Multitexture))
702 return;
703
704 makeCurrent();
705 if (m_activeTexture != ActiveTex)
706 {
707 glActiveTexture(ActiveTex);
708 m_activeTexture = ActiveTex;
709 }
710 doneCurrent();
711}
712
714{
715 if (!Texture)
716 return;
717
718 makeCurrent();
719 // N.B. Don't delete m_textureId - it is owned externally
720 delete Texture->m_texture;
721 delete [] Texture->m_data;
722 delete Texture->m_vbo;
723 delete Texture;
724 Flush();
725 doneCurrent();
726}
727
728QOpenGLFramebufferObject* MythRenderOpenGL::CreateFramebuffer(QSize &Size, bool SixteenBit)
729{
730 if (!(m_features & Framebuffers))
731 return nullptr;
732
733 OpenGLLocker locker(this);
734 QOpenGLFramebufferObject *framebuffer = nullptr;
735 if (SixteenBit)
736 {
737 framebuffer = new QOpenGLFramebufferObject(Size, QOpenGLFramebufferObject::NoAttachment,
738 GL_TEXTURE_2D, QOpenGLTexture::RGBA16_UNorm);
739 }
740 else
741 {
742 framebuffer = new QOpenGLFramebufferObject(Size);
743 }
744 if (framebuffer->isValid())
745 {
746 if (framebuffer->isBound())
747 {
748 m_activeFramebuffer = framebuffer->handle();
749 BindFramebuffer(nullptr);
750 }
751 Flush();
752 return framebuffer;
753 }
754 LOG(VB_GENERAL, LOG_ERR, "Failed to create framebuffer object");
755 delete framebuffer;
756 return nullptr;
757}
758
760MythGLTexture* MythRenderOpenGL::CreateFramebufferTexture(QOpenGLFramebufferObject *Framebuffer)
761{
762 if (!Framebuffer)
763 return nullptr;
764
765 auto *texture = new MythGLTexture(Framebuffer->texture());
766 texture->m_size = texture->m_totalSize = Framebuffer->size();
767 texture->m_vbo = CreateVBO(kVertexSize);
768 texture->m_flip = false;
769 return texture;
770}
771
772void MythRenderOpenGL::DeleteFramebuffer(QOpenGLFramebufferObject *Framebuffer)
773{
774 if (Framebuffer)
775 {
776 makeCurrent();
777 delete Framebuffer;
778 doneCurrent();
779 }
780}
781
782void MythRenderOpenGL::BindFramebuffer(QOpenGLFramebufferObject *Framebuffer)
783{
784 if ((Framebuffer && Framebuffer->handle() == m_activeFramebuffer) ||
785 (!Framebuffer && defaultFramebufferObject() == m_activeFramebuffer))
786 return;
787
788 makeCurrent();
789 if (Framebuffer == nullptr)
790 {
791 QOpenGLFramebufferObject::bindDefault();
792 m_activeFramebuffer = defaultFramebufferObject();
793 }
794 else
795 {
796 Framebuffer->bind();
797 m_activeFramebuffer = Framebuffer->handle();
798 }
799 doneCurrent();
800}
801
803{
804 makeCurrent();
805 glClear(GL_COLOR_BUFFER_BIT);
806 doneCurrent();
807}
808
809void MythRenderOpenGL::DrawProcedural(QRect Area, int Alpha, QOpenGLFramebufferObject* Target,
810 QOpenGLShaderProgram *Program, float TimeVal)
811{
812 if (!Program)
813 return;
814
815 makeCurrent();
816 BindFramebuffer(Target);
817 glEnableVertexAttribArray(VERTEX_INDEX);
818 GetCachedVBO(GL_TRIANGLE_STRIP, Area);
819 glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
820 SetShaderProjection(Program);
821 Program->setUniformValue("u_time", TimeVal);
822 Program->setUniformValue("u_alpha", static_cast<float>(Alpha / 255.0F));
823 Program->setUniformValue("u_res", QVector2D(m_window->width(), m_window->height()));
824 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
825 QOpenGLBuffer::release(QOpenGLBuffer::VertexBuffer);
826 glDisableVertexAttribArray(VERTEX_INDEX);
827 doneCurrent();
828}
829
830void MythRenderOpenGL::DrawBitmap(MythGLTexture *Texture, QOpenGLFramebufferObject *Target,
831 const QRect Source, const QRect Destination,
832 QOpenGLShaderProgram *Program, int Alpha, qreal Scale)
833{
834 makeCurrent();
835
836 if (!Texture || !((Texture->m_texture || Texture->m_textureId) && Texture->m_vbo))
837 return;
838
839 if (Program == nullptr)
841
842 BindFramebuffer(Target);
843 SetShaderProjection(Program);
844
845 GLenum textarget = Texture->m_target;
846 Program->setUniformValue("s_texture0", 0);
848 if (Texture->m_texture)
849 Texture->m_texture->bind();
850 else
851 glBindTexture(textarget, Texture->m_textureId);
852
853 QOpenGLBuffer* buffer = Texture->m_vbo;
854 buffer->bind();
855 if (UpdateTextureVertices(Texture, Source, Destination, 0, Scale))
856 {
858 {
859 void* target = buffer->map(QOpenGLBuffer::WriteOnly);
860 if (target)
861 {
863 static_cast<GLfloat*>(target));
864 }
865 buffer->unmap();
866 }
867 else
868 {
869 buffer->write(0, Texture->m_vertexData.data(), kVertexSize);
870 }
871 }
872
873 glEnableVertexAttribArray(VERTEX_INDEX);
874 glEnableVertexAttribArray(TEXTURE_INDEX);
875 glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
876 glVertexAttrib4f(COLOR_INDEX, 1.0F, 1.0F, 1.0F, Alpha / 255.0F);
877 glVertexAttribPointerI(TEXTURE_INDEX, TEXTURE_SIZE, GL_FLOAT, GL_FALSE, TEXTURE_SIZE * sizeof(GLfloat), kTextureOffset);
878 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
879 glDisableVertexAttribArray(TEXTURE_INDEX);
880 glDisableVertexAttribArray(VERTEX_INDEX);
881 QOpenGLBuffer::release(QOpenGLBuffer::VertexBuffer);
882 doneCurrent();
883}
884
885void MythRenderOpenGL::DrawBitmap(std::vector<MythGLTexture *> &Textures,
886 QOpenGLFramebufferObject *Target,
887 const QRect Source, const QRect Destination,
888 QOpenGLShaderProgram *Program,
889 int Rotation)
890{
891 if (Textures.empty())
892 return;
893
894 makeCurrent();
895 BindFramebuffer(Target);
896
897 if (Program == nullptr)
899
900 MythGLTexture* first = Textures[0];
901 if (!first || !((first->m_texture || first->m_textureId) && first->m_vbo))
902 return;
903
904 SetShaderProjection(Program);
905
906 GLenum textarget = first->m_target;
907 for (uint i = 0; i < Textures.size(); i++)
908 {
909 QString uniform = QString("s_texture%1").arg(i);
910 Program->setUniformValue(qPrintable(uniform), i);
912 if (Textures[i]->m_texture)
913 Textures[i]->m_texture->bind();
914 else
915 glBindTexture(textarget, Textures[i]->m_textureId);
916 }
917
918 QOpenGLBuffer* buffer = first->m_vbo;
919 buffer->bind();
920 if (UpdateTextureVertices(first, Source, Destination, Rotation))
921 {
923 {
924 void* target = buffer->map(QOpenGLBuffer::WriteOnly);
925 if (target)
926 {
928 static_cast<GLfloat*>(target));
929 }
930 buffer->unmap();
931 }
932 else
933 {
934 buffer->write(0, first->m_vertexData.data(), kVertexSize);
935 }
936 }
937
938 glEnableVertexAttribArray(VERTEX_INDEX);
939 glEnableVertexAttribArray(TEXTURE_INDEX);
940 glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
941 glVertexAttrib4f(COLOR_INDEX, 1.0, 1.0, 1.0, 1.0);
942 glVertexAttribPointerI(TEXTURE_INDEX, TEXTURE_SIZE, GL_FLOAT, GL_FALSE, TEXTURE_SIZE * sizeof(GLfloat), kTextureOffset);
943 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
944 glDisableVertexAttribArray(TEXTURE_INDEX);
945 glDisableVertexAttribArray(VERTEX_INDEX);
946 QOpenGLBuffer::release(QOpenGLBuffer::VertexBuffer);
947 doneCurrent();
948}
949
950static const float kLimitedRangeOffset = (16.0F / 255.0F);
951static const float kLimitedRangeScale = (219.0F / 255.0F);
952
954void MythRenderOpenGL::ClearRect(QOpenGLFramebufferObject *Target, const QRect Area, int Color, int Alpha)
955{
956 makeCurrent();
957 BindFramebuffer(Target);
958 glEnableVertexAttribArray(VERTEX_INDEX);
959
960 // Set the fill color
961 float color = m_fullRange ? Color / 255.0F : (Color * kLimitedRangeScale) + kLimitedRangeOffset;
962 glVertexAttrib4f(COLOR_INDEX, color, color, color, Alpha / 255.0F);
964
965 GetCachedVBO(GL_TRIANGLE_STRIP, Area);
966 glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
967 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
968
969 QOpenGLBuffer::release(QOpenGLBuffer::VertexBuffer);
970 glDisableVertexAttribArray(VERTEX_INDEX);
971 doneCurrent();
972}
973
974void MythRenderOpenGL::DrawRect(QOpenGLFramebufferObject *Target,
975 const QRect Area, const QBrush &FillBrush,
976 const QPen &LinePen, int Alpha)
977{
978 DrawRoundRect(Target, Area, 1, FillBrush, LinePen, Alpha);
979}
980
981
982void MythRenderOpenGL::DrawRoundRect(QOpenGLFramebufferObject *Target,
983 const QRect Area, int CornerRadius,
984 const QBrush &FillBrush,
985 const QPen &LinePen, int Alpha)
986{
987 bool fill = FillBrush.style() != Qt::NoBrush;
988 bool edge = LinePen.style() != Qt::NoPen;
989 if (!(fill || edge))
990 return;
991
992 auto SetColor = [&](const QColor& Color)
993 {
994 if (m_fullRange)
995 {
996 glVertexAttrib4f(COLOR_INDEX, Color.red() / 255.0F, Color.green() / 255.0F,
997 Color.blue() / 255.0F, (Color.alpha() / 255.0F) * (Alpha / 255.0F));
998 return;
999 }
1000 glVertexAttrib4f(COLOR_INDEX, (Color.red() * kLimitedRangeScale) + kLimitedRangeOffset,
1003 (Color.alpha() / 255.0F) * (Alpha / 255.0F));
1004 };
1005
1006 float halfwidth = Area.width() / 2.0F;
1007 float halfheight = Area.height() / 2.0F;
1008 float radius = CornerRadius;
1009 radius = std::max(radius, 1.0F);
1010 radius = std::min(radius, halfwidth);
1011 radius = std::min(radius, halfheight);
1012
1013 // Set shader parameters
1014 // Centre of the rectangle
1015 m_parameters(0,0) = Area.left() + halfwidth;
1016 m_parameters(1,0) = Area.top() + halfheight;
1017 m_parameters(2,0) = radius;
1018 // Rectangle 'size' - distances from the centre to the edge
1019 m_parameters(0,1) = halfwidth;
1020 m_parameters(1,1) = halfheight;
1021
1022 makeCurrent();
1023 BindFramebuffer(Target);
1024 glEnableVertexAttribArray(VERTEX_INDEX);
1025 GetCachedVBO(GL_TRIANGLE_STRIP, Area);
1026 glVertexAttribPointerI(VERTEX_INDEX, VERTEX_SIZE, GL_FLOAT, GL_FALSE, VERTEX_SIZE * sizeof(GLfloat), kVertexOffset);
1027
1028 if (fill)
1029 {
1030 SetColor(FillBrush.color());
1033 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
1034 }
1035
1036 if (edge)
1037 {
1038 float innerradius = radius - LinePen.width();
1039 innerradius = std::max(innerradius, 1.0F);
1040 m_parameters(3,0) = innerradius;
1041 // Adjust the size for the inner radius (edge)
1042 m_parameters(2,1) = halfwidth - LinePen.width();
1043 m_parameters(3,1) = halfheight - LinePen.width();
1044 SetColor(LinePen.color());
1047 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
1048 }
1049
1050 QOpenGLBuffer::release(QOpenGLBuffer::VertexBuffer);
1051 glDisableVertexAttribArray(VERTEX_INDEX);
1052 doneCurrent();
1053}
1054
1055inline void MythRenderOpenGL::glVertexAttribPointerI(GLuint Index, GLint Size, GLenum Type, GLboolean Normalize,
1056 GLsizei Stride, const GLuint Value)
1057{
1058#pragma GCC diagnostic push
1059#pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
1060 // NOLINTNEXTLINE(performance-no-int-to-ptr)
1061 glVertexAttribPointer(Index, Size, Type, Normalize, Stride, reinterpret_cast<const char *>(Value));
1062#pragma GCC diagnostic pop
1063}
1064
1066{
1067 SetBlend(true);
1068 glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
1069 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1070 glDisable(GL_DEPTH_TEST);
1071 glDepthMask(GL_FALSE);
1072 glDisable(GL_CULL_FACE);
1073 glClearColor(0.0F, 0.0F, 0.0F, 0.0F);
1074 glClear(GL_COLOR_BUFFER_BIT);
1075 QOpenGLFramebufferObject::bindDefault();
1076 m_activeFramebuffer = defaultFramebufferObject();
1077 Flush();
1078}
1079
1080QFunctionPointer MythRenderOpenGL::GetProcAddress(const QString &Proc) const
1081{
1082 static const std::array<const QString,4> kExts { "", "ARB", "EXT", "OES" };
1083 QFunctionPointer result = nullptr;
1084 for (const auto & ext : kExts)
1085 {
1086 result = getProcAddress((Proc + ext).toLocal8Bit().constData());
1087 if (result)
1088 break;
1089 }
1090 if (result == nullptr)
1091 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Extension not found: %1").arg(Proc));
1092 return result;
1093}
1094
1095QOpenGLBuffer* MythRenderOpenGL::CreateVBO(int Size, bool Release /*=true*/)
1096{
1097 OpenGLLocker locker(this);
1098 auto* buffer = new QOpenGLBuffer(QOpenGLBuffer::VertexBuffer);
1099 if (buffer->create())
1100 {
1101 buffer->setUsagePattern(QOpenGLBuffer::StreamDraw);
1102 buffer->bind();
1103 buffer->allocate(Size);
1104 if (Release)
1105 QOpenGLBuffer::release(QOpenGLBuffer::VertexBuffer);
1106 return buffer;
1107 }
1108 delete buffer;
1109 return nullptr;
1110}
1111
1113{
1114 OpenGLLocker locker(this);
1115 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
1116 logDebugMarker("RENDER_RELEASE_START");
1119 ExpireVBOS();
1120 if (m_vao)
1121 {
1122 extraFunctions()->glDeleteVertexArrays(1, &m_vao);
1123 m_vao = 0;
1124 }
1125
1126 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
1127 logDebugMarker("RENDER_RELEASE_END");
1128 delete m_openglDebugger;
1129 m_openglDebugger = nullptr;
1130 Flush();
1131
1132 if (!m_cachedVertices.empty())
1133 LOG(VB_GENERAL, LOG_ERR, LOC + QString(" %1 unexpired vertices").arg(m_cachedVertices.size()));
1134
1135 if (!m_cachedVBOS.empty())
1136 LOG(VB_GENERAL, LOG_ERR, LOC + QString(" %1 unexpired VBOs").arg(m_cachedVertices.size()));
1137}
1138
1140{
1141 QStringList result;
1142 result.append(tr("QPA platform") + "\t: " + QGuiApplication::platformName());
1143 result.append(tr("OpenGL vendor") + "\t: " + reinterpret_cast<const char*>(glGetString(GL_VENDOR)));
1144 result.append(tr("OpenGL renderer") + "\t: " + reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
1145 result.append(tr("OpenGL version") + "\t: " + reinterpret_cast<const char*>(glGetString(GL_VERSION)));
1146 QSurfaceFormat fmt = format();
1147 result.append(tr("Color depth (RGBA)") + "\t: " + QString("%1:%2:%3:%4")
1148 .arg(fmt.redBufferSize()).arg(fmt.greenBufferSize())
1149 .arg(fmt.blueBufferSize()).arg(fmt.alphaBufferSize()));
1150 return result;
1151}
1152
1154 const QRect Destination, int Rotation, qreal Scale)
1155{
1156 if (!Texture || Texture->m_size.isEmpty())
1157 return false;
1158
1159 if ((Texture->m_source == Source) && (Texture->m_destination == Destination) &&
1160 (Texture->m_rotation == Rotation))
1161 return false;
1162
1163 Texture->m_source = Source;
1164 Texture->m_destination = Destination;
1165 Texture->m_rotation = Rotation;
1166
1167 GLfloat *data = Texture->m_vertexData.data();
1168 QSize size = Texture->m_size;
1169
1170 int width = Texture->m_crop ? std::min(Source.width(), size.width()) : Source.width();
1171 int height = Texture->m_crop ? std::min(Source.height(), size.height()) : Source.height();
1172
1173 if (Texture->m_target != QOpenGLTexture::TargetRectangle)
1174 {
1175 data[0 + TEX_OFFSET] = Source.left() / static_cast<GLfloat>(size.width());
1176 data[(Texture->m_flip ? 7 : 1) + TEX_OFFSET] = (Source.top() + height) / static_cast<GLfloat>(size.height());
1177 data[6 + TEX_OFFSET] = (Source.left() + width) / static_cast<GLfloat>(size.width());
1178 data[(Texture->m_flip ? 1 : 7) + TEX_OFFSET] = Source.top() / static_cast<GLfloat>(size.height());
1179 }
1180 else
1181 {
1182 data[0 + TEX_OFFSET] = Source.left();
1183 data[(Texture->m_flip ? 7 : 1) + TEX_OFFSET] = (Source.top() + height);
1184 data[6 + TEX_OFFSET] = (Source.left() + width);
1185 data[(Texture->m_flip ? 1 : 7) + TEX_OFFSET] = Source.top();
1186 }
1187
1188 data[2 + TEX_OFFSET] = data[0 + TEX_OFFSET];
1189 data[3 + TEX_OFFSET] = data[7 + TEX_OFFSET];
1190 data[4 + TEX_OFFSET] = data[6 + TEX_OFFSET];
1191 data[5 + TEX_OFFSET] = data[1 + TEX_OFFSET];
1192
1193 width = Texture->m_crop ? std::min(static_cast<int>(width * Scale), Destination.width()) : Destination.width();
1194 height = Texture->m_crop ? std::min(static_cast<int>(height * Scale), Destination.height()) : Destination.height();
1195
1196 data[2] = data[0] = Destination.left();
1197 data[5] = data[1] = Destination.top();
1198 data[4] = data[6] = Destination.left() + width;
1199 data[3] = data[7] = Destination.top() + height;
1200
1201 if (Texture->m_rotation != 0)
1202 {
1203 if (Texture->m_rotation == 90)
1204 {
1205 GLfloat temp = data[(Texture->m_flip ? 7 : 1) + TEX_OFFSET];
1206 data[(Texture->m_flip ? 7 : 1) + TEX_OFFSET] = data[(Texture->m_flip ? 1 : 7) + TEX_OFFSET];
1207 data[(Texture->m_flip ? 1 : 7) + TEX_OFFSET] = temp;
1208 data[2 + TEX_OFFSET] = data[6 + TEX_OFFSET];
1209 data[4 + TEX_OFFSET] = data[0 + TEX_OFFSET];
1210 }
1211 else if (Texture->m_rotation == -90)
1212 {
1213 GLfloat temp = data[0 + TEX_OFFSET];
1214 data[0 + TEX_OFFSET] = data[6 + TEX_OFFSET];
1215 data[6 + TEX_OFFSET] = temp;
1216 data[3 + TEX_OFFSET] = data[1 + TEX_OFFSET];
1217 data[5 + TEX_OFFSET] = data[7 + TEX_OFFSET];
1218 }
1219 else if (abs(Texture->m_rotation) == 180)
1220 {
1221 GLfloat temp = data[(Texture->m_flip ? 7 : 1) + TEX_OFFSET];
1222 data[(Texture->m_flip ? 7 : 1) + TEX_OFFSET] = data[(Texture->m_flip ? 1 : 7) + TEX_OFFSET];
1223 data[(Texture->m_flip ? 1 : 7) + TEX_OFFSET] = temp;
1224 data[3 + TEX_OFFSET] = data[7 + TEX_OFFSET];
1225 data[5 + TEX_OFFSET] = data[1 + TEX_OFFSET];
1226 temp = data[0 + TEX_OFFSET];
1227 data[0 + TEX_OFFSET] = data[6 + TEX_OFFSET];
1228 data[6 + TEX_OFFSET] = temp;
1229 data[2 + TEX_OFFSET] = data[0 + TEX_OFFSET];
1230 data[4 + TEX_OFFSET] = data[6 + TEX_OFFSET];
1231 }
1232 }
1233
1234 return true;
1235}
1236
1237GLfloat* MythRenderOpenGL::GetCachedVertices(GLuint Type, const QRect Area)
1238{
1239 uint64_t ref = (static_cast<uint64_t>(Area.left()) & 0xfff) +
1240 ((static_cast<uint64_t>(Area.top()) & 0xfff) << 12) +
1241 ((static_cast<uint64_t>(Area.width()) & 0xfff) << 24) +
1242 ((static_cast<uint64_t>(Area.height()) & 0xfff) << 36) +
1243 ((static_cast<uint64_t>(Type & 0xfff)) << 48);
1244
1245 if (m_cachedVertices.contains(ref))
1246 {
1247 m_vertexExpiry.removeOne(ref);
1248 m_vertexExpiry.append(ref);
1249 return m_cachedVertices[ref];
1250 }
1251
1252 auto *vertices = new GLfloat[8];
1253
1254 vertices[2] = vertices[0] = Area.left();
1255 vertices[5] = vertices[1] = Area.top();
1256 vertices[4] = vertices[6] = Area.left() + Area.width();
1257 vertices[3] = vertices[7] = Area.top() + Area.height();
1258
1259 if (Type == GL_LINE_LOOP)
1260 {
1261 vertices[7] = vertices[1];
1262 vertices[5] = vertices[3];
1263 }
1264
1265 m_cachedVertices.insert(ref, vertices);
1266 m_vertexExpiry.append(ref);
1268
1269 return vertices;
1270}
1271
1273{
1274 while (m_vertexExpiry.size() > Max)
1275 {
1276 uint64_t ref = m_vertexExpiry.first();
1277 m_vertexExpiry.removeFirst();
1278 GLfloat *vertices = nullptr;
1279 if (m_cachedVertices.contains(ref))
1280 vertices = m_cachedVertices.value(ref);
1281 m_cachedVertices.remove(ref);
1282 delete [] vertices;
1283 }
1284}
1285
1286void MythRenderOpenGL::GetCachedVBO(GLuint Type, const QRect Area)
1287{
1288 uint64_t ref = (static_cast<uint64_t>(Area.left()) & 0xfff) +
1289 ((static_cast<uint64_t>(Area.top()) & 0xfff) << 12) +
1290 ((static_cast<uint64_t>(Area.width()) & 0xfff) << 24) +
1291 ((static_cast<uint64_t>(Area.height()) & 0xfff) << 36) +
1292 ((static_cast<uint64_t>(Type & 0xfff)) << 48);
1293
1294 if (m_cachedVBOS.contains(ref))
1295 {
1296 m_vboExpiry.removeOne(ref);
1297 m_vboExpiry.append(ref);
1298 m_cachedVBOS.value(ref)->bind();
1299 return;
1300 }
1301
1302 GLfloat *vertices = GetCachedVertices(Type, Area);
1303 QOpenGLBuffer *vbo = CreateVBO(kTextureOffset, false);
1304 m_cachedVBOS.insert(ref, vbo);
1305 m_vboExpiry.append(ref);
1306
1308 {
1309 void* target = vbo->map(QOpenGLBuffer::WriteOnly);
1310 if (target)
1311 memcpy(target, vertices, kTextureOffset);
1312 vbo->unmap();
1313 }
1314 else
1315 {
1316 vbo->write(0, vertices, kTextureOffset);
1317 }
1319}
1320
1322{
1323 while (m_vboExpiry.size() > Max)
1324 {
1325 uint64_t ref = m_vboExpiry.first();
1326 m_vboExpiry.removeFirst();
1327 if (m_cachedVBOS.contains(ref))
1328 {
1329 QOpenGLBuffer *vbo = m_cachedVBOS.value(ref);
1330 delete vbo;
1331 m_cachedVBOS.remove(ref);
1332 }
1333 }
1334}
1335
1336int MythRenderOpenGL::GetBufferSize(QSize Size, QOpenGLTexture::PixelFormat Format, QOpenGLTexture::PixelType Type)
1337{
1338 int bytes = 0;
1339 int bpp = 0;;
1340
1341 switch (Format)
1342 {
1343 case QOpenGLTexture::RGBA_Integer:
1344 case QOpenGLTexture::BGRA_Integer:
1345 case QOpenGLTexture::BGRA:
1346 case QOpenGLTexture::RGBA: bpp = 4; break;
1347 case QOpenGLTexture::RGB_Integer:
1348 case QOpenGLTexture::BGR_Integer:
1349 case QOpenGLTexture::BGR:
1350 case QOpenGLTexture::RGB: bpp = 3; break;
1351 case QOpenGLTexture::RG_Integer:
1352 case QOpenGLTexture::RG: bpp = 2; break;
1353 case QOpenGLTexture::Red:
1354 case QOpenGLTexture::Red_Integer:
1355 case QOpenGLTexture::Alpha:
1356 case QOpenGLTexture::Luminance: bpp = 1; break;
1357 default: break; // unsupported
1358 }
1359
1360 switch (Type)
1361 {
1362 case QOpenGLTexture::Int8: bytes = sizeof(GLbyte); break;
1363 case QOpenGLTexture::UInt8: bytes = sizeof(GLubyte); break;
1364 case QOpenGLTexture::Int16: bytes = sizeof(GLshort); break;
1365 case QOpenGLTexture::UInt16: bytes = sizeof(GLushort); break;
1366 case QOpenGLTexture::Int32: bytes = sizeof(GLint); break;
1367 case QOpenGLTexture::UInt32: bytes = sizeof(GLuint); break;
1368 case QOpenGLTexture::Float32: bytes = sizeof(GLfloat); break;
1369 case QOpenGLTexture::UInt32_RGB10A2: bytes = sizeof(GLuint); break;
1370 default: break; // unsupported
1371 }
1372
1373 if (!bpp || !bytes || Size.isEmpty())
1374 return 0;
1375
1376 return Size.width() * Size.height() * bpp * bytes;
1377}
1378
1379void MythRenderOpenGL::PushTransformation(const UIEffects &Fx, QPointF &Center)
1380{
1381 QMatrix4x4 newtop = m_transforms.top();
1382 if (Fx.m_hzoom != 1.0F || Fx.m_vzoom != 1.0F || Fx.m_angle != 0.0F)
1383 {
1384 newtop.translate(static_cast<GLfloat>(Center.x()), static_cast<GLfloat>(Center.y()));
1385 newtop.scale(Fx.m_hzoom, Fx.m_vzoom);
1386 newtop.rotate(Fx.m_angle, 0, 0, 1);
1387 newtop.translate(static_cast<GLfloat>(-Center.x()), static_cast<GLfloat>(-Center.y()));
1388 }
1389 m_transforms.push(newtop);
1390}
1391
1393{
1394 m_transforms.pop();
1395}
1396
1397inline QOpenGLShaderProgram* ShaderError(QOpenGLShaderProgram *Shader, const QString &Source)
1398{
1399 QString type = Source.isEmpty() ? "Shader link" : "Shader compile";
1400 LOG(VB_GENERAL, LOG_ERR, LOC + QString("%1 error").arg(type));
1401 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Log:"));
1402 LOG(VB_GENERAL, LOG_ERR, "\n" + Shader->log());
1403 if (!Source.isEmpty())
1404 {
1405 LOG(VB_GENERAL, LOG_ERR, LOC + QString("Source:"));
1406 LOG(VB_GENERAL, LOG_ERR, "\n" + Source);
1407 }
1408 delete Shader;
1409 return nullptr;
1410}
1411
1412QOpenGLShaderProgram *MythRenderOpenGL::CreateShaderProgram(const QString &Vertex, const QString &Fragment)
1413{
1414 if (!(m_features & Shaders))
1415 return nullptr;
1416
1417 OpenGLLocker locker(this);
1418 QString vertex = Vertex.isEmpty() ? kDefaultVertexShader : Vertex;
1419 QString fragment = Fragment.isEmpty() ? kDefaultFragmentShader: Fragment;
1420 auto *program = new QOpenGLShaderProgram();
1421 if (!program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertex))
1422 return ShaderError(program, vertex);
1423 if (!program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragment))
1424 return ShaderError(program, fragment);
1425 if (VERBOSE_LEVEL_CHECK(VB_GENERAL, LOG_DEBUG))
1426 {
1427 QList<QOpenGLShader*> shaders = program->shaders();
1428 for (QOpenGLShader* shader : std::as_const(shaders))
1429 LOG(VB_GENERAL, LOG_DEBUG, "\n" + shader->sourceCode());
1430 }
1431 program->bindAttributeLocation("a_position", VERTEX_INDEX);
1432 program->bindAttributeLocation("a_color", COLOR_INDEX);
1433 program->bindAttributeLocation("a_texcoord0", TEXTURE_INDEX);
1434 if (!program->link())
1435 return ShaderError(program, "");
1436 return program;
1437}
1438
1439QOpenGLShaderProgram* MythRenderOpenGL::CreateComputeShader(const QString &Source)
1440{
1441 if (!(m_extraFeaturesUsed & kGLComputeShaders) || Source.isEmpty())
1442 return nullptr;
1443
1444 OpenGLLocker locker(this);
1445 auto *program = new QOpenGLShaderProgram();
1446 if (!program->addShaderFromSourceCode(QOpenGLShader::Compute, Source))
1447 return ShaderError(program, Source);
1448
1449 if (VERBOSE_LEVEL_CHECK(VB_GENERAL, LOG_DEBUG))
1450 {
1451 QList<QOpenGLShader*> shaders = program->shaders();
1452 for (QOpenGLShader* shader : std::as_const(shaders))
1453 LOG(VB_GENERAL, LOG_DEBUG, "\n" + shader->sourceCode());
1454 }
1455
1456 if (!program->link())
1457 return ShaderError(program, "");
1458 return program;
1459}
1460
1461void MythRenderOpenGL::DeleteShaderProgram(QOpenGLShaderProgram *Program)
1462{
1463 makeCurrent();
1464 delete Program;
1465 m_cachedMatrixUniforms.clear();
1466 m_activeProgram = nullptr;
1467 m_cachedUniformLocations.remove(Program);
1468 doneCurrent();
1469}
1470
1471bool MythRenderOpenGL::EnableShaderProgram(QOpenGLShaderProgram* Program)
1472{
1473 if (!Program)
1474 return false;
1475
1476 if (m_activeProgram == Program)
1477 return true;
1478
1479 makeCurrent();
1480 Program->bind();
1481 m_activeProgram = Program;
1482 doneCurrent();
1483 return true;
1484}
1485
1486void MythRenderOpenGL::SetShaderProjection(QOpenGLShaderProgram *Program)
1487{
1488 if (Program)
1489 {
1490 SetShaderProgramParams(Program, m_projection, "u_projection");
1491 SetShaderProgramParams(Program, m_transforms.top(), "u_transform");
1492 }
1493}
1494
1495void MythRenderOpenGL::SetShaderProgramParams(QOpenGLShaderProgram *Program, const QMatrix4x4 &Value, const char *Uniform)
1496{
1497 OpenGLLocker locker(this);
1498 if (!Uniform || !EnableShaderProgram(Program))
1499 return;
1500
1501 // Uniform value cacheing
1502 QString tag = QString("%1-%2").arg(Program->programId()).arg(Uniform);
1503 QHash<QString,QMatrix4x4>::iterator it = m_cachedMatrixUniforms.find(tag);
1504 if (it == m_cachedMatrixUniforms.end())
1505 m_cachedMatrixUniforms.insert(tag, Value);
1506 else if (!qFuzzyCompare(Value, it.value()))
1507 it.value() = Value;
1508 else
1509 return;
1510
1511 // Uniform location cacheing
1512 QByteArray uniform(Uniform);
1513 GLint location = 0;
1514 QHash<QByteArray, GLint> &uniforms = m_cachedUniformLocations[Program];
1515 if (uniforms.contains(uniform))
1516 {
1517 location = uniforms[uniform];
1518 }
1519 else
1520 {
1521 location = Program->uniformLocation(Uniform);
1522 uniforms.insert(uniform, location);
1523 }
1524
1525 Program->setUniformValue(location, Value);
1526}
1527
1529{
1536}
1537
1539{
1540 for (auto & program : m_defaultPrograms)
1541 {
1542 DeleteShaderProgram(program);
1543 program = nullptr;
1544 }
1545}
1546
1548{
1549 m_projection.setToIdentity();
1550 m_projection.ortho(m_viewport);
1551}
1552
1553std::tuple<int, int, int> MythRenderOpenGL::GetGPUMemory()
1554{
1555 OpenGLLocker locker(this);
1557 {
1558 GLint total = 0;
1559 GLint dedicated = 0;
1560 GLint available = 0;
1561 glGetIntegerv(GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, &total);
1562 glGetIntegerv(GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, &dedicated);
1563 glGetIntegerv(GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &available);
1564 return { total / 1024, dedicated / 1024, available / 1024 };
1565 }
1566 return { 0, 0, 0 };
1567}
1568
1580{
1581 OpenGLLocker locker(this);
1582 QSize size{256, 256};
1583 QOpenGLFramebufferObject *fbo = CreateFramebuffer(size, true);
1584 if (fbo)
1585 {
1587 delete fbo;
1588 }
1589}
1590
1591#include "moc_mythrenderopengl.cpp"
void * GetEGLDisplay(void)
Definition: mythegl.cpp:81
bool IsEGL(void)
Definition: mythegl.cpp:32
unsigned char * m_data
MythGLTexture(QOpenGLTexture *Texture)
std::array< GLfloat, 16 > m_vertexData
QOpenGLBuffer * m_vbo
QOpenGLTexture * m_texture
MythRender * GetRenderDevice()
static MythMainWindow * getMainWindow(bool UseDB=true)
Return the existing main window, or create one.
void ExpireVBOS(int Max=0)
void GetCachedVBO(GLuint Type, QRect Area)
void SetShaderProgramParams(QOpenGLShaderProgram *Program, const QMatrix4x4 &Value, const char *Uniform)
QList< uint64_t > m_vertexExpiry
QOpenGLFunctions::OpenGLFeatures m_features
void SetWidget(QWidget *Widget)
MythGLTexture * CreateTextureFromQImage(QImage *Image)
static constexpr GLuint kVertexSize
static int GetBufferSize(QSize Size, QOpenGLTexture::PixelFormat Format, QOpenGLTexture::PixelType Type)
QOpenGLDebugLogger * m_openglDebugger
void ClearRect(QOpenGLFramebufferObject *Target, QRect Area, int Color, int Alpha)
An optimised method to clear a QRect to the given color.
void ActiveTexture(GLuint ActiveTex)
int GetMaxTextureSize(void) const
void DrawProcedural(QRect Area, int Alpha, QOpenGLFramebufferObject *Target, QOpenGLShaderProgram *Program, float TimeVal)
void DrawRoundRect(QOpenGLFramebufferObject *Target, QRect Area, int CornerRadius, const QBrush &FillBrush, const QPen &LinePen, int Alpha)
void contextToBeDestroyed(void)
QStack< QMatrix4x4 > m_transforms
static MythRenderOpenGL * Create(QWidget *Widget)
int GetMaxTextureUnits(void) const
void DrawBitmap(MythGLTexture *Texture, QOpenGLFramebufferObject *Target, QRect Source, QRect Destination, QOpenGLShaderProgram *Program, int Alpha=255, qreal Scale=1.0)
void ClearFramebuffer(void)
QRecursiveMutex m_lock
QMap< uint64_t, QOpenGLBuffer * > m_cachedVBOS
QHash< QString, QMatrix4x4 > m_cachedMatrixUniforms
void SetViewPort(QRect Rect, bool ViewportOnly=false) override
void DeleteShaderProgram(QOpenGLShaderProgram *Program)
void BindFramebuffer(QOpenGLFramebufferObject *Framebuffer)
QOpenGLShaderProgram * m_activeProgram
void DeleteFramebuffer(QOpenGLFramebufferObject *Framebuffer)
MythRenderOpenGL(const QSurfaceFormat &Format, QWidget *Widget)
void DeleteDefaultShaders(void)
void MessageLogged(const QOpenGLDebugMessage &Message)
void PushTransformation(const UIEffects &Fx, QPointF &Center)
QOpenGLFunctions::OpenGLFeatures GetFeatures(void) const
void PopTransformation(void)
bool EnableShaderProgram(QOpenGLShaderProgram *Program)
void SetBlend(bool Enable)
bool IsRecommendedRenderer(void)
QOpenGLDebugMessage::Types m_openGLDebuggerFilter
QOpenGLBuffer * CreateVBO(int Size, bool Release=true)
~MythRenderOpenGL() override
QMatrix4x4 m_projection
void logDebugMarker(const QString &Message)
void Check16BitFBO(void)
Check for 16bit framebufferobject support.
GLfloat * GetCachedVertices(GLuint Type, QRect Area)
QHash< QOpenGLShaderProgram *, QHash< QByteArray, GLint > > m_cachedUniformLocations
QOpenGLFramebufferObject * CreateFramebuffer(QSize &Size, bool SixteenBit=false)
void SetBackground(uint8_t Red, uint8_t Green, uint8_t Blue, uint8_t Alpha)
QOpenGLShaderProgram * CreateShaderProgram(const QString &Vertex, const QString &Fragment)
QFunctionPointer GetProcAddress(const QString &Proc) const
void glVertexAttribPointerI(GLuint Index, GLint Size, GLenum Type, GLboolean Normalize, GLsizei Stride, GLuint Value)
static bool UpdateTextureVertices(MythGLTexture *Texture, QRect Source, QRect Destination, int Rotation, qreal Scale=1.0)
QMap< uint64_t, GLfloat * > m_cachedVertices
void DeleteTexture(MythGLTexture *Texture)
static MythRenderOpenGL * GetOpenGLRender(void)
QSize GetTextureSize(QSize Size, bool Normalised)
QMatrix4x4 m_parameters
void DrawRect(QOpenGLFramebufferObject *Target, QRect Area, const QBrush &FillBrush, const QPen &LinePen, int Alpha)
MythGLTexture * CreateFramebufferTexture(QOpenGLFramebufferObject *Framebuffer)
This is no longer used but will probably be needed for future UI enhancements.
std::tuple< int, int, int > GetGPUMemory()
int GetExtraFeatures(void) const
bool CreateDefaultShaders(void)
void SetShaderProjection(QOpenGLShaderProgram *Program)
QList< uint64_t > m_vboExpiry
std::array< QOpenGLShaderProgram *, kShaderCount > m_defaultPrograms
void SetTextureFilters(MythGLTexture *Texture, QOpenGLTexture::Filter Filter, QOpenGLTexture::WrapMode Wrap=QOpenGLTexture::ClampToEdge)
void ReleaseResources(void) override
void ExpireVertices(int Max=0)
QStringList GetDescription(void) override
static int GetTextureDataSize(MythGLTexture *Texture)
QOpenGLShaderProgram * CreateComputeShader(const QString &Source)
RenderType Type(void) const
static bool DisplayIsRemote()
Determine if we are running a remote X11 session.
OpenGLLocker(MythRenderOpenGL *Render)
MythRenderOpenGL * m_render
unsigned int uint
Definition: compat.h:60
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
bool HasMythMainWindow(void)
@ kRenderOpenGL
#define LOC
static constexpr QLatin1String GLYesNo(bool v)
static constexpr GLuint kTextureOffset
static const float kLimitedRangeScale
static constexpr GLint TEXTURE_SIZE
static constexpr GLuint TEXTURE_INDEX
static constexpr GLuint COLOR_INDEX
QOpenGLShaderProgram * ShaderError(QOpenGLShaderProgram *Shader, const QString &Source)
static const float kLimitedRangeOffset
static constexpr int MAX_VERTEX_CACHE
static constexpr GLuint VERTEX_INDEX
static constexpr GLuint kVertexOffset
static constexpr GLint VERTEX_SIZE
@ kShaderRect
@ kShaderDefault
@ kShaderEdge
@ kShaderSimple
@ kGLLegacyTextures
@ kGL16BitFBO
@ kGLGeometryShaders
@ kGLTiled
@ kGLNVMemory
@ kGLBufferMap
@ kGLExtSubimage
@ kGLComputeShaders
@ kGLExtRects
static constexpr size_t TEX_OFFSET
#define GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX
#define GL_TEXTURE0
#define GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX
#define GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX
GLboolean(APIENTRY *)(GLenum target) MYTH_GLUNMAPBUFFERPROC
GLvoid *(APIENTRY *)(GLenum target, GLenum access) MYTH_GLMAPBUFFERPROC
static const QString kRoundedEdgeShader
static const QString kDefaultVertexShader
static const QString kRoundedRectShader
static const QString kDrawVertexShader
static const QString kDefaultFragmentShaderLimited
static const QString kSimpleVertexShader
static const QString kDefaultFragmentShader
static const QString kSimpleFragmentShader
MBASE_PUBLIC long long copy(QFile &dst, QFile &src, uint block_size=0)
Copies src file to dst file.
static QString Source(const QNetworkRequest &request)
Definition: netstream.cpp:139
Definition: graphic.h:5
VERBOSE_PREAMBLE Most true
Definition: verbosedefs.h:86
VERBOSE_PREAMBLE Most debug(nodatabase, notimestamp, noextra)") VERBOSE_MAP(VB_GENERAL