diff options
Diffstat (limited to 'src/plugins/wmf/evrd3dpresentengine.cpp')
-rw-r--r-- | src/plugins/wmf/evrd3dpresentengine.cpp | 580 |
1 files changed, 580 insertions, 0 deletions
diff --git a/src/plugins/wmf/evrd3dpresentengine.cpp b/src/plugins/wmf/evrd3dpresentengine.cpp new file mode 100644 index 000000000..c67b5d448 --- /dev/null +++ b/src/plugins/wmf/evrd3dpresentengine.cpp @@ -0,0 +1,580 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/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 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "evrd3dpresentengine.h" + +#include "mfglobal.h" + +#include <qtgui/qguiapplication.h> +#include <qpa/qplatformnativeinterface.h> +#include <qtgui/qopenglcontext.h> +#include <qabstractvideobuffer.h> +#include <QAbstractVideoSurface> +#include <qvideoframe.h> +#include <QDebug> +#include <qopenglcontext.h> +#include <qwindow.h> + +#include <EGL/egl.h> +#include <EGL/eglext.h> +#include <GLES2/gl2.h> +#include <d3d9.h> +#include <dxva2api.h> +#include <WinUser.h> +#include <evr.h> + +QT_USE_NAMESPACE + +static const DWORD PRESENTER_BUFFER_COUNT = 3; + +class TextureVideoBuffer : public QAbstractVideoBuffer +{ +public: + TextureVideoBuffer(GLuint textureId) + : QAbstractVideoBuffer(GLTextureHandle) + , m_textureId(textureId) + {} + + ~TextureVideoBuffer() {} + + MapMode mapMode() const { return NotMapped; } + uchar *map(MapMode, int*, int*) { return 0; } + void unmap() {} + + QVariant handle() const + { + return QVariant::fromValue<unsigned int>(m_textureId); + } + +private: + GLuint m_textureId; +}; + + +D3DPresentEngine::D3DPresentEngine() + : QObject() + , m_mutex(QMutex::Recursive) + , m_deviceResetToken(0) + , m_D3D9(0) + , m_device(0) + , m_deviceManager(0) + , m_surface(0) + , m_glContext(0) + , m_offscreenSurface(0) + , m_eglDisplay(0) + , m_eglConfig(0) + , m_eglSurface(0) + , m_glTexture(0) + , m_texture(0) +{ + 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() +{ + qt_wmf_safeRelease(&m_texture); + qt_wmf_safeRelease(&m_device); + qt_wmf_safeRelease(&m_deviceManager); + qt_wmf_safeRelease(&m_D3D9); + + if (m_eglSurface) { + eglReleaseTexImage(m_eglDisplay, m_eglSurface, EGL_BACK_BUFFER); + eglDestroySurface(m_eglDisplay, m_eglSurface); + m_eglSurface = NULL; + } + if (m_glTexture) + glDeleteTextures(1, &m_glTexture); + + delete m_glContext; + delete m_offscreenSurface; +} + +void D3DPresentEngine::start() +{ + QMutexLocker locker(&m_mutex); + + if (!m_surfaceFormat.isValid()) + return; + + if (!m_texture) + createOffscreenTexture(); + + if (m_surface && !m_surface->isActive()) + m_surface->start(m_surfaceFormat); +} + +void D3DPresentEngine::stop() +{ + QMutexLocker locker(&m_mutex); + if (m_surface && m_surface->isActive()) + m_surface->stop(); +} + +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) +{ + HRESULT hr = S_OK; + + UINT uAdapter = D3DADAPTER_DEFAULT; + D3DDEVTYPE type = D3DDEVTYPE_HAL; + + D3DDISPLAYMODE mode; + D3DDEVICE_CREATION_PARAMETERS params; + + // Our shared D3D/EGL surface only supports RGB32, + // reject all other formats + if (format != D3DFMT_X8R8G8B8) + return MF_E_INVALIDMEDIATYPE; + + if (m_device) { + hr = m_device->GetCreationParameters(¶ms); + if (FAILED(hr)) + return hr; + + uAdapter = params.AdapterOrdinal; + type = params.DeviceType; + } + + hr = m_D3D9->GetAdapterDisplayMode(uAdapter, &mode); + if (FAILED(hr)) + return hr; + + return m_D3D9->CheckDeviceType(uAdapter, type, mode.Format, format, TRUE); +} + +HRESULT D3DPresentEngine::createVideoSamples(IMFMediaType *format, QList<IMFSample*> &videoSampleQueue) +{ + if (!format) + return MF_E_UNEXPECTED; + + HRESULT hr = S_OK; + D3DPRESENT_PARAMETERS pp; + + IDirect3DSwapChain9 *swapChain = NULL; + IMFSample *videoSample = NULL; + + QMutexLocker locker(&m_mutex); + + releaseResources(); + + // Get the swap chain parameters from the media type. + hr = getSwapChainPresentParameters(format, &pp); + if (FAILED(hr)) + goto done; + + // Create the video samples. + for (int i = 0; i < PRESENTER_BUFFER_COUNT; i++) { + // Create a new swap chain. + hr = m_device->CreateAdditionalSwapChain(&pp, &swapChain); + if (FAILED(hr)) + goto done; + + // Create the video sample from the swap chain. + hr = createD3DSample(swapChain, &videoSample); + if (FAILED(hr)) + goto done; + + // Add it to the list. + videoSample->AddRef(); + videoSampleQueue.append(videoSample); + + // Set the swap chain pointer as a custom attribute on the sample. This keeps + // a reference count on the swap chain, so that the swap chain is kept alive + // for the duration of the sample's lifetime. + hr = videoSample->SetUnknown(MFSamplePresenter_SampleSwapChain, swapChain); + if (FAILED(hr)) + goto done; + + qt_wmf_safeRelease(&videoSample); + qt_wmf_safeRelease(&swapChain); + } + +done: + if (FAILED(hr)) + releaseResources(); + + qt_wmf_safeRelease(&swapChain); + qt_wmf_safeRelease(&videoSample); + return hr; +} + +void D3DPresentEngine::releaseResources() +{ +} + +void D3DPresentEngine::presentSample(void *opaque, qint64) +{ + HRESULT hr = S_OK; + + IMFSample *sample = reinterpret_cast<IMFSample*>(opaque); + IMFMediaBuffer* buffer = NULL; + IDirect3DSurface9* surface = NULL; + + if (sample) { + // Get the buffer from the sample. + hr = sample->GetBufferByIndex(0, &buffer); + if (FAILED(hr)) + goto done; + + // Get the surface from the buffer. + hr = MFGetService(buffer, MR_BUFFER_SERVICE, IID_PPV_ARGS(&surface)); + if (FAILED(hr)) + goto done; + } + + if (surface && updateTexture(surface)) { + m_surface->present(QVideoFrame(new TextureVideoBuffer(m_glTexture), + m_surfaceFormat.frameSize(), + m_surfaceFormat.pixelFormat())); + } + +done: + qt_wmf_safeRelease(&surface); + qt_wmf_safeRelease(&buffer); + qt_wmf_safeRelease(&sample); +} + +void D3DPresentEngine::setSurface(QAbstractVideoSurface *surface) +{ + QMutexLocker locker(&m_mutex); + m_surface = surface; +} + +void D3DPresentEngine::setSurfaceFormat(const QVideoSurfaceFormat &format) +{ + QMutexLocker locker(&m_mutex); + m_surfaceFormat = format; +} + +void D3DPresentEngine::createOffscreenTexture() +{ + // First, check if we have a context on this thread + QOpenGLContext *currentContext = QOpenGLContext::currentContext(); + + if (!currentContext) { + //Create OpenGL context and set share context from surface + QOpenGLContext *shareContext = qobject_cast<QOpenGLContext*>(m_surface->property("GLContext").value<QObject*>()); + if (!shareContext) + return; + + m_offscreenSurface = new QWindow; + m_offscreenSurface->setSurfaceType(QWindow::OpenGLSurface); + //Needs geometry to be a valid surface, but size is not important + m_offscreenSurface->setGeometry(-1, -1, 1, 1); + m_offscreenSurface->create(); + + m_glContext = new QOpenGLContext; + m_glContext->setFormat(m_offscreenSurface->requestedFormat()); + m_glContext->setShareContext(shareContext); + + if (!m_glContext->create()) { + delete m_glContext; + delete m_offscreenSurface; + m_glContext = 0; + m_offscreenSurface = 0; + return; + } + + currentContext = m_glContext; + } + + if (m_glContext) + m_glContext->makeCurrent(m_offscreenSurface); + + QPlatformNativeInterface *nativeInterface = QGuiApplication::platformNativeInterface(); + m_eglDisplay = static_cast<EGLDisplay*>( + nativeInterface->nativeResourceForContext("eglDisplay", currentContext)); + m_eglConfig = static_cast<EGLDisplay*>( + nativeInterface->nativeResourceForContext("eglConfig", currentContext)); + + glGenTextures(1, &m_glTexture); + + + int w = m_surfaceFormat.frameWidth(); + int h = m_surfaceFormat.frameHeight(); + + EGLint attribs[] = { + EGL_WIDTH, w, + EGL_HEIGHT, h, + EGL_TEXTURE_FORMAT, EGL_TEXTURE_RGB, + EGL_TEXTURE_TARGET, EGL_TEXTURE_2D, + EGL_NONE + }; + + EGLSurface pbuffer = eglCreatePbufferSurface(m_eglDisplay, m_eglConfig, attribs); + + HANDLE share_handle = 0; + PFNEGLQUERYSURFACEPOINTERANGLEPROC eglQuerySurfacePointerANGLE = + reinterpret_cast<PFNEGLQUERYSURFACEPOINTERANGLEPROC>(eglGetProcAddress("eglQuerySurfacePointerANGLE")); + eglQuerySurfacePointerANGLE( + m_eglDisplay, + pbuffer, + EGL_D3D_TEXTURE_2D_SHARE_HANDLE_ANGLE, &share_handle); + + + m_device->CreateTexture(w, h, 1, + D3DUSAGE_RENDERTARGET, + D3DFMT_X8R8G8B8, + D3DPOOL_DEFAULT, + &m_texture, + &share_handle); + + m_eglSurface = pbuffer; + + if (m_glContext) + m_glContext->doneCurrent(); +} + +bool D3DPresentEngine::updateTexture(IDirect3DSurface9 *src) +{ + if (!m_texture) + return false; + + if (m_glContext) + m_glContext->makeCurrent(m_offscreenSurface); + + glBindTexture(GL_TEXTURE_2D, m_glTexture); + + 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"); + + if (hr == S_OK) + eglBindTexImage(m_eglDisplay, m_eglSurface, EGL_BACK_BUFFER); + +done: + qt_wmf_safeRelease(&dest); + + if (m_glContext) + m_glContext->doneCurrent(); + + return SUCCEEDED(hr); +} + +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; + + // Hold the lock because we might be discarding an existing device. + QMutexLocker locker(&m_mutex); + + if (!m_D3D9 || !m_deviceManager) + return MF_E_NOT_INITIALIZED; + + hwnd = ::GetShellWindow(); + + // Note: The presenter creates additional swap chains to present the + // video frames. Therefore, it does not use the device's implicit + // swap chain, so the size of the back buffer here is 1 x 1. + + 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_wmf_safeRelease(&m_device); + + m_device = device; + m_device->AddRef(); + +done: + qt_wmf_safeRelease(&device); + return hr; +} + +HRESULT D3DPresentEngine::createD3DSample(IDirect3DSwapChain9 *swapChain, IMFSample **videoSample) +{ + D3DCOLOR clrBlack = D3DCOLOR_ARGB(0xFF, 0x00, 0x00, 0x00); + + IDirect3DSurface9* surface = NULL; + IMFSample* sample = NULL; + + // Get the back buffer surface. + HRESULT hr = swapChain->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &surface); + if (FAILED(hr)) + goto done; + + // Fill it with black. + hr = m_device->ColorFill(surface, NULL, clrBlack); + if (FAILED(hr)) + goto done; + + hr = MFCreateVideoSampleFromSurface(surface, &sample); + if (FAILED(hr)) + goto done; + + *videoSample = sample; + (*videoSample)->AddRef(); + +done: + qt_wmf_safeRelease(&surface); + qt_wmf_safeRelease(&sample); + return hr; +} + +HRESULT D3DPresentEngine::getSwapChainPresentParameters(IMFMediaType *type, D3DPRESENT_PARAMETERS* pp) +{ + ZeroMemory(pp, sizeof(D3DPRESENT_PARAMETERS)); + + // Get some information about the video format. + + UINT32 width = 0, height = 0; + + HRESULT hr = MFGetAttributeSize(type, MF_MT_FRAME_SIZE, &width, &height); + if (FAILED(hr)) + return hr; + + DWORD d3dFormat = 0; + + hr = qt_wmf_getFourCC(type, &d3dFormat); + if (FAILED(hr)) + return hr; + + ZeroMemory(pp, sizeof(D3DPRESENT_PARAMETERS)); + pp->BackBufferWidth = width; + pp->BackBufferHeight = height; + pp->Windowed = TRUE; + pp->SwapEffect = D3DSWAPEFFECT_DISCARD; + pp->BackBufferFormat = (D3DFORMAT)d3dFormat; + pp->hDeviceWindow = ::GetShellWindow(); + pp->Flags = D3DPRESENTFLAG_VIDEO; + pp->PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; + + D3DDEVICE_CREATION_PARAMETERS params; + hr = m_device->GetCreationParameters(¶ms); + if (FAILED(hr)) + return hr; + + if (params.DeviceType != D3DDEVTYPE_HAL) + pp->Flags |= D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; + + return S_OK; +} |