/* * Copyright (C) 2017-2019 Team Kodi * This file is part of Kodi - https://kodi.tv * * SPDX-License-Identifier: GPL-2.0-or-later * See LICENSES/README.md for more information. */ #include "RendererDXVA.h" #include "DVDCodecs/Video/DVDVideoCodec.h" #include "VideoRenderers/BaseRenderer.h" #include "WIN32Util.h" #include "rendering/dx/RenderContext.h" #include "utils/log.h" #include "utils/memcpy_sse2.h" #include "windowing/GraphicContext.h" #include using namespace Microsoft::WRL; CRendererBase* CRendererDXVA::Create(CVideoSettings& videoSettings) { return new CRendererDXVA(videoSettings); } void CRendererDXVA::GetWeight(std::map& weights, const VideoPicture& picture) { unsigned weight = 0; const AVPixelFormat av_pixel_format = picture.videoBuffer->GetFormat(); if (av_pixel_format == AV_PIX_FMT_D3D11VA_VLD) weight += 1000; else { // check format for buffer const DXGI_FORMAT dxgi_format = CRenderBufferImpl::GetDXGIFormat(av_pixel_format, GetDXGIFormat(picture)); if (dxgi_format == DXGI_FORMAT_UNKNOWN) return; CD3D11_TEXTURE2D_DESC texDesc( dxgi_format, FFALIGN(picture.iWidth, 32), FFALIGN(picture.iHeight, 32), 1, 1, D3D11_BIND_DECODER, D3D11_USAGE_DYNAMIC, D3D11_CPU_ACCESS_WRITE ); ComPtr pDevice = DX::DeviceResources::Get()->GetD3DDevice(); if (FAILED(pDevice->CreateTexture2D(&texDesc, nullptr, nullptr))) { CLog::LogF(LOGWARNING, "Texture format {} is not supported.", dxgi_format); return; } if (av_pixel_format == AV_PIX_FMT_NV12 || av_pixel_format == AV_PIX_FMT_P010 || av_pixel_format == AV_PIX_FMT_P016) weight += 500; // single copying else if (av_pixel_format == AV_PIX_FMT_YUV420P || av_pixel_format == AV_PIX_FMT_YUV420P10 || av_pixel_format == AV_PIX_FMT_YUV420P16) weight += 400; // single copying + convert } // prefer DXVA method for interlaced HW decoded material if (av_pixel_format == AV_PIX_FMT_D3D11VA_VLD && picture.iFlags & DVP_FLAG_INTERLACED) weight += 1000; if (weight > 0) weights[RENDER_DXVA] = weight; } CRenderInfo CRendererDXVA::GetRenderInfo() { auto info = __super::GetRenderInfo(); const int buffers = NUM_BUFFERS + m_processor->PastRefs(); info.optimal_buffer_size = std::min(NUM_BUFFERS, buffers); info.m_deintMethods.push_back(VS_INTERLACEMETHOD_DXVA_AUTO); return info; } bool CRendererDXVA::Configure(const VideoPicture& picture, float fps, unsigned orientation) { const auto support_type = D3D11_VIDEO_PROCESSOR_FORMAT_SUPPORT_INPUT; if (__super::Configure(picture, fps, orientation)) { m_format = picture.videoBuffer->GetFormat(); const DXGI_FORMAT dxgi_format = CRenderBufferImpl::GetDXGIFormat(m_format, GetDXGIFormat(picture)); // create processor m_processor = std::make_unique(); if (m_processor->PreInit() && m_processor->Open(m_sourceWidth, m_sourceHeight) && m_processor->IsFormatSupported(dxgi_format, support_type)) { return true; } CLog::LogF(LOGERROR, "unable to create DXVA processor"); m_processor.reset(); } return false; } bool CRendererDXVA::NeedBuffer(int idx) { if (m_renderBuffers[idx]->IsLoaded() && m_renderBuffers[idx]->pictureFlags & DVP_FLAG_INTERLACED) { if (m_renderBuffers[idx]->frameIdx + (m_processor->PastRefs() * 2u) >= m_renderBuffers[m_iBufferIndex]->frameIdx) return true; } return false; } void CRendererDXVA::CheckVideoParameters() { __super::CheckVideoParameters(); CreateIntermediateTarget( HasHQScaler() ? m_sourceWidth : m_viewWidth, HasHQScaler() ? m_sourceHeight : m_viewHeight); } void CRendererDXVA::RenderImpl(CD3DTexture& target, CRect& sourceRect, CPoint(&destPoints)[4], uint32_t flags) { CRect src = sourceRect; CRect dst = HasHQScaler() ? sourceRect : ApplyTransforms(CRect(destPoints[0], destPoints[2])); const CRect trg(0.0f, 0.0f, static_cast(target.GetWidth()), static_cast(target.GetHeight())); CWIN32Util::CropSource(src, dst, trg, m_renderOrientation); CRenderBuffer* buf = m_renderBuffers[m_iBufferIndex]; CRenderBuffer* views[8] = {}; FillBuffersSet(views); m_processor->Render(src, dst, target.Get(), views, flags, buf->frameIdx % UINT32_MAX, m_renderOrientation, m_videoSettings.m_Contrast, m_videoSettings.m_Brightness); if (!HasHQScaler()) { // change src and dest in case of dxva scale dst.GetQuad(destPoints); sourceRect = dst; } } CRect CRendererDXVA::ApplyTransforms(const CRect& destRect) const { CRect result; CPoint rotated[4]; ReorderDrawPoints(destRect, rotated); switch (m_renderOrientation) { case 90: result = { rotated[3], rotated[1] }; break; case 180: result = destRect; break; case 270: result = { rotated[1], rotated[3] }; break; default: result = CServiceBroker::GetWinSystem()->GetGfxContext().StereoCorrection(destRect); break; } return result; } void CRendererDXVA::FillBuffersSet(CRenderBuffer* (&buffers)[8]) { int past = 0; int future = 0; CRenderBuffer* buf = m_renderBuffers[m_iBufferIndex]; buffers[2] = buf; // set future frames while (future < 2) { bool found = false; for (int i = 0; i < m_iNumBuffers; i++) { if (m_renderBuffers[i]->frameIdx == buf->frameIdx + (future * 2 + 2)) { // a future frame may not be loaded yet if (m_renderBuffers[i]->IsLoaded() || m_renderBuffers[i]->UploadBuffer()) { buffers[1 - future++] = m_renderBuffers[i]; found = true; break; } } } if (!found) break; } // set past frames while (past < 4) { bool found = false; for (int i = 0; i < m_iNumBuffers; i++) { if (m_renderBuffers[i]->frameIdx == buf->frameIdx - (past * 2 + 2)) { if (m_renderBuffers[i]->IsLoaded()) { buffers[3 + past++] = m_renderBuffers[i]; found = true; break; } } } if (!found) break; } } bool CRendererDXVA::Supports(ESCALINGMETHOD method) const { if (method == VS_SCALINGMETHOD_DXVA_HARDWARE) return true; return __super::Supports(method); } CRenderBuffer* CRendererDXVA::CreateBuffer() { return new CRenderBufferImpl(m_format, m_sourceWidth, m_sourceHeight); } CRendererDXVA::CRenderBufferImpl::CRenderBufferImpl(AVPixelFormat av_pix_format, unsigned width, unsigned height) : CRenderBuffer(av_pix_format, width, height) { const auto dxgi_format = GetDXGIFormat(av_pix_format); if (dxgi_format == DXGI_FORMAT_UNKNOWN) return; m_widthTex = FFALIGN(width, 32); m_heightTex = FFALIGN(height, 32); m_texture.Create(m_widthTex, m_heightTex, 1, D3D11_USAGE_DYNAMIC, dxgi_format); } CRendererDXVA::CRenderBufferImpl::~CRenderBufferImpl() { CRenderBufferImpl::ReleasePicture(); } bool CRendererDXVA::CRenderBufferImpl::UploadBuffer() { if (!videoBuffer) return false; if (videoBuffer->GetFormat() == AV_PIX_FMT_D3D11VA_VLD) { m_bLoaded = true; return true; } return UploadToTexture(); } HRESULT CRendererDXVA::CRenderBufferImpl::GetResource(ID3D11Resource** ppResource, unsigned* index) const { if (!ppResource) return E_POINTER; if (!index) return E_POINTER; if (videoBuffer->GetFormat() == AV_PIX_FMT_D3D11VA_VLD) return __super::GetResource(ppResource, index); ComPtr pResource = m_texture.Get(); *ppResource = pResource.Detach(); *index = 0; return S_OK; } DXGI_FORMAT CRendererDXVA::CRenderBufferImpl::GetDXGIFormat(AVPixelFormat format, DXGI_FORMAT default_fmt) { switch (format) { case AV_PIX_FMT_NV12: case AV_PIX_FMT_YUV420P: return DXGI_FORMAT_NV12; case AV_PIX_FMT_P010: case AV_PIX_FMT_YUV420P10: return DXGI_FORMAT_P010; case AV_PIX_FMT_P016: case AV_PIX_FMT_YUV420P16: return DXGI_FORMAT_P016; default: return default_fmt; } } bool CRendererDXVA::CRenderBufferImpl::UploadToTexture() { D3D11_MAPPED_SUBRESOURCE rect; if (!m_texture.LockRect(0, &rect, D3D11_MAP_WRITE_DISCARD)) return false; // destination uint8_t* pData = static_cast(rect.pData); uint8_t* dst[] = { pData, pData + m_texture.GetHeight() * rect.RowPitch }; int dstStride[] = { static_cast(rect.RowPitch), static_cast(rect.RowPitch) }; // source uint8_t* src[3]; int srcStrides[3]; videoBuffer->GetPlanes(src); videoBuffer->GetStrides(srcStrides); const unsigned width = m_width; const unsigned height = m_height; const AVPixelFormat buffer_format = videoBuffer->GetFormat(); // copy to texture if (buffer_format == AV_PIX_FMT_NV12 || buffer_format == AV_PIX_FMT_P010 || buffer_format == AV_PIX_FMT_P016) { Concurrency::parallel_invoke([&]() { // copy Y copy_plane(src[0], srcStrides[0], height, width, dst[0], dstStride[0]); }, [&]() { // copy UV copy_plane(src[1], srcStrides[1], height >> 1, width, dst[1], dstStride[1]); }); // copy cache size of UV line again to fix Intel cache issue copy_plane(src[1], srcStrides[1], 1, 32, dst[1], dstStride[1]); } // convert 8bit else if (buffer_format == AV_PIX_FMT_YUV420P) { Concurrency::parallel_invoke([&]() { // copy Y copy_plane(src[0], srcStrides[0], height, width, dst[0], dstStride[0]); }, [&]() { // convert U+V -> UV convert_yuv420_nv12_chrome(&src[1], &srcStrides[1], height, width, dst[1], dstStride[1]); }); // copy cache size of UV line again to fix Intel cache issue // height and width multiplied by two because they will be divided by func convert_yuv420_nv12_chrome(&src[1], &srcStrides[1], 2, 64, dst[1], dstStride[1]); } // convert 10/16bit else if (buffer_format == AV_PIX_FMT_YUV420P10 || buffer_format == AV_PIX_FMT_YUV420P16) { const uint8_t bpp = buffer_format == AV_PIX_FMT_YUV420P10 ? 10 : 16; Concurrency::parallel_invoke([&]() { // copy Y copy_plane(src[0], srcStrides[0], height, width, dst[0], dstStride[0], bpp); }, [&]() { // convert U+V -> UV convert_yuv420_p01x_chrome(&src[1], &srcStrides[1], height, width, dst[1], dstStride[1], bpp); }); // copy cache size of UV line again to fix Intel cache issue // height multiplied by two because it will be divided by func convert_yuv420_p01x_chrome(&src[1], &srcStrides[1], 2, 32, dst[1], dstStride[1], bpp); } m_bLoaded = m_texture.UnlockRect(0); return m_bLoaded; }