Skip to content

Commit ad973b4

Browse files
authored
Various fixes and improvements for OpenFX filters (#1248)
* Do set unspecified parameters every frame * Enhance OFX region of definition handling in image effects * Fix clipGetImage changes const param * Fix broken RGB(A) (color) handling in paramSetValue * Fix broken double2D in paramSetValue * Add support for 2D integer and 3D parameters in paramSetValue * fix & add 2D and 3D params in paramGetValue * Add support for 2D integer parameters in openfx filter * Fix paramSetValue not updating filter-user properties * NatronParamFormatChoice -> NatronParamFormatSize Apparently some Natron OpenFX plugins need special handling. * Fix preview scaling * Add support for frame threading in OpenFX filters * Add clip preference management for OpenFX filters * Implement logging functionality for OpenFX messages * fix -Werror=unused-variable * Fix render scale update in serialized mode for OpenFX filters * Check null image_effect in mltofx_allows_frame_threading * Fix error handling in paramGetValue * Fix default value for choice parameters It was showing as the integer index, not one of the string choices.
1 parent 4d2a939 commit ad973b4

4 files changed

Lines changed: 983 additions & 95 deletions

File tree

CMakePresets.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@
104104
"binaryDir": "${sourceDir}/build/${presetName}",
105105
"cacheVariables": {
106106
"BUILD_TESTING": "OFF",
107-
"CMAKE_INSTALL_PREFIX": "${sourceDir}/install/${presetName}",
107+
"CMAKE_INSTALL_PREFIX": "c:/Projects/Shotcut",
108108
"CMAKE_BUILD_TYPE": "RelWithDebInfo",
109109
"CMAKE_PREFIX_PATH": "C:/Qt/$env{QT_VERSION}/mingw_64;C:/msys64/mingw64;C:/msys64/clangarm64",
110110
"MOD_GLAXNIMATE_QT6": "ON",

src/modules/openfx/filter_openfx.c

Lines changed: 208 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include "mlt_openfx.h"
2121

22+
#include <ctype.h>
2223
#include <framework/mlt.h>
2324
#include <ofxImageEffect.h>
2425
#include <string.h>
@@ -28,6 +29,154 @@ extern mlt_properties mltofx_context;
2829
extern mlt_properties mltofx_dl;
2930

3031
static const char OFX_IMAGE_EFFECT[] = "_ofx_image_effect";
32+
static const char NATRON_FORMAT_CHOICE_PARAM[] = "NatronParamFormatChoice";
33+
static const char NATRON_FORMAT_SIZE_PARAM[] = "NatronParamFormatSize";
34+
35+
typedef struct
36+
{
37+
int saw_choice;
38+
int width;
39+
int height;
40+
} natron_format_compat_state;
41+
42+
static int parse_choice_dimensions(const char *choice, int *out_w, int *out_h)
43+
{
44+
if (!choice || !out_w || !out_h)
45+
return 0;
46+
47+
for (const char *p = choice; *p; ++p) {
48+
if (!isdigit((unsigned char) *p))
49+
continue;
50+
51+
// Only start at the beginning of a numeric token, not the middle of one.
52+
if (p > choice && isdigit((unsigned char) p[-1]))
53+
continue;
54+
55+
const char *q = p;
56+
int w = 0;
57+
while (isdigit((unsigned char) *q)) {
58+
w = w * 10 + (*q - '0');
59+
++q;
60+
}
61+
62+
if (*q != 'x' && *q != 'X')
63+
continue;
64+
++q;
65+
if (!isdigit((unsigned char) *q))
66+
continue;
67+
68+
int h = 0;
69+
while (isdigit((unsigned char) *q)) {
70+
h = h * 10 + (*q - '0');
71+
++q;
72+
}
73+
74+
// Height token must also end cleanly (space/end/punctuation), not in the middle of digits.
75+
if (isdigit((unsigned char) *q))
76+
continue;
77+
78+
if (w > 0 && h > 0) {
79+
*out_w = w;
80+
*out_h = h;
81+
return 1;
82+
}
83+
}
84+
85+
return 0;
86+
}
87+
88+
static void natron_compat_track_choice(natron_format_compat_state *state,
89+
const char *param_name,
90+
const char *value)
91+
{
92+
if (!state || !param_name || !value)
93+
return;
94+
if (strcmp(param_name, NATRON_FORMAT_CHOICE_PARAM) != 0)
95+
return;
96+
97+
state->saw_choice = 1;
98+
parse_choice_dimensions(value, &state->width, &state->height);
99+
}
100+
101+
static void natron_compat_apply_size(mlt_properties image_effect_params,
102+
const natron_format_compat_state *state)
103+
{
104+
if (!image_effect_params || !state)
105+
return;
106+
if (!state->saw_choice || state->width <= 0 || state->height <= 0)
107+
return;
108+
109+
mltofx_param_set_value(image_effect_params,
110+
(char *) NATRON_FORMAT_SIZE_PARAM,
111+
mltofx_prop_int2d,
112+
state->width,
113+
state->height);
114+
}
115+
116+
static void reset_image_effect_action_props(mlt_properties image_effect, const char *name)
117+
{
118+
mlt_properties old_props = mlt_properties_get_properties(image_effect, name);
119+
mlt_properties_set_properties(image_effect, name, mlt_properties_new());
120+
if (old_props)
121+
mlt_properties_close(old_props);
122+
}
123+
124+
static void init_image_effect_action_props(mlt_properties image_effect)
125+
{
126+
reset_image_effect_action_props(image_effect, "begin_sequence_props");
127+
reset_image_effect_action_props(image_effect, "end_sequence_props");
128+
reset_image_effect_action_props(image_effect, "get_rod_in_args");
129+
reset_image_effect_action_props(image_effect, "get_rod_out_args");
130+
reset_image_effect_action_props(image_effect, "get_roi_in_args");
131+
reset_image_effect_action_props(image_effect, "get_roi_out_args");
132+
reset_image_effect_action_props(image_effect, "get_clippref_args");
133+
reset_image_effect_action_props(image_effect, "render_in_args");
134+
}
135+
136+
static mlt_properties create_image_effect_instance(OfxPlugin *plugin,
137+
mlt_properties filter_properties,
138+
int sync_param_backlink)
139+
{
140+
mlt_properties params = mlt_properties_new();
141+
if (!params)
142+
return NULL;
143+
144+
mlt_properties image_effect = mltofx_fetch_params(plugin, params, NULL);
145+
if (!image_effect) {
146+
mlt_properties_close(params);
147+
return NULL;
148+
}
149+
150+
if (sync_param_backlink) {
151+
mlt_properties image_effect_params = mlt_properties_get_properties(image_effect, "params");
152+
int param_count = mlt_properties_count(image_effect_params);
153+
for (int i = 0; i < param_count; ++i) {
154+
char *param_name = mlt_properties_get_name(image_effect_params, i);
155+
mlt_properties param = mlt_properties_get_properties(image_effect_params, param_name);
156+
if (!param)
157+
continue;
158+
mlt_properties param_props = mlt_properties_get_properties(param, "p");
159+
if (!param_props)
160+
continue;
161+
mlt_properties_set_data(param_props,
162+
"_filter_properties",
163+
filter_properties,
164+
0,
165+
NULL,
166+
NULL);
167+
}
168+
}
169+
170+
mltofx_create_instance(plugin, image_effect);
171+
init_image_effect_action_props(image_effect);
172+
mlt_properties_set_data(image_effect,
173+
"_runtime_params",
174+
params,
175+
0,
176+
(mlt_destructor) mlt_properties_close,
177+
NULL);
178+
return image_effect;
179+
}
31180

32181
static mlt_image_format select_image_format(mlt_image_format incoming,
33182
mltofx_depths_mask plugin_support_depths,
@@ -60,6 +209,8 @@ static void update_plugin_params(mlt_properties properties,
60209
mlt_position position,
61210
mlt_position length)
62211
{
212+
natron_format_compat_state natron_compat = {0};
213+
63214
int params_count = mlt_properties_count(params);
64215
for (int i = 0; i < params_count; ++i) {
65216
char *param_key = mlt_properties_get_name(params, i);
@@ -69,6 +220,8 @@ static void update_plugin_params(mlt_properties properties,
69220
char *param_name = mlt_properties_get(param, "identifier");
70221
if (!param_name)
71222
continue;
223+
if (!mlt_properties_exists(properties, param_name))
224+
continue;
72225
char *type = mlt_properties_get(param, "type");
73226
char *widget = mlt_properties_get(param, "widget");
74227
if (!type)
@@ -77,6 +230,12 @@ static void update_plugin_params(mlt_properties properties,
77230
&& strcmp(type, "float") == 0) {
78231
mlt_rect value = mlt_properties_anim_get_rect(properties, param_name, position, length);
79232
mltofx_param_set_value(image_effect_params, param_name, mltofx_prop_double2d, value);
233+
} else if (widget && (strcmp(widget, "point") == 0 || strcmp(widget, "size") == 0)
234+
&& strcmp(type, "integer") == 0) {
235+
mlt_rect value = mlt_properties_anim_get_rect(properties, param_name, position, length);
236+
int x = (int) value.x;
237+
int y = (int) value.y;
238+
mltofx_param_set_value(image_effect_params, param_name, mltofx_prop_int2d, x, y);
80239
} else if (strcmp(type, "float") == 0) {
81240
double value = mlt_properties_anim_get_double(properties, param_name, position, length);
82241
mltofx_param_set_value(image_effect_params, param_name, mltofx_prop_double, value);
@@ -85,14 +244,18 @@ static void update_plugin_params(mlt_properties properties,
85244
mltofx_param_set_value(image_effect_params, param_name, mltofx_prop_int, value);
86245
} else if (strcmp(type, "string") == 0) {
87246
char *value = mlt_properties_anim_get(properties, param_name, position, length);
88-
if (value)
247+
if (value) {
248+
natron_compat_track_choice(&natron_compat, param_name, value);
89249
mltofx_param_set_value(image_effect_params, param_name, mltofx_prop_string, value);
250+
}
90251
} else if (strcmp(type, "color") == 0) {
91252
mlt_color value
92253
= mlt_properties_anim_get_color(properties, param_name, position, length);
93254
mltofx_param_set_value(image_effect_params, param_name, mltofx_prop_color, value);
94255
}
95256
}
257+
258+
natron_compat_apply_size(image_effect_params, &natron_compat);
96259
}
97260

98261
static int filter_get_image(mlt_frame frame,
@@ -105,9 +268,18 @@ static int filter_get_image(mlt_frame frame,
105268
mlt_filter filter = (mlt_filter) mlt_frame_pop_service(frame);
106269
mlt_properties properties = MLT_FILTER_PROPERTIES(filter);
107270
OfxPlugin *plugin = mlt_properties_get_data(properties, "ofx_plugin", NULL);
108-
mlt_properties image_effect = mlt_properties_get_properties(properties, OFX_IMAGE_EFFECT);
271+
mlt_properties base_image_effect = mlt_properties_get_properties(properties, OFX_IMAGE_EFFECT);
272+
int allow_frame_threading = mlt_properties_get_int(properties, "_allow_frame_threading");
273+
mlt_properties image_effect = base_image_effect;
274+
if (allow_frame_threading) {
275+
image_effect = create_image_effect_instance(plugin, properties, 0);
276+
if (!image_effect)
277+
image_effect = base_image_effect;
278+
}
279+
109280
mlt_properties params = mlt_properties_get_properties(image_effect, "mltofx_params");
110281
mlt_properties image_effect_params = mlt_properties_get_properties(image_effect, "params");
282+
int lock_service = (image_effect == base_image_effect);
111283

112284
mlt_image_format requested_format = *format;
113285
mltofx_depths_mask plugin_support_depths = mltofx_plugin_supported_depths(image_effect);
@@ -133,6 +305,10 @@ static int filter_get_image(mlt_frame frame,
133305
if (pixel_aspect_ratio <= 0.0)
134306
pixel_aspect_ratio = 1.0;
135307

308+
mlt_profile profile = mlt_service_profile(MLT_FILTER_SERVICE(filter));
309+
double render_scale_x = mlt_profile_scale_width(profile, *width);
310+
double render_scale_y = mlt_profile_scale_height(profile, *height);
311+
136312
mlt_position position = mlt_filter_get_position(filter, frame);
137313
mlt_position length = mlt_filter_get_length2(filter, frame);
138314
double ofx_time = (double) position;
@@ -195,7 +371,11 @@ static int filter_get_image(mlt_frame frame,
195371
: use_float ? (uint8_t *) float_out
196372
: *image;
197373

198-
mlt_service_lock(MLT_FILTER_SERVICE(filter));
374+
if (lock_service)
375+
mlt_service_lock(MLT_FILTER_SERVICE(filter));
376+
377+
// In serialized mode, image_effect is shared across frames; update scale under lock.
378+
mltofx_set_render_scale(image_effect, render_scale_x, render_scale_y);
199379

200380
update_plugin_params(properties, image_effect_params, params, position, length);
201381

@@ -219,8 +399,10 @@ static int filter_get_image(mlt_frame frame,
219399
pixel_aspect_ratio,
220400
ofx_depth);
221401

222-
// OFX pre-render action order: GetClipPreferences → GetRegionsOfInterest → BeginSequenceRender
402+
// OFX pre-render action order:
403+
// GetClipPreferences -> GetRegionOfDefinition -> GetRegionsOfInterest -> BeginSequenceRender
223404
mltofx_get_clip_preferences(plugin, image_effect);
405+
mltofx_get_region_of_definition(plugin, image_effect, ofx_time);
224406
mltofx_get_regions_of_interest(plugin, image_effect, ofx_time, (double) *width, (double) *height);
225407
mltofx_begin_sequence_render(plugin, image_effect, ofx_time);
226408

@@ -245,7 +427,13 @@ static int filter_get_image(mlt_frame frame,
245427
mltofx_action_render(plugin, image_effect, ofx_time, *width, *height);
246428
mltofx_end_sequence_render(plugin, image_effect, ofx_time);
247429

248-
mlt_service_unlock(MLT_FILTER_SERVICE(filter));
430+
if (lock_service)
431+
mlt_service_unlock(MLT_FILTER_SERVICE(filter));
432+
433+
if (image_effect != base_image_effect) {
434+
mltofx_destroy_instance(plugin, image_effect);
435+
mlt_properties_close(image_effect);
436+
}
249437

250438
// Convert output back to rgba64 in-place — outside the lock, per-frame only.
251439
if (use_half) {
@@ -319,30 +507,24 @@ mlt_filter filter_openfx_init(mlt_profile profile, mlt_service_type type, const
319507
mlt_filter_close(filter);
320508
return NULL;
321509
}
322-
mlt_properties params = mlt_properties_new();
323-
mlt_properties image_effect = mltofx_fetch_params(pt, params, NULL);
324-
mltofx_create_instance(pt, image_effect);
325-
326-
mlt_properties_set_properties(image_effect, "begin_sequence_props", mlt_properties_new());
327-
mlt_properties_close(mlt_properties_get_properties(image_effect, "get_rod_in_args"));
328-
mlt_properties_set_properties(image_effect, "end_sequence_props", mlt_properties_new());
329-
mlt_properties_close(mlt_properties_get_properties(image_effect, "get_rod_out_args"));
330-
mlt_properties_set_properties(image_effect, "get_rod_in_args", mlt_properties_new());
331-
mlt_properties_close(mlt_properties_get_properties(image_effect, "get_roi_in_args"));
332-
mlt_properties_set_properties(image_effect, "get_rod_out_args", mlt_properties_new());
333-
mlt_properties_close(mlt_properties_get_properties(image_effect, "get_roi_out_args"));
334-
mlt_properties_set_properties(image_effect, "get_roi_in_args", mlt_properties_new());
335-
mlt_properties_close(mlt_properties_get_properties(image_effect, "get_roi_in_args"));
336-
mlt_properties_set_properties(image_effect, "get_roi_out_args", mlt_properties_new());
337-
mlt_properties_close(mlt_properties_get_properties(image_effect, "get_roi_out_args"));
338-
mlt_properties_set_properties(image_effect, "get_clippref_args", mlt_properties_new());
339-
mlt_properties_close(mlt_properties_get_properties(image_effect, "get_clippref_args"));
340-
mlt_properties_set_properties(image_effect, "render_in_args", mlt_properties_new());
341-
mlt_properties_close(mlt_properties_get_properties(image_effect, "render_in_args"));
510+
mlt_properties image_effect = create_image_effect_instance(pt, properties, 1);
511+
if (!image_effect) {
512+
mlt_log_error(filter, "Failed to create OpenFX image effect instance: %s\n", id);
513+
mlt_filter_close(filter);
514+
return NULL;
515+
}
516+
517+
mlt_properties_set_int(properties,
518+
"_allow_frame_threading",
519+
mltofx_allows_frame_threading(image_effect));
520+
mlt_log_info(filter,
521+
"OpenFX threading mode for %s: %s\n",
522+
id,
523+
mlt_properties_get_int(properties, "_allow_frame_threading") ? "frame-threaded"
524+
: "serialized");
342525
mlt_properties_set_data(properties, "ofx_plugin", pt, 0, NULL, NULL);
343526
mlt_properties_set_properties(properties, OFX_IMAGE_EFFECT, image_effect);
344527
mlt_properties_close(image_effect);
345-
mlt_properties_close(params);
346528
filter->process = filter_process;
347529
filter->close = filter_close;
348530
return filter;

0 commit comments

Comments
 (0)