The branch, master has been updated via 36e374efb05a2ad81369980de98a0d78381ec81f (commit) via 7856f57533b14c18e888e275770a0ca26685d6e4 (commit) via 5146b2fb8b700c5376244859dcd1731e4470e3b7 (commit) via 9c8a6ac85c3e56f8168a7813e4c825aff7044939 (commit) via 0ce413af9cc8040095a6b714600d6a8dceaca514 (commit) via 0362cb38062b2a8cead50840aaefb7257570e3f0 (commit) from bdb81d93474ecfc575a88b4028151940b6c8e3c8 (commit)
- Log ----------------------------------------------------------------- commit 36e374efb05a2ad81369980de98a0d78381ec81f Author: Timo Rothenpieler <t...@rothenpieler.org> AuthorDate: Sat Aug 30 00:45:22 2025 +0200 Commit: Timo Rothenpieler <t...@rothenpieler.org> CommitDate: Sun Sep 14 11:45:11 2025 +0000 avfilter: add gfxcapture, Windows.Graphics.Capture based window/monitor capture diff --git a/Changelog b/Changelog index aaab8d9b54..88e1ce6659 100644 --- a/Changelog +++ b/Changelog @@ -4,6 +4,7 @@ releases are sorted from youngest to oldest. version <next>: - ffprobe -codec option - EXIF Metadata Parsing +- gfxcapture: Windows.Graphics.Capture based window/monitor capture version 8.0: diff --git a/configure b/configure index 2364e00d08..73531a498a 100755 --- a/configure +++ b/configure @@ -2516,6 +2516,8 @@ TOOLCHAIN_FEATURES=" TYPES_LIST=" DPI_AWARENESS_CONTEXT IDXGIOutput5 + __x_ABI_CWindows_CGraphics_CCapture_CIGraphicsCaptureSession5 + IDirect3DDxgiInterfaceAccess kCMVideoCodecType_HEVC kCMVideoCodecType_HEVCWithAlpha kCMVideoCodecType_VP9 @@ -3412,6 +3414,8 @@ pad_cuda_filter_deps_any="cuda_nvcc cuda_llvm" sharpen_npp_filter_deps="ffnvcodec libnpp" ddagrab_filter_deps="d3d11va IDXGIOutput1 DXGI_OUTDUPL_FRAME_INFO" +gfxcapture_filter_deps="cxx17 threads d3d11va IGraphicsCaptureItemInterop __x_ABI_CWindows_CGraphics_CCapture_CIGraphicsCaptureSession3" +gfxcapture_filter_extralibs="-lstdc++" scale_d3d11_filter_deps="d3d11va" amf_deps_any="libdl LoadLibrary" @@ -6940,6 +6944,10 @@ check_type "windows.h" "DPI_AWARENESS_CONTEXT" -D_WIN32_WINNT=0x0A00 check_type "windows.h security.h schnlsp.h" SecPkgContext_KeyingMaterialInfo -DSECURITY_WIN32 check_type "d3d9.h dxva2api.h" DXVA2_ConfigPictureDecode -D_WIN32_WINNT=0x0602 check_func_headers mfapi.h MFCreateAlignedMemoryBuffer -lmfplat +check_type "windows.h windows.graphics.capture.h" __x_ABI_CWindows_CGraphics_CCapture_CIGraphicsCaptureSession3 -D_WIN32_WINNT=0x0A00 -DWINDOWS_FOUNDATION_UNIVERSALAPICONTRACT_VERSION=0x130000 -DCOBJMACROS +check_type "windows.h windows.graphics.capture.h" __x_ABI_CWindows_CGraphics_CCapture_CIGraphicsCaptureSession5 -D_WIN32_WINNT=0x0A00 -DWINDOWS_FOUNDATION_UNIVERSALAPICONTRACT_VERSION=0x130000 -DCOBJMACROS +check_type "windows.h windows.graphics.capture.interop.h" IGraphicsCaptureItemInterop -D_WIN32_WINNT=0x0A00 -DWINDOWS_FOUNDATION_UNIVERSALAPICONTRACT_VERSION=0x130000 -DCOBJMACROS +check_type "windows.h windows.graphics.directx.direct3d11.interop.h" IDirect3DDxgiInterfaceAccess -D_WIN32_WINNT=0x0A00 -DWINDOWS_FOUNDATION_UNIVERSALAPICONTRACT_VERSION=0x130000 -DCOBJMACROS check_type "vdpau/vdpau.h" "VdpPictureInfoHEVC" check_type "vdpau/vdpau.h" "VdpPictureInfoVP9" diff --git a/doc/filters.texi b/doc/filters.texi index 5b52fc5521..726c1d97da 100644 --- a/doc/filters.texi +++ b/doc/filters.texi @@ -29614,6 +29614,165 @@ ddagrab=video_size=800x600:offset_x=100:offset_y=100 @end example +@section gfxcapture + +Capture windows or monitors using Windows.Graphics.Capture API. + +This source provides low overhead capture of application windows or entire monitors. +The filter outputs hardware frames in @code{d3d11} format; use @code{hwdownload,format=} +if system memory frames are required. + +The window to be captured can be selected via regular expressions on its title, +class name or backing executable name, by explicit native handles, or by monitor +index or explicit native handle. A window must match all provided expressions to be +selected. The first matching window will be picked, in whatever order Windows +returns them. + +Explicit handles (@option{hwnd}, @option{hmonitor}) override pattern or index +based selection. If neither handles nor a monitor index are given, the first +window matching the provided regular expressions is captured. + +This source does NOT hold a stable FPS. It returns frames at whatever rate the compositor +provides them, only capped by the @option{max_framerate}. +If you need a stable rate, you need to add an fps filter to drop/duplicate frames as needed. + +If the capture source disappears mid-capture (window closed, monitor disconnected), the filter will return EOF. + +This source accepts the following options: +@table @option +@item window_title +ECMAScript regular expression matched against the window title. Supports a +PCRE style @code{(?i)} prefix for case-insensitive matching. + +@item window_class +As @option{window_title}, but matched against the window class name. + +@item window_exe +As @option{window_title}, but matched against the executable file name of the +window's process. + +@item monitor_idx +Zero-based index of the monitor to capture. + +Can also be set to @code{window} to capture the monitor the selected window +is displayed on at filter initialization time. + +@item hwnd +Explicit native window handle (HWND). + +@item hmonitor +Explicit native monitor handle (HMONITOR). + +@item capture_cursor +Capture the mouse cursor. Enabled by default. + +@item capture_border +Capture the full area of the window, including its window decorarions/border. +Disabled by default. + +@item display_border +Draw a yellow highlight border around the captured window. Disabled by default. + +@item max_framerate +Maximum capture frame rate. Accepts a video rate (e.g. @code{30}, @code{60/1}, +@code{24000/1001}). The default is @code{60} FPS. +The actual rate is the rate at which the compositor renders the window/monitor, +capped by this option. + +@item width +Force the output canvas width. If zero (default) the initial captured source +width is used. If a negative number is provided, the width will be rounded +down to the next multiple of that number. + +See @option{resize_mode}. + +@item height +Force the output canvas height. If zero (default) the initial captured source +height is used. If a negative number is provided, the height will be rounded +down to the next multiple of that number. + +See @option{resize_mode}. + +@item crop_left +Crop this many pixels from the left side of the captured frames. + +@item crop_top +Crop this many pixels from the left side of the captured frames. + +@item crop_right +Crop this many pixels from the left side of the captured frames. + +@item crop_bottom +Crop this many pixels from the left side of the captured frames. + +@item premultiplied +If set to 1, return frames with premultiplied alpha. Default is 0 (straight +alpha). + +@item resize_mode +Defines how the captured content is fitted into the output canvas size. +Possible values: +@table @samp +@item crop +Crop (or pad with black) to the canvas size. (default) +@item scale +Scale the source to fill the canvas, potentially altering aspect ratio. +@item scale_aspect +Scale the source to fit inside the canvas while preserving aspect ratio. +Remaining area is filled with black. +@end table + +@item scale_mode +Scaling algorithm used when resizing is required. + +Possible values: +@table @samp +@item point +Nearest neighbour (pixelated) scaling. +@item bilinear +Bilinear filtering. (default) +@item bicubic +Bicubic filtering. Potentially more blurry, but fewer scaling artifacts depending on contents. +@end table + +@item output_fmt +Desired output pixel format inside the D3D11 hardware frames. + +Possible values: +@table @samp +@item bgra +@item 8bit +8 bit BGRA output (default) +@item x2bgr10 +@item 10bit +10 bit BGR output +@item rgbaf16 +@item 16bit +16bit float RGBA output +@end table + +@end table + +@subsection Examples +@itemize +@item Capture a window by title (case-insensitive) at a maximum of 60 fps: +@example +ffmpeg -filter_complex gfxcapture=window_text='(?i)My Application':max_framerate=60,hwdownload,format=bgra,format=yuv420p -c:v libx264 -crf 15 capture.mp4 +@end example + +@item Capture monitor 1 at native refresh, 10bit color depth, scale to 1920x1080 preserving aspect: +@example +ffmpeg -filter_complex gfxcapture=monitor_idx=1:width=1920:height=1080:resize_mode=scale_aspect:output_fmt=10bit -c:v hevc_nvenc -cq 15 capture.mp4 +@end example + +@item Capture a window by executable name, draw border, force point scaling, fixed 60 fps: +@example +ffmpeg -filter_complex gfxcapture=window_exe='^firefox.exe$':display_border=1:scale_mode=point,fps=60 -rc qvbr -qvbr_quality_level 15 -c:v h264_amf capture.mp4 +@end example + +@end itemize + + @section gradients Generate several gradients. diff --git a/libavfilter/Makefile b/libavfilter/Makefile index bd3f6da27d..70b100aff1 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -609,6 +609,7 @@ OBJS-$(CONFIG_COLORSPECTRUM_FILTER) += vsrc_testsrc.o OBJS-$(CONFIG_COREIMAGESRC_FILTER) += vf_coreimage.o OBJS-$(CONFIG_DDAGRAB_FILTER) += vsrc_ddagrab.o OBJS-$(CONFIG_FREI0R_SRC_FILTER) += vf_frei0r.o +OBJS-$(CONFIG_GFXCAPTURE_FILTER) += vsrc_gfxcapture.o vsrc_gfxcapture_winrt.o OBJS-$(CONFIG_GRADIENTS_FILTER) += vsrc_gradients.o OBJS-$(CONFIG_HALDCLUTSRC_FILTER) += vsrc_testsrc.o OBJS-$(CONFIG_LIFE_FILTER) += vsrc_life.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index 3ac1502254..84f15f85c5 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -571,6 +571,7 @@ extern const FFFilter ff_vsrc_colorspectrum; extern const FFFilter ff_vsrc_coreimagesrc; extern const FFFilter ff_vsrc_ddagrab; extern const FFFilter ff_vsrc_frei0r_src; +extern const FFFilter ff_vsrc_gfxcapture; extern const FFFilter ff_vsrc_gradients; extern const FFFilter ff_vsrc_haldclutsrc; extern const FFFilter ff_vsrc_life; diff --git a/libavfilter/version.h b/libavfilter/version.h index ba8a6fdab2..77f38cb9b4 100644 --- a/libavfilter/version.h +++ b/libavfilter/version.h @@ -31,7 +31,7 @@ #include "version_major.h" -#define LIBAVFILTER_VERSION_MINOR 8 +#define LIBAVFILTER_VERSION_MINOR 9 #define LIBAVFILTER_VERSION_MICRO 100 diff --git a/libavfilter/vsrc_gfxcapture.c b/libavfilter/vsrc_gfxcapture.c new file mode 100644 index 0000000000..19d8a8a52c --- /dev/null +++ b/libavfilter/vsrc_gfxcapture.c @@ -0,0 +1,102 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" + +#include "libavutil/internal.h" +#include "libavutil/opt.h" +#include "avfilter.h" +#include "filters.h" + +#include "vsrc_gfxcapture.h" + +#define OFFSET(x) offsetof(GfxCaptureContext, x) +#define FLAGS AV_OPT_FLAG_VIDEO_PARAM|AV_OPT_FLAG_FILTERING_PARAM +static const AVOption gfxcapture_options[] = { + { "window_title", "ECMAScript regular expression to match against the window title. " + "Supports PCRE style (?i) prefix for case-insensitivity.", + OFFSET(window_text), AV_OPT_TYPE_STRING, { .str = NULL }, 0, INT_MAX, FLAGS }, + { "window_class", "as window_title, but against the window class", + OFFSET(window_class), AV_OPT_TYPE_STRING, { .str = NULL }, 0, INT_MAX, FLAGS }, + { "window_exe", "as window_title, but against the windows executable name", + OFFSET(window_exe), AV_OPT_TYPE_STRING, { .str = NULL }, 0, INT_MAX, FLAGS }, + { "monitor_idx", "index of the monitor to capture", OFFSET(monitor_idx), AV_OPT_TYPE_INT, { .i64 = GFX_MONITOR_IDX_DEFAULT }, GFX_MONITOR_IDX_DEFAULT, INT_MAX, FLAGS, .unit = "monitor_idx" }, + { "window", "derive from selected window", 0, AV_OPT_TYPE_CONST, { .i64 = GFX_MONITOR_IDX_WINDOW }, 0, 0, FLAGS, .unit = "monitor_idx" }, + { "capture_cursor", "capture mouse cursor", OFFSET(capture_cursor), AV_OPT_TYPE_BOOL, { .i64 = 1 }, 0, 1, FLAGS }, + { "capture_border", "capture full window border", OFFSET(capture_border), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS }, + { "display_border", "display yellow border around captured window", + OFFSET(display_border), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS }, + { "max_framerate", "set maximum capture frame rate", OFFSET(frame_rate), AV_OPT_TYPE_VIDEO_RATE, { .str = "60" }, 0.001, 1000, FLAGS }, + { "hwnd", "pre-existing HWND handle", OFFSET(user_hwnd), AV_OPT_TYPE_UINT64, { .i64 = 0 }, 0, UINT64_MAX, FLAGS }, + { "hmonitor", "pre-existing HMONITOR handle", OFFSET(user_hmonitor), AV_OPT_TYPE_UINT64, { .i64 = 0 }, 0, UINT64_MAX, FLAGS }, + { "width", "force width of the output frames, negative values round down the width to the nearest multiple of that number", + OFFSET(canvas_width), AV_OPT_TYPE_INT, { .i64 = 0 }, INT_MIN, INT_MAX, FLAGS }, + { "height", "force height of the output frames, negative values round down the height to the nearest multiple of that number", + OFFSET(canvas_height), AV_OPT_TYPE_INT, { .i64 = 0 }, INT_MIN, INT_MAX, FLAGS }, + { "crop_left", "number of pixels to crop from the left of the captured area", + OFFSET(crop_left), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS }, + { "crop_top", "number of pixels to crop from the top of the captured area", + OFFSET(crop_top), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS }, + { "crop_right", "number of pixels to crop from the right of the captured area", + OFFSET(crop_right), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS }, + { "crop_bottom", "number of pixels to crop from the bottom of the captured area", + OFFSET(crop_bottom), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, INT_MAX, FLAGS }, + { "premultiplied", "return premultiplied alpha frames", OFFSET(premult_alpha), AV_OPT_TYPE_BOOL, { .i64 = 0 }, 0, 1, FLAGS }, + { "resize_mode", "capture source resize behavior", OFFSET(resize_mode), AV_OPT_TYPE_INT, { .i64 = GFX_RESIZE_CROP }, 0, GFX_RESIZE_NB - 1, FLAGS, .unit = "resize_mode" }, + { "crop", "crop or add black bars into frame", 0, AV_OPT_TYPE_CONST, { .i64 = GFX_RESIZE_CROP }, 0, 0, FLAGS, .unit = "resize_mode" }, + { "scale", "scale source to fit initial size", 0, AV_OPT_TYPE_CONST, { .i64 = GFX_RESIZE_SCALE }, 0, 0, FLAGS, .unit = "resize_mode" }, + { "scale_aspect", "scale source to fit initial size while preserving aspect ratio", + 0, AV_OPT_TYPE_CONST, { .i64 = GFX_RESIZE_SCALE_ASPECT }, 0, 0, FLAGS, .unit = "resize_mode" }, + { "scale_mode", "scaling algorithm", OFFSET(scale_mode), AV_OPT_TYPE_INT, { .i64 = GFX_SCALE_BILINEAR }, 0, GFX_SCALE_NB - 1, FLAGS, .unit = "scale_mode" }, + { "point", "use point scaling", 0, AV_OPT_TYPE_CONST, { .i64 = GFX_SCALE_POINT }, 0, 0, FLAGS, .unit = "scale_mode" }, + { "bilinear", "use bilinear scaling", 0, AV_OPT_TYPE_CONST, { .i64 = GFX_SCALE_BILINEAR }, 0, 0, FLAGS, .unit = "scale_mode" }, + { "bicubic", "use bicubic scaling", 0, AV_OPT_TYPE_CONST, { .i64 = GFX_SCALE_BICUBIC }, 0, 0, FLAGS, .unit = "scale_mode" }, + { "output_fmt", "desired output format", OFFSET(out_fmt), AV_OPT_TYPE_INT, { .i64 = AV_PIX_FMT_BGRA }, 0, INT_MAX, FLAGS, .unit = "output_fmt" }, + { "8bit", "output default 8 Bit format", 0, AV_OPT_TYPE_CONST, { .i64 = AV_PIX_FMT_BGRA }, 0, 0, FLAGS, .unit = "output_fmt" }, + { "bgra", "output 8 Bit BGRA", 0, AV_OPT_TYPE_CONST, { .i64 = AV_PIX_FMT_BGRA }, 0, 0, FLAGS, .unit = "output_fmt" }, + { "10bit", "output default 10 Bit format", 0, AV_OPT_TYPE_CONST, { .i64 = AV_PIX_FMT_X2BGR10 }, 0, 0, FLAGS, .unit = "output_fmt" }, + { "x2bgr10", "output 10 Bit X2BGR10", 0, AV_OPT_TYPE_CONST, { .i64 = AV_PIX_FMT_X2BGR10 }, 0, 0, FLAGS, .unit = "output_fmt" }, + { "16bit", "output default 16 Bit format", 0, AV_OPT_TYPE_CONST, { .i64 = AV_PIX_FMT_RGBAF16 }, 0, 0, FLAGS, .unit = "output_fmt" }, + { "rgbaf16", "output 16 Bit RGBAF16", 0, AV_OPT_TYPE_CONST, { .i64 = AV_PIX_FMT_RGBAF16 }, 0, 0, FLAGS, .unit = "output_fmt" }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(gfxcapture); + +static const AVFilterPad gfxcapture_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = ff_gfxcapture_config_props, + }, +}; + +const FFFilter ff_vsrc_gfxcapture = { + .p.name = "gfxcapture", + .p.description = NULL_IF_CONFIG_SMALL("Capture graphics/screen content as a video source"), + .p.priv_class = &gfxcapture_class, + .p.inputs = NULL, + .p.flags = AVFILTER_FLAG_HWDEVICE, + .priv_size = sizeof(GfxCaptureContext), + .init = ff_gfxcapture_init, + .uninit = ff_gfxcapture_uninit, + FILTER_OUTPUTS(gfxcapture_outputs), + FILTER_SINGLE_PIXFMT(AV_PIX_FMT_D3D11), + .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, + .activate = ff_gfxcapture_activate, +}; diff --git a/libavfilter/vsrc_gfxcapture.h b/libavfilter/vsrc_gfxcapture.h new file mode 100644 index 0000000000..617f48de22 --- /dev/null +++ b/libavfilter/vsrc_gfxcapture.h @@ -0,0 +1,81 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVFILTER_VSRC_GFXCAPTURE_H +#define AVFILTER_VSRC_GFXCAPTURE_H + +typedef struct GfxCaptureContextCpp GfxCaptureContextCpp; + +enum GfxResizeMode { + GFX_RESIZE_CROP = 0, + GFX_RESIZE_SCALE, + GFX_RESIZE_SCALE_ASPECT, + GFX_RESIZE_NB +}; + +enum GfxScaleMode { + GFX_SCALE_POINT = 0, + GFX_SCALE_BILINEAR, + GFX_SCALE_BICUBIC, + GFX_SCALE_NB +}; + +enum GfxMonitorIdx { + GFX_MONITOR_IDX_WINDOW = -1, + GFX_MONITOR_IDX_DEFAULT = -2 +}; + +typedef struct GfxCaptureContext { + const AVClass *avclass; + + GfxCaptureContextCpp *ctx; + + const char *window_text; + const char *window_class; + const char *window_exe; + int monitor_idx; + + uint64_t user_hwnd; + uint64_t user_hmonitor; + + int capture_cursor; + int capture_border; + int display_border; + AVRational frame_rate; + int canvas_width, canvas_height; + int crop_left, crop_top, crop_right, crop_bottom; + int out_fmt; + int resize_mode; + int scale_mode; + int premult_alpha; +} GfxCaptureContext; + +#ifdef __cplusplus +#define NOEXCEPT noexcept +#else +#define NOEXCEPT +#endif + +av_cold int ff_gfxcapture_init(AVFilterContext *avctx) NOEXCEPT; +av_cold void ff_gfxcapture_uninit(AVFilterContext *avctx) NOEXCEPT; +int ff_gfxcapture_activate(AVFilterContext *avctx) NOEXCEPT; +int ff_gfxcapture_config_props(AVFilterLink *outlink) NOEXCEPT; + +#undef NOEXCEPT + +#endif /* AVFILTER_VSRC_GFXCAPTURE_H */ diff --git a/libavfilter/vsrc_gfxcapture_shader.h b/libavfilter/vsrc_gfxcapture_shader.h new file mode 100644 index 0000000000..3fe9d5ec0d --- /dev/null +++ b/libavfilter/vsrc_gfxcapture_shader.h @@ -0,0 +1,126 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVFILTER_VSRC_GFXCAPTURE_SHADER_H +#define AVFILTER_VSRC_GFXCAPTURE_SHADER_H + +#define HLSL_SHADER(shader) #shader + +static const char render_shader_src[] = HLSL_SHADER( + struct VSOut { + float4 pos : SV_Position; + float2 uv : TEXCOORD0; + }; + + cbuffer cb : register(b0) { + float2 tS; + float2 dS; + float2 uvMin; + float2 uvMax; + uint to_unpremult; + uint to_srgb; + uint2 pad; + }; + + Texture2D t0 : register(t0); + SamplerState s0 : register(s0); + + VSOut main_vs(uint id : SV_VertexID) { + VSOut o; + o.pos = float4(id == 2 ? 3.0 : -1.0, id == 1 ? 3.0 : -1.0, 0, 1); + o.uv = lerp(uvMin, uvMax, float2((o.pos.x + 1) * 0.5, 1 - (o.pos.y + 1) * 0.5)); + return o; + } + + float4 cubic(float v) { + float4 n = float4(1.0, 2.0, 3.0, 4.0) - v; + float4 s = n * n * n; + float x = s.x; + float y = s.y - 4.0 * s.x; + float z = s.z - 4.0 * s.y + 6.0 * s.x; + float w = 6.0 - x - y - z; + return float4(x, y, z, w) * (1.0 / 6.0); + } + + float4 texBicubic(Texture2D t, SamplerState ss, float2 uv) { + float2 itS = 1.0 / tS; + + float2 tc = uv * tS - 0.5; + float2 fxy = frac(tc); + tc -= fxy; + + float4 xc = cubic(fxy.x); + float4 yc = cubic(fxy.y); + + float4 s = float4(xc.xz + xc.yw, yc.xz + yc.yw); + float4 o = tc.xxyy + (float2(-0.5, 1.5)).xyxy + float4(xc.yw, yc.yw) / s; + o *= itS.xxyy; + + float4 s0 = t.Sample(ss, o.xz); + float4 s1 = t.Sample(ss, o.yz); + float4 s2 = t.Sample(ss, o.xw); + float4 s3 = t.Sample(ss, o.yw); + + float sx = s.x / (s.x + s.y); + float sy = s.z / (s.z + s.w); + + return lerp(lerp(s3, s2, sx), lerp(s1, s0, sx), sy); + } + + float4 unpremultiply(float4 c) { + if (c.a < 1e-6) + return float4(0.0, 0.0, 0.0, 0.0); + return float4(c.rgb / c.a, c.a); + } + + float4 premultiply(float4 c) { + return float4(c.rgb * c.a, c.a); + } + + float3 linear_to_srgb(float3 c) { + c = max(c, 0.0); + float3 lo = 12.92 * c; + float3 hi = 1.055 * pow(c, 1.0 / 2.4) - 0.055; + return saturate(lerp(hi, lo, step(c, 0.0031308))); + } + + float4 fix_color(float4 c) { + if (to_unpremult || to_srgb) + c = unpremultiply(c); + if (to_srgb) { + c.rgb = linear_to_srgb(c.rgb); + if (!to_unpremult) + c = premultiply(c); + } + return c; + } + + float4 main_ps(VSOut i) : SV_Target { + return fix_color(t0.Sample(s0, i.uv)); + } + + float4 main_ps_bicubic(VSOut i) : SV_Target { + if (all(tS == dS)) + return main_ps(i); + return fix_color(texBicubic(t0, s0, i.uv)); + } +); + +#undef HLSL_SHADER + +#endif /* AVFILTER_VSRC_GFXCAPTURE_SHADER_H */ diff --git a/libavfilter/vsrc_gfxcapture_winrt.cpp b/libavfilter/vsrc_gfxcapture_winrt.cpp new file mode 100644 index 0000000000..42977efcc5 --- /dev/null +++ b/libavfilter/vsrc_gfxcapture_winrt.cpp @@ -0,0 +1,1551 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +extern "C" { +#include "config.h" +} + +#if !defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0A00 +#undef _WIN32_WINNT +#define _WIN32_WINNT 0x0A00 +#endif + +#define WINDOWS_FOUNDATION_UNIVERSALAPICONTRACT_VERSION 0x130000 + +// work around bug in mingw double-defining IReference<unsigned char> (BYTE == boolean) +#define ____FIReference_1_boolean_INTERFACE_DEFINED__ + +#include <windows.h> +#include <initguid.h> +#include <wrl.h> +#include <roapi.h> +#include <dwmapi.h> +#include <d3d11.h> +#include <d3dcompiler.h> +#include <dispatcherqueue.h> +#include <windows.foundation.h> +#include <windows.graphics.capture.h> +#include <windows.graphics.capture.interop.h> +#include <windows.graphics.directx.direct3d11.h> +#if HAVE_IDIRECT3DDXGIINTERFACEACCESS +#include <windows.graphics.directx.direct3d11.interop.h> +#endif + +extern "C" { +#include "libavutil/avassert.h" +#include "libavutil/internal.h" +#include "libavutil/mem.h" +#include "libavutil/opt.h" +#include "libavutil/time.h" +#include "libavutil/thread.h" +#include "libavutil/pixdesc.h" +#include "libavutil/hwcontext.h" +#include "libavutil/hwcontext_d3d11va.h" +#include "avfilter.h" +#include "filters.h" +#include "video.h" + +#include "vsrc_gfxcapture.h" +} + +#include <atomic> +#include <cinttypes> +#include <condition_variable> +#include <functional> +#include <memory> +#include <mutex> +#include <regex> +#include <string> +#include <type_traits> + +#include "vsrc_gfxcapture_winrt.h" +#include "vsrc_gfxcapture_shader.h" + +using namespace ABI::Windows::System; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Graphics::Capture; +using namespace ABI::Windows::Graphics::DirectX::Direct3D11; +using namespace Windows::Graphics::DirectX::Direct3D11; +using Microsoft::WRL::ComPtr; +using ABI::Windows::Graphics::SizeInt32; +using ABI::Windows::Foundation::TimeSpan; +using ABI::Windows::Graphics::DirectX::DirectXPixelFormat; + +#define TIMESPAN_RES 10000000 +#define TIMESPAN_RES64 INT64_C(10000000) + +#define CAPTURE_POOL_SIZE 2 + +enum { + WM_WGC_THREAD_SHUTDOWN = WM_APP + 1 +}; + +#define CCTX(ctx) static_cast<GfxCaptureContext*>(ctx) + +typedef struct GfxCaptureFunctions { + hmodule_ptr_t graphicscapture_handle; + + hmodule_ptr_t combase_handle; + HRESULT (WINAPI *RoInitialize)(RO_INIT_TYPE initType); + void (WINAPI *RoUninitialize)(void); + HRESULT (WINAPI *RoGetActivationFactory)(HSTRING activatableClassId, REFIID iid, void **factory); + HRESULT (WINAPI *WindowsCreateStringReference)(PCWSTR sourceString, UINT32 length, HSTRING_HEADER *hstringHeader, HSTRING *string); + + hmodule_ptr_t dwmapi_handle; + HRESULT (WINAPI *DwmGetWindowAttribute)(HWND hwnd, DWORD dwAttribute, PVOID pvAttribute, DWORD cbAttribute); + + hmodule_ptr_t d3d11_handle; + HRESULT (WINAPI *CreateDirect3D11DeviceFromDXGIDevice)(IDXGIDevice *dxgiDevice, IInspectable **graphicsDevice); + + hmodule_ptr_t coremsg_handle; + HRESULT (WINAPI *CreateDispatcherQueueController)(DispatcherQueueOptions options, PDISPATCHERQUEUECONTROLLER *dispatcherQueueController); + + hmodule_ptr_t user32_handle; + DPI_AWARENESS_CONTEXT (WINAPI *SetThreadDpiAwarenessContext)(DPI_AWARENESS_CONTEXT dpiContext); + + hmodule_ptr_t d3dcompiler_handle; + HRESULT (WINAPI *D3DCompile)(LPCVOID pSrcData, SIZE_T SrcDataSize, LPCSTR pSourceName, const D3D10_SHADER_MACRO *pDefines, ID3DInclude *pInclude, + LPCSTR pEntrypoint, LPCSTR pTarget, UINT Flags1, UINT Flags2, ID3DBlob **ppCode, ID3DBlob **ppErrorMsgs); +} GfxCaptureFunctions; + +// This struct contains all data handled by the capture thread +struct GfxCaptureContextWgc { + ComPtr<IDispatcherQueueController> dispatcher_queue_controller; + ComPtr<IDispatcherQueue> dispatcher_queue; + + ComPtr<IGraphicsCaptureItem> capture_item; + ComPtr<IDirect3DDevice> d3d_device; + ComPtr<IDirect3D11CaptureFramePool> frame_pool; + ComPtr<IGraphicsCaptureSession> capture_session; + + EventRegistrationToken frame_arrived_token { 0 }; + EventRegistrationToken closed_token { 0 }; + + std::mutex frame_arrived_mutex; + std::condition_variable frame_arrived_cond; + std::atomic<bool> window_closed { false }; + std::atomic<uint64_t> frame_seq { 0 }; + + SizeInt32 cap_size { 0, 0 }; + RECT client_area_offsets { 0, 0, 0, 0 }; +}; + +struct GfxCaptureContextD3D { + ComPtr<ID3D11VertexShader> vertex_shader; + ComPtr<ID3D11PixelShader> pixel_shader; + ComPtr<ID3D11SamplerState> sampler_state; + ComPtr<ID3D11Buffer> shader_cb; + ComPtr<ID3D11DeviceContext> deferred_ctx; +}; + +struct GfxCaptureContextCpp { + GfxCaptureFunctions fn; + std::unique_ptr<GfxCaptureContextWgc> wgc; + std::unique_ptr<GfxCaptureContextD3D> d3d; + + pthread_t wgc_thread; + bool wgc_thread_created { false }; + DWORD wgc_thread_id { 0 }; + std::mutex wgc_thread_init_mutex; + std::condition_variable wgc_thread_init_cond; + volatile int wgc_thread_init_res { INT_MAX }; + std::recursive_mutex wgc_thread_uninit_mutex; + + HWND capture_hwnd { nullptr }; + HMONITOR capture_hmonitor { nullptr }; + + AVBufferRef *device_ref { nullptr }; + AVHWDeviceContext *device_ctx { nullptr }; + AVD3D11VADeviceContext *device_hwctx { nullptr }; + + AVBufferRef *frames_ref { nullptr }; + AVHWFramesContext *frames_ctx { nullptr }; + AVD3D11VAFramesContext *frames_hwctx { nullptr }; + + int64_t first_pts { 0 }; + int64_t last_pts { 0 }; +}; + +template <typename T> +static HRESULT get_activation_factory(GfxCaptureContextCpp *ctx, PCWSTR clsid, T** factory) { + HSTRING_HEADER hsheader = { 0 }; + HSTRING hs = NULL; + + HRESULT hr = ctx->fn.WindowsCreateStringReference(clsid, (UINT32)wcslen(clsid), &hsheader, &hs); + if (FAILED(hr)) + return hr; + + return ctx->fn.RoGetActivationFactory(hs, IID_PPV_ARGS(factory)); +} + +#define CHECK_HR(fcall, action) \ + do { \ + HRESULT fhr = fcall; \ + if (FAILED(fhr)) { \ + av_log(avctx, AV_LOG_ERROR, #fcall " failed: 0x%08lX\n", fhr); \ + action; \ + } \ + } while (0) +#define CHECK_HR_RET(...) CHECK_HR((__VA_ARGS__), return AVERROR_EXTERNAL) +#define CHECK_HR_FAIL(...) CHECK_HR((__VA_ARGS__), ret = AVERROR_EXTERNAL; goto fail) +#define CHECK_HR_LOG(...) CHECK_HR((__VA_ARGS__), (void)0) + +/**************************************************** + * Windows Graphics Capture Worker Thread * + * All wgc_* functions must run only on WGC thread! * + ****************************************************/ + +static void wgc_frame_arrived_handler(const std::unique_ptr<GfxCaptureContextWgc> &wgctx) { + wgctx->frame_seq.fetch_add(1, std::memory_order_release); + wgctx->frame_arrived_cond.notify_one(); +} + +static void wgc_closed_handler(const std::unique_ptr<GfxCaptureContextWgc> &wgctx) { + wgctx->window_closed.store(true, std::memory_order_release); + wgctx->frame_arrived_cond.notify_one(); +} + +static void wgc_stop_capture_session(AVFilterContext *avctx) noexcept +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + std::unique_ptr<GfxCaptureContextWgc> &wgctx = ctx->wgc; + + if (wgctx->closed_token.value && wgctx->capture_item) { + CHECK_HR_LOG(wgctx->capture_item->remove_Closed(wgctx->closed_token)); + wgctx->closed_token.value = 0; + } + + if (wgctx->frame_arrived_token.value && wgctx->frame_pool) { + CHECK_HR_LOG(wgctx->frame_pool->remove_FrameArrived(wgctx->frame_arrived_token)); + wgctx->frame_arrived_token.value = 0; + } + + if (wgctx->capture_session) { + ComPtr<IClosable> closable; + if (SUCCEEDED(wgctx->capture_session.As(&closable))) { + closable->Close(); + } else { + av_log(avctx, AV_LOG_ERROR, "Failed to get capture session IClosable interface\n"); + } + } + + if (wgctx->frame_pool) { + ComPtr<IClosable> closable; + if (SUCCEEDED(wgctx->frame_pool.As(&closable))) { + CHECK_HR_LOG(closable->Close()); + } else { + av_log(avctx, AV_LOG_ERROR, "Failed to get frame pool IClosable interface\n"); + } + } +} + +static int wgc_calculate_client_area(AVFilterContext *avctx) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + std::unique_ptr<GfxCaptureContextWgc> &wgctx = ctx->wgc; + + if (!ctx->capture_hwnd) { + wgctx->client_area_offsets.left = 0; + wgctx->client_area_offsets.top = 0; + wgctx->client_area_offsets.right = 0; + wgctx->client_area_offsets.bottom = 0; + return 0; + } + + RECT client_rect = {}; + RECT frame_bounds = {}; + RECT window_rect = {}; + + if (IsIconic(ctx->capture_hwnd)) { + av_log(avctx, AV_LOG_VERBOSE, "Capture window is iconic, no client area\n"); + return 0; + } + + if (!GetClientRect(ctx->capture_hwnd, &client_rect)) { + av_log(avctx, AV_LOG_ERROR, "GetClientRect failed\n"); + return AVERROR_EXTERNAL; + } + + if (!MapWindowPoints(ctx->capture_hwnd, nullptr, (POINT*)&client_rect, 2)) { + av_log(avctx, AV_LOG_ERROR, "MapWindowPoints failed\n"); + return AVERROR_EXTERNAL; + } + + if (FAILED(ctx->fn.DwmGetWindowAttribute(ctx->capture_hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &frame_bounds, sizeof(window_rect)))) + av_log(avctx, AV_LOG_DEBUG, "DwmGetWindowAttribute failed\n"); + + if (!GetWindowRect(ctx->capture_hwnd, &window_rect)) + av_log(avctx, AV_LOG_DEBUG, "GetWindowRect failed\n"); + + if (wgctx->cap_size.Width == frame_bounds.right - frame_bounds.left || + wgctx->cap_size.Height == frame_bounds.bottom - frame_bounds.top) { + av_log(avctx, AV_LOG_DEBUG, "Using window rect from DWMWA_EXTENDED_FRAME_BOUNDS\n"); + } else if (wgctx->cap_size.Width == window_rect.right - window_rect.left || + wgctx->cap_size.Height == window_rect.bottom - window_rect.top) { + av_log(avctx, AV_LOG_DEBUG, "Using window rect from GetWindowRect\n"); + frame_bounds = window_rect; + } else { + if ((frame_bounds.top == frame_bounds.bottom || frame_bounds.left == frame_bounds.right) && + (window_rect.top == window_rect.bottom || window_rect.left == window_rect.right)) + { + av_log(avctx, AV_LOG_ERROR, "No valid window rect found\n"); + return AVERROR_EXTERNAL; + } + av_log(avctx, AV_LOG_VERBOSE, "Failed to get valid window rect, client area may be inaccurate\n"); + return 0; + } + + wgctx->client_area_offsets.left = FFMAX(client_rect.left - frame_bounds.left, 0); + wgctx->client_area_offsets.top = FFMAX(client_rect.top - frame_bounds.top, 0); + wgctx->client_area_offsets.right = FFMAX(frame_bounds.right - client_rect.right, 0); + wgctx->client_area_offsets.bottom = FFMAX(frame_bounds.bottom - client_rect.bottom, 0); + + av_log(avctx, AV_LOG_DEBUG, "Client area offsets: left=%ld top=%ld right=%ld bottom=%ld\n", + wgctx->client_area_offsets.left, wgctx->client_area_offsets.top, + wgctx->client_area_offsets.right, wgctx->client_area_offsets.bottom); + + return 0; +} + +static int wgc_setup_gfxcapture_session(AVFilterContext *avctx) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + std::unique_ptr<GfxCaptureContextWgc> &wgctx = ctx->wgc; + int ret; + + ComPtr<IDirect3D11CaptureFramePoolStatics> frame_pool_statics; + ComPtr<ID3D11Device> d3d11_device = ctx->device_hwctx->device; + ComPtr<ID3D10Multithread> d3d10_multithread; + ComPtr<IDXGIDevice> dxgi_device; + ComPtr<IGraphicsCaptureSession2> session2; + ComPtr<IGraphicsCaptureSession3> session3; + ComPtr<IGraphicsCaptureSession5> session5; + + DirectXPixelFormat fmt = DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized; + if (cctx->out_fmt != AV_PIX_FMT_BGRA) + fmt = DirectXPixelFormat::DirectXPixelFormat_R16G16B16A16Float; + + CHECK_HR_RET(wgctx->capture_item->get_Size(&wgctx->cap_size)); + ret = wgc_calculate_client_area(avctx); + if (ret < 0) + return ret; + + CHECK_HR_RET(d3d11_device.As(&d3d10_multithread)); + d3d10_multithread->SetMultithreadProtected(TRUE); + + CHECK_HR_RET(d3d11_device.As(&dxgi_device)); + CHECK_HR_RET(ctx->fn.CreateDirect3D11DeviceFromDXGIDevice(dxgi_device.Get(), &wgctx->d3d_device)); + + CHECK_HR_RET(get_activation_factory<IDirect3D11CaptureFramePoolStatics>(ctx, RuntimeClass_Windows_Graphics_Capture_Direct3D11CaptureFramePool, &frame_pool_statics)); + CHECK_HR_RET(frame_pool_statics->Create(wgctx->d3d_device.Get(), fmt, CAPTURE_POOL_SIZE, wgctx->cap_size, &wgctx->frame_pool)); + CHECK_HR_RET(wgctx->frame_pool->CreateCaptureSession(wgctx->capture_item.Get(), &wgctx->capture_session)); + + if (SUCCEEDED(wgctx->capture_session.As(&session2))) { + if (FAILED(session2->put_IsCursorCaptureEnabled(cctx->capture_cursor))) { + av_log(avctx, AV_LOG_WARNING, "Failed setting cursor capture mode\n"); + } + } else { + av_log(avctx, AV_LOG_WARNING, "Cursor capture unavailable\n"); + } + + if (SUCCEEDED(wgctx->capture_session.As(&session3))) { + // this one is weird, it can return failure but still work + if (FAILED(session3->put_IsBorderRequired(cctx->display_border))) { + av_log(avctx, AV_LOG_WARNING, "Failed setting border drawing mode\n"); + } + } else { + av_log(avctx, AV_LOG_WARNING, "Disabling border drawing unavailable\n"); + } + + if (SUCCEEDED(wgctx->capture_session.As(&session5))) { + TimeSpan ivl = { av_rescale_q(1, av_inv_q(cctx->frame_rate), AVRational{1, TIMESPAN_RES}) }; + if (FAILED(session5->put_MinUpdateInterval(ivl))) { + av_log(avctx, AV_LOG_WARNING, "Failed setting minimum update interval, framerate may be limited\n"); + } + } else { + av_log(avctx, AV_LOG_WARNING, "Setting minimum update interval unavailable, framerate may be limited\n"); + } + + wgctx->window_closed = 0; + + CHECK_HR_RET(wgctx->capture_item->add_Closed( + create_cb_handler<ITypedEventHandler<GraphicsCaptureItem*,IInspectable*>, IGraphicsCaptureItem*, IInspectable*>( + [avctx, ctx](auto, auto) { + av_log(avctx, AV_LOG_INFO, "Capture item closed\n"); + wgc_closed_handler(ctx->wgc); + return S_OK; + }).Get(), &wgctx->closed_token)); + + CHECK_HR_RET(wgctx->frame_pool->add_FrameArrived( + create_cb_handler<ITypedEventHandler<Direct3D11CaptureFramePool*,IInspectable*>, IDirect3D11CaptureFramePool*, IInspectable*>( + [avctx, ctx](auto, auto) { + av_log(avctx, AV_LOG_TRACE, "Frame arrived\n"); + wgc_frame_arrived_handler(ctx->wgc); + return S_OK; + }).Get(), &wgctx->frame_arrived_token)); + + return 0; +} + +static int wgc_setup_gfxcapture_capture(AVFilterContext *avctx) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + std::unique_ptr<GfxCaptureContextWgc> &wgctx = ctx->wgc; + HRESULT hr; + int ret; + + ComPtr<IGraphicsCaptureItemInterop> capture_item_interop; + CHECK_HR_RET(get_activation_factory<IGraphicsCaptureItemInterop>(ctx, RuntimeClass_Windows_Graphics_Capture_GraphicsCaptureItem, &capture_item_interop)); + + if (ctx->capture_hmonitor) { + hr = capture_item_interop->CreateForMonitor(ctx->capture_hmonitor, IID_PPV_ARGS(&wgctx->capture_item)); + if (FAILED(hr)) { + av_log(avctx, AV_LOG_ERROR, "Failed to setup graphics capture for monitor (0x%08lX)\n", hr); + return AVERROR_EXTERNAL; + } + } else if (ctx->capture_hwnd) { + hr = capture_item_interop->CreateForWindow(ctx->capture_hwnd, IID_PPV_ARGS(&wgctx->capture_item)); + if (FAILED(hr)) { + av_log(avctx, AV_LOG_ERROR, "Failed to setup graphics capture for window (0x%08lX)\n", hr); + return AVERROR_EXTERNAL; + } + } + + ret = wgc_setup_gfxcapture_session(avctx); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to setup graphics capture pool\n"); + return ret; + } + + hr = ctx->wgc->capture_session->StartCapture(); + if (FAILED(hr)) { + av_log(avctx, AV_LOG_ERROR, "Failed to start graphics capture session (0x%08lX)\n", hr); + return AVERROR_EXTERNAL; + } + + return 0; +} + +static int wgc_try_get_next_frame(AVFilterContext *avctx, ComPtr<IDirect3D11CaptureFrame> *capture_frame) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + std::unique_ptr<GfxCaptureContextWgc> &wgctx = ctx->wgc; + + ComPtr<IDirect3DSurface> capture_surface; + ComPtr<IDirect3DDxgiInterfaceAccess> dxgi_interface_access; + ComPtr<ID3D11Texture2D> frame_texture; + SizeInt32 frame_size = { 0, 0 }; + + CHECK_HR_RET(wgctx->frame_pool->TryGetNextFrame(capture_frame->ReleaseAndGetAddressOf())); + if (!capture_frame->Get()) + return AVERROR(EAGAIN); + + CHECK_HR_RET(capture_frame->Get()->get_ContentSize(&frame_size)); + if (frame_size.Width != wgctx->cap_size.Width || frame_size.Height != wgctx->cap_size.Height) { + av_log(avctx, AV_LOG_VERBOSE, "Capture size changed to %dx%d\n", frame_size.Width, frame_size.Height); + + DirectXPixelFormat fmt = DirectXPixelFormat::DirectXPixelFormat_B8G8R8A8UIntNormalized; + if (cctx->out_fmt != AV_PIX_FMT_BGRA) + fmt = DirectXPixelFormat::DirectXPixelFormat_R16G16B16A16Float; + + CHECK_HR_RET(wgctx->frame_pool->Recreate(wgctx->d3d_device.Get(), fmt, CAPTURE_POOL_SIZE, frame_size)); + wgctx->cap_size = frame_size; + + int ret = wgc_calculate_client_area(avctx); + if (ret < 0) + return ret; + } + + return 0; +} + +static int wgc_setup_winrt(AVFilterContext *avctx) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + std::unique_ptr<GfxCaptureContextWgc> &wgctx = ctx->wgc; + MSG msg; + + // pre-create the message-queue + PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE); + + DispatcherQueueOptions options = { 0 }; + options.dwSize = sizeof(DispatcherQueueOptions); + options.threadType = DISPATCHERQUEUE_THREAD_TYPE::DQTYPE_THREAD_CURRENT; + options.apartmentType = DISPATCHERQUEUE_THREAD_APARTMENTTYPE::DQTAT_COM_NONE; + + CHECK_HR_RET(ctx->fn.CreateDispatcherQueueController(options, &wgctx->dispatcher_queue_controller)); + CHECK_HR_RET(wgctx->dispatcher_queue_controller->get_DispatcherQueue(&wgctx->dispatcher_queue)); + + return 0; +} + +static void wgc_thread_uninit(AVFilterContext *avctx) noexcept +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + + wgc_stop_capture_session(avctx); + + ctx->wgc.reset(); + ctx->fn.RoUninitialize(); +} + +static int wgc_thread_init(AVFilterContext *avctx) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + HRESULT hr; + int ret; + + ctx->wgc = std::make_unique<GfxCaptureContextWgc>(); + + ctx->fn.SetThreadDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2); + + hr = ctx->fn.RoInitialize(RO_INIT_MULTITHREADED); + if (FAILED(hr)) { + av_log(avctx, AV_LOG_ERROR, "Failed to initialize WinRT\n"); + ctx->wgc.reset(); + return AVERROR_EXTERNAL; + } + + ret = wgc_setup_winrt(avctx); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to setup WinRT\n"); + goto fail; + } + + ret = wgc_setup_gfxcapture_capture(avctx); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to setup graphics capture\n"); + goto fail; + } + + return 0; + +fail: + wgc_thread_uninit(avctx); + return ret; +} + +static int wgc_thread_worker(AVFilterContext *avctx) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + std::unique_ptr<GfxCaptureContextWgc> &wgctx = ctx->wgc; + ComPtr<IAsyncAction> async; + MSG msg; + + av_log(avctx, AV_LOG_DEBUG, "Starting message loop\n"); + + while (BOOL res = GetMessage(&msg, NULL, 0, 0)) { + if (res == -1) { + av_log(avctx, AV_LOG_ERROR, "Failed to get message\n"); + return AVERROR(EIO); + } + + if (!msg.hwnd && msg.message == WM_WGC_THREAD_SHUTDOWN) { + av_log(avctx, AV_LOG_DEBUG, "Initializing WGC thread shutdown\n"); + if (FAILED(wgctx->dispatcher_queue_controller->ShutdownQueueAsync(&async))) { + av_log(avctx, AV_LOG_ERROR, "Failed to shutdown dispatcher queue\n"); + return AVERROR_EXTERNAL; + } + async->put_Completed(create_cb_handler<IAsyncActionCompletedHandler, IAsyncAction*, AsyncStatus>( + [avctx, ctx](auto, auto status) { + PostThreadMessage(ctx->wgc_thread_id, WM_QUIT, 0, 0); + av_log(avctx, AV_LOG_DEBUG, "WGC thread async shutdown completed: %d\n", (int)status); + return S_OK; + }).Get()); + continue; + } + + av_log(avctx, AV_LOG_TRACE, "Got message: %u\n", msg.message); + + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + if (!async) { + av_log(avctx, AV_LOG_ERROR, "WGC Thread message loop ended without proper shutdown\n"); + return AVERROR_EXTERNAL; + } + + av_log(avctx, AV_LOG_DEBUG, "Message loop ended\n"); + + return msg.wParam; +} + +static void *wgc_thread_entry(void *arg) noexcept +{ + AVFilterContext *avctx = static_cast<AVFilterContext*>(arg); + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + + { + static const char name_prefix[] = "wgc_winrt@0x"; + char thread_name[sizeof(name_prefix) + sizeof(void*) * 2]; + snprintf(thread_name, sizeof(thread_name), "%s%" PRIxPTR, name_prefix, (uintptr_t)avctx); + ff_thread_setname(thread_name); + + std::lock_guard init_lock(ctx->wgc_thread_init_mutex); + ctx->wgc_thread_id = GetCurrentThreadId(); + + try { + ctx->wgc_thread_init_res = wgc_thread_init(avctx); + } catch (const std::bad_alloc &) { + ctx->wgc_thread_init_res = AVERROR(ENOMEM); + } catch (const std::exception &e) { + av_log(avctx, AV_LOG_ERROR, "unhandled exception in WGC thread init: %s\n", e.what()); + ctx->wgc_thread_init_res = AVERROR_BUG; + } catch (...) { + av_log(avctx, AV_LOG_ERROR, "Unhandled exception in WGC thread init\n"); + ctx->wgc_thread_init_res = AVERROR_BUG; + } + + ctx->wgc_thread_init_cond.notify_all(); + if (ctx->wgc_thread_init_res < 0) + return (void*)(intptr_t)AVERROR(ENOSYS); + } + + int ret; + + try { + ret = wgc_thread_worker(avctx); + } catch (const std::bad_alloc &) { + ret = AVERROR(ENOMEM); + } catch (const std::exception &e) { + av_log(avctx, AV_LOG_ERROR, "unhandled exception in WGC thread worker: %s\n", e.what()); + ret = AVERROR_BUG; + } catch (...) { + av_log(avctx, AV_LOG_ERROR, "Unhandled exception in WGC thread worker\n"); + ret = AVERROR_BUG; + } + + std::lock_guard uninit_lock(ctx->wgc_thread_uninit_mutex); + wgc_thread_uninit(avctx); + + return (void*)(intptr_t)ret; +} + +/*********************************** + * WGC Thread Management Functions * + ***********************************/ + +static int stop_wgc_thread(AVFilterContext *avctx) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + int ret = 0; + + if (ctx->wgc_thread_created) { + if (ctx->wgc_thread_id && !PostThreadMessage(ctx->wgc_thread_id, WM_WGC_THREAD_SHUTDOWN, 0, 0)) + av_log(avctx, AV_LOG_ERROR, "Failed to post shutdown message to WGC thread\n"); + + void *wgc_res = nullptr; + pthread_join(ctx->wgc_thread, &wgc_res); + ret = (int)(intptr_t)wgc_res; + + ctx->wgc_thread_id = 0; + ctx->wgc_thread_created = false; + } + + return ret; +} + +static int start_wgc_thread(AVFilterContext *avctx) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + + if (ctx->wgc_thread_created || ctx->wgc_thread_id) { + av_log(avctx, AV_LOG_ERROR, "Double-creation of WGC thread\n"); + return AVERROR_BUG; + } + + std::unique_lock wgc_lock(ctx->wgc_thread_init_mutex); + ctx->wgc_thread_init_res = INT_MAX; + + int ret = pthread_create(&ctx->wgc_thread, nullptr, wgc_thread_entry, avctx); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to create WGC thread\n"); + return ret; + } + + ctx->wgc_thread_created = true; + + if (!ctx->wgc_thread_init_cond.wait_for(wgc_lock, std::chrono::seconds(1), [&]() { + return ctx->wgc_thread_init_res != INT_MAX; + })) { + av_log(avctx, AV_LOG_ERROR, "WGC thread init timed out\n"); + return AVERROR(ETIMEDOUT); + } + + return ctx->wgc_thread_init_res; +} + +template <typename F> +static int run_on_wgc_thread(AVFilterContext *avctx, F &&cb) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + std::unique_ptr<GfxCaptureContextWgc> &wgctx = ctx->wgc; + + std::lock_guard uninit_lock(ctx->wgc_thread_uninit_mutex); + if (!wgctx) { + av_log(avctx, AV_LOG_ERROR, "WGC thread not initialized\n"); + return AVERROR(ENOSYS); + } + + struct CBData { + std::mutex mutex; + std::condition_variable cond; + std::atomic<bool> done { false }; + std::atomic<bool> cancel { false }; + int ret = AVERROR_BUG; + }; + auto cbdata = std::make_shared<CBData>(); + + std::unique_lock cblock(cbdata->mutex); + + boolean res = 0; + CHECK_HR_RET(wgctx->dispatcher_queue->TryEnqueue( + create_cb_handler<IDispatcherQueueHandler>( + [cb = std::forward<F>(cb), cbdata]() { + { + std::lock_guard lock(cbdata->mutex); + if (cbdata->cancel.load(std::memory_order_acquire)) + return S_OK; + + try { + cbdata->ret = cb(); + } catch (const std::bad_alloc &) { + cbdata->ret = AVERROR(ENOMEM); + } catch (...) { + cbdata->ret = AVERROR_BUG; + } + + cbdata->done.store(true, std::memory_order_release); + } + + cbdata->cond.notify_one(); + return S_OK; + }).Get(), &res)); + if (!res) { + av_log(avctx, AV_LOG_ERROR, "Failed to enqueue WGC thread callback\n"); + return AVERROR_EXTERNAL; + } + + if (!cbdata->cond.wait_for(cblock, std::chrono::seconds(1), [&]() { return cbdata->done.load(std::memory_order_acquire); })) { + cbdata->cancel.store(true, std::memory_order_release); + av_log(avctx, AV_LOG_ERROR, "WGC thread callback timed out\n"); + return AVERROR(ETIMEDOUT); + } + + return cbdata->ret; +} + +/******************************* + * Standard AVFilter functions * + *******************************/ + +static int build_regex(AVFilterContext *avctx, const char *pattern, std::regex *out) +{ + if (!pattern) + return 0; + + std::string pat(pattern); + + auto flags = std::regex::ECMAScript | std::regex::optimize; + if (pat.rfind("(?i)", 0) == 0 || pat.rfind("(?I)", 0) == 0) { + pat.erase(0, 4); + flags |= std::regex::icase; + } else if(pat.rfind("(?c)", 0) == 0 || pat.rfind("(?C)", 0) == 0) { + pat.erase(0, 4); + } + + try { + *out = std::regex(pat, flags); + } catch (const std::regex_error &e) { + av_log(avctx, AV_LOG_ERROR, "Failed to compile regex '%s': %s\n", pat.c_str(), e.what()); + return AVERROR(EINVAL); + } + + av_log(avctx, AV_LOG_DEBUG, "Built regex: %s\n", pattern); + + return 0; +} + +static int wstring_to_utf8(const wchar_t *in, std::string *out) +{ + int utf8size = WideCharToMultiByte(CP_UTF8, 0, in, -1, nullptr, 0, nullptr, nullptr); + if (utf8size <= 0) + return AVERROR(EINVAL); + + // over-writing std::string by one is valid in C++17 according to 27.4.3.6 if and only if it's overwritten with 0 + out->resize(utf8size - 1); + + if (WideCharToMultiByte(CP_UTF8, 0, in, -1, out->data(), utf8size, nullptr, nullptr) != utf8size) + return AVERROR_EXTERNAL; + + return 0; +} + +static int get_window_exe_name(HWND hwnd, std::string *out) +{ + out->clear(); + + DWORD pid = 0; + if (!GetWindowThreadProcessId(hwnd, &pid)) + return AVERROR(ENOENT); + + handle_ptr_t proc(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, pid)); + if (!proc) + return AVERROR(EACCES); + + std::wstring image_name; + DWORD image_name_size = 512; + + for (;;) { + DWORD len = image_name_size; + image_name.resize(len); + if (QueryFullProcessImageNameW(proc.get(), 0, image_name.data(), &len)) { + image_name.resize(len); + break; + } + if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) { + image_name_size *= 2; + continue; + } + return AVERROR_EXTERNAL; + } + + if (image_name.empty()) + return AVERROR_EXTERNAL; + + const wchar_t *base = image_name.c_str(); + size_t pos = image_name.find_last_of(L"\\/"); + if (pos != std::string::npos) + base += pos + 1; + + return wstring_to_utf8(base, out); +} + +static int find_capture_source(AVFilterContext *avctx) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + int cur_idx = 0; + + ctx->capture_hwnd = NULL; + ctx->capture_hmonitor = NULL; + + if (cctx->user_hmonitor) { + ctx->capture_hmonitor = (HMONITOR)(uintptr_t)cctx->user_hmonitor; + return 0; + } else if (cctx->user_hwnd) { + ctx->capture_hwnd = (HWND)(uintptr_t)cctx->user_hwnd; + return 0; + } else if (cctx->monitor_idx >= 0) { + auto cb = make_win32_callback([&](HMONITOR hmonitor, HDC, LPRECT) { + if (cur_idx++ == cctx->monitor_idx) { + av_log(avctx, AV_LOG_DEBUG, "Found capture monitor: %d\n", cctx->monitor_idx); + ctx->capture_hmonitor = hmonitor; + return FALSE; + } + return TRUE; + }); + if (EnumDisplayMonitors(NULL, NULL, cb->proc, cb->lparam) || !ctx->capture_hmonitor) + return AVERROR(ENOENT); + return 0; + } else if (cctx->window_text || cctx->window_class || cctx->window_exe) { + std::regex text_regex; + if (build_regex(avctx, cctx->window_text, &text_regex) < 0) + return AVERROR(EINVAL); + + std::regex class_regex; + if (build_regex(avctx, cctx->window_class, &class_regex) < 0) + return AVERROR(EINVAL); + + std::regex exe_regex; + if (build_regex(avctx, cctx->window_exe, &exe_regex) < 0) + return AVERROR(EINVAL); + + std::string window_text; + std::wstring window_text_w; + std::string window_class; + std::wstring window_class_w; + std::string window_exe; + auto cb = make_win32_callback([&](HWND hwnd) { + RECT r = { 0 }; + if (!GetWindowRect(hwnd, &r) || r.right <= r.left || r.bottom <= r.top || !IsWindowVisible(hwnd)) + return TRUE; + + window_text_w.resize(GetWindowTextLengthW(hwnd) + 1); + int len = GetWindowTextW(hwnd, window_text_w.data(), (int)window_text_w.size()); + if (len >= 0) { + window_text_w.resize(len); + if (wstring_to_utf8(window_text_w.c_str(), &window_text) < 0) + window_text.clear(); + } else { + window_text.clear(); + } + + window_class_w.resize(256); + len = GetClassNameW(hwnd, window_class_w.data(), (int)window_class_w.size()); + if (len >= 0) { + window_class_w.resize(len); + if (wstring_to_utf8(window_class_w.c_str(), &window_class) < 0) + window_class.clear(); + } else { + window_class.clear(); + } + + get_window_exe_name(hwnd, &window_exe); + + av_log(avctx, AV_LOG_TRACE, "Checking window: hwnd=%p text=%s class=%s exe=%s\n", + hwnd, window_text.c_str(), window_class.c_str(), window_exe.c_str()); + + if (cctx->window_text) { + if (window_text.empty() || !std::regex_search(window_text, text_regex)) + return TRUE; + } + + if (cctx->window_class) { + if (window_class.empty() || !std::regex_search(window_class, class_regex)) + return TRUE; + } + + if (cctx->window_exe) { + if (window_exe.empty() || !std::regex_search(window_exe, exe_regex)) + return TRUE; + } + + av_log(avctx, AV_LOG_VERBOSE, "Found capture window: %s (Class: %s, Exe: %s)\n", + window_text.c_str(), window_class.c_str(), window_exe.c_str()); + ctx->capture_hwnd = hwnd; + return FALSE; + }); + if (EnumWindows(cb->proc, cb->lparam) || !ctx->capture_hwnd) + return AVERROR(ENOENT); + + if (cctx->monitor_idx == GFX_MONITOR_IDX_WINDOW) { + ctx->capture_hmonitor = MonitorFromWindow(ctx->capture_hwnd, MONITOR_DEFAULTTONEAREST); + ctx->capture_hwnd = NULL; + if (!ctx->capture_hmonitor) { + av_log(avctx, AV_LOG_ERROR, "Failed to get monitor for capture window\n"); + return AVERROR(ENOENT); + } + } + + return 0; + } + + av_log(avctx, AV_LOG_ERROR, "No capture source specified\n"); + return AVERROR(EINVAL); +} + +static av_cold void gfxcapture_uninit(AVFilterContext *avctx) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + + if (!ctx) + return; + + stop_wgc_thread(avctx); + + ctx->d3d.reset(); + + av_buffer_unref(&ctx->frames_ref); + av_buffer_unref(&ctx->device_ref); + + delete ctx; + cctx->ctx = nullptr; +} + +template<typename T> +static av_cold void GetProcAddressTyped(const hmodule_ptr_t &hModule, LPCSTR lpProcName, T *out) { + *out = reinterpret_cast<T>(GetProcAddress(hModule.get(), lpProcName)); +} + +static av_cold int load_functions(AVFilterContext *avctx) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + +#define LOAD_DLL(handle, name) \ + handle = hmodule_ptr_t(LoadLibraryExW(L##name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32)); \ + if (!handle) { \ + av_log(avctx, AV_LOG_ERROR, "Failed opening " #name "\n"); \ + return AVERROR(ENOSYS); \ + } + +#define LOAD_FUNC(handle, name) \ + GetProcAddressTyped(handle, #name, &ctx->fn.name); \ + if (!ctx->fn.name) { \ + av_log(avctx, AV_LOG_ERROR, "Failed loading " #name "\n"); \ + return AVERROR(ENOSYS); \ + } + + // this handle is not used anywhere, but letting it get auto-freed during RoUninit causes crashes + LOAD_DLL(ctx->fn.graphicscapture_handle, "graphicscapture.dll"); + + LOAD_DLL(ctx->fn.combase_handle, "combase.dll"); + LOAD_DLL(ctx->fn.dwmapi_handle, "dwmapi.dll"); + LOAD_DLL(ctx->fn.d3d11_handle, "d3d11.dll"); + LOAD_DLL(ctx->fn.coremsg_handle, "coremessaging.dll"); + LOAD_DLL(ctx->fn.user32_handle, "user32.dll"); + LOAD_DLL(ctx->fn.d3dcompiler_handle, "d3dcompiler_47.dll"); + + LOAD_FUNC(ctx->fn.combase_handle, RoInitialize); + LOAD_FUNC(ctx->fn.combase_handle, RoUninitialize); + LOAD_FUNC(ctx->fn.combase_handle, RoGetActivationFactory); + LOAD_FUNC(ctx->fn.combase_handle, WindowsCreateStringReference); + + LOAD_FUNC(ctx->fn.dwmapi_handle, DwmGetWindowAttribute); + + LOAD_FUNC(ctx->fn.d3d11_handle, CreateDirect3D11DeviceFromDXGIDevice); + + LOAD_FUNC(ctx->fn.coremsg_handle, CreateDispatcherQueueController); + + LOAD_FUNC(ctx->fn.user32_handle, SetThreadDpiAwarenessContext); + + LOAD_FUNC(ctx->fn.d3dcompiler_handle, D3DCompile); + +#undef LOAD_FUNC +#undef LOAD_DLL + return 0; +} + +static av_cold int gfxcapture_init(AVFilterContext *avctx) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + int ret = 0; + + GfxCaptureContextCpp *ctx = cctx->ctx = new GfxCaptureContextCpp(); + ctx->d3d = std::make_unique<GfxCaptureContextD3D>(); + + ret = load_functions(avctx); + if (ret < 0) { + ctx->fn.RoUninitialize = nullptr; + goto fail; + } + + return 0; + +fail: + gfxcapture_uninit(avctx); + return ret; +} + +static int init_hwframes_ctx(AVFilterContext *avctx) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + int ret = 0; + + ctx->frames_ref = av_hwframe_ctx_alloc(ctx->device_ref); + if (!ctx->frames_ref) + return AVERROR(ENOMEM); + ctx->frames_ctx = (AVHWFramesContext*)ctx->frames_ref->data; + ctx->frames_hwctx = (AVD3D11VAFramesContext*)ctx->frames_ctx->hwctx; + + ctx->frames_ctx->format = AV_PIX_FMT_D3D11; + ctx->frames_ctx->width = cctx->canvas_width; + ctx->frames_ctx->height = cctx->canvas_height; + ctx->frames_ctx->sw_format = (AVPixelFormat)cctx->out_fmt; + if (avctx->extra_hw_frames > 0) + ctx->frames_ctx->initial_pool_size = 8 + avctx->extra_hw_frames; + + ctx->frames_hwctx->BindFlags = D3D11_BIND_RENDER_TARGET; + + ret = av_hwframe_ctx_init(ctx->frames_ref); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to initialise hardware frames context: %d.\n", ret); + goto fail; + } + + return 0; +fail: + av_buffer_unref(&ctx->frames_ref); + return ret; +} + +static int setup_gfxcapture_capture(AVFilterContext *avctx) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + std::unique_ptr<GfxCaptureContextWgc> &wgctx = ctx->wgc; + int ret = 0; + + stop_wgc_thread(avctx); + + ret = find_capture_source(avctx); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to find capture source\n"); + return ret; + } + + ret = start_wgc_thread(avctx); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to start WGC thread\n"); + return ret; + } + + int cap_w = wgctx->cap_size.Width - cctx->crop_left - cctx->crop_right; + int cap_h = wgctx->cap_size.Height - cctx->crop_top - cctx->crop_bottom; + + if (!cctx->capture_border) { + cap_w -= wgctx->client_area_offsets.left + wgctx->client_area_offsets.right; + cap_h -= wgctx->client_area_offsets.top + wgctx->client_area_offsets.bottom; + } + + if (cctx->canvas_width == 0) + cctx->canvas_width = cap_w; + else if (cctx->canvas_width < 0) + cctx->canvas_width = (cap_w / cctx->canvas_width) * cctx->canvas_width; + + if (cctx->canvas_height == 0) + cctx->canvas_height = cap_h; + else if (cctx->canvas_height < 0) + cctx->canvas_height = (cap_h / cctx->canvas_height) * cctx->canvas_height; + + return 0; +} + +static int prepare_render_resources(AVFilterContext *avctx) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + std::unique_ptr<GfxCaptureContextD3D> &d3dctx = ctx->d3d; + HRESULT hr; + + ComPtr<ID3DBlob> vs_blob, ps_blob, err_blob; + CD3D11_SAMPLER_DESC sampler_desc(CD3D11_DEFAULT{}); + UINT flags = D3DCOMPILE_OPTIMIZATION_LEVEL3; + + hr = ctx->fn.D3DCompile(render_shader_src, sizeof(render_shader_src) - 1, NULL, NULL, NULL, "main_vs", "vs_4_0", flags, 0, &vs_blob, &err_blob); + if (FAILED(hr)) { + if (err_blob) { + av_log(avctx, AV_LOG_ERROR, "Failed compiling vertex shader: %.*s\n", (int)err_blob->GetBufferSize(), (char*)err_blob->GetBufferPointer()); + } else { + av_log(avctx, AV_LOG_ERROR, "Failed compiling vertex shader: 0x%08lX\n", hr); + } + return AVERROR_EXTERNAL; + } + + const char *ps_entry = "main_ps_bicubic"; + if (cctx->resize_mode == GFX_RESIZE_CROP || cctx->scale_mode == GFX_SCALE_POINT) { + ps_entry = "main_ps"; + sampler_desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT; + } + + hr = ctx->fn.D3DCompile(render_shader_src, sizeof(render_shader_src) - 1, NULL, NULL, NULL, ps_entry, "ps_4_0", flags, 0, &ps_blob, &err_blob); + if (FAILED(hr)) { + if (err_blob) { + av_log(avctx, AV_LOG_ERROR, "Failed compiling pixel shader: %.*s\n", (int)err_blob->GetBufferSize(), (char*)err_blob->GetBufferPointer()); + } else { + av_log(avctx, AV_LOG_ERROR, "Failed compiling pixel shader: 0x%08lX\n", hr); + } + return AVERROR_EXTERNAL; + } + + CHECK_HR_RET(ctx->device_hwctx->device->CreateVertexShader(vs_blob->GetBufferPointer(), vs_blob->GetBufferSize(), NULL, &d3dctx->vertex_shader)); + CHECK_HR_RET(ctx->device_hwctx->device->CreatePixelShader(ps_blob->GetBufferPointer(), ps_blob->GetBufferSize(), NULL, &d3dctx->pixel_shader)); + + CHECK_HR_RET(ctx->device_hwctx->device->CreateSamplerState(&sampler_desc, &d3dctx->sampler_state)); + + D3D11_BUFFER_DESC cb_desc = { 0 }; + cb_desc.ByteWidth = 48; + cb_desc.Usage = D3D11_USAGE_DYNAMIC; + cb_desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; + cb_desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE; + + CHECK_HR_RET(ctx->device_hwctx->device->CreateBuffer(&cb_desc, NULL, &d3dctx->shader_cb)); + + CHECK_HR_RET(ctx->device_hwctx->device->CreateDeferredContext(0, &d3dctx->deferred_ctx)); + + return 0; +} + +static int gfxcapture_config_props(AVFilterLink *outlink) +{ + AVFilterContext *avctx = outlink->src; + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + + FilterLink *link = ff_filter_link(outlink); + int ret; + + if (avctx->hw_device_ctx) { + ctx->device_ctx = (AVHWDeviceContext*)avctx->hw_device_ctx->data; + + if (ctx->device_ctx->type != AV_HWDEVICE_TYPE_D3D11VA) { + av_log(avctx, AV_LOG_ERROR, "Non-D3D11VA input hw_device_ctx\n"); + return AVERROR(EINVAL); + } + + ctx->device_ref = av_buffer_ref(avctx->hw_device_ctx); + if (!ctx->device_ref) + return AVERROR(ENOMEM); + + av_log(avctx, AV_LOG_VERBOSE, "Using provided hw_device_ctx\n"); + } else { + ret = av_hwdevice_ctx_create(&ctx->device_ref, AV_HWDEVICE_TYPE_D3D11VA, NULL, NULL, 0); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to create D3D11VA device.\n"); + return ret; + } + + ctx->device_ctx = (AVHWDeviceContext*)ctx->device_ref->data; + + av_log(avctx, AV_LOG_VERBOSE, "Created internal hw_device_ctx\n"); + } + + ctx->device_hwctx = (AVD3D11VADeviceContext*)ctx->device_ctx->hwctx; + + ret = prepare_render_resources(avctx); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to prepare render resources\n"); + return ret; + } + + ret = setup_gfxcapture_capture(avctx); + if (ret < 0) { + av_log(avctx, AV_LOG_ERROR, "Failed to setup graphics capture\n"); + return ret; + } + + ret = init_hwframes_ctx(avctx); + if (ret < 0) + return ret; + + link->hw_frames_ctx = av_buffer_ref(ctx->frames_ref); + if (!link->hw_frames_ctx) + return AVERROR(ENOMEM); + + std::lock_guard wgc_lock(ctx->wgc_thread_uninit_mutex); + if (!ctx->wgc) { + av_log(avctx, AV_LOG_ERROR, "WGC thread died prematurely\n"); + return AVERROR(ENOSYS); + } + + outlink->w = ctx->frames_ctx->width; + outlink->h = ctx->frames_ctx->height; + outlink->time_base = AVRational{1, TIMESPAN_RES}; + outlink->alpha_mode = cctx->premult_alpha ? AVALPHA_MODE_PREMULTIPLIED : AVALPHA_MODE_STRAIGHT; + link->frame_rate = cctx->frame_rate; + + av_log(avctx, AV_LOG_DEBUG, "Capture setup with res %dx%d\n", outlink->w, outlink->h); + + return 0; +} + +static int render_capture_to_frame(AVFilterContext *avctx, AVFrame *frame, const ComPtr<ID3D11Texture2D> &src_tex) +{ + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + std::unique_ptr<GfxCaptureContextD3D> &d3dctx = ctx->d3d; + std::unique_ptr<GfxCaptureContextWgc> &wgctx = ctx->wgc; + + ID3D11Device *dev = ctx->device_hwctx->device; + ID3D11DeviceContext *dev_ctx = ctx->device_hwctx->device_context; + ComPtr<ID3D11DeviceContext> &def_ctx = d3dctx->deferred_ctx; + + D3D11_TEXTURE2D_DESC dst_tex_desc; + reinterpret_cast<ID3D11Texture2D*>(frame->data[0])->GetDesc(&dst_tex_desc); + + D3D11_TEXTURE2D_DESC src_tex_desc; + src_tex->GetDesc(&src_tex_desc); + + D3D11_RENDER_TARGET_VIEW_DESC target_desc = {}; + target_desc.Format = dst_tex_desc.Format; + + if (dst_tex_desc.ArraySize > 1) { + target_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2DARRAY; + target_desc.Texture2DArray.ArraySize = 1; + target_desc.Texture2DArray.FirstArraySlice = (uintptr_t)frame->data[1]; + target_desc.Texture2DArray.MipSlice = 0; + } else { + target_desc.ViewDimension = D3D11_RTV_DIMENSION_TEXTURE2D; + target_desc.Texture2D.MipSlice = 0; + } + + ComPtr<ID3D11RenderTargetView> rtv; + CHECK_HR_RET(dev->CreateRenderTargetView( + reinterpret_cast<ID3D11Resource*>(frame->data[0]), &target_desc, &rtv)); + + ComPtr<ID3D11ShaderResourceView> srv; + CHECK_HR_RET(dev->CreateShaderResourceView(src_tex.Get(), nullptr, &srv)); + + int crop_left = cctx->crop_left; + int crop_top = cctx->crop_top; + int crop_right = cctx->crop_right; + int crop_bottom = cctx->crop_bottom; + + if (!cctx->capture_border) { + crop_left += wgctx->client_area_offsets.left; + crop_top += wgctx->client_area_offsets.top; + crop_right += wgctx->client_area_offsets.right; + crop_bottom += wgctx->client_area_offsets.bottom; + } + + // Using the actual capture frame size here adjusts for jank that can happen during rapid + // resizing of the source window. The capture frame pool is only recreated once a frame + // of changed size came out of it, so we need to cut/pad such frames to fit. + // Just discarding such frames can lead to visible stutter if the source window is being + // resized continuously, so this code does its best to adjust them instead. With the risk + // of slight clamping artifacts when enlarging rapidly. + int cropped_w = wgctx->cap_size.Width - crop_left - crop_right; + int cropped_h = wgctx->cap_size.Height - crop_top - crop_bottom; + + D3D11_VIEWPORT viewport = { 0 }; + viewport.MinDepth = 0.f; + viewport.MaxDepth = 1.f; + + switch (cctx->resize_mode) { + case GFX_RESIZE_CROP: + viewport.Width = (float)cropped_w; + viewport.Height = (float)cropped_h; + break; + case GFX_RESIZE_SCALE: + viewport.Width = dst_tex_desc.Width; + viewport.Height = dst_tex_desc.Height; + break; + case GFX_RESIZE_SCALE_ASPECT: { + float scale = FFMIN(dst_tex_desc.Width / (float)cropped_w, + dst_tex_desc.Height / (float)cropped_h); + viewport.Width = cropped_w * scale; + viewport.Height = cropped_h * scale; + break; + } + default: + av_log(avctx, AV_LOG_ERROR, "Invalid scaling mode\n"); + return AVERROR_BUG; + }; + + def_ctx->RSSetViewports(1, &viewport); + + D3D11_MAPPED_SUBRESOURCE map; + CHECK_HR_RET(def_ctx->Map(d3dctx->shader_cb.Get(), 0, D3D11_MAP_WRITE_DISCARD, 0, &map)); + { + float *cb_f = static_cast<float*>(map.pData); + uint32_t *cb_u = static_cast<uint32_t*>(map.pData); + cb_f[0] = (float)cropped_w; + cb_f[1] = (float)cropped_h; + cb_f[2] = viewport.Width; + cb_f[3] = viewport.Height; + cb_f[4] = crop_left / (float)src_tex_desc.Width; // min_u + cb_f[5] = crop_top / (float)src_tex_desc.Height; // min_v + cb_f[6] = (crop_left + cropped_w) / (float)src_tex_desc.Width; // max_u + cb_f[7] = (crop_top + cropped_h) / (float)src_tex_desc.Height; // max_v + cb_u[8] = !cctx->premult_alpha; // to_unpremult + cb_u[9] = src_tex_desc.Format == DXGI_FORMAT_R16G16B16A16_FLOAT && + dst_tex_desc.Format != DXGI_FORMAT_R16G16B16A16_FLOAT; // to_srgb + } + def_ctx->Unmap(d3dctx->shader_cb.Get(), 0); + + def_ctx->OMSetRenderTargets(1, rtv.GetAddressOf(), nullptr); + + const float clear_color[4] = {0.f, 0.f, 0.f, 1.f}; + def_ctx->ClearRenderTargetView(rtv.Get(), clear_color); + + def_ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); + def_ctx->VSSetShader(d3dctx->vertex_shader.Get(), nullptr, 0); + def_ctx->VSSetConstantBuffers(0, 1, d3dctx->shader_cb.GetAddressOf()); + def_ctx->PSSetShader(d3dctx->pixel_shader.Get(), nullptr, 0); + def_ctx->PSSetSamplers(0, 1, d3dctx->sampler_state.GetAddressOf()); + def_ctx->PSSetShaderResources(0, 1, srv.GetAddressOf()); + def_ctx->PSSetConstantBuffers(0, 1, d3dctx->shader_cb.GetAddressOf()); + + def_ctx->Draw(3, 0); + + ComPtr<ID3D11CommandList> cmd_list; + CHECK_HR_RET(def_ctx->FinishCommandList(FALSE, &cmd_list)); + dev_ctx->ExecuteCommandList(cmd_list.Get(), FALSE); + + return 0; +} + +static int process_frame_if_exists(AVFilterLink *outlink) +{ + AVFilterContext *avctx = outlink->src; + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + int ret; + + AVFrame *frame = nullptr; + + ret = run_on_wgc_thread(avctx, [&]() { + ComPtr<IDirect3D11CaptureFrame> capture_frame; + ComPtr<IDirect3DSurface> capture_surface; + ComPtr<IDirect3DDxgiInterfaceAccess> dxgi_interface_access; + ComPtr<ID3D11Texture2D> frame_texture; + TimeSpan frame_time = { 0 }; + + ret = wgc_try_get_next_frame(avctx, &capture_frame); + if (ret < 0) + return ret; + + CHECK_HR_RET(capture_frame->get_SystemRelativeTime(&frame_time)); + + CHECK_HR_RET(capture_frame->get_Surface(&capture_surface)); + CHECK_HR_RET(capture_surface.As(&dxgi_interface_access)); + CHECK_HR_RET(dxgi_interface_access->GetInterface(IID_PPV_ARGS(&frame_texture))); + + if (!frame_texture) + return AVERROR(EAGAIN); + + frame = ff_get_video_buffer(outlink, cctx->canvas_width, cctx->canvas_height); + if (!frame) + return AVERROR(ENOMEM); + + frame->pts = frame_time.Duration; + + return render_capture_to_frame(avctx, frame, frame_texture); + }); + if (ret < 0) + return ret; + + frame->sample_aspect_ratio = AVRational{1, 1}; + + if (ctx->frames_ctx->sw_format == AV_PIX_FMT_RGBAF16) { + // According to MSDN, all floating point formats contain sRGB image data with linear 1.0 gamma. + frame->color_range = AVCOL_RANGE_JPEG; + frame->color_primaries = AVCOL_PRI_BT709; + frame->color_trc = AVCOL_TRC_LINEAR; + frame->colorspace = AVCOL_SPC_RGB; + } else { + // According to MSDN, all integer formats contain sRGB image data + frame->color_range = AVCOL_RANGE_JPEG; + frame->color_primaries = AVCOL_PRI_BT709; + frame->color_trc = AVCOL_TRC_IEC61966_2_1; + frame->colorspace = AVCOL_SPC_RGB; + } + + ctx->last_pts = frame->pts; + + if (!ctx->first_pts) + ctx->first_pts = frame->pts; + frame->pts -= ctx->first_pts; + + return ff_filter_frame(outlink, frame); +} + +static int gfxcapture_activate(AVFilterContext *avctx) +{ + AVFilterLink *outlink = avctx->outputs[0]; + GfxCaptureContext *cctx = CCTX(avctx->priv); + GfxCaptureContextCpp *ctx = cctx->ctx; + std::unique_ptr<GfxCaptureContextWgc> &wgctx = ctx->wgc; + + std::lock_guard wgc_lock(ctx->wgc_thread_uninit_mutex); + if (!wgctx) { + av_log(avctx, AV_LOG_ERROR, "WGC thread not initialized\n"); + return AVERROR(ENOSYS); + } + + if (!ff_outlink_frame_wanted(outlink)) + return FFERROR_NOT_READY; + + std::unique_lock frame_lock(wgctx->frame_arrived_mutex); + + for (;;) { + uint64_t last_seq = wgctx->frame_seq.load(std::memory_order_acquire); + + int ret = process_frame_if_exists(outlink); + if (ret != AVERROR(EAGAIN)) + return ret; + + if (wgctx->window_closed.load(std::memory_order_acquire)) { + ff_outlink_set_status(outlink, AVERROR_EOF, ctx->last_pts - ctx->first_pts + 1); + break; + } + + if (!wgctx->frame_arrived_cond.wait_for(frame_lock, std::chrono::seconds(1), [&]() { + return wgctx->frame_seq.load(std::memory_order_acquire) != last_seq || + wgctx->window_closed.load(std::memory_order_acquire); + })) + break; + } + + return 0; +} + +av_cold void ff_gfxcapture_uninit(AVFilterContext *avctx) noexcept +{ + try { + gfxcapture_uninit(avctx); + } catch (const std::exception &e) { + av_log(avctx, AV_LOG_ERROR, "unhandled exception during uninit: %s\n", e.what()); + } catch (...) { + av_log(avctx, AV_LOG_ERROR, "unhandled exception during uninit\n"); + } +} + +av_cold int ff_gfxcapture_init(AVFilterContext *avctx) noexcept +{ + try { + return gfxcapture_init(avctx); + } catch (const std::bad_alloc&) { + return AVERROR(ENOMEM); + } catch (const std::exception &e) { + av_log(avctx, AV_LOG_ERROR, "unhandled exception during init: %s\n", e.what()); + return AVERROR_BUG; + } catch (...) { + av_log(avctx, AV_LOG_ERROR, "unhandled exception during init\n"); + return AVERROR_BUG; + } +} + +int ff_gfxcapture_activate(AVFilterContext *avctx) noexcept +{ + try { + return gfxcapture_activate(avctx); + } catch (const std::bad_alloc&) { + return AVERROR(ENOMEM); + } catch (const std::exception &e) { + av_log(avctx, AV_LOG_ERROR, "unhandled exception during activate: %s\n", e.what()); + return AVERROR_BUG; + } catch (...) { + av_log(avctx, AV_LOG_ERROR, "unhandled exception during activate\n"); + return AVERROR_BUG; + } +} + +int ff_gfxcapture_config_props(AVFilterLink *outlink) noexcept +{ + AVFilterContext *avctx = outlink->src; + + try { + return gfxcapture_config_props(outlink); + } catch (const std::bad_alloc&) { + return AVERROR(ENOMEM); + } catch (const std::exception &e) { + av_log(avctx, AV_LOG_ERROR, "unhandled exception during config_props: %s\n", e.what()); + return AVERROR_BUG; + } catch (...) { + av_log(avctx, AV_LOG_ERROR, "unhandled exception during config_props\n"); + return AVERROR_BUG; + } +} diff --git a/libavfilter/vsrc_gfxcapture_winrt.h b/libavfilter/vsrc_gfxcapture_winrt.h new file mode 100644 index 0000000000..d6f11e4ef5 --- /dev/null +++ b/libavfilter/vsrc_gfxcapture_winrt.h @@ -0,0 +1,189 @@ +/* + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AVFILTER_VSRC_GFXCAPTURE_WINRT_H +#define AVFILTER_VSRC_GFXCAPTURE_WINRT_H + +// Forward-declare IDirect3DDxgiInterfaceAccess if headers too old +#if !HAVE_IDIRECT3DDXGIINTERFACEACCESS +namespace Windows::Graphics::DirectX::Direct3D11 { + MIDL_INTERFACE("A9B3D012-3DF2-4EE3-B8D1-8695F457D3C1") + IDirect3DDxgiInterfaceAccess : public IUnknown + { + public: + IFACEMETHOD(GetInterface)(REFIID iid, _COM_Outptr_ void** p) = 0; + }; +} +#ifdef __MINGW32__ +__CRT_UUID_DECL(Windows::Graphics::DirectX::Direct3D11::IDirect3DDxgiInterfaceAccess, + 0xa9b3d012, 0x3df2, 0x4ee3, 0xb8, 0xd1, 0x86, 0x95, 0xf4, 0x57, 0xd3, 0xc1) +#endif +#endif /* !HAVE_IDIRECT3DDXGIINTERFACEACCESS */ + +// Forward-declare IGraphicsCaptureSession5 if headers too old +#if !HAVE___X_ABI_CWINDOWS_CGRAPHICS_CCAPTURE_CIGRAPHICSCAPTURESESSION5 +namespace ABI::Windows ::Graphics::Capture { + MIDL_INTERFACE("67C0EA62-1F85-5061-925A-239BE0AC09CB") + IGraphicsCaptureSession5 : public IInspectable + { + public: + IFACEMETHOD(get_MinUpdateInterval)(ABI::Windows::Foundation::TimeSpan* value) = 0; + IFACEMETHOD(put_MinUpdateInterval)(ABI::Windows::Foundation::TimeSpan value) = 0; + }; +} +#ifdef __MINGW32__ +__CRT_UUID_DECL(ABI::Windows ::Graphics::Capture::IGraphicsCaptureSession5, + 0x67c0ea62, 0x1f85, 0x5061, 0x92, 0x5a, 0x23, 0x9b, 0xe0, 0xac, 0x09, 0xcb) +#endif +#endif /* !HAVE___X_ABI_CWINDOWS_CGRAPHICS_CCAPTURE_CIGRAPHICSCAPTURESESSION5 */ + +/**************************************************** + * Helper class to implement refcounted COM objects * + ****************************************************/ +template<typename... Interfaces> +struct FFComObject : Interfaces... +{ + virtual ~FFComObject() = default; + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void** ppvObject) override + { + if (!ppvObject) + return E_POINTER; + + if (query_all<Interfaces...>(riid, ppvObject)) + { + AddRef(); + return S_OK; + } + + *ppvObject = nullptr; + return E_NOINTERFACE; + } + + ULONG STDMETHODCALLTYPE AddRef() override + { + return ++ref_count; + } + + ULONG STDMETHODCALLTYPE Release() override + { + ULONG rc = --ref_count; + if (rc == 0) + delete this; + return rc; + } + +private: + template <typename Iface, typename... IFaces> + bool query_all(REFIID riid, void** ppvObject) + { + if (riid == __uuidof(Iface)) { + *ppvObject = static_cast<Iface*>(this); + return true; + } + if constexpr (sizeof...(IFaces)) { + return query_all<IFaces...>(riid, ppvObject); + } else if (riid == __uuidof(IUnknown)) { + *ppvObject = static_cast<IUnknown*>(static_cast<Iface*>(this)); + return true; + } + return false; + } + + std::atomic<ULONG> ref_count { 1 }; +}; + +/******************************************* + * Helper to implement COM/WinRT callbacks * + *******************************************/ +template<class Iface, typename F, typename... Args> +struct FFTypedCBHandler : FFComObject<Iface, IAgileObject> +{ + using Func = std::decay_t<F>; + + explicit FFTypedCBHandler(F&& f) : cb_func(std::forward<F>(f)) {} + + std::invoke_result_t<Func&, Args...> STDMETHODCALLTYPE Invoke(Args... args) override + { + if constexpr (std::is_same_v<std::invoke_result_t<Func&, Args...>, HRESULT>) { + return cb_func(std::forward<Args>(args)...); + } else { + cb_func(std::forward<Args>(args)...); + return S_OK; + } + } + +private: + Func cb_func; +}; + +template<class Iface, typename... Args, typename F> +static Microsoft::WRL::ComPtr<Iface> create_cb_handler(F&& cb_func) +{ + return Microsoft::WRL::ComPtr<Iface>( + new FFTypedCBHandler<Iface, F, Args...>(std::forward<F>(cb_func)) + ); +} + +/****************************************** + * Helpers to implement C style callbacks * + ******************************************/ +template <typename Ret, typename... Args> +struct Win32Callback { + std::function<Ret(Args...)> fn; + static Ret CALLBACK thunk(Args... args, LPARAM lparam) { + auto self = reinterpret_cast<Win32Callback*>(lparam); + return self->fn(std::forward<Args>(args)...); + } + decltype(&Win32Callback::thunk) proc = &Win32Callback::thunk; + LPARAM lparam = 0; +}; + +template <typename Ret, typename... Args> +auto make_win32_callback(const std::function<Ret(Args...)> &&fn) { + using T = Win32Callback<Ret, Args...>; + auto res = std::make_unique<T>(T{ std::forward<decltype(fn)>(fn) }); + res->lparam = reinterpret_cast<LPARAM>(res.get()); + return res; +} +#define make_win32_callback(...) make_win32_callback(std::function(__VA_ARGS__)) + +/***************************** + * Small convenience helpers * + *****************************/ +struct HMODULEDeleter { + typedef HMODULE pointer; + void operator()(HMODULE handle) const { + if (handle) + FreeLibrary(handle); + } +}; +typedef std::unique_ptr<HMODULE, HMODULEDeleter> hmodule_ptr_t; + +struct HANDLEDeleter { + typedef HANDLE pointer; + void operator()(HANDLE handle) const { + if (handle && handle != INVALID_HANDLE_VALUE) + CloseHandle(handle); + } +}; +typedef std::unique_ptr<HANDLE, HANDLEDeleter> handle_ptr_t; + +#define HLSL(shader) #shader + +#endif /* AVFILTER_VSRC_GFXCAPTURE_WINRT_H */ commit 7856f57533b14c18e888e275770a0ca26685d6e4 Author: Timo Rothenpieler <t...@rothenpieler.org> AuthorDate: Sun Sep 7 22:03:50 2025 +0200 Commit: Timo Rothenpieler <t...@rothenpieler.org> CommitDate: Sun Sep 14 11:45:11 2025 +0000 compat/w32pthreads: fix compatibility with C++ diff --git a/compat/w32pthreads.h b/compat/w32pthreads.h index 6f2734b470..61f3dfcdd9 100644 --- a/compat/w32pthreads.h +++ b/compat/w32pthreads.h @@ -46,7 +46,7 @@ #include "libavutil/time.h" #include "libavutil/wchar_filename.h" -typedef struct pthread_t { +typedef struct w32pthread_t { void *handle; void *(*func)(void* arg); void *arg; @@ -85,7 +85,7 @@ static av_unused int pthread_create(pthread_t *thread, const void *unused_attr, { pthread_t ret; - ret = av_mallocz(sizeof(*ret)); + ret = (pthread_t)av_mallocz(sizeof(*ret)); if (!ret) return EAGAIN; commit 5146b2fb8b700c5376244859dcd1731e4470e3b7 Author: Timo Rothenpieler <t...@rothenpieler.org> AuthorDate: Tue Sep 2 01:00:24 2025 +0200 Commit: Timo Rothenpieler <t...@rothenpieler.org> CommitDate: Sun Sep 14 11:45:11 2025 +0000 configure: don't use MinGW ANSI stdio when using UCRT MinGWs ANSI stdio is quite slow, and when using UCRT, its extra features are not needed. The only troublesome part with ucrt printf is that is disagrees about the size of "long double", but FFmpeg does not use that anywhere, let alone prints it. diff --git a/configure b/configure index 2db65d0153..2364e00d08 100755 --- a/configure +++ b/configure @@ -6200,7 +6200,9 @@ probe_libc(){ add_allcflags "-include $source_path/compat/msvcrt/snprintf.h" fi add_${pfx}cflags -U__STRICT_ANSI__ - add_${pfx}cppflags -D__USE_MINGW_ANSI_STDIO=1 + if ! test_${pfx}cpp_condition crtdefs.h "defined(_UCRT)"; then + add_${pfx}cppflags -D__USE_MINGW_ANSI_STDIO=1 + fi eval test \$${pfx_no_}cc_type = "gcc" && add_${pfx}cppflags -D__printf__=__gnu_printf__ test_${pfx}cpp_condition windows.h "!defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0600" && @@ -6213,7 +6215,9 @@ probe_libc(){ (__MINGW32_MAJOR_VERSION == 3 && __MINGW32_MINOR_VERSION >= 15)" || die "ERROR: MinGW32 runtime version must be >= 3.15." add_${pfx}cflags -U__STRICT_ANSI__ - add_${pfx}cppflags -D__USE_MINGW_ANSI_STDIO=1 + if ! test_${pfx}cpp_condition crtdefs.h "defined(_UCRT)"; then + add_${pfx}cppflags -D__USE_MINGW_ANSI_STDIO=1 + fi test_${pfx}cpp_condition _mingw.h "__MSVCRT_VERSION__ < 0x0700" && add_${pfx}cppflags -D__MSVCRT_VERSION__=0x0700 test_${pfx}cpp_condition windows.h "!defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0600" && commit 9c8a6ac85c3e56f8168a7813e4c825aff7044939 Author: Timo Rothenpieler <t...@rothenpieler.org> AuthorDate: Thu Sep 4 20:51:58 2025 +0200 Commit: Timo Rothenpieler <t...@rothenpieler.org> CommitDate: Sun Sep 14 11:45:11 2025 +0000 configure: fix naming of some C++/cxx functions being mislabeled as cpp diff --git a/configure b/configure index 5f84299339..2db65d0153 100755 --- a/configure +++ b/configure @@ -1445,8 +1445,8 @@ check_func_headers(){ } | test_ld "cc" "$@" && enable $funcs && enable_sanitized $headers } -check_class_headers_cpp(){ - log check_class_headers_cpp "$@" +check_class_headers_cxx(){ + log check_class_headers_cxx "$@" headers=$1 classes=$2 shift 2 @@ -1538,14 +1538,14 @@ check_lib(){ enable $name && eval ${name}_extralibs="\$@" } -check_lib_cpp(){ - log check_lib_cpp "$@" +check_lib_cxx(){ + log check_lib_cxx "$@" name="$1" headers="$2" classes="$3" shift 3 disable $name - check_class_headers_cpp "$headers" "$classes" "$@" && + check_class_headers_cxx "$headers" "$classes" "$@" && enable $name && eval ${name}_extralibs="\$@" } @@ -1716,12 +1716,12 @@ require_cc(){ check_cc "$@" || die "ERROR: $name failed" } -require_cpp(){ - log require_cpp "$@" +require_cxx(){ + log require_cxx "$@" name_version="$1" name="${1%% *}" shift - check_lib_cpp "$name" "$@" || die "ERROR: $name_version not found" + check_lib_cxx "$name" "$@" || die "ERROR: $name_version not found" } require_headers(){ @@ -7207,7 +7207,7 @@ enabled libtensorflow && require libtensorflow tensorflow/c/c_api.h TF_Versi enabled libtesseract && require_pkg_config libtesseract tesseract tesseract/capi.h TessBaseAPICreate enabled libtheora && require libtheora theora/theoraenc.h th_info_init -ltheoraenc -ltheoradec -logg enabled libtls && require_pkg_config libtls libtls tls.h tls_configure -enabled libtorch && check_cxxflags -std=c++17 && require_cpp libtorch torch/torch.h "torch::Tensor" -ltorch -lc10 -ltorch_cpu -lstdc++ -lpthread +enabled libtorch && check_cxxflags -std=c++17 && require_cxx libtorch torch/torch.h "torch::Tensor" -ltorch -lc10 -ltorch_cpu -lstdc++ -lpthread enabled libtwolame && require libtwolame twolame.h twolame_init -ltwolame && { check_lib libtwolame twolame.h twolame_encode_buffer_float32_interleaved -ltwolame || die "ERROR: libtwolame must be installed and version must be >= 0.3.10"; } commit 0ce413af9cc8040095a6b714600d6a8dceaca514 Author: Timo Rothenpieler <t...@rothenpieler.org> AuthorDate: Tue Sep 2 00:53:55 2025 +0200 Commit: Timo Rothenpieler <t...@rothenpieler.org> CommitDate: Sun Sep 14 11:45:11 2025 +0000 configure: properly split C/CXX and CPP flags Right now, CFLAGS like -std=c17 leak onto the C++ compilers commandline, leading to tons of warnings and even some errors. Undefining __STRICT_ANSI__ causes strong warnings from the stdlib headers. So only set it for C. diff --git a/configure b/configure index c6afcd7494..5f84299339 100755 --- a/configure +++ b/configure @@ -976,6 +976,12 @@ add_objcflags(){ append OBJCFLAGS $($objcflags_filter "$@") } +add_allcflags(){ + add_cflags "$@" + add_cxxflags "$@" + add_objcflags "$@" +} + add_asflags(){ append ASFLAGS $($asflags_filter "$@") } @@ -1036,6 +1042,14 @@ cc_o(){ eval printf '%s\\n' $CC_O } +cxx_e(){ + eval printf '%s\\n' $CXX_E +} + +cxx_o(){ + eval printf '%s\\n' $CXX_O +} + as_o(){ eval printf '%s\\n' $AS_O } @@ -1071,14 +1085,14 @@ test_cxx(){ log test_cxx "$@" cat > $TMPCPP log_file $TMPCPP - test_cmd $cxx $CPPFLAGS $CFLAGS $CXXFLAGS "$@" $CXX_C -o $TMPO $TMPCPP + test_cmd $cxx $CPPFLAGS $CXXFLAGS "$@" $CXX_C $(cxx_o $TMPO) $TMPCPP } test_objcc(){ log test_objcc "$@" cat > $TMPM log_file $TMPM - test_cmd $objcc -Werror=missing-prototypes $CPPFLAGS $CFLAGS $OBJCFLAGS "$@" $OBJCC_C $(cc_o $TMPO) $TMPM + test_cmd $objcc -Werror=missing-prototypes $CPPFLAGS $OBJCFLAGS "$@" $OBJCC_C $(cc_o $TMPO) $TMPM } test_nvcc(){ @@ -1288,14 +1302,19 @@ check_cflags(){ test_cflags "$@" && add_cflags "$@" } -check_cxxflags(){ - log check_cxxflags "$@" +test_cxxflags(){ + log test_cxxflags "$@" set -- $($cflags_filter "$@") - test_cxx "$@" <<EOF && append CXXFLAGS "$@" + test_cxx "$@" <<EOF int x; EOF } +check_cxxflags(){ + log check_cxxflags "$@" + test_cxxflags "$@" && add_cxxflags "$@" +} + test_objcflags(){ log test_objcflags "$@" set -- $($objcflags_filter "$@") @@ -1309,6 +1328,14 @@ check_objcflags(){ test_objcflags "$@" && add_objcflags "$@" } +check_allcflags(){ + allcret=0 + check_cflags "$@" || allcret=$? + check_cxxflags "$@" || allcret=$? + check_objcflags "$@" || allcret=$? + return $allcret +} + test_ldflags(){ log test_ldflags "$@" set -- $($ldflags_filter "$@") @@ -4180,7 +4207,7 @@ mandir_default='${prefix}/share/man' ar_default="ar" cc_default="gcc" stdc_default="c17" -stdcxx_default="c++11" +stdcxx_default="c++17" cxx_default="g++" host_cc_default="gcc" doxygen_default="doxygen" @@ -4648,28 +4675,28 @@ test -n "$valgrind" && toolchain="valgrind-memcheck" add_sanitizer_flags(){ case "$1" in asan) - add_cflags -fsanitize=address + add_allcflags -fsanitize=address add_ldflags -fsanitize=address ;; fuzz) - add_cflags -fsanitize=fuzzer-no-link + add_allcflags -fsanitize=fuzzer-no-link add_ldflags -fsanitize=fuzzer-no-link : "${libfuzzer_path:=-fsanitize=fuzzer}" ;; lsan) - add_cflags -fsanitize=leak + add_allcflags -fsanitize=leak add_ldflags -fsanitize=leak ;; msan) - add_cflags -fsanitize=memory -fsanitize-memory-track-origins + add_allcflags -fsanitize=memory -fsanitize-memory-track-origins add_ldflags -fsanitize=memory ;; tsan) - add_cflags -fsanitize=thread + add_allcflags -fsanitize=thread add_ldflags -fsanitize=thread ;; usan|ubsan) - add_cflags -fsanitize=undefined + add_allcflags -fsanitize=undefined add_ldflags -fsanitize=undefined ;; ?*) @@ -4685,7 +4712,7 @@ add_sanitizers(){ for sanitizer; do add_sanitizer_flags "$sanitizer" done - add_cflags -fno-omit-frame-pointer + add_allcflags -fno-omit-frame-pointer } case "$toolchain" in @@ -4700,7 +4727,7 @@ case "$toolchain" in cxx_default="g++" # In case of tsan with gcc, PIC has to be enabled if [ "${toolchain#gcc-}" = "tsan" ]; then - add_cflags -fPIC + add_allcflags -fPIC add_ldflags -fPIC fi ;; @@ -4751,18 +4778,18 @@ case "$toolchain" in TMPDIR=. ;; gcov) - add_cflags -fprofile-arcs -ftest-coverage + add_allcflags -fprofile-arcs -ftest-coverage add_ldflags -fprofile-arcs -ftest-coverage ;; llvm-cov) - add_cflags -fprofile-arcs -ftest-coverage + add_allcflags -fprofile-arcs -ftest-coverage add_ldflags --coverage ;; hardened) add_cppflags -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 - add_cflags -fno-strict-overflow -fstack-protector-all + add_allcflags -fno-strict-overflow -fstack-protector-all add_ldflags -Wl,-z,relro -Wl,-z,now - add_cflags -fPIE + add_allcflags -fPIE add_ldexeflags -fPIE -pie ;; ?*) @@ -5058,9 +5085,9 @@ probe_cc(){ _cc=$2 first=$3 - unset _type _ident _cc_c _cc_e _cc_o _flags _cflags + unset _type _ident _cc_c _cc_e _cc_o _flags _cflags _cxxflags unset _ld_o _ldflags _ld_lib _ld_path - unset _depflags _DEPCMD _DEPFLAGS + unset _depflags _DEPCMD _DEPFLAGS _DEPCCFLAGS _DEPCXXFLAGS _flags_filter=echo if $_cc --version 2>&1 | grep -q '^GNU assembler'; then @@ -5207,7 +5234,9 @@ probe_cc(){ else _DEPCMD='$(DEP$(1)) $(DEP$(1)FLAGS) $($(1)DEP_FLAGS) $< 2>&1 | awk '\''/including/ { sub(/^.*file: */, ""); gsub(/\\/, "/"); if (!match($$0, / /)) print "$@:", $$0 }'\'' > $(@:.o=.d)' fi - _DEPFLAGS='$(CPPFLAGS) $(CFLAGS) -showIncludes -Zs' + _DEPFLAGS='$(CPPFLAGS) -showIncludes -Zs' + _DEPCCFLAGS='$(CFLAGS)' + _DEPCXXFLAGS='$(CXXFLAGS)' _cflags_speed="-O2" _cflags_size="-O1" _cflags_noopt="-O1" @@ -5223,6 +5252,7 @@ probe_cc(){ _ld_lib='%.lib' _ld_path='-libpath:' _flags='-nologo' + _cxxflags='-Zc:__cplusplus -EHsc' disable stripping elif $_cc --version 2>/dev/null | grep -q ^cparser; then _type=cparser @@ -5243,11 +5273,11 @@ set_ccvars(){ eval ${1}_O=\${_cc_o-\${${1}_O}} if [ -n "$_depflags" ]; then - eval ${1}_DEPFLAGS=\$_depflags + eval "${1}_DEPFLAGS=\"\$_depflags\"" else - eval ${1}DEP=\${_DEPCMD:-\$DEPCMD} - eval ${1}DEP_FLAGS=\${_DEPFLAGS:-\$DEPFLAGS} - eval DEP${1}FLAGS=\$_flags + eval "${1}DEP=\"\${_DEPCMD:-\$DEPCMD}\"" + eval "${1}DEP_FLAGS=\"\${_DEPFLAGS:-\$DEPFLAGS} \${_DEP${1}FLAGS:-\$DEP${1}FLAGS}\"" + eval "DEP${1}FLAGS=\"\$_flags\"" fi } @@ -5257,6 +5287,7 @@ cflags_speed=$_cflags_speed cflags_size=$_cflags_size cflags_noopt=$_cflags_noopt add_cflags $_flags $_cflags +add_cxxflags $_flags $_cxxflags cc_ldflags=$_ldflags set_ccvars CC set_ccvars CXX @@ -5303,8 +5334,11 @@ HOSTLD_O=${_ld_o-$HOSTLD_O} if [ -z "$CC_DEPFLAGS" ] && [ "$dep_cc" != "$cc" ]; then probe_cc depcc "$dep_cc" CCDEP=${_DEPCMD:-$DEPCMD} + CXXDEP=${CCDEP} CCDEP_FLAGS=${_DEPFLAGS:=$DEPFLAGS} - DEPCCFLAGS=$_flags + CXXDEP_FLAGS=${CCDEP_FLAGS} + DEPCCFLAGS=$_flags $_cflags + DEPCXXFLAGS=$_flags $_cxxflags fi if VSLANG=1033 $ar 2>&1 | grep -q ^Microsoft; then @@ -5502,7 +5536,7 @@ elif enabled arm; then ;; esac - test_cflags -mfp16-format=ieee && add_cflags -mfp16-format=ieee + check_allcflags -mfp16-format=ieee elif enabled loongarch; then @@ -5737,7 +5771,7 @@ else fi if [ "$cpu" != generic ]; then - add_cflags $cpuflags + add_allcflags $cpuflags add_asflags $cpuflags test "$cc_type" = "$ld_type" && add_ldflags $cpuflags fi @@ -5760,6 +5794,8 @@ add_cxxflags -D__STDC_CONSTANT_MACROS check_cxxflags_cc -std=$stdcxx ctype.h "__cplusplus >= 201103L" || { check_cxxflags -std=c++11 && stdcxx="c++11" || { check_cxxflags -std=c++0x && stdcxx="c++0x"; }; } +test_cxxflags_cc -std=$stdcxx ctype.h "__cplusplus >= 201703L" && enable cxx17 + # some compilers silently accept -std=c11, so we also need to check that the # version macro is defined properly check_cflags_cc -std=$stdc ctype.h "__STDC_VERSION__ >= 201112L" || @@ -5854,7 +5890,7 @@ case $target_os in android) disable symver enable section_data_rel_ro - add_cflags -fPIE + add_allcflags -fPIE add_ldexeflags -fPIE -pie SLIB_INSTALL_NAME='$(SLIBNAME)' SLIB_INSTALL_LINKS= @@ -5935,7 +5971,7 @@ case $target_os in # Workaround for Xcode 11 -fstack-check bug if enabled clang; then clang_version=$($cc -dumpversion) - test ${clang_version%%.*} -eq 11 && add_cflags -fno-stack-check + test ${clang_version%%.*} -eq 11 && add_allcflags -fno-stack-check fi # Xcode Clang doesn't default to -fno-common while upstream llvm.org @@ -6154,15 +6190,17 @@ probe_libc(){ # MinGW headers can be installed on Cygwin, so check for newlib first. elif test_${pfx}cpp_condition newlib.h "defined _NEWLIB_VERSION"; then eval ${pfx}libc_type=newlib - add_${pfx}cppflags -U__STRICT_ANSI__ -D_XOPEN_SOURCE=600 + add_${pfx}cflags -U__STRICT_ANSI__ + add_${pfx}cppflags -D_XOPEN_SOURCE=600 # MinGW64 is backwards compatible with MinGW32, so check for it first. elif test_${pfx}cpp_condition _mingw.h "defined __MINGW64_VERSION_MAJOR"; then eval ${pfx}libc_type=mingw64 if test_${pfx}cpp_condition _mingw.h "__MINGW64_VERSION_MAJOR < 3"; then add_compat msvcrt/snprintf.o - add_cflags "-include $source_path/compat/msvcrt/snprintf.h" + add_allcflags "-include $source_path/compat/msvcrt/snprintf.h" fi - add_${pfx}cppflags -U__STRICT_ANSI__ -D__USE_MINGW_ANSI_STDIO=1 + add_${pfx}cflags -U__STRICT_ANSI__ + add_${pfx}cppflags -D__USE_MINGW_ANSI_STDIO=1 eval test \$${pfx_no_}cc_type = "gcc" && add_${pfx}cppflags -D__printf__=__gnu_printf__ test_${pfx}cpp_condition windows.h "!defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0600" && @@ -6174,7 +6212,8 @@ probe_libc(){ test_${pfx}cpp_condition _mingw.h "__MINGW32_MAJOR_VERSION > 3 || \ (__MINGW32_MAJOR_VERSION == 3 && __MINGW32_MINOR_VERSION >= 15)" || die "ERROR: MinGW32 runtime version must be >= 3.15." - add_${pfx}cppflags -U__STRICT_ANSI__ -D__USE_MINGW_ANSI_STDIO=1 + add_${pfx}cflags -U__STRICT_ANSI__ + add_${pfx}cppflags -D__USE_MINGW_ANSI_STDIO=1 test_${pfx}cpp_condition _mingw.h "__MSVCRT_VERSION__ < 0x0700" && add_${pfx}cppflags -D__MSVCRT_VERSION__=0x0700 test_${pfx}cpp_condition windows.h "!defined(_WIN32_WINNT) || _WIN32_WINNT < 0x0600" && @@ -6215,8 +6254,8 @@ probe_libc(){ #endif EOF if [ "$pfx" = "" ]; then - check_func strtoll || add_cflags -Dstrtoll=_strtoi64 - check_func strtoull || add_cflags -Dstrtoull=_strtoui64 + check_func strtoll || add_allcflags -Dstrtoll=_strtoi64 + check_func strtoull || add_allcflags -Dstrtoull=_strtoui64 fi elif test_${pfx}cpp_condition stddef.h "defined __KLIBC__"; then eval ${pfx}libc_type=klibc @@ -6228,7 +6267,7 @@ EOF elif test_${pfx}cpp_condition sys/version.h "defined __DJGPP__"; then eval ${pfx}libc_type=djgpp add_cppflags -U__STRICT_ANSI__ - add_cflags "-include $source_path/compat/djgpp/math.h" + add_allcflags "-include $source_path/compat/djgpp/math.h" add_compat djgpp/math.o fi test_${pfx}cc <<EOF @@ -6266,7 +6305,7 @@ set_default libdir set_default $PATHS_LIST set_default nm -disabled optimizations || enabled ossfuzz || echo "$CFLAGS" | grep -q -- '-fsanitize=' || check_cflags -fomit-frame-pointer +disabled optimizations || enabled ossfuzz || echo "$CFLAGS" | grep -q -- '-fsanitize=' || check_allcflags -fomit-frame-pointer enable_weak_pic() { disabled pic && return @@ -6276,7 +6315,7 @@ enable_weak_pic() { mingw*|cygwin*|win*) ;; *) - add_cflags -fPIC + add_allcflags -fPIC add_asflags -fPIC ;; esac @@ -6952,13 +6991,13 @@ fi if ! disabled pthreads && ! enabled w32threads && ! enabled os2threads; then if check_lib pthreads pthread.h pthread_join -pthread && check_lib pthreads pthread.h pthread_create -pthread; then - add_cflags -pthread + add_allcflags -pthread elif check_lib pthreads pthread.h pthread_join -pthreads && check_lib pthreads pthread.h pthread_create -pthreads; then - add_cflags -pthreads + add_allcflags -pthreads elif check_lib pthreads pthread.h pthread_join -ldl -pthread && check_lib pthreads pthread.h pthread_create -ldl -pthread; then - add_cflags -ldl -pthread + add_allcflags -ldl -pthread elif check_lib pthreads pthread.h pthread_join -lpthreadGC2 && check_lib pthreads pthread.h pthread_create -lpthreadGC2; then : @@ -7097,7 +7136,7 @@ elif enabled libmfx; then # includedir=/usr/include # Cflags: -I${includedir} # So add -I${includedir}/mfx to CFLAGS - { check_pkg_config libmfx "libmfx >= 1.28 libmfx < 2.0" "mfx/mfxvideo.h" MFXInit && add_cflags -I${libmfx_incdir}/mfx; } || + { check_pkg_config libmfx "libmfx >= 1.28 libmfx < 2.0" "mfx/mfxvideo.h" MFXInit && add_cppflags -I${libmfx_incdir}/mfx; } || { require libmfx "mfxvideo.h mfxdefs.h" MFXInit "-llibmfx $advapi32_extralibs" && { test_cpp_condition mfxdefs.h "MFX_VERSION >= 1028 && MFX_VERSION < 2000" || die "ERROR: libmfx version must be >= 1.28 and < 2.0"; } && warn "using libmfx without pkg-config"; } } && @@ -7108,7 +7147,7 @@ elif enabled libvpl; then # is extracted from "vpl >= 2.6" check_pkg_config libmfx "vpl >= 2.6" "mfxvideo.h mfxdispatcher.h" MFXLoad || \ die "ERROR: libvpl >= 2.6 not found" - add_cflags -DMFX_DEPRECATED_OFF + add_cppflags -DMFX_DEPRECATED_OFF check_type "vpl/mfxdefs.h vpl/mfxvideo.h" "struct mfxConfigInterface" fi @@ -7584,29 +7623,31 @@ elif enabled iconv; then check_func_headers iconv.h iconv || check_lib iconv iconv.h iconv -liconv fi -enabled debug && add_cflags -g"$debuglevel" && add_asflags -g"$debuglevel" +enabled debug && add_allcflags -g"$debuglevel" && add_asflags -g"$debuglevel" # add some useful compiler flags if supported -check_cflags -Wall -check_cflags -Wdisabled-optimization -check_cflags -Wpointer-arith -check_cflags -Wredundant-decls -check_cflags -Wwrite-strings -check_cflags -Wtype-limits -check_cflags -Wundef +check_allcflags -Wall +check_allcflags -Wdisabled-optimization +check_allcflags -Wpointer-arith +check_allcflags -Wredundant-decls +check_allcflags -Wwrite-strings +check_allcflags -Wtype-limits +check_allcflags -Wundef +check_allcflags -Wempty-body check_cflags -Wmissing-prototypes check_cflags -Wstrict-prototypes -check_cflags -Wempty-body if enabled extra_warnings; then - check_cflags -Wcast-qual - check_cflags -Wextra - check_cflags -Wpedantic + check_allcflags -Wcast-qual + check_allcflags -Wextra + check_allcflags -Wpedantic fi check_disable_warning(){ warning_flag=-W${1#-Wno-} test_cflags $unknown_warning_flags $warning_flag && add_cflags $1 + test_cxxflags -Werror $unknown_warning_flags $warning_flag && add_cxxflags $1 + test_objcflags $unknown_warning_flags $warning_flag && add_objcflags $1 } test_cflags -Werror=unused-command-line-argument && @@ -7700,7 +7741,7 @@ if [ -z "$optflags" ]; then fi check_optflags(){ - check_cflags "$@" + check_allcflags "$@" [ -n "$lto" ] && check_ldflags "$@" } @@ -7734,7 +7775,7 @@ EOF if enabled icc; then # Just warnings, no remarks - check_cflags -w1 + check_allcflags -w1 # -wd: Disable following warnings # 144, 167, 556: -Wno-pointer-sign # 188: enumerated type mixed with another type @@ -7745,7 +7786,7 @@ if enabled icc; then # 10156: ignoring option '-W'; no argument required # 13200: No EMMS instruction before call to function # 13203: No EMMS instruction before return from function - check_cflags -wd144,167,188,556,1292,1419,10006,10148,10156,13200,13203 + check_allcflags -wd144,167,188,556,1292,1419,10006,10148,10156,13200,13203 # 11030: Warning unknown option --as-needed # 10156: ignoring option '-export'; no argument required check_ldflags -wd10156,11030 @@ -7756,7 +7797,7 @@ if enabled icc; then if enabled x86_32; then icc_version=$($cc -dumpversion) test ${icc_version%%.*} -ge 11 && - check_cflags -falign-stack=maintain-16-byte || + check_cppflags -falign-stack=maintain-16-byte || disable aligned_stack fi elif enabled gcc; then @@ -7779,13 +7820,13 @@ elif enabled gcc; then ;; esac fi - check_cflags -Werror=format-security check_cflags -Werror=implicit-function-declaration check_cflags -Werror=missing-prototypes - check_cflags -Werror=return-type - check_cflags -Werror=vla - check_cflags -Wformat - check_cflags -fdiagnostics-color=auto + check_allcflags -Werror=format-security + check_allcflags -Werror=return-type + check_allcflags -Werror=vla + check_allcflags -Wformat + check_allcflags -fdiagnostics-color=auto enabled extra_warnings || check_disable_warning -Wno-maybe-uninitialized if enabled x86_32; then case $target_os in @@ -7794,12 +7835,12 @@ elif enabled gcc; then # request GCC to try to maintain 16 byte alignment throughout # function calls. Library entry points that might call assembly # functions align the stack. (The parameter means 2^4 bytes.) - check_cflags -mpreferred-stack-boundary=4 + check_allcflags -mpreferred-stack-boundary=4 ;; esac fi elif enabled llvm_gcc; then - check_cflags -mllvm -stack-alignment=16 + check_allcflags -mllvm -stack-alignment=16 elif enabled clang; then if enabled x86_32; then # Clang doesn't support maintaining alignment without assuming the @@ -7812,18 +7853,18 @@ elif enabled clang; then disable aligned_stack ;; *) - check_cflags -mllvm -stack-alignment=16 - check_cflags -mstack-alignment=16 + check_allcflags -mllvm -stack-alignment=16 + check_allcflags -mstack-alignment=16 ;; esac else - check_cflags -mllvm -stack-alignment=16 - check_cflags -mstack-alignment=16 + check_allcflags -mllvm -stack-alignment=16 + check_allcflags -mstack-alignment=16 fi - check_cflags -Qunused-arguments - check_cflags -Werror=implicit-function-declaration - check_cflags -Werror=missing-prototypes - check_cflags -Werror=return-type + check_allcflags -Qunused-arguments + check_allcflags -Werror=implicit-function-declaration + check_allcflags -Werror=missing-prototypes + check_allcflags -Werror=return-type elif enabled cparser; then add_cflags -Wno-missing-variable-declarations add_cflags -Wno-empty-statement @@ -8267,6 +8308,8 @@ OBJCC=$objcc LD=$ld DEPCC=$dep_cc DEPCCFLAGS=$DEPCCFLAGS \$(CPPFLAGS) +DEPCXX=$dep_cc +DEPCXXFLAGS=$DEPCXXFLAGS \$(CPPFLAGS) DEPAS=$as DEPASFLAGS=$DEPASFLAGS \$(CPPFLAGS) X86ASM=$x86asmexe @@ -8334,6 +8377,7 @@ EXTRA_VERSION=$extra_version CCDEP=$CCDEP CXXDEP=$CXXDEP CCDEP_FLAGS=$CCDEP_FLAGS +CXXDEP_FLAGS=$CXXDEP_FLAGS ASDEP=$ASDEP ASDEP_FLAGS=$ASDEP_FLAGS X86ASMDEP=$X86ASMDEP diff --git a/ffbuild/common.mak b/ffbuild/common.mak index 0dcf38bcfc..4ed1c44afb 100644 --- a/ffbuild/common.mak +++ b/ffbuild/common.mak @@ -51,7 +51,7 @@ OBJCCFLAGS = $(CPPFLAGS) $(CFLAGS) $(OBJCFLAGS) ASFLAGS := $(CPPFLAGS) $(ASFLAGS) # Use PREPEND here so that later (target-dependent) additions to CPPFLAGS # end up in CXXFLAGS. -$(call PREPEND,CXXFLAGS, CPPFLAGS CFLAGS) +$(call PREPEND,CXXFLAGS, CPPFLAGS) X86ASMFLAGS += $(IFLAGS:%=%/) -I$(<D)/ -Pconfig.asm HOSTCCFLAGS = $(IFLAGS) $(HOSTCPPFLAGS) $(HOSTCFLAGS) commit 0362cb38062b2a8cead50840aaefb7257570e3f0 Author: Timo Rothenpieler <t...@rothenpieler.org> AuthorDate: Wed Sep 3 01:34:57 2025 +0200 Commit: Timo Rothenpieler <t...@rothenpieler.org> CommitDate: Sun Sep 14 11:45:11 2025 +0000 build: link with CXX when -lstdc++ on linker commandline diff --git a/Makefile b/Makefile index 877b0071f6..17fadd58c2 100644 --- a/Makefile +++ b/Makefile @@ -53,31 +53,31 @@ FF_DEP_LIBS := $(DEP_LIBS) FF_STATIC_DEP_LIBS := $(STATIC_DEP_LIBS) $(TOOLS): %$(EXESUF): %.o - $(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $(filter-out $(FF_DEP_LIBS), $^) $(EXTRALIBS-$(*F)) $(EXTRALIBS) $(ELIBS) + $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $(filter-out $(FF_DEP_LIBS), $^) $(EXTRALIBS-$(*F)) $(EXTRALIBS) $(ELIBS)) target_dec_%_fuzzer$(EXESUF): target_dec_%_fuzzer.o $(FF_DEP_LIBS) - $(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH) + $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH)) target_enc_%_fuzzer$(EXESUF): target_enc_%_fuzzer.o $(FF_DEP_LIBS) - $(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH) + $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH)) tools/target_bsf_%_fuzzer$(EXESUF): tools/target_bsf_%_fuzzer.o $(FF_DEP_LIBS) - $(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH) + $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH)) target_dem_%_fuzzer$(EXESUF): target_dem_%_fuzzer.o $(FF_DEP_LIBS) - $(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH) + $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH)) tools/target_dem_fuzzer$(EXESUF): tools/target_dem_fuzzer.o $(FF_DEP_LIBS) - $(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH) + $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH)) tools/target_io_dem_fuzzer$(EXESUF): tools/target_io_dem_fuzzer.o $(FF_DEP_LIBS) - $(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH) + $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH)) tools/target_sws_fuzzer$(EXESUF): tools/target_sws_fuzzer.o $(FF_DEP_LIBS) - $(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH) + $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH)) tools/target_swr_fuzzer$(EXESUF): tools/target_swr_fuzzer.o $(FF_DEP_LIBS) - $(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH) + $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(ELIBS) $(FF_EXTRALIBS) $(LIBFUZZER_PATH)) tools/enum_options$(EXESUF): ELIBS = $(FF_EXTRALIBS) tools/enum_options$(EXESUF): $(FF_DEP_LIBS) @@ -144,7 +144,7 @@ else endif %$(PROGSSUF)_g$(EXESUF): $(FF_DEP_LIBS) - $(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $(OBJS-$*) $(FF_EXTRALIBS) + $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $(OBJS-$*) $(FF_EXTRALIBS)) VERSION_SH = $(SRC_PATH)/ffbuild/version.sh ifeq ($(VERSION_TRACKING),yes) diff --git a/ffbuild/common.mak b/ffbuild/common.mak index 0a60d01623..0dcf38bcfc 100644 --- a/ffbuild/common.mak +++ b/ffbuild/common.mak @@ -12,13 +12,22 @@ endif ifndef SUBDIR +LINK = $(LD) $(1) + +ifeq ($(LD),$(CC)) +ifneq ($(CXX),) +LDXX := $(CXX) +LINK = $(if $(filter -lstdc++,$(1)),$(LDXX) $(filter-out -lstdc++,$(1)),$(LD) $(1)) +endif +endif + BIN2CEXE = ffbuild/bin2c$(HOSTEXESUF) BIN2C = $(BIN2CEXE) ifndef V Q = @ ECHO = printf "$(1)\t%s\n" $(2) -BRIEF = CC CXX OBJCC HOSTCC HOSTLD AS X86ASM AR LD STRIP CP WINDRES NVCC BIN2C METALCC METALLIB +BRIEF = CC CXX OBJCC HOSTCC HOSTLD AS X86ASM AR LD LDXX STRIP CP WINDRES NVCC BIN2C METALCC METALLIB SILENT = DEPCC DEPHOSTCC DEPAS DEPX86ASM RANLIB RM MSG = $@ diff --git a/ffbuild/library.mak b/ffbuild/library.mak index dee05c5acd..91daa9c25f 100644 --- a/ffbuild/library.mak +++ b/ffbuild/library.mak @@ -55,8 +55,12 @@ $(TESTPROGS): THISLIB = $(SUBDIR)$(LIBNAME) $(LIBOBJS): CPPFLAGS += -DBUILDING_$(NAME) +$(NAME)LINK_EXE_ARGS = $(LDFLAGS) $(LDEXEFLAGS) +$(NAME)LINK_SO_ARGS = $(SHFLAGS) $(LDFLAGS) $(LDSOFLAGS) +$(NAME)LINK_EXTRA = $(FFEXTRALIBS) + $(TESTPROGS) $(TOOLS): %$(EXESUF): %.o - $$(LD) $(LDFLAGS) $(LDEXEFLAGS) $$(LD_O) $$(filter %.o,$$^) $$(THISLIB) $(FFEXTRALIBS) $$(EXTRALIBS-$$(*F)) $$(ELIBS) + $$(call LINK,$$(call $(NAME)LINK_EXE_ARGS) $$(LD_O) $$(filter %.o,$$^) $$(THISLIB) $$(call $(NAME)LINK_EXTRA) $$(EXTRALIBS-$$(*F)) $$(ELIBS)) $(SUBDIR)lib$(NAME).version: $(SUBDIR)version.h $(SUBDIR)version_major.h | $(SUBDIR) $$(M) $$(SRC_PATH)/ffbuild/libversion.sh $(NAME) $$^ > $$@ @@ -74,9 +78,9 @@ $(SUBDIR)$(SLIBNAME_WITH_MAJOR): $(OBJS) $(SHLIBOBJS) $(SUBDIR)lib$(NAME).ver $(SLIB_CREATE_DEF_CMD) ifeq ($(RESPONSE_FILES),yes) $(Q)echo $$(filter %.o,$$^) > $$@.objs - $$(LD) $(SHFLAGS) $(LDFLAGS) $(LDSOFLAGS) $$(LD_O) @$$@.objs $(FFEXTRALIBS) + $$(call LINK,$$(call $(NAME)LINK_SO_ARGS) $$(LD_O) @$$@.objs $$(call $(NAME)LINK_EXTRA)) else - $$(LD) $(SHFLAGS) $(LDFLAGS) $(LDSOFLAGS) $$(LD_O) $$(filter %.o,$$^) $(FFEXTRALIBS) + $$(call LINK,$$(call $(NAME)LINK_SO_ARGS) $$(LD_O) $$(filter %.o,$$^) $$(call $(NAME)LINK_EXTRA)) endif $(SLIB_EXTRA_CMD) -$(RM) $$@.objs diff --git a/tests/api/Makefile b/tests/api/Makefile index a2cb06a729..899aeb1f54 100644 --- a/tests/api/Makefile +++ b/tests/api/Makefile @@ -15,7 +15,7 @@ $(APITESTOBJS) $(APITESTOBJS:.o=.i): CPPFLAGS += -DTEST $(APITESTOBJS) $(APITESTOBJS:.o=.i): CFLAGS += -Umain $(APITESTPROGS): %$(EXESUF): %.o $(FF_DEP_LIBS) - $(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $(filter %.o,$^) $(FF_EXTRALIBS) $(ELIBS) + $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $(filter %.o,$^) $(FF_EXTRALIBS) $(ELIBS)) testclean:: $(RM) $(addprefix $(APITESTSDIR)/,$(CLEANSUFFIXES) *-test$(EXESUF)) diff --git a/tests/checkasm/Makefile b/tests/checkasm/Makefile index 5ce4725543..9f1dd57fa6 100644 --- a/tests/checkasm/Makefile +++ b/tests/checkasm/Makefile @@ -107,7 +107,7 @@ tests/checkasm/checkasm.o: CFLAGS += -Umain CHECKASM := tests/checkasm/checkasm$(EXESUF) $(CHECKASM): $(CHECKASMOBJS) $(FF_STATIC_DEP_LIBS) - $(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $(CHECKASMOBJS) $(FF_STATIC_DEP_LIBS) $(EXTRALIBS-avcodec) $(EXTRALIBS-avfilter) $(EXTRALIBS-avformat) $(EXTRALIBS-avutil) $(EXTRALIBS-swresample) $(EXTRALIBS) + $(call LINK,$(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $(CHECKASMOBJS) $(FF_STATIC_DEP_LIBS) $(EXTRALIBS-avcodec) $(EXTRALIBS-avfilter) $(EXTRALIBS-avformat) $(EXTRALIBS-avutil) $(EXTRALIBS-swresample) $(EXTRALIBS)) run-checkasm: $(CHECKASM) run-checkasm: ----------------------------------------------------------------------- Summary of changes: Changelog | 1 + Makefile | 20 +- compat/w32pthreads.h | 4 +- configure | 224 +++-- doc/filters.texi | 159 ++++ ffbuild/common.mak | 13 +- ffbuild/library.mak | 10 +- libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/version.h | 2 +- libavfilter/vsrc_gfxcapture.c | 102 +++ libavfilter/vsrc_gfxcapture.h | 81 ++ libavfilter/vsrc_gfxcapture_shader.h | 126 +++ libavfilter/vsrc_gfxcapture_winrt.cpp | 1551 +++++++++++++++++++++++++++++++++ libavfilter/vsrc_gfxcapture_winrt.h | 189 ++++ tests/api/Makefile | 2 +- tests/checkasm/Makefile | 2 +- 17 files changed, 2384 insertions(+), 104 deletions(-) create mode 100644 libavfilter/vsrc_gfxcapture.c create mode 100644 libavfilter/vsrc_gfxcapture.h create mode 100644 libavfilter/vsrc_gfxcapture_shader.h create mode 100644 libavfilter/vsrc_gfxcapture_winrt.cpp create mode 100644 libavfilter/vsrc_gfxcapture_winrt.h hooks/post-receive -- _______________________________________________ ffmpeg-cvslog mailing list -- ffmpeg-cvslog@ffmpeg.org To unsubscribe send an email to ffmpeg-cvslog-le...@ffmpeg.org