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