MythTV master
mythopenglvideo.cpp
Go to the documentation of this file.
1// std
2#include <algorithm>
3#include <utility>
4
5// Qt
6#include <QPen>
7
8// MythTV
9#include "libmythbase/mythconfig.h"
13#include "mythavutil.h"
17#include "tv.h"
18
19// FFmpeg
20extern "C" {
21#include "libavutil/stereo3d.h"
22}
23
24#define LOC QString("GLVid: ")
25// static constexpr int8_t MAX_VIDEO_TEXTURES { 10 }; // YV12 Kernel deinterlacer + 1
26
38 MythVideoBounds* Bounds, const MythVideoProfilePtr& VideoProfile, const QString& Profile)
39 : MythVideoGPU(Render, ColourSpace, Bounds, VideoProfile, Profile),
40 m_openglRender(Render)
41{
43 {
44 LOG(VB_GENERAL, LOG_ERR, LOC + "Fatal error");
45 return;
46 }
47
49 if (m_openglRender->isOpenGLES())
50 m_gles = m_openglRender->format().majorVersion();
51
52 // Set OpenGL feature support
55 m_valid = true;
56
57 m_chromaUpsamplingFilter = gCoreContext->GetBoolSetting("ChromaUpsamplingFilter", true);
58 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Chroma upsampling filter %1")
59 .arg(m_chromaUpsamplingFilter ? "enabled" : "disabled"));
60}
61
63{
64 if (!m_openglRender)
65 return;
66
69 delete m_toneMap;
71}
72
73void MythOpenGLVideo::ColourSpaceUpdate(bool PrimariesChanged)
74{
76
77 // if input/output type are unset - we haven't created the shaders yet
78 if (PrimariesChanged && (m_outputType != FMT_NONE))
79 {
80 LOG(VB_GENERAL, LOG_INFO, LOC + "Primaries conversion changed - recreating shaders");
82 }
83
84 float colourgamma = m_videoColourSpace->GetColourGamma();
85 float displaygamma = 1.0F / m_videoColourSpace->GetDisplayGamma();
86 QMatrix4x4 primary = m_videoColourSpace->GetPrimaryMatrix();
87 for (size_t i = Progressive; i < ShaderCount; ++i)
88 {
90 m_openglRender->SetShaderProgramParams(m_shaders[i], primary, "m_primaryMatrix");
91 if (m_shaders[i])
92 {
93 m_shaders[i]->setUniformValue("m_colourGamma", colourgamma);
94 m_shaders[i]->setUniformValue("m_displayGamma", displaygamma);
95 }
96 }
97}
98
100{
101 if (m_inputTextureSize.isEmpty())
102 return;
103
105 bool rect = m_textureTarget == QOpenGLTexture::TargetRectangle;
106 GLfloat lineheight = rect ? 1.0F : 1.0F / m_inputTextureSize.height();
107 GLfloat maxheight = rect ? m_videoDispDim.height() : m_videoDispDim.height() /
108 static_cast<GLfloat>(m_inputTextureSize.height());
109 GLfloat fieldsize = rect ? 0.5F : m_inputTextureSize.height() / 2.0F;
110 QVector4D parameters(lineheight, /* lineheight */
111 static_cast<GLfloat>(m_inputTextureSize.width()), /* 'Y' select */
112 maxheight - lineheight, /* maxheight */
113 fieldsize /* fieldsize */);
114
115 for (size_t i = Progressive; i < ShaderCount; ++i)
116 {
117 if (m_shaders[i])
118 {
120 m_shaders[i]->setUniformValue("m_frameData", parameters);
121 if (BicubicUpsize == i)
122 {
123 QVector2D size { rect ? 1.0F : static_cast<GLfloat>(m_videoDim.width()),
124 rect ? 1.0F : static_cast<GLfloat>(m_videoDim.height()) };
125 m_shaders[i]->setUniformValue("m_textureSize", size);
126 }
127 }
128 }
129}
130
132{
136}
137
139{
140 // If switching off/from basic deinterlacing, then we need to delete and
141 // recreate the input textures and sometimes the shaders as well - so start
142 // from scratch
144 {
145 // Note. Textures will be created with linear filtering - which matches
146 // no resizing - which should be the case for the basic deinterlacer - and
147 // the call to SetupFrameFormat will reset resizing anyway
148 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Removing single field textures");
149 // revert to YUY2 if preferred
150 if ((m_inputType == FMT_YV12) && (m_profile == "opengl"))
153 emit OutputChanged(m_videoDim, m_videoDim, -1.0F);
154 }
157 m_deinterlacer2x = false;
158}
159
161 MythDeintType Filter /* = DEINT_SHADER */,
162 bool CreateReferences /* = true */)
163{
164 if (!Frame)
165 return false;
166
167 // do we want an opengl shader?
168 // shaders trump CPU deinterlacers if selected and driver deinterlacers will only
169 // be available under restricted circumstances
170 // N.B. there should in theory be no situation in which shader deinterlacing is not
171 // available for software formats, hence there should be no need to fallback to cpu
172
173 if (!is_interlaced(Scan) || Frame->m_alreadyDeinterlaced)
174 {
176 return false;
177 }
178
179 m_deinterlacer2x = true;
180 MythDeintType deinterlacer = Frame->GetDoubleRateOption(Filter);
181 MythDeintType other = Frame->GetDoubleRateOption(DEINT_DRIVER);
182 if (other) // another double rate deinterlacer is enabled
183 {
185 return false;
186 }
187
188 if (!deinterlacer)
189 {
190 m_deinterlacer2x = false;
191 deinterlacer = Frame->GetSingleRateOption(Filter);
192 other = Frame->GetSingleRateOption(DEINT_DRIVER);
193 if (!deinterlacer || other) // no shader deinterlacer needed
194 {
196 return false;
197 }
198 }
199
200 // if we get this far, we cannot use driver deinterlacers, shader deints
201 // are preferred over cpu, we have a deinterlacer but don't actually care whether
202 // it is single or double rate
203 if (m_deinterlacer == deinterlacer || (m_fallbackDeinterlacer && (m_fallbackDeinterlacer == deinterlacer)))
204 return true;
205
206 // Lock
208
209 // delete old reference textures
212
213 // For basic deinterlacing of software frames, we now create 2 sets of field
214 // based textures - which is the same approach taken by the CPU based onefield/bob
215 // deinterlacer and the EGL basic deinterlacer. The advantages of this
216 // approach are:-
217 // - no dependent texturing in the samplers (it is just a basic YUV to RGB conversion
218 // in the shader)
219 // - better quality (the onefield shader line doubles but does not have the
220 // implicit interpolation/smoothing of using separate textures directly,
221 // which leads to 'blockiness').
222 // - as we are not sampling other fields, there is no need to use an intermediate
223 // framebuffer to ensure accurate sampling - so we can skip the resize stage.
224 //
225 // YUYV formats are currently not supported as it does not work correctly - force YV12 instead.
226
227 if (deinterlacer == DEINT_BASIC && MythVideoFrame::YUVFormat(m_inputType))
228 {
229 if (m_outputType == FMT_YUY2)
230 {
231 LOG(VB_GENERAL, LOG_INFO, LOC + "Forcing OpenGL YV12 for basic deinterlacer");
233 }
235 QSize size(m_videoDim.width(), m_videoDim.height() >> 1);
236 std::vector<QSize> sizes;
237 sizes.emplace_back(size);
238 // N.B. If we are currently resizing, it will be turned off for this
239 // deinterlacer, so the default linear texture filtering is OK.
241 // nextTextures will hold the other field
243 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Created %1 single field textures")
244 .arg(m_inputTextures.size() * 2));
245 // Con MythVideoBounds into display the field only
246 emit OutputChanged(m_videoDim, size, -1.0F);
247 }
248
249 // sanity check max texture units. Should only be an issue on old hardware (e.g. Pi)
251 uint refstocreate = ((deinterlacer == DEINT_HIGH) && CreateReferences) ? 2 : 0;
252 int totaltextures = static_cast<int>(MythVideoFrame::GetNumPlanes(m_outputType)) * static_cast<int>(refstocreate + 1);
253 if (totaltextures > max)
254 {
255 m_fallbackDeinterlacer = deinterlacer;
256 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Insufficent texture units for deinterlacer '%1' (%2 < %3)")
257 .arg(MythVideoFrame::DeinterlacerName(deinterlacer | DEINT_SHADER, m_deinterlacer2x)).arg(max).arg(totaltextures));
258 deinterlacer = DEINT_BASIC;
259 LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Falling back to '%1'")
261 }
262
263 // create new deinterlacers - the old ones will be deleted
264 if (!(CreateVideoShader(InterlacedBot, deinterlacer) && CreateVideoShader(InterlacedTop, deinterlacer)))
265 return false;
266
267 // create the correct number of reference textures
268 if (refstocreate)
269 {
270 std::vector<QSize> sizes;
271 sizes.emplace_back(m_videoDim);
274 // ensure we use GL_NEAREST if resizing is already active and needed
275 if ((m_resizing & Sampling) == Sampling)
276 {
279 }
280 }
281
282 // ensure they work correctly
283 UpdateColourSpace(false);
285 m_deinterlacer = deinterlacer;
286
287 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Created deinterlacer '%1' (%2->%3)")
291 return true;
292}
293
302{
303 if (!m_openglRender || !(m_features & QOpenGLFunctions::Shaders))
304 return false;
305
306 // delete the old
307 if (m_shaders[Type])
309 m_shaders[Type] = nullptr;
310
311 QStringList defines;
312 QString vertex = DefaultVertexShader;
313 QString fragment;
314 int cost = 1;
315
317 defines << "EXTOES";
318
319 if ((Default == Type) || (BicubicUpsize == Type) || (!MythVideoFrame::YUVFormat(m_outputType)))
320 {
321 QString glsldefines;
322 for (const QString& define : std::as_const(defines))
323 glsldefines += QString("#define MYTHTV_%1\n").arg(define);
324 fragment = glsldefines + YUVFragmentExtensions + ((BicubicUpsize == Type) ? BicubicShader : RGBFragmentShader);
325
326#if CONFIG_MEDIACODEC
328 vertex = MediaCodecVertexShader;
329#endif
330 }
331 // no interlaced shaders yet (i.e. interlaced chroma upsampling - not deinterlacers)
332 else
333 {
334 fragment = YUVFragmentShader;
335 QString extensions = YUVFragmentExtensions;
336 QString glsldefines;
337
338 // Any software frames that are not 8bit need to use unsigned integer
339 // samplers with GLES3.x - which need more modern shaders
341 {
342 static const QString glsl300("#version 300 es\n");
343 fragment = GLSL300YUVFragmentShader;
344 extensions = GLSL300YUVFragmentExtensions;
345 vertex = glsl300 + GLSL300VertexShader;
346 glsldefines.append(glsl300);
347 }
348
349 bool kernel = false;
350 bool topfield = InterlacedTop == Type;
351 bool progressive = (Progressive == Type) || (Deint == DEINT_NONE);
353 {
354 defines << "YV12";
355 cost = 3;
356 }
358 {
359 defines << "NV12";
360 cost = 2;
361 }
362 else if (FMT_YUY2 == m_outputType)
363 {
364 defines << "YUY2";
365 }
366
367#if CONFIG_VIDEOTOOLBOX
368 // N.B. Rectangular texture support is only currently used for VideoToolBox
369 // video frames which are NV12. Do not use rectangular textures for the 'default'
370 // shaders as it breaks video resizing and would require changes to our
371 // FramebufferObject code.
372 if ((m_textureTarget == QOpenGLTexture::TargetRectangle) && (Default != Type))
373 defines << "RECTS";
374#endif
375 if (!progressive)
376 {
377 bool basic = Deint == DEINT_BASIC && MythVideoFrame::YUVFormat(m_inputType);
378 // Chroma upsampling filter
380 m_chromaUpsamplingFilter && !basic)
381 {
382 defines << "CUE";
383 }
384
385 // field
386 if (topfield && !basic)
387 defines << "TOPFIELD";
388
389 switch (Deint)
390 {
391 case DEINT_BASIC:
392 if (!basic)
393 {
394 cost *= 2;
395 defines << "ONEFIELD";
396 }
397 break;
398 case DEINT_MEDIUM: cost *= 5; defines << "LINEARBLEND"; break;
399 case DEINT_HIGH: cost *= 15; defines << "KERNEL"; kernel = true; break;
400 default: break;
401 }
402 }
403
404 // Start building the new fragment shader
405 // We do this in code otherwise the shader string becomes monolithic
406
407 // 'expand' calls to sampleYUV for multiple planes
408 // do this before we add the samplers
409 int count = static_cast<int>(MythVideoFrame::GetNumPlanes(m_outputType));
410 for (int i = (kernel ? 2 : 0); (i >= 0) && count; i--)
411 {
412 QString find = QString("s_texture%1").arg(i);
413 QStringList replacelist;
414 replacelist.reserve(count);
415 for (int j = (i * count); j < ((i + 1) * count); ++j)
416 replacelist << QString("s_texture%1").arg(j);
417 fragment.replace(find, replacelist.join(", "));
418 }
419
420 // 'expand' calls to the kernel function
421 if (kernel && count)
422 {
423 for (int i = 1 ; i >= 0; i--)
424 {
425 QString find1 = QString("sampler2D kernelTex%1").arg(i);
426 QString find2 = QString("kernelTex%1").arg(i);
427 QStringList replacelist1;
428 QStringList replacelist2;
429 replacelist1.reserve(count);
430 replacelist2.reserve(count);
431 for (int j = 0; j < count; ++j)
432 {
433 replacelist1 << QString("sampler2D kernelTexture%1%2").arg(i).arg(j);
434 replacelist2 << QString("kernelTexture%1%2").arg(i).arg(j);
435 }
436 fragment.replace(find1, replacelist1.join(", "));
437 fragment.replace(find2, replacelist2.join(", "));
438 }
439 }
440
441 // Retrieve colour mappping defines
443
444 // Add defines
445 for (const QString& define : std::as_const(defines))
446 glsldefines += QString("#define MYTHTV_%1\n").arg(define);
447
448 // Add the required samplers
449 int start = 0;
450 int end = count;
451 if (kernel)
452 {
453 end *= 3;
454 if (topfield)
455 start += count;
456 else
457 end -= count;
458 }
459 QString glslsamplers;
460 for (int i = start; i < end; ++i)
461 glslsamplers += QString("uniform sampler2D s_texture%1;\n").arg(i);
462
463 // construct the final shader string
464 fragment = glsldefines + extensions + glslsamplers + fragment;
465 }
466
467 m_shaderCost[Type] = cost;
468 QOpenGLShaderProgram *program = m_openglRender->CreateShaderProgram(vertex, fragment);
469 if (!program)
470 return false;
471
472 m_shaders[Type] = program;
473 return true;
474}
475
477 QSize Size, GLenum TextureTarget)
478{
479 QString texnew { "2D" };
480 if (TextureTarget == QOpenGLTexture::TargetRectangle)
481 texnew = "Rect";
482 else if (TextureTarget == GL_TEXTURE_EXTERNAL_OES)
483 texnew = "OES";
484
485 QString texold { "2D" };
486 if (m_textureTarget == QOpenGLTexture::TargetRectangle)
487 texold = "Rect";
489 texold = "OES";
490
491 LOG(VB_GENERAL, LOG_INFO, LOC +
492 QString("New frame format: %1:%2 %3x%4 (Tex: %5) -> %6:%7 %8x%9 (Tex: %10)")
495 QString::number(m_videoDim.width()),
496 QString::number(m_videoDim.height()),
497 texold,
500 QString::number(Size.width()),
501 QString::number(Size.height()))
502 .arg(texnew));
503
505
506 m_inputType = InputType;
507 m_outputType = OutputType;
508 m_textureTarget = TextureTarget;
509 m_videoDim = Size;
510
511 // This is only currently needed for RGBA32 frames from composed DRM_PRIME
512 // textures that may be half height for simple bob deinterlacing
514 emit OutputChanged(m_videoDim, m_videoDim, -1.0F);
515
516 if (!MythVideoFrame::HardwareFormat(InputType))
517 {
518 std::vector<QSize> sizes;
519 sizes.push_back(Size);
521 if (m_inputTextures.empty())
522 {
523 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create input textures");
524 return false;
525 }
526
527 m_inputTextureSize = m_inputTextures[0]->m_totalSize;
528 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Created %1 input textures for '%2'")
529 .arg(m_inputTextures.size()).arg(GetProfile()));
530 }
531 else
532 {
533 m_inputTextureSize = Size;
534 }
535
536 // Create shaders
538 return false;
539
540 UpdateColourSpace(false);
542 return true;
543}
544
546{
547 for (auto & shader : m_shaders)
548 if (shader)
550 m_shaders.fill(nullptr);
551 m_shaderCost.fill(1);
555 m_textureTarget = QOpenGLTexture::Target2D;
559 m_frameBuffer = nullptr;
560 m_frameBufferTexture = nullptr;
561
563}
564
567{
568 if (Frame->m_type == FMT_NONE)
569 return;
570
571 // Hardware frames are retrieved/updated in PrepareFrame but we need to
572 // reset software frames now if necessary
574 {
576 {
577 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Resetting input format");
579 }
580 return;
581 }
582
583 // Sanitise frame
584 if ((Frame->m_width < 1) || (Frame->m_height < 1) || !Frame->m_buffer)
585 {
586 LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid software frame");
587 return;
588 }
589
590 // Can we render this frame format
591 if (!MythVideoFrame::YUVFormat(Frame->m_type))
592 {
593 LOG(VB_GENERAL, LOG_ERR, LOC + "Frame format is not supported");
594 return;
595 }
596
597 // lock
599
600 // check for input changes
601 if ((Frame->m_width != m_videoDim.width()) ||
602 (Frame->m_height != m_videoDim.height()) ||
603 (Frame->m_type != m_inputType))
604 {
605 VideoFrameType frametype = Frame->m_type;
606 if ((frametype == FMT_YV12) && (m_profile == "opengl"))
607 frametype = FMT_YUY2;
608 QSize size(Frame->m_width, Frame->m_height);
609 if (!SetupFrameFormat(Frame->m_type, frametype, size, QOpenGLTexture::Target2D))
610 return;
611 }
612
613 // Setup deinterlacing if required
614 AddDeinterlacer(Frame, Scan);
615
616 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
617 m_openglRender->logDebugMarker(LOC + "UPDATE_FRAME_START");
618
620
621 // Rotate textures if necessary
622 bool current = true;
624 {
625 if (!m_nextTextures.empty() && !m_prevTextures.empty())
626 {
627 if (qAbs(Frame->m_frameCounter - m_discontinuityCounter) > 1)
629 std::vector<MythVideoTextureOpenGL*> temp = m_prevTextures;
632 m_nextTextures = temp;
633 current = false;
634 }
635 }
636
637 m_discontinuityCounter = Frame->m_frameCounter;
638
640 {
641 // first field. Fake the pitches
642 FramePitches pitches = Frame->m_pitches;
643 Frame->m_pitches[0] = Frame->m_pitches[0] << 1;
644 Frame->m_pitches[1] = Frame->m_pitches[1] << 1;
645 Frame->m_pitches[2] = Frame->m_pitches[2] << 1;
647 // second field. Fake the offsets as well.
648 FrameOffsets offsets = Frame->m_offsets;
649 Frame->m_offsets[0] = Frame->m_offsets[0] + pitches[0];
650 Frame->m_offsets[1] = Frame->m_offsets[1] + pitches[1];
651 Frame->m_offsets[2] = Frame->m_offsets[2] + pitches[2];
653 Frame->m_pitches = pitches;
654 Frame->m_offsets = offsets;
655 }
656 else
657 {
659 }
660
661 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
662 m_openglRender->logDebugMarker(LOC + "UPDATE_FRAME_END");
663}
664
666 StereoscopicMode StereoOverride, bool DrawBorder)
667{
668 if (!m_openglRender)
669 return;
670
672
673 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
674 m_openglRender->logDebugMarker(LOC + "RENDER_FRAME_START");
675
676 // Set required input textures for the last stage
677 // ProcessFrame is always called first, which will create/destroy software
678 // textures as needed
679 bool hwframes = false;
680 bool useframebufferimage = false;
681
682 // for tiled renderers (e.g. Pi), enable scissoring to try and improve performance
683 // when not full screen and avoid the resize stage unless absolutely necessary
684 bool tiled = m_extraFeatures & kGLTiled;
685
686 // We lose the pause frame when seeking and using VDPAU/VAAPI/NVDEC direct rendering.
687 // As a workaround, we always use the resize stage so the last displayed frame
688 // should be retained in the Framebuffer used for resizing. If there is
689 // nothing to display, then fallback to this framebuffer.
690 // N.B. this is now strictly necessary with v4l2 and DRM PRIME direct rendering
691 // but ignore now for performance reasons
692 VideoResizing resize;
693 if (Frame)
695 else
697
698 std::vector<MythVideoTextureOpenGL*> inputtextures = m_inputTextures;
699 if (inputtextures.empty())
700 {
701 // This is experimental support for direct rendering to a framebuffer (e.g. DRM).
702 // It may be removed or refactored (e.g. pass presentation details through to
703 // the interop).
704 if (Frame)
705 {
706 Frame->m_displayed = false;
707 Frame->m_srcRect = m_videoRect;
708 Frame->m_dstRect = m_displayVideoRect;
709 }
710
711 // Pull in any hardware frames
713
714 if (Frame && Frame->m_displayed)
715 return;
716
717 if (!inputtextures.empty())
718 {
719 hwframes = true;
720 QSize newsize = inputtextures[0]->m_size;
721 VideoFrameType newsourcetype = inputtextures[0]->m_frameType;
722 VideoFrameType newtargettype = inputtextures[0]->m_frameFormat;
723 GLenum newtargettexture = inputtextures[0]->m_target;
724 if ((m_outputType != newtargettype) || (m_textureTarget != newtargettexture) ||
725 (m_inputType != newsourcetype) || (newsize != m_inputTextureSize))
726 {
727 SetupFrameFormat(newsourcetype, newtargettype, newsize, newtargettexture);
728 }
729
730#if CONFIG_MEDIACODEC
731 // Set the texture transform for mediacodec
733 {
734 if (inputtextures[0]->m_transform && m_shaders[Default])
735 {
737 m_shaders[Default]->setUniformValue("u_transform", *inputtextures[0]->m_transform);
738 }
739 }
740#endif
741 // Enable deinterlacing for NVDEC, VTB and VAAPI DRM if VPP is not available
742 if (inputtextures[0]->m_allowGLSLDeint)
743 AddDeinterlacer(Frame, Scan, DEINT_SHADER | DEINT_CPU, false); // pickup shader or cpu prefs
744 }
745 else
746 {
747 if ((resize == Framebuffer) && m_frameBuffer && m_frameBufferTexture)
748 {
749 LOG(VB_PLAYBACK, LOG_DEBUG, "Using existing framebuffer");
750 useframebufferimage = true;
751 }
752 else
753 {
754 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "Nothing to display");
755 // if this is live tv startup and the window rect has changed we
756 // must set the viewport
758 return;
759 }
760 }
761 }
762
763 // Determine which shader to use. This helps optimise the resize check.
764 bool deinterlacing = false;
765 bool basicdeinterlacing = false;
766 bool yuvoutput = MythVideoFrame::YUVFormat(m_outputType);
767 VideoShaderType program = yuvoutput ? Progressive : Default;
769 {
770 if (Scan == kScan_Interlaced)
771 {
772 program = TopFieldFirst ? InterlacedTop : InterlacedBot;
773 deinterlacing = true;
774 }
775 else if (Scan == kScan_Intr2ndField)
776 {
777 program = TopFieldFirst ? InterlacedBot : InterlacedTop;
778 deinterlacing = true;
779 }
780
781 // select the correct field for the basic deinterlacer
783 {
784 basicdeinterlacing = true;
785 if (program == InterlacedBot)
786 inputtextures = m_nextTextures;
787 }
788 }
789
790 // Set deinterlacer type for debug OSD
791 if (deinterlacing && Frame)
792 {
793 Frame->m_deinterlaceInuse = m_deinterlacer | DEINT_SHADER;
794 Frame->m_deinterlaceInuse2x = m_deinterlacer2x;
795 }
796
797 // Tonemapping can only render to a texture
798 if (m_toneMap)
799 resize |= ToneMap;
800
801 // Decide whether to use render to texture - for performance or quality
802 if (yuvoutput && !resize)
803 {
804 // ensure deinterlacing works correctly when down scaling in height
805 // N.B. not needed for the basic deinterlacer
806 if (deinterlacing && !basicdeinterlacing && (m_videoDispDim.height() > m_displayVideoRect.height()))
807 resize |= Deinterlacer;
808
809 // NB GL_NEAREST introduces some 'minor' chroma sampling errors
810 // for the following 2 cases. For YUY2 this may be better handled in the
811 // shader. For GLES3.0 10bit textures - Vulkan is probably the better solution.
812
813 // UYVY packed pixels must be sampled exactly with GL_NEAREST
814 if (FMT_YUY2 == m_outputType)
815 resize |= Sampling;
816 // unsigned integer texture formats need GL_NEAREST sampling
818 resize |= Sampling;
819
820 // don't enable resizing if the cost of a framebuffer switch may be
821 // prohibitive (e.g. Raspberry Pi/tiled renderers) or for basic deinterlacing,
822 // where we are trying to simplify/optimise rendering (and the framebuffer
823 // sizing gets confused by the change to m_videoDispDim)
824 if (!resize && !tiled && !basicdeinterlacing)
825 {
826 // improve performance. This is an educated guess on the relative cost
827 // of render to texture versus straight rendering.
828 int totexture = m_videoDispDim.width() * m_videoDispDim.height() * m_shaderCost[program];
829 int blitcost = m_displayVideoRect.width() * m_displayVideoRect.height() * m_shaderCost[Default];
830 int noresizecost = m_displayVideoRect.width() * m_displayVideoRect.height() * m_shaderCost[program];
831 if ((totexture + blitcost) < noresizecost)
832 resize |= Performance;
833 }
834 }
835
836 // Bicubic upsizing - test this after all other resize options have been checked
837 // to ensure it is not the only flag set
838 if (m_bicubicUpsize)
839 SetupBicubic(resize);
840
841 // We don't need an extra stage prior to bicubic if the frame is already RGB (e.g. VDPAU, MediaCodec)
842 // So bypass if we only set resize for bicubic.
843 bool needresize = resize && (!MythVideoFrame::FormatIsRGB(m_outputType) || (resize != Bicubic));
844
845 // set software frame filtering if resizing has changed
846 if (!needresize && m_resizing)
847 {
848 // remove framebuffer
850 {
852 m_frameBufferTexture = nullptr;
853 }
854 if (m_frameBuffer)
855 {
857 m_frameBuffer = nullptr;
858 }
859 // set filtering
864 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Disabled resizing");
865 }
866 else if (!m_resizing && needresize)
867 {
868 // framebuffer will be created as needed below
869 QOpenGLTexture::Filter filter = ((resize & Sampling) == Sampling) ? QOpenGLTexture::Nearest : QOpenGLTexture::Linear;
873 m_resizing = resize;
874 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Resizing from %1x%2 to %3x%4 for %5")
875 .arg(m_videoDispDim.width()).arg(m_videoDispDim.height())
876 .arg(m_displayVideoRect.width()).arg(m_displayVideoRect.height())
877 .arg(VideoResizeToString(resize)));
878 }
879
880 // check hardware frames have the correct filtering
881 if (hwframes)
882 {
883 QOpenGLTexture::Filter filter = (resize.testFlag(Sampling)) ? QOpenGLTexture::Nearest : QOpenGLTexture::Linear;
884 if (inputtextures[0]->m_filter != filter)
886 }
887
888 // texture coordinates
889 QRect trect(m_videoRect);
890
891 if (needresize)
892 {
893 MythVideoTextureOpenGL* nexttexture = nullptr;
894
895 // only render to the framebuffer if there is something to update
896 if (useframebufferimage)
897 {
898 if (m_toneMap)
899 {
900 nexttexture = m_toneMap->GetTexture();
901 trect = QRect(QPoint(0, 0), m_displayVideoRect.size());
902 }
903 else
904 {
905 nexttexture = m_frameBufferTexture;
906 }
907 }
908 else if (m_toneMap)
909 {
910 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
911 m_openglRender->logDebugMarker(LOC + "RENDER_TO_TEXTURE");
912 nexttexture = m_toneMap->Map(inputtextures, m_displayVideoRect.size());
913 trect = QRect(QPoint(0, 0), m_displayVideoRect.size());
914 }
915 else
916 {
917 // render to texture stage
918 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
919 m_openglRender->logDebugMarker(LOC + "RENDER_TO_TEXTURE");
920
921 // we need a framebuffer and associated texture
922 if (!m_frameBuffer)
923 {
925 (fbo != nullptr) && (tex != nullptr))
926 {
927 delete m_frameBuffer;
929 m_frameBuffer = fbo;
931 m_openglRender->SetTextureFilters(m_frameBufferTexture, QOpenGLTexture::Linear);
932 }
933 }
934
936 return;
937
938 // coordinates
939 QRect vrect(QPoint(0, 0), m_videoDispDim);
940 QRect trect2 = vrect;
941 if (FMT_YUY2 == m_outputType)
942 trect2.setWidth(m_videoDispDim.width() >> 1);
943
944 // framebuffer
947
948 // bind correct textures
949 std::vector<MythGLTexture*> textures {};
950 BindTextures(deinterlacing, inputtextures, textures);
951
952 // render
953 m_openglRender->DrawBitmap(textures, m_frameBuffer, trect2, vrect,
954 m_shaders[program], 0);
955 nexttexture = m_frameBufferTexture;
956 }
957
958 // reset for next stage
959 inputtextures.clear();
960 inputtextures.push_back(nexttexture);
961 program = Default;
962 deinterlacing = false;
963 }
964
965 // Use the bicubic shader if necessary
966 if (resize.testFlag(Bicubic))
967 program = BicubicUpsize;
968
969 // render to default framebuffer/screen
970 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
971 m_openglRender->logDebugMarker(LOC + "RENDER_TO_SCREEN");
972
973 // discard stereoscopic fields
974 StereoscopicMode stereo = StereoOverride;
975 m_lastStereo = Frame ? Frame->m_stereo3D : m_lastStereo;
976 // N.B. kStereoscopicModeSideBySideDiscard is a proxy here for discard of all types
977 if ((stereo == kStereoscopicModeAuto) &&
979 (m_lastStereo != AV_STEREO3D_2D))
980 {
981 if (m_lastStereo == AV_STEREO3D_SIDEBYSIDE)
983 else if (m_lastStereo == AV_STEREO3D_TOPBOTTOM)
985 }
986
988 trect = QRect(trect.left() >> 1, trect.top(), trect.width() >> 1, trect.height());
989 else if (kStereoscopicModeTopAndBottomDiscard == stereo)
990 trect = QRect(trect.left(), trect.top() >> 1, trect.width(), trect.height() >> 1);
991
992 // bind default framebuffer
995
996 // PiP border
997 if (DrawBorder)
998 {
999 QRect piprect = m_displayVideoRect.adjusted(-10, -10, +10, +10);
1000 static const QPen kNopen(Qt::NoPen);
1001 static const QBrush kRedBrush(QBrush(QColor(127, 0, 0, 255)));
1002 m_openglRender->DrawRect(nullptr, piprect, kRedBrush, kNopen, 255);
1003 }
1004
1005 // bind correct textures
1006 std::vector<MythGLTexture*> textures;
1007 BindTextures(deinterlacing, inputtextures, textures);
1008
1009 // rotation
1010 if (Frame)
1011 m_lastRotation = Frame->m_rotation;
1012
1013 // apply scissoring
1014 if (tiled)
1015 {
1016 // N.B. It's not obvious whether this helps
1017 m_openglRender->glEnable(GL_SCISSOR_TEST);
1018 m_openglRender->glScissor(m_displayVideoRect.left() - 1, m_displayVideoRect.top() - 1,
1019 m_displayVideoRect.width() + 2, m_displayVideoRect.height() + 2);
1020 }
1021
1022 // draw
1023 m_openglRender->DrawBitmap(textures, nullptr, trect, m_displayVideoRect,
1024 m_shaders[program], m_lastRotation);
1025
1026 // disable scissoring
1027 if (tiled)
1028 m_openglRender->glDisable(GL_SCISSOR_TEST);
1029
1030 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
1031 m_openglRender->logDebugMarker(LOC + "RENDER_FRAME_END");
1032}
1033
1036{
1037 for (auto & texture : m_inputTextures)
1038 texture->m_valid = false;
1039 for (auto & texture : m_prevTextures)
1040 texture->m_valid = false;
1041 for (auto & texture : m_nextTextures)
1042 texture->m_valid = false;
1043}
1044
1045void MythOpenGLVideo::BindTextures(bool Deinterlacing, std::vector<MythVideoTextureOpenGL*>& Current,
1046 std::vector<MythGLTexture*>& Textures)
1047{
1048 if (Deinterlacing && !MythVideoFrame::HardwareFormat(m_inputType))
1049 {
1050 if ((m_nextTextures.size() == Current.size()) && (m_prevTextures.size() == Current.size()))
1051 {
1052 // if we are using reference frames, we want the current frame in the middle
1053 // but next will be the first valid, followed by current...
1054 size_t count = Current.size();
1055 std::vector<MythVideoTextureOpenGL*>& current = Current[0]->m_valid ? Current : m_nextTextures;
1056 std::vector<MythVideoTextureOpenGL*>& prev = m_prevTextures[0]->m_valid ? m_prevTextures : current;
1057
1058 for (uint i = 0; i < count; ++i)
1059 Textures.push_back(reinterpret_cast<MythGLTexture*>(prev[i]));
1060 for (uint i = 0; i < count; ++i)
1061 Textures.push_back(reinterpret_cast<MythGLTexture*>(current[i]));
1062 for (uint i = 0; i < count; ++i)
1063 Textures.push_back(reinterpret_cast<MythGLTexture*>(m_nextTextures[i]));
1064 return;
1065 }
1066 }
1067
1068 std::ranges::transform(Current, std::back_inserter(Textures),
1069 [](MythVideoTextureOpenGL* Tex) { return reinterpret_cast<MythGLTexture*>(Tex); });
1070}
1071
1073{
1075 return "opengl-hw";
1076
1077 switch (Type)
1078 {
1079 case FMT_YUY2: return "opengl"; // compatibility with old profiles
1080 case FMT_YV12: return "opengl-yv12";
1081 case FMT_NV12: return "opengl-nv12";
1082 default: break;
1083 }
1084 return "opengl";
1085}
1086
1087void MythOpenGLVideo::SetupBicubic(VideoResizing& Resize)
1088{
1089 if (((m_videoDispDim.width() < m_displayVideoRect.width()) ||
1090 (m_videoDispDim.height() < m_displayVideoRect.height())))
1091 {
1093 {
1095 {
1096 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create bicubic shader. Disabling");
1097 m_bicubicUpsize = false;
1098 }
1099 else
1100 {
1102 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Created bicubic sampler");
1103 }
1104 }
1105
1107 Resize |= Bicubic;
1108 }
1109 else
1110 {
1111 if (m_shaders[BicubicUpsize] != nullptr)
1112 {
1113 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Disabling bicubic sampler");
1114 delete m_shaders[BicubicUpsize];
1115 m_shaders[BicubicUpsize] = nullptr;
1116 }
1117 }
1118}
1119
1120#include "moc_mythopenglvideo.cpp"
bool GetBoolSetting(const QString &key, bool defaultval=false)
static std::vector< MythVideoTextureOpenGL * > Retrieve(MythRenderOpenGL *Context, MythVideoColourSpace *ColourSpace, MythVideoFrame *Frame, FrameScanType Scan)
MythVideoTextureOpenGL * GetTexture()
MythVideoTextureOpenGL * Map(std::vector< MythVideoTextureOpenGL * > &Inputs, QSize DisplaySize)
std::vector< MythVideoTextureOpenGL * > m_prevTextures
MythVideoTextureOpenGL * m_frameBufferTexture
bool SetupFrameFormat(VideoFrameType InputType, VideoFrameType OutputType, QSize Size, GLenum TextureTarget)
QOpenGLFramebufferObject * m_frameBuffer
MythOpenGLVideo(MythRenderOpenGL *Render, MythVideoColourSpace *ColourSpace, MythVideoBounds *Bounds, const MythVideoProfilePtr &VideoProfile, const QString &Profile)
~MythOpenGLVideo() override
void BindTextures(bool Deinterlacing, std::vector< MythVideoTextureOpenGL * > &Current, std::vector< MythGLTexture * > &Textures)
bool m_chromaUpsamplingFilter
std::array< int, ShaderCount > m_shaderCost
std::array< QOpenGLShaderProgram *, ShaderCount > m_shaders
static QString TypeToProfile(VideoFrameType Type)
QOpenGLFunctions::OpenGLFeatures m_features
MythOpenGLTonemap * m_toneMap
std::vector< MythVideoTextureOpenGL * > m_inputTextures
void ResetTextures() override
Clear reference frames after a seek as they will contain old images.
std::vector< MythVideoTextureOpenGL * > m_nextTextures
void PrepareFrame(MythVideoFrame *Frame, FrameScanType Scan=kScan_Progressive) override
Update the current input texture using the data from the given video frame.
void ColourSpaceUpdate(bool PrimariesChanged) override
MythDeintType m_fallbackDeinterlacer
void SetupBicubic(VideoResizing &Resize)
void RenderFrame(MythVideoFrame *Frame, bool TopFieldFirst, FrameScanType Scan, StereoscopicMode StereoOverride, bool DrawBorder=false) override
void UpdateShaderParameters()
bool AddDeinterlacer(const MythVideoFrame *Frame, FrameScanType Scan, MythDeintType Filter=DEINT_SHADER, bool CreateReferences=true)
MythRenderOpenGL * m_openglRender
void ResetFrameFormat() override
bool CreateVideoShader(VideoShaderType Type, MythDeintType Deint=DEINT_NONE)
Create the appropriate shader for the operation Type.
QString GetProfile() const override
void SetShaderProgramParams(QOpenGLShaderProgram *Program, const QMatrix4x4 &Value, const char *Uniform)
int GetMaxTextureUnits(void) const
void DrawBitmap(MythGLTexture *Texture, QOpenGLFramebufferObject *Target, QRect Source, QRect Destination, QOpenGLShaderProgram *Program, int Alpha=255, qreal Scale=1.0)
void SetViewPort(QRect Rect, bool ViewportOnly=false) override
void DeleteShaderProgram(QOpenGLShaderProgram *Program)
void BindFramebuffer(QOpenGLFramebufferObject *Framebuffer)
void DeleteFramebuffer(QOpenGLFramebufferObject *Framebuffer)
QOpenGLFunctions::OpenGLFeatures GetFeatures(void) const
bool EnableShaderProgram(QOpenGLShaderProgram *Program)
void logDebugMarker(const QString &Message)
QOpenGLShaderProgram * CreateShaderProgram(const QString &Vertex, const QString &Fragment)
void DeleteTexture(MythGLTexture *Texture)
void DrawRect(QOpenGLFramebufferObject *Target, QRect Area, const QBrush &FillBrush, const QPen &LinePen, int Alpha)
int GetExtraFeatures(void) const
void SetTextureFilters(MythGLTexture *Texture, QOpenGLTexture::Filter Filter, QOpenGLTexture::WrapMode Wrap=QOpenGLTexture::ClampToEdge)
MythVideoColourSpace contains a QMatrix4x4 that can convert YCbCr data to RGB.
float GetDisplayGamma(void) const
QStringList GetColourMappingDefines(void)
QMatrix4x4 GetPrimaryMatrix(void)
bool UpdateColourSpace(const MythVideoFrame *Frame)
Set the current colourspace to use.
float GetColourGamma(void) const
static bool FormatIs422(VideoFrameType Type)
Definition: mythframe.h:443
static uint GetNumPlanes(VideoFrameType Type)
Definition: mythframe.h:213
static QString DeinterlacerName(MythDeintType Deint, bool DoubleRate, VideoFrameType Format=FMT_NONE)
Definition: mythframe.cpp:466
static bool FormatIsNV12(VideoFrameType Type)
Definition: mythframe.h:455
static QString FormatDescription(VideoFrameType Type)
Definition: mythframe.cpp:372
static bool FormatIs444(VideoFrameType Type)
Definition: mythframe.h:449
static bool FormatIs420(VideoFrameType Type)
Definition: mythframe.h:437
static bool FormatIsRGB(VideoFrameType Type)
Definition: mythframe.h:471
static bool HardwareFramesFormat(VideoFrameType Type)
Definition: mythframe.h:432
static bool YUVFormat(VideoFrameType Type)
Definition: mythframe.h:465
static int ColorDepth(int Format)
Definition: mythframe.h:398
static bool HardwareFormat(VideoFrameType Type)
Definition: mythframe.h:424
VideoResizing m_resizing
Definition: mythvideogpu.h:89
int m_lastRotation
Definition: mythvideogpu.h:90
uint m_lastStereo
Definition: mythvideogpu.h:95
VideoFrameType m_inputType
Definition: mythvideogpu.h:80
QSize m_inputTextureSize
Definition: mythvideogpu.h:88
QRect m_videoRect
Definition: mythvideogpu.h:86
StereoscopicMode m_stereoMode
Definition: mythvideogpu.h:96
uint64_t m_discontinuityCounter
Definition: mythvideogpu.h:78
QString m_profile
Definition: mythvideogpu.h:79
bool m_bicubicUpsize
Definition: mythvideogpu.h:97
QSize m_videoDim
Definition: mythvideogpu.h:83
QRect m_displayVideoRect
Definition: mythvideogpu.h:85
MythVideoColourSpace * m_videoColourSpace
Definition: mythvideogpu.h:87
QSize m_masterViewportSize
Definition: mythvideogpu.h:84
VideoFrameType m_outputType
Definition: mythvideogpu.h:81
void OutputChanged(QSize VideoDim, QSize VideoDispDim, float)
static QString VideoResizeToString(VideoResizing Resize)
void UpdateColourSpace(bool PrimariesChanged)
virtual void ResetFrameFormat()
QSize m_videoDispDim
Definition: mythvideogpu.h:82
bool m_deinterlacer2x
Definition: mythvideogpu.h:92
MythDeintType m_deinterlacer
Definition: mythvideogpu.h:91
static void SetTextureFilters(MythRenderOpenGL *Context, const std::vector< MythVideoTextureOpenGL * > &Textures, QOpenGLTexture::Filter Filter, QOpenGLTexture::WrapMode Wrap=QOpenGLTexture::ClampToEdge)
static std::vector< MythVideoTextureOpenGL * > CreateTextures(MythRenderOpenGL *Context, VideoFrameType Type, VideoFrameType Format, std::vector< QSize > Sizes, GLenum Target=QOpenGLTexture::Target2D)
Create a set of textures suitable for the given Type and Format.
static VideoFramebuffer CreateVideoFrameBuffer(MythRenderOpenGL *Context, VideoFrameType OutputType, QSize Size, bool HighPrecision=true)
static void DeleteTextures(MythRenderOpenGL *Context, std::vector< MythVideoTextureOpenGL * > &Textures)
static void UpdateTextures(MythRenderOpenGL *Context, const MythVideoFrame *Frame, const std::vector< MythVideoTextureOpenGL * > &Textures)
Update the contents of the given Textures for data held in Frame.
unsigned int uint
Definition: compat.h:60
static pid_list_t::iterator find(const PIDInfoMap &map, pid_list_t &list, pid_list_t::iterator begin, pid_list_t::iterator end, bool find_open)
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
MythDeintType
Definition: mythframe.h:67
@ DEINT_HIGH
Definition: mythframe.h:71
@ DEINT_DRIVER
Definition: mythframe.h:74
@ DEINT_MEDIUM
Definition: mythframe.h:70
@ DEINT_BASIC
Definition: mythframe.h:69
@ DEINT_NONE
Definition: mythframe.h:68
@ DEINT_SHADER
Definition: mythframe.h:73
@ DEINT_CPU
Definition: mythframe.h:72
std::array< int, 3 > FrameOffsets
Definition: mythframe.h:84
VideoFrameType
Definition: mythframe.h:20
@ FMT_RGBA32
Definition: mythframe.h:34
@ FMT_YV12
Definition: mythframe.h:23
@ FMT_DRMPRIME
Definition: mythframe.h:63
@ FMT_NONE
Definition: mythframe.h:21
@ FMT_NV12
Definition: mythframe.h:52
@ FMT_YUY2
Definition: mythframe.h:50
@ FMT_MEDIACODEC
Definition: mythframe.h:60
std::array< int, 3 > FramePitches
Definition: mythframe.h:83
static bool VERBOSE_LEVEL_CHECK(uint64_t mask, LogLevel_t level)
Definition: mythlogging.h:29
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
#define LOC
static const QString YUVFragmentShader
static const QString DefaultVertexShader
static const QString GLSL300VertexShader
static const QString BicubicShader
static const QString YUVFragmentExtensions
static const QString GLSL300YUVFragmentShader
static const QString GLSL300YUVFragmentExtensions
static const QString RGBFragmentShader
@ kGLTiled
#define GL_TEXTURE_EXTERNAL_OES
std::shared_ptr< MythVideoProfile > MythVideoProfilePtr
Definition: mythvideogpu.h:18
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:15
FrameScanType
Definition: videoouttypes.h:95
@ kScan_Intr2ndField
Definition: videoouttypes.h:99
@ kScan_Interlaced
Definition: videoouttypes.h:98
bool is_interlaced(FrameScanType Scan)
StereoscopicMode
@ kStereoscopicModeAuto
@ kStereoscopicModeTopAndBottomDiscard
@ kStereoscopicModeSideBySideDiscard