+ unique_lock<mutex> lock(bmusb_mutex);
+ card->new_data_ready = true;
+ card->new_frame = RefCountedFrame(FrameAllocator::Frame());
+ card->new_frame_length = frame_length;
+ card->new_data_ready_fence = nullptr;
+ card->dropped_frames = dropped_frames;
+ card->new_data_ready_changed.notify_all();
+ }
+ return;
+ }
+
+ PBOFrameAllocator::Userdata *userdata = (PBOFrameAllocator::Userdata *)video_frame.userdata;
+
+ // Upload the textures.
+ size_t cbcr_width = width / 2;
+ size_t cbcr_offset = video_offset / 2;
+ size_t y_offset = video_frame.size / 2 + video_offset / 2;
+
+ if (width != userdata->last_width || height != userdata->last_height) {
+ // 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);
+ check_error();
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RG8, cbcr_width, height, 0, GL_RG, GL_UNSIGNED_BYTE, nullptr);
+ check_error();
+ glBindTexture(GL_TEXTURE_2D, userdata->tex_y);
+ check_error();
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_R8, width, height, 0, GL_RED, GL_UNSIGNED_BYTE, nullptr);
+ check_error();
+ userdata->last_width = width;
+ userdata->last_height = height;
+ }
+
+ GLuint pbo = userdata->pbo;
+ check_error();
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER_ARB, pbo);
+ check_error();
+ glFlushMappedBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, video_frame.size);
+ check_error();
+ //glMemoryBarrier(GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT);
+ //check_error();
+
+ glBindTexture(GL_TEXTURE_2D, userdata->tex_cbcr);
+ check_error();
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, cbcr_width, height, GL_RG, GL_UNSIGNED_BYTE, BUFFER_OFFSET(cbcr_offset + cbcr_width * extra_lines_top * sizeof(uint16_t)));
+ check_error();
+ glBindTexture(GL_TEXTURE_2D, userdata->tex_y);
+ check_error();
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RED, GL_UNSIGNED_BYTE, BUFFER_OFFSET(y_offset + width * extra_lines_top));
+ check_error();
+ glBindTexture(GL_TEXTURE_2D, 0);
+ check_error();
+ GLsync fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, /*flags=*/0);
+ check_error();
+ assert(fence != nullptr);
+
+ {
+ unique_lock<mutex> lock(bmusb_mutex);
+ card->new_data_ready = true;
+ card->new_frame = RefCountedFrame(video_frame);
+ card->new_frame_length = frame_length;
+ card->new_data_ready_fence = fence;
+ card->dropped_frames = dropped_frames;
+ card->new_data_ready_changed.notify_all();
+ }
+}
+
+void Mixer::thread_func()
+{
+ eglBindAPI(EGL_OPENGL_API);
+ QOpenGLContext *context = create_context(mixer_surface);
+ if (!make_current(context, mixer_surface)) {
+ printf("oops\n");
+ exit(1);
+ }
+
+ struct timespec start, now;
+ clock_gettime(CLOCK_MONOTONIC, &start);
+
+ int frame = 0;
+ int stats_dropped_frames = 0;
+
+ while (!should_quit) {
+ CaptureCard card_copy[MAX_CARDS];
+ int num_samples[MAX_CARDS];
+
+ {
+ unique_lock<mutex> lock(bmusb_mutex);