Add libplacebo GPU module with render and shader filters#1236
Add libplacebo GPU module with render and shader filters#1236ddennedy wants to merge 27 commits into
Conversation
New module 'placebo' providing GPU-accelerated video processing via libplacebo. Includes two filters: - placebo.render: GPU scaling, debanding, dithering, and tonemapping with quality presets (fast/default/high_quality) - placebo.shader: Custom mpv-compatible .hook shader support Backend priority: D3D11 (Windows) -> Vulkan -> OpenGL. Vulkan loader is dynamically loaded on Windows when libplacebo is built without vk-proc-addr support. Features: - Singleton GPU context with thread-safe access - Shader cache persistence - Multiple scaling algorithms (ewa_lanczos, lanczos, mitchell, etc.) - Tone mapping (auto, clip, mobius, reinhard, hable, bt.2390, spline) - Graceful fallback to passthrough when no GPU is available The module is enabled by default but skipped automatically when libplacebo is not installed.
Use PRIu64/PRId64 from <inttypes.h> instead of %zu/%ld for size logging in the placebo module. Add libplacebo-dev packages to Ubuntu, Debian, and Fedora 42 CI workflows, and mingw-w64-x86_64-libplacebo to the MSYS2 MinGW64 workflow.
Break long mlt_log_info() call into multi-line format to match the project's clang-format rules (same style as load_cache above).
When multiple placebo filters are stacked on one clip, each filter previously did a full RAM→GPU upload and GPU→RAM download. The intermediate uploads are redundant because the next placebo filter would re-upload the same pixels immediately. Each filter now attaches its output texture to the mlt_frame via placebo_frame_put_tex(). The next placebo filter calls placebo_frame_take_tex() to grab it directly as source, skipping the upload. The download to RAM still happens every time (MLT expects the image buffer to be current for non-GPU filters). Staleness detection: put_tex records the RAM buffer pointer, take_tex compares it against the current pointer. If a CPU filter ran in between and requested a writable buffer (triggering a copy and new allocation), the pointers differ and take_tex returns NULL, falling back to a fresh upload. Also cleans up internal ticket-style comments (C1/W2/etc.) with descriptions of actual logic and pitfalls.
Add apply_shader_params() to override pl_hook DYNAMIC parameters from
MLT animated properties (shader_param.* prefix) on every frame. Uses
mlt_properties_anim_get_double/int to correctly resolve keyframe strings
("0=200;50=100") at the current frame position.
Add base64 decoding for shader_text values prefixed with "base64:" to
support inline shaders with characters that are problematic in MLT
property strings.
Run clang-format-14 (matching CI) on filter_placebo_shader.c and gpu_context.c to fix designated initializer spacing, ternary line breaks, and long argument lists.
…into libplacebo
- Introduced new `mlt_image_private` format for module internal use. - Improved placebo texture reuse across filters.
|
@copilot resume review |
- Remove incorrect pl_tex_destroy(src_tex) in error paths of the render and shader filters: src_tex is owned by the frame and freed automatically when placebo_frame_set_tex() replaces the "image" property, so manual destruction caused a double-free on frame close. - Add *image = NULL after failed placebo_frame_set_tex() in render and shader filters (consistent with filter_placebo_convert.c). - Guard filter_imageconvert's conversion_matrix access: mlt_image_private (and mlt_image_invalid) are out of range for the matrix indexed by [format - 1]; early-return 1 for any format outside [mlt_image_rgb, mlt_image_rgba64] to avoid an out-of-bounds read. Agent-Logs-Url: https://github.com/mltframework/mlt/sessions/f71fd929-5b62-4e71-864a-13291a3ec28e Co-authored-by: ddennedy <1146683+ddennedy@users.noreply.github.com>
Resumed review and found three issues in the placebo module and related code, fixed in commit 739224f:
|
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
bmatherly
left a comment
There was a problem hiding this comment.
Thanks for this great PR. Here are some comments for your consideration. I did not run the code, so I apologize in advance if my comments are just lacking understanding.
|
|
||
| int placebo_frame_is_tex(mlt_frame frame, mlt_image_format format) | ||
| { | ||
| const char *image_format = mlt_properties_get(MLT_FRAME_PROPERTIES(frame), "mlt_image_format"); |
There was a problem hiding this comment.
I would suggest a different key for this property because "placebo" is, by definition, NOT a valid mlt_image_format. How about "_priv_format_id"? This could be a standard convention that we use for other modules that use the private type.
There was a problem hiding this comment.
That was supposed to be "mlt_image_private" as described in #1235 (comment)
It is not really necessary to preface frame methods with underscore to hide them from serialization because they don't serialize, and here the value is possibly meaningful across modules.
There was a problem hiding this comment.
changed to "mlt_image_private"
| void placebo_frame_set_requested_tex(mlt_frame frame, int requested) | ||
| { | ||
| mlt_properties props = MLT_FRAME_PROPERTIES(frame); | ||
| int count = mlt_properties_get_int(props, "_placebo_requested"); |
There was a problem hiding this comment.
Maybe we don't want to support or worry about this hypothetical future idea...
But, in a future world where avfilter also uses mlt_image_private, would this still work if we had a filter stack: placebo->avfilter->placebo?
I think the second placebo might try to interpret the private avframe type as a placebo. Maybe we could use a stack to push/pop private type requests?
There was a problem hiding this comment.
It is not a hypothetical. It was needed now to make placebo->brightness(rgba)->placebo, placebo->sepia(yuv422)->placebo, and placebo->movit->placebo work, which is all I have tested so far (in addition to placebo alone and placebo->placebo). If avfilter follows this pattern, it can intermingle.
I think the second placebo might try to interpret the private avframe type as a placebo.
No, that is why it tags the requested mlt_format_private with property "_placebo_requested" and placebo.convert requires for that (placebo_frame_wants_tex()). It cannot trust mlt_image_format==mlt_image_private alone per my comment in the other PR linked above. The placebo render and shader filters also check property "mlt_image_private"=="placebo" after requesting it through mlt_frame_get_image() to be sure it can continue.
and mlt_properties_clear()
prepend the callback on the frame as-needed
and simplify cppcheck suppression for placebo module
|
I am still learning about libplacebo, and the new shader filter is obvious even though available shaders are rather limited to a few topics. However, I found the render filter rather odd. What exactly does it do for us? For example, it exposes scaling options but does no scaling. After some brief reading and a discussion with GPT:
A couple of my thoughtsMaybe this module should provide new normalizing filters. It can do color (de)linearization, image (pixel format and colorspace) conversion, deinterlace, and scaling. It is a little disconcerting that these new filters are hard-wired around 8-bit rgba. Pixel formats
So for planning, The caveat is important: exact ingest/output depends on the upload path and the GPU/backend texture support. Libplacebo’s model is capability-based, not “these exact 27 formats always work everywhere.” Color spaces / color metadata
So yes, converting to and from linear light is within libplacebo’s capability model. It also supports HDR-related workflows like PQ and HLG, and source/target color transforms rather than just “treat everything as sRGB.” A practical mental model is:
What libplacebo is good at
What not to assume
That last point is where wrappers often narrow the capability. The shortest accurate summary is:
OK, clearly this PR is just a start, and shader is clearly general purpose and extensible. However, now I think this render filter should be refactored into 2 new filters with no scaling or tonemapping options: deband and dither. |
supersedes #1201