]> git.sesse.net Git - nageru/blobdiff - mixer.cpp
Filter Qt signals about updated names and colors.
[nageru] / mixer.cpp
index d84754e2bcb40b494e7eeb2e6dbdd1518ec10791..fd982a5708d61d59f4dfca1204cd7dc6dda2e5d5 100644 (file)
--- a/mixer.cpp
+++ b/mixer.cpp
@@ -142,6 +142,7 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
        theme.reset(new Theme(global_flags.theme_filename.c_str(), resource_pool.get(), num_cards));
        for (unsigned i = 0; i < NUM_OUTPUTS; ++i) {
                output_channel[i].parent = this;
+               output_channel[i].channel = i;
        }
 
        ImageFormat inout_format;
@@ -256,7 +257,9 @@ Mixer::Mixer(const QSurfaceFormat &format, unsigned num_cards)
        // and there's a limit to how important the peak meter is.
        peak_resampler.setup(OUTPUT_FREQUENCY, OUTPUT_FREQUENCY * 4, /*num_channels=*/2, /*hlen=*/16, /*frel=*/1.0);
 
-       alsa.reset(new ALSAOutput(OUTPUT_FREQUENCY, /*num_channels=*/2));
+       if (global_flags.enable_alsa_output) {
+               alsa.reset(new ALSAOutput(OUTPUT_FREQUENCY, /*num_channels=*/2));
+       }
 }
 
 Mixer::~Mixer()
@@ -287,18 +290,6 @@ void Mixer::configure_card(unsigned card_index, const QSurfaceFormat &format, Ca
        card->frame_allocator.reset(new PBOFrameAllocator(8 << 20, WIDTH, HEIGHT));  // 8 MB.
        card->capture->set_video_frame_allocator(card->frame_allocator.get());
        card->surface = create_surface(format);
-       card->capture->set_dequeue_thread_callbacks(
-               [card]{
-                       eglBindAPI(EGL_OPENGL_API);
-                       card->context = create_context(card->surface);
-                       if (!make_current(card->context, card->surface)) {
-                               printf("failed to create bmusb context\n");
-                               exit(1);
-                       }
-               },
-               [this]{
-                       resource_pool->clean_context();
-               });
        card->resampling_queue.reset(new ResamplingQueue(OUTPUT_FREQUENCY, OUTPUT_FREQUENCY, 2));
        card->capture->configure_card();
 }
