feat: add vaapi path for video wallpapers on generic gpus
C++ / fmt (pull_request) Failing after 6s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 11s
JS/TS / fmt (pull_request) Successful in 10s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Failing after 32s
Rust / build (pull_request) Successful in 52s
Python / verify (pull_request) Failing after 1m23s
Rust / fmt (pull_request) Successful in 51s
Rust / clippy (pull_request) Successful in 1m2s
C++ / fmt (pull_request) Failing after 6s
C++ / build (pull_request) Failing after 10s
C++ / clang-tidy (pull_request) Failing after 11s
JS/TS / fmt (pull_request) Successful in 10s
JS/TS / lint (pull_request) Successful in 8s
Python / static (pull_request) Failing after 32s
Rust / build (pull_request) Successful in 52s
Python / verify (pull_request) Failing after 1m23s
Rust / fmt (pull_request) Successful in 51s
Rust / clippy (pull_request) Successful in 1m2s
This commit is contained in:
@@ -0,0 +1,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
|
||||
Reference in New Issue
Block a user