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