@@ -501,59 +492,64 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
        size_t y_offset = video_frame.size / 2 + video_offset / 2;
 
        for (unsigned field = 0; field < num_fields; ++field) {
-               unsigned field_start_line = (field == 1) ? video_format.second_field_start : video_format.extra_lines_top + field * (video_format.height + 22);
-
-               if (userdata->tex_y[field] == 0 ||
-                   userdata->tex_cbcr[field] == 0 ||
-                   video_format.width != userdata->last_width[field] ||
-                   video_format.height != userdata->last_height[field]) {
-                       // We changed resolution since last use of this texture, so we need to create
-                       // a new object. Note that this each card has its own PBOFrameAllocator,
-                       // we don't need to worry about these flip-flopping between resolutions.
+               // Put the actual texture upload in a lambda that is executed in the main thread.
+               // It is entirely possible to do this in the same thread (and it might even be
+               // faster, depending on the GPU and driver), but it appears to be trickling
+               // driver bugs very easily.
+               //
+               // Note that this means we must hold on to the actual frame data in <userdata>
+               // until the upload command is run, but we hold on to <frame> much longer than that
+               // (in fact, all the way until we no longer use the texture in rendering).
+               auto upload_func = [field, video_format, y_offset, cbcr_offset, cbcr_width, userdata]() {
+                       unsigned field_start_line = (field == 1) ? video_format.second_field_start : video_format.extra_lines_top + field * (video_format.height + 22);
+
+                       if (userdata->tex_y[field] == 0 ||
+                           userdata->tex_cbcr[field] == 0 ||
+                           video_format.width != userdata->last_width[field] ||
+                           video_format.height != userdata->last_height[field]) {
+                               // We changed resolution since last use of this texture, so we need to create
+                               // a new object. Note that this each card has its own PBOFrameAllocator,
+                               // we don't need to worry about these flip-flopping between resolutions.
+                               glBindTexture(GL_TEXTURE_2D, userdata->tex_cbcr[field]);
+                               check_error();
+                               glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, cbcr_width, video_format.height, 0, GL_RG, GL_UNSIGNED_BYTE, nullptr);
+                               check_error();
+                               glBindTexture(GL_TEXTURE_2D, userdata->tex_y[field]);
+                               check_error();
+                               glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, video_format.width, video_format.height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
+                               check_error();
+                               userdata->last_width[field] = video_format.width;
+                               userdata->last_height[field] = video_format.height;
+                       }
+
+                       GLuint pbo = userdata->pbo;
+                       check_error();
+                       glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
+                       check_error();
+
+                       size_t field_y_start = y_offset + video_format.width * field_start_line;
+                       size_t field_cbcr_start = cbcr_offset + cbcr_width * field_start_line * sizeof(uint16_t);
+
+                       if (global_flags.flush_pbos) {
+                               glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, field_y_start, video_format.width * video_format.height);
+                               check_error();
+                               glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, field_cbcr_start, cbcr_width * video_format.height * sizeof(uint16_t));
+                               check_error();
+                       }
+
                        glBindTexture(GL_TEXTURE_2D, userdata->tex_cbcr[field]);
                        check_error();
-                       glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, cbcr_width, video_format.height, 0, GL_RG, GL_UNSIGNED_BYTE, nullptr);
+                       glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cbcr_width, video_format.height, GL_RG, GL_UNSIGNED_BYTE, BUFFER_OFFSET(field_cbcr_start));
                        check_error();
                        glBindTexture(GL_TEXTURE_2D, userdata->tex_y[field]);
                        check_error();
-                       glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, video_format.width, video_format.height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
+                       glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, video_format.width, video_format.height, GL_RED, GL_UNSIGNED_BYTE, BUFFER_OFFSET(field_y_start));
                        check_error();
-                       userdata->last_width[field] = video_format.width;
-                       userdata->last_height[field] = video_format.height;
-               }
-
-               GLuint pbo = userdata->pbo;
-               check_error();
-               glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo);
-               check_error();
-
-               size_t field_y_start = y_offset + video_format.width * field_start_line;
-               size_t field_cbcr_start = cbcr_offset + cbcr_width * field_start_line * sizeof(uint16_t);
-
-               if (global_flags.flush_pbos) {
-                       glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, field_y_start, video_format.width * video_format.height);
+                       glBindTexture(GL_TEXTURE_2D, 0);
                        check_error();
-                       glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, field_cbcr_start, cbcr_width * video_format.height * sizeof(uint16_t));
+                       glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
                        check_error();
