You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
475 lines
16 KiB
475 lines
16 KiB
/*****************************************************************************
|
|
* Copyright (C) 2020 VLC authors and VideoLAN
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
|
|
*****************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include "compositor_dcomp.hpp"
|
|
|
|
#include "maininterface/mainctx_win32.hpp"
|
|
|
|
#include <vlc_window.h>
|
|
|
|
#ifndef QT_GUI_PRIVATE
|
|
#warning "QRhiD3D11 and QRhi headers are required for DirectComposition compositor."
|
|
#endif
|
|
|
|
#include <QtGui/qpa/qplatformnativeinterface.h>
|
|
|
|
#if __has_include(<d3d11_1.h>)
|
|
#define QRhiD3D11_ACTIVE
|
|
#include <QtGui/private/qrhid3d11_p.h>
|
|
#endif
|
|
|
|
#if __has_include(<d3d12.h>) && __has_include(<d3d12sdklayers.h>)
|
|
#include <QtGui/private/qrhid3d12_p.h>
|
|
#if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0)) || defined(QRHI_D3D12_AVAILABLE)
|
|
#define QRhiD3D12_ACTIVE
|
|
#endif
|
|
#endif
|
|
|
|
#if !defined(QRhiD3D11_ACTIVE) && !defined(QRhiD3D12_ACTIVE)
|
|
#warning "Neither D3D11 nor D3D12 headers are available. compositor_dcomp will not work."
|
|
#endif
|
|
|
|
#include "compositor_dcomp_acrylicsurface.hpp"
|
|
#include "maininterface/interface_window_handler.hpp"
|
|
|
|
#include <memory>
|
|
#include <type_traits>
|
|
|
|
namespace vlc {
|
|
|
|
int CompositorDirectComposition::windowEnable(const vlc_window_cfg_t *)
|
|
{
|
|
assert(m_dcompDevice);
|
|
assert(m_rootVisual);
|
|
assert(m_videoVisual);
|
|
assert(m_uiVisual);
|
|
|
|
commonWindowEnable();
|
|
|
|
const auto ret = m_rootVisual->AddVisual(m_videoVisual.Get(), FALSE, m_uiVisual);
|
|
m_dcompDevice->Commit();
|
|
|
|
if (ret == S_OK)
|
|
return VLC_SUCCESS;
|
|
else
|
|
return VLC_EGENERIC;
|
|
}
|
|
|
|
void CompositorDirectComposition::windowDisable()
|
|
{
|
|
assert(m_dcompDevice);
|
|
assert(m_rootVisual);
|
|
assert(m_videoVisual);
|
|
|
|
commonWindowDisable();
|
|
|
|
const auto ret = m_rootVisual->RemoveVisual(m_videoVisual.Get());
|
|
assert(ret == S_OK);
|
|
m_dcompDevice->Commit();
|
|
}
|
|
|
|
void CompositorDirectComposition::windowDestroy()
|
|
{
|
|
m_videoVisual.Reset();
|
|
CompositorVideo::windowDestroy();
|
|
}
|
|
|
|
CompositorDirectComposition::CompositorDirectComposition( qt_intf_t* p_intf, QObject *parent)
|
|
: CompositorVideo(p_intf, parent)
|
|
{
|
|
}
|
|
|
|
CompositorDirectComposition::~CompositorDirectComposition()
|
|
{
|
|
//m_acrylicSurface should be released before the RHI context is destroyed
|
|
assert(!m_acrylicSurface);
|
|
}
|
|
|
|
bool CompositorDirectComposition::init()
|
|
{
|
|
#if !defined(QRhiD3D11_ACTIVE) && !defined(QRhiD3D12_ACTIVE)
|
|
msg_Warn(m_intf, "compositor_dcomp was not built with D3D11 or D3D12 headers. It will not work.");
|
|
return false;
|
|
#endif
|
|
|
|
{
|
|
const QString& platformName = qApp->platformName();
|
|
if (!(platformName == QLatin1String("windows") || platformName == QLatin1String("direct2d")))
|
|
return false;
|
|
}
|
|
|
|
std::unique_ptr<std::remove_pointer_t<HMODULE>, BOOL WINAPI (*)(HMODULE)>
|
|
dcomplib(::LoadLibraryEx(TEXT("dcomp.dll"), nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32), ::FreeLibrary);
|
|
|
|
typedef HRESULT (__stdcall *DCompositionCreateDeviceFuncPtr)(
|
|
_In_opt_ IDXGIDevice *dxgiDevice,
|
|
_In_ REFIID iid,
|
|
_Outptr_ void **dcompositionDevice);
|
|
DCompositionCreateDeviceFuncPtr func = nullptr;
|
|
if (dcomplib)
|
|
{
|
|
func = reinterpret_cast<DCompositionCreateDeviceFuncPtr>(
|
|
GetProcAddress(dcomplib.get(), "DCompositionCreateDevice"));
|
|
}
|
|
|
|
Microsoft::WRL::ComPtr<IDCompositionDevice> device;
|
|
if (!func || FAILED(func(nullptr, IID_PPV_ARGS(&device))))
|
|
{
|
|
msg_Warn(m_intf, "Can not create DCompositionDevice. CompositorDirectComposition will not work.");
|
|
return false;
|
|
}
|
|
|
|
const QString& sceneGraphBackend = qEnvironmentVariable("QT_QUICK_BACKEND");
|
|
if (!sceneGraphBackend.isEmpty() /* if empty, RHI is used */ &&
|
|
sceneGraphBackend != QLatin1String("rhi"))
|
|
{
|
|
// No RHI means no D3D11 or D3D12, the graphics API check
|
|
// below is only relevant when RHI is in use.
|
|
// If QT_QUICK_BACKEND is set to software or openvg, then
|
|
// `QQuickWindow::graphicsApi()` might still report D3D11 or
|
|
// D3D12 until the scene graph is initialized.
|
|
// Unlike `QQuickWindow::graphicsApi()`, `sceneGraphBackend()`
|
|
// is only valid after the window is constructed, so instead
|
|
// of using `QQuickWindow::sceneGraphBackend()`, simply probe
|
|
// the environment variable.
|
|
return false;
|
|
}
|
|
|
|
const auto graphicsApi = QQuickWindow::graphicsApi();
|
|
if (graphicsApi != QSGRendererInterface::Direct3D11 &&
|
|
graphicsApi != QSGRendererInterface::Direct3D12)
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void CompositorDirectComposition::setup()
|
|
{
|
|
assert(m_quickView);
|
|
const auto rhi = m_quickView->rhi();
|
|
assert(rhi);
|
|
|
|
QRhiImplementation* const rhiImplementation = rhi->implementation();
|
|
assert(rhiImplementation);
|
|
QRhiSwapChain* const rhiSwapChain = m_quickView->swapChain();
|
|
assert(rhiSwapChain);
|
|
|
|
assert(m_quickView->rhi()->backend() == QRhi::D3D11 || m_quickView->rhi()->backend() == QRhi::D3D12);
|
|
|
|
IDCompositionTarget* dcompTarget;
|
|
if (rhi->backend() == QRhi::D3D11)
|
|
{
|
|
#ifdef QRhiD3D11_ACTIVE
|
|
m_dcompDevice = static_cast<QRhiD3D11*>(rhiImplementation)->dcompDevice;
|
|
auto qswapchain = static_cast<QD3D11SwapChain*>(rhiSwapChain);
|
|
dcompTarget = qswapchain->dcompTarget;
|
|
m_uiVisual = qswapchain->dcompVisual;
|
|
#endif
|
|
}
|
|
else if (rhi->backend() == QRhi::D3D12)
|
|
{
|
|
#ifdef QRhiD3D12_ACTIVE
|
|
m_dcompDevice = static_cast<QRhiD3D12*>(rhiImplementation)->dcompDevice;
|
|
auto qswapchain = static_cast<QD3D12SwapChain*>(rhiSwapChain);
|
|
dcompTarget = qswapchain->dcompTarget;
|
|
m_uiVisual = qswapchain->dcompVisual;
|
|
#endif
|
|
}
|
|
else
|
|
Q_UNREACHABLE();
|
|
|
|
assert(m_dcompDevice);
|
|
assert(dcompTarget);
|
|
assert(m_uiVisual);
|
|
|
|
HRESULT res;
|
|
res = m_dcompDevice->CreateVisual(&m_rootVisual);
|
|
assert(res == S_OK);
|
|
|
|
res = dcompTarget->SetRoot(m_rootVisual.Get());
|
|
assert(res == S_OK);
|
|
|
|
res = m_rootVisual->AddVisual(m_uiVisual, FALSE, NULL);
|
|
assert(res == S_OK);
|
|
|
|
m_dcompDevice->Commit();
|
|
|
|
if (!m_mainCtx->hasAcrylicSurface())
|
|
{
|
|
if (var_InheritBool(m_intf, "qt-backdrop-blur"))
|
|
{
|
|
try
|
|
{
|
|
m_acrylicSurface = std::make_unique<CompositorDCompositionAcrylicSurface>(m_intf, this, m_mainCtx, m_dcompDevice);
|
|
}
|
|
catch (const std::exception& exception)
|
|
{
|
|
if (const auto what = exception.what())
|
|
msg_Warn(m_intf, "%s", what);
|
|
}
|
|
}
|
|
}
|
|
|
|
{
|
|
QMutexLocker lock(&m_setupStateLock);
|
|
m_setupState = SetupState::Success;
|
|
m_setupStateCond.notify_all();
|
|
}
|
|
}
|
|
|
|
bool CompositorDirectComposition::makeMainInterface(MainCtx* mainCtx, std::function<void(QQuickWindow*)> aboutToShowQuickWindowCallback)
|
|
{
|
|
assert(mainCtx);
|
|
m_mainCtx = mainCtx;
|
|
|
|
m_quickView = std::make_unique<QQuickView>();
|
|
const auto quickViewPtr = m_quickView.get();
|
|
|
|
m_quickView->setResizeMode(QQuickView::SizeRootObjectToView);
|
|
m_quickView->setColor(Qt::transparent);
|
|
|
|
m_quickView->installEventFilter(this);
|
|
|
|
connect(quickViewPtr,
|
|
&QQuickWindow::frameSwapped, // At this stage, we can be sure that QRhi and QRhiSwapChain are valid.
|
|
this,
|
|
&CompositorDirectComposition::setup,
|
|
Qt::SingleShotConnection);
|
|
|
|
{
|
|
// Express the desire to use WS_EX_NOREDIRECTIONBITMAP, it has the following advantages:
|
|
// - Increased performance due to not having to copy buffers (within GPU, or inter GPU-CPU unlike WS_EX_LAYERED).
|
|
// - Win32 window background brush is invalidated, so window does not flash white when opened.
|
|
// - No more resize artifact until the opaque scene covers the new area when the window is resized.
|
|
// - Make it possible to use the Windows 11 22H2 native acrylic backdrop effect, because the white background does not remain as an artifact.
|
|
// When the window has a frame (currently it is the case for both SSD and CSD), clear color does not clear that background, which means
|
|
// that the window should not be exposed (transparent UI). Currently there is either the acrylic simulation visual in the background which is
|
|
// completely opaque or the video visual or the UI visual does not get transparent, so we don't suffer from this issue. With `WS_EX_NOREDIRECTIONBITMAP`,
|
|
// the background does not paint a rectangle with `WNDCLASSEX::hbrBackground`, even if `WS_EX_LAYERED` is not used, so we are fine. The scene graph
|
|
// does not have anything in the window to clear, so clear color can be transparent which means that the UI can be transparent and expose the window
|
|
// for the window to provide the (native) backdrop acrylic effect.
|
|
|
|
// WS_EX_NOREDIRECTIONBITMAP is only compatible with the flip swapchain model and D3D backend.
|
|
// In CompositorDirectComposition::init(), we already ensure that D3D is used (no OpenGL or Vulkan).
|
|
// Flip model is supported since Windows 8 and Qt does not use it only if the following environment
|
|
// variable is set. Since it targets Windows 10+, it does *not* check if flip model is supported and
|
|
// use the legacy swapchain model. For D3D12, flip model is always used.
|
|
const bool legacySwapchainModelIsExplicitlyWanted = qEnvironmentVariableIntValue("QT_D3D_NO_FLIP");
|
|
|
|
const char* const envDisableRedirectionSurface = "QT_QPA_DISABLE_REDIRECTION_SURFACE";
|
|
const bool redirectionSurfaceIsExplicitlyWanted = !qEnvironmentVariableIsEmpty(envDisableRedirectionSurface) && !qEnvironmentVariableIntValue(envDisableRedirectionSurface);
|
|
|
|
if (!legacySwapchainModelIsExplicitlyWanted && !redirectionSurfaceIsExplicitlyWanted)
|
|
qputenv(envDisableRedirectionSurface, "1"); // TODO: Other QQuickWindow (toolbar editor, independent popups)
|
|
|
|
m_quickView->create();
|
|
|
|
if (!legacySwapchainModelIsExplicitlyWanted && !redirectionSurfaceIsExplicitlyWanted)
|
|
qunsetenv(envDisableRedirectionSurface); // NOTE: We need to disable it, otherwise regular QWidget windows would have issues
|
|
}
|
|
|
|
const bool ret = commonGUICreate(quickViewPtr, quickViewPtr, CompositorVideo::CAN_SHOW_PIP | CompositorVideo::HAS_ACRYLIC);
|
|
|
|
if (!ret)
|
|
return false;
|
|
|
|
connect(quickViewPtr,
|
|
&QQuickWindow::sceneGraphError,
|
|
this,
|
|
[this](QQuickWindow::SceneGraphError error, const QString &message) {
|
|
qWarning() << "CompositorDComp: Scene Graph Error: " << error << ", Message: " << message;
|
|
QMutexLocker lock(&m_setupStateLock);
|
|
m_setupState = SetupState::Fail;
|
|
m_setupStateCond.notify_all();
|
|
}, static_cast<Qt::ConnectionType>(Qt::SingleShotConnection | Qt::DirectConnection));
|
|
|
|
// Qt "terminates the application" by default if there is no connection made to the signal
|
|
// QQuickWindow::sceneGraphError(). We need to do the same, because by the time the error
|
|
// is reported, it will likely be too late (`makeMainInterface()` already returned true,
|
|
// which is the latest point recovery is still possible) to recover from that error and
|
|
// the interface will remain unfunctional. It was proposed to wait here until the scene
|
|
// graph is done, but that was not changed in order not to slow down the application
|
|
// start up.
|
|
connect(quickViewPtr,
|
|
&QQuickWindow::sceneGraphError,
|
|
m_mainCtx,
|
|
&MainCtx::askToQuit);
|
|
|
|
if (aboutToShowQuickWindowCallback)
|
|
aboutToShowQuickWindowCallback(quickViewPtr);
|
|
|
|
m_quickView->setVisible(true);
|
|
return true;
|
|
}
|
|
|
|
void CompositorDirectComposition::onSurfacePositionChanged(const QPointF& position)
|
|
{
|
|
assert(m_videoVisual);
|
|
assert(m_dcompDevice);
|
|
|
|
m_videoVisual->SetOffsetX(position.x());
|
|
m_videoVisual->SetOffsetY(position.y());
|
|
m_dcompDevice->Commit();
|
|
}
|
|
|
|
void CompositorDirectComposition::onSurfaceSizeChanged(const QSizeF&)
|
|
{
|
|
//N/A
|
|
}
|
|
|
|
void CompositorDirectComposition::destroyMainInterface()
|
|
{
|
|
if (m_videoVisual)
|
|
msg_Err(m_intf, "video surface still active while destroying main interface");
|
|
|
|
commonIntfDestroy();
|
|
m_quickView.reset();
|
|
}
|
|
|
|
void CompositorDirectComposition::unloadGUI()
|
|
{
|
|
m_acrylicSurface.reset();
|
|
m_interfaceWindowHandler.reset();
|
|
|
|
//at this point we need to unload the QML content but the window still need to
|
|
//be valid as it may still be used by the vout window.
|
|
//we cant' just delete the qmlEngine as the QmlView as the root item is parented to the QmlView
|
|
//setSource() to nothing will effectively destroy the root item
|
|
if (Q_LIKELY(m_quickView))
|
|
m_quickView->setSource(QUrl());
|
|
|
|
commonGUIDestroy();
|
|
}
|
|
|
|
bool CompositorDirectComposition::setupVoutWindow(vlc_window_t *p_wnd, VoutDestroyCb destroyCb)
|
|
{
|
|
if (m_wnd)
|
|
return false;
|
|
|
|
{
|
|
QMutexLocker lock(&m_setupStateLock);
|
|
while (m_setupState == SetupState::Uninitialized)
|
|
{
|
|
const bool ret = m_setupStateCond.wait(&m_setupStateLock);
|
|
if (!ret)
|
|
return false;
|
|
}
|
|
if (m_setupState != SetupState::Success)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
assert(m_dcompDevice);
|
|
|
|
const HRESULT hr = m_dcompDevice->CreateVisual(&m_videoVisual);
|
|
|
|
if (FAILED(hr))
|
|
{
|
|
msg_Err(p_wnd, "create to create DComp video visual");
|
|
return false;
|
|
}
|
|
|
|
commonSetupVoutWindow(p_wnd, destroyCb);
|
|
p_wnd->type = VLC_WINDOW_TYPE_DCOMP;
|
|
|
|
p_wnd->display.dcomp_device = m_dcompDevice;
|
|
p_wnd->handle.dcomp_visual = m_videoVisual.Get();
|
|
|
|
return true;
|
|
}
|
|
|
|
QWindow *CompositorDirectComposition::interfaceMainWindow() const
|
|
{
|
|
return m_quickView.get();
|
|
}
|
|
|
|
Compositor::Type CompositorDirectComposition::type() const
|
|
{
|
|
return Compositor::DirectCompositionCompositor;
|
|
}
|
|
|
|
void CompositorDirectComposition::addVisual(IDCompositionVisual *visual)
|
|
{
|
|
assert(visual);
|
|
assert(m_rootVisual);
|
|
assert(m_dcompDevice);
|
|
|
|
HRESULT hr = m_rootVisual->AddVisual(visual, TRUE, NULL);
|
|
if (FAILED(hr))
|
|
msg_Err(m_intf, "failed to add visual, code: 0x%lX", hr);
|
|
|
|
m_dcompDevice->Commit();
|
|
}
|
|
|
|
void CompositorDirectComposition::removeVisual(IDCompositionVisual *visual)
|
|
{
|
|
assert(visual);
|
|
assert(m_rootVisual);
|
|
assert(m_dcompDevice);
|
|
|
|
auto hr = m_rootVisual->RemoveVisual(visual);
|
|
if (FAILED(hr))
|
|
msg_Err(m_intf, "failed to remove visual, code: 0x%lX", hr);
|
|
|
|
m_dcompDevice->Commit();
|
|
}
|
|
|
|
QQuickItem * CompositorDirectComposition::activeFocusItem() const /* override */
|
|
{
|
|
return m_quickView->activeFocusItem();
|
|
}
|
|
|
|
bool CompositorDirectComposition::eventFilter(QObject *watched, QEvent *event)
|
|
{
|
|
switch (event->type())
|
|
{
|
|
case QEvent::PlatformSurface:
|
|
if (watched == m_quickView.get() &&
|
|
static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed)
|
|
{
|
|
m_videoVisual.Reset();
|
|
m_acrylicSurface.reset();
|
|
// Just in case root visual deletes its children
|
|
// when it is deleted: (Qt's UI visual should be
|
|
// deleted by Qt itself)
|
|
m_rootVisual->RemoveVisual(m_uiVisual);
|
|
m_rootVisual.Reset();
|
|
|
|
// When the window receives the event `SurfaceAboutToBeDestroyed`,
|
|
// the RHI and the RHI swap chain are going to be destroyed.
|
|
// It should be noted that event filters receive events
|
|
// before the watched object receives them.
|
|
// Since these objects belong to Qt, we should clear them
|
|
// in order to prevent potential dangling pointer dereference:
|
|
m_dcompDevice = nullptr;
|
|
m_uiVisual = nullptr;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return QObject::eventFilter(watched, event);
|
|
}
|
|
|
|
}
|
|
|