diff options
author | Christian Strømme <christian.stromme@theqtcompany.com> | 2016-01-15 17:27:32 +0100 |
---|---|---|
committer | Christian Strømme <christian.stromme@theqtcompany.com> | 2016-01-15 17:28:17 +0100 |
commit | 84e426c3af2a3bb1b7f916e54263aea758db38d0 (patch) | |
tree | 4fe09a8da5b15ba466e5771239d06f29a6c123da /src/plugins/common/evr/evrd3dpresentengine.cpp | |
parent | 84aaa48fdfc1f35c9870518a3d4b6f08a1f99449 (diff) | |
parent | 924dc7f48c7003b46079623738ae531f34aed903 (diff) | |
download | qtmultimedia-84e426c3af2a3bb1b7f916e54263aea758db38d0.tar.gz |
Merge remote-tracking branch 'origin/5.6' into dev
Conflicts:
src/plugins/android/src/mediacapture/qandroidcamerasession.cpp
src/plugins/wmf/mftvideo.cpp
Change-Id: I78868b416ea4baec89ca3e2dc9eb4712db16d5fc
Diffstat (limited to 'src/plugins/common/evr/evrd3dpresentengine.cpp')
-rw-r--r-- | src/plugins/common/evr/evrd3dpresentengine.cpp | 638 |
1 files changed, 638 insertions, 0 deletions
diff --git a/src/plugins/common/evr/evrd3dpresentengine.cpp b/src/plugins/common/evr/evrd3dpresentengine.cpp new file mode 100644 index 000000000..77cd7e0c8 --- /dev/null +++ b/src/plugins/common/evr/evrd3dpresentengine.cpp @@ -0,0 +1,638 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "evrd3dpresentengine.h" + +#include "evrhelpers.h" + +#include <qabstractvideobuffer.h> +#include <QAbstractVideoSurface> +#include <qvideoframe.h> +#include <QDebug> +#include <qthread.h> +#include <private/qmediaopenglhelper_p.h> + +#ifdef MAYBE_ANGLE +# include <qtgui/qguiapplication.h> +# include <qpa/qplatformnativeinterface.h> +# include <qopenglfunctions.h> +# include <EGL/eglext.h> +#endif + +static const int PRESENTER_BUFFER_COUNT = 3; + +#ifdef MAYBE_ANGLE + +EGLWrapper::EGLWrapper() +{ +#ifndef QT_OPENGL_ES_2_ANGLE_STATIC + // Resolve the EGL functions we use. When configured for dynamic OpenGL, no + // component in Qt will link to libEGL.lib and libGLESv2.lib. We know + // however that libEGL is loaded for sure, since this is an ANGLE-only path. + +# ifdef QT_DEBUG + HMODULE eglHandle = GetModuleHandle(L"libEGLd.dll"); +# else + HMODULE eglHandle = GetModuleHandle(L"libEGL.dll"); +# endif + + if (!eglHandle) + qWarning("No EGL library loaded"); + + m_eglGetProcAddress = (EglGetProcAddress) GetProcAddress(eglHandle, "eglGetProcAddress"); + m_eglCreatePbufferSurface = (EglCreatePbufferSurface) GetProcAddress(eglHandle, "eglCreatePbufferSurface"); + m_eglDestroySurface = (EglDestroySurface) GetProcAddress(eglHandle, "eglDestroySurface"); + m_eglBindTexImage = (EglBindTexImage) GetProcAddress(eglHandle, "eglBindTexImage"); + m_eglReleaseTexImage = (EglReleaseTexImage) GetProcAddress(eglHandle, "eglReleaseTexImage"); +#else + // Static ANGLE-only build. There is no libEGL.dll in use. + + m_eglGetProcAddress = ::eglGetProcAddress; + m_eglCreatePbufferSurface = ::eglCreatePbufferSurface; + m_eglDestroySurface = ::eglDestroySurface; + m_eglBindTexImage = ::eglBindTexImage; + m_eglReleaseTexImage = ::eglReleaseTexImage; +#endif +} + +__eglMustCastToProperFunctionPointerType EGLWrapper::getProcAddress(const char *procname) +{ + Q_ASSERT(m_eglGetProcAddress); + return m_eglGetProcAddress(procname); +} + +EGLSurface EGLWrapper::createPbufferSurface(EGLDisplay dpy, EGLConfig config, const EGLint *attrib_list) +{ + Q_ASSERT(m_eglCreatePbufferSurface); + return m_eglCreatePbufferSurface(dpy, config, attrib_list); +} + +EGLBoolean EGLWrapper::destroySurface(EGLDisplay dpy, EGLSurface surface) +{ + Q_ASSERT(m_eglDestroySurface); + return m_eglDestroySurface(dpy, surface); +} + +EGLBoolean EGLWrapper::bindTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer) +{ + Q_ASSERT(m_eglBindTexImage); + return m_eglBindTexImage(dpy, surface, buffer); +} + +EGLBoolean EGLWrapper::releaseTexImage(EGLDisplay dpy, EGLSurface surface, EGLint buffer) +{ + Q_ASSERT(m_eglReleaseTexImage); + return m_eglReleaseTexImage(dpy, surface, buffer); +} + + +class OpenGLResources : public QObject +{ +public: + OpenGLResources() + : egl(new EGLWrapper) + , eglDisplay(0) + , eglSurface(0) + , glTexture(0) + {} + + void release() + { + if (thread() == QThread::currentThread()) + delete this; + else + deleteLater(); + } + + EGLWrapper *egl; + EGLDisplay *eglDisplay; + EGLSurface eglSurface; + unsigned int glTexture; + +private: + ~OpenGLResources() + { + Q_ASSERT(QOpenGLContext::currentContext() != NULL); + + if (eglSurface && egl) { + egl->releaseTexImage(eglDisplay, eglSurface, EGL_BACK_BUFFER); + egl->destroySurface(eglDisplay, eglSurface); + } + if (glTexture) + QOpenGLContext::currentContext()->functions()->glDeleteTextures(1, &glTexture); + + delete egl; + } +}; + +#endif // MAYBE_ANGLE + + +class IMFSampleVideoBuffer: public QAbstractVideoBuffer +{ +public: + IMFSampleVideoBuffer(D3DPresentEngine *engine, IMFSample *sample, QAbstractVideoBuffer::HandleType handleType) + : QAbstractVideoBuffer(handleType) + , m_engine(engine) + , m_sample(sample) + , m_surface(0) + , m_mapMode(NotMapped) + , m_textureUpdated(false) + { + if (m_sample) { + m_sample->AddRef(); + + IMFMediaBuffer *buffer; + if (SUCCEEDED(m_sample->GetBufferByIndex(0, &buffer))) { + MFGetService(buffer, + mr_BUFFER_SERVICE, + iid_IDirect3DSurface9, + reinterpret_cast<void **>(&m_surface)); + buffer->Release(); + } + } + } + + ~IMFSampleVideoBuffer() + { + if (m_surface) { + if (m_mapMode != NotMapped) + m_surface->UnlockRect(); + m_surface->Release(); + } + if (m_sample) + m_sample->Release(); + } + + QVariant handle() const; + + MapMode mapMode() const { return m_mapMode; } + uchar *map(MapMode, int*, int*); + void unmap(); + +private: + mutable D3DPresentEngine *m_engine; + IMFSample *m_sample; + IDirect3DSurface9 *m_surface; + MapMode m_mapMode; + mutable bool m_textureUpdated; +}; + +uchar *IMFSampleVideoBuffer::map(MapMode mode, int *numBytes, int *bytesPerLine) +{ + if (!m_surface || m_mapMode != NotMapped) + return 0; + + D3DSURFACE_DESC desc; + if (FAILED(m_surface->GetDesc(&desc))) + return 0; + + D3DLOCKED_RECT rect; + if (FAILED(m_surface->LockRect(&rect, NULL, mode == ReadOnly ? D3DLOCK_READONLY : 0))) + return 0; + + m_mapMode = mode; + + if (numBytes) + *numBytes = (int)(rect.Pitch * desc.Height); + + if (bytesPerLine) + *bytesPerLine = (int)rect.Pitch; + + return reinterpret_cast<uchar *>(rect.pBits); +} + +void IMFSampleVideoBuffer::unmap() +{ + if (m_mapMode == NotMapped) + return; + + m_mapMode = NotMapped; + m_surface->UnlockRect(); +} + +QVariant IMFSampleVideoBuffer::handle() const +{ + QVariant handle; + +#ifdef MAYBE_ANGLE + if (handleType() != GLTextureHandle) + return handle; + + if (m_textureUpdated || m_engine->updateTexture(m_surface)) { + m_textureUpdated = true; + handle = QVariant::fromValue<unsigned int>(m_engine->m_glResources->glTexture); + } +#endif + + return handle; +} + + +D3DPresentEngine::D3DPresentEngine() + : m_deviceResetToken(0) + , m_D3D9(0) + , m_device(0) + , m_deviceManager(0) + , m_useTextureRendering(false) +#ifdef MAYBE_ANGLE + , m_glResources(0) + , m_texture(0) +#endif +{ + ZeroMemory(&m_displayMode, sizeof(m_displayMode)); + + HRESULT hr = initializeD3D(); + + if (SUCCEEDED(hr)) { + hr = createD3DDevice(); + if (FAILED(hr)) + qWarning("Failed to create D3D device"); + } else { + qWarning("Failed to initialize D3D"); + } +} + +D3DPresentEngine::~D3DPresentEngine() +{ + releaseResources(); + + qt_evr_safe_release(&m_device); + qt_evr_safe_release(&m_deviceManager); + qt_evr_safe_release(&m_D3D9); +} + +HRESULT D3DPresentEngine::initializeD3D() +{ + HRESULT hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &m_D3D9); + + if (SUCCEEDED(hr)) + hr = DXVA2CreateDirect3DDeviceManager9(&m_deviceResetToken, &m_deviceManager); + + return hr; +} + +HRESULT D3DPresentEngine::createD3DDevice() +{ + HRESULT hr = S_OK; + HWND hwnd = NULL; + UINT uAdapterID = D3DADAPTER_DEFAULT; + DWORD vp = 0; + + D3DCAPS9 ddCaps; + ZeroMemory(&ddCaps, sizeof(ddCaps)); + + IDirect3DDevice9Ex* device = NULL; + + if (!m_D3D9 || !m_deviceManager) + return MF_E_NOT_INITIALIZED; + + hwnd = ::GetShellWindow(); + + D3DPRESENT_PARAMETERS pp; + ZeroMemory(&pp, sizeof(pp)); + + pp.BackBufferWidth = 1; + pp.BackBufferHeight = 1; + pp.BackBufferFormat = D3DFMT_UNKNOWN; + pp.BackBufferCount = 1; + pp.Windowed = TRUE; + pp.SwapEffect = D3DSWAPEFFECT_DISCARD; + pp.BackBufferFormat = D3DFMT_UNKNOWN; + pp.hDeviceWindow = hwnd; + pp.Flags = D3DPRESENTFLAG_VIDEO; + pp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; + + hr = m_D3D9->GetDeviceCaps(uAdapterID, D3DDEVTYPE_HAL, &ddCaps); + if (FAILED(hr)) + goto done; + + if (ddCaps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT) + vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; + else + vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; + + hr = m_D3D9->CreateDeviceEx( + uAdapterID, + D3DDEVTYPE_HAL, + pp.hDeviceWindow, + vp | D3DCREATE_NOWINDOWCHANGES | D3DCREATE_MULTITHREADED | D3DCREATE_FPU_PRESERVE, + &pp, + NULL, + &device + ); + if (FAILED(hr)) + goto done; + + hr = m_D3D9->GetAdapterDisplayMode(uAdapterID, &m_displayMode); + if (FAILED(hr)) + goto done; + + hr = m_deviceManager->ResetDevice(device, m_deviceResetToken); + if (FAILED(hr)) + goto done; + + qt_evr_safe_release(&m_device); + + m_device = device; + m_device->AddRef(); + +done: + qt_evr_safe_release(&device); + return hr; +} + +bool D3DPresentEngine::isValid() const +{ + return m_device != NULL; +} + +void D3DPresentEngine::releaseResources() +{ + m_surfaceFormat = QVideoSurfaceFormat(); + +#ifdef MAYBE_ANGLE + qt_evr_safe_release(&m_texture); + + if (m_glResources) { + m_glResources->release(); // deleted in GL thread + m_glResources = NULL; + } +#endif +} + +HRESULT D3DPresentEngine::getService(REFGUID, REFIID riid, void** ppv) +{ + HRESULT hr = S_OK; + + if (riid == __uuidof(IDirect3DDeviceManager9)) { + if (m_deviceManager == NULL) { + hr = MF_E_UNSUPPORTED_SERVICE; + } else { + *ppv = m_deviceManager; + m_deviceManager->AddRef(); + } + } else { + hr = MF_E_UNSUPPORTED_SERVICE; + } + + return hr; +} + +HRESULT D3DPresentEngine::checkFormat(D3DFORMAT format) +{ + if (!m_D3D9 || !m_device) + return E_FAIL; + + HRESULT hr = S_OK; + + D3DDISPLAYMODE mode; + D3DDEVICE_CREATION_PARAMETERS params; + + hr = m_device->GetCreationParameters(¶ms); + if (FAILED(hr)) + return hr; + + UINT uAdapter = params.AdapterOrdinal; + D3DDEVTYPE type = params.DeviceType; + + hr = m_D3D9->GetAdapterDisplayMode(uAdapter, &mode); + if (FAILED(hr)) + return hr; + + hr = m_D3D9->CheckDeviceFormat(uAdapter, type, mode.Format, + D3DUSAGE_RENDERTARGET, + D3DRTYPE_SURFACE, + format); + + if (m_useTextureRendering && format != D3DFMT_X8R8G8B8 && format != D3DFMT_A8R8G8B8) { + // The texture is always in RGB32 so the d3d driver must support conversion from the + // requested format to RGB32. + hr = m_D3D9->CheckDeviceFormatConversion(uAdapter, type, format, D3DFMT_X8R8G8B8); + } + + return hr; +} + +bool D3DPresentEngine::supportsTextureRendering() const +{ +#ifdef MAYBE_ANGLE + return QMediaOpenGLHelper::isANGLE(); +#else + return false; +#endif +} + +void D3DPresentEngine::setHint(Hint hint, bool enable) +{ + if (hint == RenderToTexture) + m_useTextureRendering = enable && supportsTextureRendering(); +} + +HRESULT D3DPresentEngine::createVideoSamples(IMFMediaType *format, QList<IMFSample*> &videoSampleQueue) +{ + if (!format) + return MF_E_UNEXPECTED; + + HRESULT hr = S_OK; + + IDirect3DSurface9 *surface = NULL; + IMFSample *videoSample = NULL; + + releaseResources(); + + UINT32 width = 0, height = 0; + hr = MFGetAttributeSize(format, MF_MT_FRAME_SIZE, &width, &height); + if (FAILED(hr)) + return hr; + + DWORD d3dFormat = 0; + hr = qt_evr_getFourCC(format, &d3dFormat); + if (FAILED(hr)) + return hr; + + // Create the video samples. + for (int i = 0; i < PRESENTER_BUFFER_COUNT; i++) { + hr = m_device->CreateRenderTarget(width, height, + (D3DFORMAT)d3dFormat, + D3DMULTISAMPLE_NONE, + 0, + TRUE, + &surface, NULL); + if (FAILED(hr)) + goto done; + + hr = MFCreateVideoSampleFromSurface(surface, &videoSample); + if (FAILED(hr)) + goto done; + + videoSample->AddRef(); + videoSampleQueue.append(videoSample); + + qt_evr_safe_release(&videoSample); + qt_evr_safe_release(&surface); + } + +done: + if (SUCCEEDED(hr)) { + m_surfaceFormat = QVideoSurfaceFormat(QSize(width, height), + m_useTextureRendering ? QVideoFrame::Format_RGB32 + : qt_evr_pixelFormatFromD3DFormat((D3DFORMAT)d3dFormat), + m_useTextureRendering ? QAbstractVideoBuffer::GLTextureHandle + : QAbstractVideoBuffer::NoHandle); + } else { + releaseResources(); + } + + qt_evr_safe_release(&videoSample); + qt_evr_safe_release(&surface); + return hr; +} + +QVideoFrame D3DPresentEngine::makeVideoFrame(IMFSample *sample) +{ + if (!sample) + return QVideoFrame(); + + QVideoFrame frame(new IMFSampleVideoBuffer(this, sample, m_surfaceFormat.handleType()), + m_surfaceFormat.frameSize(), + m_surfaceFormat.pixelFormat()); + + // WMF uses 100-nanosecond units, Qt uses microseconds + LONGLONG startTime = -1; + if (SUCCEEDED(sample->GetSampleTime(&startTime))) { + frame.setStartTime(startTime * 0.1); + + LONGLONG duration = -1; + if (SUCCEEDED(sample->GetSampleDuration(&duration))) + frame.setEndTime((startTime + duration) * 0.1); + } + + return frame; +} + +#ifdef MAYBE_ANGLE + +bool D3DPresentEngine::createRenderTexture() +{ + if (m_texture) + return true; + + Q_ASSERT(QOpenGLContext::currentContext() != NULL); + + if (!m_glResources) + m_glResources = new OpenGLResources; + + QOpenGLContext *currentContext = QOpenGLContext::currentContext(); + if (!currentContext) + return false; + + QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface(); + m_glResources->eglDisplay = static_cast<EGLDisplay*>( + nativeInterface->nativeResourceForContext("eglDisplay", currentContext)); + EGLConfig *eglConfig = static_cast<EGLConfig*>( + nativeInterface->nativeResourceForContext("eglConfig", currentContext)); + + currentContext->functions()->glGenTextures(1, &m_glResources->glTexture); + + bool hasAlpha = currentContext->format().hasAlpha(); + + EGLint attribs[] = { + EGL_WIDTH, m_surfaceFormat.frameWidth(), + EGL_HEIGHT, m_surfaceFormat.frameHeight(), + EGL_TEXTURE_FORMAT, hasAlpha ? EGL_TEXTURE_RGBA : EGL_TEXTURE_RGB, + EGL_TEXTURE_TARGET, EGL_TEXTURE_2D, + EGL_NONE + }; + + EGLSurface pbuffer = m_glResources->egl->createPbufferSurface(m_glResources->eglDisplay, eglConfig, attribs); + + HANDLE share_handle = 0; + PFNEGLQUERYSURFACEPOINTERANGLEPROC eglQuerySurfacePointerANGLE = + reinterpret_cast<PFNEGLQUERYSURFACEPOINTERANGLEPROC>(m_glResources->egl->getProcAddress("eglQuerySurfacePointerANGLE")); + Q_ASSERT(eglQuerySurfacePointerANGLE); + eglQuerySurfacePointerANGLE( + m_glResources->eglDisplay, + pbuffer, + EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE, &share_handle); + + + m_device->CreateTexture(m_surfaceFormat.frameWidth(), m_surfaceFormat.frameHeight(), 1, + D3DUSAGE_RENDERTARGET, + hasAlpha ? D3DFMT_A8R8G8B8 : D3DFMT_X8R8G8B8, + D3DPOOL_DEFAULT, + &m_texture, + &share_handle); + + m_glResources->eglSurface = pbuffer; + + QOpenGLContext::currentContext()->functions()->glBindTexture(GL_TEXTURE_2D, m_glResources->glTexture); + m_glResources->egl->bindTexImage(m_glResources->eglDisplay, m_glResources->eglSurface, EGL_BACK_BUFFER); + + return m_texture != NULL; +} + +bool D3DPresentEngine::updateTexture(IDirect3DSurface9 *src) +{ + if (!m_texture && !createRenderTexture()) + return false; + + IDirect3DSurface9 *dest = NULL; + + // Copy the sample surface to the shared D3D/EGL surface + HRESULT hr = m_texture->GetSurfaceLevel(0, &dest); + if (FAILED(hr)) + goto done; + + hr = m_device->StretchRect(src, NULL, dest, NULL, D3DTEXF_NONE); + if (FAILED(hr)) { + qWarning("Failed to copy D3D surface"); + } else { + // Shared surfaces are not synchronized, there's no guarantee that + // StretchRect is complete when the texture is later rendered by Qt. + // To make sure the next rendered frame is up to date, flush the command pipeline + // using an event query. + IDirect3DQuery9 *eventQuery = NULL; + m_device->CreateQuery(D3DQUERYTYPE_EVENT, &eventQuery); + eventQuery->Issue(D3DISSUE_END); + while (eventQuery->GetData(NULL, 0, D3DGETDATA_FLUSH) == S_FALSE); + eventQuery->Release(); + } + +done: + qt_evr_safe_release(&dest); + + return SUCCEEDED(hr); +} + +#endif // MAYBE_ANGLE |