]> git.sesse.net Git - nageru/blobdiff - nageru/theme.cpp
Defer creation of effects until they are added to a chain.
[nageru] / nageru / theme.cpp
index ead38fa819e877fceee43ec32cf4aa52a183dc92..71cb2c322c0a0a03c39fc0d1a0f37de8e75fb693 100644 (file)
@@ -153,22 +153,96 @@ int wrap_lua_object_nonowned(lua_State* L, const char *class_name, Args&&... arg
        return 1;
 }
 
-Effect *get_effect(lua_State *L, int idx)
-{
-       if (luaL_testudata(L, idx, "WhiteBalanceEffect") ||
-           luaL_testudata(L, idx, "ResampleEffect") ||
-           luaL_testudata(L, idx, "PaddingEffect") ||
-           luaL_testudata(L, idx, "IntegralPaddingEffect") ||
-           luaL_testudata(L, idx, "OverlayEffect") ||
-           luaL_testudata(L, idx, "ResizeEffect") ||
-           luaL_testudata(L, idx, "MultiplyEffect") ||
-           luaL_testudata(L, idx, "MixEffect") ||
-           luaL_testudata(L, idx, "LiftGammaGainEffect") ||
-           luaL_testudata(L, idx, "ImageInput")) {
-               return *(Effect **)lua_touserdata(L, idx);
+enum EffectType {
+       WHITE_BALANCE_EFFECT,
+       RESAMPLE_EFFECT,
+       PADDING_EFFECT,
+       INTEGRAL_PADDING_EFFECT,
+       OVERLAY_EFFECT,
+       RESIZE_EFFECT,
+       MULTIPLY_EFFECT,
+       MIX_EFFECT,
+       LIFT_GAMMA_GAIN_EFFECT
+};
+
+Effect *instantiate_effect(EffectChain *chain, EffectType effect_type)
+{
+       switch (effect_type) {
+       case WHITE_BALANCE_EFFECT:
+               return new WhiteBalanceEffect;
+       case RESAMPLE_EFFECT:
+               return new ResampleEffect;
+       case PADDING_EFFECT:
+               return new PaddingEffect;
+       case INTEGRAL_PADDING_EFFECT:
+               return new IntegralPaddingEffect;
+       case OVERLAY_EFFECT:
+               return new OverlayEffect;
+       case RESIZE_EFFECT:
+               return new ResizeEffect;
+       case MULTIPLY_EFFECT:
+               return new MultiplyEffect;
+       case MIX_EFFECT:
+               return new MixEffect;
+       case LIFT_GAMMA_GAIN_EFFECT:
+               return new LiftGammaGainEffect;
+       default:
+               fprintf(stderr, "Unhandled effect type %d\n", effect_type);
+               abort();
        }
-       luaL_error(L, "Error: Index #%d was not an Effect type\n", idx);
-       return nullptr;
+}
+
+// An EffectBlueprint refers to an Effect before it's being added to the graph.
+// It contains enough information to instantiate the effect, including any
+// parameters that were set before it was added to the graph. Once it is
+// instantiated, it forwards its calls on to the real Effect instead.
+struct EffectBlueprint {
+       EffectBlueprint(EffectType effect_type) : effect_type(effect_type) {}
+
+       EffectType effect_type;
+       map<string, int> int_parameters;
+       map<string, float> float_parameters;
+       map<string, array<float, 3>> vec3_parameters;
+       map<string, array<float, 4>> vec4_parameters;
+
+       Effect *effect = nullptr;  // Gets filled out when it's instantiated.
+};
+
+Effect *get_effect_from_blueprint(EffectChain *chain, lua_State *L, int idx)
+{
+       EffectBlueprint *blueprint = *(EffectBlueprint **)luaL_checkudata(L, idx, "EffectBlueprint");
+       if (blueprint->effect != nullptr) {
+               // NOTE: This will change in the future.
+               luaL_error(L, "An effect can currently only be added to one chain.\n");
+       }
+
+       Effect *effect = instantiate_effect(chain, blueprint->effect_type);
+
+       // Set the parameters that were deferred earlier.
+       for (const auto &kv : blueprint->int_parameters) {
+               if (!effect->set_int(kv.first, kv.second)) {
+                       luaL_error(L, "Effect refused set_int(\"%s\", %d) (invalid key?)", kv.first.c_str(), kv.second);
+               }
+       }
+       for (const auto &kv : blueprint->float_parameters) {
+               if (!effect->set_float(kv.first, kv.second)) {
+                       luaL_error(L, "Effect refused set_float(\"%s\", %f) (invalid key?)", kv.first.c_str(), kv.second);
+               }
+       }
+       for (const auto &kv : blueprint->vec3_parameters) {
+               if (!effect->set_vec3(kv.first, kv.second.data())) {
+                       luaL_error(L, "Effect refused set_vec3(\"%s\", %f, %f, %f) (invalid key?)", kv.first.c_str(),
+                               kv.second[0], kv.second[1], kv.second[2]);
+               }
+       }
+       for (const auto &kv : blueprint->vec4_parameters) {
+               if (!effect->set_vec4(kv.first, kv.second.data())) {
+                       luaL_error(L, "Effect refused set_vec4(\"%s\", %f, %f, %f, %f) (invalid key?)", kv.first.c_str(),
+                               kv.second[0], kv.second[1], kv.second[2], kv.second[3]);
+               }
+       }
+       blueprint->effect = effect;
+       return effect;
 }
 
 InputStateInfo *get_input_state_info(lua_State *L, int idx)
@@ -193,6 +267,57 @@ string checkstdstring(lua_State *L, int index)
        return string(cstr, len);
 }
 
+void add_outputs_and_finalize(EffectChain *chain, bool is_main_chain)
+{
+       // Add outputs as needed.
+       // NOTE: If you change any details about the output format, you will need to
+       // also update what's given to the muxer (HTTPD::Mux constructor) and
+       // what's put in the H.264 stream (sps_rbsp()).
+       ImageFormat inout_format;
+       inout_format.color_space = COLORSPACE_REC_709;
+
+       // Output gamma is tricky. We should output Rec. 709 for TV, except that
+       // we expect to run with web players and others that don't really care and
+       // just output with no conversion. So that means we'll need to output sRGB,
+       // even though H.264 has no setting for that (we use “unspecified”).
+       inout_format.gamma_curve = GAMMA_sRGB;
+
+       if (is_main_chain) {
+               YCbCrFormat output_ycbcr_format;
+               // 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 = 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);
+
+               // 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->finalize();
+}
+
 int EffectChain_new(lua_State* L)
 {
        assert(lua_gettop(L) == 2);
@@ -277,7 +402,12 @@ int EffectChain_add_effect(lua_State* L)
        EffectChain *chain = (EffectChain *)luaL_checkudata(L, 1, "EffectChain");
 
        // TODO: Better error reporting.
-       Effect *effect = get_effect(L, 2);
+       Effect *effect;
+       if (luaL_testudata(L, 2, "ImageInput")) {
+               effect = *(ImageInput **)luaL_checkudata(L, 2, "ImageInput");
+       } else {
+               effect = get_effect_from_blueprint(chain, L, 2);
+       }
        if (lua_gettop(L) == 2) {
                if (effect->num_inputs() == 0) {
                        chain->add_input((Input *)effect);
@@ -290,8 +420,13 @@ int EffectChain_add_effect(lua_State* L)
                        if (luaL_testudata(L, idx, "LiveInputWrapper")) {
                                LiveInputWrapper **input = (LiveInputWrapper **)lua_touserdata(L, idx);
                                inputs.push_back((*input)->get_effect());
+                       } else if (luaL_testudata(L, idx, "ImageInput")) {
+                               ImageInput *image = *(ImageInput **)luaL_checkudata(L, idx, "ImageInput");
+                               inputs.push_back(image);
                        } else {
-                               inputs.push_back(get_effect(L, idx));
+                               EffectBlueprint *blueprint = *(EffectBlueprint **)luaL_checkudata(L, idx, "EffectBlueprint");
+                               assert(blueprint->effect != nullptr);  // Parent must be added to the graph.
+                               inputs.push_back(blueprint->effect);
                        }
                }
                chain->add_effect(effect, inputs);
@@ -311,54 +446,7 @@ int EffectChain_finalize(lua_State* L)
        assert(lua_gettop(L) == 2);
        EffectChain *chain = (EffectChain *)luaL_checkudata(L, 1, "EffectChain");
        bool is_main_chain = checkbool(L, 2);
-
-       // Add outputs as needed.
-       // NOTE: If you change any details about the output format, you will need to
-       // also update what's given to the muxer (HTTPD::Mux constructor) and
-       // what's put in the H.264 stream (sps_rbsp()).
-       ImageFormat inout_format;
-       inout_format.color_space = COLORSPACE_REC_709;
-
-       // Output gamma is tricky. We should output Rec. 709 for TV, except that
-       // we expect to run with web players and others that don't really care and
-       // just output with no conversion. So that means we'll need to output sRGB,
-       // even though H.264 has no setting for that (we use “unspecified”).
-       inout_format.gamma_curve = GAMMA_sRGB;
-
-       if (is_main_chain) {
-               YCbCrFormat output_ycbcr_format;
-               // 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 = 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);
-
-               // 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->finalize();
+       add_outputs_and_finalize(chain, is_main_chain);
        return 0;
 }
 
@@ -455,7 +543,7 @@ int HTMLInput_new(lua_State* L)
 #else
        fprintf(stderr, "This version of Nageru has been compiled without CEF support.\n");
        fprintf(stderr, "HTMLInput is not available.\n");
-       exit(1);
+       abort();
 #endif
 }
 
@@ -517,55 +605,55 @@ int HTMLInput_get_signal_num(lua_State* L)
 int WhiteBalanceEffect_new(lua_State* L)
 {
        assert(lua_gettop(L) == 0);
-       return wrap_lua_object_nonowned<WhiteBalanceEffect>(L, "WhiteBalanceEffect");
+       return wrap_lua_object_nonowned<EffectBlueprint>(L, "EffectBlueprint", WHITE_BALANCE_EFFECT);
 }
 
 int ResampleEffect_new(lua_State* L)
 {
        assert(lua_gettop(L) == 0);
-       return wrap_lua_object_nonowned<ResampleEffect>(L, "ResampleEffect");
+       return wrap_lua_object_nonowned<EffectBlueprint>(L, "EffectBlueprint", RESAMPLE_EFFECT);
 }
 
 int PaddingEffect_new(lua_State* L)
 {
        assert(lua_gettop(L) == 0);
-       return wrap_lua_object_nonowned<PaddingEffect>(L, "PaddingEffect");
+       return wrap_lua_object_nonowned<EffectBlueprint>(L, "EffectBlueprint", PADDING_EFFECT);
 }
 
 int IntegralPaddingEffect_new(lua_State* L)
 {
        assert(lua_gettop(L) == 0);
-       return wrap_lua_object_nonowned<IntegralPaddingEffect>(L, "IntegralPaddingEffect");
+       return wrap_lua_object_nonowned<EffectBlueprint>(L, "EffectBlueprint", INTEGRAL_PADDING_EFFECT);
 }
 
 int OverlayEffect_new(lua_State* L)
 {
        assert(lua_gettop(L) == 0);
-       return wrap_lua_object_nonowned<OverlayEffect>(L, "OverlayEffect");
+       return wrap_lua_object_nonowned<EffectBlueprint>(L, "EffectBlueprint", OVERLAY_EFFECT);
 }
 
 int ResizeEffect_new(lua_State* L)
 {
        assert(lua_gettop(L) == 0);
-       return wrap_lua_object_nonowned<ResizeEffect>(L, "ResizeEffect");
+       return wrap_lua_object_nonowned<EffectBlueprint>(L, "EffectBlueprint", RESIZE_EFFECT);
 }
 
 int MultiplyEffect_new(lua_State* L)
 {
        assert(lua_gettop(L) == 0);
-       return wrap_lua_object_nonowned<MultiplyEffect>(L, "MultiplyEffect");
+       return wrap_lua_object_nonowned<EffectBlueprint>(L, "EffectBlueprint", MULTIPLY_EFFECT);
 }
 
 int MixEffect_new(lua_State* L)
 {
        assert(lua_gettop(L) == 0);
-       return wrap_lua_object_nonowned<MixEffect>(L, "MixEffect");
+       return wrap_lua_object_nonowned<EffectBlueprint>(L, "EffectBlueprint", MIX_EFFECT);
 }
 
 int LiftGammaGainEffect_new(lua_State* L)
 {
        assert(lua_gettop(L) == 0);
-       return wrap_lua_object_nonowned<LiftGammaGainEffect>(L, "LiftGammaGainEffect");
+       return wrap_lua_object_nonowned<EffectBlueprint>(L, "EffectBlueprint", LIFT_GAMMA_GAIN_EFFECT);
 }
 
 int InputStateInfo_get_width(lua_State* L)
@@ -652,63 +740,94 @@ int InputStateInfo_get_last_subtitle(lua_State* L)
        return 1;
 }
 
-int Effect_set_float(lua_State *L)
+int EffectBlueprint_set_int(lua_State *L)
 {
        assert(lua_gettop(L) == 3);
-       Effect *effect = (Effect *)get_effect(L, 1);
+       EffectBlueprint *blueprint = *(EffectBlueprint **)luaL_checkudata(L, 1, "EffectBlueprint");
        string key = checkstdstring(L, 2);
        float value = luaL_checknumber(L, 3);
-       if (!effect->set_float(key, value)) {
-               luaL_error(L, "Effect refused set_float(\"%s\", %d) (invalid key?)", key.c_str(), int(value));
+       if (blueprint->effect != nullptr) {
+               if (!blueprint->effect->set_int(key, value)) {
+                       luaL_error(L, "Effect refused set_int(\"%s\", %d) (invalid key?)", key.c_str(), int(value));
+               }
+       } else {
+               // TODO: check validity already here, if possible?
+               blueprint->int_parameters[key] = value;
        }
        return 0;
 }
 
-int Effect_set_int(lua_State *L)
+int EffectBlueprint_set_float(lua_State *L)
 {
        assert(lua_gettop(L) == 3);
-       Effect *effect = (Effect *)get_effect(L, 1);
+       EffectBlueprint *blueprint = *(EffectBlueprint **)luaL_checkudata(L, 1, "EffectBlueprint");
        string key = checkstdstring(L, 2);
        float value = luaL_checknumber(L, 3);
-       if (!effect->set_int(key, value)) {
-               luaL_error(L, "Effect refused set_int(\"%s\", %d) (invalid key?)", key.c_str(), int(value));
+       if (blueprint->effect != nullptr) {
+               if (!blueprint->effect->set_float(key, value)) {
+                       luaL_error(L, "Effect refused set_float(\"%s\", %d) (invalid key?)", key.c_str(), int(value));
+               }
+       } else {
+               // TODO: check validity already here, if possible?
+               blueprint->float_parameters[key] = value;
        }
        return 0;
 }
 
-int Effect_set_vec3(lua_State *L)
+int EffectBlueprint_set_vec3(lua_State *L)
 {
        assert(lua_gettop(L) == 5);
-       Effect *effect = (Effect *)get_effect(L, 1);
+       EffectBlueprint *blueprint = *(EffectBlueprint **)luaL_checkudata(L, 1, "EffectBlueprint");
        string key = checkstdstring(L, 2);
-       float v[3];
+       array<float, 3> v;
        v[0] = luaL_checknumber(L, 3);
        v[1] = luaL_checknumber(L, 4);
        v[2] = luaL_checknumber(L, 5);
-       if (!effect->set_vec3(key, v)) {
-               luaL_error(L, "Effect refused set_vec3(\"%s\", %f, %f, %f) (invalid key?)", key.c_str(),
-                       v[0], v[1], v[2]);
+
+       if (blueprint->effect != nullptr) {
+               if (!blueprint->effect->set_vec3(key, v.data())) {
+                       luaL_error(L, "Effect refused set_vec3(\"%s\", %f, %f, %f) (invalid key?)", key.c_str(),
+                               v[0], v[1], v[2]);
+               }
+       } else {
+               // TODO: check validity already here, if possible?
+               blueprint->vec3_parameters[key] = v;
        }
+
        return 0;
 }
 
-int Effect_set_vec4(lua_State *L)
+int EffectBlueprint_set_vec4(lua_State *L)
 {
        assert(lua_gettop(L) == 6);
-       Effect *effect = (Effect *)get_effect(L, 1);
+       EffectBlueprint *blueprint = *(EffectBlueprint **)luaL_checkudata(L, 1, "EffectBlueprint");
        string key = checkstdstring(L, 2);
-       float v[4];
+       array<float, 4> v;
        v[0] = luaL_checknumber(L, 3);
        v[1] = luaL_checknumber(L, 4);
        v[2] = luaL_checknumber(L, 5);
        v[3] = luaL_checknumber(L, 6);
-       if (!effect->set_vec4(key, v)) {
-               luaL_error(L, "Effect refused set_vec4(\"%s\", %f, %f, %f, %f) (invalid key?)", key.c_str(),
-                       v[0], v[1], v[2], v[3]);
+       if (blueprint->effect != nullptr) {
+               if (!blueprint->effect->set_vec4(key, v.data())) {
+                       luaL_error(L, "Effect refused set_vec4(\"%s\", %f, %f, %f, %f) (invalid key?)", key.c_str(),
+                               v[0], v[1], v[2], v[3]);
+               }
+       } else {
+               // TODO: check validity already here, if possible?
+               blueprint->vec4_parameters[key] = v;
        }
        return 0;
 }
 
+const luaL_Reg EffectBlueprint_funcs[] = {
+       // NOTE: No new() function; that's for the individual effects.
+       { "set_int", EffectBlueprint_set_int },
+       { "set_float", EffectBlueprint_set_float },
+       { "set_vec3", EffectBlueprint_set_vec3 },
+       { "set_vec4", EffectBlueprint_set_vec4 },
+       { NULL, NULL }
+};
+
 const luaL_Reg EffectChain_funcs[] = {
        { "new", EffectChain_new },
        { "__gc", EffectChain_gc },
@@ -729,10 +848,6 @@ const luaL_Reg LiveInputWrapper_funcs[] = {
 
 const luaL_Reg ImageInput_funcs[] = {
        { "new", ImageInput_new },
-       { "set_float", Effect_set_float },
-       { "set_int", Effect_set_int },
-       { "set_vec3", Effect_set_vec3 },
-       { "set_vec4", Effect_set_vec4 },
        { NULL, NULL }
 };
 
@@ -758,87 +873,57 @@ const luaL_Reg HTMLInput_funcs[] = {
        { NULL, NULL }
 };
 
+// Effects.
+// All of these are solely for new(); the returned metatable will be that of
+// EffectBlueprint, and Effect (returned from add_effect()) is its own type.
+
 const luaL_Reg WhiteBalanceEffect_funcs[] = {
        { "new", WhiteBalanceEffect_new },
-       { "set_float", Effect_set_float },
-       { "set_int", Effect_set_int },
-       { "set_vec3", Effect_set_vec3 },
-       { "set_vec4", Effect_set_vec4 },
        { NULL, NULL }
 };
 
 const luaL_Reg ResampleEffect_funcs[] = {
        { "new", ResampleEffect_new },
-       { "set_float", Effect_set_float },
-       { "set_int", Effect_set_int },
-       { "set_vec3", Effect_set_vec3 },
-       { "set_vec4", Effect_set_vec4 },
        { NULL, NULL }
 };
 
 const luaL_Reg PaddingEffect_funcs[] = {
        { "new", PaddingEffect_new },
-       { "set_float", Effect_set_float },
-       { "set_int", Effect_set_int },
-       { "set_vec3", Effect_set_vec3 },
-       { "set_vec4", Effect_set_vec4 },
        { NULL, NULL }
 };
 
 const luaL_Reg IntegralPaddingEffect_funcs[] = {
        { "new", IntegralPaddingEffect_new },
-       { "set_float", Effect_set_float },
-       { "set_int", Effect_set_int },
-       { "set_vec3", Effect_set_vec3 },
-       { "set_vec4", Effect_set_vec4 },
        { NULL, NULL }
 };
 
 const luaL_Reg OverlayEffect_funcs[] = {
        { "new", OverlayEffect_new },
-       { "set_float", Effect_set_float },
-       { "set_int", Effect_set_int },
-       { "set_vec3", Effect_set_vec3 },
-       { "set_vec4", Effect_set_vec4 },
        { NULL, NULL }
 };
 
 const luaL_Reg ResizeEffect_funcs[] = {
        { "new", ResizeEffect_new },
-       { "set_float", Effect_set_float },
-       { "set_int", Effect_set_int },
-       { "set_vec3", Effect_set_vec3 },
-       { "set_vec4", Effect_set_vec4 },
        { NULL, NULL }
 };
 
 const luaL_Reg MultiplyEffect_funcs[] = {
        { "new", MultiplyEffect_new },
-       { "set_float", Effect_set_float },
-       { "set_int", Effect_set_int },
-       { "set_vec3", Effect_set_vec3 },
-       { "set_vec4", Effect_set_vec4 },
        { NULL, NULL }
 };
 
 const luaL_Reg MixEffect_funcs[] = {
        { "new", MixEffect_new },
-       { "set_float", Effect_set_float },
-       { "set_int", Effect_set_int },
-       { "set_vec3", Effect_set_vec3 },
-       { "set_vec4", Effect_set_vec4 },
        { NULL, NULL }
 };
 
 const luaL_Reg LiftGammaGainEffect_funcs[] = {
        { "new", LiftGammaGainEffect_new },
-       { "set_float", Effect_set_float },
-       { "set_int", Effect_set_int },
-       { "set_vec3", Effect_set_vec3 },
-       { "set_vec4", Effect_set_vec4 },
        { NULL, NULL }
 };
 
+// End of effects.
+
 const luaL_Reg InputStateInfo_funcs[] = {
        { "get_width", InputStateInfo_get_width },
        { "get_height", InputStateInfo_get_height },
@@ -901,11 +986,7 @@ LiveInputWrapper::LiveInputWrapper(
                for (unsigned i = 0; i < num_inputs; ++i) {
                        // We upload our textures ourselves, and Movit swaps
                        // R and B in the shader if we specify BGRA, so lie and say RGBA.
-                       if (global_flags.can_disable_srgb_decoder) {
-                               rgba_inputs.push_back(new sRGBSwitchingFlatInput(inout_format, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, GL_UNSIGNED_BYTE, global_flags.width, global_flags.height));
-                       } else {
-                               rgba_inputs.push_back(new NonsRGBCapableFlatInput(inout_format, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, GL_UNSIGNED_BYTE, global_flags.width, global_flags.height));
-                       }
+                       rgba_inputs.push_back(new sRGBSwitchingFlatInput(inout_format, FORMAT_RGBA_POSTMULTIPLIED_ALPHA, GL_UNSIGNED_BYTE, global_flags.width, global_flags.height));
                        chain->add_input(rgba_inputs.back());
                }
 
@@ -1079,7 +1160,7 @@ int call_num_channels(lua_State *L)
 
        if (lua_pcall(L, 0, 1, 0) != 0) {
                fprintf(stderr, "error running function `num_channels': %s\n", lua_tostring(L, -1));
-               exit(1);
+               abort();
        }
 
        int num_channels = luaL_checknumber(L, 1);
@@ -1142,7 +1223,7 @@ Theme::Theme(const string &filename, const vector<string> &search_dirs, Resource
                for (const string &error : errors) {
                        fprintf(stderr, "%s\n", error.c_str());
                }
-               exit(1);
+               abort();
        }
        assert(lua_gettop(L) == 0);
 
@@ -1161,6 +1242,7 @@ Theme::Theme(const string &filename, const vector<string> &search_dirs, Resource
 
        // Set up the API we provide.
        register_constants();
+       register_class("EffectBlueprint", EffectBlueprint_funcs);
        register_class("EffectChain", EffectChain_funcs);
        register_class("LiveInputWrapper", LiveInputWrapper_funcs);
        register_class("ImageInput", ImageInput_funcs);
@@ -1183,7 +1265,7 @@ Theme::Theme(const string &filename, const vector<string> &search_dirs, Resource
        luaL_unref(L, LUA_REGISTRYINDEX, theme_code_ref);
        if (lua_pcall(L, 0, 0, 0)) {
                fprintf(stderr, "Error when running %s: %s\n", path.c_str(), lua_tostring(L, -1));
-               exit(1);
+               abort();
        }
        assert(lua_gettop(L) == 0);
 
@@ -1251,19 +1333,19 @@ Theme::Chain Theme::get_chain(unsigned num, float t, unsigned width, unsigned he
 
        if (lua_pcall(L, 5, 2, 0) != 0) {
                fprintf(stderr, "error running function `get_chain': %s\n", lua_tostring(L, -1));
-               exit(1);
+               abort();
        }
 
        EffectChain *effect_chain = (EffectChain *)luaL_testudata(L, -2, "EffectChain");
        if (effect_chain == nullptr) {
                fprintf(stderr, "get_chain() for chain number %d did not return an EffectChain\n",
                        num);
-               exit(1);
+               abort();
        }
        chain.chain = effect_chain;
        if (!lua_isfunction(L, -1)) {
                fprintf(stderr, "Argument #-1 should be a function\n");
-               exit(1);
+               abort();
        }
        lua_pushvalue(L, -1);
        shared_ptr<LuaRefWithDeleter> funcref(new LuaRefWithDeleter(&m, L, luaL_ref(L, LUA_REGISTRYINDEX)));
@@ -1280,8 +1362,8 @@ Theme::Chain Theme::get_chain(unsigned num, float t, unsigned width, unsigned he
                lua_rawgeti(L, LUA_REGISTRYINDEX, funcref->get());
                if (lua_pcall(L, 0, 0, 0) != 0) {
                        fprintf(stderr, "error running chain setup callback: %s\n", lua_tostring(L, -1));
-                       exit(1);
-       }
+                       abort();
+               }
                assert(lua_gettop(L) == 0);
 
                // The theme can't (or at least shouldn't!) call connect_signal() on
@@ -1321,12 +1403,12 @@ string Theme::get_channel_name(unsigned channel)
        lua_pushnumber(L, channel);
        if (lua_pcall(L, 1, 1, 0) != 0) {
                fprintf(stderr, "error running function `channel_name': %s\n", lua_tostring(L, -1));
-               exit(1);
+               abort();
        }
        const char *ret = lua_tostring(L, -1);
        if (ret == nullptr) {
                fprintf(stderr, "function `channel_name' returned nil for channel %d\n", channel);
-               exit(1);
+               abort();
        }
 
        string retstr = ret;
@@ -1342,7 +1424,7 @@ int Theme::get_channel_signal(unsigned channel)
        lua_pushnumber(L, channel);
        if (lua_pcall(L, 1, 1, 0) != 0) {
                fprintf(stderr, "error running function `channel_signal': %s\n", lua_tostring(L, -1));
-               exit(1);
+               abort();
        }
 
        int ret = luaL_checknumber(L, 1);
@@ -1358,13 +1440,13 @@ std::string Theme::get_channel_color(unsigned channel)
        lua_pushnumber(L, channel);
        if (lua_pcall(L, 1, 1, 0) != 0) {
                fprintf(stderr, "error running function `channel_color': %s\n", lua_tostring(L, -1));
-               exit(1);
+               abort();
        }
 
        const char *ret = lua_tostring(L, -1);
        if (ret == nullptr) {
                fprintf(stderr, "function `channel_color' returned nil for channel %d\n", channel);
-               exit(1);
+               abort();
        }
 
        string retstr = ret;
@@ -1380,7 +1462,7 @@ bool Theme::get_supports_set_wb(unsigned channel)
        lua_pushnumber(L, channel);
        if (lua_pcall(L, 1, 1, 0) != 0) {
                fprintf(stderr, "error running function `supports_set_wb': %s\n", lua_tostring(L, -1));
-               exit(1);
+               abort();
        }
 
        bool ret = checkbool(L, -1);
@@ -1399,7 +1481,7 @@ void Theme::set_wb(unsigned channel, double r, double g, double b)
        lua_pushnumber(L, b);
        if (lua_pcall(L, 4, 0, 0) != 0) {
                fprintf(stderr, "error running function `set_wb': %s\n", lua_tostring(L, -1));
-               exit(1);
+               abort();
        }
 
        assert(lua_gettop(L) == 0);
@@ -1412,7 +1494,7 @@ vector<string> Theme::get_transition_names(float t)
        lua_pushnumber(L, t);
        if (lua_pcall(L, 1, 1, 0) != 0) {
                fprintf(stderr, "error running function `get_transitions': %s\n", lua_tostring(L, -1));
-               exit(1);
+               abort();
        }
 
        vector<string> ret;
@@ -1424,7 +1506,7 @@ vector<string> Theme::get_transition_names(float t)
        lua_pop(L, 1);
        assert(lua_gettop(L) == 0);
        return ret;
-}      
+}
 
 int Theme::map_signal(int signal_num)
 {
@@ -1477,7 +1559,7 @@ void Theme::transition_clicked(int transition_num, float t)
 
        if (lua_pcall(L, 2, 0, 0) != 0) {
                fprintf(stderr, "error running function `transition_clicked': %s\n", lua_tostring(L, -1));
-               exit(1);
+               abort();
        }
        assert(lua_gettop(L) == 0);
 }
@@ -1490,7 +1572,7 @@ void Theme::channel_clicked(int preview_num)
 
        if (lua_pcall(L, 1, 0, 0) != 0) {
                fprintf(stderr, "error running function `channel_clicked': %s\n", lua_tostring(L, -1));
-               exit(1);
+               abort();
        }
        assert(lua_gettop(L) == 0);
 }
@@ -1530,6 +1612,6 @@ void Theme::theme_menu_entry_clicked(int lua_ref)
        lua_rawgeti(L, LUA_REGISTRYINDEX, lua_ref);
        if (lua_pcall(L, 0, 0, 0) != 0) {
                fprintf(stderr, "error running menu callback: %s\n", lua_tostring(L, -1));
-               exit(1);
+               abort();
        }
 }