]> git.sesse.net Git - pkanalytics/commitdiff
Move to a native OpenGL window.
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 22 Jul 2023 18:02:15 +0000 (20:02 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Sat, 22 Jul 2023 18:02:15 +0000 (20:02 +0200)
Seemingly the compositing is really expensive in Qt; supposedly
this has issues on macOS, but that's life.

video_widget.cpp
video_widget.h

index 6d21e1b983648d443412eda57cbe35b8b2c43e5d..0bd910ce29f826e86e62629ad609158ca72e09a1 100644 (file)
@@ -35,6 +35,8 @@ extern "C" {
 #include <QOpenGLFunctions>
 #include <QWheelEvent>
 #include <QMouseEvent>
+#include <QMouseEvent>
+#include <QHBoxLayout>
 
 #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<Frame> 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<Frame> 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> frame;
+       std::shared_ptr<VideoWidget::Frame> 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<VideoWidget::Frame> 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<Frame> 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<Frame> 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::Frame> 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::Frame> 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;
index e3734f0e5e40c9a08e8437ad39a5cd38001fb02d..4361d3098c8f3fd2d6414d69ebd868f32a3f2ed6 100644 (file)
@@ -1,7 +1,8 @@
 #ifndef _VIDEO_WIDGET_H
 #define _VIDEO_WIDGET_H 1
 
-#include <QOpenGLWidget>
+#include <QWidget>
+#include <QOpenGLWindow>
 #include <string>
 #include <atomic>
 #include <thread>
@@ -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 *> frame_freelist;  // Protected by freelist_mu.
        std::deque<AVFrameWithDeleter> 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<VideoWidget::Frame> 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<VideoWidget::Frame> current_frame;  // Protected by current_frame_mu.
+};
+
 #endif  // !defined(_VIDEO_WIDGET_H)