MythTV  master
mythnvdecinterop.cpp
Go to the documentation of this file.
1 // MythTV
2 #include "mythconfig.h"
3 #include "mythcorecontext.h"
4 #include "mythvideocolourspace.h"
6 
7 // Std
8 #include <chrono>
9 #include <thread>
10 
11 #define LOC QString("NVDECInterop: ")
12 
13 #define CUDA_CHECK(CUDA_FUNCS, CUDA_CALL) \
14 { \
15  CUresult res = (CUDA_FUNCS)->CUDA_CALL; \
16  if (res != CUDA_SUCCESS) { \
17  const char * desc; \
18  (CUDA_FUNCS)->cuGetErrorString(res, &desc); \
19  LOG(VB_GENERAL, LOG_ERR, LOC + QString("CUDA error %1 (%2)").arg(res).arg(desc)); \
20  } \
21 }
22 
24  : MythOpenGLInterop(Context, GL_NVDEC, Player),
25  m_cudaContext()
26 {
28 }
29 
31 {
32  m_referenceFrames.clear();
35 }
36 
38 {
39  if (!(m_cudaContext && m_cudaFuncs))
40  return;
41 
43  CUDA_CHECK(m_cudaFuncs, cuCtxPushCurrent(m_cudaContext))
44 
45  if (!m_openglTextures.isEmpty())
46  {
47  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Deleting CUDA resources");
48  for (auto it = m_openglTextures.constBegin(); it != m_openglTextures.constEnd(); ++it)
49  {
50  vector<MythVideoTextureOpenGL*> textures = it.value();
51  for (auto & texture : textures)
52  {
53  auto *data = reinterpret_cast<QPair<CUarray,CUgraphicsResource>*>(texture->m_data);
54  if (data && data->second)
55  CUDA_CHECK(m_cudaFuncs, cuGraphicsUnregisterResource(data->second))
56  delete data;
57  texture->m_data = nullptr;
58  }
59  }
60  }
61 
62  CUcontext dummy = nullptr;
63  CUDA_CHECK(m_cudaFuncs, cuCtxPopCurrent(&dummy))
64 
66 }
67 
69 {
70  return m_cudaFuncs && m_cudaContext;
71 }
72 
74 {
75  return m_cudaContext;
76 }
77 
79 {
80  if (!(Context && Player))
81  return nullptr;
82 
84  GetNVDECTypes(Context, types);
85  if (auto nvdec = types.find(FMT_NVDEC); nvdec != types.end())
86  {
87  for (auto type : nvdec->second)
88  if (type == GL_NVDEC)
89  return new MythNVDECInterop(Player, Context);
90  }
91  return nullptr;
92 }
93 
95 {
96  if (Render)
97  Types[FMT_NVDEC] = { GL_NVDEC };
98 }
99 
107 vector<MythVideoTextureOpenGL*> MythNVDECInterop::Acquire(MythRenderOpenGL* Context,
108  MythVideoColourSpace* ColourSpace,
110  FrameScanType Scan)
111 {
112  vector<MythVideoTextureOpenGL*> result;
113  if (!Frame || !m_cudaContext || !m_cudaFuncs)
114  return result;
115 
116  if (Context && (Context != m_openglContext))
117  LOG(VB_GENERAL, LOG_WARNING, LOC + "Mismatched OpenGL contexts");
118 
119  // Check size
120  QSize surfacesize(Frame->m_width, Frame->m_height);
121  if (m_textureSize != surfacesize)
122  {
123  if (!m_textureSize.isEmpty())
124  {
125  LOG(VB_GENERAL, LOG_WARNING, LOC + QString("Video texture size changed! %1x%2->%3x%4")
126  .arg(m_textureSize.width()).arg(m_textureSize.height())
127  .arg(Frame->m_width).arg(Frame->m_height));
128  }
129  DeleteTextures();
130  m_textureSize = surfacesize;
131  }
132 
133  // Lock
135 
136  // Update colourspace and initialise on first frame
137  if (ColourSpace)
138  {
139  if (m_openglTextures.isEmpty())
141  ColourSpace->UpdateColourSpace(Frame);
142  }
143 
144  // Retrieve hardware frames context and AVCUDADeviceContext
145  if ((Frame->m_pixFmt != AV_PIX_FMT_CUDA) || (Frame->m_type != FMT_NVDEC) ||
146  !Frame->m_buffer || !Frame->m_priv[0] || !Frame->m_priv[1])
147  {
148  return result;
149  }
150 
151  auto cudabuffer = reinterpret_cast<CUdeviceptr>(Frame->m_buffer);
152  if (!cudabuffer)
153  return result;
154 
155  // make the CUDA context current
156  CUcontext dummy = nullptr;
157  CUDA_CHECK(m_cudaFuncs, cuCtxPushCurrent(m_cudaContext))
158 
159  // create and map textures for a new buffer
160  VideoFrameType type = (Frame->m_swPixFmt == AV_PIX_FMT_NONE) ? FMT_NV12 :
161  MythAVUtil::PixelFormatToFrameType(static_cast<AVPixelFormat>(Frame->m_swPixFmt));
162  bool p010 = MythVideoFrame::ColorDepth(type) > 8;
163  if (!m_openglTextures.contains(cudabuffer))
164  {
165  vector<QSize> sizes;
166  sizes.emplace_back(QSize(Frame->m_width, Frame->m_height));
167  sizes.emplace_back(QSize(Frame->m_width, Frame->m_height >> 1));
168  vector<MythVideoTextureOpenGL*> textures =
170  if (textures.empty())
171  {
172  CUDA_CHECK(m_cudaFuncs, cuCtxPopCurrent(&dummy))
173  return result;
174  }
175 
176  bool success = true;
177  for (uint plane = 0; plane < textures.size(); ++plane)
178  {
179  // N.B. I think the texture formats for P010 are not strictly compliant
180  // with OpenGL ES 3.X but the Nvidia driver does not complain.
181  MythVideoTextureOpenGL *tex = textures[plane];
182  tex->m_allowGLSLDeint = true;
183  m_openglContext->glBindTexture(tex->m_target, tex->m_textureId);
184  QOpenGLTexture::PixelFormat format = QOpenGLTexture::Red;
185  QOpenGLTexture::PixelType pixtype = p010 ? QOpenGLTexture::UInt16 : QOpenGLTexture::UInt8;
186  QOpenGLTexture::TextureFormat internal = p010 ? QOpenGLTexture::R16_UNorm : QOpenGLTexture::R8_UNorm;
187  int width = tex->m_size.width();
188 
189  if (plane)
190  {
191  internal = p010 ? QOpenGLTexture::RG16_UNorm : QOpenGLTexture::RG8_UNorm;
192  format = QOpenGLTexture::RG;
193  width /= 2;
194  }
195 
196  m_openglContext->glTexImage2D(tex->m_target, 0, internal, width, tex->m_size.height(),
197  0, format, pixtype, nullptr);
198 
199  CUarray array = nullptr;
200  CUgraphicsResource graphicsResource = nullptr;
201  CUDA_CHECK(m_cudaFuncs, cuGraphicsGLRegisterImage(&graphicsResource, tex->m_textureId,
202  QOpenGLTexture::Target2D, CU_GRAPHICS_REGISTER_FLAGS_WRITE_DISCARD))
203  if (graphicsResource)
204  {
205  CUDA_CHECK(m_cudaFuncs, cuGraphicsMapResources(1, &graphicsResource, nullptr))
206  CUDA_CHECK(m_cudaFuncs, cuGraphicsSubResourceGetMappedArray(&array, graphicsResource, 0, 0))
207  CUDA_CHECK(m_cudaFuncs, cuGraphicsUnmapResources(1, &graphicsResource, nullptr))
208  tex->m_data = reinterpret_cast<unsigned char*>(new QPair<CUarray,CUgraphicsResource>(array, graphicsResource));
209  }
210  else
211  {
212  success = false;
213  break;
214  }
215  }
216 
217  if (success)
218  {
219  m_openglTextures.insert(cudabuffer, textures);
220  }
221  else
222  {
223  for (auto & texture : textures)
224  {
225  auto *data = reinterpret_cast<QPair<CUarray,CUgraphicsResource>*>(texture->m_data);
226  if (data && data->second)
227  CUDA_CHECK(m_cudaFuncs, cuGraphicsUnregisterResource(data->second))
228  delete data;
229  texture->m_data = nullptr;
230  if (texture->m_textureId)
231  m_openglContext->glDeleteTextures(1, &texture->m_textureId);
233  }
234  }
235  }
236 
237  if (!m_openglTextures.contains(cudabuffer))
238  {
239  CUDA_CHECK(m_cudaFuncs, cuCtxPopCurrent(&dummy))
240  return result;
241  }
242 
243  // Copy device data to array data (i.e. texture) - surely this can be avoided?
244  // In theory, asynchronous copies should not be required but we use async
245  // followed by stream synchronisation to ensure CUDA and OpenGL are in sync
246  // which avoids presenting old/stale frames when the GPU is under load.
247  result = m_openglTextures[cudabuffer];
248  for (uint i = 0; i < result.size(); ++i)
249  {
250  auto *data = reinterpret_cast<QPair<CUarray,CUgraphicsResource>*>(result[i]->m_data);
251  CUDA_MEMCPY2D cpy;
252  memset(&cpy, 0, sizeof(cpy));
253  cpy.srcMemoryType = CU_MEMORYTYPE_DEVICE;
254  cpy.srcDevice = cudabuffer + static_cast<CUdeviceptr>(Frame->m_offsets[i]);
255  cpy.srcPitch = static_cast<size_t>(Frame->m_pitches[i]);
256  cpy.dstMemoryType = CU_MEMORYTYPE_ARRAY;
257  cpy.dstArray = data->first;
258  cpy.WidthInBytes = static_cast<size_t>(result[i]->m_size.width()) * (p010 ? 2 : 1);
259  cpy.Height = static_cast<size_t>(result[i]->m_size.height());
260  CUDA_CHECK(m_cudaFuncs, cuMemcpy2DAsync(&cpy, nullptr))
261  }
262 
263  CUDA_CHECK(m_cudaFuncs, cuStreamSynchronize(nullptr))
264  CUDA_CHECK(m_cudaFuncs, cuCtxPopCurrent(&dummy))
265 
266  // GLSL deinterlacing. The decoder will pick up any CPU or driver preference
267  // and return a stream of deinterlaced frames. Just check for GLSL here.
268  bool needreferences = false;
269  if (is_interlaced(Scan) && !Frame->m_alreadyDeinterlaced)
270  {
271  MythDeintType shader = Frame->GetDoubleRateOption(DEINT_SHADER);
272  if (shader)
273  needreferences = shader == DEINT_HIGH;
274  else
275  needreferences = Frame->GetSingleRateOption(DEINT_SHADER) == DEINT_HIGH;
276  }
277 
278  if (needreferences)
279  {
280  if (qAbs(Frame->m_frameCounter - m_discontinuityCounter) > 1)
281  m_referenceFrames.clear();
282 
283  RotateReferenceFrames(cudabuffer);
284  int size = m_referenceFrames.size();
285 
286  CUdeviceptr next = m_referenceFrames[0];
287  CUdeviceptr current = m_referenceFrames[size > 1 ? 1 : 0];
288  CUdeviceptr last = m_referenceFrames[size > 2 ? 2 : 0];
289 
290  if (!m_openglTextures.contains(next) || !m_openglTextures.contains(current) ||
291  !m_openglTextures.contains(last))
292  {
293  LOG(VB_GENERAL, LOG_ERR, LOC + "Reference frame error");
294  return result;
295  }
296 
297  result = m_openglTextures[last];
298  std::copy(m_openglTextures[current].cbegin(), m_openglTextures[current].cend(), std::back_inserter(result));
299  std::copy(m_openglTextures[next].cbegin(), m_openglTextures[next].cend(), std::back_inserter(result));
300  return result;
301  }
302  m_referenceFrames.clear();
303  m_discontinuityCounter = Frame->m_frameCounter;
304 
305  return result;
306 }
307 
313 {
315 }
316 
317 bool MythNVDECInterop::CreateCUDAPriv(MythRenderOpenGL* GLContext, CudaFunctions*& CudaFuncs,
318  CUcontext& CudaContext, bool& Retry)
319 {
320  Retry = false;
321  if (!GLContext)
322  return false;
323 
324  // Make OpenGL context current
325  OpenGLLocker locker(GLContext);
326 
327  // retrieve CUDA entry points
328  if (cuda_load_functions(&CudaFuncs, nullptr) != 0)
329  {
330  LOG(VB_PLAYBACK, LOG_ERR, LOC + "Failed to load functions");
331  return false;
332  }
333 
334  // create a CUDA context for the current device
335  CUdevice cudevice = 0;
336  CUcontext dummy = nullptr;
337  CUresult res = CudaFuncs->cuInit(0);
338  if (res != CUDA_SUCCESS)
339  {
340  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to initialise CUDA API");
341  return false;
342  }
343 
344  unsigned int devicecount = 0;
345  res = CudaFuncs->cuGLGetDevices(&devicecount, &cudevice, 1, CU_GL_DEVICE_LIST_ALL);
346  if (res != CUDA_SUCCESS)
347  {
348  LOG(VB_GENERAL, LOG_ERR, LOC + "Failed to get CUDA device");
349  return false;
350  }
351 
352  if (devicecount < 1)
353  {
354  LOG(VB_GENERAL, LOG_ERR, LOC + "No CUDA devices");
355  return false;
356  }
357 
358  res = CudaFuncs->cuCtxCreate(&CudaContext, CU_CTX_SCHED_BLOCKING_SYNC, cudevice);
359  if (res != CUDA_SUCCESS)
360  {
361  LOG(VB_GENERAL, LOG_ERR, LOC + QString("Failed to create CUDA context (Err: %1)")
362  .arg(res));
363  Retry = true;
364  return false;
365  }
366 
367  CudaFuncs->cuCtxPopCurrent(&dummy);
368  LOG(VB_PLAYBACK, LOG_INFO, LOC + "Created CUDA context");
369  return true;
370 }
371 
372 bool MythNVDECInterop::CreateCUDAContext(MythRenderOpenGL* GLContext, CudaFunctions*& CudaFuncs,
373  CUcontext& CudaContext)
374 {
375  if (!gCoreContext->IsUIThread())
376  {
377  LOG(VB_GENERAL, LOG_ERR, LOC + "Must create CUDA context from main thread");
378  return false;
379  }
380 
381  int retries = 0;
382  bool retry = false;
383  while (retries++ < 5)
384  {
385  if (CreateCUDAPriv(GLContext, CudaFuncs, CudaContext, retry))
386  return true;
387  CleanupContext(GLContext, CudaFuncs, CudaContext);
388  if (!retry)
389  break;
390  LOG(VB_GENERAL, LOG_WARNING, LOC + "Will retry in 50ms");
391  std::this_thread::sleep_for(50ms);
392  }
393  return false;
394 }
395 
396 void MythNVDECInterop::CleanupContext(MythRenderOpenGL* GLContext, CudaFunctions*& CudaFuncs,
397  CUcontext& CudaContext)
398 {
399  if (!GLContext)
400  return;
401 
402  OpenGLLocker locker(GLContext);
403  if (CudaFuncs)
404  {
405  if (CudaContext)
406  CUDA_CHECK(CudaFuncs, cuCtxDestroy(CudaContext))
407  cuda_free_functions(&CudaFuncs);
408  }
409 }
410 
412 {
413  if (!Buffer)
414  return;
415 
416  // don't retain twice for double rate
417  if (!m_referenceFrames.empty() && (m_referenceFrames[0] == Buffer))
418  return;
419 
420  m_referenceFrames.push_front(Buffer);
421 
422  // release old frames
423  while (m_referenceFrames.size() > 3)
424  m_referenceFrames.pop_back();
425 }
MythNVDECInterop::GetNVDECTypes
static void GetNVDECTypes(MythRenderOpenGL *Render, MythInteropGPU::InteropMap &Types)
Definition: mythnvdecinterop.cpp:94
copy
long long copy(QFile &dst, QFile &src, uint block_size)
Copies src file to dst file.
Definition: mythmiscutil.cpp:311
MythNVDECInterop::m_referenceFrames
QVector< CUdeviceptr > m_referenceFrames
Definition: mythnvdecinterop.h:40
MythNVDECInterop::m_cudaContext
CUcontext m_cudaContext
Definition: mythnvdecinterop.h:38
DEINT_SHADER
@ DEINT_SHADER
Definition: mythframe.h:74
MythVideoTextureOpenGL
Definition: mythvideotextureopengl.h:21
Frame
Definition: zmdefines.h:93
MythVideoColourSpace::UpdateColourSpace
bool UpdateColourSpace(const MythVideoFrame *Frame)
Set the current colourspace to use.
Definition: mythvideocolourspace.cpp:321
MythGLTexture::m_data
unsigned char * m_data
Definition: mythrenderopengl.h:66
FrameScanType
FrameScanType
Definition: videoouttypes.h:94
MythCoreContext::IsUIThread
bool IsUIThread(void)
Definition: mythcorecontext.cpp:1360
types
static const struct wl_interface * types[]
Definition: idle_inhibit_unstable_v1.c:39
CUDA_CHECK
#define CUDA_CHECK(CUDA_FUNCS, CUDA_CALL)
Definition: mythnvdecinterop.cpp:13
MythVideoColourSpace::SetSupportedAttributes
void SetSupportedAttributes(PictureAttributeSupported Supported)
Enable the given set of picture attributes.
Definition: mythvideocolourspace.cpp:111
LOG
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:23
MythNVDECInterop
Definition: mythnvdecinterop.h:13
MythNVDECInterop::RotateReferenceFrames
void RotateReferenceFrames(CUdeviceptr Buffer)
Definition: mythnvdecinterop.cpp:411
MythDate::current
QDateTime current(bool stripped)
Returns current Date and Time in UTC.
Definition: mythdate.cpp:10
MythNVDECInterop::Acquire
vector< MythVideoTextureOpenGL * > Acquire(MythRenderOpenGL *Context, MythVideoColourSpace *ColourSpace, MythVideoFrame *Frame, FrameScanType Scan) override
Map CUDA video memory to OpenGL textures.
Definition: mythnvdecinterop.cpp:107
MythInteropGPU::InteropMap
std::map< VideoFrameType, InteropTypes > InteropMap
Definition: mythinteropgpu.h:45
MythPlayerUI
Definition: mythplayerui.h:10
mythvideocolourspace.h
MythOpenGLInterop
Definition: mythopenglinterop.h:17
MythOpenGLInterop::m_openglTextures
QHash< unsigned long long, vector< MythVideoTextureOpenGL * > > m_openglTextures
Definition: mythopenglinterop.h:39
MythOpenGLInterop::m_openglContext
MythRenderOpenGL * m_openglContext
Definition: mythopenglinterop.h:38
MythNVDECInterop::CreateCUDAContext
static bool CreateCUDAContext(MythRenderOpenGL *GLContext, CudaFunctions *&CudaFuncs, CUcontext &CudaContext)
Definition: mythnvdecinterop.cpp:372
MythVideoTextureOpenGL::m_allowGLSLDeint
bool m_allowGLSLDeint
Definition: mythvideotextureopengl.h:55
MythNVDECInterop::IsValid
bool IsValid()
Definition: mythnvdecinterop.cpp:68
MythVideoFrame::ColorDepth
static int ColorDepth(int Format)
Definition: mythframe.h:399
MythVideoTextureOpenGL::DeleteTexture
static void DeleteTexture(MythRenderOpenGL *Context, MythVideoTextureOpenGL *Texture)
Definition: mythvideotextureopengl.cpp:17
uint
unsigned int uint
Definition: compat.h:140
gCoreContext
MythCoreContext * gCoreContext
This global variable contains the MythCoreContext instance for the app.
Definition: mythcorecontext.cpp:60
MythInteropGPU::GL_NVDEC
@ GL_NVDEC
Definition: mythinteropgpu.h:36
ALL_PICTURE_ATTRIBUTES
#define ALL_PICTURE_ATTRIBUTES
Definition: videoouttypes.h:127
MythAVUtil::PixelFormatToFrameType
static VideoFrameType PixelFormatToFrameType(AVPixelFormat Fmt)
Definition: mythavutil.cpp:70
MythNVDECInterop::CreateCUDAPriv
static bool CreateCUDAPriv(MythRenderOpenGL *GLContext, CudaFunctions *&CudaFuncs, CUcontext &CudaContext, bool &Retry)
Definition: mythnvdecinterop.cpp:317
MythGLTexture::m_target
GLenum m_target
Definition: mythrenderopengl.h:80
FMT_NVDEC
@ FMT_NVDEC
Definition: mythframe.h:63
MythNVDECInterop::DeleteTextures
void DeleteTextures() override
Definition: mythnvdecinterop.cpp:37
MythRenderOpenGL
Definition: mythrenderopengl.h:99
MythDeintType
MythDeintType
Definition: mythframe.h:67
Buffer
Definition: MythExternControl.h:36
MythGLTexture::m_textureId
GLuint m_textureId
Definition: mythrenderopengl.h:68
mythcorecontext.h
MythNVDECInterop::GetCUDAContext
CUcontext GetCUDAContext()
Definition: mythnvdecinterop.cpp:73
MythOpenGLInterop::DeleteTextures
virtual void DeleteTextures()
Definition: mythopenglinterop.cpp:135
DEINT_HIGH
@ DEINT_HIGH
Definition: mythframe.h:72
MythGLTexture::m_size
QSize m_size
Definition: mythrenderopengl.h:73
MythNVDECInterop::InitialiseCuda
bool InitialiseCuda()
Initialise a CUDA context.
Definition: mythnvdecinterop.cpp:312
MythInteropGPU::m_discontinuityCounter
uint64_t m_discontinuityCounter
Definition: mythinteropgpu.h:64
MythNVDECInterop::CreateNVDEC
static MythNVDECInterop * CreateNVDEC(MythPlayerUI *Player, MythRenderOpenGL *Context)
Definition: mythnvdecinterop.cpp:78
MythNVDECInterop::CleanupContext
static void CleanupContext(MythRenderOpenGL *GLContext, CudaFunctions *&CudaFuncs, CUcontext &CudaContext)
Definition: mythnvdecinterop.cpp:396
MythNVDECInterop::~MythNVDECInterop
~MythNVDECInterop() override
Definition: mythnvdecinterop.cpp:30
LOC
#define LOC
Definition: mythnvdecinterop.cpp:11
mythnvdecinterop.h
VideoFrameType
VideoFrameType
Definition: mythframe.h:20
Player
Definition: zmliveplayer.h:34
MythVideoFrame
Definition: mythframe.h:88
FMT_NV12
@ FMT_NV12
Definition: mythframe.h:53
MythInteropGPU::m_textureSize
QSize m_textureSize
Definition: mythinteropgpu.h:63
is_interlaced
bool is_interlaced(FrameScanType Scan)
Definition: videoouttypes.h:188
MythVideoColourSpace
MythVideoColourSpace contains a QMatrix4x4 that can convert YCbCr data to RGB.
Definition: mythvideocolourspace.h:18
MythNVDECInterop::m_cudaFuncs
CudaFunctions * m_cudaFuncs
Definition: mythnvdecinterop.h:39
MythVideoTextureOpenGL::CreateTextures
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.
Definition: mythvideotextureopengl.cpp:57
MythNVDECInterop::MythNVDECInterop
MythNVDECInterop(MythPlayerUI *Player, MythRenderOpenGL *Context)
Definition: mythnvdecinterop.cpp:23
OpenGLLocker
Definition: mythrenderopengl.h:261