Skip to content

Commit 883ffd9

Browse files
ddennedyD-OgiCopilotCopilot
authored
Add libplacebo GPU module with render and shader filters (#1236)
* Add libplacebo GPU module with render and shader filters 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. * Fix MinGW build: portable format specifiers, add libplacebo to CI 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. * Fix clang-format violation in save_cache log line Break long mlt_log_info() call into multi-line format to match the project's clang-format rules (same style as load_cache above). * Reuse GPU textures between chained placebo filters 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. * Support DYNAMIC shader parameters and base64-encoded shader_text 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. * Fix clang-format-14 violations in placebo module 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. * Refactor image conversion - Refactored image conversion handling across various modules to use new framework functions for better clarity and maintainability. - Now, there can be multiple possible image converters per frame stopping after the first success. But also, one converter can part of the work (e.g. dowload from GPU) while a later one converts it to the actual requested format. - Replaced direct calls to `convert_image` with `mlt_frame_convert_image` and added checks for the existence of conversion functions. - Improved documentation in YAML files to clarify the purpose of image conversion callbacks. - Added tests to verify the correct propagation of image conversion functions through frames and producers. * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix early returns not releasing lock * change a log to debug level * add MLT_PLACEBO_CACHE_PATH and replace atext() * Add placebo.convert filter - Introduced new `mlt_image_private` format for module internal use. - Improved placebo texture reuse across filters. * fix cppcheck and redundant workflow runs * fix ctest on msvc * docs for placebo private image handling * Enhance image conversion logic to handle private format cases * Fix double-free of src_tex and out-of-bounds in imageconvert - 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> * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * fix lazy allocation not thread-safe * fix races in placebo.shader * Add permanent failure flag for GPU initialization and fix leak * Add permanent failure and shutdown flags for GPU management * use "mlt_image_private" and mlt_properties_clear() * add `mlt_frame_prepend_convert_image()` * add `mlt_frame_prepend_convert_image()` * fix cppcheck and redundant workflow runs * fix ctest on msvc * remove placebo.convert filter prepend the callback on the frame as-needed * fix cppcheck * mlt_frame_append_convert_image() * update CMakeLists.txt to require libplacebo version 5.229 and simplify cppcheck suppression for placebo module * improve placebo.render meta & cleanup * Change minimum Fedora build test version to 40 --------- Co-authored-by: D-Ogi <D-Ogi@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ddennedy <1146683+ddennedy@users.noreply.github.com>
1 parent 5780111 commit 883ffd9

29 files changed

Lines changed: 1890 additions & 35 deletions

.github/workflows/build-distros.yml

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ on:
1919
- debian-testing
2020
- debian-stable
2121
- fedora-44
22-
- fedora-38
22+
- fedora-40
2323
concurrency:
2424
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
2525
cancel-in-progress: true
@@ -37,7 +37,7 @@ jobs:
3737
sed -i 's/^Types: deb/Types: deb deb-src/' /etc/apt/sources.list.d/ubuntu.sources
3838
DEBIAN_FRONTEND=noninteractive apt-get -qq update
3939
DEBIAN_FRONTEND=noninteractive apt-get -yqq build-dep mlt
40-
DEBIAN_FRONTEND=noninteractive apt-get -yqq install autoconf automake git libtool pkg-config wget cmake qt6-base-dev libqt6svg6-dev
40+
DEBIAN_FRONTEND=noninteractive apt-get -yqq install autoconf automake git libtool pkg-config wget cmake libplacebo-dev qt6-base-dev libqt6svg6-dev
4141
git clone --depth 1 --branch v0.2 https://github.com/xiph/rnnoise.git /tmp/rnnoise
4242
cd /tmp/rnnoise
4343
./autogen.sh
@@ -50,7 +50,7 @@ jobs:
5050
sed -i 's/^Types: deb/Types: deb deb-src/' /etc/apt/sources.list.d/ubuntu.sources
5151
DEBIAN_FRONTEND=noninteractive apt-get -qq update
5252
DEBIAN_FRONTEND=noninteractive apt-get -yqq build-dep mlt
53-
DEBIAN_FRONTEND=noninteractive apt-get -yqq install autoconf automake git libtool pkg-config wget cmake qt6-base-dev libqt6svg6-dev
53+
DEBIAN_FRONTEND=noninteractive apt-get -yqq install autoconf automake git libtool pkg-config wget cmake qt6-base-dev libplacebo-dev libqt6svg6-dev
5454
git clone --depth 1 --branch v0.2 https://github.com/xiph/rnnoise.git /tmp/rnnoise
5555
cd /tmp/rnnoise
5656
./autogen.sh
@@ -63,7 +63,7 @@ jobs:
6363
sed -i '/^#\sdeb-src /s/^#//' "/etc/apt/sources.list"
6464
DEBIAN_FRONTEND=noninteractive apt-get -qq update
6565
DEBIAN_FRONTEND=noninteractive apt-get -yqq build-dep mlt
66-
DEBIAN_FRONTEND=noninteractive apt-get -yqq install autoconf automake git libtool pkg-config wget cmake qt6-base-dev libqt6svg6-dev libqt6core5compat6-dev
66+
DEBIAN_FRONTEND=noninteractive apt-get -yqq install autoconf automake git libtool pkg-config wget cmake libplacebo-dev qt6-base-dev libqt6svg6-dev libqt6core5compat6-dev
6767
git clone --depth 1 --branch v0.2 https://github.com/xiph/rnnoise.git /tmp/rnnoise
6868
cd /tmp/rnnoise
6969
./autogen.sh
@@ -76,7 +76,7 @@ jobs:
7676
echo 'deb-src http://deb.debian.org/debian testing main' >> /etc/apt/sources.list
7777
apt-get -qq update
7878
apt-get -yqq build-dep mlt
79-
apt-get -yqq install autoconf automake git libtool pkg-config wget cmake qt6-base-dev libqt6svg6-dev
79+
apt-get -yqq install autoconf automake git libtool pkg-config wget cmake libplacebo-dev qt6-base-dev libqt6svg6-dev
8080
git clone --depth 1 --branch v0.2 https://github.com/xiph/rnnoise.git /tmp/rnnoise
8181
cd /tmp/rnnoise
8282
./autogen.sh
@@ -90,7 +90,7 @@ jobs:
9090
echo 'deb http://deb.debian.org/debian bookworm-backports main' >> /etc/apt/sources.list
9191
apt-get -qq update
9292
apt-get -yqq build-dep mlt
93-
apt-get -yqq install autoconf automake git libtool pkg-config wget cmake qt6-base-dev libqt6svg6-dev
93+
apt-get -yqq install autoconf automake git libtool pkg-config wget cmake libplacebo-dev qt6-base-dev libqt6svg6-dev
9494
git clone --depth 1 --branch v0.2 https://github.com/xiph/rnnoise.git /tmp/rnnoise
9595
cd /tmp/rnnoise
9696
./autogen.sh
@@ -109,9 +109,9 @@ jobs:
109109
libtheora-devel libvorbis-devel libvdpau-devel \
110110
libsoup-devel liboil-devel python-devel alsa-lib \
111111
pulseaudio-libs-devel gcc-c++ cmake ffmpeg-free-devel \
112-
movit-devel rubberband-devel vid.stab-devel rnnoise-devel
113-
- name: fedora-38
114-
image: fedora:38
112+
libplacebo-devel movit-devel rubberband-devel vid.stab-devel rnnoise-devel
113+
- name: fedora-40
114+
image: fedora:40
115115
setup_script: |
116116
yum --assumeyes groupinstall "Development Tools"
117117
yum --assumeyes install \
@@ -122,7 +122,7 @@ jobs:
122122
libtheora-devel libvorbis-devel libvdpau-devel \
123123
libsoup-devel liboil-devel python-devel alsa-lib \
124124
pulseaudio-libs-devel gcc-c++ cmake ffmpeg-free-devel \
125-
movit-devel rubberband-devel vid.stab-devel rnnoise-devel
125+
libplacebo-devel movit-devel rubberband-devel vid.stab-devel rnnoise-devel
126126
127127
container:
128128
image: ${{ matrix.image }}

.github/workflows/build-linux.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ jobs:
4646
sudo sed -i '/^#\sdeb-src /s/^#//' "/etc/apt/sources.list"
4747
sudo apt-get -qq update
4848
sudo apt-get -yqq build-dep mlt
49-
sudo apt-get -yqq install autoconf automake git libtool pkg-config wget qt6-base-dev libqt6svg6-dev libqt6core5compat6-dev
49+
sudo apt-get -yqq install autoconf automake git libtool pkg-config wget libplacebo-dev qt6-base-dev libqt6svg6-dev libqt6core5compat6-dev
5050
git clone --depth 1 --branch v0.2 https://github.com/xiph/rnnoise.git /tmp/rnnoise
5151
pushd /tmp/rnnoise
5252
./autogen.sh

.github/workflows/build-msys2-mingw64.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ jobs:
6868
mingw-w64-x86_64-x264
6969
mingw-w64-x86_64-x265
7070
mingw-w64-x86_64-zimg
71+
mingw-w64-x86_64-libplacebo
7172
7273
- uses: actions/checkout@v4
7374

.github/workflows/static-code-analysis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ jobs:
2727
--library=cppcheck.cfg
2828
--suppress=ctuOneDefinitionRuleViolation
2929
--suppress=syntaxError:src/modules/xml/common.c
30+
--suppress=syntaxError:src/modules/placebo/*.c
3031
steps:
3132
- uses: actions/checkout@v4
3233
- name: Install Cppcheck

CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ option(MOD_NORMALIZE "Enable Normalize module (GPL)" ON)
3535
option(MOD_OLDFILM "Enable Oldfilm module" ON)
3636
option(MOD_OPENCV "Enable OpenCV module" OFF)
3737
option(MOD_OPENFX "Enable OpenFX module (GPL)" ON)
38+
option(MOD_PLACEBO "Enable libplacebo GPU module" ON)
3839
option(MOD_PLUS "Enable Plus module" ON)
3940
option(MOD_PLUSGPL "Enable PlusGPL module (GPL)" ON)
4041
option(MOD_QT6 "Enable Qt6 module (GPL)" ON)
@@ -319,6 +320,15 @@ if(MOD_OPENFX)
319320
pkg_check_modules(glib IMPORTED_TARGET glib-2.0)
320321
endif()
321322

323+
if(MOD_PLACEBO)
324+
pkg_check_modules(libplacebo IMPORTED_TARGET libplacebo>=5.229)
325+
if(libplacebo_FOUND)
326+
list(APPEND MLT_SUPPORTED_COMPONENTS placebo)
327+
else()
328+
set(MOD_PLACEBO OFF)
329+
endif()
330+
endif()
331+
322332
if(MOD_PLUS)
323333
pkg_check_modules(libebur128 IMPORTED_TARGET libebur128)
324334
list(APPEND MLT_SUPPORTED_COMPONENTS plus)
@@ -598,6 +608,7 @@ add_feature_info("Module: Normalize" MOD_NORMALIZE "")
598608
add_feature_info("Module: Oldfilm" MOD_OLDFILM "")
599609
add_feature_info("Module: OpenCV" MOD_OPENCV "")
600610
add_feature_info("Module: OpenFX" MOD_OPENFX "")
611+
add_feature_info("Module: Placebo" MOD_PLACEBO "")
601612
add_feature_info("Module: Plus" MOD_PLUS "")
602613
add_feature_info("Module: PlusGPL" MOD_PLUSGPL "")
603614
add_feature_info("Module: Qt6" MOD_QT6 "")

makefile

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,4 @@ cppcheck:
3030
--library=cppcheck.cfg \
3131
--suppress=ctuOneDefinitionRuleViolation \
3232
--suppress=syntaxError:src/modules/xml/common.c \
33-
--suppress=syntaxError:src/modules/placebo/filter_placebo_convert.c \
34-
--suppress=syntaxError:src/modules/placebo/filter_placebo_render.c \
35-
--suppress=syntaxError:src/modules/placebo/filter_placebo_shader.c \
36-
--suppress=syntaxError:src/modules/placebo/gpu_context.c
33+
--suppress=syntaxError:src/modules/placebo/*.c

src/framework/mlt_frame.c

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ static int generate_test_image(mlt_properties properties,
448448
case mlt_image_none:
449449
case mlt_image_movit:
450450
case mlt_image_opengl_texture:
451+
case mlt_image_private:
451452
*format = mlt_image_yuv422;
452453
break;
453454
case mlt_image_invalid:
@@ -1157,15 +1158,13 @@ mlt_frame mlt_frame_clone(mlt_frame self, int is_deep)
11571158
}
11581159
size = 0;
11591160
data = mlt_properties_get_data(properties, "image", &size);
1160-
if (data && mlt_image_movit != mlt_properties_get_int(properties, "format")) {
1161+
mlt_image_format format = mlt_properties_get_int(properties, "format");
1162+
if (data && format != mlt_image_movit && format != mlt_image_private) {
11611163
int width = mlt_properties_get_int(properties, "width");
11621164
int height = mlt_properties_get_int(properties, "height");
11631165

11641166
if (!size)
1165-
size = mlt_image_format_size(mlt_properties_get_int(properties, "format"),
1166-
width,
1167-
height,
1168-
NULL);
1167+
size = mlt_image_format_size(format, width, height, NULL);
11691168
copy = mlt_pool_alloc(size);
11701169
memcpy(copy, data, size);
11711170
mlt_properties_set_data(new_props, "image", copy, size, mlt_pool_release, NULL);
@@ -1299,15 +1298,13 @@ mlt_frame mlt_frame_clone_image(mlt_frame self, int is_deep)
12991298

13001299
if (is_deep) {
13011300
data = mlt_properties_get_data(properties, "image", &size);
1302-
if (data && mlt_image_movit != mlt_properties_get_int(properties, "format")) {
1301+
mlt_image_format format = mlt_properties_get_int(properties, "format");
1302+
if (data && format != mlt_image_movit && format != mlt_image_private) {
13031303
int width = mlt_properties_get_int(properties, "width");
13041304
int height = mlt_properties_get_int(properties, "height");
13051305

13061306
if (!size)
1307-
size = mlt_image_format_size(mlt_properties_get_int(properties, "format"),
1308-
width,
1309-
height,
1310-
NULL);
1307+
size = mlt_image_format_size(format, width, height, NULL);
13111308
copy = mlt_pool_alloc(size);
13121309
memcpy(copy, data, size);
13131310
mlt_properties_set_data(new_props, "image", copy, size, mlt_pool_release, NULL);

src/framework/mlt_image.c

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,8 @@ int mlt_image_calculate_size(mlt_image self)
188188
return self->width * self->height * 3 / 2;
189189
case mlt_image_movit:
190190
case mlt_image_opengl_texture:
191-
return 4;
191+
case mlt_image_private:
192+
return sizeof(void *);
192193
case mlt_image_yuv422p16:
193194
return self->width * self->height * 4;
194195
case mlt_image_yuv420p10:
@@ -228,6 +229,8 @@ const char *mlt_image_format_name(mlt_image_format format)
228229
return "glsl";
229230
case mlt_image_opengl_texture:
230231
return "opengl_texture";
232+
case mlt_image_private:
233+
return "private";
231234
case mlt_image_yuv422p16:
232235
return "yuv422p16";
233236
case mlt_image_yuv420p10:
@@ -489,6 +492,7 @@ mlt_colorspace mlt_image_default_colorspace(mlt_image_format format, int height)
489492
case mlt_image_rgba64:
490493
case mlt_image_movit:
491494
case mlt_image_opengl_texture:
495+
case mlt_image_private:
492496
colorspace = mlt_colorspace_rgb;
493497
break;
494498
case mlt_image_yuv422:
@@ -605,6 +609,7 @@ void mlt_image_fill_black(mlt_image self)
605609
case mlt_image_none:
606610
case mlt_image_movit:
607611
case mlt_image_opengl_texture:
612+
case mlt_image_private:
608613
return;
609614
case mlt_image_rgb:
610615
case mlt_image_rgba:
@@ -693,6 +698,7 @@ void mlt_image_fill_checkerboard(mlt_image self, double sample_aspect_ratio)
693698
case mlt_image_none:
694699
case mlt_image_movit:
695700
case mlt_image_opengl_texture:
701+
case mlt_image_private:
696702
return;
697703
case mlt_image_rgb:
698704
case mlt_image_rgba: {
@@ -798,6 +804,7 @@ void mlt_image_fill_white(mlt_image self, int full_range)
798804
case mlt_image_none:
799805
case mlt_image_movit:
800806
case mlt_image_opengl_texture:
807+
case mlt_image_private:
801808
return;
802809
case mlt_image_rgb:
803810
case mlt_image_rgba:
@@ -952,9 +959,10 @@ int mlt_image_format_size(mlt_image_format format, int width, int height, int *b
952959
return width * height * 3 / 2;
953960
case mlt_image_movit:
954961
case mlt_image_opengl_texture:
962+
case mlt_image_private:
955963
if (bpp)
956964
*bpp = 0;
957-
return 4;
965+
return sizeof(void *);
958966
case mlt_image_yuv422p16:
959967
if (bpp)
960968
*bpp = 4;

src/framework/mlt_types.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* \file mlt_types.h
33
* \brief Provides forward definitions of all public types
44
*
5-
* Copyright (C) 2003-2025 Meltytech, LLC
5+
* Copyright (C) 2003-2026 Meltytech, LLC
66
*
77
* This library is free software; you can redistribute it and/or
88
* modify it under the terms of the GNU Lesser General Public
@@ -49,7 +49,8 @@ typedef enum {
4949
mlt_image_yuv420p10, /**< planar YUV 4:2:0, 15bpp, (1 Cr & Cb sample per 2x2 Y samples), little-endian */
5050
mlt_image_yuv444p10, /**< planar YUV 4:4:4, 30bpp, (1 Cr & Cb sample per 1x1 Y samples), little-endian */
5151
mlt_image_rgba64, /**< 16-bit RGB with alpha channel */
52-
mlt_image_invalid
52+
mlt_image_invalid,
53+
mlt_image_private /**< for module internal use only */
5354
} mlt_image_format;
5455

5556
/** The set of supported audio formats */

src/modules/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ if(MOD_MOVIT)
4848
add_subdirectory(movit)
4949
endif()
5050

51+
if(MOD_PLACEBO)
52+
add_subdirectory(placebo)
53+
endif()
54+
5155
if(MOD_PLUS)
5256
add_subdirectory(plus)
5357
endif()

0 commit comments

Comments
 (0)