feat: add vaapi path for video wallpapers on generic gpus
C++ / fmt (pull_request) Failing after 6s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 11s
JS/TS / fmt (pull_request) Successful in 10s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Failing after 32s
Rust / build (pull_request) Successful in 52s
Python / verify (pull_request) Failing after 1m23s
Rust / fmt (pull_request) Successful in 51s
Rust / clippy (pull_request) Successful in 1m2s
C++ / fmt (pull_request) Failing after 6s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 11s
JS/TS / fmt (pull_request) Successful in 10s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Failing after 32s
Rust / build (pull_request) Successful in 52s
Python / verify (pull_request) Failing after 1m23s
Rust / fmt (pull_request) Successful in 51s
Rust / clippy (pull_request) Successful in 1m2s
This commit is contained in:
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
#omz revision: d2b7f557cceaddb16ba89bbf8200d8a8df35c4d8
|
||||||
|
#omz fpath: /home/zach/.oh-my-zsh/plugins/colored-man-pages /home/zach/.oh-my-zsh/plugins/universalarchive /home/zach/.oh-my-zsh/plugins/tldr /home/zach/.oh-my-zsh/plugins/extract /home/zach/.oh-my-zsh/plugins/copypath /home/zach/.oh-my-zsh/plugins/cp /home/zach/.oh-my-zsh/plugins/conda-env /home/zach/.oh-my-zsh/plugins/command-not-found /home/zach/.oh-my-zsh/plugins/dirhistory /home/zach/.oh-my-zsh/plugins/copybuffer /home/zach/.oh-my-zsh/plugins/copyfile /home/zach/.oh-my-zsh/custom/plugins/fast-syntax-highlighting /home/zach/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting /home/zach/.oh-my-zsh/custom/plugins/zsh-autosuggestions /home/zach/.oh-my-zsh/plugins/archlinux /home/zach/.oh-my-zsh/plugins/web-search /home/zach/.oh-my-zsh/plugins/sudo /home/zach/.oh-my-zsh/plugins/git /home/zach/.oh-my-zsh/functions /home/zach/.oh-my-zsh/completions /home/zach/.oh-my-zsh/custom/functions /home/zach/.oh-my-zsh/custom/completions /home/zach/tools/ripgrep/complete /home/linuxbrew/.linuxbrew/share/zsh/site-functions /home/zach/.oh-my-zsh/plugins/colored-man-pages /home/zach/.oh-my-zsh/plugins/universalarchive /home/zach/.oh-my-zsh/plugins/tldr /home/zach/.oh-my-zsh/plugins/extract /home/zach/.oh-my-zsh/plugins/copypath /home/zach/.oh-my-zsh/plugins/cp /home/zach/.oh-my-zsh/plugins/conda-env /home/zach/.oh-my-zsh/plugins/command-not-found /home/zach/.oh-my-zsh/plugins/dirhistory /home/zach/.oh-my-zsh/plugins/copybuffer /home/zach/.oh-my-zsh/plugins/copyfile /home/zach/.oh-my-zsh/custom/plugins/fast-syntax-highlighting /home/zach/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting /home/zach/.oh-my-zsh/custom/plugins/zsh-autosuggestions /home/zach/.oh-my-zsh/plugins/archlinux /home/zach/.oh-my-zsh/plugins/web-search /home/zach/.oh-my-zsh/plugins/sudo /home/zach/.oh-my-zsh/plugins/git /home/zach/.oh-my-zsh/functions /home/zach/.oh-my-zsh/completions /home/zach/.oh-my-zsh/custom/functions /home/zach/.oh-my-zsh/custom/completions /home/zach/.oh-my-zsh/cache/completions /home/zach/tools/ripgrep/complete /home/linuxbrew/.linuxbrew/share/zsh/site-functions /usr/local/share/zsh/site-functions /usr/share/zsh/site-functions /usr/share/zsh/functions/Calendar /usr/share/zsh/functions/Chpwd /usr/share/zsh/functions/Completion /usr/share/zsh/functions/Completion/Base /usr/share/zsh/functions/Completion/Linux /usr/share/zsh/functions/Completion/Unix /usr/share/zsh/functions/Completion/X /usr/share/zsh/functions/Completion/Zsh /usr/share/zsh/functions/Exceptions /usr/share/zsh/functions/MIME /usr/share/zsh/functions/Math /usr/share/zsh/functions/Misc /usr/share/zsh/functions/Newuser /usr/share/zsh/functions/Prompts /usr/share/zsh/functions/TCP /usr/share/zsh/functions/VCS_Info /usr/share/zsh/functions/VCS_Info/Backends /usr/share/zsh/functions/Zftp /usr/share/zsh/functions/Zle /home/zach/.zfunc
|
||||||
@@ -5,7 +5,7 @@ import Quickshell
|
|||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import QtQuick
|
import QtQuick
|
||||||
import ZShell.Models
|
import ZShell.Models
|
||||||
import ZShell.Internal
|
import ZShell.Wallpaper
|
||||||
import ZShell.Config
|
import ZShell.Config
|
||||||
import qs.Components
|
import qs.Components
|
||||||
import qs.Services
|
import qs.Services
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ pkg_check_modules(MPV REQUIRED IMPORTED_TARGET mpv)
|
|||||||
pkg_check_modules(AVCODEC REQUIRED IMPORTED_TARGET libavcodec)
|
pkg_check_modules(AVCODEC REQUIRED IMPORTED_TARGET libavcodec)
|
||||||
pkg_check_modules(AVFORMAT REQUIRED IMPORTED_TARGET libavformat)
|
pkg_check_modules(AVFORMAT REQUIRED IMPORTED_TARGET libavformat)
|
||||||
pkg_check_modules(AVUTIL REQUIRED IMPORTED_TARGET libavutil)
|
pkg_check_modules(AVUTIL REQUIRED IMPORTED_TARGET libavutil)
|
||||||
|
pkg_check_modules(LIBVA QUIET IMPORTED_TARGET libva)
|
||||||
|
|
||||||
find_package(CUDAToolkit QUIET)
|
find_package(CUDAToolkit QUIET)
|
||||||
pkg_check_modules(Qalculate IMPORTED_TARGET libqalculate REQUIRED)
|
pkg_check_modules(Qalculate IMPORTED_TARGET libqalculate REQUIRED)
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ list(APPEND CMAKE_AUTOMOC_MACRO_NAMES
|
|||||||
add_subdirectory(Settings)
|
add_subdirectory(Settings)
|
||||||
add_subdirectory(Models)
|
add_subdirectory(Models)
|
||||||
add_subdirectory(Internal)
|
add_subdirectory(Internal)
|
||||||
|
add_subdirectory(Wallpaper)
|
||||||
add_subdirectory(Services)
|
add_subdirectory(Services)
|
||||||
add_subdirectory(Components)
|
add_subdirectory(Components)
|
||||||
add_subdirectory(Blobs)
|
add_subdirectory(Blobs)
|
||||||
|
|||||||
@@ -1,8 +1,3 @@
|
|||||||
wl_proto(wlp-layer-shell wlr-layer-shell-unstable-v1 "${CMAKE_CURRENT_SOURCE_DIR}/protocols")
|
|
||||||
wl_proto(wlp-viewporter viewporter "${WAYLAND_PROTOCOLS}/stable/viewporter")
|
|
||||||
wl_proto(wlp-fractional-scale fractional-scale-v1 "${WAYLAND_PROTOCOLS}/staging/fractional-scale")
|
|
||||||
wl_proto(wlp-xdg-shell xdg-shell "${WAYLAND_PROTOCOLS}/stable/xdg-shell")
|
|
||||||
|
|
||||||
qml_module(ZShell-internal
|
qml_module(ZShell-internal
|
||||||
URI ZShell.Internal
|
URI ZShell.Internal
|
||||||
INCLUDE_PREFIX internal
|
INCLUDE_PREFIX internal
|
||||||
@@ -13,16 +8,11 @@ qml_module(ZShell-internal
|
|||||||
circularindicatormanager.hpp circularindicatormanager.cpp
|
circularindicatormanager.hpp circularindicatormanager.cpp
|
||||||
circularbuffer.hpp circularbuffer.cpp
|
circularbuffer.hpp circularbuffer.cpp
|
||||||
arcgauge.hpp arcgauge.cpp
|
arcgauge.hpp arcgauge.cpp
|
||||||
wallpaperimage.hpp wallpaperimage.cpp
|
|
||||||
wallpaperservice.hpp wallpaperservice.cpp
|
|
||||||
wallpapersurface.hpp wallpapersurface.cpp
|
|
||||||
wallpaperobject.hpp wallpaperobject.cpp
|
|
||||||
lidwatcher.hpp lidwatcher.cpp
|
lidwatcher.hpp lidwatcher.cpp
|
||||||
visualizerbars.hpp visualizerbars.cpp
|
visualizerbars.hpp visualizerbars.cpp
|
||||||
linearindicatormanager.hpp linearindicatormanager.cpp
|
linearindicatormanager.hpp linearindicatormanager.cpp
|
||||||
strokecanvasitem.hpp strokecanvasitem.cpp
|
strokecanvasitem.hpp strokecanvasitem.cpp
|
||||||
strokecanvasrenderer.hpp strokecanvasrenderer.cpp
|
strokecanvasrenderer.hpp strokecanvasrenderer.cpp
|
||||||
videoplayer.hpp videoplayer.cpp
|
|
||||||
cursor.hpp
|
cursor.hpp
|
||||||
LIBRARIES
|
LIBRARIES
|
||||||
Qt::Gui
|
Qt::Gui
|
||||||
@@ -34,39 +24,8 @@ qml_module(ZShell-internal
|
|||||||
Qt::DBus
|
Qt::DBus
|
||||||
Qt::CanvasPainter
|
Qt::CanvasPainter
|
||||||
Qt::GuiPrivate
|
Qt::GuiPrivate
|
||||||
wlp-layer-shell
|
|
||||||
wlp-xdg-shell
|
|
||||||
wlp-viewporter
|
|
||||||
wlp-fractional-scale
|
|
||||||
PkgConfig::EGL
|
PkgConfig::EGL
|
||||||
PkgConfig::WAYLAND_EGL
|
PkgConfig::WAYLAND_EGL
|
||||||
PkgConfig::GL
|
PkgConfig::GL
|
||||||
PkgConfig::MPV
|
|
||||||
PkgConfig::AVCODEC
|
|
||||||
PkgConfig::AVFORMAT
|
|
||||||
PkgConfig::AVUTIL
|
|
||||||
ZShell-config
|
ZShell-config
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_resources(ZShell-internal "wallpaper_shaders"
|
|
||||||
PREFIX "/"
|
|
||||||
FILES
|
|
||||||
shaders/wallpaper.vert
|
|
||||||
shaders/wallpaper.frag
|
|
||||||
shaders/wallpaper_video.frag
|
|
||||||
shaders/wallpaper_trans_bowtie.frag
|
|
||||||
shaders/wallpaper_trans_fade.frag
|
|
||||||
shaders/wallpaper_trans_linear_blur.frag
|
|
||||||
shaders/wallpaper_trans_window_slices.frag
|
|
||||||
shaders/wallpaper_trans_wipe_right.frag
|
|
||||||
shaders/wallpaper_trans_wipe_down.frag
|
|
||||||
shaders/wallpaper_trans_tiles_wave.frag
|
|
||||||
shaders/wallpaper_trans_split_slide_in_horizontal.frag
|
|
||||||
shaders/wallpaper_trans_hexagonalize.frag
|
|
||||||
shaders/wallpaper_trans_doom_melt.frag
|
|
||||||
)
|
|
||||||
|
|
||||||
if(CUDAToolkit_FOUND)
|
|
||||||
target_link_libraries(ZShell-internal PRIVATE CUDA::cuda_driver)
|
|
||||||
target_compile_definitions(ZShell-internal PRIVATE ZHAVE_VIDEO=1)
|
|
||||||
endif()
|
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ int DropImporter::importAttachments(
|
|||||||
saveBytes(id, rawImage);
|
saveBytes(id, rawImage);
|
||||||
} else if (const QString src = firstImgSrc(html); !src.isEmpty()) {
|
} else if (const QString src = firstImgSrc(html); !src.isEmpty()) {
|
||||||
if (src.startsWith(QLatin1String("data:"), Qt::CaseInsensitive)) {
|
if (src.startsWith(QLatin1String("data:"), Qt::CaseInsensitive)) {
|
||||||
const int comma = src.indexOf(QLatin1Char(','));
|
const qsizetype comma = src.indexOf(QLatin1Char(','));
|
||||||
if (comma < 0) {
|
if (comma < 0) {
|
||||||
failWith(id, tr("Malformed data URI"));
|
failWith(id, tr("Malformed data URI"));
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
wl_proto(wlp-layer-shell wlr-layer-shell-unstable-v1 "${CMAKE_CURRENT_SOURCE_DIR}/protocols")
|
||||||
|
wl_proto(wlp-viewporter viewporter "${WAYLAND_PROTOCOLS}/stable/viewporter")
|
||||||
|
wl_proto(wlp-fractional-scale fractional-scale-v1 "${WAYLAND_PROTOCOLS}/staging/fractional-scale")
|
||||||
|
wl_proto(wlp-xdg-shell xdg-shell "${WAYLAND_PROTOCOLS}/stable/xdg-shell")
|
||||||
|
|
||||||
|
qml_module(ZShell-Wallpaper
|
||||||
|
URI ZShell.Wallpaper
|
||||||
|
INCLUDE_PREFIX wallpaper
|
||||||
|
SOURCES
|
||||||
|
wallpaperservice.hpp wallpaperservice.cpp
|
||||||
|
wallpaperobject.hpp wallpaperobject.cpp
|
||||||
|
wallpapersurface.hpp wallpapersurface.cpp
|
||||||
|
wallpaperimage.hpp wallpaperimage.cpp
|
||||||
|
wallpapercache.hpp wallpapercache.cpp
|
||||||
|
videotranscoder.hpp videotranscoder.cpp
|
||||||
|
videodecoder.hpp videodecoder.cpp
|
||||||
|
nvidiaplayer.hpp nvidiaplayer.cpp
|
||||||
|
vaapiplayer.hpp vaapiplayer.cpp
|
||||||
|
LIBRARIES
|
||||||
|
Qt::Gui
|
||||||
|
Qt::Quick
|
||||||
|
Qt::Concurrent
|
||||||
|
Qt::Sql
|
||||||
|
Qt::Core
|
||||||
|
Qt::GuiPrivate
|
||||||
|
Qt::WaylandClient
|
||||||
|
Qt::WaylandClientPrivate
|
||||||
|
wlp-layer-shell
|
||||||
|
wlp-xdg-shell
|
||||||
|
wlp-viewporter
|
||||||
|
wlp-fractional-scale
|
||||||
|
PkgConfig::EGL
|
||||||
|
PkgConfig::WAYLAND_EGL
|
||||||
|
PkgConfig::GL
|
||||||
|
PkgConfig::MPV
|
||||||
|
PkgConfig::AVCODEC
|
||||||
|
PkgConfig::AVFORMAT
|
||||||
|
PkgConfig::AVUTIL
|
||||||
|
ZShell-config
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(ZShell-Wallpaper PRIVATE ZHAVE_FFMPEG=1)
|
||||||
|
|
||||||
|
if(LIBVA_FOUND)
|
||||||
|
target_link_libraries(ZShell-Wallpaper PRIVATE PkgConfig::LIBVA)
|
||||||
|
target_compile_definitions(ZShell-Wallpaper PRIVATE ZHAVE_VAAPI=1)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if(CUDAToolkit_FOUND)
|
||||||
|
target_link_libraries(ZShell-Wallpaper PRIVATE CUDA::cuda_driver)
|
||||||
|
target_compile_definitions(ZShell-Wallpaper PRIVATE ZHAVE_VIDEO=1)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
qt_add_resources(ZShell-Wallpaper "wallpaper_shaders"
|
||||||
|
PREFIX "/"
|
||||||
|
FILES
|
||||||
|
shaders/wallpaper.vert
|
||||||
|
shaders/wallpaper.frag
|
||||||
|
shaders/wallpaper_video.frag
|
||||||
|
shaders/wallpaper_trans_bowtie.frag
|
||||||
|
shaders/wallpaper_trans_fade.frag
|
||||||
|
shaders/wallpaper_trans_linear_blur.frag
|
||||||
|
shaders/wallpaper_trans_window_slices.frag
|
||||||
|
shaders/wallpaper_trans_wipe_right.frag
|
||||||
|
shaders/wallpaper_trans_wipe_down.frag
|
||||||
|
shaders/wallpaper_trans_tiles_wave.frag
|
||||||
|
shaders/wallpaper_trans_split_slide_in_horizontal.frag
|
||||||
|
shaders/wallpaper_trans_hexagonalize.frag
|
||||||
|
shaders/wallpaper_trans_doom_melt.frag
|
||||||
|
)
|
||||||
+69
-51
@@ -1,7 +1,7 @@
|
|||||||
#include "videoplayer.hpp"
|
|
||||||
|
|
||||||
#ifdef ZHAVE_VIDEO
|
#ifdef ZHAVE_VIDEO
|
||||||
|
|
||||||
|
#include "nvidiaplayer.hpp"
|
||||||
|
|
||||||
#define EGL_EGLEXT_PROTOTYPES
|
#define EGL_EGLEXT_PROTOTYPES
|
||||||
#define GL_GLEXT_PROTOTYPES
|
#define GL_GLEXT_PROTOTYPES
|
||||||
|
|
||||||
@@ -24,6 +24,8 @@ extern "C" CUresult cuGraphicsGLRegisterImage(
|
|||||||
|
|
||||||
Q_LOGGING_CATEGORY(videoLog, "ZShell.Internal.wallpaper")
|
Q_LOGGING_CATEGORY(videoLog, "ZShell.Internal.wallpaper")
|
||||||
|
|
||||||
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
enum AVPixelFormat chooseFormat(
|
enum AVPixelFormat chooseFormat(
|
||||||
@@ -34,14 +36,34 @@ enum AVPixelFormat chooseFormat(
|
|||||||
return pix_fmts[0];
|
return pix_fmts[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CudaCtx {
|
||||||
|
public:
|
||||||
|
explicit CudaCtx(CUcontext ctx) : m_ctx(ctx) {
|
||||||
|
if (m_ctx) cuCtxPushCurrent(m_ctx);
|
||||||
|
}
|
||||||
|
~CudaCtx() {
|
||||||
|
if (m_ctx) cuCtxPopCurrent(&m_prev);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
CUcontext m_ctx;
|
||||||
|
CUcontext m_prev = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
VideoPlayer::VideoPlayer(EGLDisplay display) : m_eglDisplay(display) {}
|
NvidiaPlayer::NvidiaPlayer(EGLDisplay display) {
|
||||||
|
(void)display;
|
||||||
|
}
|
||||||
|
|
||||||
VideoPlayer::~VideoPlayer() {
|
NvidiaPlayer::~NvidiaPlayer() {
|
||||||
stop();
|
stop();
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(m_mtx);
|
std::lock_guard<std::mutex> lock(m_mtx);
|
||||||
|
|
||||||
|
CUcontext prev = nullptr;
|
||||||
|
if (m_cuCtx) cuCtxPushCurrent(m_cuCtx);
|
||||||
|
|
||||||
if (m_pending) {
|
if (m_pending) {
|
||||||
av_frame_unref(m_pending);
|
av_frame_unref(m_pending);
|
||||||
av_frame_free(&m_pending);
|
av_frame_free(&m_pending);
|
||||||
@@ -51,41 +73,39 @@ VideoPlayer::~VideoPlayer() {
|
|||||||
av_frame_free(&m_displayed);
|
av_frame_free(&m_displayed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_resY) {
|
||||||
|
cuGraphicsUnregisterResource(m_resY);
|
||||||
|
m_resY = nullptr;
|
||||||
|
}
|
||||||
|
if (m_resUV) {
|
||||||
|
cuGraphicsUnregisterResource(m_resUV);
|
||||||
|
m_resUV = nullptr;
|
||||||
|
}
|
||||||
|
if (m_texY) {
|
||||||
|
glDeleteTextures(1, &m_texY);
|
||||||
|
m_texY = 0;
|
||||||
|
}
|
||||||
|
if (m_texUV) {
|
||||||
|
glDeleteTextures(1, &m_texUV);
|
||||||
|
m_texUV = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_cuCtx) cuCtxPopCurrent(&prev);
|
||||||
|
|
||||||
if (m_cuCtx) {
|
if (m_cuCtx) {
|
||||||
CUcontext prev = nullptr;
|
|
||||||
cuCtxPushCurrent(m_cuCtx);
|
|
||||||
if (m_resY) {
|
|
||||||
cuGraphicsUnregisterResource(m_resY);
|
|
||||||
m_resY = nullptr;
|
|
||||||
}
|
|
||||||
if (m_resUV) {
|
|
||||||
cuGraphicsUnregisterResource(m_resUV);
|
|
||||||
m_resUV = nullptr;
|
|
||||||
}
|
|
||||||
if (m_texY) {
|
|
||||||
glDeleteTextures(1, &m_texY);
|
|
||||||
m_texY = 0;
|
|
||||||
}
|
|
||||||
if (m_texUV) {
|
|
||||||
glDeleteTextures(1, &m_texUV);
|
|
||||||
m_texUV = 0;
|
|
||||||
}
|
|
||||||
cuCtxPopCurrent(&prev);
|
|
||||||
cuCtxDestroy(m_cuCtx);
|
cuCtxDestroy(m_cuCtx);
|
||||||
m_cuCtx = nullptr;
|
m_cuCtx = nullptr;
|
||||||
}
|
}
|
||||||
m_texSize = QSize();
|
m_texSize = QSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoPlayer::setReadyCallback(std::function<void()> callback) {
|
bool NvidiaPlayer::available() {
|
||||||
m_readyCb = std::move(callback);
|
if (cuInit(0) != CUDA_SUCCESS) return false;
|
||||||
|
CUdevice dev = 0;
|
||||||
|
return cuDeviceGet(&dev, 0) == CUDA_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoPlayer::setFrameCallback(std::function<void()> callback) {
|
bool NvidiaPlayer::openDecoder(const QString& path) {
|
||||||
m_frameCb = std::move(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool VideoPlayer::openDecoder(const QString& path) {
|
|
||||||
if (avformat_open_input(
|
if (avformat_open_input(
|
||||||
&m_fmt, path.toUtf8().constData(), nullptr, nullptr) < 0) {
|
&m_fmt, path.toUtf8().constData(), nullptr, nullptr) < 0) {
|
||||||
qCWarning(
|
qCWarning(
|
||||||
@@ -168,7 +188,7 @@ bool VideoPlayer::openDecoder(const QString& path) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoPlayer::closeDecoder() {
|
void NvidiaPlayer::closeDecoder() {
|
||||||
if (m_codec) {
|
if (m_codec) {
|
||||||
avcodec_free_context(&m_codec);
|
avcodec_free_context(&m_codec);
|
||||||
m_codec = nullptr;
|
m_codec = nullptr;
|
||||||
@@ -184,7 +204,7 @@ void VideoPlayer::closeDecoder() {
|
|||||||
m_streamIndex = -1;
|
m_streamIndex = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoPlayer::decodeLoop(const QString& path, uint32_t generation) {
|
void NvidiaPlayer::decodeLoop(const QString& path, uint32_t generation) {
|
||||||
if (!m_threadRunning.load(std::memory_order_acquire) ||
|
if (!m_threadRunning.load(std::memory_order_acquire) ||
|
||||||
m_generation.load(std::memory_order_acquire) != generation)
|
m_generation.load(std::memory_order_acquire) != generation)
|
||||||
return;
|
return;
|
||||||
@@ -257,11 +277,16 @@ void VideoPlayer::decodeLoop(const QString& path, uint32_t generation) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
av_frame_free(&frame);
|
av_frame_free(&frame);
|
||||||
closeDecoder();
|
{
|
||||||
|
CudaCtx ctxGuard(m_cuCtx);
|
||||||
|
closeDecoder();
|
||||||
|
}
|
||||||
m_threadRunning.store(false);
|
m_threadRunning.store(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoPlayer::start(const QString& path) {
|
bool NvidiaPlayer::start(const QString& path) {
|
||||||
|
if (!ensureCudaContext()) return false;
|
||||||
|
|
||||||
const uint32_t generation =
|
const uint32_t generation =
|
||||||
m_generation.fetch_add(1, std::memory_order_acq_rel) + 1;
|
m_generation.fetch_add(1, std::memory_order_acq_rel) + 1;
|
||||||
m_threadRunning.store(true);
|
m_threadRunning.store(true);
|
||||||
@@ -271,26 +296,22 @@ bool VideoPlayer::start(const QString& path) {
|
|||||||
[this, path, generation, previous = std::move(previous)]() mutable {
|
[this, path, generation, previous = std::move(previous)]() mutable {
|
||||||
if (previous.joinable()) previous.join();
|
if (previous.joinable()) previous.join();
|
||||||
closeDecoder();
|
closeDecoder();
|
||||||
|
|
||||||
if (!ensureCudaContext()) {
|
|
||||||
m_threadRunning.store(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
decodeLoop(path, generation);
|
decodeLoop(path, generation);
|
||||||
});
|
});
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void VideoPlayer::stop() {
|
void NvidiaPlayer::stop() {
|
||||||
m_generation.fetch_add(1, std::memory_order_acq_rel);
|
m_generation.fetch_add(1, std::memory_order_acq_rel);
|
||||||
m_threadRunning.store(false);
|
m_threadRunning.store(false);
|
||||||
if (m_thread.joinable()) m_thread.join();
|
if (m_thread.joinable()) m_thread.join();
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(m_mtx);
|
||||||
|
CudaCtx ctxGuard(m_cuCtx);
|
||||||
|
|
||||||
closeDecoder();
|
closeDecoder();
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(m_mtx);
|
|
||||||
if (m_pending) {
|
if (m_pending) {
|
||||||
av_frame_unref(m_pending);
|
av_frame_unref(m_pending);
|
||||||
m_pending = nullptr;
|
m_pending = nullptr;
|
||||||
@@ -303,12 +324,7 @@ void VideoPlayer::stop() {
|
|||||||
m_size = QSize();
|
m_size = QSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize VideoPlayer::frameSize() const {
|
void NvidiaPlayer::deleteTexture() {
|
||||||
std::lock_guard<std::mutex> lock(m_mtx);
|
|
||||||
return m_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
void VideoPlayer::deleteTexture() {
|
|
||||||
std::lock_guard<std::mutex> lock(m_mtx);
|
std::lock_guard<std::mutex> lock(m_mtx);
|
||||||
CUcontext prev = nullptr;
|
CUcontext prev = nullptr;
|
||||||
if (m_cuCtx) cuCtxPushCurrent(m_cuCtx);
|
if (m_cuCtx) cuCtxPushCurrent(m_cuCtx);
|
||||||
@@ -332,7 +348,7 @@ void VideoPlayer::deleteTexture() {
|
|||||||
m_texSize = QSize();
|
m_texSize = QSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoPlayer::ensureCudaContext() {
|
bool NvidiaPlayer::ensureCudaContext() {
|
||||||
if (m_cuCtx) return true;
|
if (m_cuCtx) return true;
|
||||||
|
|
||||||
if (cuInit(0) != CUDA_SUCCESS) {
|
if (cuInit(0) != CUDA_SUCCESS) {
|
||||||
@@ -354,7 +370,7 @@ bool VideoPlayer::ensureCudaContext() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoPlayer::ensureTextures(unsigned w, unsigned h, int compBytes) {
|
bool NvidiaPlayer::ensureTextures(unsigned w, unsigned h, int compBytes) {
|
||||||
if (!ensureCudaContext()) return false;
|
if (!ensureCudaContext()) return false;
|
||||||
CUcontext prev = nullptr;
|
CUcontext prev = nullptr;
|
||||||
cuCtxPushCurrent(m_cuCtx);
|
cuCtxPushCurrent(m_cuCtx);
|
||||||
@@ -431,7 +447,7 @@ bool VideoPlayer::ensureTextures(unsigned w, unsigned h, int compBytes) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool VideoPlayer::importCurrentFrame() {
|
bool NvidiaPlayer::importCurrentFrame() {
|
||||||
AVFrame* frame = nullptr;
|
AVFrame* frame = nullptr;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(m_mtx);
|
std::lock_guard<std::mutex> lock(m_mtx);
|
||||||
@@ -546,4 +562,6 @@ bool VideoPlayer::importCurrentFrame() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} // namespace ZShell::wallpaper
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
+19
-29
@@ -1,11 +1,14 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef ZHAVE_VIDEO
|
||||||
|
|
||||||
|
#include "videodecoder.hpp"
|
||||||
|
|
||||||
#include <EGL/egl.h>
|
#include <EGL/egl.h>
|
||||||
|
|
||||||
#include <cuda.h>
|
#include <cuda.h>
|
||||||
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <functional>
|
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
@@ -23,27 +26,21 @@ extern "C" {
|
|||||||
#include <libavutil/pixdesc.h>
|
#include <libavutil/pixdesc.h>
|
||||||
}
|
}
|
||||||
|
|
||||||
class VideoPlayer {
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
|
class NvidiaPlayer : public VideoDecoder {
|
||||||
public:
|
public:
|
||||||
explicit VideoPlayer(EGLDisplay display);
|
explicit NvidiaPlayer(EGLDisplay display);
|
||||||
~VideoPlayer();
|
~NvidiaPlayer() override;
|
||||||
|
|
||||||
void setReadyCallback(std::function<void()> callback);
|
[[nodiscard]] static bool available();
|
||||||
void setFrameCallback(std::function<void()> callback);
|
|
||||||
|
|
||||||
bool start(const QString& path);
|
bool start(const QString& path) override;
|
||||||
void stop();
|
void stop() override;
|
||||||
|
bool importCurrentFrame() override;
|
||||||
|
void deleteTexture() override;
|
||||||
|
|
||||||
|
[[nodiscard]] const char* backendName() const override { return "nvidia"; }
|
||||||
bool importCurrentFrame();
|
|
||||||
|
|
||||||
void deleteTexture();
|
|
||||||
|
|
||||||
[[nodiscard]] uint32_t textureId() const { return m_texY; }
|
|
||||||
[[nodiscard]] uint32_t chromaTextureId() const { return m_texUV; }
|
|
||||||
[[nodiscard]] int colorMatrix() const { return m_matrix; }
|
|
||||||
[[nodiscard]] int colorRange() const { return m_range; }
|
|
||||||
[[nodiscard]] QSize frameSize() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void decodeLoop(const QString& path, uint32_t generation);
|
void decodeLoop(const QString& path, uint32_t generation);
|
||||||
@@ -54,10 +51,6 @@ class VideoPlayer {
|
|||||||
|
|
||||||
bool ensureCudaContext();
|
bool ensureCudaContext();
|
||||||
|
|
||||||
EGLDisplay m_eglDisplay;
|
|
||||||
std::function<void()> m_readyCb;
|
|
||||||
std::function<void()> m_frameCb;
|
|
||||||
|
|
||||||
std::thread m_thread;
|
std::thread m_thread;
|
||||||
std::atomic<bool> m_threadRunning{false};
|
std::atomic<bool> m_threadRunning{false};
|
||||||
std::atomic<uint32_t> m_generation{0};
|
std::atomic<uint32_t> m_generation{0};
|
||||||
@@ -77,12 +70,9 @@ class VideoPlayer {
|
|||||||
CUgraphicsResource m_resY = nullptr;
|
CUgraphicsResource m_resY = nullptr;
|
||||||
CUgraphicsResource m_resUV = nullptr;
|
CUgraphicsResource m_resUV = nullptr;
|
||||||
QSize m_texSize;
|
QSize m_texSize;
|
||||||
|
|
||||||
uint32_t m_texY = 0;
|
|
||||||
uint32_t m_texUV = 0;
|
|
||||||
|
|
||||||
int m_matrix = 1;
|
|
||||||
int m_range = 0;
|
|
||||||
int m_texComp = 1;
|
int m_texComp = 1;
|
||||||
QSize m_size;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
} // namespace ZShell::wallpaper
|
||||||
|
|
||||||
|
#endif // ZHAVE_VIDEO
|
||||||
@@ -0,0 +1,628 @@
|
|||||||
|
#define EGL_EGLEXT_PROTOTYPES
|
||||||
|
#include "vaapiplayer.hpp"
|
||||||
|
|
||||||
|
#ifdef ZHAVE_FFMPEG
|
||||||
|
|
||||||
|
#define GL_GLEXT_PROTOTYPES
|
||||||
|
|
||||||
|
#include <EGL/egl.h>
|
||||||
|
#include <GL/gl.h>
|
||||||
|
#include <GL/glext.h>
|
||||||
|
|
||||||
|
#ifdef ZHAVE_VAAPI
|
||||||
|
#include <unistd.h>
|
||||||
|
extern "C" {
|
||||||
|
#include <va/va.h>
|
||||||
|
#include <va/va_drmcommon.h>
|
||||||
|
#include <libavutil/hwcontext_vaapi.h>
|
||||||
|
}
|
||||||
|
#ifndef DRM_FORMAT_MOD_INVALID
|
||||||
|
#define DRM_FORMAT_MOD_INVALID 0xffffffffffffffffULL
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <climits>
|
||||||
|
|
||||||
|
#include <QtLogging>
|
||||||
|
|
||||||
|
Q_LOGGING_CATEGORY(vaapiLog, "ZShell.Internal.wallpaper")
|
||||||
|
|
||||||
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
enum AVPixelFormat chooseFormat(
|
||||||
|
AVCodecContext* ctx, const AVPixelFormat* pix_fmts) {
|
||||||
|
(void)ctx;
|
||||||
|
for (const AVPixelFormat* p = pix_fmts; *p != AV_PIX_FMT_NONE; ++p)
|
||||||
|
if (*p == AV_PIX_FMT_VAAPI) return AV_PIX_FMT_VAAPI;
|
||||||
|
return pix_fmts[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ZHAVE_VAAPI
|
||||||
|
using GLEGLImageTargetProc = void (*)(GLenum, EGLImageKHR);
|
||||||
|
|
||||||
|
GLEGLImageTargetProc glEGLImageTarget() {
|
||||||
|
static GLEGLImageTargetProc fn = reinterpret_cast<GLEGLImageTargetProc>(
|
||||||
|
eglGetProcAddress("glEGLImageTargetTexture2DOES"));
|
||||||
|
return fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLImageKHR createDmaImage(
|
||||||
|
EGLDisplay dpy,
|
||||||
|
int fd,
|
||||||
|
uint32_t offset,
|
||||||
|
uint32_t pitch,
|
||||||
|
uint32_t fourcc,
|
||||||
|
uint64_t modifier) {
|
||||||
|
EGLint attrs[16];
|
||||||
|
int n = 0;
|
||||||
|
attrs[n++] = EGL_LINUX_DRM_FOURCC_EXT;
|
||||||
|
attrs[n++] = EGLint(fourcc);
|
||||||
|
attrs[n++] = EGL_DMA_BUF_PLANE0_FD_EXT;
|
||||||
|
attrs[n++] = fd;
|
||||||
|
attrs[n++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT;
|
||||||
|
attrs[n++] = EGLint(offset);
|
||||||
|
attrs[n++] = EGL_DMA_BUF_PLANE0_PITCH_EXT;
|
||||||
|
attrs[n++] = EGLint(pitch);
|
||||||
|
if (modifier != DRM_FORMAT_MOD_INVALID) {
|
||||||
|
attrs[n++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT;
|
||||||
|
attrs[n++] = EGLint(modifier & 0xffffffffULL);
|
||||||
|
attrs[n++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT;
|
||||||
|
attrs[n++] = EGLint(modifier >> 32);
|
||||||
|
}
|
||||||
|
attrs[n] = EGL_NONE;
|
||||||
|
|
||||||
|
EGLImageKHR image = eglCreateImageKHR(
|
||||||
|
dpy, EGL_NO_CONTEXT, EGL_LINUX_DMA_BUF_EXT, nullptr, attrs);
|
||||||
|
if (image == EGL_NO_IMAGE_KHR) return EGL_NO_IMAGE_KHR;
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
VaapiPlayer::VaapiPlayer(EGLDisplay display) : m_eglDisplay(display) {}
|
||||||
|
|
||||||
|
VaapiPlayer::~VaapiPlayer() {
|
||||||
|
stop();
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(m_mtx);
|
||||||
|
if (m_pending) {
|
||||||
|
av_frame_unref(m_pending);
|
||||||
|
av_frame_free(&m_pending);
|
||||||
|
}
|
||||||
|
if (m_displayed) {
|
||||||
|
av_frame_unref(m_displayed);
|
||||||
|
av_frame_free(&m_displayed);
|
||||||
|
}
|
||||||
|
if (m_hwDeviceCtx) {
|
||||||
|
av_buffer_unref(&m_hwDeviceCtx);
|
||||||
|
m_hwDeviceCtx = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ZHAVE_VAAPI
|
||||||
|
if (m_imageY != EGL_NO_IMAGE_KHR) {
|
||||||
|
eglDestroyImageKHR(m_eglDisplay, m_imageY);
|
||||||
|
m_imageY = EGL_NO_IMAGE_KHR;
|
||||||
|
}
|
||||||
|
if (m_imageUV != EGL_NO_IMAGE_KHR) {
|
||||||
|
eglDestroyImageKHR(m_eglDisplay, m_imageUV);
|
||||||
|
m_imageUV = EGL_NO_IMAGE_KHR;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VaapiPlayer::available() {
|
||||||
|
AVBufferRef* ref = nullptr;
|
||||||
|
if (av_hwdevice_ctx_create(
|
||||||
|
&ref, AV_HWDEVICE_TYPE_VAAPI, nullptr, nullptr, 0) < 0) {
|
||||||
|
if (ref) av_buffer_unref(&ref);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
av_buffer_unref(&ref);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VaapiPlayer::ensureDevice() {
|
||||||
|
if (m_hwDeviceCtx) return true;
|
||||||
|
if (av_hwdevice_ctx_create(
|
||||||
|
&m_hwDeviceCtx, AV_HWDEVICE_TYPE_VAAPI, nullptr, nullptr, 0) < 0) {
|
||||||
|
if (m_hwDeviceCtx) av_buffer_unref(&m_hwDeviceCtx);
|
||||||
|
m_hwDeviceCtx = nullptr;
|
||||||
|
qCWarning(vaapiLog, "VideoPlayer: could not create vaapi hwdevice");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VaapiPlayer::openDecoder(const QString& path) {
|
||||||
|
if (avformat_open_input(
|
||||||
|
&m_fmt, path.toUtf8().constData(), nullptr, nullptr) < 0) {
|
||||||
|
qCWarning(
|
||||||
|
vaapiLog,
|
||||||
|
"VideoPlayer: could not open %s",
|
||||||
|
path.toUtf8().constData());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (avformat_find_stream_info(m_fmt, nullptr) < 0) {
|
||||||
|
qCWarning(vaapiLog, "VideoPlayer: could not read stream info");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_streamIndex = -1;
|
||||||
|
for (unsigned i = 0; i < m_fmt->nb_streams; ++i)
|
||||||
|
if (m_fmt->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
|
||||||
|
m_streamIndex = int(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (m_streamIndex < 0) {
|
||||||
|
qCWarning(vaapiLog, "VideoPlayer: no video stream");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVCodecParameters* par = m_fmt->streams[m_streamIndex]->codecpar;
|
||||||
|
const AVCodec* codec = avcodec_find_decoder(par->codec_id);
|
||||||
|
const AVCodec* hwcodec = nullptr;
|
||||||
|
void* it = nullptr;
|
||||||
|
while (const AVCodec* c = av_codec_iterate(&it)) {
|
||||||
|
if (c->id != par->codec_id || !av_codec_is_decoder(c)) continue;
|
||||||
|
for (int i = 0;
|
||||||
|
const AVCodecHWConfig* cfg = avcodec_get_hw_config(c, i);
|
||||||
|
++i)
|
||||||
|
if (cfg->device_type == AV_HWDEVICE_TYPE_VAAPI &&
|
||||||
|
(cfg->methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX)) {
|
||||||
|
hwcodec = c;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (hwcodec) break;
|
||||||
|
}
|
||||||
|
if (hwcodec) codec = hwcodec;
|
||||||
|
if (!codec) {
|
||||||
|
qCWarning(
|
||||||
|
vaapiLog,
|
||||||
|
"VideoPlayer: no decoder for codec id %d",
|
||||||
|
int(par->codec_id));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_codec = avcodec_alloc_context3(codec);
|
||||||
|
if (!m_codec) return false;
|
||||||
|
if (avcodec_parameters_to_context(m_codec, par) < 0) {
|
||||||
|
qCWarning(vaapiLog, "VideoPlayer: could not copy codec params");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ensureDevice()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_codec->hw_device_ctx = av_buffer_ref(m_hwDeviceCtx);
|
||||||
|
m_codec->get_format = chooseFormat;
|
||||||
|
|
||||||
|
if (avcodec_open2(m_codec, codec, nullptr) < 0) {
|
||||||
|
qCWarning(vaapiLog, "VideoPlayer: could not open codec");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VaapiPlayer::closeDecoder() {
|
||||||
|
if (m_codec) {
|
||||||
|
avcodec_free_context(&m_codec);
|
||||||
|
m_codec = nullptr;
|
||||||
|
}
|
||||||
|
if (m_fmt) {
|
||||||
|
avformat_close_input(&m_fmt);
|
||||||
|
m_fmt = nullptr;
|
||||||
|
}
|
||||||
|
m_streamIndex = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VaapiPlayer::decodeLoop(const QString& path, uint32_t generation) {
|
||||||
|
if (!m_threadRunning.load(std::memory_order_acquire) ||
|
||||||
|
m_generation.load(std::memory_order_acquire) != generation)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!openDecoder(path)) {
|
||||||
|
m_threadRunning.store(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVRational fr = m_fmt->streams[m_streamIndex]->avg_frame_rate;
|
||||||
|
if (fr.num <= 0 || fr.den <= 0) fr = m_codec->framerate;
|
||||||
|
if (fr.num <= 0 || fr.den <= 0) fr = AVRational{30, 1};
|
||||||
|
const int64_t intervalUs =
|
||||||
|
av_rescale_q(1, AVRational{fr.den, fr.num}, AVRational{1, 1000000});
|
||||||
|
|
||||||
|
AVFrame* frame = av_frame_alloc();
|
||||||
|
if (!frame) {
|
||||||
|
m_threadRunning.store(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVPacket pkt{};
|
||||||
|
|
||||||
|
while (m_threadRunning.load(std::memory_order_acquire) &&
|
||||||
|
m_generation.load(std::memory_order_acquire) == generation) {
|
||||||
|
int ret = av_read_frame(m_fmt, &pkt);
|
||||||
|
if (ret == AVERROR_EOF) {
|
||||||
|
if (avformat_seek_file(
|
||||||
|
m_fmt, m_streamIndex, INT64_MIN, 0, INT64_MAX, 0) < 0) {
|
||||||
|
qCWarning(vaapiLog, "VideoPlayer: loop seek failed; stopping");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
avcodec_flush_buffers(m_codec);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ret < 0) {
|
||||||
|
qCWarning(vaapiLog, "VideoPlayer: read error; stopping");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (pkt.stream_index != m_streamIndex) {
|
||||||
|
av_packet_unref(&pkt);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (avcodec_send_packet(m_codec, &pkt) < 0) {
|
||||||
|
av_packet_unref(&pkt);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
av_packet_unref(&pkt);
|
||||||
|
|
||||||
|
while (m_threadRunning.load(std::memory_order_acquire) &&
|
||||||
|
m_generation.load(std::memory_order_acquire) == generation &&
|
||||||
|
avcodec_receive_frame(m_codec, frame) == 0) {
|
||||||
|
bool wasEmpty = false;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mtx);
|
||||||
|
if (!m_pending) m_pending = av_frame_alloc();
|
||||||
|
av_frame_unref(m_pending);
|
||||||
|
if (av_frame_ref(m_pending, frame) >= 0) {
|
||||||
|
wasEmpty = !m_haveFrame;
|
||||||
|
m_haveFrame = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wasEmpty && m_readyCb) m_readyCb();
|
||||||
|
if (m_frameCb) m_frameCb();
|
||||||
|
av_frame_unref(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::microseconds(intervalUs));
|
||||||
|
}
|
||||||
|
|
||||||
|
av_frame_free(&frame);
|
||||||
|
closeDecoder();
|
||||||
|
m_threadRunning.store(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VaapiPlayer::start(const QString& path) {
|
||||||
|
if (!ensureDevice()) return false;
|
||||||
|
|
||||||
|
const uint32_t generation =
|
||||||
|
m_generation.fetch_add(1, std::memory_order_acq_rel) + 1;
|
||||||
|
m_threadRunning.store(true);
|
||||||
|
|
||||||
|
std::thread previous = std::move(m_thread);
|
||||||
|
m_thread = std::thread(
|
||||||
|
[this, path, generation, previous = std::move(previous)]() mutable {
|
||||||
|
if (previous.joinable()) previous.join();
|
||||||
|
closeDecoder();
|
||||||
|
decodeLoop(path, generation);
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VaapiPlayer::stop() {
|
||||||
|
m_generation.fetch_add(1, std::memory_order_acq_rel);
|
||||||
|
m_threadRunning.store(false);
|
||||||
|
if (m_thread.joinable()) m_thread.join();
|
||||||
|
|
||||||
|
closeDecoder();
|
||||||
|
|
||||||
|
std::lock_guard<std::mutex> lock(m_mtx);
|
||||||
|
if (m_pending) {
|
||||||
|
av_frame_unref(m_pending);
|
||||||
|
m_pending = nullptr;
|
||||||
|
}
|
||||||
|
if (m_displayed) {
|
||||||
|
av_frame_unref(m_displayed);
|
||||||
|
m_displayed = nullptr;
|
||||||
|
}
|
||||||
|
m_haveFrame = false;
|
||||||
|
m_size = QSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
void VaapiPlayer::deleteTexture() {
|
||||||
|
#ifdef ZHAVE_VAAPI
|
||||||
|
if (m_imageY != EGL_NO_IMAGE_KHR) {
|
||||||
|
eglDestroyImageKHR(m_eglDisplay, m_imageY);
|
||||||
|
m_imageY = EGL_NO_IMAGE_KHR;
|
||||||
|
}
|
||||||
|
if (m_imageUV != EGL_NO_IMAGE_KHR) {
|
||||||
|
eglDestroyImageKHR(m_eglDisplay, m_imageUV);
|
||||||
|
m_imageUV = EGL_NO_IMAGE_KHR;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if (m_texY) {
|
||||||
|
glDeleteTextures(1, &m_texY);
|
||||||
|
m_texY = 0;
|
||||||
|
}
|
||||||
|
if (m_texUV) {
|
||||||
|
glDeleteTextures(1, &m_texUV);
|
||||||
|
m_texUV = 0;
|
||||||
|
}
|
||||||
|
m_texSize = QSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VaapiPlayer::ensureTextures(unsigned w, unsigned h, int compBytes) {
|
||||||
|
const QSize sz = QSize(int(w), int(h));
|
||||||
|
if (m_texSize == sz && m_texComp == compBytes && m_texY != 0) return true;
|
||||||
|
|
||||||
|
if (m_texY) glDeleteTextures(1, &m_texY);
|
||||||
|
if (m_texUV) glDeleteTextures(1, &m_texUV);
|
||||||
|
m_texY = m_texUV = 0;
|
||||||
|
|
||||||
|
const bool wide = compBytes == 2;
|
||||||
|
auto makeTex =
|
||||||
|
[&](GLuint& tex, GLint internal, GLenum fmt, unsigned tw, unsigned th) {
|
||||||
|
glGenTextures(1, &tex);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, tex);
|
||||||
|
glTexImage2D(
|
||||||
|
GL_TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
internal,
|
||||||
|
GLint(tw),
|
||||||
|
GLint(th),
|
||||||
|
0,
|
||||||
|
fmt,
|
||||||
|
wide ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE,
|
||||||
|
nullptr);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
};
|
||||||
|
makeTex(m_texY, wide ? GL_R16 : GL_R8, GL_RED, w, h);
|
||||||
|
makeTex(m_texUV, wide ? GL_RG16 : GL_RG8, GL_RG, w / 2, h / 2);
|
||||||
|
|
||||||
|
m_texSize = sz;
|
||||||
|
m_texComp = compBytes;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VaapiPlayer::importCurrentFrame() {
|
||||||
|
AVFrame* frame = nullptr;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mtx);
|
||||||
|
if (!m_pending || m_pending->format == AV_PIX_FMT_NONE) return false;
|
||||||
|
frame = m_pending;
|
||||||
|
m_pending = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frame->format != AV_PIX_FMT_VAAPI || !frame->hw_frames_ctx) {
|
||||||
|
static int logged = 0;
|
||||||
|
if (logged++ < 3)
|
||||||
|
qCWarning(
|
||||||
|
vaapiLog,
|
||||||
|
"VideoPlayer: frame not on GPU (fmt=%s hw_frames=%d)",
|
||||||
|
av_get_pix_fmt_name(AVPixelFormat(frame->format)),
|
||||||
|
frame->hw_frames_ctx != nullptr);
|
||||||
|
av_frame_unref(frame);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AVHWFramesContext* hf =
|
||||||
|
reinterpret_cast<const AVHWFramesContext*>(frame->hw_frames_ctx->data);
|
||||||
|
const AVPixelFormat sw = hf->sw_format;
|
||||||
|
const bool tenBit = sw == AV_PIX_FMT_P010LE || sw == AV_PIX_FMT_P010BE ||
|
||||||
|
sw == AV_PIX_FMT_P012LE || sw == AV_PIX_FMT_P016LE;
|
||||||
|
const int compBytes = tenBit ? 2 : 1;
|
||||||
|
const size_t compSize = size_t(compBytes);
|
||||||
|
|
||||||
|
const unsigned w = unsigned(frame->width);
|
||||||
|
const unsigned h = unsigned(frame->height);
|
||||||
|
if (w == 0 || h == 0) {
|
||||||
|
av_frame_unref(frame);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (frame->colorspace) {
|
||||||
|
case AVCOL_SPC_BT470BG:
|
||||||
|
case AVCOL_SPC_SMPTE170M:
|
||||||
|
m_matrix = 0;
|
||||||
|
break;
|
||||||
|
case AVCOL_SPC_BT709:
|
||||||
|
m_matrix = 1;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
m_matrix = h >= 720 ? 1 : 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
m_range = frame->color_range == AVCOL_RANGE_JPEG ? 1 : 0;
|
||||||
|
m_size = QSize(int(w), int(h));
|
||||||
|
|
||||||
|
#ifdef ZHAVE_VAAPI
|
||||||
|
if (importEglImages(frame)) {
|
||||||
|
std::lock_guard<std::mutex> lock(m_mtx);
|
||||||
|
if (m_displayed) av_frame_unref(m_displayed);
|
||||||
|
m_displayed = frame;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
AVFrame* tmp = av_frame_alloc();
|
||||||
|
if (!tmp) {
|
||||||
|
av_frame_unref(frame);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (av_hwframe_transfer_data(tmp, frame, 0) < 0) {
|
||||||
|
av_frame_free(&tmp);
|
||||||
|
av_frame_unref(frame);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pitchY = size_t(tmp->linesize[0]);
|
||||||
|
size_t pitchUV = tmp->linesize[1] > 0 ? size_t(tmp->linesize[1]) : pitchY;
|
||||||
|
if (pitchY == 0) pitchY = size_t(w) * compSize;
|
||||||
|
|
||||||
|
if (!ensureTextures(w, h, compBytes)) {
|
||||||
|
av_frame_free(&tmp);
|
||||||
|
av_frame_unref(frame);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const GLenum type = compBytes == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE;
|
||||||
|
|
||||||
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, m_texY);
|
||||||
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, GLint(pitchY / compSize));
|
||||||
|
glTexSubImage2D(
|
||||||
|
GL_TEXTURE_2D, 0, 0, 0, GLint(w), GLint(h), GL_RED, type, tmp->data[0]);
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, m_texUV);
|
||||||
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, GLint(pitchUV / (compSize * 2)));
|
||||||
|
glTexSubImage2D(
|
||||||
|
GL_TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
GLint(w / 2),
|
||||||
|
GLint(h / 2),
|
||||||
|
GL_RG,
|
||||||
|
type,
|
||||||
|
tmp->data[1]);
|
||||||
|
|
||||||
|
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
|
||||||
|
|
||||||
|
av_frame_free(&tmp);
|
||||||
|
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_mtx);
|
||||||
|
if (m_displayed) av_frame_unref(m_displayed);
|
||||||
|
m_displayed = frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ZHAVE_VAAPI
|
||||||
|
bool VaapiPlayer::importEglImages(AVFrame* frame) {
|
||||||
|
if (m_eglDisplay == EGL_NO_DISPLAY) return false;
|
||||||
|
GLEGLImageTargetProc target = glEGLImageTarget();
|
||||||
|
if (!target) return false;
|
||||||
|
|
||||||
|
const AVHWFramesContext* hf =
|
||||||
|
reinterpret_cast<const AVHWFramesContext*>(frame->hw_frames_ctx->data);
|
||||||
|
const AVHWDeviceContext* dev = hf->device_ctx;
|
||||||
|
const AVVAAPIDeviceContext* va =
|
||||||
|
reinterpret_cast<const AVVAAPIDeviceContext*>(dev->hwctx);
|
||||||
|
if (!va->display) return false;
|
||||||
|
|
||||||
|
const VASurfaceID surface = VASurfaceID(uintptr_t(frame->data[0]));
|
||||||
|
|
||||||
|
VADRMPRIMESurfaceDescriptor desc{};
|
||||||
|
VAStatus st = vaExportSurfaceHandle(
|
||||||
|
va->display,
|
||||||
|
surface,
|
||||||
|
VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2,
|
||||||
|
VA_EXPORT_SURFACE_READ_ONLY | VA_EXPORT_SURFACE_SEPARATE_LAYERS,
|
||||||
|
&desc);
|
||||||
|
if (st != VA_STATUS_SUCCESS) {
|
||||||
|
static int logged = 0;
|
||||||
|
if (logged++ < 3)
|
||||||
|
qCWarning(
|
||||||
|
vaapiLog,
|
||||||
|
"VideoPlayer: vaExportSurfaceHandle failed (%d); CPU fallback",
|
||||||
|
int(st));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (desc.num_layers != 2 || desc.num_objects < 2) {
|
||||||
|
for (uint32_t i = 0; i < desc.num_objects; ++i)
|
||||||
|
if (desc.objects[i].fd >= 0) close(desc.objects[i].fd);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto bindLayer = [&](uint32_t layer, GLuint tex) -> EGLImageKHR {
|
||||||
|
const auto& L = desc.layers[layer];
|
||||||
|
if (L.num_planes < 1) return EGL_NO_IMAGE_KHR;
|
||||||
|
const auto& obj = desc.objects[L.object_index[0]];
|
||||||
|
if (obj.fd < 0) return EGL_NO_IMAGE_KHR;
|
||||||
|
EGLImageKHR image = createDmaImage(
|
||||||
|
m_eglDisplay,
|
||||||
|
obj.fd,
|
||||||
|
L.offset[0],
|
||||||
|
L.pitch[0],
|
||||||
|
L.drm_format,
|
||||||
|
obj.drm_format_modifier);
|
||||||
|
if (image == EGL_NO_IMAGE_KHR) return EGL_NO_IMAGE_KHR;
|
||||||
|
glBindTexture(GL_TEXTURE_2D, tex);
|
||||||
|
target(GL_TEXTURE_2D, image);
|
||||||
|
return image;
|
||||||
|
};
|
||||||
|
|
||||||
|
const QSize sz = QSize(int(frame->width), int(frame->height));
|
||||||
|
if (m_texSize != sz || m_texY == 0) {
|
||||||
|
if (m_imageY != EGL_NO_IMAGE_KHR) {
|
||||||
|
eglDestroyImageKHR(m_eglDisplay, m_imageY);
|
||||||
|
m_imageY = EGL_NO_IMAGE_KHR;
|
||||||
|
}
|
||||||
|
if (m_imageUV != EGL_NO_IMAGE_KHR) {
|
||||||
|
eglDestroyImageKHR(m_eglDisplay, m_imageUV);
|
||||||
|
m_imageUV = EGL_NO_IMAGE_KHR;
|
||||||
|
}
|
||||||
|
if (m_texY == 0) glGenTextures(1, &m_texY);
|
||||||
|
if (m_texUV == 0) glGenTextures(1, &m_texUV);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, m_texY);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, m_texUV);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
|
m_texSize = sz;
|
||||||
|
}
|
||||||
|
|
||||||
|
EGLImageKHR imageY = bindLayer(0, m_texY);
|
||||||
|
EGLImageKHR imageUV = bindLayer(1, m_texUV);
|
||||||
|
|
||||||
|
if (imageY == EGL_NO_IMAGE_KHR || imageUV == EGL_NO_IMAGE_KHR) {
|
||||||
|
if (imageY != EGL_NO_IMAGE_KHR)
|
||||||
|
eglDestroyImageKHR(m_eglDisplay, imageY);
|
||||||
|
if (imageUV != EGL_NO_IMAGE_KHR)
|
||||||
|
eglDestroyImageKHR(m_eglDisplay, imageUV);
|
||||||
|
for (uint32_t i = 0; i < desc.num_objects; ++i)
|
||||||
|
if (desc.objects[i].fd >= 0) close(desc.objects[i].fd);
|
||||||
|
static int logged = 0;
|
||||||
|
if (logged++ < 3)
|
||||||
|
qCWarning(
|
||||||
|
vaapiLog, "VideoPlayer: EGLImage import failed; CPU fallback");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_imageY != EGL_NO_IMAGE_KHR)
|
||||||
|
eglDestroyImageKHR(m_eglDisplay, m_imageY);
|
||||||
|
if (m_imageUV != EGL_NO_IMAGE_KHR)
|
||||||
|
eglDestroyImageKHR(m_eglDisplay, m_imageUV);
|
||||||
|
|
||||||
|
m_imageY = imageY;
|
||||||
|
m_imageUV = imageUV;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < desc.num_objects; ++i)
|
||||||
|
if (desc.objects[i].fd >= 0) close(desc.objects[i].fd);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace ZShell::wallpaper
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "videodecoder.hpp"
|
||||||
|
|
||||||
|
#include <EGL/egl.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <mutex>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
|
#include <QSize>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include <EGL/egl.h>
|
||||||
|
#ifdef ZHAVE_VAAPI
|
||||||
|
#include <EGL/eglext.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
#include <libavutil/buffer.h>
|
||||||
|
#include <libavutil/frame.h>
|
||||||
|
#include <libavutil/hwcontext.h>
|
||||||
|
#include <libavutil/pixfmt.h>
|
||||||
|
#include <libavutil/pixdesc.h>
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
|
class VaapiPlayer : public VideoDecoder {
|
||||||
|
public:
|
||||||
|
explicit VaapiPlayer(EGLDisplay display);
|
||||||
|
~VaapiPlayer() override;
|
||||||
|
|
||||||
|
[[nodiscard]] static bool available();
|
||||||
|
|
||||||
|
bool start(const QString& path) override;
|
||||||
|
void stop() override;
|
||||||
|
bool importCurrentFrame() override;
|
||||||
|
void deleteTexture() override;
|
||||||
|
|
||||||
|
[[nodiscard]] const char* backendName() const override { return "vaapi"; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void decodeLoop(const QString& path, uint32_t generation);
|
||||||
|
bool openDecoder(const QString& path);
|
||||||
|
void closeDecoder();
|
||||||
|
|
||||||
|
bool ensureDevice();
|
||||||
|
bool ensureTextures(unsigned w, unsigned h, int compBytes);
|
||||||
|
|
||||||
|
#ifdef ZHAVE_VAAPI
|
||||||
|
bool importEglImages(AVFrame* frame);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
EGLDisplay m_eglDisplay = EGL_NO_DISPLAY;
|
||||||
|
|
||||||
|
std::thread m_thread;
|
||||||
|
std::atomic<bool> m_threadRunning{false};
|
||||||
|
std::atomic<uint32_t> m_generation{0};
|
||||||
|
|
||||||
|
mutable std::mutex m_mtx;
|
||||||
|
AVFrame* m_pending = nullptr;
|
||||||
|
bool m_haveFrame = false;
|
||||||
|
|
||||||
|
AVFrame* m_displayed = nullptr;
|
||||||
|
|
||||||
|
AVFormatContext* m_fmt = nullptr;
|
||||||
|
AVCodecContext* m_codec = nullptr;
|
||||||
|
AVBufferRef* m_hwDeviceCtx = nullptr;
|
||||||
|
int m_streamIndex = -1;
|
||||||
|
|
||||||
|
QSize m_texSize;
|
||||||
|
int m_texComp = 1;
|
||||||
|
|
||||||
|
#ifdef ZHAVE_VAAPI
|
||||||
|
EGLImageKHR m_imageY = EGL_NO_IMAGE_KHR;
|
||||||
|
EGLImageKHR m_imageUV = EGL_NO_IMAGE_KHR;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZShell::wallpaper
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
#include "videodecoder.hpp"
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
|
||||||
|
#ifdef ZHAVE_VIDEO
|
||||||
|
#include "nvidiaplayer.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ZHAVE_FFMPEG
|
||||||
|
#include "vaapiplayer.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
|
VideoDecoder::~VideoDecoder() = default;
|
||||||
|
|
||||||
|
void VideoDecoder::setReadyCallback(std::function<void()> callback) {
|
||||||
|
m_readyCb = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoDecoder::setFrameCallback(std::function<void()> callback) {
|
||||||
|
m_frameCb = std::move(callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t VideoDecoder::textureId() const {
|
||||||
|
return m_texY;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t VideoDecoder::chromaTextureId() const {
|
||||||
|
return m_texUV;
|
||||||
|
}
|
||||||
|
|
||||||
|
int VideoDecoder::colorMatrix() const {
|
||||||
|
return m_matrix;
|
||||||
|
}
|
||||||
|
|
||||||
|
int VideoDecoder::colorRange() const {
|
||||||
|
return m_range;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize VideoDecoder::frameSize() const {
|
||||||
|
return m_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
VideoBackend backendFromEnvironment() {
|
||||||
|
if (qEnvironmentVariableIsSet("ZBAR_WALLPAPER_VAAPI"))
|
||||||
|
return VideoBackend::Vaapi;
|
||||||
|
if (qEnvironmentVariableIsSet("ZBAR_WALLPAPER_NVIDIA"))
|
||||||
|
return VideoBackend::Nvidia;
|
||||||
|
return VideoBackend::Auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
VideoBackend selectBackend(VideoBackend hint) {
|
||||||
|
if (hint == VideoBackend::None) return VideoBackend::None;
|
||||||
|
if (hint == VideoBackend::Auto) hint = backendFromEnvironment();
|
||||||
|
|
||||||
|
if (hint == VideoBackend::Nvidia) {
|
||||||
|
#ifdef ZHAVE_VIDEO
|
||||||
|
if (NvidiaPlayer::available()) return VideoBackend::Nvidia;
|
||||||
|
#endif
|
||||||
|
return VideoBackend::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hint == VideoBackend::Vaapi) {
|
||||||
|
#ifdef ZHAVE_FFMPEG
|
||||||
|
if (VaapiPlayer::available()) return VideoBackend::Vaapi;
|
||||||
|
#endif
|
||||||
|
return VideoBackend::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef ZHAVE_VIDEO
|
||||||
|
if (NvidiaPlayer::available()) return VideoBackend::Nvidia;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ZHAVE_FFMPEG
|
||||||
|
if (VaapiPlayer::available()) return VideoBackend::Vaapi;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return VideoBackend::None;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
std::unique_ptr<VideoDecoder> createVideoDecoder(
|
||||||
|
EGLDisplay display, VideoBackend hint) {
|
||||||
|
switch (selectBackend(hint)) {
|
||||||
|
#ifdef ZHAVE_VIDEO
|
||||||
|
case VideoBackend::Nvidia:
|
||||||
|
return std::make_unique<NvidiaPlayer>(display);
|
||||||
|
#endif
|
||||||
|
#ifdef ZHAVE_FFMPEG
|
||||||
|
case VideoBackend::Vaapi:
|
||||||
|
return std::make_unique<VaapiPlayer>(display);
|
||||||
|
#endif
|
||||||
|
case VideoBackend::Auto:
|
||||||
|
case VideoBackend::None:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* resolveBackendName(VideoBackend hint) {
|
||||||
|
switch (selectBackend(hint)) {
|
||||||
|
case VideoBackend::Nvidia:
|
||||||
|
return "nvidia";
|
||||||
|
case VideoBackend::Vaapi:
|
||||||
|
return "vaapi";
|
||||||
|
case VideoBackend::Auto:
|
||||||
|
case VideoBackend::None:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return "mpv";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ZShell::wallpaper
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <EGL/egl.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <QSize>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
|
class VideoDecoder {
|
||||||
|
public:
|
||||||
|
virtual ~VideoDecoder();
|
||||||
|
|
||||||
|
void setReadyCallback(std::function<void()> callback);
|
||||||
|
void setFrameCallback(std::function<void()> callback);
|
||||||
|
|
||||||
|
virtual bool start(const QString& path) = 0;
|
||||||
|
virtual void stop() = 0;
|
||||||
|
|
||||||
|
virtual bool importCurrentFrame() = 0;
|
||||||
|
virtual void deleteTexture() = 0;
|
||||||
|
|
||||||
|
[[nodiscard]] uint32_t textureId() const;
|
||||||
|
[[nodiscard]] uint32_t chromaTextureId() const;
|
||||||
|
[[nodiscard]] int colorMatrix() const;
|
||||||
|
[[nodiscard]] int colorRange() const;
|
||||||
|
[[nodiscard]] QSize frameSize() const;
|
||||||
|
|
||||||
|
[[nodiscard]] virtual const char* backendName() const = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::function<void()> m_readyCb;
|
||||||
|
std::function<void()> m_frameCb;
|
||||||
|
|
||||||
|
uint32_t m_texY = 0;
|
||||||
|
uint32_t m_texUV = 0;
|
||||||
|
int m_matrix = 1;
|
||||||
|
int m_range = 0;
|
||||||
|
QSize m_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class VideoBackend {
|
||||||
|
Auto,
|
||||||
|
Nvidia,
|
||||||
|
Vaapi,
|
||||||
|
None,
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<VideoDecoder> createVideoDecoder(
|
||||||
|
EGLDisplay display, VideoBackend hint = VideoBackend::Auto);
|
||||||
|
|
||||||
|
const char* resolveBackendName(VideoBackend hint = VideoBackend::Auto);
|
||||||
|
|
||||||
|
} // namespace ZShell::wallpaper
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
#include "videotranscoder.hpp"
|
||||||
|
|
||||||
|
#include <QCryptographicHash>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QStringList>
|
||||||
|
|
||||||
|
#include <QtLogging>
|
||||||
|
|
||||||
|
Q_LOGGING_CATEGORY(transcodeLog, "ZShell.Internal.wallpaper")
|
||||||
|
|
||||||
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
|
VideoTranscoder::VideoTranscoder(QObject* parent) : QObject(parent) {}
|
||||||
|
|
||||||
|
bool VideoTranscoder::isHardwareCodec(const QString& codec) {
|
||||||
|
static const QStringList hardware{
|
||||||
|
"av1",
|
||||||
|
"hevc",
|
||||||
|
"h264",
|
||||||
|
"vp9",
|
||||||
|
"vp8",
|
||||||
|
"vc1",
|
||||||
|
"mpeg2",
|
||||||
|
"mpeg2video",
|
||||||
|
"mpeg4",
|
||||||
|
"mjpeg",
|
||||||
|
};
|
||||||
|
|
||||||
|
return hardware.contains(codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString VideoTranscoder::codecName(const QString& path) const {
|
||||||
|
QProcess probe;
|
||||||
|
probe.start(
|
||||||
|
"ffprobe",
|
||||||
|
{
|
||||||
|
"-v",
|
||||||
|
"error",
|
||||||
|
"-select_streams",
|
||||||
|
"v:0",
|
||||||
|
"-show_entries",
|
||||||
|
"stream=codec_name",
|
||||||
|
"-of",
|
||||||
|
"default=nw=1:nk=1",
|
||||||
|
path,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!probe.waitForFinished(2000) || probe.exitCode() != 0) {
|
||||||
|
qWarning(transcodeLog) << "WallpaperService: ffprobe failed for" << path
|
||||||
|
<< "(exit" << probe.exitCode() << ")";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return QString::fromUtf8(probe.readAllStandardOutput()).trimmed().toLower();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString VideoTranscoder::cacheDir() const {
|
||||||
|
return QDir::homePath() + "/.cache/zshell/wallpaper-videos";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString VideoTranscoder::transcodeTarget(const QString& path) const {
|
||||||
|
const QFileInfo info(path);
|
||||||
|
|
||||||
|
const QByteArray key =
|
||||||
|
path.toUtf8() + '|' + QByteArray::number(info.size()) + '|' +
|
||||||
|
QByteArray::number(info.lastModified().toMSecsSinceEpoch());
|
||||||
|
|
||||||
|
const QString hash = QString::fromUtf8(
|
||||||
|
QCryptographicHash::hash(key, QCryptographicHash::Sha1).toHex());
|
||||||
|
|
||||||
|
return cacheDir() + '/' + hash + ".av1.mkv";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VideoTranscoder::needsTranscode(const QString& path) const {
|
||||||
|
if (path.startsWith(cacheDir())) return false;
|
||||||
|
|
||||||
|
const QString codec = codecName(path);
|
||||||
|
if (codec.isEmpty()) return false;
|
||||||
|
|
||||||
|
return !isHardwareCodec(codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
void VideoTranscoder::start(const QString& src, const QString& dst) {
|
||||||
|
QDir().mkpath(cacheDir());
|
||||||
|
|
||||||
|
if (m_process) {
|
||||||
|
m_process->kill();
|
||||||
|
m_process->deleteLater();
|
||||||
|
m_process = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_process = new QProcess(this);
|
||||||
|
auto* proc = m_process;
|
||||||
|
|
||||||
|
connect(proc, &QProcess::readyReadStandardError, proc, [proc]() {
|
||||||
|
qWarning(transcodeLog).noquote()
|
||||||
|
<< "ffmpeg:" << QString::fromUtf8(proc->readAllStandardError());
|
||||||
|
});
|
||||||
|
|
||||||
|
connect(
|
||||||
|
proc,
|
||||||
|
&QProcess::finished,
|
||||||
|
this,
|
||||||
|
[this, proc, dst](int code, QProcess::ExitStatus status) {
|
||||||
|
const bool ok = code == 0 && status == QProcess::NormalExit &&
|
||||||
|
QFile::exists(dst);
|
||||||
|
|
||||||
|
qWarning(transcodeLog)
|
||||||
|
<< "WallpaperService: transcode finished, exit" << code
|
||||||
|
<< "status" << int(status) << "exists" << QFile::exists(dst)
|
||||||
|
<< "->" << dst;
|
||||||
|
|
||||||
|
if (proc == m_process) m_process = nullptr;
|
||||||
|
proc->deleteLater();
|
||||||
|
|
||||||
|
emit transcoded(dst, ok);
|
||||||
|
});
|
||||||
|
|
||||||
|
qWarning(transcodeLog) << "WallpaperService: transcoding" << src << "->"
|
||||||
|
<< dst;
|
||||||
|
|
||||||
|
proc->setProgram("nice");
|
||||||
|
proc->setArguments({
|
||||||
|
"-n",
|
||||||
|
"19",
|
||||||
|
"ffmpeg",
|
||||||
|
"-hide_banner",
|
||||||
|
"-loglevel",
|
||||||
|
"error",
|
||||||
|
"-y",
|
||||||
|
"-threads",
|
||||||
|
"2",
|
||||||
|
"-i",
|
||||||
|
src,
|
||||||
|
"-map",
|
||||||
|
"0:v:0",
|
||||||
|
"-vf",
|
||||||
|
"scale=in_range=pc:out_range=tv:out_color_matrix=bt709",
|
||||||
|
"-colorspace",
|
||||||
|
"bt709",
|
||||||
|
"-color_primaries",
|
||||||
|
"bt709",
|
||||||
|
"-color_trc",
|
||||||
|
"bt709",
|
||||||
|
"-color_range",
|
||||||
|
"tv",
|
||||||
|
"-c:v",
|
||||||
|
"libsvtav1",
|
||||||
|
"-preset",
|
||||||
|
"6",
|
||||||
|
"-crf",
|
||||||
|
"20",
|
||||||
|
"-pix_fmt",
|
||||||
|
"yuv420p10le",
|
||||||
|
"-an",
|
||||||
|
dst,
|
||||||
|
});
|
||||||
|
|
||||||
|
proc->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ZShell::wallpaper
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
class QProcess;
|
||||||
|
|
||||||
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
|
class VideoTranscoder : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit VideoTranscoder(QObject* parent = nullptr);
|
||||||
|
|
||||||
|
[[nodiscard]] QString cacheDir() const;
|
||||||
|
[[nodiscard]] QString transcodeTarget(const QString& path) const;
|
||||||
|
[[nodiscard]] bool needsTranscode(const QString& path) const;
|
||||||
|
|
||||||
|
void start(const QString& src, const QString& dst);
|
||||||
|
|
||||||
|
static bool isHardwareCodec(const QString& codec);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void transcoded(const QString& dst, bool ok);
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] QString codecName(const QString& path) const;
|
||||||
|
|
||||||
|
QProcess* m_process = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZShell::wallpaper
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
#include "wallpapercache.hpp"
|
||||||
|
|
||||||
|
#include <QCryptographicHash>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QImageReader>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QSqlDatabase>
|
||||||
|
#include <QSqlError>
|
||||||
|
#include <QSqlQuery>
|
||||||
|
#include <QThread>
|
||||||
|
|
||||||
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
QString connectionName() {
|
||||||
|
return QStringLiteral("wallpaper-cache-%1")
|
||||||
|
.arg(quintptr(QThread::currentThreadId()));
|
||||||
|
}
|
||||||
|
|
||||||
|
QSqlDatabase openDatabase(const QString& databasePath) {
|
||||||
|
const QString connection = connectionName();
|
||||||
|
|
||||||
|
QSqlDatabase database =
|
||||||
|
QSqlDatabase::contains(connection)
|
||||||
|
? QSqlDatabase::database(connection, false)
|
||||||
|
: QSqlDatabase::addDatabase("QSQLITE", connection);
|
||||||
|
|
||||||
|
if (database.databaseName() != databasePath) {
|
||||||
|
if (database.isOpen()) database.close();
|
||||||
|
database.setDatabaseName(databasePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!database.isOpen() && !database.open()) return {};
|
||||||
|
return database;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
void WallpaperCache::setDbPath(const QString& path) {
|
||||||
|
std::lock_guard<std::mutex> lock(m_mtx);
|
||||||
|
m_dbPath = path;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WallpaperCache::dbPath() const {
|
||||||
|
std::lock_guard<std::mutex> lock(m_mtx);
|
||||||
|
return m_dbPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray WallpaperCache::cachedBlob(const QString& path) const {
|
||||||
|
if (path.isEmpty()) return {};
|
||||||
|
|
||||||
|
const QString databasePath = dbPath();
|
||||||
|
if (databasePath.isEmpty()) return {};
|
||||||
|
|
||||||
|
const QFileInfo info(path);
|
||||||
|
|
||||||
|
QSqlDatabase database = openDatabase(databasePath);
|
||||||
|
if (!database.isOpen()) return {};
|
||||||
|
|
||||||
|
QSqlQuery query(database);
|
||||||
|
query.prepare("SELECT cache, size, mtime FROM wallpapers WHERE path = ?");
|
||||||
|
query.addBindValue(info.absoluteFilePath());
|
||||||
|
if (!query.exec() || !query.next()) return {};
|
||||||
|
|
||||||
|
const QByteArray blob = query.value(0).toByteArray();
|
||||||
|
if (blob.isEmpty()) return {};
|
||||||
|
|
||||||
|
const qint64 mtime = info.lastModified().toMSecsSinceEpoch() / 1000;
|
||||||
|
if (query.value(1).toLongLong() != info.size() ||
|
||||||
|
query.value(2).toLongLong() != mtime) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage WallpaperCache::decodeImage(
|
||||||
|
const QString& path, const QSize& decodeSize) const {
|
||||||
|
if (path.isEmpty()) return {};
|
||||||
|
|
||||||
|
QImageReader probe(path);
|
||||||
|
const QSize source = probe.size();
|
||||||
|
QSize required = source.isEmpty() ? decodeSize : source;
|
||||||
|
if (!source.isEmpty() && !decodeSize.isEmpty()) {
|
||||||
|
const QSize cover =
|
||||||
|
source.scaled(decodeSize, Qt::KeepAspectRatioByExpanding);
|
||||||
|
if (cover.width() <= source.width() &&
|
||||||
|
cover.height() <= source.height())
|
||||||
|
required = cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QByteArray blob = cachedBlob(path);
|
||||||
|
if (!blob.isEmpty()) {
|
||||||
|
QImage image = QImage::fromData(blob, "JPEG");
|
||||||
|
if (!image.isNull() && image.width() >= required.width() &&
|
||||||
|
image.height() >= required.height()) {
|
||||||
|
return image.convertToFormat(QImage::Format_RGBA8888);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QImageReader reader(path);
|
||||||
|
reader.setAutoTransform(true);
|
||||||
|
|
||||||
|
if (!decodeSize.isEmpty() && !source.isEmpty())
|
||||||
|
reader.setScaledSize(
|
||||||
|
source.scaled(decodeSize, Qt::KeepAspectRatioByExpanding));
|
||||||
|
|
||||||
|
QImage image = reader.read();
|
||||||
|
if (image.isNull()) return {};
|
||||||
|
|
||||||
|
return image.convertToFormat(QImage::Format_RGBA8888);
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray WallpaperCache::posterBlob(const QString& path) const {
|
||||||
|
if (path.isEmpty()) return {};
|
||||||
|
|
||||||
|
const QString databasePath = dbPath();
|
||||||
|
if (databasePath.isEmpty()) return {};
|
||||||
|
|
||||||
|
const QFileInfo info(path);
|
||||||
|
|
||||||
|
QSqlDatabase database = openDatabase(databasePath);
|
||||||
|
if (!database.isOpen()) return {};
|
||||||
|
|
||||||
|
QSqlQuery query(database);
|
||||||
|
query.prepare("SELECT poster, size, mtime FROM wallpapers WHERE path = ?");
|
||||||
|
query.addBindValue(info.absoluteFilePath());
|
||||||
|
if (!query.exec() || !query.next()) return {};
|
||||||
|
|
||||||
|
const QByteArray blob = query.value(0).toByteArray();
|
||||||
|
if (blob.isEmpty()) return {};
|
||||||
|
|
||||||
|
const qint64 mtime = info.lastModified().toMSecsSinceEpoch() / 1000;
|
||||||
|
if (query.value(1).toLongLong() != info.size() ||
|
||||||
|
query.value(2).toLongLong() != mtime) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantMap WallpaperCache::cropsForPath(const QString& path) const {
|
||||||
|
if (path.isEmpty()) return {};
|
||||||
|
|
||||||
|
const QString databasePath = dbPath();
|
||||||
|
if (databasePath.isEmpty()) return {};
|
||||||
|
|
||||||
|
const QFileInfo info(path);
|
||||||
|
|
||||||
|
QSqlDatabase database = openDatabase(databasePath);
|
||||||
|
if (!database.isOpen()) return {};
|
||||||
|
|
||||||
|
QSqlQuery query(database);
|
||||||
|
query.prepare("SELECT crop FROM wallpapers WHERE path = ?");
|
||||||
|
query.addBindValue(info.absoluteFilePath());
|
||||||
|
if (!query.exec() || !query.next()) return {};
|
||||||
|
|
||||||
|
const QByteArray json = query.value(0).toByteArray();
|
||||||
|
if (json.isEmpty()) return {};
|
||||||
|
|
||||||
|
const auto doc = QJsonDocument::fromJson(json);
|
||||||
|
if (!doc.isObject()) return {};
|
||||||
|
|
||||||
|
return doc.object().toVariantMap();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ZShell::wallpaper
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QImage>
|
||||||
|
#include <QSize>
|
||||||
|
#include <QString>
|
||||||
|
#include <QVariantMap>
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
|
class WallpaperCache {
|
||||||
|
public:
|
||||||
|
void setDbPath(const QString& path);
|
||||||
|
[[nodiscard]] QString dbPath() const;
|
||||||
|
|
||||||
|
[[nodiscard]] QImage decodeImage(
|
||||||
|
const QString& path, const QSize& decodeSize) const;
|
||||||
|
|
||||||
|
[[nodiscard]] QByteArray posterBlob(const QString& path) const;
|
||||||
|
|
||||||
|
[[nodiscard]] QVariantMap cropsForPath(const QString& path) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
[[nodiscard]] QByteArray cachedBlob(const QString& path) const;
|
||||||
|
|
||||||
|
mutable std::mutex m_mtx;
|
||||||
|
QString m_dbPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace ZShell::wallpaper
|
||||||
+2
-2
@@ -8,7 +8,7 @@
|
|||||||
#include <QtConcurrent>
|
#include <QtConcurrent>
|
||||||
#include <QQuickWindow>
|
#include <QQuickWindow>
|
||||||
|
|
||||||
namespace ZShell::internal {
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
QThreadPool* WallpaperImage::loadPool() {
|
QThreadPool* WallpaperImage::loadPool() {
|
||||||
static QThreadPool* pool = [] {
|
static QThreadPool* pool = [] {
|
||||||
@@ -352,4 +352,4 @@ QSGNode* WallpaperImage::updatePaintNode(
|
|||||||
return m_rootNode;
|
return m_rootNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::wallpaper
|
||||||
+2
-2
@@ -14,7 +14,7 @@
|
|||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
|
||||||
namespace ZShell::internal {
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
class WallpaperImage : public QQuickItem {
|
class WallpaperImage : public QQuickItem {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -144,4 +144,4 @@ class WallpaperImage : public QQuickItem {
|
|||||||
QSGImageNode* m_oldImageNode = nullptr;
|
QSGImageNode* m_oldImageNode = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::wallpaper
|
||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
#include "wallpaperobject.hpp"
|
#include "wallpaperobject.hpp"
|
||||||
|
|
||||||
namespace ZShell::internal {
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
WallpaperObject::WallpaperObject(QObject* parent) : QObject(parent) {}
|
WallpaperObject::WallpaperObject(QObject* parent) : QObject(parent) {}
|
||||||
|
|
||||||
@@ -118,4 +118,4 @@ void WallpaperObject::push() {
|
|||||||
m_service->setCrop(m_screen, crop);
|
m_service->setCrop(m_screen, crop);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::wallpaper
|
||||||
+2
-2
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
#include "wallpaperservice.hpp"
|
#include "wallpaperservice.hpp"
|
||||||
|
|
||||||
namespace ZShell::internal {
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
class WallpaperObject : public QObject {
|
class WallpaperObject : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
@@ -83,4 +83,4 @@ class WallpaperObject : public QObject {
|
|||||||
bool m_pushEnabled = true;
|
bool m_pushEnabled = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::wallpaper
|
||||||
+169
-410
@@ -46,25 +46,41 @@
|
|||||||
|
|
||||||
#include "config/background.hpp"
|
#include "config/background.hpp"
|
||||||
#include "config/rootnodes.hpp"
|
#include "config/rootnodes.hpp"
|
||||||
|
#include "config/services.hpp"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
namespace ZShell::internal {
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
Q_LOGGING_CATEGORY(wpLog, "ZShell.Internal.wallpaper")
|
Q_LOGGING_CATEGORY(wpLog, "ZShell.Internal.wallpaper")
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
#ifdef ZHAVE_VIDEO
|
|
||||||
const bool kMpvMode = qEnvironmentVariableIsSet("ZBAR_WALLPAPER_MPV");
|
const bool kMpvMode = qEnvironmentVariableIsSet("ZBAR_WALLPAPER_MPV");
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef ZHAVE_VIDEO
|
|
||||||
const bool kGlesMode = qEnvironmentVariableIsSet("ZBAR_WALLPAPER_GLES") ||
|
const bool kGlesMode = qEnvironmentVariableIsSet("ZBAR_WALLPAPER_GLES") ||
|
||||||
kMpvMode;
|
kMpvMode;
|
||||||
#else
|
|
||||||
const bool kGlesMode = qEnvironmentVariableIsSet("ZBAR_WALLPAPER_GLES");
|
VideoBackend backendFromConfig() {
|
||||||
#endif
|
const auto type =
|
||||||
|
ZShell::config::ConfigSingleton::instance()->services()->gpuType();
|
||||||
|
switch (type) {
|
||||||
|
case ZShell::config::GpuType::Nvidia:
|
||||||
|
return VideoBackend::Nvidia;
|
||||||
|
case ZShell::config::GpuType::Generic:
|
||||||
|
return VideoBackend::Vaapi;
|
||||||
|
case ZShell::config::GpuType::None:
|
||||||
|
return VideoBackend::None;
|
||||||
|
case ZShell::config::GpuType::Auto:
|
||||||
|
default:
|
||||||
|
return VideoBackend::Auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void logSelectedBackend() {
|
||||||
|
qInfo(wpLog) << "WallpaperService: video backend ="
|
||||||
|
<< resolveBackendName(backendFromConfig());
|
||||||
|
}
|
||||||
|
|
||||||
const char* shaderHeader(bool fragment) {
|
const char* shaderHeader(bool fragment) {
|
||||||
if (kGlesMode)
|
if (kGlesMode)
|
||||||
@@ -213,6 +229,22 @@ WallpaperService::WallpaperService(QObject* parent) : QObject(parent) {
|
|||||||
|
|
||||||
wake();
|
wake();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
m_transcoder = new VideoTranscoder(this);
|
||||||
|
connect(
|
||||||
|
m_transcoder,
|
||||||
|
&VideoTranscoder::transcoded,
|
||||||
|
this,
|
||||||
|
[this](const QString& dst, bool ok) {
|
||||||
|
if (ok) applyPath(dst);
|
||||||
|
});
|
||||||
|
|
||||||
|
auto* gpuConfig = ZShell::config::ConfigSingleton::instance()->services();
|
||||||
|
logSelectedBackend();
|
||||||
|
QObject::connect(
|
||||||
|
gpuConfig, &ZShell::config::Services::gpuTypeChanged, this, [] {
|
||||||
|
logSelectedBackend();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
WallpaperService::~WallpaperService() {
|
WallpaperService::~WallpaperService() {
|
||||||
@@ -260,11 +292,11 @@ WallpaperService::~WallpaperService() {
|
|||||||
if (m_captureTex) glDeleteTextures(1, &m_captureTex);
|
if (m_captureTex) glDeleteTextures(1, &m_captureTex);
|
||||||
if (m_captureFbo) glDeleteFramebuffers(1, &m_captureFbo);
|
if (m_captureFbo) glDeleteFramebuffers(1, &m_captureFbo);
|
||||||
if (m_posterTex) glDeleteTextures(1, &m_posterTex);
|
if (m_posterTex) glDeleteTextures(1, &m_posterTex);
|
||||||
#ifdef ZHAVE_VIDEO
|
#ifdef ZHAVE_FFMPEG
|
||||||
if (m_player) {
|
if (m_decoder) {
|
||||||
m_player->stop();
|
m_decoder->stop();
|
||||||
m_player->deleteTexture();
|
m_decoder->deleteTexture();
|
||||||
m_player.reset();
|
m_decoder.reset();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -303,6 +335,8 @@ void WallpaperService::setDbPath(const QString& path) {
|
|||||||
m_forceDecode = true;
|
m_forceDecode = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_cache.setDbPath(path);
|
||||||
|
|
||||||
emit dbPathChanged();
|
emit dbPathChanged();
|
||||||
wake();
|
wake();
|
||||||
}
|
}
|
||||||
@@ -310,8 +344,8 @@ void WallpaperService::setDbPath(const QString& path) {
|
|||||||
void WallpaperService::setPath(const QString& path) {
|
void WallpaperService::setPath(const QString& path) {
|
||||||
if (path.isEmpty() || path == this->path()) return;
|
if (path.isEmpty() || path == this->path()) return;
|
||||||
|
|
||||||
if (isVideoPath(path) && needsTranscode(path)) {
|
if (isVideoPath(path) && m_transcoder->needsTranscode(path)) {
|
||||||
const QString dst = transcodeTarget(path);
|
const QString dst = m_transcoder->transcodeTarget(path);
|
||||||
|
|
||||||
if (QFile::exists(dst)) {
|
if (QFile::exists(dst)) {
|
||||||
applyPath(dst);
|
applyPath(dst);
|
||||||
@@ -319,7 +353,7 @@ void WallpaperService::setPath(const QString& path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
applyPath(path);
|
applyPath(path);
|
||||||
transcodeVideo(path, dst);
|
m_transcoder->start(path, dst);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -342,8 +376,8 @@ void WallpaperService::applyPath(const QString& path) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ZHAVE_VIDEO
|
#ifdef ZHAVE_FFMPEG
|
||||||
const bool videoBackend = m_mpv != nullptr || m_player != nullptr;
|
const bool videoBackend = m_mpv != nullptr || m_decoder != nullptr;
|
||||||
#else
|
#else
|
||||||
const bool videoBackend = m_mpv != nullptr;
|
const bool videoBackend = m_mpv != nullptr;
|
||||||
#endif
|
#endif
|
||||||
@@ -357,19 +391,19 @@ void WallpaperService::applyPath(const QString& path) {
|
|||||||
m_baseIsVideo = true;
|
m_baseIsVideo = true;
|
||||||
m_baseIsBlack = false;
|
m_baseIsBlack = false;
|
||||||
m_baseImage = QImage();
|
m_baseImage = QImage();
|
||||||
m_baseCrops = cropsForPath(previousPath);
|
m_baseCrops = m_cache.cropsForPath(previousPath);
|
||||||
} else if (wasImage) {
|
} else if (wasImage) {
|
||||||
m_needCaptureBase = true;
|
m_needCaptureBase = true;
|
||||||
m_baseIsVideo = false;
|
m_baseIsVideo = false;
|
||||||
m_baseIsBlack = false;
|
m_baseIsBlack = false;
|
||||||
m_baseImage = m_image;
|
m_baseImage = m_image;
|
||||||
m_baseCrops = cropsForPath(previousPath);
|
m_baseCrops = m_cache.cropsForPath(previousPath);
|
||||||
} else {
|
} else {
|
||||||
m_needCaptureBase = false;
|
m_needCaptureBase = false;
|
||||||
m_baseIsVideo = false;
|
m_baseIsVideo = false;
|
||||||
m_baseIsBlack = m_baseTex == 0;
|
m_baseIsBlack = m_baseTex == 0;
|
||||||
m_baseImage = QImage();
|
m_baseImage = QImage();
|
||||||
m_baseCrops = cropsForPath(previousPath);
|
m_baseCrops = m_cache.cropsForPath(previousPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,7 +417,7 @@ void WallpaperService::applyPath(const QString& path) {
|
|||||||
TransitionLayer& newest = m_transitions.back();
|
TransitionLayer& newest = m_transitions.back();
|
||||||
newest.tex = m_texture;
|
newest.tex = m_texture;
|
||||||
newest.image = m_image;
|
newest.image = m_image;
|
||||||
newest.crops = cropsForPath(previousPath);
|
newest.crops = m_cache.cropsForPath(previousPath);
|
||||||
newest.ready = true;
|
newest.ready = true;
|
||||||
m_texture = 0;
|
m_texture = 0;
|
||||||
m_image = QImage();
|
m_image = QImage();
|
||||||
@@ -393,7 +427,7 @@ void WallpaperService::applyPath(const QString& path) {
|
|||||||
m_transitions.back().isVideo)
|
m_transitions.back().isVideo)
|
||||||
m_collapseVideo = true;
|
m_collapseVideo = true;
|
||||||
|
|
||||||
m_crops = cropsForPath(path);
|
m_crops = m_cache.cropsForPath(path);
|
||||||
|
|
||||||
m_isVideo = video;
|
m_isVideo = video;
|
||||||
|
|
||||||
@@ -424,153 +458,6 @@ void WallpaperService::applyPath(const QString& path) {
|
|||||||
queueAll();
|
queueAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WallpaperService::isHardwareCodec(const QString& codec) {
|
|
||||||
static const QStringList hardware{
|
|
||||||
"av1",
|
|
||||||
"hevc",
|
|
||||||
"h264",
|
|
||||||
"vp9",
|
|
||||||
"vp8",
|
|
||||||
"vc1",
|
|
||||||
"mpeg2",
|
|
||||||
"mpeg2video",
|
|
||||||
"mpeg4",
|
|
||||||
"mjpeg",
|
|
||||||
};
|
|
||||||
|
|
||||||
return hardware.contains(codec);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString WallpaperService::codecName(const QString& path) const {
|
|
||||||
QProcess probe;
|
|
||||||
probe.start(
|
|
||||||
"ffprobe",
|
|
||||||
{
|
|
||||||
"-v",
|
|
||||||
"error",
|
|
||||||
"-select_streams",
|
|
||||||
"v:0",
|
|
||||||
"-show_entries",
|
|
||||||
"stream=codec_name",
|
|
||||||
"-of",
|
|
||||||
"default=nw=1:nk=1",
|
|
||||||
path,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!probe.waitForFinished(2000) || probe.exitCode() != 0) {
|
|
||||||
qWarning(wpLog) << "WallpaperService: ffprobe failed for" << path
|
|
||||||
<< "(exit" << probe.exitCode() << ")";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const QString codec =
|
|
||||||
QString::fromUtf8(probe.readAllStandardOutput()).trimmed().toLower();
|
|
||||||
return codec;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString WallpaperService::cacheDir() const {
|
|
||||||
return QDir::homePath() + "/.cache/zshell/wallpaper-videos";
|
|
||||||
}
|
|
||||||
|
|
||||||
QString WallpaperService::transcodeTarget(const QString& path) const {
|
|
||||||
const QFileInfo info(path);
|
|
||||||
|
|
||||||
const QByteArray key =
|
|
||||||
path.toUtf8() + '|' + QByteArray::number(info.size()) + '|' +
|
|
||||||
QByteArray::number(info.lastModified().toMSecsSinceEpoch());
|
|
||||||
|
|
||||||
const QString hash = QString::fromUtf8(
|
|
||||||
QCryptographicHash::hash(key, QCryptographicHash::Sha1).toHex());
|
|
||||||
|
|
||||||
return cacheDir() + '/' + hash + ".av1.mkv";
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WallpaperService::needsTranscode(const QString& path) const {
|
|
||||||
if (path.startsWith(cacheDir())) return false;
|
|
||||||
|
|
||||||
const QString codec = codecName(path);
|
|
||||||
if (codec.isEmpty()) return false;
|
|
||||||
|
|
||||||
return !isHardwareCodec(codec);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WallpaperService::transcodeVideo(const QString& src, const QString& dst) {
|
|
||||||
QDir().mkpath(cacheDir());
|
|
||||||
|
|
||||||
if (m_transcode) {
|
|
||||||
m_transcode->kill();
|
|
||||||
m_transcode->deleteLater();
|
|
||||||
m_transcode = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_transcode = new QProcess(this);
|
|
||||||
auto* proc = m_transcode;
|
|
||||||
|
|
||||||
connect(proc, &QProcess::readyReadStandardError, proc, [proc]() {
|
|
||||||
qWarning(wpLog).noquote()
|
|
||||||
<< "ffmpeg:" << QString::fromUtf8(proc->readAllStandardError());
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(
|
|
||||||
proc,
|
|
||||||
&QProcess::finished,
|
|
||||||
this,
|
|
||||||
[this, proc, dst](int code, QProcess::ExitStatus status) {
|
|
||||||
const bool ok = code == 0 && status == QProcess::NormalExit &&
|
|
||||||
QFile::exists(dst);
|
|
||||||
|
|
||||||
qWarning(wpLog) << "WallpaperService: transcode finished, exit"
|
|
||||||
<< code << "status" << int(status) << "exists"
|
|
||||||
<< QFile::exists(dst) << "->" << dst;
|
|
||||||
|
|
||||||
if (proc == m_transcode) m_transcode = nullptr;
|
|
||||||
proc->deleteLater();
|
|
||||||
|
|
||||||
if (ok) applyPath(dst);
|
|
||||||
});
|
|
||||||
|
|
||||||
qWarning(wpLog) << "WallpaperService: transcoding" << src << "->" << dst;
|
|
||||||
|
|
||||||
proc->setProgram("nice");
|
|
||||||
proc->setArguments({
|
|
||||||
"-n",
|
|
||||||
"19",
|
|
||||||
"ffmpeg",
|
|
||||||
"-hide_banner",
|
|
||||||
"-loglevel",
|
|
||||||
"error",
|
|
||||||
"-y",
|
|
||||||
"-threads",
|
|
||||||
"2",
|
|
||||||
"-i",
|
|
||||||
src,
|
|
||||||
"-map",
|
|
||||||
"0:v:0",
|
|
||||||
"-vf",
|
|
||||||
"scale=in_range=pc:out_range=tv:out_color_matrix=bt709",
|
|
||||||
"-colorspace",
|
|
||||||
"bt709",
|
|
||||||
"-color_primaries",
|
|
||||||
"bt709",
|
|
||||||
"-color_trc",
|
|
||||||
"bt709",
|
|
||||||
"-color_range",
|
|
||||||
"tv",
|
|
||||||
"-c:v",
|
|
||||||
"libsvtav1",
|
|
||||||
"-preset",
|
|
||||||
"6",
|
|
||||||
"-crf",
|
|
||||||
"20",
|
|
||||||
"-pix_fmt",
|
|
||||||
"yuv420p10le",
|
|
||||||
"-an",
|
|
||||||
dst,
|
|
||||||
});
|
|
||||||
|
|
||||||
proc->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void WallpaperService::addScreen(const QString& screen) {
|
void WallpaperService::addScreen(const QString& screen) {
|
||||||
{
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||||
@@ -650,14 +537,14 @@ void WallpaperService::startVideo(const QString& path) {
|
|||||||
m_videoReady.store(false, std::memory_order_relaxed);
|
m_videoReady.store(false, std::memory_order_relaxed);
|
||||||
m_videoLoaded.store(false, std::memory_order_relaxed);
|
m_videoLoaded.store(false, std::memory_order_relaxed);
|
||||||
|
|
||||||
#ifdef ZHAVE_VIDEO
|
#ifdef ZHAVE_FFMPEG
|
||||||
if (m_usePlayer && !kMpvMode) {
|
if (m_useDecoder && !kMpvMode) {
|
||||||
if (startPlayerVideo(path)) return;
|
if (startDecoderVideo(path)) return;
|
||||||
|
|
||||||
qWarning(wpLog) << "WallpaperService: the GPU video player failed, "
|
qWarning(wpLog) << "WallpaperService: the GPU video player failed, "
|
||||||
"falling back to mpv";
|
"falling back to mpv";
|
||||||
m_usePlayer = false;
|
m_useDecoder = false;
|
||||||
m_player.reset();
|
m_decoder.reset();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@@ -975,6 +862,16 @@ void WallpaperService::present() {
|
|||||||
|
|
||||||
if (m_context == EGL_NO_CONTEXT) return;
|
if (m_context == EGL_NO_CONTEXT) return;
|
||||||
|
|
||||||
|
if (!m_orphanTextures.empty()) {
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||||
|
if (eglMakeCurrent(
|
||||||
|
m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, m_context)) {
|
||||||
|
for (uint32_t tex : m_orphanTextures)
|
||||||
|
glDeleteTextures(1, &tex);
|
||||||
|
m_orphanTextures.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
EGLSurface importTarget = EGL_NO_SURFACE;
|
EGLSurface importTarget = EGL_NO_SURFACE;
|
||||||
for (auto* surface : surfaces)
|
for (auto* surface : surfaces)
|
||||||
if (surface->eglSurface() != EGL_NO_SURFACE) {
|
if (surface->eglSurface() != EGL_NO_SURFACE) {
|
||||||
@@ -991,16 +888,63 @@ void WallpaperService::present() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!m_programsCompiled) {
|
||||||
|
m_programsCompiled = true;
|
||||||
|
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||||
|
if (m_program == 0) m_program = createProgram();
|
||||||
|
if (m_videoProgram == 0) m_videoProgram = createVideoProgram();
|
||||||
|
if (m_neutralProgram.id == 0)
|
||||||
|
m_neutralProgram =
|
||||||
|
programFor(QStringLiteral("wallpaper_trans_fade.frag"));
|
||||||
|
for (const QString& file : transitionFilesList())
|
||||||
|
(void)programFor(file);
|
||||||
|
}
|
||||||
|
|
||||||
if (videoReload) {
|
if (videoReload) {
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||||
m_videoReload = false;
|
m_videoReload = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_orphanTextures.empty()) {
|
{
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||||
|
|
||||||
for (uint32_t tex : m_orphanTextures)
|
for (uint32_t tex : m_orphanTextures)
|
||||||
glDeleteTextures(1, &tex);
|
glDeleteTextures(1, &tex);
|
||||||
m_orphanTextures.clear();
|
m_orphanTextures.clear();
|
||||||
|
|
||||||
|
const bool idle = m_transitions.empty() && !m_needCaptureBase &&
|
||||||
|
!m_collapseVideo && !m_isVideo && m_mpv == nullptr
|
||||||
|
#ifdef ZHAVE_FFMPEG
|
||||||
|
&& m_decoder == nullptr
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
if (idle) {
|
||||||
|
if (m_baseTex) glDeleteTextures(1, &m_baseTex);
|
||||||
|
m_baseTex = 0;
|
||||||
|
m_baseSize = QSize();
|
||||||
|
if (m_baseFbo) glDeleteFramebuffers(1, &m_baseFbo);
|
||||||
|
m_baseFbo = 0;
|
||||||
|
m_baseReady = false;
|
||||||
|
|
||||||
|
if (m_accumA) glDeleteTextures(1, &m_accumA);
|
||||||
|
if (m_accumB) glDeleteTextures(1, &m_accumB);
|
||||||
|
m_accumA = 0;
|
||||||
|
m_accumB = 0;
|
||||||
|
if (m_accumFbo) glDeleteFramebuffers(1, &m_accumFbo);
|
||||||
|
m_accumFbo = 0;
|
||||||
|
m_accumSize = QSize();
|
||||||
|
|
||||||
|
if (m_captureTex) glDeleteTextures(1, &m_captureTex);
|
||||||
|
m_captureTex = 0;
|
||||||
|
if (m_captureFbo) glDeleteFramebuffers(1, &m_captureFbo);
|
||||||
|
m_captureFbo = 0;
|
||||||
|
m_captureSize = QSize();
|
||||||
|
|
||||||
|
if (m_posterTex) glDeleteTextures(1, &m_posterTex);
|
||||||
|
m_posterTex = 0;
|
||||||
|
m_posterReady = false;
|
||||||
|
m_posterChanged = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool holdVideoForBase = (m_needCaptureBase && m_baseIsVideo) ||
|
const bool holdVideoForBase = (m_needCaptureBase && m_baseIsVideo) ||
|
||||||
@@ -1016,19 +960,19 @@ void WallpaperService::present() {
|
|||||||
} else if (m_mpv != nullptr) {
|
} else if (m_mpv != nullptr) {
|
||||||
teardownMpv();
|
teardownMpv();
|
||||||
}
|
}
|
||||||
#ifdef ZHAVE_VIDEO
|
#ifdef ZHAVE_FFMPEG
|
||||||
else if (m_player != nullptr) {
|
else if (m_decoder != nullptr) {
|
||||||
m_player->deleteTexture();
|
m_decoder->deleteTexture();
|
||||||
stopPlayerVideo();
|
stopDecoderVideo();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!holdVideoForBase) applyVideoBackendSwitch();
|
if (!holdVideoForBase) applyVideoBackendSwitch();
|
||||||
|
|
||||||
#ifdef ZHAVE_VIDEO
|
#ifdef ZHAVE_FFMPEG
|
||||||
const bool videoActive =
|
const bool videoActive =
|
||||||
(m_mpv != nullptr || m_player != nullptr) &&
|
(m_mpv != nullptr || m_decoder != nullptr) &&
|
||||||
(m_isVideo || m_baseIsVideo || m_collapseVideo || m_promoteOldest);
|
(m_isVideo || m_baseIsVideo || m_collapseVideo || m_promoteOldest);
|
||||||
#else
|
#else
|
||||||
const bool videoActive =
|
const bool videoActive =
|
||||||
@@ -1038,8 +982,8 @@ void WallpaperService::present() {
|
|||||||
|
|
||||||
if (videoActive && m_mpv != nullptr) pumpMpvEvents();
|
if (videoActive && m_mpv != nullptr) pumpMpvEvents();
|
||||||
|
|
||||||
#ifdef ZHAVE_VIDEO
|
#ifdef ZHAVE_FFMPEG
|
||||||
if (videoActive && m_player != nullptr) m_player->importCurrentFrame();
|
if (videoActive && m_decoder != nullptr) m_decoder->importCurrentFrame();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
for (auto* surface : surfaces)
|
for (auto* surface : surfaces)
|
||||||
@@ -1424,7 +1368,7 @@ void WallpaperService::ensureCapture(const QSize& size) {
|
|||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef ZHAVE_VIDEO
|
#ifdef ZHAVE_FFMPEG
|
||||||
uint32_t WallpaperService::createVideoProgram() {
|
uint32_t WallpaperService::createVideoProgram() {
|
||||||
const QByteArray vsSrc = shaderSource("wallpaper.vert", false);
|
const QByteArray vsSrc = shaderSource("wallpaper.vert", false);
|
||||||
const QByteArray fsSrc = shaderSource("wallpaper_video.frag", true);
|
const QByteArray fsSrc = shaderSource("wallpaper_video.frag", true);
|
||||||
@@ -1518,30 +1462,32 @@ void WallpaperService::drawVideoTexture(
|
|||||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WallpaperService::startPlayerVideo(const QString& path) {
|
bool WallpaperService::startDecoderVideo(const QString& path) {
|
||||||
if (!m_player) m_player = std::make_unique<VideoPlayer>(m_eglDisplay);
|
if (!m_decoder)
|
||||||
|
m_decoder = createVideoDecoder(m_eglDisplay, backendFromConfig());
|
||||||
|
if (!m_decoder) return false;
|
||||||
|
|
||||||
m_player->setReadyCallback([this]() {
|
m_decoder->setReadyCallback([this]() {
|
||||||
m_videoLoaded.store(true, std::memory_order_relaxed);
|
m_videoLoaded.store(true, std::memory_order_relaxed);
|
||||||
m_videoReady.store(true, std::memory_order_relaxed);
|
m_videoReady.store(true, std::memory_order_relaxed);
|
||||||
queueAll();
|
queueAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
m_player->setFrameCallback([this]() {
|
m_decoder->setFrameCallback([this]() {
|
||||||
m_videoReady.store(true, std::memory_order_relaxed);
|
m_videoReady.store(true, std::memory_order_relaxed);
|
||||||
queueAll();
|
queueAll();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!m_player->start(path)) return false;
|
if (!m_decoder->start(path)) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void WallpaperService::stopPlayerVideo() {
|
void WallpaperService::stopDecoderVideo() {
|
||||||
if (!m_player) return;
|
if (!m_decoder) return;
|
||||||
|
|
||||||
m_player->stop();
|
m_decoder->stop();
|
||||||
m_player.reset();
|
m_decoder.reset();
|
||||||
|
|
||||||
m_videoReady.store(false, std::memory_order_relaxed);
|
m_videoReady.store(false, std::memory_order_relaxed);
|
||||||
m_videoLoaded.store(false, std::memory_order_relaxed);
|
m_videoLoaded.store(false, std::memory_order_relaxed);
|
||||||
@@ -1556,19 +1502,19 @@ uint32_t WallpaperService::renderVideoIntoTexture(const QSize& size) {
|
|||||||
glViewport(0, 0, size.width(), size.height());
|
glViewport(0, 0, size.width(), size.height());
|
||||||
glDisable(GL_BLEND);
|
glDisable(GL_BLEND);
|
||||||
|
|
||||||
#ifdef ZHAVE_VIDEO
|
#ifdef ZHAVE_FFMPEG
|
||||||
if (m_player) {
|
if (m_decoder) {
|
||||||
if (m_player->textureId() != 0) {
|
if (m_decoder->textureId() != 0) {
|
||||||
if (m_videoProgram == 0) m_videoProgram = createVideoProgram();
|
if (m_videoProgram == 0) m_videoProgram = createVideoProgram();
|
||||||
|
|
||||||
if (m_videoProgram != 0) {
|
if (m_videoProgram != 0) {
|
||||||
glUseProgram(m_videoProgram);
|
glUseProgram(m_videoProgram);
|
||||||
const QSize src = m_player->frameSize();
|
const QSize src = m_decoder->frameSize();
|
||||||
drawVideoTexture(
|
drawVideoTexture(
|
||||||
m_player->textureId(),
|
m_decoder->textureId(),
|
||||||
m_player->chromaTextureId(),
|
m_decoder->chromaTextureId(),
|
||||||
m_player->colorMatrix(),
|
m_decoder->colorMatrix(),
|
||||||
m_player->colorRange(),
|
m_decoder->colorRange(),
|
||||||
QRectF(0.0, 0.0, 1.0, 1.0),
|
QRectF(0.0, 0.0, 1.0, 1.0),
|
||||||
float(size.width()),
|
float(size.width()),
|
||||||
float(size.height()),
|
float(size.height()),
|
||||||
@@ -1659,193 +1605,6 @@ void WallpaperService::drawTexture(
|
|||||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray WallpaperService::cachedBlob(const QString& path) const {
|
|
||||||
if (path.isEmpty()) return {};
|
|
||||||
|
|
||||||
QString databasePath;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
|
||||||
databasePath = m_dbPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (databasePath.isEmpty()) return {};
|
|
||||||
|
|
||||||
const QFileInfo info(path);
|
|
||||||
|
|
||||||
const QString connection = QStringLiteral("wallpaper-cache-%1")
|
|
||||||
.arg(quintptr(QThread::currentThreadId()));
|
|
||||||
|
|
||||||
QSqlDatabase database =
|
|
||||||
QSqlDatabase::contains(connection)
|
|
||||||
? QSqlDatabase::database(connection, false)
|
|
||||||
: QSqlDatabase::addDatabase("QSQLITE", connection);
|
|
||||||
|
|
||||||
if (database.databaseName() != databasePath) {
|
|
||||||
if (database.isOpen()) database.close();
|
|
||||||
database.setDatabaseName(databasePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!database.isOpen() && !database.open()) return {};
|
|
||||||
|
|
||||||
QSqlQuery query(database);
|
|
||||||
query.prepare("SELECT cache, size, mtime FROM wallpapers WHERE path = ?");
|
|
||||||
query.addBindValue(info.absoluteFilePath());
|
|
||||||
if (!query.exec() || !query.next()) return {};
|
|
||||||
|
|
||||||
const QByteArray blob = query.value(0).toByteArray();
|
|
||||||
if (blob.isEmpty()) return {};
|
|
||||||
|
|
||||||
const qint64 mtime = info.lastModified().toMSecsSinceEpoch() / 1000;
|
|
||||||
if (query.value(1).toLongLong() != info.size() ||
|
|
||||||
query.value(2).toLongLong() != mtime) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return blob;
|
|
||||||
}
|
|
||||||
|
|
||||||
QByteArray WallpaperService::posterBlob(const QString& path) const {
|
|
||||||
if (path.isEmpty()) return {};
|
|
||||||
|
|
||||||
QString databasePath;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
|
||||||
databasePath = m_dbPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (databasePath.isEmpty()) {
|
|
||||||
qWarning(wpLog) << "WallpaperService: poster lookup, no database path";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const QFileInfo info(path);
|
|
||||||
|
|
||||||
const QString connection = QStringLiteral("wallpaper-cache-%1")
|
|
||||||
.arg(quintptr(QThread::currentThreadId()));
|
|
||||||
|
|
||||||
QSqlDatabase database =
|
|
||||||
QSqlDatabase::contains(connection)
|
|
||||||
? QSqlDatabase::database(connection, false)
|
|
||||||
: QSqlDatabase::addDatabase("QSQLITE", connection);
|
|
||||||
|
|
||||||
if (database.databaseName() != databasePath) {
|
|
||||||
if (database.isOpen()) database.close();
|
|
||||||
database.setDatabaseName(databasePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!database.isOpen() && !database.open()) {
|
|
||||||
qWarning(wpLog)
|
|
||||||
<< "WallpaperService: poster lookup, database could not open";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QSqlQuery query(database);
|
|
||||||
query.prepare("SELECT poster, size, mtime FROM wallpapers WHERE path = ?");
|
|
||||||
query.addBindValue(info.absoluteFilePath());
|
|
||||||
if (!query.exec() || !query.next()) {
|
|
||||||
qWarning(wpLog) << "WallpaperService: poster lookup, no row for" << path
|
|
||||||
<< query.lastError().text();
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const QByteArray blob = query.value(0).toByteArray();
|
|
||||||
if (blob.isEmpty()) {
|
|
||||||
qWarning(wpLog) << "WallpaperService: poster column empty for" << path;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
const qint64 mtime = info.lastModified().toMSecsSinceEpoch() / 1000;
|
|
||||||
if (query.value(1).toLongLong() != info.size() ||
|
|
||||||
query.value(2).toLongLong() != mtime) {
|
|
||||||
qWarning(wpLog) << "WallpaperService: poster stale for" << path;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return blob;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantMap WallpaperService::cropsForPath(const QString& path) const {
|
|
||||||
if (path.isEmpty()) return {};
|
|
||||||
|
|
||||||
QString databasePath;
|
|
||||||
{
|
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
|
||||||
databasePath = m_dbPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (databasePath.isEmpty()) return {};
|
|
||||||
|
|
||||||
const QFileInfo info(path);
|
|
||||||
|
|
||||||
const QString connection = QStringLiteral("wallpaper-cache-%1")
|
|
||||||
.arg(quintptr(QThread::currentThreadId()));
|
|
||||||
|
|
||||||
QSqlDatabase database =
|
|
||||||
QSqlDatabase::contains(connection)
|
|
||||||
? QSqlDatabase::database(connection, false)
|
|
||||||
: QSqlDatabase::addDatabase("QSQLITE", connection);
|
|
||||||
|
|
||||||
if (database.databaseName() != databasePath) {
|
|
||||||
if (database.isOpen()) database.close();
|
|
||||||
database.setDatabaseName(databasePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!database.isOpen() && !database.open()) return {};
|
|
||||||
|
|
||||||
QSqlQuery query(database);
|
|
||||||
query.prepare("SELECT crop FROM wallpapers WHERE path = ?");
|
|
||||||
query.addBindValue(info.absoluteFilePath());
|
|
||||||
if (!query.exec() || !query.next()) return {};
|
|
||||||
|
|
||||||
const QByteArray json = query.value(0).toByteArray();
|
|
||||||
if (json.isEmpty()) return {};
|
|
||||||
|
|
||||||
const auto doc = QJsonDocument::fromJson(json);
|
|
||||||
if (!doc.isObject()) return {};
|
|
||||||
|
|
||||||
return doc.object().toVariantMap();
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage WallpaperService::decodeImage(
|
|
||||||
const QString& path, const QSize& decodeSize) const {
|
|
||||||
if (path.isEmpty()) return {};
|
|
||||||
|
|
||||||
QImageReader probe(path);
|
|
||||||
const QSize source = probe.size();
|
|
||||||
QSize required = source.isEmpty() ? decodeSize : source;
|
|
||||||
if (!source.isEmpty() && !decodeSize.isEmpty()) {
|
|
||||||
const QSize cover =
|
|
||||||
source.scaled(decodeSize, Qt::KeepAspectRatioByExpanding);
|
|
||||||
if (cover.width() <= source.width() &&
|
|
||||||
cover.height() <= source.height())
|
|
||||||
required = cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QByteArray blob = cachedBlob(path);
|
|
||||||
if (!blob.isEmpty()) {
|
|
||||||
QImage image = QImage::fromData(blob, "JPEG");
|
|
||||||
if (!image.isNull() && image.width() >= required.width() &&
|
|
||||||
image.height() >= required.height()) {
|
|
||||||
return image.convertToFormat(QImage::Format_RGBA8888);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QImageReader reader(path);
|
|
||||||
reader.setAutoTransform(true);
|
|
||||||
|
|
||||||
if (!decodeSize.isEmpty() && !source.isEmpty())
|
|
||||||
reader.setScaledSize(
|
|
||||||
source.scaled(decodeSize, Qt::KeepAspectRatioByExpanding));
|
|
||||||
|
|
||||||
QImage image = reader.read();
|
|
||||||
if (image.isNull()) {
|
|
||||||
qWarning(wpLog) << "WallpaperService: could not read the image" << path;
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
return image.convertToFormat(QImage::Format_RGBA8888);
|
|
||||||
}
|
|
||||||
|
|
||||||
void WallpaperService::ensureImage() {
|
void WallpaperService::ensureImage() {
|
||||||
QSize largest;
|
QSize largest;
|
||||||
QString path;
|
QString path;
|
||||||
@@ -1891,7 +1650,7 @@ void WallpaperService::ensureImage() {
|
|||||||
if (!needDecode) return;
|
if (!needDecode) return;
|
||||||
|
|
||||||
QThreadPool::globalInstance()->start([this, path, largest]() {
|
QThreadPool::globalInstance()->start([this, path, largest]() {
|
||||||
const QImage image = decodeImage(path, largest);
|
const QImage image = m_cache.decodeImage(path, largest);
|
||||||
|
|
||||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||||
m_stagedImage = image;
|
m_stagedImage = image;
|
||||||
@@ -1923,7 +1682,7 @@ void WallpaperService::ensurePoster() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QThreadPool::globalInstance()->start([this, path]() {
|
QThreadPool::globalInstance()->start([this, path]() {
|
||||||
const QByteArray blob = posterBlob(path);
|
const QByteArray blob = m_cache.posterBlob(path);
|
||||||
|
|
||||||
QImage image;
|
QImage image;
|
||||||
if (!blob.isEmpty()) {
|
if (!blob.isEmpty()) {
|
||||||
@@ -2074,9 +1833,9 @@ void WallpaperService::drawSurface(WallpaperSurface* surface) {
|
|||||||
|
|
||||||
if (videoNow && !m_videoReady.load(std::memory_order_relaxed)) return;
|
if (videoNow && !m_videoReady.load(std::memory_order_relaxed)) return;
|
||||||
|
|
||||||
#ifdef ZHAVE_VIDEO
|
#ifdef ZHAVE_FFMPEG
|
||||||
const bool videoAsBackground =
|
const bool videoAsBackground =
|
||||||
videoNow && (m_mpv != nullptr || m_player != nullptr);
|
videoNow && (m_mpv != nullptr || m_decoder != nullptr);
|
||||||
#else
|
#else
|
||||||
const bool videoAsBackground = videoNow && m_mpv != nullptr;
|
const bool videoAsBackground = videoNow && m_mpv != nullptr;
|
||||||
#endif
|
#endif
|
||||||
@@ -2085,20 +1844,20 @@ void WallpaperService::drawSurface(WallpaperSurface* surface) {
|
|||||||
glDisable(GL_BLEND);
|
glDisable(GL_BLEND);
|
||||||
glViewport(0, 0, size.width(), size.height());
|
glViewport(0, 0, size.width(), size.height());
|
||||||
|
|
||||||
#ifdef ZHAVE_VIDEO
|
#ifdef ZHAVE_FFMPEG
|
||||||
if (m_player) {
|
if (m_decoder) {
|
||||||
if (m_player->textureId() != 0) {
|
if (m_decoder->textureId() != 0) {
|
||||||
if (m_videoProgram == 0)
|
if (m_videoProgram == 0)
|
||||||
m_videoProgram = createVideoProgram();
|
m_videoProgram = createVideoProgram();
|
||||||
|
|
||||||
if (m_videoProgram != 0) {
|
if (m_videoProgram != 0) {
|
||||||
glUseProgram(m_videoProgram);
|
glUseProgram(m_videoProgram);
|
||||||
const QSize src = m_player->frameSize();
|
const QSize src = m_decoder->frameSize();
|
||||||
drawVideoTexture(
|
drawVideoTexture(
|
||||||
m_player->textureId(),
|
m_decoder->textureId(),
|
||||||
m_player->chromaTextureId(),
|
m_decoder->chromaTextureId(),
|
||||||
m_player->colorMatrix(),
|
m_decoder->colorMatrix(),
|
||||||
m_player->colorRange(),
|
m_decoder->colorRange(),
|
||||||
QRectF(0.0, 0.0, 1.0, 1.0),
|
QRectF(0.0, 0.0, 1.0, 1.0),
|
||||||
float(size.width()),
|
float(size.width()),
|
||||||
float(size.height()),
|
float(size.height()),
|
||||||
@@ -2506,7 +2265,7 @@ void WallpaperService::drawSurfaceTransition(WallpaperSurface* surface) {
|
|||||||
oldTex = videoTex;
|
oldTex = videoTex;
|
||||||
oldFlip = 0.0f;
|
oldFlip = 0.0f;
|
||||||
} else {
|
} else {
|
||||||
oldTex = m_texture; // still the previous image here
|
oldTex = m_texture;
|
||||||
oldFlip = 1.0f;
|
oldFlip = 1.0f;
|
||||||
oldCrop =
|
oldCrop =
|
||||||
sourceRect(m_baseCrops, screen, size, m_baseImage.size());
|
sourceRect(m_baseCrops, screen, size, m_baseImage.size());
|
||||||
@@ -2622,20 +2381,20 @@ void WallpaperService::drawSurfaceTransition(WallpaperSurface* surface) {
|
|||||||
} else {
|
} else {
|
||||||
glDisable(GL_BLEND);
|
glDisable(GL_BLEND);
|
||||||
|
|
||||||
#ifdef ZHAVE_VIDEO
|
#ifdef ZHAVE_FFMPEG
|
||||||
if (m_player) {
|
if (m_decoder) {
|
||||||
if (m_player->textureId() != 0) {
|
if (m_decoder->textureId() != 0) {
|
||||||
if (m_videoProgram == 0)
|
if (m_videoProgram == 0)
|
||||||
m_videoProgram = createVideoProgram();
|
m_videoProgram = createVideoProgram();
|
||||||
|
|
||||||
if (m_videoProgram != 0) {
|
if (m_videoProgram != 0) {
|
||||||
glUseProgram(m_videoProgram);
|
glUseProgram(m_videoProgram);
|
||||||
const QSize src = m_player->frameSize();
|
const QSize src = m_decoder->frameSize();
|
||||||
drawVideoTexture(
|
drawVideoTexture(
|
||||||
m_player->textureId(),
|
m_decoder->textureId(),
|
||||||
m_player->chromaTextureId(),
|
m_decoder->chromaTextureId(),
|
||||||
m_player->colorMatrix(),
|
m_decoder->colorMatrix(),
|
||||||
m_player->colorRange(),
|
m_decoder->colorRange(),
|
||||||
QRectF(0.0, 0.0, 1.0, 1.0),
|
QRectF(0.0, 0.0, 1.0, 1.0),
|
||||||
w,
|
w,
|
||||||
h,
|
h,
|
||||||
@@ -2753,4 +2512,4 @@ void WallpaperService::addSurface(const QString& screen) {
|
|||||||
notifyBufferSizes();
|
notifyBufferSizes();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::wallpaper
|
||||||
+37
-54
@@ -22,9 +22,9 @@
|
|||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#ifdef ZHAVE_VIDEO
|
#include "videodecoder.hpp"
|
||||||
#include "videoplayer.hpp"
|
#include "wallpapercache.hpp"
|
||||||
#endif
|
#include "videotranscoder.hpp"
|
||||||
|
|
||||||
#include <qwayland-wlr-layer-shell-unstable-v1.h>
|
#include <qwayland-wlr-layer-shell-unstable-v1.h>
|
||||||
#include <wayland-client-protocol.h>
|
#include <wayland-client-protocol.h>
|
||||||
@@ -37,7 +37,7 @@ struct mpv_render_context;
|
|||||||
|
|
||||||
class QProcess;
|
class QProcess;
|
||||||
|
|
||||||
namespace ZShell::internal {
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
class WallpaperSurface;
|
class WallpaperSurface;
|
||||||
|
|
||||||
@@ -87,14 +87,6 @@ class WallpaperService : public QObject {
|
|||||||
|
|
||||||
static bool isVideoPath(const QString& path);
|
static bool isVideoPath(const QString& path);
|
||||||
|
|
||||||
static bool isHardwareCodec(const QString& codec);
|
|
||||||
|
|
||||||
[[nodiscard]] QString codecName(const QString& path) const;
|
|
||||||
[[nodiscard]] QString cacheDir() const;
|
|
||||||
[[nodiscard]] QString transcodeTarget(const QString& path) const;
|
|
||||||
[[nodiscard]] bool needsTranscode(const QString& path) const;
|
|
||||||
void transcodeVideo(const QString& src, const QString& dst);
|
|
||||||
|
|
||||||
void applyPath(const QString& path);
|
void applyPath(const QString& path);
|
||||||
|
|
||||||
void startVideo(const QString& path);
|
void startVideo(const QString& path);
|
||||||
@@ -107,9 +99,9 @@ class WallpaperService : public QObject {
|
|||||||
static void* getMpvProcAddress(void* ctx, const char* name);
|
static void* getMpvProcAddress(void* ctx, const char* name);
|
||||||
static void mpvUpdate(void* ctx);
|
static void mpvUpdate(void* ctx);
|
||||||
|
|
||||||
#ifdef ZHAVE_VIDEO
|
#ifdef ZHAVE_FFMPEG
|
||||||
bool startPlayerVideo(const QString& path);
|
bool startDecoderVideo(const QString& path);
|
||||||
void stopPlayerVideo();
|
void stopDecoderVideo();
|
||||||
[[nodiscard]] uint32_t createVideoProgram();
|
[[nodiscard]] uint32_t createVideoProgram();
|
||||||
void drawVideoTexture(
|
void drawVideoTexture(
|
||||||
uint32_t lumaTex,
|
uint32_t lumaTex,
|
||||||
@@ -160,13 +152,13 @@ class WallpaperService : public QObject {
|
|||||||
void drawSurfaceTransition(WallpaperSurface* surface);
|
void drawSurfaceTransition(WallpaperSurface* surface);
|
||||||
|
|
||||||
struct TransitionLayer {
|
struct TransitionLayer {
|
||||||
uint32_t tex = 0; // frozen image target (older layers)
|
uint32_t tex = 0;
|
||||||
QImage image; // CPU copy for crops/sizing (image layers)
|
QImage image;
|
||||||
QVariantMap crops; // crop map captured when the layer was pushed
|
QVariantMap crops;
|
||||||
QString transitionFile; // this layer's own transition shader
|
QString transitionFile;
|
||||||
bool isVideo = false; // only the newest layer may be video
|
bool isVideo = false;
|
||||||
bool ready = false; // target is sampleable
|
bool ready = false;
|
||||||
bool started = false; // its own clock has begun
|
bool started = false;
|
||||||
QElapsedTimer timer;
|
QElapsedTimer timer;
|
||||||
qreal progress = 0.0;
|
qreal progress = 0.0;
|
||||||
};
|
};
|
||||||
@@ -216,15 +208,6 @@ class WallpaperService : public QObject {
|
|||||||
float sizeH,
|
float sizeH,
|
||||||
float alpha);
|
float alpha);
|
||||||
|
|
||||||
[[nodiscard]] QImage decodeImage(
|
|
||||||
const QString& path, const QSize& decodeSize) const;
|
|
||||||
|
|
||||||
[[nodiscard]] QByteArray cachedBlob(const QString& path) const;
|
|
||||||
|
|
||||||
[[nodiscard]] QByteArray posterBlob(const QString& path) const;
|
|
||||||
|
|
||||||
[[nodiscard]] QVariantMap cropsForPath(const QString& path) const;
|
|
||||||
|
|
||||||
void ensureImage();
|
void ensureImage();
|
||||||
|
|
||||||
void ensurePoster();
|
void ensurePoster();
|
||||||
@@ -260,6 +243,8 @@ class WallpaperService : public QObject {
|
|||||||
QStringList m_screens;
|
QStringList m_screens;
|
||||||
QVariantMap m_crops;
|
QVariantMap m_crops;
|
||||||
|
|
||||||
|
WallpaperCache m_cache;
|
||||||
|
|
||||||
QImage m_image;
|
QImage m_image;
|
||||||
|
|
||||||
bool m_isVideo = false;
|
bool m_isVideo = false;
|
||||||
@@ -271,7 +256,7 @@ class WallpaperService : public QObject {
|
|||||||
std::atomic<bool> m_videoReady{false};
|
std::atomic<bool> m_videoReady{false};
|
||||||
std::atomic<bool> m_videoLoaded{false};
|
std::atomic<bool> m_videoLoaded{false};
|
||||||
|
|
||||||
QProcess* m_transcode = nullptr;
|
VideoTranscoder* m_transcoder = nullptr;
|
||||||
|
|
||||||
QSize m_decodeSize;
|
QSize m_decodeSize;
|
||||||
|
|
||||||
@@ -299,16 +284,13 @@ class WallpaperService : public QObject {
|
|||||||
int m_alphaUniform = -1;
|
int m_alphaUniform = -1;
|
||||||
int m_sourceUniform = -1;
|
int m_sourceUniform = -1;
|
||||||
|
|
||||||
bool m_randomTransition = false; // wallTransition == Random
|
bool m_randomTransition = false;
|
||||||
QString m_transitionFile; // the transition stamped onto new layers
|
QString m_transitionFile;
|
||||||
bool m_transitionDirty = false;
|
bool m_transitionDirty = false;
|
||||||
QHash<QString, TransProgram>
|
QHash<QString, TransProgram> m_transPrograms;
|
||||||
m_transPrograms; // compiled programs, keyed by shader
|
TransProgram m_neutralProgram;
|
||||||
TransProgram
|
QVector<int> m_transitionUses;
|
||||||
m_neutralProgram; // fade program for identity copies (seed/base/final)
|
QString m_lastTransition;
|
||||||
QVector<int>
|
|
||||||
m_transitionUses; // usage counts, parallel to the transition list
|
|
||||||
QString m_lastTransition; // last random pick, so it is never repeated
|
|
||||||
|
|
||||||
uint32_t m_captureFbo = 0;
|
uint32_t m_captureFbo = 0;
|
||||||
uint32_t m_captureTex = 0;
|
uint32_t m_captureTex = 0;
|
||||||
@@ -324,17 +306,16 @@ class WallpaperService : public QObject {
|
|||||||
uint32_t m_accumB = 0;
|
uint32_t m_accumB = 0;
|
||||||
QSize m_accumSize;
|
QSize m_accumSize;
|
||||||
|
|
||||||
std::vector<TransitionLayer> m_transitions; // oldest .. newest
|
std::vector<TransitionLayer> m_transitions;
|
||||||
|
|
||||||
bool m_needCaptureBase = false;
|
bool m_needCaptureBase = false;
|
||||||
QImage m_baseImage; // old settled image, for the base capture crop
|
QImage m_baseImage;
|
||||||
QVariantMap m_baseCrops; // old settled crop map, for the base capture crop
|
QVariantMap m_baseCrops;
|
||||||
bool m_baseIsVideo = false; // old settled frame was a live video
|
bool m_baseIsVideo = false;
|
||||||
bool m_baseIsBlack = false; // shell start / first load: fade in from black
|
bool m_baseIsBlack = false;
|
||||||
bool m_collapseVideo =
|
bool m_collapseVideo = false;
|
||||||
false; // video pushed mid-stack: bake older into base
|
bool m_promoteOldest = false;
|
||||||
bool m_promoteOldest = false; // stack over the cap: promote oldest to base
|
bool m_newestDone = false;
|
||||||
bool m_newestDone = false; // newest layer finished: settle into base
|
|
||||||
|
|
||||||
static constexpr int kMaxTransitions = 4;
|
static constexpr int kMaxTransitions = 4;
|
||||||
|
|
||||||
@@ -350,13 +331,15 @@ class WallpaperService : public QObject {
|
|||||||
int m_videoRangeUniform = -1;
|
int m_videoRangeUniform = -1;
|
||||||
int m_videoChromaOffUniform = -1;
|
int m_videoChromaOffUniform = -1;
|
||||||
|
|
||||||
#ifdef ZHAVE_VIDEO
|
#ifdef ZHAVE_FFMPEG
|
||||||
std::unique_ptr<VideoPlayer> m_player;
|
std::unique_ptr<VideoDecoder> m_decoder;
|
||||||
bool m_usePlayer = true;
|
bool m_useDecoder = true;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
std::vector<uint32_t> m_orphanTextures;
|
std::vector<uint32_t> m_orphanTextures;
|
||||||
|
|
||||||
|
bool m_programsCompiled = false;
|
||||||
|
|
||||||
QList<WallpaperSurface*> m_surfaces;
|
QList<WallpaperSurface*> m_surfaces;
|
||||||
|
|
||||||
std::thread m_thread;
|
std::thread m_thread;
|
||||||
@@ -381,4 +364,4 @@ class WallpaperService : public QObject {
|
|||||||
bool m_posterChanged = false;
|
bool m_posterChanged = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::wallpaper
|
||||||
+2
-2
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
#include "wallpaperservice.hpp"
|
#include "wallpaperservice.hpp"
|
||||||
|
|
||||||
namespace ZShell::internal {
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
const QString kNamespace = "ZShell-Wallpaper";
|
const QString kNamespace = "ZShell-Wallpaper";
|
||||||
|
|
||||||
@@ -188,4 +188,4 @@ void WallpaperSurface::updateBuffer() {
|
|||||||
m_service->notifyBufferSizes();
|
m_service->notifyBufferSizes();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::wallpaper
|
||||||
+2
-2
@@ -13,7 +13,7 @@
|
|||||||
#include <qwayland-wlr-layer-shell-unstable-v1.h>
|
#include <qwayland-wlr-layer-shell-unstable-v1.h>
|
||||||
#include <wayland-client-protocol.h>
|
#include <wayland-client-protocol.h>
|
||||||
|
|
||||||
namespace ZShell::internal {
|
namespace ZShell::wallpaper {
|
||||||
|
|
||||||
class WallpaperService;
|
class WallpaperService;
|
||||||
|
|
||||||
@@ -70,4 +70,4 @@ class WallpaperSurface : public QtWayland::zwlr_layer_surface_v1,
|
|||||||
struct wl_callback* m_frameCallback = nullptr;
|
struct wl_callback* m_frameCallback = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ZShell::internal
|
} // namespace ZShell::wallpaper
|
||||||
Reference in New Issue
Block a user