]> git.sesse.net Git - nageru/blobdiff - theme.cpp
Add support for RGBA frame types.
[nageru] / theme.cpp
index 24c6cdc261b93114a6a98a6ef222dfdcb9f8d18f..b6a19acd2ea5b5eb28e36772fc560400d36d5d9c 100644 (file)
--- a/theme.cpp
+++ b/theme.cpp
@@ -202,7 +202,8 @@ int EffectChain_add_live_input(lua_State* L)
        EffectChain *chain = (EffectChain *)luaL_checkudata(L, 1, "EffectChain");
        bool override_bounce = checkbool(L, 2);
        bool deinterlace = checkbool(L, 3);
-       return wrap_lua_object<LiveInputWrapper>(L, "LiveInputWrapper", theme, chain, override_bounce, deinterlace);
+       bmusb::PixelFormat pixel_format = global_flags.ten_bit_input ? bmusb::PixelFormat_10BitYCbCr : bmusb::PixelFormat_8BitYCbCr;
+       return wrap_lua_object<LiveInputWrapper>(L, "LiveInputWrapper", theme, chain, pixel_format, override_bounce, deinterlace);
 }
 
 int EffectChain_add_effect(lua_State* L)
@@ -261,23 +262,36 @@ int EffectChain_finalize(lua_State* L)
 
        if (is_main_chain) {
                YCbCrFormat output_ycbcr_format;
-               // We actually output 4:2:0 in the end, but chroma subsampling
+               // We actually output 4:2:0 and/or 4:2:2 in the end, but chroma subsampling
                // happens in a pass not run by Movit (see ChromaSubsampler::subsample_chroma()).
                output_ycbcr_format.chroma_subsampling_x = 1;
                output_ycbcr_format.chroma_subsampling_y = 1;
+
+               // This will be overridden if HDMI/SDI output is in force.
                if (global_flags.ycbcr_rec709_coefficients) {
                        output_ycbcr_format.luma_coefficients = YCBCR_REC_709;
                } else {
                        output_ycbcr_format.luma_coefficients = YCBCR_REC_601;
                }
+
                output_ycbcr_format.full_range = false;
-               output_ycbcr_format.num_levels = 256;
+               output_ycbcr_format.num_levels = 1 << global_flags.x264_bit_depth;
+
+               GLenum type = global_flags.x264_bit_depth > 8 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_BYTE;
+
+               chain->add_ycbcr_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, output_ycbcr_format, YCBCR_OUTPUT_SPLIT_Y_AND_CBCR, type);
 
-               chain->add_ycbcr_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, output_ycbcr_format, YCBCR_OUTPUT_SPLIT_Y_AND_CBCR);
-               chain->set_dither_bits(8);
+               // If we're using zerocopy video encoding (so the destination
+               // Y texture is owned by VA-API and will be unavailable for
+               // display), add a copy, where we'll only be using the Y component.
+               if (global_flags.use_zerocopy) {
+                       chain->add_ycbcr_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED, output_ycbcr_format, YCBCR_OUTPUT_INTERLEAVED, type);  // Add a copy where we'll only be using the Y component.
+               }
+               chain->set_dither_bits(global_flags.x264_bit_depth > 8 ? 16 : 8);
                chain->set_output_origin(OUTPUT_ORIGIN_TOP_LEFT);
+       } else {
+               chain->add_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
        }
-       chain->add_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
 
        chain->finalize();
        return 0;
@@ -582,8 +596,9 @@ const luaL_Reg InputStateInfo_funcs[] = {
 
 }  // namespace
 
-LiveInputWrapper::LiveInputWrapper(Theme *theme, EffectChain *chain, bool override_bounce, bool deinterlace)
+LiveInputWrapper::LiveInputWrapper(Theme *theme, EffectChain *chain, bmusb::PixelFormat pixel_format, bool override_bounce, bool deinterlace)
        : theme(theme),
