diff --git a/app/streaming/video/ffmpeg-renderers/drm.cpp b/app/streaming/video/ffmpeg-renderers/drm.cpp index e8bb7550..702eb584 100644 --- a/app/streaming/video/ffmpeg-renderers/drm.cpp +++ b/app/streaming/video/ffmpeg-renderers/drm.cpp @@ -8,6 +8,7 @@ extern "C" { #include + #include } #include @@ -23,6 +24,11 @@ extern "C" { #define DRM_FORMAT_NV15 fourcc_code('N', 'V', '1', '5') #endif +// Same as NV15 but non-subsampled +#ifndef DRM_FORMAT_NV30 +#define DRM_FORMAT_NV30 fourcc_code('N', 'V', '3', '0') +#endif + // Special Raspberry Pi type (upstreamed) #ifndef DRM_FORMAT_P030 #define DRM_FORMAT_P030 fourcc_code('P', '0', '3', '0') @@ -33,6 +39,11 @@ extern "C" { #define DRM_FORMAT_P010 fourcc_code('P', '0', '1', '0') #endif +// Upstreamed YUV444 10-bit +#ifndef DRM_FORMAT_Q410 +#define DRM_FORMAT_Q410 fourcc_code('Q', '4', '1', '0') +#endif + // Values for "Colorspace" connector property #ifndef DRM_MODE_COLORIMETRY_DEFAULT #define DRM_MODE_COLORIMETRY_DEFAULT 0 @@ -68,7 +79,7 @@ DrmRenderer::DrmRenderer(bool hwaccel, IFFmpegRenderer *backendRenderer) m_DrmFd(-1), m_SdlOwnsDrmFd(false), m_SupportsDirectRendering(false), - m_Main10Hdr(false), + m_VideoFormat(0), m_ConnectorId(0), m_EncoderId(0), m_CrtcId(0), @@ -239,7 +250,7 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS params) int i; m_Window = params->window; - m_Main10Hdr = (params->videoFormat & VIDEO_FORMAT_MASK_10BIT); + m_VideoFormat = params->videoFormat; m_SwFrameMapper.setVideoFormat(params->videoFormat); #if SDL_VERSION_ATLEAST(2, 0, 15) @@ -508,12 +519,31 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS params) // Validate that the candidate plane supports our pixel format bool matchingFormat = false; for (uint32_t j = 0; j < plane->count_formats && !matchingFormat; j++) { - if (m_Main10Hdr) { + if (m_VideoFormat & VIDEO_FORMAT_MASK_10BIT) { + if (m_VideoFormat & VIDEO_FORMAT_MASK_YUV444) { + switch (plane->formats[j]) { + case DRM_FORMAT_Q410: + case DRM_FORMAT_NV30: + matchingFormat = true; + break; + } + } + else { + switch (plane->formats[j]) { + case DRM_FORMAT_P010: + case DRM_FORMAT_P030: + case DRM_FORMAT_NA12: + case DRM_FORMAT_NV15: + matchingFormat = true; + break; + } + } + } + else if (m_VideoFormat & VIDEO_FORMAT_MASK_YUV444) { switch (plane->formats[j]) { - case DRM_FORMAT_P010: - case DRM_FORMAT_P030: - case DRM_FORMAT_NA12: - case DRM_FORMAT_NV15: + case DRM_FORMAT_NV24: + case DRM_FORMAT_NV42: + case DRM_FORMAT_YUV444: matchingFormat = true; break; } @@ -606,7 +636,7 @@ bool DrmRenderer::initialize(PDECODER_PARAMETERS params) else if (!strcmp(prop->name, "Colorspace")) { m_ColorspaceProp = prop; } - else if (!strcmp(prop->name, "max bpc") && m_Main10Hdr) { + else if (!strcmp(prop->name, "max bpc") && (m_VideoFormat & VIDEO_FORMAT_MASK_10BIT)) { if (drmModeObjectSetProperty(m_DrmFd, m_ConnectorId, DRM_MODE_OBJECT_CONNECTOR, prop->prop_id, 16) == 0) { SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Enabled 48-bit HDMI Deep Color"); @@ -676,8 +706,29 @@ bool DrmRenderer::isPixelFormatSupported(int videoFormat, AVPixelFormat pixelFor return true; } else if (videoFormat & VIDEO_FORMAT_MASK_10BIT) { + if (videoFormat & VIDEO_FORMAT_MASK_YUV444) { + switch (pixelFormat) { + case AV_PIX_FMT_YUV444P10: + return true; + default: + return false; + } + } + else { + switch (pixelFormat) { + case AV_PIX_FMT_P010: + return true; + default: + return false; + } + } + } + else if (videoFormat & VIDEO_FORMAT_MASK_YUV444) { switch (pixelFormat) { - case AV_PIX_FMT_P010: + case AV_PIX_FMT_NV24: + case AV_PIX_FMT_NV42: + case AV_PIX_FMT_YUV444P: + case AV_PIX_FMT_YUVJ444P: return true; default: return false; @@ -845,32 +896,38 @@ bool DrmRenderer::mapSoftwareFrame(AVFrame *frame, AVDRMFrameDescriptor *mappedF freeFrame = false; } + const AVPixFmtDescriptor* formatDesc = av_pix_fmt_desc_get((AVPixelFormat) frame->format); + int planes = av_pix_fmt_count_planes((AVPixelFormat) frame->format); + uint32_t drmFormat; - bool fullyPlanar; - int bpc; // NB: Keep this list updated with isPixelFormatSupported() switch (frame->format) { case AV_PIX_FMT_NV12: drmFormat = DRM_FORMAT_NV12; - fullyPlanar = false; - bpc = 8; break; case AV_PIX_FMT_NV21: drmFormat = DRM_FORMAT_NV21; - fullyPlanar = false; - bpc = 8; break; case AV_PIX_FMT_P010: drmFormat = DRM_FORMAT_P010; - fullyPlanar = false; - bpc = 16; break; case AV_PIX_FMT_YUV420P: case AV_PIX_FMT_YUVJ420P: drmFormat = DRM_FORMAT_YUV420; - fullyPlanar = true; - bpc = 8; + break; + case AV_PIX_FMT_NV24: + drmFormat = DRM_FORMAT_NV24; + break; + case AV_PIX_FMT_NV42: + drmFormat = DRM_FORMAT_NV42; + break; + case AV_PIX_FMT_YUV444P: + case AV_PIX_FMT_YUVJ444P: + drmFormat = DRM_FORMAT_YUV444; + break; + case AV_PIX_FMT_YUV444P10: + drmFormat = DRM_FORMAT_Q410; break; default: SDL_LogError(SDL_LOG_CATEGORY_APPLICATION, @@ -884,8 +941,8 @@ bool DrmRenderer::mapSoftwareFrame(AVFrame *frame, AVDRMFrameDescriptor *mappedF struct drm_mode_create_dumb createBuf = {}; createBuf.width = frame->width; - createBuf.height = frame->height * 2; // Y + CbCr at 2x2 subsampling - createBuf.bpp = bpc; + createBuf.height = frame->height + (2 * AV_CEIL_RSHIFT(frame->height, formatDesc->log2_chroma_h)); + createBuf.bpp = av_get_padded_bits_per_pixel(formatDesc); int err = drmIoctl(m_DrmFd, DRM_IOCTL_MODE_CREATE_DUMB, &createBuf); if (err < 0) { @@ -976,18 +1033,14 @@ bool DrmRenderer::mapSoftwareFrame(AVFrame *frame, AVDRMFrameDescriptor *mappedF planeHeight = frame->height; plane.pitch = drmFrame->pitch; } - else if (fullyPlanar) { - // U/V planes are 2x2 subsampled - planeHeight = frame->height / 2; - plane.pitch = drmFrame->pitch / 2; - } else { - // UV/VU planes are 2x2 subsampled. - // - // NB: The pitch is the same between Y and UV/VU, because the 2x subsampling - // is cancelled out by the 2x plane size of UV/VU vs U/V alone. - planeHeight = frame->height / 2; - plane.pitch = drmFrame->pitch; + planeHeight = AV_CEIL_RSHIFT(frame->height, formatDesc->log2_chroma_h); + plane.pitch = AV_CEIL_RSHIFT(drmFrame->pitch, formatDesc->log2_chroma_w); + + // If UV planes are interleaved, double the pitch to count both U+V together + if (planes == 2) { + plane.pitch <<= 1; + } } // Copy the plane data into the dumb buffer @@ -1353,7 +1406,7 @@ bool DrmRenderer::canExportEGL() { "Using EGL rendering due to environment variable"); return true; } - else if (m_SupportsDirectRendering && m_Main10Hdr) { + else if (m_SupportsDirectRendering && (m_VideoFormat & VIDEO_FORMAT_MASK_10BIT)) { SDL_LogInfo(SDL_LOG_CATEGORY_APPLICATION, "Using direct rendering for HDR support"); return false; diff --git a/app/streaming/video/ffmpeg-renderers/drm.h b/app/streaming/video/ffmpeg-renderers/drm.h index e8fa1cec..1e96cae5 100644 --- a/app/streaming/video/ffmpeg-renderers/drm.h +++ b/app/streaming/video/ffmpeg-renderers/drm.h @@ -85,7 +85,7 @@ private: int m_DrmFd; bool m_SdlOwnsDrmFd; bool m_SupportsDirectRendering; - bool m_Main10Hdr; + int m_VideoFormat; uint32_t m_ConnectorId; uint32_t m_EncoderId; uint32_t m_CrtcId; diff --git a/app/streaming/video/ffmpeg-renderers/dxva2.cpp b/app/streaming/video/ffmpeg-renderers/dxva2.cpp index 9c7b1c89..b96a719a 100644 --- a/app/streaming/video/ffmpeg-renderers/dxva2.cpp +++ b/app/streaming/video/ffmpeg-renderers/dxva2.cpp @@ -765,11 +765,15 @@ bool DXVA2Renderer::initialize(PDECODER_PARAMETERS params) m_Desc.SampleFormat.VideoTransferFunction = DXVA2_VideoTransFunc_Unknown; m_Desc.SampleFormat.SampleFormat = DXVA2_SampleProgressiveFrame; - if (m_VideoFormat & VIDEO_FORMAT_MASK_10BIT) { - m_Desc.Format = (D3DFORMAT)MAKEFOURCC('P','0','1','0'); + if (m_VideoFormat & VIDEO_FORMAT_MASK_YUV444) { + m_Desc.Format = (m_VideoFormat & VIDEO_FORMAT_MASK_10BIT) ? + (D3DFORMAT)MAKEFOURCC('Y','4','1','0') : + (D3DFORMAT)MAKEFOURCC('A','Y','U','V'); } else { - m_Desc.Format = (D3DFORMAT)MAKEFOURCC('N','V','1','2'); + m_Desc.Format = (m_VideoFormat & VIDEO_FORMAT_MASK_10BIT) ? + (D3DFORMAT)MAKEFOURCC('P','0','1','0') : + (D3DFORMAT)MAKEFOURCC('N','V','1','2'); } if (!initializeDevice(params->window, params->enableVsync)) { diff --git a/app/streaming/video/ffmpeg-renderers/plvk.cpp b/app/streaming/video/ffmpeg-renderers/plvk.cpp index 479d5eba..004c551d 100644 --- a/app/streaming/video/ffmpeg-renderers/plvk.cpp +++ b/app/streaming/video/ffmpeg-renderers/plvk.cpp @@ -957,11 +957,24 @@ bool PlVkRenderer::isPixelFormatSupported(int videoFormat, AVPixelFormat pixelFo } else if (videoFormat & VIDEO_FORMAT_MASK_YUV444) { if (videoFormat & VIDEO_FORMAT_MASK_10BIT) { - return pixelFormat == AV_PIX_FMT_YUV444P10; + switch (pixelFormat) { + case AV_PIX_FMT_P410: + case AV_PIX_FMT_YUV444P10: + return true; + default: + return false; + } } else { - return pixelFormat == AV_PIX_FMT_YUV444P || - pixelFormat == AV_PIX_FMT_YUVJ444P; + switch (pixelFormat) { + case AV_PIX_FMT_NV24: + case AV_PIX_FMT_NV42: + case AV_PIX_FMT_YUV444P: + case AV_PIX_FMT_YUVJ444P: + return true; + default: + return false; + } } } else if (videoFormat & VIDEO_FORMAT_MASK_10BIT) { diff --git a/app/streaming/video/ffmpeg-renderers/sdlvid.cpp b/app/streaming/video/ffmpeg-renderers/sdlvid.cpp index 61300add..be73e366 100644 --- a/app/streaming/video/ffmpeg-renderers/sdlvid.cpp +++ b/app/streaming/video/ffmpeg-renderers/sdlvid.cpp @@ -86,6 +86,10 @@ bool SdlRenderer::isPixelFormatSupported(int videoFormat, AVPixelFormat pixelFor // SDL2 doesn't support 10-bit pixel formats return false; } + else if (videoFormat & VIDEO_FORMAT_MASK_YUV444) { + // SDL2 doesn't support YUV444 pixel formats + return false; + } else { // Remember to keep this in sync with SdlRenderer::renderFrame()! switch (pixelFormat) { @@ -108,8 +112,8 @@ bool SdlRenderer::initialize(PDECODER_PARAMETERS params) m_VideoFormat = params->videoFormat; m_SwFrameMapper.setVideoFormat(m_VideoFormat); - if (params->videoFormat & VIDEO_FORMAT_MASK_10BIT) { - // SDL doesn't support rendering YUV 10-bit textures yet + if (params->videoFormat & (VIDEO_FORMAT_MASK_10BIT | VIDEO_FORMAT_MASK_YUV444)) { + // SDL doesn't support rendering YUV444 or 10-bit textures yet return false; } diff --git a/app/streaming/video/ffmpeg-renderers/vaapi.cpp b/app/streaming/video/ffmpeg-renderers/vaapi.cpp index bff82e8a..f7c38c0a 100644 --- a/app/streaming/video/ffmpeg-renderers/vaapi.cpp +++ b/app/streaming/video/ffmpeg-renderers/vaapi.cpp @@ -20,6 +20,7 @@ VAAPIRenderer::VAAPIRenderer(int decoderSelectionPass) : m_DecoderSelectionPass(decoderSelectionPass), m_HwContext(nullptr), m_BlacklistedForDirectRendering(false), + m_RequiresExplicitPixelFormat(false), m_OverlayMutex(nullptr) #ifdef HAVE_EGL , m_EglExportType(EglExportType::Unknown), @@ -406,6 +407,8 @@ VAAPIRenderer::initialize(PDECODER_PARAMETERS params) m_BlacklistedForDirectRendering = vendorStr.contains("iHD"); } + m_RequiresExplicitPixelFormat = vendorStr.contains("i965"); + // This will populate the driver_quirks err = av_hwdevice_ctx_init(m_HwContext); if (err < 0) { @@ -922,18 +925,21 @@ VAAPIRenderer::canExportSurfaceHandle(int layerTypeFlag, VADRMPRIMESurfaceDescri } // These attributes are required for i965 to create a surface that can - // be successfully exported via vaExportSurfaceHandle(). iHD doesn't - // need these, but it doesn't seem to hurt either. - attrs[attributeCount].type = VASurfaceAttribPixelFormat; - attrs[attributeCount].flags = VA_SURFACE_ATTRIB_SETTABLE; - attrs[attributeCount].value.type = VAGenericValueTypeInteger; - attrs[attributeCount].value.value.i = (m_VideoFormat & VIDEO_FORMAT_MASK_10BIT) ? - VA_FOURCC_P010 : VA_FOURCC_NV12; - attributeCount++; + // be successfully exported via vaExportSurfaceHandle(). YUV444 is not + // handled here but i965 supports no hardware with YUV444 decoding. + if (m_RequiresExplicitPixelFormat && !(m_VideoFormat & VIDEO_FORMAT_MASK_YUV444)) { + attrs[attributeCount].type = VASurfaceAttribPixelFormat; + attrs[attributeCount].flags = VA_SURFACE_ATTRIB_SETTABLE; + attrs[attributeCount].value.type = VAGenericValueTypeInteger; + attrs[attributeCount].value.value.i = + (m_VideoFormat & VIDEO_FORMAT_MASK_10BIT) ? VA_FOURCC_P010 : VA_FOURCC_NV12; + attributeCount++; + } st = vaCreateSurfaces(vaDeviceContext->display, (m_VideoFormat & VIDEO_FORMAT_MASK_10BIT) ? - VA_RT_FORMAT_YUV420_10 : VA_RT_FORMAT_YUV420, + ((m_VideoFormat & VIDEO_FORMAT_MASK_YUV444) ? VA_RT_FORMAT_YUV444_10 : VA_RT_FORMAT_YUV420_10) : + ((m_VideoFormat & VIDEO_FORMAT_MASK_YUV444) ? VA_RT_FORMAT_YUV444 : VA_RT_FORMAT_YUV420), 1280, 720, &surfaceId, @@ -986,6 +992,9 @@ VAAPIRenderer::canExportEGL() { AVPixelFormat VAAPIRenderer::getEGLImagePixelFormat() { switch (m_EglExportType) { case EglExportType::Separate: + // YUV444 surfaces can be in a variety of different formats, so we need to + // use the composed export that returns an opaque format-agnostic texture. + SDL_assert(!(m_VideoFormat & VIDEO_FORMAT_MASK_YUV444)); return (m_VideoFormat & VIDEO_FORMAT_MASK_10BIT) ? AV_PIX_FMT_P010 : AV_PIX_FMT_NV12; diff --git a/app/streaming/video/ffmpeg-renderers/vaapi.h b/app/streaming/video/ffmpeg-renderers/vaapi.h index ad69744e..f45a18c4 100644 --- a/app/streaming/video/ffmpeg-renderers/vaapi.h +++ b/app/streaming/video/ffmpeg-renderers/vaapi.h @@ -96,6 +96,7 @@ private: AVBufferRef* m_HwContext; bool m_BlacklistedForDirectRendering; bool m_HasRfiLatencyBug; + bool m_RequiresExplicitPixelFormat; SDL_mutex* m_OverlayMutex; VAImageFormat m_OverlayFormat;