-               }
-
-               glBindTexture(GL_TEXTURE_2D, userdata->tex_cbcr[field]);
-               check_error();
-               glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cbcr_width, video_format.height, GL_RG, GL_UNSIGNED_BYTE, BUFFER_OFFSET(field_cbcr_start));
-               check_error();
-               glBindTexture(GL_TEXTURE_2D, userdata->tex_y[field]);
-               check_error();
-               glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, video_format.width, video_format.height, GL_RED, GL_UNSIGNED_BYTE, BUFFER_OFFSET(field_y_start));
-               check_error();
-               glBindTexture(GL_TEXTURE_2D, 0);
-               check_error();
-               glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
-               check_error();
-               RefCountedGLsync fence(GL_SYNC_GPU_COMMANDS_COMPLETE, /*flags=*/0);
-               check_error();
-               assert(fence.get() != nullptr);
-               glFlush();  // Make sure the main thread doesn't have to wait until we push out enough frames to make a new command buffer.
-               check_error();
+               };
 
                if (field == 1) {
                        // Don't upload the second field as fast as we can; wait until
@@ -581,7 +577,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode,
                        new_frame.length = frame_length;
                        new_frame.field = field;
                        new_frame.interlaced = video_format.interlaced;
-                       new_frame.ready_fence = fence;
+                       new_frame.upload_func = upload_func;
                        new_frame.dropped_frames = dropped_frames;
                        card->new_frames.push(move(new_frame));
                        card->new_frames_changed.notify_all();
@@ -648,13 +644,10 @@ void Mixer::thread_func()
                        insert_new_frame(new_frame->frame, new_frame->field, new_frame->interlaced, card_index, &input_state);
                        check_error();
 
-                       // The new texture might still be uploaded,
-                       // tell the GPU to wait until it's there.
-                       if (new_frame->ready_fence) {
-                               glWaitSync(new_frame->ready_fence.get(), /*flags=*/0, GL_TIMEOUT_IGNORED);
-                               check_error();
-                               new_frame->ready_fence.reset();
-                               check_error();
+                       // The new texture might need uploading before use.
+                       if (new_frame->upload_func) {
+                               new_frame->upload_func();
+                               new_frame->upload_func = nullptr;
                        }
                }
 
@@ -1148,9 +1141,47 @@ void Mixer::OutputChannel::output_frame(DisplayFrame frame)
                has_ready_frame = true;
        }
 
-       if (has_new_frame_ready_callback) {
+       if (new_frame_ready_callback) {
                new_frame_ready_callback();
        }
+
+       // Reduce the number of callbacks by filtering duplicates. The reason
+       // why we bother doing this is that Qt seemingly can get into a state
+       // where its builds up an essentially unbounded queue of signals,
+       // consuming more and more memory, and there's no good way of collapsing
+       // user-defined signals or limiting the length of the queue.
+       if (transition_names_updated_callback) {
+               vector<string> transition_names = global_mixer->get_transition_names();
+               bool changed = false;
+               if (transition_names.size() != last_transition_names.size()) {
+                       changed = true;
+               } else {
+                       for (unsigned i = 0; i < transition_names.size(); ++i) {
+                               if (transition_names[i] != last_transition_names[i]) {
+                                       changed = true;
+                                       break;
+                               }
+                       }
+               }
+               if (changed) {
+                       transition_names_updated_callback(transition_names);
+                       last_transition_names = transition_names;
+               }
+       }
+       if (name_updated_callback) {
+               string name = global_mixer->get_channel_name(channel);
+               if (name != last_name) {
+                       name_updated_callback(name);
+                       last_name = name;
+               }
+       }
+       if (color_updated_callback) {
+               string color = global_mixer->get_channel_color(channel);
+               if (color != last_color) {
+                       color_updated_callback(color);
+                       last_color = color;
+               }
+       }
 }
 
 bool Mixer::OutputChannel::get_display_frame(DisplayFrame *frame)
@@ -1181,7 +1212,21 @@ bool Mixer::OutputChannel::get_display_frame(DisplayFrame *frame)
 void Mixer::OutputChannel::set_frame_ready_callback(Mixer::new_frame_ready_callback_t callback)
 {
        new_frame_ready_callback = callback;
-       has_new_frame_ready_callback = true;
+}
+
+void Mixer::OutputChannel::set_transition_names_updated_callback(Mixer::transition_names_updated_callback_t callback)
+{
+       transition_names_updated_callback = callback;
+}
+
+void Mixer::OutputChannel::set_name_updated_callback(Mixer::name_updated_callback_t callback)
+{
+       name_updated_callback = callback;
+}
+
+void Mixer::OutputChannel::set_color_updated_callback(Mixer::color_updated_callback_t callback)
+{
+       color_updated_callback = callback;
 }
 
 mutex RefCountedGLsync::fence_lock;