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:
@@ -25,6 +25,7 @@ pkg_check_modules(MPV REQUIRED IMPORTED_TARGET mpv)
|
||||
pkg_check_modules(AVCODEC REQUIRED IMPORTED_TARGET libavcodec)
|
||||
pkg_check_modules(AVFORMAT REQUIRED IMPORTED_TARGET libavformat)
|
||||
pkg_check_modules(AVUTIL REQUIRED IMPORTED_TARGET libavutil)
|
||||
pkg_check_modules(LIBVA QUIET IMPORTED_TARGET libva)
|
||||
|
||||
find_package(CUDAToolkit QUIET)
|
||||
pkg_check_modules(Qalculate IMPORTED_TARGET libqalculate REQUIRED)
|
||||
|
||||
@@ -37,6 +37,7 @@ list(APPEND CMAKE_AUTOMOC_MACRO_NAMES
|
||||
add_subdirectory(Settings)
|
||||
add_subdirectory(Models)
|
||||
add_subdirectory(Internal)
|
||||
add_subdirectory(Wallpaper)
|
||||
add_subdirectory(Services)
|
||||
add_subdirectory(Components)
|
||||
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
|
||||
URI ZShell.Internal
|
||||
INCLUDE_PREFIX internal
|
||||
@@ -13,16 +8,11 @@ qml_module(ZShell-internal
|
||||
circularindicatormanager.hpp circularindicatormanager.cpp
|
||||
circularbuffer.hpp circularbuffer.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
|
||||
visualizerbars.hpp visualizerbars.cpp
|
||||
linearindicatormanager.hpp linearindicatormanager.cpp
|
||||
strokecanvasitem.hpp strokecanvasitem.cpp
|
||||
strokecanvasrenderer.hpp strokecanvasrenderer.cpp
|
||||
videoplayer.hpp videoplayer.cpp
|
||||
cursor.hpp
|
||||
LIBRARIES
|
||||
Qt::Gui
|
||||
@@ -34,39 +24,8 @@ qml_module(ZShell-internal
|
||||
Qt::DBus
|
||||
Qt::CanvasPainter
|
||||
Qt::GuiPrivate
|
||||
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
|
||||
)
|
||||
|
||||
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);
|
||||
} else if (const QString src = firstImgSrc(html); !src.isEmpty()) {
|
||||
if (src.startsWith(QLatin1String("data:"), Qt::CaseInsensitive)) {
|
||||
const int comma = src.indexOf(QLatin1Char(','));
|
||||
const qsizetype comma = src.indexOf(QLatin1Char(','));
|
||||
if (comma < 0) {
|
||||
failWith(id, tr("Malformed data URI"));
|
||||
} 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
|
||||
|
||||
#include "nvidiaplayer.hpp"
|
||||
|
||||
#define EGL_EGLEXT_PROTOTYPES
|
||||
#define GL_GLEXT_PROTOTYPES
|
||||
|
||||
@@ -24,6 +24,8 @@ extern "C" CUresult cuGraphicsGLRegisterImage(
|
||||
|
||||
Q_LOGGING_CATEGORY(videoLog, "ZShell.Internal.wallpaper")
|
||||
|
||||
namespace ZShell::wallpaper {
|
||||
|
||||
namespace {
|
||||
|
||||
enum AVPixelFormat chooseFormat(
|
||||
@@ -34,14 +36,34 @@ enum AVPixelFormat chooseFormat(
|
||||
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
|
||||
|
||||
VideoPlayer::VideoPlayer(EGLDisplay display) : m_eglDisplay(display) {}
|
||||
NvidiaPlayer::NvidiaPlayer(EGLDisplay display) {
|
||||
(void)display;
|
||||
}
|
||||
|
||||
VideoPlayer::~VideoPlayer() {
|
||||
NvidiaPlayer::~NvidiaPlayer() {
|
||||
stop();
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mtx);
|
||||
|
||||
CUcontext prev = nullptr;
|
||||
if (m_cuCtx) cuCtxPushCurrent(m_cuCtx);
|
||||
|
||||
if (m_pending) {
|
||||
av_frame_unref(m_pending);
|
||||
av_frame_free(&m_pending);
|
||||
@@ -51,41 +73,39 @@ VideoPlayer::~VideoPlayer() {
|
||||
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) {
|
||||
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);
|
||||
m_cuCtx = nullptr;
|
||||
}
|
||||
m_texSize = QSize();
|
||||
}
|
||||
|
||||
void VideoPlayer::setReadyCallback(std::function<void()> callback) {
|
||||
m_readyCb = std::move(callback);
|
||||
bool NvidiaPlayer::available() {
|
||||
if (cuInit(0) != CUDA_SUCCESS) return false;
|
||||
CUdevice dev = 0;
|
||||
return cuDeviceGet(&dev, 0) == CUDA_SUCCESS;
|
||||
}
|
||||
|
||||
void VideoPlayer::setFrameCallback(std::function<void()> callback) {
|
||||
m_frameCb = std::move(callback);
|
||||
}
|
||||
|
||||
bool VideoPlayer::openDecoder(const QString& path) {
|
||||
bool NvidiaPlayer::openDecoder(const QString& path) {
|
||||
if (avformat_open_input(
|
||||
&m_fmt, path.toUtf8().constData(), nullptr, nullptr) < 0) {
|
||||
qCWarning(
|
||||
@@ -168,7 +188,7 @@ bool VideoPlayer::openDecoder(const QString& path) {
|
||||
return true;
|
||||
}
|
||||
|
||||
void VideoPlayer::closeDecoder() {
|
||||
void NvidiaPlayer::closeDecoder() {
|
||||
if (m_codec) {
|
||||
avcodec_free_context(&m_codec);
|
||||
m_codec = nullptr;
|
||||
@@ -184,7 +204,7 @@ void VideoPlayer::closeDecoder() {
|
||||
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) ||
|
||||
m_generation.load(std::memory_order_acquire) != generation)
|
||||
return;
|
||||
@@ -257,11 +277,16 @@ void VideoPlayer::decodeLoop(const QString& path, uint32_t generation) {
|
||||
}
|
||||
|
||||
av_frame_free(&frame);
|
||||
closeDecoder();
|
||||
{
|
||||
CudaCtx ctxGuard(m_cuCtx);
|
||||
closeDecoder();
|
||||
}
|
||||
m_threadRunning.store(false);
|
||||
}
|
||||
|
||||
bool VideoPlayer::start(const QString& path) {
|
||||
bool NvidiaPlayer::start(const QString& path) {
|
||||
if (!ensureCudaContext()) return false;
|
||||
|
||||
const uint32_t generation =
|
||||
m_generation.fetch_add(1, std::memory_order_acq_rel) + 1;
|
||||
m_threadRunning.store(true);
|
||||
@@ -271,26 +296,22 @@ bool VideoPlayer::start(const QString& path) {
|
||||
[this, path, generation, previous = std::move(previous)]() mutable {
|
||||
if (previous.joinable()) previous.join();
|
||||
closeDecoder();
|
||||
|
||||
if (!ensureCudaContext()) {
|
||||
m_threadRunning.store(false);
|
||||
return;
|
||||
}
|
||||
|
||||
decodeLoop(path, generation);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void VideoPlayer::stop() {
|
||||
void NvidiaPlayer::stop() {
|
||||
m_generation.fetch_add(1, std::memory_order_acq_rel);
|
||||
m_threadRunning.store(false);
|
||||
if (m_thread.joinable()) m_thread.join();
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mtx);
|
||||
CudaCtx ctxGuard(m_cuCtx);
|
||||
|
||||
closeDecoder();
|
||||
|
||||
std::lock_guard<std::mutex> lock(m_mtx);
|
||||
if (m_pending) {
|
||||
av_frame_unref(m_pending);
|
||||
m_pending = nullptr;
|
||||
@@ -303,12 +324,7 @@ void VideoPlayer::stop() {
|
||||
m_size = QSize();
|
||||
}
|
||||
|
||||
QSize VideoPlayer::frameSize() const {
|
||||
std::lock_guard<std::mutex> lock(m_mtx);
|
||||
return m_size;
|
||||
}
|
||||
|
||||
void VideoPlayer::deleteTexture() {
|
||||
void NvidiaPlayer::deleteTexture() {
|
||||
std::lock_guard<std::mutex> lock(m_mtx);
|
||||
CUcontext prev = nullptr;
|
||||
if (m_cuCtx) cuCtxPushCurrent(m_cuCtx);
|
||||
@@ -332,7 +348,7 @@ void VideoPlayer::deleteTexture() {
|
||||
m_texSize = QSize();
|
||||
}
|
||||
|
||||
bool VideoPlayer::ensureCudaContext() {
|
||||
bool NvidiaPlayer::ensureCudaContext() {
|
||||
if (m_cuCtx) return true;
|
||||
|
||||
if (cuInit(0) != CUDA_SUCCESS) {
|
||||
@@ -354,7 +370,7 @@ bool VideoPlayer::ensureCudaContext() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VideoPlayer::ensureTextures(unsigned w, unsigned h, int compBytes) {
|
||||
bool NvidiaPlayer::ensureTextures(unsigned w, unsigned h, int compBytes) {
|
||||
if (!ensureCudaContext()) return false;
|
||||
CUcontext prev = nullptr;
|
||||
cuCtxPushCurrent(m_cuCtx);
|
||||
@@ -431,7 +447,7 @@ bool VideoPlayer::ensureTextures(unsigned w, unsigned h, int compBytes) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VideoPlayer::importCurrentFrame() {
|
||||
bool NvidiaPlayer::importCurrentFrame() {
|
||||
AVFrame* frame = nullptr;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(m_mtx);
|
||||
@@ -546,4 +562,6 @@ bool VideoPlayer::importCurrentFrame() {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace ZShell::wallpaper
|
||||
|
||||
#endif
|
||||
+19
-29
@@ -1,11 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
|
||||
#include "videodecoder.hpp"
|
||||
|
||||
#include <EGL/egl.h>
|
||||
|
||||
#include <cuda.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
@@ -23,27 +26,21 @@ extern "C" {
|
||||
#include <libavutil/pixdesc.h>
|
||||
}
|
||||
|
||||
class VideoPlayer {
|
||||
namespace ZShell::wallpaper {
|
||||
|
||||
class NvidiaPlayer : public VideoDecoder {
|
||||
public:
|
||||
explicit VideoPlayer(EGLDisplay display);
|
||||
~VideoPlayer();
|
||||
explicit NvidiaPlayer(EGLDisplay display);
|
||||
~NvidiaPlayer() override;
|
||||
|
||||
void setReadyCallback(std::function<void()> callback);
|
||||
void setFrameCallback(std::function<void()> callback);
|
||||
[[nodiscard]] static bool available();
|
||||
|
||||
bool start(const QString& path);
|
||||
void stop();
|
||||
bool start(const QString& path) override;
|
||||
void stop() override;
|
||||
bool importCurrentFrame() override;
|
||||
void deleteTexture() override;
|
||||
|
||||
|
||||
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;
|
||||
[[nodiscard]] const char* backendName() const override { return "nvidia"; }
|
||||
|
||||
private:
|
||||
void decodeLoop(const QString& path, uint32_t generation);
|
||||
@@ -54,10 +51,6 @@ class VideoPlayer {
|
||||
|
||||
bool ensureCudaContext();
|
||||
|
||||
EGLDisplay m_eglDisplay;
|
||||
std::function<void()> m_readyCb;
|
||||
std::function<void()> m_frameCb;
|
||||
|
||||
std::thread m_thread;
|
||||
std::atomic<bool> m_threadRunning{false};
|
||||
std::atomic<uint32_t> m_generation{0};
|
||||
@@ -77,12 +70,9 @@ class VideoPlayer {
|
||||
CUgraphicsResource m_resY = nullptr;
|
||||
CUgraphicsResource m_resUV = nullptr;
|
||||
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;
|
||||
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 <QQuickWindow>
|
||||
|
||||
namespace ZShell::internal {
|
||||
namespace ZShell::wallpaper {
|
||||
|
||||
QThreadPool* WallpaperImage::loadPool() {
|
||||
static QThreadPool* pool = [] {
|
||||
@@ -352,4 +352,4 @@ QSGNode* WallpaperImage::updatePaintNode(
|
||||
return m_rootNode;
|
||||
}
|
||||
|
||||
} // namespace ZShell::internal
|
||||
} // namespace ZShell::wallpaper
|
||||
+2
-2
@@ -14,7 +14,7 @@
|
||||
#include <qtmetamacros.h>
|
||||
#include <atomic>
|
||||
|
||||
namespace ZShell::internal {
|
||||
namespace ZShell::wallpaper {
|
||||
|
||||
class WallpaperImage : public QQuickItem {
|
||||
Q_OBJECT
|
||||
@@ -144,4 +144,4 @@ class WallpaperImage : public QQuickItem {
|
||||
QSGImageNode* m_oldImageNode = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ZShell::internal
|
||||
} // namespace ZShell::wallpaper
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
#include "wallpaperobject.hpp"
|
||||
|
||||
namespace ZShell::internal {
|
||||
namespace ZShell::wallpaper {
|
||||
|
||||
WallpaperObject::WallpaperObject(QObject* parent) : QObject(parent) {}
|
||||
|
||||
@@ -118,4 +118,4 @@ void WallpaperObject::push() {
|
||||
m_service->setCrop(m_screen, crop);
|
||||
}
|
||||
|
||||
} // namespace ZShell::internal
|
||||
} // namespace ZShell::wallpaper
|
||||
+2
-2
@@ -9,7 +9,7 @@
|
||||
|
||||
#include "wallpaperservice.hpp"
|
||||
|
||||
namespace ZShell::internal {
|
||||
namespace ZShell::wallpaper {
|
||||
|
||||
class WallpaperObject : public QObject {
|
||||
Q_OBJECT
|
||||
@@ -83,4 +83,4 @@ class WallpaperObject : public QObject {
|
||||
bool m_pushEnabled = true;
|
||||
};
|
||||
|
||||
} // namespace ZShell::internal
|
||||
} // namespace ZShell::wallpaper
|
||||
+169
-410
@@ -46,25 +46,41 @@
|
||||
|
||||
#include "config/background.hpp"
|
||||
#include "config/rootnodes.hpp"
|
||||
#include "config/services.hpp"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
namespace ZShell::internal {
|
||||
namespace ZShell::wallpaper {
|
||||
|
||||
Q_LOGGING_CATEGORY(wpLog, "ZShell.Internal.wallpaper")
|
||||
|
||||
namespace {
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
const bool kMpvMode = qEnvironmentVariableIsSet("ZBAR_WALLPAPER_MPV");
|
||||
#endif
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
const bool kGlesMode = qEnvironmentVariableIsSet("ZBAR_WALLPAPER_GLES") ||
|
||||
kMpvMode;
|
||||
#else
|
||||
const bool kGlesMode = qEnvironmentVariableIsSet("ZBAR_WALLPAPER_GLES");
|
||||
#endif
|
||||
|
||||
VideoBackend backendFromConfig() {
|
||||
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) {
|
||||
if (kGlesMode)
|
||||
@@ -213,6 +229,22 @@ WallpaperService::WallpaperService(QObject* parent) : QObject(parent) {
|
||||
|
||||
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() {
|
||||
@@ -260,11 +292,11 @@ WallpaperService::~WallpaperService() {
|
||||
if (m_captureTex) glDeleteTextures(1, &m_captureTex);
|
||||
if (m_captureFbo) glDeleteFramebuffers(1, &m_captureFbo);
|
||||
if (m_posterTex) glDeleteTextures(1, &m_posterTex);
|
||||
#ifdef ZHAVE_VIDEO
|
||||
if (m_player) {
|
||||
m_player->stop();
|
||||
m_player->deleteTexture();
|
||||
m_player.reset();
|
||||
#ifdef ZHAVE_FFMPEG
|
||||
if (m_decoder) {
|
||||
m_decoder->stop();
|
||||
m_decoder->deleteTexture();
|
||||
m_decoder.reset();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -303,6 +335,8 @@ void WallpaperService::setDbPath(const QString& path) {
|
||||
m_forceDecode = true;
|
||||
}
|
||||
|
||||
m_cache.setDbPath(path);
|
||||
|
||||
emit dbPathChanged();
|
||||
wake();
|
||||
}
|
||||
@@ -310,8 +344,8 @@ void WallpaperService::setDbPath(const QString& path) {
|
||||
void WallpaperService::setPath(const QString& path) {
|
||||
if (path.isEmpty() || path == this->path()) return;
|
||||
|
||||
if (isVideoPath(path) && needsTranscode(path)) {
|
||||
const QString dst = transcodeTarget(path);
|
||||
if (isVideoPath(path) && m_transcoder->needsTranscode(path)) {
|
||||
const QString dst = m_transcoder->transcodeTarget(path);
|
||||
|
||||
if (QFile::exists(dst)) {
|
||||
applyPath(dst);
|
||||
@@ -319,7 +353,7 @@ void WallpaperService::setPath(const QString& path) {
|
||||
}
|
||||
|
||||
applyPath(path);
|
||||
transcodeVideo(path, dst);
|
||||
m_transcoder->start(path, dst);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -342,8 +376,8 @@ void WallpaperService::applyPath(const QString& path) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
const bool videoBackend = m_mpv != nullptr || m_player != nullptr;
|
||||
#ifdef ZHAVE_FFMPEG
|
||||
const bool videoBackend = m_mpv != nullptr || m_decoder != nullptr;
|
||||
#else
|
||||
const bool videoBackend = m_mpv != nullptr;
|
||||
#endif
|
||||
@@ -357,19 +391,19 @@ void WallpaperService::applyPath(const QString& path) {
|
||||
m_baseIsVideo = true;
|
||||
m_baseIsBlack = false;
|
||||
m_baseImage = QImage();
|
||||
m_baseCrops = cropsForPath(previousPath);
|
||||
m_baseCrops = m_cache.cropsForPath(previousPath);
|
||||
} else if (wasImage) {
|
||||
m_needCaptureBase = true;
|
||||
m_baseIsVideo = false;
|
||||
m_baseIsBlack = false;
|
||||
m_baseImage = m_image;
|
||||
m_baseCrops = cropsForPath(previousPath);
|
||||
m_baseCrops = m_cache.cropsForPath(previousPath);
|
||||
} else {
|
||||
m_needCaptureBase = false;
|
||||
m_baseIsVideo = false;
|
||||
m_baseIsBlack = m_baseTex == 0;
|
||||
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();
|
||||
newest.tex = m_texture;
|
||||
newest.image = m_image;
|
||||
newest.crops = cropsForPath(previousPath);
|
||||
newest.crops = m_cache.cropsForPath(previousPath);
|
||||
newest.ready = true;
|
||||
m_texture = 0;
|
||||
m_image = QImage();
|
||||
@@ -393,7 +427,7 @@ void WallpaperService::applyPath(const QString& path) {
|
||||
m_transitions.back().isVideo)
|
||||
m_collapseVideo = true;
|
||||
|
||||
m_crops = cropsForPath(path);
|
||||
m_crops = m_cache.cropsForPath(path);
|
||||
|
||||
m_isVideo = video;
|
||||
|
||||
@@ -424,153 +458,6 @@ void WallpaperService::applyPath(const QString& path) {
|
||||
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) {
|
||||
{
|
||||
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_videoLoaded.store(false, std::memory_order_relaxed);
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
if (m_usePlayer && !kMpvMode) {
|
||||
if (startPlayerVideo(path)) return;
|
||||
#ifdef ZHAVE_FFMPEG
|
||||
if (m_useDecoder && !kMpvMode) {
|
||||
if (startDecoderVideo(path)) return;
|
||||
|
||||
qWarning(wpLog) << "WallpaperService: the GPU video player failed, "
|
||||
"falling back to mpv";
|
||||
m_usePlayer = false;
|
||||
m_player.reset();
|
||||
m_useDecoder = false;
|
||||
m_decoder.reset();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -975,6 +862,16 @@ void WallpaperService::present() {
|
||||
|
||||
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;
|
||||
for (auto* surface : surfaces)
|
||||
if (surface->eglSurface() != EGL_NO_SURFACE) {
|
||||
@@ -991,16 +888,63 @@ void WallpaperService::present() {
|
||||
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) {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
m_videoReload = false;
|
||||
}
|
||||
|
||||
if (!m_orphanTextures.empty()) {
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
|
||||
for (uint32_t tex : m_orphanTextures)
|
||||
glDeleteTextures(1, &tex);
|
||||
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) ||
|
||||
@@ -1016,19 +960,19 @@ void WallpaperService::present() {
|
||||
} else if (m_mpv != nullptr) {
|
||||
teardownMpv();
|
||||
}
|
||||
#ifdef ZHAVE_VIDEO
|
||||
else if (m_player != nullptr) {
|
||||
m_player->deleteTexture();
|
||||
stopPlayerVideo();
|
||||
#ifdef ZHAVE_FFMPEG
|
||||
else if (m_decoder != nullptr) {
|
||||
m_decoder->deleteTexture();
|
||||
stopDecoderVideo();
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
if (!holdVideoForBase) applyVideoBackendSwitch();
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
#ifdef ZHAVE_FFMPEG
|
||||
const bool videoActive =
|
||||
(m_mpv != nullptr || m_player != nullptr) &&
|
||||
(m_mpv != nullptr || m_decoder != nullptr) &&
|
||||
(m_isVideo || m_baseIsVideo || m_collapseVideo || m_promoteOldest);
|
||||
#else
|
||||
const bool videoActive =
|
||||
@@ -1038,8 +982,8 @@ void WallpaperService::present() {
|
||||
|
||||
if (videoActive && m_mpv != nullptr) pumpMpvEvents();
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
if (videoActive && m_player != nullptr) m_player->importCurrentFrame();
|
||||
#ifdef ZHAVE_FFMPEG
|
||||
if (videoActive && m_decoder != nullptr) m_decoder->importCurrentFrame();
|
||||
#endif
|
||||
|
||||
for (auto* surface : surfaces)
|
||||
@@ -1424,7 +1368,7 @@ void WallpaperService::ensureCapture(const QSize& size) {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
#ifdef ZHAVE_FFMPEG
|
||||
uint32_t WallpaperService::createVideoProgram() {
|
||||
const QByteArray vsSrc = shaderSource("wallpaper.vert", false);
|
||||
const QByteArray fsSrc = shaderSource("wallpaper_video.frag", true);
|
||||
@@ -1518,30 +1462,32 @@ void WallpaperService::drawVideoTexture(
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
}
|
||||
|
||||
bool WallpaperService::startPlayerVideo(const QString& path) {
|
||||
if (!m_player) m_player = std::make_unique<VideoPlayer>(m_eglDisplay);
|
||||
bool WallpaperService::startDecoderVideo(const QString& path) {
|
||||
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_videoReady.store(true, std::memory_order_relaxed);
|
||||
queueAll();
|
||||
});
|
||||
|
||||
m_player->setFrameCallback([this]() {
|
||||
m_decoder->setFrameCallback([this]() {
|
||||
m_videoReady.store(true, std::memory_order_relaxed);
|
||||
queueAll();
|
||||
});
|
||||
|
||||
if (!m_player->start(path)) return false;
|
||||
if (!m_decoder->start(path)) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WallpaperService::stopPlayerVideo() {
|
||||
if (!m_player) return;
|
||||
void WallpaperService::stopDecoderVideo() {
|
||||
if (!m_decoder) return;
|
||||
|
||||
m_player->stop();
|
||||
m_player.reset();
|
||||
m_decoder->stop();
|
||||
m_decoder.reset();
|
||||
|
||||
m_videoReady.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());
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
if (m_player) {
|
||||
if (m_player->textureId() != 0) {
|
||||
#ifdef ZHAVE_FFMPEG
|
||||
if (m_decoder) {
|
||||
if (m_decoder->textureId() != 0) {
|
||||
if (m_videoProgram == 0) m_videoProgram = createVideoProgram();
|
||||
|
||||
if (m_videoProgram != 0) {
|
||||
glUseProgram(m_videoProgram);
|
||||
const QSize src = m_player->frameSize();
|
||||
const QSize src = m_decoder->frameSize();
|
||||
drawVideoTexture(
|
||||
m_player->textureId(),
|
||||
m_player->chromaTextureId(),
|
||||
m_player->colorMatrix(),
|
||||
m_player->colorRange(),
|
||||
m_decoder->textureId(),
|
||||
m_decoder->chromaTextureId(),
|
||||
m_decoder->colorMatrix(),
|
||||
m_decoder->colorRange(),
|
||||
QRectF(0.0, 0.0, 1.0, 1.0),
|
||||
float(size.width()),
|
||||
float(size.height()),
|
||||
@@ -1659,193 +1605,6 @@ void WallpaperService::drawTexture(
|
||||
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() {
|
||||
QSize largest;
|
||||
QString path;
|
||||
@@ -1891,7 +1650,7 @@ void WallpaperService::ensureImage() {
|
||||
if (!needDecode) return;
|
||||
|
||||
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);
|
||||
m_stagedImage = image;
|
||||
@@ -1923,7 +1682,7 @@ void WallpaperService::ensurePoster() {
|
||||
}
|
||||
|
||||
QThreadPool::globalInstance()->start([this, path]() {
|
||||
const QByteArray blob = posterBlob(path);
|
||||
const QByteArray blob = m_cache.posterBlob(path);
|
||||
|
||||
QImage image;
|
||||
if (!blob.isEmpty()) {
|
||||
@@ -2074,9 +1833,9 @@ void WallpaperService::drawSurface(WallpaperSurface* surface) {
|
||||
|
||||
if (videoNow && !m_videoReady.load(std::memory_order_relaxed)) return;
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
#ifdef ZHAVE_FFMPEG
|
||||
const bool videoAsBackground =
|
||||
videoNow && (m_mpv != nullptr || m_player != nullptr);
|
||||
videoNow && (m_mpv != nullptr || m_decoder != nullptr);
|
||||
#else
|
||||
const bool videoAsBackground = videoNow && m_mpv != nullptr;
|
||||
#endif
|
||||
@@ -2085,20 +1844,20 @@ void WallpaperService::drawSurface(WallpaperSurface* surface) {
|
||||
glDisable(GL_BLEND);
|
||||
glViewport(0, 0, size.width(), size.height());
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
if (m_player) {
|
||||
if (m_player->textureId() != 0) {
|
||||
#ifdef ZHAVE_FFMPEG
|
||||
if (m_decoder) {
|
||||
if (m_decoder->textureId() != 0) {
|
||||
if (m_videoProgram == 0)
|
||||
m_videoProgram = createVideoProgram();
|
||||
|
||||
if (m_videoProgram != 0) {
|
||||
glUseProgram(m_videoProgram);
|
||||
const QSize src = m_player->frameSize();
|
||||
const QSize src = m_decoder->frameSize();
|
||||
drawVideoTexture(
|
||||
m_player->textureId(),
|
||||
m_player->chromaTextureId(),
|
||||
m_player->colorMatrix(),
|
||||
m_player->colorRange(),
|
||||
m_decoder->textureId(),
|
||||
m_decoder->chromaTextureId(),
|
||||
m_decoder->colorMatrix(),
|
||||
m_decoder->colorRange(),
|
||||
QRectF(0.0, 0.0, 1.0, 1.0),
|
||||
float(size.width()),
|
||||
float(size.height()),
|
||||
@@ -2506,7 +2265,7 @@ void WallpaperService::drawSurfaceTransition(WallpaperSurface* surface) {
|
||||
oldTex = videoTex;
|
||||
oldFlip = 0.0f;
|
||||
} else {
|
||||
oldTex = m_texture; // still the previous image here
|
||||
oldTex = m_texture;
|
||||
oldFlip = 1.0f;
|
||||
oldCrop =
|
||||
sourceRect(m_baseCrops, screen, size, m_baseImage.size());
|
||||
@@ -2622,20 +2381,20 @@ void WallpaperService::drawSurfaceTransition(WallpaperSurface* surface) {
|
||||
} else {
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
if (m_player) {
|
||||
if (m_player->textureId() != 0) {
|
||||
#ifdef ZHAVE_FFMPEG
|
||||
if (m_decoder) {
|
||||
if (m_decoder->textureId() != 0) {
|
||||
if (m_videoProgram == 0)
|
||||
m_videoProgram = createVideoProgram();
|
||||
|
||||
if (m_videoProgram != 0) {
|
||||
glUseProgram(m_videoProgram);
|
||||
const QSize src = m_player->frameSize();
|
||||
const QSize src = m_decoder->frameSize();
|
||||
drawVideoTexture(
|
||||
m_player->textureId(),
|
||||
m_player->chromaTextureId(),
|
||||
m_player->colorMatrix(),
|
||||
m_player->colorRange(),
|
||||
m_decoder->textureId(),
|
||||
m_decoder->chromaTextureId(),
|
||||
m_decoder->colorMatrix(),
|
||||
m_decoder->colorRange(),
|
||||
QRectF(0.0, 0.0, 1.0, 1.0),
|
||||
w,
|
||||
h,
|
||||
@@ -2753,4 +2512,4 @@ void WallpaperService::addSurface(const QString& screen) {
|
||||
notifyBufferSizes();
|
||||
}
|
||||
|
||||
} // namespace ZShell::internal
|
||||
} // namespace ZShell::wallpaper
|
||||
+37
-54
@@ -22,9 +22,9 @@
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
#include "videoplayer.hpp"
|
||||
#endif
|
||||
#include "videodecoder.hpp"
|
||||
#include "wallpapercache.hpp"
|
||||
#include "videotranscoder.hpp"
|
||||
|
||||
#include <qwayland-wlr-layer-shell-unstable-v1.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
@@ -37,7 +37,7 @@ struct mpv_render_context;
|
||||
|
||||
class QProcess;
|
||||
|
||||
namespace ZShell::internal {
|
||||
namespace ZShell::wallpaper {
|
||||
|
||||
class WallpaperSurface;
|
||||
|
||||
@@ -87,14 +87,6 @@ class WallpaperService : public QObject {
|
||||
|
||||
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 startVideo(const QString& path);
|
||||
@@ -107,9 +99,9 @@ class WallpaperService : public QObject {
|
||||
static void* getMpvProcAddress(void* ctx, const char* name);
|
||||
static void mpvUpdate(void* ctx);
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
bool startPlayerVideo(const QString& path);
|
||||
void stopPlayerVideo();
|
||||
#ifdef ZHAVE_FFMPEG
|
||||
bool startDecoderVideo(const QString& path);
|
||||
void stopDecoderVideo();
|
||||
[[nodiscard]] uint32_t createVideoProgram();
|
||||
void drawVideoTexture(
|
||||
uint32_t lumaTex,
|
||||
@@ -160,13 +152,13 @@ class WallpaperService : public QObject {
|
||||
void drawSurfaceTransition(WallpaperSurface* surface);
|
||||
|
||||
struct TransitionLayer {
|
||||
uint32_t tex = 0; // frozen image target (older layers)
|
||||
QImage image; // CPU copy for crops/sizing (image layers)
|
||||
QVariantMap crops; // crop map captured when the layer was pushed
|
||||
QString transitionFile; // this layer's own transition shader
|
||||
bool isVideo = false; // only the newest layer may be video
|
||||
bool ready = false; // target is sampleable
|
||||
bool started = false; // its own clock has begun
|
||||
uint32_t tex = 0;
|
||||
QImage image;
|
||||
QVariantMap crops;
|
||||
QString transitionFile;
|
||||
bool isVideo = false;
|
||||
bool ready = false;
|
||||
bool started = false;
|
||||
QElapsedTimer timer;
|
||||
qreal progress = 0.0;
|
||||
};
|
||||
@@ -216,15 +208,6 @@ class WallpaperService : public QObject {
|
||||
float sizeH,
|
||||
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 ensurePoster();
|
||||
@@ -260,6 +243,8 @@ class WallpaperService : public QObject {
|
||||
QStringList m_screens;
|
||||
QVariantMap m_crops;
|
||||
|
||||
WallpaperCache m_cache;
|
||||
|
||||
QImage m_image;
|
||||
|
||||
bool m_isVideo = false;
|
||||
@@ -271,7 +256,7 @@ class WallpaperService : public QObject {
|
||||
std::atomic<bool> m_videoReady{false};
|
||||
std::atomic<bool> m_videoLoaded{false};
|
||||
|
||||
QProcess* m_transcode = nullptr;
|
||||
VideoTranscoder* m_transcoder = nullptr;
|
||||
|
||||
QSize m_decodeSize;
|
||||
|
||||
@@ -299,16 +284,13 @@ class WallpaperService : public QObject {
|
||||
int m_alphaUniform = -1;
|
||||
int m_sourceUniform = -1;
|
||||
|
||||
bool m_randomTransition = false; // wallTransition == Random
|
||||
QString m_transitionFile; // the transition stamped onto new layers
|
||||
bool m_randomTransition = false;
|
||||
QString m_transitionFile;
|
||||
bool m_transitionDirty = false;
|
||||
QHash<QString, TransProgram>
|
||||
m_transPrograms; // compiled programs, keyed by shader
|
||||
TransProgram
|
||||
m_neutralProgram; // fade program for identity copies (seed/base/final)
|
||||
QVector<int>
|
||||
m_transitionUses; // usage counts, parallel to the transition list
|
||||
QString m_lastTransition; // last random pick, so it is never repeated
|
||||
QHash<QString, TransProgram> m_transPrograms;
|
||||
TransProgram m_neutralProgram;
|
||||
QVector<int> m_transitionUses;
|
||||
QString m_lastTransition;
|
||||
|
||||
uint32_t m_captureFbo = 0;
|
||||
uint32_t m_captureTex = 0;
|
||||
@@ -324,17 +306,16 @@ class WallpaperService : public QObject {
|
||||
uint32_t m_accumB = 0;
|
||||
QSize m_accumSize;
|
||||
|
||||
std::vector<TransitionLayer> m_transitions; // oldest .. newest
|
||||
std::vector<TransitionLayer> m_transitions;
|
||||
|
||||
bool m_needCaptureBase = false;
|
||||
QImage m_baseImage; // old settled image, for the base capture crop
|
||||
QVariantMap m_baseCrops; // old settled crop map, for the base capture crop
|
||||
bool m_baseIsVideo = false; // old settled frame was a live video
|
||||
bool m_baseIsBlack = false; // shell start / first load: fade in from black
|
||||
bool m_collapseVideo =
|
||||
false; // video pushed mid-stack: bake older into base
|
||||
bool m_promoteOldest = false; // stack over the cap: promote oldest to base
|
||||
bool m_newestDone = false; // newest layer finished: settle into base
|
||||
QImage m_baseImage;
|
||||
QVariantMap m_baseCrops;
|
||||
bool m_baseIsVideo = false;
|
||||
bool m_baseIsBlack = false;
|
||||
bool m_collapseVideo = false;
|
||||
bool m_promoteOldest = false;
|
||||
bool m_newestDone = false;
|
||||
|
||||
static constexpr int kMaxTransitions = 4;
|
||||
|
||||
@@ -350,13 +331,15 @@ class WallpaperService : public QObject {
|
||||
int m_videoRangeUniform = -1;
|
||||
int m_videoChromaOffUniform = -1;
|
||||
|
||||
#ifdef ZHAVE_VIDEO
|
||||
std::unique_ptr<VideoPlayer> m_player;
|
||||
bool m_usePlayer = true;
|
||||
#ifdef ZHAVE_FFMPEG
|
||||
std::unique_ptr<VideoDecoder> m_decoder;
|
||||
bool m_useDecoder = true;
|
||||
#endif
|
||||
|
||||
std::vector<uint32_t> m_orphanTextures;
|
||||
|
||||
bool m_programsCompiled = false;
|
||||
|
||||
QList<WallpaperSurface*> m_surfaces;
|
||||
|
||||
std::thread m_thread;
|
||||
@@ -381,4 +364,4 @@ class WallpaperService : public QObject {
|
||||
bool m_posterChanged = false;
|
||||
};
|
||||
|
||||
} // namespace ZShell::internal
|
||||
} // namespace ZShell::wallpaper
|
||||
+2
-2
@@ -6,7 +6,7 @@
|
||||
|
||||
#include "wallpaperservice.hpp"
|
||||
|
||||
namespace ZShell::internal {
|
||||
namespace ZShell::wallpaper {
|
||||
|
||||
const QString kNamespace = "ZShell-Wallpaper";
|
||||
|
||||
@@ -188,4 +188,4 @@ void WallpaperSurface::updateBuffer() {
|
||||
m_service->notifyBufferSizes();
|
||||
}
|
||||
|
||||
} // namespace ZShell::internal
|
||||
} // namespace ZShell::wallpaper
|
||||
+2
-2
@@ -13,7 +13,7 @@
|
||||
#include <qwayland-wlr-layer-shell-unstable-v1.h>
|
||||
#include <wayland-client-protocol.h>
|
||||
|
||||
namespace ZShell::internal {
|
||||
namespace ZShell::wallpaper {
|
||||
|
||||
class WallpaperService;
|
||||
|
||||
@@ -70,4 +70,4 @@ class WallpaperSurface : public QtWayland::zwlr_layer_surface_v1,
|
||||
struct wl_callback* m_frameCallback = nullptr;
|
||||
};
|
||||
|
||||
} // namespace ZShell::internal
|
||||
} // namespace ZShell::wallpaper
|
||||
Reference in New Issue
Block a user