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

This commit is contained in:
2026-10-27 02:15:16 +01:00
parent e655ec5bc7
commit b0cb43f75c
39 changed files with 1670 additions and 599 deletions
+3
View File
@@ -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
+1 -1
View File
@@ -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
+1
View File
@@ -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)
+1
View File
@@ -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)
-41
View File
@@ -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()
+1 -1
View File
@@ -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 {
+70
View File
@@ -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
)
@@ -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
@@ -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
+628
View File
@@ -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
+83
View File
@@ -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
+117
View File
@@ -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
+58
View File
@@ -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
+170
View File
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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