/****************************************************************************** QtAV: Multimedia framework based on Qt and FFmpeg Copyright (C) 2012-2016 Wang Bin * This file is part of QtAV This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ******************************************************************************/ #include "QtAV/VideoRenderer.h" #include #include #include #include "QtAV/private/VideoRenderer_p.h" #include "QtAV/private/factory.h" //#define CINTERFACE //http://rxlib.ru/faqs/faqc_en/15596.html #include #ifndef _Out_writes_bytes_opt_ #define _Out_writes_bytes_opt_(s) #endif #include //IID_ID2D1Factory #include //steps: http://msdn.microsoft.com/zh-cn/library/dd317121(v=vs.85).aspx //performance: http://msdn.microsoft.com/en-us/library/windows/desktop/dd372260(v=vs.85).aspx //vlc is helpful //layer(opacity): http://www.cnblogs.com/graphics/archive/2013/04/15/2781969.html namespace QtAV { template inline void SafeRelease(Interface **ppInterfaceToRelease) { if (*ppInterfaceToRelease != NULL){ (*ppInterfaceToRelease)->Release(); (*ppInterfaceToRelease) = NULL; } } class Direct2DRendererPrivate; class Direct2DRenderer : public QWidget, public VideoRenderer { Q_OBJECT DPTR_DECLARE_PRIVATE(Direct2DRenderer) public: Direct2DRenderer(QWidget* parent = 0, Qt::WindowFlags f = 0); VideoRendererId id() const Q_DECL_OVERRIDE; bool isSupported(VideoFormat::PixelFormat pixfmt) const Q_DECL_OVERRIDE; /* WA_PaintOnScreen: To render outside of Qt's paint system, e.g. If you require * native painting primitives, you need to reimplement QWidget::paintEngine() to * return 0 and set this flag */ QPaintEngine* paintEngine() const Q_DECL_OVERRIDE; QWidget* widget() Q_DECL_OVERRIDE { return this; } protected: bool receiveFrame(const VideoFrame& frame) Q_DECL_OVERRIDE; void drawBackground() Q_DECL_OVERRIDE; void drawFrame() Q_DECL_OVERRIDE; /*usually you don't need to reimplement paintEvent, just drawXXX() is ok. unless you want do all *things yourself totally*/ void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE; void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; //stay on top will change parent, hide then show(windows) void showEvent(QShowEvent *) Q_DECL_OVERRIDE; }; typedef Direct2DRenderer VideoRendererDirect2D; extern VideoRendererId VideoRendererId_Direct2D; #if 0 FACTORY_REGISTER_ID_AUTO(VideoRenderer, Direct2D, "Direct2D") #else void RegisterVideoRendererDirect2D_Man() { VideoRenderer::Register(VideoRendererId_Direct2D, "Direct2D"); } #endif VideoRendererId Direct2DRenderer::id() const { return VideoRendererId_Direct2D; } class Direct2DRendererPrivate : public VideoRendererPrivate { public: DPTR_DECLARE_PUBLIC(Direct2DRenderer) Direct2DRendererPrivate(): VideoRendererPrivate() , d2d_factory(0) , render_target(0) , bitmap(0) , bitmap_width(0) , bitmap_height(0) , interpolation(D2D1_BITMAP_INTERPOLATION_MODE_LINEAR) { dll.setFileName(QStringLiteral("d2d1")); if (!dll.load()) { available = false; qWarning("Direct2D is disabled. Failed to load 'd2d1.dll': %s", dll.errorString().toUtf8().constData()); return; } typedef HRESULT (WINAPI *D2D1CreateFactory_t)(D2D1_FACTORY_TYPE, REFIID, const D2D1_FACTORY_OPTIONS *, void **ppIFactory); D2D1CreateFactory_t D2D1CreateFactory; D2D1CreateFactory = (D2D1CreateFactory_t)dll.resolve("D2D1CreateFactory"); if (!D2D1CreateFactory) { available = false; qWarning("Direct2D is disabled. Failed to resolve symble 'D2D1CreateFactory': %s", dll.errorString().toUtf8().constData()); return; } D2D1_FACTORY_OPTIONS factory_opt = { D2D1_DEBUG_LEVEL_NONE }; /* * d2d is accessed by AVThread and GUI thread, so we use D2D1_FACTORY_TYPE_MULTI_THREADED * and let d2d to deal with the thread safe problems. otherwise, if we use * D2D1_FACTORY_TYPE_SINGLE_THREADED, we must use lock when copying ID2D1Bitmap and calling EndDraw. */ /// http://msdn.microsoft.com/en-us/library/windows/desktop/dd368104%28v=vs.85%29.aspx HRESULT hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_MULTI_THREADED , (REFIID)IID_ID2D1Factory , &factory_opt , (void**)&d2d_factory); if (FAILED(hr)) { available = false; qWarning("Direct2D is disabled. Create d2d factory failed"); return; } FLOAT dpiX, dpiY; d2d_factory->GetDesktopDpi(&dpiX, &dpiY); //gcc: extended initializer lists only available with -std=c++11 or -std=gnu++11 //vc: http://msdn.microsoft.com/zh-cn/library/t8xe60cf(v=vs.80).aspx /*pixel_format = { DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_IGNORE };*/ pixel_format.format = DXGI_FORMAT_B8G8R8A8_UNORM; pixel_format.alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;//D2D1_ALPHA_MODE_IGNORE; /*bitmap_properties = { pixel_format, dpiX, dpiY };*/ bitmap_properties.pixelFormat = pixel_format; bitmap_properties.dpiX = dpiX; bitmap_properties.dpiY = dpiY; } ~Direct2DRendererPrivate() { destroyDeviceResource(); SafeRelease(&d2d_factory);//vlc does not call this. why? bug? dll.unload(); } bool createDeviceResource() { DPTR_P(Direct2DRenderer); update_background = true; destroyDeviceResource(); // // This method creates resources which are bound to a particular // Direct3D device. It's all centralized here, in case the resources // need to be recreated in case of Direct3D device loss (eg. display // change, remoting, removal of video card, etc). // //TODO: move to prepare(), or private. how to call less times D2D1_RENDER_TARGET_PROPERTIES rtp = { D2D1_RENDER_TARGET_TYPE_DEFAULT, pixel_format, 0, 0, D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT }; D2D1_SIZE_U size = { (UINT32)p.width(), (UINT32)p.height() };//d.renderer_width, d.renderer_height? // Create a Direct2D render target. D2D1_HWND_RENDER_TARGET_PROPERTIES hwnd_rtp = { (HWND)p.winId(), size, //TODO: what do these mean? D2D1_PRESENT_OPTIONS_RETAIN_CONTENTS //D2D1_PRESENT_OPTIONS_IMMEDIATELY /* this might need fiddling */ }; HRESULT hr = d2d_factory->CreateHwndRenderTarget(&rtp//D2D1::RenderTargetProperties() //TODO: vlc set properties , &hwnd_rtp//D2D1::HwndRenderTargetProperties(, size) , &render_target); if (FAILED(hr)) { qWarning("Direct2D is disabled. CreateHwndRenderTarget() failed: %d", (int)GetLastError()); render_target = 0; return false; } SafeRelease(&bitmap); prepareBitmap(src_width, src_height); //bitmap depends on render target return hr == S_OK; } void destroyDeviceResource() { SafeRelease(&render_target); SafeRelease(&bitmap); } void recreateDeviceResource() { qDebug("D2DERR_RECREATE_TARGET"); QMutexLocker locker(&img_mutex); Q_UNUSED(locker); update_background = true; destroyDeviceResource(); createDeviceResource(); } //create an empty bitmap with given size. if size is equal as current and bitmap already exists, do nothing bool prepareBitmap(int w, int h) { if (w == bitmap_width && h == bitmap_height && bitmap) return true; if (!render_target) { qWarning("No render target, bitmap will not be created!!!"); return false; } bitmap_width = w; bitmap_height = h; qDebug("Resize bitmap to %d x %d", w, h); SafeRelease(&bitmap); if (w ==0 || h == 0) return true; D2D1_SIZE_U s = {(UINT32)w, (UINT32)h}; HRESULT hr = render_target->CreateBitmap(s , NULL , 0 , &bitmap_properties , &bitmap); if (FAILED(hr)) { qWarning("Failed to create ID2D1Bitmap (%ld)", hr); SafeRelease(&bitmap); SafeRelease(&render_target); return false; } return true; } //it seems that only D2D1_BITMAP_INTERPOLATION_MODE(used in DrawBitmap) matters when drawing an image void setupQuality() { switch (quality) { case VideoRenderer::QualityFastest: interpolation = D2D1_BITMAP_INTERPOLATION_MODE_NEAREST_NEIGHBOR; render_target->SetAntialiasMode(D2D1_ANTIALIAS_MODE_ALIASED); render_target->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_ALIASED); break; case VideoRenderer::QualityBest: interpolation = D2D1_BITMAP_INTERPOLATION_MODE_LINEAR; render_target->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); render_target->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE); break; default: interpolation = D2D1_BITMAP_INTERPOLATION_MODE_LINEAR; render_target->SetAntialiasMode(D2D1_ANTIALIAS_MODE_PER_PRIMITIVE); render_target->SetTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_DEFAULT); break; } } ID2D1Factory *d2d_factory; ID2D1HwndRenderTarget *render_target; D2D1_PIXEL_FORMAT pixel_format; D2D1_BITMAP_PROPERTIES bitmap_properties; ID2D1Bitmap *bitmap; int bitmap_width, bitmap_height; //can not use src_width, src height because bitmap not update when they changes D2D1_BITMAP_INTERPOLATION_MODE interpolation; QLibrary dll; }; Direct2DRenderer::Direct2DRenderer(QWidget *parent, Qt::WindowFlags f): QWidget(parent, f),VideoRenderer(*new Direct2DRendererPrivate()) { DPTR_INIT_PRIVATE(Direct2DRenderer); setAcceptDrops(true); setFocusPolicy(Qt::StrongFocus); /* To rapidly update custom widgets that constantly paint over their entire areas with * opaque content, e.g., video streaming widgets, it is better to set the widget's * Qt::WA_OpaquePaintEvent, avoiding any unnecessary overhead associated with repainting the * widget's background */ setAttribute(Qt::WA_OpaquePaintEvent); //setAttribute(Qt::WA_NoSystemBackground); setAutoFillBackground(false); setAttribute(Qt::WA_PaintOnScreen, true); } bool Direct2DRenderer::isSupported(VideoFormat::PixelFormat pixfmt) const { return VideoFormat::isRGB(pixfmt); } bool Direct2DRenderer::receiveFrame(const VideoFrame& frame) { DPTR_D(Direct2DRenderer); if (!frame.isValid()) { //d.prepareBitmap(0, 0); return false; } if (!d.prepareBitmap(frame.width(), frame.height())) return false; HRESULT hr = S_OK; //if d2d factory is D2D1_FACTORY_TYPE_SINGLE_THREADED, we need to lock //already locked if (frame.constBits(0)) d.video_frame = frame; else d.video_frame = frame.to(frame.pixelFormat()); //TODO: if CopyFromMemory() is deep copy, mutex can be avoided /*if lock is required, do not use locker in if() scope, it will unlock outside the scope*/ //TODO: d2d often crash, should we always lock? How about other renderer? hr = d.bitmap->CopyFromMemory(NULL //&D2D1::RectU(0, 0, image.width(), image.height()) /*&dstRect, NULL?*/, , frame.constBits(0) //data.constData() //msdn: const void* , frame.bytesPerLine(0)); if (hr != S_OK) { qWarning("Failed to copy from memory to bitmap (%ld)", hr); } updateUi(); return true; } QPaintEngine* Direct2DRenderer::paintEngine() const { return 0; //use native engine } void Direct2DRenderer::drawBackground() { const QRegion bgRegion(backgroundRegion()); if (bgRegion.isEmpty()) return; DPTR_D(Direct2DRenderer); const QColor bc(backgroundColor()); D2D1_COLOR_F c = {(float)bc.red(), (float)bc.green(), (float)bc.blue(), (float)bc.alpha()}; d.render_target->Clear(&c); //const D2D1_COlOR_F&? //http://msdn.microsoft.com/en-us/library/windows/desktop/dd535473(v=vs.85).aspx //ID2D1SolidColorBrush *brush; //d.render_target->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &brush); //d.render_target->FillRectangle(D2D1::RectF(0, 0, width(), height()), brush); } void Direct2DRenderer::drawFrame() { DPTR_D(Direct2DRenderer); if (!d.bitmap || d.out_rect.isEmpty()) return; D2D1_RECT_F out_rect = { (FLOAT)d.out_rect.x(), (FLOAT)d.out_rect.y(), (FLOAT)(d.out_rect.x() + d.out_rect.width()), // QRect.right() is x+width-1 for historical reason (FLOAT)(d.out_rect.y() + d.out_rect.height()) }; QRect roi = realROI(); D2D1_RECT_F roi_d2d = { (FLOAT)roi.x(), (FLOAT)roi.y(), (FLOAT)(roi.x() + roi.width()), (FLOAT)(roi.y() + roi.height()) }; //d.render_target->SetTransform d.render_target->DrawBitmap(d.bitmap , &out_rect , 1 //opacity , d.interpolation , &roi_d2d); } void Direct2DRenderer::paintEvent(QPaintEvent *) { DPTR_D(Direct2DRenderer); if (!d.render_target) { qWarning("No render target!!!"); return; } //http://www.daimakuai.net/?page_id=1574 d.render_target->BeginDraw(); handlePaintEvent(); HRESULT hr = S_OK; { //if d2d factory is D2D1_FACTORY_TYPE_SINGLE_THREADED, we need to lock //QMutexLocker locker(&d.img_mutex); //Q_UNUSED(locker); hr = d.render_target->EndDraw(NULL, NULL); //TODO: why it need lock? otherwise crash } if (hr == D2DERR_RECREATE_TARGET) { d.recreateDeviceResource(); } } void Direct2DRenderer::resizeEvent(QResizeEvent *e) { resizeRenderer(e->size()); DPTR_D(Direct2DRenderer); d.update_background = true; if (d.render_target) { D2D1_SIZE_U size = { (UINT32)e->size().width(), (UINT32)e->size().height() }; // Note: This method can fail, but it's okay to ignore the // error here -- it will be repeated on the next call to // EndDraw. d.render_target->Resize(&size); //D2D1_SIZE_U&? } update(); } void Direct2DRenderer::showEvent(QShowEvent *) { DPTR_D(Direct2DRenderer); d.recreateDeviceResource(); } } //namespace QtAV #include "Direct2DRenderer.moc"