}
}
-void ensure_texture_resolution(PBOFrameAllocator::Userdata *userdata, unsigned field, unsigned width, unsigned height)
+void ensure_texture_resolution(PBOFrameAllocator::Userdata *userdata, unsigned field, unsigned width, unsigned height, unsigned v210_width)
{
bool first;
if (global_flags.ten_bit_input) {
// a new object. Note that this each card has its own PBOFrameAllocator,
// we don't need to worry about these flip-flopping between resolutions.
if (global_flags.ten_bit_input) {
- const size_t v210_width = v210Converter::get_minimum_v210_texture_width(width);
-
- glBindTexture(GL_TEXTURE_2D, userdata->tex_v210[field]);
- check_error();
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB10_A2, v210_width, height, 0, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, nullptr);
- check_error();
glBindTexture(GL_TEXTURE_2D, userdata->tex_444[field]);
check_error();
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB10_A2, width, height, 0, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, nullptr);
userdata->last_width[field] = width;
userdata->last_height[field] = height;
}
+ if (global_flags.ten_bit_input &&
+ (first || v210_width != userdata->last_v210_width[field])) {
+ // Same as above; we need to recreate the texture.
+ glBindTexture(GL_TEXTURE_2D, userdata->tex_v210[field]);
+ check_error();
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB10_A2, v210_width, height, 0, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, nullptr);
+ check_error();
+ userdata->last_v210_width[field] = v210_width;
+ }
}
void upload_texture(GLuint tex, GLuint width, GLuint height, GLuint stride, bool interlaced_stride, GLenum format, GLenum type, GLintptr offset)
been_at_safe_point_since_last_starvation = false;
return;
}
- if (queue_length >= 1) {
- if (queue_length >= safe_queue_length) {
- been_at_safe_point_since_last_starvation = true;
- }
- if (++frames_with_at_least_one >= 1000 && safe_queue_length > 1) {
- --safe_queue_length;
- fprintf(stderr, "Card %u: Spare frames for more than 1000 frames, reducing safe limit to %u frame(s)\n",
- card_index, safe_queue_length);
- frames_with_at_least_one = 0;
- }
- } else {
+ if (queue_length >= safe_queue_length) {
+ been_at_safe_point_since_last_starvation = true;
+ }
+ if (++frames_with_at_least_one >= 1000 && safe_queue_length > 1) {
+ --safe_queue_length;
+ fprintf(stderr, "Card %u: Spare frames for more than 1000 frames, reducing safe limit to %u frame(s)\n",
+ card_index, safe_queue_length);
frames_with_at_least_one = 0;
}
}
inout_format.color_space = COLORSPACE_sRGB;
inout_format.gamma_curve = GAMMA_sRGB;
- // Display chain; shows the live output produced by the main chain (its RGBA version).
+ // Matches the 4:2:0 format created by the main chain.
+ YCbCrFormat ycbcr_format;
+ ycbcr_format.chroma_subsampling_x = 2;
+ ycbcr_format.chroma_subsampling_y = 2;
+ if (global_flags.ycbcr_rec709_coefficients) {
+ ycbcr_format.luma_coefficients = YCBCR_REC_709;
+ } else {
+ ycbcr_format.luma_coefficients = YCBCR_REC_601;
+ }
+ ycbcr_format.full_range = false;
+ ycbcr_format.num_levels = 256;
+ ycbcr_format.cb_x_position = 0.0f;
+ ycbcr_format.cr_x_position = 0.0f;
+ ycbcr_format.cb_y_position = 0.5f;
+ ycbcr_format.cr_y_position = 0.5f;
+
+ // Display chain; shows the live output produced by the main chain (or rather, a copy of it).
display_chain.reset(new EffectChain(global_flags.width, global_flags.height, resource_pool.get()));
check_error();
- display_input = new FlatInput(inout_format, FORMAT_RGB, GL_UNSIGNED_BYTE, global_flags.width, global_flags.height); // FIXME: GL_UNSIGNED_BYTE is really wrong.
+ display_input = new YCbCrInput(inout_format, ycbcr_format, global_flags.width, global_flags.height, YCBCR_INPUT_SPLIT_Y_AND_CBCR);
display_chain->add_input(display_input);
display_chain->add_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
display_chain->set_dither_bits(0); // Don't bother.
field_start_line = video_format.extra_lines_top;
}
- ensure_texture_resolution(userdata, field, video_format.width, video_format.height);
+ // For 8-bit input, v210_width will be nonsensical but not used.
+ size_t v210_width = video_format.stride / sizeof(uint32_t);
+ ensure_texture_resolution(userdata, field, video_format.width, video_format.height, v210_width);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, userdata->pbo);
check_error();
if (global_flags.ten_bit_input) {
size_t field_start = video_offset + video_format.stride * field_start_line;
- upload_texture(userdata->tex_v210[field], video_format.stride / sizeof(uint32_t), video_format.height, video_format.stride, interlaced_stride, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, field_start);
+ upload_texture(userdata->tex_v210[field], v210_width, video_format.height, video_format.stride, interlaced_stride, GL_RGBA, GL_UNSIGNED_INT_2_10_10_10_REV, field_start);
v210_converter->convert(userdata->tex_v210[field], userdata->tex_444[field], video_format.width, video_format.height);
} else {
size_t field_y_start = y_offset + video_format.width * field_start_line;
theme_main_chain.setup_chain();
//theme_main_chain.chain->enable_phase_timing(true);
+ // If HDMI/SDI output is active and the user has requested auto mode,
+ // its mode overrides the existing Y'CbCr setting for the chain.
+ YCbCrLumaCoefficients ycbcr_output_coefficients;
+ if (global_flags.ycbcr_auto_coefficients && output_card_index != -1) {
+ ycbcr_output_coefficients = cards[output_card_index].output->preferred_ycbcr_coefficients();
+ } else {
+ ycbcr_output_coefficients = global_flags.ycbcr_rec709_coefficients ? YCBCR_REC_709 : YCBCR_REC_601;
+ }
+
+ // TODO: Reduce the duplication against theme.cpp.
+ YCbCrFormat output_ycbcr_format;
+ output_ycbcr_format.chroma_subsampling_x = 1;
+ output_ycbcr_format.chroma_subsampling_y = 1;
+ output_ycbcr_format.luma_coefficients = ycbcr_output_coefficients;
+ output_ycbcr_format.full_range = false;
+ output_ycbcr_format.num_levels = 256;
+ chain->change_ycbcr_output_format(output_ycbcr_format);
+
+ const int64_t av_delay = lrint(global_flags.audio_queue_length_ms * 0.001 * TIMEBASE); // Corresponds to the delay in ResamplingQueue.
GLuint y_tex, cbcr_tex;
- bool got_frame = video_encoder->begin_frame(&y_tex, &cbcr_tex);
+ bool got_frame = video_encoder->begin_frame(pts_int + av_delay, duration, ycbcr_output_coefficients, theme_main_chain.input_frames, &y_tex, &cbcr_tex);
assert(got_frame);
- // Render main chain.
+ // Render main chain. We take an extra copy of the created outputs,
+ // so that we can display it back to the screen later (it's less memory
+ // bandwidth than writing and reading back an RGBA texture, even at 16-bit).
+ // Ideally, we'd like to avoid taking copies and just use the main textures
+ // for display as well, but if they're used for zero-copy Quick Sync encoding
+ // (the default case), they're just views into VA-API memory and must be
+ // unmapped during encoding, so we can't use them for display, unfortunately.
GLuint cbcr_full_tex = resource_pool->create_2d_texture(GL_RG8, global_flags.width, global_flags.height);
- GLuint rgba_tex = resource_pool->create_2d_texture(GL_RGB565, global_flags.width, global_flags.height); // Saves texture bandwidth, although dithering gets messed up.
- GLuint fbo = resource_pool->create_fbo(y_tex, cbcr_full_tex, rgba_tex);
+ GLuint y_copy_tex = resource_pool->create_2d_texture(GL_R8, global_flags.width, global_flags.height);
+ GLuint fbo = resource_pool->create_fbo(y_tex, cbcr_full_tex, y_copy_tex);
check_error();
chain->render_to_fbo(fbo, global_flags.width, global_flags.height);
resource_pool->release_fbo(fbo);
- chroma_subsampler->subsample_chroma(cbcr_full_tex, global_flags.width, global_flags.height, cbcr_tex);
+ GLuint cbcr_copy_tex = resource_pool->create_2d_texture(GL_RG8, global_flags.width / 2, global_flags.height / 2);
+ chroma_subsampler->subsample_chroma(cbcr_full_tex, global_flags.width, global_flags.height, cbcr_tex, cbcr_copy_tex);
if (output_card_index != -1) {
- cards[output_card_index].output->send_frame(y_tex, cbcr_full_tex, theme_main_chain.input_frames, pts_int, duration);
+ cards[output_card_index].output->send_frame(y_tex, cbcr_full_tex, ycbcr_output_coefficients, theme_main_chain.input_frames, pts_int, duration);
}
resource_pool->release_2d_texture(cbcr_full_tex);
- // Set the right state for rgba_tex.
+ // Set the right state for the Y' and CbCr copies.
glBindFramebuffer(GL_FRAMEBUFFER, 0);
- glBindTexture(GL_TEXTURE_2D, rgba_tex);
+ glBindTexture(GL_TEXTURE_2D, y_copy_tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
- const int64_t av_delay = lrint(global_flags.audio_queue_length_ms * 0.001 * TIMEBASE); // Corresponds to the delay in ResamplingQueue.
- RefCountedGLsync fence = video_encoder->end_frame(pts_int + av_delay, duration, theme_main_chain.input_frames);
+ glBindTexture(GL_TEXTURE_2D, cbcr_copy_tex);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ RefCountedGLsync fence = video_encoder->end_frame();
- // The live frame just shows the RGBA texture we just rendered.
- // It owns rgba_tex now.
+ // The live frame pieces the Y'CbCr texture copies back into RGB and displays them.
+ // It owns y_copy_tex and cbcr_copy_tex now.
DisplayFrame live_frame;
live_frame.chain = display_chain.get();
- live_frame.setup_chain = [this, rgba_tex]{
- display_input->set_texture_num(rgba_tex);
+ live_frame.setup_chain = [this, y_copy_tex, cbcr_copy_tex]{
+ display_input->set_texture_num(0, y_copy_tex);
+ display_input->set_texture_num(1, cbcr_copy_tex);
};
live_frame.ready_fence = fence;
live_frame.input_frames = {};
- live_frame.temp_textures = { rgba_tex };
+ live_frame.temp_textures = { y_copy_tex, cbcr_copy_tex };
output_channel[OUTPUT_LIVE].output_frame(live_frame);
// Set up preview and any additional channels.