+         pixel_format(pixel_format),
          deinterlace(deinterlace)
 {
        ImageFormat inout_format;
@@ -597,21 +612,6 @@ LiveInputWrapper::LiveInputWrapper(Theme *theme, EffectChain *chain, bool overri
        // So we pick sRGB as the least evil here.
        inout_format.gamma_curve = GAMMA_sRGB;
 
-       // The Blackmagic driver docs claim that the device outputs Y'CbCr
-       // according to Rec. 601, but practical testing indicates it definitely
-       // is Rec. 709 (at least up to errors attributable to rounding errors).
-       // Perhaps 601 was only to indicate the subsampling positions, not the
-       // colorspace itself? Tested with a Lenovo X1 gen 3 as input.
-       YCbCrFormat input_ycbcr_format;
-       input_ycbcr_format.chroma_subsampling_x = 2;
-       input_ycbcr_format.chroma_subsampling_y = 1;
-       input_ycbcr_format.cb_x_position = 0.0;
-       input_ycbcr_format.cr_x_position = 0.0;
-       input_ycbcr_format.cb_y_position = 0.5;
-       input_ycbcr_format.cr_y_position = 0.5;
-       input_ycbcr_format.luma_coefficients = YCBCR_REC_709;
-       input_ycbcr_format.full_range = false;
-
        unsigned num_inputs;
        if (deinterlace) {
                deinterlace_effect = new movit::DeinterlaceEffect();
@@ -627,18 +627,50 @@ LiveInputWrapper::LiveInputWrapper(Theme *theme, EffectChain *chain, bool overri
        } else {
                num_inputs = 1;
        }
-       for (unsigned i = 0; i < num_inputs; ++i) {
-               if (override_bounce) {
-                       inputs.push_back(new NonBouncingYCbCrInput(inout_format, input_ycbcr_format, global_flags.width, global_flags.height, YCBCR_INPUT_SPLIT_Y_AND_CBCR));
-               } else {
-                       inputs.push_back(new YCbCrInput(inout_format, input_ycbcr_format, global_flags.width, global_flags.height, YCBCR_INPUT_SPLIT_Y_AND_CBCR));
+
+       if (pixel_format == bmusb::PixelFormat_8BitRGBA) {
+               for (unsigned i = 0; i < num_inputs; ++i) {
+                       rgba_inputs.push_back(new FlatInput(inout_format, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, GL_UNSIGNED_BYTE, global_flags.width, global_flags.height));
+                       chain->add_input(rgba_inputs.back());
                }
-               chain->add_input(inputs.back());
-       }
 
-       if (deinterlace) {
-               vector<Effect *> reverse_inputs(inputs.rbegin(), inputs.rend());
-               chain->add_effect(deinterlace_effect, reverse_inputs);
+               if (deinterlace) {
+                       vector<Effect *> reverse_inputs(rgba_inputs.rbegin(), rgba_inputs.rend());
+                       chain->add_effect(deinterlace_effect, reverse_inputs);
+               }
+       } else {
+               assert(pixel_format == bmusb::PixelFormat_8BitYCbCr || pixel_format == bmusb::PixelFormat_10BitYCbCr);
+               // The Blackmagic driver docs claim that the device outputs Y'CbCr
+               // according to Rec. 601, but practical testing indicates it definitely
+               // is Rec. 709 (at least up to errors attributable to rounding errors).
+               // Perhaps 601 was only to indicate the subsampling positions, not the
+               // colorspace itself? Tested with a Lenovo X1 gen 3 as input.
+               YCbCrFormat input_ycbcr_format;
+               input_ycbcr_format.chroma_subsampling_x = (pixel_format == bmusb::PixelFormat_10BitYCbCr) ? 1 : 2;
+               input_ycbcr_format.chroma_subsampling_y = 1;
+               input_ycbcr_format.num_levels = (pixel_format == bmusb::PixelFormat_10BitYCbCr) ? 1024 : 256;
+               input_ycbcr_format.cb_x_position = 0.0;
+               input_ycbcr_format.cr_x_position = 0.0;
+               input_ycbcr_format.cb_y_position = 0.5;
+               input_ycbcr_format.cr_y_position = 0.5;
+               input_ycbcr_format.luma_coefficients = YCBCR_REC_709;
+               input_ycbcr_format.full_range = false;
+
+               for (unsigned i = 0; i < num_inputs; ++i) {
+                       // When using 10-bit input, we're converting to interleaved through v210Converter.
+                       YCbCrInputSplitting splitting = (pixel_format == bmusb::PixelFormat_10BitYCbCr) ? YCBCR_INPUT_INTERLEAVED : YCBCR_INPUT_SPLIT_Y_AND_CBCR;
+                       if (override_bounce) {
+                               ycbcr_inputs.push_back(new NonBouncingYCbCrInput(inout_format, input_ycbcr_format, global_flags.width, global_flags.height, splitting));
+                       } else {
+                               ycbcr_inputs.push_back(new YCbCrInput(inout_format, input_ycbcr_format, global_flags.width, global_flags.height, splitting));
+                       }
+                       chain->add_input(ycbcr_inputs.back());
+               }
+
+               if (deinterlace) {
+                       vector<Effect *> reverse_inputs(ycbcr_inputs.rbegin(), ycbcr_inputs.rend());
+                       chain->add_effect(deinterlace_effect, reverse_inputs);
+               }
        }
 }
 
@@ -664,7 +696,7 @@ void LiveInputWrapper::connect_signal(int signal_num)
        }
 
        BufferedFrame last_good_frame = first_frame;
-       for (unsigned i = 0; i < inputs.size(); ++i) {
+       for (unsigned i = 0; i < max(ycbcr_inputs.size(), rgba_inputs.size()); ++i) {
                BufferedFrame frame = theme->input_state->buffered_frames[signal_num][i];
                if (frame.frame == nullptr) {
                        // Not enough data; reuse last frame (well, field).
@@ -673,17 +705,35 @@ void LiveInputWrapper::connect_signal(int signal_num)
                }
                const PBOFrameAllocator::Userdata *userdata = (const PBOFrameAllocator::Userdata *)frame.frame->userdata;
 
-               if (userdata->last_width[frame.field_number] != width ||
-                   userdata->last_height[frame.field_number] != height) {
+               unsigned this_width = userdata->last_width[frame.field_number];
+               unsigned this_height = userdata->last_height[frame.field_number];
+               if (this_width != width || this_height != height) {
                        // Resolution changed; reuse last frame/field.
                        frame = last_good_frame;
                        userdata = (const PBOFrameAllocator::Userdata *)frame.frame->userdata;
                }
 
-               inputs[i]->set_texture_num(0, userdata->tex_y[frame.field_number]);
-               inputs[i]->set_texture_num(1, userdata->tex_cbcr[frame.field_number]);
-               inputs[i]->set_width(userdata->last_width[frame.field_number]);
-               inputs[i]->set_height(userdata->last_height[frame.field_number]);
+               assert(userdata->pixel_format == pixel_format);
+               switch (pixel_format) {
+               case bmusb::PixelFormat_8BitYCbCr:
+                       ycbcr_inputs[i]->set_texture_num(0, userdata->tex_y[frame.field_number]);
+                       ycbcr_inputs[i]->set_texture_num(1, userdata->tex_cbcr[frame.field_number]);
+                       ycbcr_inputs[i]->set_width(this_width);
+                       ycbcr_inputs[i]->set_height(this_height);
+                       break;
+               case bmusb::PixelFormat_10BitYCbCr:
+                       ycbcr_inputs[i]->set_texture_num(0, userdata->tex_444[frame.field_number]);
+                       ycbcr_inputs[i]->set_width(this_width);
+                       ycbcr_inputs[i]->set_height(this_height);
+                       break;
+               case bmusb::PixelFormat_8BitRGBA:
+                       rgba_inputs[i]->set_texture_num(userdata->tex_rgba[frame.field_number]);
+                       rgba_inputs[i]->set_width(this_width);
+                       rgba_inputs[i]->set_height(this_height);
+                       break;
+               default:
+                       assert(false);
+               }
 
                last_good_frame = frame;
        }
@@ -963,12 +1013,28 @@ int Theme::map_signal(int signal_num)
        if (signal_to_card_mapping.count(signal_num)) {
                return signal_to_card_mapping[signal_num];
        }
-       if (signal_num >= int(num_cards)) {
-               fprintf(stderr, "WARNING: Theme asked for input %d, but we only have %u card(s).\n", signal_num, num_cards);
-               fprintf(stderr, "Mapping to card %d instead.\n", signal_num % num_cards);
+
+       int card_index;
+       if (global_flags.output_card != -1 && num_cards > 1) {
+               // Try to exclude the output card from the default card_index.
+               card_index = signal_num % (num_cards - 1);
+               if (card_index >= global_flags.output_card) {
+                        ++card_index;
+               }
+               if (signal_num >= int(num_cards - 1)) {
+                       fprintf(stderr, "WARNING: Theme asked for input %d, but we only have %u input card(s) (card %d is busy with output).\n",
+                               signal_num, num_cards - 1, global_flags.output_card);
+                       fprintf(stderr, "Mapping to card %d instead.\n", card_index);
+               }
+       } else {
+               card_index = signal_num % num_cards;
+               if (signal_num >= int(num_cards)) {
+                       fprintf(stderr, "WARNING: Theme asked for input %d, but we only have %u card(s).\n", signal_num, num_cards);
+                       fprintf(stderr, "Mapping to card %d instead.\n", card_index);
+               }
        }
-       signal_to_card_mapping[signal_num] = signal_num % num_cards;
-       return signal_num % num_cards;
+       signal_to_card_mapping[signal_num] = card_index;
+       return card_index;
 }
 
 void Theme::set_signal_mapping(int signal_num, int card_num)