From a41db5d218a10c7863952d98eea1daab9f8d0171 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Sat, 22 Jul 2023 20:02:15 +0200 Subject: [PATCH] Move to a native OpenGL window. Seemingly the compositing is really expensive in Qt; supposedly this has issues on macOS, but that's life. --- video_widget.cpp | 75 ++++++++++++++++++++++++++++-------------------- video_widget.h | 70 +++++++++++++++++++++++++++++++++++--------- 2 files changed, 101 insertions(+), 44 deletions(-) diff --git a/video_widget.cpp b/video_widget.cpp index 6d21e1b..0bd910c 100644 --- a/video_widget.cpp +++ b/video_widget.cpp @@ -35,6 +35,8 @@ extern "C" { #include #include #include +#include +#include #define BUFFER_OFFSET(i) ((char *)nullptr + (i)) @@ -196,11 +198,7 @@ bool VideoWidget::process_queued_commands(AVFormatContext *format_ctx, AVCodecCo queue.pop_front(); queued_frames = std::move(queue); } - shared_ptr new_frame = make_video_frame(frame.get()); - { - lock_guard lock(current_frame_mu); - current_frame = std::move(new_frame); - } + video_window->set_current_frame(make_video_frame(frame.get())); update(); store_pts(frame->pts); break; @@ -231,11 +229,7 @@ bool VideoWidget::process_queued_commands(AVFormatContext *format_ctx, AVCodecCo if (frame == nullptr || error) { return true; } - shared_ptr new_frame = make_video_frame(frame.get()); - { - lock_guard lock(current_frame_mu); - current_frame = std::move(new_frame); - } + video_window->set_current_frame(make_video_frame(frame.get())); update(); store_pts(frame->pts); } @@ -243,7 +237,17 @@ bool VideoWidget::process_queued_commands(AVFormatContext *format_ctx, AVCodecCo } VideoWidget::VideoWidget(QWidget *parent) - : QOpenGLWidget(parent) {} + : QWidget(parent), + video_window(new VideoWindow(this)) { + setLayout(new QHBoxLayout); + layout()->addWidget(QWidget::createWindowContainer(video_window)); + + // ... + connect(video_window, &VideoWindow::mouse_wheel, this, &VideoWidget::wheelEvent); + connect(video_window, &VideoWindow::mouse_pressed, this, &VideoWidget::mousePressEvent); + connect(video_window, &VideoWindow::mouse_released, this, &VideoWidget::mouseReleaseEvent); + connect(video_window, &VideoWindow::mouse_moved, this, &VideoWidget::mouseMoveEvent); +} GLuint compile_shader(const string &shader_src, GLenum type) { @@ -283,7 +287,7 @@ GLuint compile_shader(const string &shader_src, GLenum type) return obj; } -void VideoWidget::initializeGL() +void VideoWindow::initializeGL() { glDisable(GL_BLEND); glDisable(GL_DEPTH_TEST); @@ -366,7 +370,7 @@ void main() glSamplerParameteri(bilinear_sampler, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); } -void VideoWidget::resizeGL(int w, int h) +void VideoWindow::resizeGL(int w, int h) { glViewport(0, 0, w, h); display_aspect = double(w) / h; @@ -383,9 +387,9 @@ int num_levels(GLuint width, GLuint height) return levels; } -void VideoWidget::paintGL() +void VideoWindow::paintGL() { - std::shared_ptr frame; + std::shared_ptr frame; { lock_guard lock(current_frame_mu); frame = current_frame; @@ -474,6 +478,15 @@ void VideoWidget::paintGL() glEnd(); } +void VideoWindow::set_current_frame(shared_ptr new_frame) +{ + { + lock_guard lock(current_frame_mu); + current_frame = std::move(new_frame); + } + update(); +} + void matmul3x3(const double a[9], const double b[9], double res[9]) { for (int i = 0; i < 3; ++i) { @@ -518,6 +531,7 @@ void VideoWidget::wheelEvent(QWheelEvent *event) matmul3x3(tmp2, translation_matrix, zoom_matrix); fixup_zoom_matrix(); + video_window->set_zoom_matrix(zoom_matrix); update(); } @@ -554,6 +568,7 @@ void VideoWidget::mouseMoveEvent(QMouseEvent *e) zoom_matrix[6] += dx; zoom_matrix[7] -= dy; fixup_zoom_matrix(); + video_window->set_zoom_matrix(zoom_matrix); last_drag_x = e->position().x(); last_drag_y = e->position().y(); @@ -950,11 +965,7 @@ bool VideoWidget::play_video(const string &pathname) bool finished_wakeup; finished_wakeup = producer_thread_should_quit.sleep_until(next_frame_start); if (finished_wakeup) { - shared_ptr new_frame = make_video_frame(frame.get()); - { - lock_guard lock(current_frame_mu); - current_frame = std::move(new_frame); - } + video_window->set_current_frame(make_video_frame(frame.get())); last_frame = steady_clock::now(); update(); break; @@ -968,11 +979,7 @@ bool VideoWidget::play_video(const string &pathname) if (paused) { // Just paused, so present the frame immediately and then go into deep sleep. - shared_ptr new_frame = make_video_frame(frame.get()); - { - lock_guard lock(current_frame_mu); - current_frame = std::move(new_frame); - } + video_window->set_current_frame(make_video_frame(frame.get())); last_frame = steady_clock::now(); update(); break; @@ -1033,17 +1040,21 @@ shared_ptr VideoWidget::alloc_frame(unsigned width, unsigned size_t len = frame->width * frame->height + 2 * frame->chroma_width * frame->chroma_height; + while (!video_window->isValid()) { + usleep(100000); + } + // Augh :-) mutex mu; condition_variable done_cv; bool done = false; post_to_main_thread([this, &frame, len, &done, &mu, &done_cv]{ - makeCurrent(); + video_window->makeCurrent(); glCreateBuffers(1, &frame->pbo); glNamedBufferStorage(frame->pbo, len, nullptr, GL_MAP_WRITE_BIT | GL_MAP_PERSISTENT_BIT); frame->data = (uint8_t *)glMapNamedBufferRange(frame->pbo, 0, len, GL_MAP_WRITE_BIT | GL_MAP_FLUSH_EXPLICIT_BIT | GL_MAP_PERSISTENT_BIT); - doneCurrent(); + video_window->doneCurrent(); lock_guard lock(mu); done = true; @@ -1064,10 +1075,10 @@ void VideoWidget::free_frame(VideoWidget::Frame *frame) if (self->frame_freelist.size() >= 16) { GLuint pbo = frame->pbo; post_to_main_thread([self, pbo]{ - self->makeCurrent(); + self->video_window->makeCurrent(); glUnmapNamedBuffer(pbo); glDeleteBuffers(1, &pbo); - self->doneCurrent(); + self->video_window->doneCurrent(); }); delete self->frame_freelist.front(); self->frame_freelist.pop_front(); @@ -1124,8 +1135,10 @@ shared_ptr VideoWidget::make_video_frame(const AVFrame *fram AV_CEIL_RSHIFT(int(frame->height), desc->log2_chroma_h)); // We always assume left chroma placement for now. - cbcr_offset[0] = compute_chroma_offset(0.0f, 1 << desc->log2_chroma_w, video_frame->chroma_width); - cbcr_offset[1] = compute_chroma_offset(0.5f, 1 << desc->log2_chroma_h, video_frame->chroma_height); + video_window->set_cbcr_offset( + compute_chroma_offset(0.0f, 1 << desc->log2_chroma_w, video_frame->chroma_width), + compute_chroma_offset(0.5f, 1 << desc->log2_chroma_h, video_frame->chroma_height) + ); pic_data[0] = video_frame->data; linesizes[0] = frame->width; diff --git a/video_widget.h b/video_widget.h index e3734f0..4361d30 100644 --- a/video_widget.h +++ b/video_widget.h @@ -1,7 +1,8 @@ #ifndef _VIDEO_WIDGET_H #define _VIDEO_WIDGET_H 1 -#include +#include +#include #include #include #include @@ -18,7 +19,9 @@ extern "C" { // Because QVideoWidget sucks, sadly. (Don't use GStreamer, kids.) -class VideoWidget : public QOpenGLWidget { +class VideoWindow; + +class VideoWidget : public QWidget { Q_OBJECT public: @@ -34,9 +37,6 @@ public: void seek_frames(int64_t relative_frames); // Relative seek. void seek_absolute(int64_t position_ms); // Absolute seek. - void initializeGL() override; - void resizeGL(int w, int h) override; - void paintGL() override; void wheelEvent(QWheelEvent *event) override; // For dragging, and for back/forward button presses. @@ -69,14 +69,6 @@ private: std::deque frame_freelist; // Protected by freelist_mu. std::deque queued_frames; // Frames decoded but not displayed. Only used when frame-stepping backwards. - GLuint ycbcr_vertex_shader, ycbcr_fragment_shader, ycbcr_program; - GLuint bilinear_sampler; - - GLuint tex[3]; - GLuint last_width = 0, last_height = 0; - GLuint last_chroma_width = 0, last_chroma_height = 0; - GLfloat cbcr_offset[2]; - double display_aspect = 1.0; double zoom_matrix[9] = { // Column-major, to match with OpenGL. 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, @@ -113,6 +105,8 @@ private: bool dragging = false; float last_drag_x, last_drag_y; + VideoWindow *video_window; + void producer_thread_func(); bool play_video(const std::string &pathname); void internal_rewind(); @@ -126,4 +120,54 @@ private: void fixup_zoom_matrix(); }; +class VideoWindow : public QOpenGLWindow { // Private implementation of VideoWidget, but moc does not support nested classes. + Q_OBJECT; + +public: + VideoWindow(VideoWidget *video) : video(video) {} + void initializeGL() override; + void resizeGL(int w, int h) override; + void paintGL() override; + void set_current_frame(std::shared_ptr new_frame); // Takes ownership. Thread-safe. + void set_cbcr_offset(float x, float y) // FIXME: Thread-safe? + { + cbcr_offset[0] = x; + cbcr_offset[1] = y; + } + void set_zoom_matrix(double new_zoom_matrix[9]) { + memcpy(zoom_matrix, new_zoom_matrix, sizeof(zoom_matrix)); + update(); + } + + void wheelEvent(QWheelEvent *e) override { emit mouse_wheel(e); } + void mousePressEvent(QMouseEvent *e) override { emit mouse_pressed(e); } + void mouseReleaseEvent(QMouseEvent *e) override { emit mouse_released(e); } + void mouseMoveEvent(QMouseEvent *e) override { emit mouse_moved(e); } + +signals: + void mouse_wheel(QWheelEvent *e); + void mouse_pressed(QMouseEvent *e); + void mouse_released(QMouseEvent *e); + void mouse_moved(QMouseEvent *e); + +private: + VideoWidget *video; + GLuint ycbcr_vertex_shader, ycbcr_fragment_shader, ycbcr_program; + GLuint bilinear_sampler; + GLuint tex[3]; + GLuint last_width = 0, last_height = 0; + GLuint last_chroma_width = 0, last_chroma_height = 0; + double display_aspect = 1.0; + GLfloat cbcr_offset[2]; + + double zoom_matrix[9] = { // Column-major, to match with OpenGL. + 1.0, 0.0, 0.0, + 0.0, 1.0, 0.0, + 0.0, 0.0, 1.0, + }; + + std::mutex current_frame_mu; + std::shared_ptr current_frame; // Protected by current_frame_mu. +}; + #endif // !defined(_VIDEO_WIDGET_H) -- 2.39.2