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 for (int j = (i * count); j < ((i + 1) * count); ++j)
415 replacelist << QString("s_texture%1").arg(j);
416 fragment.replace(find, replacelist.join(", "));
417 }
418
419 // 'expand' calls to the kernel function
420 if (kernel && count)
421 {
422 for (int i = 1 ; i >= 0; i--)
423 {
424 QString find1 = QString("sampler2D kernelTex%1").arg(i);
425 QString find2 = QString("kernelTex%1").arg(i);
426 QStringList replacelist1;
427 QStringList replacelist2;
428 for (int j = 0; j < count; ++j)
429 {
430 replacelist1 << QString("sampler2D kernelTexture%1%2").arg(i).arg(j);
431 replacelist2 << QString("kernelTexture%1%2").arg(i).arg(j);
432 }
433 fragment.replace(find1, replacelist1.join(", "));
434 fragment.replace(find2, replacelist2.join(", "));
435 }
436 }
437
438 // Retrieve colour mappping defines
440
441 // Add defines
442 for (const QString& define : std::as_const(defines))
443 glsldefines += QString("#define MYTHTV_%1\n").arg(define);
444
445 // Add the required samplers
446 int start = 0;
447 int end = count;
448 if (kernel)
449 {
450 end *= 3;
451 if (topfield)
452 start += count;
453 else
454 end -= count;
455 }
456 QString glslsamplers;
457 for (int i = start; i < end; ++i)
458 glslsamplers += QString("uniform sampler2D s_texture%1;\n").arg(i);
459
460 // construct the final shader string
461 fragment = glsldefines + extensions + glslsamplers + fragment;
462 }
463
464 m_shaderCost[Type] = cost;
465 QOpenGLShaderProgram *program = m_openglRender->CreateShaderProgram(vertex, fragment);
466 if (!program)
467 return false;
468
469 m_shaders[Type] = program;
470 return true;
471}
472
474 QSize Size, GLenum TextureTarget)
475{
476 QString texnew { "2D" };
477 if (TextureTarget == QOpenGLTexture::TargetRectangle)
478 texnew = "Rect";
479 else if (TextureTarget == GL_TEXTURE_EXTERNAL_OES)
480 texnew = "OES";
481
482 QString texold { "2D" };
483 if (m_textureTarget == QOpenGLTexture::TargetRectangle)
484 texold = "Rect";
486 texold = "OES";
487
488 LOG(VB_GENERAL, LOG_INFO, LOC +
489 QString("New frame format: %1:%2 %3x%4 (Tex: %5) -> %6:%7 %8x%9 (Tex: %10)")
492 QString::number(m_videoDim.width()),
493 QString::number(m_videoDim.height()),
494 texold,
497 QString::number(Size.width()),
498 QString::number(Size.height()))
499 .arg(texnew));
500
502
503 m_inputType = InputType;
504 m_outputType = OutputType;
505 m_textureTarget = TextureTarget;
506 m_videoDim = Size;
507
508 // This is only currently needed for RGBA32 frames from composed DRM_PRIME
509 // textures that may be half height for simple bob deinterlacing
511 emit OutputChanged(m_videoDim, m_videoDim, -1.0F);
512
513 if (!MythVideoFrame::HardwareFormat(InputType))
514 {
515 std::vector<QSize> sizes;
516 sizes.push_back(Size);
518 if (m_inputTextures.empty())
519 {
520 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create input textures");
521 return false;
522 }
523
524 m_inputTextureSize = m_inputTextures[0]->m_totalSize;
525 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Created %1 input textures for '%2'")
526 .arg(m_inputTextures.size()).arg(GetProfile()));
527 }
528 else
529 {
530 m_inputTextureSize = Size;
531 }
532
533 // Create shaders
535 return false;
536
537 UpdateColourSpace(false);
539 return true;
540}
541
543{
544 for (auto & shader : m_shaders)
545 if (shader)
547 m_shaders.fill(nullptr);
548 m_shaderCost.fill(1);
552 m_textureTarget = QOpenGLTexture::Target2D;
556 m_frameBuffer = nullptr;
557 m_frameBufferTexture = nullptr;
558
560}
561
564{
565 if (Frame->m_type == FMT_NONE)
566 return;
567
568 // Hardware frames are retrieved/updated in PrepareFrame but we need to
569 // reset software frames now if necessary
571 {
573 {
574 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Resetting input format");
576 }
577 return;
578 }
579
580 // Sanitise frame
581 if ((Frame->m_width < 1) || (Frame->m_height < 1) || !Frame->m_buffer)
582 {
583 LOG(VB_GENERAL, LOG_ERR, LOC + "Invalid software frame");
584 return;
585 }
586
587 // Can we render this frame format
588 if (!MythVideoFrame::YUVFormat(Frame->m_type))
589 {
590 LOG(VB_GENERAL, LOG_ERR, LOC + "Frame format is not supported");
591 return;
592 }
593
594 // lock
596
597 // check for input changes
598 if ((Frame->m_width != m_videoDim.width()) ||
599 (Frame->m_height != m_videoDim.height()) ||
600 (Frame->m_type != m_inputType))
601 {
602 VideoFrameType frametype = Frame->m_type;
603 if ((frametype == FMT_YV12) && (m_profile == "opengl"))
604 frametype = FMT_YUY2;
605 QSize size(Frame->m_width, Frame->m_height);
606 if (!SetupFrameFormat(Frame->m_type, frametype, size, QOpenGLTexture::Target2D))
607 return;
608 }
609
610 // Setup deinterlacing if required
611 AddDeinterlacer(Frame, Scan);
612
613 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
614 m_openglRender->logDebugMarker(LOC + "UPDATE_FRAME_START");
615
617
618 // Rotate textures if necessary
619 bool current = true;
621 {
622 if (!m_nextTextures.empty() && !m_prevTextures.empty())
623 {
624 if (qAbs(Frame->m_frameCounter - m_discontinuityCounter) > 1)
626 std::vector<MythVideoTextureOpenGL*> temp = m_prevTextures;
629 m_nextTextures = temp;
630 current = false;
631 }
632 }
633
634 m_discontinuityCounter = Frame->m_frameCounter;
635
637 {
638 // first field. Fake the pitches
639 FramePitches pitches = Frame->m_pitches;
640 Frame->m_pitches[0] = Frame->m_pitches[0] << 1;
641 Frame->m_pitches[1] = Frame->m_pitches[1] << 1;
642 Frame->m_pitches[2] = Frame->m_pitches[2] << 1;
644 // second field. Fake the offsets as well.
645 FrameOffsets offsets = Frame->m_offsets;
646 Frame->m_offsets[0] = Frame->m_offsets[0] + pitches[0];
647 Frame->m_offsets[1] = Frame->m_offsets[1] + pitches[1];
648 Frame->m_offsets[2] = Frame->m_offsets[2] + pitches[2];
650 Frame->m_pitches = pitches;
651 Frame->m_offsets = offsets;
652 }
653 else
654 {
656 }
657
658 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
659 m_openglRender->logDebugMarker(LOC + "UPDATE_FRAME_END");
660}
661
663 StereoscopicMode StereoOverride, bool DrawBorder)
664{
665 if (!m_openglRender)
666 return;
667
669
670 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
671 m_openglRender->logDebugMarker(LOC + "RENDER_FRAME_START");
672
673 // Set required input textures for the last stage
674 // ProcessFrame is always called first, which will create/destroy software
675 // textures as needed
676 bool hwframes = false;
677 bool useframebufferimage = false;
678
679 // for tiled renderers (e.g. Pi), enable scissoring to try and improve performance
680 // when not full screen and avoid the resize stage unless absolutely necessary
681 bool tiled = m_extraFeatures & kGLTiled;
682
683 // We lose the pause frame when seeking and using VDPAU/VAAPI/NVDEC direct rendering.
684 // As a workaround, we always use the resize stage so the last displayed frame
685 // should be retained in the Framebuffer used for resizing. If there is
686 // nothing to display, then fallback to this framebuffer.
687 // N.B. this is now strictly necessary with v4l2 and DRM PRIME direct rendering
688 // but ignore now for performance reasons
689 VideoResizing resize;
690 if (Frame)
692 else
694
695 std::vector<MythVideoTextureOpenGL*> inputtextures = m_inputTextures;
696 if (inputtextures.empty())
697 {
698 // This is experimental support for direct rendering to a framebuffer (e.g. DRM).
699 // It may be removed or refactored (e.g. pass presentation details through to
700 // the interop).
701 if (Frame)
702 {
703 Frame->m_displayed = false;
704 Frame->m_srcRect = m_videoRect;
705 Frame->m_dstRect = m_displayVideoRect;
706 }
707
708 // Pull in any hardware frames
710
711 if (Frame && Frame->m_displayed)
712 return;
713
714 if (!inputtextures.empty())
715 {
716 hwframes = true;
717 QSize newsize = inputtextures[0]->m_size;
718 VideoFrameType newsourcetype = inputtextures[0]->m_frameType;
719 VideoFrameType newtargettype = inputtextures[0]->m_frameFormat;
720 GLenum newtargettexture = inputtextures[0]->m_target;
721 if ((m_outputType != newtargettype) || (m_textureTarget != newtargettexture) ||
722 (m_inputType != newsourcetype) || (newsize != m_inputTextureSize))
723 {
724 SetupFrameFormat(newsourcetype, newtargettype, newsize, newtargettexture);
725 }
726
727#if CONFIG_MEDIACODEC
728 // Set the texture transform for mediacodec
730 {
731 if (inputtextures[0]->m_transform && m_shaders[Default])
732 {
734 m_shaders[Default]->setUniformValue("u_transform", *inputtextures[0]->m_transform);
735 }
736 }
737#endif
738 // Enable deinterlacing for NVDEC, VTB and VAAPI DRM if VPP is not available
739 if (inputtextures[0]->m_allowGLSLDeint)
740 AddDeinterlacer(Frame, Scan, DEINT_SHADER | DEINT_CPU, false); // pickup shader or cpu prefs
741 }
742 else
743 {
744 if ((resize == Framebuffer) && m_frameBuffer && m_frameBufferTexture)
745 {
746 LOG(VB_PLAYBACK, LOG_DEBUG, "Using existing framebuffer");
747 useframebufferimage = true;
748 }
749 else
750 {
751 LOG(VB_PLAYBACK, LOG_DEBUG, LOC + "Nothing to display");
752 // if this is live tv startup and the window rect has changed we
753 // must set the viewport
755 return;
756 }
757 }
758 }
759
760 // Determine which shader to use. This helps optimise the resize check.
761 bool deinterlacing = false;
762 bool basicdeinterlacing = false;
763 bool yuvoutput = MythVideoFrame::YUVFormat(m_outputType);
764 VideoShaderType program = yuvoutput ? Progressive : Default;
766 {
767 if (Scan == kScan_Interlaced)
768 {
769 program = TopFieldFirst ? InterlacedTop : InterlacedBot;
770 deinterlacing = true;
771 }
772 else if (Scan == kScan_Intr2ndField)
773 {
774 program = TopFieldFirst ? InterlacedBot : InterlacedTop;
775 deinterlacing = true;
776 }
777
778 // select the correct field for the basic deinterlacer
780 {
781 basicdeinterlacing = true;
782 if (program == InterlacedBot)
783 inputtextures = m_nextTextures;
784 }
785 }
786
787 // Set deinterlacer type for debug OSD
788 if (deinterlacing && Frame)
789 {
790 Frame->m_deinterlaceInuse = m_deinterlacer | DEINT_SHADER;
791 Frame->m_deinterlaceInuse2x = m_deinterlacer2x;
792 }
793
794 // Tonemapping can only render to a texture
795 if (m_toneMap)
796 resize |= ToneMap;
797
798 // Decide whether to use render to texture - for performance or quality
799 if (yuvoutput && !resize)
800 {
801 // ensure deinterlacing works correctly when down scaling in height
802 // N.B. not needed for the basic deinterlacer
803 if (deinterlacing && !basicdeinterlacing && (m_videoDispDim.height() > m_displayVideoRect.height()))
804 resize |= Deinterlacer;
805
806 // NB GL_NEAREST introduces some 'minor' chroma sampling errors
807 // for the following 2 cases. For YUY2 this may be better handled in the
808 // shader. For GLES3.0 10bit textures - Vulkan is probably the better solution.
809
810 // UYVY packed pixels must be sampled exactly with GL_NEAREST
811 if (FMT_YUY2 == m_outputType)
812 resize |= Sampling;
813 // unsigned integer texture formats need GL_NEAREST sampling
815 resize |= Sampling;
816
817 // don't enable resizing if the cost of a framebuffer switch may be
818 // prohibitive (e.g. Raspberry Pi/tiled renderers) or for basic deinterlacing,
819 // where we are trying to simplify/optimise rendering (and the framebuffer
820 // sizing gets confused by the change to m_videoDispDim)
821 if (!resize && !tiled && !basicdeinterlacing)
822 {
823 // improve performance. This is an educated guess on the relative cost
824 // of render to texture versus straight rendering.
825 int totexture = m_videoDispDim.width() * m_videoDispDim.height() * m_shaderCost[program];
826 int blitcost = m_displayVideoRect.width() * m_displayVideoRect.height() * m_shaderCost[Default];
827 int noresizecost = m_displayVideoRect.width() * m_displayVideoRect.height() * m_shaderCost[program];
828 if ((totexture + blitcost) < noresizecost)
829 resize |= Performance;
830 }
831 }
832
833 // Bicubic upsizing - test this after all other resize options have been checked
834 // to ensure it is not the only flag set
835 if (m_bicubicUpsize)
836 SetupBicubic(resize);
837
838 // We don't need an extra stage prior to bicubic if the frame is already RGB (e.g. VDPAU, MediaCodec)
839 // So bypass if we only set resize for bicubic.
840 bool needresize = resize && (!MythVideoFrame::FormatIsRGB(m_outputType) || (resize != Bicubic));
841
842 // set software frame filtering if resizing has changed
843 if (!needresize && m_resizing)
844 {
845 // remove framebuffer
847 {
849 m_frameBufferTexture = nullptr;
850 }
851 if (m_frameBuffer)
852 {
854 m_frameBuffer = nullptr;
855 }
856 // set filtering
861 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Disabled resizing");
862 }
863 else if (!m_resizing && needresize)
864 {
865 // framebuffer will be created as needed below
866 QOpenGLTexture::Filter filter = ((resize & Sampling) == Sampling) ? QOpenGLTexture::Nearest : QOpenGLTexture::Linear;
870 m_resizing = resize;
871 LOG(VB_PLAYBACK, LOG_INFO, LOC + QString("Resizing from %1x%2 to %3x%4 for %5")
872 .arg(m_videoDispDim.width()).arg(m_videoDispDim.height())
873 .arg(m_displayVideoRect.width()).arg(m_displayVideoRect.height())
874 .arg(VideoResizeToString(resize)));
875 }
876
877 // check hardware frames have the correct filtering
878 if (hwframes)
879 {
880 QOpenGLTexture::Filter filter = (resize.testFlag(Sampling)) ? QOpenGLTexture::Nearest : QOpenGLTexture::Linear;
881 if (inputtextures[0]->m_filter != filter)
883 }
884
885 // texture coordinates
886 QRect trect(m_videoRect);
887
888 if (needresize)
889 {
890 MythVideoTextureOpenGL* nexttexture = nullptr;
891
892 // only render to the framebuffer if there is something to update
893 if (useframebufferimage)
894 {
895 if (m_toneMap)
896 {
897 nexttexture = m_toneMap->GetTexture();
898 trect = QRect(QPoint(0, 0), m_displayVideoRect.size());
899 }
900 else
901 {
902 nexttexture = m_frameBufferTexture;
903 }
904 }
905 else if (m_toneMap)
906 {
907 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
908 m_openglRender->logDebugMarker(LOC + "RENDER_TO_TEXTURE");
909 nexttexture = m_toneMap->Map(inputtextures, m_displayVideoRect.size());
910 trect = QRect(QPoint(0, 0), m_displayVideoRect.size());
911 }
912 else
913 {
914 // render to texture stage
915 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
916 m_openglRender->logDebugMarker(LOC + "RENDER_TO_TEXTURE");
917
918 // we need a framebuffer and associated texture
919 if (!m_frameBuffer)
920 {
922 (fbo != nullptr) && (tex != nullptr))
923 {
924 delete m_frameBuffer;
926 m_frameBuffer = fbo;
928 m_openglRender->SetTextureFilters(m_frameBufferTexture, QOpenGLTexture::Linear);
929 }
930 }
931
933 return;
934
935 // coordinates
936 QRect vrect(QPoint(0, 0), m_videoDispDim);
937 QRect trect2 = vrect;
938 if (FMT_YUY2 == m_outputType)
939 trect2.setWidth(m_videoDispDim.width() >> 1);
940
941 // framebuffer
944
945 // bind correct textures
946 std::vector<MythGLTexture*> textures {};
947 BindTextures(deinterlacing, inputtextures, textures);
948
949 // render
950 m_openglRender->DrawBitmap(textures, m_frameBuffer, trect2, vrect,
951 m_shaders[program], 0);
952 nexttexture = m_frameBufferTexture;
953 }
954
955 // reset for next stage
956 inputtextures.clear();
957 inputtextures.push_back(nexttexture);
958 program = Default;
959 deinterlacing = false;
960 }
961
962 // Use the bicubic shader if necessary
963 if (resize.testFlag(Bicubic))
964 program = BicubicUpsize;
965
966 // render to default framebuffer/screen
967 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
968 m_openglRender->logDebugMarker(LOC + "RENDER_TO_SCREEN");
969
970 // discard stereoscopic fields
971 StereoscopicMode stereo = StereoOverride;
972 m_lastStereo = Frame ? Frame->m_stereo3D : m_lastStereo;
973 // N.B. kStereoscopicModeSideBySideDiscard is a proxy here for discard of all types
974 if ((stereo == kStereoscopicModeAuto) &&
976 (m_lastStereo != AV_STEREO3D_2D))
977 {
978 if (m_lastStereo == AV_STEREO3D_SIDEBYSIDE)
980 else if (m_lastStereo == AV_STEREO3D_TOPBOTTOM)
982 }
983
985 trect = QRect(trect.left() >> 1, trect.top(), trect.width() >> 1, trect.height());
986 else if (kStereoscopicModeTopAndBottomDiscard == stereo)
987 trect = QRect(trect.left(), trect.top() >> 1, trect.width(), trect.height() >> 1);
988
989 // bind default framebuffer
992
993 // PiP border
994 if (DrawBorder)
995 {
996 QRect piprect = m_displayVideoRect.adjusted(-10, -10, +10, +10);
997 static const QPen kNopen(Qt::NoPen);
998 static const QBrush kRedBrush(QBrush(QColor(127, 0, 0, 255)));
999 m_openglRender->DrawRect(nullptr, piprect, kRedBrush, kNopen, 255);
1000 }
1001
1002 // bind correct textures
1003 std::vector<MythGLTexture*> textures;
1004 BindTextures(deinterlacing, inputtextures, textures);
1005
1006 // rotation
1007 if (Frame)
1008 m_lastRotation = Frame->m_rotation;
1009
1010 // apply scissoring
1011 if (tiled)
1012 {
1013 // N.B. It's not obvious whether this helps
1014 m_openglRender->glEnable(GL_SCISSOR_TEST);
1015 m_openglRender->glScissor(m_displayVideoRect.left() - 1, m_displayVideoRect.top() - 1,
1016 m_displayVideoRect.width() + 2, m_displayVideoRect.height() + 2);
1017 }
1018
1019 // draw
1020 m_openglRender->DrawBitmap(textures, nullptr, trect, m_displayVideoRect,
1021 m_shaders[program], m_lastRotation);
1022
1023 // disable scissoring
1024 if (tiled)
1025 m_openglRender->glDisable(GL_SCISSOR_TEST);
1026
1027 if (VERBOSE_LEVEL_CHECK(VB_GPU, LOG_INFO))
1028 m_openglRender->logDebugMarker(LOC + "RENDER_FRAME_END");
1029}
1030
1033{
1034 for (auto & texture : m_inputTextures)
1035 texture->m_valid = false;
1036 for (auto & texture : m_prevTextures)
1037 texture->m_valid = false;
1038 for (auto & texture : m_nextTextures)
1039 texture->m_valid = false;
1040}
1041
1042void MythOpenGLVideo::BindTextures(bool Deinterlacing, std::vector<MythVideoTextureOpenGL*>& Current,
1043 std::vector<MythGLTexture*>& Textures)
1044{
1045 if (Deinterlacing && !MythVideoFrame::HardwareFormat(m_inputType))
1046 {
1047 if ((m_nextTextures.size() == Current.size()) && (m_prevTextures.size() == Current.size()))
1048 {
1049 // if we are using reference frames, we want the current frame in the middle
1050 // but next will be the first valid, followed by current...
1051 size_t count = Current.size();
1052 std::vector<MythVideoTextureOpenGL*>& current = Current[0]->m_valid ? Current : m_nextTextures;
1053 std::vector<MythVideoTextureOpenGL*>& prev = m_prevTextures[0]->m_valid ? m_prevTextures : current;
1054
1055 for (uint i = 0; i < count; ++i)
1056 Textures.push_back(reinterpret_cast<MythGLTexture*>(prev[i]));
1057 for (uint i = 0; i < count; ++i)
1058 Textures.push_back(reinterpret_cast<MythGLTexture*>(current[i]));
1059 for (uint i = 0; i < count; ++i)
1060 Textures.push_back(reinterpret_cast<MythGLTexture*>(m_nextTextures[i]));
1061 return;
1062 }
1063 }
1064
1065 std::ranges::transform(Current, std::back_inserter(Textures),
1066 [](MythVideoTextureOpenGL* Tex) { return reinterpret_cast<MythGLTexture*>(Tex); });
1067}
1068
1070{
1072 return "opengl-hw";
1073
1074 switch (Type)
1075 {
1076 case FMT_YUY2: return "opengl"; // compatibility with old profiles
1077 case FMT_YV12: return "opengl-yv12";
1078 case FMT_NV12: return "opengl-nv12";
1079 default: break;
1080 }
1081 return "opengl";
1082}
1083
1084void MythOpenGLVideo::SetupBicubic(VideoResizing& Resize)
1085{
1086 if (((m_videoDispDim.width() < m_displayVideoRect.width()) ||
1087 (m_videoDispDim.height() < m_displayVideoRect.height())))
1088 {
1090 {
1092 {
1093 LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to create bicubic shader. Disabling");
1094 m_bicubicUpsize = false;
1095 }
1096 else
1097 {
1099 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Created bicubic sampler");
1100 }
1101 }
1102
1104 Resize |= Bicubic;
1105 }
1106 else
1107 {
1108 if (m_shaders[BicubicUpsize] != nullptr)
1109 {
1110 LOG(VB_PLAYBACK, LOG_INFO, LOC + "Disabling bicubic sampler");
1111 delete m_shaders[BicubicUpsize];
1112 m_shaders[BicubicUpsize] = nullptr;
1113 }
1114 }
1115}
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