+ qf.input_first_pts = input_pts;
+ qf.display_func = move(display_func);
+
+ unique_lock<mutex> lock(queue_lock);
+ frame_queue.push_back(qf);
+ queue_changed.notify_all();
+}
+
+bool VideoStream::schedule_faded_frame(steady_clock::time_point local_pts, int64_t output_pts, function<void()> &&display_func, unsigned stream_idx, int64_t input_pts, int secondary_stream_idx, int64_t secondary_input_pts, float fade_alpha)
+{
+ fprintf(stderr, "output_pts=%ld faded input_pts=%ld,%ld fade_alpha=%.2f\n", output_pts, input_pts, secondary_input_pts, fade_alpha);
+
+ // Get the temporary OpenGL resources we need for doing the fade.
+ // (We share these with interpolated frames, which is slightly
+ // overkill, but there's no need to waste resources on keeping
+ // separate pools around.)
+ InterpolatedFrameResources resources;
+ {
+ unique_lock<mutex> lock(queue_lock);
+ if (interpolate_resources.empty()) {
+ fprintf(stderr, "WARNING: Too many interpolated frames already in transit; dropping one.\n");
+ return false;
+ }
+ resources = interpolate_resources.front();
+ interpolate_resources.pop_front();
+ }
+
+ bool did_decode;
+
+ JPEGID jpeg_id1;
+ jpeg_id1.stream_idx = stream_idx;
+ jpeg_id1.pts = input_pts;
+ jpeg_id1.interpolated = false;
+ shared_ptr<Frame> frame1 = decode_jpeg_with_cache(jpeg_id1, DECODE_IF_NOT_IN_CACHE, &did_decode);
+
+ JPEGID jpeg_id2;
+ jpeg_id2.stream_idx = secondary_stream_idx;
+ jpeg_id2.pts = secondary_input_pts;
+ jpeg_id2.interpolated = false;
+ shared_ptr<Frame> frame2 = decode_jpeg_with_cache(jpeg_id2, DECODE_IF_NOT_IN_CACHE, &did_decode);
+
+ ycbcr_semiplanar_converter->prepare_chain_for_fade(frame1, frame2, fade_alpha)->render_to_fbo(resources.fade_fbo, 1280, 720);
+
+ QueuedFrame qf;
+ qf.local_pts = local_pts;
+ qf.type = QueuedFrame::FADED;
+ qf.output_pts = output_pts;
+ qf.stream_idx = stream_idx;
+ qf.resources = resources;
+ qf.input_first_pts = input_pts;
+ qf.display_func = move(display_func);
+
+ qf.secondary_stream_idx = secondary_stream_idx;
+ qf.secondary_input_pts = secondary_input_pts;
+
+ // Subsample and split Cb/Cr.
+ chroma_subsampler->subsample_chroma(resources.fade_cbcr_output_tex, 1280, 720, resources.cb_tex, resources.cr_tex);
+
+ // Read it down (asynchronously) to the CPU.
+ glPixelStorei(GL_PACK_ROW_LENGTH, 0);
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, resources.pbo);
+ check_error();
+ glGetTextureImage(resources.fade_y_output_tex, 0, GL_RED, GL_UNSIGNED_BYTE, 1280 * 720 * 4, BUFFER_OFFSET(0));
+ check_error();
+ glGetTextureImage(resources.cb_tex, 0, GL_RED, GL_UNSIGNED_BYTE, 1280 * 720 * 3, BUFFER_OFFSET(1280 * 720));
+ check_error();
+ glGetTextureImage(resources.cr_tex, 0, GL_RED, GL_UNSIGNED_BYTE, 1280 * 720 * 3 - 640 * 720, BUFFER_OFFSET(1280 * 720 + 640 * 720));
+ check_error();
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
+
+ // Set a fence we can wait for to make sure the CPU sees the read.
+ glMemoryBarrier(GL_CLIENT_MAPPED_BUFFER_BARRIER_BIT);
+ check_error();
+ qf.fence = RefCountedGLsync(GL_SYNC_GPU_COMMANDS_COMPLETE, /*flags=*/0);
+ check_error();