feat: initial video wallpaper implementation
This commit is contained in:
@@ -7,15 +7,26 @@
|
||||
#include <GL/gl.h>
|
||||
#include <GL/glext.h>
|
||||
|
||||
#include <mpv/client.h>
|
||||
#include <mpv/render_gl.h>
|
||||
|
||||
#include <clocale>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QGuiApplication>
|
||||
#include <QImageReader>
|
||||
#include <QScreen>
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QProcess>
|
||||
|
||||
#include <qguiapplication_platform.h>
|
||||
|
||||
#include <private/qwaylandscreen_p.h>
|
||||
@@ -29,7 +40,6 @@ namespace {
|
||||
const char* kVertexShader = R"(
|
||||
#version 300 es
|
||||
void main() {
|
||||
// The corners are (-1,-1), (3,-1) and (-1,3), which cover the whole buffer.
|
||||
vec2 corner = vec2(float((gl_VertexID << 1) & 2), float(gl_VertexID & 2));
|
||||
gl_Position = vec4(corner * 2.0 - 1.0, 0.0, 1.0);
|
||||
}
|
||||
@@ -41,16 +51,36 @@ precision highp float;
|
||||
uniform sampler2D source;
|
||||
uniform vec2 size;
|
||||
uniform vec4 crop;
|
||||
uniform float alpha;
|
||||
out vec4 outColor;
|
||||
void main() {
|
||||
// The pixels are stored top-down, so the fragment coordinate is flipped.
|
||||
vec2 p = vec2(gl_FragCoord.x, size.y - gl_FragCoord.y) / size;
|
||||
outColor = texture(source, crop.xy + p * crop.zw);
|
||||
vec4 c = texture(source, crop.xy + p * crop.zw);
|
||||
outColor = vec4(c.rgb, c.a * alpha);
|
||||
}
|
||||
)";
|
||||
|
||||
constexpr int kWaitTimeoutMs = 20;
|
||||
|
||||
constexpr int kFadeDurationMs = 400;
|
||||
|
||||
const QStringList kVideoExtensions = {
|
||||
"mp4",
|
||||
"mkv",
|
||||
"webm",
|
||||
"avi",
|
||||
"mov",
|
||||
"wmv",
|
||||
"flv",
|
||||
"m4v",
|
||||
"mpg",
|
||||
"mpeg",
|
||||
"3gp",
|
||||
"ogv",
|
||||
"ts",
|
||||
"vob",
|
||||
};
|
||||
|
||||
void logShader(uint32_t object, bool program) {
|
||||
int ok = 0;
|
||||
if (program)
|
||||
@@ -94,6 +124,16 @@ WallpaperService::~WallpaperService() {
|
||||
|
||||
if (m_thread.joinable()) m_thread.join();
|
||||
|
||||
if (m_mpvRender) {
|
||||
mpv_render_context_free(m_mpvRender);
|
||||
m_mpvRender = nullptr;
|
||||
}
|
||||
|
||||
if (m_mpv) {
|
||||
mpv_terminate_destroy(m_mpv);
|
||||
m_mpv = nullptr;
|
||||
}
|
||||
|
||||
for (auto* surface : m_surfaces)
|
||||
delete surface;
|
||||
m_surfaces.clear();
|
||||
@@ -102,6 +142,8 @@ WallpaperService::~WallpaperService() {
|
||||
if (eglMakeCurrent(
|
||||
m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, m_context)) {
|
||||
if (m_texture) glDeleteTextures(1, &m_texture);
|
||||
if (m_previousTexture) glDeleteTextures(1, &m_previousTexture);
|
||||
if (m_blackTex) glDeleteTextures(1, &m_blackTex);
|
||||
if (m_program) glDeleteProgram(m_program);
|
||||
}
|
||||
|
||||
@@ -127,22 +169,210 @@ QString WallpaperService::path() const {
|
||||
}
|
||||
|
||||
void WallpaperService::setPath(const QString& path) {
|
||||
if (m_path == path) return;
|
||||
if (path.isEmpty() || path == this->path()) return;
|
||||
|
||||
if (isVideoPath(path) && needsTranscode(path)) {
|
||||
const QString dst = transcodeTarget(path);
|
||||
|
||||
if (QFile::exists(dst)) {
|
||||
applyPath(dst);
|
||||
return;
|
||||
}
|
||||
|
||||
applyPath(path);
|
||||
transcodeVideo(path, dst);
|
||||
return;
|
||||
}
|
||||
|
||||
applyPath(path);
|
||||
}
|
||||
|
||||
void WallpaperService::applyPath(const QString& path) {
|
||||
const bool video = isVideoPath(path);
|
||||
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
m_path = path;
|
||||
|
||||
const bool wasVideo = m_isVideo && m_mpv != nullptr;
|
||||
const bool wasImage = !m_image.isNull() && m_texture != 0;
|
||||
|
||||
m_previousVideo = wasVideo && !video;
|
||||
|
||||
if (wasImage) {
|
||||
m_previousImage = m_image;
|
||||
m_previousCrops = m_crops;
|
||||
m_previousTexture = 0;
|
||||
} else {
|
||||
m_previousImage = QImage();
|
||||
}
|
||||
|
||||
m_isVideo = video;
|
||||
|
||||
m_fading = true;
|
||||
m_fadeClockStarted = false;
|
||||
|
||||
m_forceDecode = true;
|
||||
m_imageChanged = true;
|
||||
m_videoReload = true;
|
||||
}
|
||||
|
||||
emit pathChanged();
|
||||
|
||||
start();
|
||||
|
||||
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() << "WallpaperService: ffprobe failed for" << path << "(exit"
|
||||
<< probe.exitCode() << ")";
|
||||
return {};
|
||||
}
|
||||
|
||||
const QString codec =
|
||||
QString::fromUtf8(probe.readAllStandardOutput()).trimmed().toLower();
|
||||
qWarning() << "WallpaperService: codec of" << path << "is"
|
||||
<< (codec.isEmpty() ? "<empty>" : codec);
|
||||
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().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() << "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() << "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);
|
||||
@@ -212,6 +442,178 @@ void WallpaperService::redraw() {
|
||||
queueAll();
|
||||
}
|
||||
|
||||
bool WallpaperService::isVideoPath(const QString& path) {
|
||||
return kVideoExtensions.contains(QFileInfo(path).suffix().toLower());
|
||||
}
|
||||
|
||||
void WallpaperService::startVideo(const QString& path) {
|
||||
if (path.isEmpty()) return;
|
||||
|
||||
m_videoReady.store(false, std::memory_order_relaxed);
|
||||
m_videoLoaded.store(false, std::memory_order_relaxed);
|
||||
|
||||
if (!m_mpv) {
|
||||
std::setlocale(LC_NUMERIC, "C");
|
||||
|
||||
m_mpv = mpv_create();
|
||||
if (!m_mpv) {
|
||||
qWarning() << "WallpaperService: could not create mpv handle";
|
||||
return;
|
||||
}
|
||||
|
||||
mpv_set_option_string(m_mpv, "config", "no");
|
||||
mpv_set_option_string(m_mpv, "vo", "libmpv");
|
||||
mpv_set_option_string(m_mpv, "audio", "no");
|
||||
mpv_set_option_string(m_mpv, "loop", "inf");
|
||||
mpv_set_option_string(m_mpv, "hwdec", "auto-copy");
|
||||
mpv_set_option_string(m_mpv, "video-sync", "display");
|
||||
mpv_set_option_string(m_mpv, "framedrop", "decoder");
|
||||
mpv_set_option_string(m_mpv, "background-color", "#00000000");
|
||||
|
||||
if (mpv_initialize(m_mpv) < 0) {
|
||||
qWarning() << "WallpaperService: mpv_initialize failed";
|
||||
mpv_terminate_destroy(m_mpv);
|
||||
m_mpv = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const QByteArray local = path.toUtf8();
|
||||
|
||||
initMpvRender();
|
||||
|
||||
const char* loadCmd[]{"loadfile", local.constData(), nullptr};
|
||||
if (mpv_command(m_mpv, loadCmd) < 0)
|
||||
qWarning() << "WallpaperService: mpv could not load" << path;
|
||||
|
||||
m_mpvFrameReady.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void WallpaperService::pumpMpvEvents() {
|
||||
if (!m_mpv) return;
|
||||
|
||||
mpv_event* event = nullptr;
|
||||
while ((event = mpv_wait_event(m_mpv, 0)) != nullptr &&
|
||||
event->event_id != MPV_EVENT_NONE) {
|
||||
if (event->event_id == MPV_EVENT_LOG_MESSAGE) {
|
||||
auto* data = static_cast<mpv_event_log_message*>(event->data);
|
||||
if (data && data->text)
|
||||
qWarning().noquote() << "mpv:" << data->prefix << data->text;
|
||||
}
|
||||
|
||||
if (event->event_id == MPV_EVENT_FILE_LOADED) {
|
||||
m_videoLoaded.store(true, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
if (event->event_id == MPV_EVENT_END_FILE) {
|
||||
m_videoLoaded.store(false, std::memory_order_relaxed);
|
||||
m_videoReady.store(false, std::memory_order_relaxed);
|
||||
|
||||
auto* data = static_cast<mpv_event_end_file*>(event->data);
|
||||
if (data && data->error != MPV_END_FILE_REASON_EOF)
|
||||
qWarning() << "WallpaperService: mpv ended the file, error"
|
||||
<< data->error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WallpaperService::stopVideo() {
|
||||
teardownMpv();
|
||||
}
|
||||
|
||||
void WallpaperService::teardownMpv() {
|
||||
if (m_mpvRender) {
|
||||
mpv_render_context_free(m_mpvRender);
|
||||
m_mpvRender = nullptr;
|
||||
}
|
||||
|
||||
if (m_mpv) {
|
||||
mpv_terminate_destroy(m_mpv);
|
||||
m_mpv = nullptr;
|
||||
}
|
||||
|
||||
m_mpvFrameReady.store(false, std::memory_order_relaxed);
|
||||
m_videoReady.store(false, std::memory_order_relaxed);
|
||||
m_videoLoaded.store(false, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void* WallpaperService::getMpvProcAddress(void* ctx, const char* name) {
|
||||
(void)ctx;
|
||||
|
||||
if (strcmp(name, "glFenceSync") == 0)
|
||||
return reinterpret_cast<void*>(
|
||||
+[](GLenum, GLbitfield) -> GLsync { return nullptr; });
|
||||
|
||||
return reinterpret_cast<void*>(eglGetProcAddress(name));
|
||||
}
|
||||
|
||||
void WallpaperService::mpvUpdate(void* ctx) {
|
||||
auto* self = static_cast<WallpaperService*>(ctx);
|
||||
|
||||
self->m_mpvFrameReady.store(true, std::memory_order_relaxed);
|
||||
|
||||
if (self->m_videoLoaded.load(std::memory_order_relaxed))
|
||||
self->m_videoReady.store(true, std::memory_order_relaxed);
|
||||
|
||||
self->wake();
|
||||
}
|
||||
|
||||
void WallpaperService::initMpvRender() {
|
||||
if (m_mpvRender || !m_mpv) return;
|
||||
|
||||
mpv_opengl_init_params gl_init{getMpvProcAddress, nullptr};
|
||||
|
||||
mpv_render_param params[]{
|
||||
{MPV_RENDER_PARAM_API_TYPE,
|
||||
const_cast<char*>(MPV_RENDER_API_TYPE_OPENGL)},
|
||||
{MPV_RENDER_PARAM_OPENGL_INIT_PARAMS, &gl_init},
|
||||
{MPV_RENDER_PARAM_INVALID, nullptr},
|
||||
};
|
||||
|
||||
if (mpv_render_context_create(&m_mpvRender, m_mpv, params) < 0) {
|
||||
qWarning() << "WallpaperService: could not create mpv render context";
|
||||
m_mpvRender = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
mpv_render_context_set_update_callback(m_mpvRender, mpvUpdate, this);
|
||||
}
|
||||
|
||||
void WallpaperService::renderMpvToSurface(const QSize& size) {
|
||||
if (!m_mpvRender) initMpvRender();
|
||||
if (!m_mpvRender) return;
|
||||
|
||||
glViewport(0, 0, size.width(), size.height());
|
||||
|
||||
mpv_opengl_fbo fbo{0, size.width(), size.height(), GL_RGBA};
|
||||
|
||||
int flip = 1;
|
||||
int block = 0;
|
||||
mpv_render_param params[]{
|
||||
{MPV_RENDER_PARAM_OPENGL_FBO, &fbo},
|
||||
{MPV_RENDER_PARAM_FLIP_Y, &flip},
|
||||
{MPV_RENDER_PARAM_BLOCK_FOR_TARGET_TIME, &block},
|
||||
{MPV_RENDER_PARAM_INVALID, nullptr},
|
||||
};
|
||||
|
||||
mpv_render_context_render(m_mpvRender, params);
|
||||
}
|
||||
|
||||
void WallpaperService::createBlackTexture(uint32_t& texture) {
|
||||
if (texture != 0) return;
|
||||
|
||||
const unsigned char pixel[4] = {0, 0, 0, 255};
|
||||
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
glTexImage2D(
|
||||
GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixel);
|
||||
}
|
||||
|
||||
void WallpaperService::start() {
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
@@ -260,11 +662,7 @@ void WallpaperService::wake() {
|
||||
}
|
||||
|
||||
void WallpaperService::queue(WallpaperSurface* surface) {
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
m_pending.push_back(surface);
|
||||
}
|
||||
|
||||
surface->markDirty();
|
||||
wake();
|
||||
}
|
||||
|
||||
@@ -273,7 +671,7 @@ void WallpaperService::queueAll() {
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
|
||||
for (auto* surface : m_surfaces)
|
||||
m_pending.push_back(surface);
|
||||
surface->markDirty();
|
||||
}
|
||||
|
||||
wake();
|
||||
@@ -323,13 +721,50 @@ void WallpaperService::present() {
|
||||
|
||||
ensureDecodeSize();
|
||||
|
||||
std::vector<WallpaperSurface*> pending;
|
||||
QList<WallpaperSurface*> surfaces;
|
||||
bool fading = false;
|
||||
bool videoReload = false;
|
||||
QString path;
|
||||
bool isVideo = false;
|
||||
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
pending.swap(m_pending);
|
||||
|
||||
if (m_videoReload) {
|
||||
m_videoReload = false;
|
||||
videoReload = true;
|
||||
path = m_path;
|
||||
isVideo = m_isVideo;
|
||||
}
|
||||
|
||||
fading = m_fading;
|
||||
const bool newVideoFrame =
|
||||
m_isVideo &&
|
||||
m_mpvFrameReady.exchange(false, std::memory_order_relaxed);
|
||||
|
||||
if (fading || newVideoFrame)
|
||||
for (auto* surface : m_surfaces)
|
||||
surface->markDirty();
|
||||
|
||||
surfaces = m_surfaces;
|
||||
}
|
||||
|
||||
for (auto* surface : pending)
|
||||
if (m_context == EGL_NO_CONTEXT) return;
|
||||
|
||||
eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, m_context);
|
||||
|
||||
if (videoReload) {
|
||||
if (isVideo)
|
||||
startVideo(path);
|
||||
else if (m_mpv && !m_previousVideo)
|
||||
teardownMpv();
|
||||
}
|
||||
|
||||
const bool videoActive = (m_isVideo || m_previousVideo) && m_mpv != nullptr;
|
||||
|
||||
if (videoActive) pumpMpvEvents();
|
||||
|
||||
for (auto* surface : surfaces)
|
||||
drawSurface(surface);
|
||||
}
|
||||
|
||||
@@ -494,7 +929,8 @@ uint32_t WallpaperService::createProgram() {
|
||||
logShader(program, true);
|
||||
|
||||
if (glGetUniformLocation(program, "size") < 0 ||
|
||||
glGetUniformLocation(program, "crop") < 0) {
|
||||
glGetUniformLocation(program, "crop") < 0 ||
|
||||
glGetUniformLocation(program, "alpha") < 0) {
|
||||
qWarning() << "WallpaperService: the program does not have the "
|
||||
"uniforms it needs";
|
||||
glDeleteProgram(program);
|
||||
@@ -503,22 +939,23 @@ uint32_t WallpaperService::createProgram() {
|
||||
|
||||
m_sizeUniform = glGetUniformLocation(program, "size");
|
||||
m_cropUniform = glGetUniformLocation(program, "crop");
|
||||
m_alphaUniform = glGetUniformLocation(program, "alpha");
|
||||
|
||||
glDeleteShader(vertex);
|
||||
glDeleteShader(fragment);
|
||||
return program;
|
||||
}
|
||||
|
||||
void WallpaperService::uploadImage() {
|
||||
if (m_image.isNull()) return;
|
||||
void WallpaperService::uploadTexture(uint32_t& texture, const QImage& image) {
|
||||
if (image.isNull()) return;
|
||||
|
||||
if (m_texture == 0) {
|
||||
glGenTextures(1, &m_texture);
|
||||
glBindTexture(GL_TEXTURE_2D, m_texture);
|
||||
if (texture == 0) {
|
||||
glGenTextures(1, &texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
} else {
|
||||
glBindTexture(GL_TEXTURE_2D, m_texture);
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
}
|
||||
|
||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||
@@ -526,12 +963,35 @@ void WallpaperService::uploadImage() {
|
||||
GL_TEXTURE_2D,
|
||||
0,
|
||||
GL_RGBA8,
|
||||
m_image.width(),
|
||||
m_image.height(),
|
||||
image.width(),
|
||||
image.height(),
|
||||
0,
|
||||
GL_RGBA,
|
||||
GL_UNSIGNED_BYTE,
|
||||
m_image.constBits());
|
||||
image.constBits());
|
||||
}
|
||||
|
||||
void WallpaperService::uploadImage() {
|
||||
uploadTexture(m_texture, m_image);
|
||||
}
|
||||
|
||||
void WallpaperService::drawTexture(
|
||||
uint32_t texture,
|
||||
const QRectF& rect,
|
||||
float sizeW,
|
||||
float sizeH,
|
||||
float alpha) {
|
||||
glBindTexture(GL_TEXTURE_2D, texture);
|
||||
glUniform2f(GLint(m_sizeUniform), sizeW, sizeH);
|
||||
glUniform4f(
|
||||
GLint(m_cropUniform),
|
||||
GLfloat(rect.x()),
|
||||
GLfloat(rect.y()),
|
||||
GLfloat(rect.width()),
|
||||
GLfloat(rect.height()));
|
||||
glUniform1f(GLint(m_alphaUniform), alpha);
|
||||
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
}
|
||||
|
||||
bool WallpaperService::decode(const QString& path) {
|
||||
@@ -564,6 +1024,8 @@ bool WallpaperService::ensureDecodeSize() {
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
|
||||
if (m_isVideo) return false;
|
||||
|
||||
largest = largestBufferSize();
|
||||
if (largest.isEmpty()) return false;
|
||||
|
||||
@@ -611,7 +1073,10 @@ QSize WallpaperService::largestBufferSize() {
|
||||
}
|
||||
|
||||
QRectF WallpaperService::sourceRect(
|
||||
const QVariantMap& crops, const QString& screen, const QSize& size) const {
|
||||
const QVariantMap& crops,
|
||||
const QString& screen,
|
||||
const QSize& size,
|
||||
const QSize& image) const {
|
||||
QVariantMap crop = cropFor(crops, screen);
|
||||
|
||||
QRectF rect(
|
||||
@@ -620,10 +1085,10 @@ QRectF WallpaperService::sourceRect(
|
||||
crop.value("width", 1.0).toDouble(),
|
||||
crop.value("height", 1.0).toDouble());
|
||||
|
||||
if (size.isEmpty() || rect.isEmpty() || m_image.isNull()) return rect;
|
||||
if (size.isEmpty() || rect.isEmpty() || image.isEmpty()) return rect;
|
||||
|
||||
qreal imageWidth = qreal(m_image.width());
|
||||
qreal imageHeight = qreal(m_image.height());
|
||||
qreal imageWidth = qreal(image.width());
|
||||
qreal imageHeight = qreal(image.height());
|
||||
|
||||
QRectF pixels(
|
||||
rect.x() * imageWidth,
|
||||
@@ -652,6 +1117,8 @@ QRectF WallpaperService::sourceRect(
|
||||
}
|
||||
|
||||
void WallpaperService::drawSurface(WallpaperSurface* surface) {
|
||||
if (!surface->needsDraw() || !surface->frameReady()) return;
|
||||
|
||||
EGLSurface target = surface->eglSurface();
|
||||
if (m_context == EGL_NO_CONTEXT || target == EGL_NO_SURFACE ||
|
||||
surface->bufferSize().isEmpty())
|
||||
@@ -662,9 +1129,8 @@ void WallpaperService::drawSurface(WallpaperSurface* surface) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Release the context when this scope ends (see FrameService::drawSurface).
|
||||
// Holding it across the draw loop's sleep serializes the two raw-EGL clients on
|
||||
// NVIDIA, which makes eglSwapBuffers fail and starves other processes' contexts.
|
||||
eglSwapInterval(m_eglDisplay, 0);
|
||||
|
||||
struct ContextRelease {
|
||||
EGLDisplay display;
|
||||
~ContextRelease() {
|
||||
@@ -675,7 +1141,6 @@ void WallpaperService::drawSurface(WallpaperSurface* surface) {
|
||||
|
||||
QVariantMap crops;
|
||||
QSize size;
|
||||
bool upload = false;
|
||||
|
||||
{
|
||||
std::lock_guard<std::recursive_mutex> lock(m_mutex);
|
||||
@@ -683,38 +1148,153 @@ void WallpaperService::drawSurface(WallpaperSurface* surface) {
|
||||
crops = m_crops;
|
||||
size = surface->bufferSize();
|
||||
|
||||
upload = m_imageChanged;
|
||||
m_imageChanged = false;
|
||||
if (size.isEmpty()) return;
|
||||
|
||||
if (m_program == 0) m_program = createProgram();
|
||||
if (m_program == 0) return;
|
||||
|
||||
const bool videoNow = m_isVideo;
|
||||
|
||||
if (videoNow && !m_videoReady.load(std::memory_order_relaxed)) return;
|
||||
|
||||
const bool videoAsBackground =
|
||||
(videoNow || (m_previousVideo && m_fading)) && m_mpv != nullptr;
|
||||
|
||||
if (videoAsBackground) {
|
||||
if (!m_mpvRender) initMpvRender();
|
||||
if (!m_mpvRender) return;
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
glViewport(0, 0, size.width(), size.height());
|
||||
renderMpvToSurface(size);
|
||||
} else {
|
||||
glUseProgram(m_program);
|
||||
glViewport(0, 0, size.width(), size.height());
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
}
|
||||
|
||||
if (!videoNow) {
|
||||
const bool upload = m_imageChanged;
|
||||
m_imageChanged = false;
|
||||
|
||||
if (m_texture == 0 || upload) uploadImage();
|
||||
|
||||
if (m_texture == 0 || m_image.isNull()) return;
|
||||
}
|
||||
|
||||
glUseProgram(m_program);
|
||||
glEnable(GL_BLEND);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
|
||||
if (m_fading && !m_fadeClockStarted) {
|
||||
m_fadeClockStarted = true;
|
||||
m_fadeTimer.start();
|
||||
}
|
||||
|
||||
bool fade = false;
|
||||
bool fadeDone = false;
|
||||
qreal progress = 1.0;
|
||||
|
||||
if (m_fading) {
|
||||
progress = qreal(m_fadeTimer.elapsed()) / qreal(kFadeDurationMs);
|
||||
if (progress >= 1.0)
|
||||
fadeDone = true;
|
||||
else
|
||||
fade = true;
|
||||
}
|
||||
|
||||
const float w = float(size.width());
|
||||
const float h = float(size.height());
|
||||
|
||||
if (videoNow) {
|
||||
if (fade && !m_previousImage.isNull()) {
|
||||
if (m_previousTexture == 0)
|
||||
uploadTexture(m_previousTexture, m_previousImage);
|
||||
|
||||
if (m_previousTexture != 0) {
|
||||
const QRectF previousRect = sourceRect(
|
||||
m_previousCrops,
|
||||
surface->screen(),
|
||||
size,
|
||||
m_previousImage.size());
|
||||
|
||||
drawTexture(
|
||||
m_previousTexture,
|
||||
previousRect,
|
||||
w,
|
||||
h,
|
||||
1.0f - float(progress));
|
||||
}
|
||||
} else if (fade && !m_firstFadeDone) {
|
||||
if (m_blackTex == 0) createBlackTexture(m_blackTex);
|
||||
|
||||
if (m_blackTex != 0)
|
||||
drawTexture(
|
||||
m_blackTex,
|
||||
QRectF(0.0, 0.0, 1.0, 1.0),
|
||||
w,
|
||||
h,
|
||||
1.0f - float(progress));
|
||||
}
|
||||
} else {
|
||||
const QRectF rect =
|
||||
sourceRect(crops, surface->screen(), size, m_image.size());
|
||||
|
||||
if (fade) {
|
||||
if (!m_previousVideo && !m_previousImage.isNull() &&
|
||||
m_previousTexture == 0) {
|
||||
uploadTexture(m_previousTexture, m_previousImage);
|
||||
}
|
||||
|
||||
if (!m_previousVideo && m_previousTexture != 0) {
|
||||
const QRectF previousRect = sourceRect(
|
||||
m_previousCrops,
|
||||
surface->screen(),
|
||||
size,
|
||||
m_previousImage.size());
|
||||
|
||||
drawTexture(m_previousTexture, previousRect, w, h, 1.0f);
|
||||
}
|
||||
|
||||
drawTexture(m_texture, rect, w, h, float(progress));
|
||||
} else {
|
||||
drawTexture(m_texture, rect, w, h, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
if (fadeDone) {
|
||||
m_fading = false;
|
||||
m_firstFadeDone = true;
|
||||
|
||||
if (m_previousVideo) {
|
||||
teardownMpv();
|
||||
m_previousVideo = false;
|
||||
}
|
||||
|
||||
if (m_previousTexture != 0) {
|
||||
glDeleteTextures(1, &m_previousTexture);
|
||||
m_previousTexture = 0;
|
||||
}
|
||||
|
||||
m_previousImage = QImage();
|
||||
}
|
||||
}
|
||||
|
||||
if (size.isEmpty()) return;
|
||||
surface->requestFrame();
|
||||
|
||||
if (m_program == 0) m_program = createProgram();
|
||||
if (m_texture == 0 || upload) uploadImage();
|
||||
|
||||
if (m_program == 0 || m_texture == 0 || m_image.isNull()) return;
|
||||
|
||||
glUseProgram(m_program);
|
||||
glBindTexture(GL_TEXTURE_2D, m_texture);
|
||||
|
||||
glUniform2f(
|
||||
GLint(m_sizeUniform), GLfloat(size.width()), GLfloat(size.height()));
|
||||
|
||||
QRectF rect = sourceRect(crops, surface->screen(), size);
|
||||
glUniform4f(
|
||||
GLint(m_cropUniform),
|
||||
GLfloat(rect.x()),
|
||||
GLfloat(rect.y()),
|
||||
GLfloat(rect.width()),
|
||||
GLfloat(rect.height()));
|
||||
|
||||
glViewport(0, 0, size.width(), size.height());
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
|
||||
if (!eglSwapBuffers(m_eglDisplay, target))
|
||||
if (!eglSwapBuffers(m_eglDisplay, target)) {
|
||||
qWarning()
|
||||
<< "WallpaperService: eglSwapBuffers failed, eglGetError = 0x"
|
||||
<< QString::number(unsigned(eglGetError()), 16);
|
||||
return;
|
||||
}
|
||||
|
||||
if ((m_isVideo || m_previousVideo) && m_mpvRender)
|
||||
mpv_render_context_report_swap(m_mpvRender);
|
||||
|
||||
surface->endDraw();
|
||||
}
|
||||
|
||||
void WallpaperService::addSurface(const QString& screen) {
|
||||
|
||||
Reference in New Issue
Block a user